Skip to content

Commit 821c7b7

Browse files
authored
Fixes new OpenAPI error (#307)
* Adapt ResidualEnergy Scale to cope with OpenAPI bugs Now reads the scale on the ResidualEnergy sample and adapts the scaling - this used to be 0.01kWh but it seems possible that 0.1kWh (*10) and even kWh (*1) are sent. For some battery systems (ECS4300) the scale is incorrect and the resulting sum is /100 (OpenAPI big) - the integration now corrects for this. * Support V1 API get real time data V0 Real time data all is deprecated, this adds V1 support for when Fox withdraw V0. It can be enabled by adding this command to the foxess platform setup in configuration .yaml `Use_V1_Api: true` * Update sensor.py * Update sensor.py Fixes change of API msg response from 'success' to 'Operation successful'....
1 parent 03b8619 commit 821c7b7

1 file changed

Lines changed: 52 additions & 33 deletions

File tree

custom_components/foxess/sensor.py

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
_ENDPOINT_OA_REPORT = "/op/v0/device/report/query"
6060
_ENDPOINT_OA_DEVICE_DETAIL = "/op/v0/device/detail?sn="
6161
_ENDPOINT_OA_DEVICE_VARIABLES = "/op/v0/device/real/query"
62+
_ENDPOINT_OA_DEVICE_VARIABLES_V1 = "/op/v1/device/real/query"
6263
_ENDPOINT_OA_DAILY_GENERATION = "/op/v0/device/generation?sn="
6364

6465
METHOD_POST = "POST"
@@ -86,6 +87,7 @@
8687
CONF_EXTPV = "extendPV"
8788
CONF_XTZONE = "xtZone"
8889
CONF_GET_VARIABLES = "Restrict"
90+
CONF_V1_API = "Use_V1_Api"
8991
RETRY_NEXT_SLOT = -1
9092

9193
DEFAULT_NAME = "FoxESS"
@@ -105,6 +107,7 @@
105107
vol.Optional(CONF_EXTPV): cv.boolean,
106108
vol.Optional(CONF_XTZONE): cv.boolean,
107109
vol.Optional(CONF_GET_VARIABLES): cv.boolean,
110+
vol.Optional(CONF_V1_API): cv.boolean,
108111
}
109112
)
110113

@@ -113,20 +116,24 @@
113116

