Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions classes/Object.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#hack to allow generic objects
class Object(object):
pass
379 changes: 355 additions & 24 deletions classes/protocol_settings.py

Large diffs are not rendered by default.

134 changes: 6 additions & 128 deletions classes/transports/modbus_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def read_data(self) -> dict[str, str]:
info = {}
for registry_type in Registry_Type:
registry = self.read_modbus_registers(ranges=self.protocolSettings.get_registry_ranges(registry_type=registry_type), registry_type=registry_type)
new_info = self.process_registery(registry, self.protocolSettings.get_registry_map(registry_type))
new_info = self.protocolSettings.process_registery(registry, self.protocolSettings.get_registry_map(registry_type))

if False:
new_info = {self.__input_register_prefix + key: value for key, value in new_info.items()}
Expand Down Expand Up @@ -260,8 +260,8 @@ def evaluate_score(entry : registry_map_entry, val):
holding_valid_count[name] = 0

#process registry based on protocol
input_info = self.process_registery(input_registry, protocol.registry_map[Registry_Type.INPUT])
holding_info = self.process_registery(input_registry, protocol.registry_map[Registry_Type.HOLDING])
input_info = protocol.process_registery(input_registry, protocol.registry_map[Registry_Type.INPUT])
holding_info = protocol.process_registery(input_registry, protocol.registry_map[Registry_Type.HOLDING])


