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

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

I was having a problem where the Widgets didnā€™t size nicely, so Iā€™ve spent the morning adding two size options.
One for the Font & Glyphs, another for the IconSets

Iā€™ve added the new widget to the Zip file

Have been using this for a while
Map

default=NA
1=Mo
2=Tu
3=We
4=Th
5=Fr
6=Sa
7=Su
rule "update Tommorows Alarm"

when
	Member of gPerson1Alarm changed or
    Time is noon
    
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[ a | a. name == "Person1_Alarm_"+tomorrowStr+"_T"].state)
end

I now have a problem where by it is returning 0 for tomorrow and therefore not mapping correctly - returns an error as cant mapto 0

Try
val tomorrow = now.plusDays(1).getDayOfWeek

You can add a default to your MAP file
=unexpected day

Thatā€™s got it thanks - I was looking at the map file to see if I could chnage it - I will anyway even though not needed

Hi everybody,
Is there a modification to do in OH 3 ?

The widget work properly, the item is updated at each change but de

PERSON1_ALARM_ACTIVE

Don t switch on at the defined time ?

I use the second widget in english this one

Raf

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

Thank you for help

In case anyone is still using this. It needs to be updated for OH3 because of the way dates are handled in rules.

So instead of
now.getDayOfWeek
you use
now.getDayOfWeek.getValue.intValue

and for
now.getMinuteOfHour
now.getHourOfDay

use

ZonedDateTime.now.getMinute
ZonedDateTime.now.getHour
1 Like