Skip to main content

Juno Driver SDK

Everything needed to write a driver for Juno. This is the open part of Juno — the contract between a controller and the drivers it runs. Core itself is closed; nothing here depends on that, and writing a driver needs this documentation and the driver-sdk repository and nothing else.

GuideWhat it covers
QuickstartA working driver, installed, in about ten minutes. Start here.
ConnectionsHow a driver reaches its device — relays, contacts, IR, serial, network — and how AV signal is routed.
RuntimesDeclarative, native and adapter drivers, and the host calls they share.
DiscoveryHow a controller recognizes your hardware on the network.
SetupPairing, codes and link buttons — and what not to ask an installer for.
PublishingBuilding a .junodrv, getting it certified, and shipping it to the registry.
Manifest referenceEvery table and field in manifest.toml.
Proxy referenceEvery device class, its commands, notifications and capabilities. Generated from the contracts a controller enforces, so it cannot drift.

The one idea​

Juno defines a device class once — a light is a light whether it is DALI, Zigbee, or a relay behind a contactor. That definition is a proxy: a fixed set of commands, notifications, and capability flags.

Your driver does two things:

  1. Declares which capabilities it has. Core computes the callable command set from that. A light that cannot dim simply has no set_level — not a set_level that fails.
  2. Translates. Proxy command in, device bytes out. Device bytes in, proxy notification out.

Everything else — the UI, automations, AV routing, and the voice assistant — talks to the proxy and never to you. That is the point. It is also why you get all of them for free.

A driver in twenty lines​

A gas fireplace. It has no protocol at all: it turns on when you close a contact. This is a real, complete, shippable driver.

manifest.toml

[driver]
id = "generic.fireplace.relay"
name = "Fireplace (relay)"
manufacturer = "Generic"
version = "1.0.0"
runtime = "declarative"
api = 1

[[proxy]]
id = 1
type = "switch"
capabilities = { has_auto_off = false }

# We need one relay. We do not care whose.
[[control]]
id = 1
kind = "relay"
name = "Fireplace valve"
required = true

commands.toml

command.on = { control = 1, invoke = "close" }
command.off = { control = 1, invoke = "open" }
command.toggle = { control = 1, invoke = "toggle" }

# Mirror the relay's own state back out as ours.
[[follow]]
control = 1
on = "relay_changed"
notify = "switch_changed"
args = { on = "$closed" }

Zip those two files as fireplace.junodrv and install it.

The installer binds control connection 1 to any relay in the project — a rack-mount relay board, a controller's onboard bank, an ESP32 someone built in a weekend. Your driver never learns which, and you never write that code. That is connections, and it is the part of this SDK worth understanding properly.

Choosing a runtime​

Take the first one that works:

RuntimeUse whenCost
declarativeThe device speaks fixed strings or codes: IR, RS-232, relay, contactNo code. A TOML file.
nativeReal parsing, real state, a real protocolA Rust crate and a compile step
adapterA whole protocol stack already exists as a program — Zigbee, Z-WaveIts own process, and its lifecycle to get right

Most AV rack gear is declarative and stays that way. python and wasm appear in the manifest format but no controller runs them yet — Runtimes says which is which, so you do not find out at install time.

Rules that are not negotiable​

  • Declare capabilities honestly. A capability you declare and cannot deliver becomes a command the voice assistant offers a resident and then fails. Under-declaring is invisible; over-declaring is a support call.
  • Notify on every state change, including ones you caused. Core's state store, the UI, and every automation trigger read from your notifications. A command that changes the device silently is a device that appears stuck.
  • Never poll faster than you need. A house has hundreds of bindings on one controller.
  • Do not assume your transport. If you hardcode a serial port you have broken every installation that puts the device behind an IP-to-serial bridge.

Where things live​

WhereWhat
junohouse/driver-sdkThe contract: proxy definitions, manifest and package formats, and junodrv.
junohouse/docsThis site. content/ is plain markdown; the site is a view over it.
driver.juno.houseThe registry — every certified driver and the builds a controller installs from.
juno.houseThe product.