Files and instructions for my "smarthome" setup based on a pile of bash and lua scripts glued together with ddb and runit.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Sebastian 8cd5548458 Added some proper timing calculations to 1 month ago
ddb_scripts Added some proper timing calculations to 1 month ago
gemini Replaced lord with runit and updated the scripts to use the new ddb version 5 months ago
old_stuff Replaced lord with runit and updated the scripts to use the new ddb version 5 months ago
sv Replaced lord with runit and updated the scripts to use the new ddb version 5 months ago
systemd-services Replaced lord with runit and updated the scripts to use the new ddb version 5 months ago
LICENSE Made it a git repo! 9 months ago README Updated links to point to codeberg instead of the old gitlab repos 2 months ago

Void setup

This repository contains the files needed to set up my "smarthome system", it will most probably not work for you, but you are encouraged to build your own and reuse some scripts and ideas from here!

The name void comes from the name of the machine this runs on and has (unfortunately) nothing to do with voidlinux (it runs raspian).


The purpose of this collection of scripts is to have some inputs (from a gemini API) and pinging other machines and driving some gpio pins on a raspberry-pi (or any other single-board-computers) that are (in my case) attached to relays that switch audio signals and mains outlets.

You can find the following modules in here

  • a ping monitor for watching another machines online status
  • a scripting helper that makes it relatively easy to do some logic with the inputs
  • scripts for generating CGI scripts for a gemini server that can be used for io
  • a script that can be used to translate events to shell commands (that's how the gpios are driven)
  • startup scripts/service files for all modules

Those modules are glued together using an object-key-value-store which lives in a separate repository and runit to start the show and keep it running.


You need the following packets installed:

  • git
  • lua (a recent version, I use 5.4 on my raspi, 5.3 is known to work too)
  • valac
  • meson
  • runit
  • ddb
  • a gemini server that supports CGI scripts (I'm using solderpunks molly brown)

Until I have an installer script Here are the steps for installing by hand.

This was intended to be a setup guide only but it also contains some information on the inner workings, and some rambling.

If you have questions feel free to send me an E-Mail or contact me on the fediverse (choose the visibility level you're comfortable with).

Possible result

My final directory tree after the installation looks something like this:

├── gemini (a copy of the gemini folder in this repo)
│   ├──
│   ├── molly.conf
│   ├── cert.*
│   ├── […]
│   └── srv
│       └── obj
│           └── [generated objects …]
├── software
│   ├── ddb (git clone
│   └── void-setup (this repo)
│       ├── ddb_scripts
│       │   ├── ddb_logic.lua
│       │   ├── keep_timer.lua
│       │   ├──
│       │   ├── void_gpio_commands.lua
│       │   └── void_script.lua
│       ├── gemini
│       ├── sv
│       └── systemd-services
│           ├── molly.service
│           └── pi-runit.service
└── .config
    ├── service
    │   ├── ddb -> /home/pi/.config/sv/ddb
    │   ├── ddb-gpio-commands -> /home/pi/.config/sv/ddb-gpio-commands
    │   ├── ddb-logic -> /home/pi/.config/sv/ddb-logic
    │   ├── ddb-pingmon-amber -> /home/pi/.config/sv/ddb-pingmon-amber
    │   └── ddb-pingmon-silver -> /home/pi/.config/sv/ddb-pingmon-silver
    └── sv
        ├── ddb
          ├── init.ddb
          └── run
        ├── ddb-gpio-commands
     └── run
        ├── ddb-logic
     └── run
        ├── ddb-pingmon-amber
     └── run
        ├── ddb-pingmon-silver
     └── run
        └── molly
            └── run

Installing runit

Runit is an easy to use and understand init-system and service manager called runsv, for this setup we will just use the service manager part (although it would be awesome to see this on a machine running runit as the init-system). You can find documentation on runit on the official website, the voidlinux handbook and the archwiki, alternatively look at the manpages titled runsvdir, runsv and sv. I recommend you to take a look at one of them and get a basic idea of how runit works.

To install it on a raspberry-pi one can simply sudo apt install runit which will automatically install a runsvdir daemon that gets started by systemd and uses /etc/service as its service directory, which we wont use.

What we want is a runsvdir process that runs as the pi user and uses ~/.config/service as its service directory since runit is pretty simple we can just mkdir ~/.config/service as the pi user, install the pi-runit.service to a location like /etc/systemd/system/pi-runit.service and start the service.

To enable a service with runit symlink the service directory (the one in sv that contains the run script) to ~/.config/service/.

Installing ddb

DDB is an object key value store that is very easy to write scripts for which was created for scripting my own desktop (that's why it is built with glib). But it is useful for all kinds of scenarios where multiple scripts have to interact with each other.

You can install it using git clone and then running the install script (which will automatically call the build-system to build the ddb binary). The binaries will end up in your ~/.local/bin folder. (You looked at the install script before running it, didn't you?)

You should also export some environment variables in your ~/.profile:

export DDB_PORT=60707
export DDB_GREETING='<insert a random character string here>'

The port is the tcp port ddb listens on (it will only accept connections coming from localhost) and the greeting is a kind of password, you can use uuidgen or your favorite password-generator to generate a random token to use as the greeting.

These environment variables will be used by most scripts (the ~/.profile file is sources manually in almost all cases to make sure these are loaded)

Preparing the ~/.profile

To make sure the services work you should also edit your .profile script to contain the following lines before local paths are added (They are needed to find the ddb binary).

All scripts executed by runit or as CGI in this repository will source the ~/.profile file to get the necessary credentials for ddb and to get the HOME and PATH set.

# Make sure the path is set
[ -z "$PATH" ] && export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games"
# Make sure $HOME points to the correct folder
export HOME=~

Installing the ddb_scripts

Quick Note about the location of the script files: If you cloned this repository to ~/software/void-setup you won't have to change anything most ddb script files are already in the place they will be searched for. If you want to change the paths edit the service runscripts.

You probably want to customize these as they are where most of the magic happens.

Note: You probably want to read ddbs README first to learn how the protocol works as most scripts here speak the ddb protocol on their standard io

So what do we have?

This script pings a specified host and when wrapped in the ddb wrap program stores the result in ddb under the specified object name.

I use it to see whether my main machine is online to turn on the screens (I have physical switches to override the automatic controls in case you are wondering how I plan to troubleshoot my network) and its ping signal as a timer signal to check whether some timers have timed out or not. The service that are responsible for this are in my case called ddb-pingmon-amber and ddb-pingmon-silver, You probably want to change and rename them.

Note: Sometimes this script stops working without exiting for some reason, you have to restart it manually in that case.


This is a pretty simple script that creates and subscribes (note that ddb will happily subscribe you to objects that don't even exist yet if you tell it to) to some objects where we can write the state of some relays to and then looks for commands on those object and simply maps them to shell commands to set the raspberry's gpio pins. You will most probably want to change the pins and commands to work with your setup.

Note: if you rename this script you have to adapt the ddb-gpio-commands service file to match the new name.

Note: after you set this up you may continue with reading the First test section


This is a small utility that will probably get its own repository in the future that provides a frame withing which you can write logic code that act on fixed ddb objects. (Perfect for our usecase).

The API is not documented yet, but the void_script.lua file uses all features it provides.

It takes one argument, the path to the lua script that does the logic. Speaking of which:


This script contains the magic/logic that makes my setup tick the way it does. It is called by the ddb_logic.lua script and only has to worry about connecting inputs on outputs and doing the right trick for each signal.

The do_logic function is called every time some input value gets set from the ddb side.

To help you understanding my mess here is a list of features it implements:

  • Mapping from audio requests to audio switch (asw) outputs
  • Turning on the right amplifier depending on the amp_mode setting (The amplifiers were made by JVC and Teleton, that why I use J T for he modes, the JVC one waits a few seconds until it turns on the speakers, that's what the JS (Jumpstart) mode is for)
  • There is also some logic that turns the TV and receiver on if audio from the TV is requested and keeps both running for 30 minutes after muting because I'm not leaving the sound on during advertisement breaks.
  • It also does some logic to figure out if the TV should stay on after my main system goes offline or not. (It will turn the TV and receiver off when the audio is muted and I turn my PC off.)

Note that this script requires the keep_timer.lua script for the timeouts and that you should either cd into the directory the keep_timer.lua is stored in or put the keep_timer.lua in the LUA_PATH, I'm using the cd approach here.

This script is called from the ddb-logic service.


This lua library exports the constructor of the KeepTimer object which can be used to add some timers to your logic. They have an input value which turns them on, after the input value goes to false it waits KeepTimer.timeout seconds and then turns the KeepTimer.output off (false) the next time the tick() function is called.

It also has an off value which turns the output off as long as its set to true and also resets the timer. There is also a reset function which only resets the timer but does nothing when the timer is not active.

A first Test

Here is a link to ddbs README again, if you need a protocol reference.

Testing a single script

After setting up some services that hook into ddb you probably want to test them.

Since all scripts so far speak the ddb protocol over their standard io and require the ddb wrap wrapper to talk to ddb (See the service runscripts for examples) you can simply run them without the wrapper and the ddb interface doubles as a command line interactive debugger (Fancy words for it looks ugly, but its surprisingly easy to poke around in), you simply play the role of the ddb server and tell the program what happened in your imaginary testworld.

If you are sure your script works you can start it as a service and hook it into ddb.

Testing and debugging the running system

To peek and poke around the running system the ddb cat command comes on handy, it connects to the ddb server sends the greeting and then gives the connection to you.

This time you play the role of the client.

To see what the scripts are doing the subscribe command (+ <object_name>) will be your best friend, for the rest just look at the README linked above.

Installing the gemini server

This is out of scope here, read the setup guide of the server you are going to use. Make sure your server supports CGI scripts, otherwise your fancy setup will be pretty useless. In the systemd-service and the gemini directories you can find some example configuration for solderpunks molly brown gemini server which is written in go. As of early 2022 some gemini servers come prepacked and are ready to install, I recommend you to search you package repositories first.

Setting up the API

After setting up the server point it to the ~/gemini/srv/ directory. In the gemini directory you will find a script called which will create a lot of simple scripts that make up your API, the make_ddb_*.sh scripts will source (import) the ~/.profile into the generated scripts to work around the semisandboxed environment the CGI scripts are placed in, this is better than the previous approach which required rebuilding the scripts after changing the ddb credentials which you no longer have to do, just update your .profile and restart the affected services.

Id you don't use a raspberry you probably also want to edit the script which generates API endpoints that directly access the gpio pins (those will be useful when your scripting goes up in smoke or comes crashing down. Fun Fact: those endpoints are the starting point of the journey that led to here)

I'm pretty sure you can figure out how to use the helper functions in

The resulting endpoints will be placed in ~/gemini/srv/obj/

Setting up the dashboard

The dashboard is simply another CGI script, however it is not automatically generated so you have to edit it yourself.

The switches work by linking to the API endpoints, following the links triggers them. To access values stored in ddb the ddb read_value comes in handy look at my example dashboard to see whats possible.

Finishing up

Thank you for reading this, I hope the information above was useful for you. I you made something based on this I'd love to hear about it (I left my contact information in the questions section at the top). In case something breaks on the software side also feel free to contact me.