amgi-paho-mqtt 0.18.0__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.
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: amgi-paho-mqtt
|
|
3
|
+
Version: 0.18.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: jack.burridge
|
|
6
|
+
Author-email: jack.burridge <jack.burridge@mail.com>
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: amgi-types==0.18.0
|
|
14
|
+
Requires-Dist: paho-mqtt>=2.1.0
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# amgi-paho-mqtt
|
|
19
|
+
|
|
20
|
+
:construction: This package is currently under development :construction:
|
|
21
|
+
|
|
22
|
+
AMGI will be here very soon
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
pip install amgi-paho-mqtt==0.18.0
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Contact
|
|
31
|
+
|
|
32
|
+
For questions or suggestions, please contact [jack.burridge@mail.com](mailto:jack.burridge@mail.com).
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
Copyright 2025 AMGI
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# amgi-paho-mqtt
|
|
2
|
+
|
|
3
|
+
:construction: This package is currently under development :construction:
|
|
4
|
+
|
|
5
|
+
AMGI will be here very soon
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pip install amgi-paho-mqtt==0.18.0
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Contact
|
|
14
|
+
|
|
15
|
+
For questions or suggestions, please contact [jack.burridge@mail.com](mailto:jack.burridge@mail.com).
|
|
16
|
+
|
|
17
|
+
## License
|
|
18
|
+
|
|
19
|
+
Copyright 2025 AMGI
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "uv_build"
|
|
3
|
+
requires = [ "uv-build>=0.8.14,<0.9.0" ]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "amgi-paho-mqtt"
|
|
7
|
+
version = "0.18.0"
|
|
8
|
+
description = "Add your description here"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "jack.burridge", email = "jack.burridge@mail.com" },
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"amgi-types==0.18.0",
|
|
24
|
+
"paho-mqtt>=2.1.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
entry-points.amgi_server.amgi-paho-mqtt = "amgi_paho_mqtt:run"
|
|
28
|
+
|
|
29
|
+
[dependency-groups]
|
|
30
|
+
dev = [
|
|
31
|
+
"test-utils",
|
|
32
|
+
"testcontainers[mqtt]>=4.13.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.uv.sources]
|
|
36
|
+
amgi-types = { workspace = true }
|
|
37
|
+
test-utils = { workspace = true }
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from asyncio import Event
|
|
3
|
+
from asyncio import Task
|
|
4
|
+
from socket import SO_SNDBUF
|
|
5
|
+
from socket import SOL_SOCKET
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from amgi_common import Lifespan
|
|
11
|
+
from amgi_types import AMGIApplication
|
|
12
|
+
from amgi_types import AMGISendEvent
|
|
13
|
+
from amgi_types import MessageReceiveEvent
|
|
14
|
+
from amgi_types import MessageScope
|
|
15
|
+
from paho.mqtt.client import Client
|
|
16
|
+
from paho.mqtt.client import ConnectFlags
|
|
17
|
+
from paho.mqtt.client import DisconnectFlags
|
|
18
|
+
from paho.mqtt.client import MQTT_ERR_SUCCESS
|
|
19
|
+
from paho.mqtt.client import MQTTMessage
|
|
20
|
+
from paho.mqtt.enums import CallbackAPIVersion
|
|
21
|
+
from paho.mqtt.properties import Properties
|
|
22
|
+
from paho.mqtt.reasoncodes import ReasonCode
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from paho.mqtt.client import SocketLike
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run(
|
|
29
|
+
app: AMGIApplication,
|
|
30
|
+
topic: str,
|
|
31
|
+
host: str = "localhost",
|
|
32
|
+
port: int = 1883,
|
|
33
|
+
client_id: Optional[str] = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
asyncio.run(_run(app, topic, host, port, client_id))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _run(
|
|
39
|
+
app: AMGIApplication, topic: str, host: str, port: int, client_id: Optional[str]
|
|
40
|
+
) -> None:
|
|
41
|
+
server = Server(app, topic, host, port, client_id)
|
|
42
|
+
await server.serve()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _MessageReceive:
|
|
46
|
+
def __init__(self, message: MQTTMessage) -> None:
|
|
47
|
+
self._message = message
|
|
48
|
+
|
|
49
|
+
async def __call__(self) -> MessageReceiveEvent:
|
|
50
|
+
return {
|
|
51
|
+
"type": "message.receive",
|
|
52
|
+
"id": str(self._message.mid),
|
|
53
|
+
"headers": [],
|
|
54
|
+
"payload": self._message.payload,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _MessageSend:
|
|
59
|
+
def __init__(self, client: Client) -> None:
|
|
60
|
+
self._client = client
|
|
61
|
+
|
|
62
|
+
async def __call__(self, message: AMGISendEvent) -> None:
|
|
63
|
+
if message["type"] == "message.send":
|
|
64
|
+
self._client.publish(message["address"], message["payload"])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Server:
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
app: AMGIApplication,
|
|
71
|
+
topic: str,
|
|
72
|
+
host: str,
|
|
73
|
+
port: int,
|
|
74
|
+
client_id: Optional[str],
|
|
75
|
+
) -> None:
|
|
76
|
+
self._app = app
|
|
77
|
+
self._topic = topic
|
|
78
|
+
self._host = host
|
|
79
|
+
self._port = port
|
|
80
|
+
self._loop = asyncio.get_running_loop()
|
|
81
|
+
|
|
82
|
+
self._client = Client(CallbackAPIVersion.VERSION2, client_id=client_id)
|
|
83
|
+
self._client.on_connect = self._on_connect
|
|
84
|
+
self._client.on_message = self._on_message
|
|
85
|
+
self._client.on_disconnect = self._on_disconnect
|
|
86
|
+
self._client.on_socket_open = self._on_socket_open
|
|
87
|
+
self._client.on_socket_close = self._on_socket_close
|
|
88
|
+
self._client.on_socket_register_write = self._on_socket_register_write
|
|
89
|
+
self._client.on_socket_unregister_write = self._on_socket_unregister_write
|
|
90
|
+
self._client.on_subscribe = self._on_subscribe
|
|
91
|
+
|
|
92
|
+
self._subscribe_event = Event()
|
|
93
|
+
self._disconnected_event = Event()
|
|
94
|
+
self._stop_event = Event()
|
|
95
|
+
self._tasks = set[Task[None]]()
|
|
96
|
+
|
|
97
|
+
def _on_connect(
|
|
98
|
+
self,
|
|
99
|
+
client: Client,
|
|
100
|
+
userdata: Any,
|
|
101
|
+
connect_flags: ConnectFlags,
|
|
102
|
+
reason_code: ReasonCode,
|
|
103
|
+
properties: Optional[Properties],
|
|
104
|
+
) -> None:
|
|
105
|
+
client.subscribe(self._topic)
|
|
106
|
+
|
|
107
|
+
def _on_message(self, client: Client, userdata: Any, message: MQTTMessage) -> None:
|
|
108
|
+
task = self._loop.create_task(self._handle_message(message))
|
|
109
|
+
self._tasks.add(task)
|
|
110
|
+
task.add_done_callback(self._tasks.discard)
|
|
111
|
+
|
|
112
|
+
async def _handle_message(self, message: MQTTMessage) -> None:
|
|
113
|
+
scope: MessageScope = {
|
|
114
|
+
"type": "message",
|
|
115
|
+
"amgi": {"version": "1.0", "spec_version": "1.0"},
|
|
116
|
+
"address": message.topic,
|
|
117
|
+
}
|
|
118
|
+
await self._app(scope, _MessageReceive(message), _MessageSend(self._client))
|
|
119
|
+
|
|
120
|
+
def _on_disconnect(
|
|
121
|
+
self,
|
|
122
|
+
client: Client,
|
|
123
|
+
userdata: Any,
|
|
124
|
+
disconnect_flags: DisconnectFlags,
|
|
125
|
+
reason_code: ReasonCode,
|
|
126
|
+
properties: Optional[Properties],
|
|
127
|
+
) -> None:
|
|
128
|
+
self._disconnected_event.set()
|
|
129
|
+
|
|
130
|
+
def _on_socket_open(
|
|
131
|
+
self, client: Client, userdata: Any, socket: "SocketLike"
|
|
132
|
+
) -> None:
|
|
133
|
+
self._loop.add_reader(socket, client.loop_read)
|
|
134
|
+
self._misc_task = self._loop.create_task(self._misc_loop())
|
|
135
|
+
|
|
136
|
+
def _on_socket_close(
|
|
137
|
+
self, client: Client, userdata: Any, socket: "SocketLike"
|
|
138
|
+
) -> None:
|
|
139
|
+
self._loop.remove_reader(socket)
|
|
140
|
+
self._misc_task.cancel()
|
|
141
|
+
|
|
142
|
+
def _on_socket_register_write(
|
|
143
|
+
self, client: Client, userdata: Any, socket: "SocketLike"
|
|
144
|
+
) -> None:
|
|
145
|
+
self._loop.add_writer(socket, client.loop_write)
|
|
146
|
+
|
|
147
|
+
def _on_socket_unregister_write(
|
|
148
|
+
self, client: Client, userdata: Any, socket: "SocketLike"
|
|
149
|
+
) -> None:
|
|
150
|
+
self._loop.remove_writer(socket)
|
|
151
|
+
|
|
152
|
+
def _on_subscribe(
|
|
153
|
+
self,
|
|
154
|
+
client: Client,
|
|
155
|
+
userdata: Any,
|
|
156
|
+
mid: int,
|
|
157
|
+
reason_code_list: list[ReasonCode],
|
|
158
|
+
properties: Optional[Properties],
|
|
159
|
+
) -> None:
|
|
160
|
+
self._subscribe_event.set()
|
|
161
|
+
|
|
162
|
+
async def _misc_loop(self) -> None:
|
|
163
|
+
while self._client.loop_misc() == MQTT_ERR_SUCCESS:
|
|
164
|
+
try:
|
|
165
|
+
await asyncio.sleep(1)
|
|
166
|
+
except asyncio.CancelledError:
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
async def serve(self) -> None:
|
|
170
|
+
self._client.connect(self._host, self._port, 60)
|
|
171
|
+
self._client.socket().setsockopt(SOL_SOCKET, SO_SNDBUF, 2048)
|
|
172
|
+
|
|
173
|
+
await self._subscribe_event.wait()
|
|
174
|
+
|
|
175
|
+
async with Lifespan(self._app) as state:
|
|
176
|
+
await self._stop_event.wait()
|
|
177
|
+
self._client.unsubscribe(self._topic)
|
|
178
|
+
await asyncio.gather(*self._tasks)
|
|
179
|
+
self._client.disconnect()
|
|
180
|
+
await self._disconnected_event.wait()
|
|
181
|
+
|
|
182
|
+
def stop(self) -> None:
|
|
183
|
+
self._stop_event.set()
|
|
File without changes
|