From 212f4abcf2e339b18ed51e79c99bd78871296feb Mon Sep 17 00:00:00 2001 From: janjaapko Date: Thu, 28 Apr 2022 21:59:44 +0200 Subject: [PATCH 1/6] add logging to file --- .gitignore | 3 +- GoodWe.py | 32 ++++++---- plugin.py | 156 +++++++++++++++++++++++++++---------------------- plugin_test.py | 6 ++ 4 files changed, 114 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index 7e99e36..bdcbe6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.pyc \ No newline at end of file +*.pyc +*.log \ No newline at end of file diff --git a/GoodWe.py b/GoodWe.py index fba78e9..89625af 100644 --- a/GoodWe.py +++ b/GoodWe.py @@ -28,6 +28,8 @@ import requests import time import exceptions +import logging + try: import Domoticz debug = False @@ -212,6 +214,7 @@ def __init__(self, stationData=None, id=None, firstDevice=0): self._address = stationData["info"]["address"] self._id = stationData["info"]["powerstation_id"] Domoticz.Debug("create station with id: '" + self._id + "' and inverters: " + str(len(stationData["inverter"])) ) + logging.debug("create station with id: '" + self._id + "' and inverters: " + str(len(stationData["inverter"])) ) self.createInverters(stationData["inverter"]) def __repr__(self): @@ -221,6 +224,7 @@ def createInverters(self, inverterData): for inverter in inverterData: self.inverters[inverter['sn']] = Inverter(inverter, self._firstDevice) Domoticz.Debug("inverter created: '" + str(inverter['sn']) + "'") + logging.debug("inverter created: '" + str(inverter['sn']) + "'") self._firstDevice += self.inverters[inverter['sn']].domoticzDevices @property @@ -292,15 +296,16 @@ def createStation(self, key, stationData): powerStation = PowerStation(stationData=stationData) self.powerStationList.update({key : powerStation}) Domoticz.Log("PowerStation created: '" + powerStation.id + "' with key '" + str(key) + "'") + logging.info("PowerStation created: '" + powerStation.id + "' with key '" + str(key) + "'") def createStationV2(self, stationData): powerStation = PowerStation(stationData=stationData) self.powerStationList.update({1 : powerStation}) Domoticz.Log("PowerStation created: '" + powerStation.id + "'") - + logging.info("PowerStation created: '" + powerStation.id + "'") def apiRequestHeaders(self): - Domoticz.Debug("build apiRequestHeaders with token: '" + json.dumps(self.token) + "'" ) + logging.debug("build apiRequestHeaders with token: '" + json.dumps(self.token) + "'" ) return { 'Content-Type': 'application/json; charset=utf-8', 'Connection': 'keep-alive', @@ -311,14 +316,14 @@ def apiRequestHeaders(self): } def apiRequestHeadersV2(self): - Domoticz.Debug("build apiRequestHeaders with token: '" + json.dumps(self.token) + "'" ) + logging.debug("build apiRequestHeaders with token: '" + json.dumps(self.token) + "'" ) return { 'User-Agent': 'Domoticz/1.0', 'token': json.dumps(self.token) } def tokenRequest(self): - Domoticz.Debug("build tokenRequest with UN: '" + self.Username + "', pwd: '" + self.Password +"'") + logging.debug("build tokenRequest with UN: '" + self.Username + "', pwd: '" + self.Password +"'") url = '/v2/Common/CrossLogin' loginPayload = { 'account': self.Username, @@ -327,7 +332,7 @@ def tokenRequest(self): r = requests.post(self.base_url + url, headers=self.apiRequestHeadersV2(), data=loginPayload, timeout=5) #r.raise_for_status() - Domoticz.Debug("building token request on URL: " + r.url + " which returned status code: " + str(r.status_code) + " and response length = " + str(len(r.text))) + logging.debug("building token request on URL: " + r.url + " which returned status code: " + str(r.status_code) + " and response length = " + str(len(r.text))) apiResponse = r.json() if apiResponse["code"] == 100005: raise exceptions.GoodweException("invalid password or username") @@ -340,10 +345,11 @@ def tokenRequest(self): if apiResponse == 'Null': Domoticz.Log("SEMS API Token not received") + logging.info("SEMS API Token not received") self.tokenAvailable = False else: self.token = apiResponse['data'] - Domoticz.Debug("SEMS API Token received: " + json.dumps(self.token)) + logging.debug("SEMS API Token received: " + json.dumps(self.token)) self.tokenAvailable = True self.base_url = apiResponse['api'] @@ -362,16 +368,16 @@ def tokenRequest(self): # } def stationListRequest(self): - Domoticz.Debug("build stationListRequest") + logging.debug("build stationListRequest") url = 'v2/HistoryData/QueryPowerStationByHistory' r = requests.post(self.base_url + url, headers=self.apiRequestHeadersV2(), timeout=5) - Domoticz.Debug("building station list on URL: " + r.url + " which returned status code: " + str(r.status_code) + " and response length = " + str(len(r.text))) + logging.debug("building station list on URL: " + r.url + " which returned status code: " + str(r.status_code) + " and response length = " + str(len(r.text))) return r.status_code def stationDataRequestV1(self, stationIndex): - Domoticz.Debug("build stationDataRequest with number of stations (len powerStationList) = '" + str(self.numStations) + "' for PS index: '" + str(stationIndex) + "'") + logging.debug("build stationDataRequest with number of stations (len powerStationList) = '" + str(self.numStations) + "' for PS index: '" + str(stationIndex) + "'") #powerStation = self.powerStationList[self.powerStationIndex] powerStation = self.powerStationList[stationIndex] return { @@ -386,7 +392,7 @@ def stationDataRequestV1(self, stationIndex): def stationDataRequestV2(self, stationId): for i in range(1, 4): try: - Domoticz.Debug("build stationDataRequest for 1 station, attempt: " + str(i)) + logging.debug("build stationDataRequest for 1 station, attempt: " + str(i)) responseData = self.stationDataRequest(stationId) try: @@ -399,11 +405,13 @@ def stationDataRequestV2(self, stationId): return responseData['data'] elif code == 100001 or code == 100002: #token has expired or is not valid + logging.info("Failed to call GoodWe API (no valid token), will be refreshed") Domoticz.Log("Failed to call GoodWe API (no valid token), will be refreshed") self.tokenRequest() else: raise exceptions.FailureWithErrorCode(code) except requests.exceptions.RequestException as exp: + logging.error("RequestException: " + str(exp)) Domoticz.Error("RequestException: " + str(exp)) time.sleep(i ** 3) else: @@ -416,7 +424,7 @@ def stationDataRequest(self, stationId): } r = requests.post(self.base_url + url, headers=self.apiRequestHeadersV2(), data=payload, timeout=5) - Domoticz.Debug("building station data request on URL: " + r.url + " which returned status code: " + str(r.status_code) + " and response length = " + str(len(r.text))) - #Domoticz.Debug("response station data request : " + json.dumps(r.json())) + logging.debug("building station data request on URL: " + r.url + " which returned status code: " + str(r.status_code) + " and response length = " + str(len(r.text))) + #logging.debug("response station data request : " + json.dumps(r.json())) responseData = r.json() return responseData diff --git a/plugin.py b/plugin.py index 1837dfa..b7c86c9 100644 --- a/plugin.py +++ b/plugin.py @@ -17,7 +17,7 @@ # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ - +

GoodWe inverter (via SEMS portal)

This plugin uses the GoodWe SEMS api to retrieve the status information of your GoodWe inverter.

@@ -77,6 +77,7 @@ from GoodWe import GoodWe from GoodWe import PowerStation import exceptions +import logging class GoodWeSEMSPlugin: httpConn = None @@ -86,6 +87,7 @@ class GoodWeSEMSPlugin: baseDeviceIndex = 0 maxDeviceIndex = 0 + log_filename = "goodwe.log" def __init__(self): return @@ -99,7 +101,7 @@ def apiConnection(self): Address=Parameters["Address"], Port=Parameters["Port"]) def startDeviceUpdateV2(self): - Domoticz.Debug("startDeviceUpdate, token availability: '" + str(self.goodWeAccount.tokenAvailable)+ "'") + logging.debug("startDeviceUpdate, token availability: '" + str(self.goodWeAccount.tokenAvailable)+ "'") if not self.goodWeAccount.tokenAvailable: self.goodWeAccount.powerStationList = {} self.goodWeAccount.powerStationIndex = 0 @@ -107,6 +109,7 @@ def startDeviceUpdateV2(self): try: self.goodWeAccount.tokenRequest() except (exceptions.GoodweException, exceptions.FailureWithMessage, exceptions.FailureWithoutMessage) as exp: + logging.error("Failed to request data: " + str(exp)) Domoticz.Error("Failed to request data: " + str(exp)) return @@ -114,6 +117,7 @@ def startDeviceUpdateV2(self): try: DeviceData = self.goodWeAccount.stationDataRequestV2(Parameters["Mode1"]) except (exceptions.TooManyRetries, exceptions.FailureWithErrorCode, exceptions.FailureWithoutErrorCode) as exp: + logging.error("Failed to request data: " + str(exp)) Domoticz.Error("Failed to request data: " + str(exp)) return self.goodWeAccount.createStationV2(DeviceData) @@ -122,7 +126,7 @@ def startDeviceUpdateV2(self): def updateDevices(self, apiData): theStation = self.goodWeAccount.powerStationList[1] for inverter in apiData["inverter"]: - Domoticz.Debug("inverter found with SN: '" + inverter["sn"] + "'") + logging.debug("inverter found with SN: '" + inverter["sn"] + "'") if inverter["sn"] in theStation.inverters: theStation.inverters[inverter["sn"]].createDevices(Devices) @@ -130,10 +134,12 @@ def updateDevices(self, apiData): if len(inverter['fault_message']) > 0: Domoticz.Log("Fault message from GoodWe inverter (SN: " + inverter["sn"] + "): '" + str(inverter['fault_message']) + "'") + logging.info("Fault message from GoodWe inverter (SN: " + inverter["sn"] + "): '" + str(inverter['fault_message']) + "'") Domoticz.Log("Status of GoodWe inverter (SN: " + inverter["sn"] + "): '" + str(inverter["status"]) + ' ' + self.goodWeAccount.INVERTER_STATE[inverter["status"]] + "'") + logging.info("Status of GoodWe inverter (SN: " + inverter["sn"] + "): '" + str(inverter["status"]) + ' ' + self.goodWeAccount.INVERTER_STATE[inverter["status"]] + "'") Devices[theInverter.inverterStateUnit].Update(nValue=inverter["status"]+1, sValue=str((inverter["status"]+2)*10)) if self.goodWeAccount.INVERTER_STATE[inverter["status"]] == 'generating': - Domoticz.Debug("inverter generating, log temp") + logging.debug("inverter generating, log temp") UpdateDevice(theInverter.inverterTemperatureUnit, 0, str(inverter["tempperature"])) UpdateDevice(theInverter.outputFreq1Unit, 0, str(inverter["d"]["fac1"])) @@ -149,7 +155,7 @@ def updateDevices(self, apiData): UpdateDevice(theInverter.inputPower1Unit, 0, "{:5.1f};{:10.2f}".format(inputPower, newCounter), AlwaysUpdate=True) if "pv_input_2" in inverter: - Domoticz.Debug("Second string found") + logging.debug("Second string found") inputVoltage,inputAmps = inverter["pv_input_2"].split('/') UpdateDevice(theInverter.inputVoltage2Unit, 0, inputVoltage, AlwaysUpdate=True) UpdateDevice(theInverter.inputAmps2Unit, 0, inputAmps, AlwaysUpdate=True) @@ -157,7 +163,7 @@ def updateDevices(self, apiData): newCounter = calculateNewEnergy(theInverter.inputPower2Unit, inputPower) UpdateDevice(theInverter.inputPower2Unit, 0, "{:5.1f};{:10.2f}".format(inputPower, newCounter), AlwaysUpdate=True) if "pv_input_3" in inverter: - Domoticz.Debug("Third string found") + logging.debug("Third string found") inputVoltage,inputAmps = inverter["pv_input_3"].split('/') UpdateDevice(theInverter.inputVoltage3Unit, 0, inputVoltage, AlwaysUpdate=True) UpdateDevice(theInverter.inputAmps3Unit, 0, inputAmps, AlwaysUpdate=True) @@ -165,7 +171,7 @@ def updateDevices(self, apiData): newCounter = calculateNewEnergy(theInverter.inputPower3Unit, inputPower) UpdateDevice(theInverter.inputPower3Unit, 0, "{:5.1f};{:10.2f}".format(inputPower, newCounter), AlwaysUpdate=True) if "pv_input_4" in inverter: - Domoticz.Debug("Fourth string found") + logging.debug("Fourth string found") inputVoltage,inputAmps = inverter["pv_input_4"].split('/') UpdateDevice(theInverter.inputVoltage4Unit, 0, inputVoltage, AlwaysUpdate=True) UpdateDevice(theInverter.inputAmps4Unit, 0, inputAmps, AlwaysUpdate=True) @@ -177,35 +183,47 @@ def onStart(self): if Parameters["Mode6"] == "Verbose": Domoticz.Debugging(1) DumpConfigToLog() - if Parameters["Mode6"] == "Debug": + logging.basicConfig(format='%(asctime)s - %(levelname)-8s - %(filename)-18s - %(message)s', filename=self.log_filename,level=logging.DEBUG) + Domoticz.Status("Starting Goodwe SEMS API plugin, logging to file {0}".format(self.log_filename)) + elif Parameters["Mode6"] == "Debug": Domoticz.Debugging(2) DumpConfigToLog() + logging.basicConfig(format='%(asctime)s - %(levelname)-8s - %(filename)-18s - %(message)s', filename=self.log_filename,level=logging.DEBUG) + Domoticz.Status("Starting Goodwe SEMS API plugin, logging to file {0}".format(self.log_filename)) + else: + Domoticz.Status("Starting Goodwe SEMS API plugin, logging to Domoticz") + + logging.info("starting plugin version "+Parameters["Version"]) self.goodWeAccount = GoodWe(Parameters["Address"], Parameters["Port"], Parameters["Username"], Parameters["Password"]) self.runAgain = int(Parameters["Mode2"]) if len(Parameters["Mode1"]) == 0: Domoticz.Error("No Power Station ID provided, exiting") + logging.error("No Power Station ID provided, exiting") return self.startDeviceUpdateV2() def onStop(self): Domoticz.Log("onStop - Plugin is stopping.") + logging.info("onStop - Plugin is stopping.") if self.httpConn is not None: self.httpConn.Disconnect() def onConnect(self, Connection, Status, Description): - Domoticz.Debug("onConnect: Status: '" + str(Status) + "', Description: '" + Description + "'") + logging.debug("onConnect: Status: '" + str(Status) + "', Description: '" + Description + "'") if (Status == 0): - Domoticz.Debug("Connected to SEMS portal API successfully.") + logging.debug("Connected to SEMS portal API successfully.") self.startDeviceUpdate(Connection) else: Domoticz.Log("Failed to connect (" + str(Status) + ") to: " + Parameters["Address"] + ":" + Parameters[ "Port"] + " with error: " + Description) + logging.info("Failed to connect (" + str(Status) + ") to: " + Parameters["Address"] + ":" + Parameters[ + "Port"] + " with error: " + Description) def onMessage(self, Connection, Data): - Domoticz.Debug("onMessage: data received: Data : '" + str(Data) + "'") + logging.debug("onMessage: data received: Data : '" + str(Data) + "'") status = int(Data["Status"]) if status == 200: @@ -216,83 +234,87 @@ def onMessage(self, Connection, Data): responseText = Data["Data"].decode("utf-8", "ignore") else: responseText = "" - Domoticz.Debug("onMessage: no data found in data") + logging.debug("onMessage: no data found in data") return apiResponse = json.loads(responseText) apiUrl = apiResponse["components"]["api"] apiData = apiResponse["data"] except json.JSONDecodeError as err: msg, doc, pos = err.args + logging.error(msg) Domoticz.Error(msg) apiResponse = None - Domoticz.Debug("The faulty message: '" + doc) + logging.debug("The faulty message: '" + doc) if "api/v2/Common/CrossLogin" in apiUrl: - Domoticz.Debug("message received: CrossLogin") + logging.debug("message received: CrossLogin") if apiData == 'Null': Domoticz.Log("SEMS API Token not received") + logging.info("SEMS API Token not received") self.goodWeAccount.tokenAvailable = False self.httpConn.Disconnect() #self.httpConn = None else: - Domoticz.Debug("message apiData: '" + str(apiData) + "'") + logging.debug("message apiData: '" + str(apiData) + "'") self.goodWeAccount.token = apiData - Domoticz.Debug("SEMS API Token: " + json.dumps(self.goodWeAccount.token)) + logging.debug("SEMS API Token: " + json.dumps(self.goodWeAccount.token)) self.goodWeAccount.tokenAvailable = True #request list of stations on this account Connection.Send(self.goodWeAccount.stationListRequest()) elif "/api/v2/HistoryData/QueryPowerStationByHistory" in apiUrl: - Domoticz.Debug("message received: QueryPowerStationByHistory") + logging.debug("message received: QueryPowerStationByHistory") if apiData is None or len(apiData)==0: Domoticz.Log("Power station history not received") + logging.info("Power station history not received") self.httpConn.Disconnect() #self.httpConn = None else: - Domoticz.Debug("message apiData: '" + str(apiData) + "'") + logging.debug("message apiData: '" + str(apiData) + "'") for stations in apiData["list"]: for data in stations: - Domoticz.Debug("station element: '"+ str(data) + "', value: '" + str(stations[data]) +"'") + logging.debug("station element: '"+ str(data) + "', value: '" + str(stations[data]) +"'") for key, station in enumerate(apiData["list"]): if len(Parameters["Mode1"]) <= 0 or (len(Parameters["Mode1"]) > 0 and station["id"] == Parameters["Mode1"]): #add all found stations if no ID entered, else only log the one that matches the ID self.goodWeAccount.createStation(key, station) Domoticz.Log("Station found: " + station["id"]) + logging.info("Station found: " + station["id"]) Connection.Send(self.goodWeAccount.stationDataRequest(self.baseDeviceIndex)) elif "api/v2/PowerStation/GetMonitorDetailByPowerstationId" in apiUrl: - Domoticz.Debug("message received: GetMonitorDetailByPowerstationId") - #Domoticz.Debug("message: '" + apiData + "'") + logging.debug("message received: GetMonitorDetailByPowerstationId") if apiData is None or len(apiData) == 0: Domoticz.Error("No station data received from GoodWe SEMS API (Station ID: " + str(self.goodWeAccount.powerStationList[ self.goodWeAccount.powerStationIndex]) + ")") + logging.error("No station data received from GoodWe SEMS API (Station ID: " + str(self.goodWeAccount.powerStationList[ + self.goodWeAccount.powerStationIndex]) + ")") self.goodWeAccount.tokenAvailable = False else: - #Domoticz.Debug("message apiData: '" + str(apiData) + "'") + #logging.debug("message apiData: '" + str(apiData) + "'") Domoticz.Log("Station data received from GoodWe SEMS API ('" + str(self.goodWeAccount.powerStationList[ self.goodWeAccount.powerStationIndex]) + "', index : '" + str(self.goodWeAccount.powerStationIndex)+ "')") + logging.info("Station data received from GoodWe SEMS API ('" + str(self.goodWeAccount.powerStationList[ + self.goodWeAccount.powerStationIndex]) + "', index : '" + str(self.goodWeAccount.powerStationIndex)+ "')") theStation = self.goodWeAccount.powerStationList[self.goodWeAccount.powerStationIndex] for inverter in apiData["inverter"]: - Domoticz.Debug("inverter found with SN: '" + inverter["sn"] + "'") + logging.debug("inverter found with SN: '" + inverter["sn"] + "'") if inverter["sn"] in theStation.inverters: theStation.inverters[inverter["sn"]].createDevices(Devices) - #invertFull = inverter["invert_full"] - #Domoticz.Debug("found in inverter: '" + str(inverter) + "'") - #Domoticz.Debug("found in inverter full: '" + str(invertFull) + "'") - # for data in inverter: - # Domoticz.Debug("data in inverter: '" + str(data) + "', value '" + str(inverter[data]) + "'") - Domoticz.Debug("Details d in inverter: '" + str(inverter['d']) + "'") + logging.debug("Details d in inverter: '" + str(inverter['d']) + "'") theInverter = theStation.inverters[inverter["sn"]] if len(inverter['fault_message']) > 0: Domoticz.Log("Fault message from GoodWe inverter (SN: " + inverter["sn"] + "): '" + str(inverter['fault_message']) + "'") + logging.info("Fault message from GoodWe inverter (SN: " + inverter["sn"] + "): '" + str(inverter['fault_message']) + "'") Domoticz.Log("Status of GoodWe inverter (SN: " + inverter["sn"] + "): '" + str(inverter["status"]) + ' ' + self.goodWeAccount.INVERTER_STATE[inverter["status"]] + "'") + logging.info("Status of GoodWe inverter (SN: " + inverter["sn"] + "): '" + str(inverter["status"]) + ' ' + self.goodWeAccount.INVERTER_STATE[inverter["status"]] + "'") Devices[theInverter.inverterStateUnit].Update(nValue=inverter["status"]+1, sValue=str((inverter["status"]+2)*10)) if self.goodWeAccount.INVERTER_STATE[inverter["status"]] == 'generating': - Domoticz.Debug("inverter generating, log temp") + logging.debug("inverter generating, log temp") UpdateDevice(theInverter.inverterTemperatureUnit, 0, str(inverter["tempperature"])) UpdateDevice(theInverter.outputFreq1Unit, 0, str(inverter["d"]["fac1"])) @@ -301,7 +323,6 @@ def onMessage(self, Connection, Data): UpdateDevice(theInverter.outputPowerUnit, 0, str(inverter["output_power"]) + ";" + str(inverter["etotal"] * 1000), AlwaysUpdate=True) inputVoltage,inputAmps = inverter["pv_input_1"].split('/') inputPower = (float(inputVoltage[:-1])) * (float(inputAmps[:-1])) - #Domoticz.Debug("power calc = V: '"+inputVoltage[:-1]+"', A: '"+inputAmps[:-1]+"', power: W: '" + str(inputPower) + "'") UpdateDevice(theInverter.inputVoltage1Unit, 0, inputVoltage, AlwaysUpdate=True) UpdateDevice(theInverter.inputAmps1Unit, 0, inputAmps, AlwaysUpdate=True) UpdateDevice(theInverter.inputPower1Unit, 0, str(inputPower) + ";0", AlwaysUpdate=True) @@ -309,23 +330,23 @@ def onMessage(self, Connection, Data): previousPower,currentCount = Devices[theInverter.inputPowerTest].sValue.split(";") newCounter = inputPower + float(currentCount) UpdateDevice(theInverter.inputPowerTest, 0, str(inputPower) + ";" + str(newCounter), AlwaysUpdate=True) - Domoticz.Debug("test previousPower, currentCount, newCounter = " + str(previousPower) + ", " + str(currentCount) + ", " + str(newCounter)) + logging.debug("test previousPower, currentCount, newCounter = " + str(previousPower) + ", " + str(currentCount) + ", " + str(newCounter)) if "pv_input_2" in inverter: - Domoticz.Debug("Second string found") + logging.debug("Second string found") inputVoltage,inputAmps = inverter["pv_input_2"].split('/') UpdateDevice(theInverter.inputVoltage2Unit, 0, inputVoltage, AlwaysUpdate=True) UpdateDevice(theInverter.inputAmps2Unit, 0, inputAmps, AlwaysUpdate=True) inputPower = (float(inputVoltage[:-1])) * (float(inputAmps[:-1])) UpdateDevice(theInverter.inputPower2Unit, 0, str(inputPower) + ";0", AlwaysUpdate=True) if "pv_input_3" in inverter: - Domoticz.Debug("Third string found") + logging.debug("Third string found") inputVoltage,inputAmps = inverter["pv_input_3"].split('/') UpdateDevice(theInverter.inputVoltage3Unit, 0, inputVoltage, AlwaysUpdate=True) UpdateDevice(theInverter.inputAmps3Unit, 0, inputAmps, AlwaysUpdate=True) inputPower = float(inputVoltage[:-1]) * float(inputAmps[:-1]) UpdateDevice(theInverter.inputPower3Unit, 0, str(inputPower) + ";0", AlwaysUpdate=True) if "pv_input_4" in inverter: - Domoticz.Debug("Fourth string found") + logging.debug("Fourth string found") inputVoltage,inputAmps = inverter["pv_input_4"].split('/') UpdateDevice(theInverter.inputVoltage4Unit, 0, inputVoltage, AlwaysUpdate=True) UpdateDevice(theInverter.inputAmps4Unit, 0, inputAmps, AlwaysUpdate=True) @@ -333,53 +354,57 @@ def onMessage(self, Connection, Data): UpdateDevice(theInverter.inputPower4Unit, 0, str(inputPower) + ";0", AlwaysUpdate=True) else: Domoticz.Log("Unknown inverter found with S/N: '" + inverter["sn"] +"'.") + logging.info("Unknown inverter found with S/N: '" + inverter["sn"] +"'.") if self.goodWeAccount.powerStationIndex == (len(self.goodWeAccount.powerStationList) - 1): - Domoticz.Debug("Last station of list found") + logging.debug("Last station of list found") if self.runAgain > 2: - Domoticz.Log("Next active heartbeat far away, disconnecting and dropping connection.") + logging.info("Next active heartbeat far away, disconnecting and dropping connection.") self.httpConn.Disconnect() - #self.httpConn = None - #Domoticz.Log("Updated " + str(len(Devices)) + " device(s) for " + str(len(self.goodWeAccount.powerStationList)) + " station(s) with " + str(int(self.baseDeviceIndex / self.maxDeviceIndex)) + " inverter(s)") self.devicesUpdated = True else: - Domoticz.Debug("Retrieving next station data (ID: " + self.goodWeAccount.powerStationList[self.baseDeviceIndex] + ")") + logging.debug("Retrieving next station data (ID: " + self.goodWeAccount.powerStationList[self.baseDeviceIndex] + ")") self.baseDeviceIndex += 1 Connection.Send(self.goodWeAccount.stationDataRequest(self.baseDeviceIndex)) elif status == 302: + logging.error("GoodWe SEMS API returned a Page Moved Error.") Domoticz.Error("GoodWe SEMS API returned a Page Moved Error.") elif status == 400: + logging.error("GoodWe SEMS API returned a Bad Request Error.") Domoticz.Error("GoodWe SEMS API returned a Bad Request Error.") elif (status == 500): + logging.error("GoodWe SEMS API returned a Server Error.") Domoticz.Error("GoodWe SEMS API returned a Server Error.") else: + logging.error("GoodWe SEMS API returned a status: " + str(status)) Domoticz.Error("GoodWe SEMS API returned a status: " + str(status)) def onCommand(self, Unit, Command, Level, Hue): - Domoticz.Debug("onCommand called for Unit " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level)) + logging.debug("onCommand called for Unit " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level)) def onDisconnect(self, Connection): - Domoticz.Debug("onDisconnect called for connection to: " + Connection.Address + ":" + Connection.Port) + logging.debug("onDisconnect called for connection to: " + Connection.Address + ":" + Connection.Port) self.httpConn = None def onHeartbeat(self): if self.httpConn is not None and (self.httpConn.Connecting() or self.httpConn.Connected()) and not self.devicesUpdated: - Domoticz.Debug("onHeartbeat called, Connection is alive.") + logging.debug("onHeartbeat called, Connection is alive.") elif len(Parameters["Mode1"]) == 0: Domoticz.Error("No Power Station ID provided, exiting") + logging.error("No Power Station ID provided, exiting") return else: self.runAgain = self.runAgain - 1 if self.runAgain <= 0: - Domoticz.Debug("onHeartbeat called, starting device update.") + logging.debug("onHeartbeat called, starting device update.") self.startDeviceUpdateV2() self.runAgain = int(Parameters["Mode2"]) else: - Domoticz.Debug("onHeartbeat called, run again in " + str(self.runAgain) + " heartbeats.") + logging.debug("onHeartbeat called, run again in " + str(self.runAgain) + " heartbeats.") global _plugin _plugin = GoodWeSEMSPlugin() @@ -396,12 +421,12 @@ def calculateNewEnergy(Unit, inputPower): dt_string = Devices[Unit].LastUpdate lastUpdateDT = datetime.fromtimestamp(time.mktime(time.strptime(dt_string, dt_format))) elapsedTime = datetime.now() - lastUpdateDT - Domoticz.Debug("Test power, previousPower: {}, last update: {:%Y-%m-%d %H:%M}, elapsedTime: {}, elapsedSeconds: {:6.2f}".format(previousPower, lastUpdateDT, elapsedTime, elapsedTime.total_seconds())) + logging.debug("Test power, previousPower: {}, last update: {:%Y-%m-%d %H:%M}, elapsedTime: {}, elapsedSeconds: {:6.2f}".format(previousPower, lastUpdateDT, elapsedTime, elapsedTime.total_seconds())) #average current and previous power (Watt) and multiply by elapsed time (hour) to get Watt hour newCount = round(((float(previousPower) + inputPower ) / 2) * elapsedTime.total_seconds()/3600,2) newCounter = newCount + float(currentCount) #add the amount of energy since last update to the already logged energy - Domoticz.Debug("Test power, previousPower: {}, currentCount: {:6.2f}, newCounter: {:6.2f}, added: {:6.2f}".format(previousPower, float(currentCount), newCounter, newCount)) + logging.debug("Test power, previousPower: {}, currentCount: {:6.2f}, newCounter: {:6.2f}, added: {:6.2f}".format(previousPower, float(currentCount), newCounter, newCount)) return newCounter def UpdateDevice(Unit, nValue, sValue, BatteryLevel=255, AlwaysUpdate=False): @@ -413,7 +438,7 @@ def UpdateDevice(Unit, nValue, sValue, BatteryLevel=255, AlwaysUpdate=False): Devices[Unit].Update(nValue, str(sValue), BatteryLevel=BatteryLevel) - Domoticz.Debug("Update %s: nValue %s - sValue %s - BatteryLevel %s" % ( + logging.debug("Update %s: nValue %s - sValue %s - BatteryLevel %s" % ( Devices[Unit].Name, nValue, sValue, @@ -423,42 +448,34 @@ def onStart(): global _plugin _plugin.onStart() - def onStop(): global _plugin _plugin.onStop() - def onConnect(Connection, Status, Description): global _plugin _plugin.onConnect(Connection, Status, Description) - def onMessage(Connection, Data): global _plugin _plugin.onMessage(Connection, Data) - def onCommand(Unit, Command, Level, Hue): global _plugin _plugin.onCommand(Unit, Command, Level, Hue) - def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile): global _plugin _plugin.onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile) - def onDisconnect(Connection): global _plugin _plugin.onDisconnect(Connection) - def onHeartbeat(): global _plugin _plugin.onHeartbeat() - # Generic helper functions def LogMessage(Message): if Parameters["Mode6"] == "File": @@ -466,30 +483,29 @@ def LogMessage(Message): f.write(Message) f.close() Domoticz.Log("File written") - + logging.info("File written") def DumpConfigToLog(): for x in Parameters: if Parameters[x] != "": - Domoticz.Debug("'" + x + "':'" + str(Parameters[x]) + "'") - Domoticz.Debug("Device count: " + str(len(Devices))) + logging.debug("'" + x + "':'" + str(Parameters[x]) + "'") + logging.debug("Device count: " + str(len(Devices))) for x in Devices: - Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x])) - Domoticz.Debug("Device ID: '" + str(Devices[x].ID) + "'") - Domoticz.Debug("Device Name: '" + Devices[x].Name + "'") - Domoticz.Debug("Device nValue: " + str(Devices[x].nValue)) - Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'") - Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel)) + logging.debug("Device: " + str(x) + " - " + str(Devices[x])) + logging.debug("Device ID: '" + str(Devices[x].ID) + "'") + logging.debug("Device Name: '" + Devices[x].Name + "'") + logging.debug("Device nValue: " + str(Devices[x].nValue)) + logging.debug("Device sValue: '" + Devices[x].sValue + "'") + logging.debug("Device LastLevel: " + str(Devices[x].LastLevel)) return - def DumpHTTPResponseToLog(httpDict): if isinstance(httpDict, dict): - Domoticz.Debug("HTTP Details (" + str(len(httpDict)) + "):") + logging.debug("HTTP Details (" + str(len(httpDict)) + "):") for x in httpDict: if isinstance(httpDict[x], dict): - Domoticz.Debug("--->'" + x + " (" + str(len(httpDict[x])) + "):") + logging.debug("--->'" + x + " (" + str(len(httpDict[x])) + "):") for y in httpDict[x]: - Domoticz.Debug("------->'" + y + "':'" + str(httpDict[x][y]) + "'") + logging.debug("------->'" + y + "':'" + str(httpDict[x][y]) + "'") else: - Domoticz.Debug("--->'" + x + "':'" + str(httpDict[x]) + "'") + logging.debug("--->'" + x + "':'" + str(httpDict[x]) + "'") diff --git a/plugin_test.py b/plugin_test.py index b95ed10..4e68d6c 100644 --- a/plugin_test.py +++ b/plugin_test.py @@ -2,6 +2,7 @@ from GoodWe import GoodWe from GoodWe import PowerStation from GoodWe import Inverter +import logging class BasicInverterTest(unittest.TestCase): @@ -37,6 +38,7 @@ class PowerStationTest(unittest.TestCase): def setUp(self): print("starting the test") + logging.info("starting the test") def test_starting_out(self): self.assertEqual(1, 1) @@ -74,6 +76,7 @@ def test_singlePowerStation(self): stationData=self.powerStationApiDataSingle ) print("Created power station: '" + str(self.powerStationSingle) + "'") + logging.info("Created power station: '" + str(self.powerStationSingle) + "'") self.assertEqual( self.powerStationSingle.id, "a73d66f6-aa49-428e-9f93-bdd781f04b7e", @@ -120,6 +123,7 @@ def test_doublePowerStation(self): self.powerStationDouble = PowerStation( stationData=self.powerStationApiDataDouble ) + logging.info("Created power station: '" + str(self.powerStationDouble) + "'") print("Created power station: '" + str(self.powerStationDouble) + "'") self.assertEqual( self.powerStationDouble.id, @@ -138,6 +142,7 @@ def test_doublePowerStation(self): # def test_doublePowerStation(self): def tearDown(self): + logging.info("tearing down the house") print("tearing down the house") self.powerStationSingle = None self.powerStationDouble = None @@ -145,6 +150,7 @@ def tearDown(self): def main(): + logging.basicConfig(format='%(asctime)s - %(levelname)-8s - %(filename)-18s - %(message)s', filename="goodwe_test.log",level=logging.DEBUG) unittest.main() From 9a22352115323e976b84980c324bc7719ef6392a Mon Sep 17 00:00:00 2001 From: janjaapko Date: Thu, 28 Apr 2022 22:17:26 +0200 Subject: [PATCH 2/6] fix logging --- plugin.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/plugin.py b/plugin.py index b7c86c9..3ebf05c 100644 --- a/plugin.py +++ b/plugin.py @@ -84,6 +84,7 @@ class GoodWeSEMSPlugin: runAgain = 6 devicesUpdated = False goodWeAccount = None + logger = None baseDeviceIndex = 0 maxDeviceIndex = 0 @@ -180,16 +181,17 @@ def updateDevices(self, apiData): UpdateDevice(theInverter.inputPower4Unit, 0, "{:5.1f};{:10.2f}".format(inputPower, newCounter), AlwaysUpdate=True) def onStart(self): + self.logger = logging.getLogger('root') if Parameters["Mode6"] == "Verbose": Domoticz.Debugging(1) - DumpConfigToLog() logging.basicConfig(format='%(asctime)s - %(levelname)-8s - %(filename)-18s - %(message)s', filename=self.log_filename,level=logging.DEBUG) Domoticz.Status("Starting Goodwe SEMS API plugin, logging to file {0}".format(self.log_filename)) + DumpConfigToLog() elif Parameters["Mode6"] == "Debug": Domoticz.Debugging(2) - DumpConfigToLog() logging.basicConfig(format='%(asctime)s - %(levelname)-8s - %(filename)-18s - %(message)s', filename=self.log_filename,level=logging.DEBUG) Domoticz.Status("Starting Goodwe SEMS API plugin, logging to file {0}".format(self.log_filename)) + DumpConfigToLog() else: Domoticz.Status("Starting Goodwe SEMS API plugin, logging to Domoticz") @@ -486,17 +488,13 @@ def LogMessage(Message): logging.info("File written") def DumpConfigToLog(): + Domoticz.Debug("Parameters count: " + str(len(Parameters))) for x in Parameters: if Parameters[x] != "": - logging.debug("'" + x + "':'" + str(Parameters[x]) + "'") - logging.debug("Device count: " + str(len(Devices))) + Domoticz.Debug("Parameter: '" + x + "':'" + str(Parameters[x]) + "'") + Domoticz.Debug("Device count: " + str(len(Devices))) for x in Devices: - logging.debug("Device: " + str(x) + " - " + str(Devices[x])) - logging.debug("Device ID: '" + str(Devices[x].ID) + "'") - logging.debug("Device Name: '" + Devices[x].Name + "'") - logging.debug("Device nValue: " + str(Devices[x].nValue)) - logging.debug("Device sValue: '" + Devices[x].sValue + "'") - logging.debug("Device LastLevel: " + str(Devices[x].LastLevel)) + Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x])) return def DumpHTTPResponseToLog(httpDict): From 81ae33c48130ebdb2ad54468e0baef2ce5b2f832 Mon Sep 17 00:00:00 2001 From: Jan-Jaap Kostelijk <44156144+JanJaapKo@users.noreply.github.com> Date: Sat, 30 Apr 2022 10:51:21 +0200 Subject: [PATCH 3/6] Update README.md Add full list of created device units --- README.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7fb74f0..cb1bfb4 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,28 @@ Current features 1. Get all stations for a specific user account 2. Automatically get data for all inverters (for one station) 3. The following devices are added to Domoticz for each inverter: - - temperature: Inverter temperature (Celcius) - - power: Current and total output power (Watts) - - current - Output current (ampere) - - voltage - Output Voltage - - frequency - Output frequncy (Hz, first phase) - - input strings: Voltage, current and power per string + +|Unit |Description |Type |Remark +|--- |--- |--- |--- +|1 |(Hardware name) - Inverter temperature (SN: (your S/N)) |LaCrosse TX3 | +|2 |(Hardware name) - Inverter output current (SN: (your S/N)) |Current | +|3 |(Hardware name) - Inverter output voltage (SN: (your S/N)) |Voltage | +|4 |(Hardware name) - Inverter output power (SN: (your S/N)) |kWh | default: used +|5 |(Hardware name) - Inverter input 1 voltage (SN: (your S/N)) |Voltage | +|6 |(Hardware name) - Inverter input 1 Current (SN: (your S/N)) |Current | +|7 |(Hardware name) - Inverter input 2 voltage (SN: (your S/N)) |Voltage | +|8 |(Hardware name) - Inverter input 2 Current (SN: (your S/N)) |Current | +|9 |(Hardware name) - Inverter state (SN: (your S/N)) |Selector Switch | default: used +|10 |(Hardware name) - Solar inverter input 3 voltage (SN: (your S/N)) |Voltage| +|11 |(Hardware name) - Solar inverter input 3 Current (SN: (your S/N)) |Current| +|12 |(Hardware name) - Solar inverter input 4 voltage (SN: (your S/N)) |Voltage| +|13 |(Hardware name) - Solar inverter input 4 Current (SN: (your S/N)) |Current| +|14 |(Hardware name) - Inverter input 1 power (SN: (your S/N)) |kWh | default: used. calculated in plugin +|15 |(Hardware name) - Inverter input 2 power (SN: (your S/N)) |kWh | calculated in plugin +|16 |(Hardware name) - Inverter input 3 power (SN: (your S/N)) |kWh | calculated in plugin +|17 |(Hardware name) - Inverter input 4 power (SN: (your S/N)) |kWh | calculated in plugin +|18 |(Hardware name) - Inverter output frequency 1 |Custom Sensor | + There is a lot more information available trough the GoodWe API if you would like to have a specific feature added to this plugin please submit an issue as indicated in the paragraph above. From 09bc67757066af846a242298a9441d125efc5834 Mon Sep 17 00:00:00 2001 From: janjaapko Date: Sat, 30 Apr 2022 13:18:51 +0200 Subject: [PATCH 4/6] add logging of some battery fields --- GoodWe.py | 2 +- plugin.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/GoodWe.py b/GoodWe.py index 89625af..fc65547 100644 --- a/GoodWe.py +++ b/GoodWe.py @@ -122,7 +122,7 @@ def createDevices(self, Devices): "SelectorStyle": "1"} Domoticz.Device(Name="Inverter state (SN: " + self.serialNumber + ")", Unit=(self.inverterStateUnit), TypeName="Selector Switch", Image=1, - Options=Options).Create() + Options=Options, Used=1).Create() if self.inputVoltage1Unit not in Devices: Domoticz.Device(Name="Inverter input 1 voltage (SN: " + self.serialNumber + ")", diff --git a/plugin.py b/plugin.py index 3ebf05c..fe79c7d 100644 --- a/plugin.py +++ b/plugin.py @@ -17,7 +17,7 @@ # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ - +

