ohmqtt 0.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ohmqtt-0.0.3/LICENSE +7 -0
- ohmqtt-0.0.3/MANIFEST.in +10 -0
- ohmqtt-0.0.3/PKG-INFO +103 -0
- ohmqtt-0.0.3/README.md +76 -0
- ohmqtt-0.0.3/examples/__init__.py +0 -0
- ohmqtt-0.0.3/examples/args.py +10 -0
- ohmqtt-0.0.3/examples/publish.py +34 -0
- ohmqtt-0.0.3/examples/publish_retain.py +41 -0
- ohmqtt-0.0.3/examples/publish_wait_for_ack.py +32 -0
- ohmqtt-0.0.3/examples/rpc_caller.py +92 -0
- ohmqtt-0.0.3/examples/rpc_server.py +62 -0
- ohmqtt-0.0.3/noxfile.py +19 -0
- ohmqtt-0.0.3/ohmqtt/__init__.py +0 -0
- ohmqtt-0.0.3/ohmqtt/client.py +266 -0
- ohmqtt-0.0.3/ohmqtt/connection/__init__.py +117 -0
- ohmqtt-0.0.3/ohmqtt/connection/address.py +91 -0
- ohmqtt-0.0.3/ohmqtt/connection/decoder.py +132 -0
- ohmqtt-0.0.3/ohmqtt/connection/fsm.py +179 -0
- ohmqtt-0.0.3/ohmqtt/connection/handlers.py +86 -0
- ohmqtt-0.0.3/ohmqtt/connection/keepalive.py +139 -0
- ohmqtt-0.0.3/ohmqtt/connection/selector.py +113 -0
- ohmqtt-0.0.3/ohmqtt/connection/states.py +468 -0
- ohmqtt-0.0.3/ohmqtt/connection/timeout.py +41 -0
- ohmqtt-0.0.3/ohmqtt/connection/types.py +98 -0
- ohmqtt-0.0.3/ohmqtt/error.py +10 -0
- ohmqtt-0.0.3/ohmqtt/logger.py +14 -0
- ohmqtt-0.0.3/ohmqtt/mqtt_spec.py +110 -0
- ohmqtt-0.0.3/ohmqtt/packet/__init__.py +78 -0
- ohmqtt-0.0.3/ohmqtt/packet/auth.py +57 -0
- ohmqtt-0.0.3/ohmqtt/packet/base.py +29 -0
- ohmqtt-0.0.3/ohmqtt/packet/connect.py +252 -0
- ohmqtt-0.0.3/ohmqtt/packet/ping.py +53 -0
- ohmqtt-0.0.3/ohmqtt/packet/publish.py +253 -0
- ohmqtt-0.0.3/ohmqtt/packet/subscribe.py +200 -0
- ohmqtt-0.0.3/ohmqtt/persistence/__init__.py +0 -0
- ohmqtt-0.0.3/ohmqtt/persistence/base.py +118 -0
- ohmqtt-0.0.3/ohmqtt/persistence/in_memory.py +142 -0
- ohmqtt-0.0.3/ohmqtt/persistence/sqlite.py +275 -0
- ohmqtt-0.0.3/ohmqtt/platform.py +11 -0
- ohmqtt-0.0.3/ohmqtt/property.py +329 -0
- ohmqtt-0.0.3/ohmqtt/protected.py +53 -0
- ohmqtt-0.0.3/ohmqtt/py.typed +0 -0
- ohmqtt-0.0.3/ohmqtt/serialization.py +181 -0
- ohmqtt-0.0.3/ohmqtt/session.py +224 -0
- ohmqtt-0.0.3/ohmqtt/subscriptions.py +429 -0
- ohmqtt-0.0.3/ohmqtt/topic_alias.py +135 -0
- ohmqtt-0.0.3/ohmqtt/topic_filter.py +91 -0
- ohmqtt-0.0.3/ohmqtt.egg-info/PKG-INFO +103 -0
- ohmqtt-0.0.3/ohmqtt.egg-info/SOURCES.txt +79 -0
- ohmqtt-0.0.3/ohmqtt.egg-info/dependency_links.txt +1 -0
- ohmqtt-0.0.3/ohmqtt.egg-info/requires.txt +3 -0
- ohmqtt-0.0.3/ohmqtt.egg-info/top_level.txt +1 -0
- ohmqtt-0.0.3/pyproject.toml +117 -0
- ohmqtt-0.0.3/setup.cfg +4 -0
- ohmqtt-0.0.3/tests/__init__.py +0 -0
- ohmqtt-0.0.3/tests/conftest.py +156 -0
- ohmqtt-0.0.3/tests/data/test_address.yml +283 -0
- ohmqtt-0.0.3/tests/data/test_packet.yml +441 -0
- ohmqtt-0.0.3/tests/data/test_serialization.yml +191 -0
- ohmqtt-0.0.3/tests/test_address.py +76 -0
- ohmqtt-0.0.3/tests/test_client.py +258 -0
- ohmqtt-0.0.3/tests/test_connection.py +180 -0
- ohmqtt-0.0.3/tests/test_decoder.py +129 -0
- ohmqtt-0.0.3/tests/test_error.py +9 -0
- ohmqtt-0.0.3/tests/test_fsm.py +280 -0
- ohmqtt-0.0.3/tests/test_keepalive.py +106 -0
- ohmqtt-0.0.3/tests/test_packet.py +285 -0
- ohmqtt-0.0.3/tests/test_persistence_handles.py +38 -0
- ohmqtt-0.0.3/tests/test_persistence_sqlite.py +234 -0
- ohmqtt-0.0.3/tests/test_property.py +18 -0
- ohmqtt-0.0.3/tests/test_protected.py +51 -0
- ohmqtt-0.0.3/tests/test_selector.py +82 -0
- ohmqtt-0.0.3/tests/test_serialization.py +178 -0
- ohmqtt-0.0.3/tests/test_session.py +140 -0
- ohmqtt-0.0.3/tests/test_states.py +1165 -0
- ohmqtt-0.0.3/tests/test_subscriptions.py +275 -0
- ohmqtt-0.0.3/tests/test_timeout.py +47 -0
- ohmqtt-0.0.3/tests/test_topic_alias.py +108 -0
- ohmqtt-0.0.3/tests/test_topic_filter.py +108 -0
- ohmqtt-0.0.3/tests/util/__init__.py +0 -0
- ohmqtt-0.0.3/tests/util/selfsigned.py +93 -0
ohmqtt-0.0.3/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2025 Matt Vollrath
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
ohmqtt-0.0.3/MANIFEST.in
ADDED
ohmqtt-0.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ohmqtt
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Persistent MQTT 5.0 client
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/ohmqtt/ohmqtt_python
|
|
7
|
+
Project-URL: Issues, https://github.com/ohmqtt/ohmqtt_python/issues
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Framework :: Pytest
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Communications
|
|
19
|
+
Classifier: Topic :: Home Automation
|
|
20
|
+
Classifier: Topic :: Internet
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: typing-extensions>=4.12.2; python_version < "3.11"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# ΩQTT
|
|
29
|
+
|
|
30
|
+
*pron. "ohm cue tee tee" or "omega cutie"*
|
|
31
|
+
|
|
32
|
+
A reliable and persistent MQTT 5.0 client library for Python.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
### QoS and Persistence
|
|
37
|
+
|
|
38
|
+
ΩQTT supports publish and subscribing all QoS levels with optional persistence to disk for QoS >0.
|
|
39
|
+
When not persisting QoS >0 messages, a fast (but volatile) in memory store is used.
|
|
40
|
+
Either way, publishing a message returns a handle with a method to wait for the message to be fully acknowledged by the broker.
|
|
41
|
+
|
|
42
|
+
### Connectivity
|
|
43
|
+
|
|
44
|
+
Connect to a remote broker with optional TLS over TCP over IPV4 or IPV6.
|
|
45
|
+
Connect to a local broker with either TCP or Unix domain socket.
|
|
46
|
+
|
|
47
|
+
### Properties
|
|
48
|
+
|
|
49
|
+
Access all optional properties of all MQTT control packet types.
|
|
50
|
+
If you ever wanted to check the user properties of SUBACK and UNSUBACK (among others), welcome home.
|
|
51
|
+
|
|
52
|
+
### Automatic Topic Alias
|
|
53
|
+
|
|
54
|
+
Set an alias policy when publishing a message and a topic alias will be generated, if allowed by the broker.
|
|
55
|
+
If bandwidth is tight, set your QoS 0 publications to require a topic alias.
|
|
56
|
+
In this case, an error will be raised if the server does not offer enough alias values.
|
|
57
|
+
|
|
58
|
+
### Toolkit
|
|
59
|
+
|
|
60
|
+
ΩQTT is built on a toolkit for efficiently serializing MQTT control messages.
|
|
61
|
+
Use it to build your own custom implementation, or to serialize your own payloads.
|
|
62
|
+
|
|
63
|
+
### Portability
|
|
64
|
+
|
|
65
|
+
ΩQTT is tested on Linux, Windows and MacOS with CPython versions 3.10-3.13.
|
|
66
|
+
It should work on any platform that CPython runs on.
|
|
67
|
+
|
|
68
|
+
### Reliability
|
|
69
|
+
|
|
70
|
+
ΩQTT has been implemented to a high standard of test coverage and static analysis, from the beginning.
|
|
71
|
+
It continues to improve.
|
|
72
|
+
|
|
73
|
+
### Performance
|
|
74
|
+
|
|
75
|
+
Every drop of pure Python performance has been squeezed out of serialization and the event loop.
|
|
76
|
+
You're not using Python because it's fast, but it can't hurt.
|
|
77
|
+
|
|
78
|
+
## TODO for 0.1
|
|
79
|
+
|
|
80
|
+
* Auth
|
|
81
|
+
* Instructions
|
|
82
|
+
* Error handling and validation
|
|
83
|
+
* Refactor Session
|
|
84
|
+
|
|
85
|
+
## TODO for 1.0
|
|
86
|
+
|
|
87
|
+
* E2E Tests
|
|
88
|
+
* Autodoc
|
|
89
|
+
* Publish automation
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
This project uses `nox` and `uv` to run the tests against all supported Python versions.
|
|
94
|
+
|
|
95
|
+
To do all of this in a venv:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python3 -m venv .venv
|
|
99
|
+
source .venv/bin/activate
|
|
100
|
+
pip install nox uv
|
|
101
|
+
nox
|
|
102
|
+
```
|
|
103
|
+
|
ohmqtt-0.0.3/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# ΩQTT
|
|
2
|
+
|
|
3
|
+
*pron. "ohm cue tee tee" or "omega cutie"*
|
|
4
|
+
|
|
5
|
+
A reliable and persistent MQTT 5.0 client library for Python.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
### QoS and Persistence
|
|
10
|
+
|
|
11
|
+
ΩQTT supports publish and subscribing all QoS levels with optional persistence to disk for QoS >0.
|
|
12
|
+
When not persisting QoS >0 messages, a fast (but volatile) in memory store is used.
|
|
13
|
+
Either way, publishing a message returns a handle with a method to wait for the message to be fully acknowledged by the broker.
|
|
14
|
+
|
|
15
|
+
### Connectivity
|
|
16
|
+
|
|
17
|
+
Connect to a remote broker with optional TLS over TCP over IPV4 or IPV6.
|
|
18
|
+
Connect to a local broker with either TCP or Unix domain socket.
|
|
19
|
+
|
|
20
|
+
### Properties
|
|
21
|
+
|
|
22
|
+
Access all optional properties of all MQTT control packet types.
|
|
23
|
+
If you ever wanted to check the user properties of SUBACK and UNSUBACK (among others), welcome home.
|
|
24
|
+
|
|
25
|
+
### Automatic Topic Alias
|
|
26
|
+
|
|
27
|
+
Set an alias policy when publishing a message and a topic alias will be generated, if allowed by the broker.
|
|
28
|
+
If bandwidth is tight, set your QoS 0 publications to require a topic alias.
|
|
29
|
+
In this case, an error will be raised if the server does not offer enough alias values.
|
|
30
|
+
|
|
31
|
+
### Toolkit
|
|
32
|
+
|
|
33
|
+
ΩQTT is built on a toolkit for efficiently serializing MQTT control messages.
|
|
34
|
+
Use it to build your own custom implementation, or to serialize your own payloads.
|
|
35
|
+
|
|
36
|
+
### Portability
|
|
37
|
+
|
|
38
|
+
ΩQTT is tested on Linux, Windows and MacOS with CPython versions 3.10-3.13.
|
|
39
|
+
It should work on any platform that CPython runs on.
|
|
40
|
+
|
|
41
|
+
### Reliability
|
|
42
|
+
|
|
43
|
+
ΩQTT has been implemented to a high standard of test coverage and static analysis, from the beginning.
|
|
44
|
+
It continues to improve.
|
|
45
|
+
|
|
46
|
+
### Performance
|
|
47
|
+
|
|
48
|
+
Every drop of pure Python performance has been squeezed out of serialization and the event loop.
|
|
49
|
+
You're not using Python because it's fast, but it can't hurt.
|
|
50
|
+
|
|
51
|
+
## TODO for 0.1
|
|
52
|
+
|
|
53
|
+
* Auth
|
|
54
|
+
* Instructions
|
|
55
|
+
* Error handling and validation
|
|
56
|
+
* Refactor Session
|
|
57
|
+
|
|
58
|
+
## TODO for 1.0
|
|
59
|
+
|
|
60
|
+
* E2E Tests
|
|
61
|
+
* Autodoc
|
|
62
|
+
* Publish automation
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
This project uses `nox` and `uv` to run the tests against all supported Python versions.
|
|
67
|
+
|
|
68
|
+
To do all of this in a venv:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 -m venv .venv
|
|
72
|
+
source .venv/bin/activate
|
|
73
|
+
pip install nox uv
|
|
74
|
+
nox
|
|
75
|
+
```
|
|
76
|
+
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""This example demonstrates connecting to a broker, publishing messages to a topic, and then disconnecting."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from ohmqtt.client import Client
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main(args: argparse.Namespace) -> None:
|
|
11
|
+
with Client() as client:
|
|
12
|
+
|
|
13
|
+
print("*** Connecting to broker...")
|
|
14
|
+
client.connect(args.address)
|
|
15
|
+
|
|
16
|
+
client.wait_for_connect(timeout=5.0)
|
|
17
|
+
print("*** Connected to broker")
|
|
18
|
+
|
|
19
|
+
for n in range(1, 9):
|
|
20
|
+
client.publish("ohmqtt/examples/publish", b"test_payload: " + str(n).encode(), qos=0)
|
|
21
|
+
print(f"*** Published message {n}")
|
|
22
|
+
time.sleep(1.0)
|
|
23
|
+
|
|
24
|
+
print("*** Disconnecting from broker...")
|
|
25
|
+
client.disconnect()
|
|
26
|
+
|
|
27
|
+
client.wait_for_disconnect(timeout=5.0)
|
|
28
|
+
print("*** Disconnected from broker")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
from .args import parser
|
|
33
|
+
args = parser.parse_args()
|
|
34
|
+
main(args)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""This example demonstrates publishing a retained message,
|
|
3
|
+
then subscribing to the topic and receiving the retained message."""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from queue import Queue
|
|
7
|
+
|
|
8
|
+
from ohmqtt.client import Client
|
|
9
|
+
from ohmqtt.packet import MQTTPublishPacket
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main(args: argparse.Namespace) -> None:
|
|
13
|
+
with Client() as client:
|
|
14
|
+
|
|
15
|
+
client.connect("localhost")
|
|
16
|
+
client.wait_for_connect(timeout=5.0)
|
|
17
|
+
print("*** Connected to broker")
|
|
18
|
+
|
|
19
|
+
pub = client.publish("ohmqtt/examples/publish_retain", b"test_payload", qos=1, retain=True)
|
|
20
|
+
assert pub.wait_for_ack()
|
|
21
|
+
|
|
22
|
+
q: Queue[MQTTPublishPacket] = Queue()
|
|
23
|
+
def callback(_: Client, msg: MQTTPublishPacket) -> None:
|
|
24
|
+
q.put(msg)
|
|
25
|
+
client.subscribe("ohmqtt/examples/publish_retain", callback)
|
|
26
|
+
msg = q.get(timeout=5.0)
|
|
27
|
+
assert msg.topic == "ohmqtt/examples/publish_retain"
|
|
28
|
+
assert msg.payload == b"test_payload"
|
|
29
|
+
assert msg.qos == 1
|
|
30
|
+
assert msg.retain
|
|
31
|
+
print(f"*** Received retained message: {msg!s}")
|
|
32
|
+
|
|
33
|
+
client.disconnect()
|
|
34
|
+
client.wait_for_disconnect(timeout=5.0)
|
|
35
|
+
print("*** Disconnected from broker")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
from .args import parser
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
main(args)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""This example demonstrates waiting for a published message to be fully acknowledged by the broker
|
|
3
|
+
before proceeding to send the next message.
|
|
4
|
+
|
|
5
|
+
It also demonstrates the debug logging output of the client.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from ohmqtt.client import Client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(args: argparse.Namespace) -> None:
|
|
15
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
16
|
+
with Client() as client:
|
|
17
|
+
|
|
18
|
+
client.connect(args.address)
|
|
19
|
+
client.wait_for_connect(timeout=5.0)
|
|
20
|
+
|
|
21
|
+
for n in range(1, 9):
|
|
22
|
+
publish_handle = client.publish("ohmqtt/examples/publish_wait_for_ack", b"test_payload: " + str(n).encode(), qos=2)
|
|
23
|
+
assert publish_handle.wait_for_ack()
|
|
24
|
+
|
|
25
|
+
client.disconnect()
|
|
26
|
+
client.wait_for_disconnect(timeout=5.0)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
from .args import parser
|
|
31
|
+
args = parser.parse_args()
|
|
32
|
+
main(args)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""This example demonstrates a simple RPC caller.
|
|
3
|
+
|
|
4
|
+
It sends an RPC request to a specific topic and waits for the response.
|
|
5
|
+
|
|
6
|
+
The ResponseTopic property is used to specify the topic to which the response should be sent."""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import threading
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Callable
|
|
12
|
+
|
|
13
|
+
from ohmqtt.client import Client
|
|
14
|
+
from ohmqtt.packet import MQTTPublishPacket
|
|
15
|
+
from ohmqtt.property import MQTTPublishProps
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
RPCCallback = Callable[[bytes], None]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RPCCaller:
|
|
22
|
+
"""A simple RPC caller."""
|
|
23
|
+
def __init__(self, client: Client) -> None:
|
|
24
|
+
self.client = client
|
|
25
|
+
self.callbacks: dict[str, RPCCallback] = {}
|
|
26
|
+
|
|
27
|
+
def send_request(self, payload: bytes, callback: RPCCallback) -> None:
|
|
28
|
+
# Expect the response to be sent to a unique topic.
|
|
29
|
+
unique_id = str(uuid.uuid4())
|
|
30
|
+
response_topic = f"ohmqtt/examples/rpc/response/{unique_id}"
|
|
31
|
+
|
|
32
|
+
# Store the callback for this request.
|
|
33
|
+
self.callbacks[unique_id] = callback
|
|
34
|
+
|
|
35
|
+
# Subscribe to the response topic.
|
|
36
|
+
self.client.subscribe(response_topic, self.handle_response)
|
|
37
|
+
|
|
38
|
+
# Publish the request with the necessary properties.
|
|
39
|
+
self.client.publish(
|
|
40
|
+
"ohmqtt/examples/rpc/request",
|
|
41
|
+
payload,
|
|
42
|
+
qos=2,
|
|
43
|
+
properties=MQTTPublishProps(ResponseTopic=response_topic),
|
|
44
|
+
).wait_for_ack()
|
|
45
|
+
|
|
46
|
+
print(f"Sent RPC request with response topic: {response_topic}")
|
|
47
|
+
|
|
48
|
+
def handle_response(self, client: Client, msg: MQTTPublishPacket) -> None:
|
|
49
|
+
"""Handle incoming RPC responses."""
|
|
50
|
+
try:
|
|
51
|
+
callback = self.callbacks.pop(msg.topic.split("/")[-1])
|
|
52
|
+
except KeyError:
|
|
53
|
+
print("Couldn't find callback for response")
|
|
54
|
+
return
|
|
55
|
+
else:
|
|
56
|
+
# Call the callback with the response payload.
|
|
57
|
+
callback(msg.payload)
|
|
58
|
+
finally:
|
|
59
|
+
# Unsubscribe from the response topic.
|
|
60
|
+
client.unsubscribe(msg.topic, self.handle_response)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main(args: argparse.Namespace) -> None:
|
|
64
|
+
with Client() as client:
|
|
65
|
+
rpc_caller = RPCCaller(client)
|
|
66
|
+
client.connect(args.address)
|
|
67
|
+
print("*** Waiting for connection...")
|
|
68
|
+
client.wait_for_connect(timeout=5.0)
|
|
69
|
+
|
|
70
|
+
# Set up a callback which helps us block until a response is received.
|
|
71
|
+
recvd = threading.Event()
|
|
72
|
+
def callback(payload: bytes) -> None:
|
|
73
|
+
print(f"*** Received RPC response: {payload.decode()}")
|
|
74
|
+
recvd.set() # Signal that the response has been received.
|
|
75
|
+
|
|
76
|
+
while True:
|
|
77
|
+
recvd.clear() # Reset the event for the next request.
|
|
78
|
+
try:
|
|
79
|
+
payload = input("*** Enter something fun (or 'exit' to quit): ")
|
|
80
|
+
if payload.lower() == "exit":
|
|
81
|
+
break
|
|
82
|
+
rpc_caller.send_request(payload.encode(), callback)
|
|
83
|
+
recvd.wait(timeout=5.0) # Wait for the response to be received.
|
|
84
|
+
except (EOFError, KeyboardInterrupt):
|
|
85
|
+
print("\n*** Shutting down RPC caller...")
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
from .args import parser
|
|
91
|
+
args = parser.parse_args()
|
|
92
|
+
main(args)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""This example demonstrates a simple RPC server.
|
|
3
|
+
|
|
4
|
+
It listens for incoming RPC requests on a specific topic and responds with the result of the RPC call.
|
|
5
|
+
|
|
6
|
+
The ResponseTopic property is used by the requestor to specify the topic to which the response should be sent.
|
|
7
|
+
|
|
8
|
+
See the "rpc_caller" example for the request side of this RPC implementation.
|
|
9
|
+
|
|
10
|
+
This also demonstrates running the client loop in the main thread."""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
from ohmqtt.client import Client
|
|
15
|
+
from ohmqtt.packet import MQTTPublishPacket
|
|
16
|
+
from ohmqtt.property import MQTTPublishProps
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RPCServer:
|
|
20
|
+
"""A simple stateless RPC server."""
|
|
21
|
+
def handle_request(self, client: Client, msg: MQTTPublishPacket) -> None:
|
|
22
|
+
"""Handle incoming RPC requests."""
|
|
23
|
+
print(f"*** Received RPC request: {msg!s}")
|
|
24
|
+
|
|
25
|
+
# Find the response topic in the message properties.
|
|
26
|
+
if not msg.properties.ResponseTopic:
|
|
27
|
+
print("Request was missing required response topic property")
|
|
28
|
+
return
|
|
29
|
+
response_topic = msg.properties.ResponseTopic
|
|
30
|
+
|
|
31
|
+
response_props = MQTTPublishProps()
|
|
32
|
+
# If the request includes correlation data, send it back in the response.
|
|
33
|
+
if msg.properties.CorrelationData is not None:
|
|
34
|
+
response_props.CorrelationData = msg.properties.CorrelationData
|
|
35
|
+
|
|
36
|
+
# Simulate some processing.
|
|
37
|
+
response = f"This is a good day for {msg.payload.decode()}"
|
|
38
|
+
|
|
39
|
+
# Send the response back to the specified topic.
|
|
40
|
+
client.publish(response_topic, response.encode(), qos=2, properties=response_props)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main(args: argparse.Namespace) -> None:
|
|
44
|
+
rpc_server = RPCServer()
|
|
45
|
+
client = Client()
|
|
46
|
+
client.connect(args.address)
|
|
47
|
+
print("*** Waiting for connection...")
|
|
48
|
+
assert client.loop_until_connected(timeout=5.0), "Timeout waiting for connection"
|
|
49
|
+
|
|
50
|
+
client.subscribe("ohmqtt/examples/rpc/request", rpc_server.handle_request)
|
|
51
|
+
print("*** Waiting for RPC requests...")
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
client.loop_forever() # Wait indefinitely for incoming messages.
|
|
55
|
+
except KeyboardInterrupt:
|
|
56
|
+
print("\n*** Shutting down RPC server...")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
from .args import parser
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
main(args)
|
ohmqtt-0.0.3/noxfile.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import nox
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
nox.options.default_venv_backend = "uv|virtualenv"
|
|
5
|
+
py_versions = ("3.10", "3.11", "3.12", "3.13")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@nox.session(python=py_versions)
|
|
9
|
+
def tests(session):
|
|
10
|
+
session.install(".")
|
|
11
|
+
session.install("--group", "dev")
|
|
12
|
+
session.run("ruff", "check")
|
|
13
|
+
session.run("typos")
|
|
14
|
+
session.run("mypy")
|
|
15
|
+
if sys.platform != "win32":
|
|
16
|
+
session.run("complexipy", "-d", "low", "ohmqtt", "examples", "tests")
|
|
17
|
+
else:
|
|
18
|
+
session.log("Skipping complexipy check on Windows: https://github.com/rohaquinlop/complexipy/issues/67")
|
|
19
|
+
session.run("pytest", "--cov-report=term-missing")
|
|
File without changes
|