leaf-framework 0.10.4__tar.gz → 0.10.6__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 (106) hide show
  1. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/PKG-INFO +1 -1
  2. leaf_framework-0.10.6/leaf/adapters/core_adapters/streaming_adapter.py +240 -0
  3. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/adapters/equipment_adapter.py +4 -1
  4. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/data/marketplace.yaml +100 -18
  5. leaf_framework-0.10.6/leaf/modules/phase_modules/measure.py +289 -0
  6. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/start.py +7 -0
  7. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/pyproject.toml +2 -1
  8. leaf_framework-0.10.4/leaf/modules/phase_modules/measure.py +0 -180
  9. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/LICENSE +0 -0
  10. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/README.md +0 -0
  11. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/__init__.py +0 -0
  12. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/adapters/__init__.py +0 -0
  13. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/adapters/core_adapters/__init__.py +0 -0
  14. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/adapters/core_adapters/continuous_experiment_adapter.py +0 -0
  15. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/adapters/core_adapters/discrete_experiment_adapter.py +0 -0
  16. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/adapters/core_adapters/upload_adapter.py +0 -0
  17. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/config/configuration.yaml +0 -0
  18. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/error_handler/__init__.py +0 -0
  19. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/error_handler/error_holder.py +0 -0
  20. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/error_handler/exceptions.py +0 -0
  21. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/measurement_handler/__init__.py +0 -0
  22. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/measurement_handler/handler.py +0 -0
  23. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/measurement_handler/measurements.yaml +0 -0
  24. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/measurement_handler/terms.py +0 -0
  25. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/.gitkeep +0 -0
  26. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/__init__.py +0 -0
  27. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/__init__.py +0 -0
  28. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/ble_watcher.py +0 -0
  29. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/db_watcher.py +0 -0
  30. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/event_watcher.py +0 -0
  31. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/external_api_watcher.py +0 -0
  32. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/external_event_watcher.py +0 -0
  33. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/file_watcher.py +0 -0
  34. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/http_watcher.py +0 -0
  35. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/modbus_watcher.py +0 -0
  36. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/mqtt_external_event_watcher.py +0 -0
  37. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/mqtt_watcher.py +0 -0
  38. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/opc_watcher.py +0 -0
  39. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/polling_watcher.py +0 -0
  40. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/input_modules/simple_watcher.py +0 -0
  41. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/measurement_modules/__init__.py +0 -0
  42. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/measurement_modules/carbon_dioxide.py +0 -0
  43. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/measurement_modules/dissolved_oxygen.py +0 -0
  44. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/measurement_modules/fluorescence.py +0 -0
  45. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/measurement_modules/measurement_module.py +0 -0
  46. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/measurement_modules/optical_density.py +0 -0
  47. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/measurement_modules/ph.py +0 -0
  48. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/measurement_modules/temperature.py +0 -0
  49. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/output_modules/__init__.py +0 -0
  50. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/output_modules/file.py +0 -0
  51. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/output_modules/keydb.py +0 -0
  52. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/output_modules/mqtt.py +0 -0
  53. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/output_modules/output_module.py +0 -0
  54. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/output_modules/sql.py +0 -0
  55. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/output_modules/topic_store.py +0 -0
  56. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/phase_modules/__init__.py +0 -0
  57. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/phase_modules/control.py +0 -0
  58. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/phase_modules/error.py +0 -0
  59. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/phase_modules/external_event_phase.py +0 -0
  60. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/phase_modules/initialisation.py +0 -0
  61. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/phase_modules/phase.py +0 -0
  62. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/phase_modules/start.py +0 -0
  63. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/phase_modules/stop.py +0 -0
  64. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/process_modules/__init__.py +0 -0
  65. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/process_modules/continous_module.py +0 -0
  66. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/process_modules/discrete_module.py +0 -0
  67. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/process_modules/external_event_process.py +0 -0
  68. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/process_modules/process_module.py +0 -0
  69. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/modules/process_modules/upload_module.py +0 -0
  70. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/register/__init__.py +0 -0
  71. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/register/abstract_leaf.py +0 -0
  72. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/register/equipment_actions.yaml +0 -0
  73. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/register/equipment_data.yaml +0 -0
  74. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/register/metadata.py +0 -0
  75. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/register/py.typed +0 -0
  76. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/register/topic_utilities.py +0 -0
  77. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/registry/discovery.py +0 -0
  78. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/registry/loader.py +0 -0
  79. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/registry/registry.py +0 -0
  80. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/registry/utils.py +0 -0
  81. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/__init__.py +0 -0
  82. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/adapter_ui_extension.py +0 -0
  83. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/components/__init__.py +0 -0
  84. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/components/header.py +0 -0
  85. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/constants.py +0 -0
  86. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/desktop_launcher.py +0 -0
  87. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/images/icon.icns +0 -0
  88. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/images/icon.ico +0 -0
  89. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/images/icon.svg +0 -0
  90. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/interface.py +0 -0
  91. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/markdown/configuration_help.md +0 -0
  92. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/markdown/documentation_main.md +0 -0
  93. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/markdown/documentation_protips.md +0 -0
  94. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/scripts.js +0 -0
  95. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/styles.css +0 -0
  96. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/tabs/__init__.py +0 -0
  97. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/tabs/adapters.py +0 -0
  98. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/tabs/configuration.py +0 -0
  99. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/tabs/documentation.py +0 -0
  100. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/tabs/logs.py +0 -0
  101. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/tabs/monitoring.py +0 -0
  102. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/ui/utils.py +0 -0
  103. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/utility/config_utils.py +0 -0
  104. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/utility/logger/__init__.py +0 -0
  105. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/leaf/utility/logger/logger_utils.py +0 -0
  106. {leaf_framework-0.10.4 → leaf_framework-0.10.6}/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.4