114117
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
115118
"""Set up the FoxESS sensor."""
116-
global LastHour, timeslice, last_api, RestrictGetVar, xtzone
119+
global LastHour, timeslice, last_api, RestrictGetVar, xtzone, V1_Api
117120
name = config.get(CONF_NAME)
118121
deviceID = config.get(CONF_DEVICEID)
119122
devicesn = config.get(CONF_DEVICESN)
120123
apiKey = config.get(CONF_APIKEY)
121124
ExtPV = config.get(CONF_EXTPV)
122125
xtzone = config.get(CONF_XTZONE)
123126
RestrictGetVar = config.get(CONF_GET_VARIABLES)
127+
V1_Api = config.get(CONF_V1_API)
124128
_LOGGER.debug("API Key: %s", apiKey)
125129
_LOGGER.debug("Device SN: %s", devicesn)
126130
_LOGGER.debug("Device ID: %s", deviceID)
127131
_LOGGER.debug("FoxESS Scan Interval: %s minutes", SCAN_MINUTES)
128132
_LOGGER.debug("Cross Time Zone: %s", xtzone)
129133
_LOGGER.debug("Extended PV: %s", ExtPV)
134+
_LOGGER.debug("V1 Api Calls: %s", V1_Api)
135+
if V1_Api is not True:
136+
V1_Api = False
130137
if ExtPV is not True:
131138
ExtPV = False
132139
else:
@@ -765,7 +772,7 @@ async def getOADeviceDetail(hass, allData, devicesn, apiKey):
765772
return True
766773
else:
767774
response = json.loads(restOADeviceDetail.data)
768-
if response["errno"] == 0 and response["msg"] == "success":
775+
if response["errno"] == 0 and (response["msg"]=='success' or response["msg"]=='Operation successful'):
769776
ResponseTime = round(time.time() * 1000) - timestamp
770777
if ResponseTime > 0:
771778
allData["raw"]["ResponseTime"] = ResponseTime
@@ -824,7 +831,7 @@ async def getOABatterySettings(hass, allData, devicesn, apiKey):
824831
return True
825832
else:
826833
response = json.loads(restOABatterySettings.data)
827-
if response["errno"] == 0 and response["msg"] == "success":
834+
if response["errno"] == 0 and (response["msg"]=='success' or response["msg"]=='Operation successful'):
828835
_LOGGER.debug(
829836
"OA Battery Settings Good Response: %s", response["result"]
830837
)
@@ -895,7 +902,7 @@ async def getReport(hass, allData, apiKey, devicesn):
895902
else:
896903
# Openapi responded so process data
897904
response = json.loads(restOAReport.data)
898-
if response["errno"] == 0 and response["msg"] == "success":
905+
if response["errno"] == 0 and (response["msg"]=='success' or response["msg"]=='Operation successful'):
899906
_LOGGER.debug(
900907
"OA Report Data fetched OK: %s %s ", response, restOAReport.data[:350]
901908
)
@@ -914,7 +921,7 @@ async def getReport(hass, allData, apiKey, devicesn):
914921
if dataItem != None:
915922
cumulative_total = dataItem
916923
else:
917-
_LOGGER.warning("Report month fetch, None received")
924+
_LOGGER.debug("Report month fetch, None received")
918925
break
919926
index += 1
920927
# cumulative_total += dataItem
@@ -962,7 +969,7 @@ async def getReportDailyGeneration(hass, allData, apiKey, devicesn):
962969
return True
963970
else:
964971
response = json.loads(restOAgen.data)
965-
if response["errno"] == 0 and response["msg"] == "success":
972+
if response["errno"] == 0 and (response["msg"]=='success' or response["msg"]=='Operation successful'):
966973
_LOGGER.debug(
967974
"OA Daily Generation Report Data fetched OK Response: %s",
968975
restOAgen.data[:500],
@@ -1018,38 +1025,40 @@ async def getRaw(hass, allData, apiKey, devicesn):
10181025

10191026
# "deviceSN" used for OpenAPI and it only fetches the real time data
10201027

1028+
# build the devicesn string
1029+
if V1_Api:
1030+
dsn = '{"sns":["' + devicesn + '"] }'
1031+
else:
1032+
dsn = '{"sn":"' + devicesn + '" }'
1033+
10211034
if RestrictGetVar:
10221035
_LOGGER.debug("Getting Device Variable in restricted mode")
1036+
# build the devicesn string
1037+
if V1_Api:
1038+
dsn = '{"sns":["' + devicesn + '"] '
1039+
else:
1040+
dsn = '{"sn":"' + devicesn + '"'
1041+
10231042
rawData = (
1024-
'{"sn":"'
1025-
+ devicesn
1026-
+ '","variables":["ambientTemperation", \
1027-
"batChargePower","batCurrent","batCurrent_1","batCurrent_2","batDischargePower", \
1028-
"batTemperature","batTemperature_1","batTemperature_2","batVolt", "batVolt_1", "batVolt_2",\
1029-
"boostTemperation", "chargeTemperature", "dspTemperature", \
1030-
"epsCurrentR","epsCurrentS","epsCurrentT","epsPower","epsPowerR","epsPowerS","epsPowerT","epsVoltR","epsVoltS","epsVoltT", \
1031-
"feedinPower", "generationPower","gridConsumptionPower", \
1032-
"input","invBatCurrent","invBatPower","invBatVolt","invTemperation", \
1033-
"loadsPower","loadsPowerR","loadsPowerS","loadsPowerT", \
1034-
"meterPower","meterPower2","meterPowerR","meterPowerS","meterPowerT","PowerFactor", \
1035-
"pv1Current","pv1Power","pv1Volt","pv2Current","pv2Power","pv2Volt", \
1036-
"pv3Current","pv3Power","pv3Volt","pv4Current","pv4Power","pv4Volt","pvPower", \
1037-
"RCurrent","ReactivePower","RFreq","RPower","RVolt", \
1038-
"SCurrent","SFreq","SoC","SPower","SVolt", \
1039-
"TCurrent","TFreq","TPower","TVolt", "SoC_1","Soc_2", \
1040-
"ResidualEnergy","energyThroughput","runningState","currentFaultCount"] }'
1043+
dsn + ',"variables":["ambientTemperation", "batChargePower", "batCurrent", "batCurrent_1", "batCurrent_2", "batDischargePower", "batTemperature", "batTemperature_1", "batTemperature_2", "batVolt", "batVolt_1", "batVolt_2", "boostTemperation", "chargeTemperature", "dspTemperature", "epsCurrentR", "epsCurrentS", "epsCurrentT", "epsPower", "epsPowerR", "epsPowerS", "epsPowerT", "epsVoltR", "epsVoltS", "epsVoltT", "feedinPower", "generationPower", "gridConsumptionPower", "input", "invBatCurrent", "invBatPower", "invBatVolt", "invTemperation", "loadsPower", "loadsPowerR", "loadsPowerS", "loadsPowerT", "meterPower", "meterPower2", "meterPowerR", "meterPowerS", "meterPowerT", "PowerFactor", "pv1Current", "pv1Power", "pv1Volt", "pv2Current", "pv2Power", "pv2Volt", "pv3Current", "pv3Power", "pv3Volt", "pv4Current", "pv4Power", "pv4Volt", "pvPower", "RCurrent", "ReactivePower", "RFreq", "RPower", "RVolt", "SCurrent", "SFreq", "SoC", "SPower", "SVolt", "TCurrent", "TFreq", "TPower", "TVolt", "SoC_1", "Soc_2", "ResidualEnergy", "energyThroughput", "runningState", "currentFaultCount"] }'
10411044
)
10421045
else:
1043-
rawData = '{"sn":"' + devicesn + '" }'
1046+
rawData = dsn # '{"sn":"' + dsn + '" }'
10441047

