Skip to content

Commit ab0416d

Browse files
authored
Merge pull request #77 from HotNoob/v1.1.8
V1.1.8
2 parents 11bce70 + c41bb31 commit ab0416d

15 files changed

+348
-249
lines changed

classes/protocol_settings.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class Data_Type(Enum):
3030

3131
ASCII = 84
3232
''' 2 characters '''
33+
HEX = 85
34+
''' HEXADECIMAL STRING '''
3335

3436
_1BIT = 201
3537
_2BIT = 202
@@ -516,7 +518,7 @@ def process_row(row):
516518
value_max = strtoint(val_match.group('end'))
517519
matched = True
518520

519-
if data_type == Data_Type.ASCII:
521+
if data_type == Data_Type.ASCII: #might need to apply too hex values as well? or min-max works for hex?
520522
#value_regex
521523
val_match = ascii_value_regex.search(row['values'])
522524
if val_match:
@@ -885,6 +887,8 @@ def process_register_bytes(self, registry : dict[int,bytes], entry : registry_ma
885887
bit_mask = (1 << bit_size) - 1 # Create a mask for extracting X bits
886888
bit_index = entry.register_bit
887889
value = (register >> bit_index) & bit_mask
890+
elif entry.data_type == Data_Type.HEX:
891+
value = register.hex() #convert bytes to hex
888892
elif entry.data_type == Data_Type.ASCII:
889893
try:
890894
value = register.decode("utf-8") #convert bytes to ascii
@@ -999,6 +1003,9 @@ def process_register_ushort(self, registry : dict[int, int], entry : registry_ma
9991003
bit_mask = (1 << bit_size) - 1 # Create a mask for extracting X bits
10001004
bit_index = entry.register_bit
10011005
value = (registry[entry.register] >> bit_index) & bit_mask
1006+
elif entry.data_type == Data_Type.HEX:
1007+
value = registry[entry.register].to_bytes((16 + 7) // 8, byteorder=self.byteorder) #convert to ushort to bytes
1008+
value = value.hex() #convert bytes to hex
10021009
elif entry.data_type == Data_Type.ASCII:
10031010
value = registry[entry.register].to_bytes((16 + 7) // 8, byteorder=self.byteorder) #convert to ushort to bytes
10041011
try:

classes/transports/canbus.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def __init__(self, settings : 'SectionProxy', protocolSettings : 'protocol_setti
6262
self.linux = platform.system() != 'Windows'
6363

6464

65-
self.port = settings.get(["port", "channel"], "").lower()
65+
self.port = settings.get(["port", "channel"], "")
6666
if not self.port:
6767
raise ValueError("Port/Channel is not set")
6868

@@ -77,6 +77,7 @@ def __init__(self, settings : 'SectionProxy', protocolSettings : 'protocol_setti
7777
#setup / configure socketcan
7878
if self.interface == "socketcan":
7979
self.setup_socketcan()
80+
self.port = self.port.lower()
8081

8182
self.bus = can.interface.Bus(interface=self.interface, channel=self.port, bitrate=self.baudrate)
8283
self.reader = can.AsyncBufferedReader()

classes/transports/modbus_base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,20 @@
1212
from typing import TYPE_CHECKING
1313
if TYPE_CHECKING:
1414
from configparser import SectionProxy
15+
try:
16+
from pymodbus.client.sync import BaseModbusClient
17+
except ImportError:
18+
from pymodbus.client import BaseModbusClient
19+
1520

1621
class modbus_base(transport_base):
1722

23+
24+
#this is specifically static
25+
clients : dict[str, 'BaseModbusClient'] = {}
26+
''' str is identifier, dict of clients when multiple transports use the same ports '''
27+
28+
#non-static here for reference, type hinting, python bs ect...
1829
modbus_delay_increament : float = 0.05
1930
''' delay adjustment every error. todo: add a setting for this '''
2031

@@ -491,6 +502,7 @@ def read_modbus_registers(self, ranges : list[tuple] = None, start : int = 0, en
491502
if retry < 0:
492503
retry = 0
493504

505+
494506
#combine registers into "registry"
495507
i = -1
496508
while(i := i + 1 ) < range[1]:

classes/transports/modbus_rtu.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
except ImportError:
1010
from pymodbus.client import ModbusSerialClient
1111

12+
1213
from .modbus_base import modbus_base
1314
from configparser import SectionProxy
1415
from defs.common import find_usb_serial_port, get_usb_serial_port_info, strtoint
@@ -54,16 +55,26 @@ def __init__(self, settings : SectionProxy, protocolSettings : protocol_settings
5455
# Get the signature of the __init__ method
5556
init_signature = inspect.signature(ModbusSerialClient.__init__)
5657

58+
client_str = self.port+"("+str(self.baudrate)+")"
59+
60+
if client_str in modbus_base.clients:
61+
self.client = modbus_base.clients[client_str]
62+
return
63+
5764
if 'method' in init_signature.parameters:
5865
self.client = ModbusSerialClient(method='rtu', port=self.port,
5966
baudrate=int(self.baudrate),
6067
stopbits=1, parity='N', bytesize=8, timeout=2
6168
)
6269
else:
63-
self.client = ModbusSerialClient(port=self.port,
70+
self.client = ModbusSerialClient(
71+
port=self.port,
6472
baudrate=int(self.baudrate),
6573
stopbits=1, parity='N', bytesize=8, timeout=2
6674
)
75+
76+
#add to clients
77+
modbus_base.clients[client_str] = self.client
6778

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

@@ -75,9 +86,9 @@ def read_registers(self, start, count=1, registry_type : Registry_Type = Registr
7586
kwargs['slave'] = kwargs.pop('unit')
7687

7788
if registry_type == Registry_Type.INPUT:
78-
return self.client.read_input_registers(start, count, **kwargs)
89+
return self.client.read_input_registers(address=start, count=count, **kwargs)
7990
elif registry_type == Registry_Type.HOLDING:
80-
return self.client.read_holding_registers(start, count, **kwargs)
91+
return self.client.read_holding_registers(address=start, count=count, **kwargs)
8192

8293
def write_register(self, register : int, value : int, **kwargs):
8394
if not self.write_enabled:

classes/transports/modbus_tcp.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
import logging
2+
import inspect
3+
24
from classes.protocol_settings import Registry_Type, protocol_settings
3-
from pymodbus.client.sync import ModbusTcpClient
4-
from .transport_base import transport_base
5+
6+
#compatability
7+
try:
8+
from pymodbus.client.sync import ModbusTcpClient
9+
except ImportError:
10+
from pymodbus.client import ModbusTcpClient
11+
12+
from .modbus_base import modbus_base
513
from configparser import SectionProxy
614

715

8-
class modbus_tcp(transport_base):
16+
class modbus_tcp(modbus_base):
917
port : str = 502
1018
host : str = ""
1119
client : ModbusTcpClient
20+
pymodbus_slave_arg = 'unit'
1221

1322
def __init__(self, settings : SectionProxy, protocolSettings : protocol_settings = None):
1423
#logger = logging.getLogger(__name__)
@@ -20,12 +29,34 @@ def __init__(self, settings : SectionProxy, protocolSettings : protocol_settings
2029

2130
self.port = settings.getint("port", self.port)
2231

32+
# pymodbus compatability; unit was renamed to address
33+
if 'slave' in inspect.signature(ModbusTcpClient.read_holding_registers).parameters:
34+
self.pymodbus_slave_arg = 'slave'
35+
36+
client_str = self.host+"("+str(self.port)+")"
37+
#check if client is already initialied
38+
if client_str in modbus_base.clients:
39+
self.client = modbus_base.clients[client_str]
40+
return
41+
2342
self.client = ModbusTcpClient(host=self.host, port=self.port, timeout=7, retries=3)
43+
44+
#add to clients
45+
modbus_base.clients[client_str] = self.client
46+
2447
super().__init__(settings, protocolSettings=protocolSettings)
2548

2649
def read_registers(self, start, count=1, registry_type : Registry_Type = Registry_Type.INPUT, **kwargs):
50+
51+
if 'unit' not in kwargs:
52+
kwargs = {'unit': 1, **kwargs}
53+
54+
#compatability
55+
if self.pymodbus_slave_arg != 'unit':
56+
kwargs['slave'] = kwargs.pop('unit')
57+
2758
if registry_type == Registry_Type.INPUT:
28-
return self.client.read_input_registers(start, count, **kwargs)
59+
return self.client.read_input_registers(start, count, **kwargs )
2960
elif registry_type == Registry_Type.HOLDING:
3061
return self.client.read_holding_registers(start, count, **kwargs)
3162

classes/transports/mqtt.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def connect(self):
110110
def exit_handler(self):
111111
'''on exit handler'''
112112
self._log.warning("MQTT Exiting...")
113-
self.client.publish( self.base_topic + "/availability","offline")
113+
self.client.publish( self.base_topic + '/' + self.device_identifier + "/availability","offline")
114114
return
115115

116116
def mqtt_reconnect(self):
@@ -163,7 +163,7 @@ def write_data(self, data : dict[str, str], from_transport : transport_base):
163163
self._log.info(f"write data from [{from_transport.transport_name}] to mqtt transport")
164164
self._log.info(data)
165165
#have to send this every loop, because mqtt doesnt disconnect when HA restarts. HA bug.
166-
info = self.client.publish(self.base_topic + "/availability","online", qos=0,retain=True)
166+
info = self.client.publish(self.base_topic + '/' + from_transport.device_identifier + "/availability","online", qos=0,retain=True)
167167
if info.rc == MQTT_ERR_NO_CONN:
168168
self.connected = False
169169

@@ -196,7 +196,7 @@ def init_bridge(self, from_transport : transport_base):
196196
for entry in from_transport.protocolSettings.get_registry_map(Registry_Type.HOLDING):
197197
if entry.write_mode == WriteMode.WRITE or entry.write_mode == WriteMode.WRITEONLY:
198198
#__write_topics
199-
topic : str = self.base_topic + "/write/" + entry.variable_name.lower().replace(' ', '_')
199+
topic : str = self.base_topic + '/'+ from_transport.device_identifier + "/write/" + entry.variable_name.lower().replace(' ', '_')
200200
self.__write_topics[topic] = entry
201201
self.client.subscribe(topic)
202202

@@ -207,7 +207,7 @@ def mqtt_discovery(self, from_transport : transport_base):
207207
self._log.info("Publishing HA Discovery Topics...")
208208

209209
disc_payload = {}
210-
disc_payload['availability_topic'] = self.base_topic + "/availability"
210+
disc_payload['availability_topic'] = self.base_topic + '/' + from_transport.device_identifier + "/availability"
211211

212212
device = {}
213213
device['manufacturer'] = from_transport.device_manufacturer
@@ -247,7 +247,7 @@ def mqtt_discovery(self, from_transport : transport_base):
247247

248248
#device['sw_version'] = bms_version
249249
disc_payload = {}
250-
disc_payload['availability_topic'] = self.base_topic + "/availability"
250+
disc_payload['availability_topic'] = self.base_topic + '/' + from_transport.device_identifier + "/availability"
251251
disc_payload['device'] = device
252252
disc_payload['name'] = clean_name
253253
disc_payload['unique_id'] = "hotnoob_" + from_transport.device_serial_number + "_"+clean_name

defs/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def strtoint(val : str) -> int:
2020
if isinstance(val, int): #is already int.
2121
return val
2222

23-
val = val.lower()
23+
val = val.lower().strip()
2424

2525
if val and val[0] == 'x':
2626
val = val[1:]
Binary file not shown.

documentation/devices/EG4.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515

1616
3. Connect appropriate wires to USB RS485 Adapter
1717

18+
## Raspberry Pi Can Hat
19+
If using a Raspberry Pi Can Hat, The expected pinout RS485 A/B configuration maybe be reversed.
20+
21+
If you get the following error while using a Raspberry Pi Can Hat swap your A/B wires:
22+
23+
```
24+
ERROR:.transport_base[transport.0]:<bound method ModbusException.__str__ of ModbusIOException()>
25+
```
26+
27+
1828
## Configuration
1929
Follow configuration example for ModBus RTU to MQTT
2030
https://github.com/HotNoob/PythonProtocolGateway/wiki/Configuration-Examples#modbus-rtu-to-mqtt

0 commit comments

Comments
 (0)