3
+ Version: 0.10.6
4
4
  Summary: Lightweight Adapter Framework (LEAF)
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -0,0 +1,240 @@
1
+ import threading
2
+ import time
3
+ from typing import Any, NamedTuple
4
+
5
+ from leaf.adapters.equipment_adapter import AbstractInterpreter, EquipmentAdapter
6
+ from leaf.error_handler.error_holder import ErrorHolder
7
+ from leaf.error_handler.exceptions import AdapterBuildError
8
+ from leaf.modules.input_modules.event_watcher import EventWatcher
9
+ from leaf.modules.input_modules.external_event_watcher import ExternalEventWatcher
10
+ from leaf.modules.output_modules.output_module import OutputModule
11
+ from leaf.modules.phase_modules.error import ErrorPhase
12
+ from leaf.modules.phase_modules.initialisation import InitialisationPhase
13
+ from leaf.modules.phase_modules.measure import MeasurePhase
14
+ from leaf.modules.phase_modules.start import StartPhase
15
+ from leaf.modules.phase_modules.stop import StopPhase
16
+ from leaf.modules.process_modules.process_module import ProcessModule
17
+ from leaf.register.metadata import MetadataManager
18
+ from leaf.utility.logger.logger_utils import get_logger
19
+
20
+ logger = get_logger(__name__, log_file="streaming_adapter.log")
21
+
22
+ # Mirrors the reconnect backoff used by OPCWatcher (opc_watcher.py) for consistency.
23
+ FIRST_RESTART_DELAY = 1
24
+ RESTART_RATE = 2
25
+ MAX_RESTART_DELAY = 60
26
+ # If the previous run stayed up at least this long, treat the next failure
27
+ # as a fresh incident rather than continuing to escalate the backoff.
28
+ STABLE_RUN_THRESHOLD = 60
29
+
30
+
31
+ class _Stream(NamedTuple):
32
+ thread: threading.Thread
33
+ stop_event: threading.Event
34
+
35
+
36
+ class StreamingAdapter(EquipmentAdapter):
37
+ """
38
+ Adapter for equipment whose interpreter produces measurements
39
+ continuously via its own `stream(filename)` generator, started once per
40
+ detected run, rather than being invoked per discrete measurement event.
41
+
42
+ The interpreter must implement `stream(self, filename) -> Iterator[Any]`,
43
+ yielding parsed measurements at its own pace (e.g. tailing a growing
44
+ file). Use this instead of ContinuousExperimentAdapter when the input
45
+ source changes too fast, or too continuously, for a per-event watcher
46
+ callback to keep up.
47
+
48
+ Unlike measurement(), stream() is not given data on every call - it has
49
+ no per-event trigger. Instead, what to stream is discovered from the
50
+ watcher's start event (e.g. FileWatcher.on_created configured with
51
+ return_data=False, so it carries the created filepath rather than file
52
+ content) and handed to stream() once, when that run begins. A matching
53
+ stop event (e.g. on_deleted) halts it.
54
+
55
+ Multiple filenames can stream concurrently, each on its own thread and
56
+ with independent restart/backoff state: a start event for a filename
57
+ that isn't already active launches a new stream alongside any others; a
58
+ start event for a filename that IS already active restarts just that one.
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ instance_data: dict[str, Any],
64
+ watcher: EventWatcher,
65
+ output: OutputModule,
66
+ interpreter: AbstractInterpreter,
67
+ maximum_message_size: int | None = 1,
68
+ error_holder: ErrorHolder | None = None,
69
+ metadata_manager: MetadataManager | None = None,
70
+ experiment_timeout: int | None = None,
71
+ external_watcher: ExternalEventWatcher | None = None,
72
+ ):
73
+ """
74
+ Initialise the StreamingAdapter.
75
+
76
+ Args:
77
+ instance_data (dict): Instance-level configuration and metadata.
78
+ watcher (EventWatcher): Input module used for start/stop/details
79
+ events. Its start event's data is passed straight through to
80
+ interpreter.stream(filename) - see class docstring.
81
+ output (OutputModule): The output module used to transmit parsed data.
82
+ interpreter (AbstractInterpreter): Must implement stream(filename).
83
+ maximum_message_size (Optional[int]): Max number of measurements per transmission.
84
+ error_holder (Optional[ErrorHolder]): Error tracking object.
85
+ metadata_manager (Optional[MetadataManager]): Optional metadata manager.
86
+ experiment_timeout (Optional[int]): Optional timeout between measurements.
87
+ external_watcher (Optional[ExternalEventWatcher]): Optional listener for external inputs.
88
+
89
+ Raises:
90
+ AdapterBuildError: If the interpreter does not implement stream().
91
+ """
92
+ if not hasattr(interpreter, "stream"):
93
+ raise AdapterBuildError("StreamingAdapter requires an interpreter that implements stream(filename)")
94
+
95
+ start_p = StartPhase(metadata_manager)
96
+ stop_p = StopPhase(metadata_manager)
97
+ details_p = InitialisationPhase(metadata_manager)
98
+ error_p = ErrorPhase(metadata_manager)
99
+ # Not included in _control_process's phases: measurements are driven
100
+ # entirely by stream(), never by the watcher's measurement-topic
101
+ # dispatch, so this is only ever touched directly by _run_stream_loop.
102
+ self._measure_p = MeasurePhase(metadata_manager, maximum_message_size=maximum_message_size)
103
+
104
+ self._control_process = ProcessModule(
105
+ output,
106
+ [start_p, stop_p, details_p, error_p],
107
+ metadata_manager=metadata_manager,
108
+ error_holder=error_holder,
109
+ )
110
+
111
+ self._streams: dict[Any, _Stream] = {}
112
+ self._streams_guard = threading.Lock()
113
+
114
+ super().__init__(
115
+ instance_data,
116
+ watcher,
117
+ output,
118
+ [self._control_process],
119
+ interpreter,
120
+ metadata_manager=metadata_manager,
121
+ error_holder=error_holder,
122
+ experiment_timeout=experiment_timeout,
123
+ external_watcher=external_watcher,
124
+ )
125
+ # EquipmentAdapter.__init__ calls p.set_interpreter/set_metadata_manager
126
+ # for each phase in _control_process, which no longer includes
127
+ # _measure_p (see above) - wire it up here instead.
128
+ self._measure_p.set_interpreter(interpreter)
129
+ self._measure_p.set_metadata_manager(self._metadata_manager)
130
+ self._measure_p.set_error_holder(error_holder)
131
+
132
+ # Separate from the standard process_input wiring: this callback
133
+ # exists purely to learn the filename to stream from a real start
134
+ # event, and to halt the stream on a real stop event.
135
+ self._watcher.add_callback(self._handle_watcher_event)
136
+
137
+ def stop(self) -> None:
138
+ """
139
+ Stop the adapter, halting all active streams first.
140
+ """
141
+ with self._streams_guard:
142
+ filenames = list(self._streams.keys())
143
+ for filename in filenames:
144
+ self._stop_stream(filename)
145
+ super().stop()
146
+
147
+ def _handle_watcher_event(self, topic, data: Any) -> None:
148
+ """
149
+ Watches for the real start/stop topics dispatched by the watcher
150
+ (as opposed to the process_input wiring used for messaging) purely
151
+ to drive each filename's stream lifecycle.
152
+
153
+ Any other event (e.g. the "measurement" topic FileWatcher uses for
154
+ files that already existed when it started watching, via
155
+ _process_existing_files) is treated as an implicit start too, as
156
+ long as we're not already streaming that filename - otherwise a
157
+ file present before the watcher starts would never get streamed at
158
+ all, since it never produces a real "created" event. Once a stream
159
+ is active for a filename, further non-start/stop events for it are
160
+ no-ops; the running stream() call handles tailing on its own.
161
+ """
162
+ if topic() == self._metadata_manager.experiment.start():
163
+ self._launch_stream(data)
164
+ elif topic() == self._metadata_manager.experiment.stop():
165
+ self._stop_stream(data)
166
+ else:
167
+ with self._streams_guard:
168
+ already_streaming = data in self._streams
169
+ if not already_streaming:
170
+ self._launch_stream(data)
171
+
172
+ def _launch_stream(self, filename: Any) -> None:
173
+ """
174
+ (Re)launch the background stream thread for the given filename. Any
175
+ existing stream for this SAME filename is halted first; streams for
176
+ other filenames are left untouched and keep running independently.
177
+ """
178
+ self._stop_stream(filename)
179
+
180
+ stop_event = threading.Event()
181
+ thread = threading.Thread(
182
+ target=self._run_stream_loop,
183
+ args=(filename, stop_event),
184
+ daemon=True,
185
+ name=f"leaf-stream-{self._metadata_manager.get_instance_id()}-{filename}",
186
+ )
187
+ with self._streams_guard:
188
+ self._streams[filename] = _Stream(thread, stop_event)
189
+ thread.start()
190
+
191
+ def _stop_stream(self, filename: Any) -> None:
192
+ """
193
+ Signal the given filename's stream generation to stop and wait
194
+ briefly for it to exit. If interpreter.stream() is blocked
195
+ mid-iteration (not suspended at a yield), it cannot be forced to
196
+ stop; the join is best-effort and the thread is a daemon so it
197
+ won't block process shutdown, but it may keep running until that
198
+ blocking call returns.
199
+ """
200
+ with self._streams_guard:
201
+ stream = self._streams.pop(filename, None)
202
+ if stream is None:
203
+ return
204
+ stream.stop_event.set()
205
+ stream.thread.join(timeout=5)
206
+
207
+ def _run_stream_loop(self, filename: Any, stop_event: threading.Event) -> None:
208
+ """
209
+ Run interpreter.stream(filename) via MeasurePhase.stream(), restarting
210
+ it with exponential backoff whenever it ends - whether by returning
211
+ normally or by raising - since neither is expected while this run is
212
+ meant to be active. Stops restarting once stop_event is set (either
213
+ by a real stop event for this filename, or by a newer start event
214
+ superseding it).
215
+
216
+ Note: if the interpreter doesn't keep track of its own resume point
217
+ (e.g. a file offset held on the interpreter instance), a restart
218
+ will call stream(filename) fresh and may re-emit data from the beginning.
219
+ """
220
+ restart_delay = FIRST_RESTART_DELAY
221
+ while not stop_event.is_set():
222
+ started_at = time.monotonic()
223
+ self._measure_p.stream(self._output, filename)
224
+ ran_for = time.monotonic() - started_at
225
+
226
+ if stop_event.is_set():
227
+ break
228
+
229
+ if ran_for >= STABLE_RUN_THRESHOLD:
230
+ restart_delay = FIRST_RESTART_DELAY
231
+
232
+ logger.warning(
233
+ f"Interpreter stream({filename!r}) for {self._metadata_manager.get_instance_id()} ended "
234
+ f"after {ran_for:.1f}s; restarting in {restart_delay}s."
235
+ )
236
+ if stop_event.wait(timeout=restart_delay):
237
+ break
238
+ restart_delay = min(restart_delay * RESTART_RATE, MAX_RESTART_DELAY)
239
+
240
+ logger.info(f"Streaming thread for {self._metadata_manager.get_instance_id()} ({filename!r}) exiting.")
@@ -73,7 +73,10 @@ class AbstractInterpreter(ABC):
73
73
  data (Any): Raw measurement data.
74
74
 
75
75
  Returns:
76
- Any: Parsed or transformed measurement data.
76
+ Any: Parsed or transformed measurement data. May also be a
77
+ (finite) generator; MeasurePhase drains it lazily and chunks
78
+ the yielded items into messages of maximum_message_size,
79
+ instead of requiring the full result built up front.
77
80
  """
