Skip to content

Commit 14ddbdb

Browse files
author
Maksym Shtembuliak
committed
fix: model protocol 3 and 4 telemetry maps
1 parent 602e5d7 commit 14ddbdb

25 files changed

Lines changed: 1651 additions & 965 deletions

‎CHANGELOG.md‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ the GitHub release body should be rendered from the matching version section her
1111

1212
- Added built-in detection and telemetry mapping for the Sandisolar SD 11KP48V
1313
WIFI fingerprint (`layout=4`, `model=0x8003`) from issue #13 evidence.
14-
Compatible controls remain explicitly untested and Full-Control-only.
14+
Its two hardware-confirmed Secondary Priority controls are available normally;
15+
the other compatible controls remain explicitly untested and Full-Control-only.
1516
- Added a typed support-acquisition boundary so read-only cloud evidence can be
1617
collected for an identified collector before an inverter driver is known.
1718
- Added 118 opt-in, document-backed settings for the exact Kevolt
@@ -21,6 +22,11 @@ the GitHub release body should be rendered from the matching version section her
2122

2223
### Changed
2324

25+
- Rebuilt the Anenji Protocol No. 3-10 telemetry layer as one documented shared
26+
schema with separate Protocol 3 and Protocol 4 output projections. ANJ-11KW,
27+
HHS-11kW and Sandisolar fingerprints now select registers by protocol identity,
28+
never by a zero/non-zero value fallback, while ordinary SMG Protocol 1 devices
29+
retain their existing map.
2430
- Moved device-support analysis and collector cloud-traffic capture under one
2531
**Expand device support** menu with separate readiness rules for read-only
2632
metadata, active learning, and proxy capture.
@@ -38,10 +44,10 @@ the GitHub release body should be rendered from the matching version section her
3844
challenge on the exact observed session, accepts only a correlated full-PN
3945
response, and never falls back to peer-IP routing. Unknown wire dialects are
4046
tried only on separate callback attempts and separate physical sockets.
41-
- Correct the Anenji HHS-11kW non-parallel Grid Power source for the exact
42-
layout-3/model-29440 fingerprint: hardware-correlated cloud traffic reads
43-
holding register 340, not the compatible-family register 204.
44-
47+
- Corrected the canonical grid and inverter power projection for documented
48+
Protocol 3/4 layouts. Grid Power now comes from holding register 340, with
49+
protocol-specific output/load blocks; legacy total fields remain diagnostic
50+
evidence rather than speculative runtime fallbacks.
4551
- Corrected the Kevolt PD0080G-TPM-EU rated-power scale from 80 kW to its
4652
hardware-confirmed 8 kW rating without changing the stable internal
4753
fingerprint key or its already-correct battery-value sign.

