Skip to content

Commit f3f5679

Browse files
authored
Merge pull request #40 from HotNoob/v1.1.3
V1.1.3
2 parents 38f1626 + 36edefb commit f3f5679

16 files changed

+1196
-331
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,23 @@ sigineer_v0.11 = sigineer inverters
4343
growatt_2020_v1.24 = alt protocol for large growatt inverters - currently untested
4444
eg4_v58 = eg4 inverters ( EG4-6000XP ) - confirmed working
4545
srne_v3.9 = SRNE inverters - Untested
46+
victron_gx_3.3 = Victron GX Devices - Untested
47+
solark_v1.1 = SolarArk 8/12K Inverters - Untested
4648
hdhk_16ch_ac_module = some chinese current monitoring device :P
4749
```
4850

4951
more details on these protocols can be found in the wiki
5052

5153
### run as script
52-
```python3 -u protocol_gateway.py```
54+
```
55+
python3 -u protocol_gateway.py
56+
```
57+
58+
or
5359

60+
```
61+
python3 -u protocol_gateway.py config.cfg
62+
```
5463

5564
### install as service
5665
ppg can be used as a shorter service name ;)

classes/protocol_settings.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ def fromString(cls, name : str):
8686
alias : dict[str,str] = {
8787
"UINT8" : "BYTE",
8888
"INT16" : "SHORT",
89-
"UINT16" : "USHORT"
89+
"UINT16" : "USHORT",
90+
"UINT32" : "UINT",
91+
"INT32" : "INT"
9092
}
9193

9294
if name in alias:
@@ -134,6 +136,7 @@ def fromString(cls, name : str):
134136
#common alternative names
135137
alias : dict[str,WriteMode] = {
136138
"R" : "READ",
139+
"NO" : "READ",
137140
"READ" : "READ",
138141
"WD" : "READ",
139142
"RD" : "READDISABLED",
@@ -142,7 +145,7 @@ def fromString(cls, name : str):
142145
"D" : "READDISABLED",
143146
"RW" : "WRITE",
144147
"W" : "WRITE",
145-
"WRITE" : "WRITE"
148+
"YES" : "WRITE"
146149
}
147150

148151
if name in alias:
@@ -436,8 +439,8 @@ def determine_delimiter(first_row) -> str:
436439
matched : bool = False
437440
val_match = range_regex.search(row['values'])
438441
if val_match:
439-
value_min = int(val_match.group('start'))
440-
value_max = int(val_match.group('end'))
442+
value_min = strtoint(val_match.group('start'))
443+
value_max = strtoint(val_match.group('end'))
441444
matched = True
442445

443446
if data_type == Data_Type.ASCII:
@@ -622,6 +625,10 @@ def load_registry_map(self, registry_type : Registry_Type, file : str = '', sett
622625

623626
path = settings_dir + '/' + file
624627

628+
#if path does not exist; nothing to load. skip.
629+
if not os.path.exists(path):
630+
return
631+
625632
self.registry_map[registry_type] = self.load__registry(path, registry_type)
626633

627634
size : int = 0

classes/transports/modbus_base.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from .transport_base import transport_base
99
from ..protocol_settings import Data_Type, Registry_Type, registry_map_entry, protocol_settings
10+
from defs.common import strtobool
1011

1112
from typing import TYPE_CHECKING
1213
if TYPE_CHECKING:
@@ -20,13 +21,28 @@ class modbus_base(transport_base):
2021
analyze_protocol_save_load : bool = False
2122
first_connect : bool = True
2223

24+
send_holding_register : bool = True
25+
send_input_register : bool = True
26+
2327
def __init__(self, settings : 'SectionProxy', protocolSettings : 'protocol_settings' = None):
2428
super().__init__(settings, protocolSettings=protocolSettings)
2529

26-
self.analyze_protocol_enabled = settings.getboolean('analyze_protocol', fallback=self.analyze_protocol)
30+
self.analyze_protocol_enabled = settings.getboolean('analyze_protocol', fallback=self.analyze_protocol_enabled)
2731
self.analyze_protocol_save_load = settings.getboolean('analyze_protocol_save_load', fallback=self.analyze_protocol_save_load)
2832

2933

34+
#get defaults from protocol settings
35+
if 'send_input_register' in self.protocolSettings.settings:
36+
self.send_input_register = strtobool(self.protocolSettings.settings['send_input_register'])
37+
38+
if 'send_holding_register' in self.protocolSettings.settings:
39+
self.send_holding_register = strtobool(self.protocolSettings.settings['send_holding_register'])
40+
41+
#allow enable/disable of which registers to send
42+
self.send_holding_register = settings.getboolean('send_holding_register', fallback=self.send_holding_register)
43+
self.send_input_register = settings.getboolean('send_input_register', fallback=self.send_input_register)
44+
45+
3046
if self.analyze_protocol_enabled:
3147
self.connect()
3248
self.analyze_protocol()
@@ -71,7 +87,7 @@ def read_serial_number(self) -> str:
7187
sn2 = sn2 + str(data_bytes.decode('utf-8'))
7288
sn3 = str(data_bytes.decode('utf-8')) + sn3
7389

74-
time.sleep(self.modbus_delay) #sleep inbetween requests so modbus can rest
90+
time.sleep(self.modbus_delay*2) #sleep inbetween requests so modbus can rest
7591

7692
print(sn2)
7793
print(sn3)
@@ -105,7 +121,16 @@ def write_data(self, data : dict[str, str]) -> None:
105121

106122
def read_data(self) -> dict[str, str]:
107123
info = {}
108-
for registry_type in Registry_Type:
124+
#modbus - only read input/holding registries
125+
for registry_type in (Registry_Type.INPUT, Registry_Type.HOLDING):
126+
127+
#enable / disable input/holding register
128+
if registry_type == Registry_Type.INPUT and not self.send_input_register:
129+
continue
130+
131+
if registry_type == Registry_Type.HOLDING and not self.send_holding_register:
132+
continue
133+
109134
registry = self.read_modbus_registers(ranges=self.protocolSettings.get_registry_ranges(registry_type=registry_type), registry_type=registry_type)
110135
new_info = self.protocolSettings.process_registery(registry, self.protocolSettings.get_registry_map(registry_type))
111136

classes/transports/modbus_rtu.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pymodbus.client.sync import ModbusSerialClient
44
from .modbus_base import modbus_base
55
from configparser import SectionProxy
6-
from defs.common import find_usb_serial_port, get_usb_serial_port_info
6+
from defs.common import find_usb_serial_port, get_usb_serial_port_info, strtoint
77

88
class modbus_rtu(modbus_base):
99
port : str = "/dev/ttyUSB0"
@@ -15,16 +15,20 @@ def __init__(self, settings : SectionProxy, protocolSettings : protocol_settings
1515
#logger = logging.getLogger(__name__)
1616
#logging.basicConfig(level=logging.DEBUG)
1717

18-
#todo: implement send holding/input option? here?
18+
super().__init__(settings, protocolSettings=protocolSettings)
19+
1920

2021
self.port = settings.get("port", "")
2122
if not self.port:
2223
raise ValueError("Port is not set")
2324

2425
self.port = find_usb_serial_port(self.port)
25-
print("Serial Port : " + self.port + " = "+get_usb_serial_port_info(self.port)) #print for config convience
26+
print("Serial Port : " + self.port + " = ", get_usb_serial_port_info(self.port)) #print for config convience
2627

27-
self.baudrate = settings.getint("baudrate", 9600)
28+
if "baud" in self.protocolSettings.settings:
29+
self.baudrate = strtoint(self.protocolSettings.settings["baud"])
30+
31+
self.baudrate = settings.getint("baudrate", self.baudrate)
2832

2933
address : int = settings.getint("address", 0)
3034
self.addresses = [address]
@@ -33,7 +37,6 @@ def __init__(self, settings : SectionProxy, protocolSettings : protocol_settings
3337
baudrate=int(self.baudrate),
3438
stopbits=1, parity='N', bytesize=8, timeout=2
3539
)
36-
super().__init__(settings, protocolSettings=protocolSettings)
3740

3841
def read_registers(self, start, count=1, registry_type : Registry_Type = Registry_Type.INPUT, **kwargs):
3942

classes/transports/mqtt.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ def __init__(self, settings : SectionProxy):
6868
self.holding_register_prefix = settings.get("holding_register_prefix", fallback="")
6969
self.input_register_prefix = settings.get("input_register_prefix", fallback="")
7070

71-
username = settings.get('user')
72-
password = settings.get('pass')
71+
username = settings.get('user', fallback="")
72+
password = settings.get('pass', fallback="")
7373

7474
if not username:
7575
raise ValueError("User is not set")

classes/transports/transport_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def __init__(self, settings : 'SectionProxy', protocolSettings : 'protocol_setti
5959
self.read_interval = settings.getfloat("read_interval", self.read_interval)
6060
self.max_precision = settings.getint(["max_precision", "precision"], self.max_precision)
6161
if "write_enabled" in settings:
62-
self.write_enabled = settings.getboolean("write_enabled", self.write_enabled)
62+
self.write_enabled = settings.getboolean(["write_enabled", "enable_write"], self.write_enabled)
6363
else:
6464
self.write_enabled = settings.getboolean("write", self.write_enabled)
6565

defs/common.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ def strtobool (val):
55
"""Convert a string representation of truth to true (1) or false (0).
66
True values are 'y', 'yes', 't', 'true', 'on', and '1'
77
"""
8+
if isinstance(val, bool):
9+
return val
10+
811
val = val.lower()
912
if val in ('y', 'yes', 't', 'true', 'on', '1'):
1013
return 1
@@ -28,22 +31,28 @@ def get_usb_serial_port_info(port : str = '') -> str:
2831
for p in serial.tools.list_ports.comports():
2932
if str(p.device).upper() == port.upper():
3033
return "["+hex(p.vid)+":"+hex(p.pid)+":"+str(p.serial_number)+":"+str(p.location)+"]"
34+
35+
return ""
3136

3237
def find_usb_serial_port(port : str = '', vendor_id : str = '', product_id : str = '', serial_number : str = '', location : str = '') -> str:
3338
if not port.startswith('['):
3439
return port
3540

36-
match = re.match(r"\[(?P<vendor>[x\d]+|):?(?P<product>[x\d]+|):?(?P<serial>\d+|):?(?P<location>[\d\-]+|)\]", port)
41+
match = re.match(r"\[(?P<vendor>[\da-zA-Z]+|):?(?P<product>[\da-zA-Z]+|):?(?P<serial>[\da-zA-Z]+|):?(?P<location>[\d\-]+|)\]", port)
3742
if match:
3843
vendor_id = int(match.group("vendor"), 16) if match.group("vendor") else ''
3944
product_id = int(match.group("product"), 16) if match.group("product") else ''
4045
serial_number = match.group("serial") if match.group("serial") else ''
4146
location = match.group("location") if match.group("location") else ''
4247

43-
for port in serial.tools.list_ports.comports():
44-
if ((not vendor_id or port.vid == vendor_id) and
45-
( not product_id or port.pid == product_id) and
46-
( not serial_number or port.serial_number == serial_number) and
47-
( not location or port.location == location)):
48-
return port.device
48+
for port in serial.tools.list_ports.comports():
49+
if ((not vendor_id or port.vid == vendor_id) and
50+
( not product_id or port.pid == product_id) and
51+
( not serial_number or port.serial_number == serial_number) and
52+
( not location or port.location == location)):
53+
return port.device
54+
else:
55+
print("Bad Port Pattern", port)
56+
return None
57+
4958
return None

docs/Sol-Ark ModBus V1.1.pdf

563 KB
Binary file not shown.
58.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)