This is an implementation of a bridge between shutter controls and home assistant.
In particular I have connected one Velux KLF 150 gateway and one Schellenberg shutter remote to my Raspberry Pi using the GPIO pins.
But you can use this for Velux only, of course. You will need 3 GPIO pins for each Velux shutter and you can use up to 5 shutters with the KLF 150 in this setup.
No soldering required if you stick with Velux only - everything is just screws.
- Supports Velux KLF 150 or any 3-button remote control
- MQTT interface with auto-discovery for home assistant
- Positioning of the Velux shutters (time-based)
flowchart LR
HA[Home Assistant/MQTT] <--> Bridge[THIS LIBRARY]
Bridge <--> GPIO[Raspberry Pi GPIOs]
GPIO --> Relays[Relay Modules]
Relays --> KLF[Velux KLF 150]
Relays --> Remote[Schellenberg Remote]
KLF <--> Velux[Velux Shutters]
KLF --> GPIO
Remote --> Balcony[Balcony Shutter]
style Bridge fill:#ff9900,stroke:#333,stroke-width:2px,color:#fff
This is how everything looks before "unboxing" it into a more permanent shape:
Just to give you an insight, you can pobably use this project with any other hardware. It could make sense to buy a complete ESP32 GPIO board instead of using a Raspberry Pi.
- 1 Schellenberg shutter motor in my balcony door
- 1 Schellenberg remote control
- Which I opened up and soldered contacts to all three buttons (up/stop/down) as well as the power supply.
- This way I can supply power directly via the 3V output pin of the raspberry pi without needing batteries.
- I'm trusting you with this definitely professional piece of art.
- 3 Velux covers/shutters on my roof windows
- Velux KLF 150 gateway
- See https://www.velux.com/klf150
- Before buying this, I also tried soldering contacts to the Velux remotes (model 3UR B01 WW), but I broke the first...
- 9 relay modules
- If you use a Raspberry PI, you need relays that work with 3V and it helps if they already have a pull-up resistor because the GPIO pins can have a floating voltage when the PI boots and they are not yet assigned as outputs, triggering the relay when it shouldn't.
- I bought this 10 pack on amazon
- You will need 2 relays per Velux shutter, so for my three windows I needed 6 relays.
- Additionally I needed 3 relays for my Schellenberg remote control.
- If you find a board with multiple 3V relays on it, go ahead. I just screwed mine together on a wooden board.
- Raspberry Pi Model B
- Yes, the first version of the Raspberry Pi with the single armv6 CPU!
- Still had this lying around, so why waste it.
- I got node 21 running on it for this project.
- One USB power supply for the Raspberry Pi
- I used one of my many 2A supplies, not the 1.4A one that comes with the Velux KLF 150.
- The Pi then powers the relays, the KLF 150 and the Schellenberg remote.
- Connect all VCC pins of the relays to a 3V pin of the Raspberry Pi.
- I have daisy chained them together. Only the most left relay is connected to the Pi.
- Connect all GND pins of the relays to a GND pin of the Raspberry Pi.
- Again daisy chained.
- Connect each IN pin of the relays to one GPIO pin of the Raspberry Pi.
- If you use all 10 relays, for all 5 shutters, I'm suggesting pins 2, 3, 4, 17, 27, 22, 10, 9, 11, 8.
This is for the default configuration as described in the Velux KLF 150 manual. Meaning: You have 2 output pins and 4 input pins per shutter (A, B, C, D, E). For each shutter, the first input pin makes the shutter open, the second input pin makes it close, and both together make the shutter stop. The output pins for each shutter should close on success (also the default behavior).
- Connect all 5 bottom pins of the output (A-E) together and to GND.
- Connect all 5 top pins of the output (A-E) to any GPIO pin.
- I'm suggesting pins 14, 15, 18, 23, 24.
- The bottom line of all input pins are for GND. However, since the cabling comes in pairs anyway and you need to connect them each to one of the relays, I just used all the existing cables.
- So connect all 10 inputs to the 10 relays, the top one to NO (normally open) and the bottom one to COM (common).
- You will need to have
nodeinstalled.- I have to use node 21, but it should work with newer versions as well.
- Make sure you have either
raspi-gpioorpinctrl(newer) available.- Check with
raspi-gpio getorpinctrl get. - Either one is used to set the GPIO input pins to the "pull up" mode. This is not handled in the
onoff-library I'm using. pinctrlis newer, but requires permissions.- So you either need to be root, or make sure you have access to all
/dev/gpio*devices. - E.g. use the
gpiogroup:- Check
ll /dev/gpio*and see that every device is owned by thegpiogroup and hasg+rwpermissions. - If not, use
sudo chgrp gpio /dev/gpio*and/orsudo chmod g+rw /dev/gpio*. - You can add yourself to the
gpiogroup withsudo usermod -a -G gpio $USER. - Verify with
pinctrl getthat you can use the tool without further permissions.
- Check
- So you either need to be root, or make sure you have access to all
- Check with
- Go into a folder where you wish to install this project (e.g.
~/shutter). - Install the library with
npm install @uncaught/gpio-shutter-bridge - Create a javascript file (e.g.
run.mjs), require my library and call it with your shutter-pin-layout:
import {createVeluxShutters, initRuntime, initMqtt} from '@uncaught/gpio-shutter-bridge';
const {onDispose} = initRuntime();
initMqtt(createVeluxShutters([
{ident: 'Velux_A', up: 2, down: 3, input: 14},
{ident: 'Velux_B', up: 4, down: 17, input: 15},
{ident: 'Velux_C', up: 27, down: 22, input: 18},
{ident: 'Velux_D', up: 10, down: 9, input: 23},
{ident: 'Velux_E', up: 11, down: 8, input: 24}, //same row!
], onDispose), onDispose, {url: 'mqtt://your-mqtt-or-home-assistant'});- The ident should match
/[a-zA-Z][a-zA-Z0-9_-]*/. - See my personal example for a few more details.
Auto start with screen and crontab
I'm using screen to keep the process running and to check in on its output.
I created an additional start.sh file:
#!/usr/bin/env bash
set -e
selfDir=$(dirname $(readlink -f $0))
cd $selfDir
screen -dmS shutter node run.mjsThen added @reboot /home/nc/shutter/start.sh to my crontab -e.
If you are unfamiliar with screen, you can detach from a session with ctrl+a+d and reattach with screen -r shutter.
I tried to dockerize this project, but I was not able to get it to work with only specific mapped devices. The pinctrl kept saying "No GPIO chips found". I've only managed to get it working with the --privileged flag, which for me, kind of defeats the purpose of running this inside docker.
If you find a way to get it working, please let me know, I'll add it as an example.
My attempts
I tried based off of this:
FROM node:25.0.0-trixie-slim
RUN apt update && apt install -y git build-essential cmake \
&& git clone https://github.com/raspberrypi/utils.git \
&& cd utils/pinctrl \
&& cmake . \
&& make \
&& make install
ENTRYPOINT ["bash"]Building with docker build -t gpio . and running with docker run --rm -it --device /dev/gpiochip0 --device /dev/gpiochip1 --device /dev/gpiochip2 --device /dev/gpiomem --cap-add SYS_RAWIO gpio