‎catalog/inverter_models/models/anenji_anj_11kw_48v_wifi_p.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@
2626
"vendor_register_map": "documented",
2727
"notes": [
2828
"Device-specific runtime profile is user-confirmed.",
29-
"Private Anenji Protocol No. 3-10 documentation backs a model-specific register map for this runtime family.",
29+
"Communication Protocol No. 3-10 documentation defines the protocol-4 telemetry projection used by this model.",
3030
"Register 602 secondary-output priority is documented and hardware-read-correlated; local write behavior remains conditional pending a device retest."
3131
]
3232
},
3333
"known_limitations": [
3434
"Secondary Output Priority writes are available only in Full Control until register 602 is write-tested on hardware."
3535
],
36-
"knowledge_summary": "Anenji 11kW unit with a model-specific profile and register schema, backed by donor captures, user-confirmed controls, and the Protocol No. 3-10 register documentation.",
36+
"knowledge_summary": "Anenji 11kW unit with a model-specific control profile over the shared protocol-4 register projection, backed by donor captures, user-confirmed controls, and the Protocol No. 3-10 documentation.",
3737
"source_keys": [
3838
"corpus_anenji_anj_11kw",
3939
"private_message_anenji_anj_11kw_validation",

‎catalog/inverter_models/models/anenji_hhs_11kw_wifi_no_parallel.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@
2626
"vendor_register_map": "documented",
2727
"notes": [
2828
"The exact layout/model fingerprint is captured from a beta.3 support archive.",
29-
"The compatible protocol 3-10 telemetry schema is enabled read-only.",
30-
"A later proxy capture and matching DESSMonitor export confirmed Grid Power at holding register 340 for this exact fingerprint.",
29+
"The protocol-3 telemetry projection is enabled read-only.",
30+
"A later proxy capture and matching DESSMonitor export confirmed the documented L1 Grid Power at holding register 340 for this exact fingerprint.",
3131
"No ANJ-11KW writable capability is inherited until this exact HHS model is write-validated."
3232
]
3333
},
3434
"known_limitations": [
3535
"Local controls remain unavailable pending model-specific hardware validation."
3636
],
37-
"knowledge_summary": "An Anenji HHS 11kW non-parallel inverter with an exact captured protocol-3 fingerprint. The integration exposes its compatible telemetry schema read-only, including hardware-correlated Grid Power, and deliberately withholds ANJ write controls.",
37+
"knowledge_summary": "An Anenji HHS 11kW non-parallel inverter with an exact captured protocol-3 fingerprint. The integration exposes the shared protocol-3 telemetry projection read-only, including hardware-correlated Grid Power, and deliberately withholds ANJ write controls.",
3838
"source_keys": [
3939
"github_issue_13_hhs_11kw_beta3",
4040
"github_issue_13_hhs_grid_power_20260827"

‎catalog/inverter_models/models/sandisolar_sd_11kp48v_wifi.json‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,29 @@
1818
"validation": {
1919
"hardware": "captured",
2020
"telemetry": "partial",
21-
"controls": "none"
21+
"controls": "partial"
2222
},
2323
"coverage": {
2424
"runtime_control_surface": "available",
2525
"smartess_control_surface": "unknown",
2626
"vendor_register_map": "documented",
2727
"notes": [
2828
"The exact layout/model fingerprint is captured from the issue #13 beta.4 support archive.",
29-
"Telemetry uses the documented Anenji protocol-4 register schema; this prevents generic SMG interpretation of register 227 as temperature.",
30-
"The compatible ANJ write surface is inherited only as untested Full-Control candidates. Automatic control mode exposes none of these writes."
29+
"Telemetry uses the documented protocol-4 projection; this prevents generic SMG interpretation of register 227 as temperature.",
30+
"Canonical Grid and inverter power values use the protocol-4 L1 points. Legacy total fields remain available in support captures instead of acting as value-based fallbacks.",
31+
"Secondary Charging Priority and Secondary Output Priority are hardware-confirmed on this exact fingerprint.",
32+
"The remaining compatible ANJ write surface is inherited only as untested Full-Control candidates."
3133
]
3234
},
3335
"known_limitations": [
34-
"No local write has been validated on this exact Sandisolar fingerprint; every inherited control remains Full-Control-only until hardware retesting."
36+
"Only Secondary Charging Priority and Secondary Output Priority have been hardware-validated; every other inherited control remains Full-Control-only until hardware retesting."
3537
],
36-
"knowledge_summary": "Sandisolar SD 11KP48V WIFI with exact layout-4/model-0x8003 identity. Captured telemetry follows the documented ANJ protocol-4 map, while compatible write candidates remain explicitly untested.",
38+
"knowledge_summary": "Sandisolar SD 11KP48V WIFI with exact layout-4/model-0x8003 identity. Captured telemetry and protocol documentation select the shared protocol-4 projection, while compatible write candidates remain explicitly untested.",
3739
"source_keys": [
3840
"github_issue_13_sandisolar_sd_11kp48v_beta4",
39-
"documentation_anenji_protocol_3_10"
41+
"github_issue_13_sandisolar_grid_power_20260829",
42+
"github_issue_13_sandisolar_secondary_controls_20260827",
43+
"documentation_anenji_protocol_3_10",
44+
"third_party_easun_smg_ii_11kw_register_map"
4045
]
4146
}

‎catalog/inverter_models/sources/documentation_anenji_protocol_3_10.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"captured_at": "2026-06-19",
88
"visibility": "private",
99
"summary_publishable": true,
10-
"summary": "Private local copy of Communication.Protocol.No.3-10.pdf, a 20-page Modbus RTU register document covering protocol numbers 3, 4, 5, and 6 for the ANJ-11KW runtime family.",
10+
"summary": "Private local copy of Communication.Protocol.No.3-10.pdf, a Modbus RTU register document that distinguishes shared, total, and per-output points across protocol numbers 3, 4, 5, and 6 for the ANJ-11KW-compatible runtime family.",
1111
"assertions": [
1212
"register_map",
1313
"runtime_compatibility"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"schema_version": 1,
3+
"source_key": "github_issue_13_sandisolar_grid_power_20260829",
4+
"kind": "github_issue",
5+
"title": "Sandisolar SD 11KP48V WIFI Grid Power correlation",
6+
"reference": "project-issue:13",
7+
"captured_at": "2026-08-29",
8+
"visibility": "public",
9+
"summary": "For the exact layout-4/model-0x8003 Sandisolar fingerprint, manufacturer-cloud screenshots and the inverter display showed non-zero Grid Power while the inherited register-204 projection remained zero in Home Assistant. This is supporting evidence that register 204 is not a usable canonical Grid Power source for this layout; the primary register-340 mapping comes from the protocol-4 documentation rather than from a same-instant raw correlation.",
10+
"assertions": [
11+
"telemetry_validation",
12+
"register_map"
13+
]
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"schema_version": 1,
3+
"source_key": "github_issue_13_sandisolar_secondary_controls_20260827",
4+
"kind": "github_issue",
5+
"title": "Sandisolar SD 11KP48V WIFI secondary-priority control validation",
6+
"reference": "project-issue:13#sandisolar-secondary-priority-validation",
7+
"captured_at": "2026-08-27",
8+
"visibility": "public",
9+
"summary": "The owner of the exact Sandisolar SD 11KP48V WIFI layout-4/model-0x8003 fingerprint confirmed that local writes and subsequent readback for Secondary Charging Priority and Secondary Output Priority both worked correctly. No other inherited write capability was validated by this report.",
10+
"assertions": [
11+
"hardware_test",
12+
"write_validation",
13+
"control_behavior"
14+
]
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"schema_version": 1,
3+
"source_key": "third_party_easun_smg_ii_11kw_register_map",
4+
"kind": "third_party_impl",
5+
"title": "Easun SMG II 11kW register map implementation",
6+
"reference": "https://github.com/alcestide/easun-smg-ii-11kw",
7+
"captured_at": "2026-08-29",
8+
"visibility": "public",
9+
"summary": "An independent compatible implementation maps holding register 340 to signed L1 Grid Active Power and marks it applicable to protocol layouts 3, 4, 5, and 6. Its register 204 Total Grid Active Power entry is not marked applicable to those layouts.",
10+
"assertions": [
11+
"register_map"
12+
]
13+
}

