Skip to main content

Device Connect Edge — lightweight edge device runtime with Zenoh/NATS messaging and D2D communication

Project description

device-connect-edge

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.

Contents

Where This Fits

  device-connect-edge        device-connect-server         device-connect-agent-tools
  (Device Connect SDK — this) (server runtime)    (agent SDK)
        │                       │                       │
        └──────────── Device Connect Mesh ──────────────────────┘
  • device-connect-edge — runs on physical devices (Raspberry Pi, robots, cameras, sensors)
  • device-connect-server — runs on servers. Adds registry, security, state, and CLIs
  • device-connect-agent-tools — connects AI agents (Strands, LangChain, MCP) to the device mesh

Install

Not yet on PyPI. Install from Git:

python3 -m venv .venv
source .venv/bin/activate
pip install "device-connect-edge @ git+https://github.com/arm/device-connect.git#subdirectory=packages/device-connect-edge"

Decorators

Decorator Purpose
@rpc() Expose a method as a remotely-callable function
@emit() Declare an event that can be published to subscribers
@periodic(interval=N) Run a method every N seconds in the background
@on(device_type=..., event_name=...) Subscribe to events from other devices (D2D)
@before_emit("event_name") Intercept an event before it's published

Quick Start

After installing the Device Connect SDK, write a driver and run it.

1. Write a driver

from device_connect_edge.drivers import DeviceDriver, rpc, emit, periodic
from device_connect_edge.types import DeviceIdentity, DeviceStatus

class SensorDriver(DeviceDriver):
    device_type = "sensor"

    @property
    def identity(self) -> DeviceIdentity:
        return DeviceIdentity(device_type="sensor", manufacturer="Acme", model="TH-100")

    @property
    def status(self) -> DeviceStatus:
        return DeviceStatus(availability="available")

    @rpc()
    async def get_reading(self) -> dict:
        """Return the current sensor reading."""
        return {"temperature": 22.5, "humidity": 45}

    @emit()
    async def alert(self, level: str, message: str):
        """Emit an alert event."""
        pass

    @periodic(interval=10.0)
    async def poll_sensor(self):
        reading = await self.get_reading()
        if reading["temperature"] > 30:
            await self.alert(level="warning", message="High temperature")

    async def connect(self) -> None:
        pass  # initialize hardware

    async def disconnect(self) -> None:
        pass  # cleanup hardware

2. Connect to the mesh

import asyncio
from device_connect_edge import DeviceRuntime

async def main():
    device = DeviceRuntime(
        driver=SensorDriver(),
        device_id="sensor-001",
        messaging_urls=["tcp/localhost:7447"],
        # Or use NATS:
        # messaging_urls=["nats://localhost:4222"],
    )
    await device.run()

asyncio.run(main())

3. Run the simulator

Save the code above to my_sensor.py and run it:

# Zenoh (default) — or omit messaging_urls entirely for D2D mode
DEVICE_CONNECT_ALLOW_INSECURE=true python my_sensor.py

# Or NATS
# DEVICE_CONNECT_ALLOW_INSECURE=true NATS_URL=nats://localhost:4222 python my_sensor.py

4. More examples

Example Description
examples/number_generator/ Simulated random number generator with on-demand and periodic emission
examples/string_generator/ Simulated random word fragment generator with mood themes
examples/dht22_sensor/ Real DHT22 temperature/humidity sensor on Raspberry Pi

Real hardware drivers run as a Python process on the physical device and require credentials provisioned by device-connect-server.

# Real hardware (on the device)
NATS_CREDENTIALS_FILE=~/.device-connect/credentials/dht22-001.creds.json python examples/dht22_sensor/device_driver.py

Device-to-Device Mode (No Infrastructure)

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.

D2D mode is the default when no broker endpoint URLs are configured:

device = DeviceRuntime(
    driver=SensorDriver(),
    device_id="sensor-001",
    allow_insecure=True,
    # No messaging_urls → Zenoh peer mode with multicast discovery
)
await device.run()

Or via environment variables:

DEVICE_CONNECT_ALLOW_INSECURE=true python my_device.py

To force D2D mode even when a router URL is set (e.g., router available but no registry):

DEVICE_CONNECT_DISCOVERY_MODE=d2d ZENOH_CONNECT=tcp/localhost:7447 DEVICE_CONNECT_ALLOW_INSECURE=true python my_device.py

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.

Trade-offs vs full infrastructure:

Full Infrastructure D2D Mode
Device state Persistent (etcd) Ephemeral (in-memory)
Offline tracking Registry remembers devices Gone when device stops
Cross-network Zenoh router bridges LANs LAN only (multicast)
Scale 1000s of devices ~50-100 devices

Credentials

Credentials are generated server-side using device-connect-server's provisioning tools. See device-connect-server — Device Commissioning.

The credentials file is JSON with JWT and NKey seed:

{
  "device_id": "sensor-001",
  "auth_type": "jwt",
  "tenant": "default",
  "nats": {
    "urls": ["nats://nats-jwt:4222"],
    "jwt": "<NATS user JWT>",
    "nkey_seed": "<NKey seed>"
  }
}

Pass the file path via environment variable or constructor parameter:

# Via environment variable
NATS_CREDENTIALS_FILE=~/.device-connect/credentials/sensor-001.creds.json \
  NATS_URL=nats://localhost:4222 python my_device.py
# Via constructor
device = DeviceRuntime(
    driver=SensorDriver(),
    device_id="sensor-001",
    nats_credentials_file="~/.device-connect/credentials/sensor-001.creds.json",
    messaging_urls=["nats://localhost:4222"],
)

For development without auth, set DEVICE_CONNECT_ALLOW_INSECURE=true or pass allow_insecure=True to DeviceRuntime.

Testing

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -v --timeout=30

Unit tests run without external services. Integration tests are in tests/.

Contributing

We welcome contributions! Please open an issue to report bugs or suggest features, or submit a pull request directly.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

device_connect_edge-0.2.0.tar.gz (98.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

device_connect_edge-0.2.0-py3-none-any.whl (84.4 kB view details)

Uploaded Python 3

File details

Details for the file device_connect_edge-0.2.0.tar.gz.

File metadata

  • Download URL: device_connect_edge-0.2.0.tar.gz
  • Upload date:
  • Size: 98.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for device_connect_edge-0.2.0.tar.gz
Algorithm Hash digest
SHA256 dd97cad63dcbde37a57854c39711c0c162e71acab523944a6a4e5218fc9d91f1
MD5 91583b12d78fc7f78f75d133c5c2a0d9
BLAKE2b-256 8e9bd6e0e56f95bf17da7e136dc550bb1193522bcc2a1e3bc63d08206fda0d13

See more details on using hashes here.

File details

Details for the file device_connect_edge-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for device_connect_edge-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bae3f4cdc83029a848f41584b51f39114021fdea48564be321f58af43c79c2a5
MD5 24fa33444f6695ada4c182f6a08434bd
BLAKE2b-256 6da02cfe0d065f9f2a4146c448fa8a3d1e6a2ca0e599fa0b007fb3f1c2294089

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page