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.

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