Files
OpenBK7231T_App/src/driver/drv_bl_shared.c
MaxineMuster a34f90bcbb DS3231 RTC driver embedded in "local clock" (PR#1729) (#1792)
* Try NTP split update to actual source

* fix windows makefiles and timed_events

* fix typo

* Fix sprintf format

* sprintf format - next try to fix for ESP and ESP8266

* sprintf - special case for ESP8266

* Adding GUI button to set clock

* fix

* Add debug output

* fix missing HTTP output
use original obk_config.h - only renaming functions - to see filesizes

* limit functions to NTP ode CLOCK present

* try fix selftests

* Enable simple clock for all platforms, even if NTP is not enabled

* test fixing merge-conflict

* try fixing merge conflicts #1

* try fix merge conflict

* Fix typos
consolidate time display on main page
fix double comments preventing several documentation details ($mday, $month ...) on docs/constants.md
enabling more time stuff for W800 for local testing.

* Fix setDST argument passing

* only test DST if clock is running

* Fix setting epoch time - use atol(<string arg>) instead of Tokenizer_GetArgInteger(0) for epoch
Change user_main.c to use xticks as uptime for g_secondsElapsed

* Fix g_secondsElapesd for Windows - no xTicks there so stick to old way with g_secondsElapesd++ here ...

* Fix simulator build
remove additional defines for W800 used for testing

* Use esp_timer_get_time() instead of xTicks for ESP.
Seems much better, 30 seconds difference in one day
(used to be over 10 minutes off in 12 hours)

* Include DS3231 code

* Include DS3231 RTC driver

* Try simple fix for TXW81X without RTOS

* Second try fix TXW81X

* Fix comments for doc

* fix obk_config (still using "#define ENABLE_NTP_SUNRISE_SUNSET" instead of new "#define ENABLE_CLOCK_SUNRISE_SUNSET")
fix RDA5981
(as already done in branch Split_NTP_new)

* fix deviceclock code for corrected #define

* enable DS3231 driver for all platforms to test compilation and compare sizes

* fix selftest DST and sunrise/sunset (command names are "CLOCK_..." not "NTP...")

* Fix selftest for DST and sunset/sunrise events

* Add debug to try fixing selftest issue

* more debug

* Disable bedug output after fix.

* Disable DS3231 for all platforms. To tes, use previous commit

* Fix comments and handling of year when setting time of DS3231
Switch to internal time functions to reduce flash usage (avoid gmtime and mktime)

* Fix direct acces of NTP-time via g_ntpTime - use function "Clock_GetCurrentTime()"
switch to obktime instead of time.h "gmtime"

* Fix missing include for deviceclock and obktime

* made ds3231 driver dependent on "#define ENABLE_DRIVER_DS3231"
change "deviceclock.c" to set DS3231 RTC on every clock set command if driver is running, so e.g. every NTP update will also adjust RTC time

* fix missing includes for DRV_IsRunning() and DS3231_SetEpoch()

* introducing DS3231_informClockWasSet(bool force) - called by drv_deviceclock whenever clock is set.
so DS3231 driver can ste the clock, if needed - using "force" will force setting (no supprise ;-))

* Fixes to drv_main.c (missing args for onHassDiscovery)
Fixed missing arg "bPreState" in XX_AppendInformationToHTTPIndexPage()
Changed name from CLOCK to TIME
changed some "gmtime" to obktime "calculateComponents()a

* fixes

* fix obk_config.h

* fix missing include

* fix include for drv_txw81x_camera.c

* Directly call TIME "driver" functions, but don't use as real driver - so hiding it's presence in GUI

* fix missing OnEverySecond for TIME

* Prepare for merge: disable DS3231 driver for all platforms

* fix broken obk_config.h

* revert unintended change of src/driver/drv_max72xx_single.c
2025-12-04 14:46:51 +01:00

1089 lines
47 KiB
C

