mirror of
https://github.com/airgradienthq/arduino.git
synced 2026-06-11 19:51:22 +02:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7529ccb89 | |||
| 783b765df6 | |||
| d47331c9de | |||
| 1906cc1606 | |||
| 2829e1f5a8 | |||
| 7068ede0fc | |||
| 733ddf17ef | |||
| 0b7c8c0cb7 | |||
| 2fd53ac303 | |||
| 1c8fc0ffa2 | |||
| e5a3ddccc4 | |||
| ff61145b16 | |||
| a5e3ea2a7b | |||
| 0e1b517685 | |||
| 4b24bf8819 | |||
| b370f5f245 | |||
| 03789e53ce | |||
| 8b4bf500a3 | |||
| 170cbf753e | |||
| 68ee315352 |
@@ -80,6 +80,10 @@ jobs:
|
||||
- source-path: ./
|
||||
- name: NimBLE-Arduino
|
||||
version: 2.3.7
|
||||
- name: Sensirion Core
|
||||
version: 0.7.3
|
||||
- name: Sensirion UART SPS30
|
||||
version: 1.0.0
|
||||
- name: ArduinoJson
|
||||
version: 7.4.3
|
||||
cli-compile-flags: |
|
||||
|
||||
@@ -28,7 +28,11 @@ Using library manager install the latest version (Tools ➝ Manage Libraries...
|
||||
|
||||
#### Version >= 3.6.0
|
||||
|
||||
- Ensure `NimBLE-Arduino` by h2zero library version `2.3.7` is installed using Arduino library manager
|
||||
- Ensure following library is installed using Arduino library manager
|
||||
- `NimBLE-Arduino` by h2zero `^2.3.7`
|
||||
- `Sensirion Core` by Sensirion `^0.7.3`
|
||||
- `Sensirion UART SPS30` by Sensirion `^1.0.0`
|
||||
- `ArduinoJson` by Benoit Blanchon `^7.4.3`
|
||||
- Follow steps of ">= 3.3.0"
|
||||
|
||||
3. On tools tab, follow settings below
|
||||
|
||||
@@ -142,7 +142,7 @@ If the monitor is set up on the AirGradient dashboard, it will also receive the
|
||||
| `country` | Country where the device is. | String | Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | `{"country": "TH"}` |
|
||||
| `model` | Hardware identifier (only GET). | String | I-9PSL-DE | `{"model": "I-9PSL-DE"}` |
|
||||
| `pmStandard` | Particle matter standard used on the display. | String | `ugm3`: ug/m3 <br> `us-aqi`: USAQI | `{"pmStandard": "ugm3"}` |
|
||||
| `ledBarMode` | Mode in which the led bar can be set. | String | `co2`: LED bar displays CO2 <br>`pm`: LED bar displays PM <br>`off`: Turn off LED bar | `{"ledBarMode": "off"}` |
|
||||
| `ledBarMode` | Mode in which the led bar can be set. | String | `co2`: LED bar displays CO2 <br>`pm`: LED bar displays PM <br>`iaqs`: LED bar displays GO IAQS Starter Score (PM2.5 + CO2) <br>`off`: Turn off LED bar | `{"ledBarMode": "off"}` |
|
||||
| `displayBrightness` | Brightness of the Display. | Number | 0-100 | `{"displayBrightness": 50}` |
|
||||
| `ledBarBrightness` | Brightness of the LEDBar. | Number | 0-100 | `{"ledBarBrightness": 40}` |
|
||||
| `abcDays` | Number of days for CO2 automatic baseline calibration. | Number | Maximum 200 days. Default 8 days. | `{"abcDays": 8}` |
|
||||
|
||||
@@ -140,6 +140,7 @@ static void configUpdateHandle(void);
|
||||
static void updateDisplayAndLedBar(void);
|
||||
static void updateTvoc(void);
|
||||
static void updatePm(void);
|
||||
static void updateSPS30(SPS30 &sensor, int channel);
|
||||
static void sendDataToServer(void);
|
||||
static void tempHumUpdate(void);
|
||||
static void co2Update(void);
|
||||
@@ -334,7 +335,8 @@ void loop() {
|
||||
if (configuration.hasSensorS8) {
|
||||
co2Schedule.run();
|
||||
}
|
||||
if (configuration.hasSensorPMS1 || configuration.hasSensorPMS2) {
|
||||
if (configuration.hasSensorPMS1 || configuration.hasSensorPMS2 ||
|
||||
configuration.hasSensorSPS30_1 || configuration.hasSensorSPS30_2) {
|
||||
pmsSchedule.run();
|
||||
}
|
||||
if (ag->isOne()) {
|
||||
@@ -822,12 +824,18 @@ static void oneIndoorInit(void) {
|
||||
dispSensorNotFound("S8");
|
||||
}
|
||||
|
||||
/** Init PMS5003 */
|
||||
/** Init PMS5003, fallback to SPS30 if not found */
|
||||
if (ag->pms5003.begin(Serial0) == false) {
|
||||
Serial.println("PMS sensor not found");
|
||||
Serial.println("PMS5003 not found, trying SPS30...");
|
||||
configuration.hasSensorPMS1 = false;
|
||||
|
||||
dispSensorNotFound("PMS");
|
||||
if (ag->sps30_1.begin(Serial0)) {
|
||||
Serial.println("SPS30 detected on Serial0");
|
||||
configuration.hasSensorSPS30_1 = true;
|
||||
} else {
|
||||
Serial.println("SPS30 not found either");
|
||||
dispSensorNotFound("PM sensor");
|
||||
}
|
||||
}
|
||||
}
|
||||
static void openAirInit(void) {
|
||||
@@ -880,56 +888,100 @@ static void openAirInit(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Attempt to detect PM sensors */
|
||||
if (fwMode == FW_MODE_O_1PST) {
|
||||
bool pmInitSuccess = false;
|
||||
/**
|
||||
* Attempt to detect PM sensors on available serial ports.
|
||||
* Per-port order: PMS5003T first (@9600), fallback to SPS30 (@115200).
|
||||
* For single-PM modes the detected sensor is always assigned to channel 1.
|
||||
* For dual-PM modes Serial0 → channel 1, Serial1 → channel 2.
|
||||
*/
|
||||
auto detectPmOnSerial = [](HardwareSerial &serial, PMS5003T &pms, SPS30 &sps,
|
||||
const char *portName) -> int {
|
||||
// Returns: 0 = none, 1 = PMS5003T, 2 = SPS30
|
||||
if (pms.begin(serial)) {
|
||||
Serial.printf("Detected PMS5003T on %s\n", portName);
|
||||
return 1;
|
||||
}
|
||||
Serial.printf("PMS5003T not found on %s, trying SPS30...\n", portName);
|
||||
if (sps.begin(serial)) {
|
||||
Serial.printf("Detected SPS30 on %s\n", portName);
|
||||
return 2;
|
||||
}
|
||||
Serial.printf("No PM sensor detected on %s\n", portName);
|
||||
return 0;
|
||||
};
|
||||
|
||||
if (fwMode == FW_MODE_O_1PST || fwMode == FW_MODE_O_1PS) {
|
||||
// Single PM channel expected — try Serial0 first, fallback Serial1
|
||||
configuration.hasSensorPMS1 = false;
|
||||
configuration.hasSensorPMS2 = false;
|
||||
bool pmFound = false;
|
||||
|
||||
if (serial0Available) {
|
||||
if (ag->pms5003t_1.begin(Serial0) == false) {
|
||||
configuration.hasSensorPMS1 = false;
|
||||
Serial.println("No PM sensor detected on Serial0");
|
||||
} else {
|
||||
int result =
|
||||
detectPmOnSerial(Serial0, ag->pms5003t_1, ag->sps30_1, "Serial0");
|
||||
if (result == 1) {
|
||||
configuration.hasSensorPMS1 = true;
|
||||
serial0Available = false;
|
||||
pmInitSuccess = true;
|
||||
Serial.println("Detected PM 1 on Serial0");
|
||||
pmFound = true;
|
||||
} else if (result == 2) {
|
||||
configuration.hasSensorSPS30_1 = true;
|
||||
serial0Available = false;
|
||||
pmFound = true;
|
||||
}
|
||||
}
|
||||
if (pmInitSuccess == false) {
|
||||
if (serial1Available) {
|
||||
if (ag->pms5003t_1.begin(Serial1) == false) {
|
||||
configuration.hasSensorPMS1 = false;
|
||||
Serial.println("No PM sensor detected on Serial1");
|
||||
} else {
|
||||
serial1Available = false;
|
||||
Serial.println("Detected PM 1 on Serial1");
|
||||
}
|
||||
if (!pmFound && serial1Available) {
|
||||
int result =
|
||||
detectPmOnSerial(Serial1, ag->pms5003t_1, ag->sps30_1, "Serial1");
|
||||
if (result == 1) {
|
||||
configuration.hasSensorPMS1 = true;
|
||||
serial1Available = false;
|
||||
} else if (result == 2) {
|
||||
configuration.hasSensorSPS30_1 = true;
|
||||
serial1Available = false;
|
||||
}
|
||||
}
|
||||
configuration.hasSensorPMS2 = false; // Disable PM2
|
||||
} else {
|
||||
if (ag->pms5003t_1.begin(Serial0) == false) {
|
||||
configuration.hasSensorPMS1 = false;
|
||||
Serial.println("No PM sensor detected on Serial0");
|
||||
} else {
|
||||
Serial.println("Detected PM 1 on Serial0");
|
||||
}
|
||||
if (ag->pms5003t_2.begin(Serial1) == false) {
|
||||
configuration.hasSensorPMS2 = false;
|
||||
Serial.println("No PM sensor detected on Serial1");
|
||||
} else {
|
||||
Serial.println("Detected PM 2 on Serial1");
|
||||
// Dual PM channel modes (O_1PPT / O_1PP) — Serial0 → ch1, Serial1 → ch2
|
||||
configuration.hasSensorPMS1 = false;
|
||||
configuration.hasSensorPMS2 = false;
|
||||
|
||||
// Channel 1 on Serial0
|
||||
int result1 =
|
||||
detectPmOnSerial(Serial0, ag->pms5003t_1, ag->sps30_1, "Serial0");
|
||||
if (result1 == 1) {
|
||||
configuration.hasSensorPMS1 = true;
|
||||
} else if (result1 == 2) {
|
||||
configuration.hasSensorSPS30_1 = true;
|
||||
}
|
||||
|
||||
// Channel 2 on Serial1
|
||||
int result2 =
|
||||
detectPmOnSerial(Serial1, ag->pms5003t_2, ag->sps30_2, "Serial1");
|
||||
if (result2 == 1) {
|
||||
configuration.hasSensorPMS2 = true;
|
||||
} else if (result2 == 2) {
|
||||
configuration.hasSensorSPS30_2 = true;
|
||||
}
|
||||
|
||||
// Check if we should downgrade from two-PM to single-PM mode
|
||||
if (fwMode == FW_MODE_O_1PP) {
|
||||
int count = (configuration.hasSensorPMS1 ? 1 : 0) + (configuration.hasSensorPMS2 ? 1 : 0);
|
||||
if (count == 1) {
|
||||
bool ch1HasPm =
|
||||
configuration.hasSensorPMS1 || configuration.hasSensorSPS30_1;
|
||||
bool ch2HasPm =
|
||||
configuration.hasSensorPMS2 || configuration.hasSensorSPS30_2;
|
||||
if (ch1HasPm != ch2HasPm) {
|
||||
fwMode = FW_MODE_O_1P;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** update the PMS poll period base on fw mode and sensor available */
|
||||
if (fwMode != FW_MODE_O_1PST) {
|
||||
if (configuration.hasSensorPMS1 && configuration.hasSensorPMS2) {
|
||||
/** Update the PMS poll period based on fw mode and sensor availability */
|
||||
if (fwMode != FW_MODE_O_1PST && fwMode != FW_MODE_O_1PS) {
|
||||
bool ch1HasPm =
|
||||
configuration.hasSensorPMS1 || configuration.hasSensorSPS30_1;
|
||||
bool ch2HasPm =
|
||||
configuration.hasSensorPMS2 || configuration.hasSensorSPS30_2;
|
||||
if (ch1HasPm && ch2HasPm) {
|
||||
pmsSchedule.setPeriod(2000);
|
||||
}
|
||||
}
|
||||
@@ -1308,19 +1360,53 @@ static void updatePMS5003() {
|
||||
}
|
||||
}
|
||||
|
||||
static void updateSPS30(SPS30 &sensor, int channel) {
|
||||
if (sensor.readValues()) {
|
||||
// Mass concentrations — mapped to both Ae and SP (SPS30 has no distinction)
|
||||
measurements.update(Measurements::PM01, sensor.getPm01Ae(), channel);
|
||||
measurements.update(Measurements::PM25, sensor.getPm25Ae(), channel);
|
||||
measurements.update(Measurements::PM10, sensor.getPm10Ae(), channel);
|
||||
measurements.update(Measurements::PM01_SP, sensor.getPm01Sp(), channel);
|
||||
measurements.update(Measurements::PM25_SP, sensor.getPm25Sp(), channel);
|
||||
measurements.update(Measurements::PM10_SP, sensor.getPm10Sp(), channel);
|
||||
|
||||
// Number concentrations (already converted to #/0.1L by wrapper)
|
||||
measurements.update(Measurements::PM05_PC, sensor.getPm05ParticleCount(), channel);
|
||||
measurements.update(Measurements::PM01_PC, sensor.getPm01ParticleCount(), channel);
|
||||
measurements.update(Measurements::PM25_PC, sensor.getPm25ParticleCount(), channel);
|
||||
measurements.update(Measurements::PM10_PC, sensor.getPm10ParticleCount(), channel);
|
||||
} else {
|
||||
measurements.update(Measurements::PM01, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM25, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM10, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM01_SP, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM25_SP, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM10_SP, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM01_PC, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM25_PC, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM5_PC, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM10_PC, utils::getInvalidPmValue(), channel);
|
||||
}
|
||||
}
|
||||
|
||||
static void updatePm(void) {
|
||||
if (ag->isOne()) {
|
||||
updatePMS5003();
|
||||
if (configuration.hasSensorSPS30_1) {
|
||||
updateSPS30(ag->sps30_1, 1);
|
||||
} else {
|
||||
updatePMS5003();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Open Air Monitor series, can have two PMS5003T sensor
|
||||
bool newPMS1Value = false;
|
||||
bool newPMS2Value = false;
|
||||
// Open Air Monitor series — each channel can be PMS5003T or SPS30.
|
||||
// Track which channels produced valid PMS5003T T/RH for SGP41 compensation.
|
||||
bool newPmsTempHumCh1 = false;
|
||||
bool newPmsTempHumCh2 = false;
|
||||
|
||||
// Read PMS channel 1 if available
|
||||
int channel = 1;
|
||||
// ---- Channel 1 ----
|
||||
if (configuration.hasSensorPMS1) {
|
||||
int channel = 1;
|
||||
if (ag->pms5003t_1.connected()) {
|
||||
measurements.update(Measurements::PM01, ag->pms5003t_1.getPm01Ae(), channel);
|
||||
measurements.update(Measurements::PM25, ag->pms5003t_1.getPm25Ae(), channel);
|
||||
@@ -1334,11 +1420,8 @@ static void updatePm(void) {
|
||||
measurements.update(Measurements::PM25_PC, ag->pms5003t_1.getPm25ParticleCount(), channel);
|
||||
measurements.update(Measurements::Temperature, ag->pms5003t_1.getTemperature(), channel);
|
||||
measurements.update(Measurements::Humidity, ag->pms5003t_1.getRelativeHumidity(), channel);
|
||||
|
||||
// flag that new valid PMS value exists
|
||||
newPMS1Value = true;
|
||||
newPmsTempHumCh1 = true;
|
||||
} else {
|
||||
// PMS channel 1 now is not connected, update using invalid value
|
||||
measurements.update(Measurements::PM01, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM25, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM10, utils::getInvalidPmValue(), channel);
|
||||
@@ -1352,11 +1435,13 @@ static void updatePm(void) {
|
||||
measurements.update(Measurements::Temperature, utils::getInvalidTemperature(), channel);
|
||||
measurements.update(Measurements::Humidity, utils::getInvalidHumidity(), channel);
|
||||
}
|
||||
} else if (configuration.hasSensorSPS30_1) {
|
||||
updateSPS30(ag->sps30_1, 1);
|
||||
}
|
||||
|
||||
// Read PMS channel 2 if available
|
||||
channel = 2;
|
||||
// ---- Channel 2 ----
|
||||
if (configuration.hasSensorPMS2) {
|
||||
int channel = 2;
|
||||
if (ag->pms5003t_2.connected()) {
|
||||
measurements.update(Measurements::PM01, ag->pms5003t_2.getPm01Ae(), channel);
|
||||
measurements.update(Measurements::PM25, ag->pms5003t_2.getPm25Ae(), channel);
|
||||
@@ -1370,11 +1455,8 @@ static void updatePm(void) {
|
||||
measurements.update(Measurements::PM25_PC, ag->pms5003t_2.getPm25ParticleCount(), channel);
|
||||
measurements.update(Measurements::Temperature, ag->pms5003t_2.getTemperature(), channel);
|
||||
measurements.update(Measurements::Humidity, ag->pms5003t_2.getRelativeHumidity(), channel);
|
||||
|
||||
// flag that new valid PMS value exists
|
||||
newPMS2Value = true;
|
||||
newPmsTempHumCh2 = true;
|
||||
} else {
|
||||
// PMS channel 2 now is not connected, update using invalid value
|
||||
measurements.update(Measurements::PM01, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM25, utils::getInvalidPmValue(), channel);
|
||||
measurements.update(Measurements::PM10, utils::getInvalidPmValue(), channel);
|
||||
@@ -1388,30 +1470,32 @@ static void updatePm(void) {
|
||||
measurements.update(Measurements::Temperature, utils::getInvalidTemperature(), channel);
|
||||
measurements.update(Measurements::Humidity, utils::getInvalidHumidity(), channel);
|
||||
}
|
||||
} else if (configuration.hasSensorSPS30_2) {
|
||||
updateSPS30(ag->sps30_2, 2);
|
||||
}
|
||||
|
||||
// SGP41 compensation — only uses T/RH from PMS5003T channels (SPS30 has no T/RH)
|
||||
if (configuration.hasSensorSGP) {
|
||||
float temp, hum;
|
||||
if (newPMS1Value && newPMS2Value) {
|
||||
// Both PMS has new valid value
|
||||
temp = (measurements.getFloat(Measurements::Temperature, 1) +
|
||||
measurements.getFloat(Measurements::Temperature, 2)) /
|
||||
2.0f;
|
||||
hum = (measurements.getFloat(Measurements::Humidity, 1) +
|
||||
measurements.getFloat(Measurements::Humidity, 2)) /
|
||||
2.0f;
|
||||
} else if (newPMS1Value) {
|
||||
// Only PMS1 has new valid value
|
||||
temp = measurements.getFloat(Measurements::Temperature, 1);
|
||||
hum = measurements.getFloat(Measurements::Humidity, 1);
|
||||
} else {
|
||||
// Only PMS2 has new valid value
|
||||
temp = measurements.getFloat(Measurements::Temperature, 2);
|
||||
hum = measurements.getFloat(Measurements::Humidity, 2);
|
||||
if (newPmsTempHumCh1 || newPmsTempHumCh2) {
|
||||
float temp, hum;
|
||||
if (newPmsTempHumCh1 && newPmsTempHumCh2) {
|
||||
temp = (measurements.getFloat(Measurements::Temperature, 1) +
|
||||
measurements.getFloat(Measurements::Temperature, 2)) /
|
||||
2.0f;
|
||||
hum = (measurements.getFloat(Measurements::Humidity, 1) +
|
||||
measurements.getFloat(Measurements::Humidity, 2)) /
|
||||
2.0f;
|
||||
} else if (newPmsTempHumCh1) {
|
||||
temp = measurements.getFloat(Measurements::Temperature, 1);
|
||||
hum = measurements.getFloat(Measurements::Humidity, 1);
|
||||
} else {
|
||||
temp = measurements.getFloat(Measurements::Temperature, 2);
|
||||
hum = measurements.getFloat(Measurements::Humidity, 2);
|
||||
}
|
||||
ag->sgp41.setCompensationTemperatureHumidity(temp, hum);
|
||||
}
|
||||
|
||||
// Update compensation temperature and humidity for SGP41
|
||||
ag->sgp41.setCompensationTemperatureHumidity(temp, hum);
|
||||
// When no PMS5003T channel provides T/RH (e.g. 2× SPS30), SGP41 keeps
|
||||
// its previous compensation values (default 25 °C / 50 %RH on first run).
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,14 +77,28 @@ String OpenMetrics::getPayload(void) {
|
||||
int nox = utils::getInvalidNOx();
|
||||
int noxRaw = utils::getInvalidNOx();
|
||||
|
||||
// Convenience flags: does this channel have any PM sensor?
|
||||
bool ch1HasPm = config.hasSensorPMS1 || config.hasSensorSPS30_1;
|
||||
bool ch2HasPm = config.hasSensorPMS2 || config.hasSensorSPS30_2;
|
||||
|
||||
// Get values
|
||||
if (config.hasSensorPMS1 && config.hasSensorPMS2) {
|
||||
_temp = (measure.getFloat(Measurements::Temperature, 1) +
|
||||
measure.getFloat(Measurements::Temperature, 2)) /
|
||||
2.0f;
|
||||
_hum = (measure.getFloat(Measurements::Humidity, 1) +
|
||||
measure.getFloat(Measurements::Humidity, 2)) /
|
||||
2.0f;
|
||||
if (ch1HasPm && ch2HasPm) {
|
||||
// Two PM channels — average T/RH only from PMS5003T channels
|
||||
if (config.hasSensorPMS1 && config.hasSensorPMS2) {
|
||||
_temp = (measure.getFloat(Measurements::Temperature, 1) +
|
||||
measure.getFloat(Measurements::Temperature, 2)) /
|
||||
2.0f;
|
||||
_hum = (measure.getFloat(Measurements::Humidity, 1) +
|
||||
measure.getFloat(Measurements::Humidity, 2)) /
|
||||
2.0f;
|
||||
} else if (config.hasSensorPMS1) {
|
||||
_temp = measure.getFloat(Measurements::Temperature, 1);
|
||||
_hum = measure.getFloat(Measurements::Humidity, 1);
|
||||
} else if (config.hasSensorPMS2) {
|
||||
_temp = measure.getFloat(Measurements::Temperature, 2);
|
||||
_hum = measure.getFloat(Measurements::Humidity, 2);
|
||||
}
|
||||
// PM values averaged across both channels regardless of brand
|
||||
pm01 = (measure.get(Measurements::PM01, 1) + measure.get(Measurements::PM01, 2)) / 2.0f;
|
||||
float correctedPm25_1 = measure.getCorrectedPM25(false, 1);
|
||||
float correctedPm25_2 = measure.getCorrectedPM25(false, 2);
|
||||
@@ -100,7 +114,7 @@ String OpenMetrics::getPayload(void) {
|
||||
_hum = measure.getFloat(Measurements::Humidity);
|
||||
}
|
||||
|
||||
if (config.hasSensorPMS1) {
|
||||
if (ch1HasPm) {
|
||||
pm01 = measure.get(Measurements::PM01);
|
||||
float correctedPm = measure.getCorrectedPM25(false, 1);
|
||||
pm25 = round(correctedPm);
|
||||
@@ -108,18 +122,23 @@ String OpenMetrics::getPayload(void) {
|
||||
pm03PCount = measure.get(Measurements::PM03_PC);
|
||||
}
|
||||
} else {
|
||||
if (config.hasSensorPMS1) {
|
||||
_temp = measure.getFloat(Measurements::Temperature, 1);
|
||||
_hum = measure.getFloat(Measurements::Humidity, 1);
|
||||
// Outdoor single-channel: T/RH only from PMS5003T, PM from any sensor
|
||||
if (ch1HasPm) {
|
||||
if (config.hasSensorPMS1) {
|
||||
_temp = measure.getFloat(Measurements::Temperature, 1);
|
||||
_hum = measure.getFloat(Measurements::Humidity, 1);
|
||||
}
|
||||
pm01 = measure.get(Measurements::PM01, 1);
|
||||
float correctedPm = measure.getCorrectedPM25(false, 1);
|
||||
pm25 = round(correctedPm);
|
||||
pm10 = measure.get(Measurements::PM10, 1);
|
||||
pm03PCount = measure.get(Measurements::PM03_PC, 1);
|
||||
}
|
||||
if (config.hasSensorPMS2) {
|
||||
_temp = measure.getFloat(Measurements::Temperature, 2);
|
||||
_hum = measure.getFloat(Measurements::Humidity, 2);
|
||||
if (ch2HasPm) {
|
||||
if (config.hasSensorPMS2) {
|
||||
_temp = measure.getFloat(Measurements::Temperature, 2);
|
||||
_hum = measure.getFloat(Measurements::Humidity, 2);
|
||||
}
|
||||
pm01 = measure.get(Measurements::PM01, 2);
|
||||
float correctedPm = measure.getCorrectedPM25(false, 2);
|
||||
pm25 = round(correctedPm);
|
||||
@@ -154,7 +173,7 @@ String OpenMetrics::getPayload(void) {
|
||||
}
|
||||
|
||||
// Add measurements that valid to the metrics
|
||||
if (config.hasSensorPMS1 || config.hasSensorPMS2) {
|
||||
if (ch1HasPm || ch2HasPm) {
|
||||
if (utils::isValidPm(pm01)) {
|
||||
add_metric("pm1",
|
||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
name=AirGradient Air Quality Sensor
|
||||
version=3.6.4
|
||||
version=3.6.5
|
||||
author=AirGradient <support@airgradient.com>
|
||||
maintainer=AirGradient <support@airgradient.com>
|
||||
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ lib_deps =
|
||||
Update
|
||||
DNSServer
|
||||
h2zero/NimBLE-Arduino@^2.1.0
|
||||
bblanchon/ArduinoJson@^7
|
||||
sensirion/Sensirion Core@^0.7.3
|
||||
sensirion/Sensirion UART SPS30@^1.0.0
|
||||
bblanchon/ArduinoJson@^7.4.3
|
||||
|
||||
[env:esp8266]
|
||||
platform = espressif8266
|
||||
|
||||
+10
-2
@@ -19,6 +19,7 @@ const char *LED_BAR_MODE_NAMES[] = {
|
||||
[LedBarModeOff] = "off",
|
||||
[LedBarModePm] = "pm",
|
||||
[LedBarModeCO2] = "co2",
|
||||
[LedBarModeIaqs] = "iaqs",
|
||||
};
|
||||
|
||||
const char *PM_CORRECTION_ALGORITHM_NAMES[] = {
|
||||
@@ -112,6 +113,8 @@ String Configuration::getLedBarModeName(LedBarMode mode) {
|
||||
return String(LED_BAR_MODE_NAMES[LedBarModePm]);
|
||||
} else if (mode == LedBarModeCO2) {
|
||||
return String(LED_BAR_MODE_NAMES[LedBarModeCO2]);
|
||||
} else if (mode == LedBarModeIaqs) {
|
||||
return String(LED_BAR_MODE_NAMES[LedBarModeIaqs]);
|
||||
}
|
||||
return String("unknown");
|
||||
}
|
||||
@@ -740,7 +743,8 @@ bool Configuration::parse(String data, bool isLocal) {
|
||||
String mode = root[jprop_ledBarMode];
|
||||
if (mode == getLedBarModeName(LedBarMode::LedBarModeCO2) ||
|
||||
mode == getLedBarModeName(LedBarMode::LedBarModeOff) ||
|
||||
mode == getLedBarModeName(LedBarMode::LedBarModePm)) {
|
||||
mode == getLedBarModeName(LedBarMode::LedBarModePm) ||
|
||||
mode == getLedBarModeName(LedBarMode::LedBarModeIaqs)) {
|
||||
String oldMode = jconfig[jprop_ledBarMode];
|
||||
if (mode != oldMode) {
|
||||
jconfig[jprop_ledBarMode] = mode;
|
||||
@@ -1190,6 +1194,9 @@ LedBarMode Configuration::getLedBarMode(void) {
|
||||
if (mode == getLedBarModeName(LedBarModePm)) {
|
||||
return LedBarModePm;
|
||||
}
|
||||
if (mode == getLedBarModeName(LedBarModeIaqs)) {
|
||||
return LedBarModeIaqs;
|
||||
}
|
||||
return LedBarModeOff;
|
||||
}
|
||||
|
||||
@@ -1398,7 +1405,8 @@ void Configuration::toConfig(const char *buf) {
|
||||
String mode = jconfig[jprop_ledBarMode];
|
||||
if (mode != getLedBarModeName(LedBarMode::LedBarModeCO2) &&
|
||||
mode != getLedBarModeName(LedBarMode::LedBarModeOff) &&
|
||||
mode != getLedBarModeName(LedBarMode::LedBarModePm)) {
|
||||
mode != getLedBarModeName(LedBarMode::LedBarModePm) &&
|
||||
mode != getLedBarModeName(LedBarMode::LedBarModeIaqs)) {
|
||||
isConfigFieldInvalid = true;
|
||||
} else {
|
||||
isConfigFieldInvalid = false;
|
||||
|
||||
+7
-3
@@ -1,11 +1,11 @@
|
||||
#ifndef _AG_CONFIG_H_
|
||||
#define _AG_CONFIG_H_
|
||||
|
||||
#include "App/AppDef.h"
|
||||
#include "Main/PrintLog.h"
|
||||
#include "AirGradient.h"
|
||||
#include <Arduino.h>
|
||||
#include "App/AppDef.h"
|
||||
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
||||
#include "Main/PrintLog.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#define MAX_SATELLITES 10
|
||||
|
||||
@@ -75,6 +75,10 @@ public:
|
||||
bool hasSensorS8 = true;
|
||||
bool hasSensorPMS1 = true;
|
||||
bool hasSensorPMS2 = true;
|
||||
bool hasSensorSPS30_1 =
|
||||
false; ///< SPS30 detected on PM channel 1 (auto-detect)
|
||||
bool hasSensorSPS30_2 =
|
||||
false; ///< SPS30 detected on PM channel 2 (auto-detect)
|
||||
bool hasSensorSGP = true;
|
||||
bool hasSensorSHT = true;
|
||||
|
||||
|
||||
+91
-18
@@ -1,5 +1,6 @@
|
||||
#include "AgOledDisplay.h"
|
||||
#include "Libraries/U8g2/src/U8g2lib.h"
|
||||
#include "Main/GoIaqs.h"
|
||||
#include "Main/utils.h"
|
||||
|
||||
/** Cast U8G2 */
|
||||
@@ -411,28 +412,100 @@ void OledDisplay::showDashboard(DashboardStatus status) {
|
||||
DISP()->drawUTF8(55, 61, "ug/m³");
|
||||
}
|
||||
|
||||
/** Draw tvocIndexlabel */
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawStr(100, 27, "VOC:");
|
||||
if (config.getLedBarMode() == LedBarMode::LedBarModeIaqs) {
|
||||
/** Draw IAQS panel (column 3 replaces VOC/NOx when LED bar mode
|
||||
* is set to iaqs). Three rows aligned with the CO2/PM2.5 column
|
||||
* rhythm: header (y=27), big score + grade letter (y=48),
|
||||
* dominant pollutant (y=61). */
|
||||
const int IAQS_COL_X = 100;
|
||||
const int IAQS_COL_W = 28;
|
||||
const int SCORE_GRADE_GAP = 1;
|
||||
const int ARROW_DOM_GAP = 2;
|
||||
const int ARROW_DOM_BOTH_GAP = 1;
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawStr(IAQS_COL_X, 27, "IAQS");
|
||||
|
||||
/** Draw tvocIndexvalue */
|
||||
int tvoc = round(value.getAverage(Measurements::TVOC));
|
||||
if (utils::isValidVOC(tvoc)) {
|
||||
sprintf(strBuf, "%d", tvoc);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(100, 39, strBuf);
|
||||
float pm25Avg = value.getAverage(Measurements::PM25);
|
||||
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||
pm25Avg = value.getCorrectedPM25(true);
|
||||
}
|
||||
float co2Avg = value.getAverage(Measurements::CO2);
|
||||
bool pmOk = utils::isValidPm((int)round(pm25Avg));
|
||||
bool coOk = utils::isValidCO2((int)round(co2Avg));
|
||||
|
||||
/** Draw NOx label */
|
||||
int nox = round(value.getAverage(Measurements::NOx));
|
||||
DISP()->drawStr(100, 53, "NOx:");
|
||||
if (utils::isValidNOx(nox)) {
|
||||
sprintf(strBuf, "%d", nox);
|
||||
if (!pmOk || !coOk) {
|
||||
DISP()->setFont(u8g2_font_t0_22b_tf);
|
||||
int scoreW = DISP()->getStrWidth("-");
|
||||
DISP()->drawStr(IAQS_COL_X + ((IAQS_COL_W - scoreW) / 2), 48, "-");
|
||||
} else {
|
||||
int pmScore = GoIaqs::pm25Score(pm25Avg);
|
||||
int coScore = GoIaqs::co2Score(co2Avg);
|
||||
int totalIaqs = GoIaqs::totalScore(pmScore, coScore);
|
||||
GoIaqs::Category cat = GoIaqs::categoryOf(totalIaqs);
|
||||
GoIaqs::Dominant dom = GoIaqs::dominantOf(pmScore, coScore);
|
||||
|
||||
/** Row 2: big score + letter grade centered in the IAQS column. */
|
||||
sprintf(strBuf, "%d", totalIaqs);
|
||||
DISP()->setFont(u8g2_font_t0_22b_tf);
|
||||
int scoreW = DISP()->getStrWidth(strBuf);
|
||||
|
||||
char gradeStr[2] = {GoIaqs::letterOf(cat), '\0'};
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
int gradeW = DISP()->getStrWidth(gradeStr);
|
||||
|
||||
int scoreX = IAQS_COL_X +
|
||||
((IAQS_COL_W - scoreW - SCORE_GRADE_GAP - gradeW) / 2);
|
||||
DISP()->setFont(u8g2_font_t0_22b_tf);
|
||||
DISP()->drawStr(scoreX, 48, strBuf);
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawStr(scoreX + scoreW + SCORE_GRADE_GAP, 48, gradeStr);
|
||||
|
||||
/** Row 3: dominant pollutant. */
|
||||
const char *domStr = "BOTH";
|
||||
if (dom == GoIaqs::DominantPm25) {
|
||||
domStr = "PM";
|
||||
} else if (dom == GoIaqs::DominantCo2) {
|
||||
domStr = "CO2";
|
||||
}
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
const int ARROW_W = 5;
|
||||
int arrowDomGap = (dom == GoIaqs::DominantBoth) ? ARROW_DOM_BOTH_GAP
|
||||
: ARROW_DOM_GAP;
|
||||
int domW = DISP()->getStrWidth(domStr);
|
||||
int domX = IAQS_COL_X +
|
||||
((IAQS_COL_W - ARROW_W - arrowDomGap - domW) / 2);
|
||||
int arrowY = 57;
|
||||
DISP()->drawLine(domX, arrowY, domX + ARROW_W, arrowY);
|
||||
DISP()->drawLine(domX + ARROW_W, arrowY, domX + ARROW_W - 2,
|
||||
arrowY - 2);
|
||||
DISP()->drawLine(domX + ARROW_W, arrowY, domX + ARROW_W - 2,
|
||||
arrowY + 2);
|
||||
DISP()->drawStr(domX + ARROW_W + arrowDomGap, 61, domStr);
|
||||
}
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
/** Draw tvocIndexlabel */
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawStr(100, 27, "VOC:");
|
||||
|
||||
/** Draw tvocIndexvalue */
|
||||
int tvoc = round(value.getAverage(Measurements::TVOC));
|
||||
if (utils::isValidVOC(tvoc)) {
|
||||
sprintf(strBuf, "%d", tvoc);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(100, 39, strBuf);
|
||||
|
||||
/** Draw NOx label */
|
||||
int nox = round(value.getAverage(Measurements::NOx));
|
||||
DISP()->drawStr(100, 53, "NOx:");
|
||||
if (utils::isValidNOx(nox)) {
|
||||
sprintf(strBuf, "%d", nox);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(100, 63, strBuf);
|
||||
}
|
||||
DISP()->drawStr(100, 63, strBuf);
|
||||
} while (DISP()->nextPage());
|
||||
} else if (ag->isBasic()) {
|
||||
ag->display.clear();
|
||||
|
||||
+75
-6
@@ -1,5 +1,6 @@
|
||||
#include "AgStateMachine.h"
|
||||
#include "AgOledDisplay.h"
|
||||
#include "Main/GoIaqs.h"
|
||||
|
||||
#define LED_TEST_BLINK_DELAY 50 /** ms */
|
||||
#define LED_FAST_BLINK_DELAY 250 /** ms */
|
||||
@@ -62,6 +63,9 @@ bool StateMachine::sensorhandleLeds(void) {
|
||||
case LedBarMode::LedBarModePm:
|
||||
totalLedUsed = pm25handleLeds();
|
||||
break;
|
||||
case LedBarMode::LedBarModeIaqs:
|
||||
totalLedUsed = iaqsHandleLeds();
|
||||
break;
|
||||
default:
|
||||
ag->ledBar.clear();
|
||||
break;
|
||||
@@ -267,6 +271,54 @@ int StateMachine::pm25handleLeds(void) {
|
||||
return totalUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Show GO IAQS Starter Score LED status.
|
||||
*
|
||||
* Computes the score from PM2.5 (corrected when enabled) and CO2 averages,
|
||||
* then lights a number of LEDs (from the right end) proportional to the
|
||||
* severity, all painted with the category color (Good/Moderate/Unhealthy).
|
||||
*
|
||||
* Mapping is 1:1 between LEDs and score: ledsLit = numLeds - totalScore,
|
||||
* so score 10 lights 1 LED (LED 10) and score 0 lights all 11 LEDs.
|
||||
* Connectivity-status overlays (WiFiLost, ServerLost, SensorConfigFailed)
|
||||
* are handled at the dispatcher level for IAQS mode: the entire bar is
|
||||
* turned off and only LED 0 is lit with the notification color, so the
|
||||
* notification is always unambiguous regardless of the IAQS score.
|
||||
*
|
||||
* @return number of LEDs used on the bar.
|
||||
*/
|
||||
int StateMachine::iaqsHandleLeds(void) {
|
||||
float pm25Value = value.getAverage(Measurements::PM25);
|
||||
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||
pm25Value = value.getCorrectedPM25(true);
|
||||
}
|
||||
float co2Value = value.getAverage(Measurements::CO2);
|
||||
|
||||
int pmScore = GoIaqs::pm25Score(pm25Value);
|
||||
int coScore = GoIaqs::co2Score(co2Value);
|
||||
int total = GoIaqs::totalScore(pmScore, coScore);
|
||||
GoIaqs::Rgb color = GoIaqs::colorOf(GoIaqs::categoryOf(total));
|
||||
|
||||
int numLeds = ag->ledBar.getNumberOfLeds();
|
||||
/** 1:1 LED-per-score mapping: worse score -> more LEDs lit. */
|
||||
int ledsLit = numLeds - total;
|
||||
if (ledsLit < 1) {
|
||||
ledsLit = 1;
|
||||
}
|
||||
if (ledsLit > numLeds) {
|
||||
ledsLit = numLeds;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ledsLit; i++) {
|
||||
ag->ledBar.setColor(color.r, color.g, color.b, numLeds - 1 - i);
|
||||
}
|
||||
|
||||
Serial.printf("GO IAQS = %d [pm25 score %d, co2 score %d, leds %d]\n", total,
|
||||
pmScore, coScore, ledsLit);
|
||||
|
||||
return ledsLit;
|
||||
}
|
||||
|
||||
void StateMachine::co2Calibration(void) {
|
||||
if (config.isCo2CalibrationRequested() && config.hasSensorS8) {
|
||||
logInfo("CO2 Calibration");
|
||||
@@ -764,9 +816,16 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
||||
/** Connection to WiFi network failed credentials incorrect encryption not
|
||||
* supported etc. */
|
||||
if (ag->isOne()) {
|
||||
bool allUsed = sensorhandleLeds();
|
||||
if (allUsed == false) {
|
||||
if (config.getLedBarMode() == LedBarMode::LedBarModeIaqs) {
|
||||
/** IAQS mode: suppress the score bar so only the notification LED
|
||||
* is visible. */
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(255, 0, 0, 0);
|
||||
} else {
|
||||
bool allUsed = sensorhandleLeds();
|
||||
if (allUsed == false) {
|
||||
ag->ledBar.setColor(255, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
@@ -777,9 +836,14 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
||||
/** Connected to WiFi network but the server cannot be reached through the
|
||||
* internet, e.g. blocked by firewall */
|
||||
if (ag->isOne()) {
|
||||
bool allUsed = sensorhandleLeds();
|
||||
if (allUsed == false) {
|
||||
if (config.getLedBarMode() == LedBarMode::LedBarModeIaqs) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(233, 183, 54, 0);
|
||||
} else {
|
||||
bool allUsed = sensorhandleLeds();
|
||||
if (allUsed == false) {
|
||||
ag->ledBar.setColor(233, 183, 54, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
@@ -790,9 +854,14 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
||||
/** Server is reachable but there is some configuration issue to be fixed on
|
||||
* the server side */
|
||||
if (ag->isOne()) {
|
||||
bool allUsed = sensorhandleLeds();
|
||||
if (allUsed == false) {
|
||||
if (config.getLedBarMode() == LedBarMode::LedBarModeIaqs) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(139, 24, 248, 0);
|
||||
} else {
|
||||
bool allUsed = sensorhandleLeds();
|
||||
if (allUsed == false) {
|
||||
ag->ledBar.setColor(139, 24, 248, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
|
||||
@@ -27,6 +27,7 @@ private:
|
||||
bool sensorhandleLeds(void);
|
||||
int co2handleLeds(void);
|
||||
int pm25handleLeds(void);
|
||||
int iaqsHandleLeds(void);
|
||||
void co2Calibration(void);
|
||||
void ledBarTest(void);
|
||||
void ledBarPowerUpTest(void);
|
||||
|
||||
+27
-25
@@ -125,7 +125,7 @@ void Measurements::printCurrentAverage() {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hasSensorPMS1) {
|
||||
if (config.hasSensorPMS1 || config.hasSensorSPS30_1) {
|
||||
printCurrentPMAverage(1);
|
||||
if (!config.hasSensorSHT) {
|
||||
if (utils::isValidTemperature(_temperature[0].update.avg)) {
|
||||
@@ -140,7 +140,7 @@ void Measurements::printCurrentAverage() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (config.hasSensorPMS2) {
|
||||
if (config.hasSensorPMS2 || config.hasSensorSPS30_2) {
|
||||
printCurrentPMAverage(2);
|
||||
if (!config.hasSensorSHT) {
|
||||
if (utils::isValidTemperature(_temperature[1].update.avg)) {
|
||||
@@ -1161,38 +1161,39 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi)
|
||||
|
||||
JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode) {
|
||||
JSONVar outdoor;
|
||||
bool ch1HasPm = config.hasSensorPMS1 || config.hasSensorSPS30_1;
|
||||
bool ch2HasPm = config.hasSensorPMS2 || config.hasSensorSPS30_2;
|
||||
|
||||
if (fwMode == FW_MODE_O_1P || fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) {
|
||||
// buildPMS params:
|
||||
/// Because only have 1 PMS, allCh is set to false
|
||||
/// But enable temp hum from PMS
|
||||
/// compensated values if requested by local server
|
||||
/// Set ch based on hasSensorPMSx
|
||||
if (config.hasSensorPMS1) {
|
||||
// Single PM channel — pick whichever channel is populated
|
||||
// Enable temp/hum from PMS; compensated values if requested by local server
|
||||
if (ch1HasPm) {
|
||||
outdoor = buildPMS(1, false, true, localServer);
|
||||
if (!localServer) {
|
||||
// Firmware version only available for PMS5003T
|
||||
if (!localServer && config.hasSensorPMS1) {
|
||||
outdoor[json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
|
||||
}
|
||||
} else {
|
||||
} else if (ch2HasPm) {
|
||||
outdoor = buildPMS(2, false, true, localServer);
|
||||
if (!localServer) {
|
||||
if (!localServer && config.hasSensorPMS2) {
|
||||
outdoor[json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// FW_MODE_O_1PPT && FW_MODE_O_1PP: Outdoor monitor that have 2 PMS sensor
|
||||
// buildPMS params:
|
||||
/// Have 2 PMS sensor, allCh is set to true (ch params ignored)
|
||||
/// Enable temp hum from PMS
|
||||
/// compensated values if requested by local server
|
||||
// FW_MODE_O_1PPT / FW_MODE_O_1PP: two PM channels, average via allCh
|
||||
outdoor = buildPMS(1, true, true, localServer);
|
||||
// PMS5003T version
|
||||
// Per-channel firmware version — only for PMS5003T channels
|
||||
if (!localServer) {
|
||||
outdoor["channels"]["1"][json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
|
||||
outdoor["channels"]["2"][json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
|
||||
if (config.hasSensorPMS1) {
|
||||
outdoor["channels"]["1"][json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
|
||||
}
|
||||
if (config.hasSensorPMS2) {
|
||||
outdoor["channels"]["2"][json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1202,15 +1203,16 @@ JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode) {
|
||||
JSONVar Measurements::buildIndoor(bool localServer) {
|
||||
JSONVar indoor;
|
||||
|
||||
if (config.hasSensorPMS1) {
|
||||
if (config.hasSensorPMS1 || config.hasSensorSPS30_1) {
|
||||
// buildPMS params:
|
||||
/// PMS channel 1 (indoor only have 1 PMS; hence allCh false)
|
||||
/// Not include temperature and humidity from PMS sensor
|
||||
/// Include compensated calculation
|
||||
indoor = buildPMS(1, false, false, true);
|
||||
if (!localServer) {
|
||||
// Indoor is using PMS5003
|
||||
indoor[json_prop_pmFirmware] = this->pms5003FirmwareVersion(ag->pms5003.getFirmwareVersion());
|
||||
if (!localServer && config.hasSensorPMS1) {
|
||||
// PMS firmware version only available for PMS5003
|
||||
indoor[json_prop_pmFirmware] =
|
||||
this->pms5003FirmwareVersion(ag->pms5003.getFirmwareVersion());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -846,8 +846,7 @@ void WifiConnector::CharacteristicCallbacks::onRead(NimBLECharacteristic *pChara
|
||||
}
|
||||
|
||||
void WifiConnector::CharacteristicCallbacks::onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) {
|
||||
Serial.printf("%s : onWrite(), value: %s\n", pCharacteristic->getUUID().toString().c_str(),
|
||||
pCharacteristic->getValue().c_str());
|
||||
Serial.printf("%s : onWrite()\n", pCharacteristic->getUUID().toString().c_str());
|
||||
|
||||
auto bleCred = NimBLEUUID(BLE_CRED_CHAR_UUID);
|
||||
if (pCharacteristic->getUUID().equals(bleCred)) {
|
||||
|
||||
+4
-6
@@ -6,9 +6,9 @@
|
||||
#endif
|
||||
|
||||
AirGradient::AirGradient(BoardType type)
|
||||
: pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sgp41(type),
|
||||
display(type), boardType(type), button(type), statusLed(type),
|
||||
ledBar(type), watchdog(type), sht(type) {}
|
||||
: pms5003(type), pms5003t_1(type), pms5003t_2(type), sps30_1(type),
|
||||
sps30_2(type), s8(type), sgp41(type), display(type), boardType(type),
|
||||
button(type), statusLed(type), ledBar(type), watchdog(type), sht(type) {}
|
||||
|
||||
/**
|
||||
* @brief Get pin number for I2C SDA
|
||||
@@ -61,9 +61,7 @@ String AirGradient::getBoardName(void) {
|
||||
* @return true ONE_INDOOR
|
||||
* @return false Other
|
||||
*/
|
||||
bool AirGradient::isOne(void) {
|
||||
return boardType == BoardType::ONE_INDOOR;
|
||||
}
|
||||
bool AirGradient::isOne(void) { return boardType == BoardType::ONE_INDOOR; }
|
||||
|
||||
bool AirGradient::isOpenAir(void) {
|
||||
return boardType == BoardType::OPEN_AIR_OUTDOOR;
|
||||
|
||||
+15
-3
@@ -7,18 +7,18 @@
|
||||
#include "Main/LedBar.h"
|
||||
#include "Main/PushButton.h"
|
||||
#include "Main/StatusLed.h"
|
||||
#include "Main/utils.h"
|
||||
#include "PMS/PMS5003.h"
|
||||
#include "PMS/PMS5003T.h"
|
||||
#include "S8/S8.h"
|
||||
#include "SPS30/SPS30.h"
|
||||
#include "Sgp41/Sgp41.h"
|
||||
#include "Sht/Sht.h"
|
||||
#include "Main/utils.h"
|
||||
|
||||
#ifndef GIT_VERSION
|
||||
#define GIT_VERSION "3.6.4-snap"
|
||||
#define GIT_VERSION "3.6.5-snap"
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ESP8266
|
||||
// Airgradient server root ca certificate
|
||||
const char *const AG_SERVER_ROOT_CA =
|
||||
@@ -81,6 +81,18 @@ public:
|
||||
*/
|
||||
PMS5003T pms5003t_2;
|
||||
|
||||
/**
|
||||
* @brief Sensirion SPS30 particulate matter sensor (UART), channel 1.
|
||||
* Used as alternative PM sensor via auto-detection on Serial0.
|
||||
*/
|
||||
SPS30 sps30_1;
|
||||
|
||||
/**
|
||||
* @brief Sensirion SPS30 particulate matter sensor (UART), channel 2.
|
||||
* Used on OPEN_AIR_OUTDOOR when a second SPS30 is detected.
|
||||
*/
|
||||
SPS30 sps30_2;
|
||||
|
||||
/**
|
||||
* @brief SenseAirS8 CO2 sensor
|
||||
*/
|
||||
|
||||
@@ -81,6 +81,9 @@ enum LedBarMode {
|
||||
|
||||
/** Use LED bar for show CO2 value level */
|
||||
LedBarModeCO2,
|
||||
|
||||
/** Use LED bar to show GO IAQS Starter Score (PM2.5 + CO2) */
|
||||
LedBarModeIaqs,
|
||||
};
|
||||
|
||||
enum ConfigurationControl {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
AlignAfterOpenBracket: Align
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
IndentCaseLabels: true
|
||||
SpacesBeforeTrailingComments: 2
|
||||
PointerAlignment: Left
|
||||
AlignEscapedNewlines: Left
|
||||
ForEachMacros: ['TEST_GROUP', 'TEST']
|
||||
...
|
||||
@@ -1,11 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.bat]
|
||||
end_of_line = crlf
|
||||
@@ -1,3 +0,0 @@
|
||||
/examples/AllCommandsShdlc/build/
|
||||
/examples/AllCommandsI2c/build/
|
||||
.vscode
|
||||
@@ -1,39 +0,0 @@
|
||||
stages:
|
||||
- validate
|
||||
- test
|
||||
|
||||
compile_test_shdlc:
|
||||
stage: test
|
||||
image:
|
||||
name: registry.gitlab.sensirion.lokal/sensirion/docker/docker-arduino:0.5.0
|
||||
tags: [docker, linux]
|
||||
script:
|
||||
- tests/compile_test.py -s examples/AllCommandsShdlc/
|
||||
|
||||
compile_test_i2c:
|
||||
stage: test
|
||||
image:
|
||||
name: registry.gitlab.sensirion.lokal/sensirion/docker/docker-arduino:0.5.0
|
||||
tags: [docker, linux]
|
||||
script:
|
||||
- tests/compile_test.py -s examples/AllCommandsI2c/
|
||||
|
||||
syntax_check:
|
||||
stage: validate
|
||||
image:
|
||||
name: registry.gitlab.sensirion.lokal/sensirion/docker/docker-python:3.8-20.04-2.7.0
|
||||
tags: [linux, docker]
|
||||
before_script:
|
||||
# For performance and stability reasons, use offline installation.
|
||||
- pip install --no-index --find-links=/pip editorconfig-checker
|
||||
- pip install flake8
|
||||
- apt-get update && apt-get install -yq clang-format-6.0
|
||||
script:
|
||||
- tests/syntax_check.sh
|
||||
|
||||
cppcheck:
|
||||
stage: validate
|
||||
image: registry.gitlab.sensirion.lokal/sensirion/docker/docker-cppcheck:1.0.0
|
||||
tags: [linux, docker]
|
||||
script:
|
||||
- tests/run_cppcheck.sh
|
||||
@@ -1,161 +0,0 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_
|
||||
and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
|
||||
|
||||
`Unreleased`_
|
||||
-------------
|
||||
|
||||
|
||||
`0.6.0`_ 2022-06-22
|
||||
-------------------
|
||||
|
||||
- Fix compiler warnings in SensirionErrors.cpp
|
||||
- Allow drivers to choose CRC function
|
||||
|
||||
`0.5.3`_ 2021-10-19
|
||||
-------------------
|
||||
|
||||
- Add support for sensor specific errors
|
||||
- Update keywords.txt
|
||||
|
||||
|
||||
`0.5.2`_ 2021-08-03
|
||||
-------------------
|
||||
|
||||
Fixed
|
||||
.....
|
||||
|
||||
- Fix CRC insertion in ``SensirionI2CTxFrame`` when more then one parameter
|
||||
is sent to the sensor.
|
||||
|
||||
`0.5.1`_ 2021-07-08
|
||||
-------------------
|
||||
|
||||
Changed
|
||||
.......
|
||||
|
||||
- Adjusted deprecation warnings
|
||||
|
||||
`0.5.0`_ 2021-07-07
|
||||
-------------------
|
||||
|
||||
Added
|
||||
.....
|
||||
|
||||
- Enable SensirionTxFrame to incorporate Uint8 and Uint16 commands
|
||||
|
||||
|
||||
`0.4.3`_ 2021-02-12
|
||||
-------------------
|
||||
|
||||
Added
|
||||
.....
|
||||
|
||||
- Added ``const`` modifier to functions which process MOSI array data.
|
||||
|
||||
`0.4.2`_ 2021-01-29
|
||||
-------------------
|
||||
|
||||
Changed
|
||||
.......
|
||||
|
||||
- Renamed the library header from ``SensirionCoreArduinoLibrary.h`` to ``SensirionCore.h``.
|
||||
We keep the old header for legacy support.
|
||||
|
||||
`0.4.1`_ 2021-01-28
|
||||
-------------------
|
||||
|
||||
Fixed
|
||||
.....
|
||||
|
||||
- Properly handle I2C write errors
|
||||
|
||||
|
||||
`0.4.0`_ 2021-01-20
|
||||
-------------------
|
||||
|
||||
Added
|
||||
.....
|
||||
|
||||
- Documentation for all functions.
|
||||
|
||||
Breaking
|
||||
........
|
||||
|
||||
- Change interface of ``errorToString()`` function to include length of the
|
||||
provided buffer.
|
||||
|
||||
Removed
|
||||
.......
|
||||
|
||||
- Removed ``reset()`` function from ``SensirionI2CTxFrame`` since the
|
||||
functionality is not needed.
|
||||
|
||||
|
||||
`0.3.0`_ 2021-01-13
|
||||
-------------------
|
||||
|
||||
Added
|
||||
.....
|
||||
|
||||
- Core implementation for I2C communication. This includes a RX and TX frame
|
||||
and a I2C communication class.
|
||||
|
||||
Changed
|
||||
.......
|
||||
|
||||
- SHDLC and I2C RX frame inherit from a RX frame base class.
|
||||
- ESP8266 test board from esp8266:esp8266:arduino to esp8266:esp8266:generic.
|
||||
- Sorted errors into general, SHDLC and I2C errors.
|
||||
- Replace C style casts with ``static_cast``.
|
||||
|
||||
|
||||
`0.2.0`_ 2021-01-11
|
||||
-------------------
|
||||
|
||||
Added
|
||||
.....
|
||||
|
||||
- Explanation what SHDLC is in README.
|
||||
- ``SensirionErrors.h`` to ``SensirionCoreArduinoLibrary.h``.
|
||||
- ``sendAndReceiveFrame()`` function to ``SensirionShdlcCommunication``. This
|
||||
function combines ``sendFrame()`` and ``receiveFrame()`` into one function and
|
||||
adds additional error checking.
|
||||
|
||||
Changed
|
||||
.......
|
||||
|
||||
- Rename DeviceError to ExecutionError.
|
||||
- Move check for execution error after the whole frame is read and checksum is
|
||||
checked. This prevents that a wrong checksum can't be displayed as an
|
||||
execution error.
|
||||
|
||||
Removed
|
||||
.......
|
||||
|
||||
- ``reset()`` function from ``SensirionShdlcTxFrame`` and ``SensirionShdlcRxFrame``,
|
||||
since one can just create a new frame object which has the same effect.
|
||||
|
||||
`0.1.0`_ 2021-01-07
|
||||
-------------------
|
||||
|
||||
- Initial release
|
||||
|
||||
|
||||
.. _Unreleased: https://github.com/Sensirion/arduino-core/compare/0.6.0...main
|
||||
.. _0.6.0: https://github.com/Sensirion/arduino-core/compare/0.6.0...0.5.3
|
||||
.. _0.5.3: https://github.com/Sensirion/arduino-core/compare/0.5.2...0.5.3
|
||||
.. _0.5.2: https://github.com/Sensirion/arduino-core/compare/0.5.1...0.5.2
|
||||
.. _0.5.1: https://github.com/Sensirion/arduino-core/compare/0.5.0...0.5.1
|
||||
.. _0.5.0: https://github.com/Sensirion/arduino-core/compare/0.4.3...0.5.0
|
||||
.. _0.4.3: https://github.com/Sensirion/arduino-core/compare/0.4.2...0.4.3
|
||||
.. _0.4.2: https://github.com/Sensirion/arduino-core/compare/0.4.1...0.4.2
|
||||
.. _0.4.1: https://github.com/Sensirion/arduino-core/compare/0.4.0...0.4.1
|
||||
.. _0.4.0: https://github.com/Sensirion/arduino-core/compare/0.3.0...0.4.0
|
||||
.. _0.3.0: https://github.com/Sensirion/arduino-core/compare/0.2.0...0.3.0
|
||||
.. _0.2.0: https://github.com/Sensirion/arduino-core/compare/0.1.0...0.2.0
|
||||
.. _0.1.0: https://github.com/Sensirion/arduino-core/releases/tag/0.1.0
|
||||
@@ -1,29 +0,0 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2020, Sensirion AG
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,137 +0,0 @@
|
||||
# Sensirion Arduino Core Library
|
||||
|
||||
This library provides SHDLC and I2C protocol implementations for Sensirion
|
||||
sensors. There shouldn't be a reason to use it directly, but is required by the
|
||||
sensor driver libraries provided here:
|
||||
|
||||
- [SCD4x](https://github.com/Sensirion/arduino-i2c-scd4x)
|
||||
- [SVM40-I2C](https://github.com/Sensirion/arduino-i2c-svm40)
|
||||
- [SVM40-UART](https://github.com/Sensirion/arduino-uart-svm40)
|
||||
- [SFA3x-I2C](https://github.com/Sensirion/arduino-i2c-sfa3x)
|
||||
- [SFA3x-UART](https://github.com/Sensirion/arduino-uart-sfa3x)
|
||||
|
||||
# More Drivers
|
||||
|
||||
Not looking for Arduino drivers? Check out our other drivers here:
|
||||
|
||||
- [Embedded](https://github.com/Sensirion/info#repositories)
|
||||
- [Python](https://github.com/Sensirion/info#python-drivers)
|
||||
|
||||
# Usage
|
||||
|
||||
## SHDLC
|
||||
|
||||
SHDLC (Sensirion High-Level Data Link Control) is a byte-oriented master-slave
|
||||
communication protocol based on [ISO
|
||||
HDLC](https://en.wikipedia.org/wiki/High-Level_Data_Link_Control). It is used
|
||||
to control some of Sensirion’s devices (for example mass flow controllers). The
|
||||
detailed protocol documentation is not publicly available (yet). If you need
|
||||
it, please contact our [customer
|
||||
support](https://www.sensirion.com/en/about-us/contact/).
|
||||
|
||||
This library provides the following classes for communication with Sensirion
|
||||
Sensors using the SHDLC protocol.
|
||||
- `SensirionShdlcTxFrame`
|
||||
- `SensirionShdlcRxFrame`
|
||||
- `SensirionShdlcCommunication`
|
||||
|
||||
### Example Usage
|
||||
First initialize an instance of `SensirionShdlcTxFrame` and
|
||||
`SensirionShdlcRxFrame` with a properly sized buffer. A good worst case
|
||||
estimation for the buffer size is `2 * (n+6)` where `n` is the number of bytes
|
||||
you want to send. After that you can build your frame by first calling
|
||||
`begin()`. Information about the correct COMMAND and ADDRESS can be found on
|
||||
the data sheet of your sensor. Then you can add data to the frame by using
|
||||
different add member functions. See the code below for examples. After adding
|
||||
your data finish the frame by calling `finish()`.
|
||||
|
||||
To send this frame to the sensor you first need to initialize the correct
|
||||
Stream object (Serial,UART,...) to talk to your sensor. Don't forget to also
|
||||
call the `.begin()` function with the right configuration. Then call the static
|
||||
function `sendAndReceiveFrame()` from `SensirionShdlcCommunication` as shown
|
||||
below. You need to replace `STREAMOBJECT` with the initialized Stream object of
|
||||
your choice. Additionally you need to provide a timeout for to receive data
|
||||
back, consult the data sheet of your sensor for information on the best timeout
|
||||
value.
|
||||
|
||||
You can decode the frame by using the different get members to convert the
|
||||
received data to desired data types.
|
||||
|
||||
All functions return a error code if an error occurs during execution and zero
|
||||
otherwise.
|
||||
|
||||
```cpp
|
||||
uint8_t txBuffer[256];
|
||||
uint8_t rxBuffer[256];
|
||||
|
||||
SensirionShdlcTxFrame txFrame(txBuffer, 256);
|
||||
SensirionShdlcRxFrame rxFrame(rxBuffer, 256);
|
||||
|
||||
txFrame.begin(COMMAND, ADDRESS, DATALENGTH);
|
||||
|
||||
txFrame.addUInt8(UINT8);
|
||||
txFrame.addUInt32(UINT32);
|
||||
|
||||
txFrame.finish();
|
||||
|
||||
SensirionShdlcCommunication::sendAndReceiveFrame(STREAMOBJECT, txFrame, rxFrame, TIMEOUT);
|
||||
|
||||
rxFrame.getUInt16(UINT16);
|
||||
rxFrame.getFloat(FLOAT);
|
||||
|
||||
```
|
||||
|
||||
## I2C
|
||||
|
||||
This library provides the following classes for communication with Sensirion
|
||||
Sensors using the I2C protocol.
|
||||
- `SensirionI2cTxFrame`
|
||||
- `SensirionI2cRxFrame`
|
||||
- `SensirionI2cCommunication`
|
||||
|
||||
### Example Usage
|
||||
|
||||
First initialize an instance of `SensirionI2CTxFrame` and `SensirionI2CRxFrame`
|
||||
with a buffer sized the amount of data to read times 1.5. This is needed to
|
||||
account for the CRC which is added after every second byte. After that you can
|
||||
build your frame by first calling `addCommand()` to add the command at the
|
||||
beginning of the frame. Information about the different COMMANDs can be found
|
||||
on the data sheet of your sensor. Then you can add data to the frame by using
|
||||
different add member functions. See the code below for examples.
|
||||
|
||||
To send this frame to the sensor you first need to initialize a Wire object.
|
||||
Don't forget to also call the `.begin()` function with the right configuration.
|
||||
Then call the static function `sendFrame()` form `SensirionI2CCommunication` as
|
||||
shown below. You can find the ADDRESS on the data sheet of the sensor. You also
|
||||
need to replace `WIREOBJECT` with the initialized Wire object. Then wait the in
|
||||
the data sheet documented `READ_DELAY` before receiving the reply from the
|
||||
sensor by calling `receiveFrame()` with the same Wire object.
|
||||
|
||||
You then can decode the frame by using the different get members to convert the
|
||||
received data to desired data types.
|
||||
|
||||
All functions return a error code if an error occurs during execution and zero
|
||||
otherwise.
|
||||
|
||||
```cpp
|
||||
uint8_t txBuffer[256];
|
||||
uint8_t rxBuffer[256];
|
||||
|
||||
SensirionShdlcTxFrame txFrame(txBuffer, 256);
|
||||
SensirionShdlcRxFrame rxFrame(rxBuffer, 256);
|
||||
|
||||
txFrame.addCommand(COMMAND);
|
||||
|
||||
txFrame.addUInt8(UINT8);
|
||||
txFrame.addUInt32(UINT32);
|
||||
|
||||
SensirionShdlcCommunication::sendFrame(ADDRESS, txFrame, WIREOBJECT);
|
||||
|
||||
delay(READ_DELAY);
|
||||
|
||||
SensirionShdlcCommunication::receiveFrame(ADDRESS, rxFrame, WIREOBJECT);
|
||||
|
||||
rxFrame.getUInt16(UINT16);
|
||||
rxFrame.getFloat(FLOAT);
|
||||
|
||||
```
|
||||
@@ -1,62 +0,0 @@
|
||||
#include <SensirionCore.h>
|
||||
#include <Wire.h>
|
||||
#include <stdint.h>
|
||||
|
||||
uint8_t txBuffer[256];
|
||||
uint8_t rxBuffer[256];
|
||||
|
||||
SensirionI2CTxFrame txFrame(txBuffer, 256);
|
||||
SensirionI2CRxFrame rxFrame(rxBuffer, 256);
|
||||
|
||||
void setup() {
|
||||
Wire.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint16_t mockCommand = 42;
|
||||
uint16_t error = txFrame.addCommand(mockCommand);
|
||||
|
||||
uint32_t mockUInt32 = 42;
|
||||
error |= txFrame.addUInt32(mockUInt32);
|
||||
|
||||
int32_t mockInt32 = 42;
|
||||
error |= txFrame.addInt32(mockInt32);
|
||||
|
||||
uint16_t mockUInt16 = 42;
|
||||
error |= txFrame.addUInt16(mockUInt16);
|
||||
|
||||
int16_t mockInt16 = 42;
|
||||
error |= txFrame.addInt16(mockInt16);
|
||||
|
||||
uint8_t mockUInt8 = 42;
|
||||
error |= txFrame.addUInt8(mockUInt8);
|
||||
|
||||
int8_t mockInt8 = 42;
|
||||
error |= txFrame.addInt8(mockInt8);
|
||||
|
||||
float mockFloat = 42.0f;
|
||||
error |= txFrame.addFloat(mockFloat);
|
||||
|
||||
bool mockBool = true;
|
||||
error |= txFrame.addBool(mockBool);
|
||||
|
||||
uint8_t mockBytes[] = {42, 42, 42, 42};
|
||||
error |= txFrame.addBytes(mockBytes, 4);
|
||||
|
||||
uint8_t mockAddress = 42;
|
||||
|
||||
error |= SensirionI2CCommunication::sendFrame(mockAddress, txFrame, Wire);
|
||||
|
||||
size_t mockNumBytes = 42;
|
||||
error |= SensirionI2CCommunication::receiveFrame(mockAddress, mockNumBytes,
|
||||
rxFrame, Wire);
|
||||
|
||||
error |= rxFrame.getUInt32(mockUInt32);
|
||||
error |= rxFrame.getInt32(mockInt32);
|
||||
error |= rxFrame.getUInt16(mockUInt16);
|
||||
error |= rxFrame.getInt16(mockInt16);
|
||||
error |= rxFrame.getUInt8(mockUInt8);
|
||||
error |= rxFrame.getInt8(mockInt8);
|
||||
error |= rxFrame.getFloat(mockFloat);
|
||||
error |= rxFrame.getBytes(mockBytes, 4);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
#include <SensirionCore.h>
|
||||
#include <stdint.h>
|
||||
|
||||
uint8_t txBuffer[256];
|
||||
uint8_t rxBuffer[256];
|
||||
|
||||
SensirionShdlcTxFrame txFrame(txBuffer, 256);
|
||||
SensirionShdlcRxFrame rxFrame(rxBuffer, 256);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint8_t mockCommand = 42;
|
||||
uint8_t mockAddress = 42;
|
||||
uint8_t mockDataLength = 42;
|
||||
uint16_t error = txFrame.begin(mockCommand, mockAddress, mockDataLength);
|
||||
|
||||
uint32_t mockUInt32 = 42;
|
||||
error |= txFrame.addUInt32(mockUInt32);
|
||||
|
||||
int32_t mockInt32 = 42;
|
||||
error |= txFrame.addInt32(mockInt32);
|
||||
|
||||
uint16_t mockUInt16 = 42;
|
||||
error |= txFrame.addUInt16(mockUInt16);
|
||||
|
||||
int16_t mockInt16 = 42;
|
||||
error |= txFrame.addInt16(mockInt16);
|
||||
|
||||
uint8_t mockUInt8 = 42;
|
||||
error |= txFrame.addUInt8(mockUInt8);
|
||||
|
||||
int8_t mockInt8 = 42;
|
||||
error |= txFrame.addInt8(mockInt8);
|
||||
|
||||
float mockFloat = 42.0f;
|
||||
error |= txFrame.addFloat(mockFloat);
|
||||
|
||||
bool mockBool = true;
|
||||
error |= txFrame.addBool(mockBool);
|
||||
|
||||
uint8_t mockBytes[] = {42, 42, 42, 42};
|
||||
error |= txFrame.addBytes(mockBytes, 4);
|
||||
|
||||
error |= txFrame.finish();
|
||||
|
||||
error |= SensirionShdlcCommunication::sendFrame(txFrame, Serial);
|
||||
|
||||
error |= SensirionShdlcCommunication::sendAndReceiveFrame(
|
||||
Serial, txFrame, rxFrame, 10000000);
|
||||
|
||||
error |=
|
||||
SensirionShdlcCommunication::receiveFrame(rxFrame, Serial, 1000000);
|
||||
|
||||
error |= rxFrame.getUInt32(mockUInt32);
|
||||
error |= rxFrame.getInt32(mockInt32);
|
||||
error |= rxFrame.getUInt16(mockUInt16);
|
||||
error |= rxFrame.getInt16(mockInt16);
|
||||
error |= rxFrame.getUInt8(mockUInt8);
|
||||
error |= rxFrame.getInt8(mockInt8);
|
||||
error |= rxFrame.getFloat(mockFloat);
|
||||
error |= rxFrame.getBytes(mockBytes, 4);
|
||||
|
||||
mockCommand = rxFrame.getCommand();
|
||||
mockAddress = rxFrame.getAddress();
|
||||
mockDataLength = rxFrame.getDataLength();
|
||||
uint8_t mockState = rxFrame.getState();
|
||||
if (mockState) {
|
||||
// There is an error in the device.
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
SensirionShdlcCommunication KEYWORD1
|
||||
SensirionShdlcRxFrame KEYWORD1
|
||||
SensirionShdlcTxFrame KEYWORD1
|
||||
SensirionI2CTxFrame KEYWORD1
|
||||
SensirionI2CRxFrame KEYWORD1
|
||||
SensirionI2CCommunication KEYWORD1
|
||||
|
||||
# SensirionErrors.h
|
||||
HighLevelError KEYWORD1
|
||||
LowLevelError KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
sendFrame KEYWORD2
|
||||
receiveFrame KEYWORD2
|
||||
sendAndReceiveFrame KEYWORD2
|
||||
addUInt32 KEYWORD2
|
||||
addInt32 KEYWORD2
|
||||
addUInt16 KEYWORD2
|
||||
addInt16 KEYWORD2
|
||||
addUInt8 KEYWORD2
|
||||
addInt8 KEYWORD2
|
||||
addFloat KEYWORD2
|
||||
addBytes KEYWORD2
|
||||
addBool KEYWORD2
|
||||
addCommand KEYWORD2
|
||||
begin KEYWORD2
|
||||
finish KEYWORD2
|
||||
reset KEYWORD2
|
||||
getUInt32 KEYWORD2
|
||||
getInt32 KEYWORD2
|
||||
getUInt16 KEYWORD2
|
||||
getInt16 KEYWORD2
|
||||
getUInt8 KEYWORD2
|
||||
getInt8 KEYWORD2
|
||||
getFloat KEYWORD2
|
||||
getBytes KEYWORD2
|
||||
getCommand KEYWORD2
|
||||
getAddress KEYWORD2
|
||||
getDataLength KEYWORD2
|
||||
getState KEYWORD2
|
||||
|
||||
# SensirionErrors.h
|
||||
errorToString KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
@@ -1,9 +0,0 @@
|
||||
name=Sensirion Core
|
||||
version=0.6.0
|
||||
author=Sensirion
|
||||
maintainer=Sensirion
|
||||
sentence=Library containing code base for Sensirion Sensor Libraries.
|
||||
paragraph=All Libraries for Sensirion Sensors use this library as a code base. In this library the Sensirion specific parts for I2C and UART communication are implemented. It provides dynamic frame construction, checksum calculation and buffer handling.
|
||||
category=Communication
|
||||
url=https://github.com/Sensirion/arduino-core/
|
||||
includes=SensirionCore.h
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef _SENSIRION_CORE_H_
|
||||
#define _SENSIRION_CORE_H_
|
||||
|
||||
#include "SensirionCrc.h"
|
||||
#include "SensirionErrors.h"
|
||||
#include "SensirionRxFrame.h"
|
||||
|
||||
#include "SensirionShdlcCommunication.h"
|
||||
#include "SensirionShdlcRxFrame.h"
|
||||
#include "SensirionShdlcTxFrame.h"
|
||||
|
||||
#include "SensirionI2CCommunication.h"
|
||||
#include "SensirionI2CRxFrame.h"
|
||||
#include "SensirionI2CTxFrame.h"
|
||||
|
||||
#endif /* _SENSIRION_CORE_H_ */
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* THIS IS A LEGACY FILE AND WILL BE REMOVED SOON.
|
||||
*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef _SENSIRION_CORE_ARDUINO_LIBRARY_H_
|
||||
#define _SENSIRION_CORE_ARDUINO_LIBRARY_H_
|
||||
|
||||
#pragma GCC warning \
|
||||
"Legacy file SensirionCoreArdunioLibrary.h included. Please include SensirionCore.h instead."
|
||||
|
||||
#include "SensirionErrors.h"
|
||||
#include "SensirionRxFrame.h"
|
||||
|
||||
#include "SensirionShdlcCommunication.h"
|
||||
#include "SensirionShdlcRxFrame.h"
|
||||
#include "SensirionShdlcTxFrame.h"
|
||||
|
||||
#include "SensirionI2CCommunication.h"
|
||||
#include "SensirionI2CRxFrame.h"
|
||||
#include "SensirionI2CTxFrame.h"
|
||||
|
||||
#endif /* _SENSIRION_CORE_ARDUION_LIBRARY_H_ */
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "SensirionCrc.h"
|
||||
|
||||
uint8_t generateCRCGeneric(const uint8_t* data, size_t count, uint8_t init,
|
||||
uint8_t polynomial) {
|
||||
uint8_t crc = init;
|
||||
|
||||
/* calculates 8-Bit checksum with given polynomial */
|
||||
for (size_t current_byte = 0; current_byte < count; ++current_byte) {
|
||||
crc ^= (data[current_byte]);
|
||||
for (uint8_t crc_bit = 8; crc_bit > 0; --crc_bit) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ polynomial;
|
||||
else
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint8_t generateCRC31_ff(const uint8_t* data, size_t count) {
|
||||
return generateCRCGeneric(data, count, 0xff, 0x31);
|
||||
}
|
||||
|
||||
uint8_t generateCRC31_00(const uint8_t* data, size_t count) {
|
||||
return generateCRCGeneric(data, count, 0x00, 0x31);
|
||||
}
|
||||
|
||||
uint8_t generateCRC(const uint8_t* data, size_t count, CrcPolynomial type) {
|
||||
if (CRC31_00 == type) {
|
||||
return generateCRC31_00(data, count);
|
||||
}
|
||||
return generateCRC31_ff(data, count);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _SENSIRION_CRC_H_
|
||||
#define _SENSIRION_CRC_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enum CrcPolynomial : uint8_t {
|
||||
CRC31_00 = 0x0,
|
||||
CRC31_ff = 0x1,
|
||||
};
|
||||
|
||||
uint8_t generateCRCGeneric(const uint8_t* data, size_t count, uint8_t init,
|
||||
uint8_t polynomial);
|
||||
|
||||
uint8_t generateCRC31_ff(const uint8_t* data, size_t count);
|
||||
|
||||
uint8_t generateCRC31_00(const uint8_t* data, size_t count);
|
||||
|
||||
/**
|
||||
* @brief Generate a crc for data given a polynomial type
|
||||
*
|
||||
* @param data data to calculate CRC for
|
||||
* @param count the array size of data
|
||||
* @param poly CRC polynomal to use
|
||||
*/
|
||||
uint8_t generateCRC(const uint8_t* data, size_t count, CrcPolynomial type);
|
||||
|
||||
#endif /* _SENSIRION_CRC_H_ */
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "SensirionErrors.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void errorToString(uint16_t error, char errorMessage[],
|
||||
size_t errorMessageSize) {
|
||||
|
||||
uint16_t highLevelError = error & 0xFF00;
|
||||
uint16_t lowLevelError = error & 0x00FF;
|
||||
|
||||
if (error & HighLevelError::SensorSpecificError) {
|
||||
snprintf(errorMessage, errorMessageSize, "Sensor specific error: 0x%2x",
|
||||
lowLevelError);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (highLevelError) {
|
||||
case HighLevelError::NoError:
|
||||
if (!error) {
|
||||
strncpy(errorMessage, "No error", errorMessageSize);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case HighLevelError::WriteError:
|
||||
switch (lowLevelError) {
|
||||
case LowLevelError::SerialWriteError:
|
||||
strncpy(errorMessage, "Error writing to serial",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::InternalBufferSizeError:
|
||||
strncpy(errorMessage,
|
||||
"Data too long to fit in transmit buffer",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::I2cAddressNack:
|
||||
strncpy(errorMessage,
|
||||
"Received NACK on transmit of address",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::I2cDataNack:
|
||||
strncpy(errorMessage, "Received NACK on transmit of data",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::I2cOtherError:
|
||||
strncpy(errorMessage, "Error writing to I2C bus",
|
||||
errorMessageSize);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case HighLevelError::ReadError:
|
||||
switch (lowLevelError) {
|
||||
case LowLevelError::NonemptyFrameError:
|
||||
strncpy(errorMessage, "Frame already contains data",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::TimeoutError:
|
||||
strncpy(errorMessage, "Timeout while reading data",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::ChecksumError:
|
||||
strncpy(errorMessage, "Checksum is wrong",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::CRCError:
|
||||
strncpy(errorMessage, "Wrong CRC found", errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::WrongNumberBytesError:
|
||||
strncpy(errorMessage, "Number of bytes not a multiple of 3",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::NotEnoughDataError:
|
||||
strncpy(errorMessage, "Not enough data received",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::InternalBufferSizeError:
|
||||
strncpy(errorMessage, "Internal I2C buffer too small",
|
||||
errorMessageSize);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case HighLevelError::ExecutionError: {
|
||||
char format[] = "Execution error, status register: 0x%x";
|
||||
snprintf(errorMessage, errorMessageSize, format, lowLevelError);
|
||||
return;
|
||||
}
|
||||
case HighLevelError::TxFrameError:
|
||||
switch (lowLevelError) {
|
||||
case LowLevelError::BufferSizeError:
|
||||
strncpy(errorMessage, "Not enough space in buffer",
|
||||
errorMessageSize);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case HighLevelError::RxFrameError:
|
||||
switch (lowLevelError) {
|
||||
case LowLevelError::BufferSizeError:
|
||||
strncpy(errorMessage, "Not enough space in buffer",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::NoDataError:
|
||||
strncpy(errorMessage, "No more data in frame",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::RxAddressError:
|
||||
strncpy(errorMessage, "Wrong address in return frame",
|
||||
errorMessageSize);
|
||||
return;
|
||||
case LowLevelError::RxCommandError:
|
||||
strncpy(errorMessage, "Wrong command in return frame",
|
||||
errorMessageSize);
|
||||
return;
|
||||
}
|
||||
}
|
||||
strncpy(errorMessage, "Error processing error", errorMessageSize);
|
||||
return;
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef _SENSIRION_ERRORS_H_
|
||||
#define _SENSIRION_ERRORS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enum HighLevelError : uint16_t {
|
||||
// general errors
|
||||
NoError = 0,
|
||||
WriteError = 0x0100,
|
||||
ReadError = 0x0200,
|
||||
TxFrameError = 0x0300,
|
||||
RxFrameError = 0x0400,
|
||||
// shdlc errors
|
||||
ExecutionError = 0x0500,
|
||||
// i2c errors
|
||||
|
||||
// Sensor specific errors. All errors higher than that are depending on the
|
||||
// sensor used.
|
||||
SensorSpecificError = 0x8000,
|
||||
};
|
||||
|
||||
enum LowLevelError : uint16_t {
|
||||
// general errors
|
||||
NonemptyFrameError,
|
||||
NoDataError,
|
||||
BufferSizeError,
|
||||
// shdlc errors
|
||||
StopByteError,
|
||||
ChecksumError,
|
||||
TimeoutError,
|
||||
RxCommandError,
|
||||
RxAddressError,
|
||||
SerialWriteError,
|
||||
// i2c errors
|
||||
WrongNumberBytesError,
|
||||
CRCError,
|
||||
I2cAddressNack,
|
||||
I2cDataNack,
|
||||
I2cOtherError,
|
||||
NotEnoughDataError,
|
||||
InternalBufferSizeError,
|
||||
};
|
||||
|
||||
/**
|
||||
* errorToString() - Convert error code to a human readable error message
|
||||
*
|
||||
* @param error Error code to be converted.
|
||||
* @param errorMessage String where the error text can be
|
||||
* stored.
|
||||
* @param errorMessageSize Size in bytes of the string buffer for the error
|
||||
* message.
|
||||
*/
|
||||
void errorToString(uint16_t error, char errorMessage[],
|
||||
size_t errorMessageSize);
|
||||
|
||||
#endif /* _SENSIRION_ERRORS_H_ */
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "SensirionI2CCommunication.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "SensirionCrc.h"
|
||||
#include "SensirionErrors.h"
|
||||
#include "SensirionI2CRxFrame.h"
|
||||
#include "SensirionI2CTxFrame.h"
|
||||
|
||||
static void clearRxBuffer(TwoWire& i2cBus) {
|
||||
while (i2cBus.available()) {
|
||||
(void)i2cBus.read();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CCommunication::sendFrame(uint8_t address,
|
||||
SensirionI2CTxFrame& frame,
|
||||
TwoWire& i2cBus) {
|
||||
i2cBus.beginTransmission(address);
|
||||
size_t writtenBytes = i2cBus.write(frame._buffer, frame._index);
|
||||
uint8_t i2c_error = i2cBus.endTransmission();
|
||||
if (writtenBytes != frame._index) {
|
||||
return WriteError | I2cOtherError;
|
||||
}
|
||||
// translate Arduino errors, see
|
||||
// https://www.arduino.cc/en/Reference/WireEndTransmission
|
||||
switch (i2c_error) {
|
||||
case 0:
|
||||
return NoError;
|
||||
case 1:
|
||||
return WriteError | InternalBufferSizeError;
|
||||
case 2:
|
||||
return WriteError | I2cAddressNack;
|
||||
case 3:
|
||||
return WriteError | I2cDataNack;
|
||||
default:
|
||||
return WriteError | I2cOtherError;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CCommunication::receiveFrame(uint8_t address,
|
||||
size_t numBytes,
|
||||
SensirionI2CRxFrame& frame,
|
||||
TwoWire& i2cBus,
|
||||
CrcPolynomial poly) {
|
||||
size_t readAmount;
|
||||
size_t i = 0;
|
||||
|
||||
#ifdef I2C_BUFFER_LENGTH
|
||||
const uint8_t sizeBuffer =
|
||||
(static_cast<uint8_t>(I2C_BUFFER_LENGTH) / static_cast<uint8_t>(3)) * 3;
|
||||
#elif defined(BUFFER_LENGTH)
|
||||
const uint8_t sizeBuffer =
|
||||
(static_cast<uint8_t>(BUFFER_LENGTH) / static_cast<uint8_t>(3)) * 3;
|
||||
#else
|
||||
const uint8_t sizeBuffer = 30;
|
||||
#endif
|
||||
|
||||
if (numBytes % 3) {
|
||||
return ReadError | WrongNumberBytesError;
|
||||
}
|
||||
if ((numBytes / 3) * 2 > frame._bufferSize) {
|
||||
return ReadError | BufferSizeError;
|
||||
}
|
||||
if (numBytes > sizeBuffer) {
|
||||
return ReadError | InternalBufferSizeError;
|
||||
}
|
||||
|
||||
readAmount = i2cBus.requestFrom(address, static_cast<uint8_t>(numBytes),
|
||||
static_cast<uint8_t>(true));
|
||||
if (numBytes != readAmount) {
|
||||
return ReadError | NotEnoughDataError;
|
||||
}
|
||||
do {
|
||||
frame._buffer[i++] = i2cBus.read();
|
||||
frame._buffer[i++] = i2cBus.read();
|
||||
uint8_t actualCRC = i2cBus.read();
|
||||
uint8_t expectedCRC = generateCRC(&frame._buffer[i - 2], 2, poly);
|
||||
if (actualCRC != expectedCRC) {
|
||||
clearRxBuffer(i2cBus);
|
||||
return ReadError | CRCError;
|
||||
}
|
||||
readAmount -= 3;
|
||||
} while (readAmount > 0);
|
||||
frame._numBytes = i;
|
||||
return NoError;
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef SENSIRION_I2C_COMMUNICATION_H_
|
||||
#define SENSIRION_I2C_COMMUNICATION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Wire.h>
|
||||
|
||||
#include "SensirionI2CRxFrame.h"
|
||||
#include "SensirionI2CTxFrame.h"
|
||||
|
||||
class SensirionI2CTxFrame;
|
||||
class SensirionI2CRxFrame;
|
||||
|
||||
/*
|
||||
* SensirionI2CCommunication - Class which is responsible for the communication
|
||||
* via a I2C bus. It provides functionality to send and receive frames from a
|
||||
* Sensirion sensor. The data is sent and received in a SensirionI2cTxFrame or
|
||||
* SensirionI2cRxFrame respectively.
|
||||
*/
|
||||
class SensirionI2CCommunication {
|
||||
public:
|
||||
/**
|
||||
* sendFrame() - Sends frame to sensor
|
||||
*
|
||||
* @param address I2C address of the sensor.
|
||||
* @param frame Tx frame object containing a finished frame to send to
|
||||
* the sensor.
|
||||
* @param i2cBus TwoWire object to communicate with the sensor.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
static uint16_t sendFrame(uint8_t address, SensirionI2CTxFrame& frame,
|
||||
TwoWire& i2cBus);
|
||||
|
||||
/**
|
||||
* receiveFrame() - Receive Frame from sensor
|
||||
*
|
||||
* @param address I2C address of the sensor.
|
||||
* @param numBytes Number of bytes to receive.
|
||||
* @param frame Rx frame to store the received data in.
|
||||
* @param i2cBus TwoWire object to communicate with the sensor.
|
||||
* @param poly CRC polynomal to use. Defaults to 0x31 with init 0xFF
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
static uint16_t receiveFrame(uint8_t address, size_t numBytes,
|
||||
SensirionI2CRxFrame& frame, TwoWire& i2cBus,
|
||||
CrcPolynomial poly = CRC31_ff);
|
||||
};
|
||||
|
||||
#endif /* SENSIRION_I2C_COMMUNICATION_H_ */
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef SENSIRION_I2C_RX_FRAME_H_
|
||||
#define SENSIRION_I2C_RX_FRAME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "SensirionI2CCommunication.h"
|
||||
#include "SensirionRxFrame.h"
|
||||
|
||||
/**
|
||||
* SenirionI2CRxFrame - Class which decodes the through I2C received data into
|
||||
* common data types. It contains a buffer which is filled by the
|
||||
* SensirionI2CCommunication class. By calling the different decode function
|
||||
* inherited from the SensirionRxFrame base class the raw data can be decoded
|
||||
* into different data types.
|
||||
*/
|
||||
class SensirionI2CRxFrame : public SensirionRxFrame {
|
||||
|
||||
friend class SensirionI2CCommunication;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param buffer Buffer in which the receive frame will be
|
||||
* stored.
|
||||
* @param bufferSize Number of bytes in the buffer for the receive frame.
|
||||
*
|
||||
*/
|
||||
SensirionI2CRxFrame(uint8_t buffer[], size_t bufferSize)
|
||||
: SensirionRxFrame(buffer, bufferSize){};
|
||||
};
|
||||
|
||||
#endif /* SENSIRION_I2C_RX_FRAME_H_ */
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "SensirionI2CTxFrame.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "SensirionCrc.h"
|
||||
#include "SensirionErrors.h"
|
||||
|
||||
SensirionI2CTxFrame::SensirionI2CTxFrame(uint8_t buffer[], size_t bufferSize,
|
||||
size_t numCommandBytes,
|
||||
CrcPolynomial poly)
|
||||
: _buffer(buffer), _bufferSize(bufferSize), _index(numCommandBytes),
|
||||
_numCommandBytes(numCommandBytes), _polynomial_type(poly) {
|
||||
}
|
||||
|
||||
SensirionI2CTxFrame::SensirionI2CTxFrame(uint8_t buffer[], size_t bufferSize,
|
||||
CrcPolynomial poly)
|
||||
: SensirionI2CTxFrame(buffer, bufferSize, 2, poly) {
|
||||
}
|
||||
|
||||
SensirionI2CTxFrame SensirionI2CTxFrame::createWithUInt8Command(
|
||||
uint8_t command, uint8_t buffer[], size_t bufferSize, CrcPolynomial poly) {
|
||||
SensirionI2CTxFrame instance =
|
||||
SensirionI2CTxFrame(buffer, bufferSize, 1, poly);
|
||||
instance._buffer[0] = command;
|
||||
return instance;
|
||||
}
|
||||
|
||||
SensirionI2CTxFrame SensirionI2CTxFrame::createWithUInt16Command(
|
||||
uint16_t command, uint8_t buffer[], size_t bufferSize, CrcPolynomial poly) {
|
||||
SensirionI2CTxFrame instance =
|
||||
SensirionI2CTxFrame(buffer, bufferSize, 2, poly);
|
||||
instance._buffer[0] = static_cast<uint8_t>((command & 0xFF00) >> 8);
|
||||
instance._buffer[1] = static_cast<uint8_t>((command & 0x00FF) >> 0);
|
||||
return instance;
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addCommand(uint16_t command) {
|
||||
if (_bufferSize < 2) {
|
||||
return TxFrameError | BufferSizeError;
|
||||
}
|
||||
_buffer[0] = static_cast<uint8_t>((command & 0xFF00) >> 8);
|
||||
_buffer[1] = static_cast<uint8_t>((command & 0x00FF) >> 0);
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addUInt32(uint32_t data) {
|
||||
uint16_t error = _addByte(static_cast<uint8_t>((data & 0xFF000000) >> 24));
|
||||
error |= _addByte(static_cast<uint8_t>((data & 0x00FF0000) >> 16));
|
||||
error |= _addByte(static_cast<uint8_t>((data & 0x0000FF00) >> 8));
|
||||
error |= _addByte(static_cast<uint8_t>((data & 0x000000FF) >> 0));
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addInt32(int32_t data) {
|
||||
return addUInt32(static_cast<uint32_t>(data));
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addUInt16(uint16_t data) {
|
||||
uint16_t error = _addByte(static_cast<uint8_t>((data & 0xFF00) >> 8));
|
||||
error |= _addByte(static_cast<uint8_t>((data & 0x00FF) >> 0));
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addInt16(int16_t data) {
|
||||
return addUInt16(static_cast<uint16_t>(data));
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addUInt8(uint8_t data) {
|
||||
return _addByte(data);
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addInt8(int8_t data) {
|
||||
return _addByte(static_cast<uint8_t>(data));
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addBool(bool data) {
|
||||
return _addByte(static_cast<uint8_t>(data));
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addFloat(float data) {
|
||||
union {
|
||||
uint32_t uInt32Data;
|
||||
float floatData;
|
||||
} convert;
|
||||
|
||||
convert.floatData = data;
|
||||
return addUInt32(convert.uInt32Data);
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::addBytes(const uint8_t data[],
|
||||
size_t dataLength) {
|
||||
uint16_t error = 0;
|
||||
for (size_t i = 0; i < dataLength; i++) {
|
||||
error |= _addByte(data[i]);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t SensirionI2CTxFrame::_addByte(uint8_t data) {
|
||||
if (_bufferSize <= _index) {
|
||||
return TxFrameError | BufferSizeError;
|
||||
}
|
||||
_buffer[_index++] = data;
|
||||
if ((_index - _numCommandBytes) % 3 == 2) {
|
||||
if (_bufferSize <= _index) {
|
||||
return TxFrameError | BufferSizeError;
|
||||
}
|
||||
uint8_t crc = generateCRC(&_buffer[_index - 2], 2, _polynomial_type);
|
||||
_buffer[_index++] = crc;
|
||||
}
|
||||
return NoError;
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef SENSIRION_I2C_TX_FRAME_H_
|
||||
#define SENSIRION_I2C_TX_FRAME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "SensirionCrc.h"
|
||||
#include "SensirionI2CCommunication.h"
|
||||
|
||||
/*
|
||||
* SensirionI2CTxFrame - Class which helps to build a correct I2C frame for
|
||||
* Sensirion Sensors. The different addDatatype() functions add the frame data
|
||||
* and the addCommand() function writes the command at the beginning. Using
|
||||
* these functions one can easily construct a I2C frame for Sensirion sensors.
|
||||
*/
|
||||
class SensirionI2CTxFrame {
|
||||
|
||||
friend class SensirionI2CCommunication;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Factory to create a SensirionI2CTxFrame using a UInt8 command.
|
||||
*
|
||||
* @param command Command to add to the send frame.
|
||||
* @param buffer Buffer in which the send frame will be stored.
|
||||
* @param bufferSize Number of bytes in the buffer for the send frame.
|
||||
* @param poly CRC polynomal to use. Defaults to 0x31 with init 0xFF
|
||||
*
|
||||
* @return the constructed SensirionI2CTxFrame.
|
||||
*/
|
||||
static SensirionI2CTxFrame
|
||||
createWithUInt8Command(uint8_t command, uint8_t buffer[], size_t bufferSize,
|
||||
CrcPolynomial poly = CRC31_ff);
|
||||
|
||||
/**
|
||||
* Factory to create a SensirionI2CTxFrame using a UInt16 command.
|
||||
*
|
||||
* @param command Command to add to the send frame.
|
||||
* @param buffer Buffer in which the send frame will be stored.
|
||||
* @param bufferSize Number of bytes in the buffer for the send frame.
|
||||
* @param poly CRC polynomal to use. Defaults to 0x31 with init 0xFF
|
||||
*
|
||||
* @return the constructed SensirionI2CTxFrame.
|
||||
*/
|
||||
static SensirionI2CTxFrame
|
||||
createWithUInt16Command(uint16_t command, uint8_t buffer[],
|
||||
size_t bufferSize, CrcPolynomial poly = CRC31_ff);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param buffer Buffer in which the send frame will be stored.
|
||||
* @param bufferSize Number of bytes in the buffer for the send frame.
|
||||
* @param poly CRC polynomal to use. Defaults to 0x31 with init 0xFF
|
||||
*
|
||||
* @deprecated Use createWithUInt16Command() instead
|
||||
*/
|
||||
SensirionI2CTxFrame(uint8_t buffer[], size_t bufferSize,
|
||||
CrcPolynomial poly = CRC31_ff);
|
||||
|
||||
/**
|
||||
* addCommand() - Add command to the send frame.
|
||||
*
|
||||
* @param command Command to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*
|
||||
* @deprecated Use createWithUInt16Command() instead
|
||||
*/
|
||||
uint16_t addCommand(uint16_t command);
|
||||
|
||||
/**
|
||||
* addUInt32() - Add unsigned 32bit integer to the send frame.
|
||||
*
|
||||
* @param data Unsigned 32bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addUInt32(uint32_t data);
|
||||
|
||||
/**
|
||||
* addInt32() - Add signed 32bit integer to the send frame.
|
||||
*
|
||||
* @param data Signed 32bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addInt32(int32_t data);
|
||||
|
||||
/**
|
||||
* addUInt16() - Add unsigned 16bit integer to the send frame.
|
||||
*
|
||||
* @param data Unsigned 16bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addUInt16(uint16_t data);
|
||||
|
||||
/**
|
||||
* addInt16() - Add signed 16bit integer to the send frame.
|
||||
*
|
||||
* @param data Signed 16bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addInt16(int16_t data);
|
||||
|
||||
/**
|
||||
* addUInt8() - Add unsigned 8bit integer to the send frame.
|
||||
*
|
||||
* @param data Unsigned 8bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addUInt8(uint8_t data);
|
||||
|
||||
/**
|
||||
* addInt8() - Add signed 8bit integer to the send frame.
|
||||
*
|
||||
* @param data Signed 8bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addInt8(int8_t data);
|
||||
|
||||
/**
|
||||
* addBool() - Add boolean to the send frame.
|
||||
*
|
||||
* @param data Boolean to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addBool(bool data);
|
||||
|
||||
/**
|
||||
* addFloat() - Add float to the send frame.
|
||||
*
|
||||
* @param data Float to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addFloat(float data);
|
||||
|
||||
/**
|
||||
* addBytes() - Add byte array to the send frame.
|
||||
*
|
||||
* @param data Byte array to add to the send frame.
|
||||
* @param dataLength Number of bytes to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addBytes(const uint8_t data[], size_t dataLength);
|
||||
|
||||
private:
|
||||
SensirionI2CTxFrame(uint8_t buffer[], size_t bufferSize,
|
||||
size_t numCommandBytes, CrcPolynomial poly = CRC31_ff);
|
||||
|
||||
uint16_t _addByte(uint8_t data);
|
||||
|
||||
uint8_t* _buffer;
|
||||
size_t _bufferSize;
|
||||
size_t _index;
|
||||
size_t _numCommandBytes;
|
||||
CrcPolynomial _polynomial_type;
|
||||
};
|
||||
|
||||
#endif /* SENSIRION_I2C_TX_FRAME_H_ */
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "SensirionRxFrame.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "SensirionErrors.h"
|
||||
|
||||
SensirionRxFrame::SensirionRxFrame(uint8_t buffer[], size_t bufferSize)
|
||||
: _buffer(buffer), _bufferSize(bufferSize), _index(0), _numBytes(0) {
|
||||
}
|
||||
|
||||
uint16_t SensirionRxFrame::getUInt32(uint32_t& data) {
|
||||
if (_numBytes < 4) {
|
||||
return RxFrameError | NoDataError;
|
||||
}
|
||||
data = static_cast<uint32_t>(_buffer[_index++]) << 24;
|
||||
data |= static_cast<uint32_t>(_buffer[_index++]) << 16;
|
||||
data |= static_cast<uint32_t>(_buffer[_index++]) << 8;
|
||||
data |= static_cast<uint32_t>(_buffer[_index++]);
|
||||
_numBytes -= 4;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionRxFrame::getInt32(int32_t& data) {
|
||||
uint32_t ret;
|
||||
uint16_t error = getUInt32(ret);
|
||||
data = static_cast<int32_t>(ret);
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t SensirionRxFrame::getUInt16(uint16_t& data) {
|
||||
if (_numBytes < 2) {
|
||||
return RxFrameError | NoDataError;
|
||||
}
|
||||
data = static_cast<uint16_t>(_buffer[_index++]) << 8;
|
||||
data |= static_cast<uint16_t>(_buffer[_index++]);
|
||||
_numBytes -= 2;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionRxFrame::getInt16(int16_t& data) {
|
||||
uint16_t ret;
|
||||
uint16_t error = getUInt16(ret);
|
||||
data = static_cast<int16_t>(ret);
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t SensirionRxFrame::getUInt8(uint8_t& data) {
|
||||
if (_numBytes < 1) {
|
||||
return RxFrameError | NoDataError;
|
||||
}
|
||||
data = _buffer[_index++];
|
||||
_numBytes -= 1;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionRxFrame::getInt8(int8_t& data) {
|
||||
if (_numBytes < 1) {
|
||||
return RxFrameError | NoDataError;
|
||||
}
|
||||
data = static_cast<int8_t>(_buffer[_index++]);
|
||||
_numBytes -= 1;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionRxFrame::getBool(bool& data) {
|
||||
if (_numBytes < 1) {
|
||||
return RxFrameError | NoDataError;
|
||||
}
|
||||
data = static_cast<bool>(_buffer[_index++]);
|
||||
_numBytes -= 1;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionRxFrame::getFloat(float& data) {
|
||||
union {
|
||||
uint32_t uInt32Data;
|
||||
float floatData;
|
||||
} convert;
|
||||
uint16_t error = getUInt32(convert.uInt32Data);
|
||||
data = convert.floatData;
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t SensirionRxFrame::getBytes(uint8_t data[], size_t maxBytes) {
|
||||
if (_numBytes < 1) {
|
||||
return RxFrameError | NoDataError;
|
||||
}
|
||||
size_t readAmount = maxBytes;
|
||||
if (_numBytes < maxBytes) {
|
||||
readAmount = _numBytes;
|
||||
}
|
||||
for (size_t i = 0; i < readAmount; i++) {
|
||||
data[i] = _buffer[_index++];
|
||||
}
|
||||
_numBytes -= readAmount;
|
||||
return NoError;
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef SENSIRION_RX_FRAME_H_
|
||||
#define SENSIRION_RX_FRAME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* SenirionRxFrame - Base class for SensirionShdlcRxFrame and
|
||||
* SensirionI2cRxFrame. It decodes received data into common data types. The
|
||||
* data is contained in a buffer which is filled by the one of the two
|
||||
* communication classes. By calling the different decode function the raw data
|
||||
* can be decoded into different data types.
|
||||
*/
|
||||
class SensirionRxFrame {
|
||||
|
||||
friend class SensirionI2CCommunication;
|
||||
friend class SensirionShdlcCommunication;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param buffer Buffer in which the receive frame will be stored.
|
||||
* @param bufferSize Number of bytes in the buffer for the receive frame.
|
||||
*/
|
||||
SensirionRxFrame(uint8_t buffer[], size_t bufferSize);
|
||||
|
||||
/**
|
||||
* getUInt32() - Get unsigned 32bit integer from the received data.
|
||||
*
|
||||
* @param data Memory to store unsigned 32bit integer in.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t getUInt32(uint32_t& data);
|
||||
|
||||
/**
|
||||
* getInt32() - Get signed 32bit integer from the received data.
|
||||
*
|
||||
* @param data Memory to store signed 32bit integer in.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t getInt32(int32_t& data);
|
||||
|
||||
/**
|
||||
* getUInt16() - Get unsigned 16bit integer from the received data.
|
||||
*
|
||||
* @param data Memory to store unsigned 16bit integer in.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t getUInt16(uint16_t& data);
|
||||
|
||||
/**
|
||||
* getInt16() - Get signed 16bit integer from the received data.
|
||||
*
|
||||
* @param data Memory to store signed 16bit integer in.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t getInt16(int16_t& data);
|
||||
|
||||
/**
|
||||
* getUInt8() - Get unsigned 8bit integer from the received data.
|
||||
*
|
||||
* @param data Memory to store unsigned 8bit integer in.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t getUInt8(uint8_t& data);
|
||||
|
||||
/**
|
||||
* getInt8() - Get signed 8bit integer from the received data.
|
||||
*
|
||||
* @param data Memory to store signed 8bit integer in.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t getInt8(int8_t& data);
|
||||
|
||||
/**
|
||||
* getBool() - Get Boolean from the received data.
|
||||
*
|
||||
* @param data Memory to store boolean in.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t getBool(bool& data);
|
||||
|
||||
/**
|
||||
* getFloat() - Get float from the received data.
|
||||
*
|
||||
* @param data Memory to store float in.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t getFloat(float& data);
|
||||
|
||||
/**
|
||||
* getBytes() - Get an array of bytes from the received data.
|
||||
*
|
||||
* @param data Buffer to store the bytes in.
|
||||
* @param maxBytes Maximal amount of bytes to read from the received data.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t getBytes(uint8_t data[], size_t maxBytes);
|
||||
|
||||
private:
|
||||
uint8_t* _buffer = 0;
|
||||
size_t _bufferSize = 0;
|
||||
size_t _index = 0;
|
||||
size_t _numBytes = 0;
|
||||
};
|
||||
|
||||
#endif /* SENSIRION_RX_FRAME_H_ */
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "SensirionShdlcCommunication.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "SensirionErrors.h"
|
||||
#include "SensirionShdlcRxFrame.h"
|
||||
#include "SensirionShdlcTxFrame.h"
|
||||
|
||||
static uint16_t readByte(uint8_t& data, Stream& serial, unsigned long startTime,
|
||||
unsigned long timeoutMicros) {
|
||||
do {
|
||||
if (micros() - startTime > timeoutMicros) {
|
||||
return ReadError | TimeoutError;
|
||||
}
|
||||
} while (!serial.available());
|
||||
data = serial.read();
|
||||
return NoError;
|
||||
}
|
||||
|
||||
static uint16_t unstuffByte(uint8_t& data, Stream& serial,
|
||||
unsigned long startTime,
|
||||
unsigned long timeoutMicros) {
|
||||
uint16_t error = readByte(data, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
if (data == 0x7d) {
|
||||
error = readByte(data, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
data = data ^ (1 << 5);
|
||||
}
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcCommunication::sendFrame(SensirionShdlcTxFrame& frame,
|
||||
Stream& serial) {
|
||||
size_t writtenBytes = serial.write(&frame._buffer[0], frame._index);
|
||||
if (writtenBytes != frame._index) {
|
||||
return WriteError | SerialWriteError;
|
||||
}
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcCommunication::receiveFrame(
|
||||
SensirionShdlcRxFrame& frame, Stream& serial, unsigned long timeoutMicros) {
|
||||
unsigned long startTime = micros();
|
||||
uint16_t error;
|
||||
uint8_t dataLength;
|
||||
uint8_t current = 0;
|
||||
|
||||
if (frame._numBytes) {
|
||||
return ReadError | NonemptyFrameError;
|
||||
}
|
||||
|
||||
// Wait for start byte and ignore all other bytes in case a partial frame
|
||||
// is still in the receive buffer due to a previous error.
|
||||
do {
|
||||
error = readByte(current, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
} while (current != 0x7e);
|
||||
|
||||
// Handle a repeated start byte which may happen
|
||||
do {
|
||||
error = unstuffByte(current, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
} while (current == 0x7e);
|
||||
|
||||
frame._address = current;
|
||||
error = unstuffByte(frame._command, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
error = unstuffByte(frame._state, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
error = unstuffByte(dataLength, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
uint8_t checksum =
|
||||
frame._address + frame._command + frame._state + dataLength;
|
||||
|
||||
if (dataLength > frame._bufferSize) {
|
||||
return RxFrameError | BufferSizeError;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
while (i < dataLength) {
|
||||
error = unstuffByte(current, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
frame._buffer[i] = current;
|
||||
checksum += current;
|
||||
i++;
|
||||
}
|
||||
|
||||
uint8_t expectedChecksum = ~checksum;
|
||||
uint8_t actualChecksum;
|
||||
error = unstuffByte(actualChecksum, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
if (expectedChecksum != actualChecksum) {
|
||||
return ReadError | ChecksumError;
|
||||
}
|
||||
|
||||
uint8_t stop;
|
||||
error = readByte(stop, serial, startTime, timeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
if (stop != 0x7e) {
|
||||
return ReadError | StopByteError;
|
||||
}
|
||||
if (frame._state & 0x7F) {
|
||||
return ExecutionError | frame._state;
|
||||
}
|
||||
frame._dataLength = dataLength;
|
||||
frame._numBytes = dataLength;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcCommunication::sendAndReceiveFrame(
|
||||
Stream& serial, SensirionShdlcTxFrame& txFrame,
|
||||
SensirionShdlcRxFrame& rxFrame, unsigned long rxTimeoutMicros) {
|
||||
uint16_t error;
|
||||
error = SensirionShdlcCommunication::sendFrame(txFrame, serial);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
error = SensirionShdlcCommunication::receiveFrame(rxFrame, serial,
|
||||
rxTimeoutMicros);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
if (rxFrame.getCommand() != txFrame.getCommand()) {
|
||||
return RxFrameError | RxCommandError;
|
||||
}
|
||||
if (rxFrame.getAddress() != txFrame.getAddress()) {
|
||||
return RxFrameError | RxAddressError;
|
||||
}
|
||||
return NoError;
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef SENSIRION_SHDLC_COMMUNICATION_H_
|
||||
#define SENSIRION_SHDLC_COMMUNICATION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "SensirionShdlcRxFrame.h"
|
||||
#include "SensirionShdlcTxFrame.h"
|
||||
|
||||
class SensirionShdlcTxFrame;
|
||||
class SensirionShdlcRxFrame;
|
||||
|
||||
/*
|
||||
* SensirionShdlcCommunication - Class which is responsible for the
|
||||
* communication via a UART (Serial) interface. It provides functionality to
|
||||
* send and receive frames from a Sensirion sensor. The data is sent and
|
||||
* received in a SensirionShdlcTxFrame or SensirionShdlcRxFrame respectively.
|
||||
*/
|
||||
class SensirionShdlcCommunication {
|
||||
|
||||
public:
|
||||
/**
|
||||
* sendFrame() - Sends frame to sensor
|
||||
*
|
||||
* @param frame Tx frame object containing a finished frame to send to the
|
||||
* sensor.
|
||||
* @param serial Stream object to communicate with the sensor.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
static uint16_t sendFrame(SensirionShdlcTxFrame& frame, Stream& serial);
|
||||
|
||||
/**
|
||||
* receiveFrame() - Receive Frame from sensor
|
||||
*
|
||||
* @param frame Rx frame to store the received data in.
|
||||
* @param serial Stream object to communicate with the sensor.
|
||||
* @param timeoutMicros Timeout in micro seconds for the receive operation.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
static uint16_t receiveFrame(SensirionShdlcRxFrame& frame, Stream& serial,
|
||||
unsigned long timeoutMicros);
|
||||
|
||||
/**
|
||||
* sendAndReceiveFrame() - Send and receive a frame from sensor.
|
||||
*
|
||||
* @param serial Stream object to communicate with the sensor.
|
||||
* @param txFrame Tx frame object containing a finished frame to
|
||||
* send to the sensor.
|
||||
* @param rxFrame Rx frame to store the received data in.
|
||||
* @param rxTimeoutMicros Timeout in micro seconds for the receive
|
||||
* operation.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
static uint16_t sendAndReceiveFrame(Stream& serial,
|
||||
SensirionShdlcTxFrame& txFrame,
|
||||
SensirionShdlcRxFrame& rxFrame,
|
||||
unsigned long rxTimeoutMicros);
|
||||
};
|
||||
|
||||
#endif /* SENSIRION_SHDLC_COMMUNICATION_H_ */
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef SENSIRION_SHDLC_RX_FRAME_H_
|
||||
#define SENSIRION_SHDLC_RX_FRAME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "SensirionRxFrame.h"
|
||||
#include "SensirionShdlcCommunication.h"
|
||||
|
||||
/**
|
||||
* SenirionShdlcRxFrame - Class which decodes the through UART received data
|
||||
* into common data types. It contains a buffer which is filled by the
|
||||
* SensirionShdlcCommunication class. By calling the different decode function
|
||||
* inherited from the SensirionRxFrame base class the raw data can be decoded
|
||||
* into different data types. In addition to that it also stores the four
|
||||
* header bytes defined by the SHDLC protocol state, command, address,
|
||||
* datalength. These bytes can be read out by the corresponding getter method.
|
||||
*/
|
||||
class SensirionShdlcRxFrame : public SensirionRxFrame {
|
||||
|
||||
friend class SensirionShdlcCommunication;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param buffer Buffer in which the receive frame will be stored.
|
||||
* @param bufferSize Number of bytes in the buffer for the receive frame.
|
||||
*/
|
||||
SensirionShdlcRxFrame(uint8_t buffer[], size_t bufferSize)
|
||||
: SensirionRxFrame(buffer, bufferSize){};
|
||||
|
||||
uint8_t getAddress(void) const {
|
||||
return _address;
|
||||
};
|
||||
|
||||
uint8_t getCommand(void) const {
|
||||
return _command;
|
||||
};
|
||||
|
||||
uint8_t getState(void) const {
|
||||
return _state;
|
||||
};
|
||||
|
||||
uint8_t getDataLength(void) const {
|
||||
return _dataLength;
|
||||
};
|
||||
|
||||
private:
|
||||
uint8_t _address = 0;
|
||||
uint8_t _command = 0;
|
||||
uint8_t _state = 0;
|
||||
uint8_t _dataLength = 0;
|
||||
};
|
||||
|
||||
#endif /* SENSIRION_SHDLC_RX_FRAME_H_ */
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "SensirionShdlcTxFrame.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "SensirionErrors.h"
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::begin(uint8_t command, uint8_t address,
|
||||
uint8_t dataLength) {
|
||||
_buffer[_index++] = 0x7e;
|
||||
uint16_t error = addUInt8(address);
|
||||
error |= addUInt8(command);
|
||||
error |= addUInt8(dataLength);
|
||||
_command = command;
|
||||
_address = address;
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::finish(void) {
|
||||
uint16_t error = addUInt8(~_checksum);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
if (_index + 1 > _bufferSize) {
|
||||
return TxFrameError | BufferSizeError;
|
||||
}
|
||||
_buffer[_index++] = 0x7e;
|
||||
_isFinished = true;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::addUInt32(uint32_t data) {
|
||||
uint16_t error = addUInt8(static_cast<uint8_t>((data & 0xFF000000) >> 24));
|
||||
error |= addUInt8(static_cast<uint8_t>((data & 0x00FF0000) >> 16));
|
||||
error |= addUInt8(static_cast<uint8_t>((data & 0x0000FF00) >> 8));
|
||||
error |= addUInt8(static_cast<uint8_t>((data & 0x000000FF) >> 0));
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::addInt32(int32_t data) {
|
||||
return addUInt32(static_cast<uint32_t>(data));
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::addUInt16(uint16_t data) {
|
||||
uint16_t error = addUInt8(static_cast<uint8_t>((data & 0xFF00) >> 8));
|
||||
error |= addUInt8(static_cast<uint8_t>((data & 0x00FF) >> 0));
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::addInt16(int16_t data) {
|
||||
return addUInt16(static_cast<uint16_t>(data));
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::addUInt8(uint8_t data) {
|
||||
if (_index + 2 > _bufferSize) {
|
||||
return TxFrameError | BufferSizeError;
|
||||
}
|
||||
switch (data) {
|
||||
case 0x11:
|
||||
case 0x13:
|
||||
case 0x7d:
|
||||
case 0x7e:
|
||||
// byte stuffing is done by inserting 0x7d and inverting bit 5
|
||||
_buffer[_index++] = 0x7d;
|
||||
_buffer[_index++] = data ^ (1 << 5);
|
||||
break;
|
||||
default:
|
||||
_buffer[_index++] = data;
|
||||
}
|
||||
_checksum += data;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::addInt8(int8_t data) {
|
||||
return addUInt8(static_cast<uint8_t>(data));
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::addBool(bool data) {
|
||||
return addUInt8(static_cast<uint8_t>(data));
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::addFloat(float data) {
|
||||
union {
|
||||
uint32_t uInt32Data;
|
||||
float floatData;
|
||||
} convert;
|
||||
|
||||
convert.floatData = data;
|
||||
return addUInt32(convert.uInt32Data);
|
||||
}
|
||||
|
||||
uint16_t SensirionShdlcTxFrame::addBytes(const uint8_t data[],
|
||||
size_t dataLength) {
|
||||
uint16_t error = 0;
|
||||
for (size_t i = 0; i < dataLength; i++) {
|
||||
error |= addUInt8(data[i]);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Sensirion AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Sensirion AG nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef SENSIRION_SHDLC_TX_FRAME_H_
|
||||
#define SENSIRION_SHDLC_TX_FRAME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "SensirionShdlcCommunication.h"
|
||||
|
||||
/*
|
||||
* SensirionShdlcTxFrame - Class which helps to build a correct SHDLC frame.
|
||||
* The begin() functions writes the header. The different addDatatype()
|
||||
* functions add the frame data and the finish() function writes the tail.
|
||||
* Using these functions one can easily construct a SHDLC frame.
|
||||
*/
|
||||
class SensirionShdlcTxFrame {
|
||||
|
||||
friend class SensirionShdlcCommunication;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param buffer Buffer in which the send frame will be stored.
|
||||
* @param bufferSize Number of bytes in the buffer for the send frame.
|
||||
*/
|
||||
SensirionShdlcTxFrame(uint8_t buffer[], size_t bufferSize)
|
||||
: _buffer(buffer), _bufferSize(bufferSize) {
|
||||
}
|
||||
|
||||
/**
|
||||
* begin() - Begin frame and write header.
|
||||
*
|
||||
* @note This function needs to be called before calling other
|
||||
* data add functions to write the header at the beginning.
|
||||
*
|
||||
* @param command Command byte to add to the send frame.
|
||||
* @param address Address byte to add to the send frame.
|
||||
* @param dataLength Data length byte to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t begin(uint8_t command, uint8_t address, uint8_t dataLength);
|
||||
|
||||
/**
|
||||
* finish() - Finish frame and write tail.
|
||||
*
|
||||
* @note This function needs to be called last, after adding all
|
||||
* data to frame and before sending the frame to the sensor.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t finish(void);
|
||||
|
||||
/**
|
||||
* addUInt32() - Add unsigned 32bit integer to the send frame.
|
||||
*
|
||||
* @param data Unsigned 32bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addUInt32(uint32_t data);
|
||||
|
||||
/**
|
||||
* addInt32() - Add signed 32bit integer to the send frame.
|
||||
*
|
||||
* @param data Signed 32bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addInt32(int32_t data);
|
||||
|
||||
/**
|
||||
* addUInt16() - Add unsigned 16bit integer to the send frame.
|
||||
*
|
||||
* @param data Unsigned 16bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addUInt16(uint16_t data);
|
||||
|
||||
/**
|
||||
* addInt16() - Add signed 16bit integer to the send frame.
|
||||
*
|
||||
* @param data Signed 16bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addInt16(int16_t data);
|
||||
|
||||
/**
|
||||
* addUInt8() - Add unsigned 8bit integer to the send frame.
|
||||
*
|
||||
* @param data Unsigned 8bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addUInt8(uint8_t data);
|
||||
|
||||
/**
|
||||
* addInt8() - Add signed 8bit integer to the send frame.
|
||||
*
|
||||
* @param data Signed 8bit integer to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addInt8(int8_t data);
|
||||
|
||||
/**
|
||||
* addBool() - Add boolean to the send frame.
|
||||
*
|
||||
* @param data Boolean to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addBool(bool data);
|
||||
|
||||
/**
|
||||
* addFloat() - Add float to the send frame.
|
||||
*
|
||||
* @param data Float to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addFloat(float data);
|
||||
|
||||
/**
|
||||
* addBytes() - Add byte array to the send frame.
|
||||
*
|
||||
* @param data Byte array to add to the send frame.
|
||||
* @param dataLength Number of bytes to add to the send frame.
|
||||
*
|
||||
* @return NoError on success, an error code otherwise
|
||||
*/
|
||||
uint16_t addBytes(const uint8_t data[], size_t dataLength);
|
||||
|
||||
uint8_t getCommand(void) const {
|
||||
return _command;
|
||||
};
|
||||
|
||||
uint8_t getAddress(void) const {
|
||||
return _address;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* _buffer;
|
||||
size_t _bufferSize;
|
||||
size_t _index = 0;
|
||||
uint8_t _checksum = 0;
|
||||
bool _isFinished = false;
|
||||
uint8_t _command = 0;
|
||||
uint8_t _address = 0;
|
||||
};
|
||||
|
||||
#endif /* SENSIRION_SHDLC_TX_FRAME_H_ */
|
||||
@@ -1,45 +0,0 @@
|
||||
#! /usr/bin/env python3
|
||||
import subprocess
|
||||
import json
|
||||
import argparse
|
||||
import sys
|
||||
import logging
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.description = u'Compile test a sketch for all available boards'
|
||||
parser.add_argument(u'-s', u'--sketch', dest=u'sketch',
|
||||
required=True, help=u'Path to sketch')
|
||||
args = parser.parse_args()
|
||||
test_all_boards(args.sketch)
|
||||
|
||||
|
||||
def test_all_boards(sketch):
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s [%(levelname)s] %(message)s')
|
||||
log = logging.getLogger('arduino-compile-test')
|
||||
process = subprocess.run("arduino-cli board listall --format json".split(),
|
||||
stdout=subprocess.PIPE)
|
||||
board_list_json = process.stdout.decode('utf-8')
|
||||
board_list = json.loads(board_list_json)
|
||||
test_list = ["arduino:samd:mkrzero", "arduino:avr:mega",
|
||||
"arduino:avr:nano", "arduino:avr:uno",
|
||||
"esp32:esp32:esp32", "esp8266:esp8266:generic"]
|
||||
for board in test_list:
|
||||
if board in (b['fqbn'] for b in board_list['boards']):
|
||||
log.info('Test compilation for board {}'.format(board))
|
||||
command = 'arduino-cli compile --libraries="." --warnings all'\
|
||||
' --fqbn {board} {sketch}'.format(board=board,
|
||||
sketch=sketch)
|
||||
process = subprocess.run(command.split(), stdout=subprocess.PIPE)
|
||||
if process.returncode:
|
||||
log.error(process.stdout.decode('utf-8'))
|
||||
sys.exit(process.returncode)
|
||||
else:
|
||||
log.error('Board not installed: {}'.format(board))
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euxo pipefail
|
||||
cppcheck --std=c++11 --language=c++ --error-exitcode=1 --enable=warning,style,performance,portability src/*
|
||||
@@ -1,5 +0,0 @@
|
||||
#! /bin/bash
|
||||
set -euxo pipefail
|
||||
editorconfig-checker
|
||||
flake8
|
||||
find . -type f -iregex ".*\.\(c\|h\|cpp\|ino\)" -exec clang-format-6.0 -i -style=file {} \; && git diff --exit-code
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
#include "../../SensirionCore/src/SensirionCore.h"
|
||||
#include <SensirionCore.h>
|
||||
|
||||
class SensirionI2CSgp41 {
|
||||
|
||||
|
||||
Submodule src/Libraries/airgradient-client updated: ed5ea9d40d...95575eb762
@@ -0,0 +1,142 @@
|
||||
#include "GoIaqs.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Anchor {
|
||||
float x;
|
||||
int y;
|
||||
};
|
||||
|
||||
/** White-paper PM2.5 anchors (ug/m^3, score). */
|
||||
const Anchor PM25_ANCHORS[] = {
|
||||
{0.0f, 10}, {10.0f, 8}, {11.0f, 7}, {25.0f, 4}, {26.0f, 3}, {100.0f, 0},
|
||||
};
|
||||
|
||||
/** White-paper CO2 anchors (ppm, score). */
|
||||
const Anchor CO2_ANCHORS[] = {
|
||||
{400.0f, 10}, {800.0f, 8}, {801.0f, 7},
|
||||
{1400.0f, 4}, {1401.0f, 3}, {5000.0f, 0},
|
||||
};
|
||||
|
||||
const float PM25_MIN = 0.0f;
|
||||
const float PM25_MAX = 100.0f;
|
||||
const float CO2_MIN = 400.0f;
|
||||
const float CO2_MAX = 5000.0f;
|
||||
|
||||
inline float clampF(float v, float lo, float hi) {
|
||||
if (v < lo) {
|
||||
return lo;
|
||||
}
|
||||
if (v > hi) {
|
||||
return hi;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
inline int clampI(int v, int lo, int hi) {
|
||||
if (v < lo) {
|
||||
return lo;
|
||||
}
|
||||
if (v > hi) {
|
||||
return hi;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/** Round-half-up (matching the reference TS implementation). */
|
||||
inline int roundHalfUp(float v) { return (int)floorf(v + 0.5f); }
|
||||
|
||||
int interpolate(float value, const Anchor *anchors, size_t count) {
|
||||
if (value <= anchors[0].x) {
|
||||
return anchors[0].y;
|
||||
}
|
||||
const Anchor &last = anchors[count - 1];
|
||||
if (value >= last.x) {
|
||||
return last.y;
|
||||
}
|
||||
for (size_t i = 0; i < count - 1; i++) {
|
||||
const Anchor &left = anchors[i];
|
||||
const Anchor &right = anchors[i + 1];
|
||||
if (value <= right.x) {
|
||||
float interp =
|
||||
(float)left.y +
|
||||
((value - left.x) * (float)(right.y - left.y)) / (right.x - left.x);
|
||||
return clampI(roundHalfUp(interp), GoIaqs::SCORE_MIN, GoIaqs::SCORE_MAX);
|
||||
}
|
||||
}
|
||||
return GoIaqs::SCORE_MIN;
|
||||
}
|
||||
|
||||
int pollutantScore(float value, float min, float max, const Anchor *anchors,
|
||||
size_t count) {
|
||||
if (!isfinite(value) || value > max) {
|
||||
return GoIaqs::SCORE_MIN;
|
||||
}
|
||||
return interpolate(clampF(value, min, max), anchors, count);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int GoIaqs::pm25Score(float pm25UgM3) {
|
||||
return pollutantScore(pm25UgM3, PM25_MIN, PM25_MAX, PM25_ANCHORS,
|
||||
sizeof(PM25_ANCHORS) / sizeof(PM25_ANCHORS[0]));
|
||||
}
|
||||
|
||||
int GoIaqs::co2Score(float co2Ppm) {
|
||||
return pollutantScore(co2Ppm, CO2_MIN, CO2_MAX, CO2_ANCHORS,
|
||||
sizeof(CO2_ANCHORS) / sizeof(CO2_ANCHORS[0]));
|
||||
}
|
||||
|
||||
int GoIaqs::totalScore(int pm25, int co2) {
|
||||
if (pm25 == co2) {
|
||||
if (pm25 <= 7) {
|
||||
int reduced = pm25 - 1;
|
||||
return reduced < SCORE_MIN ? SCORE_MIN : reduced;
|
||||
}
|
||||
return pm25;
|
||||
}
|
||||
return pm25 < co2 ? pm25 : co2;
|
||||
}
|
||||
|
||||
GoIaqs::Category GoIaqs::categoryOf(int totalScore) {
|
||||
if (totalScore >= 8) {
|
||||
return CategoryGood;
|
||||
}
|
||||
if (totalScore >= 4) {
|
||||
return CategoryModerate;
|
||||
}
|
||||
return CategoryUnhealthy;
|
||||
}
|
||||
|
||||
GoIaqs::Rgb GoIaqs::colorOf(Category category) {
|
||||
switch (category) {
|
||||
case CategoryGood:
|
||||
return Rgb{0x64, 0x8E, 0xFF};
|
||||
case CategoryModerate:
|
||||
return Rgb{255, 128, 0};
|
||||
case CategoryUnhealthy:
|
||||
default:
|
||||
return Rgb{255, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
GoIaqs::Dominant GoIaqs::dominantOf(int pm25Score, int co2Score) {
|
||||
if (pm25Score == co2Score) {
|
||||
return DominantBoth;
|
||||
}
|
||||
return (pm25Score < co2Score) ? DominantPm25 : DominantCo2;
|
||||
}
|
||||
|
||||
char GoIaqs::letterOf(Category category) {
|
||||
switch (category) {
|
||||
case CategoryGood:
|
||||
return 'A';
|
||||
case CategoryModerate:
|
||||
return 'B';
|
||||
case CategoryUnhealthy:
|
||||
default:
|
||||
return 'Z';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#ifndef _GO_IAQS_H_
|
||||
#define _GO_IAQS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief GO IAQS Starter Score implementation.
|
||||
*
|
||||
* Reference:
|
||||
* https://www.airgradient.com/blog/go-iaqs-starter-score-technical-implementation
|
||||
* Attribution: Achim Haug (AirGradient) for GO AQS. License: CC BY-SA 4.0.
|
||||
*
|
||||
* Converts PM2.5 (ug/m^3) and CO2 (ppm) concentrations into an integer
|
||||
* "0..10" total score plus category and category color, using piecewise
|
||||
* linear anchors and the synergetic combination rule defined in the
|
||||
* white paper.
|
||||
*
|
||||
* Pure computation; no Arduino / hardware dependencies.
|
||||
*/
|
||||
class GoIaqs {
|
||||
public:
|
||||
enum Category {
|
||||
CategoryGood = 0, /** total score 8..10, color #648EFF */
|
||||
CategoryModerate = 1, /** total score 4..7, color #FFB000 */
|
||||
CategoryUnhealthy = 2, /** total score 0..3, color #FF190C */
|
||||
};
|
||||
|
||||
enum Dominant {
|
||||
DominantPm25 = 0, /** PM2.5 has the worse per-pollutant score */
|
||||
DominantCo2 = 1, /** CO2 has the worse per-pollutant score */
|
||||
DominantBoth = 2, /** PM2.5 and CO2 per-pollutant scores are equal */
|
||||
};
|
||||
|
||||
struct Rgb {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
/** Score bounds (inclusive). */
|
||||
static const int SCORE_MIN = 0;
|
||||
static const int SCORE_MAX = 10;
|
||||
|
||||
/**
|
||||
* @brief Compute per-pollutant score for PM2.5.
|
||||
*
|
||||
* @param pm25UgM3 PM2.5 concentration in ug/m^3.
|
||||
* @return int in [SCORE_MIN .. SCORE_MAX]
|
||||
*/
|
||||
static int pm25Score(float pm25UgM3);
|
||||
|
||||
/**
|
||||
* @brief Compute per-pollutant score for CO2.
|
||||
*
|
||||
* @param co2Ppm CO2 concentration in ppm.
|
||||
* @return int in [SCORE_MIN .. SCORE_MAX]
|
||||
*/
|
||||
static int co2Score(float co2Ppm);
|
||||
|
||||
/**
|
||||
* @brief Combine two per-pollutant scores into a single total score
|
||||
* applying the synergetic rule:
|
||||
* - If scores differ: total = min(pm25, co2).
|
||||
* - If equal and shared <= 7: total = max(shared - 1, 0).
|
||||
* - If equal and shared >= 8: total = shared.
|
||||
*/
|
||||
static int totalScore(int pm25, int co2);
|
||||
|
||||
/**
|
||||
* @brief Map a total score to a category (Good / Moderate / Unhealthy).
|
||||
*/
|
||||
static Category categoryOf(int totalScore);
|
||||
|
||||
/**
|
||||
* @brief Get the RGB color associated with a category, tuned for the
|
||||
* LED bar (may differ from the white-paper hex values to match the
|
||||
* physical LED output).
|
||||
*/
|
||||
static Rgb colorOf(Category category);
|
||||
|
||||
/**
|
||||
* @brief Identify the pollutant that drives the total score. The lower
|
||||
* per-pollutant score wins; equal scores return DominantBoth.
|
||||
*/
|
||||
static Dominant dominantOf(int pm25Score, int co2Score);
|
||||
|
||||
/**
|
||||
* @brief Get the single-character letter grade for a category, per the
|
||||
* white paper: Good -> 'A', Moderate -> 'B', Unhealthy -> 'Z'.
|
||||
*/
|
||||
static char letterOf(Category category);
|
||||
};
|
||||
|
||||
#endif /** _GO_IAQS_H_ */
|
||||
@@ -0,0 +1,156 @@
|
||||
#include "SPS30.h"
|
||||
|
||||
#if !defined(ESP8266)
|
||||
|
||||
// The Sensirion library defines NO_ERROR which can conflict with ESP-IDF.
|
||||
// Re-define locally if needed.
|
||||
#ifdef NO_ERROR
|
||||
#undef NO_ERROR
|
||||
#endif
|
||||
#define NO_ERROR 0
|
||||
|
||||
SPS30::SPS30(BoardType def) : _boardDef(def) {}
|
||||
|
||||
bool SPS30::begin(HardwareSerial &serial) {
|
||||
if (_isBegin) {
|
||||
AgLog("Already initialized, call end() then try again");
|
||||
return true;
|
||||
}
|
||||
|
||||
_bsp = getBoardDef(_boardDef);
|
||||
if (_bsp == nullptr) {
|
||||
AgLog("Board [%d] not supported", _boardDef);
|
||||
return false;
|
||||
}
|
||||
|
||||
_serial = &serial;
|
||||
|
||||
// Fully reset the serial port — it may have been left at 9600 baud
|
||||
// with stale buffer data from a failed PMS5003 detection attempt.
|
||||
_serial->end();
|
||||
delay(100);
|
||||
|
||||
// Serial0 (UART0) defaults map to the correct PM connector pins.
|
||||
// Serial1 shares the S8 connector pins and needs explicit GPIO mapping,
|
||||
// same as PMS5003T::begin() does.
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
if (_serial == &Serial0) {
|
||||
#else
|
||||
if (_serial == &Serial) {
|
||||
#endif
|
||||
_serial->begin(115200);
|
||||
} else {
|
||||
_serial->begin(115200, SERIAL_8N1, _bsp->SenseAirS8.uart_rx_pin,
|
||||
_bsp->SenseAirS8.uart_tx_pin);
|
||||
}
|
||||
|
||||
// Flush any garbage bytes left in the RX buffer
|
||||
while (_serial->available()) {
|
||||
_serial->read();
|
||||
}
|
||||
|
||||
_driver.begin(serial);
|
||||
|
||||
// Reset sensor to a known state — handles the case where the sensor
|
||||
// was previously running or in an unknown mode.
|
||||
_driver.deviceReset();
|
||||
delay(100);
|
||||
|
||||
// Ensure sensor is in idle state before detection
|
||||
_driver.stopMeasurement();
|
||||
|
||||
// Try reading serial number as a detection mechanism
|
||||
int8_t serialNumber[32] = {0};
|
||||
int16_t error = _driver.readSerialNumber(serialNumber, sizeof(serialNumber));
|
||||
if (error != NO_ERROR) {
|
||||
AgLog("SPS30 not detected (readSerialNumber error: %d)", error);
|
||||
_serial = nullptr;
|
||||
return false;
|
||||
}
|
||||
AgLog("SPS30 detected, serial: %s", reinterpret_cast<char *>(serialNumber));
|
||||
|
||||
// Start continuous measurement in float output format
|
||||
error = _driver.startMeasurement(SPS30_OUTPUT_FORMAT_OUTPUT_FORMAT_FLOAT);
|
||||
if (error != NO_ERROR) {
|
||||
AgLog("SPS30 startMeasurement failed (error: %d)", error);
|
||||
_serial = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
_isBegin = true;
|
||||
_connected = true;
|
||||
_consecutiveErrors = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SPS30::end() {
|
||||
if (!_isBegin) {
|
||||
return;
|
||||
}
|
||||
|
||||
_driver.stopMeasurement();
|
||||
_isBegin = false;
|
||||
_connected = false;
|
||||
_serial = nullptr;
|
||||
AgLog("De-initialized");
|
||||
}
|
||||
|
||||
bool SPS30::readValues() {
|
||||
if (!_isBegin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float mc1p0, mc2p5, mc4p0, mc10p0;
|
||||
float nc0p5, nc1p0, nc2p5, nc4p0, nc10p0;
|
||||
float typicalParticleSize;
|
||||
|
||||
int16_t error = _driver.readMeasurementValuesFloat(
|
||||
mc1p0, mc2p5, mc4p0, mc10p0, nc0p5, nc1p0, nc2p5, nc4p0, nc10p0,
|
||||
typicalParticleSize);
|
||||
|
||||
if (error != NO_ERROR) {
|
||||
_consecutiveErrors++;
|
||||
if (_consecutiveErrors >= kMaxConsecutiveErrors) {
|
||||
_connected = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cache the successfully read values
|
||||
_mc1p0 = mc1p0;
|
||||
_mc2p5 = mc2p5;
|
||||
_mc4p0 = mc4p0;
|
||||
_mc10p0 = mc10p0;
|
||||
_nc0p5 = nc0p5;
|
||||
_nc1p0 = nc1p0;
|
||||
_nc2p5 = nc2p5;
|
||||
_nc4p0 = nc4p0;
|
||||
_nc10p0 = nc10p0;
|
||||
_typicalParticleSize = typicalParticleSize;
|
||||
|
||||
_connected = true;
|
||||
_consecutiveErrors = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SPS30::connected() { return _connected; }
|
||||
|
||||
// -- Mass concentration (µg/m³) → int, matching PMS5003 interface -----------
|
||||
|
||||
int SPS30::getPm01Ae() { return static_cast<int>(_mc1p0); }
|
||||
int SPS30::getPm25Ae() { return static_cast<int>(_mc2p5); }
|
||||
int SPS30::getPm10Ae() { return static_cast<int>(_mc10p0); }
|
||||
|
||||
// SPS30 has no Ae/SP distinction — return the same values
|
||||
int SPS30::getPm01Sp() { return static_cast<int>(_mc1p0); }
|
||||
int SPS30::getPm25Sp() { return static_cast<int>(_mc2p5); }
|
||||
int SPS30::getPm10Sp() { return static_cast<int>(_mc10p0); }
|
||||
|
||||
// -- Number concentration: convert #/cm³ → #/0.1L (×100) -------------------
|
||||
|
||||
int SPS30::getPm05ParticleCount() { return static_cast<int>(_nc0p5 * 100.0f); }
|
||||
int SPS30::getPm01ParticleCount() { return static_cast<int>(_nc1p0 * 100.0f); }
|
||||
int SPS30::getPm25ParticleCount() { return static_cast<int>(_nc2p5 * 100.0f); }
|
||||
int SPS30::getPm10ParticleCount() { return static_cast<int>(_nc10p0 * 100.0f); }
|
||||
|
||||
#endif // !ESP8266
|
||||
@@ -0,0 +1,132 @@
|
||||
#ifndef _AIR_GRADIENT_SPS30_H_
|
||||
#define _AIR_GRADIENT_SPS30_H_
|
||||
|
||||
#include "../Main/BoardDef.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
|
||||
/**
|
||||
* @brief Stub class for ESP8266 — SPS30 UART is not supported on this platform.
|
||||
* Provides the type so AirGradient.h compiles, but begin() always returns false.
|
||||
*/
|
||||
class SPS30 {
|
||||
public:
|
||||
explicit SPS30(BoardType def) {}
|
||||
bool begin(Stream &serial) { return false; }
|
||||
void end() {}
|
||||
bool readValues() { return false; }
|
||||
bool connected() { return false; }
|
||||
int getPm01Ae() { return 0; }
|
||||
int getPm25Ae() { return 0; }
|
||||
int getPm10Ae() { return 0; }
|
||||
int getPm01Sp() { return 0; }
|
||||
int getPm25Sp() { return 0; }
|
||||
int getPm10Sp() { return 0; }
|
||||
int getPm05ParticleCount() { return 0; }
|
||||
int getPm01ParticleCount() { return 0; }
|
||||
int getPm25ParticleCount() { return 0; }
|
||||
int getPm10ParticleCount() { return 0; }
|
||||
};
|
||||
|
||||
#else // ESP32
|
||||
|
||||
#include <SensirionUartSps30.h>
|
||||
|
||||
/**
|
||||
* @brief Wrapper class for Sensirion SPS30 particulate matter sensor over UART.
|
||||
*
|
||||
* Follows the same interface pattern as PMS5003 so it can be used as a
|
||||
* drop-in alternative PM sensor on the ONE_INDOOR board.
|
||||
*
|
||||
* Key differences from PMS5003:
|
||||
* - SHDLC protocol at 115200 baud (vs PMS5003 streaming at 9600)
|
||||
* - No continuous handle() needed; values are polled via readValues()
|
||||
* - No PM0.3 or PM5.0 particle count bins
|
||||
* - No distinction between atmospheric-environment and standard-particle
|
||||
* mass concentrations (same values mapped to both)
|
||||
*/
|
||||
class SPS30 {
|
||||
public:
|
||||
explicit SPS30(BoardType def);
|
||||
|
||||
/**
|
||||
* @brief Initialize sensor on the given serial port.
|
||||
*
|
||||
* Configures the serial port to 115200 baud, performs sensor detection
|
||||
* (reads serial number), and starts continuous measurement in float mode.
|
||||
*
|
||||
* @param serial HardwareSerial instance (e.g. Serial0)
|
||||
* @return true Sensor detected and measurement started
|
||||
* @return false Sensor not found or initialization failed
|
||||
*/
|
||||
bool begin(HardwareSerial &serial);
|
||||
|
||||
/**
|
||||
* @brief Stop measurement and release the serial port.
|
||||
*/
|
||||
void end();
|
||||
|
||||
/**
|
||||
* @brief Poll the sensor for the latest measurement.
|
||||
*
|
||||
* Should be called periodically (e.g. every 2 s via pmsSchedule).
|
||||
* Internally calls readMeasurementValuesFloat() over SHDLC.
|
||||
*
|
||||
* @return true New data read successfully
|
||||
* @return false Read failed (sensor may be disconnected)
|
||||
*/
|
||||
bool readValues();
|
||||
|
||||
/**
|
||||
* @brief Check whether the sensor is connected / responding.
|
||||
*
|
||||
* Based on the result of the most recent readValues() call.
|
||||
*/
|
||||
bool connected();
|
||||
|
||||
// -- Mass concentration (µg/m³), truncated to int like PMS5003 ----------
|
||||
|
||||
int getPm01Ae();
|
||||
int getPm25Ae();
|
||||
int getPm10Ae();
|
||||
|
||||
// SPS30 has no Ae/SP distinction — return the same values
|
||||
int getPm01Sp();
|
||||
int getPm25Sp();
|
||||
int getPm10Sp();
|
||||
|
||||
// -- Number concentration (converted from #/cm³ to #/0.1 L) -------------
|
||||
|
||||
int getPm05ParticleCount();
|
||||
int getPm01ParticleCount();
|
||||
int getPm25ParticleCount();
|
||||
int getPm10ParticleCount();
|
||||
|
||||
private:
|
||||
bool _isBegin = false;
|
||||
bool _connected = false;
|
||||
int _consecutiveErrors = 0;
|
||||
BoardType _boardDef;
|
||||
const BoardDef *_bsp = nullptr;
|
||||
SensirionUartSps30 _driver;
|
||||
HardwareSerial *_serial = nullptr;
|
||||
|
||||
/// Maximum consecutive read errors before reporting disconnected
|
||||
static constexpr int kMaxConsecutiveErrors = 3;
|
||||
|
||||
// Cached raw values from last successful read
|
||||
float _mc1p0 = 0.0f;
|
||||
float _mc2p5 = 0.0f;
|
||||
float _mc4p0 = 0.0f;
|
||||
float _mc10p0 = 0.0f;
|
||||
float _nc0p5 = 0.0f;
|
||||
float _nc1p0 = 0.0f;
|
||||
float _nc2p5 = 0.0f;
|
||||
float _nc4p0 = 0.0f;
|
||||
float _nc10p0 = 0.0f;
|
||||
float _typicalParticleSize = 0.0f;
|
||||
};
|
||||
|
||||
#endif // ESP8266 / ESP32
|
||||
|
||||
#endif // _AIR_GRADIENT_SPS30_H_
|
||||
Reference in New Issue
Block a user