78
81
  self._last_measurement = time.time()
79
82
  return data
@@ -11,10 +11,10 @@
11
11
  application: Table parser
12
12
  requirements:
13
13
  folder: "<path>"
14
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-table.git
14
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-table.git
15
15
 
16
16
  - adapter_id: LiveViewUI
17
- image: "./images/plot.png"
17
+ image: "./images/plot.jpg"
18
18
  description: "A plugin with a graph viewer to show submitted data in real-time." # TODO: Add description
19
19
  equipment_data:
20
20
  version: "1.0"
@@ -23,7 +23,20 @@
23
23
  application: UI Extension Demonstration
24
24
  requirements:
25
25
  interval: int
26
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-live-view.git
26
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-live-view.git
27
+
28
+ - adapter_id: Example
29
+ image: ""
30
+ description: "A basic LEAF adapter example. Creates a measurement message every x seconds. Useful as a starting point for developing new adapters."
31
+ equipment_data:
32
+ version: "0.2.1"
33
+ manufacturer: LEAF
34
+ device_type: EXAMPLE
35
+ application: Example application
36
+ requirements:
37
+ interval: int
38
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-example.git
39
+ name: LEAF-Example
27
40
 
28
41
  # - adapter_id: MockFunctionalAdapter
29
42
  # image: ""
