device-connect-edge 0.2.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.
Files changed (47) hide show
  1. device_connect_edge-0.2.0/LICENSE +23 -0
  2. device_connect_edge-0.2.0/PKG-INFO +274 -0
  3. device_connect_edge-0.2.0/README.md +230 -0
  4. device_connect_edge-0.2.0/device_connect_edge/__init__.py +69 -0
  5. device_connect_edge-0.2.0/device_connect_edge/device.py +1616 -0
  6. device_connect_edge-0.2.0/device_connect_edge/discovery.py +375 -0
  7. device_connect_edge-0.2.0/device_connect_edge/drivers/__init__.py +51 -0
  8. device_connect_edge-0.2.0/device_connect_edge/drivers/base.py +1217 -0
  9. device_connect_edge-0.2.0/device_connect_edge/drivers/decorators.py +844 -0
  10. device_connect_edge-0.2.0/device_connect_edge/drivers/transport.py +64 -0
  11. device_connect_edge-0.2.0/device_connect_edge/errors.py +120 -0
  12. device_connect_edge-0.2.0/device_connect_edge/messaging/__init__.py +89 -0
  13. device_connect_edge-0.2.0/device_connect_edge/messaging/base.py +231 -0
  14. device_connect_edge-0.2.0/device_connect_edge/messaging/config.py +235 -0
  15. device_connect_edge-0.2.0/device_connect_edge/messaging/exceptions.py +38 -0
  16. device_connect_edge-0.2.0/device_connect_edge/messaging/mqtt_adapter.py +470 -0
  17. device_connect_edge-0.2.0/device_connect_edge/messaging/nats_adapter.py +473 -0
  18. device_connect_edge-0.2.0/device_connect_edge/messaging/zenoh_adapter.py +819 -0
  19. device_connect_edge-0.2.0/device_connect_edge/telemetry/__init__.py +46 -0
  20. device_connect_edge-0.2.0/device_connect_edge/telemetry/config.py +251 -0
  21. device_connect_edge-0.2.0/device_connect_edge/telemetry/file_buffer_exporter.py +228 -0
  22. device_connect_edge-0.2.0/device_connect_edge/telemetry/metrics.py +144 -0
  23. device_connect_edge-0.2.0/device_connect_edge/telemetry/propagation.py +137 -0
  24. device_connect_edge-0.2.0/device_connect_edge/telemetry/tracer.py +119 -0
  25. device_connect_edge-0.2.0/device_connect_edge/types.py +248 -0
  26. device_connect_edge-0.2.0/device_connect_edge.egg-info/PKG-INFO +274 -0
  27. device_connect_edge-0.2.0/device_connect_edge.egg-info/SOURCES.txt +45 -0
  28. device_connect_edge-0.2.0/device_connect_edge.egg-info/dependency_links.txt +1 -0
  29. device_connect_edge-0.2.0/device_connect_edge.egg-info/requires.txt +19 -0
  30. device_connect_edge-0.2.0/device_connect_edge.egg-info/top_level.txt +1 -0
  31. device_connect_edge-0.2.0/pyproject.toml +60 -0
  32. device_connect_edge-0.2.0/setup.cfg +4 -0
  33. device_connect_edge-0.2.0/tests/test_device.py +552 -0
  34. device_connect_edge-0.2.0/tests/test_discovery.py +568 -0
  35. device_connect_edge-0.2.0/tests/test_driver_transport.py +136 -0
  36. device_connect_edge-0.2.0/tests/test_drivers.py +190 -0
  37. device_connect_edge-0.2.0/tests/test_errors.py +35 -0
  38. device_connect_edge-0.2.0/tests/test_file_buffer_exporter.py +183 -0
  39. device_connect_edge-0.2.0/tests/test_messaging_base.py +87 -0
  40. device_connect_edge-0.2.0/tests/test_messaging_config.py +175 -0
  41. device_connect_edge-0.2.0/tests/test_messaging_exceptions.py +50 -0
  42. device_connect_edge-0.2.0/tests/test_mqtt_adapter.py +607 -0
  43. device_connect_edge-0.2.0/tests/test_nats_adapter.py +593 -0
  44. device_connect_edge-0.2.0/tests/test_telemetry.py +254 -0
  45. device_connect_edge-0.2.0/tests/test_types.py +73 -0
  46. device_connect_edge-0.2.0/tests/test_wait_for_device.py +138 -0
  47. device_connect_edge-0.2.0/tests/test_zenoh_adapter.py +1298 -0
