--- a/features.py Thu Jan 09 09:46:43 2025 +0100
+++ b/features.py Fri Dec 20 14:32:33 2024 +0100
@@ -12,7 +12,8 @@
('Native', 'NativeLib.NativeLibrary', True),
('Python', 'py_ext.PythonLibrary', True),
# FIXME ('Etherlab', 'etherlab.EthercatMaster.EtherlabLibrary', False),
- ('SVGHMI', 'svghmi.SVGHMILibrary', 'svghmi')]
+ ('SVGHMI', 'svghmi.SVGHMILibrary', 'svghmi'), + ('MQTT', 'mqtt.MQTTLibrary', False)] ('mqtt', _('MQTT client'), _('Map MQTT topics as located variables'), 'mqtt.MQTTClient'),
--- a/mqtt/__init__.py Thu Jan 09 09:46:43 2025 +0100
+++ b/mqtt/__init__.py Fri Dec 20 14:32:33 2024 +0100
@@ -3,4 +3,5 @@
from __future__ import absolute_import
from .client import MQTTClient
+from .library import MQTTLibrary --- a/mqtt/client.py Thu Jan 09 09:46:43 2025 +0100
+++ b/mqtt/client.py Fri Dec 20 14:32:33 2024 +0100
@@ -178,7 +178,8 @@
config = self.GetConfig()
- c_code += self.modeldata.GenerateC(c_path, locstr, config, self.GetDataTypeInfos)
+ name = self.BaseParams.getName() + c_code += self.modeldata.GenerateC(name, c_path, locstr, config, self.GetDataTypeInfos) with open(c_path, 'w') as c_file:
--- a/mqtt/mqtt_client_gen.py Thu Jan 09 09:46:43 2025 +0100
+++ b/mqtt/mqtt_client_gen.py Fri Dec 20 14:32:33 2024 +0100
@@ -356,7 +356,7 @@
writer.writerow([direction] + row)
- def GenerateC(self, path, locstr, config, datatype_info_getter):
+ def GenerateC(self, name, path, locstr, config, datatype_info_getter): c_template_filepath = paths.AbsNeighbourFile(__file__, "mqtt_template.c")
c_template_file = open(c_template_filepath , 'rb')
c_template = c_template_file.read()
@@ -365,6 +365,7 @@
clientID = config["clientID"],
--- a/mqtt/mqtt_template.c Thu Jan 09 09:46:43 2025 +0100
+++ b/mqtt/mqtt_template.c Fri Dec 20 14:32:33 2024 +0100
@@ -260,8 +260,10 @@
+typedef int(*callback_fptr_t)(char* topic, char* data, uint32_t datalen); +static callback_fptr_t __mqtt_python_callback_fptr_{name} = NULL; -int messageArrived(void *context, char *topicName, int topicLen, MQTTClient_message *message)
+static int messageArrived(void *context, char *topicName, int topicLen, MQTTClient_message *message) int size = sizeof(topics) / sizeof(topics[0]);
@@ -307,7 +309,15 @@
// If we reach here, then the element was not present
- LogWarning("MQTT unknown topic: %s\n", topicName);
+ if(__mqtt_python_callback_fptr_{name} && + (*__mqtt_python_callback_fptr_{name})(topicName, + (char*)message->payload, + message->payloadlen) == 0){{ + // Topic was handled in python + LogWarning("MQTT unknown topic: %s\n", topicName); @@ -357,19 +367,19 @@
#define _SUBSCRIBE(Topic, QoS) \
- MQTTResponse response = MQTTClient_subscribe5(client, #Topic, QoS, NULL, NULL); \
+ MQTTResponse response = MQTTClient_subscribe5(client, Topic, QoS, NULL, NULL); \ /* when using MQTT5 responce code is 1 for some reason even if no error */ \
rc = response.reasonCode == 1 ? MQTTCLIENT_SUCCESS : response.reasonCode; \
MQTTResponse_free(response);
#define _SUBSCRIBE(Topic, QoS) \
- rc = MQTTClient_subscribe(client, #Topic, QoS);
+ rc = MQTTClient_subscribe(client, Topic, QoS); #define INIT_SUBSCRIPTION(Topic, QoS) \
- _SUBSCRIBE(Topic, QoS) \
+ _SUBSCRIBE(#Topic, QoS) \ if (rc != MQTTCLIENT_SUCCESS) \
LogError("MQTT client failed to subscribe to '%s', return code %d\n", #Topic, rc); \
@@ -379,18 +389,18 @@
#define _PUBLISH(Topic, QoS, cstring_size, cstring_ptr, Retained) \
- MQTTResponse response = MQTTClient_publish5(client, #Topic, cstring_size, \
+ MQTTResponse response = MQTTClient_publish5(client, Topic, cstring_size, \ cstring_ptr, QoS, Retained, NULL, NULL); \
rc = response.reasonCode; \
MQTTResponse_free(response);
#define _PUBLISH(Topic, QoS, cstring_size, cstring_ptr, Retained) \
- rc = MQTTClient_publish(client, #Topic, cstring_size, \
+ rc = MQTTClient_publish(client, Topic, cstring_size, \ cstring_ptr, QoS, Retained, NULL);
#define PUBLISH_SIMPLE(Topic, QoS, C_type, c_loc_name, Retained) \
- _PUBLISH(Topic, QoS, sizeof(C_type), &MQTT_##c_loc_name##_buf, Retained)
+ _PUBLISH(#Topic, QoS, sizeof(C_type), &MQTT_##c_loc_name##_buf, Retained) #define PUBLISH_JSON(Topic, QoS, C_type, c_loc_name, Retained) \
int res = json_gen_##c_loc_name(&MQTT_##c_loc_name##_buf); \
@@ -589,3 +599,39 @@
+int __mqtt_python_publish_{name}(char* topic, char* data, uint32_t datalen, uint8_t QoS, uint8_t Retained) + if((rc = pthread_mutex_lock(&MQTT_thread_wakeup_mutex)) == 0){{ + _PUBLISH(topic, QoS, datalen, data, Retained) + pthread_mutex_unlock(&MQTT_thread_wakeup_mutex); + if (rc != MQTTCLIENT_SUCCESS){{ + LogError("MQTT python can't publish \"%s\", return code %d\n", topic, rc); + LogError("MQTT python can't obtain lock, return code %d\n", rc); +int __mqtt_python_subscribe_{name}(char* topic, uint8_t QoS) + if (rc != MQTTCLIENT_SUCCESS) + LogError("MQTT client failed to subscribe to '%s', return code %d\n", topic, rc); +int __mqtt_python_callback_setter_{name}(callback_fptr_t cb) + __mqtt_python_callback_fptr_{name} = cb; --- a/tests/projects/mqtt_client/beremiz.xml Thu Jan 09 09:46:43 2025 +0100
+++ b/tests/projects/mqtt_client/beremiz.xml Fri Dec 20 14:32:33 2024 +0100
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="PYRO://localhost:61131" Disable_Extensions="false">
- <Libraries Enable_Python_Library="false"/>
+ <Libraries Enable_Python_Library="true" Enable_MQTT_Library="true"/> --- a/tests/projects/mqtt_client/plc.xml Thu Jan 09 09:46:43 2025 +0100
+++ b/tests/projects/mqtt_client/plc.xml Fri Dec 20 14:32:33 2024 +0100
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
<fileHeader companyName="Beremiz" productName="Beremiz" productVersion="1" creationDateTime="2016-10-24T18:09:22"/>
- <contentHeader name="First Steps" modificationDateTime="2024-07-25T16:55:27">
+ <contentHeader name="First Steps" modificationDateTime="2024-12-18T14:47:09"> @@ -41,6 +41,18 @@
+ <variable name="python_eval0"> + <derived name="python_eval"/> + <variable name="python_eval1"> + <derived name="python_eval"/> @@ -52,7 +64,7 @@
<expression>LocalVar0</expression>
<inVariable localId="3" executionOrderId="0" height="27" width="82" negated="false">
- <position x="126" y="224"/>
+ <position x="814" y="106"/> <relPosition x="82" y="13"/>
@@ -224,11 +236,11 @@
<variable formalParameter="IN0">
<relPosition x="0" y="50"/>
- <connection refLocalId="12">
+ <connection refLocalId="3"> <position x="956" y="103"/>
- <position x="927" y="103"/>
- <position x="927" y="115"/>
- <position x="898" y="115"/>
+ <position x="926" y="103"/> + <position x="926" y="119"/> + <position x="896" y="119"/> @@ -238,8 +250,8 @@
<connection refLocalId="13">
<position x="956" y="123"/>
<position x="933" y="123"/>
- <position x="933" y="171"/>
- <position x="910" y="171"/>
+ <position x="933" y="159"/> + <position x="910" y="159"/> @@ -253,20 +265,186 @@
- <inVariable localId="12" executionOrderId="0" height="27" width="34" negated="false">
- <position x="872" y="102"/>
+ <inVariable localId="13" executionOrderId="0" height="27" width="34" negated="false"> + <position x="876" y="146"/> <relPosition x="34" y="13"/>
<expression>666</expression>
- <inVariable localId="13" executionOrderId="0" height="27" width="34" negated="false">
- <position x="876" y="158"/>
+ <block localId="14" typeName="python_eval" instanceName="python_eval0" executionOrderId="0" height="60" width="98"> + <position x="849" y="235"/> + <variable formalParameter="TRIG"> + <relPosition x="0" y="30"/> + <connection refLocalId="8" formalParameter="OUT"> + <position x="849" y="265"/> + <position x="782" y="265"/> + <position x="782" y="199"/> + <position x="794" y="199"/> + <position x="794" y="95"/> + <position x="784" y="95"/> + <variable formalParameter="CODE"> + <relPosition x="0" y="50"/> + <connection refLocalId="16"> + <position x="849" y="285"/> + <position x="797" y="285"/> + <position x="797" y="291"/> + <position x="745" y="291"/> + <variable formalParameter="ACK"> + <relPosition x="98" y="30"/> + <variable formalParameter="RESULT"> + <relPosition x="98" y="50"/> + <inVariable localId="15" executionOrderId="0" height="27" width="354" negated="false"> + <position x="1384" y="186"/> + <relPosition x="354" y="13"/> + <expression>'sys.stdout.write("MQTT PYTHON TEST OK\n")'</expression> + <inVariable localId="16" executionOrderId="0" height="27" width="218" negated="false"> + <position x="527" y="278"/> - <relPosition x="34" y="13"/>
+ <relPosition x="218" y="13"/> - <expression>666</expression>
+ <expression>'publish_example("mhooo")'</expression> + <block localId="12" typeName="python_eval" instanceName="python_eval1" executionOrderId="0" height="60" width="98"> + <position x="1803" y="146"/> + <variable formalParameter="TRIG"> + <relPosition x="0" y="30"/> + <connection refLocalId="19" formalParameter="OUT"> + <position x="1803" y="176"/> + <position x="1472" y="176"/> + <position x="1472" y="173"/> + <position x="1302" y="173"/> + <variable formalParameter="CODE"> + <relPosition x="0" y="50"/> + <connection refLocalId="15"> + <position x="1803" y="196"/> + <position x="1748" y="196"/> + <position x="1748" y="199"/> + <position x="1738" y="199"/> + <variable formalParameter="ACK"> + <relPosition x="98" y="30"/> + <variable formalParameter="RESULT"> + <relPosition x="98" y="50"/> + <block localId="17" typeName="EQ" executionOrderId="0" height="60" width="63"> + <position x="1144" y="204"/> + <variable formalParameter="IN1"> + <relPosition x="0" y="30"/> + <connection refLocalId="18"> + <position x="1144" y="234"/> + <position x="1114" y="234"/> + <position x="1114" y="235"/> + <position x="1104" y="235"/> + <variable formalParameter="IN2"> + <relPosition x="0" y="50"/> + <connection refLocalId="14" formalParameter="RESULT"> + <position x="1144" y="254"/> + <position x="1063" y="254"/> + <position x="1063" y="285"/> + <position x="947" y="285"/> + <variable formalParameter="OUT"> + <relPosition x="63" y="30"/> + <inVariable localId="18" executionOrderId="0" height="27" width="95" negated="false"> + <position x="1009" y="222"/> + <relPosition x="95" y="13"/> + <expression>'mhooo'</expression> + <block localId="19" typeName="AND" executionOrderId="0" height="60" width="63"> + <position x="1239" y="143"/> + <variable formalParameter="IN1"> + <relPosition x="0" y="30"/> + <connection refLocalId="14" formalParameter="ACK"> + <position x="1239" y="173"/> + <position x="990" y="173"/> + <position x="990" y="265"/> + <position x="947" y="265"/> + <variable formalParameter="IN2"> + <relPosition x="0" y="50"/> + <connection refLocalId="17" formalParameter="OUT"> + <position x="1239" y="193"/> + <position x="1223" y="193"/> + <position x="1223" y="234"/> + <position x="1207" y="234"/> + <variable formalParameter="OUT"> + <relPosition x="63" y="30"/> --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/mqtt_client/py_ext_0@py_ext/baseconfnode.xml Fri Dec 20 14:32:33 2024 +0100
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?> +<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="1" Name="py_ext_0"/> --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/mqtt_client/py_ext_0@py_ext/pyfile.xml Fri Dec 20 14:32:33 2024 +0100
@@ -0,0 +1,38 @@
+<?xml version='1.0' encoding='utf-8'?> +<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> +def topic_callback(topic, payload): +def publish_example(data): + MQTT_publish("mqtt_0", "py_test", data) +MQTT_subscribe("mqtt_0", "py_test", topic_callback)