leaf-framework 0.10.16__tar.gz → 0.10.18__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.
Files changed (108) hide show
  1. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/PKG-INFO +1 -1
  2. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/mqtt_watcher.py +65 -5
  3. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/pyproject.toml +1 -1
  4. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/LICENSE +0 -0
  5. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/README.md +0 -0
  6. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/__init__.py +0 -0
  7. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/adapters/__init__.py +0 -0
  8. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/adapters/core_adapters/__init__.py +0 -0
  9. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/adapters/core_adapters/continuous_experiment_adapter.py +0 -0
  10. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/adapters/core_adapters/discrete_experiment_adapter.py +0 -0
  11. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/adapters/core_adapters/streaming_adapter.py +0 -0
  12. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/adapters/core_adapters/upload_adapter.py +0 -0
  13. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/adapters/equipment_adapter.py +0 -0
  14. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/config/configuration.yaml +0 -0
  15. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/data/marketplace.yaml +0 -0
  16. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/error_handler/__init__.py +0 -0
  17. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/error_handler/error_holder.py +0 -0
  18. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/error_handler/exceptions.py +0 -0
  19. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/measurement_handler/__init__.py +0 -0
  20. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/measurement_handler/handler.py +0 -0
  21. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/measurement_handler/measurements.yaml +0 -0
  22. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/measurement_handler/terms.py +0 -0
  23. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/.gitkeep +0 -0
  24. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/__init__.py +0 -0
  25. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/__init__.py +0 -0
  26. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/ble_watcher.py +0 -0
  27. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/db_watcher.py +0 -0
  28. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/event_watcher.py +0 -0
  29. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/external_api_watcher.py +0 -0
  30. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/external_event_watcher.py +0 -0
  31. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/file_watcher.py +0 -0
  32. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/http_watcher.py +0 -0
  33. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/lads_watcher.py +0 -0
  34. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/modbus_watcher.py +0 -0
  35. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/mqtt_external_event_watcher.py +0 -0
  36. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/opc_watcher.py +0 -0
  37. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/polling_watcher.py +0 -0
  38. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/input_modules/simple_watcher.py +0 -0
  39. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/measurement_modules/__init__.py +0 -0
  40. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/measurement_modules/carbon_dioxide.py +0 -0
  41. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/measurement_modules/dissolved_oxygen.py +0 -0
  42. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/measurement_modules/fluorescence.py +0 -0
  43. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/measurement_modules/measurement_module.py +0 -0
  44. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/measurement_modules/optical_density.py +0 -0
  45. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/measurement_modules/ph.py +0 -0
  46. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/measurement_modules/temperature.py +0 -0
  47. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/output_modules/__init__.py +0 -0
  48. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/output_modules/file.py +0 -0
  49. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/output_modules/keydb.py +0 -0
  50. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/output_modules/mqtt.py +0 -0
  51. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/output_modules/output_module.py +0 -0
  52. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/output_modules/sql.py +0 -0
  53. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/output_modules/topic_store.py +0 -0
  54. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/phase_modules/__init__.py +0 -0
  55. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/phase_modules/control.py +0 -0
  56. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/phase_modules/error.py +0 -0
  57. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/phase_modules/external_event_phase.py +0 -0
  58. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/phase_modules/initialisation.py +0 -0
  59. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/phase_modules/measure.py +0 -0
  60. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/phase_modules/phase.py +0 -0
  61. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/phase_modules/start.py +0 -0
  62. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/phase_modules/stop.py +0 -0
  63. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/process_modules/__init__.py +0 -0
  64. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/process_modules/continous_module.py +0 -0
  65. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/process_modules/discrete_module.py +0 -0
  66. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/process_modules/external_event_process.py +0 -0
  67. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/process_modules/process_module.py +0 -0
  68. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/modules/process_modules/upload_module.py +0 -0
  69. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/register/__init__.py +0 -0
  70. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/register/abstract_leaf.py +0 -0
  71. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/register/equipment_actions.yaml +0 -0
  72. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/register/equipment_data.yaml +0 -0
  73. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/register/metadata.py +0 -0
  74. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/register/py.typed +0 -0
  75. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/register/topic_utilities.py +0 -0
  76. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/registry/discovery.py +0 -0
  77. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/registry/loader.py +0 -0
  78. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/registry/registry.py +0 -0
  79. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/registry/utils.py +0 -0
  80. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/start.py +0 -0
  81. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/__init__.py +0 -0
  82. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/adapter_ui_extension.py +0 -0
  83. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/components/__init__.py +0 -0
  84. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/components/header.py +0 -0
  85. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/components/update_notice.py +0 -0
  86. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/constants.py +0 -0
  87. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/desktop_launcher.py +0 -0
  88. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/images/icon.icns +0 -0
  89. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/images/icon.ico +0 -0
  90. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/images/icon.svg +0 -0
  91. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/interface.py +0 -0
  92. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/markdown/configuration_help.md +0 -0
  93. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/markdown/documentation_main.md +0 -0
  94. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/markdown/documentation_protips.md +0 -0
  95. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/scripts.js +0 -0
  96. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/styles.css +0 -0
  97. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/tabs/__init__.py +0 -0
  98. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/tabs/adapters.py +0 -0
  99. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/tabs/configuration.py +0 -0
  100. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/tabs/documentation.py +0 -0
  101. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/tabs/logs.py +0 -0
  102. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/tabs/monitoring.py +0 -0
  103. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/update_checker.py +0 -0
  104. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/ui/utils.py +0 -0
  105. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/utility/config_utils.py +0 -0
  106. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/utility/logger/__init__.py +0 -0
  107. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/utility/logger/logger_utils.py +0 -0
  108. {leaf_framework-0.10.16 → leaf_framework-0.10.18}/leaf/utility/running_utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: leaf-framework