‎custom_components/eybond_local/drivers/smg.py‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -499,14 +499,6 @@ async def async_read_values(
499499
config_block = await session.read_holding(config_block_start, config_block_count)
500500
values.update(_decode_block(config_block_start, config_block, config_fields))
501501
values.update(await _read_optional_specs(session, aux_config_fields))
502-
# Exact model overlays may correct a value inherited from a compatible
503-
# family schema. Remove the inherited projection before the optional
504-
# read so a failed model-specific read is reported as unavailable,
505-
# never as a plausible value from the wrong register.
506-
value_overrides = schema.spec_sets.get("value_overrides", ())
507-
for spec in value_overrides:
508-
values.pop(spec.key, None)
509-
values.update(await _read_optional_specs(session, value_overrides))
510502
missing_probe_details = await _read_missing_optional_probe_details(session, inverter)
511503
if missing_probe_details:
512504
inverter.details.update(missing_probe_details)

‎custom_components/eybond_local/metadata/register_schema_loader.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,15 @@ def _merge_raw_schema(
462462
**base.get("scalar_registers", {}),
463463
**overlay.get("scalar_registers", {}),
464464
}
465+
merged["spec_sets"] = _remove_spec_set_items(
466+
merged["spec_sets"],
467+
overlay.get("spec_set_removals", {}),
468+
)
469+
merged["measurement_descriptions"] = _remove_keyed_items(
470+
merged["measurement_descriptions"],
471+
overlay.get("measurement_description_removals", []),
472+
key_field="key",
473+
)
465474

466475
for key, value in overlay.items():
467476
if key in {
@@ -472,6 +481,8 @@ def _merge_raw_schema(
472481
"enum_tables",
473482
"bit_labels",
474483
"scalar_registers",
484+
"spec_set_removals",
485+
"measurement_description_removals",
475486
}:
476487
continue
477488
merged[key] = value
@@ -502,6 +513,45 @@ def _merge_nested_maps(
502513
return merged
503514

504515

516+
def _remove_spec_set_items(
517+
spec_sets: Mapping[str, list[dict[str, Any]]],
518+
removals: Any,
519+
) -> dict[str, list[dict[str, Any]]]:
520+
"""Remove inherited specs explicitly named by a declarative overlay."""
521+
522+
merged = {key: [dict(item) for item in value] for key, value in spec_sets.items()}
523+
if not isinstance(removals, Mapping):
524+
raise ValueError("register_schema:spec_set_removals_must_be_mapping")
525+
for set_key, removed_keys in removals.items():
526+
if not isinstance(removed_keys, list) or any(
527+
type(item) is not str or not item or item != item.strip()
528+
for item in removed_keys
529+
):
530+
raise ValueError(f"register_schema:invalid_spec_set_removals:{set_key}")
531+
removed = set(removed_keys)
532+
merged[str(set_key)] = [
533+
item for item in merged.get(str(set_key), []) if item.get("key") not in removed
534+
]
535+
return merged
536+
537+
538+
def _remove_keyed_items(
539+
items: list[Mapping[str, Any]],
540+
removed_keys: Any,
541+
*,
542+
key_field: str,
543+
) -> list[dict[str, Any]]:
544+
"""Remove inherited keyed records explicitly named by an overlay."""
545+
546+
if not isinstance(removed_keys, list) or any(
547+
type(item) is not str or not item or item != item.strip()
548+
for item in removed_keys
549+
):
550+
raise ValueError(f"register_schema:invalid_{key_field}_removals")
551+
removed = set(removed_keys)
552+
return [dict(item) for item in items if item.get(key_field) not in removed]
553+
554+
505555
def _merge_keyed_list(
506556
base: list[Mapping[str, Any]],
507557
overlay: list[Mapping[str, Any]],

0 commit comments

Comments
 (0)