[HowTo] Philips Somneo control

In this subforum you can show projects you have made, or you are busy with. Please create your own topic.

Moderator: leecollings

Post Reply
DeKnep
Posts: 5
Joined: Tuesday 09 June 2020 22:24
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Netherlands
Contact:

[HowTo] Philips Somneo control

Post by DeKnep »

The Wi-Fi enabled version (HF3671) of the Philips Somneo alarm clock / wake-up light can normally be remote controlled only using the Philips SleepMapper app on your phone or tablet. The app isn't all that great, and you can only pair a single mobile device with the Somneo.

I figured out how to add some of its functionality to Domoticz:
  • Reading sensors (light, sound, temperature, humidity)
  • Controlling the dimmable light
So, here we go...

Reading sensors
  1. Create a HTTP/HTTPS poller device.
    Method: GET
    ContentType: application/json
    URL: https://YOUR_SOMNEO_HOST/di/v1/products/1/wusrd
    Command: somneo_sensors.lua
    Refresh: Whatever you like, but don't set it insanely fast. It looks like the Somneo only supports 1 active connection, so a too low value (like 1 second) can interfere with the SleepMapper app behavior. I've just set it to 60 as I'm mostly interested in temperature and humidity, which don't change that rapidly anyway.

    .
  2. Create three virtual sensors:
    • Temp + Humidity
    • Lux
    • Sound Level
  3. Create the domoticz/scripts/lua_parsers/somneo_sensors.lua script.
    Of course you'll need to change the device indices to match your setup.

    Code: Select all

    s = request['content'];
    
    -- Philips Somneo sensors
    devSomneoTempHumIdx = 103 -- Temperature + humidity
    devSomneoLuxIdx     = 104 -- Light intensity
    devSomneoDbIdx      = 105 -- Noise
    
    local l_Temp    = domoticz_applyJsonPath(s, '.mstmp') -- Temperature
    local l_Hum     = domoticz_applyJsonPath(s, '.msrhu') -- Humidity
    local l_Lux     = domoticz_applyJsonPath(s, '.mslux') -- Light
    local l_Db      = domoticz_applyJsonPath(s, '.mssnd') -- Noise
    local l_HumStat = 0 -- Humidity status: 0 = Normal (30%..40% and 60%..70%)
    
    if ((l_Hum >= 40) and (l_Hum <= 60)) then
        l_HumStat = 1 -- Comfortable
    elseif (l_Hum < 30) then
        l_HumStat = 2 -- Dry
    elseif (l_Hum > 70) then
        l_HumStat = 3 -- Wet
    end
    
    -- nvalue is always zero. All values are passed as svalue.
    domoticz_updateDevice(devSomneoTempHumIdx, 0, l_Temp .. ";" .. l_Hum .. ";" .. l_HumStat)
    domoticz_updateDevice(devSomneoLuxIdx,     0, l_Lux)
    domoticz_updateDevice(devSomneoDbIdx,      0, l_Db)
    
And presto, you now have your Somneo sensors available in Domoticz :)

