edgelens-robot 0.2.1__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,12 @@
1
+ .venv
2
+ __pycache__
3
+ *.egg-info
4
+ dist
5
+ build
6
+ .pytest_cache
7
+ .mypy_cache
8
+ .ruff_cache
9
+ *.pyc
10
+ output.xml
11
+ log.html
12
+ report.html
@@ -0,0 +1,14 @@
1
+ """Robot Framework library for the EdgeLens Edge AI Device Farm.
2
+
3
+ Import in a Robot suite via ``Library EdgeLensLibrary``. Keywords
4
+ mirror the Python SDK's surface — session lifecycle (Request Device /
5
+ Release Device), file ops (Deploy Model), the display
6
+ namespace (Play On Display / Show On Display / ...), the inference
7
+ namespace (Start Inference / Run Inference / ...), and helpers (Get
8
+ SSH Details / Execute On Device / Get Device Info).
9
+ """
10
+
11
+ from EdgeLensLibrary._version import __version__ as __version__
12
+ from EdgeLensLibrary.library import EdgeLensLibrary
13
+
14
+ __all__ = ["EdgeLensLibrary"]
@@ -0,0 +1 @@
1
+ __version__ = "0.2.1"
@@ -0,0 +1,328 @@
1
+ """``EdgeLensLibrary`` — the keyword-driven Robot Framework surface.
2
+
3
+ Designed for the SUITE scope (one device per suite by default — cheaper
4
+ than per-test allocation, matches the pytest plugin's session scope).
5
+ Pass ``scope=TEST`` at import time for per-test sessions when tests
6
+ mutate device state.
7
+
8
+ Usage::
9
+
10
+ *** Settings ***
11
+ Library EdgeLensLibrary api_key=%{EDGELENS_API_KEY}
12
+
13
+ *** Test Cases ***
14
+ Camera Renders The Test Pattern
15
+ Request Device os=stock-os
16
+ Deploy Model ${MODEL_PATH}
17
+ Start Inference
18
+ ${stats}= Run Inference duration=30
19
+ Should Be True ${stats.fps.avg} > 15
20
+ [Teardown] Release Device
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import os
26
+ from typing import Any, Optional
27
+
28
+ from edgelens import EdgeLensClient, Session
29
+ from edgelens.errors import EdgeLensError
30
+ from robot.api import logger as robot_logger
31
+ from robot.api.deco import keyword, library
32
+
33
+ from EdgeLensLibrary._version import __version__
34
+
35
+
36
+ @library(scope="SUITE", auto_keywords=False, version=__version__)
37
+ class EdgeLensLibrary:
38
+ """Keyword-driven Robot Framework interface to the EdgeLens SDK.
39
+
40
+ Constructor arguments (set on the ``Library EdgeLensLibrary`` line):
41
+
42
+ - ``api_key``: API key. Defaults to ``$EDGELENS_API_KEY``.
43
+ - ``base_url``: Override the platform base URL.
44
+ - ``scope``: Library scope — ``SUITE`` (default), ``TEST``, or ``GLOBAL``.
45
+
46
+ Set scope via the import line:
47
+
48
+ ::
49
+
50
+ Library EdgeLensLibrary scope=TEST
51
+ """
52
+
53
+ ROBOT_LIBRARY_VERSION = __version__
54
+ ROBOT_AUTO_KEYWORDS = False
55
+
56
+ def __init__(
57
+ self,
58
+ api_key: Optional[str] = None,
59
+ base_url: Optional[str] = None,
60
+ scope: str = "SUITE",
61
+ ):
62
+ # Robot translates the keyword scope into ROBOT_LIBRARY_SCOPE
63
+ # at class level; @library(scope=...) handles the static case
64
+ # but we expose it via constructor for late binding too.
65
+ self.__class__.ROBOT_LIBRARY_SCOPE = scope.upper() # type: ignore[attr-defined]
66
+
67
+ self._api_key = api_key or os.environ.get("EDGELENS_API_KEY")
68
+ self._base_url = base_url
69
+ self._client: Optional[EdgeLensClient] = None
70
+ self._session: Optional[Session] = None
71
+
72
+ # ------------------------------------------------------------------
73
+ # Internal plumbing
74
+ # ------------------------------------------------------------------
75
+
76
+ def _ensure_client(self) -> EdgeLensClient:
77
+ if self._client is None:
78
+ kwargs: dict[str, Any] = {}
79
+ if self._api_key:
80
+ kwargs["api_key"] = self._api_key
81
+ if self._base_url:
82
+ kwargs["base_url"] = self._base_url
83
+ self._client = EdgeLensClient(**kwargs)
84
+ return self._client
85
+
86
+ def _require_session(self) -> Session:
87
+ if self._session is None:
88
+ raise EdgeLensError(
89
+ "No active device session. Call `Request Device` first."
90
+ )
91
+ return self._session
92
+
93
+ # ------------------------------------------------------------------
94
+ # Session lifecycle
95
+ # ------------------------------------------------------------------
96
+
97
+ @keyword("Request Device")
98
+ def request_device(
99
+ self,
100
+ device_type: str = "recamera-2002",
101
+ os: str = "stock-os",
102
+ timeout: int = 3600,
103
+ wait_timeout: int = 300,
104
+ ) -> str:
105
+ """Allocate a device and wait for ``status=ready``.
106
+
107
+ Returns the session id. The allocated session is stored on the
108
+ library instance — subsequent keywords (`Deploy Model`,
109
+ `Execute On Device`, ...) target it implicitly.
110
+
111
+ Example::
112
+
113
+ Request Device device_type=recamera-2002 os=stock-os
114
+ """
115
+ client = self._ensure_client()
116
+ try:
117
+ session = client.create_session(
118
+ device_type=device_type, os=os, timeout=int(timeout),
119
+ )
120
+ except ValueError as e:
121
+ raise EdgeLensError(f"Request Device failed: {e}") from e
122
+ # Until self._session is set, nothing else can release this billed
123
+ # session. BaseException so a Ctrl-C during the silent wait still
124
+ # releases it.
125
+ try:
126
+ session.wait_ready(timeout=int(wait_timeout))
127
+ except BaseException:
128
+ try:
129
+ session.release()
130
+ except Exception as release_exc: # noqa: BLE001
131
+ robot_logger.warn(
132
+ f"Release of session {session.id} failed after Request "
133
+ f"Device did not complete: {release_exc} (original error "
134
+ "follows)"
135
+ )
136
+ raise
137
+ self._session = session
138
+ return session.id
139
+
140
+ @keyword("Release Device")
141
+ def release_device(self) -> None:
142
+ """End the active session and return the device to the pool.
143
+
144
+ No-op when there's no active session — safe to call from
145
+ ``[Teardown]`` even on failed setup.
146
+ """
147
+ if self._session is None:
148
+ return
149
+ try:
150
+ self._session.release()
151
+ finally:
152
+ self._session = None
153
+
154
+ @keyword("Get Session Id")
155
+ def get_session_id(self) -> str:
156
+ return self._require_session().id
157
+
158
+ @keyword("Get Session Status")
159
+ def get_session_status(self) -> str:
160
+ session = self._require_session()
161
+ session.refresh()
162
+ return session.status.value
163
+
164
+ # ------------------------------------------------------------------
165
+ # Device operations
166
+ # ------------------------------------------------------------------
167
+
168
+ @keyword("Execute On Device")
169
+ def execute_on_device(self, command: str, timeout: float = 60.0) -> dict:
170
+ """Run ``command`` via SSH (proxied). Returns a dict with
171
+ ``stdout``, ``stderr``, ``exit_code``. Raises on transport
172
+ failure (use ``Run Keyword And Ignore Error`` if you want soft
173
+ handling).
174
+ """
175
+ result = self._require_session().device.exec(command, timeout=float(timeout))
176
+ return {
177
+ "stdout": result.stdout,
178
+ "stderr": result.stderr,
179
+ "exit_code": result.exit_code,
180
+ }
181
+
182
+ @keyword("Deploy Model")
183
+ def deploy_model(
184
+ self,
185
+ model: str,
186
+ config: Optional[str] = None,
187
+ files: Optional[list] = None,
188
+ ) -> dict:
189
+ """Deploy a model file (and optional config + extra files) to the device.
190
+
191
+ On reCamaginedOS, files land in /userdata/Models/ readable but not
192
+ executable; ``chmod +x`` a deployed binary before running it.
193
+ """
194
+ return dict(
195
+ self._require_session().device.deploy(model=model, config=config, files=files)
196
+ )
197
+
198
+ @keyword("Get SSH Details")
199
+ def get_ssh_details(self) -> dict:
200
+ """Return SSH connection details (user/host/port).
201
+
202
+ The password is NOT included by
203
+ default. Robot Framework's log.html / output.xml record
204
+ keyword return values verbatim, so returning the password
205
+ here would leak it to CI artifacts.
206
+
207
+ Use ``Set SSH Password Variable`` to make the password available
208
+ to the suite without writing it to the log.
209
+ """
210
+ creds = self._require_session().device.ssh()
211
+ return {
212
+ "user": creds.user,
213
+ "host": creds.host,
214
+ "port": creds.port,
215
+ }
216
+
217
+ @keyword("Set SSH Password Variable", tags=["secret"])
218
+ def set_ssh_password_variable(
219
+ self, name: str = "${SSH_PASSWORD}", scope: str = "TEST",
220
+ ) -> None:
221
+ """Store the SSH password in a variable without logging its value.
222
+
223
+ ``scope`` is ``TEST`` (or ``TASK``), ``SUITE`` or ``GLOBAL``.
224
+ Example::
225
+
226
+ Set SSH Password Variable
227
+ SSHLibrary.Login ${user} ${SSH_PASSWORD}
228
+
229
+ Robot logs keyword arguments only at TRACE level, so passing the
230
+ variable on keeps the value out of log.html and output.xml at the
231
+ default INFO level. Do not run with ``--loglevel TRACE`` or
232
+ ``DEBUG`` when the log is published.
233
+ """
234
+ from robot.libraries.BuiltIn import BuiltIn
235
+ from robot.utils import escape
236
+
237
+ setters = {
238
+ "TEST": "set_test_variable",
239
+ "TASK": "set_test_variable",
240
+ "SUITE": "set_suite_variable",
241
+ "GLOBAL": "set_global_variable",
242
+ }
243
+ setter = setters.get(scope.upper())
244
+ if setter is None:
245
+ raise ValueError(f"scope must be one of {', '.join(setters)}")
246
+ password = self._require_session().device.ssh().password
247
+ builtin = BuiltIn()
248
+ # BuiltIn's Set * Variable keywords log "${name} = <value>" at INFO;
249
+ # suppress logging while the value is set.
250
+ previous = builtin.set_log_level("NONE")
251
+ try:
252
+ getattr(builtin, setter)(name, escape(password))
253
+ finally:
254
+ builtin.set_log_level(previous)
255
+
256
+ @keyword("Get SSH Password", tags=["secret"])
257
+ def get_ssh_password(self) -> str:
258
+ """Return the SSH password as a string.
259
+
260
+ Robot logs the assignment ``${pw}= Get SSH Password`` at INFO
261
+ level, value included, so the password lands in log.html and
262
+ output.xml. Use ``Set SSH Password Variable`` instead.
263
+ """
264
+ return self._require_session().device.ssh().password
265
+
266
+ @keyword("Get Device Info")
267
+ def get_device_info(self) -> dict:
268
+ info = self._require_session().device.info()
269
+ if hasattr(info, "model_dump"):
270
+ return info.model_dump()
271
+ return dict(getattr(info, "__dict__", {}))
272
+
273
+ @keyword("Get Stream URL")
274
+ def get_stream_url(self) -> str:
275
+ return self._require_session().device.stream_url()
276
+
277
+ # ------------------------------------------------------------------
278
+ # Display
279
+ # ------------------------------------------------------------------
280
+
281
+ @keyword("Play On Display")
282
+ def play_on_display(self, source: str, loop: bool = False) -> None:
283
+ self._require_session().device.display.play(source, loop=bool(loop))
284
+
285
+ @keyword("Show On Display")
286
+ def show_on_display(self, source: str) -> None:
287
+ self._require_session().device.display.show(source)
288
+
289
+ @keyword("Stop Display")
290
+ def stop_display(self) -> None:
291
+ self._require_session().device.display.stop()
292
+
293
+ @keyword("Get Display Status")
294
+ def get_display_status(self) -> dict:
295
+ return dict(self._require_session().device.display.status())
296
+
297
+ # ------------------------------------------------------------------
298
+ # Inference
299
+ # ------------------------------------------------------------------
300
+
301
+ @keyword("Start Inference")
302
+ def start_inference(self) -> None:
303
+ self._require_session().device.inference.start()
304
+
305
+ @keyword("Stop Inference")
306
+ def stop_inference(self) -> None:
307
+ self._require_session().device.inference.stop()
308
+
309
+ @keyword("Run Inference Once")
310
+ def run_inference_once(self) -> dict:
311
+ return dict(self._require_session().device.inference.once())
312
+
313
+ @keyword("Get Inference Stats")
314
+ def get_inference_stats(self) -> dict:
315
+ snap = self._require_session().device.inference.snapshot()
316
+ if hasattr(snap, "model_dump"):
317
+ return snap.model_dump()
318
+ return dict(getattr(snap, "__dict__", {}))
319
+
320
+ @keyword("Run Inference")
321
+ def run_inference(self, duration: float = 30.0, collect_frames: bool = False) -> dict:
322
+ """Run inference for ``duration`` seconds, return aggregated stats."""
323
+ stats = self._require_session().device.inference.run(
324
+ duration=float(duration), collect_frames=bool(collect_frames)
325
+ )
326
+ if hasattr(stats, "model_dump"):
327
+ return stats.model_dump()
328
+ return dict(getattr(stats, "__dict__", {}))
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EdgeLens
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.5
2
+ Name: edgelens-robot
3
+ Version: 0.2.1
4
+ Summary: Robot Framework library for the EdgeLens Edge AI Device Farm.
5
+ Project-URL: Homepage, https://edgelens.ai
6
+ Project-URL: Repository, https://github.com/edgelens-ai/edgelens-sdk
7
+ Project-URL: Issues, https://github.com/edgelens-ai/edgelens-sdk/issues
8
+ Author-email: EdgeLens <support@edgelens.ai>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: edgelens,robotframework,testing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: Robot Framework
14
+ Classifier: Framework :: Robot Framework :: Library
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: edgelens-sdk<0.3,>=0.2.1
20
+ Requires-Dist: robotframework>=6.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: mypy>=1.0; extra == 'dev'
23
+ Requires-Dist: pytest>=7.0; extra == 'dev'
24
+ Requires-Dist: ruff>=0.1; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # edgelens-robot
28
+
29
+ Robot Framework library for the EdgeLens Edge AI Device Farm. Keyword-
30
+ driven wrapper around the Python SDK — write `Request Device`,
31
+ `Deploy Model`, `Run Inference`, and let Robot drive the camera.
32
+
33
+ Counterpart to [`edgelens-pytest`](../pytest) for pytest users and
34
+ [`@edgelens/playwright-plugin`](../playwright) for JS/TS.
35
+
36
+ ## Install
37
+
38
+ ```sh
39
+ pip install edgelens-robot
40
+ ```
41
+
42
+ Requires Python 3.9+ and Robot Framework 6.0+.
43
+
44
+ ## Quickstart
45
+
46
+ ```robotframework
47
+ *** Settings ***
48
+ Library EdgeLensLibrary api_key=%{EDGELENS_API_KEY}
49
+ Suite Teardown Release Device
50
+
51
+ *** Test Cases ***
52
+ Camera Renders The Test Pattern
53
+ Request Device os=stock-os
54
+ Deploy Model ./face.cvimodel
55
+ Start Inference
56
+ ${stats}= Run Inference duration=30
57
+ Should Be True ${stats}[fps][avg] > 15
58
+ ```
59
+
60
+ Run it:
61
+
62
+ ```sh
63
+ EDGELENS_API_KEY=el_test_xxx robot tests/
64
+ ```
65
+
66
+ ## Keywords
67
+
68
+ ### Session lifecycle
69
+
70
+ | Keyword | Arguments | Returns |
71
+ |---|---|---|
72
+ | `Request Device` | `device_type=recamera-2002`, `os=stock-os`, `timeout=3600`, `wait_timeout=300` | Session id |
73
+ | `Release Device` | — | (no-op if no session) |
74
+ | `Get Session Id` | — | Session id |
75
+ | `Get Session Status` | — | Refreshes + returns status string |
76
+
77
+ ### Device operations
78
+
79
+ | Keyword | Arguments | Returns |
80
+ |---|---|---|
81
+ | `Execute On Device` | `command`, `timeout=60.0` | `{stdout, stderr, exit_code}` dict |
82
+ | `Deploy Model` | `model`, `config=None`, `files=None` | dict |
83
+ | `Get SSH Details` | — | `{user, host, port}` dict |
84
+ | `Set SSH Password Variable` | `name=${SSH_PASSWORD}`, `scope=TEST` | Sets the variable without logging the value |
85
+ | `Get SSH Password` | — | Password string; Robot logs the assigned value at INFO, so prefer `Set SSH Password Variable` |
86
+ | `Get Device Info` | — | full info report dict |
87
+ | `Get Stream URL` | — | URL string (stock OS only) |
88
+
89
+ ### Display
90
+
91
+ | Keyword | Arguments |
92
+ |---|---|
93
+ | `Play On Display` | `source`, `loop=False` |
94
+ | `Show On Display` | `source` |
95
+ | `Stop Display` | — |
96
+ | `Get Display Status` | — → `{ready, state, source}` |
97
+
98
+ ### Inference (stock OS only)
99
+
100
+ | Keyword | Arguments |
101
+ |---|---|
102
+ | `Start Inference` | — |
103
+ | `Stop Inference` | — |
104
+ | `Run Inference Once` | — → detection dict |
105
+ | `Get Inference Stats` | — → snapshot dict |
106
+ | `Run Inference` | `duration=30.0`, `collect_frames=False` → full stats dict |
107
+
108
+ ## Library scope
109
+
110
+ Default is **SUITE** — one camera per suite, shared across every test in
111
+ the file. The right tradeoff for read-only suites where allocation
112
+ takes 30s+. Override on the `Library` line:
113
+
114
+ ```robotframework
115
+ Library EdgeLensLibrary scope=TEST # fresh camera per test
116
+ Library EdgeLensLibrary scope=GLOBAL # one camera across the whole run
117
+ ```
118
+
119
+ ## Configuration
120
+
121
+ | Argument | Default | Purpose |
122
+ |---|---|---|
123
+ | `api_key` | `%{EDGELENS_API_KEY}` | API key. Pass via env or library import. |
124
+ | `base_url` | — | Self-hosted EdgeLens deployments. |
125
+ | `scope` | `SUITE` | `SUITE` / `TEST` / `GLOBAL`. |
126
+
127
+ ## CI
128
+
129
+ The `edgelensai/edgelens-sdk` image ships Robot Framework — drop the library
130
+ in and you're set:
131
+
132
+ ### GitHub Actions
133
+
134
+ ```yaml
135
+ - uses: edgelens-ai/edgelens-sdk/ci/github-action@v0.2.1
136
+ with:
137
+ api-key: ${{ secrets.EDGELENS_API_KEY }}
138
+ command: robot --output output.xml tests/
139
+ junit-file: output.xml
140
+ ```
141
+
142
+ ### GitLab CI
143
+
144
+ ```yaml
145
+ include:
146
+ - remote: "https://raw.githubusercontent.com/edgelens-ai/edgelens-sdk/main/ci/gitlab/edgelens.gitlab-ci.yml"
147
+
148
+ edgelens-robot:
149
+ extends: .edgelens:robot
150
+ ```
151
+
152
+ ## Develop
153
+
154
+ ```sh
155
+ python -m venv .venv && source .venv/bin/activate
156
+ pip install -e ".[dev]"
157
+ pytest
158
+ ```
159
+
160
+ ## See also
161
+
162
+ Pairs with edgelens-pytest and @edgelens/playwright-plugin.
@@ -0,0 +1,136 @@
1
+ # edgelens-robot
2
+
3
+ Robot Framework library for the EdgeLens Edge AI Device Farm. Keyword-
4
+ driven wrapper around the Python SDK — write `Request Device`,
5
+ `Deploy Model`, `Run Inference`, and let Robot drive the camera.
6
+
7
+ Counterpart to [`edgelens-pytest`](../pytest) for pytest users and
8
+ [`@edgelens/playwright-plugin`](../playwright) for JS/TS.
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ pip install edgelens-robot
14
+ ```
15
+
16
+ Requires Python 3.9+ and Robot Framework 6.0+.
17
+
18
+ ## Quickstart
19
+
20
+ ```robotframework
21
+ *** Settings ***
22
+ Library EdgeLensLibrary api_key=%{EDGELENS_API_KEY}
23
+ Suite Teardown Release Device
24
+
25
+ *** Test Cases ***
26
+ Camera Renders The Test Pattern
27
+ Request Device os=stock-os
28
+ Deploy Model ./face.cvimodel
29
+ Start Inference
30
+ ${stats}= Run Inference duration=30
31
+ Should Be True ${stats}[fps][avg] > 15
32
+ ```
33
+
34
+ Run it:
35
+
36
+ ```sh
37
+ EDGELENS_API_KEY=el_test_xxx robot tests/
38
+ ```
39
+
40
+ ## Keywords
41
+
42
+ ### Session lifecycle
43
+
44
+ | Keyword | Arguments | Returns |
45
+ |---|---|---|
46
+ | `Request Device` | `device_type=recamera-2002`, `os=stock-os`, `timeout=3600`, `wait_timeout=300` | Session id |
47
+ | `Release Device` | — | (no-op if no session) |
48
+ | `Get Session Id` | — | Session id |
49
+ | `Get Session Status` | — | Refreshes + returns status string |
50
+
51
+ ### Device operations
52
+
53
+ | Keyword | Arguments | Returns |
54
+ |---|---|---|
55
+ | `Execute On Device` | `command`, `timeout=60.0` | `{stdout, stderr, exit_code}` dict |
56
+ | `Deploy Model` | `model`, `config=None`, `files=None` | dict |
57
+ | `Get SSH Details` | — | `{user, host, port}` dict |
58
+ | `Set SSH Password Variable` | `name=${SSH_PASSWORD}`, `scope=TEST` | Sets the variable without logging the value |
59
+ | `Get SSH Password` | — | Password string; Robot logs the assigned value at INFO, so prefer `Set SSH Password Variable` |
60
+ | `Get Device Info` | — | full info report dict |
61
+ | `Get Stream URL` | — | URL string (stock OS only) |
62
+
63
+ ### Display
64
+
65
+ | Keyword | Arguments |
66
+ |---|---|
67
+ | `Play On Display` | `source`, `loop=False` |
68
+ | `Show On Display` | `source` |
69
+ | `Stop Display` | — |
70
+ | `Get Display Status` | — → `{ready, state, source}` |
71
+
72
+ ### Inference (stock OS only)
73
+
74
+ | Keyword | Arguments |
75
+ |---|---|
76
+ | `Start Inference` | — |
77
+ | `Stop Inference` | — |
78
+ | `Run Inference Once` | — → detection dict |
79
+ | `Get Inference Stats` | — → snapshot dict |
80
+ | `Run Inference` | `duration=30.0`, `collect_frames=False` → full stats dict |
81
+
82
+ ## Library scope
83
+
84
+ Default is **SUITE** — one camera per suite, shared across every test in
85
+ the file. The right tradeoff for read-only suites where allocation
86
+ takes 30s+. Override on the `Library` line:
87
+
88
+ ```robotframework
89
+ Library EdgeLensLibrary scope=TEST # fresh camera per test
90
+ Library EdgeLensLibrary scope=GLOBAL # one camera across the whole run
91
+ ```
92
+
93
+ ## Configuration
94
+
95
+ | Argument | Default | Purpose |
96
+ |---|---|---|
97
+ | `api_key` | `%{EDGELENS_API_KEY}` | API key. Pass via env or library import. |
98
+ | `base_url` | — | Self-hosted EdgeLens deployments. |
99
+ | `scope` | `SUITE` | `SUITE` / `TEST` / `GLOBAL`. |
100
+
101
+ ## CI
102
+
103
+ The `edgelensai/edgelens-sdk` image ships Robot Framework — drop the library
104
+ in and you're set:
105
+
106
+ ### GitHub Actions
107
+
108
+ ```yaml
109
+ - uses: edgelens-ai/edgelens-sdk/ci/github-action@v0.2.1
110
+ with:
111
+ api-key: ${{ secrets.EDGELENS_API_KEY }}
112
+ command: robot --output output.xml tests/
113
+ junit-file: output.xml
114
+ ```
115
+
116
+ ### GitLab CI
117
+
118
+ ```yaml
119
+ include:
120
+ - remote: "https://raw.githubusercontent.com/edgelens-ai/edgelens-sdk/main/ci/gitlab/edgelens.gitlab-ci.yml"
121
+
122
+ edgelens-robot:
123
+ extends: .edgelens:robot
124
+ ```
125
+
126
+ ## Develop
127
+
128
+ ```sh
129
+ python -m venv .venv && source .venv/bin/activate
130
+ pip install -e ".[dev]"
131
+ pytest
132
+ ```
133
+
134
+ ## See also
135
+
136
+ Pairs with edgelens-pytest and @edgelens/playwright-plugin.
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "edgelens-robot"
7
+ dynamic = ["version"]
8
+ description = "Robot Framework library for the EdgeLens Edge AI Device Farm."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "EdgeLens", email = "support@edgelens.ai" }]
13
+ requires-python = ">=3.9"
14
+ keywords = ["robotframework", "edgelens", "testing"]
15
+ classifiers = [
16
+ "Framework :: Robot Framework",
17
+ "Framework :: Robot Framework :: Library",
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Software Development :: Testing",
22
+ ]
23
+ dependencies = [
24
+ "edgelens-sdk>=0.2.1,<0.3",
25
+ "robotframework>=6.0",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "pytest>=7.0",
31
+ "ruff>=0.1",
32
+ "mypy>=1.0",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://edgelens.ai"
37
+ Repository = "https://github.com/edgelens-ai/edgelens-sdk"
38
+ Issues = "https://github.com/edgelens-ai/edgelens-sdk/issues"
39
+
40
+ [tool.hatch.build.targets.sdist]
41
+ include = ["/EdgeLensLibrary", "/README.md", "/LICENSE"]
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["EdgeLensLibrary"]
45
+
46
+ [tool.ruff]
47
+ line-length = 100
48
+ target-version = "py39"
49
+
50
+ [tool.hatch.version]
51
+ path = "EdgeLensLibrary/_version.py"