|
|
@ -26,23 +26,15 @@ |
|
|
|
// Sensor type specified data |
|
|
|
#define PMS_SLOT_MAX 4 |
|
|
|
#define PMS_DATA_MAX 17 |
|
|
|
const static struct { |
|
|
|
const char *name; |
|
|
|
unsigned char data_count; |
|
|
|
unsigned char slot_count; |
|
|
|
unsigned char slot_types[PMS_SLOT_MAX]; |
|
|
|
} pms_specs[] = { |
|
|
|
{"PMSX003", 13, 3, {MAGNITUDE_PM1DOT0, MAGNITUDE_PM2DOT5, MAGNITUDE_PM10}}, |
|
|
|
{"PMSX003_9", 9, 3, {MAGNITUDE_PM1DOT0, MAGNITUDE_PM2DOT5, MAGNITUDE_PM10}}, |
|
|
|
{"PMS5003T", 13, 3, {MAGNITUDE_PM2DOT5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY}}, |
|
|
|
{"PMS5003ST", 17, 4, {MAGNITUDE_PM2DOT5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY, MAGNITUDE_HCHO}}, |
|
|
|
{"PMS5003S", 13, 3, {MAGNITUDE_PM2DOT5, MAGNITUDE_PM10, MAGNITUDE_HCHO}}, |
|
|
|
}; |
|
|
|
|
|
|
|
// [MAGIC][LEN][DATA9|13|17][SUM] |
|
|
|
#define PMS_PACKET_SIZE(data_count) ((data_count + 3) * 2) |
|
|
|
#define PMS_PAYLOAD_SIZE(data_count) ((data_count + 1) * 2) |
|
|
|
inline int PMS_PACKET_SIZE(int size) { |
|
|
|
return (size + 3) * 2; |
|
|
|
} |
|
|
|
|
|
|
|
inline int PMS_PAYLOAD_SIZE(int size) { |
|
|
|
return (size + 1) * 2; |
|
|
|
} |
|
|
|
|
|
|
|
// PMS sensor utils |
|
|
|
// Command functions copied from: https://github.com/fu-hsi/PMS/blob/master/src/PMS.cpp |
|
|
@ -50,49 +42,49 @@ const static struct { |
|
|
|
class PMSX003 { |
|
|
|
|
|
|
|
protected: |
|
|
|
Stream *_serial = NULL; // Should initialized by child class |
|
|
|
Stream *_serial = nullptr; // Should initialized by child class |
|
|
|
|
|
|
|
public: |
|
|
|
|
|
|
|
// Standby mode. For low power consumption and prolong the life of the sensor. |
|
|
|
inline void sleep() { |
|
|
|
uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x00, 0x01, 0x73 }; |
|
|
|
const uint8_t command[] { 0x42, 0x4D, 0xE4, 0x00, 0x00, 0x01, 0x73 }; |
|
|
|
_serial->write(command, sizeof(command)); |
|
|
|
} |
|
|
|
|
|
|
|
// Operating mode. Stable data should be got at least 30 seconds after the sensor wakeup from the sleep mode because of the fan's performance. |
|
|
|
inline void wakeUp() { |
|
|
|
uint8_t command[] = { 0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74 }; |
|
|
|
const uint8_t command[] { 0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74 }; |
|
|
|
_serial->write(command, sizeof(command)); |
|
|
|
} |
|
|
|
|
|
|
|
// Active mode. Default mode after power up. In this mode sensor would send serial data to the host automatically. |
|
|
|
inline void activeMode() { |
|
|
|
uint8_t command[] = { 0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71 }; |
|
|
|
const uint8_t command[] { 0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71 }; |
|
|
|
_serial->write(command, sizeof(command)); |
|
|
|
} |
|
|
|
|
|
|
|
// Passive mode. In this mode, sensor would send serial data to the host only for request. |
|
|
|
inline void passiveMode() { |
|
|
|
uint8_t command[] = { 0x42, 0x4D, 0xE1, 0x00, 0x00, 0x01, 0x70 }; |
|
|
|
const uint8_t command[] { 0x42, 0x4D, 0xE1, 0x00, 0x00, 0x01, 0x70 }; |
|
|
|
_serial->write(command, sizeof(command)); |
|
|
|
} |
|
|
|
|
|
|
|
// Request read, ONLY needed in Passive Mode!! |
|
|
|
inline void requestRead() { |
|
|
|
uint8_t command[] = { 0x42, 0x4D, 0xE2, 0x00, 0x00, 0x01, 0x71 }; |
|
|
|
const uint8_t command[] { 0x42, 0x4D, 0xE2, 0x00, 0x00, 0x01, 0x71 }; |
|
|
|
_serial->write(command, sizeof(command)); |
|
|
|
} |
|
|
|
|
|
|
|
// Read sensor's data |
|
|
|
bool readData(uint16_t data[], unsigned char data_count) { |
|
|
|
bool readData(uint16_t* data, size_t data_count) { |
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
int avail = _serial->available(); |
|
|
|
#if SENSOR_DEBUG |
|
|
|
//DEBUG_MSG("[SENSOR] PMS: Packet available = %d\n", avail); |
|
|
|
#endif |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG_P(PSTR("[SENSOR] PMS: Packet available = %d\n"), avail); |
|
|
|
#endif |
|
|
|
if (avail < PMS_PACKET_SIZE(data_count)) { |
|
|
|
break; |
|
|
|
} |
|
|
@ -102,26 +94,27 @@ class PMSX003 { |
|
|
|
uint16_t sum = 0x42 + 0x4D; |
|
|
|
uint16_t size = read16(sum); |
|
|
|
if (size != PMS_PAYLOAD_SIZE(data_count)) { |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG(("[SENSOR] PMS: Payload size: %d != %d.\n"), size, PMS_PAYLOAD_SIZE(data_count)); |
|
|
|
#endif |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG_P(PSTR("[SENSOR] PMS: Payload size: %hu != %zu.\n"), |
|
|
|
size, PMS_PAYLOAD_SIZE(data_count)); |
|
|
|
#endif |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
for (int i = 0; i < data_count; i++) { |
|
|
|
for (size_t i = 0; i < data_count; i++) { |
|
|
|
data[i] = read16(sum); |
|
|
|
#if SENSOR_DEBUG |
|
|
|
//DEBUG_MSG(("[SENSOR] PMS: data[%d] = %d\n"), i, data[i]); |
|
|
|
#endif |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG_P(PSTR("[SENSOR] PMS: data[%zu] = %hu\n"), i, data[i]); |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
uint16_t checksum = read16(); |
|
|
|
if (sum == checksum) { |
|
|
|
return true; |
|
|
|
} else { |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG(("[SENSOR] PMS checksum: %04X != %04X\n"), sum, checksum); |
|
|
|
#endif |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG_P(PSTR("[SENSOR] PMS checksum: %04X != %04X\n"), sum, checksum); |
|
|
|
#endif |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
@ -135,7 +128,7 @@ class PMSX003 { |
|
|
|
private: |
|
|
|
|
|
|
|
// Read 16-bit |
|
|
|
inline uint16_t read16() { |
|
|
|
uint16_t read16() { |
|
|
|
return ((uint16_t) _serial->read()) << 8 | _serial->read(); |
|
|
|
} |
|
|
|
|
|
|
@ -152,15 +145,24 @@ class PMSX003 { |
|
|
|
|
|
|
|
class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
|
|
|
|
public: |
|
|
|
private: |
|
|
|
|
|
|
|
// --------------------------------------------------------------------- |
|
|
|
// Public |
|
|
|
// --------------------------------------------------------------------- |
|
|
|
PMSX003Sensor() { |
|
|
|
_count = pms_specs[_type].slot_count; |
|
|
|
_sensor_id = SENSOR_PMSX003_ID; |
|
|
|
} |
|
|
|
struct Spec { |
|
|
|
const char *name; |
|
|
|
unsigned char data_count; |
|
|
|
unsigned char slot_count; |
|
|
|
unsigned char slot_types[PMS_SLOT_MAX]; |
|
|
|
}; |
|
|
|
|
|
|
|
static constexpr Spec Specs[] { |
|
|
|
{"PMSX003", 13, 3, {MAGNITUDE_PM1DOT0, MAGNITUDE_PM2DOT5, MAGNITUDE_PM10}}, |
|
|
|
{"PMSX003_9", 9, 3, {MAGNITUDE_PM1DOT0, MAGNITUDE_PM2DOT5, MAGNITUDE_PM10}}, |
|
|
|
{"PMS5003T", 13, 3, {MAGNITUDE_PM2DOT5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY}}, |
|
|
|
{"PMS5003ST", 17, 4, {MAGNITUDE_PM2DOT5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY, MAGNITUDE_HCHO}}, |
|
|
|
{"PMS5003S", 13, 3, {MAGNITUDE_PM2DOT5, MAGNITUDE_PM10, MAGNITUDE_HCHO}}, |
|
|
|
}; |
|
|
|
|
|
|
|
public: |
|
|
|
|
|
|
|
~PMSX003Sensor() { |
|
|
|
removeSerial(); |
|
|
@ -187,7 +189,6 @@ class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
// Should call setType after constructor immediately to enable corresponding slot count |
|
|
|
void setType(unsigned char type) { |
|
|
|
_type = type; |
|
|
|
_count = pms_specs[_type].slot_count; |
|
|
|
} |
|
|
|
|
|
|
|
// --------------------------------------------------------------------- |
|
|
@ -208,8 +209,16 @@ class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
// Sensor API |
|
|
|
// --------------------------------------------------------------------- |
|
|
|
|
|
|
|
unsigned char id() const override { |
|
|
|
return SENSOR_PMSX003_ID; |
|
|
|
} |
|
|
|
|
|
|
|
unsigned char count() const override { |
|
|
|
return Specs[_type].slot_count; |
|
|
|
} |
|
|
|
|
|
|
|
// Initialization method, must be idempotent |
|
|
|
void begin() { |
|
|
|
void begin() override { |
|
|
|
|
|
|
|
if (!_dirty) return; |
|
|
|
|
|
|
@ -227,61 +236,73 @@ class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
|
|
|
|
passiveMode(); |
|
|
|
|
|
|
|
_startTime = millis(); |
|
|
|
_startTime = TimeSource::now(); |
|
|
|
_warmedUp = false; |
|
|
|
_ready = true; |
|
|
|
_dirty = false; |
|
|
|
} |
|
|
|
|
|
|
|
// Descriptive name of the sensor |
|
|
|
String description() { |
|
|
|
String description() const override { |
|
|
|
char buffer[28]; |
|
|
|
if (_soft) { |
|
|
|
snprintf(buffer, sizeof(buffer), "%s @ SwSerial(%u,%u)", pms_specs[_type].name, _pin_rx, _pin_tx); |
|
|
|
snprintf_P(buffer, sizeof(buffer), |
|
|
|
PSTR("%s @ SwSerial(%u,%u)"), |
|
|
|
Specs[_type].name, _pin_rx, _pin_tx); |
|
|
|
} else { |
|
|
|
snprintf(buffer, sizeof(buffer), "%s @ HwSerial", pms_specs[_type].name); |
|
|
|
snprintf_P(buffer, sizeof(buffer), |
|
|
|
PSTR("%s @ HwSerial"), Specs[_type].name); |
|
|
|
} |
|
|
|
|
|
|
|
return String(buffer); |
|
|
|
} |
|
|
|
|
|
|
|
// Descriptive name of the slot # index |
|
|
|
String description(unsigned char index) { |
|
|
|
String description(unsigned char index) const override { |
|
|
|
char buffer[36] = {0}; |
|
|
|
if (_soft) { |
|
|
|
snprintf(buffer, sizeof(buffer), "%d @ %s @ SwSerial(%u,%u)", int(index + 1), pms_specs[_type].name, _pin_rx, _pin_tx); |
|
|
|
snprintf_P(buffer, sizeof(buffer), |
|
|
|
PSTR("%d @ %s @ SwSerial(%u,%u)"), |
|
|
|
int(index + 1), Specs[_type].name, _pin_rx, _pin_tx); |
|
|
|
} else { |
|
|
|
snprintf(buffer, sizeof(buffer), "%d @ %s @ HwSerial", int(index + 1), pms_specs[_type].name); |
|
|
|
snprintf_P(buffer, sizeof(buffer), |
|
|
|
PSTR("%d @ %s @ HwSerial"), |
|
|
|
int(index + 1), Specs[_type].name); |
|
|
|
} |
|
|
|
return String(buffer); |
|
|
|
} |
|
|
|
|
|
|
|
// Address of the sensor (it could be the GPIO or I2C address) |
|
|
|
String address(unsigned char index) { |
|
|
|
String address(unsigned char index) const override { |
|
|
|
char buffer[6]; |
|
|
|
snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx); |
|
|
|
snprintf(buffer, sizeof(buffer), "%hhu:%hhu", _pin_rx, _pin_tx); |
|
|
|
return String(buffer); |
|
|
|
} |
|
|
|
|
|
|
|
// Type for slot # index |
|
|
|
unsigned char type(unsigned char index) { |
|
|
|
return pms_specs[_type].slot_types[index]; |
|
|
|
unsigned char type(unsigned char index) const override { |
|
|
|
return Specs[_type].slot_types[index]; |
|
|
|
} |
|
|
|
|
|
|
|
void pre() { |
|
|
|
void pre() override { |
|
|
|
|
|
|
|
if (millis() - _startTime < 30000) { |
|
|
|
static constexpr auto WarmupDuration = espurna::duration::Seconds(30); |
|
|
|
if (!_warmedUp && TimeSource::now() - _startTime < WarmupDuration) { |
|
|
|
_error = SENSOR_ERROR_WARM_UP; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
_warmedUp = true; |
|
|
|
|
|
|
|
#if PMS_SMART_SLEEP |
|
|
|
unsigned int readCycle; |
|
|
|
if (_readCount++ > 30) { |
|
|
|
readCycle = _readCount % 30; |
|
|
|
if (readCycle == 0) { |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG("[SENSOR] %s: Wake up: %d\n", pms_specs[_type].name, _readCount); |
|
|
|
#endif |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG_P(PSTR("[SENSOR] %s: Wake up: %d\n"), |
|
|
|
Specs[_type].name, _readCount); |
|
|
|
#endif |
|
|
|
wakeUp(); |
|
|
|
return; |
|
|
|
} else if (readCycle == 1) { |
|
|
@ -298,7 +319,7 @@ class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
#endif |
|
|
|
|
|
|
|
uint16_t data[PMS_DATA_MAX]; |
|
|
|
if (readData(data, pms_specs[_type].data_count)) { |
|
|
|
if (readData(data, Specs[_type].data_count)) { |
|
|
|
if (_type == PMS_TYPE_5003ST) { |
|
|
|
if (data[14] > 10 && data[14] < 1000 && data[13] < 1000) { |
|
|
|
_slot_values[0] = data[4]; |
|
|
@ -308,9 +329,10 @@ class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
_error = SENSOR_ERROR_OK; |
|
|
|
} else { |
|
|
|
_error = SENSOR_ERROR_OUT_OF_RANGE; |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG("[SENSOR] %s: Invalid temperature=%d humidity=%d.\n", pms_specs[_type].name, (int)data[13], (int)data[14]); |
|
|
|
#endif |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG_P(PSTR("[SENSOR] %s: Invalid temperature=%d humidity=%d.\n"), |
|
|
|
Specs[_type].name, (int)data[13], (int)data[14]); |
|
|
|
#endif |
|
|
|
} |
|
|
|
} else if (_type == PMS_TYPE_5003S) { |
|
|
|
_slot_values[0] = data[4]; |
|
|
@ -325,9 +347,10 @@ class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
_error = SENSOR_ERROR_OK; |
|
|
|
} else { |
|
|
|
_error = SENSOR_ERROR_OUT_OF_RANGE; |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG("[SENSOR] %s: Invalid temperature=%d humidity=%d.\n", pms_specs[_type].name, (int)data[10], (int)data[11]); |
|
|
|
#endif |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG_P(PSTR("[SENSOR] %s: Invalid temperature=%d humidity=%d.\n"), |
|
|
|
Specs[_type].name, (int)data[10], (int)data[11]); |
|
|
|
#endif |
|
|
|
} |
|
|
|
} else { |
|
|
|
_slot_values[0] = data[3]; |
|
|
@ -340,9 +363,10 @@ class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
#if PMS_SMART_SLEEP |
|
|
|
if (readCycle == 6) { |
|
|
|
sleep(); |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG("[SENSOR] %s: Enter sleep mode: %d\n", pms_specs[_type].name, _readCount); |
|
|
|
#endif |
|
|
|
#if SENSOR_DEBUG |
|
|
|
DEBUG_MSG_P(PSTR("[SENSOR] %s: Enter sleep mode: %d\n"), |
|
|
|
Specs[_type].name, _readCount); |
|
|
|
#endif |
|
|
|
return; |
|
|
|
} |
|
|
|
#endif |
|
|
@ -352,7 +376,7 @@ class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
} |
|
|
|
|
|
|
|
// Current value for slot # index |
|
|
|
double value(unsigned char index) { |
|
|
|
double value(unsigned char index) override { |
|
|
|
return _slot_values[index]; |
|
|
|
} |
|
|
|
|
|
|
@ -365,15 +389,19 @@ class PMSX003Sensor : public BaseSensor, PMSX003 { |
|
|
|
|
|
|
|
protected: |
|
|
|
bool _soft = true; |
|
|
|
unsigned int _pin_rx; |
|
|
|
unsigned int _pin_tx; |
|
|
|
unsigned long _startTime; |
|
|
|
unsigned char _pin_rx; |
|
|
|
unsigned char _pin_tx; |
|
|
|
|
|
|
|
using TimeSource = espurna::time::CoreClock; |
|
|
|
TimeSource::time_point _startTime; |
|
|
|
bool _warmedUp = false; |
|
|
|
|
|
|
|
unsigned char _type = PMS_TYPE_X003; |
|
|
|
double _slot_values[PMS_SLOT_MAX] = {0}; |
|
|
|
|
|
|
|
#if PMS_SMART_SLEEP |
|
|
|
unsigned int _readCount = 0; |
|
|
|
#endif |
|
|
|
#if PMS_SMART_SLEEP |
|
|
|
size_t _readCount = 0; |
|
|
|
#endif |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|