Reading and controlling the light
  1. Create a HTTP/HTTPS poller device.
    Method: GET
    ContentType: application/json
    URL: https://YOUR_SOMNEO_HOST/di/v1/products/1/wulgt
    Command: somneo_light.lua
    Refresh: See above. This one I've set to 5 seconds so the dimmer device in Domoticz accurately reflects the status.

    .
  2. Create a virtual dimmer device.
    Warning: do NOT use "Create Virtual Sensors" from the HTTP/HTTPS poller for this. A virtual dimmer created in this way won't trigger the event system when on/off/level is changed by the user (I'll submit a bug for that). Instead create a separate Dummy hardware device -if you don't have one already- and use that to create the virtual dimmer. (Edit: warning no longer applicable since build 15177)
    .
  3. Create a user variable (type: integer).
    When the somneo_light.lua script reads the lamp status and updates the virtual dimmer, you don't want the dimmer device to send that same status back to your Somneo. That would result in unwanted loop behavior as well as unnecessary network activity every few seconds.
    This user variable is used as a flag to prevent that. Yes, using a flag is kind of an ugly hack :roll: If anyone knows whether it's possible to update a device from a LUA parser without triggering the event system, I'd be very interested.
    .
  4. Create the domoticz/scripts/lua_parsers/somneo_light.lua script.
    Of course you'll need to change the device index and the polling flag (user variable) name to match your setup.
    Note: This works on Linux, but I guess you can also find a Windows port of the "curl" utility.

    Code: Select all

    -- Use IP address instead of 'localhost' hostname.
    -- Otherwise the "local networks (no username/password)" setting doesn't seem to work.
    jsonBaseUrl = 'http://127.0.0.1:8080/json.htm?'
    
    function callJsonUrl(a_UrlSuffix)
        os.execute("curl '"..jsonBaseUrl..a_UrlSuffix.."'")
    end
    
    -- Philips Somneo light dimmer
    devDimmerIdx = 106
    pollingFlag  = 'Wekkerverlichting_pollingUpdate'
    
    s = request['content'];
    
    -- domoticz_applyJsonPath doesn't support booleans.
    -- Simple hack: replace by an integer, which we'll need in the end anyway.
    s = s:gsub("false", "0")
    s = s:gsub("true",  "1")
    
    local l_Level = domoticz_applyJsonPath(s, '.ltlvl')
    local l_IsOn  = domoticz_applyJsonPath(s, '.onoff')
    
    -- The Somneo's maximum level is 25. Scale this to a percentage.
    l_Level = l_Level * 4
    
    -- The event system will be triggered by this update.
    -- It should only process user input, so we'll first set the "polling update"
    -- flag to 1 so the event system knows to ignore this update (and it will then
    -- just reset the flag). 
    callJsonUrl('type=command&param=updateuservariable&vname='..pollingFlag..'&vtype=0&vvalue=1')
    
    -- nvalue = on/off status
    -- svalue = level
    domoticz_updateDevice(devDimmerIdx, l_IsOn, l_Level)
    
    Now the Somneo's light status will be shown in Domoticz. Change the light setting via the SleepMapper app (or on the Somneo itself) and check that your virtual dimmer device reflects it.
    .
  5. Create the domoticz/scripts/lua/script_device_somneo.lua script.
    Again, of course: update the device name, polling flag and hostname to match your setup.

    Code: Select all

    devName      = 'Wekkerverlichting'
    pollingFlag  = 'Wekkerverlichting_pollingUpdate'
    somneoUrl    = 'https://YOUR_SOMNEO_HOST/di/v1/products/1/wulgt'
    commandArray = {}
    
    if (devicechanged[devName]) then
        if (uservariables[pollingFlag] == 1) then
            -- This event was triggered by an update of the polling script.
            -- Ignore this and reset the polling flag.
            commandArray['Variable:'..pollingFlag] = '0'
        else
            -- This event was triggered by user input, so process it.
            local jsonPayload = ''
    
            if (devicechanged[devName] == 'On') then
                -- Switch on the Somneo light.
                -- The "tempy" setting has something to do with the sunset mode.
                -- When just switching the light, it must explicitly be set to false.
                jsonPayload = '{ "onoff": true, "tempy": false }'
    
            elseif (devicechanged[devName] == 'Off') then
                -- Switch off the Somneo light
                jsonPayload = '{ "onoff": false, "tempy": false }'
            end
    
            -- Check if the event starts with "Set Level".
            -- We can't simply do a compare because the slider level will also
            -- be part of the string. We don't bother parsing that though, as
            -- it's also available through otherdevices_svalues.
            if (devicechanged[devName]:find('^Set Level') ~= nil) then
                local dimLevel = otherdevices_svalues[devName]
    
                -- Scale to Somneo range 0..25
                dimLevel = math.floor((dimLevel / 4) + 0.5)
    
                -- Make sure the "onoff" setting corresponds with the dimming level.
                -- This way it also becomes possible to switch the light on or off
                -- by just moving the slider.
                jsonOnOff   = (dimLevel > 0) and 'true' or 'false' -- Lua variant of a ternary operator
                jsonPayload = '{ "ltlvl": '..tostring(dimLevel)..', "onoff": '..jsonOnOff..', "tempy": false }'
            end
    
            if (jsonPayload ~= '') then
                -- Send our JSON request to the Somneo device.
                -- The "insecure" option is required because Philips used a self-signed SSL certificate.
                os.execute("curl --insecure --request PUT --header 'Content-Type: application/json' --data '"..jsonPayload.."' "..somneoUrl)
            end
        end
    end
    
    return commandArray
    
    Et voilà, you can now also control your Somneo's light from Domoticz :D

    Update for Domoticz build 15028 and later:
    Since build 15028, which includes commit 0986bd0, the above script doesn't work anymore because the "Set Level" event will no longer be received.
    A simplified version that will work:

    Code: Select all

    devName      = 'Wekkerverlichting'
    pollingFlag  = 'Wekkerverlichting_pollingUpdate'
    somneoUrl    = 'https://YOUR_SOMNEO_HOST/di/v1/products/1/wulgt'
    commandArray = {}
    
    if (devicechanged[devName]) then
        if (uservariables[pollingFlag] == 1) then
            -- This event was triggered by an update of the polling script.
            -- Ignore this and reset the polling flag.
            commandArray['Variable:'..pollingFlag] = '0'
        else
            -- This event was triggered by user input, so process it.
            local jsonPayload = ''
    
            if (devicechanged[devName] == 'On') then
                local dimLevel = otherdevices_svalues[devName]
    
                -- Scale to Somneo range 0..25
                dimLevel = math.floor((dimLevel / 4) + 0.5)
    
                -- Switch on the Somneo light.
                -- The "tempy" setting has something to do with the sunset mode.
                -- When just switching the light, it must explicitly be set to false.
                jsonPayload = '{ "ltlvl": '..tostring(dimLevel)..', "onoff": true, "tempy": false }'
    
            elseif (devicechanged[devName] == 'Off') then
                -- Switch off the Somneo light
                jsonPayload = '{ "onoff": false, "tempy": false }'
            end
    
            if (jsonPayload ~= '') then
                -- Send our JSON request to the Somneo device.
                -- The "insecure" option is required because Philips used a self-signed SSL certificate.
                os.execute("curl --insecure --request PUT --header 'Content-Type: application/json' --data '"..jsonPayload.."' "..somneoUrl)
            end
        end
    end
    
    return commandArray
    
