8
8
from homeassistant .config_entries import ConfigEntry
9
9
from homeassistant .const import TIME_MINUTES
10
10
from homeassistant .core import HomeAssistant
11
- from homeassistant .helpers import device_registry
11
+ from homeassistant .helpers import device_registry , entity_component , entity_registry
12
12
import voluptuous as vol
13
13
import websockets
14
14
31
31
DataTransferStatus ,
32
32
Measurand ,
33
33
MessageTrigger ,
34
+ Phase ,
34
35
RegistrationStatus ,
35
36
RemoteStartStopStatus ,
36
37
ResetStatus ,
@@ -176,6 +177,12 @@ def get_unit(self, cp_id: str, measurand: str):
176
177
return self .charge_points [cp_id ].get_unit (measurand )
177
178
return None
178
179
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
+
179
186
async def set_charger_state (
180
187
self , cp_id : str , service_name : str , state : bool = True
181
188
):
@@ -195,6 +202,19 @@ async def set_charger_state(
195
202
resp = False
196
203
return resp
197
204
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
+
198
218
def device_info (self ):
199
219
"""Return device information."""
200
220
return {
@@ -226,6 +246,7 @@ def __init__(
226
246
self ._requires_reboot = False
227
247
self ._metrics = {}
228
248
self ._units = {}
249
+ self ._extra_attr = {}
229
250
self ._features_supported = {}
230
251
self .preparing = asyncio .Event ()
231
252
self ._transactionId = 0
@@ -635,7 +656,7 @@ async def async_update_device_info(self, boot_info: dict):
635
656
636
657
_LOGGER .debug ("Updating device info %s: %s" , self .central .cpid , boot_info )
637
658
638
- dr = await device_registry .async_get_registry (self .hass )
659
+ dr = device_registry .async_get (self .hass )
639
660
640
661
serial = boot_info .get (om .charge_point_serial_number .name , None )
641
662
@@ -652,31 +673,88 @@ async def async_update_device_info(self, boot_info: dict):
652
673
sw_version = boot_info .get (om .firmware_version .name , None ),
653
674
)
654
675
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
+
655
725
@on (Action .MeterValues )
656
726
def on_meter_values (self , connector_id : int , meter_value : Dict , ** kwargs ):
657
727
"""Request handler for MeterValues Calls."""
658
728
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 :
662
733
self ._metrics [sv [om .measurand .value ]] = round (
663
- float (self . _metrics [ sv [om .measurand .value ] ]), 1
734
+ float (sv [om .value .value ]), 1
664
735
)
665
736
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 :
668
738
self ._metrics [sv [om .measurand .value ]] = (
669
- float (self . _metrics [ sv [om .measurand .value ] ]) / 1000
739
+ float (sv [om .value .value ]) / 1000
670
740
)
671
741
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 :
673
743
self ._metrics [sv [om .measurand .value ]] = (
674
- float (self . _metrics [ sv [om .measurand .value ] ]) / 1000
744
+ float (sv [om .value .value ]) / 1000
675
745
)
676
746
self ._units [sv [om .measurand .value ]] = HA_ENERGY_UNIT
747
+ processed_keys .append (idx )
677
748
if len (sv .keys ()) == 1 : # for backwards compatibility
678
749
self ._metrics [DEFAULT_MEASURAND ] = float (sv [om .value .value ]) / 1000
679
750
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 )
680
758
if csess .meter_start .value not in self ._metrics :
681
759
self ._metrics [csess .meter_start .value ] = self ._metrics [DEFAULT_MEASURAND ]
682
760
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):
692
770
- float (self ._metrics [csess .meter_start .value ]),
693
771
1 ,
694
772
)
773
+ self .hass .async_create_task (self .central .update (self .central .cpid ))
695
774
return call_result .MeterValuesPayload ()
696
775
697
776
@on (Action .BootNotification )
@@ -711,7 +790,7 @@ def on_boot_notification(self, **kwargs):
711
790
)
712
791
713
792
asyncio .create_task (self .async_update_device_info (kwargs ))
714
-
793
+ self . hass . async_create_task ( self . central . update ( self . central . cpid ))
715
794
return call_result .BootNotificationPayload (
716
795
current_time = datetime .now (tz = timezone .utc ).isoformat (),
717
796
interval = 30 ,
@@ -733,12 +812,14 @@ def on_status_notification(self, connector_id, error_code, status, **kwargs):
733
812
if Measurand .power_reactive_import .value in self ._metrics :
734
813
self ._metrics [Measurand .power_reactive_import .value ] = 0
735
814
self ._metrics [cstat .error_code .value ] = error_code
815
+ self .hass .async_create_task (self .central .update (self .central .cpid ))
736
816
return call_result .StatusNotificationPayload ()
737
817
738
818
@on (Action .FirmwareStatusNotification )
739
819
def on_firmware_status (self , status , ** kwargs ):
740
820
"""Handle firmware status notification."""
741
821
self ._metrics [cstat .firmware_status .value ] = status
822
+ self .hass .async_create_task (self .central .update (self .central .cpid ))
742
823
return call_result .FirmwareStatusNotificationPayload ()
743
824
744
825
@on (Action .Authorize )
@@ -755,6 +836,7 @@ def on_start_transaction(self, connector_id, id_tag, meter_start, **kwargs):
755
836
self ._metrics [cstat .stop_reason .value ] = ""
756
837
self ._metrics [csess .transaction_id .value ] = self ._transactionId
757
838
self ._metrics [csess .meter_start .value ] = int (meter_start ) / 1000
839
+ self .hass .async_create_task (self .central .update (self .central .cpid ))
758
840
return call_result .StartTransactionPayload (
759
841
id_tag_info = {om .status .value : AuthorizationStatus .accepted .value },
760
842
transaction_id = self ._transactionId ,
@@ -776,6 +858,7 @@ def on_stop_transaction(self, meter_stop, timestamp, transaction_id, **kwargs):
776
858
self ._metrics [Measurand .power_active_import .value ] = 0
777
859
if Measurand .power_reactive_import .value in self ._metrics :
778
860
self ._metrics [Measurand .power_reactive_import .value ] = 0
861
+ self .hass .async_create_task (self .central .update (self .central .cpid ))
779
862
return call_result .StopTransactionPayload (
780
863
id_tag_info = {om .status .value : AuthorizationStatus .accepted .value }
781
864
)
@@ -791,13 +874,17 @@ def on_heartbeat(self, **kwargs):
791
874
"""Handle a Heartbeat."""
792
875
now = datetime .now (tz = timezone .utc ).isoformat ()
793
876
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 ))
795
878
return call_result .HeartbeatPayload (current_time = now )
796
879
797
880
def get_metric (self , measurand : str ):
798
881
"""Return last known value for given measurand."""
799
882
return self ._metrics .get (measurand , None )
800
883
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
+
801
888
def get_unit (self , measurand : str ):
802
889
"""Return unit of given measurand."""
803
890
return self ._units .get (measurand , None )
0 commit comments