GoodWe inverter (via SEMS portal)

This plugin uses the GoodWe SEMS api to retrieve the status information of your GoodWe inverter.

@@ -179,6 +179,9 @@ def updateDevices(self, apiData): inputPower = float(inputVoltage[:-1]) * float(inputAmps[:-1]) newCounter = calculateNewEnergy(theInverter.inputPower4Unit, inputPower) UpdateDevice(theInverter.inputPower4Unit, 0, "{:5.1f};{:10.2f}".format(inputPower, newCounter), AlwaysUpdate=True) + #log data of battery + Domoticz.Debug("Battery values: battery: '{0}', bms_status: '{1}', battery_power: '{2}'".format(inverter["battery"],inverter["bms_status"],inverter["battery_power"])) + logging.debug("Battery values: battery: '{0}', bms_status: '{1}', battery_power: '{2}'".format(inverter["battery"],inverter["bms_status"],inverter["battery_power"])) def onStart(self): self.logger = logging.getLogger('root') From e67ed7d4566c4d3556137af5e127bec348e26d95 Mon Sep 17 00:00:00 2001 From: janjaapko Date: Fri, 17 Jun 2022 21:00:51 +0200 Subject: [PATCH 5/6] add logging of station data response --- GoodWe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoodWe.py b/GoodWe.py index fc65547..1ce2ab3 100644 --- a/GoodWe.py +++ b/GoodWe.py @@ -425,6 +425,6 @@ def stationDataRequest(self, stationId): r = requests.post(self.base_url + url, headers=self.apiRequestHeadersV2(), data=payload, timeout=5) logging.debug("building station data request on URL: " + r.url + " which returned status code: " + str(r.status_code) + " and response length = " + str(len(r.text))) - #logging.debug("response station data request : " + json.dumps(r.json())) + logging.debug("response station data request : " + json.dumps(r.json())) responseData = r.json() return responseData From f4351e679573ec18e67e2301867c60ba5e6a5328 Mon Sep 17 00:00:00 2001 From: janjaapko Date: Tue, 6 Sep 2022 10:44:06 +0200 Subject: [PATCH 6/6] increase read timeout to decrease errors --- GoodWe.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/GoodWe.py b/GoodWe.py index 1ce2ab3..5952e2a 100644 --- a/GoodWe.py +++ b/GoodWe.py @@ -330,7 +330,14 @@ def tokenRequest(self): 'pwd': self.Password, } - r = requests.post(self.base_url + url, headers=self.apiRequestHeadersV2(), data=loginPayload, timeout=5) + try: + r = requests.post(self.base_url + url, headers=self.apiRequestHeadersV2(), data=loginPayload, timeout=10) + except requests.exceptions.RequestException as exp: + logging.error("RequestException: " + str(exp)) + Domoticz.Error("RequestException: " + str(exp)) + self.tokenAvailable = False + return + #r.raise_for_status() logging.debug("building token request on URL: " + r.url + " which returned status code: " + str(r.status_code) + " and response length = " + str(len(r.text))) apiResponse = r.json() @@ -423,7 +430,7 @@ def stationDataRequest(self, stationId): 'powerStationId' : stationId } - r = requests.post(self.base_url + url, headers=self.apiRequestHeadersV2(), data=payload, timeout=5) + r = requests.post(self.base_url + url, headers=self.apiRequestHeadersV2(), data=payload, timeout=10) logging.debug("building station data request on URL: " + r.url + " which returned status code: " + str(r.status_code) + " and response length = " + str(len(r.text))) logging.debug("response station data request : " + json.dumps(r.json())) responseData = r.json()