From 020c2ea536d3a29b125130a3d0a1d22f66ff369b Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Fri, 2 Aug 2019 19:08:04 +0100 Subject: [PATCH 01/17] registers adjusted to real world --- src/registers.json | 80 ++++------------------------------------------ 1 file changed, 6 insertions(+), 74 deletions(-) diff --git a/src/registers.json b/src/registers.json index e5f1b44..7d39c54 100644 --- a/src/registers.json +++ b/src/registers.json @@ -32,91 +32,23 @@ "converter": null, "count": 1, "label": "Switches", - "publishTopic": "Pub/Switches", + "publishTopic": "IoT/Status/Modbus2/Switches", "scanRate": 0.5, "unit": 4, "updateOnly": false }, "type": "DiscreteInputDatapoint" }, - { - "args": { - "address": 40010, - "converter": "uint32", - "count": 2, - "feedbackTopic": "FB/Counter1", - "label": "Counter1", - "publishTopic": "Pub/Counter1", - "scanRate": 1.0, - "subscribeTopic": "Sub/Counter1", - "unit": 4 - }, - "type": "HoldingRegisterDatapoint" - }, - { - "args": { - "address": 40012, - "converter": "uint32", - "count": 2, - "feedbackTopic": "FB/Counter2", - "label": "Counter2", - "publishTopic": "Pub/Counter2", - "scanRate": null, - "subscribeTopic": "Pub/Counter2", - "unit": 4 - }, - "type": "HoldingRegisterDatapoint" - }, - { - "args": { - "address": 40014, - "converter": "uint32", - "count": 2, - "feedbackTopic": "FB/Counter3", - "label": "Counter3", - "publishTopic": "Pub/Counter3", - "scanRate": null, - "subscribeTopic": "FB/Counter3", - "unit": 4 - }, - "type": "HoldingRegisterDatapoint" - }, - { - "args": { - "address": 40016, - "converter": "uint32", - "count": 2, - "feedbackTopic": "FB/Counter4", - "label": "Counter4", - "publishTopic": "Pub/Counter4", - "scanRate": 1.0, - "subscribeTopic": "Sub/Counter4", - "unit": 4 - }, - "type": "HoldingRegisterDatapoint" - }, { "args": { "address": 0, - "feedbackTopic": "FB/Coil1", + "feedbackTopic": "IoT/Feedback/Modbus2/Coils/1", "label": "Coil1", - "publishTopic": "Pub/Coil1", - "scanRate": 1.0, - "subscribeTopic": "Sub/Coil1", - "unit": 4 - }, - "type": "CoilDatapoint" - }, - { - "args": { - "address": 1, - "feedbackTopic": "FB/Coil2", - "label": "Coil2", - "publishTopic": "Pub/Coil2", - "scanRate": 1.0, - "subscribeTopic": "Sub/Coil2", + "publishTopic": "IoT/Status/Modbus2/Coils/1", + "scanRate": 0.0, + "subscribeTopic": "IoT/Action/Modbus2/Coils/1", "unit": 4 }, "type": "CoilDatapoint" } -] \ No newline at end of file +] From 1d078ba03dab2a62af1e04aa0eea0c78df6a322c Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Sat, 3 Aug 2019 11:15:44 +0100 Subject: [PATCH 02/17] adjust registers --- src/registers.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/registers.json b/src/registers.json index 7d39c54..aeb5e8f 100644 --- a/src/registers.json +++ b/src/registers.json @@ -42,11 +42,11 @@ { "args": { "address": 0, - "feedbackTopic": "IoT/Feedback/Modbus2/Coils/1", - "label": "Coil1", - "publishTopic": "IoT/Status/Modbus2/Coils/1", + "feedbackTopic": "IoT/Feedback/Modbus2/Coils/0", + "label": "Coil0", + "publishTopic": "IoT/Status/Modbus2/Coils/0", "scanRate": 0.0, - "subscribeTopic": "IoT/Action/Modbus2/Coils/1", + "subscribeTopic": "IoT/Action/Modbus2/Coils/0", "unit": 4 }, "type": "CoilDatapoint" From f8f7d4b57e699e7c709b291b7c9c7f45f98ed7f5 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 6 Aug 2019 21:38:55 +0100 Subject: [PATCH 03/17] adjust registers --- src/registers.json | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/registers.json b/src/registers.json index aeb5e8f..66fb608 100644 --- a/src/registers.json +++ b/src/registers.json @@ -35,7 +35,7 @@ "publishTopic": "IoT/Status/Modbus2/Switches", "scanRate": 0.5, "unit": 4, - "updateOnly": false + "updateOnly": true }, "type": "DiscreteInputDatapoint" }, @@ -50,5 +50,41 @@ "unit": 4 }, "type": "CoilDatapoint" + }, + { + "args": { + "address": 1, + "feedbackTopic": "IoT/Feedback/Modbus2/Coils/1", + "label": "Coil1", + "publishTopic": "IoT/Status/Modbus2/Coils/1", + "scanRate": 0.0, + "subscribeTopic": "IoT/Action/Modbus2/Coils/1", + "unit": 4 + }, + "type": "CoilDatapoint" + }, + { + "args": { + "address": 2, + "feedbackTopic": "IoT/Feedback/Modbus2/Coils/2", + "label": "Coil2", + "publishTopic": "IoT/Status/Modbus2/Coils/2", + "scanRate": 0.0, + "subscribeTopic": "IoT/Action/Modbus2/Coils/2", + "unit": 4 + }, + "type": "CoilDatapoint" + }, + { + "args": { + "address": 3, + "feedbackTopic": "IoT/Feedback/Modbus2/Coils/3", + "label": "Coil3", + "publishTopic": "IoT/Status/Modbus2/Coils/3", + "scanRate": 0.0, + "subscribeTopic": "IoT/Action/Modbus2/Coils/3", + "unit": 4 + }, + "type": "CoilDatapoint" } -] +] \ No newline at end of file From 64c26103dffd4616b5a207b1ffcc78bf9da6a5a8 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 15:21:11 +0200 Subject: [PATCH 04/17] additional datapoints --- src/registers.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/registers.json b/src/registers.json index 66fb608..0745362 100644 --- a/src/registers.json +++ b/src/registers.json @@ -86,5 +86,31 @@ "unit": 4 }, "type": "CoilDatapoint" + }, + { + "args": { + "address": 1, + "converter": "dht20TOFloat", + "count": 1, + "label": "wago1", + "publishTopic": "IoT/Measurement/Modbus2/Wago1", + "scanRate": 1.0, + "unit": 11, + "updateOnly": false + }, + "type": "InputRegisterDatapoint" + }, + { + "args": { + "address": 0, + "converter": "dht20TOFloat", + "count": 1, + "label": "wago0", + "publishTopic": "IoT/Measurement/Modbus2/Wago0", + "scanRate": 1.0, + "unit": 11, + "updateOnly": false + }, + "type": "InputRegisterDatapoint" } ] \ No newline at end of file From 880794f966727399d5a2230520e2168f777a5d79 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 15:31:36 +0200 Subject: [PATCH 05/17] converter for twos-complement fixed decimal numbers --- src/Converters.py | 13 +++++++++++++ src/registers.json | 10 +++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Converters.py b/src/Converters.py index 05319a8..07f94fa 100644 --- a/src/Converters.py +++ b/src/Converters.py @@ -6,6 +6,14 @@ from struct import pack, unpack +def fix1twos(x): + x = x[0] + r = x + if x & 0x8000: + r = ((x - 1) ^ 0xffff) * -1 + return r / 10 + + Converters = { "dht20TOFloat": { "in": lambda x : float(x[0]) / 10.0, @@ -14,5 +22,10 @@ Converters = { "uint32": { "in": lambda x : unpack('L', pack('HH', *x))[0], "out": lambda x : unpack('HH', pack('L', int(x))) + }, + "fix1twos": { + "in": lambda x: fix1twos(x), + "out": None } } + diff --git a/src/registers.json b/src/registers.json index 0745362..401f6ce 100644 --- a/src/registers.json +++ b/src/registers.json @@ -90,7 +90,7 @@ { "args": { "address": 1, - "converter": "dht20TOFloat", + "converter": "fix1twos", "count": 1, "label": "wago1", "publishTopic": "IoT/Measurement/Modbus2/Wago1", @@ -103,14 +103,14 @@ { "args": { "address": 0, - "converter": "dht20TOFloat", + "converter": "fix1twos", "count": 1, - "label": "wago0", - "publishTopic": "IoT/Measurement/Modbus2/Wago0", + "label": "Freezer", + "publishTopic": "IoT/Measurement/Modbus2/Freezer", "scanRate": 1.0, "unit": 11, "updateOnly": false }, "type": "InputRegisterDatapoint" } -] \ No newline at end of file +] From 832402fea6c220f69d732ef5f8e83508dfcb61c6 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 15:54:09 +0200 Subject: [PATCH 06/17] service file --- src/modbusMaster.service | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/modbusMaster.service diff --git a/src/modbusMaster.service b/src/modbusMaster.service new file mode 100644 index 0000000..a7da40d --- /dev/null +++ b/src/modbusMaster.service @@ -0,0 +1,19 @@ +[Unit] +Description=ModbusMaster +Wants=network-online.target +After=network-online.target + + +[Service] +Type=simple +GuessMainPID=yes +ExecStart=/usr/bin/python master.py +ExecStop=kill -SIGINT $mainpid +Restart=on-failure +WorkingDirectory=/opt/services/modbusMaster + +[Install] +Alias=ModbusMaster +WantedBy=multi-user.target + + From 90de6537de0a89689c591707e2d309619612b1a6 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 15:59:13 +0200 Subject: [PATCH 07/17] add ci script --- .gitlab-ci.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..c07d644 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,27 @@ +stages: + - check + - deploy + +build: + stage: check + image: registry.gitlab.com/wolutator/base-build-env:latest + tags: + - hottis + - linux + - docker + script: + - for I in *.py; do python -m py_compile $I; done + - for I in *.py; do python -m pycodestyle --max-line-length=120 $I; done + +deploy: + stage: deploy + tags: + - hottis + - linux + - rpi + - modbus + only: + - deploy + script: + - whoami + From 61de3f3a5ba8a5d5dcd7e701976dd335a5a90c3b Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:01:12 +0200 Subject: [PATCH 08/17] fix in ci script --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c07d644..4986d18 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,8 +10,8 @@ build: - linux - docker script: - - for I in *.py; do python -m py_compile $I; done - - for I in *.py; do python -m pycodestyle --max-line-length=120 $I; done + - for I in src/*.py; do python -m py_compile $I; done + - for I in src/*.py; do python -m pycodestyle --max-line-length=120 $I; done deploy: stage: deploy From c1bf7fd13ae092c4234d0344e4a56a85293847d8 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:03:54 +0200 Subject: [PATCH 09/17] some fixes --- src/CmdServer.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CmdServer.py b/src/CmdServer.py index 626aecb..4684ab0 100644 --- a/src/CmdServer.py +++ b/src/CmdServer.py @@ -8,7 +8,9 @@ import RegisterDatapoint import logging import Converters -class CmdInterpreterException(ValueError): pass +class CmdInterpreterException(ValueError): + pass + def parseIntArbitraryBase(s): i = 0 @@ -20,6 +22,7 @@ def parseIntArbitraryBase(s): i = int(s, 10) return i + class CmdInterpreter(cmd.Cmd): def __init__(self, infile, outfile, config, notifier, registers): super().__init__(stdin=infile, stdout=outfile) @@ -50,11 +53,12 @@ class CmdInterpreter(cmd.Cmd): def __listConverterNames(self): - return [ name for name in Converters.Converters ] + return [name for name in Converters.Converters] def do_add_hr(self, arg): try: - (label, unit, address, count, scanrate, readTopic, writeTopic, feedbackTopic, converter) = self.splitterRe.split(arg) + (label, unit, address, count, scanrate, readTopic, writeTopic, feedbackTopic, converter) = + self.splitterRe.split(arg) self.__println("Label: {0}".format(label)) self.__println("Unit: {0}".format(unit)) self.__println("Address: {0}".format(address)) From 77d01ca675b964ea32880b5117d9a3f22537a825 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:18:35 +0200 Subject: [PATCH 10/17] fix style issues --- src/CmdServer.py | 100 ++++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/src/CmdServer.py b/src/CmdServer.py index 4684ab0..b743af3 100644 --- a/src/CmdServer.py +++ b/src/CmdServer.py @@ -8,7 +8,8 @@ import RegisterDatapoint import logging import Converters -class CmdInterpreterException(ValueError): + +class CmdInterpreterException(ValueError): pass @@ -32,7 +33,7 @@ class CmdInterpreter(cmd.Cmd): self.registers = registers self.prompt = "test8> " self.intro = "test8 admin interface" - self.splitterRe = re.compile('\s+') + self.splitterRe = re.compile(r'\s+') self.logger = logging.getLogger('CmdInterpreter') def onecmd(self, line): @@ -50,15 +51,14 @@ class CmdInterpreter(cmd.Cmd): self.stdout.write(text) self.stdout.write("\n\r") - - def __listConverterNames(self): return [name for name in Converters.Converters] def do_add_hr(self, arg): try: - (label, unit, address, count, scanrate, readTopic, writeTopic, feedbackTopic, converter) = - self.splitterRe.split(arg) + (label, unit, address, count, + scanrate, readTopic, writeTopic, + feedbackTopic, converter) = self.splitterRe.split(arg) self.__println("Label: {0}".format(label)) self.__println("Unit: {0}".format(unit)) self.__println("Address: {0}".format(address)) @@ -81,14 +81,14 @@ class CmdInterpreter(cmd.Cmd): address = parseIntArbitraryBase(address) count = parseIntArbitraryBase(count) scanrate = float(scanrate) - r = RegisterDatapoint.HoldingRegisterDatapoint(label=label, - unit=unit, - address=address, - count=count, - scanRate=datetime.timedelta(seconds=scanrate), - publishTopic=readTopic, - subscribe=writeTopic, - feedbackTopic=feedbackTopic, + r = RegisterDatapoint.HoldingRegisterDatapoint(label=label, + unit=unit, + address=address, + count=count, + scanRate=datetime.timedelta(seconds=scanrate), + publishTopic=readTopic, + subscribe=writeTopic, + feedbackTopic=feedbackTopic, converter=converter) self.registers.append(r) except ValueError as e: @@ -112,9 +112,9 @@ class CmdInterpreter(cmd.Cmd): self.__println(" Topic to be subscribe to receive data to be") self.__println(" written") self.__println(" Topic to publish feedback after a write process,") - self.__println(" Converter for data, one of {0}".format(', '.join(self.__listConverterNames()))) + self.__println(" Converter for data, one of {0}" + .format(', '.join(self.__listConverterNames()))) - def do_add_coil(self, arg): try: (label, unit, address, scanrate, readTopic, writeTopic, feedbackTopic) = self.splitterRe.split(arg) @@ -135,12 +135,12 @@ class CmdInterpreter(cmd.Cmd): unit = parseIntArbitraryBase(unit) address = parseIntArbitraryBase(address) scanrate = float(scanrate) - r = RegisterDatapoint.CoilDatapoint(label=label, - unit=unit, - address=address, - scanRate=datetime.timedelta(seconds=scanrate), - publishTopic=readTopic, - subscribeTopic=writeTopic, + r = RegisterDatapoint.CoilDatapoint(label=label, + unit=unit, + address=address, + scanRate=datetime.timedelta(seconds=scanrate), + publishTopic=readTopic, + subscribeTopic=writeTopic, feedbackTopic=feedbackTopic) self.registers.append(r) except ValueError as e: @@ -161,7 +161,7 @@ class CmdInterpreter(cmd.Cmd): self.__println(" Topic to be subscribe to receive data to be") self.__println(" written") self.__println(" Topic to publish feedback after a write process,") - + def do_add_ir(self, arg): try: (label, unit, address, count, scanrate, updateOnly, readTopic, converter) = self.splitterRe.split(arg) @@ -188,12 +188,12 @@ class CmdInterpreter(cmd.Cmd): address = parseIntArbitraryBase(address) count = parseIntArbitraryBase(count) scanrate = float(scanrate) - r = RegisterDatapoint.InputRegisterDatapoint(label=label, - unit=unit, - address=address, - count=count, scanRate=datetime.timedelta(seconds=scanrate), - updateOnly=updateOnly, - publishTopic=readTopic, + r = RegisterDatapoint.InputRegisterDatapoint(label=label, + unit=unit, + address=address, + count=count, scanRate=datetime.timedelta(seconds=scanrate), + updateOnly=updateOnly, + publishTopic=readTopic, converter=converter) self.registers.append(r) except ValueError as e: @@ -212,7 +212,8 @@ class CmdInterpreter(cmd.Cmd): self.__println(" Scanrate in seconds (float)") self.__println(" Publish only when value has changed") self.__println(" Topic to publish read data") - self.__println(" Converter for data, one of {0}".format(', '.join(self.__listConverterNames()))) + self.__println(" Converter for data, one of {0}" + .format(', '.join(self.__listConverterNames()))) def do_add_di(self, arg): try: @@ -239,13 +240,13 @@ class CmdInterpreter(cmd.Cmd): count = parseIntArbitraryBase(count) scanrate = float(scanrate) bitCount = int(bitCount) - r = RegisterDatapoint.DiscreteInputDatapoint(label=label, - unit=unit, - address=address, - count=count, - scanRate=datetime.timedelta(seconds=scanrate), - updateOnly=updateOnly, - publishTopic=readTopic, + r = RegisterDatapoint.DiscreteInputDatapoint(label=label, + unit=unit, + address=address, + count=count, + scanRate=datetime.timedelta(seconds=scanrate), + updateOnly=updateOnly, + publishTopic=readTopic, bitCount=bitCount) self.registers.append(r) except ValueError as e: @@ -269,7 +270,7 @@ class CmdInterpreter(cmd.Cmd): def do_list(self, arg): for i, r in enumerate(self.registers): self.__println("#{0}: {1!s}".format(i, r)) - + def help_list(self): self.__println("Usage: list") self.__println("-----------") @@ -280,7 +281,7 @@ class CmdInterpreter(cmd.Cmd): r.errorCount = 0 r.writeCount = 0 r.readCount = 0 - + def help_reset(self): self.__println("Usage: reset") self.__println("-----------") @@ -293,7 +294,8 @@ class CmdInterpreter(cmd.Cmd): ratio = -1 else: ratio = float(r.errorCount) / float(processCount) - self.__println("#{0:2d}: {1:15s} ({2:2d}, {3:5d}), rc: {4:7d}, wc: {5:7d}, pc: {6:7d}, ec: {7:7d}, q: {8:1.4f}" + self.__println("#{0:2d}: {1:15s} ({2:2d}, {3:5d}), rc: {4:7d}, " + "wc: {5:7d}, pc: {6:7d}, ec: {7:7d}, q: {8:1.4f}" .format(i, r.label, r.unit, r.address, r.readCount, r.writeCount, processCount, r.errorCount, ratio)) @@ -328,10 +330,10 @@ class CmdInterpreter(cmd.Cmd): value = None else: raise CmdInterpreterException('unknown type specifier, must be I, F, B, S or T') - + if key not in r.__dict__: raise CmdInterpreterException('selected datapoint does not support key') - + r.__dict__[key] = value except ValueError as e: self.__println("ERROR: {0!s}, {1!s}".format(e.__class__.__name__, e)) @@ -380,7 +382,7 @@ class CmdInterpreter(cmd.Cmd): def do_quit(self, arg): self.__println("Bye!") - return True + return True def __save(self): RegisterDatapoint.saveRegisterList(self.registers, self.config.registerFile) @@ -404,11 +406,10 @@ class CmdInterpreter(cmd.Cmd): self.__println("Reload the register file, overwrite all unsaved changes.") - class CmdHandle(socketserver.StreamRequestHandler): def handle(self): logger = logging.getLogger('CmdHandle') - cmd = CmdInterpreter(io.TextIOWrapper(self.rfile), io.TextIOWrapper(self.wfile), self.server.userData.config, + cmd = CmdInterpreter(io.TextIOWrapper(self.rfile), io.TextIOWrapper(self.wfile), self.server.userData.config, self.server.userData.notifier, self.server.userData.registers) try: cmd.cmdloop() @@ -416,25 +417,28 @@ class CmdHandle(socketserver.StreamRequestHandler): except ConnectionAbortedError as e: logger.info("Cmd handle externally interrupted") + class MyThreadingTCPServer(socketserver.ThreadingTCPServer): def __init__(self, host, handler, userData): super().__init__(host, handler) self.userData = userData + class MyCmdUserData(object): def __init__(self, config, notifier, registers): self.config = config self.notifier = notifier self.registers = registers + class CmdServer(threading.Thread): def __init__(self, config, notifier, registers): super().__init__() self.config = config - self.server = MyThreadingTCPServer((config.cmdAddress, config.cmdPort), CmdHandle, MyCmdUserData(config, notifier, registers)) + self.server = MyThreadingTCPServer((config.cmdAddress, config.cmdPort), + CmdHandle, + MyCmdUserData(config, notifier, registers)) # self.daemon = True def run(self): self.server.serve_forever() - - From 94e60ee172892763e6c03d0e4b2a0ad7394faf10 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:33:18 +0200 Subject: [PATCH 11/17] fix style issues --- src/CommunicationProcessor.py | 6 +-- src/Converters.py | 13 +++---- src/Heartbeat.py | 1 + src/MqttProcessor.py | 15 +++++--- src/MyPriorityQueue.py | 1 + src/MyRS485.py | 2 +- src/NotificationForwarder.py | 1 + src/Pins.py | 2 - src/RegisterDatapoint.py | 55 ++++++++++++++------------- src/ScanRateConsideringQueueFeeder.py | 7 ++-- src/initialRegisterFile.py | 10 +++-- src/loadRegisterFile.py | 2 +- src/master.py | 3 +- src/updateRegisterFile.py | 7 +--- 14 files changed, 64 insertions(+), 61 deletions(-) diff --git a/src/CommunicationProcessor.py b/src/CommunicationProcessor.py index c5c74a0..9676fd3 100644 --- a/src/CommunicationProcessor.py +++ b/src/CommunicationProcessor.py @@ -12,6 +12,7 @@ import logging ERROR_PIN = 29 + class CommunicationProcessor(threading.Thread): def __init__(self, config, queue, pubQueue): super().__init__() @@ -33,7 +34,6 @@ class CommunicationProcessor(threading.Thread): return MyRS485.MyRS485(port=self.config.serialPort, baudrate=self.config.serialBaudRate, stopbits=1, timeout=1) - def run(self): client = ModbusSerialClient(method='rtu') client.socket = self.__getSerial() @@ -56,7 +56,3 @@ class CommunicationProcessor(threading.Thread): client.socket = self.__getSerial() finally: time.sleep(self.config.interCommDelay) - - - - diff --git a/src/Converters.py b/src/Converters.py index 07f94fa..8accab9 100644 --- a/src/Converters.py +++ b/src/Converters.py @@ -1,6 +1,6 @@ -# in: from Modbus to MQTT, input is a list of 16bit integers, output shall be the desired format +# in: from Modbus to MQTT, input is a list of 16bit integers, output shall be the desired format # to be sent in the MQTT message -# out: from MQTT to Modbus, input is the format received from MQTT, output shall be a list of +# out: from MQTT to Modbus, input is the format received from MQTT, output shall be a list of # 16bit integers to be written to the Modbus slave from struct import pack, unpack @@ -12,20 +12,19 @@ def fix1twos(x): if x & 0x8000: r = ((x - 1) ^ 0xffff) * -1 return r / 10 - + Converters = { "dht20TOFloat": { - "in": lambda x : float(x[0]) / 10.0, + "in": lambda x: float(x[0]) / 10.0, "out": None }, "uint32": { - "in": lambda x : unpack('L', pack('HH', *x))[0], - "out": lambda x : unpack('HH', pack('L', int(x))) + "in": lambda x: unpack('L', pack('HH', *x))[0], + "out": lambda x: unpack('HH', pack('L', int(x))) }, "fix1twos": { "in": lambda x: fix1twos(x), "out": None } } - diff --git a/src/Heartbeat.py b/src/Heartbeat.py index 150316a..9eacb6e 100644 --- a/src/Heartbeat.py +++ b/src/Heartbeat.py @@ -3,6 +3,7 @@ import MqttProcessor import logging import time + class Heartbeat(threading.Thread): def __init__(self, config, pubQueue): super().__init__() diff --git a/src/MqttProcessor.py b/src/MqttProcessor.py index 0e81de5..3c07f30 100644 --- a/src/MqttProcessor.py +++ b/src/MqttProcessor.py @@ -4,6 +4,7 @@ from NotificationForwarder import AbstractNotificationReceiver import logging import Pins + class PublishItem(object): def __init__(self, topic, payload): self.topic = topic @@ -12,15 +13,19 @@ class PublishItem(object): def __str__(self): return 'Topic: {0}, Payload: {1}'.format(self.topic, self.payload) + def mqttOnConnectCallback(client, userdata, flags, rc): userdata.onConnect() + def mqttOnMessageCallback(client, userdata, message): userdata.onMessage(message.topic, message.payload) + def mqttOnDisconnectCallback(client, userdata, rc): userdata.onDisconnect(rc) + class MqttProcessor(threading.Thread, AbstractNotificationReceiver): def __init__(self, config, registers, queue, pubQueue): super().__init__() @@ -30,14 +35,15 @@ class MqttProcessor(threading.Thread, AbstractNotificationReceiver): self.pubQueue = pubQueue self.client = mqtt.Client(userdata=self) self.subscriptions = [] - self.topicRegisterMap ={} + self.topicRegisterMap = {} # self.daemon = True self.logger = logging.getLogger('MqttProcessor') def __processUpdatedRegisters(self, force=False): self.logger.debug("MqttProcessor.__updateSubscriptions") - subscribeTopics = [ r.subscribeTopic for r in self.registers if hasattr(r,'subscribeTopic') and r.subscribeTopic] + subscribeTopics = [r.subscribeTopic for r in self.registers if hasattr(r, 'subscribeTopic') + and r.subscribeTopic] self.logger.debug("Topics: {0!s}".format(subscribeTopics)) for subscribeTopic in subscribeTopics: @@ -52,7 +58,8 @@ class MqttProcessor(threading.Thread, AbstractNotificationReceiver): self.client.unsubscribe(subscription) self.subscriptions.remove(subscription) - self.topicRegisterMap = { r.subscribeTopic: r for r in self.registers if hasattr(r,'subscribeTopic') and r.subscribeTopic } + self.topicRegisterMap = {r.subscribeTopic: r for r in self.registers if hasattr(r, 'subscribeTopic') + and r.subscribeTopic} def receiveNotification(self, arg): self.logger.info("MqttProcessor:registersChanged") @@ -76,7 +83,6 @@ class MqttProcessor(threading.Thread, AbstractNotificationReceiver): else: self.logger.error("Invalid object in publish queue") - def onConnect(self): # print("MqttProcessor.onConnect") self.__processUpdatedRegisters(force=True) @@ -91,4 +97,3 @@ class MqttProcessor(threading.Thread, AbstractNotificationReceiver): self.logger.debug("{0}: {1!s} -> {2!s}".format(topic, payload, r)) r.onMessage(payload) self.queue.put(r) - diff --git a/src/MyPriorityQueue.py b/src/MyPriorityQueue.py index 71656df..a143c23 100644 --- a/src/MyPriorityQueue.py +++ b/src/MyPriorityQueue.py @@ -12,6 +12,7 @@ class MyPriorityQueueItem(object): def __gt__(self, other): return self.itemWithPriority.priority > other.itemWithPriority.priority def __ge__(self, other): return self.itemWithPriority.priority >= other.itemWithPriority.priority + class MyPriorityQueue(queue.PriorityQueue): def _put(self, itemWithPriority): i = MyPriorityQueueItem(itemWithPriority) diff --git a/src/MyRS485.py b/src/MyRS485.py index e02b209..38032ce 100644 --- a/src/MyRS485.py +++ b/src/MyRS485.py @@ -7,6 +7,7 @@ import termios DE_PIN = 0 + class MyRS485(serial.Serial): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -24,4 +25,3 @@ class MyRS485(serial.Serial): break # wiringpi.digitalWrite(DE_PIN, wiringpi.LOW) Pins.pinsWrite('DE', False) - diff --git a/src/NotificationForwarder.py b/src/NotificationForwarder.py index dfd6a6a..632a474 100644 --- a/src/NotificationForwarder.py +++ b/src/NotificationForwarder.py @@ -3,6 +3,7 @@ class AbstractNotificationReceiver(object): def receiveNotification(self, arg): raise NotImplementedError + class NotificationForwarder(object): def __init__(self): self.receivers = [] diff --git a/src/Pins.py b/src/Pins.py index e639603..3633072 100644 --- a/src/Pins.py +++ b/src/Pins.py @@ -8,7 +8,6 @@ PINS = { } - def pinsInit(): wiringpi.wiringPiSetup() for pin in PINS.values(): @@ -21,4 +20,3 @@ def pinsWrite(pinName, v): else: pinState = wiringpi.LOW wiringpi.digitalWrite(PINS[pinName], pinState) - diff --git a/src/RegisterDatapoint.py b/src/RegisterDatapoint.py index 4a72a7b..43454c9 100644 --- a/src/RegisterDatapoint.py +++ b/src/RegisterDatapoint.py @@ -6,7 +6,10 @@ import logging import json import Converters -class DatapointException(Exception): pass + +class DatapointException(Exception): + pass + class AbstractModbusDatapoint(object): def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, converter=None): @@ -40,17 +43,16 @@ class AbstractModbusDatapoint(object): self.errorCount, self.readCount, self.writeCount, self.converter)) def jsonify(self): - return {'type':self.__class__.__name__, - 'args': { k: getattr(self, k) for k in self.argList } - } + return {'type': self.__class__.__name__, + 'args': {k: getattr(self, k) for k in self.argList} + } def process(self, client): raise NotImplementedError - class HoldingRegisterDatapoint(AbstractModbusDatapoint): - def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, + def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, publishTopic=None, subscribeTopic=None, feedbackTopic=None, converter=None): super().__init__(label, unit, address, count, scanRate, converter) self.argList = self.argList + ['publishTopic', 'subscribeTopic', 'feedbackTopic'] @@ -83,16 +85,16 @@ class HoldingRegisterDatapoint(AbstractModbusDatapoint): except Exception as e: raise DatapointException("Exception caught when trying to converter modbus data: {0!s}".format(e)) result = client.write_registers(address=self.address, - unit=self.unit, - values=values) - logger.debug("Write result: {0!s}".format(result)) + unit=self.unit, + values=values) + logger.debug("Write result: {0!s}".format(result)) self.writeRequestValue = None else: # perform read operation logger.debug("Holding register, perform read operation") self.readCount += 1 - result = client.read_holding_registers(address=self.address, - count=self.count, + result = client.read_holding_registers(address=self.address, + count=self.count, unit=self.unit) if type(result) in [ExceptionResponse, ModbusIOException]: self.errorCount += 1 @@ -110,7 +112,7 @@ class HoldingRegisterDatapoint(AbstractModbusDatapoint): if self.publishTopic: pubQueue.put(MqttProcessor.PublishItem(self.publishTopic, str(value))) self.lastContact = datetime.datetime.now() - + def onMessage(self, value): self.writeRequestValue = value @@ -119,7 +121,7 @@ class CoilDatapoint(AbstractModbusDatapoint): def __init__(self, label=None, unit=None, address=None, scanRate=None, publishTopic=None, subscribeTopic=None, feedbackTopic=None): super().__init__(label, unit, address, 1, scanRate, None) - self.argList = ['label', 'unit','address','scanRate','publishTopic', 'subscribeTopic', 'feedbackTopic'] + self.argList = ['label', 'unit', 'address', 'scanRate', 'publishTopic', 'subscribeTopic', 'feedbackTopic'] self.publishTopic = publishTopic self.subscribeTopic = subscribeTopic self.feedbackTopic = feedbackTopic @@ -132,7 +134,7 @@ class CoilDatapoint(AbstractModbusDatapoint): "writeCount: {9}, publishTopic: {10}, subscribeTopic: {11}, feedbackTopic: {12}" .format(self.type, self.label, self.unit, self.address, self.scanRate, self.enqueued, self.lastContact, - self.errorCount, self.readCount, self.writeCount, + self.errorCount, self.readCount, self.writeCount, self.publishTopic, self.subscribeTopic, self.feedbackTopic)) def onMessage(self, value): @@ -145,7 +147,7 @@ class CoilDatapoint(AbstractModbusDatapoint): logger.debug("Coil, perform write operation") self.writeCount += 1 logger.debug("{0}: raw: {1!s}".format(self.label, self.writeRequestValue)) - value=None + value = None if self.writeRequestValue in ['true', 'True', 'yes', 'Yes', 'On', 'on']: value = True elif self.writeRequestValue in ['false', 'False', 'no', 'No', 'Off', 'off']: @@ -156,13 +158,13 @@ class CoilDatapoint(AbstractModbusDatapoint): result = client.write_coil(address=self.address, unit=self.unit, value=value) - logger.debug("Write result: {0!s}".format(result)) + logger.debug("Write result: {0!s}".format(result)) self.writeRequestValue = None else: # perform read operation logger.debug("Coil, perform read operation") self.readCount += 1 - result = client.read_coils(address=self.address, + result = client.read_coils(address=self.address, unit=self.unit) if type(result) in [ExceptionResponse, ModbusIOException]: self.errorCount += 1 @@ -175,7 +177,8 @@ class CoilDatapoint(AbstractModbusDatapoint): class ReadOnlyDatapoint(AbstractModbusDatapoint): - def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, updateOnly=None, publishTopic=None, converter=None): + def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, updateOnly=None, + publishTopic=None, converter=None): super().__init__(label, unit, address, count, scanRate, converter) self.argList = self.argList + ['updateOnly', 'publishTopic'] self.updateOnly = updateOnly @@ -188,9 +191,8 @@ class ReadOnlyDatapoint(AbstractModbusDatapoint): self.lastValue)) - class InputRegisterDatapoint(ReadOnlyDatapoint): - def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, updateOnly=None, + def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, updateOnly=None, publishTopic=None, converter=None): super().__init__(label, unit, address, count, scanRate, updateOnly, publishTopic, converter) self.type = 'input register' @@ -224,7 +226,7 @@ class InputRegisterDatapoint(ReadOnlyDatapoint): class DiscreteInputDatapoint(ReadOnlyDatapoint): - def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, updateOnly=None, + def __init__(self, label=None, unit=None, address=None, count=None, scanRate=None, updateOnly=None, publishTopic=None, converter=None, bitCount=8): super().__init__(label, unit, address, count, scanRate, updateOnly, publishTopic, converter) self.argList = self.argList + ['bitCount'] @@ -253,12 +255,11 @@ class DiscreteInputDatapoint(ReadOnlyDatapoint): self.lastValues[i] = result.getBit(i) logger.debug("{0}, {1}: changed: {2!s}".format(self.label, i, result.getBit(i))) if self.publishTopic: - pubQueue.put(MqttProcessor.PublishItem("{0}/{1}".format(self.publishTopic, i), str(result.getBit(i)))) + pubQueue.put(MqttProcessor.PublishItem("{0}/{1}" + .format(self.publishTopic, i), str(result.getBit(i)))) self.lastContact = datetime.datetime.now() - - class JsonifyEncoder(json.JSONEncoder): def default(self, o): res = None @@ -271,6 +272,7 @@ class JsonifyEncoder(json.JSONEncoder): res = super().default(o) return res + def datapointObjectHook(j): if type(j) == dict and 'type' in j and 'args' in j: klass = eval(j['type']) @@ -279,14 +281,15 @@ def datapointObjectHook(j): else: return j + def saveRegisterList(registerList, registerListFile): js = json.dumps(registerList, cls=JsonifyEncoder, sort_keys=True, indent=4) with open(registerListFile, 'w') as f: f.write(js) - + + def loadRegisterList(registerListFile): with open(registerListFile, 'r') as f: js = f.read() registerList = json.loads(js, object_hook=datapointObjectHook) return registerList - diff --git a/src/ScanRateConsideringQueueFeeder.py b/src/ScanRateConsideringQueueFeeder.py index 99364c9..1fd3aad 100644 --- a/src/ScanRateConsideringQueueFeeder.py +++ b/src/ScanRateConsideringQueueFeeder.py @@ -3,6 +3,7 @@ import datetime from NotificationForwarder import AbstractNotificationReceiver import logging + class ScanRateConsideringQueueFeeder(threading.Thread, AbstractNotificationReceiver): def __init__(self, config, registers, queue): super().__init__() @@ -14,7 +15,7 @@ class ScanRateConsideringQueueFeeder(threading.Thread, AbstractNotificationRecei self.logger = logging.getLogger('ScanRateConsideringQueueFeeder') def getMinimalScanrate(self): - return min([r.scanRate.total_seconds() for r in self.registers if r.scanRate]) + return min([r.scanRate.total_seconds() for r in self.registers if r.scanRate]) def receiveNotification(self, arg): self.logger.info("ScanRateConsideringQueueFeeder:registersChanged") @@ -26,10 +27,10 @@ class ScanRateConsideringQueueFeeder(threading.Thread, AbstractNotificationRecei registersToBeHandled = [ r for r in self.registers if ((not r.enqueued) and (r.scanRate) and - ((not r.lastContact) or + ((not r.lastContact) or (r.lastContact + r.scanRate < datetime.datetime.now()))) ] - registersToBeHandled.sort(key=lambda x : x.scanRate) + registersToBeHandled.sort(key=lambda x: x.scanRate) for r in registersToBeHandled: self.queue.put(r) r.enqueued = True diff --git a/src/initialRegisterFile.py b/src/initialRegisterFile.py index 615679b..fa10058 100644 --- a/src/initialRegisterFile.py +++ b/src/initialRegisterFile.py @@ -4,12 +4,14 @@ import pickle datapoints = [ - RegisterDatapoint.InputRegisterDatapoint('Temperature', 5, 0x0001, 1, datetime.timedelta(seconds=1.0), False, 'Pub/Temperature'), - RegisterDatapoint.InputRegisterDatapoint('Humidity', 5, 0x0002, 1, datetime.timedelta(seconds=1.0), True, 'Pub/Humidity'), - RegisterDatapoint.DiscreteInputDatapoint('Switches', 4, 0x0000, 1, datetime.timedelta(seconds=1.0), True, 'Pub/Switches'), + RegisterDatapoint.InputRegisterDatapoint('Temperature', 5, 0x0001, 1, + datetime.timedelta(seconds=1.0), False, 'Pub/Temperature'), + RegisterDatapoint.InputRegisterDatapoint('Humidity', 5, 0x0002, 1, + datetime.timedelta(seconds=1.0), True, 'Pub/Humidity'), + RegisterDatapoint.DiscreteInputDatapoint('Switches', 4, 0x0000, 1, + datetime.timedelta(seconds=1.0), True, 'Pub/Switches'), ] with open('registers.pkl', 'wb') as f: pickle.dump(datapoints, f) - diff --git a/src/loadRegisterFile.py b/src/loadRegisterFile.py index 7a18791..49a2223 100644 --- a/src/loadRegisterFile.py +++ b/src/loadRegisterFile.py @@ -3,4 +3,4 @@ import RegisterDatapoint registers = RegisterDatapoint.loadRegisterList('registers.json') for r in registers: - print("{0!s}".format(r)) \ No newline at end of file + print("{0!s}".format(r)) diff --git a/src/master.py b/src/master.py index 011fad5..d015004 100644 --- a/src/master.py +++ b/src/master.py @@ -36,7 +36,6 @@ if __name__ == "__main__": nf = NotificationForwarder.NotificationForwarder() logger.debug('infrastructure prepared') - datapoints = RegisterDatapoint.loadRegisterList(config.registerFile) logger.debug('datapoints read') @@ -56,7 +55,7 @@ if __name__ == "__main__": hb = Heartbeat.Heartbeat(config, pubQueue) hb.start() logger.debug('Heartbeat started') - + qf = ScanRateConsideringQueueFeeder.ScanRateConsideringQueueFeeder(config, datapoints, queue) nf.register(qf) qf.start() diff --git a/src/updateRegisterFile.py b/src/updateRegisterFile.py index 39d7b5c..64f46ae 100644 --- a/src/updateRegisterFile.py +++ b/src/updateRegisterFile.py @@ -10,15 +10,12 @@ with open('registers.pkl', 'rb') as f: newDatapoints = [] for dp in datapoints: ndp = type(dp)() - for k,v in dp.__dict__.items(): + for k, v in dp.__dict__.items(): if k != 'logger': ndp.__dict__[k] = v newDatapoints.append(ndp) - - js = json.dumps(newDatapoints, cls=RegisterDatapoint.JsonifyEncoder, sort_keys=True, indent=4) print(js) - -RegisterDatapoint.saveRegisterList(newDatapoints, 'registers.json') \ No newline at end of file +RegisterDatapoint.saveRegisterList(newDatapoints, 'registers.json') From 0f007b058de5bb06405744fd80f6869ca47676c3 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:34:45 +0200 Subject: [PATCH 12/17] fix style issues --- src/MyPriorityQueue.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/MyPriorityQueue.py b/src/MyPriorityQueue.py index a143c23..2305fad 100644 --- a/src/MyPriorityQueue.py +++ b/src/MyPriorityQueue.py @@ -6,10 +6,15 @@ class MyPriorityQueueItem(object): self.itemWithPriority = itemWithPriority def __lt__(self, other): return self.itemWithPriority.priority < other.itemWithPriority.priority + def __le__(self, other): return self.itemWithPriority.priority <= other.itemWithPriority.priority + def __eq__(self, other): return self.itemWithPriority.priority == other.itemWithPriority.priority + def __ne__(self, other): return self.itemWithPriority.priority != other.itemWithPriority.priority + def __gt__(self, other): return self.itemWithPriority.priority > other.itemWithPriority.priority + def __ge__(self, other): return self.itemWithPriority.priority >= other.itemWithPriority.priority From 2930d393454eb5381dfdacfc21564a818d5d2089 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:35:51 +0200 Subject: [PATCH 13/17] fix style issue --- src/MyPriorityQueue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MyPriorityQueue.py b/src/MyPriorityQueue.py index 2305fad..a8c600b 100644 --- a/src/MyPriorityQueue.py +++ b/src/MyPriorityQueue.py @@ -14,7 +14,7 @@ class MyPriorityQueueItem(object): def __ne__(self, other): return self.itemWithPriority.priority != other.itemWithPriority.priority def __gt__(self, other): return self.itemWithPriority.priority > other.itemWithPriority.priority - + def __ge__(self, other): return self.itemWithPriority.priority >= other.itemWithPriority.priority From 78bf04f191421a0ae03ec711be89059ce3140a29 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:40:59 +0200 Subject: [PATCH 14/17] update ci script --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4986d18..e49fa22 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,5 +23,6 @@ deploy: only: - deploy script: - - whoami + - cp src/*.py /opt/services/modbusMaster + From 489eb0dda8f814466f5f39dc7303ffbf646be166 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:48:55 +0200 Subject: [PATCH 15/17] more ci --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e49fa22..664858c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,5 +24,5 @@ deploy: - deploy script: - cp src/*.py /opt/services/modbusMaster - + - cp src/modbusMaster.service /opt/services/modbusMaster From e78df8be4076a511d65be9be5fea2a584d1ce90f Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:55:12 +0200 Subject: [PATCH 16/17] more ci --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 664858c..4b10154 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,6 +23,8 @@ deploy: only: - deploy script: + - sudo service modbusMaster stop - cp src/*.py /opt/services/modbusMaster - cp src/modbusMaster.service /opt/services/modbusMaster - + - sudo service modbusMaster start + From 612c4ab2ac03e768b744db206b1eff3a3e8a4cdf Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 10 Sep 2019 16:58:59 +0200 Subject: [PATCH 17/17] more ci --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4b10154..3f4a36e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,6 +25,5 @@ deploy: script: - sudo service modbusMaster stop - cp src/*.py /opt/services/modbusMaster - - cp src/modbusMaster.service /opt/services/modbusMaster - sudo service modbusMaster start - +