diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index a59ba99cb..0b3c115cd 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -1,4 +1,8 @@ -/* 6.2.0.1 20180902 +/* 6.2.0.2 20180904 + * Rewrite energy monitoring using energy sensor driver modules + * Fix lost today and total energy value after power cycle (#3689) + * + * 6.2.0.1 20180902 * Fix possible ambiguity on command parameters if StateText contains numbers only (#3656) * Fix possible exception due to buffer overflow (#3659) * Add Wifi channel number to state message (#3664) diff --git a/sonoff/settings.h b/sonoff/settings.h index ded2c140b..3876baf60 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -333,6 +333,12 @@ struct SYSCFG { // E00 - FFF free locations } Settings; +struct RTCRBT { + uint16_t valid; // 000 + uint8_t fast_reboot_count; // 002 + uint8_t free_003[1]; // 003 +} RtcReboot; + struct RTCMEM { uint16_t valid; // 000 byte oswatch_blocked_loop; // 002 @@ -341,9 +347,7 @@ struct RTCMEM { unsigned long energy_kWhtotal; // 008 unsigned long pulse_counter[MAX_COUNTERS]; // 00C power_t power; // 01C - uint16_t extended_valid; // 020 Extended valid flag (v6.1.1.14) - uint8_t fast_reboot_count; // 022 - uint8_t free_023[57]; // 023 + uint8_t free_020[60]; // 020 // 05C next free location (64 (=core) + 100 (=tasmota offset) + 92 (=0x5C RTCMEM struct) = 256 bytes (max = 512)) } RtcSettings; diff --git a/sonoff/settings.ino b/sonoff/settings.ino index cba043696..5626f862b 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -84,7 +84,6 @@ void RtcSettingsSave() { if (GetRtcSettingsCrc() != rtc_settings_crc) { RtcSettings.valid = RTC_MEM_VALID; - RtcSettings.extended_valid = RTC_MEM_VALID; ESP.rtcUserMemoryWrite(100, (uint32_t*)&RtcSettings, sizeof(RTCMEM)); rtc_settings_crc = GetRtcSettingsCrc(); #ifdef DEBUG_THEO @@ -104,14 +103,12 @@ void RtcSettingsLoad() if (RtcSettings.valid != RTC_MEM_VALID) { memset(&RtcSettings, 0, sizeof(RTCMEM)); RtcSettings.valid = RTC_MEM_VALID; - RtcSettings.extended_valid = RTC_MEM_VALID; RtcSettings.energy_kWhtoday = Settings.energy_kWhtoday; RtcSettings.energy_kWhtotal = Settings.energy_kWhtotal; for (byte i = 0; i < MAX_COUNTERS; i++) { RtcSettings.pulse_counter[i] = Settings.pulse_counter[i]; } RtcSettings.power = Settings.power; -// RtcSettings.fast_reboot_count = 0; // Explicit by memset RtcSettingsSave(); } rtc_settings_crc = GetRtcSettingsCrc(); @@ -122,9 +119,45 @@ boolean RtcSettingsValid() return (RTC_MEM_VALID == RtcSettings.valid); } -boolean RtcSettingsExtendedValid() +/********************************************************************************************/ + +uint32_t rtc_reboot_crc = 0; + +uint32_t GetRtcRebootCrc() { - return (RTC_MEM_VALID == RtcSettings.extended_valid); + uint32_t crc = 0; + uint8_t *bytes = (uint8_t*)&RtcReboot; + + for (uint16_t i = 0; i < sizeof(RTCRBT); i++) { + crc += bytes[i]*(i+1); + } + return crc; +} + +void RtcRebootSave() +{ + if (GetRtcRebootCrc() != rtc_reboot_crc) { + RtcReboot.valid = RTC_MEM_VALID; + ESP.rtcUserMemoryWrite(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT)); + rtc_reboot_crc = GetRtcRebootCrc(); + } +} + +void RtcRebootLoad() +{ + ESP.rtcUserMemoryRead(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT)); + if (RtcReboot.valid != RTC_MEM_VALID) { + memset(&RtcReboot, 0, sizeof(RTCRBT)); + RtcReboot.valid = RTC_MEM_VALID; +// RtcReboot.fast_reboot_count = 0; // Explicit by memset + RtcRebootSave(); + } + rtc_reboot_crc = GetRtcRebootCrc(); +} + +boolean RtcRebootValid() +{ + return (RTC_MEM_VALID == RtcReboot.valid); } /*********************************************************************************************\ diff --git a/sonoff/sonoff.h b/sonoff/sonoff.h index 3a1dc14c6..2c5d5cce9 100644 --- a/sonoff/sonoff.h +++ b/sonoff/sonoff.h @@ -202,9 +202,9 @@ enum LightTypes {LT_BASIC, LT_PWM1, LT_PWM2, LT_PWM3, LT_PWM4, LT_PWM5, LT_PWM6, enum LichtSubtypes {LST_NONE, LST_SINGLE, LST_COLDWARM, LST_RGB, LST_RGBW, LST_RGBWC}; enum LichtSchemes {LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_MAX}; -enum XsnsFunctions {FUNC_PRE_INIT, FUNC_INIT, FUNC_LOOP, FUNC_EVERY_50_MSECOND, FUNC_EVERY_100_MSECOND, FUNC_EVERY_250_MSECOND, FUNC_EVERY_SECOND, FUNC_PREP_BEFORE_TELEPERIOD, - FUNC_JSON_APPEND, FUNC_WEB_APPEND, FUNC_SAVE_BEFORE_RESTART, FUNC_COMMAND, FUNC_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA, FUNC_SET_POWER, FUNC_SHOW_SENSOR, - FUNC_RULES_PROCESS, FUNC_FREE_MEM}; +enum XsnsFunctions {FUNC_PRE_INIT, FUNC_INIT, FUNC_LOOP, FUNC_EVERY_50_MSECOND, FUNC_EVERY_100_MSECOND, FUNC_EVERY_200_MSECOND, FUNC_EVERY_250_MSECOND, FUNC_EVERY_SECOND, FUNC_PREP_BEFORE_TELEPERIOD, + FUNC_JSON_APPEND, FUNC_WEB_APPEND, FUNC_SAVE_BEFORE_RESTART, FUNC_COMMAND, FUNC_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA, FUNC_SET_POWER, FUNC_SHOW_SENSOR, + FUNC_RULES_PROCESS, FUNC_SERIAL, FUNC_FREE_MEM}; const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 }; diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 8d82efc18..c92a8db76 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -172,7 +172,7 @@ uint8_t led_inverted = 0; // LED inverted flag (1 = (0 = On, 1 uint8_t pwm_inverted = 0; // PWM inverted flag (1 = inverted) uint8_t counter_no_pullup = 0; // Counter input pullup flag (1 = No pullup) uint8_t dht_flg = 0; // DHT configured -uint8_t energy_flg = 1; // Energy monitor configured +uint8_t energy_flg = 0; // Energy monitor configured uint8_t i2c_flg = 0; // I2C configured uint8_t spi_flg = 0; // SPI configured uint8_t light_type = 0; // Light types @@ -1568,8 +1568,8 @@ void PerformEverySecond() uptime++; if (BOOT_LOOP_TIME == uptime) { - RtcSettings.fast_reboot_count = 0; - RtcSettingsSave(); + RtcReboot.fast_reboot_count = 0; + RtcRebootSave(); } if ((4 == uptime) && (SONOFF_IFAN02 == Settings.module)) { // Microcontroller needs 3 seconds before accepting commands @@ -2193,29 +2193,14 @@ void SerialInput() } } -/*-------------------------------------------------------------------------------------------*\ - * Sonoff bridge 19200 baud serial interface -\*-------------------------------------------------------------------------------------------*/ - if (SONOFF_BRIDGE == Settings.module) { - if (SonoffBridgeSerialInput()) { - serial_in_byte_counter = 0; - Serial.flush(); - return; - } +/*-------------------------------------------------------------------------------------------*/ + + if (XdrvCall(FUNC_SERIAL)) { + serial_in_byte_counter = 0; + Serial.flush(); + return; } -#ifdef USE_ENERGY_SENSOR -/*-------------------------------------------------------------------------------------------*\ - * Sonoff S31 and Sonoff Pow R2 4800 baud serial interface -\*-------------------------------------------------------------------------------------------*/ - if ((SONOFF_S31 == Settings.module) || (SONOFF_POW_R2 == Settings.module)) { - if (CseSerialInput()) { - serial_in_byte_counter = 0; - Serial.flush(); - return; - } - } -#endif // USE_ENERGY_SENSOR /*-------------------------------------------------------------------------------------------*/ if (serial_in_byte > 127 && !Settings.flag.mqtt_serial_raw) { // binary data... @@ -2491,10 +2476,10 @@ void setup() { byte idx; - RtcSettingsLoad(); - if (!RtcSettingsExtendedValid()) { RtcSettings.fast_reboot_count = 0; } - RtcSettings.fast_reboot_count++; - RtcSettingsSave(); + RtcRebootLoad(); + if (!RtcRebootValid()) { RtcReboot.fast_reboot_count = 0; } + RtcReboot.fast_reboot_count++; + RtcRebootSave(); Serial.begin(baudrate); delay(10); @@ -2528,26 +2513,26 @@ void setup() sleep = Settings.sleep; // Disable functionality as possible cause of fast restart within BOOT_LOOP_TIME seconds (Exception, WDT or restarts) - if (RtcSettings.fast_reboot_count > 1) { // Restart twice + if (RtcReboot.fast_reboot_count > 1) { // Restart twice Settings.flag3.user_esp8285_enable = 0; // Disable ESP8285 Generic GPIOs interfering with flash SPI - if (RtcSettings.fast_reboot_count > 2) { // Restart 3 times + if (RtcReboot.fast_reboot_count > 2) { // Restart 3 times for (byte i = 0; i < MAX_RULE_SETS; i++) { if (bitRead(Settings.rule_stop, i)) { bitWrite(Settings.rule_enabled, i, 0); // Disable rules causing boot loop } } } - if (RtcSettings.fast_reboot_count > 3) { // Restarted 4 times + if (RtcReboot.fast_reboot_count > 3) { // Restarted 4 times Settings.rule_enabled = 0; // Disable all rules } - if (RtcSettings.fast_reboot_count > 4) { // Restarted 5 times + if (RtcReboot.fast_reboot_count > 4) { // Restarted 5 times Settings.module = SONOFF_BASIC; // Reset module to Sonoff Basic Settings.last_module = SONOFF_BASIC; for (byte i = 0; i < MAX_GPIO_PIN; i++) { Settings.my_gp.io[i] = GPIO_NONE; // Reset user defined GPIO disabling sensors } } - snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_LOG_SOME_SETTINGS_RESET " (%d)"), RtcSettings.fast_reboot_count); + snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_LOG_SOME_SETTINGS_RESET " (%d)"), RtcReboot.fast_reboot_count); AddLog(LOG_LEVEL_DEBUG); } diff --git a/sonoff/sonoff_post.h b/sonoff/sonoff_post.h index b6b8d320c..dc49211d1 100755 --- a/sonoff/sonoff_post.h +++ b/sonoff/sonoff_post.h @@ -52,6 +52,8 @@ void KNX_CB_Action(message_t const &msg, void *arg); #define USE_DHT // Default DHT11 sensor needs no external library #define USE_ENERGY_SENSOR // Use energy sensors +#define USE_HLW8012 // Use energy sensor for Sonoff Pow and WolfBlitz +#define USE_CSE7766 // Use energy sensor for Sonoff S31 and Pow R2 /*********************************************************************************************\ * [sonoff-sensors.bin] diff --git a/sonoff/sonoff_version.h b/sonoff/sonoff_version.h index c3ea9381e..e9e92cc9d 100644 --- a/sonoff/sonoff_version.h +++ b/sonoff/sonoff_version.h @@ -20,7 +20,7 @@ #ifndef _SONOFF_VERSION_H_ #define _SONOFF_VERSION_H_ -#define VERSION 0x06020001 +#define VERSION 0x06020002 #define D_PROGRAMNAME "Sonoff-Tasmota" #define D_AUTHOR "Theo Arends" diff --git a/sonoff/xdrv_03_energy.ino b/sonoff/xdrv_03_energy.ino index 4ea594cdb..5c122c314 100644 --- a/sonoff/xdrv_03_energy.ino +++ b/sonoff/xdrv_03_energy.ino @@ -1,5 +1,5 @@ /* - xdrv_03_energy.ino - HLW8012 (Sonoff Pow) and PZEM004T energy sensor support for Sonoff-Tasmota + xdrv_03_energy.ino - Energy sensor support for Sonoff-Tasmota Copyright (C) 2018 Theo Arends @@ -19,12 +19,12 @@ #ifdef USE_ENERGY_SENSOR /*********************************************************************************************\ - * HLW8012 and PZEM004T - Energy + * Energy \*********************************************************************************************/ -#define FEATURE_POWER_LIMIT true +#define ENERGY_NONE 0 -enum EnergyHardware { ENERGY_NONE, ENERGY_HLW8012, ENERGY_CSE7766, ENERGY_PZEM004T }; +#define FEATURE_POWER_LIMIT true enum EnergyCommands { CMND_POWERDELTA, @@ -76,6 +76,7 @@ uint16_t energy_mplw_counter = 0; byte energy_fifth_second = 0; Ticker ticker_energy; +int energy_command_code = 0; /********************************************************************************************/ void EnergyUpdateToday() @@ -90,543 +91,15 @@ void EnergyUpdateToday() energy_total = (float)(RtcSettings.energy_kWhtotal + energy_kWhtoday) / 100000; } -/*********************************************************************************************\ - * HLW8012, BL0937 or HJL-01 - Energy (Sonoff Pow, HuaFan, KMC70011, BlitzWolf) - * - * Based on Source: Shenzhen Heli Technology Co., Ltd -\*********************************************************************************************/ - -// HLW8012 based (Sonoff Pow, KMC70011, HuaFan) -#define HLW_PREF 10000 // 1000.0W -#define HLW_UREF 2200 // 220.0V -#define HLW_IREF 4545 // 4.545A -#define HLW_SEL_VOLTAGE 1 - -// HJL-01 based (BlitzWolf, Homecube, Gosund) -#define HJL_PREF 1362 -#define HJL_UREF 822 -#define HJL_IREF 3300 -#define HJL_SEL_VOLTAGE 0 - -#define HLW_POWER_PROBE_TIME 10 // Number of seconds to probe for power before deciding none used - -byte hlw_select_ui_flag; -byte hlw_ui_flag = 1; -byte hlw_load_off; -byte hlw_cf1_timer; -unsigned long hlw_cf_pulse_length; -unsigned long hlw_cf_pulse_last_time; -unsigned long hlw_cf1_pulse_length; -unsigned long hlw_cf1_pulse_last_time; -unsigned long hlw_cf1_summed_pulse_length; -unsigned long hlw_cf1_pulse_counter; -unsigned long hlw_cf1_voltage_pulse_length; -unsigned long hlw_cf1_current_pulse_length; -unsigned long hlw_energy_period_counter; - -unsigned long hlw_power_ratio = 0; -unsigned long hlw_voltage_ratio = 0; -unsigned long hlw_current_ratio = 0; - -unsigned long hlw_cf1_voltage_max_pulse_counter; -unsigned long hlw_cf1_current_max_pulse_counter; - -#ifndef USE_WS2812_DMA // Collides with Neopixelbus but solves exception -void HlwCfInterrupt() ICACHE_RAM_ATTR; -void HlwCf1Interrupt() ICACHE_RAM_ATTR; -#endif // USE_WS2812_DMA - -void HlwCfInterrupt() // Service Power -{ - unsigned long us = micros(); - - if (hlw_load_off) { // Restart plen measurement - hlw_cf_pulse_last_time = us; - hlw_load_off = 0; - } else { - hlw_cf_pulse_length = us - hlw_cf_pulse_last_time; - hlw_cf_pulse_last_time = us; - hlw_energy_period_counter++; - } -} - -void HlwCf1Interrupt() // Service Voltage and Current -{ - unsigned long us = micros(); - - hlw_cf1_pulse_length = us - hlw_cf1_pulse_last_time; - hlw_cf1_pulse_last_time = us; - if ((hlw_cf1_timer > 2) && (hlw_cf1_timer < 8)) { // Allow for 300 mSec set-up time and measure for up to 1 second - hlw_cf1_summed_pulse_length += hlw_cf1_pulse_length; - hlw_cf1_pulse_counter++; - if (10 == hlw_cf1_pulse_counter) { - hlw_cf1_timer = 8; // We need up to ten samples within 1 second (low current could take up to 0.3 second) - } - } -} - -void HlwEverySecond() -{ - unsigned long hlw_len; - - if (hlw_energy_period_counter) { - hlw_len = 10000 / hlw_energy_period_counter; - hlw_energy_period_counter = 0; - if (hlw_len) { - energy_kWhtoday_delta += ((hlw_power_ratio * Settings.energy_power_calibration) / hlw_len) / 36; - EnergyUpdateToday(); - } - } -} - -void HlwEvery200ms() -{ - unsigned long hlw_w = 0; - unsigned long hlw_u = 0; - unsigned long hlw_i = 0; - - if (micros() - hlw_cf_pulse_last_time > (HLW_POWER_PROBE_TIME * 1000000)) { - hlw_cf_pulse_length = 0; // No load for some time - hlw_load_off = 1; - } - - if (hlw_cf_pulse_length && energy_power_on && !hlw_load_off) { - hlw_w = (hlw_power_ratio * Settings.energy_power_calibration) / hlw_cf_pulse_length; - energy_power = (float)hlw_w / 10; - } else { - energy_power = 0; - } - - hlw_cf1_timer++; - if (hlw_cf1_timer >= 8) { - hlw_cf1_timer = 0; - hlw_select_ui_flag = (hlw_select_ui_flag) ? 0 : 1; - digitalWrite(pin[GPIO_HLW_SEL], hlw_select_ui_flag); - - if (hlw_cf1_pulse_counter) { - hlw_cf1_pulse_length = hlw_cf1_summed_pulse_length / hlw_cf1_pulse_counter; - } else { - hlw_cf1_pulse_length = 0; - } - if (hlw_select_ui_flag == hlw_ui_flag) { - hlw_cf1_voltage_pulse_length = hlw_cf1_pulse_length; - hlw_cf1_voltage_max_pulse_counter = hlw_cf1_pulse_counter; - - if (hlw_cf1_voltage_pulse_length && energy_power_on) { // If powered on always provide voltage - hlw_u = (hlw_voltage_ratio * Settings.energy_voltage_calibration) / hlw_cf1_voltage_pulse_length; - energy_voltage = (float)hlw_u / 10; - } else { - energy_voltage = 0; - } - - } else { - hlw_cf1_current_pulse_length = hlw_cf1_pulse_length; - hlw_cf1_current_max_pulse_counter = hlw_cf1_pulse_counter; - - if (hlw_cf1_current_pulse_length && energy_power) { // No current if no power being consumed - hlw_i = (hlw_current_ratio * Settings.energy_current_calibration) / hlw_cf1_current_pulse_length; - energy_current = (float)hlw_i / 1000; - } else { - energy_current = 0; - } - - } - hlw_cf1_summed_pulse_length = 0; - hlw_cf1_pulse_counter = 0; - } -} - -void HlwInit() -{ - if (!Settings.energy_power_calibration || (4975 == Settings.energy_power_calibration)) { - Settings.energy_power_calibration = HLW_PREF_PULSE; - Settings.energy_voltage_calibration = HLW_UREF_PULSE; - Settings.energy_current_calibration = HLW_IREF_PULSE; - } - - if (BLITZWOLF_BWSHP2 == Settings.module) { - hlw_power_ratio = HJL_PREF; - hlw_voltage_ratio = HJL_UREF; - hlw_current_ratio = HJL_IREF; - hlw_ui_flag = HJL_SEL_VOLTAGE; - } else { - hlw_power_ratio = HLW_PREF; - hlw_voltage_ratio = HLW_UREF; - hlw_current_ratio = HLW_IREF; - hlw_ui_flag = HLW_SEL_VOLTAGE; - } - - hlw_cf_pulse_length = 0; - hlw_cf_pulse_last_time = 0; - hlw_cf1_pulse_length = 0; - hlw_cf1_pulse_last_time = 0; - hlw_cf1_voltage_pulse_length = 0; - hlw_cf1_current_pulse_length = 0; - hlw_cf1_voltage_max_pulse_counter = 0; - hlw_cf1_current_max_pulse_counter = 0; - - hlw_load_off = 1; - hlw_energy_period_counter = 0; - - hlw_select_ui_flag = 0; // Voltage; - - pinMode(pin[GPIO_HLW_SEL], OUTPUT); - digitalWrite(pin[GPIO_HLW_SEL], hlw_select_ui_flag); - pinMode(pin[GPIO_HLW_CF1], INPUT_PULLUP); - attachInterrupt(pin[GPIO_HLW_CF1], HlwCf1Interrupt, FALLING); - pinMode(pin[GPIO_HLW_CF], INPUT_PULLUP); - attachInterrupt(pin[GPIO_HLW_CF], HlwCfInterrupt, FALLING); - - hlw_cf1_timer = 0; -} - -/*********************************************************************************************\ - * CSE7766 - Energy (Sonoff S31 and Sonoff Pow R2) - * - * Based on datasheet from http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf -\*********************************************************************************************/ - -#define CSE_NOT_CALIBRATED 0xAA - -#define CSE_PULSES_NOT_INITIALIZED -1 - -#define CSE_PREF 1000 -#define CSE_UREF 100 - -uint8_t cse_receive_flag = 0; - -long voltage_cycle = 0; -long current_cycle = 0; -long power_cycle = 0; -unsigned long power_cycle_first = 0; -long cf_pulses = 0; -long cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED; - -void CseReceived() -{ - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - // 55 5A 02 F7 60 00 03 AB 00 40 10 02 60 5D 51 A6 58 03 E9 EF 71 0B 7A 36 - // Hd Id VCal---- Voltage- ICal---- Current- PCal---- Power--- Ad CF--- Ck - - uint8_t header = serial_in_buffer[0]; - if ((header & 0xFC) == 0xFC) { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Abnormal hardware")); - return; - } - - // Get chip calibration data (coefficients) and use as initial defaults - if (HLW_UREF_PULSE == Settings.energy_voltage_calibration) { - long voltage_coefficient = 191200; // uSec - if (CSE_NOT_CALIBRATED != header) { - voltage_coefficient = serial_in_buffer[2] << 16 | serial_in_buffer[3] << 8 | serial_in_buffer[4]; - } - Settings.energy_voltage_calibration = voltage_coefficient / CSE_UREF; - } - if (HLW_IREF_PULSE == Settings.energy_current_calibration) { - long current_coefficient = 16140; // uSec - if (CSE_NOT_CALIBRATED != header) { - current_coefficient = serial_in_buffer[8] << 16 | serial_in_buffer[9] << 8 | serial_in_buffer[10]; - } - Settings.energy_current_calibration = current_coefficient; - } - if (HLW_PREF_PULSE == Settings.energy_power_calibration) { - long power_coefficient = 5364000; // uSec - if (CSE_NOT_CALIBRATED != header) { - power_coefficient = serial_in_buffer[14] << 16 | serial_in_buffer[15] << 8 | serial_in_buffer[16]; - } - Settings.energy_power_calibration = power_coefficient / CSE_PREF; - } - - uint8_t adjustement = serial_in_buffer[20]; - voltage_cycle = serial_in_buffer[5] << 16 | serial_in_buffer[6] << 8 | serial_in_buffer[7]; - current_cycle = serial_in_buffer[11] << 16 | serial_in_buffer[12] << 8 | serial_in_buffer[13]; - power_cycle = serial_in_buffer[17] << 16 | serial_in_buffer[18] << 8 | serial_in_buffer[19]; - cf_pulses = serial_in_buffer[21] << 8 | serial_in_buffer[22]; - - if (energy_power_on) { // Powered on - if (adjustement & 0x40) { // Voltage valid - energy_voltage = (float)(Settings.energy_voltage_calibration * CSE_UREF) / (float)voltage_cycle; - } - if (adjustement & 0x10) { // Power valid - if ((header & 0xF2) == 0xF2) { // Power cycle exceeds range - energy_power = 0; - } else { - if (0 == power_cycle_first) power_cycle_first = power_cycle; // Skip first incomplete power_cycle - if (power_cycle_first != power_cycle) { - power_cycle_first = -1; - energy_power = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)power_cycle; - } else { - energy_power = 0; - } - } - } else { - power_cycle_first = 0; - energy_power = 0; // Powered on but no load - } - if (adjustement & 0x20) { // Current valid - if (0 == energy_power) { - energy_current = 0; - } else { - energy_current = (float)Settings.energy_current_calibration / (float)current_cycle; - } - } - } else { // Powered off - power_cycle_first = 0; - energy_voltage = 0; - energy_power = 0; - energy_current = 0; - } -} - -bool CseSerialInput() -{ - if (cse_receive_flag) { - serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; - if (24 == serial_in_byte_counter) { - - AddLogSerial(LOG_LEVEL_DEBUG_MORE); - - uint8_t checksum = 0; - for (byte i = 2; i < 23; i++) { checksum += serial_in_buffer[i]; } - if (checksum == serial_in_buffer[23]) { - CseReceived(); - cse_receive_flag = 0; - return 1; - } else { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE)); - do { // Sync buffer with data (issue #1907 and #3425) - memmove(serial_in_buffer, serial_in_buffer +1, 24); - serial_in_byte_counter--; - } while ((serial_in_byte_counter > 2) && (0x5A != serial_in_buffer[1])); - if (0x5A != serial_in_buffer[1]) { - cse_receive_flag = 0; - serial_in_byte_counter = 0; - } - } - } - } else { - if ((0x5A == serial_in_byte) && (1 == serial_in_byte_counter)) { // 0x5A - Packet header 2 - cse_receive_flag = 1; - } else { - serial_in_byte_counter = 0; - } - serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; - } - serial_in_byte = 0; // Discard - return 0; -} - -void CseEverySecond() -{ - long cf_frequency = 0; - - if (CSE_PULSES_NOT_INITIALIZED == cf_pulses_last_time) { - cf_pulses_last_time = cf_pulses; // Init after restart - } else { - if (cf_pulses < cf_pulses_last_time) { // Rolled over after 65535 pulses - cf_frequency = (65536 - cf_pulses_last_time) + cf_pulses; - } else { - cf_frequency = cf_pulses - cf_pulses_last_time; - } - if (cf_frequency && energy_power) { - cf_pulses_last_time = cf_pulses; - energy_kWhtoday_delta += (cf_frequency * Settings.energy_power_calibration) / 36; - EnergyUpdateToday(); - } - } -} - -#ifdef USE_PZEM004T -/*********************************************************************************************\ - * PZEM004T - Energy - * - * Source: Victor Ferrer https://github.com/vicfergar/Sonoff-MQTT-OTA-Arduino - * Based on: PZEM004T library https://github.com/olehs/PZEM004T - * - * Hardware Serial will be selected if GPIO1 = [PZEM Rx] and [GPIO3 = PZEM Tx] -\*********************************************************************************************/ - -#include - -TasmotaSerial *PzemSerial; - -#define PZEM_VOLTAGE (uint8_t)0xB0 -#define RESP_VOLTAGE (uint8_t)0xA0 - -#define PZEM_CURRENT (uint8_t)0xB1 -#define RESP_CURRENT (uint8_t)0xA1 - -#define PZEM_POWER (uint8_t)0xB2 -#define RESP_POWER (uint8_t)0xA2 - -#define PZEM_ENERGY (uint8_t)0xB3 -#define RESP_ENERGY (uint8_t)0xA3 - -#define PZEM_SET_ADDRESS (uint8_t)0xB4 -#define RESP_SET_ADDRESS (uint8_t)0xA4 - -#define PZEM_POWER_ALARM (uint8_t)0xB5 -#define RESP_POWER_ALARM (uint8_t)0xA5 - -#define PZEM_DEFAULT_READ_TIMEOUT 500 - /*********************************************************************************************/ -struct PZEMCommand { - uint8_t command; - uint8_t addr[4]; - uint8_t data; - uint8_t crc; -}; - -IPAddress pzem_ip(192, 168, 1, 1); - -uint8_t PzemCrc(uint8_t *data) -{ - uint16_t crc = 0; - for (uint8_t i = 0; i < sizeof(PZEMCommand) -1; i++) crc += *data++; - return (uint8_t)(crc & 0xFF); -} - -void PzemSend(uint8_t cmd) -{ - PZEMCommand pzem; - - pzem.command = cmd; - for (uint8_t i = 0; i < sizeof(pzem.addr); i++) pzem.addr[i] = pzem_ip[i]; - pzem.data = 0; - - uint8_t *bytes = (uint8_t*)&pzem; - pzem.crc = PzemCrc(bytes); - - PzemSerial->flush(); - PzemSerial->write(bytes, sizeof(pzem)); -} - -bool PzemReceiveReady() -{ - return PzemSerial->available() >= (int)sizeof(PZEMCommand); -} - -bool PzemRecieve(uint8_t resp, float *data) -{ - // 0 1 2 3 4 5 6 - // A4 00 00 00 00 00 A4 - Set address - // A0 00 D4 07 00 00 7B - Voltage (212.7V) - // A1 00 00 0A 00 00 AB - Current (0.1A) - // A1 00 00 00 00 00 A1 - No current - // A2 00 16 00 00 00 B8 - Power (22W) - // A2 00 00 00 00 00 A2 - No power - // A3 00 08 A4 00 00 4F - Energy (2.212kWh) - // A3 01 86 9F 00 00 C9 - Energy (99.999kWh) - - uint8_t buffer[sizeof(PZEMCommand)] = { 0 }; - - unsigned long start = millis(); - uint8_t len = 0; - while ((len < sizeof(PZEMCommand)) && (millis() - start < PZEM_DEFAULT_READ_TIMEOUT)) { - if (PzemSerial->available() > 0) { - uint8_t c = (uint8_t)PzemSerial->read(); - if (!c && !len) { - continue; // skip 0 at startup - } - if ((1 == len) && (buffer[0] == c)) { - len--; - continue; // fix skewed data - } - buffer[len++] = c; - } - } - - AddLogSerial(LOG_LEVEL_DEBUG_MORE, buffer, len); - - if (len != sizeof(PZEMCommand)) { -// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem comms timeout")); - return false; - } - if (buffer[6] != PzemCrc(buffer)) { -// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem crc error")); - return false; - } - if (buffer[0] != resp) { -// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem bad response")); - return false; - } - - switch (resp) { - case RESP_VOLTAGE: - *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 10.0); // 65535.x V - break; - case RESP_CURRENT: - *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 100.0); // 65535.xx A - break; - case RESP_POWER: - *data = (float)(buffer[1] << 8) + buffer[2]; // 65535 W - break; - case RESP_ENERGY: - *data = (float)((uint32_t)buffer[1] << 16) + ((uint16_t)buffer[2] << 8) + buffer[3]; // 16777215 Wh - break; - } - return true; -} - -/*********************************************************************************************/ - -const uint8_t pzem_commands[] { PZEM_SET_ADDRESS, PZEM_VOLTAGE, PZEM_CURRENT, PZEM_POWER, PZEM_ENERGY }; -const uint8_t pzem_responses[] { RESP_SET_ADDRESS, RESP_VOLTAGE, RESP_CURRENT, RESP_POWER, RESP_ENERGY }; - -uint8_t pzem_read_state = 0; -uint8_t pzem_sendRetry = 0; - -void PzemEvery200ms() -{ - bool data_ready = PzemReceiveReady(); - - if (data_ready) { - float value = 0; - if (PzemRecieve(pzem_responses[pzem_read_state], &value)) { - switch (pzem_read_state) { - case 1: // Voltage as 230.2V - energy_voltage = value; - break; - case 2: // Current as 17.32A - energy_current = value; - break; - case 3: // Power as 20W - energy_power = value; - break; - case 4: // Total energy as 99999Wh - if (!energy_start || (value < energy_start)) energy_start = value; // Init after restart and hanlde roll-over if any - energy_kWhtoday += (value - energy_start) * 100; - energy_start = value; - EnergyUpdateToday(); - break; - } - pzem_read_state++; - if (5 == pzem_read_state) pzem_read_state = 1; - } - } - - if (0 == pzem_sendRetry || data_ready) { - pzem_sendRetry = 5; - PzemSend(pzem_commands[pzem_read_state]); - } - else { - pzem_sendRetry--; - } -} - -/********************************************************************************************/ -#endif // USE_PZEM004T - void Energy200ms() { energy_fifth_second++; if (5 == energy_fifth_second) { energy_fifth_second = 0; - if (ENERGY_HLW8012 == energy_flg) HlwEverySecond(); - if (ENERGY_CSE7766 == energy_flg) CseEverySecond(); + XnrgCall(FUNC_EVERY_SECOND); if (RtcTime.valid) { if (LocalTime() == Midnight()) { @@ -647,10 +120,7 @@ void Energy200ms() energy_power_on = (power &1) | Settings.flag.no_power_on_check; - if (ENERGY_HLW8012 == energy_flg) HlwEvery200ms(); -#ifdef USE_PZEM004T - if (ENERGY_PZEM004T == energy_flg) PzemEvery200ms(); -#endif // USE_PZEM004T + XnrgCall(FUNC_EVERY_200_MSECOND); float power_factor = 0; if (energy_voltage && energy_current && energy_power) { @@ -721,7 +191,7 @@ void EnergyMarginCheck() energy_voltage_u = (uint16_t)(energy_voltage); energy_current_u = (uint16_t)(energy_current * 1000); -// snprintf_P(log_data, sizeof(log_data), PSTR("HLW: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u); +// snprintf_P(log_data, sizeof(log_data), PSTR("NRG: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u); // AddLog(LOG_LEVEL_DEBUG); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{")); @@ -849,6 +319,7 @@ boolean EnergyCommand() unsigned long nvalue = 0; int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kEnergyCommands); + energy_command_code = command_code; if (-1 == command_code) { serviced = false; // Unknown command } @@ -936,63 +407,38 @@ boolean EnergyCommand() command, energy_total_chr, energy_yesterday_chr, energy_daily_chr); status_flag = 1; } - - else if (((ENERGY_HLW8012 == energy_flg) || (ENERGY_CSE7766 == energy_flg)) && (CMND_POWERCAL == command_code)) { + else if ((CMND_POWERCAL == command_code) && XnrgCall(FUNC_COMMAND)) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 32001)) { Settings.energy_power_calibration = (XdrvMailbox.payload > 4000) ? XdrvMailbox.payload : HLW_PREF_PULSE; // HLW = 12530, CSE = 5364 } nvalue = Settings.energy_power_calibration; unit = UNIT_MICROSECOND; } - else if (((ENERGY_HLW8012 == energy_flg) || (ENERGY_CSE7766 == energy_flg)) && (CMND_POWERSET == command_code)) { // Watt - if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3601)) { - if ((ENERGY_HLW8012 == energy_flg) && hlw_cf_pulse_length) { - Settings.energy_power_calibration = (XdrvMailbox.payload * 10 * hlw_cf_pulse_length) / hlw_power_ratio; - } - else if ((ENERGY_CSE7766 == energy_flg) && power_cycle) { - Settings.energy_power_calibration = (XdrvMailbox.payload * power_cycle) / CSE_PREF; - } - } - snprintf_P(command, sizeof(command), PSTR(D_CMND_POWERCAL)); - nvalue = Settings.energy_power_calibration; - unit = UNIT_MICROSECOND; - } - else if (((ENERGY_HLW8012 == energy_flg) || (ENERGY_CSE7766 == energy_flg)) && (CMND_VOLTAGECAL == command_code)) { + else if ((CMND_VOLTAGECAL == command_code) && XnrgCall(FUNC_COMMAND)) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 32001)) { Settings.energy_voltage_calibration = (XdrvMailbox.payload > 999) ? XdrvMailbox.payload : HLW_UREF_PULSE; // HLW = 1950, CSE = 1912 } nvalue = Settings.energy_voltage_calibration; unit = UNIT_MICROSECOND; } - else if (((ENERGY_HLW8012 == energy_flg) || (ENERGY_CSE7766 == energy_flg)) && (CMND_VOLTAGESET == command_code)) { // Volt - if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 501)) { - if ((ENERGY_HLW8012 == energy_flg) && hlw_cf1_voltage_pulse_length) { - Settings.energy_voltage_calibration = (XdrvMailbox.payload * 10 * hlw_cf1_voltage_pulse_length) / hlw_voltage_ratio; - } - else if ((ENERGY_CSE7766 == energy_flg) && voltage_cycle) { - Settings.energy_voltage_calibration = (XdrvMailbox.payload * voltage_cycle) / CSE_UREF; - } - } - snprintf_P(command, sizeof(command), PSTR(D_CMND_VOLTAGECAL)); - nvalue = Settings.energy_voltage_calibration; - unit = UNIT_MICROSECOND; - } - else if (((ENERGY_HLW8012 == energy_flg) || (ENERGY_CSE7766 == energy_flg)) && (CMND_CURRENTCAL == command_code)) { + else if ((CMND_CURRENTCAL == command_code) && XnrgCall(FUNC_COMMAND)) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 32001)) { Settings.energy_current_calibration = (XdrvMailbox.payload > 1100) ? XdrvMailbox.payload : HLW_IREF_PULSE; // HLW = 3500, CSE = 16140 } nvalue = Settings.energy_current_calibration; unit = UNIT_MICROSECOND; } - else if (((ENERGY_HLW8012 == energy_flg) || (ENERGY_CSE7766 == energy_flg)) && (CMND_CURRENTSET == command_code)) { // milliAmpere - if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 16001)) { - if ((ENERGY_HLW8012 == energy_flg) && hlw_cf1_current_pulse_length) { - Settings.energy_current_calibration = (XdrvMailbox.payload * hlw_cf1_current_pulse_length) / hlw_current_ratio; - } - else if ((ENERGY_CSE7766 == energy_flg) && current_cycle) { - Settings.energy_current_calibration = (XdrvMailbox.payload * current_cycle) / 1000; - } - } + else if ((CMND_POWERSET == command_code) && XnrgCall(FUNC_COMMAND)) { // Watt + snprintf_P(command, sizeof(command), PSTR(D_CMND_POWERCAL)); + nvalue = Settings.energy_power_calibration; + unit = UNIT_MICROSECOND; + } + else if ((CMND_VOLTAGESET == command_code) && XnrgCall(FUNC_COMMAND)) { // Volt + snprintf_P(command, sizeof(command), PSTR(D_CMND_VOLTAGECAL)); + nvalue = Settings.energy_voltage_calibration; + unit = UNIT_MICROSECOND; + } + else if ((CMND_CURRENTSET == command_code) && XnrgCall(FUNC_COMMAND)) { // milliAmpere snprintf_P(command, sizeof(command), PSTR(D_CMND_CURRENTCAL)); nvalue = Settings.energy_current_calibration; unit = UNIT_MICROSECOND; @@ -1070,38 +516,15 @@ boolean EnergyCommand() return serviced; } -/********************************************************************************************/ - void EnergyDrvInit() { energy_flg = ENERGY_NONE; - if ((pin[GPIO_HLW_SEL] < 99) && (pin[GPIO_HLW_CF1] < 99) && (pin[GPIO_HLW_CF] < 99)) { // Sonoff Pow or any HLW8012 based device - energy_flg = ENERGY_HLW8012; - } else if ((SONOFF_S31 == Settings.module) || (SONOFF_POW_R2 == Settings.module)) { // Sonoff S31 or Sonoff Pow R2 - baudrate = 4800; - serial_config = SERIAL_8E1; - energy_flg = ENERGY_CSE7766; -#ifdef USE_PZEM004T - } else if ((pin[GPIO_PZEM_RX] < 99) && (pin[GPIO_PZEM_TX] < 99)) { // Any device with a Pzem004T - energy_flg = ENERGY_PZEM004T; -#endif // USE_PZEM004T - } + XnrgCall(FUNC_PRE_INIT); } void EnergySnsInit() { - if (ENERGY_HLW8012 == energy_flg) HlwInit(); - -#ifdef USE_PZEM004T - if (ENERGY_PZEM004T == energy_flg) { // Software serial init needs to be done here as earlier (serial) interrupts may lead to Exceptions - PzemSerial = new TasmotaSerial(pin[GPIO_PZEM_RX], pin[GPIO_PZEM_TX], 1); - if (PzemSerial->begin(9600)) { - if (PzemSerial->hardwareSerial()) { ClaimSerial(); } - } else { - energy_flg = ENERGY_NONE; - } - } -#endif // USE_PZEM004T + XnrgCall(FUNC_INIT); if (energy_flg) { energy_kWhtoday = (RtcSettingsValid()) ? RtcSettings.energy_kWhtoday : (RtcTime.day_of_year == Settings.energy_kWhdoy) ? Settings.energy_kWhtoday : 0; @@ -1192,17 +615,20 @@ boolean Xdrv03(byte function) { boolean result = false; - if (energy_flg) { + if (FUNC_PRE_INIT == function) { + EnergyDrvInit(); + } + else if (energy_flg) { switch (function) { - case FUNC_PRE_INIT: - EnergyDrvInit(); - break; case FUNC_COMMAND: result = EnergyCommand(); break; case FUNC_SET_POWER: EnergySetPowerSteadyCounter(); break; + case FUNC_SERIAL: + result = XnrgCall(FUNC_SERIAL); + break; } } return result; diff --git a/sonoff/xdrv_06_snfbridge.ino b/sonoff/xdrv_06_snfbridge.ino index 73c93d374..b0518f133 100644 --- a/sonoff/xdrv_06_snfbridge.ino +++ b/sonoff/xdrv_06_snfbridge.ino @@ -592,6 +592,9 @@ boolean Xdrv06(byte function) case FUNC_COMMAND: result = SonoffBridgeCommand(); break; + case FUNC_SERIAL: + result = SonoffBridgeSerialInput(); + break; } } return result; diff --git a/sonoff/xdsp_03_matrix.ino b/sonoff/xdsp_03_matrix.ino index aef33dbc3..4989ad24b 100644 --- a/sonoff/xdsp_03_matrix.ino +++ b/sonoff/xdsp_03_matrix.ino @@ -253,6 +253,10 @@ void MatrixPrintLog(uint8_t direction) strncat(mtx_buffer, (const char*)txt +i, 1); } } + + snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION "[%s]"), mtx_buffer); + AddLog(LOG_LEVEL_DEBUG); + mtx_done = 1; } diff --git a/sonoff/xnrg_01_hlw8012.ino b/sonoff/xnrg_01_hlw8012.ino new file mode 100644 index 000000000..440112c15 --- /dev/null +++ b/sonoff/xnrg_01_hlw8012.ino @@ -0,0 +1,283 @@ +/* + xnrg_01_hlw8012.ino - HLW8012 (Sonoff Pow) energy sensor support for Sonoff-Tasmota + + Copyright (C) 2018 Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_ENERGY_SENSOR +#ifdef USE_HLW8012 +/*********************************************************************************************\ + * HLW8012, BL0937 or HJL-01 - Energy (Sonoff Pow, HuaFan, KMC70011, BlitzWolf) + * + * Based on Source: Shenzhen Heli Technology Co., Ltd +\*********************************************************************************************/ + +#define XNRG_01 1 + +// HLW8012 based (Sonoff Pow, KMC70011, HuaFan) +#define HLW_PREF 10000 // 1000.0W +#define HLW_UREF 2200 // 220.0V +#define HLW_IREF 4545 // 4.545A +#define HLW_SEL_VOLTAGE 1 + +// HJL-01 based (BlitzWolf, Homecube, Gosund) +#define HJL_PREF 1362 +#define HJL_UREF 822 +#define HJL_IREF 3300 +#define HJL_SEL_VOLTAGE 0 + +#define HLW_POWER_PROBE_TIME 10 // Number of seconds to probe for power before deciding none used + +static byte hlw_select_ui_flag; +static byte hlw_ui_flag = 1; +static byte hlw_load_off; +static byte hlw_cf1_timer; +static unsigned long hlw_cf_pulse_length; +static unsigned long hlw_cf_pulse_last_time; +static unsigned long hlw_cf1_pulse_length; +static unsigned long hlw_cf1_pulse_last_time; +static unsigned long hlw_cf1_summed_pulse_length; +static unsigned long hlw_cf1_pulse_counter; +static unsigned long hlw_cf1_voltage_pulse_length; +static unsigned long hlw_cf1_current_pulse_length; +static unsigned long hlw_energy_period_counter; + +static unsigned long hlw_power_ratio = 0; +static unsigned long hlw_voltage_ratio = 0; +static unsigned long hlw_current_ratio = 0; + +static unsigned long hlw_cf1_voltage_max_pulse_counter; +static unsigned long hlw_cf1_current_max_pulse_counter; + +#ifndef USE_WS2812_DMA // Collides with Neopixelbus but solves exception +void HlwCfInterrupt() ICACHE_RAM_ATTR; +void HlwCf1Interrupt() ICACHE_RAM_ATTR; +#endif // USE_WS2812_DMA + +void HlwCfInterrupt() // Service Power +{ + unsigned long us = micros(); + + if (hlw_load_off) { // Restart plen measurement + hlw_cf_pulse_last_time = us; + hlw_load_off = 0; + } else { + hlw_cf_pulse_length = us - hlw_cf_pulse_last_time; + hlw_cf_pulse_last_time = us; + hlw_energy_period_counter++; + } +} + +void HlwCf1Interrupt() // Service Voltage and Current +{ + unsigned long us = micros(); + + hlw_cf1_pulse_length = us - hlw_cf1_pulse_last_time; + hlw_cf1_pulse_last_time = us; + if ((hlw_cf1_timer > 2) && (hlw_cf1_timer < 8)) { // Allow for 300 mSec set-up time and measure for up to 1 second + hlw_cf1_summed_pulse_length += hlw_cf1_pulse_length; + hlw_cf1_pulse_counter++; + if (10 == hlw_cf1_pulse_counter) { + hlw_cf1_timer = 8; // We need up to ten samples within 1 second (low current could take up to 0.3 second) + } + } +} + +/********************************************************************************************/ + +void HlwEvery200ms() +{ + unsigned long hlw_w = 0; + unsigned long hlw_u = 0; + unsigned long hlw_i = 0; + + if (micros() - hlw_cf_pulse_last_time > (HLW_POWER_PROBE_TIME * 1000000)) { + hlw_cf_pulse_length = 0; // No load for some time + hlw_load_off = 1; + } + + if (hlw_cf_pulse_length && energy_power_on && !hlw_load_off) { + hlw_w = (hlw_power_ratio * Settings.energy_power_calibration) / hlw_cf_pulse_length; + energy_power = (float)hlw_w / 10; + } else { + energy_power = 0; + } + + hlw_cf1_timer++; + if (hlw_cf1_timer >= 8) { + hlw_cf1_timer = 0; + hlw_select_ui_flag = (hlw_select_ui_flag) ? 0 : 1; + digitalWrite(pin[GPIO_HLW_SEL], hlw_select_ui_flag); + + if (hlw_cf1_pulse_counter) { + hlw_cf1_pulse_length = hlw_cf1_summed_pulse_length / hlw_cf1_pulse_counter; + } else { + hlw_cf1_pulse_length = 0; + } + if (hlw_select_ui_flag == hlw_ui_flag) { + hlw_cf1_voltage_pulse_length = hlw_cf1_pulse_length; + hlw_cf1_voltage_max_pulse_counter = hlw_cf1_pulse_counter; + + if (hlw_cf1_voltage_pulse_length && energy_power_on) { // If powered on always provide voltage + hlw_u = (hlw_voltage_ratio * Settings.energy_voltage_calibration) / hlw_cf1_voltage_pulse_length; + energy_voltage = (float)hlw_u / 10; + } else { + energy_voltage = 0; + } + + } else { + hlw_cf1_current_pulse_length = hlw_cf1_pulse_length; + hlw_cf1_current_max_pulse_counter = hlw_cf1_pulse_counter; + + if (hlw_cf1_current_pulse_length && energy_power) { // No current if no power being consumed + hlw_i = (hlw_current_ratio * Settings.energy_current_calibration) / hlw_cf1_current_pulse_length; + energy_current = (float)hlw_i / 1000; + } else { + energy_current = 0; + } + + } + hlw_cf1_summed_pulse_length = 0; + hlw_cf1_pulse_counter = 0; + } +} + +void HlwEverySecond() +{ + unsigned long hlw_len; + + if (hlw_energy_period_counter) { + hlw_len = 10000 / hlw_energy_period_counter; + hlw_energy_period_counter = 0; + if (hlw_len) { + energy_kWhtoday_delta += ((hlw_power_ratio * Settings.energy_power_calibration) / hlw_len) / 36; + EnergyUpdateToday(); + } + } +} + +void HlwSnsInit() +{ + if (!Settings.energy_power_calibration || (4975 == Settings.energy_power_calibration)) { + Settings.energy_power_calibration = HLW_PREF_PULSE; + Settings.energy_voltage_calibration = HLW_UREF_PULSE; + Settings.energy_current_calibration = HLW_IREF_PULSE; + } + + if (BLITZWOLF_BWSHP2 == Settings.module) { + hlw_power_ratio = HJL_PREF; + hlw_voltage_ratio = HJL_UREF; + hlw_current_ratio = HJL_IREF; + hlw_ui_flag = HJL_SEL_VOLTAGE; + } else { + hlw_power_ratio = HLW_PREF; + hlw_voltage_ratio = HLW_UREF; + hlw_current_ratio = HLW_IREF; + hlw_ui_flag = HLW_SEL_VOLTAGE; + } + + hlw_cf_pulse_length = 0; + hlw_cf_pulse_last_time = 0; + hlw_cf1_pulse_length = 0; + hlw_cf1_pulse_last_time = 0; + hlw_cf1_voltage_pulse_length = 0; + hlw_cf1_current_pulse_length = 0; + hlw_cf1_voltage_max_pulse_counter = 0; + hlw_cf1_current_max_pulse_counter = 0; + + hlw_load_off = 1; + hlw_energy_period_counter = 0; + + hlw_select_ui_flag = 0; // Voltage; + + pinMode(pin[GPIO_HLW_SEL], OUTPUT); + digitalWrite(pin[GPIO_HLW_SEL], hlw_select_ui_flag); + pinMode(pin[GPIO_HLW_CF1], INPUT_PULLUP); + attachInterrupt(pin[GPIO_HLW_CF1], HlwCf1Interrupt, FALLING); + pinMode(pin[GPIO_HLW_CF], INPUT_PULLUP); + attachInterrupt(pin[GPIO_HLW_CF], HlwCfInterrupt, FALLING); + + hlw_cf1_timer = 0; +} + +void HlwDrvInit() +{ + if (!energy_flg) { + if ((pin[GPIO_HLW_SEL] < 99) && (pin[GPIO_HLW_CF1] < 99) && (pin[GPIO_HLW_CF] < 99)) { // Sonoff Pow or any HLW8012 based device + energy_flg = XNRG_01; + } + } +} + +boolean HlwCommand() +{ + boolean serviced = true; + + if ((CMND_POWERCAL == energy_command_code) || (CMND_VOLTAGECAL == energy_command_code) || (CMND_CURRENTCAL == energy_command_code)) { + + } + else if (CMND_POWERSET == energy_command_code) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3601) && hlw_cf_pulse_length) { + Settings.energy_power_calibration = (XdrvMailbox.payload * 10 * hlw_cf_pulse_length) / hlw_power_ratio; + } + } + else if (CMND_VOLTAGESET == energy_command_code) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 501) && hlw_cf1_voltage_pulse_length) { + Settings.energy_voltage_calibration = (XdrvMailbox.payload * 10 * hlw_cf1_voltage_pulse_length) / hlw_voltage_ratio; + } + } + else if (CMND_CURRENTSET == energy_command_code) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 16001) && hlw_cf1_current_pulse_length) { + Settings.energy_current_calibration = (XdrvMailbox.payload * hlw_cf1_current_pulse_length) / hlw_current_ratio; + } + } + else serviced = false; // Unknown command + + return serviced; +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +int Xnrg01(byte function) +{ + int result = 0; + + if (FUNC_PRE_INIT == function) { + HlwDrvInit(); + } + else if (XNRG_01 == energy_flg) { + switch (function) { + case FUNC_INIT: + HlwSnsInit(); + break; + case FUNC_EVERY_SECOND: + HlwEverySecond(); + break; + case FUNC_EVERY_200_MSECOND: + HlwEvery200ms(); + break; + case FUNC_COMMAND: + result = HlwCommand(); + break; + } + } + return result; +} + +#endif // USE_HLW8012 +#endif // USE_ENERGY_SENSOR diff --git a/sonoff/xnrg_02_cse7766.ino b/sonoff/xnrg_02_cse7766.ino new file mode 100644 index 000000000..640da704e --- /dev/null +++ b/sonoff/xnrg_02_cse7766.ino @@ -0,0 +1,248 @@ +/* + xnrg_02_cse7766.ino - CSE7766 energy sensor support for Sonoff-Tasmota + + Copyright (C) 2018 Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_ENERGY_SENSOR +#ifdef USE_CSE7766 +/*********************************************************************************************\ + * CSE7766 - Energy (Sonoff S31 and Sonoff Pow R2) + * + * Based on datasheet from http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf +\*********************************************************************************************/ + +#define XNRG_02 2 + +#define CSE_NOT_CALIBRATED 0xAA + +#define CSE_PULSES_NOT_INITIALIZED -1 + +#define CSE_PREF 1000 +#define CSE_UREF 100 + +uint8_t cse_receive_flag = 0; + +long voltage_cycle = 0; +long current_cycle = 0; +long power_cycle = 0; +unsigned long power_cycle_first = 0; +long cf_pulses = 0; +long cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED; + +void CseReceived() +{ + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 + // 55 5A 02 F7 60 00 03 AB 00 40 10 02 60 5D 51 A6 58 03 E9 EF 71 0B 7A 36 + // Hd Id VCal---- Voltage- ICal---- Current- PCal---- Power--- Ad CF--- Ck + + uint8_t header = serial_in_buffer[0]; + if ((header & 0xFC) == 0xFC) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Abnormal hardware")); + return; + } + + // Get chip calibration data (coefficients) and use as initial defaults + if (HLW_UREF_PULSE == Settings.energy_voltage_calibration) { + long voltage_coefficient = 191200; // uSec + if (CSE_NOT_CALIBRATED != header) { + voltage_coefficient = serial_in_buffer[2] << 16 | serial_in_buffer[3] << 8 | serial_in_buffer[4]; + } + Settings.energy_voltage_calibration = voltage_coefficient / CSE_UREF; + } + if (HLW_IREF_PULSE == Settings.energy_current_calibration) { + long current_coefficient = 16140; // uSec + if (CSE_NOT_CALIBRATED != header) { + current_coefficient = serial_in_buffer[8] << 16 | serial_in_buffer[9] << 8 | serial_in_buffer[10]; + } + Settings.energy_current_calibration = current_coefficient; + } + if (HLW_PREF_PULSE == Settings.energy_power_calibration) { + long power_coefficient = 5364000; // uSec + if (CSE_NOT_CALIBRATED != header) { + power_coefficient = serial_in_buffer[14] << 16 | serial_in_buffer[15] << 8 | serial_in_buffer[16]; + } + Settings.energy_power_calibration = power_coefficient / CSE_PREF; + } + + uint8_t adjustement = serial_in_buffer[20]; + voltage_cycle = serial_in_buffer[5] << 16 | serial_in_buffer[6] << 8 | serial_in_buffer[7]; + current_cycle = serial_in_buffer[11] << 16 | serial_in_buffer[12] << 8 | serial_in_buffer[13]; + power_cycle = serial_in_buffer[17] << 16 | serial_in_buffer[18] << 8 | serial_in_buffer[19]; + cf_pulses = serial_in_buffer[21] << 8 | serial_in_buffer[22]; + + if (energy_power_on) { // Powered on + if (adjustement & 0x40) { // Voltage valid + energy_voltage = (float)(Settings.energy_voltage_calibration * CSE_UREF) / (float)voltage_cycle; + } + if (adjustement & 0x10) { // Power valid + if ((header & 0xF2) == 0xF2) { // Power cycle exceeds range + energy_power = 0; + } else { + if (0 == power_cycle_first) power_cycle_first = power_cycle; // Skip first incomplete power_cycle + if (power_cycle_first != power_cycle) { + power_cycle_first = -1; + energy_power = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)power_cycle; + } else { + energy_power = 0; + } + } + } else { + power_cycle_first = 0; + energy_power = 0; // Powered on but no load + } + if (adjustement & 0x20) { // Current valid + if (0 == energy_power) { + energy_current = 0; + } else { + energy_current = (float)Settings.energy_current_calibration / (float)current_cycle; + } + } + } else { // Powered off + power_cycle_first = 0; + energy_voltage = 0; + energy_power = 0; + energy_current = 0; + } +} + +bool CseSerialInput() +{ + if (cse_receive_flag) { + serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; + if (24 == serial_in_byte_counter) { + + AddLogSerial(LOG_LEVEL_DEBUG_MORE); + + uint8_t checksum = 0; + for (byte i = 2; i < 23; i++) { checksum += serial_in_buffer[i]; } + if (checksum == serial_in_buffer[23]) { + CseReceived(); + cse_receive_flag = 0; + return 1; + } else { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE)); + do { // Sync buffer with data (issue #1907 and #3425) + memmove(serial_in_buffer, serial_in_buffer +1, 24); + serial_in_byte_counter--; + } while ((serial_in_byte_counter > 2) && (0x5A != serial_in_buffer[1])); + if (0x5A != serial_in_buffer[1]) { + cse_receive_flag = 0; + serial_in_byte_counter = 0; + } + } + } + } else { + if ((0x5A == serial_in_byte) && (1 == serial_in_byte_counter)) { // 0x5A - Packet header 2 + cse_receive_flag = 1; + } else { + serial_in_byte_counter = 0; + } + serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; + } + serial_in_byte = 0; // Discard + return 0; +} + +/********************************************************************************************/ + +void CseEverySecond() +{ + long cf_frequency = 0; + + if (CSE_PULSES_NOT_INITIALIZED == cf_pulses_last_time) { + cf_pulses_last_time = cf_pulses; // Init after restart + } else { + if (cf_pulses < cf_pulses_last_time) { // Rolled over after 65535 pulses + cf_frequency = (65536 - cf_pulses_last_time) + cf_pulses; + } else { + cf_frequency = cf_pulses - cf_pulses_last_time; + } + if (cf_frequency && energy_power) { + cf_pulses_last_time = cf_pulses; + energy_kWhtoday_delta += (cf_frequency * Settings.energy_power_calibration) / 36; + EnergyUpdateToday(); + } + } +} + +void CseDrvInit() +{ + if (!energy_flg) { + if ((SONOFF_S31 == Settings.module) || (SONOFF_POW_R2 == Settings.module)) { // Sonoff S31 or Sonoff Pow R2 + baudrate = 4800; + serial_config = SERIAL_8E1; + energy_flg = XNRG_02; + } + } +} + +boolean CseCommand() +{ + boolean serviced = true; + + if ((CMND_POWERCAL == energy_command_code) || (CMND_VOLTAGECAL == energy_command_code) || (CMND_CURRENTCAL == energy_command_code)) { + + } + else if (CMND_POWERSET == energy_command_code) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3601) && power_cycle) { + Settings.energy_power_calibration = (XdrvMailbox.payload * power_cycle) / CSE_PREF; + } + } + else if (CMND_VOLTAGESET == energy_command_code) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 501) && voltage_cycle) { + Settings.energy_voltage_calibration = (XdrvMailbox.payload * voltage_cycle) / CSE_UREF; + } + } + else if (CMND_CURRENTSET == energy_command_code) { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 16001) && current_cycle) { + Settings.energy_current_calibration = (XdrvMailbox.payload * current_cycle) / 1000; + } + } + else serviced = false; // Unknown command + + return serviced; +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +int Xnrg02(byte function) +{ + int result = 0; + + if (FUNC_PRE_INIT == function) { + CseDrvInit(); + } + else if (XNRG_02 == energy_flg) { + switch (function) { + case FUNC_EVERY_SECOND: + CseEverySecond(); + break; + case FUNC_COMMAND: + result = CseCommand(); + break; + case FUNC_SERIAL: + result = CseSerialInput(); + break; + } + } + return result; +} + +#endif // USE_CSE7766 +#endif // USE_ENERGY_SENSOR diff --git a/sonoff/xnrg_03_pzem004t.ino b/sonoff/xnrg_03_pzem004t.ino new file mode 100644 index 000000000..d009feea4 --- /dev/null +++ b/sonoff/xnrg_03_pzem004t.ino @@ -0,0 +1,248 @@ +/* + xnrg_03_pzem004t.ino - PZEM004T energy sensor support for Sonoff-Tasmota + + Copyright (C) 2018 Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_ENERGY_SENSOR +#ifdef USE_PZEM004T +/*********************************************************************************************\ + * PZEM004T - Energy + * + * Source: Victor Ferrer https://github.com/vicfergar/Sonoff-MQTT-OTA-Arduino + * Based on: PZEM004T library https://github.com/olehs/PZEM004T + * + * Hardware Serial will be selected if GPIO1 = [PZEM Rx] and [GPIO3 = PZEM Tx] +\*********************************************************************************************/ + +#define XNRG_03 3 + +#include + +TasmotaSerial *PzemSerial; + +#define PZEM_VOLTAGE (uint8_t)0xB0 +#define RESP_VOLTAGE (uint8_t)0xA0 + +#define PZEM_CURRENT (uint8_t)0xB1 +#define RESP_CURRENT (uint8_t)0xA1 + +#define PZEM_POWER (uint8_t)0xB2 +#define RESP_POWER (uint8_t)0xA2 + +#define PZEM_ENERGY (uint8_t)0xB3 +#define RESP_ENERGY (uint8_t)0xA3 + +#define PZEM_SET_ADDRESS (uint8_t)0xB4 +#define RESP_SET_ADDRESS (uint8_t)0xA4 + +#define PZEM_POWER_ALARM (uint8_t)0xB5 +#define RESP_POWER_ALARM (uint8_t)0xA5 + +#define PZEM_DEFAULT_READ_TIMEOUT 500 + +/*********************************************************************************************/ + +struct PZEMCommand { + uint8_t command; + uint8_t addr[4]; + uint8_t data; + uint8_t crc; +}; + +IPAddress pzem_ip(192, 168, 1, 1); + +uint8_t PzemCrc(uint8_t *data) +{ + uint16_t crc = 0; + for (uint8_t i = 0; i < sizeof(PZEMCommand) -1; i++) crc += *data++; + return (uint8_t)(crc & 0xFF); +} + +void PzemSend(uint8_t cmd) +{ + PZEMCommand pzem; + + pzem.command = cmd; + for (uint8_t i = 0; i < sizeof(pzem.addr); i++) pzem.addr[i] = pzem_ip[i]; + pzem.data = 0; + + uint8_t *bytes = (uint8_t*)&pzem; + pzem.crc = PzemCrc(bytes); + + PzemSerial->flush(); + PzemSerial->write(bytes, sizeof(pzem)); +} + +bool PzemReceiveReady() +{ + return PzemSerial->available() >= (int)sizeof(PZEMCommand); +} + +bool PzemRecieve(uint8_t resp, float *data) +{ + // 0 1 2 3 4 5 6 + // A4 00 00 00 00 00 A4 - Set address + // A0 00 D4 07 00 00 7B - Voltage (212.7V) + // A1 00 00 0A 00 00 AB - Current (0.1A) + // A1 00 00 00 00 00 A1 - No current + // A2 00 16 00 00 00 B8 - Power (22W) + // A2 00 00 00 00 00 A2 - No power + // A3 00 08 A4 00 00 4F - Energy (2.212kWh) + // A3 01 86 9F 00 00 C9 - Energy (99.999kWh) + + uint8_t buffer[sizeof(PZEMCommand)] = { 0 }; + + unsigned long start = millis(); + uint8_t len = 0; + while ((len < sizeof(PZEMCommand)) && (millis() - start < PZEM_DEFAULT_READ_TIMEOUT)) { + if (PzemSerial->available() > 0) { + uint8_t c = (uint8_t)PzemSerial->read(); + if (!c && !len) { + continue; // skip 0 at startup + } + if ((1 == len) && (buffer[0] == c)) { + len--; + continue; // fix skewed data + } + buffer[len++] = c; + } + } + + AddLogSerial(LOG_LEVEL_DEBUG_MORE, buffer, len); + + if (len != sizeof(PZEMCommand)) { +// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem comms timeout")); + return false; + } + if (buffer[6] != PzemCrc(buffer)) { +// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem crc error")); + return false; + } + if (buffer[0] != resp) { +// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem bad response")); + return false; + } + + switch (resp) { + case RESP_VOLTAGE: + *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 10.0); // 65535.x V + break; + case RESP_CURRENT: + *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 100.0); // 65535.xx A + break; + case RESP_POWER: + *data = (float)(buffer[1] << 8) + buffer[2]; // 65535 W + break; + case RESP_ENERGY: + *data = (float)((uint32_t)buffer[1] << 16) + ((uint16_t)buffer[2] << 8) + buffer[3]; // 16777215 Wh + break; + } + return true; +} + +/*********************************************************************************************/ + +const uint8_t pzem_commands[] { PZEM_SET_ADDRESS, PZEM_VOLTAGE, PZEM_CURRENT, PZEM_POWER, PZEM_ENERGY }; +const uint8_t pzem_responses[] { RESP_SET_ADDRESS, RESP_VOLTAGE, RESP_CURRENT, RESP_POWER, RESP_ENERGY }; + +uint8_t pzem_read_state = 0; +uint8_t pzem_sendRetry = 0; + +void PzemEvery200ms() +{ + bool data_ready = PzemReceiveReady(); + + if (data_ready) { + float value = 0; + if (PzemRecieve(pzem_responses[pzem_read_state], &value)) { + switch (pzem_read_state) { + case 1: // Voltage as 230.2V + energy_voltage = value; + break; + case 2: // Current as 17.32A + energy_current = value; + break; + case 3: // Power as 20W + energy_power = value; + break; + case 4: // Total energy as 99999Wh + if (!energy_start || (value < energy_start)) energy_start = value; // Init after restart and hanlde roll-over if any + energy_kWhtoday += (value - energy_start) * 100; + energy_start = value; + EnergyUpdateToday(); + break; + } + pzem_read_state++; + if (5 == pzem_read_state) pzem_read_state = 1; + } + } + + if (0 == pzem_sendRetry || data_ready) { + pzem_sendRetry = 5; + PzemSend(pzem_commands[pzem_read_state]); + } + else { + pzem_sendRetry--; + } +} + +void PzemSnsInit() +{ + // Software serial init needs to be done here as earlier (serial) interrupts may lead to Exceptions + PzemSerial = new TasmotaSerial(pin[GPIO_PZEM_RX], pin[GPIO_PZEM_TX], 1); + if (PzemSerial->begin(9600)) { + if (PzemSerial->hardwareSerial()) { ClaimSerial(); } + } else { + energy_flg = ENERGY_NONE; + } +} + +void PzemDrvInit() +{ + if (!energy_flg) { + if ((pin[GPIO_PZEM_RX] < 99) && (pin[GPIO_PZEM_TX] < 99)) { // Any device with a Pzem004T + energy_flg = XNRG_03; + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +int Xnrg03(byte function) +{ + int result = 0; + + if (FUNC_PRE_INIT == function) { + PzemDrvInit(); + } + else if (XNRG_03 == energy_flg) { + switch (function) { + case FUNC_INIT: + PzemSnsInit(); + break; + case FUNC_EVERY_200_MSECOND: + PzemEvery200ms(); + break; + } + } + return result; +} + +#endif // USE_PZEM004T +#endif // USE_ENERGY_SENSOR diff --git a/sonoff/xnrg_interface.ino b/sonoff/xnrg_interface.ino new file mode 100644 index 000000000..d9726ac05 --- /dev/null +++ b/sonoff/xnrg_interface.ino @@ -0,0 +1,57 @@ +/* + xnrg_interface.ino - Energy driver interface support for Sonoff-Tasmota + + Copyright (C) 2018 Theo Arends inspired by ESPEasy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +int (* const xnrg_func_ptr[])(byte) PROGMEM = { // Energy driver Function Pointers +#ifdef XNRG_01 + &Xnrg01, +#endif + +#ifdef XNRG_02 + &Xnrg02, +#endif + +#ifdef XNRG_03 + &Xnrg03, +#endif + +#ifdef XNRG_04 + &Xnrg04, +#endif + +#ifdef XNRG_05 + &Xnrg05, +#endif + +#ifdef XNRG_06 + &Xnrg06 +#endif +}; + +const uint8_t xnrg_present = sizeof(xnrg_func_ptr) / sizeof(xnrg_func_ptr[0]); // Number of drivers found + +int XnrgCall(byte Function) +{ + int result = 0; + + for (byte x = 0; x < xnrg_present; x++) { + result = xnrg_func_ptr[x](Function); + if (result) break; + } + return result; +}