for entry in protocol.registry_map[Registry_Type.INPUT]:
Expand Down Expand Up @@ -302,7 +302,7 @@ def write_variable(self, entry : registry_map_entry, value : str, registry_type

#read current value
current_registers = self.read_modbus_registers(start=entry.register, end=entry.register, registry_type=registry_type)
results = self.process_registery(current_registers, self.protocolSettings.get_registry_map(registry_type))
results = self.protocolSettings.process_registery(current_registers, self.protocolSettings.get_registry_map(registry_type))
current_value = current_registers[entry.register]


Expand Down Expand Up @@ -381,7 +381,7 @@ def read_variable(self, variable_name : str, registry_type : Registry_Type, entr
end = max(entry.concatenate_registers)

registers = self.read_modbus_registers(start=start, end=end, registry_type=registry_type)
results = self.process_registery(registers, registry_map)
results = self.protocolSettings.process_registery(registers, registry_map)
return results[entry.variable_name]

def read_modbus_registers(self, ranges : list[tuple] = None, start : int = 0, end : int = None, batch_size : int = 45, registry_type : Registry_Type = Registry_Type.INPUT ) -> dict:
Expand Down Expand Up @@ -452,134 +452,12 @@ def read_modbus_registers(self, ranges : list[tuple] = None, start : int = 0, en
registry[i+range[0]] = register.registers[i]

return registry

def process_registery(self, registry : dict, map : list[registry_map_entry]) -> dict[str,str]:
'''process registry into appropriate datatypes and names'''

concatenate_registry : dict = {}
info = {}
for item in map:

if item.register not in registry:
continue
value = ''

if item.data_type == Data_Type.UINT: #read uint
if item.register + 1 not in registry:
continue
value = float((registry[item.register] << 16) + registry[item.register + 1])
elif item.data_type == Data_Type.SHORT: #read signed short
val = registry[item.register]

# Convert the combined unsigned value to a signed integer if necessary
if val & (1 << 15): # Check if the sign bit (bit 31) is set
# Perform two's complement conversion to get the signed integer
value = val - (1 << 16)
else:
value = val
value = -value
elif item.data_type == Data_Type.INT: #read int
if item.register + 1 not in registry:
continue

combined_value_unsigned = (registry[item.register] << 16) + registry[item.register + 1]

# Convert the combined unsigned value to a signed integer if necessary
if combined_value_unsigned & (1 << 31): # Check if the sign bit (bit 31) is set
# Perform two's complement conversion to get the signed integer
value = combined_value_unsigned - (1 << 32)
else:
value = combined_value_unsigned
value = -value
#value = struct.unpack('<h', bytes([min(max(registry[item.register], 0), 255), min(max(registry[item.register+1], 0), 255)]))[0]
#value = int.from_bytes(bytes([registry[item.register], registry[item.register + 1]]), byteorder='little', signed=True)
elif item.data_type == Data_Type._16BIT_FLAGS or item.data_type == Data_Type._8BIT_FLAGS:
val = registry[item.register]
#16 bit flags
start_bit : int = 0
if item.data_type == Data_Type._8BIT_FLAGS:
start_bit = 8

if item.documented_name+'_codes' in self.protocolSettings.codes:
flags : list[str] = []
for i in range(start_bit, 16): # Iterate over each bit position (0 to 15)
# Check if the i-th bit is set
if (val >> i) & 1:
flag_index = "b"+str(i)
if flag_index in self.protocolSettings.codes[item.documented_name+'_codes']:
flags.append(self.protocolSettings.codes[item.documented_name+'_codes'][flag_index])

value = ",".join(flags)
else:
flags : list[str] = []
for i in range(start_bit, 16): # Iterate over each bit position (0 to 15)
# Check if the i-th bit is set
if (val >> i) & 1:
flags.append("1")
else:
flags.append("0")
value = ''.join(flags)
elif item.data_type.value > 200 or item.data_type == Data_Type.BYTE: #bit types
bit_size = Data_Type.getSize(item.data_type)
bit_mask = (1 << bit_size) - 1 # Create a mask for extracting X bits
bit_index = item.register_bit
value = (registry[item.register] >> bit_index) & bit_mask
elif item.data_type == Data_Type.ASCII:
value = registry[item.register].to_bytes((16 + 7) // 8, byteorder='big') #convert to ushort to bytes
try:
value = value.decode("utf-8") #convert bytes to ascii
except UnicodeDecodeError as e:
print("UnicodeDecodeError:", e)

else: #default, Data_Type.USHORT
value = float(registry[item.register])

if item.unit_mod != float(1):
value = value * item.unit_mod

#move this to transport level
#if isinstance(value, float) and self.max_precision > -1:
# value = round(value, self.max_precision)

if (item.data_type != Data_Type._16BIT_FLAGS and
item.documented_name+'_codes' in self.protocolSettings.codes):
try:
cleanval = str(int(value))

if cleanval in self.protocolSettings.codes[item.documented_name+'_codes']:
value = self.protocolSettings.codes[item.documented_name+'_codes'][cleanval]
except:
#do nothing; try is for intval
value = value

#if item.unit:
# value = str(value) + item.unit
if item.concatenate:
concatenate_registry[item.register] = value

all_exist = True
for key in item.concatenate_registers:
if key not in concatenate_registry:
all_exist = False
break
if all_exist:
#if all(key in concatenate_registry for key in item.concatenate_registers):
concatenated_value = ""
for key in item.concatenate_registers:
concatenated_value = concatenated_value + str(concatenate_registry[key])
del concatenate_registry[key]

info[item.variable_name] = concatenated_value
else:
info[item.variable_name] = value

return info

def read_registry(self, registry_type : Registry_Type = Registry_Type.INPUT) -> dict[str,str]:
map = self.protocolSettings.get_registry_map(registry_type)
if not map:
return {}

registry = self.read_modbus_registers(self.protocolSettings.get_registry_ranges(registry_type), registry_type=registry_type)
info = self.process_registery(registry, map)
info = self.protocolSettings.process_registery(registry, map)
return info
2 changes: 1 addition & 1 deletion classes/transports/modbus_rtu.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self, settings : SectionProxy, protocolSettings : protocol_settings
if not self.port:
raise ValueError("Port is not set")

self.baudrate = settings.getint("buadrate", 9600)
self.baudrate = settings.getint("baudrate", 9600)

address : int = settings.getint("address", 0)
self.addresses = [address]
Expand Down
23 changes: 14 additions & 9 deletions classes/transports/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import paho.mqtt.properties
import paho.mqtt.packettypes

from paho.mqtt.client import Client as MQTTClient
from paho.mqtt.client import Client as MQTTClient, MQTT_ERR_NO_CONN

from defs.common import strtobool
from .transport_base import transport_base
Expand Down Expand Up @@ -42,16 +42,16 @@ class mqtt(transport_base):

__first_connection : bool = True
__reconnecting : bool = False
__connected : bool = False
connected : bool = False

def __init__(self, settings : SectionProxy):
self.host = settings.get('host', fallback="")
if not self.host:
raise ValueError("Host is not set")

self.port = settings.getint('port', fallback=self.port)
self.base_topic = settings.get('base_topic', fallback=self.base_topic)
self.error_topic = settings.get('error_topic', fallback=self.error_topic)
self.base_topic = settings.get('base_topic', fallback=self.base_topic).rstrip('/')
self.error_topic = settings.get('error_topic', fallback=self.error_topic).rstrip('/')
self.discovery_topic = settings.get('discovery_topic', fallback=self.discovery_topic)
self.discovery_enabled = strtobool(settings.get('discovery_enabled', self.discovery_enabled))
self.json = strtobool(settings.get('json', self.json))
Expand Down Expand Up @@ -131,7 +131,7 @@ def mqtt_reconnect(self):

#sleep to give a chance to reconnect.
time.sleep(self.reconnect_delay)
if self.__connected:
if self.connected:
self.__reconnecting = 0
return
except:
Expand All @@ -144,23 +144,28 @@ def mqtt_reconnect(self):
quit() #exit, service should restart entire script

def on_disconnect(self, client, userdata, rc):
self.mqtt_reconnect()
self.connected = False

def on_connect(self, client, userdata, flags, rc):
""" The callback for when the client receives a CONNACK response from the server. """
self._log.info("Connected with result code %s\n",str(rc))
self.__connected = True
self.connected = True

__write_topics : dict[str, registry_map_entry] = {}

def write_data(self, data : dict[str, str]):
if not self.write_enabled:
return

if self.connected:
self.connected = self.client.is_connected()

self._log.info("write data to mqtt transport")
self._log.info(data)
#have to send this every loop, because mqtt doesnt disconnect when HA restarts. HA bug.
self.client.publish(self.base_topic + "/availability","online", qos=0,retain=True)
info = self.client.publish(self.base_topic + "/availability","online", qos=0,retain=True)
if info.rc == MQTT_ERR_NO_CONN:
self.connected = False

if(self.json):
# Serializing json
Expand Down Expand Up @@ -204,7 +209,7 @@ def mqtt_discovery(self, from_transport : transport_base):
device = {}
device['manufacturer'] = from_transport.device_manufacturer
device['model'] = from_transport.device_model
device['identifiers'] = "hotnoob_" + from_transport.device_serial_number
device['identifiers'] = "hotnoob_" + from_transport.device_model + "_" + from_transport.device_serial_number
device['name'] = from_transport.device_name

registry_map : list[registry_map_entry] = []
Expand Down
4 changes: 2 additions & 2 deletions classes/transports/pace.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ def __init__(self, settings : dict[str,str]):
if "port" in settings:
self.port = settings["port"]

if "buadrate" in settings:
self.baudrate = settings["buadrate"]
if "baudrate" in settings:
self.baudrate = settings["baudrate"]

self.client = CustomModbusSerialClient(method='binary', port=self.port,
baudrate=int(self.baudrate),
Expand Down
Loading