@@ -0,0 +1,23 @@
1
+ Copyright 2024-2026 Arm Limited
2
+ PROPRIETARY AND CONFIDENTIAL
3
+ All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ proprietary and confidential property of Arm Limited. The Software is
7
+ provided under a separate Non-Disclosure Agreement (NDA) and may not be
8
+ used, copied, modified, merged, published, distributed, sublicensed, or
9
+ sold except as expressly permitted by that agreement.
10
+
11
+ Unauthorized copying, distribution, or use of this Software is strictly
12
+ prohibited and may result in civil and criminal penalties.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ARM
17
+ LIMITED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM
18
+ THE USE OF THE SOFTWARE.
19
+
20
+ ---
21
+
22
+ NOTE: This software is intended for future release under the Apache License,
23
+ Version 2.0. The open-source license will become effective upon public release.
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.4
2
+ Name: device-connect-edge
3
+ Version: 0.2.0
4
+ Summary: Device Connect Edge — lightweight edge device runtime with Zenoh/NATS messaging and D2D communication
5
+ Author-email: Arm <opensource@arm.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/arm/device-connect
8
+ Project-URL: Repository, https://github.com/arm/device-connect.git
9
+ Project-URL: Issues, https://github.com/arm/device-connect/issues
10
+ Project-URL: License, https://github.com/arm/device-connect?tab=Apache-2.0-1-ov-file
11
+ Project-URL: Code of Conduct, https://github.com/arm/device-connect?tab=coc-ov-file
12
+ Project-URL: Contributing, https://github.com/arm/device-connect?tab=contributing-ov-file
13
+ Project-URL: Security, https://github.com/arm/device-connect?tab=security-ov-file
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: Apache Software License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: eclipse-zenoh>=1.0.0
28
+ Requires-Dist: nats-py<3,>=2.5.0
29
+ Requires-Dist: pydantic<3,>=2.7.1
30
+ Requires-Dist: nkeys>=0.2.0
31
+ Requires-Dist: pyyaml>=6.0
32
+ Provides-Extra: zenoh
33
+ Provides-Extra: telemetry
34
+ Requires-Dist: opentelemetry-api>=1.30.0; extra == "telemetry"
35
+ Requires-Dist: opentelemetry-sdk>=1.30.0; extra == "telemetry"
36
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.30.0; extra == "telemetry"
37
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.30.0; extra == "telemetry"
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest>=8.0; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
41
+ Requires-Dist: pytest-timeout>=2.0; extra == "dev"
42
+ Requires-Dist: device-connect-edge[telemetry]; extra == "dev"
43
+ Dynamic: license-file
44
+
45
+ # device-connect-edge
46
+
47
+ Lightweight Python SDK for enabling physical devices to work with Device Connect. You write the device logic; the runtime handles registration, heartbeats, and command routing.
48
+
49
+ ## Contents
50
+
51
+ - [Where This Fits](#where-this-fits)
52
+ - [Install](#install)
53
+ - [Decorators](#decorators)
54
+ - [Quick Start](#quick-start)
55
+ - [Device-to-Device Mode](#device-to-device-mode-no-infrastructure)
56
+ - [Credentials](#credentials)
57
+ - [Testing](#testing)
58
+ - [Contributing](#contributing)
59
+
60
+ ## Where This Fits
61
+
62
+ ```
63
+ device-connect-edge device-connect-server device-connect-agent-tools
64
+ (Device Connect SDK — this) (server runtime) (agent SDK)
65
+ │ │ │
66
+ └──────────── Device Connect Mesh ──────────────────────┘
67
+ ```
68
+
69
+ - **device-connect-edge** — runs on physical devices (Raspberry Pi, robots, cameras, sensors)
70
+ - **device-connect-server** — runs on servers. Adds registry, security, state, and CLIs
71
+ - **device-connect-agent-tools** — connects AI agents (Strands, LangChain, MCP) to the device mesh
72
+
73
+ ## Install
74
+
75
+ > Not yet on PyPI. Install from Git:
76
+
77
+ ```bash
78
+ python3 -m venv .venv
79
+ source .venv/bin/activate
80
+ pip install "device-connect-edge @ git+https://github.com/arm/device-connect.git#subdirectory=packages/device-connect-edge"
81
+ ```
82
+
83
+ ## Decorators
84
+
85
+ | Decorator | Purpose |
86
+ |-----------|---------|
87
+ | `@rpc()` | Expose a method as a remotely-callable function |
88
+ | `@emit()` | Declare an event that can be published to subscribers |
89
+ | `@periodic(interval=N)` | Run a method every N seconds in the background |
90
+ | `@on(device_type=..., event_name=...)` | Subscribe to events from other devices (D2D) |
91
+ | `@before_emit("event_name")` | Intercept an event before it's published |
92
+
93
+ ## Quick Start
94
+
95
+ After installing the Device Connect SDK, write a driver and run it.
96
+
97
+ ### 1. Write a driver
98
+
99
+ ```python
100
+ from device_connect_edge.drivers import DeviceDriver, rpc, emit, periodic
101
+ from device_connect_edge.types import DeviceIdentity, DeviceStatus
102
+
103
+ class SensorDriver(DeviceDriver):
104
+ device_type = "sensor"
105
+
106
+ @property
107
+ def identity(self) -> DeviceIdentity:
108
+ return DeviceIdentity(device_type="sensor", manufacturer="Acme", model="TH-100")
109
+
110
+ @property
111
+ def status(self) -> DeviceStatus:
112
+ return DeviceStatus(availability="available")
113
+
114
+ @rpc()
115
+ async def get_reading(self) -> dict:
116
+ """Return the current sensor reading."""
117
+ return {"temperature": 22.5, "humidity": 45}
118
+
119
+ @emit()
120
+ async def alert(self, level: str, message: str):
121
+ """Emit an alert event."""
122
+ pass
123
+
124
+ @periodic(interval=10.0)
125
+ async def poll_sensor(self):
126
+ reading = await self.get_reading()
127
+ if reading["temperature"] > 30:
128
+ await self.alert(level="warning", message="High temperature")
129
+
130
+ async def connect(self) -> None:
131
+ pass # initialize hardware
132
+
133
+ async def disconnect(self) -> None:
134
+ pass # cleanup hardware
135
+ ```
136
+
137
+ ### 2. Connect to the mesh
138
+
139
+ ```python
140
+ import asyncio
141
+ from device_connect_edge import DeviceRuntime
142
+
143
+ async def main():
144
+ device = DeviceRuntime(
145
+ driver=SensorDriver(),
146
+ device_id="sensor-001",
147
+ messaging_urls=["tcp/localhost:7447"],
148
+ # Or use NATS:
149
+ # messaging_urls=["nats://localhost:4222"],
150
+ )
151
+ await device.run()
152
+
153
+ asyncio.run(main())
154
+ ```
155
+
156
+ ### 3. Run the simulator
157
+
158
+ Save the code above to `my_sensor.py` and run it:
159
+
160
+ ```bash
161
+ # Zenoh (default) — or omit messaging_urls entirely for D2D mode
162
+ DEVICE_CONNECT_ALLOW_INSECURE=true python my_sensor.py
163
+
164
+ # Or NATS
165
+ # DEVICE_CONNECT_ALLOW_INSECURE=true NATS_URL=nats://localhost:4222 python my_sensor.py
166
+ ```
167
+
168
+ ### 4. More examples
169
+
170
+ | Example | Description |
171
+ |---------|-------------|
172
+ | [`examples/number_generator/`](examples/number_generator/) | Simulated random number generator with on-demand and periodic emission |
173
+ | [`examples/string_generator/`](examples/string_generator/) | Simulated random word fragment generator with mood themes |
174
+ | [`examples/dht22_sensor/`](examples/dht22_sensor/) | Real DHT22 temperature/humidity sensor on Raspberry Pi |
175
+
176
+ > **Real hardware drivers** run as a Python process on the physical device and require credentials provisioned by [device-connect-server](../device-connect-server/).
177
+
178
+ ```bash
179
+ # Real hardware (on the device)
180
+ NATS_CREDENTIALS_FILE=~/.device-connect/credentials/dht22-001.creds.json python examples/dht22_sensor/device_driver.py
181
+ ```
182
+
183
+ ## Device-to-Device Mode (No Infrastructure)
184
+
185
+ Devices can discover each other directly on the LAN without any infrastructure (no broker, no etcd, no device registry). This uses Zenoh's built-in multicast scouting.
186
+
187
+ **D2D mode is the default** when no broker endpoint URLs are configured:
188
+
189
+ ```python
190
+ device = DeviceRuntime(
191
+ driver=SensorDriver(),
192
+ device_id="sensor-001",
193
+ allow_insecure=True,
194
+ # No messaging_urls → Zenoh peer mode with multicast discovery
195
+ )
196
+ await device.run()
197
+ ```
198
+
199
+ Or via environment variables:
200
+
201
+ ```bash
202
+ DEVICE_CONNECT_ALLOW_INSECURE=true python my_device.py
203
+ ```
204
+
205
+ To force D2D mode even when a router URL is set (e.g., router available but no registry):
206
+
207
+ ```bash
208
+ DEVICE_CONNECT_DISCOVERY_MODE=d2d ZENOH_CONNECT=tcp/localhost:7447 DEVICE_CONNECT_ALLOW_INSECURE=true python my_device.py
209
+ ```
210
+
211
+ **How it works:** Each device announces its presence (capabilities, identity, status) via `device-connect.{tenant}.{device_id}.presence` messages. Other devices subscribe to a wildcard and maintain an in-memory peer table. Device-to-device RPC works identically to infrastructure mode.
212
+
213
+ **Trade-offs vs full infrastructure:**
214
+
215
+ | | Full Infrastructure | D2D Mode |
216
+ |---|---|---|
217
+ | Device state | Persistent (etcd) | Ephemeral (in-memory) |
218
+ | Offline tracking | Registry remembers devices | Gone when device stops |
219
+ | Cross-network | Zenoh router bridges LANs | LAN only (multicast) |
220
+ | Scale | 1000s of devices | ~50-100 devices |
221
+
222
+ ## Credentials
223
+
224
+ Credentials are generated server-side using device-connect-server's provisioning tools. See [device-connect-server — Device Commissioning](../device-connect-server/README.md#device-commissioning-flow).
225
+
226
+ The credentials file is JSON with JWT and NKey seed:
227
+
228
+ ```json
229
+ {
230
+ "device_id": "sensor-001",
231
+ "auth_type": "jwt",
232
+ "tenant": "default",
233
+ "nats": {
234
+ "urls": ["nats://nats-jwt:4222"],
235
+ "jwt": "<NATS user JWT>",
236
+ "nkey_seed": "<NKey seed>"
237
+ }
238
+ }
239
+ ```
240
+
241
+ Pass the file path via environment variable or constructor parameter:
242
+
243
+ ```bash
244
+ # Via environment variable
245
+ NATS_CREDENTIALS_FILE=~/.device-connect/credentials/sensor-001.creds.json \
246
+ NATS_URL=nats://localhost:4222 python my_device.py
247
+ ```
248
+
249
+ ```python
250
+ # Via constructor
251
+ device = DeviceRuntime(
252
+ driver=SensorDriver(),
253
+ device_id="sensor-001",
254
+ nats_credentials_file="~/.device-connect/credentials/sensor-001.creds.json",
255
+ messaging_urls=["nats://localhost:4222"],
256
+ )
257
+ ```
258
+
259
+ For development without auth, set `DEVICE_CONNECT_ALLOW_INSECURE=true` or pass `allow_insecure=True` to `DeviceRuntime`.
260
+
261
+ ## Testing
262
+
263
+ ```bash
264
+ python3 -m venv .venv
265
+ source .venv/bin/activate
266
+ pip install -e ".[dev]"
267
+ pytest tests/ -v --timeout=30
268
+ ```
269
+
270
+ Unit tests run without external services. Integration tests are in [tests/](../../tests/).
271
+
272
+ ## Contributing
273
+
274
+ We welcome contributions! Please open an [issue](https://github.com/arm/device-connect/issues) to report bugs or suggest features, or submit a [pull request](https://github.com/arm/device-connect/pulls) directly.
@@ -0,0 +1,230 @@
1
+ # device-connect-edge
2
+
3
+ Lightweight Python SDK for enabling physical devices to work with Device Connect. You write the device logic; the runtime handles registration, heartbeats, and command routing.
4
+
5
+ ## Contents
6
+
7
+ - [Where This Fits](#where-this-fits)
8
+ - [Install](#install)
9
+ - [Decorators](#decorators)
10
+ - [Quick Start](#quick-start)
11
+ - [Device-to-Device Mode](#device-to-device-mode-no-infrastructure)
12
+ - [Credentials](#credentials)
13
+ - [Testing](#testing)
14
+ - [Contributing](#contributing)
15
+
16
+ ## Where This Fits
17
+
18
+ ```
19
+ device-connect-edge device-connect-server device-connect-agent-tools
20
+ (Device Connect SDK — this) (server runtime) (agent SDK)
21
+ │ │ │
22
+ └──────────── Device Connect Mesh ──────────────────────┘
23
+ ```
24
+
25
+ - **device-connect-edge** — runs on physical devices (Raspberry Pi, robots, cameras, sensors)
26
+ - **device-connect-server** — runs on servers. Adds registry, security, state, and CLIs
27
+ - **device-connect-agent-tools** — connects AI agents (Strands, LangChain, MCP) to the device mesh
28
+
29
+ ## Install
30
+
31
+ > Not yet on PyPI. Install from Git:
32
+
33
+ ```bash
34
+ python3 -m venv .venv
35
+ source .venv/bin/activate
36
+ pip install "device-connect-edge @ git+https://github.com/arm/device-connect.git#subdirectory=packages/device-connect-edge"
37
+ ```
38
+
39
+ ## Decorators
40
+
41
+ | Decorator | Purpose |
42
+ |-----------|---------|
43
+ | `@rpc()` | Expose a method as a remotely-callable function |
44
+ | `@emit()` | Declare an event that can be published to subscribers |
45
+ | `@periodic(interval=N)` | Run a method every N seconds in the background |
46
+ | `@on(device_type=..., event_name=...)` | Subscribe to events from other devices (D2D) |
47
+ | `@before_emit("event_name")` | Intercept an event before it's published |
48
+
49
+ ## Quick Start
50
+
51
+ After installing the Device Connect SDK, write a driver and run it.
52
+
53
+ ### 1. Write a driver
54
+
55
+ ```python
56
+ from device_connect_edge.drivers import DeviceDriver, rpc, emit, periodic
57
+ from device_connect_edge.types import DeviceIdentity, DeviceStatus
58
+
59
+ class SensorDriver(DeviceDriver):
60
+ device_type = "sensor"
61
+
62
+ @property
63
+ def identity(self) -> DeviceIdentity:
64
+ return DeviceIdentity(device_type="sensor", manufacturer="Acme", model="TH-100")
65
+
66
+ @property
67
+ def status(self) -> DeviceStatus:
68
+ return DeviceStatus(availability="available")
69
+
70
+ @rpc()
71
+ async def get_reading(self) -> dict:
72
+ """Return the current sensor reading."""
73
+ return {"temperature": 22.5, "humidity": 45}
74
+
75
+ @emit()
76
+ async def alert(self, level: str, message: str):
77
+ """Emit an alert event."""
78
+ pass
79
+
80
+ @periodic(interval=10.0)
81
+ async def poll_sensor(self):
82
+ reading = await self.get_reading()
83
+ if reading["temperature"] > 30:
84
+ await self.alert(level="warning", message="High temperature")
85
+
86
+ async def connect(self) -> None:
87
+ pass # initialize hardware
88
+
89
+ async def disconnect(self) -> None:
90
+ pass # cleanup hardware
91
+ ```
92
+
93
+ ### 2. Connect to the mesh
94
+
95
+ ```python
96
+ import asyncio
97
+ from device_connect_edge import DeviceRuntime
98
+
99
+ async def main():
100
+ device = DeviceRuntime(
101
+ driver=SensorDriver(),
102
+ device_id="sensor-001",
103
+ messaging_urls=["tcp/localhost:7447"],
104
+ # Or use NATS:
105
+ # messaging_urls=["nats://localhost:4222"],
106
+ )
107
+ await device.run()
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ ### 3. Run the simulator
113
+
114
+ Save the code above to `my_sensor.py` and run it:
115
+
116
+ ```bash
117
+ # Zenoh (default) — or omit messaging_urls entirely for D2D mode
118
+ DEVICE_CONNECT_ALLOW_INSECURE=true python my_sensor.py
119
+
120
+ # Or NATS
121
+ # DEVICE_CONNECT_ALLOW_INSECURE=true NATS_URL=nats://localhost:4222 python my_sensor.py
122
+ ```
123
+
124
+ ### 4. More examples
125
+
126
+ | Example | Description |
127
+ |---------|-------------|
128
+ | [`examples/number_generator/`](examples/number_generator/) | Simulated random number generator with on-demand and periodic emission |
129
+ | [`examples/string_generator/`](examples/string_generator/) | Simulated random word fragment generator with mood themes |
130
+ | [`examples/dht22_sensor/`](examples/dht22_sensor/) | Real DHT22 temperature/humidity sensor on Raspberry Pi |
131
+
132
+ > **Real hardware drivers** run as a Python process on the physical device and require credentials provisioned by [device-connect-server](../device-connect-server/).
133
+
134
+ ```bash
135
+ # Real hardware (on the device)
136
+ NATS_CREDENTIALS_FILE=~/.device-connect/credentials/dht22-001.creds.json python examples/dht22_sensor/device_driver.py
137
+ ```
138
+
139
+ ## Device-to-Device Mode (No Infrastructure)
140
+
141
+ Devices can discover each other directly on the LAN without any infrastructure (no broker, no etcd, no device registry). This uses Zenoh's built-in multicast scouting.
142
+
143
+ **D2D mode is the default** when no broker endpoint URLs are configured:
144
+
145
+ ```python
146
+ device = DeviceRuntime(
147
+ driver=SensorDriver(),
148
+ device_id="sensor-001",
149
+ allow_insecure=True,
150
+ # No messaging_urls → Zenoh peer mode with multicast discovery
151
+ )
152
+ await device.run()
153
+ ```
154
+
155
+ Or via environment variables:
156
+
157
+ ```bash
158
+ DEVICE_CONNECT_ALLOW_INSECURE=true python my_device.py
159
+ ```
160
+
161
+ To force D2D mode even when a router URL is set (e.g., router available but no registry):
162
+
163
+ ```bash
164
+ DEVICE_CONNECT_DISCOVERY_MODE=d2d ZENOH_CONNECT=tcp/localhost:7447 DEVICE_CONNECT_ALLOW_INSECURE=true python my_device.py
165
+ ```
166
+
167
+ **How it works:** Each device announces its presence (capabilities, identity, status) via `device-connect.{tenant}.{device_id}.presence` messages. Other devices subscribe to a wildcard and maintain an in-memory peer table. Device-to-device RPC works identically to infrastructure mode.
168
+
169
+ **Trade-offs vs full infrastructure:**
170
+
171
+ | | Full Infrastructure | D2D Mode |
172
+ |---|---|---|
173
+ | Device state | Persistent (etcd) | Ephemeral (in-memory) |
174
+ | Offline tracking | Registry remembers devices | Gone when device stops |
175
+ | Cross-network | Zenoh router bridges LANs | LAN only (multicast) |
176
+ | Scale | 1000s of devices | ~50-100 devices |
177
+
178
+ ## Credentials
179
+
180
+ Credentials are generated server-side using device-connect-server's provisioning tools. See [device-connect-server — Device Commissioning](../device-connect-server/README.md#device-commissioning-flow).
181
+
182
+ The credentials file is JSON with JWT and NKey seed:
183
+
184
+ ```json
185
+ {
186
+ "device_id": "sensor-001",
187
+ "auth_type": "jwt",
188
+ "tenant": "default",
189
+ "nats": {
190
+ "urls": ["nats://nats-jwt:4222"],
191
+ "jwt": "<NATS user JWT>",
192
+ "nkey_seed": "<NKey seed>"
193
+ }
194
+ }
195
+ ```
196
+
197
+ Pass the file path via environment variable or constructor parameter:
198
+
199
+ ```bash
200
+ # Via environment variable
201
+ NATS_CREDENTIALS_FILE=~/.device-connect/credentials/sensor-001.creds.json \
202
+ NATS_URL=nats://localhost:4222 python my_device.py
203
+ ```
204
+
205
+ ```python
206
+ # Via constructor
207
+ device = DeviceRuntime(
208
+ driver=SensorDriver(),
209
+ device_id="sensor-001",
210
+ nats_credentials_file="~/.device-connect/credentials/sensor-001.creds.json",
211
+ messaging_urls=["nats://localhost:4222"],
212
+ )
213
+ ```
214
+
215
+ For development without auth, set `DEVICE_CONNECT_ALLOW_INSECURE=true` or pass `allow_insecure=True` to `DeviceRuntime`.
216
+
217
+ ## Testing
218
+
219
+ ```bash
220
+ python3 -m venv .venv
221
+ source .venv/bin/activate
222
+ pip install -e ".[dev]"
223
+ pytest tests/ -v --timeout=30
224
+ ```
225
+
226
+ Unit tests run without external services. Integration tests are in [tests/](../../tests/).
227
+
228
+ ## Contributing
229
+
230
+ We welcome contributions! Please open an [issue](https://github.com/arm/device-connect/issues) to report bugs or suggest features, or submit a [pull request](https://github.com/arm/device-connect/pulls) directly.
@@ -0,0 +1,69 @@
1
+ """Device Connect SDK — lightweight runtime for edge devices.
2
+
3
+ Build IoT devices with Python. Connect them over Zenoh or NATS. Communicate
4
+ device-to-device using RPC and events. This is the only package a
5
+ Raspberry Pi (or any edge device) needs to install.
6
+
7
+ Example:
8
+ from device_connect_edge import DeviceRuntime
9
+ from device_connect_edge.drivers import DeviceDriver, rpc, emit
10
+
11
+ class Sensor(DeviceDriver):
12
+ device_type = "sensor"
13
+
14
+ @rpc()
15
+ async def get_reading(self) -> dict:
16
+ return {"temp": 22.5}
17
+
18
+ @emit()
19
+ async def alert(self, level: str, msg: str):
20
+ pass
21
+
22
+ device = DeviceRuntime(
23
+ driver=Sensor(),
24
+ device_id="sensor-001",
25
+ messaging_urls=["tcp/localhost:7447"],
26
+ )
27
+ await device.run()
28
+ """
29
+ from device_connect_edge.device import (
30
+ DeviceRuntime,
31
+ build_rpc_error,
32
+ build_rpc_response,
33
+ )
34
+ from device_connect_edge.types import (
35
+ DeviceState,
36
+ DeviceCapabilities,
37
+ DeviceIdentity,
38
+ DeviceStatus,
39
+ FunctionDef,
40
+ EventDef,
41
+ )
42
+ from device_connect_edge.errors import (
43
+ DeviceConnectError,
44
+ DeviceError,
45
+ DeviceDependencyError,
46
+ RegistrationError,
47
+ FunctionInvocationError,
48
+ ValidationError,
49
+ CommissioningError,
50
+ )
51
+
52
+ __all__ = [
53
+ "DeviceRuntime",
54
+ "build_rpc_error",
55
+ "build_rpc_response",
56
+ "DeviceState",
57
+ "DeviceCapabilities",
58
+ "DeviceIdentity",
59
+ "DeviceStatus",
60
+ "FunctionDef",
61
+ "EventDef",
62
+ "DeviceConnectError",
63
+ "DeviceError",
64
+ "DeviceDependencyError",
65
+ "RegistrationError",
66
+ "FunctionInvocationError",
67
+ "ValidationError",
68
+ "CommissioningError",
69
+ ]