Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

233 lines
6.3 KiB

  1. // -----------------------------------------------------------------------------
  2. // SenseAir S8 CO2 Sensor
  3. // Uses SoftwareSerial library
  4. // Contribution by Yonsm Guo
  5. // -----------------------------------------------------------------------------
  6. #if SENSOR_SUPPORT && SENSEAIR_SUPPORT
  7. #pragma once
  8. #include <SoftwareSerial.h>
  9. #include "BaseSensor.h"
  10. // SenseAir sensor utils
  11. class SenseAir
  12. {
  13. protected:
  14. SoftwareSerial *_serial; // Should initialized by child class
  15. public:
  16. int sendCommand(byte command[]) {
  17. byte recv_buf[7] = {0xff};
  18. byte data_buf[2] = {0xff};
  19. long value = -1;
  20. _serial->write(command, 8); //Send the byte array
  21. delay(50);
  22. // Read answer from sensor
  23. int ByteCounter = 0;
  24. while(_serial->available()) {
  25. recv_buf[ByteCounter] = _serial->read();
  26. ByteCounter++;
  27. }
  28. data_buf[0] = recv_buf[3];
  29. data_buf[1] = recv_buf[4];
  30. value = (data_buf[0] << 8) | (data_buf[1]);
  31. return value;
  32. }
  33. int readCo2(void) {
  34. int co2 = 0;
  35. byte frame[8] = {0};
  36. buildFrame(0xFE, 0x04, 0x03, 1, frame);
  37. co2 = sendCommand(frame);
  38. return co2;
  39. }
  40. private:
  41. // Compute the MODBUS RTU CRC
  42. static unsigned int modRTU_CRC(byte buf[], int len, byte checkSum[2]) {
  43. unsigned int crc = 0xFFFF;
  44. for (int pos = 0; pos < len; pos++) {
  45. crc ^= (unsigned int)buf[pos]; // XOR byte into least sig. byte of crc
  46. for (int i = 8; i != 0; i--) { // Loop over each bit
  47. if ((crc & 0x0001) != 0) { // If the LSB is set
  48. crc >>= 1; // Shift right and XOR 0xA001
  49. crc ^= 0xA001;
  50. }
  51. else // Else LSB is not set
  52. crc >>= 1; // Just shift right
  53. }
  54. }
  55. // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
  56. checkSum[1] = (byte)((crc >> 8) & 0xFF);
  57. checkSum[0] = (byte)(crc & 0xFF);
  58. return crc;
  59. }
  60. static int getBitOfInt(int reg, int pos) {
  61. // Create a mask
  62. int mask = 0x01 << pos;
  63. // Mask the status register
  64. int masked_register = mask & reg;
  65. // Shift the result of masked register back to position 0
  66. int result = masked_register >> pos;
  67. return result;
  68. }
  69. static void buildFrame(byte slaveAddress,
  70. byte functionCode,
  71. short startAddress,
  72. short numberOfRegisters,
  73. byte frame[8]) {
  74. frame[0] = slaveAddress;
  75. frame[1] = functionCode;
  76. frame[2] = (byte)(startAddress >> 8);
  77. frame[3] = (byte)(startAddress);
  78. frame[4] = (byte)(numberOfRegisters >> 8);
  79. frame[5] = (byte)(numberOfRegisters);
  80. // CRC-calculation
  81. byte checkSum[2] = {0};
  82. modRTU_CRC(frame, 6, checkSum);
  83. frame[6] = checkSum[0];
  84. frame[7] = checkSum[1];
  85. }
  86. };
  87. //
  88. class SenseAirSensor : public BaseSensor, SenseAir {
  89. public:
  90. // ---------------------------------------------------------------------
  91. // Public
  92. // ---------------------------------------------------------------------
  93. SenseAirSensor() {
  94. _count = 1;
  95. _co2 = 0;
  96. _lastCo2 = 0;
  97. _serial = NULL;
  98. _sensor_id = SENSOR_SENSEAIR_ID;
  99. }
  100. ~SenseAirSensor() {
  101. if (_serial) delete _serial;
  102. _serial = NULL;
  103. }
  104. void setRX(unsigned char pin_rx) {
  105. if (_pin_rx == pin_rx) return;
  106. _pin_rx = pin_rx;
  107. _dirty = true;
  108. }
  109. void setTX(unsigned char pin_tx) {
  110. if (_pin_tx == pin_tx) return;
  111. _pin_tx = pin_tx;
  112. _dirty = true;
  113. }
  114. // ---------------------------------------------------------------------
  115. unsigned char getRX() {
  116. return _pin_rx;
  117. }
  118. unsigned char getTX() {
  119. return _pin_tx;
  120. }
  121. // ---------------------------------------------------------------------
  122. // Sensor API
  123. // ---------------------------------------------------------------------
  124. // Initialization method, must be idempotent
  125. void begin() {
  126. if (!_dirty) return;
  127. if (_serial) delete _serial;
  128. _serial = new SoftwareSerial(_pin_rx, _pin_tx, false);
  129. _serial->enableIntTx(false);
  130. _serial->begin(9600);
  131. _serial->enableRx(true);
  132. _startTime = 0;
  133. _ready = true;
  134. _dirty = false;
  135. }
  136. // Descriptive name of the sensor
  137. String description() {
  138. char buffer[28];
  139. snprintf(buffer, sizeof(buffer), "SenseAir S8 @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
  140. return String(buffer);
  141. }
  142. // Descriptive name of the slot # index
  143. String description(unsigned char index) {
  144. return description();
  145. }
  146. // Address of the sensor (it could be the GPIO or I2C address)
  147. String address(unsigned char index) {
  148. char buffer[6];
  149. snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx);
  150. return String(buffer);
  151. }
  152. // Type for slot # index
  153. unsigned char type(unsigned char index) {
  154. return MAGNITUDE_CO2;
  155. }
  156. void pre() {
  157. if (millis() - _startTime < 20000) {
  158. _error = SENSOR_ERROR_WARM_UP;
  159. return;
  160. }
  161. unsigned int co2 = readCo2();
  162. if (co2 >= 5000 || co2 < 100)
  163. {
  164. _co2 = _lastCo2;
  165. _error = SENSOR_ERROR_OUT_OF_RANGE;
  166. }
  167. else
  168. {
  169. _co2 = (co2 > _lastCo2 + 2000) ? _lastCo2 : co2;
  170. _lastCo2 = co2;
  171. _error = SENSOR_ERROR_OK;
  172. }
  173. }
  174. // Current value for slot # index
  175. double value(unsigned char index) {
  176. return _co2;
  177. }
  178. protected:
  179. unsigned int _pin_rx;
  180. unsigned int _pin_tx;
  181. unsigned long _startTime;
  182. unsigned int _co2;
  183. unsigned int _lastCo2;
  184. };
  185. #endif // SENSOR_SUPPORT && SENSEAIR_SUPPORT