The smart bulb / smart switch dilemma: smartening up a dumb wall switch

by snm, December 9th, 2017

How to make a “dumb” wall switch into a smart switch. We all have switches on our walls, such as these:

Wall switch

which controls power to an AC wall outlet.

As a new user of home automation, I plugged a lamp with a smart light bulb into this switched outlet. This creates a problem: when the dumb light switch is off, you can’t turn on the smart bulb. Obviously, because the switch cuts the power. But it is annoying because I would have to whip out my smartphone whenever I want to turn on/off the lights! Despite the ability to change bulb color etc. remotely e.g. via smart speakers or smartphones, installing this smart bulb was a step backwards.

This isn’t an unknown problem, and there are possible solutions on the market. Smart light switches? But they are $25-45 and are installed into the wall, replacing the dumb switch. Very invasive, not to mention costly. My landlord probably won’t appreciate ripping out her switches. I wanted to use my existing wall switch, efficiently. Hence this blog post. I’ll be using an ESP8266 WeMos D1 mini purchased from Aliexpress for a couple bucks (varies about $2-5 + shipping), with two spare USB chargers and a few small inexpensive miscellaneous electronic components. Here’s the behavior before, which sucks:

State Desired Result Action Needed
Lamp on Turn off Flip switch or use app
Lamp off Turn on If the switch was on: flip it twice (off then on) or use the app
If the switch was off: flip the switch, you can’t use the app

Behavior after implementing the “smartened up” device for the dumb switch, it all just works:

State Desired Result Action Needed
Lamp on Turn off Flip switch or use app
Lamp off Turn on Flip switch or use app

This I believe achieves the best of both worlds. If you only use a smart bulb with a dumb switch, then the problems above arise. If you only use a smart switch, then you lose the ability to control the color of the bulb, and have to change out or alter your wall switches. By using a smart bulb and dumb light switch plus this device we’ll build soon, both these problems are solved.

Design overview

In my case, I have a dual-outlet face plate, the top is switched, the second is always-on:


We’ll wire the switched socket to GPIO on a microcontroller powered by the always-on socket, in order to sense when the dumb switch is on or off, allowing it to smoothly integrate with the rest of the home automation network.

USB +5V power to GPIO digital inputs

Connect a USB charger to the wall socket, break out the +5V power from the USB cable, then connect to an ESP8266 GPIO input. The ESP8266 is a 3.3V device, but the input pins are 5 volt tolerant so no level shifter is needed. I took a WeMos D1 Mini ESP8266, cannabilized an old USB cable, split out the +5 V power supply line (red wire) to the D4 GPIO input, and ground (black wire and shield) to ground, via a header plugged into the WeMos:

USB power as GPIO input, take 1

Wrote an Arduino sketch to loop and digitalRead(D4), logging any changes, as in the Arduino StateChangeDetection example. First I tested with the ESP8266 powered by my computer plugged into USB, and the D4/GND cable plugged into a USB wall charger not plugged into the wall. When the charger is off, yet +5V from its USB cable is wired to D4, we consistently read a low, and when disconnected, a high. The built-in LED also illuminates respectively. So far so good.

However I ran into trouble powering the ESP8266 itself from another USB charger plugged into the wall. The built-in LED would light up dimly:


and if I plug it into an AC outlet, the ESP8266 crashes. Fortunately it doesn’t kill it, and I can recover by cycling power, but something is clearly wrong. Measuring the voltage from the USB charger output shows 5.1 volts with no load, maybe this is too much for the 5-volt tolerant ESP8266 GPIO inputs? Maybe the USB charger expects a load, or the GPIO inputs need pull-down resistors to not float open? If anyone has more insight into why I can’t simply connect +5V USB power to GPIO inputs I’d be interested but until then, time to try a different approach.

Voltage divider to analog input

The ESP8266 has one analog ADC input pin, A0. Read it in Arduino using analogRead(A0). But when I read it in a tight loop, the Wi-Fi became unresponsive, see: ESP8266 Analog read interferes with wifi?. Read it only every 50 ms:

static int analog_value = 0;