#include "drv_bl_shared.h"
#include "../obk_config.h"
#if ENABLE_BL_SHARED
#include "../new_cfg.h"
#include "../new_pins.h"
#include "../cJSON/cJSON.h"
#include "../hal/hal_flashVars.h"
#include "../logging/logging.h"
#include "../mqtt/new_mqtt.h"
#include "../hal/hal_ota.h"
#include "drv_local.h"
#include "drv_ntp.h"
#include "drv_deviceclock.h"
#include "drv_public.h"
#include "drv_uart.h"
#include "../cmnds/cmd_public.h" //for enum EventCode
#include <math.h>
//#include <time.h>
#include "../libraries/obktime/obktime.h" // for time functions
#if ENABLE_BL_TWIN
#define BL_SENSDATASETS_COUNT 2
#else
#define BL_SENSDATASETS_COUNT 1
#endif
#if ENABLE_BL_TWIN
const int OBK_CONSUMPTION_STORED_LAST[2] = { OBK_CONSUMPTION_YESTERDAY,OBK_CONSUMPTION_TODAY };
#else
const int OBK_CONSUMPTION_STORED_LAST[1] = { OBK_CONSUMPTION__DAILY_LAST };
#endif
#if ENABLE_BL_TWIN
int stat_updatesSkipped[BL_SENSDATASETS_COUNT] = { 0 ,0 };
int stat_updatesSent[BL_SENSDATASETS_COUNT] = { 0,0 };
bool sensors_reciveddata[BL_SENSDATASETS_COUNT] = { 0,0 }; //1 if data received
float lastSavedEnergyCounterValue[BL_SENSDATASETS_COUNT] = { 0.0f, 0.0f };
int actual_mday[BL_SENSDATASETS_COUNT] = { -1 ,-1 };
#else
float lastSavedEnergyCounterValue[BL_SENSDATASETS_COUNT] = { 0.0f };
int stat_updatesSkipped[BL_SENSDATASETS_COUNT] = { 0 };
int stat_updatesSent[BL_SENSDATASETS_COUNT] = { 0 };
bool sensors_reciveddata[BL_SENSDATASETS_COUNT] = { 0 }; //1 if data received
int actual_mday[BL_SENSDATASETS_COUNT] = { -1 };
#endif
// Order corrsponds to enums OBK_VOLTAGE - OBK__LAST
// note that Wh/kWh units are overridden in hass_init_energy_sensor_device_info()
const char UNIT_WH[] = "Wh";
struct energysensor {
energySensorNames_t names;
byte rounding_decimals;
// Variables below are for optimization
// We can't send a full MQTT update every second.
// It's too much for Beken, and it's too much for LWIP 2 MQTT library,
// especially when actively browsing site and using JS app Log Viewer.
// It even fails to publish with -1 error (can't alloc next packet)
// So we publish when value changes from certain threshold or when a certain time passes.
float changeSendThreshold;
double lastReading; //double only needed for energycounter i.e. OBK_CONSUMPTION_TOTAL to avoid rounding issues as value becomes high
double lastSentValue; // what are the last values we sent over the MQTT?
int noChangeFrame; // how much update frames has passed without sending MQTT update of read values?
};
typedef struct energysensor energysensor_t;
typedef struct {
energysensor_t sensors[OBK__NUM_SENSORS];
} energysensdataset_t;
energysensdataset_t datasetlist[BL_SENSDATASETS_COUNT] = {
{ {
//.hass_dev_class, .units, .name_friendly, .name_mqtt, .hass_uniq_id_suffix, .rounding_decimals, .changeSendThreshold
{{"voltage", "V", "Voltage", "voltage", "0", }, 1, 0.25, }, // OBK_VOLTAGE
{{"current", "A", "Current", "current", "1", }, 3, 0.002, }, // OBK_CURRENT
{{"power", "W", "Power", "power", "2", }, 2, 0.25, }, // OBK_POWER
{{"frequency", "Hz", "Frequency", "138", "138", }, 2, 0.02, }, // OBK_FREQUENCY - SPECIAL_CHANNEL_OBK_FREQUENCY is 138
{{"apparent_power", "VA", "Apparent Power", "power_apparent", "9", }, 2, 0.25, }, // OBK_POWER_APPARENT
{{"reactive_power", "var", "Reactive Power", "power_reactive", "10", }, 2, 0.25, }, // OBK_POWER_REACTIVE
{{"power_factor", "", "Power Factor", "power_factor", "11", }, 2, 0.05, }, // OBK_POWER_FACTOR
{{"energy", UNIT_WH, "Energy Total", "energycounter", "3", }, 3, 0.1, }, // OBK_CONSUMPTION_TOTAL
{{"energy", UNIT_WH, "Energy Last Hour", "energycounter_last_hour", "4", }, 3, 0.1, }, // OBK_CONSUMPTION_LAST_HOUR
//{{"", "", "Consumption Stats", "consumption_stats", "5", }, 0, 0, }, // OBK_CONSUMPTION_STATS
{{"energy", UNIT_WH, "Energy Today", "energycounter_today", "7", }, 3, 0.1, }, // OBK_CONSUMPTION_TODAY
{{"energy", UNIT_WH, "Energy Yesterday", "energycounter_yesterday", "6", }, 3, 0.1, }, // OBK_CONSUMPTION_YESTERDAY
{{"energy", UNIT_WH, "Energy 2 Days Ago", "energycounter_2_days_ago", "12", }, 3, 0.1, }, // OBK_CONSUMPTION_2_DAYS_AGO
{{"energy", UNIT_WH, "Energy 3 Days Ago", "energycounter_3_days_ago", "13", }, 3, 0.1, }, // OBK_CONSUMPTION_3_DAYS_AGO
{{"timestamp", "", "Energy Clear Date", "energycounter_clear_date", "8", }, 0, 86400, }, // OBK_CONSUMPTION_CLEAR_DATE
} }
#if BL_SENSDATASETS_COUNT==2
,
{ {
//.hass_dev_class, .units, .name_friendly, .name_mqtt, .hass_uniq_id_suffix, .rounding_decimals, .changeSendThreshold
{{"voltage", "V", "Voltage B", "voltage_b", "b_0", }, 1, 0.25, }, // OBK_VOLTAGE
{{"current", "A", "Current B", "current_b", "b_1", }, 3, 0.002, }, // OBK_CURRENT
{{"power", "W", "Power B", "power_b", "b_2", }, 2, 0.25, }, // OBK_POWER
{{"frequency", "Hz", "Frequency B", "frequency_b", "b_138", }, 2, 0.02, }, // OBK_FREQUENCY
{{"apparent_power", "VA", "Apparent Power B", "power_apparent_b", "b_9", }, 2, 0.25, }, // OBK_POWER_APPARENT
{{"reactive_power", "var", "Reactive Power B", "power_reactive_b", "b_10", }, 2, 0.25, }, // OBK_POWER_REACTIVE
{{"power_factor", "", "Power Factor B", "power_factor_b", "b_11", }, 2, 0.05, }, // OBK_POWER_FACTOR
{{"energy", UNIT_WH, "Energy Total B", "energycounter_b", "b_3", }, 3, 0.1, }, // OBK_CONSUMPTION_TOTAL
{{"energy", UNIT_WH, "Energy Last Hour B", "energycounter_last_hour_b", "b_4", }, 3, 0.1, }, // OBK_CONSUMPTION_LAST_HOUR
//{{"", "", "Consumption Stats B", "consumption_stats_b", "b_5", }, 0, 0, }, // OBK_CONSUMPTION_STATS
{{"energy", UNIT_WH, "Energy Today B", "energycounter_today_b", "b_7", }, 3, 0.1, }, // OBK_CONSUMPTION_TODAY
{{"energy", UNIT_WH, "Energy Yesterday B", "energycounter_yesterday_b", "b_6", }, 3, 0.1, }, // OBK_CONSUMPTION_YESTERDAY
{{"energy", UNIT_WH, "Energy 2 Days Ago B", "energycounter_2_days_ago_b", "b_12", }, 3, 0.1, }, // OBK_CONSUMPTION_2_DAYS_AGO
{{"energy", UNIT_WH, "Energy 3 Days Ago B", "energycounter_3_days_ago_b", "b_13", }, 3, 0.1, }, // OBK_CONSUMPTION_3_DAYS_AGO
{{"timestamp", "", "Energy Clear Date B", "energycounter_clear_date_b", "b_8", }, 0, 86400, }, // OBK_CONSUMPTION_CLEAR_DATE
} }
#endif
};
//static double energyCounter = 0.0;
portTickType energyCounterStamp[BL_SENSDATASETS_COUNT];
bool energyCounterStatsEnable = false;
int energyCounterSampleCount = 60;
int energyCounterSampleInterval = 60;
float *energyCounterMinutes = NULL;
portTickType energyCounterMinutesStamp;
long energyCounterMinutesIndex;
bool energyCounterStatsJSONEnable = false;
float changeSavedThresholdEnergy = 10.0f;
long ConsumptionSaveCounter = 0;
portTickType lastConsumptionSaveStamp;
time_t ConsumptionResetTime = 0;
int changeSendAlwaysFrames = 60;
int changeDoNotSendMinFrames = 5;
void BL_ResetRecivedDataBool() {
for (int i = 0; i < BL_SENSDATASETS_COUNT; i++) sensors_reciveddata[i] = 0;
}
#if ENABLE_BL_TWIN
void BL09XX_AppendInformationToHTTPIndexPageEx(int asensdatasetix, http_request_t *request)
{
if ((asensdatasetix < 0) || (asensdatasetix >= BL_SENSDATASETS_COUNT)) return; //to avoid bad index on data[BL_SENSDATASETS_COUNT]
#else
void BL09XX_AppendInformationToHTTPIndexPage(http_request_t * request, int bPreState)
{
if (bPreState)
return;
int asensdatasetix = BL_SENSORS_IX_0;
#endif
energysensdataset_t* sensdataset = &datasetlist[asensdatasetix];
int i;
const char *mode;
// struct tm *ltm;
if(DRV_IsRunning("BL0937")) {
mode = "BL0937";
} else if(DRV_IsRunning("BL0942")) {
mode = "BL0942";
} else if (DRV_IsRunning("BL0942SPI")) {
mode = "BL0942SPI";
} else if(DRV_IsRunning("CSE7766")) {
mode = "CSE7766";
} else if(DRV_IsRunning("RN8209")) {
mode = "RN8209";
} else {
mode = "PWR";
}
poststr(request, "<hr><table style='width:100%'>");
for (int i = OBK__FIRST; i <= OBK_CONSUMPTION__DAILY_LAST; i++) {
#if ENABLE_BL_TWIN
//in twin mode, for ix1 is possible to skip OBK_VOLTAGE, dont skip for now
//if ((asensdatasetix > BL_SENSORS_IX_0) && (i == OBK_VOLTAGE)) continue;
//in twin mode, for ix0 is last OBK_CONSUMPTION_YESTERDAY, for ix1 ,OBK_CONSUMPTION_TODAY
if (i > OBK_CONSUMPTION_STORED_LAST[asensdatasetix]) continue;
#endif
// conditions for frequency
if (i == OBK_FREQUENCY && (asensdatasetix != BL_SENSORS_IX_0 || isnan(sensdataset->sensors[i].lastReading))) continue;
if ((energyCounterMinutes == NULL) && (i == OBK_CONSUMPTION_LAST_HOUR)) {
continue;
}
if (i <= OBK__NUM_MEASUREMENTS || TIME_IsTimeSynced()) {
poststr(request, "<tr><td><b>");
poststr(request, sensdataset->sensors[i].names.name_friendly);
poststr(request, "</b></td><td style='text-align: right;'>");
hprintf255(request, "%.*f</td><td>%s</td>", sensdataset->sensors[i].rounding_decimals,
(i == OBK_CONSUMPTION_TOTAL ? 0.001 : 1) * sensdataset->sensors[i].lastReading, //always display OBK_CONSUMPTION_TOTAL in kwh
i == OBK_CONSUMPTION_TOTAL ? "kWh": sensdataset->sensors[i].names.units);
}
};
poststr(request, "</table>");
hprintf255(request, "(changes sent %i, skipped %i, saved %li) - %s<hr>",
stat_updatesSent[asensdatasetix], stat_updatesSkipped[asensdatasetix], ConsumptionSaveCounter,
mode);
if (asensdatasetix == BL_SENSORS_IX_0)
{
poststr(request, "<h5>Energy Clear Date: ");
if (ConsumptionResetTime) {
/* ltm = gmtime(&ConsumptionResetTime);
hprintf255(request, "%04d-%02d-%02d %02d:%02d:%02d",
ltm->tm_year+1900, ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
*/
hprintf255(request, "%s",TS2STR(ConsumptionResetTime,TIME_FORMAT_LONG));
} else {
poststr(request, "(not set)");
}
hprintf255(request, "<br>");
if(TIME_IsTimeSynced()==false) {
hprintf255(request,"Device time not in sync, daily energy stats disabled.");
}
hprintf255(request, "</h5>");
if (energyCounterStatsEnable == true)
{
/********************************************************************************************************************/
hprintf255(request,"<h2>Periodic Statistics</h2><h5>Consumption (during this period): ");
hprintf255(request,"%1.*f Wh<br>", sensdataset->sensors[OBK_CONSUMPTION_LAST_HOUR].rounding_decimals, DRV_GetReading(OBK_CONSUMPTION_LAST_HOUR));
hprintf255(request,"Sampling interval: %d sec<br>History length: ",energyCounterSampleInterval);
hprintf255(request,"%d samples<br>History per samples:<br>",energyCounterSampleCount);
if (energyCounterMinutes != NULL)
{
for(i=0; i<energyCounterSampleCount; i++)
{
if ((i%20)==0)
{
hprintf255(request, "%1.1f", energyCounterMinutes[i]);
} else {
hprintf255(request, ", %1.1f", energyCounterMinutes[i]);
}
if ((i%20)==19)
{
hprintf255(request, "<br>");
}
}
// energyCounterMinutesIndex is a long type, we need to use %ld instead of %d
if ((i%20)!=0)
hprintf255(request, "<br>");
hprintf255(request, "History Index: %ld<br>JSON Stats: %s <br>", energyCounterMinutesIndex,
(energyCounterStatsJSONEnable == true) ? "enabled" : "disabled");
}
hprintf255(request, "</h5>");
} else {
hprintf255(request,"<h5>Periodic Statistics disabled. Use startup command SetupEnergyStats to enable function.</h5>");
}
/********************************************************************************************************************/
}
}
#if ENABLE_BL_TWIN
void BL09XX_AppendInformationToHTTPIndexPage(http_request_t* request, int bPreState) {
if (bPreState)
return;
if (sensors_reciveddata[BL_SENSORS_IX_0]) {
BL09XX_AppendInformationToHTTPIndexPageEx(BL_SENSORS_IX_0, request);
}
if (sensors_reciveddata[BL_SENSORS_IX_1]) {
BL09XX_AppendInformationToHTTPIndexPageEx(BL_SENSORS_IX_1, request);
}
}
#endif
void BL09XX_SaveEmeteringStatistics()
{
energysensdataset_t* sensdataset = &datasetlist[BL_SENSORS_IX_0];
#if ENABLE_BL_TWIN
energysensdataset_t* sensdataset1 = &datasetlist[BL_SENSORS_IX_1];
#endif
ENERGY_METERING_DATA data;
memset(&data, 0, sizeof(ENERGY_METERING_DATA));
data.TotalConsumption = (float)sensdataset->sensors[OBK_CONSUMPTION_TOTAL].lastReading;
data.TodayConsumpion = (float)sensdataset->sensors[OBK_CONSUMPTION_TODAY].lastReading;
data.YesterdayConsumption = (float)sensdataset->sensors[OBK_CONSUMPTION_YESTERDAY].lastReading;
data.actual_mday = actual_mday[BL_SENSORS_IX_0];//one in flashvars is enough, I assume that both channels are synchronized
#if ENABLE_BL_TWIN
data.TotalConsumption_b = (float)sensdataset1->sensors[OBK_CONSUMPTION_TOTAL].lastReading;
data.TodayConsumpion_b = (float)sensdataset1->sensors[OBK_CONSUMPTION_TODAY].lastReading;
#else
data.ConsumptionHistory[0] = (float)sensdataset->sensors[OBK_CONSUMPTION_2_DAYS_AGO].lastReading;
data.ConsumptionHistory[1] = (float)sensdataset->sensors[OBK_CONSUMPTION_3_DAYS_AGO].lastReading;
#endif
data.ConsumptionResetTime = ConsumptionResetTime;
ConsumptionSaveCounter++;
data.save_counter = ConsumptionSaveCounter;
HAL_SetEnergyMeterStatus(&data);
}
commandResult_t BL09XX_ResetEnergyCounterEx(int asensdatasetix, float* pvalue)
{
if ((asensdatasetix < 0) || (asensdatasetix >= BL_SENSDATASETS_COUNT)) return CMD_RES_ERROR; //to avoid bad index on data[BL_SENSDATASETS_COUNT]
energysensdataset_t* sensdataset = &datasetlist[asensdatasetix];
int i;
if (!pvalue) {
//addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "ResEC %i", asensdatasetix);
lastSavedEnergyCounterValue[asensdatasetix] = 0.0; //20250203 reset lastSavedEnergyCounterValue, otherwise the values will not be saved until restart BL
sensdataset->sensors[OBK_CONSUMPTION_TOTAL].lastReading = 0.0;
energyCounterStamp[asensdatasetix] = xTaskGetTickCount();
if (energyCounterStatsEnable == true)
{
if (energyCounterMinutes != NULL)
{
for(i = 0; i < energyCounterSampleCount; i++)
{
energyCounterMinutes[i] = 0.0;
}
}
energyCounterMinutesStamp = xTaskGetTickCount();
energyCounterMinutesIndex = 0;
}
for(i = OBK_CONSUMPTION__DAILY_FIRST; i <= OBK_CONSUMPTION__DAILY_LAST; i++)
{
sensdataset->sensors[i].lastReading = 0.0;
}
} else {
//addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "ResEC %i t=%f", asensdatasetix,avalue);
sensdataset->sensors[OBK_CONSUMPTION_TOTAL].lastReading = *pvalue;
energyCounterStamp[asensdatasetix] = xTaskGetTickCount();
}
ConsumptionResetTime = (time_t)TIME_GetCurrentTime();
if (OTA_GetProgress()==-1)
{
BL09XX_SaveEmeteringStatistics();
lastConsumptionSaveStamp = xTaskGetTickCount();
}
return CMD_RES_OK;
}
commandResult_t BL09XX_ResetEnergyCounter(const void* context, const char* cmd, const char* args, int cmdFlags)
{
Tokenizer_TokenizeString(args, 0);
int acnt = Tokenizer_GetArgsCount();
if (acnt == 0) {
#if ENABLE_BL_TWIN
BL09XX_ResetEnergyCounterEx(BL_SENSORS_IX_1, NULL);
#endif
return BL09XX_ResetEnergyCounterEx(BL_SENSORS_IX_0, NULL);
} else {
float fvalue = Tokenizer_GetArgFloat(0);
int fsix = BL_SENSORS_IX_0;
#if ENABLE_BL_TWIN
if (acnt>=2) fsix = Tokenizer_GetArgInteger(1);
#endif
return BL09XX_ResetEnergyCounterEx(fsix, &fvalue);
}
}
commandResult_t BL09XX_SetupEnergyStatistic(const void *context, const char *cmd, const char *args, int cmdFlags)
{
// SetupEnergyStats enable sample_time sample_count
int enable;
int sample_time;
int sample_count;
int json_enable;
Tokenizer_TokenizeString(args,0);
// following check must be done after 'Tokenizer_TokenizeString',
// so we know arguments count in Tokenizer. 'cmd' argument is
// only for warning display
if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 3)) {
return CMD_RES_NOT_ENOUGH_ARGUMENTS;
}
enable = Tokenizer_GetArgInteger(0);
sample_time = Tokenizer_GetArgInteger(1);
sample_count = Tokenizer_GetArgInteger(2);
if (Tokenizer_GetArgsCount() >= 4)
json_enable = Tokenizer_GetArgInteger(3);
else
json_enable = 0;
/* Security limits for sample interval */
if (sample_time <10)
sample_time = 10;
if (sample_time >900)
sample_time = 900;
/* Security limits for sample count */
if (sample_count < 10)
sample_count = 10;
if (sample_count > 180)
sample_count = 180;
/* process changes */
if (enable != 0)
{
addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "Consumption History enabled");
/* Enable function */
energyCounterStatsEnable = true;
if (energyCounterSampleCount != sample_count)
{
/* upgrade sample count, free memory */
if (energyCounterMinutes != NULL)
os_free(energyCounterMinutes);
energyCounterMinutes = NULL;
energyCounterSampleCount = sample_count;
}
addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "Sample Count: %d", energyCounterSampleCount);
if (energyCounterSampleInterval != sample_time)
{
/* change sample time */
energyCounterSampleInterval = sample_time;
if (energyCounterMinutes != NULL)
memset(energyCounterMinutes, 0, energyCounterSampleCount*sizeof(float));
}
if (energyCounterMinutes == NULL)
{
/* allocate new memeory */
energyCounterMinutes = (float*)os_malloc(sample_count*sizeof(float));
if (energyCounterMinutes != NULL)
{
memset(energyCounterMinutes, 0, energyCounterSampleCount*sizeof(float));
}
}
addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "Sample Interval: %d", energyCounterSampleInterval);
energyCounterMinutesStamp = xTaskGetTickCount();
energyCounterMinutesIndex = 0;
} else {
/* Disable Consimption Nistory */
addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "Consumption History disabled");
energyCounterStatsEnable = false;
if (energyCounterMinutes != NULL)
{
os_free(energyCounterMinutes);
energyCounterMinutes = NULL;
}
energyCounterSampleCount = sample_count;
energyCounterSampleInterval = sample_time;
}
energyCounterStatsJSONEnable = (json_enable != 0) ? true : false;
return CMD_RES_OK;
}
commandResult_t BL09XX_VCPPublishIntervals(const void *context, const char *cmd, const char *args, int cmdFlags)
{
Tokenizer_TokenizeString(args, 0);
// following check must be done after 'Tokenizer_TokenizeString',
// so we know arguments count in Tokenizer. 'cmd' argument is
// only for warning display
if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 2)) {
return CMD_RES_NOT_ENOUGH_ARGUMENTS;
}
changeDoNotSendMinFrames = Tokenizer_GetArgInteger(0);
changeSendAlwaysFrames = Tokenizer_GetArgInteger(1);
return CMD_RES_OK;
}
#if ENABLE_BL_TWIN
commandResult_t BL09XX_VCPPrecisionEx(int asensdatasetix, const void *context, const char *cmd, const char *args, int cmdFlags)
{
if ((asensdatasetix < 0) || (asensdatasetix >= BL_SENSDATASETS_COUNT)) return CMD_RES_ERROR; //to avoid bad index on data[BL_SENSDATASETS_COUNT]
#else
commandResult_t BL09XX_VCPPrecision(const void* context, const char* cmd, const char* args, int cmdFlags) {
int asensdatasetix = BL_SENSORS_IX_0;
#endif
energysensdataset_t* sensdataset = &datasetlist[asensdatasetix];
int i;
Tokenizer_TokenizeString(args, 0);
// following check must be done after 'Tokenizer_TokenizeString',
// so we know arguments count in Tokenizer. 'cmd' argument is
// only for warning display
if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 1)) {
return CMD_RES_NOT_ENOUGH_ARGUMENTS;
}
for (i = 0; i < Tokenizer_GetArgsCount(); i++) {
int val = Tokenizer_GetArgInteger(i);
switch(i) {
case 0: // voltage
sensdataset->sensors[OBK_VOLTAGE].rounding_decimals = val;
break;
case 1: // current
sensdataset->sensors[OBK_CURRENT].rounding_decimals = val;
break;
case 2: // power
sensdataset->sensors[OBK_POWER].rounding_decimals = val;
sensdataset->sensors[OBK_POWER_APPARENT].rounding_decimals = val;
sensdataset->sensors[OBK_POWER_REACTIVE].rounding_decimals = val;
break;
case 3: // energy
for (int j = OBK_CONSUMPTION__DAILY_FIRST; j <= OBK_CONSUMPTION__DAILY_LAST; j++) {
sensdataset->sensors[j].rounding_decimals = val;
};
break;
case 4: // frequency
sensdataset->sensors[OBK_FREQUENCY].rounding_decimals = val;
break;
};
}
return CMD_RES_OK;
}
#if ENABLE_BL_TWIN
commandResult_t BL09XX_VCPPrecision(const void* context, const char* cmd, const char* args, int cmdFlags) {
return BL09XX_VCPPrecisionEx(BL_SENSORS_IX_0, context, cmd, args, cmdFlags);
}
#endif
#if ENABLE_BL_TWIN
commandResult_t BL09XX_VCPPublishThresholdEx(int asensdatasetix, const void* context, const char* cmd, const char* args, int cmdFlags)
{
if ((asensdatasetix < 0) || (asensdatasetix >= BL_SENSDATASETS_COUNT)) return CMD_RES_ERROR; //to avoid bad index on data[BL_SENSDATASETS_COUNT]
#else
commandResult_t BL09XX_VCPPublishThreshold(const void* context, const char* cmd, const char* args, int cmdFlags)
{
int asensdatasetix = BL_SENSORS_IX_0;
#endif
energysensdataset_t* sensdataset = &datasetlist[asensdatasetix];
Tokenizer_TokenizeString(args, 0);
// following check must be done after 'Tokenizer_TokenizeString',
// so we know arguments count in Tokenizer. 'cmd' argument is
// only for warning display
if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 3)) {
return CMD_RES_NOT_ENOUGH_ARGUMENTS;
}
sensdataset->sensors[OBK_VOLTAGE].changeSendThreshold = Tokenizer_GetArgFloat(0);
sensdataset->sensors[OBK_CURRENT].changeSendThreshold = Tokenizer_GetArgFloat(1);
sensdataset->sensors[OBK_POWER].changeSendThreshold = Tokenizer_GetArgFloat(2);
sensdataset->sensors[OBK_POWER_APPARENT].changeSendThreshold = Tokenizer_GetArgFloatDefault(2,sensdataset->sensors[OBK_POWER_APPARENT].changeSendThreshold);
sensdataset->sensors[OBK_POWER_REACTIVE].changeSendThreshold = Tokenizer_GetArgFloatDefault(2,sensdataset->sensors[OBK_POWER_REACTIVE].changeSendThreshold);
//sensdataset->sensors[OBK_POWER_FACTOR].changeSendThreshold = Tokenizer_GetArgFloat(TODO);
if (Tokenizer_GetArgsCount() >= 4) {
for (int i = OBK_CONSUMPTION_LAST_HOUR; i <= OBK_CONSUMPTION__DAILY_LAST; i++) {
sensdataset->sensors[i].changeSendThreshold = Tokenizer_GetArgFloat(3);
}
}
sensdataset->sensors[OBK_FREQUENCY].changeSendThreshold = Tokenizer_GetArgFloatDefault(4,sensdataset->sensors[OBK_FREQUENCY].changeSendThreshold);
return CMD_RES_OK;
}
#if ENABLE_BL_TWIN
commandResult_t BL09XX_VCPPublishThreshold(const void* context, const char* cmd, const char* args, int cmdFlags)
{
BL09XX_VCPPublishThresholdEx(BL_SENSORS_IX_1, context, cmd, args, cmdFlags);
return BL09XX_VCPPublishThresholdEx(BL_SENSORS_IX_0, context, cmd, args, cmdFlags);
}
#endif
commandResult_t BL09XX_SetupConsumptionThreshold(const void *context, const char *cmd, const char *args, int cmdFlags)
{
float threshold;
Tokenizer_TokenizeString(args,0);
// following check must be done after 'Tokenizer_TokenizeString',
// so we know arguments count in Tokenizer. 'cmd' argument is
// only for warning display
if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 1)) {
return CMD_RES_NOT_ENOUGH_ARGUMENTS;
}
threshold = (float)atof(Tokenizer_GetArg(0));
if (threshold<1.0f)
threshold = 1.0f;
if (threshold>5000.0f)
threshold = 5000.0f;
changeSavedThresholdEnergy = threshold;
addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "ConsumptionThreshold: %1.1f", changeSavedThresholdEnergy);
return CMD_RES_OK;
}
bool Channel_AreAllRelaysOpen() {
int i, role, ch;
for (i = 0; i < PLATFORM_GPIO_MAX; i++) {
role = g_cfg.pins.roles[i];
ch = g_cfg.pins.channels[i];
if (role == IOR_Relay) {
// this channel is high = relay is set
if (CHANNEL_Get(ch)) {
return false;
}
}
if (role == IOR_Relay_n) {
// this channel is low = relay_n is set
if (CHANNEL_Get(ch)==false) {
return false;
}
}
if (role == IOR_BridgeForward) {
// this channel is high = relay is set
if (CHANNEL_Get(ch)) {
return false;
}
}
}
return true;
}
float BL_ChangeEnergyUnitIfNeeded(float Wh) {
if (CFG_HasFlag(OBK_FLAG_MQTT_ENERGY_IN_KWH)) {
return Wh * 0.001f;
}
return Wh;
}
#if ENABLE_BL_TWIN
void BL_ProcessUpdateEx(int asensdatasetix, float voltage, float current, float power,
float frequency, float energyWh) {
if ((asensdatasetix < 0) || (asensdatasetix >= BL_SENSDATASETS_COUNT)) return; //to avoid bad index on data[BL_SENSDATASETS_COUNT]
#else
void BL_ProcessUpdate(float voltage, float current, float power,
float frequency, float energyWh) {
int asensdatasetix = BL_SENSORS_IX_0;
#endif
energysensdataset_t* sensdataset = &datasetlist[asensdatasetix];
int i;
int xPassedTicks;
cJSON* root;
cJSON* stats;
char *msg;
portTickType interval;
time_t deviceTime;
// struct tm *ltm;
char datetime[64];
float diff;
// I had reports that BL0942 sometimes gives
// a large, negative peak of current/power
if (!CFG_HasFlag(OBK_FLAG_POWER_ALLOW_NEGATIVE))
{
if (power < 0.0f)
power = 0.0f;
if (voltage < 0.0f)
voltage = 0.0f;
if (current < 0.0f)
current = 0.0f;
}
if (CFG_HasFlag(OBK_FLAG_POWER_FORCE_ZERO_IF_RELAYS_OPEN))
{
if (Channel_AreAllRelaysOpen()) {
power = 0;
current = 0;
}
}
#ifdef ENABLE_BL_MOVINGAVG
power = XJ_MovingAverage_float((float)sensdataset->sensors[OBK_POWER].lastReading, power);
current = XJ_MovingAverage_float((float)sensdataset->sensors[OBK_CURRENT].lastReading, current);
#endif
sensdataset->sensors[OBK_VOLTAGE].lastReading = voltage;
sensdataset->sensors[OBK_CURRENT].lastReading = current;
sensdataset->sensors[OBK_POWER].lastReading = power;
sensdataset->sensors[OBK_FREQUENCY].lastReading = frequency;
sensdataset->sensors[OBK_POWER_APPARENT].lastReading = sensdataset->sensors[OBK_VOLTAGE].lastReading * sensdataset->sensors[OBK_CURRENT].lastReading;
sensdataset->sensors[OBK_POWER_REACTIVE].lastReading = (sensdataset->sensors[OBK_POWER_APPARENT].lastReading <= fabsf((float)sensdataset->sensors[OBK_POWER].lastReading)
? 0
: sqrtf(powf((float)sensdataset->sensors[OBK_POWER_APPARENT].lastReading, 2) -
powf((float)sensdataset->sensors[OBK_POWER].lastReading, 2)));
sensdataset->sensors[OBK_POWER_FACTOR].lastReading =
(sensdataset->sensors[OBK_POWER_APPARENT].lastReading == 0 ? 1 : sensdataset->sensors[OBK_POWER].lastReading / sensdataset->sensors[OBK_POWER_APPARENT].lastReading);
sensors_reciveddata[asensdatasetix] = 1;
{
float energy = 0;
if (isnan(energyWh)) {
xPassedTicks = (int)(xTaskGetTickCount() - energyCounterStamp[asensdatasetix]);
// FIXME: Wrong calculation if tick count overflows
if (xPassedTicks <= 0)
xPassedTicks = 1;
energy = xPassedTicks * power / (3600000.0f / portTICK_PERIOD_MS);
} else
energy = energyWh;
if (energy < 0)
energy = 0.0;
sensdataset->sensors[OBK_CONSUMPTION_TOTAL].lastReading += (double)energy;
energyCounterStamp[asensdatasetix] = xTaskGetTickCount();
#if ENABLE_BL_TWIN
if (asensdatasetix == BL_SENSORS_IX_0) {
//update only IX0, IX1 will be saved later in BL09XX_SaveEmeteringStatistics()
HAL_FlashVars_SaveTotalConsumption((float)sensdataset->sensors[OBK_CONSUMPTION_TOTAL].lastReading);
}
#else
HAL_FlashVars_SaveTotalConsumption((float)sensdataset->sensors[OBK_CONSUMPTION_TOTAL].lastReading);
#endif
sensdataset->sensors[OBK_CONSUMPTION_TODAY].lastReading += energy;
if (TIME_IsTimeSynced()) {
deviceTime = (time_t)TIME_GetCurrentTime();
// ltm = gmtime(&deviceTime);
uint8_t mday=TIME_GetMDay();
if (ConsumptionResetTime == 0)
ConsumptionResetTime = (time_t)deviceTime;
if (actual_mday[asensdatasetix] == -1)
{
// actual_mday[asensdatasetix] = ltm->tm_mday;
actual_mday[asensdatasetix] = mday;
}
// if (actual_mday[asensdatasetix] != ltm->tm_mday)
if (actual_mday[asensdatasetix] != mday)
{
for (i = OBK_CONSUMPTION__DAILY_LAST; i >= OBK_CONSUMPTION__DAILY_FIRST; i--) {
sensdataset->sensors[i].lastReading = sensdataset->sensors[i - 1].lastReading;
}
sensdataset->sensors[OBK_CONSUMPTION_TODAY].lastReading = 0.0;
// actual_mday[asensdatasetix] = ltm->tm_mday;
actual_mday[asensdatasetix] = mday;
//MQTT_PublishMain_StringFloat(sensdataset->sensors[OBK_CONSUMPTION_YESTERDAY].names.name_mqtt, BL_ChangeEnergyUnitIfNeeded(sensors[OBK_CONSUMPTION_YESTERDAY].lastReading ),
// sensdataset->sensors[OBK_CONSUMPTION_YESTERDAY].rounding_decimals, 0);
//stat_updatesSent++;
if (OTA_GetProgress()==-1)
{
BL09XX_SaveEmeteringStatistics();
lastConsumptionSaveStamp = xTaskGetTickCount();
}
}
}
if ((energyCounterStatsEnable == true) && (asensdatasetix==BL_SENSORS_IX_0))
{
interval = energyCounterSampleInterval;
interval *= (1000 / portTICK_PERIOD_MS);
if ((xTaskGetTickCount() - energyCounterMinutesStamp) >= interval)
{
if (energyCounterMinutes != NULL) {
sensdataset->sensors[OBK_CONSUMPTION_LAST_HOUR].lastReading = 0;
for (int i = 0; i < energyCounterSampleCount; i++) {
sensdataset->sensors[OBK_CONSUMPTION_LAST_HOUR].lastReading += energyCounterMinutes[i];
}
}
#if ENABLE_MQTT
if ((energyCounterStatsJSONEnable == true) && (MQTT_IsReady() == true))
{
root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "uptime", g_secondsElapsed);
cJSON_AddNumberToObject(root, "consumption_total", BL_ChangeEnergyUnitIfNeeded(DRV_GetReading(OBK_CONSUMPTION_TOTAL)));
cJSON_AddNumberToObject(root, "consumption_last_hour", BL_ChangeEnergyUnitIfNeeded(DRV_GetReading(OBK_CONSUMPTION_LAST_HOUR)));
cJSON_AddNumberToObject(root, "consumption_stat_index", energyCounterMinutesIndex);
cJSON_AddNumberToObject(root, "consumption_sample_count", energyCounterSampleCount);
cJSON_AddNumberToObject(root, "consumption_sampling_period", energyCounterSampleInterval);
if(TIME_IsTimeSynced() == true)
{
cJSON_AddNumberToObject(root, "consumption_today", BL_ChangeEnergyUnitIfNeeded(DRV_GetReading(OBK_CONSUMPTION_TODAY)));
cJSON_AddNumberToObject(root, "consumption_yesterday", BL_ChangeEnergyUnitIfNeeded(DRV_GetReading(OBK_CONSUMPTION_YESTERDAY)));
// ltm = gmtime(&ConsumptionResetTime);
/*
if (TIME_GetTimesZoneOfsSeconds()>0)
{
snprintf(datetime,sizeof(datetime), "%04i-%02i-%02iT%02i:%02i+%02i:%02i",
ltm->tm_year+1900, ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min,
TIME_GetTimesZoneOfsSeconds()/3600, (TIME_GetTimesZoneOfsSeconds()/60) % 60);
} else {
snprintf(datetime, sizeof(datetime), "%04i-%02i-%02iT%02i:%02i-%02i:%02i",
ltm->tm_year+1900, ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min,
abs(TIME_GetTimesZoneOfsSeconds()/3600), (abs(TIME_GetTimesZoneOfsSeconds())/60) % 60);
}
*/
// optimized output: since we can be sure, a negative offset is minumum 1 hour,
// the sign for the hour will be "-" for "negative" timezones
// this wouldn't work if a negative offset less than one hour would be possible
/*
snprintf(datetime, sizeof(datetime), "%04i-%02i-%02iT%02i:%02i%+03i:%02i",
ltm->tm_year+1900, ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min,
TIME_GetTimesZoneOfsSeconds()/3600, (abs(TIME_GetTimesZoneOfsSeconds())/60) % 60);
*/
snprintf(datetime, sizeof(datetime), "%.16s%+03i:%02i",TS2STR(ConsumptionResetTime, TIME_FORMAT_ISO_8601),
TIME_GetTimesZoneOfsSeconds()/3600, (abs(TIME_GetTimesZoneOfsSeconds())/60) % 60);
cJSON_AddStringToObject(root, "consumption_clear_date", datetime);
}
if (energyCounterMinutes != NULL)
{
stats = cJSON_CreateArray();
// WARNING - it causes HA problems?
// See: https://github.com/openshwprojects/OpenBK7231T_App/issues/870
// Basically HA has 256 chars state limit?
// Wait, no, it's over 256 even without samples?
for(i = 0; i < energyCounterSampleCount; i++)
{
cJSON_AddItemToArray(stats, cJSON_CreateNumber(energyCounterMinutes[i]));
}
cJSON_AddItemToObject(root, "consumption_samples", stats);
}
if(NTP_IsTimeSynced() == true)
{
stats = cJSON_CreateArray();
for(i = OBK_CONSUMPTION__DAILY_FIRST; i <= OBK_CONSUMPTION__DAILY_LAST; i++)
{
cJSON_AddItemToArray(stats, cJSON_CreateNumber(DRV_GetReading(i)));
}
cJSON_AddItemToObject(root, "consumption_daily", stats);
}
msg = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
// addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "JSON Printed: %d bytes", strlen(msg));
MQTT_PublishMain_StringString("consumption_stats", msg, 0);
stat_updatesSent[asensdatasetix]++;
os_free(msg);
}
#endif
if (energyCounterMinutes != NULL)
{
for (i=energyCounterSampleCount-1;i>0;i--)
{
if (energyCounterMinutes[i-1]>0.0)
{
energyCounterMinutes[i] = energyCounterMinutes[i-1];
} else {
energyCounterMinutes[i] = 0.0;
}
}
energyCounterMinutes[0] = 0.0;
}
energyCounterMinutesStamp = xTaskGetTickCount();
energyCounterMinutesIndex++;
}
if (energyCounterMinutes != NULL)
energyCounterMinutes[0] += energy;
}
}
for (i = OBK__FIRST; i <= OBK__LAST; i++)
{
#ifdef ENABLE_BL_TWIN
//in twin mode, for ix1 is possible to skip OBK_VOLTAGE, dont skip for now
//if ((asensdatasetix > 0) && (i== OBK_VOLTAGE)) continue;
//in twin mode, for ix0 is last OBK_CONSUMPTION_YESTERDAY, for ix1 ,OBK_CONSUMPTION_TODAY
if ((i > OBK_CONSUMPTION_STORED_LAST[asensdatasetix]) && (i <= OBK_CONSUMPTION__DAILY_LAST)) continue;
#endif
// send update only if there was a big change or if certain time has passed
// Do not send message with every measurement.
diff = (float)sensdataset->sensors[i].lastSentValue - (float)sensdataset->sensors[i].lastReading;
// check for change
if (((fabsf(diff) > sensdataset->sensors[i].changeSendThreshold) &&
(sensdataset->sensors[i].noChangeFrame >= changeDoNotSendMinFrames)) ||
(sensdataset->sensors[i].noChangeFrame >= changeSendAlwaysFrames))
{
sensdataset->sensors[i].noChangeFrame = 0;
enum EventCode eventChangeCode;
switch (i) {
case OBK_VOLTAGE: eventChangeCode = CMD_EVENT_CHANGE_VOLTAGE; break;
case OBK_CURRENT: eventChangeCode = CMD_EVENT_CHANGE_CURRENT; break;
case OBK_POWER: eventChangeCode = CMD_EVENT_CHANGE_POWER; break;
case OBK_FREQUENCY: eventChangeCode = CMD_EVENT_CHANGE_FREQUENCY; break;
case OBK_CONSUMPTION_TOTAL: eventChangeCode = CMD_EVENT_CHANGE_CONSUMPTION_TOTAL; break;
case OBK_CONSUMPTION_LAST_HOUR: eventChangeCode = CMD_EVENT_CHANGE_CONSUMPTION_LAST_HOUR; break;
default: eventChangeCode = CMD_EVENT_NONE; break;
}
switch (eventChangeCode) {
case CMD_EVENT_NONE:
break;
case CMD_EVENT_CHANGE_FREQUENCY:;
// the event change comparisons are stored as int types, so frequency compared as *100
// i.e. 50.10 becomes 5010
int prev_hz = (int)(sensdataset->sensors[i].lastSentValue * 100);
int now_hz = (int)(sensdataset->sensors[i].lastReading * 100);
EventHandlers_ProcessVariableChange_Integer(eventChangeCode, prev_hz, now_hz);
break;
case CMD_EVENT_CHANGE_CURRENT:;
int prev_mA = (int)(sensdataset->sensors[i].lastSentValue * 1000);
int now_mA = (int)(sensdataset->sensors[i].lastReading * 1000);
EventHandlers_ProcessVariableChange_Integer(eventChangeCode, prev_mA, now_mA);
break;
default:
EventHandlers_ProcessVariableChange_Integer(eventChangeCode, (int)sensdataset->sensors[i].lastSentValue, (int)sensdataset->sensors[i].lastReading);
break;
}
sensdataset->sensors[i].lastSentValue = sensdataset->sensors[i].lastReading;
#if ENABLE_MQTT
if (MQTT_IsReady() == true)
{
if (i == OBK_CONSUMPTION_CLEAR_DATE) {
{
sensdataset->sensors[i].lastReading = ConsumptionResetTime; //Only to make the 'nochangeframe' mechanism work here
// ltm = gmtime(&ConsumptionResetTime);
/* 2019-09-07T15:50-04:00 */
/*
if (TIME_GetTimesZoneOfsSeconds()>0)
{
snprintf(datetime, sizeof(datetime), "%04i-%02i-%02iT%02i:%02i+%02i:%02i",
ltm->tm_year+1900, ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min,
TIME_GetTimesZoneOfsSeconds()/3600, (TIME_GetTimesZoneOfsSeconds()/60) % 60);
} else {
snprintf(datetime, sizeof(datetime), "%04i-%02i-%02iT%02i:%02i-%02i:%02i",
ltm->tm_year+1900, ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min,
abs(TIME_GetTimesZoneOfsSeconds()/3600), (abs(TIME_GetTimesZoneOfsSeconds())/60) % 60);
}
*/
// optimized output: since we can be sure, a negative offset is minumum 1 hour,
// the sign for the hour will be "-" for "negative" timezones
// this wouldn't work if a negative offset less than one hour would be possible
/*
snprintf(datetime, sizeof(datetime), "%04i-%02i-%02iT%02i:%02i%+03i:%02i",
ltm->tm_year+1900, ltm->tm_mon+1, ltm->tm_mday, ltm->tm_hour, ltm->tm_min,
TIME_GetTimesZoneOfsSeconds()/3600, (abs(TIME_GetTimesZoneOfsSeconds())/60) % 60);
*/
snprintf(datetime, sizeof(datetime), "%.16s%+03i:%02i",TS2STR(ConsumptionResetTime, TIME_FORMAT_ISO_8601),
TIME_GetTimesZoneOfsSeconds()/3600, (abs(TIME_GetTimesZoneOfsSeconds())/60) % 60);
MQTT_PublishMain_StringString(sensdataset->sensors[i].names.name_mqtt, datetime, 0);
}
} else { //all other sensors
float val = (float)sensdataset->sensors[i].lastReading;
if (sensdataset->sensors[i].names.units == UNIT_WH) val = BL_ChangeEnergyUnitIfNeeded(val);
MQTT_PublishMain_StringFloat(sensdataset->sensors[i].names.name_mqtt, val, sensdataset->sensors[i].rounding_decimals, OBK_PUBLISH_FLAG_QOS_ZERO);
}
stat_updatesSent[asensdatasetix]++;
}
#endif
} else {
// no change frame
sensdataset->sensors[i].noChangeFrame++;
stat_updatesSkipped[asensdatasetix]++;
}
}
{
if (((sensdataset->sensors[OBK_CONSUMPTION_TOTAL].lastReading - lastSavedEnergyCounterValue[asensdatasetix]) >= changeSavedThresholdEnergy) ||
((xTaskGetTickCount() - lastConsumptionSaveStamp) >= (6 * 3600 * 1000 / portTICK_PERIOD_MS)))
{
if (OTA_GetProgress() == -1)
{
lastSavedEnergyCounterValue[asensdatasetix] = (float)sensdataset->sensors[OBK_CONSUMPTION_TOTAL].lastReading;
BL09XX_SaveEmeteringStatistics();
lastConsumptionSaveStamp = xTaskGetTickCount();
}
}
}
}
#if ENABLE_BL_TWIN
void BL_ProcessUpdate(float voltage, float current, float power,
float frequency, float energyWh) {
BL_ProcessUpdateEx(BL_SENSORS_IX_0, voltage, current, power, frequency, energyWh);
}
#endif
void BL_Shared_Init(void) {
energysensdataset_t* sensdataset = &datasetlist[BL_SENSORS_IX_0];
#if ENABLE_BL_TWIN
energysensdataset_t* sensdataset1 = &datasetlist[BL_SENSORS_IX_1];
#endif
int i;
ENERGY_METERING_DATA data;
for(i = OBK__FIRST; i <= OBK__LAST; i++)
{
sensdataset->sensors[i].noChangeFrame = 0;
sensdataset->sensors[i].lastReading = 0;
}
{
if (energyCounterStatsEnable == true)
{
if (energyCounterMinutes == NULL)
{
energyCounterMinutes = (float*)os_malloc(energyCounterSampleCount*sizeof(float));
}
if (energyCounterMinutes != NULL)
{
for(i = 0; i < energyCounterSampleCount; i++)
{
energyCounterMinutes[i] = 0.0;
}
}
energyCounterMinutesStamp = xTaskGetTickCount();
energyCounterMinutesIndex = 0;
}
addLogAdv(LOG_INFO, LOG_FEATURE_ENERGYMETER, "Read ENERGYMETER values sz=%d\n", sizeof(ENERGY_METERING_DATA));
HAL_GetEnergyMeterStatus(&data);
sensdataset->sensors[OBK_CONSUMPTION_TOTAL].lastReading = data.TotalConsumption;
sensdataset->sensors[OBK_CONSUMPTION_TODAY].lastReading = data.TodayConsumpion;
sensdataset->sensors[OBK_CONSUMPTION_YESTERDAY].lastReading = data.YesterdayConsumption;
actual_mday[BL_SENSORS_IX_0] = data.actual_mday;//one in flashvars is enough, I assume that both channels are synchronized
#if ENABLE_BL_TWIN
sensdataset1->sensors[OBK_CONSUMPTION_TOTAL].lastReading = data.TotalConsumption_b;
sensdataset1->sensors[OBK_CONSUMPTION_TODAY].lastReading = data.TodayConsumpion_b;
lastSavedEnergyCounterValue[BL_SENSORS_IX_0] = data.TotalConsumption;
lastSavedEnergyCounterValue[BL_SENSORS_IX_1] = data.TotalConsumption_b;
energyCounterStamp[BL_SENSORS_IX_0] = xTaskGetTickCount();
energyCounterStamp[BL_SENSORS_IX_1] = xTaskGetTickCount();
actual_mday[BL_SENSORS_IX_1] = data.actual_mday;//one in flashvars is enough, I assume that both channels are synchronized
#else
lastSavedEnergyCounterValue[BL_SENSORS_IX_0] = data.TotalConsumption;
sensdataset->sensors[OBK_CONSUMPTION_2_DAYS_AGO].lastReading = data.ConsumptionHistory[0];
sensdataset->sensors[OBK_CONSUMPTION_3_DAYS_AGO].lastReading = data.ConsumptionHistory[1];
energyCounterStamp[BL_SENSORS_IX_0] = xTaskGetTickCount();
#endif
ConsumptionResetTime = data.ConsumptionResetTime;
ConsumptionSaveCounter = data.save_counter;
lastConsumptionSaveStamp = xTaskGetTickCount();
//int HAL_SetEnergyMeterStatus(ENERGY_METERING_DATA *data);
}
//cmddetail:{"name":"EnergyCntReset","args":"[OptionalNewValue][sensorix]",
//cmddetail:"descr":"Resets the total Energy Counter, the one that is usually kept after device reboots. After this commands, the counter will start again from 0 (or from the value you specified). sensorix is used in ENABLE_BL_TWIN",
//cmddetail:"fn":"BL09XX_ResetEnergyCounter","file":"driver/drv_bl_shared.c","requires":"",
//cmddetail:"examples":""}
CMD_RegisterCommand("EnergyCntReset", BL09XX_ResetEnergyCounter, NULL);
//cmddetail:{"name":"SetupEnergyStats","args":"[Enable1or0][SampleTime][SampleCount][JSonEnable]",
//cmddetail:"descr":"Setup Energy Statistic Parameters: [enable 0 or 1] [sample_time[10..90]] [sample_count[10..180]] [JsonEnable 0 or 1]. JSONEnable is optional.",
//cmddetail:"fn":"BL09XX_SetupEnergyStatistic","file":"driver/drv_bl_shared.c","requires":"",
//cmddetail:"examples":""}
CMD_RegisterCommand("SetupEnergyStats", BL09XX_SetupEnergyStatistic, NULL);
//cmddetail:{"name":"ConsumptionThreshold","args":"[FloatValue]",
//cmddetail:"descr":"Setup value for automatic save of consumption data [1..100]",
//cmddetail:"fn":"BL09XX_SetupConsumptionThreshold","file":"driver/drv_bl_shared.c","requires":"",
//cmddetail:"examples":""}
CMD_RegisterCommand("ConsumptionThreshold", BL09XX_SetupConsumptionThreshold, NULL);
//cmddetail:{"name":"VCPPublishThreshold","args":"[VoltageDeltaVolts][CurrentDeltaAmpers][PowerDeltaWats][EnergyDeltaWh][Frequency]",
//cmddetail:"descr":"Sets the minimal change between previous reported value over MQTT and next reported value over MQTT. Very useful for BL0942, BL0937, etc. So, if you set, `VCPPublishThreshold 0.25 0.002 0.5 0.25 0.25 0.01` (the default), it will only report voltage again if the delta from previous reported value is larger than 0.25V. Remember, that the device will also ALWAYS force-report values every N seconds (default 60). The last there arugments are optional",
//cmddetail:"fn":"BL09XX_VCPPublishThreshold","file":"driver/drv_bl_shared.c","requires":"",
//cmddetail:"examples":"`VCPPublishThreshold 0.5 0.02 1`"}
CMD_RegisterCommand("VCPPublishThreshold", BL09XX_VCPPublishThreshold, NULL);
//cmddetail:{"name":"VCPPrecision","args":"[VoltageDigits][CurrentDigitsAmpers][PowerDigitsWats][EnergyDigitsWh][Frequency]",
//cmddetail:"descr":"Sets the number of digits after decimal point for power metering publishes for BL09XX. Default is `VCPPrecision 1 3 2 3 3`. This works for OBK-style publishes. Only the first argument is required, others are optional.",
//cmddetail:"fn":"BL09XX_VCPPrecision","file":"driver/drv_bl_shared.c","requires":"",
//cmddetail:"examples":""}
CMD_RegisterCommand("VCPPrecision", BL09XX_VCPPrecision, NULL);
//cmddetail:{"name":"VCPPublishIntervals","args":"[MinDelayBetweenPublishes][ForcedPublishInterval]",
//cmddetail:"descr":"First argument is minimal allowed interval in second between Voltage/Current/Power/Energy publishes (even if there is a large change), second value is an interval in which V/C/P/E is always published, even if there is no change. Frequency is published with Voltage",
//cmddetail:"fn":"BL09XX_VCPPublishIntervals","file":"driver/drv_bl_shared.c","requires":"",
//cmddetail:"examples":""}
CMD_RegisterCommand("VCPPublishIntervals", BL09XX_VCPPublishIntervals, NULL);
}
// OBK_POWER etc
float DRV_GetReading(energySensor_t type)
{
return (float)datasetlist[BL_SENSORS_IX_0].sensors[type].lastReading;
}
#if ENABLE_BL_TWIN
int BL_IsMeteringDeviceIndexActive(int asensdatasetix){
if (asensdatasetix < 0) return false;
if (asensdatasetix >= BL_SENSDATASETS_COUNT) return false;
return sensors_reciveddata[asensdatasetix];
}
#endif
energySensorNames_t* DRV_GetEnergySensorNames(energySensor_t type)
{
return &datasetlist[BL_SENSORS_IX_0].sensors[type].names;
}
/// @param asensdatasetix dataset index when using two energy sensors
/// @param type energySensor_t
energySensorNames_t* DRV_GetEnergySensorNamesEx(int asensdatasetix, energySensor_t type)
{
if ((asensdatasetix < 0) || (asensdatasetix >= BL_SENSDATASETS_COUNT)) asensdatasetix= BL_SENSORS_IX_0;//here default 0 - hass.c not checking result
return &datasetlist[asensdatasetix].sensors[type].names;
}
#endif