--- author: email: mail@petermolnar.net image: https://petermolnar.net/favicon.jpg name: Peter Molnar url: https://petermolnar.net/ copies: - http://web.archive.org/web/20210207142823/https://petermolnar.net/article/diy-sunrise-lamp/ lang: en published: '2021-02-07T12:30:00+00:00' summary: I made a smart wake up lamp, just to see if it is better, than a normal alarm clock. It is an insane difference, waking up is so much nicer, and subtle, than before, so I recommend doing one. tags: - DIY title: DIY smart wake up lamp with IKEA, Sonoff D1 Dimmer, and Domoticz --- ------------------------------------------------------------------------ **WARNING: this tutorial contains works on a unit that runs on mains electricity. Be extremely aware that mains electricity is dangerous, and could kill you, or anyone exposed to it. ** **Please also note that in certain countries, like Australia, you need to be a licensed electrician to deal with mains electricity, or you could be heavily fined.** **NEVER, EVER TAKE ANYTHING APART THAT IS STILL CONNECTED TO MAINS. ** **DO NOT CONNECT THE DIMMER TO THE MAIN ELECTRICITY UNTIL IT'S SAFE** **I am not liable for any damage or injury that could happen due to following this tutorial** ------------------------------------------------------------------------ ## Why not a smart bulb? When I started out with Domoticz I wanted to get into Z-Wave, because from a tech perspective, it's far superior to the rest - or so I thought. However, the only z-wave equipment I ended up with was our thermostat and boiler controller, and these two turned out to be a lousy choice: - the thermostat is actually a z-wave controller, and having two controllers on the same network is tricky - the thermostat is dumb: it doesn't even accept changing the desired temperature remotely - the boiler controller is hard wired, yet the Aeotec Z-Wave USB stick regularly marked it as dead, resulting needing a full network reset The reason for not making everything into z-wave was due to the bad experiences with these ones, and because it's way more expensive, than anything else. Before getting into this project, I bought an IKEA smart bulb, namely a TRÅDFRI white spectrum dimmable one. I'm not going to link it, because it's not a good product (especially for the purpose of a wake up lamp), for two reasons: - the initial dim level - where the lamp turns on - is way too bright, close to 40-50% of the total possible brightness - it has a very loud coil whine when it's "off"; apparently this is sort of a known issue, and is surprisingly common The other problem was that it's Zigbee. There is no common zigbee protocol over USB, and it's an utter mess to get zigbee properly working with domoticz: one needs a good enough zigbee sniffer, an open source firmware on it, an mqtt server, and a node.js service that translates between mqtt and zigbee. In comparison: rflink, a free arduino mega firmware for 433MHz bridging is essentially plug-and-play. I could have gone for flashing ESPHome[^1] or Tasmota[^2] on a light bulb that might be running on Tuya, but I got tired of "maybe I get lucky and receive the device I actually ordered on eBay". And this is how I arrived at the Sonoff D1: it's a real dimmer, it can accept any dimmable bulb, and it runs on WiFi. ## Sonoff D1 Dimmer[^3] {#sonoff-d1-dimmer2} I've been eyeing the Sonoff products for a while, but until I had an actual use case for them, I wasn't going to buy one. With the need for a wake up lamp, I bumped into the D1, and it is a very decent device - except for one thing, see later. ### DIY mode[^4] {#diy-mode1} Newer Sonoff devices have something called a "DIY" mode: this turns the sonoff device's web interface to accept API calls directly. It's a simple, reversible, official process, and you'll end up with a device that can be easily commaneded with mere HTTP calls. **Note: to turn on DIY mode on the D1 you still need the 433MHz remote controller.** ### Troubles with the 433MHz remote The only trouble with D1 is with it's remote controller. The remote is a 433MHz, good looking thing, that can even be mounted as a light switch on the wall. There's but one funny thing: apparently it sometimes turns on on it's own[^5]. This can easily result in the lamp coming on full 100% brightness at 4am. Yes, it did happen to me. One solution is crude, but works: un-pair the remote from the D1. After this, if you have a 433MHz bridge, like and rflink[^6] you could map the buttons to devices in domoticz and trigger and API call to the D1, so the remote still functions as intended. Since un-pairing the remote, I had no fun surprises in the middle of the night. There might be another solution which involves adding an rc filter to the device[^7] but I didn't want to solder just yet, so I can't verify if it works. ## The base: an old(ish) IKEA lamp I have two IKEA SÅNGEN Table lamps that have been out of production for a while. The good thing about them is that they have an open bottom base with plenty of space in there to attach the D1. ![IKEA SÅNGEN Table lamp](ikea-sangen-sonoff-d1-mod-03.jpg) ![Sonoff D1 attached to the base of an IKEA SÅNGEN with double sided tape.](ikea-sangen-sonoff-d1-mod-01.jpg) Notice the tape over the connectors if the D1: **you MUST cover the electrical connectors with insulating tape!** This is no joke. The D1 was never planned to be use like this, it was made to be an internal or in wall element. Without the tape **it exposes barely sunk mains cables:** ![If you leave it like this it could kill someone.](ikea-sangen-sonoff-d1-mod-05.jpg) ![Use the tape generously. Cover every inch that could expose the live wires.](ikea-sangen-sonoff-d1-mod-06.jpg) ![Position it in a corner, so it becomes even harder for anyone to come in contact with the wires.](ikea-sangen-sonoff-d1-mod-07.jpg) I also replaced the bulb fixture: the original it was an E14, and most of the bulbs in our home are B22; it's simpler to have everything as B22. ![The replaced bulb fixture.](ikea-sangen-sonoff-d1-mod-02.jpg) I opted for a halogen bulb: unlike LED, halogen can be properly dimmed, from near no light to full brightness. ![An old halogen bulb with B22 bayonette](ikea-sangen-sonoff-d1-mod-04.jpg) ## Domoticz ### Plugin for the Sonoff DIY mode Not so surprisingly nobody fiddled with the D1 Dimmer in DIY mode wired into Domoticz, so I wrote a plugin - consider the plugin to be in beta stage, given I've been running it for a few months now, without any real issues (well, once the initial bugs were ironed out). It's also on Github[^8]. This is a Python plugin; your Domoticz has to support Python plugins in order to work. `/path/to/your/domoticz/dir/plugins/sonoff-d1-diy/plugin.py` ``` {.python} __author__ = "Peter Molnar" __maintainer__ = "https://petermolnar.net" __email__ = "mail@petermolnar.net" """

