xnrg_02_cse7766.ino 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /*
  2. xnrg_02_cse7766.ino - CSE7766 energy sensor support for Sonoff-Tasmota
  3. Copyright (C) 2018 Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_ENERGY_SENSOR
  16. #ifdef USE_CSE7766
  17. /*********************************************************************************************\
  18. * CSE7766 - Energy (Sonoff S31 and Sonoff Pow R2)
  19. *
  20. * Based on datasheet from http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf
  21. \*********************************************************************************************/
  22. #define XNRG_02 2
  23. #define CSE_MAX_INVALID_POWER 128 // Number of invalid power receipts before deciding active power is zero
  24. #define CSE_NOT_CALIBRATED 0xAA
  25. #define CSE_PULSES_NOT_INITIALIZED -1
  26. #define CSE_PREF 1000
  27. #define CSE_UREF 100
  28. uint8_t cse_receive_flag = 0;
  29. long voltage_cycle = 0;
  30. long current_cycle = 0;
  31. long power_cycle = 0;
  32. unsigned long power_cycle_first = 0;
  33. long cf_pulses = 0;
  34. long cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED;
  35. uint8_t cse_power_invalid = CSE_MAX_INVALID_POWER;
  36. void CseReceived(void)
  37. {
  38. // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
  39. // 55 5A 02 F7 60 00 03 5A 00 40 10 04 8B 9F 51 A6 58 18 72 75 61 AC A1 30 - Power not valid (load below 5W)
  40. // 55 5A 02 F7 60 00 03 AB 00 40 10 02 60 5D 51 A6 58 03 E9 EF 71 0B 7A 36
  41. // Hd Id VCal---- Voltage- ICal---- Current- PCal---- Power--- Ad CF--- Ck
  42. uint8_t header = serial_in_buffer[0];
  43. if ((header & 0xFC) == 0xFC) {
  44. AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Abnormal hardware"));
  45. return;
  46. }
  47. // Get chip calibration data (coefficients) and use as initial defaults
  48. if (HLW_UREF_PULSE == Settings.energy_voltage_calibration) {
  49. long voltage_coefficient = 191200; // uSec
  50. if (CSE_NOT_CALIBRATED != header) {
  51. voltage_coefficient = serial_in_buffer[2] << 16 | serial_in_buffer[3] << 8 | serial_in_buffer[4];
  52. }
  53. Settings.energy_voltage_calibration = voltage_coefficient / CSE_UREF;
  54. }
  55. if (HLW_IREF_PULSE == Settings.energy_current_calibration) {
  56. long current_coefficient = 16140; // uSec
  57. if (CSE_NOT_CALIBRATED != header) {
  58. current_coefficient = serial_in_buffer[8] << 16 | serial_in_buffer[9] << 8 | serial_in_buffer[10];
  59. }
  60. Settings.energy_current_calibration = current_coefficient;
  61. }
  62. if (HLW_PREF_PULSE == Settings.energy_power_calibration) {
  63. long power_coefficient = 5364000; // uSec
  64. if (CSE_NOT_CALIBRATED != header) {
  65. power_coefficient = serial_in_buffer[14] << 16 | serial_in_buffer[15] << 8 | serial_in_buffer[16];
  66. }
  67. Settings.energy_power_calibration = power_coefficient / CSE_PREF;
  68. }
  69. uint8_t adjustement = serial_in_buffer[20];
  70. voltage_cycle = serial_in_buffer[5] << 16 | serial_in_buffer[6] << 8 | serial_in_buffer[7];
  71. current_cycle = serial_in_buffer[11] << 16 | serial_in_buffer[12] << 8 | serial_in_buffer[13];
  72. power_cycle = serial_in_buffer[17] << 16 | serial_in_buffer[18] << 8 | serial_in_buffer[19];
  73. cf_pulses = serial_in_buffer[21] << 8 | serial_in_buffer[22];
  74. if (energy_power_on) { // Powered on
  75. if (adjustement & 0x40) { // Voltage valid
  76. energy_voltage = (float)(Settings.energy_voltage_calibration * CSE_UREF) / (float)voltage_cycle;
  77. }
  78. if (adjustement & 0x10) { // Power valid
  79. cse_power_invalid = 0;
  80. if ((header & 0xF2) == 0xF2) { // Power cycle exceeds range
  81. energy_active_power = 0;
  82. } else {
  83. if (0 == power_cycle_first) { power_cycle_first = power_cycle; } // Skip first incomplete power_cycle
  84. if (power_cycle_first != power_cycle) {
  85. power_cycle_first = -1;
  86. energy_active_power = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)power_cycle;
  87. } else {
  88. energy_active_power = 0;
  89. }
  90. }
  91. } else {
  92. if (cse_power_invalid < CSE_MAX_INVALID_POWER) { // Allow measurements down to about 1W
  93. cse_power_invalid++;
  94. } else {
  95. power_cycle_first = 0;
  96. energy_active_power = 0; // Powered on but no load
  97. }
  98. }
  99. if (adjustement & 0x20) { // Current valid
  100. if (0 == energy_active_power) {
  101. energy_current = 0;
  102. } else {
  103. energy_current = (float)Settings.energy_current_calibration / (float)current_cycle;
  104. }
  105. }
  106. } else { // Powered off
  107. power_cycle_first = 0;
  108. energy_voltage = 0;
  109. energy_active_power = 0;
  110. energy_current = 0;
  111. }
  112. }
  113. bool CseSerialInput(void)
  114. {
  115. if (cse_receive_flag) {
  116. serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
  117. if (24 == serial_in_byte_counter) {
  118. AddLogSerial(LOG_LEVEL_DEBUG_MORE);
  119. uint8_t checksum = 0;
  120. for (byte i = 2; i < 23; i++) { checksum += serial_in_buffer[i]; }
  121. if (checksum == serial_in_buffer[23]) {
  122. CseReceived();
  123. cse_receive_flag = 0;
  124. return 1;
  125. } else {
  126. AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE));
  127. do { // Sync buffer with data (issue #1907 and #3425)
  128. memmove(serial_in_buffer, serial_in_buffer +1, 24);
  129. serial_in_byte_counter--;
  130. } while ((serial_in_byte_counter > 2) && (0x5A != serial_in_buffer[1]));
  131. if (0x5A != serial_in_buffer[1]) {
  132. cse_receive_flag = 0;
  133. serial_in_byte_counter = 0;
  134. }
  135. }
  136. }
  137. } else {
  138. if ((0x5A == serial_in_byte) && (1 == serial_in_byte_counter)) { // 0x5A - Packet header 2
  139. cse_receive_flag = 1;
  140. } else {
  141. serial_in_byte_counter = 0;
  142. }
  143. serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
  144. }
  145. serial_in_byte = 0; // Discard
  146. return 0;
  147. }
  148. /********************************************************************************************/
  149. void CseEverySecond(void)
  150. {
  151. long cf_frequency = 0;
  152. if (CSE_PULSES_NOT_INITIALIZED == cf_pulses_last_time) {
  153. cf_pulses_last_time = cf_pulses; // Init after restart
  154. } else {
  155. if (cf_pulses < cf_pulses_last_time) { // Rolled over after 65535 pulses
  156. cf_frequency = (65536 - cf_pulses_last_time) + cf_pulses;
  157. } else {
  158. cf_frequency = cf_pulses - cf_pulses_last_time;
  159. }
  160. if (cf_frequency && energy_active_power) {
  161. cf_pulses_last_time = cf_pulses;
  162. energy_kWhtoday_delta += (cf_frequency * Settings.energy_power_calibration) / 36;
  163. EnergyUpdateToday();
  164. }
  165. }
  166. }
  167. void CseDrvInit(void)
  168. {
  169. if (!energy_flg) {
  170. if ((SONOFF_S31 == Settings.module) || (SONOFF_POW_R2 == Settings.module)) { // Sonoff S31 or Sonoff Pow R2
  171. baudrate = 4800;
  172. serial_config = SERIAL_8E1;
  173. energy_flg = XNRG_02;
  174. }
  175. }
  176. }
  177. boolean CseCommand(void)
  178. {
  179. boolean serviced = true;
  180. if (CMND_POWERSET == energy_command_code) {
  181. if (XdrvMailbox.data_len && power_cycle) {
  182. Settings.energy_power_calibration = ((unsigned long)CharToDouble(XdrvMailbox.data) * power_cycle) / CSE_PREF;
  183. }
  184. }
  185. else if (CMND_VOLTAGESET == energy_command_code) {
  186. if (XdrvMailbox.data_len && voltage_cycle) {
  187. Settings.energy_voltage_calibration = ((unsigned long)CharToDouble(XdrvMailbox.data) * voltage_cycle) / CSE_UREF;
  188. }
  189. }
  190. else if (CMND_CURRENTSET == energy_command_code) {
  191. if (XdrvMailbox.data_len && current_cycle) {
  192. Settings.energy_current_calibration = ((unsigned long)CharToDouble(XdrvMailbox.data) * current_cycle) / 1000;
  193. }
  194. }
  195. else serviced = false; // Unknown command
  196. return serviced;
  197. }
  198. /*********************************************************************************************\
  199. * Interface
  200. \*********************************************************************************************/
  201. int Xnrg02(byte function)
  202. {
  203. int result = 0;
  204. if (FUNC_PRE_INIT == function) {
  205. CseDrvInit();
  206. }
  207. else if (XNRG_02 == energy_flg) {
  208. switch (function) {
  209. case FUNC_EVERY_SECOND:
  210. CseEverySecond();
  211. break;
  212. case FUNC_COMMAND:
  213. result = CseCommand();
  214. break;
  215. case FUNC_SERIAL:
  216. result = CseSerialInput();
  217. break;
  218. }
  219. }
  220. return result;
  221. }
  222. #endif // USE_CSE7766
  223. #endif // USE_ENERGY_SENSOR