One click wakeup alarm in music with LMS

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

Moderator: leecollings

Post Reply
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

One click wakeup alarm in music with LMS

Post by hestia »

Hi all,
I made a DzVents script to wakeup with a Logitech Media Player. I wanted something simple to use and easy for changing the wake-up hour, and without any calendar.
Why Domoticz and not LMS GUI directly?!?
I've been using LMS GUI for years but I could not get what I wanted: a specific alarm routine: music in random at low level for 5 minutes, radio not random at normal level for 30 minutes, music in random at low level for 40 minutes. I should be up not too late after the radio slot ;-) and have music on when I come back after breakfast...
This sequence was not possible with LMS, probably due to bugs, I could not start with music and after have radio, the music never stop at the end, not possible to have different levels on each slot. And also sometimes no wake up :-(
So I did it why Domoticz.
I’d like to share it for the ones who want to use it, and to get feedback on the way I did it, it as I’m not a coder.
The GUI is a simple selector
seq_reveil.png
seq_reveil.png (78.63 KiB) Viewed 880 times
vac_pause.png
vac_pause.png (46.2 KiB) Viewed 880 times
This part could be improved, but I know nothing with GUI and html…

The prerequisite is to have a Logitech Media Server, a player and the integration in Domoticz with the plugin Logitech Media Server.
The script is

Code: Select all

--[[
A one-click alarm to wake-up with the music off your Logitech Media Server (LMS).
Your can have several playlists played for the wakeup (music random, radio, other music random)
and with different level for each slot.
You can pause for one day ; there is also a vacation mode
When a playlist is send, there are some check that it is playing for real to be sure to wake up
If problem there is a fallback mode (for me blink my light)
If domoticz was down a the alarm time, the alarm is triggered when domoticz is back

About LMS API: http://tutoriels.domotique-store.fr/content/54/95/fr/api-logitech-squeezebox-server-_-player-http.html

Prerequisite: V2020.2 build 12924 (or from dzVents V 3.0.2 to domoticz 2020.2 build 12819)

hestia 29/05/2020> go live
hestia 28/10/2020> fix error when calling Blink (for fallback mode)
hestia 05/02/2021> move CheckResponse after PlayingKO to avoid error
hestia 07/02/2021> fix some issues with logging
hestia 08/02/2021> add some afterSec(x) on the OpenUrlLMS to avoid some risks of mix in calls
    due to openURL optimisation #4585 (http requests on seperate threads dzVents)
--]]

local alarmStartPerDay = {'Pause', '6h40', '6h40', '6h40', '6h40', '6h40', '7h15'} -- to be present in WAKEUPTIME_IDX  /!\ 1 is Sunday
local alarmDurations = {5, 30, 40, 0} -- alarm duration for each slot - 0 to stop the last alarm
local alarmPlayLists = {"zzReveilMusic","zzReveilInfo", "zzReveilDeezer"} -- playlists names for each slot  /!\ no blank nor é à ...
local alarmLevels = {60, 77, 60} -- alarm level for each playlist
local alarmShuffle = {true, false, true} -- to shuffe or not each alarm slot

local userDayNames = {'Dim.', 'Lun.', 'Mar.', 'Mer.', 'Jeu.', 'Ven.', 'Sam.'} -- to change from dz config, to comment if not necessary

--local FAKE_LMS = 1037 -- to uncomment for testing
--local DUMMY = true -- to set to true for testing only w/o playing for real
local PLAYER_IDX = 1303 -- LMS player idx for dzVent commands
local LMS_player= "b8:27:eb:7d:00:36"  -- LMS Player MAC Address for json commands
local LMS_serverPort='erato:9000'  -- LMS Server Hostname and HTTP Port Number
local LMS_playlistsFolder="/media/playlists/" -- LMS Playlists Folder
local ERR_IDX = 967 -- backup device to use in case of issue with LMS

local ICON_ON = 237
local ICON_PLAY = 236
local ICON_OFF = 238
local ICON_PAUSE = 239
local WAKEUPTIME_IDX = 1035 -- dummy device to create with the wakeup times
local WAKEUPTIME_NAME = 'Réveil' -- inital name of the dummy device to create with the wakeup times
    -- switch type selector, with level name like 6h30 6h35 6h40... and 'Pause' and 'Stop' (see param for value below
local alarmPauseValue = 'Pause' -- the value choosen to have the alarm off for 1 day
local alarmOffValue = 'Stop' -- the value choosen to have the alarm off

-- user variables to declare
local alarmStatus_idx=8
local alarmDate_idx =7
local alarmTime_idx =9
local alarmIndex_idx =10

local CALLBACKPREFIX=WAKEUPTIME_IDX -- if several instances of the script
local HHMM_SEPARATOR='h'
local TIME_DELAY=15

return {
    logging =   {
                level =
                domoticz.LOG_ERROR, --select one to override system log level normal = LOG_ERROR
                --domoticz.LOG_DEBUG,
                --domoticz.LOG_INFO,
                --domoticz.LOG_ERROR,
                --domoticz.LOG_FORCE
                },
    on =    {
            timer = {function(dz)
                    if dz.time.rawDate == dz.variables(alarmDate_idx ).date.rawDate then
                        if dz.time.matchesRule('at ' .. dz.variables(alarmTime_idx).value) then -- check exact minutes
                            --print('Exact time checked ' .. dz.variables(alarmTime_idx).value)                            
                            return true
                        else -- check if the time was missed
                            local maxTime= dz.variables(alarmTime_idx).time.addMinutes(TIME_DELAY)
                            local checkTime=dz.time.matchesRule('between ' .. dz.variables(alarmTime_idx).time.hour .. ":" .. dz.variables(alarmTime_idx).time.minutes
                            .. ' and ' .. maxTime.hour .. ":" .. maxTime.minutes)
                            --print('alarmTime ' .. dz.variables(alarmTime_idx).time.hour .. ":" .. dz.variables(alarmTime_idx).time.minutes)
                            --print('maxTime ' .. maxTime.hour .. ':' .. maxTime.minutes, dz.LOG_DEBUG)
                            if checkTime and dz.variables(alarmStatus_idx).value ~= 'PLAY' then -- the time was missed, no alarm triggered!
                                print('Alarm missed ' .. dz.variables(alarmTime_idx).value)
                                return true
                            else
                                --print('Alarm not missed')
                            end
                        end
            	    else
            	        --print('NOT today')
            	    end
        	        return false
        	    end
    		},
            devices =   {
                        WAKEUPTIME_IDX
                        },
            httpResponses = {
                            CALLBACKPREFIX..'LMS_Play',
                            CALLBACKPREFIX..'LMS_Shuffle',
                            CALLBACKPREFIX..'LMS_Unshuf',
                            CALLBACKPREFIX..'LMS_Status'
                            }
            },



 execute = function(dz, item, triggerInfo)
    _G.logMarker =  dz.moduleLabel -- set logmarker to scriptname
    local LOG_LEVEL = dz.LOG_INFO -- normal 
    --local LOG_LEVEL = dz.LOG_DEBUG
    --local LOG_LEVEL = dz.LOG_ERROR
    --local LOG_LEVEL = dz.LOG_FORCE

    dz.log('==>> triggered by ' ..  triggerInfo.type, LOG_LEVEL)

    local scriptRC=0 -- global retrun code to stop the scrip
    
    local wakeupTimeBool=false
    local wakeUpTime
    local devPLAYER_IDX = dz.devices(PLAYER_IDX)

    local devWAKEUPTIME = dz.devices(WAKEUPTIME_IDX)
    
    local alarmStatus
    local alarmDate
    local alarmTime
    local alarmIndex
    local alarmValue
      
-----< BEG FUNCTIONS > ----------------------------------------------------------------------------------------

        
    local function OpenUrlLMS (urlParams, urlCallback, p_afterSec)
        -- open the player url with a callback to check the return code and player status
        local httpLMS='http://'..LMS_serverPort..'/status.html?'
 	    dz.openURL({url = httpLMS .. urlParams, callback = CALLBACKPREFIX..urlCallback}).afterSec(p_afterSec)
        dz.log(urlCallback .. ' - ' .. urlParams, LOG_LEVEL)
        return
    end

    
    local function PlayLMS(cmd, volume, playlist, shuffle)
        -- cmd='On' => play the playlist with the volune and shuffle or not / "Off" => switch off the player
        if cmd == 'On' then
            dz.log('PlayLMS ' .. cmd .. ' '  .. volume .. ' ' .. playlist, LOG_LEVEL)
            -- set the player w or w/o shuffle
            local paramShuffle=0
            if shuffle then  paramShuffle=1 end
            local shufflePlaylist='p0=playlist&p1=shuffle&p2='.. paramShuffle ..'&player='..LMS_player
            -- adjust the volume
            if DUMMY then
                dz.log('DUMMY', LOG_LEVEL)
            else
                OpenUrlLMS(shufflePlaylist, 'LMS_Shuffle', 0)                
                devPLAYER_IDX.setVolume(volume).afterSec(1)
                -- play the playlist
                local playPlaylist='p0=playlist&p1=play&p2='..LMS_playlistsFolder..playlist..'.m3u&p3=1&player='..LMS_player
                OpenUrlLMS(playPlaylist, 'LMS_Play', 2)
                -- check the status, is the player playing? result in the callback...
                local playStatus='&player='..LMS_player
                OpenUrlLMS(playStatus, 'LMS_Status', 3)
            end
            if FAKE_LMS ~= nil then -- dummy to test the script w/o activate LMS
                dz.log('==>> FAKE_LMS ' .. FAKE_LMS .. ' ' .. cmd, LOG_LEVEL)
                dz.devices(FAKE_LMS).updateAlertSensor(dz.ALERTLEVEL_GREEN, playlist)
            end
        else
            dz.log('PlayLMS ' .. cmd, LOG_LEVEL)
            -- Stop, Unshuffle Playlist and SwitchOff
            if DUMMY then
                dz.log('DUMMY', LOG_LEVEL)
            else
                devPLAYER_IDX.stop()
                local paramShuffle=0
                local shufflePlaylist='p0=playlist&p1=shuffle&p2='.. paramShuffle ..'&player='..LMS_player
                OpenUrlLMS(shufflePlaylist, 'LMS_Unshuf', 1)
                devPLAYER_IDX.switchOff().afterSec(2)
            end
            if FAKE_LMS ~= nil then -- dummy to test the script w/o activate LMS
                dz.log('==>> FAKE_LMS ' .. FAKE_LMS .. ' ' .. cmd, LOG_LEVEL)
                dz.devices(FAKE_LMS).updateAlertSensor(dz.ALERTLEVEL_GREY, 'Off')
            end
        end
        return
    end


    local function CheckWakeUpTime (P_wakeUpTimeHM)
        -- function to check if the new wakeupTime is a time
        dz.log('CheckWakeUpTime ' .. P_wakeUpTimeHM, LOG_LEVEL)
        local F_wakeupTimeBool = false
        local F_hhmmTime = string.gsub(P_wakeUpTimeHM, HHMM_SEPARATOR, "")
        if tonumber(F_hhmmTime) ~= nil then -- it is a time
            F_wakeupTimeBool = true
        end
        dz.log("CheckWupTime> hhmmTime " .. P_wakeUpTimeHM .. ' '..  tostring(F_wakeupTimeBool), LOG_LEVEL)
        return(F_wakeupTimeBool)
    end

    
    local function ConvertWakeUpTime (P_wakeUpTimeHM)
        -- function to convert wakeupTime in hh:mm:ss, if with separator like 'h'
        dz.log('ConvertWakeUpTime ' .. P_wakeUpTimeHM, LOG_LEVEL)
        local F_wakeUpTimeHMS
        if HHMM_SEPARATOR ~= ":" then
            F_wakeUpTimeHMS = (string.gsub(P_wakeUpTimeHM, HHMM_SEPARATOR, ":") .. ':00')
        else
            F_wakeUpTimeHMS = (P_wakeUpTimeHM .. ':00')
        end
        dz.log("ConvertWakeUpTime hhmmTime " .. F_wakeUpTimeHMS, LOG_LEVEL)
        return(F_wakeUpTimeHMS)
    end


    local function SetNextAlarmTime()
        local F_NextDayNum
        local F_nextWakeTime
        dz.log("SetNextAlarmTime nil", LOG_LEVEL)
        -- set the time for the tomorrow alarm based on a list of the day of the week
        F_NextDayNum = dz.time.wday + 1 -- Sunday = 1
        if F_NextDayNum == 8 then F_NextDayNum = 1 end
        F_nextWakeTime = alarmStartPerDay[F_NextDayNum]
        dz.log("Day#: " .. F_NextDayNum .. ' ' .. F_nextWakeTime, LOG_LEVEL)
        dz.log("Set next wakeup time: " .. F_nextWakeTime, LOG_LEVEL)
        return (F_nextWakeTime)
    end

    
    local function CalculateNextAlarmInfos(P_wakeUpTimeState, P_Origin) --  P_Origin: 'U' User: after a change by GUI / 'S' Script for the next alarm day
        local F_wakeUpTimeHM = P_wakeUpTimeState -- the time selected in the dummy selector
        dz.log('CalculateNextAlarmInfos F_wakeUpTimeHM ' .. F_wakeUpTimeHM, LOG_LEVEL) 
        local F_wakeupTimeBool=CheckWakeUpTime(F_wakeUpTimeHM)
        local F_wakeUpTimeHMS
        if F_wakeupTimeBool then -- it is a time
            dz.log('Wakeup time :' .. F_wakeUpTimeHM, LOG_LEVEL)
            F_wakeUpTimeHMS=ConvertWakeUpTime(F_wakeUpTimeHM)
            local F_checkAlarmTomorrow
            if P_Origin == 'S' then -- if by the script, it is for tomorrow
                F_checkAlarmTomorrow = true
            else
                F_checkAlarmTomorrow = dz.time.matchesRule('between ' .. F_wakeUpTimeHMS .. ' and 23:59:59')
                -- if by the GUI, if the selected next alarm is before now, it is for tomorow
            end
            if F_checkAlarmTomorrow then
                alarmDate=dz.time.addHours(24)
                --print(tostring(checkExec))
                dz.log('Set alarm for tomorrow ' .. F_wakeUpTimeHMS, LOG_LEVEL)
            else
                --print(tostring(checkExec))
                alarmDate=dz.time
                dz.log('Reset alarm for today ' .. F_wakeUpTimeHMS, LOG_LEVEL) 
            end
            alarmTime= dz.time.makeTime(alarmDate.rawDate .. " " ..  F_wakeUpTimeHMS) -- the wakeup time as a time object
            alarmStatus='ON'
            alarmIndex=1            
        else
            if F_wakeUpTimeHM == alarmPauseValue then
                dz.log('Alarm pause', LOG_LEVEL)
                if alarmStatus == 'ON' then
                    -- alarm is ON and to be set on PAUSE, not a change of the trigger date/time
                    -- there should be already a next date/time set to be trigger the script again at the next alarm slot
                    dz.log('Alarm cancelled when ON', LOG_LEVEL) 
                else -- PLAY, OFF
                    alarmDate=dz.time.addHours(24) -- tomorrow to update the alarm for the day after
                    alarmTime=dz.time.makeTime(alarmDate.rawDate .. ' 12:00:00')
                    dz.log('Alarm cancelled when not ON', LOG_LEVEL) 
                end
                alarmStatus='PAUSE'
            elseif  F_wakeUpTimeHM == alarmOffValue then
                dz.log('Alarm stopped', LOG_LEVEL)
                alarmDate=dz.time.addHours(-24) -- before today, not to be triggered
                --print('alarmDate.rawTime ' .. alarmDate.rawTime)
                --print('alarmDate.rawDate ' .. alarmDate.rawDate)
                alarmTime=dz.time.makeTime(alarmDate.rawDate .. ' 12:00:00')
                alarmStatus='OFF'
            else
                dz.log('CalculateNextAlarmInfos: Alarm ?????? <' .. F_wakeUpTimeHM .. '>', LOG_LEVEL)
            end
            --print('=> 3 alarmIndex ' .. alarmIndex)
            alarmIndex=0
        end
    return
    end


    local function CancelAlarm() -- cancel the alarm after the icon is clicked
        if alarmStatus=='PLAY' then
            dz.log('CancelAlarm PLAY', LOG_LEVEL)
            PlayLMS('Off')
        elseif alarmStatus=='PAUSE' then
            dz.log('CancelAlarm: alarm already on ' .. alarmStatus, LOG_LEVEL)
            return(-1)
        end
        alarmStatus='CANCELLED' -- the next alarm will be set when the next timer happens
        devWAKEUPTIME.setIcon(ICON_PAUSE)
        dz.log('CancelAlarm => ' .. alarmStatus, LOG_LEVEL)
        return(0)
    end


    local function GetAlarmInfo() -- get status, date and time of the alarm
        alarmStatus=dz.variables(alarmStatus_idx).value
        alarmDate=dz.variables(alarmDate_idx).date
        alarmTime=dz.variables(alarmTime_idx).time
        alarmIndex=dz.variables(alarmIndex_idx).value
        --print('=> 4 alarmIndex ' .. alarmIndex)

        if alarmStatus=='PAUSE' or alarmStatus=='ON' or alarmStatus=='PLAY' or alarmStatus== 'CANCELLED' or alarmStatus== 'OFF' then
            dz.log('AlarmStatus OK: ' .. alarmStatus, LOG_LEVEL)
        else
            dz.log('AlarmStatus KO: ' .. dz.variables(alarmStatus_idx).value, LOG_LEVEL)
            -------------SetNextAlarmTime(alarmOffValue)
            alarmDate=dz.time.addHours(-24) -- tomorrow to update the alarm for the day after
            alarmTime=dz.time.makeTime(alarmDate.rawDate .. ' 12:00:00')
            alarmStatus='OFF'

            alarmIndex=0
            --print('=> 5 alarmIndex ' .. alarmIndex)
            return(-1)
        end
        dz.log('Alarm status ' ..  alarmStatus, LOG_LEVEL)
        dz.log('Alarm date ' .. alarmDate.rawDate .. ' time ' .. alarmTime.rawTime, LOG_LEVEL)
        return(0)
    end

    
    local function LangDayName(P_alarmDate) -- give the name of the day in user language (if dz GUI is not)
        if userDayNames == nil then -- the day list exist
            return(P_alarmDate.dayName)
        else
            return(userDayNames[P_alarmDate.wday])
        end
    end


    local function SetIconNameDevice(P_alarmStatus, P_alarmDate)
        if P_alarmStatus == 'PAUSE' then
            devWAKEUPTIME.setIcon(ICON_PAUSE)
            devWAKEUPTIME.rename(WAKEUPTIME_NAME .. ' - ' .. LangDayName(P_alarmDate))
        elseif P_alarmStatus == 'OFF' then
            devWAKEUPTIME.setIcon(ICON_OFF)
            devWAKEUPTIME.rename(WAKEUPTIME_NAME)
        elseif P_alarmStatus == 'PLAY' then
            devWAKEUPTIME.setIcon(ICON_PLAY)
        elseif P_alarmStatus == 'ON' then
            devWAKEUPTIME.setIcon(ICON_ON)
            devWAKEUPTIME.rename(WAKEUPTIME_NAME .. ' - ' .. LangDayName(P_alarmDate))
        elseif P_alarmStatus == 'CANCELLED' then
            -- the icon is already ok
        else
            dz.log('SetIconNameDevice ' .. P_alarmStatus .. ' unknown', dz.LOG_ERROR)
        end
        return
    end
  
    
    local function SetAlarmInfo(P_alarmStatus, P_alarmDate, P_alarmTime, P_alarmIndex) -- set status, date and time of the alarm
        dz.variables(alarmStatus_idx).set(P_alarmStatus) --.checkFirst()
        dz.variables(alarmDate_idx ).set(P_alarmDate.day..'/'..P_alarmDate.month..'/'..P_alarmDate.year) -- .checkFirst()
        dz.variables(alarmTime_idx).set(P_alarmTime.hour..':'..P_alarmTime.min) -- .checkFirst()
        dz.variables(alarmIndex_idx).set(P_alarmIndex)
        --print('=> 6 alarmIndex ' .. P_alarmIndex)(
        SetIconNameDevice(P_alarmStatus, P_alarmDate)
        return
    end


    local function Blink(deviceId) -- 28/10/2020 -- move before PlayingKO to avoid error
        -- blink a device based on time on and time off
        dz.devices(deviceId).cancelQueuedCommands()
    
        local blinkDurations= {2, 1, 2, 1, 2, 1}
        local duration=0
    
        local boolOn=true
        for key, id in ipairs(blinkDurations) do
            --print(duration)
            if boolOn then
                dz.devices(deviceId).switchOn().afterSec(duration)
                boolOn=false
            else
                dz.devices(deviceId).switchOff().afterSec(duration)
                boolOn=true
            end
            duration=duration+blinkDurations[key]
        end
        return
    end

    
    local function PlayingKO(playError)
        -- in case of error when playing, do something to wakeup anayway...
        dz.log('Error playing ' .. playError, dz.LOG_ERROR)
        Blink(ERR_IDX) -- blink a specific device
        return
    end


    local function CheckResponse() -- check http response -- 05/02/2021 -- move after PlayingKO to avoid error
        if item.callback == CALLBACKPREFIX..'LMS_Status' then
            -- check if the player is present (selected)
            --dz.log('item.data ' .. item.data, LOG_LEVEL)
            local CheckPlayer=string.find(item.data,'<option selected value="'..LMS_player)
            if CheckPlayer ~= nil then -- the player is present
                dz.log('HTTPResponse CheckPlayer OK ' .. CheckPlayer, LOG_LEVEL)
                -- check if the player is playing
                --dz.log('item.data ' .. item.data, LOG_LEVEL)
                local CheckPlaying=string.find(item.data,'Now Playing') 
                if CheckPlaying ~= nil then -- the player is playing
                    dz.log('HTTPResponse CheckPlaying OK ' .. CheckPlaying, LOG_LEVEL)
                else  -- the player is not playing
                    dz.log('item.data ' .. item.data, dz.LOG_FORCE)
                end    
            else -- the player is not present
                PlayingKO('Player')
            end
        end 
        return
    end
   
   
-----< END FUNCTIONS > ----------------------------------------------------------------------------------------


-- < Device trigger
    if item.isDevice then -- the wakeup time has been changed
        dz.log(dz.moduleLabel .. ' triggered by ' .. item.id .. ' ' .. item.name, LOG_LEVEL)
        scriptRC=GetAlarmInfo()
        if scriptRC ~= 0 then
            SetAlarmInfo(alarmStatus, alarmDate, alarmTime, alarmIndex)
            return
        end
        dz.log(devWAKEUPTIME.name .. ' ' .. devWAKEUPTIME.state .. " nValue: " .. devWAKEUPTIME.nValue, dz.LOG_DEBUG)
        if devWAKEUPTIME.nValue == 0 and devWAKEUPTIME.state ~= alarmOffValue then -- the icon is clicked to cancel the alarm
            scriptRC=CancelAlarm()
            if scriptRC ~= 0 then
                return
            end
            dz.log('==>> Alarm cancelled', LOG_LEVEL)
            SetAlarmInfo(alarmStatus, alarmDate, alarmTime, alarmIndex)
        else
            -------SetNextAlarmTime(devWAKEUPTIME.state)
            CalculateNextAlarmInfos(devWAKEUPTIME.state, 'U') -- user: after a change by GUI
            SetAlarmInfo(alarmStatus, alarmDate, alarmTime, alarmIndex)
            devWAKEUPTIME.switchSelector(devWAKEUPTIME.state).silent() -- to refresh icon and name selector on the GUI
        end
-- < Timer to fire the alarm
    elseif item.isTimer then
        dz.log(dz.moduleLabel .. ' triggered by timer', LOG_LEVEL)
        scriptRC=GetAlarmInfo()
        if scriptRC ~= 0 then
            SetAlarmInfo(alarmStatus, alarmDate, alarmTime, alarmIndex)
            return
        else
            -- Wakeup management
            local W_nextWakeTime 
            if alarmStatus == 'ON' or alarmStatus == 'PLAY' then
                local wakeUpTimeHM = devWAKEUPTIME.state -- the time selected in the dummy selector
                dz.log(alarmStatus .. ' Wakeup at ' .. wakeUpTimeHM .. ' slot ' .. alarmTime.rawTime .. ' ' .. alarmIndex, LOG_LEVEL)
   
                if alarmDurations[alarmIndex]==0 then -- it was the last slot of the alarm
                    PlayLMS('Off')
                    W_nextWakeTime = SetNextAlarmTime() -- set alarm for next day
                    CalculateNextAlarmInfos(W_nextWakeTime, 'S') -- script
                    dz.log('Off alarmIndex ' .. alarmIndex, LOG_LEVEL)
                else -- it is a slot to play
                    alarmStatus='PLAY'
                    PlayLMS('On', alarmLevels[alarmIndex], alarmPlayLists[alarmIndex],alarmShuffle[alarmIndex])
                    alarmTime=alarmTime.addMinutes(alarmDurations[alarmIndex])
                    --print('=> 1 alarmIndex ' .. alarmIndex)
                    alarmIndex=alarmIndex+1
                    dz.log('=> On alarmIndex ' .. alarmIndex, LOG_LEVEL)
                    W_nextWakeTime = devWAKEUPTIME.state
                end
            elseif alarmStatus =='CANCELLED' or alarmStatus =='PAUSE' then -- set alarm for next day
                W_nextWakeTime = SetNextAlarmTime() -- set alarm for next day
                CalculateNextAlarmInfos(W_nextWakeTime, 'S') -- script
            else
                dz.log('Alarm status: ' .. alarmStatus .. ' ?!?', dz.LOG_ERROR)
                return
            end
            SetAlarmInfo(alarmStatus, alarmDate, alarmTime, alarmIndex)
            devWAKEUPTIME.switchSelector(W_nextWakeTime).silent() -- and to refresh icon and name selector on the GUI
        end
-- < The callbacks to check if the player is playing
    elseif item.isHTTPResponse then -- the callback to check error code and player status
        dz.log(dz.moduleLabel .. ' callbacked by ' ..  item.callback, LOG_LEVEL)
        if item.ok then -- statusCode == 2xx
            dz.log('HTTPResponse OK !!! callback: ' .. item.callback .. ' statusCode: ' .. item.statusCode, LOG_LEVEL)
            CheckResponse()
        else -- error http
            dz.log('HTTPResponse KO !!! callback: ' .. item.callback .. ' statusCode: ' .. item.statusCode, dz.LOG_ERROR)
            --if item.callback == CALLBACKPREFIX..'LMS_Play' then --Error playing alarm
                dz.log('Backup alarm to fire!!!', LOG_LEVEL)
                PlayingKO('HTTPResponse')
            --else
            --    dz.log('HTTPResponse KO for something else?!?', dz.LOG_ERROR)                
            --end
    end
-- < Impossible error!    
    else
        dz.log(dz.moduleLabel .. ' triggered by ?????', dz.LOG_ERROR)
    end   
    
end
} 
updated on 06/02/2021 13:35 to fix an error (05/02/2021 in the script)
updated on 08/02/2021 fix the logging and an error due to new dz version
The configuration is done with variables explained in the script
I comment the following ones:
  • The normal wake-up time for each day of the week, Pause for the days you don’t want an alarm
    alarmStartPerDay = {'Pause', '6h40', '6h40', '6h40', '6h40', '6h40', '7h15'} the first one is Sunday, but perhaps depends on settings.
    They have to be present in the selector WAKEUPTIME_IDX so one could change it.
  • alarmDurations = {5, 30, 40, 0} -- alarm duration for each slot - 0 to stop the last alarm
  • alarmPlayLists = {"zzReveilMusic","zzReveilInfo", "zzReveilDeezer"} -- playlists names for each slot /!\ no blank nor é à ...
  • alarmLevels = {60, 77, 60} -- alarm level for each playlist
  • alarmShuffle = {true, false, true} -- to shuffe or not each alarm slot
Some user variables are needed, info in the script
There is also a fallback possibility is case the LMS doesn’t play ; I blink a light (ERR_IDX)
If Domoticz missed the wakeup time (for some cause), when Domoticz could do something after, it will play the LMS.
As all the wanted methods for the LMS doesn’t exist in DzVents, I used direct api and so I need 2 sort of id for the LMS
local LMS_player= "b8:27:eb:7d:00:36" -- LMS Player MAC Address for json commands
local LMS_serverPort='erato:9000' -- LMS Server Hostname and HTTP Port Number
It is a morning wakeup alarm ; I’m not sure it works to wake up after noon 😉

Next steps
Better widget?
More configuration via GUI rather than in the script…
Build a device like Oregon Projection Clocks to display if there an alarm on and other info from Domoticz like temperature… But I didn’t find anything regarding the projector…
Disclaimer: I'm not responsible if you don't wake up on time to "go" to work ;-)
Post Reply