From 0fcd505e6540851f90af30ec6aa85ac6c38e9588 Mon Sep 17 00:00:00 2001 From: Luigi Caiffa Date: Thu, 25 Dec 2025 23:26:59 +0100 Subject: [PATCH 1/7] fix: update mosquitto config and docker command for run test with last mosquitto version --- .travis.yml | 4 ++-- README.rst | 4 ++-- mosquitto/anonymous/mosquitto.conf | 2 ++ mosquitto/{ => private}/mosquitto.conf | 1 + mosquitto/{ => private}/passwd_file | 0 5 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 mosquitto/anonymous/mosquitto.conf rename mosquitto/{ => private}/mosquitto.conf (81%) rename mosquitto/{ => private}/passwd_file (100%) diff --git a/.travis.yml b/.travis.yml index 37e6327..d67c7b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,8 @@ python: - '3.7' before_install: - docker pull eclipse-mosquitto -- docker run -d -p 11883:1883 -p 9001:9001 -v $(pwd)/mosquitto:/mosquitto/config eclipse-mosquitto -- docker run -d -p 1883:1883 eclipse-mosquitto +- docker run -d -p 11883:1883 -p 9001:9001 -v $(pwd)/mosquitto/private:/mosquitto/config eclipse-mosquitto +- docker run -d -p 1883:1883 -v $(pwd)/mosquitto/anonymous:/mosquitto/config eclipse-mosquitto install: - pip install -r requirements.txt script: diff --git a/README.rst b/README.rst index 4d1c125..0c4b8b0 100644 --- a/README.rst +++ b/README.rst @@ -74,8 +74,8 @@ The keywords in this library are based on some of the methods available in eclip The tests are in ``tests`` folder and make use of Robot Framework itself. They are run automatically through travis when code is pushed to a branch. When run locally, these tests rely on locally running mqtt brokers. We need 2 running brokers, one without auth that is used by most of the tests, and the other one with auth (configuration file is provided). You'll need to start them before running the tests. You can then run the tests locally:: docker pull eclipse-mosquitto - docker run -d -p 1883:1883 eclipse-mosquitto - docker run -d -p 11883:1883 -p 9001:9001 -v $(pwd)/mosquitto:/mosquitto/config eclipse-mosquitto + docker run -d -p 1883:1883 -v $(pwd)/mosquitto/anonymous:/mosquitto/config eclipse-mosquitto + docker run -d -p 11883:1883 -p 9001:9001 -v $(pwd)/mosquitto/private:/mosquitto/config eclipse-mosquitto robot -P src tests diff --git a/mosquitto/anonymous/mosquitto.conf b/mosquitto/anonymous/mosquitto.conf new file mode 100644 index 0000000..daa4137 --- /dev/null +++ b/mosquitto/anonymous/mosquitto.conf @@ -0,0 +1,2 @@ +listener 1883 +allow_anonymous true \ No newline at end of file diff --git a/mosquitto/mosquitto.conf b/mosquitto/private/mosquitto.conf similarity index 81% rename from mosquitto/mosquitto.conf rename to mosquitto/private/mosquitto.conf index 5f47cb3..58223bb 100644 --- a/mosquitto/mosquitto.conf +++ b/mosquitto/private/mosquitto.conf @@ -1,2 +1,3 @@ allow_anonymous false +listener 11883 password_file ./mosquitto/config/passwd_file \ No newline at end of file diff --git a/mosquitto/passwd_file b/mosquitto/private/passwd_file similarity index 100% rename from mosquitto/passwd_file rename to mosquitto/private/passwd_file From 9d0524f0a2e2c854ab61f0a812202566f230b9aa Mon Sep 17 00:00:00 2001 From: Luigi Caiffa Date: Thu, 25 Dec 2025 23:29:35 +0100 Subject: [PATCH 2/7] fix: import local Library --- tests/connect.robot | 2 +- tests/keywords.robot | 9 ++++----- tests/publish.robot | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/connect.robot b/tests/connect.robot index 39a03f3..dfb0c8e 100644 --- a/tests/connect.robot +++ b/tests/connect.robot @@ -1,6 +1,6 @@ *** Settings *** | Library | String -| Library | MQTTLibrary +| Library | ../src/MQTTLibrary/MQTTKeywords.py | Test Timeout | 30 seconds diff --git a/tests/keywords.robot b/tests/keywords.robot index 59f824d..b881056 100644 --- a/tests/keywords.robot +++ b/tests/keywords.robot @@ -1,6 +1,5 @@ | *Settings* | *Value* -| Library | MQTTLibrary -| Library | BuiltIn +| Library | ../src/MQTTLibrary/MQTTKeywords.py | *Variables* | *Value* #| ${broker.uri} | mqtt.eclipse.org @@ -51,7 +50,7 @@ | | Connect | ${broker.uri} | ${port} | ${client.id} | ${false} | | @{messages} | Subscribe | ${topic} | ${qos} | ${timeout} | ${limit} | | [Teardown] | Disconnect -| | [Return] | @{messages} +| | RETURN | @{messages} | Subscribe Async | | [Arguments] | ${broker.uri}=${broker.uri} | ${port}=${broker.port} @@ -82,10 +81,10 @@ | | @{messages} | Subscribe | ${topic} | ${qos} | ${timeout} | ${limit} | | Unsubscribe | ${topic} | | [Teardown] | Disconnect -| | [Return] | @{messages} +| | RETURN | @{messages} | Listen and Get Messages | | [Arguments] | ${topic}=${topic} | ${timeout}=1s | | ... | ${limit}=1 | | @{messages} | Listen | ${topic} | ${timeout} | ${limit} -| | [Return] | @{messages} +| | RETURN | @{messages} diff --git a/tests/publish.robot b/tests/publish.robot index 9199e4c..0939c07 100644 --- a/tests/publish.robot +++ b/tests/publish.robot @@ -1,5 +1,5 @@ *** Settings *** -| Library | MQTTLibrary +| Library | ../src/MQTTLibrary/MQTTKeywords.py | Library | Collections | Test Timeout | 30 seconds From 8142366f691332f369d51f51889800395f9035cd Mon Sep 17 00:00:00 2001 From: Luigi Caiffa Date: Thu, 25 Dec 2025 23:30:02 +0100 Subject: [PATCH 3/7] feat: update deps --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index c27739d..20ae982 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -paho-mqtt~=1.1 -robotframework~=3.0 +paho-mqtt~=2.0 +robotframework~=7.0 From 708b7c184ee97f43a1979fb033f94837c163b2e2 Mon Sep 17 00:00:00 2001 From: Luigi Caiffa Date: Thu, 25 Dec 2025 23:30:52 +0100 Subject: [PATCH 4/7] feat: migrate paho to v2 version --- src/MQTTLibrary/MQTTKeywords.py | 106 ++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/src/MQTTLibrary/MQTTKeywords.py b/src/MQTTLibrary/MQTTKeywords.py index 63934b5..16e41b1 100644 --- a/src/MQTTLibrary/MQTTKeywords.py +++ b/src/MQTTLibrary/MQTTKeywords.py @@ -1,13 +1,13 @@ from paho.mqtt.matcher import MQTTMatcher import paho.mqtt.client as mqtt import paho.mqtt.publish as publish -import robot import time import re from robot.libraries.DateTime import convert_time from robot.api import logger + # https://github.com/eclipse/paho.mqtt.python/blob/1eec03edf39128e461e6729694cf5d7c1959e5e4/src/paho/mqtt/client.py#L250 def topic_matches_sub(sub, topic): """Check whether a topic matches a subscription. @@ -23,6 +23,7 @@ def topic_matches_sub(sub, topic): except StopIteration: return False + class MQTTKeywords(object): # Timeout used for all blocking loop* functions. This serves as a @@ -35,7 +36,7 @@ def __init__(self, loop_timeout=LOOP_TIMEOUT): self._messages = {} self._username = None self._password = None - #self._mqttc = mqtt.Client() + # self._mqttc = mqtt.Client() def set_username_and_password(self, username, password=None): self._username = username @@ -68,7 +69,11 @@ def connect(self, broker, port=1883, client_id="", clean_session=True): logger.info('Connecting to %s at port %s' % (broker, port)) self._connected = False self._unexpected_disconnect = False - self._mqttc = mqtt.Client(client_id, clean_session) + self._mqttc = mqtt.Client( + callback_api_version=mqtt.CallbackAPIVersion.VERSION2, + client_id=client_id, + clean_session=clean_session + ) # set callbacks self._mqttc.on_connect = self._on_connect @@ -82,7 +87,7 @@ def connect(self, broker, port=1883, client_id="", clean_session=True): timer_start = time.time() while time.time() < timer_start + self._loop_timeout: if self._connected or self._unexpected_disconnect: - break; + break self._mqttc.loop() if self._unexpected_disconnect: @@ -108,8 +113,10 @@ def publish(self, topic, message=None, qos=0, retain=False): | Publish | test/test | test message | 1 | ${false} | """ - logger.info('Publish topic: %s, message: %s, qos: %s, retain: %s' - % (topic, message, qos, retain)) + logger.info( + 'Publish topic: %s, message: %s, qos: %s, retain: %s' + % (topic, message, qos, retain) + ) self._mid = -1 self._mqttc.on_publish = self._on_publish result, mid = self._mqttc.publish(topic, message, int(qos), retain) @@ -119,7 +126,7 @@ def publish(self, topic, message=None, qos=0, retain=False): timer_start = time.time() while time.time() < timer_start + self._loop_timeout: if mid == self._mid: - break; + break self._mqttc.loop() if mid != self._mid: @@ -133,7 +140,8 @@ def subscribe(self, topic, qos, timeout=1, limit=1): `qos` quality of service for the subscription - `timeout` duration of subscription. Specify 0 to enable background looping (async) + `timeout` duration of subscription. Specify 0 to enable background + looping (async) `limit` the max number of payloads that will be returned. Specify 0 for no limit @@ -180,7 +188,8 @@ def subscribe(self, topic, qos, timeout=1, limit=1): def listen(self, topic, timeout=1, limit=1): """ Listen to a topic and return a list of message payloads received - within the specified time. Requires an async Subscribe to have been called previously. + within the specified time. Requires an async Subscribe to have + been called previously. `topic` topic to listen to @@ -202,14 +211,17 @@ def listen(self, topic, timeout=1, limit=1): timer_start = time.time() while time.time() < timer_start + self._loop_timeout: if self._subscribed: - break; + break time.sleep(1) if not self._subscribed: logger.warn('Cannot listen when not subscribed to a topic') return [] if topic not in self._messages: - logger.warn('Cannot listen when not subscribed to topic: %s' % topic) + logger.warn( + 'Cannot listen when not subscribed to topic: %s' + % topic + ) return [] # If enough messages have already been gathered, return them @@ -279,7 +291,9 @@ def subscribe_and_validate(self, topic, qos, payload, timeout=1): self._mqttc.loop() if not self._verified: - raise AssertionError("The expected payload didn't arrive in the topic") + raise AssertionError( + "The expected payload didn't arrive in the topic" + ) def unsubscribe(self, topic): """ Unsubscribe the client from the specified topic. @@ -291,9 +305,11 @@ def unsubscribe(self, topic): """ try: - tmp = self._mqttc + self._mqttc except AttributeError: - logger.info('No MQTT Client instance found so nothing to unsubscribe from.') + logger.info( + 'No MQTT Client instance found so nothing to unsubscribe from.' + ) return if self._background_mqttc: @@ -325,9 +341,11 @@ def disconnect(self): """ try: - tmp = self._mqttc + self._mqttc except AttributeError: - logger.info('No MQTT Client instance found so nothing to disconnect from.') + logger.info( + 'No MQTT Client instance found so nothing to disconnect from.' + ) return self._disconnected = False @@ -338,14 +356,16 @@ def disconnect(self): timer_start = time.time() while time.time() < timer_start + self._loop_timeout: if self._disconnected or self._unexpected_disconnect: - break; + break self._mqttc.loop() if self._unexpected_disconnect: raise RuntimeError("The client disconnected unexpectedly") - def publish_single(self, topic, payload=None, qos=0, retain=False, - hostname="localhost", port=1883, client_id="", keepalive=60, - will=None, auth=None, tls=None, protocol=mqtt.MQTTv31): + def publish_single( + self, topic, payload=None, qos=0, retain=False, + hostname="localhost", port=1883, client_id="", keepalive=60, + will=None, auth=None, tls=None, protocol=mqtt.MQTTv31 + ): """ Publish a single message and disconnect. This keyword uses the [http://eclipse.org/paho/clients/python/docs/#single|single] @@ -389,12 +409,16 @@ def publish_single(self, topic, payload=None, qos=0, retain=False, """ logger.info('Publishing to: %s:%s, topic: %s, payload: %s, qos: %s' % (hostname, port, topic, payload, qos)) - publish.single(topic, payload, qos, retain, hostname, port, - client_id, keepalive, will, auth, tls, protocol) + publish.single( + topic, payload, qos, retain, hostname, port, + client_id, keepalive, will, auth, tls, protocol + ) - def publish_multiple(self, msgs, hostname="localhost", port=1883, - client_id="", keepalive=60, will=None, auth=None, - tls=None, protocol=mqtt.MQTTv31): + def publish_multiple( + self, msgs, hostname="localhost", port=1883, + client_id="", keepalive=60, will=None, auth=None, + tls=None, protocol=mqtt.MQTTv31 + ): """ Publish multiple messages and disconnect. This keyword uses the [http://eclipse.org/paho/clients/python/docs/#multiple|multiple] @@ -423,40 +447,46 @@ def publish_multiple(self, msgs, hostname="localhost", port=1883, """ logger.info('Publishing to: %s:%s, msgs: %s' % (hostname, port, msgs)) - publish.multiple(msgs, hostname, port, client_id, keepalive, - will, auth, tls, protocol) + publish.multiple( + msgs, hostname, port, client_id, keepalive, + will, auth, tls, protocol + ) def _on_message(self, client, userdata, message): payload = message.payload.decode('utf-8') - logger.debug('Received message: %s on topic: %s with QoS: %s' - % (payload, message.topic, str(message.qos))) + logger.debug( + 'Received message: %s on topic: %s with QoS: %s' + % (payload, message.topic, str(message.qos)) + ) self._verified = re.match(self._payload, payload) def _on_message_list(self, client, userdata, message): payload = message.payload.decode('utf-8') - logger.debug('Received message: %s on topic: %s with QoS: %s' - % (payload, message.topic, str(message.qos))) + logger.debug( + 'Received message: %s on topic: %s with QoS: %s' + % (payload, message.topic, str(message.qos)) + ) if message.topic not in self._messages: self._messages[message.topic] = [] for sub in self._messages: if topic_matches_sub(sub, message.topic): self._messages[sub].append(payload) - def _on_connect(self, client, userdata, flags, rc): - self._connected = True if rc == 0 else False + def _on_connect(self, client, userdata, flags, reason_code, properties): + self._connected = True if reason_code == 0 else False - def _on_disconnect(self, client, userdata, rc): - if rc == 0: + def _on_disconnect(self, client, userdata, flags, reason_code, properties): + if reason_code == 0: self._disconnected = True self._unexpected_disconnect = False else: self._unexpected_disconnect = True - def _on_subscribe(self, client, userdata, mid, granted_qos): + def _on_subscribe(self, client, userdata, mid, reason_codes, properties): self._subscribed = True - def _on_unsubscribe(self, client, userdata, mid): + def _on_unsubscribe(self, client, userdata, mid, reason_codes, properties): self._unsubscribed = True - def _on_publish(self, client, userdata, mid): + def _on_publish(self, client, userdata, mid, reason_codes, properties): self._mid = mid From de65fa9a8b9935de5840d15a2a43bd311dd7bcea Mon Sep 17 00:00:00 2001 From: Luigi Caiffa Date: Fri, 26 Dec 2025 00:16:12 +0100 Subject: [PATCH 5/7] fix: listener port for private mosquitto scenario --- mosquitto/private/mosquitto.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mosquitto/private/mosquitto.conf b/mosquitto/private/mosquitto.conf index 58223bb..352b986 100644 --- a/mosquitto/private/mosquitto.conf +++ b/mosquitto/private/mosquitto.conf @@ -1,3 +1,3 @@ allow_anonymous false -listener 11883 +listener 1883 password_file ./mosquitto/config/passwd_file \ No newline at end of file From a34eb6c6a4f93b607e41383e5a4981b6baf1bf12 Mon Sep 17 00:00:00 2001 From: Luigi Caiffa Date: Fri, 26 Dec 2025 00:16:46 +0100 Subject: [PATCH 6/7] feat: update mqtt protocol --- src/MQTTLibrary/MQTTKeywords.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MQTTLibrary/MQTTKeywords.py b/src/MQTTLibrary/MQTTKeywords.py index 16e41b1..a101bb0 100644 --- a/src/MQTTLibrary/MQTTKeywords.py +++ b/src/MQTTLibrary/MQTTKeywords.py @@ -364,7 +364,7 @@ def disconnect(self): def publish_single( self, topic, payload=None, qos=0, retain=False, hostname="localhost", port=1883, client_id="", keepalive=60, - will=None, auth=None, tls=None, protocol=mqtt.MQTTv31 + will=None, auth=None, tls=None, protocol=mqtt.MQTTv5 ): """ Publish a single message and disconnect. This keyword uses the @@ -399,7 +399,7 @@ def publish_single( 'keyfile':"", 'tls_version':"", 'ciphers':"} - `protocol` MQTT protocol version (MQTTv31 or MQTTv311) + `protocol` MQTT protocol version (MQTTv31, MQTTv311 or MQTTv5) Example: @@ -417,7 +417,7 @@ def publish_single( def publish_multiple( self, msgs, hostname="localhost", port=1883, client_id="", keepalive=60, will=None, auth=None, - tls=None, protocol=mqtt.MQTTv31 + tls=None, protocol=mqtt.MQTTv5 ): """ Publish multiple messages and disconnect. This keyword uses the From 50df9a2339dbc1c8dc32205a7fb77693108aa93b Mon Sep 17 00:00:00 2001 From: Luigi Caiffa Date: Fri, 26 Dec 2025 00:17:50 +0100 Subject: [PATCH 7/7] minor: update version --- src/MQTTLibrary/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MQTTLibrary/version.py b/src/MQTTLibrary/version.py index 2f3282d..f8428b8 100644 --- a/src/MQTTLibrary/version.py +++ b/src/MQTTLibrary/version.py @@ -1 +1 @@ -VERSION = '0.7.1.post3' +VERSION = '0.8.0'