From 452a3bc207177d0aa4bd9190b02c097b46442b4e Mon Sep 17 00:00:00 2001 From: Timothy Allen Date: Sat, 10 Oct 2026 09:03:32 +0200 Subject: [PATCH] Correct the ups_alarm field spelling The upstream River 3 schema spells field 355 `ups_alram`. Use `ups_alarm` in the decoder, the generated schema, and the field reference. The schema generator now applies a rename map, so a regenerated schema keeps the corrected name. --- bmspy/ecoflow_river3.py | 2 +- bmspy/ecoflow_river3_schema.py | 2 +- docs/ecoflow-river3-fields.md | 2 +- tests/test_ecoflow_schema.py | 8 ++++++++ tools/generate_ecoflow_river3_schema.py | 4 +++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bmspy/ecoflow_river3.py b/bmspy/ecoflow_river3.py index bf1005b..1893c48 100644 --- a/bmspy/ecoflow_river3.py +++ b/bmspy/ecoflow_river3.py @@ -110,7 +110,7 @@ _DISPLAY_PROPERTY_UPLOAD: dict[int, ProtoField] = { 30: ProtoField("pcs_fan_level"), 195: ProtoField("en_beep"), 358: ProtoField("low_power_alarm", "%"), - 355: ProtoField("ups_alram"), + 355: ProtoField("ups_alarm"), } # ── River 3 — RuntimePropertyUpload field map ────────────────────────────────── diff --git a/bmspy/ecoflow_river3_schema.py b/bmspy/ecoflow_river3_schema.py index 9978daa..f61783d 100644 --- a/bmspy/ecoflow_river3_schema.py +++ b/bmspy/ecoflow_river3_schema.py @@ -238,7 +238,7 @@ MESSAGES = { 317: ('generator_sub_battery_state', 'uint32', False), 319: ('generator_maintence_state', 'bool', False), 330: ('generator_pcs_err_code', 'uint32', False), - 355: ('ups_alram', 'bool', False), + 355: ('ups_alarm', 'bool', False), 356: ('plug_in_info_pv_dc_amp_max', 'uint32', False), 357: ('led_mode', 'uint32', False), 358: ('low_power_alarm', 'uint32', False), diff --git a/docs/ecoflow-river3-fields.md b/docs/ecoflow-river3-fields.md index c596d1f..0f32e7d 100644 --- a/docs/ecoflow-river3-fields.md +++ b/docs/ecoflow-river3-fields.md @@ -141,7 +141,7 @@ still establish that a field arrived. | 275 | `cms_bms_run_state` | Battery-system operating-state code. The value mapping remains unverified. | | 281 | `bms_chg_dsg_state` | Battery-management charge/discharge state code. It is not decoded using River 2 meanings. | | 282 | `cms_chg_dsg_state` | Device-level charge/discharge state code. Exported separately from power-based direction. | -| 355 | `ups_alram` | Alarm flag associated with uninterruptible power supply operation. The protocol spelling is retained. Exact trigger conditions remain unverified. | +| 355 | `ups_alarm` | Alarm flag associated with uninterruptible power supply operation. Upstream spells this field `ups_alram`. bmspy corrects the spelling. Exact trigger conditions remain unverified. | | 361 | `pow_get_pv` | Solar/DC input power, W. | | 368 | `pow_get_ac_out` | AC output power, W. | diff --git a/tests/test_ecoflow_schema.py b/tests/test_ecoflow_schema.py index b984ada..10a07ff 100644 --- a/tests/test_ecoflow_schema.py +++ b/tests/test_ecoflow_schema.py @@ -189,6 +189,14 @@ def test_generator_keeps_reachable_types_and_ignores_unrelated_nested_types(gene assert 'Unrelated' not in messages +def test_generator_corrects_upstream_field_spelling(generator): + messages, _ = generator.parse_schema( + 'message DisplayPropertyUpload { optional bool ups_alram = 355; } message RuntimePropertyUpload {}' + ) + assert messages['DisplayPropertyUpload'][355] == ('ups_alarm', 'bool', False) + assert MESSAGES['DisplayPropertyUpload'][355] == ('ups_alarm', 'bool', False) + + @pytest.mark.parametrize('body', [ 'optional Missing value = 1;', 'optional uint32 a = 1; optional uint32 b = 1;', diff --git a/tools/generate_ecoflow_river3_schema.py b/tools/generate_ecoflow_river3_schema.py index e2dbc43..070772e 100644 --- a/tools/generate_ecoflow_river3_schema.py +++ b/tools/generate_ecoflow_river3_schema.py @@ -18,6 +18,8 @@ SCALARS = { "bool", "bytes", "string", "float", "double", "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64", } +# Corrected upstream field spellings. Field numbers, not names, identify wire data. +RENAMES = {"ups_alram": "ups_alarm"} def api(path): @@ -68,7 +70,7 @@ def parse_schema(text): if not match: raise ValueError(f"Unsupported field declaration in {name}: {statement}") cardinality, data_type, field_name, number = match.groups() - value = (field_name, data_type, cardinality == "repeated") + value = (RENAMES.get(field_name, field_name), data_type, cardinality == "repeated") else: match = re.fullmatch(r"(\w+)\s*=\s*(-?\d+)", statement) if not match: