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.
- device_connect_edge-0.2.0/LICENSE +23 -0
- device_connect_edge-0.2.0/PKG-INFO +274 -0
- device_connect_edge-0.2.0/README.md +230 -0
- device_connect_edge-0.2.0/device_connect_edge/__init__.py +69 -0
- device_connect_edge-0.2.0/device_connect_edge/device.py +1616 -0
- device_connect_edge-0.2.0/device_connect_edge/discovery.py +375 -0
- device_connect_edge-0.2.0/device_connect_edge/drivers/__init__.py +51 -0
- device_connect_edge-0.2.0/device_connect_edge/drivers/base.py +1217 -0
- device_connect_edge-0.2.0/device_connect_edge/drivers/decorators.py +844 -0
- device_connect_edge-0.2.0/device_connect_edge/drivers/transport.py +64 -0
- device_connect_edge-0.2.0/device_connect_edge/errors.py +120 -0
- device_connect_edge-0.2.0/device_connect_edge/messaging/__init__.py +89 -0
- device_connect_edge-0.2.0/device_connect_edge/messaging/base.py +231 -0
- device_connect_edge-0.2.0/device_connect_edge/messaging/config.py +235 -0
- device_connect_edge-0.2.0/device_connect_edge/messaging/exceptions.py +38 -0
- device_connect_edge-0.2.0/device_connect_edge/messaging/mqtt_adapter.py +470 -0
- device_connect_edge-0.2.0/device_connect_edge/messaging/nats_adapter.py +473 -0
- device_connect_edge-0.2.0/device_connect_edge/messaging/zenoh_adapter.py +819 -0
- device_connect_edge-0.2.0/device_connect_edge/telemetry/__init__.py +46 -0
- device_connect_edge-0.2.0/device_connect_edge/telemetry/config.py +251 -0
- device_connect_edge-0.2.0/device_connect_edge/telemetry/file_buffer_exporter.py +228 -0
- device_connect_edge-0.2.0/device_connect_edge/telemetry/metrics.py +144 -0
- device_connect_edge-0.2.0/device_connect_edge/telemetry/propagation.py +137 -0
- device_connect_edge-0.2.0/device_connect_edge/telemetry/tracer.py +119 -0
- device_connect_edge-0.2.0/device_connect_edge/types.py +248 -0
- device_connect_edge-0.2.0/device_connect_edge.egg-info/PKG-INFO +274 -0
- device_connect_edge-0.2.0/device_connect_edge.egg-info/SOURCES.txt +45 -0
- device_connect_edge-0.2.0/device_connect_edge.egg-info/dependency_links.txt +1 -0
- device_connect_edge-0.2.0/device_connect_edge.egg-info/requires.txt +19 -0
- device_connect_edge-0.2.0/device_connect_edge.egg-info/top_level.txt +1 -0
- device_connect_edge-0.2.0/pyproject.toml +60 -0
- device_connect_edge-0.2.0/setup.cfg +4 -0
- device_connect_edge-0.2.0/tests/test_device.py +552 -0
- device_connect_edge-0.2.0/tests/test_discovery.py +568 -0
- device_connect_edge-0.2.0/tests/test_driver_transport.py +136 -0
- device_connect_edge-0.2.0/tests/test_drivers.py +190 -0
- device_connect_edge-0.2.0/tests/test_errors.py +35 -0
- device_connect_edge-0.2.0/tests/test_file_buffer_exporter.py +183 -0
- device_connect_edge-0.2.0/tests/test_messaging_base.py +87 -0
- device_connect_edge-0.2.0/tests/test_messaging_config.py +175 -0
- device_connect_edge-0.2.0/tests/test_messaging_exceptions.py +50 -0
- device_connect_edge-0.2.0/tests/test_mqtt_adapter.py +607 -0
- device_connect_edge-0.2.0/tests/test_nats_adapter.py +593 -0
- device_connect_edge-0.2.0/tests/test_telemetry.py +254 -0
- device_connect_edge-0.2.0/tests/test_types.py +73 -0
- device_connect_edge-0.2.0/tests/test_wait_for_device.py +138 -0
- 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
|
+
]
|