├── Resources ├── SRNE-MODBUS.pdf ├── Solar_inverter_charger_register_definition.pdf ├── SRNE hybrid solar inverter MODBUS protocol V1 7.pdf ├── SRNE.Solar.Charge.Inverter.MODBUS.Protocol1.96.pdf └── Solar_inverter_charger_communication_protocol.pdf ├── README.md └── src ├── validator.py ├── output.json ├── srnecommands.py ├── inverter-monitor.py ├── main.py └── SRNEinverter.py /Resources/SRNE-MODBUS.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakthisachintha/SRNE-Hybrid-Inverter-Monitor/HEAD/Resources/SRNE-MODBUS.pdf -------------------------------------------------------------------------------- /Resources/Solar_inverter_charger_register_definition.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakthisachintha/SRNE-Hybrid-Inverter-Monitor/HEAD/Resources/Solar_inverter_charger_register_definition.pdf -------------------------------------------------------------------------------- /Resources/SRNE hybrid solar inverter MODBUS protocol V1 7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakthisachintha/SRNE-Hybrid-Inverter-Monitor/HEAD/Resources/SRNE hybrid solar inverter MODBUS protocol V1 7.pdf -------------------------------------------------------------------------------- /Resources/SRNE.Solar.Charge.Inverter.MODBUS.Protocol1.96.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakthisachintha/SRNE-Hybrid-Inverter-Monitor/HEAD/Resources/SRNE.Solar.Charge.Inverter.MODBUS.Protocol1.96.pdf -------------------------------------------------------------------------------- /Resources/Solar_inverter_charger_communication_protocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakthisachintha/SRNE-Hybrid-Inverter-Monitor/HEAD/Resources/Solar_inverter_charger_communication_protocol.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SRNE Hybrid Inverter Monitor 2 | 3 | Python based program to fetch data from the inverter and send it to a cloud for remote monitoring of the inverter. 4 | This project is mainly based on (https://github.com/jgimbel/snre-solar-inverter-mqtt) project. 5 | -------------------------------------------------------------------------------- /src/validator.py: -------------------------------------------------------------------------------- 1 | class Validator(): 2 | def __init__(self, value: [int, float]): 3 | self._value = value 4 | self._validated = True 5 | self._error = "" 6 | self._validator = "" 7 | 8 | def maximum(self, maxlimit=[int, float], message: str = ""): 9 | if not self._validated: 10 | return self 11 | err = f"Value should be less than or equal {maxlimit}." if message == "" else message 12 | if (self._value <= maxlimit): 13 | return self 14 | self._validated = False 15 | self._error = err 16 | self._validator = 'maximum' 17 | return self 18 | 19 | def minimum(self, minlimit=[int, float], message: str = ""): 20 | if not self._validated: 21 | return self 22 | err = f"Value should be greater than or equal {minlimit}." if message == "" else message 23 | if (self._value >= minlimit): 24 | return self 25 | self._validated = False 26 | self._error = err 27 | self._validator = 'minimum' 28 | return self 29 | 30 | def multiple(self, multiplier=int, message: str = ""): 31 | if not self._validated: 32 | return self 33 | err = f"Value is not a multiple of {multiplier}." if message == "" else message 34 | if (self._value % multiplier == 0): 35 | return self 36 | self._validated = False 37 | self._error = err 38 | self._validator = 'multiple' 39 | return self 40 | 41 | def validate(self): 42 | if self._validated: 43 | return True 44 | else: 45 | return { 46 | 'message': self._error, 47 | 'validation': self._validator 48 | } -------------------------------------------------------------------------------- /src/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "batteryVoltage": { 3 | "value": 26.1, 4 | "unit": 3 5 | }, 6 | "batteryCurrent": { 7 | "value": -10.0, 8 | "unit": 2 9 | }, 10 | "batteryChargePower": { 11 | "value": 0, 12 | "unit": 1 13 | }, 14 | "batterySoc": { 15 | "value": 58, 16 | "unit": 4 17 | }, 18 | "batteryMaxChargeCurrent": { 19 | "value": 40.0, 20 | "unit": 2 21 | }, 22 | "pvVoltage": { 23 | "value": 0.0, 24 | "unit": 3 25 | }, 26 | "pvCurrent": { 27 | "value": 0.0, 28 | "unit": 2 29 | }, 30 | "pvPower": { 31 | "value": 0, 32 | "unit": 1 33 | }, 34 | "pvBatteryChargeCurrent": { 35 | "value": 0.0, 36 | "unit": 2 37 | }, 38 | "gridVoltage": { 39 | "value": 228.4, 40 | "unit": 3 41 | }, 42 | "gridInputCurrent": { 43 | "value": 0.0, 44 | "unit": 2 45 | }, 46 | "gridBatteryChargeCurrent": { 47 | "value": 0.0, 48 | "unit": 2 49 | }, 50 | "gridFrequency": { 51 | "value": 49.99, 52 | "unit": 6 53 | }, 54 | "gridBatteryChargeMaxCurrent": { 55 | "value": 10.0, 56 | "unit": 2 57 | }, 58 | "inverterVoltage": { 59 | "value": 229.9, 60 | "unit": 3 61 | }, 62 | "inverterCurrent": { 63 | "value": 1.1, 64 | "unit": 2 65 | }, 66 | "inverterFrequency": { 67 | "value": 49.99, 68 | "unit": 6 69 | }, 70 | "inverterPower": { 71 | "value": 253, 72 | "unit": 1 73 | }, 74 | "inverterOutputPriority": 2, 75 | "inverterChargerPriority": 0, 76 | "tempDc": { 77 | "value": 0, 78 | "unit": 3 79 | }, 80 | "tempAc": { 81 | "value": 0, 82 | "unit": 3 83 | }, 84 | "tempTr": { 85 | "value": 0, 86 | "unit": 3 87 | } 88 | } -------------------------------------------------------------------------------- /src/srnecommands.py: -------------------------------------------------------------------------------- 1 | """ 2 | SRNE All-in-one solar charger inverter 3 | Target models HF2430S80-H | HF2430U80-H 4 | """ 5 | 6 | READ_FUNCTION_CODE = 3 7 | WRITE_FUNCTION_CODE = 6 8 | 9 | MAX_UTILITY_CHARGE_CURRENT = 80 10 | MIN_UTILITY_CHARGE_CURRENT = 0 11 | MULTIPLIER_UTILITY_CHARGE_CURRENT = 5 12 | 13 | MAX_CHARGE_CURRENT = 80 14 | MIN_CHARGE_CURRENT = 0 15 | MULTIPLIER_CHARGE_CURRENT = 5 16 | 17 | INVERTER_COMMANDS = { 18 | #region Reading Commands 19 | 'battery_voltage': (0x0101, 1, READ_FUNCTION_CODE, False), 20 | 'battery_current': (0x0102, 1, READ_FUNCTION_CODE, True), 21 | 'battery_charge_power':(0x010e, 0, READ_FUNCTION_CODE, False), 22 | 'battery_soc': (0x0100, 0, READ_FUNCTION_CODE, False), 23 | 'battery_max_charge_current': (0xe20a, 1, READ_FUNCTION_CODE, False), 24 | 'pv_voltage': (0x0107, 1, READ_FUNCTION_CODE, False), 25 | 'pv_current': (0x0108, 1, READ_FUNCTION_CODE, False), 26 | 'pv_power': (0x0109, 0, READ_FUNCTION_CODE, False), 27 | 'pv_battery_charge_current': (0x0224, 1, READ_FUNCTION_CODE, False), 28 | 'grid_voltage': (0x0213, 1, READ_FUNCTION_CODE, False), 29 | 'grid_input_current': (0x0214, 1, READ_FUNCTION_CODE, False), 30 | 'grid_battery_charge_current': (0x021E, 1, READ_FUNCTION_CODE, False), 31 | 'grid_frequency':(0x0215, 2, READ_FUNCTION_CODE, False), 32 | 'grid_battery_charge_max_current': (0xe205, 1, READ_FUNCTION_CODE, False), 33 | 'inverter_voltage': (0x0216, 1, READ_FUNCTION_CODE, False), 34 | 'inverter_current': (0x0219, 1, READ_FUNCTION_CODE, False), 35 | 'inverter_frequency': (0x0218, 2, READ_FUNCTION_CODE, False), 36 | 'inverter_power': (0x021b, 0, READ_FUNCTION_CODE, False), 37 | 'inverter_output_priority': (0xe204, 0, READ_FUNCTION_CODE, False), 38 | 'inverter_charger_priority': (0xe20f, 0, READ_FUNCTION_CODE, False), 39 | 'temp_dc': (0x0221, 1, READ_FUNCTION_CODE, True), 40 | 'temp_ac': (0x0222, 1, READ_FUNCTION_CODE, True), 41 | 'temp_tr': (0x0223, 1, READ_FUNCTION_CODE, True), 42 | #endregion 43 | 44 | #region Writing Commands 45 | 'grid_battery_charge_max_current_write': (0xe205, 1, WRITE_FUNCTION_CODE, False), 46 | 'battery_max_charge_current_write': (0xe20a, 1, WRITE_FUNCTION_CODE, False), 47 | 'inverter_output_priority_write': (0xe204, 0, WRITE_FUNCTION_CODE, False), 48 | 'inverter_charger_priority_write': (0xe20f, 0, WRITE_FUNCTION_CODE, False), 49 | #endregion 50 | #register: int, value: [int, float], decimals: int = 0, functioncode: int = 6, signed: bool = False 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/inverter-monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | from time import sleep 3 | import minimalmodbus 4 | 5 | instr = minimalmodbus.Instrument('/dev/tty.usbserial-143240', 1) 6 | instr.serial.baudrate = 9600 7 | instr.serial.timeout = 1 8 | # instr.debug = True 9 | 10 | ''' 11 | Command format 12 | (RegisterAddress, NumberOfDecimals, FunctionCode, Signed) => (0x0216, 1, 3, True) 13 | 14 | #1 Battery Voltage 15 | #2 Battery Current (Charge/Discharge) 16 | #3 Battery Charge Power 17 | #4 Battery SoC 18 | #5 Battery Max Charge Current 19 | 20 | #6 PV Input Voltage 21 | #7 PV Input Current 22 | #8 PV Input Power 23 | #9 PV Charge Current 24 | 25 | #10 Grid Voltage 26 | #11 Grid Input Current 27 | #12 Grid Charge Current 28 | #13 Grid Frequency 29 | #14 Grid Charge Max Current 30 | 31 | #15 Inverter Voltage 32 | #16 Inverter Output Current 33 | #17 Inverter Output Frequency 34 | #18 Inverter Output Power 35 | 36 | #18 Temp A 37 | #19 Temp B 38 | ''' 39 | cmds = { 40 | # 1 Battery Voltage 41 | 'battery_voltage': { 42 | 'cmd': (0x0101, 1, 3, False), 43 | 'name': 'Battery Voltage', 44 | 'unit_of_measurement': 'v', 45 | 'device_class': 'voltage' 46 | }, 47 | # 2 Battey Current (Charge/Discharge) 48 | 'battery_current': { 49 | 'cmd': (0x0102, 1, 3, True), 50 | 'name': 'Battery Current(Charging/Discharge)', 51 | 'unit_of_measurement': 'a', 52 | 'device_class': 'current', 53 | 'value_template': '{{value | float * -1}}' 54 | }, 55 | # 3 Battery Charge Power (Grid + PV) 56 | 'battery_charge_power': { 57 | 'cmd': (0x010e, 0, 3, False), 58 | 'name': 'Battery Charge Power(Grid + PV)', 59 | 'unit_of_measurement': 'w', 60 | 'device_class': 'power' 61 | }, 62 | # 4 Battery SoC 63 | 'battery_soc': { 64 | 'cmd': (0x0100, 0, 3, False), 65 | 'name': 'Battery State of Charge', 66 | 'unit_of_measurement': '%', 67 | 'device_class': 'percentage' 68 | }, 69 | # 5 Battery Max Charging Current 70 | 'battery_max_charge_current': { 71 | 'cmd': (0xe20a, 1, 3, False), 72 | 'name': 'Battery Max Charging Current', 73 | 'unit_of_measurement': 'a', 74 | 'device_class': 'current' 75 | }, 76 | # 5 PV Input Voltage 77 | 'solar_voltage': { 78 | 'cmd': (0x0107, 1, 3, False), 79 | 'name': 'PV Voltage', 80 | 'unit_of_measurement': 'v', 81 | 'device_class': 'voltage' 82 | }, 83 | # 6 PV Input Current 84 | 'solar_amps': { 85 | 'cmd': (0x0108, 1, 3, False), 86 | 'name': 'PV Amps', 87 | 'unit_of_measurement': 'a', 88 | 'device_class': 'current', 89 | }, 90 | # 7 PV Input Power 91 | 'solar_watts': { 92 | 'cmd': (0x0109, 1, 3, False), 93 | 'name': 'Solar Power', 94 | 'unit_of_measurement': 'w', 95 | 'device_class': 'power' 96 | }, 97 | # 8 PV Charge Current 98 | 'pv_battery_charge_current': { 99 | 'cmd': (0x0224, 1, 3, False), 100 | 'name': 'PV Battery Charging Amps', 101 | 'unit_of_measurement': 'a', 102 | 'device_class': 'current', 103 | 'value_template': '{{value | float * -1}}' 104 | }, 105 | # 9 Grid Voltage 106 | 'grid_voltage': { 107 | 'cmd': (0x0213, 1, 3, False), 108 | 'name': 'Grid Voltage', 109 | 'unit_of_measurement': 'v', 110 | 'device_class': 'voltage' 111 | }, 112 | # 10 Grid Input Current 113 | 'grid_input_current': { 114 | 'cmd': (0x0214, 1, 3, False), 115 | 'name': 'Grid Input Current', 116 | 'unit_of_measurement': 'a', 117 | 'device_class': 'current' 118 | }, 119 | # 12 Grid Charge Current 120 | 'grid_battery_charge_current': { 121 | 'cmd': (0x021e, 1, 3, False), 122 | 'name': 'Grid Battery Charging Amps', 123 | 'unit_of_measurement': 'a', 124 | 'device_class': 'current', 125 | 'value_template': '{{value | float * -1}}' 126 | }, 127 | # 13 Grid Frequency 128 | 'grid_frequency': { 129 | 'cmd': (0x0215, 2, 3, False), 130 | 'name': 'Grid Frequency Hz', 131 | 'unit_of_measurement': 'Hz', 132 | 'device_class': 'frequency', 133 | }, 134 | 135 | # 14 Inverter Output Voltage 136 | 'inverter_voltage': { 137 | 'cmd': (0x0216, 1, 3, False), 138 | 'name': 'Inverter Output Voltage', 139 | 'unit_of_measurement': 'v', 140 | 'device_class': 'voltage' 141 | }, 142 | # 15 Inverter Output Current 143 | 'inverter_current': { 144 | 'cmd': (0x0217, 1, 3, False), 145 | 'name': 'Inverter Output Current', 146 | 'unit_of_measurement': 'a', 147 | 'device_class': 'current' 148 | }, 149 | # 16 Inverter Output Frequency 150 | 'inverter_frequency': { 151 | 'cmd': (0x0218, 2, 3, False), 152 | 'name': 'Inverter Output Frequency', 153 | 'unit_of_measurement': 'Hz', 154 | 'device_class': 'frequency', 155 | }, 156 | 157 | 'temp_dc': { 158 | 'cmd': (0x0221, 1, 3, True), 159 | 'name': 'Temperature DC', 160 | 'unit_of_measurement': 'C', 161 | 'device_class': 'temperature' 162 | }, 163 | 'temp_ac': { 164 | 'cmd': (0x0222, 1, 3, True), 165 | 'name': 'Temperature AC', 166 | 'unit_of_measurement': 'C', 167 | 'device_class': 'temperature' 168 | }, 169 | 'temp_tr': { 170 | 'cmd': (0x0223, 1, 3, True), 171 | 'name': 'Temperature TR', 172 | 'unit_of_measurement': 'C', 173 | 'device_class': 'temperature' 174 | } 175 | } 176 | 177 | # for name, vals in cmds.items(): 178 | # print(name) 179 | # try: 180 | # value = instr.read_register(*vals['cmd']) #unpack operator 181 | # print(value) 182 | # except IOError: 183 | # print("Failed to read from instrument") 184 | # sleep(0.1) 185 | 186 | # Writing / Modifying Settings 187 | value = instr.write_register(0xe204, 2, 0, functioncode=6) 188 | print(value) 189 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | from SRNEinverter import SRNEInverter, OutputPriority, ChargerPriority 2 | import srnecommands 3 | # import asyncio 4 | # import uvicorn 5 | # from fastapi import FastAPI, Request 6 | # from sse_starlette.sse import EventSourceResponse 7 | # from validator import Validator 8 | # import json 9 | 10 | # device_id = '/dev/tty.usbserial-143240' 11 | device_id = '/dev/ttyUSB0' 12 | inverter = SRNEInverter(device_id, mock=False) 13 | 14 | record = inverter.get_record() 15 | val = inverter.get_inverter_output_priority() 16 | print(record) 17 | print(val.value) 18 | # inverter.set_maxmimum_charging_current(40) 19 | # inverter.set_grid_maxmimum_charging_current(10) 20 | # inverter.set_inverter_charger_priority(ChargerPriority.CSO) 21 | # inverter.set_inverter_output_priority(OutputPriority.SBU) 22 | # Serializing json 23 | # json_object = json.dumps(record, indent=4) 24 | 25 | # Writing to sample.json 26 | # with open("output.json", "w") as outfile: 27 | # outfile.write(json_object) 28 | 29 | exit() 30 | 31 | # STREAM_DELAY = 1 # second 32 | # RETRY_TIMEOUT = 15000 # milisecond 33 | 34 | # app = FastAPI() 35 | 36 | 37 | # @app.get("/") 38 | # async def root(): 39 | # return {"message": "Hello World"} 40 | 41 | 42 | # @app.get('/stream') 43 | # async def message_stream(request: Request): 44 | # def new_messages(): 45 | # try: 46 | # record = inverter.get_record() 47 | # return record 48 | # except: 49 | # return {} 50 | 51 | # async def event_generator(): 52 | # while True: 53 | # if await request.is_disconnected(): 54 | # break 55 | # yield { 56 | # "event": "message", 57 | # "id": "message_id", 58 | # "retry": RETRY_TIMEOUT, 59 | # "data": json.dumps(new_messages()) 60 | # } 61 | # await asyncio.sleep(STREAM_DELAY) 62 | 63 | # return EventSourceResponse(event_generator()) 64 | 65 | 66 | # @app.post("/set/output-priority") 67 | # async def set_output_priority(request: Request): 68 | # request_data = await request.json() 69 | # value = request_data.get('value') 70 | # validation = Validator(value).maximum(2).minimum(0).validate() 71 | # if validation == True: 72 | # if (inverter.set_inverter_output_priority(OutputPriority(value))): 73 | # new_value = inverter.get_inverter_output_priority() 74 | # return {'success': True, 75 | # 'value': value 76 | # } 77 | # else: 78 | # return { 79 | # 'success': False, 80 | # 'message': 'Error occured when setting the value.' 81 | # } 82 | # else: 83 | # return { 84 | # 'success': False, 85 | # 'message': validation.get('message') 86 | # } 87 | 88 | 89 | # @app.post("/set/charger-priority") 90 | # async def set_charger_priority(request: Request): 91 | # request_data = await request.json() 92 | # value = request_data.get('value') 93 | # validation = Validator(value).maximum(3).minimum(0).validate() 94 | # if validation == True: 95 | # if (inverter.set_inverter_charger_priority(ChargerPriority(value))): 96 | # new_value = inverter.get_inverter_charger_priority() 97 | # return {'success': True, 98 | # 'value': value 99 | # } 100 | # else: 101 | # return { 102 | # 'success': False, 103 | # 'message': 'Error occured when setting the value.' 104 | # } 105 | # else: 106 | # return { 107 | # 'success': False, 108 | # 'message': validation.get('message') 109 | # } 110 | 111 | 112 | # @app.post("/set/grid-charge-current") 113 | # async def set_grid_charge_current(request: Request): 114 | # request_data = await request.json() 115 | # value = request_data.get('value') 116 | # validation = Validator(value).maximum( 117 | # srnecommands.MAX_UTILITY_CHARGE_CURRENT).minimum( 118 | # srnecommands.MIN_UTILITY_CHARGE_CURRENT).multiple( 119 | # srnecommands.MULTIPLIER_UTILITY_CHARGE_CURRENT).validate() 120 | # if validation == True: 121 | # if (inverter.set_grid_battery_charger_maxmimum_current(value)): 122 | # new_value = inverter.get_grid_battery_charge_max_current() 123 | # return {'success': True, 124 | # 'value': value 125 | # } 126 | # else: 127 | # return { 128 | # 'success': False, 129 | # 'message': 'Error occured when setting the value.' 130 | # } 131 | # else: 132 | # return { 133 | # 'success': False, 134 | # 'message': validation.get('message') 135 | # } 136 | 137 | 138 | # @app.post("/set/max-charge-current") 139 | # async def set_max_charge_current(request: Request): 140 | # request_data = await request.json() 141 | # value = request_data.get('value') 142 | # validation = Validator(value).maximum( 143 | # srnecommands.MAX_CHARGE_CURRENT).minimum( 144 | # srnecommands.MIN_CHARGE_CURRENT).multiple( 145 | # srnecommands.MULTIPLIER_CHARGE_CURRENT).validate() 146 | # if validation == True: 147 | # if (inverter.set_battery_charge_max_current(value)): 148 | # new_value = inverter.get_battery_charge_max_current() 149 | # return {'success': True, 150 | # 'value': value 151 | # } 152 | # else: 153 | # return { 154 | # 'success': False, 155 | # 'message': 'Error occured when setting the value.' 156 | # } 157 | # else: 158 | # return { 159 | # 'success': False, 160 | # 'message': validation.get('message') 161 | # } 162 | 163 | 164 | # @app.get("/get/all-configs") 165 | # async def get_all_config(request: Request): 166 | # try: 167 | # return { 168 | # "success": True, 169 | # "chargerPriority": inverter.get_inverter_charger_priority(), 170 | # "outputPriority": inverter.get_inverter_output_priority(), 171 | # "maxBatteryChargeCurrent": inverter.get_battery_charge_max_current(), 172 | # "maxGridChargeCurrent": inverter.get_grid_battery_charge_max_current() 173 | # } 174 | # except: 175 | # return { 176 | # "success": False, 177 | # "message": "Some unknown error occured when reading data." 178 | # } 179 | 180 | 181 | # if __name__ == "__main__": 182 | # config = uvicorn.Config("main:app", port=5004, log_level="info") 183 | # server = uvicorn.Server(config) 184 | # server.run() 185 | -------------------------------------------------------------------------------- /src/SRNEinverter.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | import minimalmodbus 3 | from srnecommands import INVERTER_COMMANDS 4 | from enum import Enum 5 | from threading import Lock 6 | 7 | 8 | class Units(Enum): 9 | POWER = 1 10 | CURRRENT = 2 11 | VOLTAGE = 3 12 | PERCENTAGE = 4 13 | TEMPERATURE = 5 14 | FREQUENCY = 6 15 | 16 | 17 | class OutputPriority(Enum): 18 | SOL = 0 19 | UTI = 1 20 | SBU = 2 21 | 22 | 23 | class ChargerPriority(Enum): 24 | CSO = 0 25 | CUB = 1 26 | SNU = 2 27 | OSO = 3 28 | 29 | 30 | # region SRNE Inverter Class 31 | 32 | 33 | class SRNEInverter(): 34 | """ 35 | SRNE All-in-one solar charger inverter 36 | Target models HF2430S80-H | HF2430U80-H 37 | """ 38 | # region Private 39 | 40 | def __init__(self, deviceid: str, baudrate: int = 9600, slaveaddress: int = 1, debug: bool = False, serialtimeout: int = 1, mock: bool = True) -> None: 41 | if not mock: 42 | instr = minimalmodbus.Instrument(deviceid, slaveaddress) 43 | instr.serial.baudrate = baudrate 44 | instr.serial.timeout = serialtimeout 45 | instr.debug = debug 46 | self._instrument = instr 47 | self.mock = mock 48 | self._lock = Lock() 49 | 50 | def _write_register(self, value: [int, float], register: int, decimals: int = 0, functioncode: int = 6, signed: bool = False) -> bool: 51 | with self._lock: 52 | if self.mock: 53 | return self._mock_write_register() 54 | sleep(0.1) 55 | try: 56 | self._instrument.write_register( 57 | register, value, decimals, functioncode, signed) 58 | return True 59 | except IOError: 60 | return False 61 | 62 | def _read_register(self, register: int, decimals: int, functioncode: int = 3, signed: bool = False) -> [int, float]: 63 | with self._lock: 64 | if self.mock: 65 | return self._mock_read_register() 66 | sleep(0.1) 67 | try: 68 | value = self._instrument.read_register( 69 | register, decimals, functioncode, signed) 70 | return value 71 | except IOError: 72 | return -10 73 | 74 | def _mock_write_register(self) -> bool: 75 | sleep(0.1) 76 | return True 77 | 78 | def _mock_read_register(self) -> [int, float]: 79 | sleep(0.1) 80 | return 1 81 | # endregion 82 | 83 | # region Getters 84 | 85 | # Battery Voltage 86 | def get_battery_voltage(self) -> float: 87 | value = self._read_register(*INVERTER_COMMANDS.get('battery_voltage')) 88 | return float(value) 89 | 90 | # Battery Current (Charge/Discharge) 91 | def get_battery_charge_current(self) -> float: 92 | """Returns charging/dischargin current value 93 | Negative value if discharging 94 | Positive value if charging 95 | """ 96 | value = self._read_register(*INVERTER_COMMANDS.get('battery_current')) 97 | return (-1) * float(value) 98 | 99 | # Battery Charge Power 100 | def get_battery_charge_power(self) -> int: 101 | """Battery Charge Power(Grid + PV)""" 102 | value = self._read_register( 103 | *INVERTER_COMMANDS.get('battery_charge_power')) 104 | return int(value) 105 | 106 | # Battery State of Charge 107 | def get_battery_soc(self) -> int: 108 | value = self._read_register(*INVERTER_COMMANDS.get('battery_soc')) 109 | return int(value) 110 | 111 | # Battery Max Charge Current 112 | def get_battery_charge_max_current(self) -> float: 113 | value = self._read_register( 114 | *INVERTER_COMMANDS.get('battery_max_charge_current')) 115 | return float(value) 116 | 117 | # PV Input Voltage 118 | def get_pv_input_voltage(self) -> float: 119 | value = self._read_register(*INVERTER_COMMANDS.get('pv_voltage')) 120 | return float(value) 121 | 122 | # PV Input Current 123 | def get_pv_input_current(self) -> float: 124 | value = self._read_register(*INVERTER_COMMANDS.get('pv_current')) 125 | return float(value) 126 | 127 | # PV Input Power 128 | def get_pv_input_power(self) -> int: 129 | value = self._read_register(*INVERTER_COMMANDS.get('pv_power')) 130 | return int(value) 131 | 132 | # PV Battery Charge Current 133 | def get_pv_battery_charge_current(self) -> float: 134 | value = self._read_register( 135 | *INVERTER_COMMANDS.get('pv_battery_charge_current')) 136 | return float(value) 137 | 138 | # Grid Voltage 139 | def get_grid_voltage(self) -> float: 140 | value = self._read_register(*INVERTER_COMMANDS.get('grid_voltage')) 141 | return float(value) 142 | 143 | # Grid Input Current 144 | def get_grid_input_current(self) -> float: 145 | value = self._read_register( 146 | *INVERTER_COMMANDS.get('grid_input_current')) 147 | return float(value) 148 | 149 | # Grid Battery Charge Current 150 | def get_grid_battery_charge_current(self) -> float: 151 | value = self._read_register( 152 | *INVERTER_COMMANDS.get('grid_battery_charge_current')) 153 | return float(value) 154 | 155 | # Grid Frequency 156 | def get_grid_frequency(self) -> float: 157 | value = self._read_register(*INVERTER_COMMANDS.get('grid_frequency')) 158 | return float(value) 159 | 160 | # Grid Battery Charge Max Current 161 | def get_grid_battery_charge_max_current(self) -> int: 162 | value = self._read_register( 163 | *INVERTER_COMMANDS.get('grid_battery_charge_max_current')) 164 | return float(value) 165 | 166 | # Inverter Output Voltage 167 | def get_inverter_output_voltage(self) -> float: 168 | value = self._read_register(*INVERTER_COMMANDS.get('inverter_voltage')) 169 | return float(value) 170 | 171 | # Inverter Output Current 172 | def get_inverter_output_current(self) -> float: 173 | value = self._read_register(*INVERTER_COMMANDS.get('inverter_current')) 174 | return float(value) 175 | 176 | # Inverter Output Frequency 177 | def get_inverter_frequency(self) -> float: 178 | value = self._read_register( 179 | *INVERTER_COMMANDS.get('inverter_frequency')) 180 | return float(value) 181 | 182 | # Inverter Output Power 183 | def get_inverter_output_power(self) -> int: 184 | value = self._read_register(*INVERTER_COMMANDS.get('inverter_power')) 185 | return int(value) 186 | 187 | # Inverter output priority 188 | def get_inverter_output_priority(self) -> OutputPriority: 189 | value = self._read_register( 190 | *INVERTER_COMMANDS.get('inverter_output_priority')) 191 | return OutputPriority(int(value)) 192 | 193 | # Inverter charger priority 194 | def get_inverter_charger_priority(self) -> ChargerPriority: 195 | value = self._read_register( 196 | *INVERTER_COMMANDS.get('inverter_charger_priority')) 197 | return ChargerPriority(int(value)) 198 | 199 | # Get a complete record of all the parameters 200 | def get_record(self): 201 | record = { 202 | 'battery': { 203 | 'voltage': self.get_battery_voltage(), 204 | 'current': self.get_battery_charge_current(), 205 | 'chargePower': self.get_battery_charge_power(), 206 | 'soc': self.get_battery_soc(), 207 | }, 208 | 'pv': { 209 | 'voltage': self.get_pv_input_voltage(), 210 | 'current': self.get_pv_input_current(), 211 | 'power': self.get_pv_input_power(), 212 | 'batteryChargeCurrent': self.get_pv_battery_charge_current() 213 | }, 214 | 'grid': { 215 | 'voltage': self.get_grid_voltage(), 216 | 'inputCurrent': self.get_grid_input_current(), 217 | 'batteryChargeCurrent': self.get_grid_battery_charge_current(), 218 | 'frequency': self.get_grid_frequency(), 219 | }, 220 | 'inverter': { 221 | 'voltage': self.get_inverter_output_voltage(), 222 | 'current': self.get_inverter_output_current(), 223 | 'frequency': self.get_inverter_frequency(), 224 | 'power': self.get_inverter_output_power(), 225 | }, 226 | } 227 | return record 228 | 229 | # endregion 230 | 231 | # region Setters 232 | 233 | # Set inverter output priority 234 | def set_inverter_output_priority(self, priority: OutputPriority) -> bool: 235 | params = (priority.value, * 236 | INVERTER_COMMANDS.get('inverter_output_priority_write')) 237 | return self._write_register(*params) 238 | 239 | # Set inverter charging priority 240 | def set_inverter_charger_priority(self, priority: ChargerPriority) -> bool: 241 | params = (priority.value, * 242 | INVERTER_COMMANDS.get('inverter_charger_priority_write')) 243 | return self._write_register(*params) 244 | 245 | # Set max charging current 246 | def set_battery_charge_max_current(self, current: int) -> bool: 247 | params = ( 248 | current, *INVERTER_COMMANDS.get('battery_max_charge_current_write')) 249 | return self._write_register(*params) 250 | 251 | # Set max utility charging current 252 | def set_grid_battery_charger_maxmimum_current(self, current: int) -> bool: 253 | params = ( 254 | current, *INVERTER_COMMANDS.get('grid_battery_charge_max_current_write')) 255 | return self._write_register(*params) 256 | # endregion 257 | 258 | # endregion 259 | --------------------------------------------------------------------------------