10451048
_LOGGER.debug("getRaw OA request: %s", rawData)
10461049

10471050
timestamp = round(time.time() * 1000)
10481051

1049-
path = _ENDPOINT_OA_DEVICE_VARIABLES
1052+
if V1_Api:
1053+
path = _ENDPOINT_OA_DEVICE_VARIABLES_V1
1054+
_LOGGER.debug("Using V1 API")
1055+
else:
1056+
path = _ENDPOINT_OA_DEVICE_VARIABLES
1057+
10501058
headerData = GetAuth().get_signature(token=apiKey, path=path)
10511059

1052-
path = _ENDPOINT_OA_DOMAIN + _ENDPOINT_OA_DEVICE_VARIABLES
1060+
path = _ENDPOINT_OA_DOMAIN + path
1061+
_LOGGER.debug("Path: %s", path)
10531062

10541063
restOADeviceVariables = RestData(
10551064
hass,
@@ -1073,7 +1082,7 @@ async def getRaw(hass, allData, apiKey, devicesn):
10731082
else:
10741083
# Openapi responded correctly
10751084
response = json.loads(restOADeviceVariables.data)
1076-
if response["errno"] == 0 and response["msg"] == "success":
1085+
if response["errno"] == 0 and (response["msg"]=='success' or response["msg"]=='Operation successful'):
10771086
ResponseTime = round(time.time() * 1000) - timestamp
10781087
if ResponseTime > 0:
10791088
allData["raw"]["ResponseTime"] = ResponseTime
@@ -1158,15 +1167,24 @@ async def getRaw(hass, allData, apiKey, devicesn):
11581167
else:
11591168
variableValue = 0
11601169
_LOGGER.debug("Variable %s no value, set to zero", variableName)
1161-
# fix for second battery items
1170+
# fix for various battery and scale items
11621171
if variableName == "SoC_1":
11631172
variableName = "SoC_1" # do nothing for the moment, future release might align this correctly to use SoC
11641173
elif variableName == "batTemperature_1":
1165-
variableName = "batTemperature" # use same entity as for single battery systems
1174+
variableName = "batTemperature" # use entity for single battery systems
11661175
elif variableName == "invBatPower_1":
1167-
variableName = (
1168-
"invBatPower" # use same entity as for single battery systems
1169-
)
1176+
variableName = "invBatPower" # use entity for single battery systems
1177+
elif variableName == "ResidualEnergy":
1178+
if item.get("unit") is not None:
1179+
scale=item["unit"]
1180+
if scale in ['1.0kWh', 'kWh', None]:
1181+
variableValue = round((variableValue * 100),2)
1182+
_LOGGER.debug("OA Variables ResidualEnergy Scale: *100 %s", scale)
1183+
elif scale=="0.1kWh":
1184+
variableValue = round((variableValue * 10),2)
1185+
_LOGGER.debug("OA Variables ResidualEnergy Scale: *10 %s", scale)
1186+
else:
1187+
_LOGGER.debug("OA Variables ResidualEnergy Scale: %s", scale)
11701188

11711189
allData["raw"][variableName] = variableValue
11721190
_LOGGER.debug(
@@ -2073,7 +2091,8 @@ def native_value(self) -> float | None:
20732091
else:
20742092
re = self.coordinator.data["raw"]["ResidualEnergy"]
20752093
if re > 0:
2076-
re = re / 100
2094+
if re > 50: # if openAPI scale is invalid (bug)
2095+
re = re / 100
20772096
else:
20782097
re = 0
20792098
return re

0 commit comments

Comments
 (0)