Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
78f001fd70
|
|||
fc78dc7f4d
|
|||
1c8a3a2093
|
|||
92aeb8efdf
|
|||
8120df60ac
|
|||
3d97536b9b
|
|||
220d1e0ef9
|
@ -11,7 +11,8 @@ ARG CONF_DIR="${APP_DIR}/config"
|
|||||||
RUN \
|
RUN \
|
||||||
apt update && \
|
apt update && \
|
||||||
pip3 install loguru && \
|
pip3 install loguru && \
|
||||||
pip3 install pymodbus
|
pip3 install pymodbus && \
|
||||||
|
pip3 install paho-mqtt
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
mkdir -p ${APP_DIR} && \
|
mkdir -p ${APP_DIR} && \
|
||||||
@ -26,5 +27,5 @@ WORKDIR ${APP_DIR}
|
|||||||
VOLUME ${CONF_DIR}
|
VOLUME ${CONF_DIR}
|
||||||
|
|
||||||
|
|
||||||
CMD "python digitaltwin1.py -f ./config/config.ini"
|
CMD [ "/usr/local/bin/python", "digitaltwin1.py", "-f", "./config/config.ini" ]
|
||||||
|
|
||||||
|
@ -9,3 +9,4 @@ digitalInputTopicPrefix = dt1/di
|
|||||||
analogInputEventTopicPrefix = dt1/ai/event
|
analogInputEventTopicPrefix = dt1/ai/event
|
||||||
analogInputPeriodicTopicPrefix = dt1/ai/periodic
|
analogInputPeriodicTopicPrefix = dt1/ai/periodic
|
||||||
analogInputPublishPeriod = 10.0
|
analogInputPublishPeriod = 10.0
|
||||||
|
disableAnalogInputEventPublishing = true
|
55
readme.md
Normal file
55
readme.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# DigitalTwin1
|
||||||
|
## Digital twin for Beckhoff BK-9000 Modbus-TCP coupler (and compatible Wago device)
|
||||||
|
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
This tool connects to a Modbus-TCP coupler, reads it process image and publishes
|
||||||
|
|
||||||
|
* the new state of a discrete input register (digital input) after a change
|
||||||
|
* the new value of an input register (analog input) after a change
|
||||||
|
* periodically publishes the current values of all input registers (analog inputs)
|
||||||
|
|
||||||
|
using MQTT messages. The topic is built from a configured prefix and the index of the particular registers.
|
||||||
|
|
||||||
|
Furthermore it subscribes to MQTT messages related to the coils for the setup and anytime a message is received, it set the particular coils accordingly.
|
||||||
|
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Configuration is done using a configuration file
|
||||||
|
|
||||||
|
[modbus]
|
||||||
|
client = 172.16.2.157
|
||||||
|
scanrate = 0.25
|
||||||
|
|
||||||
|
[mqtt]
|
||||||
|
broker = 172.16.2.16
|
||||||
|
digitalOutputTopicPrefix = dt1/coil
|
||||||
|
digitalInputTopicPrefix = dt1/di
|
||||||
|
analogInputEventTopicPrefix = dt1/ai/event
|
||||||
|
analogInputPeriodicTopicPrefix = dt1/ai/periodic
|
||||||
|
analogInputPublishPeriod = 10.0
|
||||||
|
disableAnalogInputEventPublishing = true
|
||||||
|
|
||||||
|
### Operation details
|
||||||
|
|
||||||
|
While the input and discrete input registers are scanned with the configured scanrate, an action on a coil according to a received message is issued immediately. At the same time the all input registers are scanned.
|
||||||
|
|
||||||
|
Using a configuration option it is possible to disable the publishing of analog change event - if only periodic information (like for a thermometer or so) is required.
|
||||||
|
|
||||||
|
The MQTT messages related to change events of input or discrete input registers are marked as //retained//.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
The tool is simply started using
|
||||||
|
|
||||||
|
python digitaltwin1.py
|
||||||
|
|
||||||
|
On default it searches the configuration file at ``$PWD/config``. This can be changed using the commandline argument ``-f``.
|
||||||
|
|
||||||
|
The Python modules ``pymodbus``, ``loguru`` and ``paho-mqtt`` are required.
|
||||||
|
|
||||||
|
A docker image with the required dependencies, the tool and a prepared default configuration in a volume is provided at ``registry.hottis.de/wolutator/digitaltwin1``.
|
||||||
|
|
@ -12,7 +12,7 @@ def modbusStart(config, processImage):
|
|||||||
|
|
||||||
def modbusHandler(config, processImage):
|
def modbusHandler(config, processImage):
|
||||||
modbusClient = config["modbus"]["client"]
|
modbusClient = config["modbus"]["client"]
|
||||||
modbusRefreshPeriod = config["modbus"]["scanrate"]
|
modbusRefreshPeriod = float(config["modbus"]["scanrate"])
|
||||||
|
|
||||||
|
|
||||||
client = ModbusClient(modbusClient)
|
client = ModbusClient(modbusClient)
|
||||||
|
@ -15,7 +15,7 @@ class MqttCoilSubscriber(AbstractMqttPublisher):
|
|||||||
|
|
||||||
def localLoop(self):
|
def localLoop(self):
|
||||||
while True:
|
while True:
|
||||||
sleep(self.config["analogInputPublishPeriod"])
|
sleep(float(self.config["analogInputPublishPeriod"]))
|
||||||
|
|
||||||
def onMessage(self, topic, payload):
|
def onMessage(self, topic, payload):
|
||||||
logger.warning("mqtt message received: {} -> {}".format(topic, str(payload)))
|
logger.warning("mqtt message received: {} -> {}".format(topic, str(payload)))
|
||||||
|
@ -12,6 +12,7 @@ def mqttEventPublisherStart(config, processImage):
|
|||||||
class MqttEventPublisher(AbstractMqttPublisher):
|
class MqttEventPublisher(AbstractMqttPublisher):
|
||||||
def __init__(self, config, processImage):
|
def __init__(self, config, processImage):
|
||||||
super().__init__(config, processImage)
|
super().__init__(config, processImage)
|
||||||
|
self.disableAnalogInputEventPublishing = self.config["disableAnalogInputEventPublishing"].lower() in [ "true", "on" ]
|
||||||
|
|
||||||
def localLoop(self):
|
def localLoop(self):
|
||||||
while True:
|
while True:
|
||||||
@ -19,7 +20,8 @@ class MqttEventPublisher(AbstractMqttPublisher):
|
|||||||
self.processImage.wait()
|
self.processImage.wait()
|
||||||
|
|
||||||
discreteInputChangeset = self.processImage.getChangedDiscreteInputs()
|
discreteInputChangeset = self.processImage.getChangedDiscreteInputs()
|
||||||
analogInputChangeset = self.processImage.getChangedAnalogsInputs()
|
if not self.disableAnalogInputEventPublishing:
|
||||||
|
analogInputChangeset = self.processImage.getChangedAnalogsInputs()
|
||||||
|
|
||||||
for discreteInputChangeItem in discreteInputChangeset:
|
for discreteInputChangeItem in discreteInputChangeset:
|
||||||
logger.debug("Discrete input {} changed from {} to {}"
|
logger.debug("Discrete input {} changed from {} to {}"
|
||||||
@ -27,15 +29,18 @@ class MqttEventPublisher(AbstractMqttPublisher):
|
|||||||
discreteInputChangeItem[1][1],
|
discreteInputChangeItem[1][1],
|
||||||
discreteInputChangeItem[1][0]))
|
discreteInputChangeItem[1][0]))
|
||||||
self.client.publish("{}/{}".format(self.config["digitalInputTopicPrefix"], str(discreteInputChangeItem[0])),
|
self.client.publish("{}/{}".format(self.config["digitalInputTopicPrefix"], str(discreteInputChangeItem[0])),
|
||||||
str(discreteInputChangeItem[1][0]))
|
str(discreteInputChangeItem[1][0]),
|
||||||
|
retain=True)
|
||||||
|
|
||||||
for analogInputChangeItem in analogInputChangeset:
|
if not self.disableAnalogInputEventPublishing:
|
||||||
logger.debug("Analog input {} changed from {} to {}"
|
for analogInputChangeItem in analogInputChangeset:
|
||||||
.format(analogInputChangeItem[0],
|
logger.debug("Analog input {} changed from {} to {}"
|
||||||
analogInputChangeItem[1][1],
|
.format(analogInputChangeItem[0],
|
||||||
analogInputChangeItem[1][0]))
|
analogInputChangeItem[1][1],
|
||||||
|
analogInputChangeItem[1][0]))
|
||||||
self.client.publish("{}/{}".format(self.config["analogInputEventTopicPrefix"], str(analogInputChangeItem[0])),
|
|
||||||
str(analogInputChangeItem[1][0]))
|
self.client.publish("{}/{}".format(self.config["analogInputEventTopicPrefix"], str(analogInputChangeItem[0])),
|
||||||
|
str(analogInputChangeItem[1][0]),
|
||||||
|
retain=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,5 +28,5 @@ class MqttPeriodPublisher(AbstractMqttPublisher):
|
|||||||
self.client.publish("{}/{}".format(self.config["analogInputPeriodicTopicPrefix"], str(analogInputItem[0])),
|
self.client.publish("{}/{}".format(self.config["analogInputPeriodicTopicPrefix"], str(analogInputItem[0])),
|
||||||
str(analogInputItem[1]))
|
str(analogInputItem[1]))
|
||||||
|
|
||||||
sleep(self.config["analogInputPublishPeriod"])
|
sleep(float(self.config["analogInputPublishPeriod"]))
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user