Skip to content

Commit 9bf50dd

Browse files
jthunJan Thunqvist
andauthored
fixes for initial release of multiple connector support
Co-authored-by: Jan Thunqvist <[email protected]>
1 parent 00e5fd1 commit 9bf50dd

File tree

6 files changed

+908
-132
lines changed

6 files changed

+908
-132
lines changed

.vscode/settings.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,12 @@
1010
],
1111
"files.associations": {
1212
"*.yaml": "home-assistant"
13-
}
13+
},
14+
"python.testing.pytestArgs": [
15+
"tests",
16+
"--cov=custom_components",
17+
"--cov-report=term-missing",
18+
"--cov-report=xml:coverage.xml",
19+
"--timeout=30"
20+
],
1421
}

custom_components/ocpp/api.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,10 @@ def get_unit(self, id: str, measurand: str, connector_id: int | None = None):
367367

368368
def _try_unit(key):
369369
with contextlib.suppress(Exception):
370-
return m[key].unit
370+
val = m[key].unit
371+
if isinstance(val, str) and val.strip() == "":
372+
return None
373+
return val
371374
return None
372375

373376
if connector_id is not None:
@@ -380,6 +383,8 @@ def _try_unit(key):
380383

381384
with contextlib.suppress(Exception):
382385
val = m[measurand].unit
386+
if isinstance(val, str) and val.strip() == "":
387+
val = None
383388
if val is not None:
384389
return val
385390

@@ -404,7 +409,10 @@ def get_ha_unit(self, id: str, measurand: str, connector_id: int | None = None):
404409

405410
def _try_ha_unit(key):
406411
with contextlib.suppress(Exception):
407-
return m[key].ha_unit
412+
val = m[key].ha_unit
413+
if isinstance(val, str) and val.strip() == "":
414+
return None
415+
return val
408416
return None
409417

410418
if connector_id is not None:
@@ -417,6 +425,8 @@ def _try_ha_unit(key):
417425

418426
with contextlib.suppress(Exception):
419427
val = m[measurand].ha_unit
428+
if isinstance(val, str) and val.strip() == "":
429+
val = None
420430
if val is not None:
421431
return val
422432

@@ -441,7 +451,10 @@ def get_extra_attr(self, id: str, measurand: str, connector_id: int | None = Non
441451

442452
def _try_extra(key):
443453
with contextlib.suppress(Exception):
444-
return m[key].extra_attr
454+
val = m[key].extra_attr
455+
if isinstance(val, dict) and not val:
456+
return None
457+
return val
445458
return None
446459

447460
if connector_id is not None:
@@ -454,6 +467,8 @@ def _try_extra(key):
454467

455468
with contextlib.suppress(Exception):
456469
val = m[measurand].extra_attr
470+
if isinstance(val, dict) and not val:
471+
val = None
457472
if val is not None:
458473
return val
459474

custom_components/ocpp/chargepoint.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,7 @@ async def post_connect(self):
327327
try:
328328
self.status = STATE_OK
329329
await self.fetch_supported_features()
330-
num_connectors: int = await self.get_number_of_connectors()
331-
self.num_connectors = num_connectors
330+
self.num_connectors = await self.get_number_of_connectors()
332331
for conn in range(1, self.num_connectors + 1):
333332
self._init_connector_slots(conn)
334333
self._metrics[(0, cdet.connectors.value)].value = self.num_connectors
@@ -355,13 +354,20 @@ async def post_connect(self):
355354

356355
# nice to have, but not needed for integration to function
357356
# and can cause issues with some chargers
358-
await self.set_availability()
357+
try:
358+
await self.set_availability()
359+
except asyncio.CancelledError:
360+
raise
361+
except Exception as ex:
362+
_LOGGER.debug("post_connect: set_availability ignored error: %s", ex)
363+
359364
if prof.REM in self._attr_supported_features:
360365
if self.received_boot_notification is False:
361366
await self.trigger_boot_notification()
362367
await self.trigger_status_notification()
363-
except NotImplementedError as e:
364-
_LOGGER.error("Configuration of the charger failed: %s", e)
368+
369+
except Exception as e:
370+
_LOGGER.debug("post_connect aborted non-fatally: %s", e)
365371

366372
async def trigger_boot_notification(self):
367373
"""Trigger a boot notification."""
@@ -640,8 +646,17 @@ def get_authorization_status(self, id_tag):
640646
)
641647
return auth_status
642648

