From 88528d8ef3b1613b065ac225ec757d4adef5a08f Mon Sep 17 00:00:00 2001 From: Norbert Richter Date: Mon, 19 Nov 2018 12:56:54 +0100 Subject: [PATCH] Fix NovaSDS sensor rare checksum failure - NodeSDS send/rec rewritten (common func) --- sonoff/xsns_20_novasds.ino | 134 +++++++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 44 deletions(-) diff --git a/sonoff/xsns_20_novasds.ino b/sonoff/xsns_20_novasds.ino index 63f5b76f3..909711472 100644 --- a/sonoff/xsns_20_novasds.ino +++ b/sonoff/xsns_20_novasds.ino @@ -21,6 +21,8 @@ /*********************************************************************************************\ * Nova Fitness SDS011 (and possibly SDS021) particle concentration sensor * For background information see http://aqicn.org/sensor/sds011/ + * For protocol specification see + * https://cdn.sparkfun.com/assets/parts/1/2/2/7/5/Laser_Dust_Sensor_Control_Protocol_V1.3.pdf * * Hardware Serial will be selected if GPIO3 = [SDS0X01] \*********************************************************************************************/ @@ -30,70 +32,114 @@ #include #ifndef WORKING_PERIOD -#define WORKING_PERIOD 5 +#define WORKING_PERIOD 5 // NodaSDS sleep working period in minutes #endif -#ifndef XSNS_20_QUERY_INTERVAL -#define XSNS_20_QUERY_INTERVAL 3 // query every 3 seconds +#ifndef NOVA_SDS_REINIT_CHECK +#define NOVA_SDS_REINIT_CHECK 80 // NodaSDS reinitalized check in seconds #endif +#ifndef NOVA_SDS_QUERY_INTERVAL +#define NOVA_SDS_QUERY_INTERVAL 3 // NodaSDS query interval in seconds +#endif +#ifndef NOVA_SDS_RECDATA_TIMEOUT +#define NOVA_SDS_RECDATA_TIMEOUT 150 // NodaSDS query data timeout in ms +#endif +#ifndef NOVA_SDS_DEVICE_ID +#define NOVA_SDS_DEVICE_ID 0xFFFF // NodaSDS all sensor response +#endif + TasmotaSerial *NovaSdsSerial; uint8_t novasds_type = 1; uint8_t novasds_valid = 0; -uint8_t novasds_workperiod[19] = {0xAA, 0xB4, 0x08, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x0C, 0xAB}; //5 minutes -uint8_t novasds_setquerymode[19] = {0xAA, 0xB4, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0xAB}; //query mode -uint8_t novasds_querydata[19] = {0xAA, 0xB4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0xAB}; //query DATA - struct sds011data { uint16_t pm100; uint16_t pm25; } novasds_data; +// NovaSDS commands +#define NOVA_SDS_REPORTING_MODE 2 // Cmnd "data reporting mode" +#define NOVA_SDS_QUERY_DATA 4 // Cmnd "Query data" +#define NOVA_SDS_SET_DEVICE_ID 5 // Cmnd "Set Device ID" +#define NOVA_SDS_SLEEP_AND_WORK 6 // Cmnd "sleep and work mode" +#define NOVA_SDS_WORKING_PERIOD 8 // Cmnd "working period" +#define NOVA_SDS_CHECK_FIRMWARE_VER 7 // Cmnd "Check firmware version" + #define NOVA_SDS_QUERY_MODE 0 // Subcmnd "query mode" + #define NOVA_SDS_SET_MODE 1 // Subcmnd "set mode" + #define NOVA_SDS_REPORT_ACTIVE 0 // Subcmnd "report active mode" - Sensor received query data command to report a measurement data + #define NOVA_SDS_REPORT_QUERY 1 // Subcmnd "report query mode" - Sensor automatically reports a measurement data in a work period + #define NOVA_SDS_WORK 0 // Subcmnd "work mode" + #define NOVA_SDS_SLEEP 1 // Subcmnd "sleep mode" + + +bool NovaSdsCommand(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint16_t sensorid, byte *buffer) +{ + uint8_t novasds_cmnd[19] = {0xAA, 0xB4, byte1, byte2, byte3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (uint8_t)(sensorid & 0xFF), (uint8_t)((sensorid>>8) & 0xFF), 0x00, 0xAB}; + + // calc crc + for (byte i = 2; i < 17; i++) { + novasds_cmnd[17] += novasds_cmnd[i]; + } + //~ snprintf_P(log_data, sizeof(log_data), PSTR("SDS: Send %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X"), + //~ novasds_cmnd[0],novasds_cmnd[1],novasds_cmnd[2],novasds_cmnd[3],novasds_cmnd[4],novasds_cmnd[5],novasds_cmnd[6],novasds_cmnd[7],novasds_cmnd[8],novasds_cmnd[9], + //~ novasds_cmnd[10],novasds_cmnd[11],novasds_cmnd[12],novasds_cmnd[13],novasds_cmnd[14],novasds_cmnd[15],novasds_cmnd[16],novasds_cmnd[17],novasds_cmnd[18]); + //~ AddLog(LOG_LEVEL_DEBUG); + // send cmnd + NovaSdsSerial->write(novasds_cmnd, sizeof(novasds_cmnd)); + NovaSdsSerial->flush(); + + // wait for any response + unsigned long cmndtime = millis(); + while ( (TimePassedSince(cmndtime) < NOVA_SDS_RECDATA_TIMEOUT) && ( ! NovaSdsSerial->available() ) ); + if ( ! NovaSdsSerial->available() ) { + // timeout + return false; + } + byte recbuf[10]; + memset(recbuf, 0, sizeof(recbuf)); + // sync to 0xAA header + while ( (TimePassedSince(cmndtime) < NOVA_SDS_RECDATA_TIMEOUT) && ( NovaSdsSerial->available() > 0) && (0xAA != (recbuf[0] = NovaSdsSerial->read())) ); + if ( 0xAA != recbuf[0] ) { + // no head found + return false; + } + + // read rest (9 of 10 bytes) of message + NovaSdsSerial->readBytes(&recbuf[1], 9); + AddLogSerial(LOG_LEVEL_DEBUG_MORE, recbuf, sizeof(recbuf)); + + if ( NULL != buffer ) { + // return data to buffer + memcpy(buffer, recbuf, sizeof(recbuf)); + } + + // checksum & tail check + if ((0xAB != recbuf[9] ) || (recbuf[8] != ((recbuf[2] + recbuf[3] + recbuf[4] + recbuf[5] + recbuf[6] + recbuf[7]) & 0xFF))) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("SDS: " D_CHECKSUM_FAILURE)); + return false; + } + + return true; +} + void NovaSdsSetWorkPeriod(void) { - - while (NovaSdsSerial->available() > 0) { - NovaSdsSerial->read(); - } - - novasds_workperiod[4] = WORKING_PERIOD; - novasds_workperiod[17] = ((novasds_workperiod[2] + novasds_workperiod[3] + novasds_workperiod[4] + novasds_workperiod[15] + novasds_workperiod[16]) & 0xFF); //checksum - - NovaSdsSerial->flush(); - NovaSdsSerial->write(novasds_workperiod, sizeof(novasds_workperiod)); - - while (NovaSdsSerial->available() > 0) { - NovaSdsSerial->read(); - } - - NovaSdsSerial->flush(); - NovaSdsSerial->write(novasds_setquerymode, sizeof(novasds_setquerymode)); - - while (NovaSdsSerial->available() > 0) { - NovaSdsSerial->read(); - } + // set sensor working period + NovaSdsCommand(NOVA_SDS_WORKING_PERIOD, NOVA_SDS_SET_MODE, WORKING_PERIOD, NOVA_SDS_DEVICE_ID, NULL); + // set sensor report only on query + NovaSdsCommand(NOVA_SDS_REPORTING_MODE, NOVA_SDS_SET_MODE, NOVA_SDS_REPORT_QUERY, NOVA_SDS_DEVICE_ID, NULL); } bool NovaSdsReadData(void) { - if (! NovaSdsSerial->available()) return false; - - byte d[10] = { 0 }; - NovaSdsSerial->flush(); - NovaSdsSerial->write(novasds_querydata, sizeof(novasds_querydata)); - NovaSdsSerial->readBytes(d, 10); - - AddLogSerial(LOG_LEVEL_DEBUG_MORE, d, 10); - - if (d[0] == 0xAA && d[9] == 0xAB && (d[8] == ((d[2] + d[3] + d[4] + d[5] + d[6] + d[7]) & 0xFF))) { - novasds_data.pm25 = (d[2] + 256 * d[3]); - novasds_data.pm100 = (d[4] + 256 * d[5]); - } else { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("SDS: " D_CHECKSUM_FAILURE)); + byte d[10]; + if ( ! NovaSdsCommand(NOVA_SDS_QUERY_DATA, 0, 0, NOVA_SDS_DEVICE_ID, d) ) { return false; } + novasds_data.pm25 = (d[2] + 256 * d[3]); + novasds_data.pm100 = (d[4] + 256 * d[5]); return true; } @@ -102,11 +148,11 @@ bool NovaSdsReadData(void) void NovaSdsSecond(void) // Every second { - if (XSNS_20 == (uptime % 100)) { + if (0 == (uptime % NOVA_SDS_REINIT_CHECK)) { if (!novasds_valid) { NovaSdsSetWorkPeriod(); } - } else if (0 == (uptime % XSNS_20_QUERY_INTERVAL)) { // Every 5 seconds + } else if (0 == (uptime % NOVA_SDS_QUERY_INTERVAL)) { if (NovaSdsReadData()) { novasds_valid = 10; } else {