Skip to content

Commit 9b0f4e0

Browse files
authored
draft 3 phase processing of metrics (#90)
1 parent 7a3d95e commit 9b0f4e0

File tree

3 files changed

+104
-19
lines changed

3 files changed

+104
-19
lines changed

custom_components/ocpp/api.py

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from homeassistant.config_entries import ConfigEntry
99
from homeassistant.const import TIME_MINUTES
1010
from homeassistant.core import HomeAssistant
11-
from homeassistant.helpers import device_registry
11+
from homeassistant.helpers import device_registry, entity_component, entity_registry
1212
import voluptuous as vol
1313
import websockets
1414

@@ -31,6 +31,7 @@
3131
DataTransferStatus,
3232
Measurand,
3333
MessageTrigger,
34+
Phase,
3435
RegistrationStatus,
3536
RemoteStartStopStatus,
3637
ResetStatus,
@@ -176,6 +177,12 @@ def get_unit(self, cp_id: str, measurand: str):
176177
return self.charge_points[cp_id].get_unit(measurand)
177178
return None
178179

180+
def get_extra_attr(self, cp_id: str, measurand: str):
181+
"""Return last known extra attributes for given measurand."""
182+
if cp_id in self.charge_points:
183+
return self.charge_points[cp_id].get_extra_attr(measurand)
184+
return None
185+
179186
async def set_charger_state(
180187
self, cp_id: str, service_name: str, state: bool = True
181188
):
@@ -195,6 +202,19 @@ async def set_charger_state(
195202
resp = False
196203
return resp
197204

205+
async def update(self, cp_id: str):
206+
"""Update sensors values in HA."""
207+
er = entity_registry.async_get(self.hass)
208+
dr = device_registry.async_get(self.hass)
209+
identifiers = {(DOMAIN, cp_id)}
210+
dev = dr.async_get_device(identifiers)
211+
# _LOGGER.info("Device id: %s updating", dev.name)
212+
for ent in entity_registry.async_entries_for_device(er, dev.id):
213+
# _LOGGER.info("Entity id: %s updating", ent.entity_id)
214+
self.hass.async_create_task(
215+
entity_component.async_update_entity(self.hass, ent.entity_id)
216+
)
217+
198218
def device_info(self):
199219
"""Return device information."""
200220
return {
@@ -226,6 +246,7 @@ def __init__(
226246
self._requires_reboot = False
227247
self._metrics = {}
228248
self._units = {}
249+
self._extra_attr = {}
229250
self._features_supported = {}
230251
self.preparing = asyncio.Event()
231252
self._transactionId = 0
@@ -635,7 +656,7 @@ async def async_update_device_info(self, boot_info: dict):
635656

636657
_LOGGER.debug("Updating device info %s: %s", self.central.cpid, boot_info)
637658

638-
dr = await device_registry.async_get_registry(self.hass)
659+
dr = device_registry.async_get(self.hass)
639660

640661
serial = boot_info.get(om.charge_point_serial_number.name, None)
641662

@@ -652,31 +673,88 @@ async def async_update_device_info(self, boot_info: dict):
652673
sw_version=boot_info.get(om.firmware_version.name, None),
653674
)
654675

676+
def process_phases(self, data):
677+
"""Process phase data from meter values payload."""
678+
extra_attr = {}
679+
for sv in data:
680+
# ordered Dict for each phase eg {"metric":{"unit":"V","L1":"230"...}}
681+
if sv.get(om.phase.value) is not None:
682+
metric = sv[om.measurand.value]
683+
if extra_attr.get(metric) is None:
684+
extra_attr[metric] = {}
685+
(extra_attr[metric])[om.unit.value] = sv.get(om.unit.value)
686+
if sv.get(om.phase.value) in [Phase.l1.value, Phase.l1_n.value]:
687+
(extra_attr[metric])[sv.get(om.phase.value)] = float(
688+
sv[om.value.value]
689+
)
690+
if sv.get(om.phase.value) in [Phase.l2.value, Phase.l2_n.value]:
691+
(extra_attr[metric])[sv.get(om.phase.value)] = float(
692+
sv[om.value.value]
693+
)
694+
if sv.get(om.phase.value) in [Phase.l3.value, Phase.l3_n.value]:
695+
(extra_attr[metric])[sv.get(om.phase.value)] = float(
696+
sv[om.value.value]
697+
)
698+
for metric, value in extra_attr.items():
699+
# _LOGGER.debug("Metric: %s, extra attributes: %s", metric, value)
700+
if metric in Measurand.voltage.value:
701+
sum = (
702+
value[Phase.l1_n.value]
703+
+ value[Phase.l2_n.value]
704+
+ value[Phase.l3_n.value]
705+
)
706+
if sum > 0:
707+
self._metrics[metric] = round(sum / 3, 1)
708+
else:
709+
self._metrics[metric] = round(sum, 1)
710+
if metric in [
711+
Measurand.current_import.value,
712+
Measurand.current_export.value,
713+
]:
714+
sum = (
715+
value[Phase.l1.value]
716+
+ value[Phase.l2.value]
717+
+ value[Phase.l3.value]
718+
)
719+
if sum > 0:
720+
self._metrics[metric] = round(sum / 3, 1)
721+
else:
722+
self._metrics[metric] = round(sum, 1)
723+
self._extra_attr[metric] = value
724+
655725
@on(Action.MeterValues)
656726
def on_meter_values(self, connector_id: int, meter_value: Dict, **kwargs):
657727
"""Request handler for MeterValues Calls."""
658728
for bucket in meter_value:
659-
for sv in bucket[om.sampled_value.name]:
660-
if om.measurand.value in sv:
661-
self._metrics[sv[om.measurand.value]] = sv[om.value.value]
729+
unprocessed = bucket[om.sampled_value.name]
730+
processed_keys = []
731+
for idx, sv in enumerate(bucket[om.sampled_value.name]):
732+
if om.measurand.value in sv and om.phase.value not in sv:
662733
self._metrics[sv[om.measurand.value]] = round(
663-
float(self._metrics[sv[om.measurand.value]]), 1
734+
float(sv[om.value.value]), 1
664735
)
665736
if om.unit.value in sv:
666-
self._units[sv[om.measurand.value]] = sv[om.unit.value]
667-
if self._units[sv[om.measurand.value]] == DEFAULT_POWER_UNIT:
737+
if sv[om.unit.value] == DEFAULT_POWER_UNIT:
668738
self._metrics[sv[om.measurand.value]] = (
669-
float(self._metrics[sv[om.measurand.value]]) / 1000
739+
float(sv[om.value.value]) / 1000
670740
)
671741
self._units[sv[om.measurand.value]] = HA_POWER_UNIT
672-
if self._units[sv[om.measurand.value]] == DEFAULT_ENERGY_UNIT:
742+
if sv[om.unit.value] == DEFAULT_ENERGY_UNIT:
673743
self._metrics[sv[om.measurand.value]] = (
674-
float(self._metrics[sv[om.measurand.value]]) / 1000
744+
float(sv[om.value.value]) / 1000
675745
)
676746
self._units[sv[om.measurand.value]] = HA_ENERGY_UNIT
747+
processed_keys.append(idx)
677748
if len(sv.keys()) == 1: # for backwards compatibility
678749
self._metrics[DEFAULT_MEASURAND] = float(sv[om.value.value]) / 1000
679750
self._units[DEFAULT_MEASURAND] = HA_ENERGY_UNIT
751+
processed_keys.append(idx)
752+
self._extra_attr[om.location.value] = sv.get(om.location.value)
753+
for idx in sorted(processed_keys, reverse=True):
754+
unprocessed.pop(idx)
755+
# _LOGGER.debug("Meter data not yet processed: %s", unprocessed)
756+
if unprocessed is not None:
757+
self.process_phases(unprocessed)
680758
if csess.meter_start.value not in self._metrics:
681759
self._metrics[csess.meter_start.value] = self._metrics[DEFAULT_MEASURAND]
682760
if csess.transaction_id.value not in self._metrics:
@@ -692,6 +770,7 @@ def on_meter_values(self, connector_id: int, meter_value: Dict, **kwargs):
692770
- float(self._metrics[csess.meter_start.value]),
693771
1,
694772
)
773+
self.hass.async_create_task(self.central.update(self.central.cpid))
695774
return call_result.MeterValuesPayload()
696775

697776
@on(Action.BootNotification)
@@ -711,7 +790,7 @@ def on_boot_notification(self, **kwargs):
711790
)
712791

713792
asyncio.create_task(self.async_update_device_info(kwargs))
714-
793+
self.hass.async_create_task(self.central.update(self.central.cpid))
715794
return call_result.BootNotificationPayload(
716795
current_time=datetime.now(tz=timezone.utc).isoformat(),
717796
interval=30,
@@ -733,12 +812,14 @@ def on_status_notification(self, connector_id, error_code, status, **kwargs):
733812
if Measurand.power_reactive_import.value in self._metrics:
734813
self._metrics[Measurand.power_reactive_import.value] = 0
735814
self._metrics[cstat.error_code.value] = error_code
815+
self.hass.async_create_task(self.central.update(self.central.cpid))
736816
return call_result.StatusNotificationPayload()
737817

738818
@on(Action.FirmwareStatusNotification)
739819
def on_firmware_status(self, status, **kwargs):
740820
"""Handle firmware status notification."""
741821
self._metrics[cstat.firmware_status.value] = status
822+
self.hass.async_create_task(self.central.update(self.central.cpid))
742823
return call_result.FirmwareStatusNotificationPayload()
743824

744825
@on(Action.Authorize)
@@ -755,6 +836,7 @@ def on_start_transaction(self, connector_id, id_tag, meter_start, **kwargs):
755836
self._metrics[cstat.stop_reason.value] = ""
756837
self._metrics[csess.transaction_id.value] = self._transactionId
757838
self._metrics[csess.meter_start.value] = int(meter_start) / 1000
839+
self.hass.async_create_task(self.central.update(self.central.cpid))
758840
return call_result.StartTransactionPayload(
759841
id_tag_info={om.status.value: AuthorizationStatus.accepted.value},
760842
transaction_id=self._transactionId,
@@ -776,6 +858,7 @@ def on_stop_transaction(self, meter_stop, timestamp, transaction_id, **kwargs):
776858
self._metrics[Measurand.power_active_import.value] = 0
777859
if Measurand.power_reactive_import.value in self._metrics:
778860
self._metrics[Measurand.power_reactive_import.value] = 0
861+
self.hass.async_create_task(self.central.update(self.central.cpid))
779862
return call_result.StopTransactionPayload(
780863
id_tag_info={om.status.value: AuthorizationStatus.accepted.value}
781864
)
@@ -791,13 +874,17 @@ def on_heartbeat(self, **kwargs):
791874
"""Handle a Heartbeat."""
792875
now = datetime.now(tz=timezone.utc).isoformat()
793876
self._metrics[cstat.heartbeat.value] = now
794-
self._units[cstat.heartbeat.value] = "time"
877+
self.hass.async_create_task(self.central.update(self.central.cpid))
795878
return call_result.HeartbeatPayload(current_time=now)
796879

797880
def get_metric(self, measurand: str):
798881
"""Return last known value for given measurand."""
799882
return self._metrics.get(measurand, None)
800883

884+
def get_extra_attr(self, measurand: str):
885+
"""Return last known extra attributes for given measurand."""
886+
return self._extra_attr.get(measurand, None)
887+
801888
def get_unit(self, measurand: str):
802889
"""Return unit of given measurand."""
803890
return self._units.get(measurand, None)

custom_components/ocpp/sensor.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def __init__(
5050
self.cp_id = cp_id
5151
self.metric = metric
5252
self._state = None
53+
self._extra_attr = {}
5354

5455
@property
5556
def name(self):
@@ -95,11 +96,8 @@ def device_info(self):
9596
@property
9697
def extra_state_attributes(self):
9798
"""Return the state attributes."""
98-
return {
99-
"unique_id": self.unique_id,
100-
"integration": DOMAIN,
101-
}
99+
return self.central_system.get_extra_attr(self.cp_id, self.metric)
102100

103-
def update(self):
101+
async def async_update(self):
104102
"""Get the latest data and update the states."""
105103
pass

custom_components/ocpp/switch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,6 @@ def extra_state_attributes(self):
148148
"integration": DOMAIN,
149149
}
150150

151-
def update(self):
151+
async def async_update(self):
152152
"""Get the latest data and update the states."""
153153
pass

0 commit comments

Comments
 (0)