Skip to content

Commit 9c14cf1

Browse files
author
Maksym Shtembuliak
committed
feat: resolve unknown SMG models by protocol
1 parent 3279199 commit 9c14cf1

19 files changed

Lines changed: 628 additions & 47 deletions

‎CHANGELOG.md‎

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

2828
### Changed
2929

30+
- Unknown inverter models that report documented Anenji Communication Protocol
31+
3, 4, 5, or 6 now select the matching protocol-specific telemetry map
32+
automatically instead of requiring a new exact-model catalog entry. Their
33+
document-backed controls remain untested and monitoring-only in Auto mode;
34+
users can opt into non-blocked controls through Full Control.
3035
- Replaced the ambiguous writable `modbus_smg/base.json` profile with one
3136
explicit, document-backed classic SMG RS232 V1 map shared by compatible
3237
layout 1/2/11 model profiles. Exact hardware overlays retain their existing

‎custom_components/eybond_local/metadata/detection_descriptor_loader.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ def _descriptor_from_family_default(default: FamilyDefault) -> DetectionDeviceDe
427427
optional_ascii=_optional_ascii_descriptors(default.runtime_probe.optional_ascii),
428428
provenance_sources=(default.note,) if default.note else (),
429429
source_catalog="device_catalog.family_defaults",
430-
read_only=True,
430+
read_only=catalog_surface.read_only,
431431
family_fallback=True,
432432
priority=0,
433433
)

