From 0130ce972a32b75c4aab161d61c94419afdb6073 Mon Sep 17 00:00:00 2001 From: FransOv <57999223+FransOv@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:34:49 +0200 Subject: [PATCH] Support of Winsum ZH03B sensor Dust particle sensor in xsns_18_pms5003.ino (#19850) * Update xsns_18_pms5003.ino The Winsum ZH03B sensor is quite similar to the Plantower PMS3003 sensor. The main difference is the response in passive mode. With these changes xsns_18_pms5003.ino can support the ZH03B sensor when PMS_MODEL_ZH03X is defined in the configuration file. * Update xsns_18_pms5003.ino --- .../tasmota_xsns_sensor/xsns_18_pms5003.ino | 103 ++++++++++++++++-- 1 file changed, 92 insertions(+), 11 deletions(-) diff --git a/tasmota/tasmota_xsns_sensor/xsns_18_pms5003.ino b/tasmota/tasmota_xsns_sensor/xsns_18_pms5003.ino index 9471d9c18..426b6e286 100644 --- a/tasmota/tasmota_xsns_sensor/xsns_18_pms5003.ino +++ b/tasmota/tasmota_xsns_sensor/xsns_18_pms5003.ino @@ -27,6 +27,8 @@ * You can either support PMS3003 or PMS5003-7003 at one time. To enable the PMS3003 support * you must enable the define PMS_MODEL_PMS3003 on your configuration file. * For PMSx003T models that report temperature and humidity define PMS_MODEL_PMS5003T + * This module can also support de Winsen ZH03x series of dust particle sensors, + * To support those sensors, you must define PMS_MODEL_ZH03X in the confuguration file. \*********************************************************************************************/ #define XSNS_18 18 @@ -61,6 +63,15 @@ enum PmsCommands CMD_READ_DATA }; +#ifdef PMS_MODEL_ZH03X +const uint8_t kPmsCommands[][9] PROGMEM = { + // 0 1 2 3 4 5 6 7 8 + {0xFF, 0x01, 0x78, 0x40, 0x00, 0x00, 0x00, 0x00, 0x47}, // pms_set_active_mode + {0xFF, 0x01, 0xA7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x57}, // pms_sleep + {0xFF, 0x01, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58}, // pms_wake + {0xFF, 0x01, 0x78, 0x41, 0x00, 0x00, 0x00, 0x00, 0x46}, // pms_set_passive_mode + {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79}}; // pms_passive_mode_read +#else const uint8_t kPmsCommands[][7] PROGMEM = { // 0 1 2 3 4 5 6 {0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71}, // pms_set_active_mode @@ -68,12 +79,13 @@ const uint8_t kPmsCommands[][7] PROGMEM = { {0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74}, // pms_wake {0x42, 0x4D, 0xE1, 0x00, 0x00, 0x01, 0x70}, // pms_set_passive_mode {0x42, 0x4D, 0xE2, 0x00, 0x00, 0x01, 0x71}}; // pms_passive_mode_read +#endif // PMS_MODEL_ZH03X struct pmsX003data { uint16_t framelen; uint16_t pm10_standard, pm25_standard, pm100_standard; uint16_t pm10_env, pm25_env, pm100_env; -#ifdef PMS_MODEL_PMS3003 +#if defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X) uint16_t reserved1, reserved2, reserved3; #else uint16_t particles_03um, particles_05um, particles_10um, particles_25um; @@ -104,7 +116,7 @@ bool PmsReadData(void) while ((PmsSerial->peek() != 0x42) && PmsSerial->available()) { PmsSerial->read(); } -#ifdef PMS_MODEL_PMS3003 +#if defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X) if (PmsSerial->available() < 24) { #else if (PmsSerial->available() < 32) { @@ -112,7 +124,7 @@ bool PmsReadData(void) return false; } -#ifdef PMS_MODEL_PMS3003 +#if defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X) uint8_t buffer[24]; PmsSerial->readBytes(buffer, 24); #else @@ -122,14 +134,14 @@ bool PmsReadData(void) uint16_t sum = 0; PmsSerial->flush(); // Make room for another burst -#ifdef PMS_MODEL_PMS3003 +#if defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X) AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 24); #else AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 32); #endif // PMS_MODEL_PMS3003 // get checksum ready -#ifdef PMS_MODEL_PMS3003 +#if defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X) for (uint32_t i = 0; i < 22; i++) { #else for (uint32_t i = 0; i < 30; i++) { @@ -137,7 +149,7 @@ bool PmsReadData(void) sum += buffer[i]; } // The data comes in endian'd, this solves it so it works on all platforms -#ifdef PMS_MODEL_PMS3003 +#if defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X) uint16_t buffer_u16[12]; for (uint32_t i = 0; i < 12; i++) { #else @@ -147,7 +159,8 @@ bool PmsReadData(void) buffer_u16[i] = buffer[2 + i*2 + 1]; buffer_u16[i] += (buffer[2 + i*2] << 8); } -#ifdef PMS_MODEL_PMS3003 + +#if defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X) if (sum != buffer_u16[10]) { #else if (sum != buffer_u16[14]) { @@ -156,7 +169,7 @@ bool PmsReadData(void) return false; } -#ifdef PMS_MODEL_PMS3003 +#if defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X) memcpy((void *)&pms_data, (void *)buffer_u16, 22); #else memcpy((void *)&pms_data, (void *)buffer_u16, 30); @@ -170,6 +183,60 @@ bool PmsReadData(void) return true; } +#ifdef PMS_MODEL_ZH03X +bool ZH03ReadDataPassive() // process the passive mode response of the ZH03x sensor +{ + if (! PmsSerial->available()) { + return false; + } + while ((PmsSerial->peek() != 0xFF) && PmsSerial->available()) { + PmsSerial->read(); + } + if (PmsSerial->available() < 9) { + return false; + } + uint8_t buffer[9]; + PmsSerial->readBytes(buffer, 9); + if (buffer[1] != 0x86) { + return false; + } + PmsSerial->flush(); // Make room for another burst + + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 9); + + uint8_t sum = 0; + for (uint32_t i = 1; i < 7; i++) { + sum += buffer[i]; + } + sum=(~sum)+1; + if (sum != buffer[8]) { + AddLog(LOG_LEVEL_DEBUG, PSTR("ZH03x: " D_CHECKSUM_FAILURE)); + return false; + } + + uint16_t buffer_u16[12]; + for (uint32_t i = 1; i < 4; i++) { + buffer_u16[i] = buffer[i*2 + 1]; + buffer_u16[i] += (buffer[i*2] << 8); + buffer_u16[i+3] = buffer[i*2 + 1]; // Direct and Environment values identical + buffer_u16[i+3] += (buffer[i*2] << 8); // Direct and Environment values identical + buffer_u16[0] = 20; // set dummy framelength + buffer_u16[11] = buffer[8]; // copy checksum + } + + memcpy((void *)&pms_data, (void *)buffer_u16, 22); + + Pms.valid = 10; + + if (!Pms.discovery_triggered) { + TasmotaGlobal.discovery_counter = 1; // Force discovery + Pms.discovery_triggered = true; + } + + return true; + +} +#endif // PMS_MODEL_ZH03X /*********************************************************************************************\ * Command Sensor18 @@ -227,7 +294,18 @@ void PmsSecond(void) // Every second } if (Pms.ready) { +#ifdef PMS_MODEL_ZH03X + bool validread; + if (Settings->pms_wake_interval >= MIN_INTERVAL_PERIOD) { + validread = ZH03ReadDataPassive(); // in passive mode, the response is different from the PMS sensors + } + else { + validread = PmsReadData(); // In active mode the rsponse is identical to the PMS sensors + } + if (validread) { +#else if (PmsReadData()) { +#endif // PMS_MODEL_ZH03X Pms.valid = 10; if (Settings->pms_wake_interval >= MIN_INTERVAL_PERIOD) { PmsSendCmd(CMD_SLEEP); @@ -276,7 +354,10 @@ void PmsInit(void) { void PmsShow(bool json) { if (Pms.valid) { char types[10]; -#ifdef PMS_MODEL_PMS3003 + +#ifdef PMS_MODEL_ZH03X + strcpy_P(types, PSTR("ZH03x")); +#elif defined(PMS_MODEL_PMS3003) strcpy_P(types, PSTR("PMS3003")); #elif defined(PMS_MODEL_PMS5003T) strcpy_P(types, PSTR("PMS5003T")); @@ -294,7 +375,7 @@ void PmsShow(bool json) { types, pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard, pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env); -#ifndef PMS_MODEL_PMS3003 +#if !(defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X)) ResponseAppend_P(PSTR(",\"PB0.3\":%d,\"PB0.5\":%d,\"PB1\":%d,\"PB2.5\":%d,"), pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um); #ifdef PMS_MODEL_PMS5003T @@ -321,7 +402,7 @@ void PmsShow(bool json) { WSContentSend_PD(HTTP_SNS_ENVIRONMENTAL_CONCENTRATION, types, "1", pms_data.pm10_env); WSContentSend_PD(HTTP_SNS_ENVIRONMENTAL_CONCENTRATION, types, "2.5", pms_data.pm25_env); WSContentSend_PD(HTTP_SNS_ENVIRONMENTAL_CONCENTRATION, types, "10", pms_data.pm100_env); -#ifndef PMS_MODEL_PMS3003 +#if !(defined(PMS_MODEL_PMS3003) || defined(PMS_MODEL_ZH03X)) WSContentSend_PD(HTTP_SNS_PARTICALS_BEYOND, types, "0.3", pms_data.particles_03um); WSContentSend_PD(HTTP_SNS_PARTICALS_BEYOND, types, "0.5", pms_data.particles_05um); WSContentSend_PD(HTTP_SNS_PARTICALS_BEYOND, types, "1", pms_data.particles_10um);