52
52
import time
53
53
from functools import wraps
54
54
from http import HTTPStatus
55
- from typing import Dict , List
55
+ from typing import Dict , List , Tuple
56
56
57
57
import requests
58
58
import urllib3
@@ -960,8 +960,90 @@ def get_fan_speeds(self, force=False):
960
960
Get the fan speeds for the Powerwall / Inverter
961
961
"""
962
962
return self .extract_fan_speeds (self .get_device_controller (force = force ))
963
-
964
-
963
+
964
+
965
+ def derive_meter_config (self , config ) -> dict :
966
+ # Build meter Lookup if available
967
+ meter_config = {}
968
+ if not "meters" in config :
969
+ return meter_config
970
+ # Loop through each meter and use device_serial as the key
971
+ for meter in config ['meters' ]:
972
+ if meter .get ('type' ) != "neurio_w2_tcp" :
973
+ continue
974
+ device_serial = lookup (meter , ['connection' , 'device_serial' ])
975
+ if not device_serial :
976
+ continue
977
+ # Check to see if we already have this meter in meter_config
978
+ if device_serial in meter_config :
979
+ cts = meter .get ('cts' , [False ] * 4 )
980
+ if not isinstance (cts , list ):
981
+ cts = [False ] * 4
982
+ for i , ct in enumerate (cts ):
983
+ if not ct :
984
+ continue
985
+ meter_config [device_serial ]['cts' ][i ] = True
986
+ meter_config [device_serial ]['location' ][i ] = meter .get ('location' , "" )
987
+ else :
988
+ # New meter, add to meter_config
989
+ cts = meter .get ('cts' , [False ] * 4 )
990
+ if not isinstance (cts , list ):
991
+ cts = [False ] * 4
992
+ location = meter .get ('location' , "" )
993
+ meter_config [device_serial ] = {
994
+ "type" : meter .get ('type' ),
995
+ "location" : [location ] * 4 ,
996
+ "cts" : cts ,
997
+ "inverted" : meter .get ('inverted' ),
998
+ "connection" : meter .get ('connection' ),
999
+ "real_power_scale_factor" : meter .get ('real_power_scale_factor' , 1 )
1000
+ }
1001
+ return meter_config
1002
+
1003
+
1004
+ def aggregate_neurio_data (self , config_data , status_data , meter_config_data ) -> Tuple [dict , dict ]:
1005
+ # Create NEURIO block
1006
+ neurio_flat = {}
1007
+ neurio_hierarchy = {}
1008
+ # Loop through each Neurio device serial number
1009
+ for c , n in enumerate (lookup (status_data , ['neurio' , 'readings' ]) or {}, start = 1000 ):
1010
+ # Loop through each CT on the Neurio device
1011
+ sn = n .get ('serial' , str (c ))
1012
+ cts_flat = {}
1013
+ for i , ct in enumerate (n ['dataRead' ] or {}):
1014
+ # Only show if we have a meter configuration and cts[i] is true
1015
+ cts_bool = lookup (meter_config_data , [sn , 'cts' ])
1016
+ if isinstance (cts_bool , list ) and i < len (cts_bool ):
1017
+ if not cts_bool [i ]:
1018
+ # Skip this CT
1019
+ continue
1020
+ factor = lookup (meter_config_data , [sn , 'real_power_scale_factor' ]) or 1
1021
+ location = lookup (meter_config_data , [sn , 'location' ])
1022
+ ct_hierarchy = {
1023
+ "Index" : i ,
1024
+ "InstRealPower" : ct .get ('realPowerW' , 0 ) * factor ,
1025
+ "InstReactivePower" : ct .get ('reactivePowerVAR' ),
1026
+ "InstVoltage" : ct .get ('voltageV' ),
1027
+ "InstCurrent" : ct .get ('currentA' ),
1028
+ "Location" : location [i ] if location and len (location ) > i else None
1029
+ }
1030
+ neurio_hierarchy [f"CT{ i } " ] = ct_hierarchy
1031
+ cts_flat .update ({f"NEURIO_CT{ i } _" + key : value for key , value in ct_hierarchy .items () if key != "Index" })
1032
+ meter_manufacturer = "NEURIO" if lookup (meter_config_data , [sn , "type" ]) == "neurio_w2_tcp" else None
1033
+ rest = {
1034
+ "componentParentDin" : lookup (config_data , ['vin' ]),
1035
+ "firmwareVersion" : None ,
1036
+ "lastCommunicationTime" : lookup (n , ['timestamp' ]),
1037
+ "manufacturer" : meter_manufacturer ,
1038
+ "meterAttributes" : {
1039
+ "meterLocation" : []
1040
+ },
1041
+ "serialNumber" : sn
1042
+ }
1043
+ neurio_flat [f"NEURIO--{ sn } " ] = {** cts_flat , ** rest }
1044
+ return (neurio_flat , neurio_hierarchy )
1045
+
1046
+
965
1047
# Vitals API Mapping Function
966
1048
def vitals (self , force = False ):
967
1049
"""
@@ -984,38 +1066,6 @@ def calculate_dc_power(V, I):
984
1066
if not isinstance (status , dict ) or not isinstance (config , dict ):
985
1067
return None
986
1068
987
- # Build meter Lookup if available
988
- meter_config = {}
989
- if "meters" in config :
990
- # Loop through each meter and use device_serial as the key
991
- for meter in config ['meters' ]:
992
- if meter .get ('type' ) == "neurio_w2_tcp" :
993
- device_serial = lookup (meter , ['connection' , 'device_serial' ])
994
- if device_serial :
995
- # Check to see if we already have this meter in meter_config
996
- if device_serial in meter_config :
997
- cts = meter .get ('cts' , [False ] * 4 )
998
- if not isinstance (cts , list ):
999
- cts = [False ] * 4
1000
- for i , ct in enumerate (cts ):
1001
- if ct :
1002
- meter_config [device_serial ]['cts' ][i ] = True
1003
- meter_config [device_serial ]['location' ][i ] = meter .get ('location' , "" )
1004
- else :
1005
- # New meter, add to meter_config
1006
- cts = meter .get ('cts' , [False ] * 4 )
1007
- if not isinstance (cts , list ):
1008
- cts = [False ] * 4
1009
- location = meter .get ('location' , "" )
1010
- meter_config [device_serial ] = {
1011
- "type" : meter .get ('type' ),
1012
- "location" : [location ] * 4 ,
1013
- "cts" : cts ,
1014
- "inverted" : meter .get ('inverted' ),
1015
- "connection" : meter .get ('connection' ),
1016
- "real_power_scale_factor" : meter .get ('real_power_scale_factor' , 1 )
1017
- }
1018
-
1019
1069
# Create Header
1020
1070
tesla = {}
1021
1071
header = {}
@@ -1025,45 +1075,11 @@ def calculate_dc_power(V, I):
1025
1075
"gateway" : self .gw_ip ,
1026
1076
"pyPowerwall" : __version__ ,
1027
1077
}
1028
-
1029
- # Create NEURIO block
1030
- neurio = {}
1031
- c = 1000
1032
- # Loop through each Neurio device serial number
1033
- for n in lookup (status , ['neurio' , 'readings' ]) or {}:
1034
- # Loop through each CT on the Neurio device
1035
- sn = n .get ('serial' , str (c ))
1036
- cts = {}
1037
- c = c + 1
1038
- for i , ct in enumerate (n ['dataRead' ] or {}):
1039
- # Only show if we have a meter configuration and cts[i] is true
1040
- cts_bool = lookup (meter_config , [sn , 'cts' ])
1041
- if isinstance (cts_bool , list ) and i < len (cts_bool ):
1042
- if not cts_bool [i ]:
1043
- # Skip this CT
1044
- continue
1045
- factor = lookup (meter_config , [sn , 'real_power_scale_factor' ]) or 1
1046
- device = f"NEURIO_CT{ i } _"
1047
- cts [device + "InstRealPower" ] = lookup (ct , ['realPowerW' ]) * factor
1048
- cts [device + "InstReactivePower" ] = lookup (ct , ['reactivePowerVAR' ])
1049
- cts [device + "InstVoltage" ] = lookup (ct , ['voltageV' ])
1050
- cts [device + "InstCurrent" ] = lookup (ct , ['currentA' ])
1051
- location = lookup (meter_config , [sn , 'location' ])
1052
- cts [device + "Location" ] = location [i ] if len (location ) > i else None
1053
- meter_manufacturer = None
1054
- if lookup (meter_config , [sn , 'type' ]) == "neurio_w2_tcp" :
1055
- meter_manufacturer = "NEURIO"
1056
- rest = {
1057
- "componentParentDin" : lookup (config , ['vin' ]),
1058
- "firmwareVersion" : None ,
1059
- "lastCommunicationTime" : lookup (n , ['timestamp' ]),
1060
- "manufacturer" : meter_manufacturer ,
1061
- "meterAttributes" : {
1062
- "meterLocation" : []
1063
- },
1064
- "serialNumber" : sn
1065
- }
1066
- neurio [f"NEURIO--{ sn } " ] = {** cts , ** rest }
1078
+ neurio = self .aggregate_neurio_data (
1079
+ config_data = config ,
1080
+ status_data = status ,
1081
+ meter_config_data = self .derive_meter_config (config )
1082
+ )[0 ]
1067
1083
1068
1084
# Create PVAC, PVS, and TESLA blocks - Assume the are aligned
1069
1085
pvac = {}
0 commit comments