10 Commits

Author SHA1 Message Date
532b5b7211 read coils 6
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2025-12-15 11:45:55 +01:00
25f6a1f43f read coils 5
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2025-12-15 11:35:52 +01:00
a5f9527f4d read coils 4
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2025-12-15 11:20:46 +01:00
08c1faf606 read coils 3
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2025-12-15 11:09:21 +01:00
00afad4a3d read coils 2 2025-12-15 11:07:41 +01:00
086c240638 read coils 2025-12-15 11:06:36 +01:00
93bbccf5c3 fix typo in configuration
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-11 15:32:32 +01:00
012bb46b2a fix configuration
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-11 15:27:28 +01:00
ae1828a06e fix use of modbus module
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-11 15:23:07 +01:00
51dec2b281 fix ci script
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-11 15:16:47 +01:00
7 changed files with 45 additions and 27 deletions

View File

@@ -2,23 +2,23 @@ steps:
build: build:
image: plugins/kaniko image: plugins/kaniko
settings: settings:
repo: gitea.hottis.de/wn/pv-controller repo: gitea.hottis.de/wn/digitaltwin1
registry: registry:
from_secret: container_registry from_secret: local_registry
tags: latest,${CI_COMMIT_SHA},${CI_COMMIT_TAG} tags: latest,${CI_COMMIT_TAG}
username: username:
from_secret: container_registry_username from_secret: local_username
password: password:
from_secret: container_registry_password from_secret: local_password
dockerfile: Dockerfile dockerfile: Dockerfile
when: when:
- event: [push, tag] - event: tag
deploy: deploy:
image: portainer/kubectl-shell:latest image: portainer/kubectl-shell:latest
secrets: environment:
- source: kube_config KUBE_CONFIG_CONTENT:
target: KUBE_CONFIG_CONTENT from_secret: kube_config
commands: commands:
- export IMAGE_TAG=$CI_COMMIT_TAG - export IMAGE_TAG=$CI_COMMIT_TAG
- printf "$KUBE_CONFIG_CONTENT" > /tmp/kubeconfig - printf "$KUBE_CONFIG_CONTENT" > /tmp/kubeconfig
@@ -27,3 +27,4 @@ steps:
when: when:
- event: tag - event: tag

View File

@@ -11,7 +11,7 @@ ARG CONF_DIR="${APP_DIR}/config"
RUN \ RUN \
apt update && \ apt update && \
pip3 install loguru && \ pip3 install loguru && \
pip3 install pymodbus && \ pip3 install pymodbus==3.6.3 && \
pip3 install paho-mqtt pip3 install paho-mqtt
RUN \ RUN \

View File

@@ -12,11 +12,12 @@ data:
MQTT__BROKER: "emqx01-anonymous-cluster-internal.broker.svc.cluster.local" MQTT__BROKER: "emqx01-anonymous-cluster-internal.broker.svc.cluster.local"
MQTT__DIGITALOUTPUTTOPICPREFIX: "dt1/coil" MQTT__DIGITALOUTPUTTOPICPREFIX: "dt1/coil"
MQTT__DIGITALINPUTTOPICPREFIX: "dt1/di" MQTT__DIGITALINPUTTOPICPREFIX: "dt1/di"
MQTT__COILINPUTTOPICPREFIX: "dt1/ci"
MQTT__ANALOGINPUTEVENTTOPICPREFIX: "dt1/ai/event" MQTT__ANALOGINPUTEVENTTOPICPREFIX: "dt1/ai/event"
MQTT__ANALOGINPUTPERIODICTOPICPREFIX: "dt1/ai/periodic" MQTT__ANALOGINPUTPERIODICTOPICPREFIX: "dt1/ai/periodic"
MQTT__ANALOGPUBLISHPERIOD: "10.0" MQTT__ANALOGINPUTPUBLISHPERIOD: "60.0"
MQTT__DISABLEANALOGINPUTEVENTPUBLISHING: "true" MQTT__DISABLEANALOGINPUTEVENTPUBLISHING: "true"
MODBUS__CLIENT: "172.16.2.157" MODBUS__CLIENT: "172.16.2.31"
MODBUS__SCANRATE: "0.25" MODBUS__SCANRATE: "0.25"
--- ---
apiVersion: apps/v1 apiVersion: apps/v1

View File