@@ -35,7 +48,7 @@
35
48
  # application: DEMO application
36
49
  # requirements:
37
50
  # interval: int
38
- # repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-examples
51
+ # repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-examples
39
52
  # name: LEAF-Examples
40
53
 
41
54
  - adapter_id: BiolectorXT
@@ -49,7 +62,7 @@
49
62
  interval: 11
50
63
  host: string
51
64
  token: string
52
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-biolector-xt
65
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-biolector-xt
53
66
  name: LEAF-Biolector XT
54
67
 
55
68
  - adapter_id: BioLector1
@@ -88,7 +101,7 @@
88
101
  weight: "10kg"
89
102
  requirements:
90
103
  write_file: str
91
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-biolector1
104
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-biolector1
92
105
  name: LEAF-biolector1
93
106
 
94
107
  - adapter_id: leaf_indpensim
@@ -102,7 +115,7 @@
102
115
  application: Industrial Penicilin Simulation
103
116
  requirements:
104
117
  write_file: str
105
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-indpensim
118
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-indpensim
106
119
  name: LEAF-IndPenSim
107
120
 
108
121
  - adapter_id: maq
@@ -118,7 +131,7 @@
118
131
  interval: int
119
132
  token: str
120
133
  site: int
121
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-maq
134
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-maq
122
135
  name: LEAF-MAQ
