Package: alphaess-modbus
AlphaESS ModBus readerAsync Python 3 library to read ModBus from an AlphaESS inverter. Tested and assumes using a Raspberry Pi as the host for RTU.Usesasynciominimalmodbusfor ModBus/RS485 RTU communication.
Usespymodbysfor Modbus TCP communication.Seealphaess_collectorwhich uses this library to store values in MySQL.Compatible with RTU:DeviceBaudTestedSMILE59600✅SMILE-B39600SMILE-T109600Storion T3019200Hardware (RTU)⚠️⚠️ This worked for me, so do at your own risk!! ⚠️⚠️More information (and pictures) in theNotessection below.Use the inverter menu to enable modbus in slave mode.Snip one end of an ethernet cable off and connect (may vary):Blue/white to RS485 ABlue to RS485 BRS485 RX to GPIO 15RS485 TX to GPIO 14Enable serial port on Raspberry Pi withraspi-config.Connect other end of ethernet cable to the inverter CAN port.Quick startPIPInstall with:python3-mpipinstallalphaess-modbusCheckoutexample.pyorexample-tcp.pyto get startedCloneClone repo and runexample.py:gitclonegit@github.com:SorX14/alphaess_modbus.gitcd./alphaess_modbus
./example.py[Sun,20Nov202221:36:54]INFO[example.py.main:27]PV:0WGRID:1078WUSE:1078WBattery:0WDone! 🎉ArchitectureThis library concentrates on reading data, butwritingis possible.Uses a JSON definition file containing all the ModBus registers and how to parse them - lookup the register you want from thePDFand request it using the reader functions below.For example, to get the capacity of your installed system, find the item in the PDF:Copy the name -PV Capacity of Grid Inverter- and request withawait reader.get_value("PV Capacity of Grid Inverter")DefinitionsAn excerpt fromregisters.json:{"name":"pv2_current","address":1058,"hex":"0x0422","type":"register","signed":false,"decimals":1,"units":"A"},which would be used when called with:awaitreader.get_value("PV2 current")# or await reader.get_value("pv2_current")It will read register0x0422, process the result as unsigned, divide it by 10, and optionally addAas the units.The default JSON file was created withalphaess_pdf_parser. You can override the default JSON file withReader(json_file=location)Reading valuesReader()Create a new RTU readerimportasynciofromalphaess_modbusimportReaderasyncdefmain():reader:Reader=Reader()definition=awaitreader.get_definition("pv2_voltage")print(definition)asyncio.run(main())Optionally change the defaults with:decimalAddress=85serial='/dev/serial0'debug=Falsebaud=9600json_file=Noneformatter=NoneReaderTCP()Create a new TCP readerimportasynciofromalphaess_modbusimportReaderTCPasyncdefmain():reader:ReaderTCP=ReaderTCP(ip="192.168.1.100",port=502)definition=awaitreader.get_definition("pv2_voltage")print(definition)asyncio.run(main())Optionally change the defaults with:ip=Noneport=502slave_id=int(0x55)json_file=Noneformatter=Noneget_value(name) -> intRequests a value from the inverter.grid=awaitreader.get_value("total_active_power_grid_meter")print(grid)# 1234Prints the current grid usage as an integer.get_units(name) -> strGet units (if any) for a register name.grid_units=awaitreader.get_units("total_active_power_grid_meter")print(grid_units)# Wget_formatted_value(name, use_formatter=True)Same asget_value()but returns a string with units. If aformatteris defined for the register, a different return type is possible.grid=awaitreader.get_formatted_value("total_active_power_grid_meter")print(grid)# 1234WSetuse_formattertoFalseto prevent a formatter from being invoked.get_definition(name) -> dictGet the JSON entry for an item. Useful if you're trying towritea register.item=awaitreader.get_definition("inverter_power_total")print(item)# {'name': 'inverter_power_total', 'address': 1036, 'hex': '0x040C', 'type': 'long', 'signed': True, 'decimals': 0, 'units': 'W'}FormattersSome registers are special and not just simple numbers - they could contain ASCII, hex-encoded numbers or another format.For example,0x0809Local IPreturns 4 bytes of the current IP, e.g.0xC0，0xA8，0x01，0x01(192.168.1.1).To help, there is a built-in formatter which will be invoked when calling.get_formatted_value()e.g:ip=awaitreader.get_formatted_value("Local IP")print(ip)# 192.168.0.1Not all registers have a formatter, and you might have a preference on how the value is returned (e.g. time-format). To help with this, you can pass aformattertoReader()and override or add to the default:classmy_custom_formatter:deflocal_ip(self,val)->str:bytes=val.to_bytes(4,byteorder='big')returnf"IP of device:{int(bytes[0])}-{int(bytes[1])}-{int(bytes[2])}-{int(bytes[3])}"reader:Reader=Reader(formatter=my_customer_formatter)local_ip=awaitreader.get_formatted_value("local_ip")print(local_ip)# IP of device: 192 - 168 - 0 - 0Each formatting function is based on the conformed name of a register. You can find the conformed name of a register by searchingregisters.jsonor by usingawait reader.get_definition(name)Writing values☠️ ModBus gives full control of the inverter. There are device-level protections in place but be very careful ☠️This library is intended to read values, but you can get a reference to theinternal ModBus librarywithreader.instrument:# Using internal reference to read a valueread=awaitreader.instrument.read_long(int(0x0021),3,False)print(read)# Untested, but should set system languageawaitreader.instrument.write_register(int(0x071D),1,0)Read the library docs for what to do next:https://minimalmodbus.readthedocs.io/en/stable/Use theAlphaESS manualfor how each register works.NotesDefinitionsWhilemy parsing scriptdid its best, there are likely to be many faults and missing entries. I only need a few basic registers so haven't tested them all.Some registers are longer than the default 4 bytes and won't work- you'll have to use the internal reader instead.PR's are welcome 🙂Registers always returning 0There are a lot of registers, but they might not all be relevant depending on your system setup. For example, the PV meter section is useless if your AlphaESS is in DC mode.Error handlingI've had the connection break a few times while testing, make sure you handle reconnecting correctly.example.pywill output the full exception should one happen.My TCP setupSome of the more recent AlphaESS inverters have this out of the box, but mine didn't. The original RTU setup was to bridge this gap.Eventually, I purchased aWaveShare RS485 TO POE Ethernet Converterbut I'm sure there are alternatives. You want something that converts a RTU device to TCP.The WaveShare one is powered by PoE, it was simple to unplug my RTU setup and put this in its place.Added a small piece of DIN rail next to my inverter and gave the converter a static IP.My RTU setupI used am5stamp RS485 modulewith a digital isolator and DC/DC isolator.Installed in an enclosure with a PoE adapter to power the Pi and provide connectivity.Enabled ModBus interface on the inverter. You'll need the service password, mine was set to the default of1111.Then connected to the CAN port.Credit and thanksSpecial thanks go tohttps://github.com/CharlesGillanders/alphaesswhere I originally started
playing around with my PV system. Their project uses the AlphaESS dashboard backend API to unofficially get inverter values from the cloud.Invaluable resource for discussing with other users. Highly
recommend readinghttps://github.com/CharlesGillanders/alphaess/issues/9which ended up with
AlphaESS creating an official API to retrieve data -https://github.com/alphaess-developer/alphacloud_open_apiAnother great resource ishttps://github.com/dxoverdy/Alpha2MQTTwhich uses a ESP8266 instead
of a Raspberry PI to communicate with the inverter - again - highly recommended.https://github.com/scanapi/scanapifor 'helping' with github actions (I used their workflow actions as templates for this project).