Skip to content

Commit

Permalink
Merge pull request #749 from gruberth/modbus_tcp
Browse files Browse the repository at this point in the history
modbus_tcp: Fixed bug
  • Loading branch information
bmxp committed Jul 7, 2023
2 parents 04f01f9 + e98e339 commit 9ad245c
Showing 1 changed file with 60 additions and 50 deletions.
110 changes: 60 additions & 50 deletions modbus_tcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

# pymodbus library from https://github.com/riptideio/pymodbus
from pymodbus.version import version

pymodbus_baseversion = int(version.short().split('.')[0])

if pymodbus_baseversion > 2:
Expand All @@ -55,6 +56,7 @@
AttrObjectType = 'modBusObjectType'
AttrDirection = 'modBusDirection'


class modbus_tcp(SmartPlugin):
ALLOW_MULTIINSTANCE = True
PLUGIN_VERSION = '1.0.8'
Expand Down Expand Up @@ -87,14 +89,13 @@ def __init__(self, sh, *args, **kwargs):

self.init_webinterface(WebInterface)


return

def run(self):
"""
Run method for the plugin
"""
self._sh.scheduler.add('modbusTCP_poll_device', self.poll_device, cycle=self._cycle)
self.scheduler_add('poll_device_' + self._host, self.poll_device, cycle=self._cycle, prio=5)
self.alive = True

def stop(self):
Expand All @@ -103,7 +104,7 @@ def stop(self):
"""
self.alive = False
self.logger.debug("stop modbus_tcp plugin")
self.scheduler_remove('modbusTCP_poll_device')
self.scheduler_remove('poll_device_' + self._host)
self._Mclient.close()
self.connected = False

Expand Down Expand Up @@ -137,7 +138,7 @@ def parse_item(self, item):
if self.has_iattr(item.conf, AttrObjectType):
objectType = self.get_iattr_value(item.conf, AttrObjectType)

reg = str(objectType) # dictionary key: objectType.regAddr.slaveUnit // HoldingRegister.528.1
reg = str(objectType) # dictionary key: objectType.regAddr.slaveUnit // HoldingRegister.528.1
reg += '.'
reg += str(regAddr)
reg += '.'
Expand All @@ -151,23 +152,25 @@ def parse_item(self, item):
byteOrder = self.get_iattr_value(item.conf, AttrByteOrder)
if self.has_iattr(item.conf, AttrWordOrder):
wordOrder = self.get_iattr_value(item.conf, AttrWordOrder)
if byteOrder == 'Endian.Big': # Von String in Endian-Konstante "umwandeln"
if byteOrder == 'Endian.Big': # Von String in Endian-Konstante "umwandeln"
byteOrder = Endian.Big
elif byteOrder == 'Endian.Little':
byteOrder = Endian.Little
else:
byteOrder = Endian.Big
self.logger.warning("Invalid byte order -> default(Endian.Big) is used")
if wordOrder == 'Endian.Big': # Von String in Endian-Konstante "umwandeln"
if wordOrder == 'Endian.Big': # Von String in Endian-Konstante "umwandeln"
wordOrder = Endian.Big
elif wordOrder == 'Endian.Little':
wordOrder = Endian.Little
else:
wordOrder = Endian.Big
self.logger.warning("Invalid byte order -> default(Endian.Big) is used")

regPara = {'regAddr': regAddr, 'slaveUnit': slaveUnit, 'dataType': dataType, 'factor': factor, 'byteOrder': byteOrder,
'wordOrder': wordOrder, 'item': item, 'value': value, 'objectType': objectType, 'dataDir': dataDirection }
regPara = {'regAddr': regAddr, 'slaveUnit': slaveUnit, 'dataType': dataType, 'factor': factor,
'byteOrder': byteOrder,
'wordOrder': wordOrder, 'item': item, 'value': value, 'objectType': objectType,
'dataDir': dataDirection}
if dataDirection == 'read':
self._regToRead.update({reg: regPara})
self.logger.info("parse item: {0} Attributes {1}".format(item, regPara))
Expand All @@ -177,8 +180,8 @@ def parse_item(self, item):
self.logger.info("parse item: {0} Attributes {1}".format(item, regPara))
return self.update_item
else:
self.logger.warning("Invalid data direction -> default(read) is used")
self._regToRead.update({reg: regPara})
self.logger.warning("Invalid data direction -> default(read) is used")
self._regToRead.update({reg: regPara})

def poll_device(self):
"""
Expand All @@ -204,22 +207,21 @@ def poll_device(self):
self.connected = False
return


startTime = datetime.now()
regCount = 0
try:
for reg, regPara in self._regToRead.items():
with self.lock:
regAddr = regPara['regAddr']
value = self.__read_Registers(regPara)
#self.logger.debug("value readed: {0} type: {1}".format(value, type(value)))
# self.logger.debug("value readed: {0} type: {1}".format(value, type(value)))
if value is not None:
item = regPara['item']
if regPara['factor'] != 1:
value = value * regPara['factor']
#self.logger.debug("value {0} multiply by: {1}".format(value, regPara['factor']))
# self.logger.debug("value {0} multiply by: {1}".format(value, regPara['factor']))
item(value, self.get_fullname())
regCount+=1
regCount += 1