3
- Version: 0.10.16
3
+ Version: 0.10.18
4
4
  Summary: Lightweight Adapter Framework (LEAF)
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -81,6 +81,7 @@ class MQTTEventWatcher(EventWatcher):
81
81
  self.client.on_connect_fail = self.on_connect_fail
82
82
  self.client.on_disconnect = self.on_disconnect
83
83
  self.client.on_message = self.on_message
84
+ self.client.on_subscribe = self.on_subscribe
84
85
 
85
86
  if tls:
86
87
  try:
@@ -99,6 +100,11 @@ class MQTTEventWatcher(EventWatcher):
99
100
  # Set when on_connect fires so start() can block until subscriptions are active
100
101
  self._connected_event = threading.Event()
101
102
 
103
+ # wait for the SUBACKs instead of just for CONNACK.
104
+ self._pending_subscriptions: set[int] = set()
105
+ self._subscriptions_lock = threading.Lock()
106
+ self._subscribed_event = threading.Event()
107
+
102
108
  def _register_topics(self, topics: list[str] | None, event: str):
103
109
  if topics:
104
110
  for topic in topics:
@@ -107,9 +113,10 @@ class MQTTEventWatcher(EventWatcher):
107
113
  def start(self):
108
114
  """
109
115
  Connects to the MQTT broker and sets a thread looping.
110
- Blocks until on_connect fires and subscriptions are active, so callers
111
- can publish immediately after start() returns. On reconnects after a
112
- broker restart, on_connect re-subscribes automatically.
116
+ Blocks until on_connect fires and every subscription has been
117
+ acknowledged by the broker, so callers can publish immediately after
118
+ start() returns. On reconnects after a broker restart, on_connect
119
+ re-subscribes automatically.
113
120
  """
114
121
  super().start()
115
122
  try:
@@ -117,7 +124,16 @@ class MQTTEventWatcher(EventWatcher):
117
124
  self.client.username_pw_set(self._username, self._password)
118
125
  self.client.connect(self._broker, self._port, 60)