123
136
 
124
137
  - adapter_id: MinKNOW
@@ -134,7 +147,7 @@
134
147
  host: string
135
148
  port: int
136
149
  token: string
137
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-minknow
150
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-minknow
138
151
  name: LEAF-MinKNOW
139
152
 
140
153
  - adapter_id: iRODS
@@ -152,7 +165,7 @@
152
165
  zone: string
153
166
  user: string
154
167
  password: string
155
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-irods
168
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-irods
156
169
  name: LEAF-iRODS
157
170
 
158
171
  # - adapter_id: HelloWorld
@@ -165,11 +178,11 @@
165
178
  # application: DEMO application
166
179
  # requirements:
167
180
  # interval: int
168
- # repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-hello-world
181
+ # repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-hello-world
169
182
  # name: LEAF-Hello World
170
183
 
171
184
  - adapter_id: Pioreactor
172
- image: ./images/pioreactor.png
185
+ image: ./images/pioreactor.jpg
173
186
  description: "A LEAF adapter for the Pioreactor bioreactor system by Pioreactor. This plugin connects to the Pioreactor MQTT broker to retrieve real-time data on bioreactor conditions and performance."
174
187
  equipment_data:
175
188
  version: "1.0"
@@ -182,7 +195,7 @@
182
195
  port: int