void loop() {

  if (millis() % 50 == 0) {
    analog_value = analogRead(A0);

but don’t connect it just yet. The analog pin only accepts 1 V max, so the 5 V has to be divided using a voltage divider. With 10 kΩ and 47 kΩ, 5 volts will read as 5 * 10 / (10 + 47) = 0.877 volts, providing some margin of error if it exceeds this voltage, up to 5.7 V for 1.0 V. Current is I=V/R, so 5V/(10kΩ) = 0.5 mA, and 5V/(47kΩ) = 0.11 mA, with power dissipation of P=IV, (0.5 mA)(5 V) = 0.53 mW. The common 1/4 W or 1/8 W resistors or even smaller will be more than enough. Wire it up:

Voltage divider with 10k and 47k resistors

Reads 0.888 V with the USB charger plugged into the wall socket with the light switch on:

5V USB divided to <1V

Good, it is safe to connect to the ESP8266 analog input:

Connected to A0

Moving from a breadboard to protoboard, solder it up:

ESP8266 + vdiv to A0 on protoboard

Reverse side

Back to the software. This voltage is 264 or 263 units from analogRead. When flipped off, the voltage quickly drops to 3 or 2 units (the units are 1024th of a volt). If the USB cable is unplugged, then it instantly drops, but if the charger is unplugged, then it drops slower, due to residual charge. A bigger load may make it drop faster, but we can detect the change in software. If it drops below a threshold, consider it off:

  int analog_value = analogRead(A0);
  input = analog_value > 260;

Detecting when the light switch turns on is visually instanteous, but even using a closer threshold, detecting when it turns off is noticeably slower. Would like it to be instant. RE: WHAT IS SAMPLE RATE OF ANALOGREAD()? #16837 says it is 200 Hz. With the 1/(50 ms) delay we were reading only 20 Hz. Tenfold increase, sampling at the maximum rate:

  if (millis() % 5 != 0) {

With this change, the web server still works. And yet, off detection remains perceptably slow. Not good enough.

Not a complete loss, I kept this circuit, but the voltage is shown (converted to for display: /1024*(47.0+10)/10) only for informational purposes, on the ESP8266 web server interface. However for triggering will be done digitally instead:

Optocoupler to digital input

The slow sample rate of the ESP8266’s analog-to-digital converter is unbearable, so how about going back to digital? We may be able to get the resistive voltage divider working for logic level input, or use a fancier MOSFET-based level shifter, but for better isolation (avoiding tying together both grounds), one can use an optocoupler. I had one lying around I wanted to use anyways, the Fairchild FOD817 4-pin DIP phototranistor optocoupler:

Optocoupler FOD817 on breadboard

Optocoupler FOD817 schematic

Per the data sheet, the absolute maximum continuous forward current is 50 mA, so like with any LED, we need a current-limiting resistor. R=V/I, (5 volts) / (50 mA) = 100 Ω. To allow for tolerances and overvoltages, a higher resistance of 220 Ω gives 22.7 mA, not far from typical LED current. On the other side, 10 kΩ resistor from the collector pulls up to 3.3V. An instructive diagram from WestFlorida components (pardon the blurriness and low resolution):

Optocoupler resistor usage)

Construct the circuit on a breadboard:

Breadboarded optocoupler in circuit

Now when the light switch is turned on, the optocoupler emitter activates and the detector turns on, pulling the GPIO input (using D2 on the ESP8266) low. When the light switch is off, it is pulled high (3.3V). Since it works, solder it up permanently:

Soldered optocoupler

Reverse side of soldered optocoupler complete board

Software (Arduino sketch for the ESP8266):

Home automation

Now that the hardware and software is complete, time to integrate it with the rest of your home automation system. YMMV, but I decided to use Homebridge, due to its plethora of plugins.

homebridge-udp-multiswitch looks useful, but it does the opposite of what we want: sending UDP packets, not receiving them. However with some changes, we can make it into a UDP server, here:

Homebridge plugin UDP server:

Configure in ~/.homebridge/config.json:

                "accessory": "UdpServerMultiswitch",
                "name": "Wallswitch",
                "port": 8261,
                "on_payload": "ff",
                "off_payload": "00",
                "toggle_payload": "80"

The switch reacts when driven by the physical wall switch via the ESP8266 device, but can also be switched in the app. Next up: automation, tying this switch to a lamp. I used a lamp with a smart bulb, and configured it to turn on with the wall switch turns on, turn off when it turns off. The smartified wall switch controls the smart lamp almost the same as before:

All that work for the same results? No actually this is way better. We have eliminated the fundamental flaw of losing control when the wall switch turns off the smart lamp. Now this works:

  1. Flip the light switch off
  2. Turn the lamp on using your smartphone (or other home automation tool)

The lamp turns on!


Since this a smart switch, we can do better.

You ever used a lamp controlled by two wall switches? Flip one of the switches, it toggles the lamp, flip the other, it also toggles the lamp. How does this work? If the two switches were wired in series, then both switches would have to be on (AND), if in parallel, then either switch (OR) but not both. Physics 230 has the answer: these wall switches are “3-way switches”:

3-way switches

technically a SPDT instead of SPST, implementing a logical XOR. To expand beyond two, there are also 4-way switches:

4-way switches

These types of switches are very useful because they allow controlling the lamp from two places, and whenever you flip a switch something happens. They don’t have the problem where if you turn off a switch when the lamp is already off, or vice versa, nothing happens. Sound familiar?

The current smart switch implementation has this problem, but it is easily fixed in homebridge-udpserver-multiswitch by adding a toggling UDP payload, effectively handled like this:

var previousValue = services[i].getCharacteristic(Characteristic.On).value


Now whether you turn the physical light switch “on” or “off”, it will always toggle the lamp. There no longer strictly an or or off physical state, because the switch can also be controlled programmatically. Much better.


The lamp can now be equally controlled programmatically by the app and by the physical light switch, as we expect, no compromise:

State Desired Result Actions Supported
Lamp on Turn off Flip switch or use app
Lamp off Turn on Flip switch or use app