‎custom_components/eybond_local/metadata/detection_evidence.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def _descriptor_report_agreement(
157157
if catalog_match_kind == "device":
158158
return "match" if catalog_entry_key and catalog_entry_key == resolved_key else "mismatch"
159159
if catalog_match_kind == "family":
160-
return "match" if resolved_key.endswith(".family_fallback") else "mismatch"
160+
return "match" if resolved_key.endswith("family_fallback") else "mismatch"
161161
if catalog_match_kind in {"no_data", "unidentified"}:
162162
return "not_applicable"
163163
return "unknown"

‎custom_components/eybond_local/protocol_catalogs/inverter_catalog.json‎

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"schema_version": 2,
3-
"catalog_version": "2026.08.6",
3+
"catalog_version": "2026.08.7",
44
"description": "Universal offline inverter catalog with reusable protocol actions, runtime surfaces, and deterministic identity evidence.",
55
"protocols": [
66
{
@@ -1345,6 +1345,50 @@
13451345
"read_only": true,
13461346
"support_capture_policy": "smg_protocol_1"
13471347
},
1348+
{
1349+
"key": "smg_protocol_3_family_fallback",
1350+
"protocol_key": "modbus_smg",
1351+
"driver_key": "modbus_smg",
1352+
"variant_key": "protocol_3_family_fallback",
1353+
"profile_name": "modbus_smg/protocols/communication_protocol_3.json",
1354+
"register_schema_name": "modbus_smg/protocols/communication_protocol_3.json",
1355+
"tier": "full",
1356+
"read_only": false,
1357+
"support_capture_policy": "smg_anenji_11k"
1358+
},
1359+
{
1360+
"key": "smg_protocol_4_family_fallback",
1361+
"protocol_key": "modbus_smg",
1362+
"driver_key": "modbus_smg",
1363+
"variant_key": "protocol_4_family_fallback",
1364+
"profile_name": "modbus_smg/protocols/communication_protocol_4.json",
1365+
"register_schema_name": "modbus_smg/protocols/communication_protocol_4.json",
1366+
"tier": "full",
1367+
"read_only": false,
1368+
"support_capture_policy": "smg_anenji_11k"
1369+
},
1370+
{
1371+
"key": "smg_protocol_5_family_fallback",
1372+
"protocol_key": "modbus_smg",
1373+
"driver_key": "modbus_smg",
1374+
"variant_key": "protocol_5_family_fallback",
1375+
"profile_name": "modbus_smg/protocols/communication_protocol_5.json",
1376+
"register_schema_name": "modbus_smg/protocols/communication_protocol_5.json",
1377+
"tier": "full",
1378+
"read_only": false,
1379+
"support_capture_policy": "smg_anenji_11k"
1380+
},
1381+
{
1382+
"key": "smg_protocol_6_family_fallback",
1383+
"protocol_key": "modbus_smg",
1384+
"driver_key": "modbus_smg",
1385+
"variant_key": "protocol_6_family_fallback",
1386+
"profile_name": "modbus_smg/protocols/communication_protocol_6.json",
1387+
"register_schema_name": "modbus_smg/protocols/communication_protocol_6.json",
1388+
"tier": "full",
1389+
"read_only": false,
1390+
"support_capture_policy": "smg_anenji_11k"
1391+
},
13481392
{
13491393
"key": "pi30_default_full",
13501394
"protocol_key": "pi30",
@@ -2838,6 +2882,110 @@
28382882
]
28392883
},
28402884
"note": "Known SMG layout, unknown model: base reads only; writes require learning."
2885+
},
2886+
{
2887+
"when_layout_codes": [
2888+
3
2889+
],
2890+
"model_name": "SMG Protocol 3 (Unverified Variant)",
2891+
"surface_key": "smg_protocol_3_family_fallback",
2892+
"runtime_probe": {
2893+
"validation": [
2894+
{
2895+
"key": "output_rating_voltage",
2896+
"min": 1
2897+
},
2898+
{
2899+
"key": "output_rating_frequency",
2900+
"min": 1
2901+
},
2902+
{
2903+
"key": "protocol_number",
2904+
"one_of": [
2905+
3
2906+
]
2907+
}
2908+
]
2909+
},
2910+
"note": "Documented Protocol 3, unknown model: protocol-specific telemetry; document-backed untested controls require explicit Full Control."
2911+
},
2912+
{
2913+
"when_layout_codes": [
2914+
4
2915+
],
2916+
"model_name": "SMG Protocol 4 (Unverified Variant)",
2917+
"surface_key": "smg_protocol_4_family_fallback",
2918+
"runtime_probe": {
2919+
"validation": [
2920+
{
2921+
"key": "output_rating_voltage",
2922+
"min": 1
2923+
},
2924+
{
2925+
"key": "output_rating_frequency",
2926+
"min": 1
2927+
},
2928+
{
2929+
"key": "protocol_number",
2930+
"one_of": [
2931+
4
2932+
]
2933+
}
2934+
]
2935+
},
2936+
"note": "Documented Protocol 4, unknown model: protocol-specific telemetry; document-backed untested controls require explicit Full Control."
2937+
},
2938+
{
2939+
"when_layout_codes": [
2940+
5
2941+
],
2942+
"model_name": "SMG Protocol 5 (Unverified Variant)",
2943+
"surface_key": "smg_protocol_5_family_fallback",
2944+
"runtime_probe": {
2945+
"validation": [
2946+
{
2947+
"key": "output_rating_voltage",
2948+
"min": 1
2949+
},
2950+
{
2951+
"key": "output_rating_frequency",
2952+
"min": 1
2953+
},
2954+
{
2955+
"key": "protocol_number",
2956+
"one_of": [
2957+
5
2958+
]
2959+
}
2960+
]
2961+
},
2962+
"note": "Documented Protocol 5, unknown model: protocol-specific telemetry; document-backed untested controls require explicit Full Control."
2963+
},
2964+
{
2965+
"when_layout_codes": [
2966+
6
2967+
],
2968+
"model_name": "SMG Protocol 6 (Unverified Variant)",
2969+
"surface_key": "smg_protocol_6_family_fallback",
2970+
"runtime_probe": {
2971+
"validation": [
2972+
{
2973+
"key": "output_rating_voltage",
2974+
"min": 1
2975+
},
2976+
{
2977+
"key": "output_rating_frequency",
2978+
"min": 1
2979+
},
2980+
{
2981+
"key": "protocol_number",
2982+
"one_of": [
2983+
6
2984+
]
2985+
}
2986+
]
2987+
},
2988+
"note": "Documented Protocol 6, unknown model: protocol-specific telemetry; document-backed untested controls require explicit Full Control."
28412989
}
28422990
]
28432991
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"extends": "./communication_protocol_3_10.json",
3+
"schema_key": "modbus_smg_communication_protocol_5",
4+
"title": "Communication Protocol No. 5 Register Schema",
5+
"spec_set_removals": {
6+
"aux_config": [
7+
"pv_power",
8+
"pv_charging_power",
9+
"pv_charging_current",
10+
"pv2_voltage",
11+
"pv2_current",
12+
"pv2_power"
13+
]
14+
},
15+
"measurement_description_removals": [
16+
"pv_power",
17+
"pv_charging_power",
18+
"pv_charging_current",
19+
"pv2_voltage",
20+
"pv2_current",
21+
"pv2_power"
22+
],
23+
"spec_sets": {
24+
"aux_config": [
25+
{
26+
"key": "output_current",
27+
"register": 347,
28+
"signed": true,
29+
"divisor": 10,
30+
"decimals": 1
31+
},
32+
{
33+
"key": "output_power",
34+
"register": 348,
35+
"signed": true
36+
},
37+
{
38+
"key": "output_va",
39+
"register": 349,
40+
"signed": true
41+
},
42+
{
43+
"key": "load_percent",
44+
"register": 350,
45+
"signed": true
46+
}
47+
]
48+
}
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"extends": "./communication_protocol_3_10.json",
3+
"schema_key": "modbus_smg_communication_protocol_6",
4+
"title": "Communication Protocol No. 6 Register Schema",
5+
"spec_set_removals": {
6+
"aux_config": [
7+
"pv_power",
8+
"pv_charging_power",
9+
"pv_charging_current",
10+
"pv2_voltage",
11+
"pv2_current",
12+
"pv2_power"
13+
]
14+
},
15+
"measurement_description_removals": [
16+
"pv_power",
17+
"pv_charging_power",
18+
"pv_charging_current",
19+
"pv2_voltage",
20+
"pv2_current",
21+
"pv2_power"
22+
],
23+
"spec_sets": {
24+
"aux_config": [
25+
{
26+
"key": "output_current",
27+
"register": 252,
28+
"signed": true,
29+
"divisor": 10,
30+
"decimals": 1
31+
},
32+
{
33+
"key": "output_power",
34+
"register": 254,
35+
"signed": true
36+
},
37+
{
38+
"key": "output_va",
39+
"register": 255,
40+
"signed": true
41+
},
42+
{
43+
"key": "load_percent",
44+
"register": 256,
45+
"signed": true
46+
}
47+
]
48+
}
49+
}

