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.

372 lines
11 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. void expectedCurrent(double expected) {
  43. if ((expected > 0) && (_current > 0)) {
  44. _ratioC = _ratioC * (expected / _current);
  45. }
  46. }
  47. void expectedVoltage(unsigned int expected) {
  48. if ((expected > 0) && (_voltage > 0)) {
  49. _ratioV = _ratioV * (expected / _voltage);
  50. }
  51. }
  52. void expectedPower(unsigned int expected) {
  53. if ((expected > 0) && (_active > 0)) {
  54. _ratioP = _ratioP * (expected / _active);
  55. }
  56. }
  57. void setCurrentRatio(double value) {
  58. _ratioC = value;
  59. };
  60. void setVoltageRatio(double value) {
  61. _ratioV = value;
  62. };
  63. void setPowerRatio(double value) {
  64. _ratioP = value;
  65. };
  66. double getCurrentRatio() {
  67. return _ratioC;
  68. };
  69. double getVoltageRatio() {
  70. return _ratioV;
  71. };
  72. double getPowerRatio() {
  73. return _ratioP;
  74. };
  75. void resetRatios() {
  76. _ratioC = _ratioV = _ratioP = 1.0;
  77. }
  78. void resetEnergy() {
  79. _energy = 0;
  80. }
  81. // ---------------------------------------------------------------------
  82. // Sensor API
  83. // ---------------------------------------------------------------------
  84. // Initialization method, must be idempotent
  85. void begin() {
  86. if (!_dirty) return;
  87. if (_serial) delete _serial;
  88. if (1 == _pin_rx) {
  89. Serial.begin(CSE7766_BAUDRATE);
  90. } else {
  91. _serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
  92. _serial->enableIntTx(false);
  93. _serial->begin(CSE7766_BAUDRATE);
  94. }
  95. _ready = true;
  96. _dirty = false;
  97. }
  98. // Descriptive name of the sensor
  99. String description() {
  100. char buffer[28];
  101. if (1 == _pin_rx) {
  102. snprintf(buffer, sizeof(buffer), "CSE7766 @ HwSerial");
  103. } else {
  104. snprintf(buffer, sizeof(buffer), "CSE7766 @ SwSerial(%u,NULL)", _pin_rx);
  105. }
  106. return String(buffer);
  107. }
  108. // Descriptive name of the slot # index
  109. String slot(unsigned char index) {
  110. return description();
  111. };
  112. // Address of the sensor (it could be the GPIO or I2C address)
  113. String address(unsigned char index) {
  114. return String(_pin_rx);
  115. }
  116. // Loop-like method, call it in your main loop
  117. void tick() {
  118. _read();
  119. }
  120. // Type for slot # index
  121. unsigned char type(unsigned char index) {
  122. if (index == 0) return MAGNITUDE_CURRENT;
  123. if (index == 1) return MAGNITUDE_VOLTAGE;
  124. if (index == 2) return MAGNITUDE_POWER_ACTIVE;
  125. if (index == 3) return MAGNITUDE_ENERGY;
  126. return MAGNITUDE_NONE;
  127. }
  128. // Current value for slot # index
  129. double value(unsigned char index) {
  130. if (index == 0) return _current;
  131. if (index == 1) return _voltage;
  132. if (index == 2) return _active;
  133. if (index == 3) return _energy;
  134. return 0;
  135. }
  136. protected:
  137. // ---------------------------------------------------------------------
  138. // Protected
  139. // ---------------------------------------------------------------------
  140. /**
  141. * "
  142. * Checksum is the sum of all data
  143. * except for packet header and packet tail lowering by 8bit (...)
  144. * "
  145. * @return bool
  146. */
  147. bool _checksum() {
  148. unsigned char checksum = 0;
  149. for (unsigned char i = 2; i < 23; i++) {
  150. checksum += _data[i];
  151. }
  152. return checksum == _data[23];
  153. }
  154. void _process() {
  155. // Sample data:
  156. // 55 5A 02 E9 50 00 03 31 00 3E 9E 00 0D 30 4F 44 F8 00 12 65 F1 81 76 72 (w/ load)
  157. // F2 5A 02 E9 50 00 03 2B 00 3E 9E 02 D7 7C 4F 44 F8 CF A5 5D E1 B3 2A B4 (w/o load)
  158. #if SENSOR_DEBUG
  159. DEBUG_MSG("[SENSOR] CSE7766: _process: ");
  160. for (byte i=0; i<24; i++) DEBUG_MSG("%02X ", _data[i]);
  161. DEBUG_MSG("\n");
  162. #endif
  163. // Checksum
  164. if (!_checksum()) {
  165. _error = SENSOR_ERROR_CRC;
  166. #if SENSOR_DEBUG
  167. DEBUG_MSG("[SENSOR] CSE7766: Checksum error\n");
  168. #endif
  169. return;
  170. }
  171. // Calibration
  172. if (0xAA == _data[0]) {
  173. _error = SENSOR_ERROR_CALIBRATION;
  174. #if SENSOR_DEBUG
  175. DEBUG_MSG("[SENSOR] CSE7766: Chip not calibrated\n");
  176. #endif
  177. return;
  178. }
  179. if ((_data[0] & 0xFC) > 0xF0) {
  180. _error = SENSOR_ERROR_OTHER;
  181. #if SENSOR_DEBUG
  182. if (0xF1 == _data[0] & 0xF1) DEBUG_MSG("[SENSOR] CSE7766: Abnormal coefficient storage area\n");
  183. if (0xF2 == _data[0] & 0xF2) DEBUG_MSG("[SENSOR] CSE7766: Power cycle exceeded range\n");
  184. if (0xF4 == _data[0] & 0xF4) DEBUG_MSG("[SENSOR] CSE7766: Current cycle exceeded range\n");
  185. if (0xF8 == _data[0] & 0xF8) DEBUG_MSG("[SENSOR] CSE7766: Voltage cycle exceeded range\n");
  186. #endif
  187. return;
  188. }
  189. // Calibration coefficients
  190. unsigned long _coefV = (_data[2] << 16 | _data[3] << 8 | _data[4] ); // 190770
  191. unsigned long _coefC = (_data[8] << 16 | _data[9] << 8 | _data[10]); // 16030
  192. unsigned long _coefP = (_data[14] << 16 | _data[15] << 8 | _data[16]); // 5195000
  193. // Adj: this looks like a sampling report
  194. uint8_t adj = _data[20]; // F1 11110001
  195. // Calculate voltage
  196. _voltage = 0;
  197. if ((adj & 0x40) == 0x40) {
  198. unsigned long voltage_cycle = _data[5] << 16 | _data[6] << 8 | _data[7]; // 817
  199. _voltage = _ratioV * _coefV / voltage_cycle / CSE7766_V2R; // 190700 / 817 = 233.41
  200. }
  201. // Calculate power
  202. _active = 0;
  203. if ((adj & 0x10) == 0x10) {
  204. if ((_data[0] & 0xF2) != 0xF2) {
  205. unsigned long power_cycle = _data[17] << 16 | _data[18] << 8 | _data[19]; // 4709
  206. _active = _ratioP * _coefP / power_cycle / CSE7766_V1R / CSE7766_V2R; // 5195000 / 4709 = 1103.20
  207. }
  208. }
  209. // Calculate current
  210. _current = 0;
  211. if ((adj & 0x20) == 0x20) {
  212. if (_active > 0) {
  213. unsigned long current_cycle = _data[11] << 16 | _data[12] << 8 | _data[13]; // 3376
  214. _current = _ratioC * _coefC / current_cycle / CSE7766_V1R; // 16030 / 3376 = 4.75
  215. }
  216. }
  217. // Calculate energy
  218. static unsigned long cf_pulses_last = 0;
  219. unsigned long cf_pulses = _data[21] << 8 | _data[22];
  220. if (0 == cf_pulses_last) cf_pulses_last = cf_pulses;
  221. _energy += (cf_pulses - cf_pulses_last) * (float) _coefP / 1000000.0;
  222. cf_pulses_last = cf_pulses;
  223. }
  224. void _read() {
  225. _error = SENSOR_ERROR_OK;
  226. static unsigned char index = 0;
  227. static unsigned long last = millis();
  228. while (_serial_available()) {
  229. // A 24 bytes message takes ~55ms to go through at 4800 bps
  230. // Reset counter if more than 1000ms have passed since last byte.
  231. if (millis() - last > CSE7766_SYNC_INTERVAL) index = 0;
  232. last = millis();
  233. uint8_t byte = _serial_read();
  234. // first byte must be 0x55 or 0xF?
  235. if (0 == index) {
  236. if ((0x55 != byte) && (byte < 0xF0)) {
  237. continue;
  238. }
  239. // second byte must be 0x5A
  240. } else if (1 == index) {
  241. if (0x5A != byte) {
  242. index = 0;
  243. continue;
  244. }
  245. }
  246. _data[index++] = byte;
  247. if (index > 23) {
  248. _serial_flush();
  249. break;
  250. }
  251. }
  252. // Process packet
  253. if (24 == index) {
  254. _process();
  255. index = 0;
  256. }
  257. }
  258. // ---------------------------------------------------------------------
  259. bool _serial_available() {
  260. if (1 == _pin_rx) {
  261. return Serial.available();
  262. } else {
  263. return _serial->available();
  264. }
  265. }
  266. void _serial_flush() {
  267. if (1 == _pin_rx) {
  268. return Serial.flush();
  269. } else {
  270. return _serial->flush();
  271. }
  272. }
  273. uint8_t _serial_read() {
  274. if (1 == _pin_rx) {
  275. return Serial.read();
  276. } else {
  277. return _serial->read();
  278. }
  279. }
  280. // ---------------------------------------------------------------------
  281. unsigned int _pin_rx = CSE7766_PIN;
  282. bool _inverted = CSE7766_PIN_INVERSE;
  283. SoftwareSerial * _serial = NULL;
  284. double _active = 0;
  285. double _voltage = 0;
  286. double _current = 0;
  287. double _energy = 0;
  288. double _ratioV = 1.0;
  289. double _ratioC = 1.0;
  290. double _ratioP = 1.0;
  291. unsigned char _data[24];
  292. };
  293. #endif // SENSOR_SUPPORT && CSE7766_SUPPORT