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..646c8e91 100644 --- a/code/espurna/config/types.h +++ b/code/espurna/config/types.h @@ -249,6 +249,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/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/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