‎custom_components/eybond_local/runtime/driver_detection.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ def _build_driver_match(driver: InverterDriver, inverter: DetectedInverter) -> D
936936
if inverter.details.get("rated_power"):
937937
reasons.append("rated_power_present")
938938
surface, candidates = _catalog_surface_context(inverter)
939-
if surface is not None and surface.read_only and inverter.variant_key == "family_fallback":
939+
if str(inverter.variant_key or "").strip().endswith("family_fallback"):
940940
reasons.append("family_fallback_variant")
941941
elif candidates and all(
942942
candidate.provenance_confidence == "rule-migrated"

‎docs/generated/INVERTER_MODEL_CATALOG.generated.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Generated by `tools/model_catalog.py`. Do not edit by hand.
44

55
- Model catalog version: 2026.08.1
6-
- Runtime inverter catalog version: 2026.08.6
6+
- Runtime inverter catalog version: 2026.08.7
77
- Models: 17 | Variants: 18
88

99
## How To Read This Catalog
@@ -60,6 +60,11 @@ Runtime descriptors with no specific commercial model record. These are generic
6060
| `deye_lv_family` | modbus_catalog | deye_lv_untested | full | no |
6161
| `eybond_g_ascii_family` | eybond_g_ascii | eybond_g_ascii_read_only | partial | yes |
6262
| `growatt_spf_family` | modbus_catalog | growatt_spf_untested | full | no |
63+
| `modbus_smg.family_fallback` | modbus_smg | smg_family_read_only | partial | yes |
64+
| `modbus_smg.protocol_3_family_fallback` | modbus_smg | smg_protocol_3_family_fallback | full | no |
65+
| `modbus_smg.protocol_4_family_fallback` | modbus_smg | smg_protocol_4_family_fallback | full | no |
66+
| `modbus_smg.protocol_5_family_fallback` | modbus_smg | smg_protocol_5_family_fallback | full | no |
67+
| `modbus_smg.protocol_6_family_fallback` | modbus_smg | smg_protocol_6_family_fallback | full | no |
6368
| `pi18_family` | pi18 | pi18_read_only | partial | yes |
6469
| `pi30_family` | pi30 | pi30_default_full | full | no |
6570
| `pi30_max_qflag` | pi30 | pi30_max_full | full | no |

‎docs/maintainer/ADDING_DRIVERS.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ Create or extend a profile JSON under `custom_components/eybond_local/protocol_c
127127

128128
Prefer shared family-level metadata plus model overlays over copy-pasting full profiles. If multiple variants reuse the same logical controls with different register locations, put the common capability shape into `capability_templates` in the family base and materialize the variant-specific entries from the overlay. If a device is clearly in the same protocol family but still lacks verified write semantics, add a separate read-only fallback profile instead of inheriting a writable default surface prematurely.
129129

130+
When a device reports a documented protocol/layout number, prefer one
131+
protocol-specific family surface over adding an exact catalog model only to
132+
expose telemetry. Keep separate surfaces wherever protocol numbers move or
133+
remove fields. A family surface may attach the matching document-backed control
134+
profile only when every capability is untested and therefore requires an
135+
explicit Full Control choice. Add an exact model record for commercial naming,
136+
model-specific register differences, or controls with stronger validation.
137+
130138
The profile should carry:
131139

132140
- groups

‎docs/user/RUNTIME_AND_INVERTER.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ settings, and fields without a valid protocol number remain model-specific.
101101
These document-backed controls are untested until confirmed on an exact model,
102102
so Auto mode does not expose them.
103103

104+
The reported protocol number also selects a version-specific telemetry and
105+
control map for an inverter model that is not yet listed in the catalog. Normal
106+
monitoring starts without pretending that a similar commercial model was
107+
detected. All document-backed controls remain marked untested and are available
108+
only after the user explicitly selects Full Control; Auto mode stays
109+
monitoring-only. Protocol 3/5 and Protocol 4/6 use their documented
110+
output-register locations, and fields documented only for Protocol 3/4 are not
111+
projected on Protocol 5/6. An exact catalog model still takes priority when
112+
available.
113+
104114
Classic SMG-family inverters use a separate documented RS232 V1 register map.
105115
The document version is not treated as an inverter identity: compatible devices
106116
have been observed with layout values 1, 2, and 11. EyeBond Local therefore

0 commit comments

Comments
 (0)