643-
def process_phases(self, data: list[MeasurandValue], connector_id: int = 0):
649+
def process_phases(self, data: list[MeasurandValue], connector_id: int | None = 0):
644650
"""Process phase data from meter values."""
651+
# For single-connector chargers, use connector 1.
652+
n_connectors = getattr(self, CONF_NUM_CONNECTORS, DEFAULT_NUM_CONNECTORS) or 1
653+
if connector_id in (None, 0):
654+
target_cid = 1 if n_connectors == 1 else 0
655+
else:
656+
try:
657+
target_cid = int(connector_id)
658+
except Exception:
659+
target_cid = 1 if n_connectors == 1 else 0
645660

646661
def average_of_nonzero(values):
647662
nonzero_values: list = [v for v in values if v != 0.0]
@@ -662,14 +677,12 @@ def average_of_nonzero(values):
662677
measurand_data[measurand] = {}
663678
measurand_data[measurand][om.unit.value] = unit
664679
measurand_data[measurand][phase] = value
665-
self._metrics[(connector_id, measurand)].unit = unit
666-
self._metrics[(connector_id, measurand)].extra_attr[om.unit.value] = (
667-
unit
680+
self._metrics[(target_cid, measurand)].unit = unit
681+
self._metrics[(target_cid, measurand)].extra_attr[om.unit.value] = unit
682+
self._metrics[(target_cid, measurand)].extra_attr[phase] = value
683+
self._metrics[(target_cid, measurand)].extra_attr[om.context.value] = (
684+
context
668685
)
669-
self._metrics[(connector_id, measurand)].extra_attr[phase] = value
670-
self._metrics[(connector_id, measurand)].extra_attr[
671-
om.context.value
672-
] = context
673686

674687
line_phases = [Phase.l1.value, Phase.l2.value, Phase.l3.value, Phase.n.value]
675688
line_to_neutral_phases = [Phase.l1_n.value, Phase.l2_n.value, Phase.l3_n.value]
@@ -707,15 +720,16 @@ def average_of_nonzero(values):
707720

708721
if metric_value is not None:
709722
metric_unit = phase_info.get(om.unit.value)
723+
m = self._metrics[(target_cid, metric)]
710724
if metric_unit == DEFAULT_POWER_UNIT:
711-
self._metrics[(connector_id, metric)].value = metric_value / 1000
712-
self._metrics[(connector_id, metric)].unit = HA_POWER_UNIT
725+
m.value = metric_value / 1000
726+
m.unit = HA_POWER_UNIT
713727
elif metric_unit == DEFAULT_ENERGY_UNIT:
714-
self._metrics[(connector_id, metric)].value = metric_value / 1000
715-
self._metrics[(connector_id, metric)].unit = HA_ENERGY_UNIT
728+
m.value = metric_value / 1000
729+
m.unit = HA_ENERGY_UNIT
716730
else:
717-
self._metrics[(connector_id, metric)].value = metric_value
718-
self._metrics[(connector_id, metric)].unit = metric_unit
731+
m.value = metric_value
732+
m.unit = metric_unit
719733

720734
@staticmethod
721735
def get_energy_kwh(measurand_value: MeasurandValue) -> float:

custom_components/ocpp/number.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass
6+
import logging
67
from typing import Final
78

89
from homeassistant.components.number import (
@@ -31,6 +32,9 @@
3132
)
3233
from .enums import Profiles
3334

35+
_LOGGER: logging.Logger = logging.getLogger(__package__)
36+
logging.getLogger(DOMAIN).setLevel(logging.INFO)
37+
3438

3539
@dataclass
3640
class OcppNumberDescription(NumberEntityDescription):
@@ -213,13 +217,25 @@ def available(self) -> bool:
213217
)
214218

215219
async def async_set_native_value(self, value):
216-
"""Set new value for max current (station-wide when _op_connector_id==0, otherwise per-connector)."""
217-
num_value = float(value)
218-
resp = await self.central_system.set_max_charge_rate_amps(
219-
self.cpid,
220-
num_value,
221-
connector_id=self._op_connector_id,
222-
)
223-
if resp is True:
224-
self._attr_native_value = num_value
225-
self.async_write_ha_state()
220+
"""Set new value for max current (station-wide when _op_connector_id==0, otherwise per-connector).
221+
222+
- Optimistic UI: move the slider immediately; attempt backend; never raise.
223+
"""
224+
self._attr_native_value = float(value)
225+
self.async_write_ha_state()
226+
227+
try:
228+
ok = await self.central_system.set_max_charge_rate_amps(
229+
self.cpid, self._attr_native_value, connector_id=self._op_connector_id
230+
)
231+
if not ok:
232+
_LOGGER.warning(
233+
"Set current limit rejected by CP (kept optimistic UI at %.1f A).",
234+
value,
235+
)
236+
except Exception as ex:
237+
_LOGGER.warning(
238+
"Set current limit failed: %s (kept optimistic UI at %.1f A).",
239+
ex,
240+
value,
241+
)

0 commit comments

Comments
 (0)