Skip to content

Commit f747879

Browse files
committed
+fix: hourly stats become invalid if battery is not reachable, sanity check added
1 parent 5bb8aaa commit f747879

6 files changed

Lines changed: 89 additions & 27 deletions

File tree

nginx/app.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,9 @@ function renderConsStats(dayIdx) {
167167
}
168168
const bars = document.getElementById("statsBars");
169169
bars.innerHTML = "";
170-
const currentDay = new Date().getDay();
171-
const currentHour = new Date().getHours()
170+
const now = new Date();
171+
const currentDay = now.getDay();
172+
const currentHour = now.getHours()
172173
const currentAvg = document.getElementById("cons_avg_w").getAttribute("data-value");
173174
data.forEach((v, h) => {
174175
const col = document.createElement("div");
@@ -197,7 +198,7 @@ function renderConsStats(dayIdx) {
197198
for (let h = 0; h < 24; h++) {
198199
const lbl = document.createElement("div");
199200
lbl.className = "bar-lbl";
200-
lbl.textContent = (h % 6 === 0) ? h : "";
201+
lbl.textContent = (h % 6 === 0) ? (h.toString().padStart(2, '0') + ":00") : "";
201202
xAxis.appendChild(lbl);
202203
}
203204
}

smartswitch.espxx/include/Logic.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ void updateSystemState(SystemConfig *systemConfig, SystemState *systemState)
7070
uint8_t slot_day = (uint8_t)(systemState->stat_hour_key / 24); // day of the hour being closed out (differs from current day at midnight)
7171
uint8_t slot_hour = (uint8_t)(systemState->stat_hour_key % 24);
7272
uint16_t *slot = &systemConfig->cons_stats_Wh[slot_day][slot_hour];
73-
*slot = (*slot == 0) ? systemState->cons_avg_W
74-
: (uint16_t)(((uint32_t)*slot * 9 + systemState->cons_avg_W) / 10);
73+
// sanity check, do we have enough samples collected in one hour, at least 80% uptime?
74+
if (systemState->cons_count >= (0.8 * 3600 / (SYSTEM_UPDATE_INTERVAL_MS / 1000)))
75+
{
76+
*slot = (*slot == 0) ? systemState->cons_avg_W : (uint16_t)(((uint32_t)*slot * 9 + systemState->cons_avg_W) / 10);
77+
}
7578
systemState->cons_sum_W = 0;
7679
systemState->cons_count = 0;
7780
}
@@ -190,7 +193,7 @@ const char *const EVENTS[] = {
190193
"SoC %0% - consumption %2W too high, to much grid purchase %3W",
191194
"SoC %0% - consumption %2W, battery min SoC %4% reached%7",
192195
"SoC %0% - boiler temperature %1°C >= %5°C (max) reached",
193-
"SoC %0% - battery will full charge, but SoC too low",
196+
"SoC %0% - battery will be full charged%7, but SoC too low",
194197
};
195198

196199
static bool isSurplusAvailable(SystemConfig *systemConfig, SystemState *systemState)
@@ -267,8 +270,8 @@ static bool determineDesiredState(char *msg, int len, SystemConfig *systemConfig
267270

268271
if (event != 0)
269272
{
270-
char label[16];
271-
snprintf(label, sizeof(label), state.hours > 0 ? " in ~%dh" : "", state.hours);
273+
char hour_label[16];
274+
snprintf(hour_label, sizeof(hour_label), state.hours > 0 ? " in ~%dh" : "", state.hours);
272275

273276
Arg args[] = {
274277
ARG_UINT8, &systemState->usoc,
@@ -278,7 +281,7 @@ static bool determineDesiredState(char *msg, int len, SystemConfig *systemConfig
278281
ARG_UINT8, &systemConfig->bat_soc_min,
279282
ARG_FLT, &temp_off,
280283
ARG_FLT, &temp_on,
281-
ARG_STR, &label,
284+
ARG_STR, &hour_label,
282285
ARG_STR, (void *)SystemStatusLabel[status]};
283286

284287
format_indexed(msg, len, EVENTS[event], args);

smartswitch.espxx/include/SmartSwitch.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
#define LPB_ADDR_DEST 0
5050
#define LPB_RETRIES 5
5151

52+
#define SYSTEM_UPDATE_INTERVAL_MS 2000 // update intervall millis
53+
#define SYSTEM_ON_COUNT MAX(1, (10000 + (SYSTEM_UPDATE_INTERVAL_MS >> 1)) / SYSTEM_UPDATE_INTERVAL_MS)
54+
5255
#define SONNEN_API_URI "api/v2"
5356
#define SONNEN_API_CONFIGURATIONS "configurations"
5457
#define SONNEN_API_LATEST_DATA "latestdata"
@@ -57,8 +60,6 @@
5760
#define SONNEN_INVERTER_LATENCY_MS 5000 // battery inverter latency until load is compensated - sonnen battery 10 specific
5861
#define SONNEN_INVERTER_LATENCY_COUNT (1 + (SONNEN_INVERTER_LATENCY_MS + (SYSTEM_UPDATE_INTERVAL_MS >> 1)) / SYSTEM_UPDATE_INTERVAL_MS)
5962

60-
#define SYSTEM_ON_COUNT MAX(1, (10000 + (SYSTEM_UPDATE_INTERVAL_MS >> 1)) / SYSTEM_UPDATE_INTERVAL_MS)
61-
6263
#define URL_LOCATION "http://ip-api.com/json/"
6364

6465
#define SOLAR_FORECAST_INTERVAL_MS (12 * 60 * 1000) // update intervall every 12min
@@ -67,7 +68,6 @@
6768
#define URL_SOLAR_FORECAST "http://api.forecast.solar/estimate/watthours/period/%.4f/%.4f/%d/%d/%.2f?time=seconds&no_sun=0&full=1"
6869
#define URL_SOLAR_FORECAST_DEV "http://192.168.188.20:8080/estimate/watthours/period/%.4f/%.4f/%d/%d/%.2f?time=seconds&no_sun=0&full=1"
6970

70-
#define SYSTEM_UPDATE_INTERVAL_MS 2000 // update intervall millis
7171
#define GRID_PURCHASE_THRESHOLD_W 100
7272
#define BATTERY_MAX_USOC 10
7373
#define BATTERY_MIN_USOC 3
@@ -156,8 +156,7 @@ typedef struct
156156
typedef struct
157157
{
158158
uint32_t start_ts; // system start timestamp
159-
uint32_t ts; // current system time (UTC) - taken from battery status
160-
uint16_t tm_yday; // day of year
159+
uint32_t ts; // current system timestamp in seconds (UTC) - taken from battery status
161160
int16_t utc_offset; // UTC to local time offset in seconds
162161
bool switchEnabled; // state of the load switch
163162

smartswitch.espxx/include/app_js.h

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

smartswitch.espxx/src/main.ino

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -467,8 +467,10 @@ void handleData()
467467
json[F("start_ts")] = toLocalDate(&systemState, systemState.start_ts);
468468

469469
JsonArray forecast = json[F("pv_forecast")].to<JsonArray>();
470-
for (int i = 0; i < SOLAR_FORECAST_HOURS; i++) {
471-
if (systemState.pv_forecast_ts_wh[i][0] == 0) break;
470+
for (int i = 0; i < SOLAR_FORECAST_HOURS; i++)
471+
{
472+
if (systemState.pv_forecast_ts_wh[i][0] == 0)
473+
break;
472474
JsonArray entry = forecast.add<JsonArray>();
473475
entry.add(systemState.pv_forecast_ts_wh[i][0]); // Unix ts (s)
474476
entry.add(systemState.pv_forecast_ts_wh[i][1]); // Wh
@@ -1029,7 +1031,6 @@ static bool updateSystemData()
10291031
{
10301032
systemState.start_ts = systemState.ts;
10311033
}
1032-
systemState.tm_yday = time.tm_yday;
10331034

10341035
int16_t prev_stat_key = systemState.stat_hour_key;
10351036
updateSystemState(&config, &systemState);

smartswitch.espxx/test/test_logic.cpp

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ void test_determineDesiredStateBelowMinCapacityButSurplusWillFullCharge()
334334
systemState.switchEnabled = true;
335335

336336
bool state = determineState(msg, sizeof(msg));
337-
TEST_ASSERT_EQUAL_STRING("SoC 1% - battery will full charge, but SoC too low", msg);
337+
TEST_ASSERT_EQUAL_STRING("SoC 1% - battery will be full charged in ~7h, but SoC too low", msg);
338338
TEST_ASSERT_FALSE(state);
339339

340340
systemState.ts = TEST_TS + 35 * 3600;
@@ -428,6 +428,50 @@ void test_determineDesiredState_BatteryCapacityFinallyAboveMinCapacityButUsocToo
428428
TEST_ASSERT_FALSE(state);
429429
}
430430

431+
void test_updateConsumptionStats_Mean()
432+
{
433+
time_t local_ts0 = (time_t)(TEST_TS + systemState.utc_offset);
434+
struct tm lt0;
435+
gmtime_r(&local_ts0, &lt0);
436+
int wday = lt0.tm_wday;
437+
int hour0 = lt0.tm_hour;
438+
int hour1 = (hour0 + 1) % 24;
439+
int wday1 = wday;
440+
441+
systemState.ts = TEST_TS;
442+
systemState.cons_W = 300;
443+
updateSystemState(&systemConfig, &systemState);
444+
TEST_ASSERT_EQUAL(0, systemConfig.cons_stats_Wh[wday][hour0]); // slot not written yet
445+
TEST_ASSERT_EQUAL(300, systemState.cons_sum_W);
446+
TEST_ASSERT_EQUAL(1, systemState.cons_count);
447+
int i = 0;
448+
for (; i < (55 * 60 * 1000 / SYSTEM_UPDATE_INTERVAL_MS); i++)
449+
{
450+
updateSystemState(&systemConfig, &systemState);
451+
systemState.ts += SYSTEM_UPDATE_INTERVAL_MS / 1000;
452+
}
453+
TEST_ASSERT_EQUAL(300 + 300 * i, systemState.cons_sum_W);
454+
TEST_ASSERT_EQUAL(1 + i, systemState.cons_count);
455+
456+
systemState.cons_W = 4200;
457+
int j = 0;
458+
for (; j < (5 * 60 * 1000 / SYSTEM_UPDATE_INTERVAL_MS); j++) // 5min before next hour
459+
{
460+
updateSystemState(&systemConfig, &systemState);
461+
systemState.ts += SYSTEM_UPDATE_INTERVAL_MS / 1000;
462+
}
463+
TEST_ASSERT_EQUAL(300 + 300 * i + 4200 * j, systemState.cons_sum_W);
464+
TEST_ASSERT_EQUAL(1 + i + j, systemState.cons_count);
465+
466+
// rollover
467+
systemState.cons_W = 1200;
468+
updateSystemState(&systemConfig, &systemState);
469+
systemState.ts += SYSTEM_UPDATE_INTERVAL_MS / 1000;
470+
TEST_ASSERT_EQUAL(1200, systemState.cons_sum_W);
471+
TEST_ASSERT_EQUAL(1, systemState.cons_count);
472+
TEST_ASSERT_EQUAL(624, systemConfig.cons_stats_Wh[wday][hour0]); // slot has been written yet
473+
}
474+
431475
void test_updateConsumptionStats()
432476
{
433477
// Use TEST_TS local time to find wday/hour of hour being tested (hour 0) and next hour (hour 1)
@@ -437,7 +481,7 @@ void test_updateConsumptionStats()
437481
int wday = lt0.tm_wday;
438482
int hour0 = lt0.tm_hour;
439483
int hour1 = (hour0 + 1) % 24;
440-
int wday1 = (hour0 == 23) ? (wday + 1) % 7 : wday;
484+
int wday1 = wday;
441485

442486
// Accumulate 3 ticks in hour0: 300W, 400W, 500W → mean = 400W
443487
systemState.ts = TEST_TS;
@@ -454,23 +498,35 @@ void test_updateConsumptionStats()
454498
TEST_ASSERT_EQUAL(0, systemConfig.cons_stats_Wh[wday][hour0]); // still within same hour
455499

456500
// Advance to hour1 — triggers rollover: slot gets mean of hour0 samples (300+400+500)/3 = 400
457-
systemState.ts = TEST_TS + 3600;
501+
for (int i = 0; i < 750; i++)
502+
{
503+
updateSystemState(&systemConfig, &systemState);
504+
}
458505
systemState.cons_W = 200;
506+
for (int i = 0; i < 750; i++)
507+
{
508+
updateSystemState(&systemConfig, &systemState);
509+
}
510+
systemState.ts = TEST_TS + 3600;
459511
updateSystemState(&systemConfig, &systemState);
460-
TEST_ASSERT_EQUAL(400, systemConfig.cons_stats_Wh[wday][hour0]); // true mean written
461-
TEST_ASSERT_EQUAL(0, systemConfig.cons_stats_Wh[wday1][hour1]); // hour1 slot not yet written
512+
TEST_ASSERT_EQUAL(350, systemConfig.cons_stats_Wh[wday][hour0]); // true mean written
513+
TEST_ASSERT_EQUAL(0, systemConfig.cons_stats_Wh[wday1][hour1]); // hour1 slot not yet written
462514

463515
// Simulate a second occurrence of hour0 (next week): accumulate 200W
464516
systemState.ts = TEST_TS + 7 * 24 * 3600; // same weekday, same hour, one week later
465517
systemState.cons_W = 200;
466-
updateSystemState(&systemConfig, &systemState); // triggers hour1→hour0 rollover (writes hour1, resets)
467-
TEST_ASSERT_EQUAL(400, systemConfig.cons_stats_Wh[wday][hour0]); // unchanged until its own rollover
518+
updateSystemState(&systemConfig, &systemState); // triggers hour1→hour0 rollover (writes hour1, resets)
519+
TEST_ASSERT_EQUAL(350, systemConfig.cons_stats_Wh[wday][hour0]); // unchanged until its own rollover
468520

469-
// Advance past hour0 again: EMA blends 200W mean into existing 400W: (400*9 + 200) / 10 = 380
521+
// Advance past hour0 again: EMA blends 200W mean into existing 350W: (350*9 + 200) / 10 = 335
522+
for (int i = 0; i < 1440; i++)
523+
{
524+
updateSystemState(&systemConfig, &systemState);
525+
}
470526
systemState.ts = TEST_TS + 7 * 24 * 3600 + 3600;
471527
systemState.cons_W = 200;
472528
updateSystemState(&systemConfig, &systemState);
473-
TEST_ASSERT_EQUAL(380, systemConfig.cons_stats_Wh[wday][hour0]); // cross-week EMA applied
529+
TEST_ASSERT_EQUAL(335, systemConfig.cons_stats_Wh[wday][hour0]); // cross-week EMA applied
474530
}
475531

476532
void test_predictBatteryCapacityStateUsesStats()
@@ -516,7 +572,9 @@ int main(int argc, char **argv)
516572
UNITY_BEGIN();
517573

518574
RUN_TEST(test_upateConsumption);
575+
RUN_TEST(test_updateConsumptionStats_Mean);
519576
RUN_TEST(test_updateConsumptionStats);
577+
520578
RUN_TEST(test_predictBatteryCapacityStateNoData);
521579
RUN_TEST(test_predictBatteryCapacityStateSwitchOff);
522580
RUN_TEST(test_predictBatteryCapacityStateSwitchOn);

0 commit comments

Comments
 (0)