Last edited by DeKnep on Tuesday 05 September 2023 23:24, edited 3 times in total.
User avatar
Anatolis
Posts: 8
Joined: Thursday 23 March 2017 22:47
Target OS: Raspberry Pi / ODroid
Domoticz version: V2020.2
Contact:

Re: [HowTo] Philips Somneo control

Post by Anatolis »

Thanks a lot for sharing DeKnep! Worked like a charm for me as well.
I only chose to turn of the status polling of the light switch. Somehow it was causing the "Sync" symbol below the wifi icon on the Somneo to flash at every poll. For me personally I do no see a huge drawback in having the wrong status, though I think for the Somneo I will look if I can create a manual status update button in Domoticz :roll:

By the way, I'd love to learn from you how you figured out the correct JSON paths to query. (now where is that 'angel' emoticon ;) )

Note to myself:
For those of you that do not very often create virtual devices to create the dimmer device:
  1. When creating a new virtual sensor under the separate "Dummy Hardware", just choose "Switch" (Dimmer does not exist in the list of Dummy Hardware sensors)
  2. Now go to your "Switches" tab and click the "Edit" button of your new create virtual switch
  3. Change the "Switch Type" to "Dimmer" instead of "On/Off"
  4. Now you have your Dimmer! ;)
DeKnep
Posts: 5
Joined: Tuesday 09 June 2020 22:24
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Netherlands
Contact:

Re: [HowTo] Philips Somneo control

Post by DeKnep »

Anatolis wrote: Sunday 29 November 2020 10:48 Thanks a lot for sharing DeKnep! Worked like a charm for me as well.
You're welcome. Always good to know that this is also useful to others :)
Somehow it was causing the "Sync" symbol below the wifi icon on the Somneo to flash at every poll.
Hm, that's odd. My Somneo doesn't do that. If it did I would surely have noticed.
I guess I have a slightly different model or different firmware then.
By the way, I'd love to learn from you how you figured out the correct JSON paths to query. (now where is that 'angel' emoticon ;) )
Part Google, part automated trial-and-error :D
I found a post on another home automation forum that listed a few of those paths (among them the "wulgt" one), plus an explanation that there are many more - all starting with "wu" followed by 3-letter abbreviations. So I created a simple script that used wget to brute-force query all URLs from "wuaaa" to "wuzzz". That way I found out which ones work, and by inspecting the downloaded JSON payloads I figured out that "wusrd" contained the sensor data.

I also tried to route my phone's connection through Fiddler (a debugging proxy) to inspect the traffic between the SleepMapper app and the Somneo, but I couldn't get that to work. I guess the app does some kind of certificate validation to check that it's connected directly to the Somneo. When connected via Fiddler, the app just wouldn't query or send anything, so there was no traffic to inspect. But in the end I didn't need this; as you can see the light control proved to be rather simple.

If anyone is interested in playing around with more Somneo control, these are the working "wu" URLs that I found.
I did not yet explore how to use most of them, as my only goal was having the sensors and light accessible through Domoticz.
  • wualm - alarm settings
  • wudsk - dusk/sunset mode
  • wufmp - FM radio presets? (not sure)
  • wufmr - FM radio frequencies
  • wulgt - light settings
  • wumem - ?
  • wumus - ?
  • wungt - night settings? (not sure what this is)
  • wuply - ?
  • wurlx - RelaxBreathe settings
  • wusnd - sound settings
  • wusrd - sensor readings
  • wusts - status?
  • wutim - current time and date
  • wutmr - ?
  • wutms - ?
Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests