Skip to content

Commit 6d68cc8

Browse files
Update victron_mqtt to 2026.6.8 (#446)
## Changes - feat: add display\_id to decouple entity\_id default from unique\_id @pos-ei-don (#106) - Add writable ESS / Hub4 switches @pos-ei-don (#102) - Serialize integer settings as int, not float @pos-ei-don (#101) - feat: Add VRM portal access level settings support - Create a service for W/{installation_id}/multi/{device_id}/Ess/AcPowerSetpoint: ## Contributors @pos-ei-don, @tomer-w Co-authored-by: tomer-w <57483589+tomer-w@users.noreply.github.com>
1 parent d7a194c commit 6d68cc8

10 files changed

Lines changed: 426 additions & 25 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Vendored third-party packages."""
22

3-
VICTRON_MQTT_VERSION = "2026.6.7"
3+
VICTRON_MQTT_VERSION = "2026.6.8"

custom_components/victron_mqtt/_vendor/victron_mqtt/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
TemperatureStatus,
4545
TemperatureType,
4646
VictronDeviceEnum,
47+
VrmPortalMode,
4748
)
4849
from .constants import MetricKind, MetricNature, MetricType, OperationMode, RangeType, VictronEnum
4950
from .data_classes import GpsLocation
@@ -120,5 +121,6 @@
120121
"TopicNotFoundError",
121122
"VictronDeviceEnum",
122123
"VictronEnum",
124+
"VrmPortalMode",
123125
"WritableMetric",
124126
]

custom_components/victron_mqtt/_vendor/victron_mqtt/_unwrappers.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,24 +151,24 @@ def wrap_bitmask(
151151
return json.dumps({"value": val})
152152

153153

154-
def wrap_int(value: int | None) -> str:
154+
def wrap_int(value: int | float | None) -> str:
155155
"""Wrap an integer value into a JSON string with a 'value' key."""
156-
return json.dumps({"value": value})
156+
return json.dumps({"value": int(value) if value is not None else None})
157157

158158

159-
def wrap_int_hours_to_seconds(value: int | None) -> str:
159+
def wrap_int_hours_to_seconds(value: int | float | None) -> str:
160160
"""Wrap an integer value into a JSON string with a 'value' key."""
161-
return json.dumps({"value": value * 3600 if value is not None else None})
161+
return json.dumps({"value": int(value) * 3600 if value is not None else None})
162162

163163

164-
def wrap_int_minutes_to_seconds(value: int | None) -> str:
164+
def wrap_int_minutes_to_seconds(value: int | float | None) -> str:
165165
"""Wrap an integer value into a JSON string with a 'value' key."""
166-
return json.dumps({"value": value * 60 if value is not None else None})
166+
return json.dumps({"value": int(value) * 60 if value is not None else None})
167167

168168

169-
def wrap_int_default_0(value: int | None) -> str:
169+
def wrap_int_default_0(value: int | float | None) -> str:
170170
"""Wrap an integer value into a JSON string with a 'value' key, defaulting to 0 if None."""
171-
return json.dumps({"value": value if value is not None else 0})
171+
return json.dumps({"value": int(value) if value is not None else 0})
172172

173173

174174
def wrap_float(value: float | None) -> str:

custom_components/victron_mqtt/_vendor/victron_mqtt/_victron_enums.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class DeviceType(VictronDeviceEnum):
3333
) # Orion XS 1400 in alternator to battery charging mode.
3434
SWITCH = ("switch", "switch", "Switch")
3535
GPS = ("gps", "gps", "GPS")
36-
SYSTEM_SETUP = ("SystemSetup", "system_setup", "System setup", "system") # Should be mapped to SYSTEM
36+
SYSTEM_SETUP = ("SystemSetup", "system_setup", "<Not used>", "system") # Should be mapped to SYSTEM
3737
SERVICES = ("Services", "services", "<Not used>", "system") # Should be mapped to SYSTEM
3838
TRANSFER_SWITCH = ("TransferSwitch", "transfer_switch", "Transfer switch")
3939
DIGITAL_INPUT = ("digitalinput", "digital_input", "Digital input")
@@ -46,12 +46,13 @@ class DeviceType(VictronDeviceEnum):
4646
"system",
4747
) # For whatever reason some system topics are under platform
4848
HEATPUMP = ("heatpump", "heatpump", "Heat pump")
49+
NETWORK = ("Network", "network", "<Not used>", "system") # Network settings are under system
4950
METEO = ("meteo", "meteo", "Irradiance sensor")
50-
DYNAMIC_ESS = ("DynamicEss", "dynamic_ess", "Dynamic ESS", "system") # Dynamic ESS settings are under system
51+
DYNAMIC_ESS = ("DynamicEss", "dynamic_ess", "<Not used>", "system") # Dynamic ESS settings are under system
5152
ACLOAD = ("acload", "acload", "AC load")
5253
CHARGER = ("charger", "charger", "Charger")
5354
HUB4 = ("hub4", "hub4", "Hub4")
54-
ACSYSTEM = ("acsystem", "acsystem", "AC system", "system") # Should be mapped to SYSTEM
55+
ACSYSTEM = ("acsystem", "acsystem", "<Not used>", "system") # Should be mapped to SYSTEM
5556
DCDC = ("dcdc", "dcdc", "DC/DC charger") # Orion XS 1400 in battery to battery charging mode.
5657

5758

@@ -83,6 +84,14 @@ class GenericOnOffInverted(VictronEnum):
8384
OFF = (1, "off", "Off")
8485

8586

87+
class VrmPortalMode(VictronEnum):
88+
"""VRM Portal access level enum."""
89+
90+
OFF = (0, "off", "Off")
91+
READ_ONLY = (1, "read_only", "Read-only")
92+
FULL = (2, "full", "Full")
93+
94+
8695
class PreferRenewableEnergyEnum(VictronEnum):
8796
"""Prefer Renewable Energy state.
8897

custom_components/victron_mqtt/_vendor/victron_mqtt/_victron_topics.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Maps all the MQTT topics to either attributes or metrics.
2+
Defines the TopicDescriptor mappings for Victron MQTT topics and derived formula topics.
33
"""
44

55
from ._victron_enums import (
@@ -42,6 +42,7 @@
4242
SwitchableOutputType,
4343
TemperatureStatus,
4444
TemperatureType,
45+
VrmPortalMode,
4546
)
4647
from .constants import MetricKind, MetricNature, MetricType, RangeType, ValueType
4748
from .data_classes import TopicDependency, TopicDescriptor
@@ -2001,12 +2002,10 @@
20012002
),
20022003
TopicDescriptor(
20032004
topic="N/{installation_id}/multi/{device_id}/Ess/AcPowerSetpoint",
2004-
message_type=MetricKind.NUMBER,
2005+
message_type=MetricKind.SENSOR,
20052006
short_id="multi_ess_ac_power_setpoint",
20062007
name="ESS AC power setpoint",
20072008
metric_type=MetricType.POWER,
2008-
min=-12500,
2009-
max=12500,
20102009
),
20112010
TopicDescriptor(
20122011
topic="N/{installation_id}/multi/{device_id}/Ess/DisableCharge",
@@ -2253,6 +2252,14 @@
22532252
max=10000, # Dynamic range, depends on device model
22542253
step=10,
22552254
),
2255+
TopicDescriptor(
2256+
topic="N/{installation_id}/settings/{device_id}/Settings/CGwacs/AlwaysPeakShave",
2257+
message_type=MetricKind.SWITCH,
2258+
short_id="system_ess_always_peak_shave",
2259+
name="ESS always peak shave",
2260+
value_type=ValueType.ENUM,
2261+
enum=GenericOnOff,
2262+
),
22562263
TopicDescriptor(
22572264
topic="N/{installation_id}/settings/{device_id}/Settings/CGwacs/BatteryLife/MinimumSocLimit",
22582265
message_type=MetricKind.NUMBER,
@@ -2634,6 +2641,15 @@
26342641
max=1800,
26352642
depends_on=["generator_{gen_id}_generator_autorun"],
26362643
),
2644+
# Network settings
2645+
TopicDescriptor(
2646+
topic="N/{installation_id}/settings/{device_id}/Settings/Network/VrmPortal",
2647+
message_type=MetricKind.SELECT,
2648+
short_id="system_vrm_portal_mode",
2649+
name="VRM portal access level",
2650+
value_type=ValueType.ENUM,
2651+
enum=VrmPortalMode,
2652+
),
26372653
# Relay Custom Name topics
26382654
TopicDescriptor(
26392655
topic="N/{installation_id}/settings/{device_id}/Settings/Relay/{relay}/CustomName",
@@ -3878,6 +3894,30 @@
38783894
name="Energy from out to inverter",
38793895
metric_type=MetricType.ENERGY,
38803896
),
3897+
TopicDescriptor(
3898+
topic="N/{installation_id}/vebus/{device_id}/Hub4/DoNotFeedInOvervoltage",
3899+
message_type=MetricKind.SWITCH,
3900+
short_id="vebus_hub4_do_not_feed_in_overvoltage",
3901+
name="Hub4 do not feed in on overvoltage",
3902+
value_type=ValueType.ENUM,
3903+
enum=GenericOnOff,
3904+
),
3905+
TopicDescriptor(
3906+
topic="N/{installation_id}/vebus/{device_id}/Hub4/FixSolarOffsetTo100mV",
3907+
message_type=MetricKind.SWITCH,
3908+
short_id="vebus_hub4_fix_solar_offset_100mv",
3909+
name="Hub4 fix solar offset to 100mV",
3910+
value_type=ValueType.ENUM,
3911+
enum=GenericOnOff,
3912+
),
3913+
TopicDescriptor(
3914+
topic="N/{installation_id}/vebus/{device_id}/Hub4/TargetPowerIsMaxFeedIn",
3915+
message_type=MetricKind.SWITCH,
3916+
short_id="vebus_hub4_target_power_is_max_feed_in",
3917+
name="Hub4 target power is max feed-in",
3918+
value_type=ValueType.ENUM,
3919+
enum=GenericOnOff,
3920+
),
38813921
TopicDescriptor(
38823922
topic="N/{installation_id}/vebus/{device_id}/Hub4/{phase}/AcPowerSetpoint",
38833923
message_type=MetricKind.NUMBER,
@@ -3896,6 +3936,14 @@
38963936
enum=InverterMode,
38973937
main_topic=True,
38983938
),
3939+
TopicDescriptor(
3940+
topic="N/{installation_id}/vebus/{device_id}/PvInverter/Disable",
3941+
message_type=MetricKind.SWITCH,
3942+
short_id="vebus_pvinverter_disable",
3943+
name="Vebus PV inverter disable",
3944+
value_type=ValueType.ENUM,
3945+
enum=GenericOnOff,
3946+
),
38993947
TopicDescriptor(
39003948
topic="N/{installation_id}/vebus/{device_id}/Settings/Alarm/System/GridLost",
39013949
message_type=MetricKind.SWITCH,
@@ -3933,4 +3981,13 @@
39333981
unit_of_measurement="resets",
39343982
value_type=ValueType.INT,
39353983
),
3984+
TopicDescriptor(
3985+
topic="W/{installation_id}/multi/{device_id}/Ess/AcPowerSetpoint",
3986+
message_type=MetricKind.SERVICE,
3987+
short_id="multi_service_ess_ac_power_setpoint",
3988+
name="Set ESS AC power setpoint",
3989+
metric_type=MetricType.POWER,
3990+
min=-12500,
3991+
max=12500,
3992+
),
39363993
]

custom_components/victron_mqtt/_vendor/victron_mqtt/data_classes.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,27 @@ def unique_id(self) -> str:
456456
assert self._unique_id is not None, f"unique_id is None for topic: {self.full_topic}"
457457
return self._unique_id
458458

459+
@property
460+
def display_id(self) -> str:
461+
"""Get a display identifier with the device-type prefix de-duplicated.
462+
463+
The ``unique_id`` has a known formatting quirk: when ``short_id`` already
464+
starts with the device-type prefix (e.g. ``solarcharger_total_pv_yield``
465+
under device ``solarcharger_3``), the resulting id contains the prefix
466+
twice — ``solarcharger_3_solarcharger_total_pv_yield``. ``display_id``
467+
strips the redundant leading prefix, yielding ``solarcharger_3_total_pv_yield``.
468+
469+
``unique_id`` is intentionally left unchanged for backward compatibility
470+
with existing consumers. ``display_id`` is provided for new consumers
471+
that can adopt the cleaner form going forward.
472+
"""
473+
assert self._short_id is not None, f"short_id is None for topic: {self.full_topic}"
474+
device_prefix = f"{self.device_type.code}_"
475+
short = self._short_id
476+
if short.startswith(device_prefix):
477+
short = short[len(device_prefix):]
478+
return ParsedTopic.make_unique_id(self.get_device_unique_id(), short)
479+
459480
@property
460481
def key_values(self) -> dict[str, str]:
461482
"""Get the key values of the ParsedTopic."""

custom_components/victron_mqtt/_vendor/victron_mqtt/device.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ def _create_metric_from_placeholder(
258258
name=name,
259259
descriptor=new_topic_desc,
260260
unique_id=metric_placeholder.parsed_topic.unique_id,
261+
display_id=metric_placeholder.parsed_topic.display_id,
261262
short_id=metric_placeholder.parsed_topic.short_id,
262263
key_values=metric_placeholder.parsed_topic.key_values,
263264
topic=metric_placeholder.parsed_topic.full_topic,
@@ -269,6 +270,7 @@ def _create_metric_from_placeholder(
269270
name=name,
270271
descriptor=new_topic_desc,
271272
unique_id=metric_placeholder.parsed_topic.unique_id,
273+
display_id=metric_placeholder.parsed_topic.display_id,
272274
short_id=metric_placeholder.parsed_topic.short_id,
273275
key_values=metric_placeholder.parsed_topic.key_values,
274276
hub=hub,

custom_components/victron_mqtt/_vendor/victron_mqtt/metric.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def __init__(
3636
name: str | None = None,
3737
descriptor: TopicDescriptor | None = None,
3838
unique_id: str | None = None,
39+
display_id: str | None = None,
3940
short_id: str | None = None,
4041
key_values: dict[str, str] | None = None,
4142
hub: Hub | None = None,
@@ -58,6 +59,7 @@ def __init__(
5859
self._device: Device = device
5960
self._descriptor: TopicDescriptor = descriptor
6061
self._unique_id: str = unique_id
62+
self._display_id: str = display_id if display_id is not None else unique_id
6163
self._value: Any = None
6264
self._short_id: str = short_id
6365
self._name: str = name
@@ -197,6 +199,17 @@ def unique_id(self) -> str:
197199
"""Return the unique id of the metric."""
198200
return self._unique_id
199201

202+
@property
203+
def display_id(self) -> str:
204+
"""Return the display identifier with the device-type prefix de-duplicated.
205+
206+
Equal to ``unique_id`` for most metrics. Where ``short_id`` starts with
207+
the device-type prefix, the redundant copy is stripped, giving a cleaner
208+
identifier. See :attr:`ParsedTopic.display_id` for details.
209+
The ``unique_id`` is never modified.
210+
"""
211+
return self._display_id
212+
200213
@property
201214
def key_values(self) -> dict[str, str]:
202215
"""Return the key_values dictionary as read-only."""

0 commit comments

Comments
 (0)