Skip to content

Commit 3ad5874

Browse files
committed
2 parents fa57a5b + 4897350 commit 3ad5874

7 files changed

Lines changed: 115 additions & 24 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.5.7"
3+
VICTRON_MQTT_VERSION = "2026.5.11"

custom_components/victron_mqtt/_vendor/victron_mqtt/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
GeneratorRunningByConditionCode,
3232
GenericAlarmEnum,
3333
GenericOnOff,
34+
GenericOnOffInverted,
3435
InverterMode,
3536
MppOperationMode,
3637
PhoenixInverterMode,
@@ -91,6 +92,7 @@
9192
"GeneratorRunningByConditionCode",
9293
"GenericAlarmEnum",
9394
"GenericOnOff",
95+
"GenericOnOffInverted",
9496
"GpsLocation",
9597
"Hub",
9698
"InvalidInstallationIdError",

custom_components/victron_mqtt/_vendor/victron_mqtt/_unwrappers.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def unwrap_bool(json_str: str) -> bool | None:
1717
except (json.JSONDecodeError, KeyError, ValueError, TypeError):
1818
return None
1919

20+
2021
def unwrap_int(json_str: str) -> int | None:
2122
"""Unwrap an integer value from a JSON string."""
2223
try:
@@ -27,6 +28,7 @@ def unwrap_int(json_str: str) -> int | None:
2728
except (json.JSONDecodeError, KeyError, ValueError, TypeError):
2829
return None
2930

31+
3032
def unwrap_int_default_0(json_str: str) -> int:
3133
"""Unwrap an integer value from a JSON string, defaulting to 0."""
3234
try:
@@ -37,6 +39,7 @@ def unwrap_int_default_0(json_str: str) -> int:
3739
except (json.JSONDecodeError, KeyError, ValueError, TypeError):
3840
return 0
3941

42+
4043
def unwrap_int_seconds_to_hours(json_str: str, precision: int | None) -> float | None:
4144
"""Convert seconds to hours."""
4245
seconds = unwrap_int(json_str)
@@ -45,6 +48,7 @@ def unwrap_int_seconds_to_hours(json_str: str, precision: int | None) -> float |
4548
hours = seconds / 3600
4649
return hours if precision is None else round(hours, precision)
4750

51+
4852
def unwrap_int_seconds_to_minutes(json_str: str, precision: int | None) -> float | None:
4953
"""Convert seconds to minutes."""
5054
seconds = unwrap_int(json_str)
@@ -53,6 +57,7 @@ def unwrap_int_seconds_to_minutes(json_str: str, precision: int | None) -> float
5357
minutes = seconds / 60
5458
return minutes if precision is None else round(minutes, precision)
5559

