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