Since a long time I wanted to build a wake-up light with my Logitech Squeezebox radio and the ceiling light – a Philips Hue bulb.
All components are connected through an openHAB server where such a functionality can be easily implemented.
For usability reasons and to consider the WAF (woman acceptance factor) the alarms should also be configured/managed by the Squeezebox radio – respectively the LMS in the background.
The code can be easily extended, e.g. to show more alarms or to edit the days the alarm will occur. It’s also possible to implement other LMS-CLI commands. But for me it works like this.
There is one string item to send and receive the command to/from the LMS. This item is triggered every 5 minutes to query the current alarms form the server and to update the other items. If the alarm state is changed, the alarm will be updated in the server. This behavior could be extended to update the alarm when the time items are changes… Feel free 🙂
import java.net.URLDecoder
import org.eclipse.smarthome.model.script.ScriptServiceUtil
import org.eclipse.xtext.xbase.lib.Procedures
import java.util.ArrayList
import java.util.LinkedHashMap
import java.util.concurrent.locks.ReentrantLock
var ArrayList<LinkedHashMap<String,String>> alarms = null
var Timer timer_alarm = null
val ReentrantLock mutex = new ReentrantLock
val String player_id = “<your player mac>”
val num_alarm_items = 2
val alarms_clear = [|
logInfo(“lms”, “clear all alarms”)
newArrayList()
]
val Procedures$Procedure1<LinkedHashMap<String,String>> update_alarm = [ alarm |
// get Items
logInfo(“test”, “update_alarm: “ + alarm.get(“nr”))
val Integer alarm_nr = Integer::parseInt(alarm.get(“nr”)) + 1
logInfo(“test”, “alarm_nr=” + alarm_nr.toString)
val item_hour = ScriptServiceUtil.getItemRegistry.getItem(“alarm” + alarm_nr.toString + “_hour”)
val item_min = ScriptServiceUtil.getItemRegistry.getItem(“alarm” + alarm_nr.toString + “_minute”)
val item_dow = ScriptServiceUtil.getItemRegistry.getItem(“alarm” + alarm_nr.toString + “_dow”)
val item_state = ScriptServiceUtil.getItemRegistry.getItem(“alarm” + alarm_nr.toString + “_state”)
var String dow = item_dow.state.toString
var hour = (item_hour.state as DecimalType).intValue
var min = (item_min.state as DecimalType).intValue
var state = item_state.state.toString
if(state == “ON”)
state = “1”
else
state = “0”
val time = (hour * 60 + min)*60
val String cmd = alarm.get(“player”) + ” alarm update id:” + alarm.get(“id”) + ” time:” + time.toString + ” enabled:” + state + ” dow:” + dow
logInfo(“test”, “Set Alarm – “ + cmd)
cmd
]
val Procedures$Procedure2<ArrayList<LinkedHashMap<String,String>>, Integer> update_items = [ alarms, num_alarm_items |
for(var n=0; n < num_alarm_items; n++)
{
var String dow = “-“
var hour = 0
var min = 0
var state = OFF
var String label = “-“
if( n < alarms.length)
{
val current_alarm = alarms.get(n)
dow = current_alarm.get(“dow”)
dow = dow.replace(“0”, “So”).replace(“1”, “Mo”).replace(“2”, “Di”).replace(“3”, “Mi”).replace(“4”, “Do”).replace(“5”, “Fr”).replace(“6”, “Sa”)
val t = Integer::parseInt(current_alarm.get(“time”)) / 60 // time in minutes since midnight
hour = t / 60
min = t % 60
if( current_alarm.get(“enabled”) == “1” )
state = ON
label = ” Time: “ + hour.toString + “:” + min.toString + ” Days: “ + dow + ” – “+ state.toString
}
if( n <= num_alarm_items)
{
var Integer alarm_nr = n
alarm_nr += 1
val item_hour = ScriptServiceUtil.getItemRegistry.getItem(“alarm” + alarm_nr.toString + “_hour”)
item_hour.postUpdate(hour)
val item_min = ScriptServiceUtil.getItemRegistry.getItem(“alarm” + alarm_nr.toString + “_minute”)
item_min.postUpdate(min)
val item_dow = ScriptServiceUtil.getItemRegistry.getItem(“alarm” + alarm_nr.toString + “_dow”)
item_dow.postUpdate(dow)
val item_state = ScriptServiceUtil.getItemRegistry.getItem(“alarm” + alarm_nr.toString + “_state”)
item_state.postUpdate(state)
val item_label = ScriptServiceUtil.getItemRegistry.getItem(“alarm” + alarm_nr.toString + “_label”)
item_label.postUpdate(label)
}
}
0
]
val Procedures$Procedure2<String, String> get_next_offset_day = [ dow, time |
val today = now.getDayOfWeek % 7
val days_str = dow.split(“,”)
var days = newArrayList()
for(var i=0; i<days_str.length; i++)
{
var diff = Integer::parseInt(days_str.get(i)) – today
if(diff < 0)
diff += 7
days.add(diff)
}
val sorted = days.sort
val t = Integer::parseInt(time) / 60 // time in minutes since midnight
var alarm_time = now.withTimeAtStartOfDay.plusHours(t / 60).plusMinutes(t % 60)
var doff = sorted.get(0)
if(alarm_time.isBefore(now))
{
if(doff == 0 && sorted.length > 1)
doff = sorted.get(1) // get next difference
else
doff = 7 // postpone by one week
}
doff
]
val Procedures$Procedure1<ArrayList<LinkedHashMap<String,String>>> get_next_alarm = [ alarms |
var next_alarm = –1
var next_time = now.plusDays(7)
for(var n=0; n < alarms.length; n++)
{
if( alarms.get(n).get(“enabled”) == “1” )
{
val t = Integer::parseInt(alarms.get(n).get(“time”)) / 60 // time in minutes since midnight
var alarm_time = now.withTimeAtStartOfDay.plusHours(t / 60).plusMinutes(t % 60)
var days_str = alarms.get(n).get(“dow”).split(“,”)
var days = newArrayList()
var today = now.getDayOfWeek % 7
for(var i=0; i<days_str.length; i++)
{
var diff = Integer::parseInt(days_str.get(i)) – today
if(diff < 0)
diff += 7
days.add(diff)
}
val sorted = days.sort
var doff = sorted.get(0)
if(alarm_time.isBefore(now))
{
if(doff == 0 && sorted.length > 1)
doff = sorted.get(1) // get next difference
else
doff = 7 // postpone by one week
}
alarm_time = alarm_time.plusDays(doff)
if( alarm_time.isBefore(next_time) && alarm_time.isAfter(now) )
{
next_alarm = n
next_time = alarm_time
}
}
}
// return next alarm
next_alarm
]
rule “alarm polling”
when
Time cron “0 0/5 * * * ?” // every 5 minutes
then
logInfo(“lms”, “Send command”)
lms_out.sendCommand( player_id + ” alarms 0 “ + num_alarm_items.toString + ” filter:all \rexit\r\n“ )
end
rule “receive and parse alarm”
when
Item lms_out received update
then
mutex.lock()
try
{
var String in = URLDecoder::decode(lms_out.state.toString, ‘UTF-8’)
if( in.contains(‘fade:’) )
{
logInfo(“lms”, “cmd: “ + in) // output correctly encoded command
var str_split = in.split(” id:”)
if(str_split.length>1)
{
// clear alarms
alarms = alarms_clear.apply()
for(var n=0; n < str_split.length-1; n++)
{
var s = str_split.get(n+1).split(” “)
var dublicate = false
for(var l=0; l<alarms.length; l++)
{
if(alarms.get(l).get(“id”) == s.get(0))
dublicate = true
}
if( dublicate == false )
{
var current_alarm = newLinkedHashMap();
current_alarm.put(“nr”, n.toString)
current_alarm.put(“player”, player_id)
current_alarm.put(“id”, s.get(0))
logInfo(“lms”, “Alarm “ + current_alarm.get(“nr”) + ” ID: “ + current_alarm.get(“id”))
for(var i=1; i<s.length; i++)
{
var ss = s.get(i).split(“:”)
var val_name = ss.get(0)
var val_value = “”
if( ss.length > 1 )
val_value = ss.get(1)
if( ss.length > 2 )
val_value = ss.get(1) + “:” + ss.get(2)
current_alarm.put(val_name, val_value)
//logInfo(“lms”, “name: ” + val_name + “, value: ” + val_value)
}
alarms.add(current_alarm)
}
}
}
// Update items
update_items.apply( alarms, num_alarm_items )
// get next alarm
var DateTime alarm_time
val Integer na = get_next_alarm.apply(alarms)
if( na >= 0 )
{
//logInfo(“test”, “next alarm: ” + na.toString)
val d = get_next_offset_day.apply(alarms.get(na).get(“dow”), alarms.get(na).get(“time”))
val t = Integer::parseInt(alarms.get(na).get(“time”)) / 60 // time in minutes since midnight
val fade_before_alarm = (alarm_fade_in_before_alarm.state as DecimalType).intValue
alarm_time = now.withTimeAtStartOfDay.plusHours(t / 60).plusMinutes(t % 60).plusDays(d).minusMinutes(fade_before_alarm)
}
// schedule timer
if(alarm_time !== null)
{
logInfo(“test”, “Schedule Light for “ + alarm_time.toString)
if(timer_alarm === null)
{
val fade_time = ((alarm_fade_in_before_alarm.state as DecimalType).intValue + (alarm_fade_in_after_alarm.state as DecimalType).intValue) * 60000
val step_size = 1
val String dim_cmd = “Dimmer,”+ (alarm_dim_target_value.state as DecimalType).intValue.toString + “,” + fade_time.toString + “,” + step_size.toString + “,BedDimmer,0”
timer_alarm = createTimer(alarm_time, [|
timer_alarm = null
universaldimmer.sendCommand(dim_cmd)
timer_alarm = null
])
}
else
{
timer_alarm.reschedule(alarm_time)
}
}
}
}
catch(Throwable t)
{
mutex.unlock()
logError(“lms”, t.toString)
}
finally {
mutex.unlock()
}
end
rule “alarm state”
when
Item alarm1_state received command or
Item alarm2_state received command
then
val str_n = triggeringItem.name.replace(“alarm”, “”).replace(“_state”,“”)
val n = Integer::parseInt(str_n)-1
if( alarms.length > n )
{
val String cmd = update_alarm.apply(alarms.get(n))
logInfo(“lms”, “Update alarm: “ + cmd)
lms_out.sendCommand( cmd + ” \rexit” )
}
else
logInfo(“lms”, “Update: alarm ‘” + n.toString + “‘ not found”)
end