Tanuki schreef op zaterdag 10 mei 2025 @ 08:34:
Zijn mensen hier nog mee bezig geweest? Zijn er evt nieuwe ontwikkelingen, bijvoorbeeld community projecten met esp's / printplaatjes / hacking? Of nieuwere modules / type motoren die zich wel goed laten integreren?
Anderhalf jaar geleden kocht ik een huis waarin al twee Somfy-zonweringen waren geïnstalleerd. Aangezien ik veel werk met Home Assistant, wilde ik deze zonweringen uiteraard ook kunnen bedienen via mijn domoticasysteem.
Na wat onderzoek bleek dat er ruwweg twee opties waren:
- Volledig nieuwe rolluiken aanschaffen die compatibel zijn met Home Assistant
- Zelf iets bouwen dat het bestaande Somfy-systeem kon bedienen
Aangezien ik graag zelf dingen ontwikkel, koos ik voor de tweede optie: zelf knutselen

.
Hoe werkt het?
Ik ben aan de slag gegaan met een
ESP32-board, een kleine microcontroller met Wi-Fi, die ik programmeerde met
Arduino IDE. De opzet is als volgt:
- Ik heb de originele afstandsbediening van Somfy elektrisch gekoppeld aan het ESP32-bord
- Met een paar transistors simuleert de ESP32 een druk op de knoppen (Up, Down, Stop)
- Via Wi-Fi maakt het bord verbinding met mijn netwerk
- Een ingebouwde webserver op het ESP32-bord luistert naar HTTP-verzoeken zoals http://192.168.1.213/down of /up
- Zo kan Home Assistant eenvoudig commando’s sturen via een shell_command
Voorbeeld: zo werkt de scene-logica
Soms wil je niet alleen "open" of "dicht", maar bijvoorbeeld halverwege stoppen. Dat kan niet met de originele afstandsbediening, maar wel met wat timing-logica.
Ik heb daarom een
/scene-endpoint gemaakt. Dit accepteert een JSON-array met stappen, zoals:
JSON:
1
2
3
4
| [
{"action": "down", "time": 18000},
{"action": "up", "time": 7300}
] |
Bovenstaande sequentie laat het rolluik eerst 18 seconden naar beneden gaan, en daarna 7,3 seconden omhoog, wat neerkomt op ongeveer "halverwege dicht" in mijn geval. Dit alles loopt op de ESP32 zelf, zonder tussenkomst van Home Assistant tijdens de uitvoering.
Integratie in Home Assistant
In
configuration.yaml heb ik de volgende shell_command-definities toegevoegd:
YAML:
1
2
3
4
5
6
7
8
9
10
| shell_command:
roller_shutter_studeerkamer_down: "curl http://192.168.1.213/down"
roller_shutter_studeerkamer_up: "curl http://192.168.1.213/up"
roller_shutter_studeerkamer_stop: "curl http://192.168.1.213/stop"
roller_shutter_studeerkamer_halfway_down: >-
curl -X POST http://192.168.1.213/scene -H "Content-Type: application/json" -d '[{"action": "down", "time": 18000},{"action": "up", "time": 7300}]'
roller_shutter_studeerkamer_almost_down: >-
curl -X POST http://192.168.1.213/scene -H "Content-Type: application/json" -d '[{"action": "down", "time": 18000},{"action": "up", "time": 3300}]'
roller_shutter_studeerkamer_sun_block: >-
curl -X POST http://192.168.1.213/scene -H "Content-Type: application/json" -d '[{"action": "down", "time": 18000},{"action": "up", "time": 2250}]' |
Met deze commando’s kan ik scenes aanroepen zoals "zon blokkeren" of "bijna helemaal dicht".
In Home Assistant heb ik een dashboard gemaakt (1 van de vele

) waarbij ik rechtsboven de optie heb op een popup te triggeren met alle rolluikopties:
De rolluikopties met de scene's:
Hardware
Op het bord zit:
- Een ESP32 Wroom-bord met Wi-Fi
- Een handvol transistors om de knoppen op de afstandsbediening aan te sturen
- Een breadboard om alles tijdelijk op aan te sluiten (later wordt dit gesoldeerd)
Code
Ik heb een lokale git repo draaien, maar hierbij de laatste versie van de code:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
| #include <WiFi.h>
#include <WebServer.h>
#include <WiFiManager.h>
#include <ArduinoJson.h>
constexpr int pinDown = 18;
constexpr int pinStop = 19;
constexpr int pinUp = 21;
WebServer server(80);
bool isSceneRunning = false;
volatile bool stopRequested = false;
// Scene state
StaticJsonDocument<1024> sceneDoc;
JsonArray sceneSteps;
size_t currentStepIndex = 0;
unsigned long stepStartTime = 0;
bool stepInProgress = false;
int getPinFromAction(const String& action) {
if (action == "down") return pinDown;
if (action == "up") return pinUp;
if (action == "stop") return pinStop;
return -1;
}
void simulateButtonPress(int pin) {
digitalWrite(pin, HIGH);
delay(300);
digitalWrite(pin, LOW);
}
void pressButton(int pin, const char* message) {
simulateButtonPress(pin);
server.send(200, "text/plain", message);
Serial.println(message);
}
void handleDown() {
pressButton(pinDown, "'Blinds down' sequence executed");
}
void handleUp() {
pressButton(pinUp, "'Blinds up' sequence executed");
}
void handleStop() {
Serial.println("Stop button activated");
if (isSceneRunning) {
stopRequested = true;
server.send(200, "text/plain", "Stop request sent to running scene.");
} else {
server.send(200, "text/plain", "'Blinds stop' sequence executed.");
}
simulateButtonPress(pinStop);
}
void handleSceneRequest() {
if (isSceneRunning) {
server.send(403, "text/plain", "Another scene is already running.");
return;
}
if (!server.hasArg("plain")) {
server.send(400, "text/plain", "Missing JSON payload.");
return;
}
DeserializationError error = deserializeJson(sceneDoc, server.arg("plain"));
if (error) {
server.send(400, "text/plain", "Invalid JSON payload.");
return;
}
sceneSteps = sceneDoc.as<JsonArray>();
currentStepIndex = 0;
isSceneRunning = true;
stopRequested = false;
stepInProgress = false;
server.send(200, "text/plain", "Scene started.");
Serial.println("Scene execution started.");
}
void connectToWifi() {
WiFiManager wifiManager;
wifiManager.autoConnect("Roller-Blinds-Setup");
if (WiFi.status() == WL_CONNECTED) {
Serial.println("WiFi connected.");
} else {
Serial.println("WiFi connection failed...");
}
}
void setup() {
Serial.begin(115200);
pinMode(pinDown, OUTPUT);
pinMode(pinUp, OUTPUT);
pinMode(pinStop, OUTPUT);
connectToWifi();
server.on("/down", handleDown);
server.on("/up", handleUp);
server.on("/stop", handleStop);
server.on("/scene", HTTP_POST, handleSceneRequest);
server.begin();
Serial.println("Server started.");
}
void loop() {
server.handleClient();
// Wi-Fi reconnect logic
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi lost. Reconnecting...");
WiFi.begin();
unsigned long startAttemptTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) {
delay(500);
Serial.print(".");
}
Serial.println(WiFi.status() == WL_CONNECTED ? "\nWiFi reconnected." : "\nReconnect failed.");
}
// Scene execution (non-blocking)
if (isSceneRunning && !stopRequested) {
if (!stepInProgress && currentStepIndex < sceneSteps.size()) {
JsonObject step = sceneSteps[currentStepIndex];
String action = step["action"] | "";
unsigned long duration = step["time"] | 0;
int pin = getPinFromAction(action);
if (pin == -1 || action.isEmpty() || duration == 0) {
Serial.printf("Invalid step %u: action='%s', time=%lu\n", currentStepIndex, action.c_str(), duration);
currentStepIndex++;
return;
}
Serial.printf("Starting step %u: action='%s', time=%lu\n", currentStepIndex, action.c_str(), duration);
simulateButtonPress(pin);
stepStartTime = millis();
stepInProgress = true;
}
if (stepInProgress) {
JsonObject step = sceneSteps[currentStepIndex];
unsigned long duration = step["time"] | 0;
if (millis() - stepStartTime >= duration) {
Serial.printf("Completed step %u\n", currentStepIndex);
currentStepIndex++;
stepInProgress = false;
}
}
}
if (stopRequested && isSceneRunning) {
Serial.println("Stop requested during scene. Stopping now.");
simulateButtonPress(pinStop);
isSceneRunning = false;
stepInProgress = false;
currentStepIndex = 0;
}
if (isSceneRunning && currentStepIndex >= sceneSteps.size()) {
Serial.println("Scene complete.");
simulateButtonPress(pinStop);
isSceneRunning = false;
stepInProgress = false;
}
} |
Resultaat
De zonwering is nu mooi geïntegreerd in Home Assistant, volledig lokaal, zonder cloud- of vendor lock-in. En dat voor een fractie van de kosten van nieuwe motoren of een proprietary bridge.
Er is wel een nadeel en dat is dat ik niet de huidige stand van een rolluik weet. Dit is ook de reden waarom dat de rolluik eerst in zijn geheel naar beneden gaat om vervolgens weer deels op te gaan.
Toekomst
In de toekomst ga ik de batterij ook nog vervangen door een
step down converter die ik inmiddels al heb. Dit zorgt ervoor dat ik de USB stroom die op 5v draait kan omzetten naar 3.3v die de batterij nu levert.
Daarnaast wil ik een cursus 3D printen volgen om een nette behuizing te maken voor het gehele pakket. Hoewel ik het doosje nu prima kan verbergen, voelt het toch een beetje als niet af op deze manier

.