From ca1e01e9914874107a01c7ddff68108cb2982735 Mon Sep 17 00:00:00 2001 From: Mark Gascoyne Date: Wed, 12 Aug 2026 10:23:57 +0100 Subject: [PATCH 1/3] feat(gateway): sync internal-DRAM fields into the shared status schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the three fields added to the gateway firmware's copy of gateway_status.proto. The two copies must stay in sync for wire compatibility. heap_internal_free (15) heap_internal_largest (16) heap_internal_min_free (17) Why they exist: free_heap_bytes (9) is ESP.getFreeHeap(), which can include PSRAM and so cannot predict the allocations that actually fail on the device — WiFi/lwIP/TLS/AsyncTCP buffers, task stacks and lwIP PCBs all need internal DRAM specifically. A hub silently lost its :80 listener (and with it the OCPP endpoint its EV charger connects to) for two days; field 9 looked fine throughout, while largest-contiguous-block was the number that showed the squeeze. Fields are appended, so both directions stay compatible — verified by round-trip across the old and new generated bindings: new firmware -> old PredBat: parses cleanly, unknown fields ignored old firmware -> new PredBat: parses, heap fields default to 0 That matters because firmware and PredBat version independently. No entities are published from these yet; the cloud consumes the status message directly. Exposing them in Home Assistant would add entities to every install and is a separate decision. Regenerated with protoc 34.1, then reformatted by the repo's black hook. Co-Authored-By: Claude Opus 5 --- apps/predbat/gateway_status.proto | 10 ++++++++++ apps/predbat/gateway_status_pb2.py | 16 ++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/predbat/gateway_status.proto b/apps/predbat/gateway_status.proto index 852db446d..ac98b4f55 100644 --- a/apps/predbat/gateway_status.proto +++ b/apps/predbat/gateway_status.proto @@ -160,6 +160,16 @@ message GatewayStatus { optional bool ev_control = 12; // present+true = EV-control mesh leader; absent = single/non-elected repeated EvCharger ev_chargers = 13; // one entry per connected/recent EV charger string wifi_ssid = 14; // WiFi.SSID() of the currently-connected network, empty if not on WiFi + + // Internal-DRAM health. free_heap_bytes (9) is ESP.getFreeHeap(), which can include + // PSRAM and so cannot predict the failures that actually matter: WiFi/lwIP/TLS/AsyncTCP + // and every task stack need INTERNAL DRAM specifically. A silent :80 bind failure that + // cost a customer two days of EV charging was invisible in field 9 but obvious in + // heap_internal_largest — AsyncServer::begin() needs one contiguous PCB allocation. + // Report all three so the fleet can be alerted on before a hub loses a service. + uint32 heap_internal_free = 15; // heap_caps_get_free_size(MALLOC_CAP_INTERNAL) + uint32 heap_internal_largest = 16; // largest contiguous block — the leading indicator + uint32 heap_internal_min_free = 17; // low-water mark since boot } // Execution plan sent from PredBat cloud to gateway diff --git a/apps/predbat/gateway_status_pb2.py b/apps/predbat/gateway_status_pb2.py index 6f1aa3815..8bb9fbed7 100644 --- a/apps/predbat/gateway_status_pb2.py +++ b/apps/predbat/gateway_status_pb2.py @@ -17,7 +17,7 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\x14gateway_status.proto\x12\x07predbat"\xe5\x01\n\rBatteryStatus\x12\x13\n\x0bsoc_percent\x18\x01 \x01(\r\x12\x11\n\tvoltage_v\x18\x02 \x01(\x02\x12\x11\n\tcurrent_a\x18\x03 \x01(\x02\x12\x0f\n\x07power_w\x18\x04 \x01(\x05\x12\x15\n\rtemperature_c\x18\x05 \x01(\x02\x12\x13\n\x0bsoh_percent\x18\x06 \x01(\r\x12\x13\n\x0b\x63ycle_count\x18\x07 \x01(\r\x12\x13\n\x0b\x63\x61pacity_wh\x18\x08 \x01(\x05\x12\x12\n\nrate_max_w\x18\t \x01(\r\x12\x1e\n\x16\x64\x65pth_of_discharge_pct\x18\n \x01(\r"\x1b\n\x08PvStatus\x12\x0f\n\x07power_w\x18\x01 \x01(\x05"F\n\nGridStatus\x12\x0f\n\x07power_w\x18\x01 \x01(\x05\x12\x11\n\tvoltage_v\x18\x02 \x01(\x02\x12\x14\n\x0c\x66requency_hz\x18\x03 \x01(\x02"\x1d\n\nLoadStatus\x12\x0f\n\x07power_w\x18\x01 \x01(\x05"Q\n\x0cInverterData\x12\x16\n\x0e\x61\x63tive_power_w\x18\x01 \x01(\x05\x12\x15\n\rtemperature_c\x18\x02 \x01(\x02\x12\x12\n\nrate_max_w\x18\x03 \x01(\r"\xea\x01\n\rControlStatus\x12\x16\n\x0e\x63harge_enabled\x18\x02 \x01(\x08\x12\x19\n\x11\x64ischarge_enabled\x18\x03 \x01(\x08\x12\x15\n\rcharge_rate_w\x18\x04 \x01(\r\x12\x18\n\x10\x64ischarge_rate_w\x18\x05 \x01(\r\x12\x13\n\x0breserve_soc\x18\x06 \x01(\r\x12\x12\n\ntarget_soc\x18\x07 \x01(\r\x12\x15\n\rforce_power_w\x18\x08 \x01(\r\x12\x17\n\x0f\x63ommand_expires\x18\t \x01(\r\x12\x16\n\x0e\x65xport_limit_w\x18\n \x01(\rJ\x04\x08\x01\x10\x02"j\n\x0eScheduleStatus\x12\x14\n\x0c\x63harge_start\x18\x01 \x01(\r\x12\x12\n\ncharge_end\x18\x02 \x01(\r\x12\x17\n\x0f\x64ischarge_start\x18\x03 \x01(\r\x12\x15\n\rdischarge_end\x18\x04 \x01(\r"^\n\x0e\x45msSubInverter\x12\x0b\n\x03soc\x18\x01 \x01(\r\x12\x11\n\tbattery_w\x18\x02 \x01(\x05\x12\x0c\n\x04pv_w\x18\x03 \x01(\x05\x12\x0e\n\x06grid_w\x18\x04 \x01(\x05\x12\x0e\n\x06temp_c\x18\x05 \x01(\x02"\xd8\x01\n\tEmsStatus\x12\x15\n\rnum_inverters\x18\x01 \x01(\r\x12\x11\n\ttotal_soc\x18\x02 \x01(\r\x12\x16\n\x0etotal_charge_w\x18\x03 \x01(\x05\x12\x19\n\x11total_discharge_w\x18\x04 \x01(\x05\x12\x14\n\x0ctotal_grid_w\x18\x05 \x01(\x05\x12\x12\n\ntotal_pv_w\x18\x06 \x01(\x05\x12\x14\n\x0ctotal_load_w\x18\x07 \x01(\x05\x12.\n\rsub_inverters\x18\x08 \x03(\x0b\x32\x17.predbat.EmsSubInverter"\xc4\x01\n\x0e\x45nergyCounters\x12\x13\n\x0bpv_today_wh\x18\x01 \x01(\r\x12\x1c\n\x14grid_import_today_wh\x18\x02 \x01(\r\x12\x1c\n\x14grid_export_today_wh\x18\x03 \x01(\r\x12\x1f\n\x17\x62\x61ttery_charge_today_wh\x18\x04 \x01(\r\x12"\n\x1a\x62\x61ttery_discharge_today_wh\x18\x05 \x01(\r\x12\x1c\n\x14\x63onsumption_today_wh\x18\x06 \x01(\r"\x90\x04\n\rInverterEntry\x12#\n\x04type\x18\x01 \x01(\x0e\x32\x15.predbat.InverterType\x12\x0e\n\x06serial\x18\x02 \x01(\t\x12\n\n\x02ip\x18\x03 \x01(\t\x12\x11\n\tconnected\x18\x04 \x01(\x08\x12\x0e\n\x06\x61\x63tive\x18\x05 \x01(\x08\x12\'\n\x07\x62\x61ttery\x18\x06 \x01(\x0b\x32\x16.predbat.BatteryStatus\x12\x1d\n\x02pv\x18\x07 \x01(\x0b\x32\x11.predbat.PvStatus\x12!\n\x04grid\x18\x08 \x01(\x0b\x32\x13.predbat.GridStatus\x12!\n\x04load\x18\t \x01(\x0b\x32\x13.predbat.LoadStatus\x12\'\n\x08inverter\x18\n \x01(\x0b\x32\x15.predbat.InverterData\x12\'\n\x07\x63ontrol\x18\x0b \x01(\x0b\x32\x16.predbat.ControlStatus\x12)\n\x08schedule\x18\x0c \x01(\x0b\x32\x17.predbat.ScheduleStatus\x12\x1f\n\x03\x65ms\x18\r \x01(\x0b\x32\x12.predbat.EmsStatus\x12\'\n\x06\x65nergy\x18\x0e \x01(\x0b\x32\x17.predbat.EnergyCounters\x12\x0f\n\x07managed\x18\x0f \x01(\x08\x12\x0f\n\x07primary\x18\x10 \x01(\x08\x12\x15\n\rdongle_serial\x18\x11 \x01(\t\x12\r\n\x05model\x18\x12 \x01(\t"\x8b\x02\n\tEvCharger\x12\x11\n\tconnected\x18\x01 \x01(\x08\x12\x16\n\x0esession_active\x18\x02 \x01(\x08\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x0f\n\x07power_w\x18\x04 \x01(\x05\x12\x19\n\x11session_energy_wh\x18\x05 \x01(\r\x12\x14\n\x0csession_secs\x18\x06 \x01(\r\x12\x17\n\x0f\x63urrent_limit_a\x18\x07 \x01(\r\x12\x13\n\x0bsoc_percent\x18\x08 \x01(\r\x12\x17\n\x0f\x63harge_point_id\x18\t \x01(\t\x12\x15\n\rmax_current_a\x18\n \x01(\r\x12\x11\n\tvoltage_v\x18\x0b \x01(\r\x12\x10\n\x08\x65\x63o_mode\x18\x0c \x01(\t"\xf0\x02\n\rGatewayStatus\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12\x14\n\x0c\x64ongle_count\x18\x02 \x01(\r\x12\x10\n\x08\x66irmware\x18\x03 \x01(\t\x12\x11\n\ttimestamp\x18\x04 \x01(\r\x12\x16\n\x0eschema_version\x18\x05 \x01(\r\x12)\n\tinverters\x18\x06 \x03(\x0b\x32\x16.predbat.InverterEntry\x12\x16\n\x0euptime_seconds\x18\x07 \x01(\r\x12\x15\n\rwifi_rssi_dbm\x18\x08 \x01(\x05\x12\x17\n\x0f\x66ree_heap_bytes\x18\t \x01(\r\x12\x11\n\tlog_level\x18\n \x01(\r\x12\x0f\n\x07wifi_ip\x18\x0b \x01(\t\x12\x17\n\nev_control\x18\x0c \x01(\x08H\x00\x88\x01\x01\x12\'\n\x0b\x65v_chargers\x18\r \x03(\x0b\x32\x12.predbat.EvCharger\x12\x11\n\twifi_ssid\x18\x0e \x01(\tB\r\n\x0b_ev_control"\xc9\x01\n\tPlanEntry\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x12\n\nstart_hour\x18\x02 \x01(\r\x12\x14\n\x0cstart_minute\x18\x03 \x01(\r\x12\x10\n\x08\x65nd_hour\x18\x04 \x01(\r\x12\x12\n\nend_minute\x18\x05 \x01(\r\x12\x0c\n\x04mode\x18\x06 \x01(\r\x12\x0f\n\x07power_w\x18\x07 \x01(\r\x12\x12\n\ntarget_soc\x18\x08 \x01(\r\x12\x14\n\x0c\x64\x61ys_of_week\x18\t \x01(\r\x12\x12\n\nuse_native\x18\n \x01(\x08"o\n\rExecutionPlan\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x14\n\x0cplan_version\x18\x02 \x01(\r\x12\x10\n\x08timezone\x18\x03 \x01(\t\x12#\n\x07\x65ntries\x18\x04 \x03(\x0b\x32\x12.predbat.PlanEntry*\xf2\x02\n\x0cInverterType\x12\x19\n\x15INVERTER_TYPE_UNKNOWN\x10\x00\x12\x1e\n\x1aINVERTER_TYPE_SOLIS_HYBRID\x10\x01\x12\x1a\n\x16INVERTER_TYPE_SOLIS_AC\x10\x02\x12\x1a\n\x16INVERTER_TYPE_SOFAR_G3\x10\x03\x12\x1d\n\x19INVERTER_TYPE_GROWATT_SPH\x10\x04\x12\x1e\n\x1aINVERTER_TYPE_DEYE_SUNSYNK\x10\x05\x12\x1b\n\x17INVERTER_TYPE_GIVENERGY\x10\x06\x12\x1f\n\x1bINVERTER_TYPE_GIVENERGY_EMS\x10\x07\x12#\n\x1fINVERTER_TYPE_GIVENERGY_GATEWAY\x10\x08\x12\x1b\n\x17INVERTER_TYPE_ALPHA_ESS\x10\t\x12\x16\n\x12INVERTER_TYPE_DEMO\x10\n\x12\x18\n\x14INVERTER_TYPE_CUSTOM\x10\x0b\x62\x06proto3' + b'\n\x14gateway_status.proto\x12\x07predbat"\xe5\x01\n\rBatteryStatus\x12\x13\n\x0bsoc_percent\x18\x01 \x01(\r\x12\x11\n\tvoltage_v\x18\x02 \x01(\x02\x12\x11\n\tcurrent_a\x18\x03 \x01(\x02\x12\x0f\n\x07power_w\x18\x04 \x01(\x05\x12\x15\n\rtemperature_c\x18\x05 \x01(\x02\x12\x13\n\x0bsoh_percent\x18\x06 \x01(\r\x12\x13\n\x0b\x63ycle_count\x18\x07 \x01(\r\x12\x13\n\x0b\x63\x61pacity_wh\x18\x08 \x01(\x05\x12\x12\n\nrate_max_w\x18\t \x01(\r\x12\x1e\n\x16\x64\x65pth_of_discharge_pct\x18\n \x01(\r"\x1b\n\x08PvStatus\x12\x0f\n\x07power_w\x18\x01 \x01(\x05"F\n\nGridStatus\x12\x0f\n\x07power_w\x18\x01 \x01(\x05\x12\x11\n\tvoltage_v\x18\x02 \x01(\x02\x12\x14\n\x0c\x66requency_hz\x18\x03 \x01(\x02"\x1d\n\nLoadStatus\x12\x0f\n\x07power_w\x18\x01 \x01(\x05"Q\n\x0cInverterData\x12\x16\n\x0e\x61\x63tive_power_w\x18\x01 \x01(\x05\x12\x15\n\rtemperature_c\x18\x02 \x01(\x02\x12\x12\n\nrate_max_w\x18\x03 \x01(\r"\xea\x01\n\rControlStatus\x12\x16\n\x0e\x63harge_enabled\x18\x02 \x01(\x08\x12\x19\n\x11\x64ischarge_enabled\x18\x03 \x01(\x08\x12\x15\n\rcharge_rate_w\x18\x04 \x01(\r\x12\x18\n\x10\x64ischarge_rate_w\x18\x05 \x01(\r\x12\x13\n\x0breserve_soc\x18\x06 \x01(\r\x12\x12\n\ntarget_soc\x18\x07 \x01(\r\x12\x15\n\rforce_power_w\x18\x08 \x01(\r\x12\x17\n\x0f\x63ommand_expires\x18\t \x01(\r\x12\x16\n\x0e\x65xport_limit_w\x18\n \x01(\rJ\x04\x08\x01\x10\x02"j\n\x0eScheduleStatus\x12\x14\n\x0c\x63harge_start\x18\x01 \x01(\r\x12\x12\n\ncharge_end\x18\x02 \x01(\r\x12\x17\n\x0f\x64ischarge_start\x18\x03 \x01(\r\x12\x15\n\rdischarge_end\x18\x04 \x01(\r"^\n\x0e\x45msSubInverter\x12\x0b\n\x03soc\x18\x01 \x01(\r\x12\x11\n\tbattery_w\x18\x02 \x01(\x05\x12\x0c\n\x04pv_w\x18\x03 \x01(\x05\x12\x0e\n\x06grid_w\x18\x04 \x01(\x05\x12\x0e\n\x06temp_c\x18\x05 \x01(\x02"\xd8\x01\n\tEmsStatus\x12\x15\n\rnum_inverters\x18\x01 \x01(\r\x12\x11\n\ttotal_soc\x18\x02 \x01(\r\x12\x16\n\x0etotal_charge_w\x18\x03 \x01(\x05\x12\x19\n\x11total_discharge_w\x18\x04 \x01(\x05\x12\x14\n\x0ctotal_grid_w\x18\x05 \x01(\x05\x12\x12\n\ntotal_pv_w\x18\x06 \x01(\x05\x12\x14\n\x0ctotal_load_w\x18\x07 \x01(\x05\x12.\n\rsub_inverters\x18\x08 \x03(\x0b\x32\x17.predbat.EmsSubInverter"\xc4\x01\n\x0e\x45nergyCounters\x12\x13\n\x0bpv_today_wh\x18\x01 \x01(\r\x12\x1c\n\x14grid_import_today_wh\x18\x02 \x01(\r\x12\x1c\n\x14grid_export_today_wh\x18\x03 \x01(\r\x12\x1f\n\x17\x62\x61ttery_charge_today_wh\x18\x04 \x01(\r\x12"\n\x1a\x62\x61ttery_discharge_today_wh\x18\x05 \x01(\r\x12\x1c\n\x14\x63onsumption_today_wh\x18\x06 \x01(\r"\x90\x04\n\rInverterEntry\x12#\n\x04type\x18\x01 \x01(\x0e\x32\x15.predbat.InverterType\x12\x0e\n\x06serial\x18\x02 \x01(\t\x12\n\n\x02ip\x18\x03 \x01(\t\x12\x11\n\tconnected\x18\x04 \x01(\x08\x12\x0e\n\x06\x61\x63tive\x18\x05 \x01(\x08\x12\'\n\x07\x62\x61ttery\x18\x06 \x01(\x0b\x32\x16.predbat.BatteryStatus\x12\x1d\n\x02pv\x18\x07 \x01(\x0b\x32\x11.predbat.PvStatus\x12!\n\x04grid\x18\x08 \x01(\x0b\x32\x13.predbat.GridStatus\x12!\n\x04load\x18\t \x01(\x0b\x32\x13.predbat.LoadStatus\x12\'\n\x08inverter\x18\n \x01(\x0b\x32\x15.predbat.InverterData\x12\'\n\x07\x63ontrol\x18\x0b \x01(\x0b\x32\x16.predbat.ControlStatus\x12)\n\x08schedule\x18\x0c \x01(\x0b\x32\x17.predbat.ScheduleStatus\x12\x1f\n\x03\x65ms\x18\r \x01(\x0b\x32\x12.predbat.EmsStatus\x12\'\n\x06\x65nergy\x18\x0e \x01(\x0b\x32\x17.predbat.EnergyCounters\x12\x0f\n\x07managed\x18\x0f \x01(\x08\x12\x0f\n\x07primary\x18\x10 \x01(\x08\x12\x15\n\rdongle_serial\x18\x11 \x01(\t\x12\r\n\x05model\x18\x12 \x01(\t"\x8b\x02\n\tEvCharger\x12\x11\n\tconnected\x18\x01 \x01(\x08\x12\x16\n\x0esession_active\x18\x02 \x01(\x08\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x0f\n\x07power_w\x18\x04 \x01(\x05\x12\x19\n\x11session_energy_wh\x18\x05 \x01(\r\x12\x14\n\x0csession_secs\x18\x06 \x01(\r\x12\x17\n\x0f\x63urrent_limit_a\x18\x07 \x01(\r\x12\x13\n\x0bsoc_percent\x18\x08 \x01(\r\x12\x17\n\x0f\x63harge_point_id\x18\t \x01(\t\x12\x15\n\rmax_current_a\x18\n \x01(\r\x12\x11\n\tvoltage_v\x18\x0b \x01(\r\x12\x10\n\x08\x65\x63o_mode\x18\x0c \x01(\t"\xcb\x03\n\rGatewayStatus\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12\x14\n\x0c\x64ongle_count\x18\x02 \x01(\r\x12\x10\n\x08\x66irmware\x18\x03 \x01(\t\x12\x11\n\ttimestamp\x18\x04 \x01(\r\x12\x16\n\x0eschema_version\x18\x05 \x01(\r\x12)\n\tinverters\x18\x06 \x03(\x0b\x32\x16.predbat.InverterEntry\x12\x16\n\x0euptime_seconds\x18\x07 \x01(\r\x12\x15\n\rwifi_rssi_dbm\x18\x08 \x01(\x05\x12\x17\n\x0f\x66ree_heap_bytes\x18\t \x01(\r\x12\x11\n\tlog_level\x18\n \x01(\r\x12\x0f\n\x07wifi_ip\x18\x0b \x01(\t\x12\x17\n\nev_control\x18\x0c \x01(\x08H\x00\x88\x01\x01\x12\'\n\x0b\x65v_chargers\x18\r \x03(\x0b\x32\x12.predbat.EvCharger\x12\x11\n\twifi_ssid\x18\x0e \x01(\t\x12\x1a\n\x12heap_internal_free\x18\x0f \x01(\r\x12\x1d\n\x15heap_internal_largest\x18\x10 \x01(\r\x12\x1e\n\x16heap_internal_min_free\x18\x11 \x01(\rB\r\n\x0b_ev_control"\xc9\x01\n\tPlanEntry\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x12\n\nstart_hour\x18\x02 \x01(\r\x12\x14\n\x0cstart_minute\x18\x03 \x01(\r\x12\x10\n\x08\x65nd_hour\x18\x04 \x01(\r\x12\x12\n\nend_minute\x18\x05 \x01(\r\x12\x0c\n\x04mode\x18\x06 \x01(\r\x12\x0f\n\x07power_w\x18\x07 \x01(\r\x12\x12\n\ntarget_soc\x18\x08 \x01(\r\x12\x14\n\x0c\x64\x61ys_of_week\x18\t \x01(\r\x12\x12\n\nuse_native\x18\n \x01(\x08"o\n\rExecutionPlan\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x14\n\x0cplan_version\x18\x02 \x01(\r\x12\x10\n\x08timezone\x18\x03 \x01(\t\x12#\n\x07\x65ntries\x18\x04 \x03(\x0b\x32\x12.predbat.PlanEntry*\xf2\x02\n\x0cInverterType\x12\x19\n\x15INVERTER_TYPE_UNKNOWN\x10\x00\x12\x1e\n\x1aINVERTER_TYPE_SOLIS_HYBRID\x10\x01\x12\x1a\n\x16INVERTER_TYPE_SOLIS_AC\x10\x02\x12\x1a\n\x16INVERTER_TYPE_SOFAR_G3\x10\x03\x12\x1d\n\x19INVERTER_TYPE_GROWATT_SPH\x10\x04\x12\x1e\n\x1aINVERTER_TYPE_DEYE_SUNSYNK\x10\x05\x12\x1b\n\x17INVERTER_TYPE_GIVENERGY\x10\x06\x12\x1f\n\x1bINVERTER_TYPE_GIVENERGY_EMS\x10\x07\x12#\n\x1fINVERTER_TYPE_GIVENERGY_GATEWAY\x10\x08\x12\x1b\n\x17INVERTER_TYPE_ALPHA_ESS\x10\t\x12\x16\n\x12INVERTER_TYPE_DEMO\x10\n\x12\x18\n\x14INVERTER_TYPE_CUSTOM\x10\x0b\x62\x06proto3' ) _globals = globals() @@ -25,8 +25,8 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "gateway_status_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals["_INVERTERTYPE"]._serialized_start = 2829 - _globals["_INVERTERTYPE"]._serialized_end = 3199 + _globals["_INVERTERTYPE"]._serialized_start = 2920 + _globals["_INVERTERTYPE"]._serialized_end = 3290 _globals["_BATTERYSTATUS"]._serialized_start = 34 _globals["_BATTERYSTATUS"]._serialized_end = 263 _globals["_PVSTATUS"]._serialized_start = 265 @@ -52,9 +52,9 @@ _globals["_EVCHARGER"]._serialized_start = 1871 _globals["_EVCHARGER"]._serialized_end = 2138 _globals["_GATEWAYSTATUS"]._serialized_start = 2141 - _globals["_GATEWAYSTATUS"]._serialized_end = 2509 - _globals["_PLANENTRY"]._serialized_start = 2512 - _globals["_PLANENTRY"]._serialized_end = 2713 - _globals["_EXECUTIONPLAN"]._serialized_start = 2715 - _globals["_EXECUTIONPLAN"]._serialized_end = 2826 + _globals["_GATEWAYSTATUS"]._serialized_end = 2600 + _globals["_PLANENTRY"]._serialized_start = 2603 + _globals["_PLANENTRY"]._serialized_end = 2804 + _globals["_EXECUTIONPLAN"]._serialized_start = 2806 + _globals["_EXECUTIONPLAN"]._serialized_end = 2917 # @@protoc_insertion_point(module_scope) From 1a71ad2566960bbad6847b42c59b5a4ce7c700e3 Mon Sep 17 00:00:00 2001 From: Mark Gascoyne Date: Thu, 13 Aug 2026 00:15:24 +0100 Subject: [PATCH 2/3] docs(gateway): clarify the heap fields measure byte-addressable internal DRAM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comment-only, mirroring the firmware copy. MALLOC_CAP_INTERNAL alone can include regions that are not byte-addressable; lwIP objects, task stacks and most library allocations need internal 8-bit-capable memory specifically, so the firmware queries MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT for these fields. Querying the wider set would overstate usable headroom — several KB can remain in an internal 32-bit/IRAM region while 8-bit DRAM is exhausted and allocations still fail. No wire or field-number change. Co-Authored-By: Claude Opus 5 --- apps/predbat/gateway_status.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/predbat/gateway_status.proto b/apps/predbat/gateway_status.proto index ac98b4f55..3a357fda6 100644 --- a/apps/predbat/gateway_status.proto +++ b/apps/predbat/gateway_status.proto @@ -163,11 +163,11 @@ message GatewayStatus { // Internal-DRAM health. free_heap_bytes (9) is ESP.getFreeHeap(), which can include // PSRAM and so cannot predict the failures that actually matter: WiFi/lwIP/TLS/AsyncTCP - // and every task stack need INTERNAL DRAM specifically. A silent :80 bind failure that + // and every task stack need byte-addressable INTERNAL DRAM specifically. A silent :80 bind failure that // cost a customer two days of EV charging was invisible in field 9 but obvious in // heap_internal_largest — AsyncServer::begin() needs one contiguous PCB allocation. // Report all three so the fleet can be alerted on before a hub loses a service. - uint32 heap_internal_free = 15; // heap_caps_get_free_size(MALLOC_CAP_INTERNAL) + uint32 heap_internal_free = 15; // heap_caps_get_free_size(INTERNAL | 8BIT) uint32 heap_internal_largest = 16; // largest contiguous block — the leading indicator uint32 heap_internal_min_free = 17; // low-water mark since boot } From 5f08d5053003064dc1902f888c67f0a7289c3e57 Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:48:33 +0100 Subject: [PATCH 3/3] Update gateway_status.proto with schema version info --- apps/predbat/gateway_status.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/predbat/gateway_status.proto b/apps/predbat/gateway_status.proto index 3a357fda6..847ad3ad7 100644 --- a/apps/predbat/gateway_status.proto +++ b/apps/predbat/gateway_status.proto @@ -6,6 +6,7 @@ package predbat; // Schema version: 1 // This is the canonical contract for ESP32 gateway → cloud MQTT status payloads. // Proto3 zero-value omission means unpopulated future fields cost nothing on the wire. +// // Mirrors InverterType enum in types.h (values must match 1:1 for safe casting) enum InverterType {