Discussion: detect inverters based on IP only
Closed this issue · 1 comments
I wrote a little proof of concept which works fine for me. In its current state it takes ~20 seconds to run for a single IP and it does find my inverters. Not sure why it takes that long.
Does it make sense to pursue this further?
Maybe there can be two options to add devices, a simple and a manual one?
Assuming we somehow get the IP-Address, we could extend this to proper auto detection...
Output:
Running Sungrow auto detection on 192.168.13.79...
Found 2 inverter(s):
A2350415770 (Type 0xe12) at 192.168.13.79:502 (unit 1) via modbus
A2350415770 (Type 0xe12) at 192.168.13.79:502 (unit 1) via sungrow
Running Sungrow auto detection on 192.168.13.80...
Found 2 inverter(s):
A2350415779 (Type 0xe12) at 192.168.13.80:502 (unit 2) via modbus
A2350415779 (Type 0xe12) at 192.168.13.80:502 (unit 2) via sungrow
Note: for me sungrow works <50% of the time, while modbus is reliable.
(edit: Actually "sungrow" is not maintained anymore. Doesn't even make much sense to keep it?!)
For reference here is the script:
#!/usr/bin/env python3
from pprint import pprint
from pymodbus.client.sync import ModbusTcpClient
from SungrowModbusTcpClient.SungrowModbusTcpClient import SungrowModbusTcpClient
from SungrowModbusWebClient.SungrowModbusWebClient import SungrowModbusWebClient
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
import logging
logger = logging.getLogger(__name__)
def try_connect_and_find_correct_unit(
exectutor: ThreadPoolExecutor, client_class, connection_type: str, host, port
):
"""Try to connect to the inverter and find the correct unit id."""
successfull_connections = []
try:
client = client_class(host=host, port=port)
if client.connect():
logger.debug(
f"{connection_type} connected to {host}:{port}. Looking for unit id..."
)
future = {}
# It seems that usually the unit is 1 or 2, so scanning 1-4 should be enough
for unit in range(1, 5):
# Note: The serial number is stored in the inverter at address 4990 (-1), and is 10 bytes long.
# Right after the serial number, we have the device type, which is 2 bytes long.
future[unit] = exectutor.submit(
client.read_input_registers,
4989,
count=11,
unit=unit,
retries=1,
timeout=3,
)
for unit in range(1, 5):
result = future[unit].result()
if not result.isError():
# Decode the serial number as UTF-8
serial_number = "".join(
[chr(c >> 8) + chr(c & 0xFF) for c in result.registers[:10]]
).strip("\x00")
device_type = hex(result.registers[10])
successfull_connections.append(
{
"connection_type": connection_type,
"host": host,
"port": port,
"unit": unit,
"serial_number": serial_number,
"device_type": device_type,
}
)
logger.debug(f"Found valid unit: {successfull_connections[-1]}")
client.close()
except Exception as e:
logger.debug(f"Exception in try_connect: {e}")
return successfull_connections
def scan_ip(ip):
with ThreadPoolExecutor() as ex:
modbus = ex.submit(
try_connect_and_find_correct_unit,
ex,
ModbusTcpClient,
"modbus",
ip,
port=502,
)
http = ex.submit(
try_connect_and_find_correct_unit,
ex,
SungrowModbusWebClient,
"http",
ip,
port=8082,
)
# SungrowModbusTcpClient cannot run in parallel with ModbusTcpClient!
# The inverter then drops all connections.
modbus.result()
sungrow = try_connect_and_find_correct_unit(
ex, SungrowModbusTcpClient, "sungrow", ip, port=502
)
successfull_connections = modbus.result() + http.result() + sungrow
return successfull_connections
def scan_and_print(host):
print(f"Running Sungrow auto detection on {host}...")
scan_result = scan_ip(host)
print(f"Found {len(scan_result)} inverter(s):")
for inverter in scan_result:
print(
f" {inverter['serial_number']} (Type {inverter['device_type']}) at {inverter['host']}:{inverter['port']} (unit {inverter['unit']}) via {inverter['connection_type']}"
)
if __name__ == "__main__":
logging.basicConfig()
logger.setLevel(logging.INFO)
scan_and_print("192.168.13.79")
scan_and_print("192.168.13.80")
Autodetection would be a nice feature.
Assuming we somehow get the IP-Address
That's the hardest part. The inverters would need to be doing mDNS broadcasts for it to be efficient. Try firing up Wireshark and see if they are broadcasting anything.