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.

396 lines
12 KiB

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