Run a Linux gadget
The Linux client runs the same device core as the ESP32 firmware. It keeps a device identity, pairs with Hermes, reconnects after network interruptions, and accepts local text messages and events. It runs without a desktop or display.
The initial target is Raspberry Pi 4 or 5 with 64-bit Raspberry Pi OS Lite Trixie. This port is experimental. No physical Pi verification report is recorded yet. CI runs the native core and Linux socket tests on x86-64 and ARM64 Ubuntu.
Install on Raspberry Pi
Use Raspberry Pi Imager to install 64-bit Raspberry Pi OS Lite Trixie. Configure your network and SSH access there, then boot the Pi with a suitable power supply. The Linux client uses the OS network settings.
Download the hermes-gadget-VERSION-linux-arm64.tar.gz archive and its .sha256
file from a release that
includes the Pi package. Until the first such release is tagged, the same files
are available in the linux-arm64 artifact of a successful main
CI run.
Extract the artifact ZIP first if downloading from CI.
In a directory containing just the chosen archive and checksum file:
sudo apt update
sudo apt install python3-venv libportaudio2 libstdc++6
sha256sum --check hermes-gadget-*-linux-arm64.tar.gz.sha256
tar -xzf hermes-gadget-*-linux-arm64.tar.gz
cd hermes-gadget-*-linux-arm64
sudo sh install.sh
sudoedit /etc/hermes-gadget/config.json
Set server to your Hermes host, such as ws://192.168.1.20:8765/gadget, and
choose a name. Add token if your gateway requires it. The installer downloads
Python dependencies into a private environment, creates the hermes-gadget
service account, and installs the native core. No compiler is needed on the Pi.
Configuration and device state stay outside the installed release directory.
sudo systemctl enable --now hermes-gadget
sudo hermes-gadget-device status
Approve the pairing code on the Hermes host with hermes gadget approve CODE.
Then use the installed device:
sudo hermes-gadget-device send "Hello from the Pi"
sudo hermes-gadget-device messages
hermes-gadget-device runs controls as the service account and selects the
correct state directory. Use it in place of hermes-gadget linux in the examples
below when working with a package installation. To diagnose startup, use
sudo journalctl -u hermes-gadget -n 50.
Build from source
On Linux, install Python 3.10 or later, a C++17 compiler, CMake and Git. For Raspberry Pi OS:
sudo apt update
sudo apt install git python3-venv cmake build-essential
git clone https://github.com/Adolanium/hermes-gadget-sdk.git
cd hermes-gadget-sdk
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e .
hermes-gadget build-sim --test
build-sim builds the shared native library used by both the Linux client and
desktop simulator. Keep this checkout available while running the client.
Create device-config.json with your Hermes host address:
{
"server": "ws://192.168.1.20:8765/gadget",
"name": "Kitchen Gadget"
}
Add a token string if your Hermes gateway requires an access token. Protect the
configuration with chmod 600 device-config.json. Use wss:// outside a trusted
local network. Follow Connect Hermes to enable the plugin.
hermes-gadget linux run --config device-config.json
In another terminal with the environment activated:
hermes-gadget linux status
Approve the reported pairing code with hermes gadget approve CODE on your
Hermes host. Then send a message and read the response:
hermes-gadget linux send "Hello from the kitchen"
hermes-gadget linux messages
Local controls
hermes-gadget linux event door.opened --data '{"room":"kitchen"}' --notify
hermes-gadget linux button cancel press
hermes-gadget linux button cancel release
send and event require a paired, connected device. They do not queue messages
while offline. --notify asks the agent to respond to the event; omit it to
report the event without starting a turn. The service keeps the latest 20
replies and notices in memory. messages includes sequence numbers so a local
consumer can ignore entries it already read. Restarting clears that history.
Add USB audio
Install PortAudio and the audio extra, then list the connected devices:
The Pi installer already installs these dependencies. For a source checkout:
sudo apt install libportaudio2
python -m pip install -e '.[audio]'
hermes-gadget linux audio-devices
Add audio to your configuration. Use a device number from the list or a unique
part of its name. Names are preferable when USB device numbers change:
{
"server": "ws://192.168.1.20:8765/gadget",
"name": "Kitchen Gadget",
"audio": {"input": "USB Audio", "output": "USB Audio", "rate": 16000}
}
Omit input or output when that device is absent. Supported sample rates are
8000, 16000, 24000, 32000, 44100 and 48000 Hz, mono PCM16. The selected devices
must support the configured rate. Stop the service and run:
hermes-gadget linux audio-check --config device-config.json
Speak for three seconds. The check prints the captured peak level and plays the
clip back at half volume. It sends nothing to Hermes and saves no recording.
If the check rejects 16000 Hz, try 48000 Hz or an ALSA device that supports
conversion. Check capture levels with alsamixer if the signal is silent.
The systemd service account needs access to /dev/snd, typically through the
audio group. Test as that account when deploying a service.
Restart the client, press TALK with hermes-gadget linux button talk press,
then release with hermes-gadget linux button talk release. Use physical
buttons for everyday voice interaction. status reports audio errors. A failed
microphone stops the recording; the client does not substitute silent input.
Reattach an unplugged audio device and start a new recording to retry it.
Add Raspberry Pi buttons and outputs
Use GPIO Zero with the lgpio backend on Pi 4 and Pi 5. On Raspberry Pi OS, install
python3-lgpio and create the virtual environment with --system-site-packages
so it can import that system package, then install .[gpio].
The Pi installer's environment already includes GPIO Zero and can import
system packages. Install python3-lgpio, then restart the service. The installer
adds the service account to existing audio and gpio groups.
Add this object to the configuration:
"gpio": {
"chip": 0,
"talk": 17,
"cancel": 27,
"status_led": 22,
"outputs": {"desk_light": 23}
}
The numbers are BCM GPIO numbers, not physical header positions. Each pin must be unique. Wire each momentary button between its GPIO and ground; the client enables pull-ups and debounces presses. Connect LEDs through a suitable series resistor. Use a driver circuit for loads a GPIO cannot supply. Only use 3.3 V-compatible logic on the header.
The status LED stays on when paired and connected, blinks slowly while offline
or awaiting pairing, and blinks quickly during recording. gpio.desk_light
becomes a device action with one boolean parameter, on. Only configured
outputs are exposed. Outputs start off and return off on a clean shutdown.
Do not use this software as a safety controller; a power failure cannot promise
a controlled output transition.
The service account needs access to /dev/gpiochip0, normally through the
gpio group. Older Pi 5 kernels may expose the header on gpiochip4; set chip
to 4 in that case. Verify the header controller with gpioinfo before wiring.
See GPIO Zero's pin documentation
for the lgpio backend and permissions.
Add a screen
Add display to the configuration to show the firmware's device screen:
"display": {
"width": 320,
"height": 240,
"fullscreen": true,
"rotation": 0,
"touch": true
}
The dimensions describe the device canvas. The client scales that canvas to
fit the monitor and leaves black borders where needed. Both dimensions must
be even numbers between 160 and 800. Set round to true for a circular
canvas with equal width and height. Rotation accepts 0, 90, 180 or 270 degrees
counterclockwise. Use the same orientation as your touch input.
On a Linux desktop, install .[display], set fullscreen to false for a
window, and start the client from your desktop terminal. Space is TALK, Escape
is Cancel, and the arrow keys scroll. Touch or hold the screen to interact;
swipe down to cancel. Closing the window stops the client. Omitting display
keeps the service headless.
On Raspberry Pi OS Lite, the optional screen needs an SDL build with KMS/DRM support. Prefer the OS package so it uses the system graphics drivers:
sudo apt install python3-pygame
Use a virtual environment created with --system-site-packages. From the
local console, set SDL_VIDEODRIVER=kmsdrm before starting the client. If SDL
reports that the driver is unavailable, use a desktop session or install a
system SDL/pygame build with KMS/DRM support. A pip wheel's available video
drivers can differ from the OS package.
Direct display access requires the graphics device and an active local seat. Test from the Pi's local console before configuring unattended display startup. The supplied system service runs headlessly by default; an SSH session or a background service does not automatically get permission to own the display. Do not run the client as root to bypass a display error. For a desktop kiosk, start the client in the logged-in user's graphical session with its own state directory. Never run it alongside the system service with the same identity.
The screen uses the existing firmware renderer for pairing, conversations, cards, prompts and images. Audio and GPIO still use the configured real devices. Software rendering and touch mapping pass automated tests; monitor, touch-controller and direct-console behavior still need physical verification.
State and recovery
To expose an existing lamp and temperature sensor through this Linux client, use the Home Assistant and MQTT examples. They register named actions before pairing and perform network work outside the device loop.
The default state directory is $XDG_STATE_HOME/hermes-gadget, or
~/.local/state/hermes-gadget. To use another directory, put
--state-dir /path/to/state immediately after linux in every command.
The directory is private to its owner. It contains device.json, a process
lock, and control.sock. The socket accepts one newline-terminated JSON request
per connection. For example, {"command":"status"} returns the same JSON as
the CLI. Only local processes with permission to access the directory can use
it. The control API cannot execute shell commands or reset the device identity.
Back up device.json securely. It contains the device key and optional access
token. A damaged state file stops startup rather than replacing your identity.
Restore your backup, or stop the service and move the state directory aside to
enroll a new device. Configuration changes take effect on the next start.
Ctrl+C or SIGTERM closes the connection and exits. Only one service can use a
state directory at a time. status reports connection state and uptime even
when Hermes is unavailable. Pairing failures appear in messages.
For systemd deployments, linux/hermes-gadget.service
defines a dedicated hermes-gadget user, a private /var/lib/hermes-gadget state
directory, and restart on failure. It expects an installation and virtual
environment at /opt/hermes-gadget/current, plus a configuration file at
/etc/hermes-gadget/config.json. The package installer creates these paths.
The client only advertises configured display, audio and output actions. It reports no battery or ESP32 update slot.
Update or roll back
For a package installation, download and verify the new archive, extract it,
then run its install.sh with sudo. The installer validates the new native
library before stopping the current service. It switches the current link and
restarts a previously running service. Reinstalling the same package is safe.
The installer preserves /etc/hermes-gadget/config.json and
/var/lib/hermes-gadget/device.json, including the pairing identity.
Back up those two files securely before updating. Installed releases remain in
/opt/hermes-gadget/releases; the previous link identifies the prior release.
To roll back, rerun the installer from the previous release's archive. Check
sudo hermes-gadget-device status after either operation. An interrupted
dependency installation leaves a named incomplete directory; move that specific
directory aside before retrying, as the installer instructs.
For a source checkout, stop the process, update the checkout, rebuild the native library, and restart with the same state directory.
To stop using the installed service, run sudo systemctl disable --now hermes-gadget. Remove its unit from /etc/systemd/system/hermes-gadget.service
and its helper from /usr/local/bin/hermes-gadget-device, then run
sudo systemctl daemon-reload. Application files are under
/opt/hermes-gadget. Retain configuration and state if you may reinstall later.