diff --git a/README.md b/README.md index 03fbd18..dc01c8b 100755 --- a/README.md +++ b/README.md @@ -60,12 +60,20 @@ Any methods that may be useful. `api.get_plant_settings(plant_id)` Get the current settings for the specified plant +`api.is_plant_noah_system(plant_id)` Get the Information if noah devices are configured for the specified plant + +`api.noah_system_status(serial_number)` Get the current status for the specified noah device e.g. workMode, soc, chargePower, disChargePower, current import/export etc. + +`api.noah_info(serial_number)` Get all information for the specified noah device e.g. configured Operation Modes, configured Battery Management charging upper & lower limit, configured System Default Output Power, Firmware Version + `api.update_plant_settings(plant_id, changed_settings, current_settings)` Update the settings for a plant to the values specified in the dictionary, if the `current_settings` are not provided it will look them up automatically using the `get_plant_settings` function - See 'Plant settings' below for more information `api.update_mix_inverter_setting(serial_number, setting_type, parameters)` Applies the provided parameters (dictionary or array) for the specified setting on the specified mix inverter; see 'Inverter settings' below for more information `api.update_ac_inverter_setting(serial_number, setting_type, parameters)` Applies the provided parameters (dictionary or array) for the specified setting on the specified AC-coupled inverter; see 'Inverter settings' below for more information +`api.update_noah_settings(serial_number, setting_type, parameters)` Applies the provided parameters (dictionary or array) for the specified setting on the specified noah device; see 'Noah settings' below for more information + ### Variables Some variables you may want to set. @@ -186,6 +194,41 @@ Known working settings & parameters are as follows (all parameter values are str The three functions `update_mix_inverter_setting`, `update_ac_inverter_setting`, and `update_inverter_setting` take either a dictionary or an array. If an array is passed it will automatically generate the `paramN` key based on array index since all params for settings seem to used the same numbering scheme. +## Noah Settings +The noah settings function allow you to change individual values on your noah system e.g. system default output power, battery management, operation mode and currency +From what has been reverse engineered from the api, each setting has a `setting_type` and a set of `parameters` that are relevant to it. + +Known working settings & parameters are as follows (all parameter values are strings): +* **Change "System Default Output Power"** + * function: `api.update_noah_settings` + * setting type: `default_power` + * params: + * `param1`: System default output power in watt +* **Change "Battery Management"** + * function: `api.update_noah_settings` + * setting type: `charging_soc` + * params: + * `param1`: Charge upper limit in % + * `param2`: Charge lower limit in % +* **Change "Operation Mode" Time Segment** + * function: `api.update_noah_settings` + * setting type: `time_segment` key from `api.noah_info(serial_number)`, for new `time_segment` count the ending number up + * params: + * `param1`: Workingmode (0 = Load First, 1 = Battery First) + * `param2`: Start time - Hour e.g. "01" (1am) + * `param3`: Start time - Minute e.g. "00" (0 minutes) + * `param4`: End time - Hour e.g. "02" (2am) + * `param5`: End time - Minute e.g. "00" (0 minutes) + * `param6`: Output power in watt (For Workingmode "Battery First" always "0") + * `param7`: Enabled/Disabled (0 = Disabled, 1 = Enabled) +* **Change "Currency"** + * function: `api.update_noah_settings` + * setting type: `updatePlantMoney` + * params: + * `param1`: Plant Id + * `param2`: Cost per kWh e.g. "0.22" + * `param3`: Unit value from `api.noah_info(serial_number)` - `unitList` + ## Settings Discovery The settings for the Plant and Inverter have been reverse engineered by using the ShinePhone Android App and the NetCapture SSL application together to inspect the API calls that are made by the application and the parameters that are provided with it. diff --git a/examples/noah_example.py b/examples/noah_example.py new file mode 100644 index 0000000..cc899f4 --- /dev/null +++ b/examples/noah_example.py @@ -0,0 +1,80 @@ +import growattServer +import datetime +import getpass +import pprint + +""" +This is a very trivial script that logs into a user's account and prints out useful data for a "NOAH" system. +This has been tested against my personal system (NOAH2000) which is a 2kW Balcony Storage system. + +Throughout the script there are points where 'pp.pprint' has been commented out. If you wish to see all the data that is returned from those +specific library calls, just uncomment them and they will appear as part of the output. +""" + +pp = pprint.PrettyPrinter(indent=4) + +""" +A really hacky function to allow me to print out things with an indent in-front +""" +def indent_print(to_output, indent): + indent_string = "" + for x in range(indent): + indent_string += " " + print(indent_string + to_output) + +#Prompt user for username +username=input("Enter username:") + +#Prompt user to input password +user_pass=getpass.getpass("Enter password:") + +api = growattServer.GrowattApi() +login_response = api.login(username, user_pass) + +plant_list = api.plant_list(login_response['user']['id']) +#pp.pprint(plant_list) + +print("***Totals for all plants***") +pp.pprint(plant_list['totalData']) +print("") + +print("***List of plants***") +for plant in plant_list['data']: + indent_print("ID: %s, Name: %s"%(plant['plantId'], plant['plantName']), 2) +print("") + +for plant in plant_list['data']: + plant_id = plant['plantId'] + plant_name = plant['plantName'] + plant_info=api.plant_info(plant_id) + #pp.pprint(plant_info) + print("***Info for Plant %s - %s***"%(plant_id, plant_name)) + #There are more values in plant_info, but these are some of the useful/interesting ones + indent_print("CO2 Reducion: %s"%(plant_info['Co2Reduction']),2) + indent_print("Nominal Power (w): %s"%(plant_info['nominal_Power']),2) + indent_print("Solar Energy Today (kw): %s"%(plant_info['todayEnergy']),2) + indent_print("Solar Energy Total (kw): %s"%(plant_info['totalEnergy']),2) + print("") + indent_print("Devices in plant:",2) + for device in plant_info['deviceList']: + device_sn = device['deviceSn'] + device_type = device['deviceType'] + indent_print("- Device - SN: %s, Type: %s"%(device_sn, device_type),4) + + is_noah = api.is_plant_noah_system(plant['plantId']) + if is_noah['result'] == 1 and (is_noah['obj']['isPlantNoahSystem'] or is_noah['obj']['isPlantHaveNoah']): + device_sn = is_noah['obj']['deviceSn'] + indent_print("**NOAH - SN: %s**"%(device_sn),2) + + noah_system = api.noah_system_status(is_noah['obj']['deviceSn']) + pp.pprint(noah_system['obj']) + print("") + + noah_infos = api.noah_info(is_noah['obj']['deviceSn']) + pp.pprint(noah_infos['obj']['noah']) + print("") + indent_print("Remaining battery (" + "%" + "): %s"%(noah_system['obj']['soc']),2) + indent_print("Solar Power (w): %s"%(noah_system['obj']['ppv']),2) + indent_print("Charge Power (w): %s"%(noah_system['obj']['chargePower']),2) + indent_print("Discharge Power (w): %s"%(noah_system['obj']['disChargePower']),2) + indent_print("Output Power (w): %s"%(noah_system['obj']['pac']),2) \ No newline at end of file diff --git a/growattServer/__init__.py b/growattServer/__init__.py index 24fc9dd..bb8a685 100755 --- a/growattServer/__init__.py +++ b/growattServer/__init__.py @@ -522,7 +522,7 @@ def plant_info(self, plant_id): Get basic plant information with device list. """ response = self.session.get(self.get_url('newTwoPlantAPI.do'), params={ - 'op': 'getAllDeviceList', + 'op': 'getAllDeviceListTwo', 'plantId': plant_id, 'pageNum': 1, 'pageSize': 1 @@ -547,6 +547,109 @@ def get_plant_settings(self, plant_id): }) data = json.loads(response.content.decode('utf-8')) return data + + def is_plant_noah_system(self, plant_id): + """ + Returns a dictionary containing if noah devices are configured for the specified plant + + Keyword arguments: + plant_id -- The id of the plant you want the noah devices of (str) + + Returns + 'msg' + 'result' -- True or False + 'obj' -- An Object containing if noah devices are configured + 'isPlantNoahSystem' -- Is the specified plant a noah system (True or False) + 'plantId' -- The ID of the plant + 'isPlantHaveNoah' -- Are noah devices configured in the specified plant (True or False) + 'deviceSn' -- Serial number of the configured noah device + 'plantName' -- Friendly name of the plant + """ + response = self.session.post(self.get_url('noahDeviceApi/noah/isPlantNoahSystem'), data={ + 'plantId': plant_id + }) + data = json.loads(response.content.decode('utf-8')) + return data + + def noah_system_status(self, serial_number): + """ + Returns a dictionary containing the status for the specified Noah Device + + Keyword arguments: + serial_number -- The Serial number of the noah device you want the status of (str) + + Returns + 'msg' + 'result' -- True or False + 'obj' -- An Object containing the noah device status + 'chargePower' -- Battery charging rate in watt e.g. '200Watt' + 'workMode' -- Workingmode of the battery (0 = Load First, 1 = Battery First) + 'soc' -- Statement of charge (remaining battery %) + 'associatedInvSn' -- ??? + 'batteryNum' -- Numbers of batterys + 'profitToday' -- Today generated profit through noah device + 'plantId' -- The ID of the plant + 'disChargePower' -- Battery discharging rate in watt e.g. '200Watt' + 'eacTotal' -- Total energy exported to the grid in kWh e.g. '20.5kWh' + 'eacToday' -- Today energy exported to the grid in kWh e.g. '20.5kWh' + 'pac' -- Export to grid rate in watt e.g. '200Watt' + 'ppv' -- Solar generation in watt e.g. '200Watt' + 'alias' -- Friendly name of the noah device + 'profitTotal' -- Total generated profit through noah device + 'moneyUnit' -- Unit of currency e.g. '€' + 'status' -- Is the noah device online (True or False) + """ + response = self.session.post(self.get_url('noahDeviceApi/noah/getSystemStatus'), data={ + 'deviceSn': serial_number + }) + data = json.loads(response.content.decode('utf-8')) + return data + + def noah_info(self, serial_number): + """ + Returns a dictionary containing the informations for the specified Noah Device + + Keyword arguments: + serial_number -- The Serial number of the noah device you want the informations of (str) + + Returns + 'msg' + 'result' -- True or False + 'obj' -- An Object containing the noah device informations + 'neoList' -- A List containing Objects + 'unitList' -- A Object containing currency units e.g. "Euro": "euro", "DOLLAR": "dollar" + 'noah' -- A Object containing the folowing + 'time_segment' -- A List containing Objects with configured "Operation Mode" + NOTE: The keys are generated numerical, the values are generated with folowing syntax "[workingmode (0 = Load First, 1 = Battery First)]_[starttime]_[endtime]_[output power]" + 'time_segment': { + 'time_segment1': "0_0:0_8:0_150", ([Load First]_[00:00]_[08:00]_[150 watt]) + 'time_segment2': "1_8:0_18:0_0", ([Battery First]_[08:00]_[18:00]_[0 watt]) + .... + } + 'batSns' -- A List containing all battery Serial Numbers + 'associatedInvSn' -- ??? + 'plantId' -- The ID of the plant + 'chargingSocHighLimit' -- Configured "Battery Management" charging upper limit + 'chargingSocLowLimit' -- Configured "Battery Management" charging lower limit + 'defaultPower' -- Configured "System Default Output Power" + 'version' -- The Firmware Version of the noah device + 'deviceSn' -- The Serial number of the noah device + 'formulaMoney' -- Configured "Select Currency" energy cost per kWh e.g. '0.22' + 'alias' -- Friendly name of the noah device + 'model' -- Model Name of the noah device + 'plantName' -- Friendly name of the plant + 'tempType' -- ??? + 'moneyUnitText' -- Configured "Select Currency" (Value from the unitList) e.G. "euro" + 'plantList' -- A List containing Objects containing the folowing + 'plantId' -- The ID of the plant + 'plantImgName' -- Friendly name of the plant Image + 'plantName' -- Friendly name of the plant + """ + response = self.session.post(self.get_url('noahDeviceApi/noah/getNoahInfoBySn'), data={ + 'deviceSn': serial_number + }) + data = json.loads(response.content.decode('utf-8')) + return data def update_plant_settings(self, plant_id, changed_settings, current_settings = None): """ @@ -669,3 +772,36 @@ def update_ac_inverter_setting(self, serial_number, setting_type, parameters): } return self.update_inverter_setting(serial_number, setting_type, default_parameters, parameters) + + def update_noah_settings(self, serial_number, setting_type, parameters): + """ + Applies settings for specified noah device based on serial number + See README for known working settings + + Arguments: + serial_number -- Serial number (device_sn) of the noah (str) + setting_type -- Setting to be configured (str) + parameters -- Parameters to be sent to the system (dict or list of str) + (array which will be converted to a dictionary) + + Returns: + JSON response from the server whether the configuration was successful + """ + default_parameters = { + 'serialNum': serial_number, + 'type': setting_type + } + settings_parameters = parameters + + #If we've been passed an array then convert it into a dictionary + if isinstance(parameters, list): + settings_parameters = {} + for index, param in enumerate(parameters, start=1): + settings_parameters['param' + str(index)] = param + + settings_parameters = {**default_parameters, **settings_parameters} + + response = self.session.post(self.get_url('noahDeviceApi/noah/set'), + data=settings_parameters) + data = json.loads(response.content.decode('utf-8')) + return data \ No newline at end of file