183
196
  username: str
184
197
  password: str
185
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-pioreactor
198
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-pioreactor
186
199
  name: LEAF-Pioreactor
187
200
 
188
201
  - adapter_id: OPC
@@ -194,9 +207,78 @@
194
207
  device_type: OPC sensor module
195
208
  application: OPC Monitoring
196
209
  requirements:
197
- port: int
198
- host: string
199
- topics: list[string]
200
- exclude_topics: list[string]
201
- repo_url: https://gitlab.com/LabEquipmentAdapterFramework/leaf-adapters/leaf-opc-adapter.git
210
+ port: int # OPC server port
211
+ host: string # OPC server host address
212
+ topics: list[string] # Optional: list of OPC node IDs to subscribe to
213
+ exclude_topics: list[string] # Optional: list of OPC node IDs to exclude
214
+ mapper: # Optional: tag mapping rules applied to each measurement
215
+ - field: node # Tag field to match against (e.g. "node")
216
+ pattern: '^R1_.*' # Regex pattern to match the field value
217
+ tags: # Tags to add when the pattern matches
218
+ entity: 9
219
+ - field: node
220
+ pattern: '^R2_.*'
221
+ tags:
222
+ entity: 10
223
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-opc-adapter.git
202
224
  name: LEAF OPC
225
+ - adapter_id: CAFE
226
+ image: icon:bi-thermometer
227
+ description: "A LEAF adapter for the CAFE system from the PROSE-lab Université Paris-Saclay, INRAE."
228
+ equipment_data:
229
+ version: "1.0.0"
230
+ manufacturer: PROSE
231
+ device_type: CAFE system
232
+ application: Monitoring
233
+ requirements:
234
+ time_column: string
235
+ watch: list[string]
236
+ sep: string
237
+ repo_url: https://gitlab.com/bioindustry-4.0/leafs/leaf-cafe.git
238
+ name: LEAF CAFE
239
+ - adapter_id: RAMSMART
240
+ image: ./images/movesense.png
241
+ description: "A LEAF adapter for the RAMSMART system by Movesense. This plugin detects and retrieves BLE real-time data from RAMSMART sensors for monitoring and analysis."
242
+ equipment_data:
243
+ version: "1.0"
244
+ manufacturer: Movesense
245
+ device_type: RAMSMART sensor
246
+ application: Monitoring
247
+ # requirements:
248
+ repo_url: https://git.wur.nl/leaf/adapters/leaf-ramsmart.git
249
+ name: LEAF-RAMSMART
250
+ - adapter_id: BluetoothScanner
251
+ image: icon:bi-bluetooth
252
+ description: "A LEAF adapter for scanning nearby Bluetooth devices. This plugin retrieves real-time raw-data on detected Bluetooth devices for monitoring and analysis."
253
+ equipment_data:
254
+ version: "1.0"
255
+ manufacturer: LEAF
256
+ device_type: Bluetooth Scanner
257
+ application: Monitoring
258
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-bluetooth
259
+ name: leaf_bluetooth
260
+ - adapter_id: leaf_modbus
261
+ image: icon:bi-hdd-network
262
+ description: "A LEAF adapter for Modbus TCP devices. Polls configurable holding registers at a fixed interval and publishes measurements for monitoring and analysis."
263
+ equipment_data:
264
+ version: "0.1.0"
265
+ manufacturer: Modbus
266
+ device_type: Modbus TCP device
267
+ application: Modbus Monitoring
268
+ requirements:
269
+ host: string # Modbus TCP host address
270
+ port: int # Modbus TCP port (default 502)
271
+ device_id: int # Modbus unit/device ID (default 1)
272
+ interval: int # Polling interval in seconds
273
+ registers:
274
+ - name: temperature # Key used in the output payload
275
+ address: 40301 # Holding register address in 4xxxx notation
276
+ scale: 0.01 # Optional: multiply raw value by this factor (default 1.0)
277
+ - name: presence
278
+ address: 40803
279
+ - name: last_ir_ts
280
+ address: 42002
281
+ count: 2 # Optional: number of consecutive registers to read (default 1)
282
+ # Multi-register fields are stored as last_ir_ts_0, last_ir_ts_1, ...
283
+ repo_url: https://gitlab.com/leaf-framework/leaf-adapters/leaf-modbus.git
284
+ name: LEAF-Modbus