mirror of
https://github.com/openshwprojects/OpenBK7231T_App.git
synced 2026-03-01 04:56:44 +00:00
* 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
1089 lines
47 KiB
C
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
|