1
1
"""Representation of a OCCP Entities."""
2
2
import asyncio
3
+ from collections import defaultdict
3
4
from datetime import datetime , timedelta , timezone
4
5
import logging
5
6
from math import sqrt
@@ -167,27 +168,28 @@ async def on_connect(self, websocket, path: str):
167
168
cp = self .charge_points [self .cpid ]
168
169
await self .charge_points [self .cpid ].reconnect (websocket )
169
170
except Exception as e :
170
- _LOGGER .info (f"Exception occurred:\n { e } " )
171
+ _LOGGER .error (f"Exception occurred:\n { e } " , exc_info = True )
172
+
171
173
finally :
172
174
self .charge_points [self .cpid ].status = STATE_UNAVAILABLE
173
175
_LOGGER .info (f"Charger { cp_id } disconnected from { self .host } :{ self .port } ." )
174
176
175
177
def get_metric (self , cp_id : str , measurand : str ):
176
178
"""Return last known value for given measurand."""
177
179
if cp_id in self .charge_points :
178
- return self .charge_points [cp_id ].get_metric ( measurand )
180
+ return self .charge_points [cp_id ]._metrics [ measurand ]. value
179
181
return None
180
182
181
183
def get_unit (self , cp_id : str , measurand : str ):
182
184
"""Return unit of given measurand."""
183
185
if cp_id in self .charge_points :
184
- return self .charge_points [cp_id ].get_unit ( measurand )
186
+ return self .charge_points [cp_id ]._metrics [ measurand ]. unit
185
187
return None
186
188
187
189
def get_extra_attr (self , cp_id : str , measurand : str ):
188
190
"""Return last known extra attributes for given measurand."""
189
191
if cp_id in self .charge_points :
190
- return self .charge_points [cp_id ].get_extra_attr ( measurand )
192
+ return self .charge_points [cp_id ]._metrics [ measurand ]. extra_attr
191
193
return None
192
194
193
195
def get_available (self , cp_id : str ):
@@ -257,16 +259,14 @@ def __init__(
257
259
# Indicates if the charger requires a reboot to apply new
258
260
# configuration.
259
261
self ._requires_reboot = False
260
- self ._metrics = {}
261
- self ._units = {}
262
- self ._extra_attr = {}
263
262
self ._features_supported = {}
264
263
self .preparing = asyncio .Event ()
265
264
self ._transactionId = 0
266
- self ._metrics [cdet .identifier .value ] = id
267
- self ._units [csess .session_time .value ] = TIME_MINUTES
268
- self ._units [csess .session_energy .value ] = UnitOfMeasure .kwh .value
269
- self ._units [csess .meter_start .value ] = UnitOfMeasure .kwh .value
265
+ self ._metrics = defaultdict (lambda : Metric (None , None ))
266
+ self ._metrics [cdet .identifier .value ].value = id
267
+ self ._metrics [csess .session_time .value ].unit = TIME_MINUTES
268
+ self ._metrics [csess .session_energy .value ].unit = UnitOfMeasure .kwh .value
269
+ self ._metrics [csess .meter_start .value ].unit = UnitOfMeasure .kwh .value
270
270
271
271
async def post_connect (self ):
272
272
"""Logic to be executed right after a charger connects."""
@@ -349,7 +349,7 @@ async def handle_get_diagnostics(call):
349
349
# "StopTxnSampledData", ",".join(self.entry.data[CONF_MONITORED_VARIABLES])
350
350
# )
351
351
resp = await self .get_configuration (ckey .number_of_connectors .value )
352
- self ._metrics [cdet .connectors .value ] = resp
352
+ self ._metrics [cdet .connectors .value ]. value = resp
353
353
# await self.start_transaction()
354
354
355
355
# Register custom services with home assistant
@@ -397,7 +397,7 @@ async def get_supported_features(self):
397
397
resp = await self .call (req )
398
398
for key_value in resp .configuration_key :
399
399
self ._features_supported = key_value [om .value .value ]
400
- self ._metrics [cdet .features .value ] = self ._features_supported
400
+ self ._metrics [cdet .features .value ]. value = self ._features_supported
401
401
_LOGGER .debug ("Supported feature profiles: %s" , self ._features_supported )
402
402
403
403
async def trigger_boot_notification (self ):
@@ -532,7 +532,7 @@ async def start_transaction(self, limit_amps: int = 32, limit_watts: int = 22000
532
532
stack_level = int (resp )
533
533
req = call .RemoteStartTransactionPayload (
534
534
connector_id = 1 ,
535
- id_tag = self ._metrics [cdet .identifier .value ],
535
+ id_tag = self ._metrics [cdet .identifier .value ]. value ,
536
536
charging_profile = {
537
537
om .charging_profile_id .value : 1 ,
538
538
om .stack_level .value : stack_level ,
@@ -748,8 +748,9 @@ def process_phases(self, data):
748
748
measurand_data [measurand ] = {}
749
749
measurand_data [measurand ][om .unit .value ] = unit
750
750
measurand_data [measurand ][phase ] = float (value )
751
- # store the measurand data as extra attributes
752
- self ._extra_attr .update (measurand_data )
751
+ self ._metrics [measurand ].extra_attr [om .unit .value ] = unit
752
+ self ._metrics [measurand ].extra_attr [phase ] = float (value )
753
+
753
754
for metric , phase_info in measurand_data .items ():
754
755
# _LOGGER.debug("Metric: %s, extra attributes: %s", metric, phase_info)
755
756
metric_value = None
@@ -782,56 +783,68 @@ def process_phases(self, data):
782
783
+ phase_info .get (Phase .l3 .value , 0 )
783
784
)
784
785
if metric_value is not None :
785
- self ._metrics [metric ] = round (metric_value , 1 )
786
+ self ._metrics [metric ]. value = round (metric_value , 1 )
786
787
787
788
@on (Action .MeterValues )
788
789
def on_meter_values (self , connector_id : int , meter_value : Dict , ** kwargs ):
789
790
"""Request handler for MeterValues Calls."""
790
- m = om .measurand .value
791
791
for bucket in meter_value :
792
792
unprocessed = bucket [om .sampled_value .name ]
793
793
processed_keys = []
794
794
for idx , sv in enumerate (bucket [om .sampled_value .name ]):
795
- if m in sv and om .phase .value not in sv :
796
- self ._metrics [sv [m ]] = round (float (sv [om .value .value ]), 1 )
797
- if om .unit .value in sv :
798
- if sv [om .unit .value ] == DEFAULT_POWER_UNIT :
799
- self ._metrics [sv [m ]] = float (sv [om .value .value ]) / 1000
800
- self ._units [sv [m ]] = HA_POWER_UNIT
801
- if sv [om .unit .value ] == DEFAULT_ENERGY_UNIT :
802
- self ._metrics [sv [m ]] = float (sv [om .value .value ]) / 1000
803
- self ._units [sv [m ]] = HA_ENERGY_UNIT
795
+ measurand = sv .get (om .measurand .value , None )
796
+ value = sv .get (om .value .value , None )
797
+ unit = sv .get (om .unit .value , None )
798
+ phase = sv .get (om .phase .value , None )
799
+ location = sv .get (om .location .value , None )
800
+
801
+ if len (sv .keys ()) == 1 : # Backwars compatibility
802
+ measurand = DEFAULT_MEASURAND
803
+ unit = DEFAULT_ENERGY_UNIT
804
+
805
+ if phase is None :
806
+ if unit == DEFAULT_POWER_UNIT :
807
+ self ._metrics [measurand ].value = float (value ) / 1000
808
+ self ._metrics [measurand ].unit = HA_POWER_UNIT
809
+ elif unit == DEFAULT_ENERGY_UNIT :
810
+ self ._metrics [measurand ].value = float (value ) / 1000
811
+ self ._metrics [measurand ].unit = HA_ENERGY_UNIT
812
+ else :
813
+ self ._metrics [measurand ].value = round (float (value ), 1 )
814
+ self ._metrics [measurand ].unit = unit
815
+ if location is not None :
816
+ self ._metrics [measurand ].extra_attr [
817
+ om .location .value
818
+ ] = location
804
819
processed_keys .append (idx )
805
- if len (sv .keys ()) == 1 : # for backwards compatibility
806
- self ._metrics [DEFAULT_MEASURAND ] = float (sv [om .value .value ]) / 1000
807
- self ._units [DEFAULT_MEASURAND ] = HA_ENERGY_UNIT
808
- processed_keys .append (idx )
809
- if m in sv and om .location .value in sv :
810
- if self ._extra_attr .get (sv [m ]) is None :
811
- self ._extra_attr [sv [m ]] = {}
812
- self ._extra_attr [sv [m ]][om .location .value ] = sv .get (
813
- om .location .value
814
- )
815
820
for idx in sorted (processed_keys , reverse = True ):
816
821
unprocessed .pop (idx )
817
822
# _LOGGER.debug("Meter data not yet processed: %s", unprocessed)
818
823
if unprocessed is not None :
819
824
self .process_phases (unprocessed )
820
825
if csess .meter_start .value not in self ._metrics :
821
- self ._metrics [csess .meter_start .value ] = self ._metrics [DEFAULT_MEASURAND ]
826
+ self ._metrics [csess .meter_start .value ].value = self ._metrics [
827
+ DEFAULT_MEASURAND
828
+ ]
822
829
if csess .transaction_id .value not in self ._metrics :
823
- self ._metrics [csess .transaction_id .value ] = kwargs .get (
830
+ self ._metrics [csess .transaction_id .value ]. value = kwargs .get (
824
831
om .transaction_id .name
825
832
)
826
833
self ._transactionId = kwargs .get (om .transaction_id .name )
827
- self ._metrics [csess .session_time .value ] = round (
828
- (int (time .time ()) - float (self ._metrics [csess .transaction_id .value ])) / 60
829
- )
830
- self ._metrics [csess .session_energy .value ] = round (
831
- float (self ._metrics [DEFAULT_MEASURAND ])
832
- - float (self ._metrics [csess .meter_start .value ]),
833
- 1 ,
834
- )
834
+ if self ._metrics [csess .transaction_id .value ].value is not None :
835
+ self ._metrics [csess .session_time .value ].value = round (
836
+ (
837
+ int (time .time ())
838
+ - float (self ._metrics [csess .transaction_id .value ].value )
839
+ )
840
+ / 60
841
+ )
842
+ if self ._metrics [csess .meter_start .value ].value is not None :
843
+ self ._metrics [csess .session_energy .value ].value = round (
844
+ float (self ._metrics [DEFAULT_MEASURAND ].value or 0 )
845
+ - float (self ._metrics [csess .meter_start .value ].value ),
846
+ 1 ,
847
+ )
835
848
self .hass .async_create_task (self .central .update (self .central .cpid ))
836
849
return call_result .MeterValuesPayload ()
837
850
@@ -842,12 +855,16 @@ def on_boot_notification(self, **kwargs):
842
855
_LOGGER .debug ("Received boot notification for %s: %s" , self .id , kwargs )
843
856
844
857
# update metrics
845
- self ._metrics [cdet .model .value ] = kwargs .get (om .charge_point_model .name , None )
846
- self ._metrics [cdet .vendor .value ] = kwargs .get (om .charge_point_vendor .name , None )
847
- self ._metrics [cdet .firmware_version .value ] = kwargs .get (
858
+ self ._metrics [cdet .model .value ].value = kwargs .get (
859
+ om .charge_point_model .name , None
860
+ )
861
+ self ._metrics [cdet .vendor .value ].value = kwargs .get (
862
+ om .charge_point_vendor .name , None
863
+ )
864
+ self ._metrics [cdet .firmware_version .value ].value = kwargs .get (
848
865
om .firmware_version .name , None
849
866
)
850
- self ._metrics [cdet .serial .value ] = kwargs .get (
867
+ self ._metrics [cdet .serial .value ]. value = kwargs .get (
851
868
om .charge_point_serial_number .name , None
852
869
)
853
870
@@ -862,25 +879,25 @@ def on_boot_notification(self, **kwargs):
862
879
@on (Action .StatusNotification )
863
880
def on_status_notification (self , connector_id , error_code , status , ** kwargs ):
864
881
"""Handle a status notification."""
865
- self ._metrics [cstat .status .value ] = status
882
+ self ._metrics [cstat .status .value ]. value = status
866
883
if (
867
884
status == ChargePointStatus .suspended_ev .value
868
885
or status == ChargePointStatus .suspended_evse .value
869
886
):
870
887
if Measurand .current_import .value in self ._metrics :
871
- self ._metrics [Measurand .current_import .value ] = 0
888
+ self ._metrics [Measurand .current_import .value ]. value = 0
872
889
if Measurand .power_active_import .value in self ._metrics :
873
- self ._metrics [Measurand .power_active_import .value ] = 0
890
+ self ._metrics [Measurand .power_active_import .value ]. value = 0
874
891
if Measurand .power_reactive_import .value in self ._metrics :
875
- self ._metrics [Measurand .power_reactive_import .value ] = 0
876
- self ._metrics [cstat .error_code .value ] = error_code
892
+ self ._metrics [Measurand .power_reactive_import .value ]. value = 0
893
+ self ._metrics [cstat .error_code .value ]. value = error_code
877
894
self .hass .async_create_task (self .central .update (self .central .cpid ))
878
895
return call_result .StatusNotificationPayload ()
879
896
880
897
@on (Action .FirmwareStatusNotification )
881
898
def on_firmware_status (self , status , ** kwargs ):
882
899
"""Handle firmware status notification."""
883
- self ._metrics [cstat .firmware_status .value ] = status
900
+ self ._metrics [cstat .firmware_status .value ]. value = status
884
901
self .hass .async_create_task (self .central .update (self .central .cpid ))
885
902
return call_result .FirmwareStatusNotificationPayload ()
886
903
@@ -901,9 +918,9 @@ def on_authorize(self, id_tag, **kwargs):
901
918
def on_start_transaction (self , connector_id , id_tag , meter_start , ** kwargs ):
902
919
"""Handle a Start Transaction request."""
903
920
self ._transactionId = int (time .time ())
904
- self ._metrics [cstat .stop_reason .value ] = ""
905
- self ._metrics [csess .transaction_id .value ] = self ._transactionId
906
- self ._metrics [csess .meter_start .value ] = int (meter_start ) / 1000
921
+ self ._metrics [cstat .stop_reason .value ]. value = ""
922
+ self ._metrics [csess .transaction_id .value ]. value = self ._transactionId
923
+ self ._metrics [csess .meter_start .value ]. value = int (meter_start ) / 1000
907
924
self .hass .async_create_task (self .central .update (self .central .cpid ))
908
925
return call_result .StartTransactionPayload (
909
926
id_tag_info = {om .status .value : AuthorizationStatus .accepted .value },
@@ -913,19 +930,20 @@ def on_start_transaction(self, connector_id, id_tag, meter_start, **kwargs):
913
930
@on (Action .StopTransaction )
914
931
def on_stop_transaction (self , meter_stop , timestamp , transaction_id , ** kwargs ):
915
932
"""Stop the current transaction."""
916
- self ._metrics [cstat .stop_reason .value ] = kwargs .get (om .reason .name , None )
933
+ self ._metrics [cstat .stop_reason .value ]. value = kwargs .get (om .reason .name , None )
917
934
918
- if csess .meter_start .value in self ._metrics :
919
- self ._metrics [csess .session_energy .value ] = round (
920
- int (meter_stop ) / 1000 - float (self ._metrics [csess .meter_start .value ]),
935
+ if self ._metrics [csess .meter_start .value ].value is not None :
936
+ self ._metrics [csess .session_energy .value ].value = round (
937
+ int (meter_stop ) / 1000
938
+ - float (self ._metrics [csess .meter_start .value ].value ),
921
939
1 ,
922
940
)
923
941
if Measurand .current_import .value in self ._metrics :
924
- self ._metrics [Measurand .current_import .value ] = 0
942
+ self ._metrics [Measurand .current_import .value ]. value = 0
925
943
if Measurand .power_active_import .value in self ._metrics :
926
- self ._metrics [Measurand .power_active_import .value ] = 0
944
+ self ._metrics [Measurand .power_active_import .value ]. value = 0
927
945
if Measurand .power_reactive_import .value in self ._metrics :
928
- self ._metrics [Measurand .power_reactive_import .value ] = 0
946
+ self ._metrics [Measurand .power_reactive_import .value ]. value = 0
929
947
self .hass .async_create_task (self .central .update (self .central .cpid ))
930
948
return call_result .StopTransactionPayload (
931
949
id_tag_info = {om .status .value : AuthorizationStatus .accepted .value }
@@ -941,18 +959,58 @@ def on_data_transfer(self, vendor_id, **kwargs):
941
959
def on_heartbeat (self , ** kwargs ):
942
960
"""Handle a Heartbeat."""
943
961
now = datetime .now (tz = timezone .utc ).isoformat ()
944
- self ._metrics [cstat .heartbeat .value ] = now
962
+ self ._metrics [cstat .heartbeat .value ]. value = now
945
963
self .hass .async_create_task (self .central .update (self .central .cpid ))
946
964
return call_result .HeartbeatPayload (current_time = now )
947
965
948
966
def get_metric (self , measurand : str ):
949
967
"""Return last known value for given measurand."""
950
- return self ._metrics . get ( measurand , None )
968
+ return self ._metrics [ measurand ]. value
951
969
952
970
def get_extra_attr (self , measurand : str ):
953
971
"""Return last known extra attributes for given measurand."""
954
- return self ._extra_attr . get ( measurand , None )
972
+ return self ._metrics [ measurand ]. extra_attr
955
973
956
974
def get_unit (self , measurand : str ):
957
975
"""Return unit of given measurand."""
958
- return self ._units .get (measurand , None )
976
+ return self ._metrics [measurand ].unit
977
+
978
+
979
+ class Metric :
980
+ """Metric class."""
981
+
982
+ def __init__ (self , value , unit ):
983
+ """Initialize a Metric."""
984
+ self ._value = value
985
+ self ._unit = unit
986
+ self ._extra_attr = {}
987
+
988
+ @property
989
+ def value (self ):
990
+ """Get the value of the metric."""
991
+ return self ._value
992
+
993
+ @value .setter
994
+ def value (self , value ):
995
+ """Set the value of the metric."""
996
+ self ._value = value
997
+
998
+ @property
999
+ def unit (self ):
1000
+ """Get the unit of the metric."""
1001
+ return self ._unit
1002
+
1003
+ @unit .setter
1004
+ def unit (self , unit : str ):
1005
+ """Set the unit of the metric."""
1006
+ self ._unit = unit
1007
+
1008
+ @property
1009
+ def extra_attr (self ):
1010
+ """Get the extra attributes of the metric."""
1011
+ return self ._extra_attr
1012
+
1013
+ @extra_attr .setter
1014
+ def extra_attr (self , extra_attr : dict ):
1015
+ """Set the unit of the metric."""
1016
+ self ._extra_attr = extra_attr
0 commit comments