@@ -1,4 +1,4 @@
from pymodbus.client.sync import ModbusTcpClient as ModbusClient from pymodbus.client import ModbusTcpClient as ModbusClient
from pymodbus.exceptions import ModbusIOException from pymodbus.exceptions import ModbusIOException
from time import sleep from time import sleep
import threading import threading
@@ -39,17 +39,17 @@ class ModbusHandler(threading.Thread):
self.processImage.init(digitalOutputBits, digitalInputBits, analogInputBits) self.processImage.init(digitalOutputBits, digitalInputBits, analogInputBits)
reg = client.read_coils(0, digitalOutputBits)
if isinstance(reg, ModbusIOException):
raise Exception(reg)
with self.processImage:
self.processImage.setCoils(reg.bits)
while not self.killBill: while not self.killBill:
try: try:
if not client.is_socket_open(): if not client.is_socket_open():
client.connect() client.connect()
reg = client.read_coils(0, digitalOutputBits)
if isinstance(reg, ModbusIOException):
raise Exception(reg)
readCoils = reg.bits
reg = client.read_input_registers(0, analogInputBits // 8) reg = client.read_input_registers(0, analogInputBits // 8)
if isinstance(reg, ModbusIOException): if isinstance(reg, ModbusIOException):
raise Exception(reg) raise Exception(reg)
@@ -64,6 +64,7 @@ class ModbusHandler(threading.Thread):
with self.processImage: with self.processImage:
self.processImage.setAnalogsInputs(analogInputs) self.processImage.setAnalogsInputs(analogInputs)
self.processImage.setDiscreteInputs(discreteInputs) self.processImage.setDiscreteInputs(discreteInputs)
self.processImage.setCoils(readCoils)
if self.processImage.hasPendingInputChanges(): if self.processImage.hasPendingInputChanges():
self.processImage.notify() self.processImage.notify()
if self.processImage.hasPendingOutputChanges(): if self.processImage.hasPendingOutputChanges():

View File

@@ -22,6 +22,7 @@ class MqttEventPublisher(AbstractMqttPublisher):
continue continue
discreteInputChangeset = self.processImage.getChangedDiscreteInputs() discreteInputChangeset = self.processImage.getChangedDiscreteInputs()
coilInputChangeset = self.processImage.getChangedCoils()
if not self.disableAnalogInputEventPublishing: if not self.disableAnalogInputEventPublishing:
analogInputChangeset = self.processImage.getChangedAnalogsInputs() analogInputChangeset = self.processImage.getChangedAnalogsInputs()
@@ -34,6 +35,15 @@ class MqttEventPublisher(AbstractMqttPublisher):
str(discreteInputChangeItem[1][0]), str(discreteInputChangeItem[1][0]),
retain=True) retain=True)
for coilInputChangeItem in coilInputChangeset:
logger.debug("Coil input {} changed from {} to {}"
.format(coilInputChangeItem[0],
coilInputChangeItem[1][1],
coilInputChangeItem[1][0]))
self.client.publish("{}/{}".format(self.config["coilInputTopicPrefix"], str(coilInputChangeItem[0])),
str(coilInputChangeItem[1][0]),
retain=True)
if not self.disableAnalogInputEventPublishing: if not self.disableAnalogInputEventPublishing:
for analogInputChangeItem in analogInputChangeset: for analogInputChangeItem in analogInputChangeset:
logger.debug("Analog input {} changed from {} to {}" logger.debug("Analog input {} changed from {} to {}"

View File

@@ -16,6 +16,8 @@ class ProcessImage(Condition):
self.numCoils = numCoils self.numCoils = numCoils
self.coils = [] self.coils = []
self.shadowCoils = [ None ] * numCoils self.shadowCoils = [ None ] * numCoils
self.readCoils = [ None ] * numCoils
self.readShadowCoils = [ None ] * numCoils
self.numDiscreteInputs = numDiscreteInputs self.numDiscreteInputs = numDiscreteInputs
self.discreteInputs = [] self.discreteInputs = []
@@ -31,7 +33,7 @@ class ProcessImage(Condition):
return self.initialized return self.initialized
def hasPendingInputChanges(self): def hasPendingInputChanges(self):
return (self.discreteInputs != self.shadowDiscreteInputs) or (self.analogInputs != self.shadowAnalogInputs) return (self.discreteInputs != self.shadowDiscreteInputs) or (self.analogInputs != self.shadowAnalogInputs) or (self.readCoils != self.readShadowCoils)
def hasPendingOutputChanges(self): def hasPendingOutputChanges(self):
return self.shadowCoils != self.coils return self.shadowCoils != self.coils
@@ -56,6 +58,8 @@ class ProcessImage(Condition):
def setCoils(self, coils): def setCoils(self, coils):
if not self.initialized: if not self.initialized:
raise NotInitializedException raise NotInitializedException
self.readCoils = coils
if self.coils == []:
self.coils = coils self.coils = coils
def setCoil(self, coilNum, value): def setCoil(self, coilNum, value):
@@ -64,12 +68,12 @@ class ProcessImage(Condition):
self.coils[coilNum] = value self.coils[coilNum] = value
self.coilEvent.set() self.coilEvent.set()
# def getChangedCoils(self): def getChangedCoils(self):
# if not self.initialized: if not self.initialized:
# raise NotInitializedException raise NotInitializedException
# changedCoils = zippingFilter(self.coils, self.shadowCoils) changedCoils = zippingFilter(self.coils, self.readCoils)
# self.shadowCoils = self.coils self.readShadowCoils = self.coils
# return changedCoils return changedCoils
def getCoils(self): def getCoils(self):
if not self.initialized: if not self.initialized:

View File

@@ -4,8 +4,9 @@ from loguru import logger
class Config: class Config:
OPTIONS = { OPTIONS = {
'mqtt': [ 'broker', 'mqtt': [ 'broker',
'digitalOutputTopicPrefix',
'digitalInputTopicPrefix', 'digitalInputTopicPrefix',
'digitalInputTopicPrefix', 'coilInputTopicPrefix',
'analogInputEventTopicPrefix', 'analogInputEventTopicPrefix',
'analogInputPeriodicTopicPrefix', 'analogInputPeriodicTopicPrefix',
'analogInputPublishPeriod', 'analogInputPublishPeriod',