Skip to content

V1.1.1 #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Apr 16, 2024
Merged
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
@@ -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()}
@@ -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]:
@@ -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]


@@ -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:
@@ -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
@@ -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]
23 changes: 14 additions & 9 deletions classes/transports/mqtt.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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))
@@ -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:
@@ -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
@@ -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] = []
4 changes: 2 additions & 2 deletions classes/transports/pace.py
Original file line number Diff line number Diff line change
@@ -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),
Loading