Skip to content

Commit df32330

Browse files
committed
Distinguish operational controls from configuration entities
Follow-up to the EntityCategory work in #28: introduces a "control" flag on sensor/switch/select/time entity definitions so genuine operational controls (storage-mode bits, TOU V2 enable switches, TOU V2 SOC/current/voltage/time entities, Work Mode select, the active- protocol select) stay in the default category instead of being lumped into EntityCategory.CONFIG alongside one-time settings. Also renames storage-mode switch bits to a shorter "Mode - X" scheme, disables two rarely-used bits (Battery Forcecharge Peakshaving, Battery Current Correction) by default, and unifies TOU V2 entity names to "Timed Charge {field} N" (N=1-6 charge slots, 7-12 discharge slots), mirroring how V1 TOU already uses "Time-Charging" as an umbrella term for both directions. A new _apply_control_metadata() helper in hybrid_sensors.py tags and renames entities by register instead of hand-annotating each dict.
1 parent 0d99b7a commit df32330

14 files changed

Lines changed: 253 additions & 60 deletions

‎custom_components/solisconnect/sensor_data/hybrid_sensors.py‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3443,6 +3443,60 @@
34433443
]
34443444
## Remove 33132 in next update if this works (43110)
34453445

3446+
3447+
def _apply_control_metadata() -> None:
3448+
direct_control_names = {
3449+
"Max Charge SOC",
3450+
"Overdischarge SOC",
3451+
"Max Charge Current",
3452+
"Max Discharge Current",
3453+
"Force Charge SOC",
3454+
"Battery Max Charge Current",
3455+
"Battery Max Discharge Current",
3456+
}
3457+
direct_control_renames = {
3458+
"Backup SOC": "Backup Mode SOC",
3459+
}
3460+
3461+
timed_charge_registers: dict[int, tuple[str, int]] = {}
3462+
for slot in range(1, 7):
3463+
charge_base = 43708 + ((slot - 1) * 7)
3464+
timed_charge_registers[charge_base] = ("SOC", slot)
3465+
timed_charge_registers[charge_base + 1] = ("Current", slot)
3466+
timed_charge_registers[charge_base + 2] = ("Voltage", slot)
3467+
3468+
discharge_slot = slot + 6
3469+
discharge_base = 43750 + ((slot - 1) * 7)
3470+
timed_charge_registers[discharge_base] = ("SOC", discharge_slot)
3471+
timed_charge_registers[discharge_base + 1] = ("Current", discharge_slot)
3472+
timed_charge_registers[discharge_base + 2] = ("Voltage", discharge_slot)
3473+
3474+
for group in hybrid_sensors:
3475+
for entity in group.get("entities", []):
3476+
if entity.get("type") == "reserve":
3477+
continue
3478+
3479+
if entity["name"] in direct_control_renames:
3480+
entity["name"] = direct_control_renames[entity["name"]]
3481+
3482+
if entity["name"] in direct_control_names or entity["name"] in direct_control_renames.values():
3483+
entity["control"] = True
3484+
3485+
registers = entity.get("register", [])
3486+
if len(registers) != 1:
3487+
continue
3488+
3489+
register = int(registers[0])
3490+
timed_control = timed_charge_registers.get(register)
3491+
if timed_control:
3492+
field, slot = timed_control
3493+
entity["name"] = f"Timed Charge {field} {slot}"
3494+
entity["control"] = True
3495+
3496+
3497+
_apply_control_metadata()
3498+
3499+
34463500
hybrid_sensors_derived = [
34473501
{
34483502
"name": "Last Modbus Success",

‎custom_components/solisconnect/sensor_data/select_sensors.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def get_select_sensors(inverter_config):
3434
{
3535
"register": 43110,
3636
"name": "Work Mode",
37+
"control": True,
3738
"entities": [
3839
# Adheres to RS485_MODBUS ESINV-33000ID Hybrid Inverter V3.2 / Appendix 8
3940
{"bit_position": 0, "name": "Self-Use", "conflicts_with": [0, 6, 11]},

‎custom_components/solisconnect/sensor_data/switch_sensors.py‎

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,18 @@ def get_switch_sensors(inverter_config):
3636
"register": 43110,
3737
"entities": [
3838
# Adheres to RS485_MODBUS ESINV-33000ID Hybrid Inverter V3.2 / Appendix 8
39-
{"bit_position": 0, "name": "Self-Use Mode", "conflicts_with": [2, 6, 11]},
40-
{"bit_position": 1, "name": "Time of Use", "requires_any": [0, 6]},
41-
{"bit_position": 2, "name": "Off-Grid Mode", "conflicts_with": [0, 1, 6, 11]},
42-
{"bit_position": 3, "name": "Battery Wakeup Switch"},
43-
{"bit_position": 4, "name": "Reserve Battery Mode", "conflicts_with": [11]},
44-
{"bit_position": 5, "name": "Allow Grid to Charge the Battery"},
45-
{"bit_position": 6, "name": "Feed-In Priority Mode", "conflicts_with": [0, 11]},
46-
{"bit_position": 7, "name": "Batt OVC"},
47-
{"bit_position": 8, "name": "Battery Forcecharge Peakshaving"},
48-
{"bit_position": 9, "name": "Battery Current Correction"},
49-
{"bit_position": 10, "name": "Battery Healing Mode"},
50-
{"bit_position": 11, "name": "Peak Shaving Mode", "conflicts_with": [0, 4, 6]},
39+
{"bit_position": 0, "name": "Mode - Self Use", "conflicts_with": [2, 6, 11], "control": True},
40+
{"bit_position": 1, "name": "Mode - Timed Charge", "requires_any": [0, 6], "control": True},
41+
{"bit_position": 2, "name": "Mode - Off-Grid", "conflicts_with": [0, 1, 6, 11], "control": True},
42+
{"bit_position": 3, "name": "Mode - Battery Wake Up", "control": True},
43+
{"bit_position": 4, "name": "Mode - Backup", "conflicts_with": [11], "control": True},
44+
{"bit_position": 5, "name": "Mode - Grid Charge", "control": True},
45+
{"bit_position": 6, "name": "Mode - Feed In Priority", "conflicts_with": [0, 11], "control": True},
46+
{"bit_position": 7, "name": "Mode - Night OVD Retain", "control": True},
47+
{"bit_position": 8, "name": "Battery Forcecharge Peakshaving", "enabled": False},
48+
{"bit_position": 9, "name": "Battery Current Correction", "enabled": False},
49+
{"bit_position": 10, "name": "Mode - Battery Condition", "control": True},
50+
{"bit_position": 11, "name": "Mode - Peak Shaving", "conflicts_with": [0, 4, 6], "control": True},
5151
],
5252
},
5353
{
@@ -146,18 +146,18 @@ def get_switch_sensors(inverter_config):
146146
{
147147
"register": 43707,
148148
"entities": [
149-
{"name": "Grid Time of Use Charging Period 1", "bit_position": 0},
150-
{"name": "Grid Time of Use Charging Period 2", "bit_position": 1},
151-
{"name": "Grid Time of Use Charging Period 3", "bit_position": 2},
152-
{"name": "Grid Time of Use Charging Period 4", "bit_position": 3},
153-
{"name": "Grid Time of Use Charging Period 5", "bit_position": 4},
154-
{"name": "Grid Time of Use Charging Period 6", "bit_position": 5},
155-
{"name": "Grid Time of Use Discharge Period 1", "bit_position": 6},
156-
{"name": "Grid Time of Use Discharge Period 2", "bit_position": 7},
157-
{"name": "Grid Time of Use Discharge Period 3", "bit_position": 8},
158-
{"name": "Grid Time of Use Discharge Period 4", "bit_position": 9},
159-
{"name": "Grid Time of Use Discharge Period 5", "bit_position": 10},
160-
{"name": "Grid Time of Use Discharge Period 6", "bit_position": 11},
149+
{"name": "Timed Charge Enable 1", "bit_position": 0, "control": True},
150+
{"name": "Timed Charge Enable 2", "bit_position": 1, "control": True},
151+
{"name": "Timed Charge Enable 3", "bit_position": 2, "control": True},
152+
{"name": "Timed Charge Enable 4", "bit_position": 3, "control": True},
153+
{"name": "Timed Charge Enable 5", "bit_position": 4, "control": True},
154+
{"name": "Timed Charge Enable 6", "bit_position": 5, "control": True},
155+
{"name": "Timed Charge Enable 7", "bit_position": 6, "control": True},
156+
{"name": "Timed Charge Enable 8", "bit_position": 7, "control": True},
157+
{"name": "Timed Charge Enable 9", "bit_position": 8, "control": True},
158+
{"name": "Timed Charge Enable 10", "bit_position": 9, "control": True},
159+
{"name": "Timed Charge Enable 11", "bit_position": 10, "control": True},
160+
{"name": "Timed Charge Enable 12", "bit_position": 11, "control": True},
161161
],
162162
},
163163
]

‎custom_components/solisconnect/sensor_data/time_sensors.py‎

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,30 @@ def get_time_sensors(inverter_config):
2323
if inverter_config.type == InverterType.HYBRID or InverterFeature.V2 in inverter_config.features:
2424
time_definitions.extend(
2525
[
26-
{"name": "Grid Time of Use Charge Start (Slot 1)", "register": 43711, "enabled": True},
27-
{"name": "Grid Time of Use Charge End (Slot 1)", "register": 43713, "enabled": True},
28-
{"name": "Grid Time of Use Discharge Start (Slot 1)", "register": 43753, "enabled": True},
29-
{"name": "Grid Time of Use Discharge End (Slot 1)", "register": 43755, "enabled": True},
30-
{"name": "Grid Time of Use Charge Start (Slot 2)", "register": 43718, "enabled": True},
31-
{"name": "Grid Time of Use Charge End (Slot 2)", "register": 43720, "enabled": True},
32-
{"name": "Grid Time of Use Discharge Start (Slot 2)", "register": 43760, "enabled": True},
33-
{"name": "Grid Time of Use Discharge End (Slot 2)", "register": 43762, "enabled": True},
34-
{"name": "Grid Time of Use Charge Start (Slot 3)", "register": 43725, "enabled": True},
35-
{"name": "Grid Time of Use Charge End (Slot 3)", "register": 43727, "enabled": True},
36-
{"name": "Grid Time of Use Discharge Start (Slot 3)", "register": 43767, "enabled": True},
37-
{"name": "Grid Time of Use Discharge End (Slot 3)", "register": 43769, "enabled": True},
38-
{"name": "Grid Time of Use Charge Start (Slot 4)", "register": 43732, "enabled": True},
39-
{"name": "Grid Time of Use Charge End (Slot 4)", "register": 43734, "enabled": True},
40-
{"name": "Grid Time of Use Discharge Start (Slot 4)", "register": 43774, "enabled": True},
41-
{"name": "Grid Time of Use Discharge End (Slot 4)", "register": 43776, "enabled": True},
42-
{"name": "Grid Time of Use Charge Start (Slot 5)", "register": 43739, "enabled": True},
43-
{"name": "Grid Time of Use Charge End (Slot 5)", "register": 43741, "enabled": True},
44-
{"name": "Grid Time of Use Discharge Start (Slot 5)", "register": 43781, "enabled": True},
45-
{"name": "Grid Time of Use Discharge End (Slot 5)", "register": 43783, "enabled": True},
46-
{"name": "Grid Time of Use Charge Start (Slot 6)", "register": 43746, "enabled": True},
47-
{"name": "Grid Time of Use Charge End (Slot 6)", "register": 43748, "enabled": True},
48-
{"name": "Grid Time of Use Discharge Start (Slot 6)", "register": 43788, "enabled": True},
49-
{"name": "Grid Time of Use Discharge End (Slot 6)", "register": 43790, "enabled": True},
26+
{"name": "Timed Charge Start 1", "register": 43711, "enabled": True, "control": True},
27+
{"name": "Timed Charge End 1", "register": 43713, "enabled": True, "control": True},
28+
{"name": "Timed Charge Start 7", "register": 43753, "enabled": True, "control": True},
29+
{"name": "Timed Charge End 7", "register": 43755, "enabled": True, "control": True},
30+
{"name": "Timed Charge Start 2", "register": 43718, "enabled": True, "control": True},
31+
{"name": "Timed Charge End 2", "register": 43720, "enabled": True, "control": True},
32+
{"name": "Timed Charge Start 8", "register": 43760, "enabled": True, "control": True},
33+
{"name": "Timed Charge End 8", "register": 43762, "enabled": True, "control": True},
34+
{"name": "Timed Charge Start 3", "register": 43725, "enabled": True, "control": True},
35+
{"name": "Timed Charge End 3", "register": 43727, "enabled": True, "control": True},
36+
{"name": "Timed Charge Start 9", "register": 43767, "enabled": True, "control": True},
37+
{"name": "Timed Charge End 9", "register": 43769, "enabled": True, "control": True},
38+
{"name": "Timed Charge Start 4", "register": 43732, "enabled": True, "control": True},
39+
{"name": "Timed Charge End 4", "register": 43734, "enabled": True, "control": True},
40+
{"name": "Timed Charge Start 10", "register": 43774, "enabled": True, "control": True},
41+
{"name": "Timed Charge End 10", "register": 43776, "enabled": True, "control": True},
42+
{"name": "Timed Charge Start 5", "register": 43739, "enabled": True, "control": True},
43+
{"name": "Timed Charge End 5", "register": 43741, "enabled": True, "control": True},
44+
{"name": "Timed Charge Start 11", "register": 43781, "enabled": True, "control": True},
45+
{"name": "Timed Charge End 11", "register": 43783, "enabled": True, "control": True},
46+
{"name": "Timed Charge Start 6", "register": 43746, "enabled": True, "control": True},
47+
{"name": "Timed Charge End 6", "register": 43748, "enabled": True, "control": True},
48+
{"name": "Timed Charge Start 12", "register": 43788, "enabled": True, "control": True},
49+
{"name": "Timed Charge End 12", "register": 43790, "enabled": True, "control": True},
5050
]
5151
)
5252

‎custom_components/solisconnect/sensors/entity_category.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
}
2020

2121

22-
def entity_category_for_sensor_category(category: Category | None) -> EntityCategory | None:
22+
def entity_category_for_sensor_category(category: Category | None, control: bool = False) -> EntityCategory | None:
23+
if control:
24+
return None
2325
if category in SETTING_CATEGORIES:
2426
return EntityCategory.CONFIG
2527
return None

‎custom_components/solisconnect/sensors/solis_base_sensor.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(
5353
poll_speed=PollSpeed.NORMAL,
5454
data_type: str | None = None,
5555
value_format: str | None = None,
56+
control: bool = False,
5657
):
5758
"""
5859
:param name: Sensor name
@@ -92,6 +93,7 @@ def __init__(
9293
self.category = category
9394
self.identification = identification
9495
self.value_format = value_format
96+
self.control = control
9597

9698
self.dynamic_adjustments()
9799

@@ -234,6 +236,7 @@ def __init__(self, hass, definition, controller, identification=None):
234236
multiplier=entity.get("multiplier", 1),
235237
data_type=entity.get("data_type", None),
236238
value_format=entity.get("value_format", None),
239+
control=entity.get("control", False),
237240
unique_id=unique_id_generator(controller, entity),
238241
poll_speed=definition.get("poll_speed", PollSpeed.NORMAL),
239242
),

‎custom_components/solisconnect/sensors/solis_binary_sensor.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ def __init__(self, hass, modbus_controller, entity_definition):
3434
self._on_value = entity_definition.get("on_value", None)
3535
self._off_value = entity_definition.get("off_value", None)
3636
self._inverted = entity_definition.get("inverted", None)
37+
self._control = entity_definition.get("control", False)
3738
self._attr_unique_id = unique_id_generator_binary(modbus_controller, self._register, self._bit_position, self._on_value)
3839
self._attr_name = entity_definition["name"]
3940
self._attr_available = False
40-
self._attr_entity_category = EntityCategory.CONFIG
41+
self._attr_entity_category = None if self._control else EntityCategory.CONFIG
42+
self._attr_entity_registry_enabled_default = entity_definition.get("enabled", True)
4143

4244
async def async_added_to_hass(self) -> None:
4345
"""Called when entity is added to HA."""

‎custom_components/solisconnect/sensors/solis_number_sensor.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ def __init__(self, hass, sensor: SolisBaseSensor):
4444
self._attr_state_class = sensor.state_class
4545
self._attr_native_unit_of_measurement = sensor.unit_of_measurement
4646
self._attr_available = not sensor.hidden and sensor.enabled
47-
self._attr_entity_category = entity_category_for_sensor_category(getattr(sensor, "category", None))
47+
self._attr_entity_category = entity_category_for_sensor_category(
48+
getattr(sensor, "category", None),
49+
getattr(sensor, "control", False),
50+
)
4851

4952
self._received_values = {}
5053

‎custom_components/solisconnect/sensors/solis_protocol_select.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22

33
from homeassistant.components.select import SelectEntity
4-
from homeassistant.helpers.entity import EntityCategory
54
from homeassistant.helpers.restore_state import RestoreEntity
65

76
from custom_components.solisconnect.const import DOMAIN, PROTOCOL_CLOUD, PROTOCOL_MODBUS
@@ -16,7 +15,6 @@ class SolisProtocolSelect(RestoreEntity, SelectEntity):
1615
"""Chooses which protocol actively polls and takes writes (manual dual mode only)."""
1716

1817
_attr_has_entity_name = True
19-
_attr_entity_category = EntityCategory.CONFIG
2018
_attr_icon = "mdi:swap-horizontal"
2119

2220
def __init__(self, hass, hub):

‎custom_components/solisconnect/sensors/solis_select_entity.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def __init__(self, hass, modbus_controller, entity_definition) -> None:
1717
self._register = entity_definition["register"]
1818
self._attr_name = entity_definition["name"]
1919
self._attr_unique_id = unique_id_generator(modbus_controller, entity_definition["register"], "select")
20-
self._attr_entity_category = EntityCategory.CONFIG
20+
self._attr_entity_category = None if entity_definition.get("control", False) else EntityCategory.CONFIG
2121

2222
self._attr_options = [e["name"] for e in entity_definition["entities"]]
2323
self._attr_options_raw = entity_definition["entities"]

0 commit comments

Comments
 (0)