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.

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