@@ -26,11 +26,15 @@ MicroPython's `asyncio` when used in a microcontroller context.
|
26 | 26 | 6.1 [Encoder class](./DRIVERS.md#61-encoder-class)
|
27 | 27 | 7. [Ringbuf Queue](./DRIVERS.md#7-ringbuf-queue) A MicroPython optimised queue primitive.
|
28 | 28 | 8. [Delay_ms class](./DRIVERS.md#8-delay_ms-class) A flexible retriggerable delay with callback or Event interface.
|
29 |
| -9. [Additional functions](./DRIVERS.md#9-additional-functions) |
30 |
| -9.1 [launch](./DRIVERS.md#91-launch) Run a coro or callback interchangeably. |
31 |
| -9.2 [set_global_exception](./DRIVERS.md#92-set_global_exception) Simplify debugging with a global exception handler. |
| 29 | +9. [Message Broker](./DRIVERS.md#9-message-broker) A flexible means of messaging between |
| 30 | +tasks. |
| 31 | +9.1 [Further examples](./DRIVERS.md#91-further-examples) |
| 32 | +9.2 [User agents](./DRIVERS.md#92-user-agents) |
| 33 | +10. [Additional functions](./DRIVERS.md#10-additional-functions) |
| 34 | +10.1 [launch](./DRIVERS.md#101-launch) Run a coro or callback interchangeably. |
| 35 | +10.2 [set_global_exception](./DRIVERS.md#102-set_global_exception) Simplify debugging with a global exception handler. |
32 | 36 |
|
33 |
| -###### [Tutorial](./TUTORIAL.md#contents) |
| 37 | +###### [asyncio Tutorial](./TUTORIAL.md#contents) |
34 | 38 |
|
35 | 39 | # 1. Introduction
|
36 | 40 |
|
@@ -1126,9 +1130,116 @@ finally:
|
1126 | 1130 | ```
|
1127 | 1131 | ###### [Contents](./DRIVERS.md#0-contents)
|
1128 | 1132 |
|
1129 |
| -# 9. Additional functions |
| 1133 | +# 9. Message Broker |
| 1134 | + |
| 1135 | +This is under development: please check for updates. |
| 1136 | + |
| 1137 | +The `Broker` class provides a flexible means of messaging between running tasks. |
| 1138 | +It uses a publish-subscribe model (akin to MQTT) whereby the transmitting task |
| 1139 | +publishes to a topic. Any tasks subscribed to that topic will receive the |
| 1140 | +message. This enables one to one, one to many or many to many messaging. |
| 1141 | + |
| 1142 | +A task subscribes to a topic with an `agent`. This is stored by the broker. When |
| 1143 | +the broker publishes a message, the `agent` of each task subscribed to its topic |
| 1144 | +will be triggered. In the simplest case the `agent` is a `Queue` instance: the |
| 1145 | +broker puts the topic and message onto the subscriber's queue for retrieval. |
| 1146 | + |
| 1147 | +More advanced agents can perform actions in response to a message, such as |
| 1148 | +calling a function or launching a `task`. |
| 1149 | + |
| 1150 | +Broker methods. All are synchronous, constructor has no args: |
| 1151 | +* `subscribe(topic, agent)` Passed `agent` will be triggered by messages with a |
| 1152 | +matching `topic`. |
| 1153 | +* `unsubscribe(topic, agent)` The `agent` will stop being triggered. |
| 1154 | +* `publish(topic, message)` All `agent` instances subscribed to `topic` will be |
| 1155 | +triggered, receiving `topic` and `message` args. Returns `True` unless a `Queue` |
| 1156 | +agent has become full, in which case data for that queue has been lost. |
| 1157 | + |
| 1158 | +The `topic` arg is typically a string but may be any hashable object. A |
| 1159 | +`message` is an arbitrary Python object. An `agent` may be any of the following: |
| 1160 | +* `Queue` When a message is received receives 2-tuple `(topic, message)`. |
| 1161 | +* `function` Called when a message is received. Gets 2 args, topic and message. |
| 1162 | +* `bound method` Called when a message is received. Gets 2 args, topic and |
| 1163 | +message. |
| 1164 | +* `coroutine` Task created when a message is received with 2 args, topic and |
| 1165 | +message. |
| 1166 | +* `bound coroutine` Task created when a message is received with 2 args, topic |
| 1167 | +and message. |
| 1168 | +* Instance of a user class. See user agents below. |
| 1169 | +* `Event` Set when a message is received. |
| 1170 | + |
| 1171 | +Note that synchronous `agent` instances must run to completion quickly otherwise |
| 1172 | +the `publish` method will be slowed. |
| 1173 | + |
| 1174 | +The following is a simple example: |
| 1175 | +```py |
| 1176 | +import asyncio |
| 1177 | +from primitives import Broker, Queue |
| 1178 | + |
| 1179 | +broker = Broker() |
| 1180 | +queue = Queue() |
| 1181 | +async def sender(t): |
| 1182 | +for x in range(t): |
| 1183 | +await asyncio.sleep(1) |
| 1184 | +broker.publish("foo_topic", f"test {x}") |
| 1185 | + |
| 1186 | +async def main(): |
| 1187 | +broker.subscribe("foo_topic", queue) |
| 1188 | +n = 10 |
| 1189 | +asyncio.create_task(sender(n)) |
| 1190 | +print("Letting queue part-fill") |
| 1191 | +await asyncio.sleep(5) |
| 1192 | +for _ in range(n): |
| 1193 | +topic, message = await queue.get() |
| 1194 | +print(topic, message) |
| 1195 | + |
| 1196 | +asyncio.run(main()) |
| 1197 | +``` |
| 1198 | +## 9.1 Further examples |
| 1199 | + |
| 1200 | +An interesting application is to extend MQTT into the Python code |
| 1201 | +(see [mqtt_as](https://.com/peterhinch/micropython-mqtt/tree/master)). |
| 1202 | +This is as simple as: |
| 1203 | +```py |
| 1204 | +async def messages(client): |
| 1205 | +async for topic, msg, retained in client.queue: |
| 1206 | +broker.publish(topic.decode(), msg.decode()) |
| 1207 | +``` |
| 1208 | +Assuming the MQTT client is subscribed to multiple topics, message strings are |
| 1209 | +directed to individual tasks each supporting one topic. |
| 1210 | + |
| 1211 | +## 9.2 User agents |
| 1212 | + |
| 1213 | +An `agent` can be an instance of a user class. The class must be a subclass of |
| 1214 | +`Agent`, and it must support a synchronous `.put` method. The latter takes two |
| 1215 | +args, being `topic` and `message`. It should run to completion quickly. |
| 1216 | + |
| 1217 | +```py |
| 1218 | +import asyncio |
| 1219 | +from primitives import Broker, Agent |
| 1220 | + |
| 1221 | +broker = Broker() |
| 1222 | +class MyAgent(Agent): |
| 1223 | +def put(sef, topic, message): |
| 1224 | +print(f"User agent. Topic: {topic} Message: {message}") |
| 1225 | + |
| 1226 | +async def sender(t): |
| 1227 | +for x in range(t): |
| 1228 | +await asyncio.sleep(1) |
| 1229 | +broker.publish("foo_topic", f"test {x}") |
| 1230 | + |
| 1231 | +async def main(): |
| 1232 | +broker.subscribe("foo_topic", MyAgent()) |
| 1233 | +await sender(10) |
| 1234 | + |
| 1235 | +asyncio.run(main()) |
| 1236 | +``` |
| 1237 | + |
| 1238 | +###### [Contents](./DRIVERS.md#0-contents) |
| 1239 | + |
| 1240 | +# 10. Additional functions |
1130 | 1241 |
|
1131 |
| -## 9.1 Launch |
| 1242 | +## 10.1 Launch |
1132 | 1243 |
|
1133 | 1244 | Import as follows:
|
1134 | 1245 | ```python
|
@@ -1140,7 +1251,7 @@ runs it and returns the callback's return value. If a coro is passed, it is
|
1140 | 1251 | converted to a `task` and run asynchronously. The return value is the `task`
|
1141 | 1252 | instance. A usage example is in `primitives/switch.py`.
|
1142 | 1253 |
|
1143 |
| -## 9.2 set_global_exception |
| 1254 | +## 10.2 set_global_exception |
1144 | 1255 |
|
1145 | 1256 | Import as follows:
|
1146 | 1257 | ```python
|
|
0 commit comments