// ----------------------------------------------------------------------------- // SenseAir S8 CO2 Sensor // Uses SoftwareSerial library // Contribution by Yonsm Guo // ----------------------------------------------------------------------------- #if SENSOR_SUPPORT && SENSEAIR_SUPPORT #pragma once #include #include #include "BaseSensor.h" // 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() { _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; } unsigned int co2 = readCo2(); if (co2 >= 5000 || co2 < 100) { _co2 = _lastCo2; _error = SENSOR_ERROR_OUT_OF_RANGE; } else { _co2 = (co2 > _lastCo2 + 2000) ? _lastCo2 : co2; _lastCo2 = co2; _error = SENSOR_ERROR_OK; } } // 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