sources
This commit is contained in:
parent
3192897b06
commit
d62356b1f5
30
src/DummyPublisher.py
Normal file
30
src/DummyPublisher.py
Normal 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
77
src/ModbusHandler.py
Normal 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
97
src/ProcessImage.py
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
BIN
src/__pycache__/DummyPublisher.cpython-38.pyc
Normal file
BIN
src/__pycache__/DummyPublisher.cpython-38.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/ModbusHandler.cpython-38.pyc
Normal file
BIN
src/__pycache__/ModbusHandler.cpython-38.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/ProcessImage.cpython-38.pyc
Normal file
BIN
src/__pycache__/ProcessImage.cpython-38.pyc
Normal file
Binary file not shown.
14
src/digitaltwin1.py
Normal file
14
src/digitaltwin1.py
Normal 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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user