From d62356b1f59d7bbd79c6bcc35c285829ea954017 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Sun, 22 Aug 2021 23:57:45 +0200 Subject: [PATCH] sources --- src/DummyPublisher.py | 30 ++++++ src/ModbusHandler.py | 77 ++++++++++++++ src/ProcessImage.py | 97 ++++++++++++++++++ src/__pycache__/DummyPublisher.cpython-38.pyc | Bin 0 -> 509 bytes src/__pycache__/ModbusHandler.cpython-38.pyc | Bin 0 -> 1903 bytes src/__pycache__/ProcessImage.cpython-38.pyc | Bin 0 -> 3701 bytes src/digitaltwin1.py | 14 +++ 7 files changed, 218 insertions(+) create mode 100644 src/DummyPublisher.py create mode 100644 src/ModbusHandler.py create mode 100644 src/ProcessImage.py create mode 100644 src/__pycache__/DummyPublisher.cpython-38.pyc create mode 100644 src/__pycache__/ModbusHandler.cpython-38.pyc create mode 100644 src/__pycache__/ProcessImage.cpython-38.pyc create mode 100644 src/digitaltwin1.py diff --git a/src/DummyPublisher.py b/src/DummyPublisher.py new file mode 100644 index 0000000..1580b67 --- /dev/null +++ b/src/DummyPublisher.py @@ -0,0 +1,30 @@ + +def dummyPublisher(processImage): + while True: + discreteInputChangeset = [] + analogInputChangeset = [] + + with processImage: + processImage.wait() + + discreteInputChangeset = processImage.getChangedDiscreteInputs() + analogInputChangeset = processImage.getChangedAnalogsInputs() + + if discreteInputChangeset != []: + print("Discrete: ".format(discreteInputChangeset)) + if analogInputChangeset != []: + print("Analog: ".format(analogInputChangeset)) + +# for discreteInputChangeItem in discreteInputChangeset: +# print("Discrete input {} changed from {} to {}" +# .format(discreteInputChangeItem[0]), +# discreteInputChangeItem[1][1], +# discreteInputChangeItem[1][0]) +# +# for analogInputChangeItem in analogInputChangeset: +# print("Analog input {} changed from {} to {}" +# .format(analogInputChangeItem[0]), +# analogInputChangeItem[1][1], +# analogInputChangeItem[1][0]) + + diff --git a/src/ModbusHandler.py b/src/ModbusHandler.py new file mode 100644 index 0000000..fd6de66 --- /dev/null +++ b/src/ModbusHandler.py @@ -0,0 +1,77 @@ +from pymodbus.client.sync import ModbusTcpClient as ModbusClient +from pymodbus.exceptions import ModbusIOException +from time import sleep +import threading + + +MODBUS_CLIENT = '172.16.2.157' +MODBUS_REFRESH_PERIOD = 0.25 + + + +def modbusHandler(processImage): + client = ModbusClient(MODBUS_CLIENT) + + try: + client.connect() + + res = client.read_holding_registers(0x1010, 4) + if isinstance(res, ModbusIOException): + raise Exception(processImage) + + if len(res.registers) != 4: + raise Exception("Unexpected number of registers for process image ({})".format(len(res.registers))) + + (analogOutputBits, analogInputBits, digitalOutputBits, digitalInputBits) = res.registers + print(f"AO: {analogOutputBits}, AI: {analogInputBits}, DO: {digitalOutputBits}, DI: {digitalInputBits}") + + processImage.init(digitalOutputBits, digitalInputBits, analogInputBits) + + reg = client.read_coils(0, digitalOutputBits) + if isinstance(reg, ModbusIOException): + raise Exception(reg) + with processImage: + processImage.setCoils(reg.bits) + + while True: + try: + if not client.is_socket_open(): + client.connect() + + reg = client.read_input_registers(0, analogInputBits // 8) + print("M0: ".format(type(reg))) + if isinstance(reg, ModbusIOException): + raise Exception(reg) + analogInputs = reg.registers + print("M1: ".format(reg.registers)) + + reg = client.read_discrete_inputs(0, digitalInputBits) + if isinstance(reg, ModbusIOException): + raise Exception(reg) + discreteInputs = reg.bits + print("M2: ".format(reg.bits)) + + coils = None + with processImage: + processImage.setAnalogsInputs(analogInputs) + processImage.setDiscreteInputs(discreteInputs) + if processImage.hasPendingInputChanges(): + processImage.notify() + if processImage.hasPendingOutputChanges: + coils = processImage.getCoils() + + if coils: + reg = client.write_coils(0, coils) + if isinstance(reg, ModbusIOException): + raise Exception(reg) + + except Exception as e: + print("Exception in inner modbus handler loop: {}".format(e)) + client.close() + finally: + sleep(MODBUS_REFRESH_PERIOD) + + except Exception as e: + print("Exception in modbus handler: {}".format(e)) + finally: + client.close() diff --git a/src/ProcessImage.py b/src/ProcessImage.py new file mode 100644 index 0000000..775444f --- /dev/null +++ b/src/ProcessImage.py @@ -0,0 +1,97 @@ +from threading import Condition + + +def zippingFilter(a, b): + return [ x for x in enumerate(zip(a, b)) if x[1][0] == x[1][1] ] + +class NotInitializedException(Exception): pass + +class ProcessImage(Condition): + def __init__(self): + super().__init__() + self.initialized = False + + def init(self, numCoils, numDiscreteInputs, numAnalogInputs): + self.coils = [] + self.shadowCoils = [ None ] * numCoils + + self.discreteInputs = [] + self.shadowDiscreteInputs = [ None ] * numDiscreteInputs + + self.analogInputs = [] + self.shadowAnalogInputs = [ None ] * (numAnalogInputs // 8) + + self.initialized = True + + def isInitialized(self): + return self.initialized + + def hasPendingInputChanges(self): + return (self.discreteInputs != self.shadowDiscreteInputs) or (self.analogInputs != self.shadowAnalogInputs) + + def hasPendingOutputChanges(self): + return self.shadowCoils != self.coils + + def setDiscreteInputs(self, discreteInputs): + if not self.initialized: + raise NotInitializedException + self.discreteInputs = discreteInputs + + def getChangedDiscreteInputs(self): + if not self.initialized: + raise NotInitializedException + print("D1: ".format(self.discreteInputs)) + print("D2: ".format(self.shadowDiscreteInputs)) + changedDiscreteInputs = zippingFilter(self.discreteInputs, self.shadowDiscreteInputs) + print("D3: ".format(changedDiscreteInputs)) + self.shadowDiscreteInputs = self.discreteInputs + print("D4: ".format(self.shadowDiscreteInputs)) + return changedDiscreteInputs + + def getDiscreteInputs(self): + if not self.initialized: + raise NotInitializedException + self.shadowDiscreteInputs = self.discreteInputs + return self.discreteInputs + + def setCoils(self, coils): + if not self.initialized: + raise NotInitializedException + self.coils = coils + + def getChangedCoils(self): + if not self.initialized: + raise NotInitializedException + changedCoils = zippingFilter(self.coils, self.shadowCoils) + self.shadowCoils = self.coils + return changedCoils + + def getCoils(self): + if not self.initialized: + raise NotInitializedException + self.shadowCoils = self.coils + return self.coils + + def setAnalogsInputs(self, analogInputs): + if not self.initialized: + raise NotInitializedException + self.analogInputs = analogInputs + + def getChangedAnalogsInputs(self): + if not self.initialized: + raise NotInitializedException + changedAnalogInputs = zippingFilter(self.analogInputs, self.shadowAnalogInputs) + self.shadowAnalogInputs = self.analogInputs + return changedAnalogInputs + + def getAnalogsInputs(self): + if not self.initialized: + raise NotInitializedException + self.shadowAnalogInputs = self.analogInputs + return self.analogInputs + + + + + + diff --git a/src/__pycache__/DummyPublisher.cpython-38.pyc b/src/__pycache__/DummyPublisher.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..752461f2c7c1a6f430d6acd8ce2ca9684938b3ec GIT binary patch literal 509 zcmY*Wy-or_5T4yT@FYNCh*}w9ca0YdEr>B1hzSkxHyFC*ZapsCpSw#4rq1#%0L-$ZgckLaL9Mr_z0wtLA`(E_YUS z?JyoGneP27i(7Ij6k_3pDlI-mB4EnY(sC>!n@iaan9F;+&T?K}zUVEv>eX02dz1YD zv4t=3duk1$fLr6x>PBKHV}|{#mgjnk`S)WlJhr5;t=1?ACfB3B@5uodNAV=BIvZ5> t6|#m7s7`cRBnCCe4x&m7BC^Ntn2VT?U2_I%sc{xW&dBHIS)U&S`~eN>g3AB^ literal 0 HcmV?d00001 diff --git a/src/__pycache__/ModbusHandler.cpython-38.pyc b/src/__pycache__/ModbusHandler.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80f587f84ad943600816a9e2cc5d33d11ff46e36 GIT binary patch literal 1903 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