diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index eb2377ce..5f51b2ac 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -1138,6 +1138,38 @@ #endif +//Remote Buttons SET 3 (samsung AA59-00608A 8 Toggle Buttons for generic 8CH module) +#if IR_BUTTON_SET == 3 +/* + +------+------+------+ + | 1 | 2 | 3 | + +------+------+------+ + | 4 | 5 | 6 | + +------+------+------+ + | 7 | 8 | 9 | + +------+------+------+ + | | 0 | | + +------+------+------+ +*/ +#define IR_BUTTON_COUNT 10 + + const unsigned long IR_BUTTON[IR_BUTTON_COUNT][3] PROGMEM = { + + { 0xE0E020DF, IR_BUTTON_MODE_TOGGLE, 0 }, // Toggle Relay #0 + { 0xE0E0A05F, IR_BUTTON_MODE_TOGGLE, 1 }, // Toggle Relay #1 + { 0xE0E0609F, IR_BUTTON_MODE_TOGGLE, 2 }, // Toggle Relay #2 + + { 0xE0E010EF, IR_BUTTON_MODE_TOGGLE, 3 }, // Toggle Relay #3 + { 0xE0E0906F, IR_BUTTON_MODE_TOGGLE, 4 }, // Toggle Relay #4 + { 0xE0E050AF, IR_BUTTON_MODE_TOGGLE, 5 }, // Toggle Relay #5 + + { 0xE0E030CF, IR_BUTTON_MODE_TOGGLE, 6 }, // Toggle Relay #6 + { 0xE0E0B04F, IR_BUTTON_MODE_TOGGLE, 7 } // Toggle Relay #7 + //{ 0xE0E0708F, IR_BUTTON_MODE_TOGGLE, 8 } //Extra Button + + //{ 0xE0E08877, IR_BUTTON_MODE_TOGGLE, 9 } //Extra Button + }; +#endif #endif // IR_SUPPORT //-------------------------------------------------------------------------------- diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index 5ef65e6b..254af634 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -373,6 +373,23 @@ #define MHZ19_TX_PIN 15 #endif +//------------------------------------------------------------------------------ +// SenseAir CO2 sensor +// Enable support by passing SENSEAIR_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef SENSEAIR_SUPPORT +#define SENSEAIR_SUPPORT 0 +#endif + +#ifndef SENSEAIR_RX_PIN +#define SENSEAIR_RX_PIN 0 +#endif + +#ifndef SENSEAIR_TX_PIN +#define SENSEAIR_TX_PIN 2 +#endif + //------------------------------------------------------------------------------ // Particle Monitor based on Plantower PMSX003 // Enable support by passing PMSX003_SUPPORT=1 build flag @@ -503,6 +520,7 @@ HCSR04_SUPPORT || \ HLW8012_SUPPORT || \ MHZ19_SUPPORT || \ + SENSEAIR_SUPPORT || \ PMSX003_SUPPORT || \ PZEM004T_SUPPORT || \ SHT3X_I2C_SUPPORT || \ @@ -624,6 +642,11 @@ #include "../sensors/MHZ19Sensor.h" #endif +#if SENSEAIR_SUPPORT + #include + #include "../sensors/SenseAirSensor.h" +#endif + #if PMSX003_SUPPORT #include #include diff --git a/code/espurna/config/types.h b/code/espurna/config/types.h index d67a0903..5aa5b865 100644 --- a/code/espurna/config/types.h +++ b/code/espurna/config/types.h @@ -159,6 +159,7 @@ #define IR_BUTTON_MODE_BRIGHTER 3 #define IR_BUTTON_MODE_STATE 4 #define IR_BUTTON_MODE_EFFECT 5 +#define IR_BUTTON_MODE_TOGGLE 6 #define LIGHT_EFFECT_SOLID 0 #define LIGHT_EFFECT_FLASH 1 @@ -249,6 +250,7 @@ #define SENSOR_CSE7766_ID 0x21 #define SENSOR_TMP3X_ID 0x22 #define SENSOR_HCSR04_ID 0x23 +#define SENSOR_SENSEAIR_ID 0x24 //-------------------------------------------------------------------------------- // Magnitudes diff --git a/code/espurna/ir.ino b/code/espurna/ir.ino index 241c6fa2..93d75878 100644 --- a/code/espurna/ir.ino +++ b/code/espurna/ir.ino @@ -42,7 +42,9 @@ void _irProcessCode(unsigned long code) { if (button_mode == IR_BUTTON_MODE_STATE) { relayStatus(0, button_value); } - + if (button_mode == IR_BUTTON_MODE_TOGGLE) { + relayToggle(button_value); + } #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE if (button_mode == IR_BUTTON_MODE_BRIGHTER) { diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.ino index da446a70..98f5049b 100644 --- a/code/espurna/sensor.ino +++ b/code/espurna/sensor.ino @@ -487,6 +487,15 @@ void _sensorLoad() { } #endif + #if SENSEAIR_SUPPORT + { + SenseAirSensor * sensor = new SenseAirSensor(); + sensor->setRX(SENSEAIR_RX_PIN); + sensor->setTX(SENSEAIR_TX_PIN); + _sensors.push_back(sensor); + } + #endif + #if PMSX003_SUPPORT { PMSX003Sensor * sensor = new PMSX003Sensor(); diff --git a/code/espurna/sensors/PMSX003Sensor.h b/code/espurna/sensors/PMSX003Sensor.h index 95e5933a..c9e8463e 100644 --- a/code/espurna/sensors/PMSX003Sensor.h +++ b/code/espurna/sensors/PMSX003Sensor.h @@ -2,6 +2,7 @@ // PMSX003 Dust Sensor // Uses SoftwareSerial library // Contribution by Òscar Rovira López +// Refine to support PMS5003T/PMS5003ST by Yonsm Guo // ----------------------------------------------------------------------------- #if SENSOR_SUPPORT && PMSX003_SUPPORT @@ -14,7 +15,158 @@ #include #include -class PMSX003Sensor : public BaseSensor { +// +#define PMS_TYPE_X003 0 +#define PMS_TYPE_X003_9 1 +#define PMS_TYPE_5003T 2 +#define PMS_TYPE_5003ST 3 + +#ifndef PMS_TYPE +#define PMS_TYPE PMS_TYPE_X003 +#endif + +// You can enable smart sleep (read 6-times then sleep on 24-reading-cycles) to extend PMS sensor's life. +// Otherwise the default lifetime of PMS sensor is about 8000-hours/1-years. +// The PMS's fan will stop working on sleeping cycle, and will wake up on reading cycle. +#ifndef PMS_SMART_SLEEP +#define PMS_SMART_SLEEP 0 +#endif + +// [MAGIC][LEN][DATA9|13|17][SUM] +#if PMS_TYPE == PMS_TYPE_5003ST +#define PMS_TYPE_NAME "PMS5003ST" +#define PMS_DATA_COUNT 17 +#define PMS_SLOT_COUNT 4 +#define PMS_SLOT_NAMES {"PM2.5", "TEMP", "HUMI", "HCHO"} +#define PMS_SLOT_TYPES {MAGNITUDE_PM2dot5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY, MAGNITUDE_ANALOG} +#elif PMS_TYPE == PMS_TYPE_5003T +#define PMS_TYPE_NAME "PMS5003T" +#define PMS_DATA_COUNT 13 +#define PMS_SLOT_COUNT 3 +#define PMS_SLOT_NAMES {"PM2.5", "TEMP", "HUMI"} +#define PMS_SLOT_TYPES {MAGNITUDE_PM2dot5, MAGNITUDE_TEMPERATURE, MAGNITUDE_HUMIDITY} +#elif PMS_TYPE == PMS_TYPE_X003_9 +#define PMS_TYPE_NAME "PMSX003_9" +#define PMS_DATA_COUNT 9 +#define PMS_SLOT_COUNT 3 +#define PMS_SLOT_NAMES {"PM1.0", "PM2.5", "PM10"} +#define PMS_SLOT_TYPES {MAGNITUDE_PM1dot0, MAGNITUDE_PM2dot5, MAGNITUDE_PM10} +#else +#define PMS_TYPE_NAME "PMSX003" +#define PMS_DATA_COUNT 13 +#define PMS_SLOT_COUNT 3 +#define PMS_SLOT_NAMES {"PM1.0", "PM2.5", "PM10"} +#define PMS_SLOT_TYPES {MAGNITUDE_PM1dot0, MAGNITUDE_PM2dot5, MAGNITUDE_PM10} +#endif + +#define PMS_PACKET_SIZE ((PMS_DATA_COUNT + 3) * 2) +#define PMS_PAYLOAD_SIZE (PMS_DATA_COUNT * 2 + 2) + + +// PMSX003 sensor utils +// Command functions copied from: https://github.com/fu-hsi/PMS/blob/master/src/PMS.cpp +// Reading function is rewrited to support flexible reading for PMS5003T/PMS5003ST +class PMSX003 { +protected: + SoftwareSerial *_serial = NULL; // 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 }; + _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 }; + _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 }; + _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 }; + _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 }; + _serial->write(command, sizeof(command)); + } + + // Read sensor's data + bool readData(uint16_t data[PMS_DATA_COUNT]) { + do + { + int avail = _serial->available(); + #if SENSOR_DEBUG + //debugSend("[SENSOR] %s: Packet available = %d\n", PMS_TYPE_NAME, avail); + #endif + if (avail < PMS_PACKET_SIZE) + break; + + if (_serial->read() == 0x42 && _serial->read() == 0x4D) + { + uint16_t sum = 0x42 + 0x4D; + uint16_t size = read16(sum); + #if SENSOR_DEBUG + debugSend("[SENSOR] %s: Payload size = %d\n", PMS_TYPE_NAME, size); + #endif + if (size != PMS_PAYLOAD_SIZE) + { + #if SENSOR_DEBUG + debugSend(("[SENSOR] %s: Payload size != %d \n"), PMS_TYPE_NAME, PMS_PAYLOAD_SIZE); + #endif + break; + } + + for (int i = 0; i < PMS_DATA_COUNT; i++) + { + data[i] = read16(sum); + #if SENSOR_DEBUG + //debugSend(("[SENSOR] %s: data[%d] = %d\n"), PMS_TYPE_NAME, i, data[i]); + #endif + } + + uint16_t checksum = read16(); + #if SENSOR_DEBUG + debugSend(("[SENSOR] %s: Sum=%04X, Checksum=%04X\n"), PMS_TYPE_NAME, sum, checksum); + #endif + if (sum == checksum) + { + return true; + } + break; + } + } + while (true); + return false; + } + +private: + // Read 16-bit + inline uint16_t read16() { + return ((uint16_t) _serial->read()) << 8 | _serial->read(); + } + + // Read 16-bit and calculate checksum + uint16_t read16(uint16_t &checksum) { + uint8_t high = _serial->read(); + uint8_t low = _serial->read(); + checksum += high; + checksum += low; + return ((uint16_t) high) << 8 | low; + } +}; + +class PMSX003Sensor : public BaseSensor, PMSX003 { public: @@ -23,13 +175,12 @@ class PMSX003Sensor : public BaseSensor { // --------------------------------------------------------------------- PMSX003Sensor(): BaseSensor() { - _count = 3; + _count = PMS_SLOT_COUNT; _sensor_id = SENSOR_PMSX003_ID; } ~PMSX003Sensor() { if (_serial) delete _serial; - if (_pms) delete _pms; } void setRX(unsigned char pin_rx) { @@ -64,13 +215,11 @@ class PMSX003Sensor : public BaseSensor { if (!_dirty) return; if (_serial) delete _serial; - if (_pms) delete _pms; - _serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32); + _serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 64); _serial->enableIntTx(false); _serial->begin(9600); - _pms = new PMS(* _serial); - _pms->passiveMode(); + passiveMode(); _startTime = millis(); _ready = true; @@ -81,16 +230,15 @@ class PMSX003Sensor : public BaseSensor { // Descriptive name of the sensor String description() { char buffer[28]; - snprintf(buffer, sizeof(buffer), "PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); + snprintf(buffer, sizeof(buffer), "%s @ SwSerial(%u,%u)", PMS_TYPE_NAME, _pin_rx, _pin_tx); return String(buffer); } // Descriptive name of the slot # index String slot(unsigned char index) { char buffer[36] = {0}; - if (index == 0) snprintf(buffer, sizeof(buffer), "PM1.0 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); - if (index == 1) snprintf(buffer, sizeof(buffer), "PM2.5 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); - if (index == 2) snprintf(buffer, sizeof(buffer), "PM10 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); + const static char *_slot_names[] = PMS_SLOT_NAMES; + snprintf(buffer, sizeof(buffer), "%s @ %s @ SwSerial(%u,%u)", _slot_names[index], PMS_TYPE_NAME, _pin_rx, _pin_tx); return String(buffer); } @@ -103,10 +251,8 @@ class PMSX003Sensor : public BaseSensor { // Type for slot # index unsigned char type(unsigned char index) { - if (index == 0) return MAGNITUDE_PM1dot0; - if (index == 1) return MAGNITUDE_PM2dot5; - if (index == 2) return MAGNITUDE_PM10; - return MAGNITUDE_NONE; + const static unsigned char _slot_types[] = PMS_SLOT_TYPES; + return _slot_types[index]; } void pre() { @@ -118,35 +264,71 @@ class PMSX003Sensor : public BaseSensor { _error = SENSOR_ERROR_OK; - if(_pms->read(_data)) { - _pm1dot0 = _data.PM_AE_UG_1_0; - _pm2dot5 = _data.PM_AE_UG_2_5; - _pm10 = _data.PM_AE_UG_10_0; + #if PMS_SMART_SLEEP + unsigned int readCycle; + if (_readCount++ > 30) { + readCycle = _readCount % 30; + if (readCycle == 0) { + #if SENSOR_DEBUG + debugSend("[SENSOR] %s: Wake up: %d\n", PMS_TYPE_NAME, _readCount); + #endif + wakeUp(); + return; + } else if (readCycle == 1) { + requestRead(); + } else if (readCycle > 6) { + return; + } + } else { + readCycle = -1; + } + #endif + + uint16_t data[PMS_DATA_COUNT]; + if (readData(data)) { + #if PMS_TYPE == PMS_TYPE_5003ST + _slot_values[0] = data[4]; + _slot_values[1] = (double)data[13] / 10; + _slot_values[2] = (double)data[14] / 10; + _slot_values[3] = (double)data[12] / 1000; + #elif PMS_TYPE == PMS_TYPE_5003T + _slot_values[0] = data[4]; + _slot_values[1] = (double)data[10] / 10; + _slot_values[2] = (double)data[11] / 10; + #else + _slot_values[0] = data[3]; + _slot_values[1] = data[4]; + _slot_values[2] = data[5]; + #endif } - _pms->requestRead(); - + #if PMS_SMART_SLEEP + if (readCycle == 6) { + sleep(); + #if SENSOR_DEBUG + debugSend("[SENSOR] %s: Enter sleep mode: %d\n", PMS_TYPE_NAME, _readCount); + #endif + return; + } + #endif + + requestRead(); } // Current value for slot # index double value(unsigned char index) { - if(index == 0) return _pm1dot0; - if(index == 1) return _pm2dot5; - if(index == 2) return _pm10; - return 0; + return _slot_values[index]; } protected: - unsigned int _pm1dot0; - unsigned int _pm2dot5; - unsigned int _pm10; unsigned int _pin_rx; unsigned int _pin_tx; unsigned long _startTime; - SoftwareSerial * _serial = NULL; - PMS * _pms = NULL; - PMS::DATA _data; + double _slot_values[PMS_SLOT_COUNT] = {0}; +#if PMS_SMART_SLEEP + unsigned int _readCount = 0; +#endif }; #endif // SENSOR_SUPPORT && PMSX003_SUPPORT diff --git a/code/espurna/sensors/SenseAirSensor.h b/code/espurna/sensors/SenseAirSensor.h new file mode 100644 index 00000000..c73d25e3 --- /dev/null +++ b/code/espurna/sensors/SenseAirSensor.h @@ -0,0 +1,233 @@ +// ----------------------------------------------------------------------------- +// SenseAir S8 CO2 Sensor +// Uses SoftwareSerial library +// Contribution by Yonsm Guo +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && SENSEAIR_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +#include + +// SenseAir sensor utils +class SenseAir +{ +protected: + SoftwareSerial *_serial; // Should initialized by child class + +public: + int sendCommand(byte command[]) { + byte recv_buf[7] = {0xff}; + byte data_buf[2] = {0xff}; + long value = -1; + + _serial->write(command, 8); //Send the byte array + delay(50); + + // Read answer from sensor + int ByteCounter = 0; + while(_serial->available()) { + recv_buf[ByteCounter] = _serial->read(); + ByteCounter++; + } + + data_buf[0] = recv_buf[3]; + data_buf[1] = recv_buf[4]; + value = (data_buf[0] << 8) | (data_buf[1]); + + return value; + } + + int readCo2(void) { + int co2 = 0; + byte frame[8] = {0}; + buildFrame(0xFE, 0x04, 0x03, 1, frame); + co2 = sendCommand(frame); + return co2; + } + +private: + // Compute the MODBUS RTU CRC + static unsigned int modRTU_CRC(byte buf[], int len, byte checkSum[2]) { + unsigned int crc = 0xFFFF; + + for (int pos = 0; pos < len; pos++) { + crc ^= (unsigned int)buf[pos]; // XOR byte into least sig. byte of crc + + for (int i = 8; i != 0; i--) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } + else // Else LSB is not set + crc >>= 1; // Just shift right + } + } + // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes) + checkSum[1] = (byte)((crc >> 8) & 0xFF); + checkSum[0] = (byte)(crc & 0xFF); + return crc; + } + + static int getBitOfInt(int reg, int pos) { + // Create a mask + int mask = 0x01 << pos; + + // Mask the status register + int masked_register = mask & reg; + + // Shift the result of masked register back to position 0 + int result = masked_register >> pos; + + return result; + } + + + static void buildFrame(byte slaveAddress, + byte functionCode, + short startAddress, + short numberOfRegisters, + byte frame[8]) { + frame[0] = slaveAddress; + frame[1] = functionCode; + frame[2] = (byte)(startAddress >> 8); + frame[3] = (byte)(startAddress); + frame[4] = (byte)(numberOfRegisters >> 8); + frame[5] = (byte)(numberOfRegisters); + // CRC-calculation + byte checkSum[2] = {0}; + modRTU_CRC(frame, 6, checkSum); + frame[6] = checkSum[0]; + frame[7] = checkSum[1]; + } +}; + +// +class SenseAirSensor : public BaseSensor, SenseAir { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + SenseAirSensor(): BaseSensor() { + _count = 1; + _co2 = 0; + _lastCo2 = 0; + _serial = NULL; + _sensor_id = SENSOR_SENSEAIR_ID; + } + + ~SenseAirSensor() { + if (_serial) delete _serial; + _serial = NULL; + } + + void setRX(unsigned char pin_rx) { + if (_pin_rx == pin_rx) return; + _pin_rx = pin_rx; + _dirty = true; + } + + void setTX(unsigned char pin_tx) { + if (_pin_tx == pin_tx) return; + _pin_tx = pin_tx; + _dirty = true; + } + + // --------------------------------------------------------------------- + + unsigned char getRX() { + return _pin_rx; + } + + unsigned char getTX() { + return _pin_tx; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + if (_serial) delete _serial; + + _serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 64); + _serial->enableIntTx(false); + _serial->begin(9600); + _serial->enableRx(true); + + _startTime = 0; + _ready = true; + _dirty = false; + } + + // Descriptive name of the sensor + String description() { + char buffer[28]; + snprintf(buffer, sizeof(buffer), "SenseAir S8 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + } + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + char buffer[6]; + snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + return MAGNITUDE_CO2; + } + + void pre() { + + if (millis() - _startTime < 20000) { + _error = SENSOR_ERROR_WARM_UP; + return; + } + + _error = SENSOR_ERROR_OK; + + unsigned int co2 = readCo2(); + if (co2 >= 5000 || co2 < 100) + { + _co2 = _lastCo2; + } + else + { + _co2 = (co2 > _lastCo2 + 2000) ? _lastCo2 : co2; + _lastCo2 = co2; + } + } + + // Current value for slot # index + double value(unsigned char index) { + return _co2; + } + + protected: + unsigned int _pin_rx; + unsigned int _pin_tx; + unsigned long _startTime; + unsigned int _co2; + unsigned int _lastCo2; +}; + + +#endif // SENSOR_SUPPORT && SENSEAIR_SUPPORT diff --git a/code/platformio.ini b/code/platformio.ini index db3a4034..1bfbd65b 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -70,7 +70,6 @@ lib_deps = https://bitbucket.org/xoseperez/nofuss.git#0.2.5 https://github.com/xoseperez/NtpClient.git#0016a59 OneWire - PMS Library PZEM004T PubSubClient rc-switch