119
126
  self.client.loop_start()
120
- self._connected_event.wait(timeout=10)
127
+ # Both waits are bounded, and a timeout warns and carries on rather
128
+ # than blocking: a broker that accepts the connection but never
129
+ # acknowledges a SUBSCRIBE must not hang the caller. The second
130
+ # wait is skipped when the connect wait already timed out, so the
131
+ # worst case stays 10s rather than doubling.
132
+ if self._connected_event.wait(timeout=10) and not self._subscribed_event.wait(timeout=10):
133
+ logger.warning(
134
+ f"Timed out waiting for subscription acknowledgement from {self._broker}. "
135
+ "Continuing anyway - messages published immediately may be missed."
136
+ )
121
137
  except (gaierror, OSError) as e:
122
138
  self._handle_exception(
123
139
  ClientUnreachableError(f"Error connecting to broker: {self._broker} {e}", output_module=self)
@@ -167,7 +183,11 @@ class MQTTEventWatcher(EventWatcher):
167
183
  str: The subscribed topic.
168
184
  """
169
185
  logger.debug(f"Subscribing to {topic}")
170
- self.client.subscribe(topic)
186
+ _, mid = self.client.subscribe(topic)
187
+ if mid is not None:
188
+ with self._subscriptions_lock:
189
+ self._pending_subscriptions.add(mid)
190
+ self._subscribed_event.clear()
171
191
  return topic
172
192
 
173
193
  def unsubscribe(self, topic: str) -> str:
@@ -220,11 +240,51 @@ class MQTTEventWatcher(EventWatcher):
220
240
  self._handle_exception(ClientUnreachableError(f"Connection refused: {message}", output_module=self))
221
241
  return
222
242
 
243
+ # Reset before re-subscribing: on_connect also runs on reconnects, and
244
+ # the previous session's mids are meaningless on the new one.
245
+ with self._subscriptions_lock:
246
+ self._pending_subscriptions.clear()
247
+ self._subscribed_event.clear()
248
+
223
249
  for topic in self._topic_event_map.keys():
224
250
  self.subscribe(topic)
225
251
 
252
+ # Nothing to wait for when there are no topics. Safe to check here:
253
+ # on_connect and on_subscribe both run on paho's network thread, so no
254
+ # SUBACK can be processed while this callback is still running.
255
+ with self._subscriptions_lock:
256
+ if not self._pending_subscriptions:
257
+ self._subscribed_event.set()
258
+
226
259
  self._connected_event.set()
227
260
 
261
+ def on_subscribe(
262
+ self,
263
+ client: mqtt.Client,
264
+ userdata: Any,
265
+ mid: int,
266
+ reason_code_list: Any | None = None,
267
+ properties: Any | None = None,
268
+ ) -> None:
269
+ """
270
+ Callback for when the broker acknowledges a SUBSCRIBE.
271
+
272
+ Clears the mid from the pending set so start() can tell when every
273
+ subscription is actually live, rather than merely requested.
274
+
275
+ Args:
276
+ client (mqtt.Client): The MQTT client instance.
277
+ userdata (Any): The private user data as set in
278
+ Client() or userdata_set().
279
+ mid (int): Message id of the SUBSCRIBE being acknowledged.
280
+ reason_code_list (Optional[Any]): Per-subscription reason codes.
281
+ properties (Optional[Any]): Additional metadata (if any).
282
+ """
283
+ with self._subscriptions_lock:
284
+ self._pending_subscriptions.discard(mid)
285
+ if not self._pending_subscriptions:
286
+ self._subscribed_event.set()
287
+
228
288
  def on_connect_fail(
229
289
  self,
230
290
  client: mqtt.Client,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "leaf-framework"
3
- version = "0.10.16"
3
+ version = "0.10.18"
4
4
  description = "Lightweight Adapter Framework (LEAF)"
5
5
  authors = [
6
6
  "Matthew Crowther <nmc215@ncl.ac.uk>",