mirror of https://github.com/arendst/Tasmota.git
Merge branch 'arendst/development' into development
This commit is contained in:
commit
6d80213657
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
|
|
|
@ -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 };
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.h>
|
||||
|
||||
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;
|
||||
|
|
|
@ -592,6 +592,9 @@ boolean Xdrv06(byte function)
|
|||
case FUNC_COMMAND:
|
||||
result = SonoffBridgeCommand();
|
||||
break;
|
||||
case FUNC_SERIAL:
|
||||
result = SonoffBridgeSerialInput();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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.h>
|
||||
|
||||
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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
Loading…
Reference in New Issue