60+
5661
def unwrap_float(json_str: str, precision: int | None, json_value: str = "value") -> float | None:
5762
"""Unwrap a float value from a JSON string."""
5863
try:
@@ -64,6 +69,7 @@ def unwrap_float(json_str: str, precision: int | None, json_value: str = "value"
6469
except (json.JSONDecodeError, KeyError, ValueError, TypeError):
6570
return None
6671

72+
6773
def unwrap_float_m3_to_liters(json_str: str, precision: int | None) -> float | None:
6874
"""Convert cubic meters to liters."""
6975
value = unwrap_float(json_str, None)
@@ -72,6 +78,7 @@ def unwrap_float_m3_to_liters(json_str: str, precision: int | None) -> float | N
7278
liters = value * 1000
7379
return liters if precision is None else round(liters, precision)
7480

81+
7582
def unwrap_string(json_str: str) -> str | None:
7683
"""Unwrap a string value from a JSON string."""
7784
try:
@@ -92,6 +99,7 @@ def unwrap_enum(json_str: str, enum: type[VictronEnum]) -> VictronEnum | None:
9299
val = data["value"]
93100
return enum.from_code(val) if val is not None else None
94101

102+
95103
def unwrap_bitmask(json_str: str, enum: type[VictronEnum]) -> str | None:
96104
"""Unwrap a bitmask value from a JSON string."""
97105
try:
@@ -101,10 +109,11 @@ def unwrap_bitmask(json_str: str, enum: type[VictronEnum]) -> str | None:
101109
val = data["value"]
102110
if val is None:
103111
return None
104-
vals = [2**idx for idx,bit in enumerate(bin(val)[:1:-1]) if int(bit)] if int(val)>0 else [0]
112+
vals = [2**idx for idx, bit in enumerate(bin(val)[:1:-1]) if int(bit)] if int(val) > 0 else [0]
105113
enums = [enum.from_code(v) for v in vals]
106114
return str.join(BITMASK_SEPARATOR, [e.string for e in enums if e is not None])
107115

116+
108117
def unwrap_epoch(json_str: str) -> datetime | None:
109118
"""Unwrap a timestamp value from a JSON string."""
110119
try:
@@ -116,13 +125,17 @@ def unwrap_epoch(json_str: str) -> datetime | None:
116125
except (json.JSONDecodeError, KeyError, ValueError, TypeError):
117126
return None
118127

128+
119129
def wrap_enum(enum_val: VictronEnum | str, enum_expected: type[VictronEnum]) -> str:
120130
"""Wrap an Enum value into a JSON string with a 'value' key."""
121131
if isinstance(enum_val, VictronEnum):
122132
return json.dumps({"value": enum_val.code})
123133
return json.dumps({"value": enum_expected.from_id_or_string(enum_val).code})
124134

125-
def wrap_bitmask(bitmask_val: VictronEnum | str | Iterable[VictronEnum] | Iterable[str], enum_expected: type[VictronEnum]) -> str:
135+
136+
def wrap_bitmask(
137+
bitmask_val: VictronEnum | str | Iterable[VictronEnum] | Iterable[str], enum_expected: type[VictronEnum]
138+
) -> str:
126139
"""Wrap an bitmask value into a JSON string with a 'value' key."""
127140
if isinstance(bitmask_val, VictronEnum):
128141
bitmask_val = [bitmask_val]
@@ -137,18 +150,22 @@ def wrap_bitmask(bitmask_val: VictronEnum | str | Iterable[VictronEnum] | Iterab
137150
val += int(enum_expected.from_id_or_string(v).code)
138151
return json.dumps({"value": val})
139152

153+
140154
def wrap_int(value: int | None) -> str:
141155
"""Wrap an integer value into a JSON string with a 'value' key."""
142156
return json.dumps({"value": value})
143157

158+
144159
def wrap_int_hours_to_seconds(value: int | None) -> str:
145160
"""Wrap an integer value into a JSON string with a 'value' key."""
146161
return json.dumps({"value": value * 3600 if value is not None else None})
147162

163+
148164
def wrap_int_minutes_to_seconds(value: int | None) -> str:
149165
"""Wrap an integer value into a JSON string with a 'value' key."""
150166
return json.dumps({"value": value * 60 if value is not None else None})
151167

168+
152169
def wrap_int_default_0(value: int | None) -> str:
153170
"""Wrap an integer value into a JSON string with a 'value' key, defaulting to 0 if None."""
154171
return json.dumps({"value": value if value is not None else 0})
@@ -163,11 +180,12 @@ def wrap_string(value: str | None) -> str:
163180
"""Wrap a string value into a JSON string with a 'value' key."""
164181
return json.dumps({"value": value})
165182

183+
166184
def wrap_epoch(value: datetime | None) -> str:
167185
"""Wrap a datetime value into a JSON string with a 'value' key in the format of an epoch timestamp."""
168186
if value is None:
169187
return json.dumps({"value": None})
170-
return json.dumps({"value": datetime.timestamp(value) })
188+
return json.dumps({"value": datetime.timestamp(value)})
171189

172190

173191
VALUE_TYPE_UNWRAPPER = {
@@ -180,7 +198,7 @@ def wrap_epoch(value: datetime | None) -> str:
180198
ValueType.EPOCH: unwrap_epoch,
181199
ValueType.INT_SECONDS_TO_HOURS: unwrap_int_seconds_to_hours,
182200
ValueType.INT_SECONDS_TO_MINUTES: unwrap_int_seconds_to_minutes,
183-
ValueType.FLOAT_M3_TO_LITERS: unwrap_float_m3_to_liters
201+
ValueType.FLOAT_M3_TO_LITERS: unwrap_float_m3_to_liters,
184202
}
185203

186204
VALUE_TYPE_WRAPPER = {
@@ -192,5 +210,5 @@ def wrap_epoch(value: datetime | None) -> str:
192210
ValueType.BITMASK: wrap_bitmask,
193211
ValueType.EPOCH: wrap_epoch,
194212
ValueType.INT_SECONDS_TO_HOURS: wrap_int_hours_to_seconds,
195-
ValueType.INT_SECONDS_TO_MINUTES: wrap_int_minutes_to_seconds
213+
ValueType.INT_SECONDS_TO_MINUTES: wrap_int_minutes_to_seconds,
196214
}

custom_components/victron_mqtt/_vendor/victron_mqtt/_victron_enums.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ class GenericOnOff(VictronEnum):
6060
ON = (1, "on", "On")
6161

6262

63+
class GenericOnOffInverted(VictronEnum):
64+
"""Inverted On/Off Enum (0=On, 1=Off)"""
65+
66+
ON = (0, "on", "On")
67+
OFF = (1, "off", "Off")
68+
69+
6370
class PreferRenewableEnergyEnum(VictronEnum):
6471
"""Prefer Renewable Energy state.
6572

custom_components/victron_mqtt/_vendor/victron_mqtt/_victron_topics.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
GeneratorRunningByConditionCode,
3131
GenericAlarmEnum,
3232
GenericOnOff,
33+
GenericOnOffInverted,
3334
InverterMode,
3435
MppOperationMode,
3536
PhoenixInverterMode,
@@ -1323,6 +1324,14 @@
13231324
metric_type=MetricType.VOLTAGE,
13241325
),
13251326
# Hub4 topics
1327+
TopicDescriptor(
1328+
topic="N/{installation_id}/hub4/{device_id}/Overrides/ForceCharge",
1329+
message_type=MetricKind.SWITCH,
1330+
short_id="hub4_force_charge",
1331+
name="Force charge",
1332+
value_type=ValueType.ENUM,
1333+
enum=GenericOnOff,
1334+
),
13261335
TopicDescriptor(
13271336
topic="N/{installation_id}/hub4/{device_id}/Overrides/Setpoint",
13281337
message_type=MetricKind.NUMBER,
@@ -2062,7 +2071,7 @@
20622071
short_id="system_settings_prevent_ac_feedin",
20632072
name="AC-coupled PV - feed in excess",
20642073
value_type=ValueType.ENUM,
2065-
enum=GenericOnOff,
2074+
enum=GenericOnOffInverted,
20662075
),
20672076
# Dynamic ESS settings topics
20682077
TopicDescriptor(

custom_components/victron_mqtt/translations/en.json

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@
127127
"on": "On"
128128
}
129129
},
130+
"hub4_force_charge": {
131+
"name": "Force charge",
132+
"state": {
133+
"off": "Off",
134+
"on": "On"
135+
}
136+
},
130137
"inverter_alarm_high_temperature": {
131138
"name": "High temperature alarm",
132139
"state": {
@@ -274,14 +281,14 @@
274281
}
275282
},
276283
"system_settings_overvoltage_feedin": {
277-
"name": "ESS feed-in excess solar charger power",
284+
"name": "DC-coupled PV - feed in excess",
278285
"state": {
279286
"off": "Off",
280287
"on": "On"
281288
}
282289
},
283290
"system_settings_prevent_ac_feedin": {
284-
"name": "ESS PV inverter zero feed-in",
291+
"name": "AC-coupled PV - feed in excess",
285292
"state": {
286293
"off": "Off",
287294
"on": "On"
@@ -1355,8 +1362,7 @@
13551362
"name": "Installation power"
13561363
},
13571364
"meteo_irradiance": {
1358-
"name": "Irradiance",
1359-
"unit_of_measurement": "W/m²"
1365+
"name": "Irradiance"
13601366
},
13611367
"meteo_time_since_last_sun": {
13621368
"name": "Time since last sun"
@@ -1892,7 +1898,7 @@
18921898
}
18931899
},
18941900
"system_ess_batterylife_state_sensor": {
1895-
"name": "ESS BatteryLife state (detailed)",
1901+
"name": "ESS BatteryLife state",
18961902
"state": {
18971903
"keep_batteries_charged": "'Keep batteries charged' mode enabled",
18981904
"recharge": "Recharge, SoC dropped 5% or more below minimum SoC",
@@ -2405,6 +2411,13 @@
24052411
"on": "On"
24062412
}
24072413
},
2414+
"hub4_force_charge": {
2415+
"name": "Force charge",
2416+
"state": {
2417+
"off": "Off",
2418+
"on": "On"
2419+
}
2420+
},
24082421
"multi_disable_charge": {
24092422
"name": "ESS disable charge",
24102423
"state": {
@@ -2468,14 +2481,14 @@
24682481
}
24692482
},
24702483
"system_settings_overvoltage_feedin": {
2471-
"name": "ESS feed-in excess solar charger power",
2484+
"name": "DC-coupled PV - feed in excess",
24722485
"state": {
24732486
"off": "Off",
24742487
"on": "On"
24752488
}
24762489
},
24772490
"system_settings_prevent_ac_feedin": {
2478-
"name": "ESS PV inverter zero feed-in",
2491+
"name": "AC-coupled PV - feed in excess",
24792492
"state": {
24802493
"off": "Off",
24812494
"on": "On"

0 commit comments

Comments
 (0)