Individual Alarm clock for each Day of week, with adjustable duration

Here is my rules file. It is basically a copy from yours above, but with my family!
I get this error in my log:

2017-11-27 16:18:06.052 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model AlarmClock.rules'
2017-11-27 16:18:06.077 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'AlarmClock.rules' is either empty or cannot be parsed correctly!
2017-11-27 16:18:06.327 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'AlarmClock.rules' has errors, therefore ignoring it: [33,23]: mismatched input ',' expecting '}' [40,4]: mismatched input '}' expecting ']'

but, in SmartHome Designer I get A LOT of errors:

(Line#6)
Multiple markers at this line 
- Incorrect number of arguments for type Function3; it cannot be parameterized  with arguments <string, map, Function0, Function0, Boolean> 
- Function0 is a raw type. References to generic  type Function0 should be parameterized

(Line#7)
Type mismatch: cannot convert from (Object, Object, Object, Object)=>Timer to Function3<string, map, Function0, Function0, Boolean>

(Line 20)
Cannot refer to the non-final variable dayName inside a lambda expression

(Line 23, 24, 25)
The method or field day is undefined

(Line 28, 36)
Type mismatch: cannot convert from OnOffType to String

(Line 31, 35)
The method apply() is undefined for the type Object

(Line 33)
Multiple markers at this line 
- mismatched input ',' expecting '}' 
- The method or field put is undefined for the type Object

(Line 37)
The method put(Object, Object) is undefined for the type Object

(Line 40)
mismatched input '}' expecting ']'

Whew! I am sure it’s something silly, but my lack of experience with openHab functions is glaringly obvious!

This starts at Line 1:

import org.eclipse.xtext.xbase.lib.Functions
import java.util.Map

val Map<String, Timer> timers = newHashMap   

val Functions$Function3 <String, Map<String, Timer>, Functions$Function0, Functions$Function0, Boolean> processAlarm = 
	[person, timers, alarmStart, alarmStop |

		 var dayName= "NA"
		 switch now.getDayOfWeek{
			  case 1: dayName = "MO"
			  case 2: dayName = "DI"
			  case 3: dayName= "MI"
			  case 4: dayName= "DO"
			  case 5: dayName= "FR"
			  case 6: dayName= "SA"
			  case 7: dayName= "SO"
		 }

		 val person1On = gAlarm.members.filter[s | s.name == person+dayName].head.state

		 if(person1On == ON){
			  var sollMinute = (gAlarm.members.filter[s | s.name == person+day+"_M"].head.state as Number).intValue
			  var sollHour = (gAlarm.members.filter[s | s.name == person+day+"_H"].head.state as Number).intValue
			  var runTime = (gAlarm.members.filter[s | s.name == person+day+"_RUN"].head.state as Number).intValue

			  if(sollMinute == now.getMinuteOfHour && sollHour == now.getHourOfDay) {
					sendCommand(person+"_Active", ON)

					// alarmStart is a passed in lambda that implements the stuff to do for that person's alarm
					alarmStart.apply()

					timers.put<person, createTimer(now.plusMinutes(runTime), [|
						 // alarmStop is a passed in lambda that implements the stuff to do at the end of that person's alarm
						 alarmStop.apply()
						 sendCommand(person+"_Active", OFF)
						 timers.put(person, null)
					])
			  }
		 }

		 true
	]

//===================================================================================
rule "Alarms"
when
    Time cron "0 0/1 * * * ?"
then

    val Functions$Function0 <Boolean> CraigAlarmStart = [|
			sendCommand(Light_US_MasterRoom_CraigsLamp, ON)
        true
    ]
    val Functions$Function0 <Boolean> CraigAlarmStop = [|
			sendCommand(Light_US_MasterRoom_CraigsLamp, OFF)
        true
    ]

    // define functions for each person, if there is nothing to do for that person you can use [| true]
    // as illustrated with person2 below.

    processAlarm.apply("Craig", timers, CraigAlarmStart, CraigAlarmStop)
    processAlarm.apply("Ellen", timers, [| true], [| true]) // PERSON2 has nothing to do when the alarm starts and ends so we pass empty lambdas
    processAlarm.apply("Nick", timers, [| true], [| true]) // PERSON2 has nothing to do when the alarm starts and ends so we pass empty lambdas
    processAlarm.apply("Marcus", timers, [| true], [| true]) // PERSON2 has nothing to do when the alarm starts and ends so we pass empty lambdas
    
end

What version of Designer are you using?

val Functions$Function3 <String, Map<String, Timer>, Functions$Function0, Functions$Function0, Boolean>

There are four arguments plus the return type in the < >. You need to change it to Functions$Function4

sendCommand(person+"_Active", ON)

change to

sendCommand(person+"_Active", "ON")

It’s not a problem but this will make the error go away.

					timers.put<person, createTimer(now.plusMinutes(runTime), [|
						 // alarmStop is a passed in lambda that implements the stuff to do at the end of that person's alarm
						 alarmStop.apply()
						 sendCommand(person+"_Active", OFF)
						 timers.put(person, null)
					])

should be

					timers.put(person, createTimer(now.plusMinutes(runTime), [|
						 // alarmStop is a passed in lambda that implements the stuff to do at the end of that person's alarm
						 alarmStop.apply()
						 sendCommand(person+"_Active", "OFF")
						 timers.put(person, null)
					]))

As a general rule, when the entire lambda is marked as an error it is because of a mismatched or missing (), {}, or [].

Those changes made all the errors except for those related to the fact that I don’t have the items defined go away in ESH Designer 0.8.

Note that ESH Designer is about to become deprecated in favor of the VSCODE plugin which, now that I’ve been using it a bit, is way nicer than ESH Designer. And soon it will have the same syntax checking that ESH Designer has, only better because it will be complete.

Thank you kind sir!
I will make those changes when I get home. I am using version 0.8 which I find is good but it seems to have memory issues. I find it gets slower and slower as I use it. I find this on both my Linux and windows machines. I would like to try the VSCode when it’s ready.

Also many thanks to Oliver for this thread. It’ll save me a lot of time coding which my wife appreciates :wink:

Craig

Hi Craig,

glad you like it, and sorry for not answering earlier. But real world stuff…you know how it is. Thanks Rich for helping out, you’re the best :slight_smile:
Regards,
-OLI

It is pretty usable now and it is so much nicer to use. It adds in all the stuff I missed that ESHD lacked, like a terminal where I can tail OH’s log as I edit. And I can install other plugins and do my Ansible and Python develop all in the same tool now. I’d say don’t wait. If you run into a thorny syntax problem you can always bring up ESHD again to debug those parts until the language server stuff gets finished.

1 Like

OK. Getting closer!

I get as a validation error:

Function0 is a raw type. References to generic type Function0 should be parameterized

on the line:

val Functions$Function4 <String, Map<String, Timer>, Functions$Function0, Functions$Function0, Boolean>

and

Cannot refer to the non-final variable dayName inside a lambda expression

as an error on:

val person1On = gAlarm.members.filter[s | s.name == person+dayName].head.state

I fixed this one by declaring dayName outside Function4

However, still no love… in my test I ended up with:

2017-12-02 10:19:57.723 [DEBUG] [.eclipse.jetty.server.HttpConnection] - org.eclipse.jetty.server.HttpConnection$SendCallback@643313[PROCESSING][i=null,cb=Blocker@13da6c5{null}] generate: DONE (null,[p=150,l=150,c=32768,r=0],false)@COMMITTED
2017-12-02 10:20:00.003 [DEBUG] [rg.quartz.core.QuartzSchedulerThread] - batch acquisition of 1 triggers
2017-12-02 10:20:00.003 [DEBUG] [org.quartz.core.JobRunShell         ] - Calling execute on job DEFAULT.AlarmClock.rules#Alarms#0 0/1 * * * ?
2017-12-02 10:20:00.052 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule Alarms: Cannot assign a value in null context.
2017-12-02 10:20:00.166 [DEBUG] [rg.quartz.core.QuartzSchedulerThread] - batch acquisition of 0 triggers
2017-12-02 10:20:00.166 [DEBUG] [org.quartz.core.JobRunShell         ] - Calling execute on job MapDB_SchedulerGroup.Commit_Transaction
2017-12-02 10:20:00.195 [DEBUG] [rg.quartz.core.QuartzSchedulerThread] - batch acquisition of 1 triggers
2017-12-02 10:20:00.929 [DEBUG] [org.eclipse.jetty.server.HttpChannel] - sendResponse info=null content=DirectByteBuffer@6a3773[p=0,l=177,c=32768,r=177]={<<<event: message\nda...usInfoEvent"}\n\n>>>\\":\\"49.2611283,-...ptions":[]},"ty} complete=false committing=false callback=Blocker@13da6c5{null}

in my log, and nothing triggered.

Cannot assign a value in null context.???

That should be a warning, not an error.

You can get rid of that by using Functions$Function0<Boolean> in the arguments to the lambda.

Add logging to your rules to verify that they are triggering it not. If not, what ever event the roles the to trigger is not happening or the trigger is not working.

Add logging to your lambdas to see if they are being executed and where they are failing.

Will do when I get back home!

All,

it took some time, but to save others from debugging the same snippet, please find included my working version of these rules, items en widget.
I translated them to English.

At least, the debugging of the snippets learned me a lot about working with lambda’s and groups :stuck_out_tongue_winking_eye:

Full code is attached, here parts of it:

Group gAlarm (gPersistme)
//Alarm Monday
Switch PERSON1_ALARM_MON      "ALARM Monday"                        <clock>	        (gAlarm)
Number PERSON1_ALARM_MON_H    "Alarm start Monday Hour [%s]"        <calendar>	    (gAlarm)
Number PERSON1_ALARM_MON_M    "Alarm start Monday Minutes [%s]"     <calendar>	    (gAlarm)
Number PERSON1_ALARM_MON_RUN    "ALARM Monday Duration [%s]"        <clock>	   

<../..>

****************************
import org.eclipse.xtext.xbase.lib.Functions
import java.util.Map

var Map<String, Timer> timers = newHashMap   


val Functions$Function4 
<String, Map<String, Timer>, Functions$Function0<Boolean> , Functions$Function0<Boolean> , Boolean> 
processAlarm = [person, timers, alarmStart, alarmStop |
    
        
    var dayTempName="NA"

    switch now.getDayOfWeek()
    {
        case 1: dayTempName = "MON"
        case 2: dayTempName = "TUE"
        case 3: dayTempName = "WED"
        case 4: dayTempName = "THU"
        case 5: dayTempName = "FRI"
        case 6: dayTempName = "SAT"
        case 7: dayTempName = "SUN"
    }

    val dayName=dayTempName

    val myPerson = gAlarm.members.filter[ s | s.name == person+dayName ].head.state
	if(myPerson == ON)
    {
        val startMinutes = (gAlarm.members.filter[ s | s.name == person+dayName+"_M" ].head.state as Number).intValue
        val startHour = (gAlarm.members.filter[ s | s.name == person+dayName+"_H" ].head.state as Number).intValue
		val runTime = (gAlarm.members.filter[ s | s.name == person+dayName+"_RUN" ].head.state as Number).intValue

        if(startMinutes == now.getMinuteOfHour && startHour == now.getHourOfDay) 
        {
		    sendCommand(person+"ACTIVE", "ON")

			// alarmStart is a passed in lambda that implements the stuff to do for that person's alarm
			alarmStart.apply()

			timers.put(person, createTimer(now.plusMinutes(runTime), [|
		    // alarmStop is a passed in lambda that implements the stuff to do at the end of that person's alarm
			    alarmStop.apply()
				sendCommand(person+"ACTIVE", "OFF")
				timers.put(person, null)
			]))
		}
    }
		

	true
]

//===================================================================================
rule "Alarms"
when
    Time cron "0 0/1 * * * ?"
then
    val Functions$Function0 <Boolean> person1Start = [|
        logInfo("Alarms", "alarm start code")// alarm start code
        true
    ]
    val Functions$Function0 <Boolean> person1Stop = [|
        logInfo("Alarms", "alarm stop code")// alarm stop code
        true
    ]
    

    
    // define functions for each person, if there is nothing to do for that person you can use [| true]
    // as illustrated with person2 below.

    processAlarm.apply("PERSON1_ALARM_", timers, person1Start, person1Stop)
    // processAlarm.apply("Ellen", timers, [| true], [| true]) // PERSON2 has nothing to do when the alarm starts and ends so we pass empty lambdas
    
end


rule "init"
when
    System started   
then
    logInfo("init alarm", "Initialise alarm items if needed")
    
    gAlarm.members.filter[ i | i.type.toString == "Switch" ].forEach[ s | 
        if (s.state.toString == "NULL")
            {
                s.postUpdate(OFF)
            }
    ]
    gAlarm.members.filter[ i | i.type.toString == "Number" ].forEach[ s | 
        if (s.state.toString == "NULL")
            {
                s.postUpdate(0)
            }
    ]
    
end

I hope this is helpful for others.

brgds,

Raf

Alarm.widget.json (2.9 KB)
scheduler.items.txt (2.8 KB)
scheduler.rules.txt (2.9 KB)

2 Likes

A bit has changed in the more than year since the last post and there are some minor improvements.

Don’t import the Functions. It is already imported for you.
If you don’t care about the return value, use a Procedures instead.

A better way to define a lambda is

    val processAlarm = [String person, Map<String, Timer>, Procedures$Procedure0 alarmStart, Procedures$Prpocedure0 alarmStop |

        ...
    ]

Use a filter before your forEach to exclude the NULLs.

gAlarm.members.filter[ i | instanceof SwitchItem && i.state != NULL ].forEach[ s | s.postUpdate(OFF) ]

Thanks for posting!

This has been really useful - I have it as my alarm clock now
I have conacated the H/M variables to a time variable to display in the basic UI
However what I haven’t managed to do is to pass that time and date to another item that displays tomorrow’s alarm time
eg - using above variables

if i=(now.getDayOfWeek) Person1_Alarm_Tomorrow=("Person1_Alarm"+TheDay+"_T")

Where Person1_Alarm_Tomorrow is the display item and _T is the time string used to display the label on the frame
What I want is a simple item to display tomorrows alarm time on a habpanel
Maybe I’m making too complicated using rules based approach

Trigger a rule once a day and on any change to any one of the alarm Items. Midnight is probably a good choice.

Get the current day. val today = now.getDayOfWeek

Get tomorrow. val tomorrow = (today + 1) % 7

Convert the number for tomorrow to the characters representation. I recommend using a .map file and the transform Action. val tomorrowStr = transform("MAP", "alarm.map", tomorrow.toString)

Now we can pull the parts of the alarm out from the gPerson1Wecker Group by name.

    Person1_Alarm_Tomorrow.postUpdate(gPerson1Wecker.members.findFirst("Person1_Alarm_"+tomorrowStr+"_T"))

Note the use of postUpdate.

Rule ‘update Tommorows Alarm’: An error occurred during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst(java.lang.Iterable,org.eclipse.xtext.xbase.lib.Functions$Function1) on instance: null

is what I get with

rule "update Tommorows Alarm"

when
	Member of gPerson1Alarm changed
    
then
	
    val today = now.getDayOfWeek
    val tomorrow = (today + 1) % 7
    val tomorrowStr = transform("MAP", "alarm.map", tomorrow.toString)
    Person1_Alarm_Tomorrow.postUpdate(gPerson1Alarm.members.findFirst("Person1_Alarm_"+tomorrowStr+"_T"))
end

alarm.map

default=NA
1=Mo
2=Tu
3=We
4=Th
5=Fr
6=Sa
7=Su

in Items

String Person1_Alarm_Tomorrow "Tomorrow [%s]" <none> (gPerson1Alarm)

Make sure gPerson1Alarm exactly matches the Group definition.

Try this syntax for findFirst.

Person1_Alarm_Tomorrow.postUpdate(gPerson1Alarm.members.findFirst[ a | a. name == “Person1_Alarm_”+tomorrowStr+"_T"]. state)

Yup that’s works - Thank you
I’ve also taken Person1_Alarm_Tomorrow out of the group gPerson1Alarm…hadn’t considered that changing the former would trigger the rule also!
Oddly I copied and pasted and it didn’t work as the quotation marks " were wrong - but sorted that

Is there a way to implement a limit in the widget so it doesn’t go below 0 or above 59 when setting the minutes? By default it is possible to set the alarm to “negative” minutes or beyond the end of the hour.

alarm_negative_minutes

The sitemap version of the interface implements this, I just don’t know how to do this in the HABPanel version.

1 Like

Hi

I’ve been looking for something like this for a while.

Thank you so much for your efforts :slight_smile:

To give a little back, I have added two rules to limit the hours and minutes.

I created two extra groups

Group gMinute
Group gHour

and assigned them to all the minutes and hour ITEMS like this

Group gAlarm (StartPersist) // Where "StartPersist" is the catch all for Peristance
Group gMinute
Group gHour

//Alarm Monday
Switch PERSON1_ALARM_MON      "ALARM Monday"                        <clock>	        (gAlarm)
Number PERSON1_ALARM_MON_H    "Alarm start Monday Hour [%s]"        <calendar>	    (gAlarm,gHour)
Number PERSON1_ALARM_MON_M    "Alarm start Monday Minutes [%s]"     <calendar>	    (gAlarm,gMinute)
Number PERSON1_ALARM_MON_RUN    "ALARM Monday Duration [%s]"        <clock>	        (gAlarm)
//Alarm Tuesday
Switch PERSON1_ALARM_TUE      "ALARM Tuesday"                       <clock>	        (gAlarm)
Number PERSON1_ALARM_TUE_H    "Alarm start Tuesday Hour [%s]"       <calendar>	    (gAlarm,gHour)
Number PERSON1_ALARM_TUE_M    "Alarm start Tuesday Minutes [%s]"    <calendar>	    (gAlarm,gMinute)
Number PERSON1_ALARM_TUE_RUN    "ALARM Tuesday Duration [%s]"       <clock>	        (gAlarm)
//Alarm Wednesday
Switch PERSON1_ALARM_WED      "ALARM Wednesday"                     <clock>	        (gAlarm)
Number PERSON1_ALARM_WED_H    "Alarm start Wednesday Hour [%s]"     <calendar>	    (gAlarm,gHour)
Number PERSON1_ALARM_WED_M    "Alarm start Wednesday Minutes [%s]"  <calendar>	    (gAlarm,gMinute)
Number PERSON1_ALARM_WED_RUN    "ALARM Wednesday Duration [%s]"     <clock>	        (gAlarm)
//Alarm Thursday
Switch PERSON1_ALARM_THU      "ALARM Thursday"                      <clock>	        (gAlarm)
Number PERSON1_ALARM_THU_H    "Alarm start Thursday Hour [%s]"      <calendar>	    (gAlarm,gHour)
Number PERSON1_ALARM_THU_M    "Alarm start Thursday Minutes [%s]"   <calendar>	    (gAlarm,gMinute)
Number PERSON1_ALARM_THU_RUN    "ALARM Thursday Duration [%s]"      <clock>	        (gAlarm)
//Alarm Friday
Switch PERSON1_ALARM_FRI      "ALARM Friday"                        <clock>	        (gAlarm)
Number PERSON1_ALARM_FRI_H    "Alarm start Friday Hour [%s]"        <calendar>	    (gAlarm,gHour)
Number PERSON1_ALARM_FRI_M    "Alarm start Friday Minutes [%s]"     <calendar>	    (gAlarm,gMinute)
Number PERSON1_ALARM_FRI_RUN    "ALARM Friday Duration [%s]"        <clock>	        (gAlarm)
//Alarm Saturday
Switch PERSON1_ALARM_SAT      "ALARM Saturday"                      <clock>	        (gAlarm)
Number PERSON1_ALARM_SAT_H    "Alarm start Saturday Hour [%s]"      <calendar>	    (gAlarm,gHour)
Number PERSON1_ALARM_SAT_M    "Alarm start Saturday Minutes [%s]"   <calendar>	    (gAlarm,gMinute)
Number PERSON1_ALARM_SAT_RUN    "ALARM Saturday Duration [%s]"      <clock>	        (gAlarm)
//Alarm Sunday
Switch PERSON1_ALARM_SUN      "ALARM Sunday"                        <clock>	        (gAlarm)
Number PERSON1_ALARM_SUN_H    "Alarm start Sunday Hour [%s]"        <calendar>	    (gAlarm,gHour)
Number PERSON1_ALARM_SUN_M    "Alarm start Sunday Minutes [%s]"     <calendar>	    (gAlarm,gMinute)
Number PERSON1_ALARM_SUN_RUN    "ALARM Sunday Duration [%s]"        <clock>	        (gAlarm)


Switch PERSON1_ALARM_ACTIVE "ALARM PERSON1 Active"                                  (gAlarm)
Number PERSON1_ALARM_PRESETS "ALARM Preset Load"				    <calendar>	    (gAlarm)

then just added these two rules

rule "Confine Minute"

when	
	Member of gMinute changed
then
	
	   logInfo("Minute Limiting", "Minute Value of "+triggeringItem.name+" Currently "+triggeringItem.state)
	
	if (triggeringItem.state > 59) {
	
	postUpdate(triggeringItem.name,"0")
	}
	
		if (triggeringItem.state < 0) {
	
	postUpdate(triggeringItem.name,"59")
	}
	
end


rule "Confine Hour"

when	
	Member of gHour changed
then
	
	   logInfo("Hour Limiting", "Hour Value of "+triggeringItem.name+" Currently "+triggeringItem.state)
	
	if (triggeringItem.state > 23) {
	
	postUpdate(triggeringItem.name,"0")
	}
	
		if (triggeringItem.state < 0) {
	
	postUpdate(triggeringItem.name,"23")
	}
	
end

The only thing I need to work out now is how to change the colours of the Glypths

image

Update

I couldn’t get 14 of those widgets to fit within the Android Tablet screen in an elegant way, so I had to create a custom layout using SVG files.

Considering that I would compare my artistic skills to that of a feral infant, I’m quite happy with the result.

Since you are using HABPanel, I’m pretty sure someone had created a time picker widget that you could use to set a DateTime I tem and save a lot of these Rules. Then you just need a ride to run one a day to change the date to today.

Thanks Rich

I’ve tried the other two timeline schedulers and using this seems to be the nearest I can get to a perfect solution for a particular use case.
You remember we chatted about using Google Calendar… I suggested it and it wasn’t welcomed as well as I had hoped.

Hi

I’ve managed to tweak this widget to suit the Velbus alarm times,
image

it can be downloaded here

http:/ /www. mdar. co. uk /dl/ forum_assets/ Velbus_Alarm.widget.zip – Removed

It requires the following naming convention, Where “AlarmName” is the only variable that has to be set in the widget.

AlarmName_Enabled
AlarmName_Wake_H
AlarmName_Wake_M
AlarmName_BedTime_H
AlarmName_BedTime_M

Update 2020 Jan
The widget has evolved quite a bit, if anyone wants to try it, the zip file is here
http://www.mdar.co.uk/dl/openhab2/Velbus_Alarm_V2.widget.zip

This uses an extra Item named

AlarmName_Type - Which in Velbus has the states “Global” and “Local”


Update

The minute and hour constraint rule has been updated, thanks to Rossko