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.

273 lines
8.4 KiB

  1. // -----------------------------------------------------------------------------
  2. // CSE7766 based power monitor
  3. // Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
  4. // http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf
  5. // -----------------------------------------------------------------------------
  6. #if SENSOR_SUPPORT && CSE7766_SUPPORT
  7. #pragma once
  8. #include "Arduino.h"
  9. #include "BaseSensor.h"
  10. #include <SoftwareSerial.h>
  11. class CSE7766Sensor : public BaseSensor {
  12. public:
  13. // ---------------------------------------------------------------------
  14. // Public
  15. // ---------------------------------------------------------------------
  16. CSE7766Sensor(): BaseSensor(), _data() {
  17. _count = 4;
  18. _sensor_id = SENSOR_CSE7766_ID;
  19. }
  20. ~CSE7766Sensor() {
  21. if (_serial) delete _serial;
  22. }
  23. // ---------------------------------------------------------------------
  24. void setRX(unsigned char pin_rx) {
  25. if (_pin_rx == pin_rx) return;
  26. _pin_rx = pin_rx;
  27. _dirty = true;
  28. }
  29. void setInverted(bool inverted) {
  30. if (_inverted == inverted) return;
  31. _inverted = inverted;
  32. _dirty = true;
  33. }
  34. // ---------------------------------------------------------------------
  35. unsigned char getRX() {
  36. return _pin_rx;
  37. }
  38. bool getInverted() {
  39. return _inverted;
  40. }
  41. // ---------------------------------------------------------------------
  42. // Sensor API
  43. // ---------------------------------------------------------------------
  44. // Initialization method, must be idempotent
  45. void begin() {
  46. if (!_dirty) return;
  47. if (_serial) delete _serial;
  48. _serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
  49. _serial->enableIntTx(false);
  50. _serial->begin(CSE7766_BAUDRATE);
  51. _ready = true;
  52. _dirty = false;
  53. }
  54. // Descriptive name of the sensor
  55. String description() {
  56. char buffer[28];
  57. snprintf(buffer, sizeof(buffer), "CSE7766 @ SwSerial(%u,NULL)", _pin_rx);
  58. return String(buffer);
  59. }
  60. // Descriptive name of the slot # index
  61. String slot(unsigned char index) {
  62. return description();
  63. };
  64. // Address of the sensor (it could be the GPIO or I2C address)
  65. String address(unsigned char index) {
  66. return String(_pin_rx);
  67. }
  68. // Loop-like method, call it in your main loop
  69. void tick() {
  70. _read();
  71. }
  72. // Type for slot # index
  73. unsigned char type(unsigned char index) {
  74. if (index == 0) return MAGNITUDE_CURRENT;
  75. if (index == 1) return MAGNITUDE_VOLTAGE;
  76. if (index == 2) return MAGNITUDE_POWER_ACTIVE;
  77. if (index == 3) return MAGNITUDE_ENERGY;
  78. return MAGNITUDE_NONE;
  79. }
  80. // Current value for slot # index
  81. double value(unsigned char index) {
  82. if (index == 0) return _current;
  83. if (index == 1) return _voltage;
  84. if (index == 2) return _active;
  85. if (index == 3) return _energy;
  86. return 0;
  87. }
  88. protected:
  89. // ---------------------------------------------------------------------
  90. // Protected
  91. // ---------------------------------------------------------------------
  92. /**
  93. * "
  94. * Checksum is the sum of all data
  95. * except for packet header and packet tail lowering by 8bit (...)
  96. * "
  97. * @return bool
  98. */
  99. bool _checksum() {
  100. unsigned char checksum = 0;
  101. for (unsigned char i = 2; i < 23; i++) {
  102. checksum += _data[i];
  103. }
  104. return checksum == _data[23];
  105. }
  106. void _process() {
  107. // Checksum
  108. if (!_checksum()) {
  109. _error = SENSOR_ERROR_CRC;
  110. #if SENSOR_DEBUG
  111. DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Checksum error"));
  112. #endif
  113. return;
  114. }
  115. // Calibration
  116. if (0xAA == _data[0]) {
  117. _error = SENSOR_ERROR_CALIBRATION;
  118. #if SENSOR_DEBUG
  119. DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Chip not calibrated"));
  120. #endif
  121. return;
  122. }
  123. if ((_data[0] & 0xFC) > 0xF0) {
  124. _error = SENSOR_ERROR_OTHER;
  125. #if SENSOR_DEBUG
  126. if (0xF1 == _data[0] & 0xF1) DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Abnormal coefficient storage area"));
  127. if (0xF2 == _data[0] & 0xF2) DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Power cycle exceeded range"));
  128. if (0xF4 == _data[0] & 0xF4) DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Current cycle exceeded range"));
  129. if (0xF8 == _data[0] & 0xF8) DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Voltage cycle exceeded range"));
  130. #endif
  131. return;
  132. }
  133. // Calibration coefficients
  134. if (0 == _coefV) {
  135. _coefV = (_data[2] << 16 | _data[3] << 8 | _data[4]) / 100;
  136. _coefV *= 100;
  137. _coefC = (_data[8] << 16 | _data[9] << 8 | _data[10]);
  138. _coefP = (_data[14] << 16 | _data[15] << 8 | _data[16]) / 1000;
  139. _coefP *= 1000;
  140. }
  141. // Adj: this looks like a sampling report
  142. uint8_t adj = _data[20];
  143. // Calculate voltage
  144. _voltage = 0;
  145. if ((adj & 0x40) == 0x40) {
  146. unsigned long voltage_cycle = _data[5] << 16 | _data[6] << 8 | _data[7];
  147. _voltage = _coefV / voltage_cycle / CSE7766_V2R;
  148. }
  149. // Calculate power
  150. _active = 0;
  151. if ((adj & 0x10) == 0x10) {
  152. if ((_data[0] & 0xF2) != 0xF2) {
  153. unsigned long power_cycle = _data[17] << 16 | _data[18] << 8 | _data[19];
  154. _active = _coefP / power_cycle / CSE7766_V1R / CSE7766_V2R;
  155. }
  156. }
  157. // Calculate current
  158. _current = 0;
  159. if ((adj & 0x20) == 0x20) {
  160. if (_active > 0) {
  161. unsigned long current_cycle = _data[11] << 16 | _data[12] << 8 | _data[13];
  162. _current = _coefC / current_cycle / CSE7766_V1R;
  163. }
  164. }
  165. // Calculate energy
  166. /*
  167. static unsigned long cf_pulses_last = 0;
  168. unsigned long cf_pulses = _data[21] << 8 | _data[22];
  169. unsigned long frequency = cf_pulses - cf_pulses_last;
  170. cf_pulses_last = cf_pulses;
  171. _energy += (100000 * frequency * _coefP);
  172. */
  173. }
  174. void _read() {
  175. _error = SENSOR_ERROR_OK;
  176. static unsigned char index = 0;
  177. static unsigned long last = millis();
  178. while (_serial->available()) {
  179. // A 24 bytes message takes ~55ms to go through at 4800 bps
  180. // Reset counter if more than 1000ms have passed since last byte.
  181. if (millis() - last > CSE7766_SYNC_INTERVAL) index = 0;
  182. last = millis();
  183. uint8_t byte = _serial->read();
  184. // second byte in packet must be 0x5A
  185. if ((1 == index) && (0xA5 != byte)) {
  186. index = 0;
  187. } else {
  188. _data[index++] = byte;
  189. if (index > 23) {
  190. _serial->flush();
  191. break;
  192. }
  193. }
  194. }
  195. // Process packet
  196. if (24 == index) {
  197. _process();
  198. index = 0;
  199. }
  200. }
  201. // ---------------------------------------------------------------------
  202. unsigned int _pin_rx = CSE7766_PIN;
  203. bool _inverted = CSE7766_PIN_INVERSE;
  204. SoftwareSerial * _serial = NULL;
  205. double _active = 0;
  206. double _voltage = 0;
  207. double _current = 0;
  208. double _energy = 0;
  209. unsigned long _coefV = 0;
  210. unsigned long _coefC = 0;
  211. unsigned long _coefP = 0;
  212. unsigned char _data[24];
  213. };
  214. #endif // SENSOR_SUPPORT && CSE7766_SUPPORT