This commit is contained in:
Wolfgang Hottgenroth 2021-08-22 23:57:45 +02:00
parent 3192897b06
commit d62356b1f5
Signed by: wn
GPG Key ID: E49AF3B9EF6DD469
7 changed files with 218 additions and 0 deletions

30
src/DummyPublisher.py Normal file
View File

@ -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])

77
src/ModbusHandler.py Normal file
View File

@ -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()

97
src/ProcessImage.py Normal file
View File

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

14
src/digitaltwin1.py Normal file
View File

@ -0,0 +1,14 @@
from ProcessImage import ProcessImage
from ModbusHandler import modbusHandler
from DummyPublisher import dummyPublisher as publisher
import threading
processImage = ProcessImage()
modbusThread = threading.Thread(target=modbusHandler, args=[processImage])
publisherThread = threading.Thread(target=publisher, args=[processImage])
modbusThread.start()
publisherThread.start()