if 'read_dt' in regPara:
regPara['last_read_dt'] = regPara['read_dt']
Expand All @@ -232,13 +234,13 @@ def poll_device(self):
endTime = datetime.now()
duration = endTime - startTime
if regCount > 0:
self._pollStatus['last_dt']=datetime.now()
self._pollStatus['regCount']=regCount
self._pollStatus['last_dt'] = datetime.now()
self._pollStatus['regCount'] = regCount
self.logger.debug("poll_device: {0} register readed requed-time: {1}".format(regCount, duration))
except Exception as e:
self.logger.error("something went wrong in the poll_device function: {0}".format(e))

# called each time an item changes.
# called each time an item changes.
def update_item(self, item, caller=None, source=None, dest=None):
"""
Item has been updated
Expand All @@ -256,17 +258,17 @@ def update_item(self, item, caller=None, source=None, dest=None):
slaveUnit = self._slaveUnit
dataDirection = 'read'


if caller == self.get_fullname():
#self.logger.debug('item was changed by the plugin itself - caller:{0} source:{1} dest:{2} '.format(caller, source, dest))
# self.logger.debug('item was changed by the plugin itself - caller:{0} source:{1} dest:{2} '.format(caller, source, dest))
return

if self.has_iattr(item.conf, AttrDirection):
dataDirection = self.get_iattr_value(item.conf, AttrDirection)
if not dataDirection == 'read_write':
self.logger.debug('update_item:{0} Writing is not allowed - selected dataDirection:{1}'.format(item, dataDirection))
self.logger.debug(
'update_item:{0} Writing is not allowed - selected dataDirection:{1}'.format(item, dataDirection))
return
#else:
# else:
# self.logger.debug('update_item:{0} dataDirection:{1}'.format(item, dataDirection))
if self.has_iattr(item.conf, AttrAddress):
regAddr = int(self.get_iattr_value(item.conf, AttrAddress))
Expand All @@ -282,7 +284,7 @@ def update_item(self, item, caller=None, source=None, dest=None):
else:
return

reg = str(objectType) # Dict-key: HoldingRegister.528.1 *** objectType.regAddr.slaveUnit ***
reg = str(objectType) # Dict-key: HoldingRegister.528.1 *** objectType.regAddr.slaveUnit ***
reg += '.'
reg += str(regAddr)
reg += '.'
Expand Down Expand Up @@ -319,24 +321,26 @@ def __write_Registers(self, regPara, value):
bo = regPara['byteOrder']
wo = regPara['wordOrder']
dataTypeStr = regPara['dataType']
dataType = ''.join(filter(str.isalpha, dataTypeStr)) # vom dataType die Ziffen entfernen z.B. uint16 = uint
registerCount = 0 # Anzahl der zu schreibenden Register (Words)
dataType = ''.join(filter(str.isalpha, dataTypeStr)) # vom dataType die Ziffen entfernen z.B. uint16 = uint
registerCount = 0 # Anzahl der zu schreibenden Register (Words)

try:
bits = int(''.join(filter(str.isdigit, dataTypeStr))) # bit-Zahl aus aus dataType z.B. uint16 = 16
except:
bits = 16

if dataType.lower() == 'string':
registerCount = int(bits/2) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount
registerCount = int(bits / 2) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount
else:
registerCount = int(bits/16)
registerCount = int(bits / 16)

if regPara['factor'] != 1:
#self.logger.debug("value {0} divided by: {1}".format(value, regPara['factor']))
value = value * (1/regPara['factor'])
# self.logger.debug("value {0} divided by: {1}".format(value, regPara['factor']))
value = value * (1 / regPara['factor'])

self.logger.debug("write {0} to {1}.{2}.{3} (address.slaveUnit) dataType:{4}".format(value, objectType, address, slaveUnit, dataTypeStr))
self.logger.debug(
"write {0} to {1}.{2}.{3} (address.slaveUnit) dataType:{4}".format(value, objectType, address, slaveUnit,
dataTypeStr))
builder = BinaryPayloadBuilder(byteorder=bo, wordorder=wo)

if dataType.lower() == 'uint':
Expand All @@ -359,20 +363,20 @@ def __write_Registers(self, regPara, value):
self.logger.error("Number of bits or datatype not supported : {0}".format(dataTypeStr))
elif dataType.lower() == 'float':
if bits == 32:
builder.add_32bit_float(value)
if bits == 64:
builder.add_64bit_float(value)
builder.add_32bit_float(value)
elif bits == 64:
builder.add_64bit_float(value)
else:
self.logger.error("Number of bits or datatype not supported : {0}".format(dataTypeStr))
elif dataType.lower() == 'string':
builder.add_string(value)
elif dataType.lower() == 'bit':
if objectType == 'Coil' or objectType == 'DiscreteInput':
if not type(value) == type(True): # test is boolean
if not isinstance(value, bool): # test is boolean
self.logger.error("Value is not boolean: {0}".format(value))
return
else:
if set(value).issubset({'0', '1'}) and bool(value): # test is bit-string '00110101'
if set(value).issubset({'0', '1'}) and bool(value): # test is bit-string '00110101'
builder.add_bits(value)
else:
self.logger.error("Value is not a bitstring: {0}".format(value))
Expand All @@ -386,15 +390,18 @@ def __write_Registers(self, regPara, value):
registers = builder.to_registers()
result = self._Mclient.write_registers(address, registers, unit=slaveUnit)
elif objectType == 'DiscreteInput':
self.logger.warning("this object type cannot be written {0}:{1} slaveUnit:{2}".format(objectType, address, slaveUnit))
self.logger.warning(
"this object type cannot be written {0}:{1} slaveUnit:{2}".format(objectType, address, slaveUnit))
return
elif objectType == 'InputRegister':
self.logger.warning("this object type cannot be written {0}:{1} slaveUnit:{2}".format(objectType, address, slaveUnit))
self.logger.warning(
"this object type cannot be written {0}:{1} slaveUnit:{2}".format(objectType, address, slaveUnit))
return
else:
return
if result.isError():
self.logger.error("write error: {0} {1}.{2}.{3} (address.slaveUnit)".format(result, objectType, address, slaveUnit))
self.logger.error(
"write error: {0} {1}.{2}.{3} (address.slaveUnit)".format(result, objectType, address, slaveUnit))
return None

if 'write_dt' in regPara:
Expand All @@ -409,9 +416,8 @@ def __write_Registers(self, regPara, value):
else:
regPara.update({'write_value': value})

#regPara['write_dt'] = datetime.now()
#regPara['write_value'] = value

# regPara['write_dt'] = datetime.now()
# regPara['write_value'] = value

def __read_Registers(self, regPara):
objectType = regPara['objectType']
Expand All @@ -430,15 +436,15 @@ def __read_Registers(self, regPara):
bits = 16

if dataType.lower() == 'string':
registerCount = int(bits/2) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount
registerCount = int(bits / 2) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount
else:
registerCount = int(bits/16)
registerCount = int(bits / 16)

if self.connected == False:
self.logger.error(" not connect {0}:{1}".format(self._host, self._port))
return None

#self.logger.debug("read {0}.{1}.{2} (address.slaveUnit) regCount:{3}".format(objectType, address, slaveUnit, registerCount))
# self.logger.debug("read {0}.{1}.{2} (address.slaveUnit) regCount:{3}".format(objectType, address, slaveUnit, registerCount))
if objectType == 'Coil':
if pymodbus_baseversion > 2:
result = self._Mclient.read_coils(address, registerCount, slave=slaveUnit)
Expand All @@ -464,19 +470,23 @@ def __read_Registers(self, regPara):
return None

if result.isError():
self.logger.error("read error: {0} {1}.{2}.{3} (address.slaveUnit) regCount:{4}".format(result, objectType, address, slaveUnit, registerCount))
self.logger.error(
"read error: {0} {1}.{2}.{3} (address.slaveUnit) regCount:{4}".format(result, objectType, address,
slaveUnit, registerCount))
return None

if objectType == 'Coil':
value = result.bits[0]
elif objectType == 'DiscreteInput':
value = result.bits[0]
elif objectType == 'InputRegister':
decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=bo,wordorder=wo)
decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=bo, wordorder=wo)
else:
decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=bo,wordorder=wo)
decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=bo, wordorder=wo)

self.logger.debug("read {0}.{1}.{2} (address.slaveUnit) regCount:{3} result:{4}".format(objectType, address, slaveUnit, registerCount, result))
self.logger.debug(
"read {0}.{1}.{2} (address.slaveUnit) regCount:{3} result:{4}".format(objectType, address, slaveUnit,
registerCount, result))

if dataType.lower() == 'uint':
if bits == 16:
Expand All @@ -499,17 +509,17 @@ def __read_Registers(self, regPara):
elif dataType.lower() == 'float':
if bits == 32:
return decoder.decode_32bit_float()
if bits == 64:
elif bits == 64:
return decoder.decode_64bit_float()
else:
self.logger.error("Number of bits or datatype not supported : {0}".format(dataTypeStr))
elif dataType.lower() == 'string':
# bei string: bits = bytes !! string16 -> 16Byte
ret = decoder.decode_string(bits)
return str( ret, 'ASCII')
return str(ret, 'ASCII')
elif dataType.lower() == 'bit':
if objectType == 'Coil' or objectType == 'DiscreteInput':
#self.logger.debug("readed bit value: {0}".format(value))
# self.logger.debug("readed bit value: {0}".format(value))
return value
else:
self.logger.debug("readed bits values: {0}".format(value.decode_bits()))
Expand Down

0 comments on commit 9ad245c

Please sign in to comment.