Sonoff D1 Dimmer DIY connector


""" import Domoticz import json class BasePlugin: httpConn = None oustandingPings = 0 connectRetry = 3 def __init__(self): return def onStart(self): if Parameters["Mode6"] != "0": Domoticz.Debugging(int(Parameters["Mode6"])) self.httpConn = Domoticz.Connection( Name=Parameters["Address"], Transport="TCP/IP", Protocol="HTTP", Address=Parameters["Address"], Port=Parameters["Mode1"], ) self.httpConn.Connect() def onStop(self): self.httpConn.Disconnect() self.httpConn = None Domoticz.Log("onStop - Plugin is stopping.") def onConnect(self, Connection, Status, Description): if Status == 0: Domoticz.Debug("Connected to Sonoff DIY interface") self.query_status(Connection) else: Domoticz.Log( "Failed to connect (" + str(Status) + ") to: " + Parameters["Address"] + ":" + Parameters["Mode1"] + " with error: " + Description ) def onMessage(self, Connection, Data): try: strData = Data["Data"].decode("utf-8", "ignore") strData = json.loads(strData) # Status = int(Data["Status"]) Domoticz.Debug("Parsed JSON:" + json.dumps(strData)) self.oustandingPings = self.oustandingPings - 1 except: Domoticz.Error("Failed to parse response as JSON" + strData) return if "data" in strData and "deviceid" in strData["data"]: return self.update_device(strData["data"]) def onCommand(self, Unit, Command, Level, Hue): Domoticz.Debug( "onCommand called for Unit " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level) ) # in case of 'on' and 'off', the dimmable endpoint doesn't seem to respect the switch status if "on" == Command.lower(): url = "/zeroconf/switch" data = {"switch": "on"} n_value = 1 elif "off" == Command.lower(): url = "/zeroconf/switch" data = {"switch": "off"} n_value = 0 else: url = "/zeroconf/dimmable" if Level > 0: switch = "on" n_value = 1 else: switch = "off" n_value = 0 data = {"switch": switch, "brightness": Level} self.httpConn.Send( { "Verb": "POST", "URL": url, "Headers": {"Content-Type": "application/json"}, "Data": json.dumps({"deviceid": "", "data": data}), } ) self.query_status(self.httpConn) def onDisconnect(self, Connection): Domoticz.Log( "onDisconnect called for connection to: " + Connection.Address + ":" + Connection.Port ) def onHeartbeat(self): try: if self.httpConn and self.httpConn.Connected(): self.oustandingPings = self.oustandingPings + 1 if self.oustandingPings > 6: Domoticz.Log( "Too many outstanding connection issues forcing disconnect." ) self.httpConn.Disconnect() self.nextConnect = 0 else: self.query_status(self.httpConn) elif self.httpConn: # if not connected try and reconnected every 3 heartbeats self.oustandingPings = 0 self.nextConnect = self.nextConnect - 1 if self.nextConnect <= 0: self.nextConnect = 3 self.httpConn.Connect() else: self.onStart() return True except: Domoticz.Log( "Unhandled exception in onHeartbeat; resetting" ) self.httpConn = None self.onStart() def update_device(self, data): # create new devices if the don't exist just yet existing_devices = [d.DeviceID for d in Devices.values()] if data["deviceid"] not in existing_devices: # I guess brightness is only present in a dimmer # I could be wrong if "brightness" in data: Domoticz.Device( Name=data["deviceid"], Unit=1, Type=244, Subtype=73, Switchtype=7, DeviceID=data["deviceid"], ).Create() # now the device certainly exists, so find it device = None for index, d in Devices.items(): if data["deviceid"] == d.DeviceID: device = Devices[index] if not device: Domoticz.Error("something is wrong: the device was not found?!") return if "switch" in data and "brightness" in data: if data["switch"] == "on": n_value = 1 else: n_value = 0 s_value = str(data["brightness"]) # SignalLevel: see https://stackoverflow.com/a/31852591 device.Update( nValue=n_value, sValue=s_value, SignalLevel=min( max(2 * (data["signalStrength"] + 100), 0), 100 ), BatteryLevel=100, ) def query_status(self, Connection): Connection.Send( { "Verb": "POST", "URL": "/zeroconf/info", "Headers": {"Content-Type": "application/json"}, "Data": json.dumps({"data": ""}), } ) global _plugin _plugin = BasePlugin() def onStart(): global _plugin _plugin.onStart() def onStop(): global _plugin _plugin.onStop() def onConnect(Connection, Status, Description): global _plugin _plugin.onConnect(Connection, Status, Description) def onMessage(Connection, Data): global _plugin _plugin.onMessage(Connection, Data) def onCommand(Unit, Command, Level, Hue): global _plugin _plugin.onCommand(Unit, Command, Level, Hue) def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile): global _plugin _plugin.onNotification( Name, Subject, Text, Status, Priority, Sound, ImageFile ) def onDisconnect(Connection): global _plugin _plugin.onDisconnect(Connection) def onHeartbeat(): global _plugin _plugin.onHeartbeat() ``` ![You'll need to add a new hardware device for each D1](domoticz-hardware.png) ### DzVents wake up script Unless I overlooked it, there isn't a simple functionality to add custom timed dimmer in domoticz, but it's simple to add one as a DzVents event script: ``` {.lua} return { on = { timer = { 'at 7:30 on mon,tue,wed,thu,fri' } }, execute = function(domoticz, timer) domoticz.log('Timer event was triggered by ' .. timer.trigger, domoticz.LOG_INFO) -- replace '742' with the id of your D1 local dimmer = domoticz.devices(742) local level = 0 -- this is ugly, because it fires literally 100 events -- to be executed with 3 seconds from eachother for -- each % level of brightness - but it works -- feel free to alter this for a more subtle or more powerful -- wake up experience while (level < dimmer.maxDimLevel) do level = level+1 dimmer.dimTo(level).afterSec(level*3) end -- these were the tricky ones to find: -- dim to 1 and not 0, because 0 means turn off, but sadly -- doesn't actually set the dim dimmer.dimTo(1).afterMin(44) -- make sure it's off once it's dimmed to 1% dimmer.switchOff().afterMin(45) end } ``` ## Closing words This was surprisingly fun to make. The DIY mode in Sonoff is reasonable to work with, and if it wasn't for the bug of having the light turning on randomly when paired with the remote, this would be an incredibly nice, and affordable device. This was it's a good, and affordably device. Don't fiddle with dimmable LEDs; just get a dimmable halogen bulb. I know it eats more, but it also gives off a more natural (might even say healthier) light[^9], which will make it a better wake up light. [^1]: [^2]: [^3]: [^4]: [^5]: [^6]: [^7]: [^8]: [^9]: