xdrv_03_energy.ino 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. /*
  2. xdrv_03_energy.ino - 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. /*********************************************************************************************\
  17. * Energy
  18. \*********************************************************************************************/
  19. #define XDRV_03 3
  20. #define XSNS_03 3
  21. #define ENERGY_NONE 0
  22. #define FEATURE_POWER_LIMIT true
  23. #include <Ticker.h>
  24. enum EnergyCommands {
  25. CMND_POWERDELTA,
  26. CMND_POWERLOW, CMND_POWERHIGH, CMND_VOLTAGELOW, CMND_VOLTAGEHIGH, CMND_CURRENTLOW, CMND_CURRENTHIGH,
  27. CMND_POWERSET, CMND_VOLTAGESET, CMND_CURRENTSET, CMND_FREQUENCYSET,
  28. CMND_ENERGYRESET, CMND_MAXENERGY, CMND_MAXENERGYSTART,
  29. CMND_MAXPOWER, CMND_MAXPOWERHOLD, CMND_MAXPOWERWINDOW,
  30. CMND_SAFEPOWER, CMND_SAFEPOWERHOLD, CMND_SAFEPOWERWINDOW };
  31. const char kEnergyCommands[] PROGMEM =
  32. D_CMND_POWERDELTA "|"
  33. D_CMND_POWERLOW "|" D_CMND_POWERHIGH "|" D_CMND_VOLTAGELOW "|" D_CMND_VOLTAGEHIGH "|" D_CMND_CURRENTLOW "|" D_CMND_CURRENTHIGH "|"
  34. D_CMND_POWERSET "|" D_CMND_VOLTAGESET "|" D_CMND_CURRENTSET "|" D_CMND_FREQUENCYSET "|"
  35. D_CMND_ENERGYRESET "|" D_CMND_MAXENERGY "|" D_CMND_MAXENERGYSTART "|"
  36. D_CMND_MAXPOWER "|" D_CMND_MAXPOWERHOLD "|" D_CMND_MAXPOWERWINDOW "|"
  37. D_CMND_SAFEPOWER "|" D_CMND_SAFEPOWERHOLD "|" D_CMND_SAFEPOWERWINDOW ;
  38. float energy_voltage = 0; // 123.1 V
  39. float energy_current = 0; // 123.123 A
  40. float energy_active_power = 0; // 123.1 W
  41. float energy_apparent_power = NAN; // 123.1 VA
  42. float energy_reactive_power = NAN; // 123.1 VAr
  43. float energy_power_factor = NAN; // 0.12
  44. float energy_frequency = NAN; // 123.1 Hz
  45. float energy_start = 0; // 12345.12345 kWh total previous
  46. float energy_daily = 0; // 123.123 kWh
  47. float energy_total = 0; // 12345.12345 kWh
  48. unsigned long energy_kWhtoday_delta = 0; // 1212312345 Wh 10^-5 (deca micro Watt hours) - Overflows to energy_kWhtoday (HLW and CSE only)
  49. unsigned long energy_kWhtoday; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = energy_daily
  50. unsigned long energy_period = 0; // 12312312 Wh * 10^-2 (deca milli Watt hours) - 5764 = 0.05764 kWh = 0.058 kWh = energy_daily
  51. float energy_power_last[3] = { 0 };
  52. uint8_t energy_power_delta = 0;
  53. bool energy_type_dc = false;
  54. bool energy_power_on = true;
  55. byte energy_min_power_flag = 0;
  56. byte energy_max_power_flag = 0;
  57. byte energy_min_voltage_flag = 0;
  58. byte energy_max_voltage_flag = 0;
  59. byte energy_min_current_flag = 0;
  60. byte energy_max_current_flag = 0;
  61. byte energy_power_steady_cntr = 8; // Allow for power on stabilization
  62. byte energy_max_energy_state = 0;
  63. #if FEATURE_POWER_LIMIT
  64. byte energy_mplr_counter = 0;
  65. uint16_t energy_mplh_counter = 0;
  66. uint16_t energy_mplw_counter = 0;
  67. #endif // FEATURE_POWER_LIMIT
  68. byte energy_fifth_second = 0;
  69. Ticker ticker_energy;
  70. int energy_command_code = 0;
  71. /********************************************************************************************/
  72. void EnergyUpdateToday(void)
  73. {
  74. if (energy_kWhtoday_delta > 1000) {
  75. unsigned long delta = energy_kWhtoday_delta / 1000;
  76. energy_kWhtoday_delta -= (delta * 1000);
  77. energy_kWhtoday += delta;
  78. }
  79. RtcSettings.energy_kWhtoday = energy_kWhtoday;
  80. energy_daily = (float)energy_kWhtoday / 100000;
  81. energy_total = (float)(RtcSettings.energy_kWhtotal + energy_kWhtoday) / 100000;
  82. }
  83. /*********************************************************************************************/
  84. void Energy200ms(void)
  85. {
  86. energy_power_on = (power != 0) | Settings.flag.no_power_on_check;
  87. energy_fifth_second++;
  88. if (5 == energy_fifth_second) {
  89. energy_fifth_second = 0;
  90. XnrgCall(FUNC_EVERY_SECOND);
  91. if (RtcTime.valid) {
  92. if (LocalTime() == Midnight()) {
  93. Settings.energy_kWhyesterday = energy_kWhtoday;
  94. Settings.energy_kWhtotal += energy_kWhtoday;
  95. RtcSettings.energy_kWhtotal = Settings.energy_kWhtotal;
  96. energy_kWhtoday = 0;
  97. energy_kWhtoday_delta = 0;
  98. energy_period = energy_kWhtoday;
  99. EnergyUpdateToday();
  100. energy_max_energy_state = 3;
  101. }
  102. if ((RtcTime.hour == Settings.energy_max_energy_start) && (3 == energy_max_energy_state)) {
  103. energy_max_energy_state = 0;
  104. }
  105. }
  106. }
  107. XnrgCall(FUNC_EVERY_200_MSECOND);
  108. }
  109. void EnergySaveState(void)
  110. {
  111. Settings.energy_kWhdoy = (RtcTime.valid) ? RtcTime.day_of_year : 0;
  112. Settings.energy_kWhtoday = energy_kWhtoday;
  113. RtcSettings.energy_kWhtoday = energy_kWhtoday;
  114. Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal;
  115. }
  116. boolean EnergyMargin(byte type, uint16_t margin, uint16_t value, byte &flag, byte &save_flag)
  117. {
  118. byte change;
  119. if (!margin) return false;
  120. change = save_flag;
  121. if (type) {
  122. flag = (value > margin);
  123. } else {
  124. flag = (value < margin);
  125. }
  126. save_flag = flag;
  127. return (change != save_flag);
  128. }
  129. void EnergySetPowerSteadyCounter(void)
  130. {
  131. energy_power_steady_cntr = 2;
  132. }
  133. void EnergyMarginCheck(void)
  134. {
  135. uint16_t energy_daily_u = 0;
  136. uint16_t energy_power_u = 0;
  137. uint16_t energy_voltage_u = 0;
  138. uint16_t energy_current_u = 0;
  139. boolean flag;
  140. boolean jsonflg;
  141. if (energy_power_steady_cntr) {
  142. energy_power_steady_cntr--;
  143. return;
  144. }
  145. if (Settings.energy_power_delta) {
  146. float delta = abs(energy_power_last[0] - energy_active_power);
  147. // Any delta compared to minimal delta
  148. float min_power = (energy_power_last[0] > energy_active_power) ? energy_active_power : energy_power_last[0];
  149. if (((delta / min_power) * 100) > Settings.energy_power_delta) {
  150. energy_power_delta = 1;
  151. energy_power_last[1] = energy_active_power; // We only want one report so reset history
  152. energy_power_last[2] = energy_active_power;
  153. }
  154. }
  155. energy_power_last[0] = energy_power_last[1]; // Shift in history every second allowing power changes to settle for up to three seconds
  156. energy_power_last[1] = energy_power_last[2];
  157. energy_power_last[2] = energy_active_power;
  158. if (energy_power_on && (Settings.energy_min_power || Settings.energy_max_power || Settings.energy_min_voltage || Settings.energy_max_voltage || Settings.energy_min_current || Settings.energy_max_current)) {
  159. energy_power_u = (uint16_t)(energy_active_power);
  160. energy_voltage_u = (uint16_t)(energy_voltage);
  161. energy_current_u = (uint16_t)(energy_current * 1000);
  162. // snprintf_P(log_data, sizeof(log_data), PSTR("NRG: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u);
  163. // AddLog(LOG_LEVEL_DEBUG);
  164. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{"));
  165. jsonflg = 0;
  166. if (EnergyMargin(0, Settings.energy_min_power, energy_power_u, flag, energy_min_power_flag)) {
  167. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_POWERLOW "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag));
  168. jsonflg = 1;
  169. }
  170. if (EnergyMargin(1, Settings.energy_max_power, energy_power_u, flag, energy_max_power_flag)) {
  171. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_POWERHIGH "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag));
  172. jsonflg = 1;
  173. }
  174. if (EnergyMargin(0, Settings.energy_min_voltage, energy_voltage_u, flag, energy_min_voltage_flag)) {
  175. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_VOLTAGELOW "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag));
  176. jsonflg = 1;
  177. }
  178. if (EnergyMargin(1, Settings.energy_max_voltage, energy_voltage_u, flag, energy_max_voltage_flag)) {
  179. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_VOLTAGEHIGH "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag));
  180. jsonflg = 1;
  181. }
  182. if (EnergyMargin(0, Settings.energy_min_current, energy_current_u, flag, energy_min_current_flag)) {
  183. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_CURRENTLOW "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag));
  184. jsonflg = 1;
  185. }
  186. if (EnergyMargin(1, Settings.energy_max_current, energy_current_u, flag, energy_max_current_flag)) {
  187. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_CURRENTHIGH "\":\"%s\""), mqtt_data, (jsonflg)?",":"", GetStateText(flag));
  188. jsonflg = 1;
  189. }
  190. if (jsonflg) {
  191. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
  192. MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_MARGINS), MQTT_TELE_RETAIN);
  193. EnergyMqttShow();
  194. }
  195. }
  196. #if FEATURE_POWER_LIMIT
  197. // Max Power
  198. if (Settings.energy_max_power_limit) {
  199. if (energy_active_power > Settings.energy_max_power_limit) {
  200. if (!energy_mplh_counter) {
  201. energy_mplh_counter = Settings.energy_max_power_limit_hold;
  202. } else {
  203. energy_mplh_counter--;
  204. if (!energy_mplh_counter) {
  205. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_MAXPOWERREACHED "\":\"%d%s\"}"), energy_power_u, (Settings.flag.value_units) ? " " D_UNIT_WATT : "");
  206. MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING);
  207. EnergyMqttShow();
  208. ExecuteCommandPower(1, POWER_OFF, SRC_MAXPOWER);
  209. if (!energy_mplr_counter) {
  210. energy_mplr_counter = Settings.param[P_MAX_POWER_RETRY] +1;
  211. }
  212. energy_mplw_counter = Settings.energy_max_power_limit_window;
  213. }
  214. }
  215. }
  216. else if (power && (energy_power_u <= Settings.energy_max_power_limit)) {
  217. energy_mplh_counter = 0;
  218. energy_mplr_counter = 0;
  219. energy_mplw_counter = 0;
  220. }
  221. if (!power) {
  222. if (energy_mplw_counter) {
  223. energy_mplw_counter--;
  224. } else {
  225. if (energy_mplr_counter) {
  226. energy_mplr_counter--;
  227. if (energy_mplr_counter) {
  228. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_POWERMONITOR "\":\"%s\"}"), GetStateText(1));
  229. MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_JSON_POWERMONITOR));
  230. ExecuteCommandPower(1, POWER_ON, SRC_MAXPOWER);
  231. } else {
  232. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_MAXPOWERREACHEDRETRY "\":\"%s\"}"), GetStateText(0));
  233. MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING);
  234. EnergyMqttShow();
  235. }
  236. }
  237. }
  238. }
  239. }
  240. // Max Energy
  241. if (Settings.energy_max_energy) {
  242. energy_daily_u = (uint16_t)(energy_daily * 1000);
  243. if (!energy_max_energy_state && (RtcTime.hour == Settings.energy_max_energy_start)) {
  244. energy_max_energy_state = 1;
  245. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_ENERGYMONITOR "\":\"%s\"}"), GetStateText(1));
  246. MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_JSON_ENERGYMONITOR));
  247. ExecuteCommandPower(1, POWER_ON, SRC_MAXENERGY);
  248. }
  249. else if ((1 == energy_max_energy_state) && (energy_daily_u >= Settings.energy_max_energy)) {
  250. energy_max_energy_state = 2;
  251. dtostrfd(energy_daily, 3, mqtt_data);
  252. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_MAXENERGYREACHED "\":\"%s%s\"}"), mqtt_data, (Settings.flag.value_units) ? " " D_UNIT_KILOWATTHOUR : "");
  253. MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING);
  254. EnergyMqttShow();
  255. ExecuteCommandPower(1, POWER_OFF, SRC_MAXENERGY);
  256. }
  257. }
  258. #endif // FEATURE_POWER_LIMIT
  259. if (energy_power_delta) EnergyMqttShow();
  260. }
  261. void EnergyMqttShow(void)
  262. {
  263. // {"Time":"2017-12-16T11:48:55","ENERGY":{"Total":0.212,"Yesterday":0.000,"Today":0.014,"Period":2.0,"Power":22.0,"Factor":1.00,"Voltage":213.6,"Current":0.100}}
  264. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_TIME "\":\"%s\""), GetDateAndTime(DT_LOCAL).c_str());
  265. int tele_period_save = tele_period;
  266. tele_period = 2;
  267. EnergyShow(1);
  268. tele_period = tele_period_save;
  269. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
  270. MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
  271. energy_power_delta = 0;
  272. }
  273. /*********************************************************************************************\
  274. * Commands
  275. \*********************************************************************************************/
  276. boolean EnergyCommand(void)
  277. {
  278. char command [CMDSZ];
  279. char sunit[CMDSZ];
  280. boolean serviced = true;
  281. uint8_t status_flag = 0;
  282. uint8_t unit = 0;
  283. unsigned long nvalue = 0;
  284. int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kEnergyCommands);
  285. energy_command_code = command_code;
  286. if (-1 == command_code) {
  287. serviced = false; // Unknown command
  288. }
  289. else if (CMND_POWERDELTA == command_code) {
  290. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 101)) {
  291. Settings.energy_power_delta = (1 == XdrvMailbox.payload) ? DEFAULT_POWER_DELTA : XdrvMailbox.payload;
  292. }
  293. nvalue = Settings.energy_power_delta;
  294. unit = UNIT_PERCENTAGE;
  295. }
  296. else if (CMND_POWERLOW == command_code) {
  297. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
  298. Settings.energy_min_power = XdrvMailbox.payload;
  299. }
  300. nvalue = Settings.energy_min_power;
  301. unit = UNIT_WATT;
  302. }
  303. else if (CMND_POWERHIGH == command_code) {
  304. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
  305. Settings.energy_max_power = XdrvMailbox.payload;
  306. }
  307. nvalue = Settings.energy_max_power;
  308. unit = UNIT_WATT;
  309. }
  310. else if (CMND_VOLTAGELOW == command_code) {
  311. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) {
  312. Settings.energy_min_voltage = XdrvMailbox.payload;
  313. }
  314. nvalue = Settings.energy_min_voltage;
  315. unit = UNIT_VOLT;
  316. }
  317. else if (CMND_VOLTAGEHIGH == command_code) {
  318. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) {
  319. Settings.energy_max_voltage = XdrvMailbox.payload;
  320. }
  321. nvalue = Settings.energy_max_voltage;
  322. unit = UNIT_VOLT;
  323. }
  324. else if (CMND_CURRENTLOW == command_code) {
  325. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) {
  326. Settings.energy_min_current = XdrvMailbox.payload;
  327. }
  328. nvalue = Settings.energy_min_current;
  329. unit = UNIT_MILLIAMPERE;
  330. }
  331. else if (CMND_CURRENTHIGH == command_code) {
  332. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) {
  333. Settings.energy_max_current = XdrvMailbox.payload;
  334. }
  335. nvalue = Settings.energy_max_current;
  336. unit = UNIT_MILLIAMPERE;
  337. }
  338. else if ((CMND_ENERGYRESET == command_code) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) {
  339. char *p;
  340. unsigned long lnum = strtoul(XdrvMailbox.data, &p, 10);
  341. if (p != XdrvMailbox.data) {
  342. switch (XdrvMailbox.index) {
  343. case 1:
  344. energy_kWhtoday = lnum *100;
  345. energy_kWhtoday_delta = 0;
  346. energy_period = energy_kWhtoday;
  347. Settings.energy_kWhtoday = energy_kWhtoday;
  348. RtcSettings.energy_kWhtoday = energy_kWhtoday;
  349. energy_daily = (float)energy_kWhtoday / 100000;
  350. break;
  351. case 2:
  352. Settings.energy_kWhyesterday = lnum *100;
  353. break;
  354. case 3:
  355. RtcSettings.energy_kWhtotal = lnum *100;
  356. Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal;
  357. energy_total = (float)(RtcSettings.energy_kWhtotal + energy_kWhtoday) / 100000;
  358. if (!energy_total) { Settings.energy_kWhtotal_time = LocalTime(); }
  359. break;
  360. }
  361. }
  362. char energy_total_chr[33];
  363. dtostrfd(energy_total, Settings.flag2.energy_resolution, energy_total_chr);
  364. char energy_daily_chr[33];
  365. dtostrfd(energy_daily, Settings.flag2.energy_resolution, energy_daily_chr);
  366. char energy_yesterday_chr[33];
  367. dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
  368. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s}}"),
  369. command, energy_total_chr, energy_yesterday_chr, energy_daily_chr);
  370. status_flag = 1;
  371. }
  372. else if ((CMND_POWERSET == command_code) && XnrgCall(FUNC_COMMAND)) { // Watt
  373. nvalue = Settings.energy_power_calibration;
  374. unit = UNIT_MICROSECOND;
  375. }
  376. else if ((CMND_VOLTAGESET == command_code) && XnrgCall(FUNC_COMMAND)) { // Volt
  377. nvalue = Settings.energy_voltage_calibration;
  378. unit = UNIT_MICROSECOND;
  379. }
  380. else if ((CMND_CURRENTSET == command_code) && XnrgCall(FUNC_COMMAND)) { // milliAmpere
  381. nvalue = Settings.energy_current_calibration;
  382. unit = UNIT_MICROSECOND;
  383. }
  384. else if ((CMND_FREQUENCYSET == command_code) && XnrgCall(FUNC_COMMAND)) { // Hz
  385. nvalue = Settings.energy_frequency_calibration;
  386. unit = UNIT_MICROSECOND;
  387. }
  388. #if FEATURE_POWER_LIMIT
  389. else if (CMND_MAXPOWER == command_code) {
  390. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
  391. Settings.energy_max_power_limit = XdrvMailbox.payload;
  392. }
  393. nvalue = Settings.energy_max_power_limit;
  394. unit = UNIT_WATT;
  395. }
  396. else if (CMND_MAXPOWERHOLD == command_code) {
  397. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
  398. Settings.energy_max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload;
  399. }
  400. nvalue = Settings.energy_max_power_limit_hold;
  401. unit = UNIT_SECOND;
  402. }
  403. else if (CMND_MAXPOWERWINDOW == command_code) {
  404. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
  405. Settings.energy_max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload;
  406. }
  407. nvalue = Settings.energy_max_power_limit_window;
  408. unit = UNIT_SECOND;
  409. }
  410. else if (CMND_SAFEPOWER == command_code) {
  411. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
  412. Settings.energy_max_power_safe_limit = XdrvMailbox.payload;
  413. }
  414. nvalue = Settings.energy_max_power_safe_limit;
  415. unit = UNIT_WATT;
  416. }
  417. else if (CMND_SAFEPOWERHOLD == command_code) {
  418. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
  419. Settings.energy_max_power_safe_limit_hold = (1 == XdrvMailbox.payload) ? SAFE_POWER_HOLD : XdrvMailbox.payload;
  420. }
  421. nvalue = Settings.energy_max_power_safe_limit_hold;
  422. unit = UNIT_SECOND;
  423. }
  424. else if (CMND_SAFEPOWERWINDOW == command_code) {
  425. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 1440)) {
  426. Settings.energy_max_power_safe_limit_window = (1 == XdrvMailbox.payload) ? SAFE_POWER_WINDOW : XdrvMailbox.payload;
  427. }
  428. nvalue = Settings.energy_max_power_safe_limit_window;
  429. unit = UNIT_MINUTE;
  430. }
  431. else if (CMND_MAXENERGY == command_code) {
  432. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
  433. Settings.energy_max_energy = XdrvMailbox.payload;
  434. energy_max_energy_state = 3;
  435. }
  436. nvalue = Settings.energy_max_energy;
  437. unit = UNIT_WATTHOUR;
  438. }
  439. else if (CMND_MAXENERGYSTART == command_code) {
  440. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) {
  441. Settings.energy_max_energy_start = XdrvMailbox.payload;
  442. }
  443. nvalue = Settings.energy_max_energy_start;
  444. unit = UNIT_HOUR;
  445. }
  446. #endif // FEATURE_POWER_LIMIT
  447. else serviced = false; // Unknown command
  448. if (serviced && !status_flag) {
  449. if (UNIT_MICROSECOND == unit) {
  450. snprintf_P(command, sizeof(command), PSTR("%sCal"), command);
  451. }
  452. if (Settings.flag.value_units) {
  453. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_LVALUE_SPACE_UNIT, command, nvalue, GetTextIndexed(sunit, sizeof(sunit), unit, kUnitNames));
  454. } else {
  455. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_LVALUE, command, nvalue);
  456. }
  457. }
  458. return serviced;
  459. }
  460. void EnergyDrvInit(void)
  461. {
  462. energy_flg = ENERGY_NONE;
  463. XnrgCall(FUNC_PRE_INIT);
  464. }
  465. void EnergySnsInit(void)
  466. {
  467. XnrgCall(FUNC_INIT);
  468. if (energy_flg) {
  469. energy_kWhtoday = (RtcSettingsValid()) ? RtcSettings.energy_kWhtoday : (RtcTime.day_of_year == Settings.energy_kWhdoy) ? Settings.energy_kWhtoday : 0;
  470. energy_kWhtoday_delta = 0;
  471. energy_period = energy_kWhtoday;
  472. EnergyUpdateToday();
  473. ticker_energy.attach_ms(200, Energy200ms);
  474. }
  475. }
  476. #ifdef USE_WEBSERVER
  477. const char HTTP_ENERGY_SNS1[] PROGMEM = "%s"
  478. "{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"
  479. "{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"
  480. "{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}";
  481. const char HTTP_ENERGY_SNS2[] PROGMEM = "%s"
  482. "{s}" D_POWERUSAGE_APPARENT "{m}%s " D_UNIT_VA "{e}"
  483. "{s}" D_POWERUSAGE_REACTIVE "{m}%s " D_UNIT_VAR "{e}"
  484. "{s}" D_POWER_FACTOR "{m}%s{e}";
  485. const char HTTP_ENERGY_SNS3[] PROGMEM = "%s"
  486. "{s}" D_FREQUENCY "{m}%s " D_UNIT_HERTZ "{e}";
  487. const char HTTP_ENERGY_SNS4[] PROGMEM = "%s"
  488. "{s}" D_ENERGY_TODAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
  489. "{s}" D_ENERGY_YESTERDAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
  490. "{s}" D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
  491. #endif // USE_WEBSERVER
  492. void EnergyShow(boolean json)
  493. {
  494. char speriod[20];
  495. char sfrequency[20];
  496. bool show_energy_period = (0 == tele_period);
  497. float power_factor = energy_power_factor;
  498. char apparent_power_chr[33];
  499. char reactive_power_chr[33];
  500. char power_factor_chr[33];
  501. char frequency_chr[33];
  502. if (!energy_type_dc) {
  503. float apparent_power = energy_apparent_power;
  504. if (isnan(apparent_power)) {
  505. apparent_power = energy_voltage * energy_current;
  506. }
  507. if (apparent_power < energy_active_power) { // Should be impossible
  508. energy_active_power = apparent_power;
  509. }
  510. if (isnan(power_factor)) {
  511. power_factor = (energy_active_power && apparent_power) ? energy_active_power / apparent_power : 0;
  512. if (power_factor > 1) power_factor = 1;
  513. }
  514. float reactive_power = energy_reactive_power;
  515. if (isnan(reactive_power)) {
  516. reactive_power = 0;
  517. uint32_t difference = ((uint32_t)(apparent_power * 100) - (uint32_t)(energy_active_power * 100)) / 10;
  518. if ((energy_current > 0.005) && ((difference > 15) || (difference > (uint32_t)(apparent_power * 100 / 1000)))) {
  519. // calculating reactive power only if current is greater than 0.005A and
  520. // difference between active and apparent power is greater than 1.5W or 1%
  521. reactive_power = (float)(RoundSqrtInt((uint32_t)(apparent_power * apparent_power * 100) - (uint32_t)(energy_active_power * energy_active_power * 100))) / 10;
  522. }
  523. }
  524. dtostrfd(apparent_power, Settings.flag2.wattage_resolution, apparent_power_chr);
  525. dtostrfd(reactive_power, Settings.flag2.wattage_resolution, reactive_power_chr);
  526. dtostrfd(power_factor, 2, power_factor_chr);
  527. if (!isnan(energy_frequency)) {
  528. dtostrfd(energy_frequency, Settings.flag2.frequency_resolution, frequency_chr);
  529. snprintf_P(sfrequency, sizeof(sfrequency), PSTR(",\"" D_JSON_FREQUENCY "\":%s"), frequency_chr);
  530. }
  531. }
  532. char voltage_chr[33];
  533. dtostrfd(energy_voltage, Settings.flag2.voltage_resolution, voltage_chr);
  534. char current_chr[33];
  535. dtostrfd(energy_current, Settings.flag2.current_resolution, current_chr);
  536. char active_power_chr[33];
  537. dtostrfd(energy_active_power, Settings.flag2.wattage_resolution, active_power_chr);
  538. char energy_daily_chr[33];
  539. dtostrfd(energy_daily, Settings.flag2.energy_resolution, energy_daily_chr);
  540. char energy_yesterday_chr[33];
  541. dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
  542. char energy_total_chr[33];
  543. dtostrfd(energy_total, Settings.flag2.energy_resolution, energy_total_chr);
  544. float energy = 0;
  545. char energy_period_chr[33];
  546. if (show_energy_period) {
  547. if (energy_period) energy = (float)(energy_kWhtoday - energy_period) / 100;
  548. energy_period = energy_kWhtoday;
  549. dtostrfd(energy, Settings.flag2.wattage_resolution, energy_period_chr);
  550. snprintf_P(speriod, sizeof(speriod), PSTR(",\"" D_JSON_PERIOD "\":%s"), energy_period_chr);
  551. }
  552. if (json) {
  553. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL_START_TIME "\":\"%s\",\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s%s,\"" D_JSON_POWERUSAGE "\":%s"),
  554. mqtt_data, GetDateAndTime(DT_ENERGY).c_str(), energy_total_chr, energy_yesterday_chr, energy_daily_chr, (show_energy_period) ? speriod : "", active_power_chr);
  555. if (!energy_type_dc) {
  556. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_JSON_APPARENT_POWERUSAGE "\":%s,\"" D_JSON_REACTIVE_POWERUSAGE "\":%s,\"" D_JSON_POWERFACTOR "\":%s%s"),
  557. mqtt_data, apparent_power_chr, reactive_power_chr, power_factor_chr, (!isnan(energy_frequency)) ? sfrequency : "");
  558. }
  559. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s}"), mqtt_data, voltage_chr, current_chr);
  560. #ifdef USE_DOMOTICZ
  561. if (show_energy_period) { // Only send if telemetry
  562. dtostrfd(energy_total * 1000, 1, energy_total_chr);
  563. DomoticzSensorPowerEnergy((int)energy_active_power, energy_total_chr); // PowerUsage, EnergyToday
  564. DomoticzSensor(DZ_VOLTAGE, voltage_chr); // Voltage
  565. DomoticzSensor(DZ_CURRENT, current_chr); // Current
  566. }
  567. #endif // USE_DOMOTICZ
  568. #ifdef USE_KNX
  569. if (show_energy_period) {
  570. KnxSensor(KNX_ENERGY_VOLTAGE, energy_voltage);
  571. KnxSensor(KNX_ENERGY_CURRENT, energy_current);
  572. KnxSensor(KNX_ENERGY_POWER, energy_active_power);
  573. if (!energy_type_dc) { KnxSensor(KNX_ENERGY_POWERFACTOR, power_factor); }
  574. KnxSensor(KNX_ENERGY_DAILY, energy_daily);
  575. KnxSensor(KNX_ENERGY_TOTAL, energy_total);
  576. KnxSensor(KNX_ENERGY_START, energy_start);
  577. }
  578. #endif // USE_KNX
  579. #ifdef USE_WEBSERVER
  580. } else {
  581. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS1, mqtt_data, voltage_chr, current_chr, active_power_chr);
  582. if (!energy_type_dc) {
  583. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS2, mqtt_data, apparent_power_chr, reactive_power_chr, power_factor_chr);
  584. if (!isnan(energy_frequency)) { snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS3, mqtt_data, frequency_chr); }
  585. }
  586. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_ENERGY_SNS4, mqtt_data, energy_daily_chr, energy_yesterday_chr, energy_total_chr);
  587. #endif // USE_WEBSERVER
  588. }
  589. }
  590. /*********************************************************************************************\
  591. * Interface
  592. \*********************************************************************************************/
  593. boolean Xdrv03(byte function)
  594. {
  595. boolean result = false;
  596. if (FUNC_PRE_INIT == function) {
  597. EnergyDrvInit();
  598. }
  599. else if (energy_flg) {
  600. switch (function) {
  601. case FUNC_COMMAND:
  602. result = EnergyCommand();
  603. break;
  604. case FUNC_SET_POWER:
  605. EnergySetPowerSteadyCounter();
  606. break;
  607. case FUNC_SERIAL:
  608. result = XnrgCall(FUNC_SERIAL);
  609. break;
  610. }
  611. }
  612. return result;
  613. }
  614. boolean Xsns03(byte function)
  615. {
  616. boolean result = false;
  617. if (energy_flg) {
  618. switch (function) {
  619. case FUNC_INIT:
  620. EnergySnsInit();
  621. break;
  622. case FUNC_EVERY_SECOND:
  623. EnergyMarginCheck();
  624. break;
  625. case FUNC_JSON_APPEND:
  626. EnergyShow(1);
  627. break;
  628. #ifdef USE_WEBSERVER
  629. case FUNC_WEB_APPEND:
  630. EnergyShow(0);
  631. break;
  632. #endif // USE_WEBSERVER
  633. case FUNC_SAVE_BEFORE_RESTART:
  634. EnergySaveState();
  635. break;
  636. }
  637. }
  638. return result;
  639. }
  640. #endif // USE_ENERGY_SENSOR