Skip to main content

Quickstart

A real driver, from an empty directory to something installed on a controller. About ten minutes, and no code — the device in this walkthrough speaks RS-232, which the declarative runtime handles on its own.

The device is a projector. It has a serial port, discrete power commands, discrete input selection, no volume worth controlling, and it ignores everything for about twenty seconds while its lamp comes up.

1. Get the tools​

One binary, junodrv. It validates a manifest against the real proxy contracts and builds the .junodrv archive a controller installs.

cargo install --locked --features pack \
--git https://github.com/junohouse/driver-sdk driver-sdk

That is the only thing you need. junodrv carries the contracts compiled in, so it works offline and there is no controller in the loop — which is the point: writing and checking a Juno driver requires access to nothing private.

2. Declare what the device is​

mkdir acme-projector && cd acme-projector

manifest.toml

[driver]
id = "acme.projector.serial"
name = "Acme Projector (RS-232)"
manufacturer = "Acme"
version = "1.0.0"
runtime = "declarative"
api = 1

# One binding: this device is a display, so it is a `tv`.
[[proxy]]
id = 1
type = "tv"
primary = true

# A sub-table rather than `capabilities = { … }` only because there are four of them: a TOML
# inline table has to fit on one line. Both forms parse to the same thing.
[proxy.capabilities]
has_discrete_power = true
has_discrete_input = true
has_volume = false
warmup_ms = 20000

# We need a serial port. We do not care whose — see Connections.
[[control]]
id = 1
kind = "serial"
name = "Projector RS-232"
required = true

# The two inputs on the back panel, so the pathfinder can route video to them.
[[connection]]
id = 1001
proxy = 1
dir = "consumer"
class = "HDMI"
name = "HDMI 1"

[[connection]]
id = 1002
proxy = 1
dir = "consumer"
class = "HDMI"
name = "HDMI 2"

Three of those capability lines do real work:

  • has_discrete_power = true gives you on and off instead of only power_toggle. A toggle is guesswork the first time something asks for a room to be on.
  • has_volume = false removes volume_up, volume_down, set_volume and mute_toggle from the callable set. has_volume defaults to true, so leaving it out would advertise four commands this projector does not have.
  • warmup_ms = 20000 is the promise that makes AV routing work. Core waits that long after powering the projector on before sending set_input, which is the difference between a working room and "the projector came on but it is on the wrong input".

Everything a tv may declare is in the tv contract.

3. Say what the bytes are​

commands.toml

# The port settings. Core issues `configure` on the bound serial connection from this block
# when the driver binds; you never call it yourself.
[transport]
kind = "serial"
baud = 9600
databits = 8
parity = "none"
stopbits = 1

command.on = { tx = "PWR ON\r", expect = ":", timeout_ms = 3000 }
command.off = { tx = "PWR OFF\r", expect = ":" }

# The proxy passes `connection`, which is one of the ids from the manifest. The device wants
# its own numbering, so the mapping happens here rather than anywhere downstream.
command.set_input = { tx = "SOURCE {connection:04d}\r" }

# What the projector says back. Each frame is matched against every rule in order.
[receive]
delimiter = "\r"

parse = [
{ regex = "PWR=(\\d)", notify = "power_changed", args = { on = "$1 == 1" } },
{ regex = "SOURCE=(\\d+)", notify = "input_changed", args = { connection = "$1" } },
]

# It never volunteers anything, so ask.
[poll.power]
tx = "PWR?\r"
every_ms = 5000

4. Check it​

junodrv check .
acme.projector.serial v1.0.0 ok

This is the same validation the certification pipeline runs, against the same contracts a controller enforces. It catches a capability that does not exist, a parameter out of range, a connection pointing at a proxy you did not declare, and a notification the capabilities you declared do not permit. Get in the habit of running it before you build.

Try breaking it on purpose. A manifest that fails here is one that would otherwise have failed at install time, in somebody's house:

error: acme.projector.serial:
proxy 1: capability `warmup_ms`: "slow" is not a U32

error: acme.projector.serial:
proxy 1: unknown capability `nope_not_real` for proxy `tv`

5. Build it​

junodrv pack . --out dist
acme.projector.serial v1.0.0 -> dist/acme.projector.serial-1.0.0.junodrv

pack validates first and separately, so it never writes an archive that would not install.

6. Put it on a controller​

Drag the .junodrv onto the configurator's driver pane, or drop it in the controller's drivers/ directory and restart. Either way it is marked Third-party: nobody certified it, and the controller will never auto-update it. That is the right state for a driver you are still writing.

Then, in the configurator:

  1. Add a device using the driver.
  2. Draw a line from its Projector RS-232 control connection to any serial port in the project. A DB9 on the controller, a Global Caché iTach across the LAN, a USB dongle — the driver is byte-identical for all three and never learns which it got.
  3. Press On.

7. Watch what it is doing​

The driver's pane shows every command in, every byte out, and every notification back. Two things are worth knowing while you are debugging:

  • A command that never appears was never callable. Check the capability that gates it in the proxy reference — an absent command is nearly always an undeclared capability, not a broken driver.
  • A device that appears stuck is usually one that is not notifying. Core's state, the UI and every automation trigger read from your notifications, not from your commands, so a PWR ON with no power_changed behind it looks like nothing happened.

Where to go next​

NextWhy
ConnectionsWhy the driver above never mentions a serial device, and how AV signal routing works. The part of this SDK worth understanding properly.
RuntimesWhen declarative runs out, and what to write instead.
DiscoveryMaking a controller find the device on its own rather than being told an address.
Manifest referenceEverything the file in step 2 can contain.
PublishingShipping it — to your own users, or into the registry.