AlarmClock doesn´t fire

Sure!

var Timer timerAlarm = null

rule "Alarm Clock"

when

    Item AlarmClock changed

then

    if (newState instanceof DateTimeType) {

        val epoch = newState.toLocaleZone.zonedDateTime.toInstant.toEpochMilli

        logInfo("alarm", "Scheduling alarm for {} ({})", newState.toLocaleZone, epoch)

        if (timerAlarm !== null) {

            logInfo("alarm", "Reschedule alarm")

            timerAlarm.reschedule(newState.toLocaleZone.zonedDateTime)

        } else {

            logInfo("alarm", "New alarm")

            timerAlarm = createTimer(newState.toLocaleZone.zonedDateTime.minusSeconds(5), [ |

        ----  YOUR ACTIONS HERE ----

                logInfo("alarm", "Alarm expired")

                timerAlarm = null

            ])

        }

    } else {

        if (timerAlarm !== null) {

            timerAlarm.cancel

            timerAlarm = null

        }

        logInfo("alarm", "Alarm canceled")

    }

end

Saddly I’m not very good at programming but it would be cool to be able to use more than one alarm (at different times).

I haven’t been following this thread very closely but if this rule is being considered for some docs there is a little bit of adjustements that we can do to make it a little cleaner.

var Timer timerAlarm = null

rule "Alarm Clock"
when 
    Item AlarmClock changed
then

    // RLK: Fail fast and avoid the extra indentations later so it will be easier to read
    if(!newState instance of DateTimeType){
        return;
    }

    // RLK: Since epoch is only used to log and it's essentially meaningless from a human point of view
    // let's leave it out.
    logInfo("alarm", "Scheduling alarm for {}", newState.toLocaleZone)

    if(timerAlarm !== null) {
        logInfo("alarm", "Rescheduling alarm")
        // RLK: It's unnecessary to get the local date time, you can get the ZonedDateTime straight from
        // the DateTimeType.
        timerAlarm.reschedule(newState.getZonedDateTime)
    } else {
        logInfo("alarm", "New alarm")
        // RLK: I think one second would be more than enough to avoid the problem
        timerAlarm = createTimer(newState.toZonedDateTime.minusSeconds(1), [ |
            --- YOUR ACTIONS HERE ----
            logInfo("alarm", "Alarm expired")
            timerAlarm = null
        ])
    }
end

When the marketplace for rules is ready for use, we can write a rule template that users can just install and configure it to call a Script when the timer goes off. That would be pretty nice as all they would have to write is the “— YOUR ACTIONS HERE —” part and configure the triggering Item.

The comments look good, but I have two issues with this rule:

  1. The first if must be if(!(newState instanceof DateTimeType)){ (instanceof as one word and additional brackets)
  2. After my change in 1. I get the following error when the rule is executed: 'toLocaleZone' is not a member of 'org.openhab.core.types.State'

Grumble… Rules DSL and it’s crappy type inference.

We need to cast newState to DateTimeType. That’s not required in JavaScript. It also occurs to me that we need this rule to run at System started and when the rule reloads so the timer gets recreated. Until we have a rule reloaded trigger though, we’ll need to manually trigger the rule when it’s changed which can be done through MainUI or the Karaf console. I’ve updated the rule to support that as well.

var Timer timerAlarm = null

rule "Alarm Clock"
when 
    Item AlarmClock changed or
    System started
then

    // RLK: Fail fast and avoid the extra indentations later so it will be easier to read
    if(!(AlarmClock.state instanceof DateTimeType)){
        return;
    }

    // RLK: Since epoch is only used to log and it's essentially meaningless from a human point of view
    // let's leave it out.
    val alarmTime = AlarmClock.state as DateTimeType
    logInfo("alarm", "Scheduling alarm for {}", alarmTime.toString)

    if(timerAlarm !== null) {
        logInfo("alarm", "Rescheduling alarm")
        // RLK: It's unnecessary to get the local date time, you can get the ZonedDateTime straight from
        // the DateTimeType.
        timerAlarm.reschedule(alarmTime.getZonedDateTime)
    } else {
        logInfo("alarm", "New alarm")
        // RLK: I think one second would be more than enough to avoid the problem
        timerAlarm = createTimer(alarmTime.toZonedDateTime.minusSeconds(1), [ |
            --- YOUR ACTIONS HERE ----
            logInfo("alarm", "Alarm expired")
            timerAlarm = null
        ])
    }
end

Ultimately this will be a good candidate for a Rule Template that can be installed through the up coming marketplace. In that case it’ll need to be written as a managed rule and I would write it in JavaScript and the YOUR ACTIONS HERE would be a call to a UI managed Script that is configured when installing the rule template.

Anyway, in JavaScript as a managed rule (i.e in the UI) it would look something like:

triggers:
  - id: "1"
    configuration:
      itemName: AlarmClock
    type: core.ItemStateChangeTrigger
  - id: "3"
    configuration:
      startlevel: 100
    type: core.SystemStartlevelTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >-
        // Change to ID of the Script Rule

        var alarmScriptId = "alarm_script"; 


        var FrameworkUtil = Java.type("org.osgi.framework.FrameworkUtil");

        var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");

        var ZDT = Java.type("java.time.ZonedDateTime");


        var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Alarm");


        // Declaring the variable like this preserves the value from one run to the next

        this.timer = (this.timer === undefined) ? null : this.timer;


        // Body of the timer, calls a UI Script with the ID "alarm_script"

        var callScript = function() {

          logger.info("About to call script action");
          
          // Get the RuleManager
          var _bundle = FrameworkUtil.getBundle(scriptExtension.class);
          var bundle_context = _bundle.getBundleContext()
          var classname = "org.openhab.core.automation.RuleManager"
          var RuleManager_Ref = bundle_context.getServiceReference(classname);
          var RuleManager = bundle_context.getService(RuleManager_Ref);

          RuleManager.runNow(alarmScriptId);
        }


        // No alarm schedculed

        if(items["AlarmClock"].class != DateTimeType.class || items["AlarmClock"].getZonedDateTime().isBefore(ZDT.now())) {
          logger.info("No alarm scheduled");
          if(this.timer !== null) {
            this.timner.cancel();
          }
        }

        // create or schedule a timer to run at the configured time

        else {
          logger.info("Scheduling alarm for " + items["AlarmClock"]);

          if(this.timer !== null) {
            logger.info("Rescheduling alarm");
            this.timer.reschedule(items["AlarmClock"].getZonedDateTime());
          }
          else {
            logger.info("Setting a new alarm");
            this.timer = ScriptExecution.createTimer(items["AlarmClock"].getZonedDateTime(), callScript)
          }
        }
    type: script.ScriptAction

This part handles the timer. Until it’s a rule template with parameters the only thing the user may need to change is the first line of the rule to use the ID of the rule to run when the alarm goes off. Once it becomes a rule template that can be a parameter.

The Script looks like the following:

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Alarm");

// Alarm clock code goes here
logger.info("The alarm went off!");

Click on Settings → Scripts → +

Enter “alarm_script” as the “Unique ID” and something meaningful for Name and Description. Choose your language of choice (even Rule DSL) for what to do when the alarm goes off. If you enter something other than “alarm_script”, update the main rule.

For users that want to use text based rules, pay attention to the UID of the rule as it appears in MainUI and update the alarmScriptId as appropriate. Hint: in .rules files the ID is the name of the .rules file with a one up number based on the order the rules appear in the file.

I separated the code that handles the AlarmCode Item’s changes and the management of the Timer from the code the user needs to provide because:

  • all the users need to care about is their code and they don’t need to mess with timers or error checking
  • the users can implement the code that executes in any language they are comfortable with; they are not forced to use just the one in the example
  • when it becomes a rule template, it will allow for an easier upgrades/updates that won’t wipe out the user’s custom code.

Note: I’ve tested the JavaScript stuff. I did not test the Rules DSL rule so there may still be typos.

That has to be a getZonedDateTime instead of toZonedDateTime. Otherwise the rule looks good now. I’ll test it a few days and then update the docs.

Just trying out on OH 2.5 via DSL Rule. I’m setting the Locale/TimeZone correctly in my OH installation and Paper UI. I’m on a +2 Timezone (Berlin/Amsterdam, CEST right now). I get the following with an alarm at 8:15 AM from my Android Phone via the OH App:

2021-08-02 22:38:19.701 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-03T04:15:00.000+0000

I’m puzzling since 2 or more hours… The rule executes correctly (“Alarm expired” triggered at the right time). However if I add the Item in a Sitemap, it shows 2021-08-03T06:15+02:00
(2 hours after the one sent by Android OH App and 4 hours after the Alarm time set by me).

Rule copied with pride from @rlkoshak :

import org.openhab.model.script.actions.Timer

var Timer timerAlarm = null

rule "Alarm Clock"
when 
    Item AlarmClock changed or
    System started
then

    if(!(AlarmClock.state instanceof DateTimeType)){
        return;
    }

    val alarmTime = AlarmClock.state as DateTimeType
    
    logInfo("alarm", "Scheduling alarm for {}", alarmTime.toString)

    if(timerAlarm !== null) {
        logInfo("alarm", "Rescheduling alarm for{}", alarmTime.toString)
        timerAlarm.reschedule(new DateTime(alarmTime.getZonedDateTime.toInstant().toEpochMilli()))
    } else {
        logInfo("alarm", "New alarm")
        timerAlarm = createTimer(new DateTime(alarmTime.getZonedDateTime.toInstant().toEpochMilli()).minusSeconds(1), [ |
            //--- YOUR ACTIONS HERE ----
            logInfo("alarm", "Alarm expired")
            timerAlarm = null
        ])
    }
end

I tried all the possible conversions in the Rule to show the right Alarm Time… No way, just exhausted. I ran out of ideas… May somebody enlighten me, please.

The Android sends times to openHAB as Epoch milliseconds, which have no timezone information attached, so they’ll always be interpreted as UTC times. That’s fine, openHAB can mix’n’match zones if it knows what they are. Hence +0000 shown here, because an Epoch must be in
UTC

The puzzle is why it’s not showing 06:15+0000, the same instant as 08:15+0200

Sitemap based UI will fight you here, whatever zone you give to a DateTime Item, UI will present it in local zone form. For diagnostics, stick to what you can see in events.log and get rules to log in openhab.log

Feels like you’ve got a double time offset somewhere.
There are four “clocks” involved here.

The Android clock + zone. If the alarm is going off at the correct time, that must surely be correct. But you can find out, check what Epoch numeric is sent for a given alarm time.

The operating system time and locale.

The Java clock is derived from that, but may have a different locale. A clue here is to look at the timestamping in the logs - that is the Java clock, not openHABs.

The openHAB clock is derived from Java, but may have a different locale. The clue here is what ‘now’ sets into a DateTime type.

If the alarm is set on the next day, then 3 hours behind instead of 2. Some tests here:

Alarm was at 7.45
2021-08-03 20:49:57.307 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-04T04:45:00.000+0000
2021-08-03 20:49:57.314 [INFO ] [eclipse.smarthome.model.script.alarm] - New alarm

Alarm was at 20.53
2021-08-03 20:51:11.824 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-03T18:53:00.000+0000
2021-08-03 20:51:11.839 [INFO ] [eclipse.smarthome.model.script.alarm] - Rescheduling alarm for2021-08-03T18:53:00.000+0000
2021-08-03 20:53:00.009 [INFO ] [eclipse.smarthome.model.script.alarm] - Alarm expired

Next alarm is at 7.45
2021-08-03 20:53:01.013 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-04T04:45:00.000+0000
2021-08-03 20:53:01.024 [INFO ] [eclipse.smarthome.model.script.alarm] - New alarm

Alarm at 10.00
2021-08-03 21:02:04.676 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-04T07:00:00.000+0000
2021-08-03 21:02:04.686 [INFO ] [eclipse.smarthome.model.script.alarm] - Rescheduling alarm for2021-08-04T07:00:00.000+0000

alarm at 12.00
2021-08-03 21:02:48.081 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-04T09:00:00.000+0000
2021-08-03 21:02:48.089 [INFO ] [eclipse.smarthome.model.script.alarm] - Rescheduling alarm for2021-08-04T09:00:00.000+0000

Alarm at 19.00
2021-08-03 21:04:43.367 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-04T16:00:00.000+0000
2021-08-03 21:04:43.377 [INFO ] [eclipse.smarthome.model.script.alarm] - Rescheduling alarm for2021-08-04T16:00:00.000+0000

Alarm at 21.00
2021-08-03 21:05:17.329 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-04T18:00:00.000+0000
2021-08-03 21:05:17.337 [INFO ] [eclipse.smarthome.model.script.alarm] - Rescheduling alarm for2021-08-04T18:00:00.000+0000

Alarm at 21.07
2021-08-03 21:06:09.149 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-03T19:07:00.000+0000
2021-08-03 21:06:09.157 [INFO ] [eclipse.smarthome.model.script.alarm] - Rescheduling alarm for2021-08-03T19:07:00.000+0000

The Android clock + zone. If the alarm is going off at the correct time, that must surely be correct. But you can find out, check what Epoch numeric is sent for a given alarm time.

Rule modified as follows to get epoch

    val alarmTime = AlarmClock.state as DateTimeType
    
    logInfo("alarm", "Scheduling alarm for {}", alarmTime.getZonedDateTime.toString)
    val epoch = alarmTime.getZonedDateTime.toInstant.toEpochMilli
    logInfo("alarm", "Scheduling alarm for EPOCH {}", epoch)

For alarm at 7.45 AM (CEST)

2021-08-03 21:20:55.396 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-04T04:45Z
2021-08-03 21:20:55.403 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for EPOCH 1628052300000

The operating system time and locale.

OS reports following

openhabian@openHABian:~ $ timedatectl
               Local time: Tue 2021-08-03 21:23:27 CEST
           Universal time: Tue 2021-08-03 19:23:27 UTC
                 RTC time: Tue 2021-08-03 19:23:27
                Time zone: Europe/Berlin (CEST, +0200)
System clock synchronized: yes
              NTP service: inactive
          RTC in local TZ: no
openhabian@openHABian:~ $

The Java clock is derived from that, but may have a different locale. A clue here is to look at the timestamping in the logs - that is the Java clock, not openHABs.

see logs above (is that what you mean?)

The openHAB clock is derived from Java, but may have a different locale. The clue here is what ‘now’ sets into a DateTime type.

added following to log the Rule:

            logInfo("alarm", "what is now: " + new DateTime(now))

Output is:

2021-08-03 21:28:42.837 [INFO ] [eclipse.smarthome.model.script.alarm] - what is now: 2021-08-03T21:28:42.782+02:00
2021-08-03 21:28:42.843 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for 2021-08-04T04:45Z
2021-08-03 21:28:42.849 [INFO ] [eclipse.smarthome.model.script.alarm] - Scheduling alarm for EPOCH 1628052300000

There must be a fundamental invisible error somewhere tricking me.

Does the java version have inflluence?

openhabian@openHABian:~ $ java -version
openjdk version "11.0.9" 2020-10-20 LTS
OpenJDK Runtime Environment Zulu11.43+88-CA (build 11.0.9+11-LTS)
OpenJDK Client VM Zulu11.43+88-CA (build 11.0.9+11-LTS, mixed mode)

Not a lot of help. By this time its been through two conversions on your suspect system.

Just change your AlarmClock Item to a Number type temporarily, and see what Android sends you as a raw Epoch number, with no conversions at all.
Stick that in an online converter and make sure that is your expected alarm instant in UTC, i.e. clocktime 2 hours different from Berlin.

That’s interesting.
I’d expect scheduler to run on Java time, but have doubts now.

I meant the timestamps in the log, the first part of the log entry. Surprisingly, we’ve had people before not notice the logging is timestamping two hours out.

But this looks good

assuming that was really the time when you ran the rule.

What I get with AlarmClock as Number in the items:

2021-08-04 21:41:18.494 [vent.ItemStateChangedEvent] - AlarmClock changed from NULL to 1628138700000

Which converter results in the following:
Assuming that this timestamp is in milliseconds :

**GMT** : Thursday, August 5, 2021 4:45:00 AM
**Your time zone** : Thursday, August 5, 2021 6:45:00 AM [GMT+02:00](https://www.epochconverter.com/timezones?q=1628138700000) DST
**Relative** : In 9 hours

Still at 4.45 AM… indeed the rule triggers at the right time (7.45 AM my time and not at 6.45 AM, as
AlarmClock in DateTime format would say:

2021-08-04 22:01:41.883 [vent.ItemStateChangedEvent] - AlarmClock changed from NULL to 2021-08-05T06:45:00.000+0200

.

I’ve set RTC in UTC since I’m using a Rpi with RTC module. I followed a guide on that. However I cannot remember why the RTC shall have been set to UTC. The fakehwclock was also removed since of physical RTC.

still more puzzling… I’ve double checked my Android for correct time and locale. Are some other checks I can run? Is that related to a OH 2.5 issue with my Java version, e.g., library not fully compatible?

Okay, you wanted 07:45 local time, the Android has sent 04:45 UTC (because that’s all it can send in Epoch form).

openHAB converts the Epoch to local time, 06:45:00.000+0200, exactly in agreement with the online calculator.

I reckon your Android has messed up. Still on Winter time?

Might be worth looking at this more closely. As I understand it, when the “alarm goes off” on the Android, it also sends an updated (but empty?or tomorrows?) new value to openHAB.
This will trigger the rule, entirely independently of any openHAB scheduling.
Look in your logs, see what happens at 0745.

I use Network provided time and timezone (incl. daylight saving). I can try to set timezone manually and double check.

This was a trial I did: Alarm set at 20:53. Next alarm would be next day at 7:45.

At 20:53, the Alarm expired is a logInfo in the Rule triggered by changed Item AlarmClock. At the same time my phone was ringing.

I’m looking at the Android App log and the ItemUpdateWorker is updating to 4:15 +0 for an alarm at 7:15 +2.
→ So you’re right on the App sending wrong value (should have been 5:15+0). App version is 2.18.2-beta.

BTW, thank you for your replies :slight_smile:

The app just sends the time it gets from the alarm clock api. Some clock apps tend to schedule the alarm earlier than it actually is. This makes sure the device is awake when the real alarm goes off.

What alarm app are you using? Some weeks ago I migrated from my previous solution (Alarm clock) to this one. It’s working flawless for me with the default alarm app (by Google).

The APK is com.android.deskclock on a Xiaomi Redmi 9 stock. I believe that’s a replacement of the Google one on the MIUI.

Should I try with a different App?

Yes, please try https://play.google.com/store/apps/details?id=com.google.android.deskclock

just tried… same behavior :face_with_head_bandage:

Circling back around on this. Now that we have a Marketplace that supports Rule Templates, I’ve added the Alarm Clock rule (post 68 above) to the Marketplace.

Now instead of copy and paste, users can find and install the rule through MainUI. It’s listed under Automations. Look for the Alarm Clock icon labeled “Alarm Clock Rule”.

Install the template then when you go to create a new rule you can select the template from the list. Select the AlarmClock Item and the Script/Rule you want to run when the alarm goes off and you are golden. No copy and paste and no messing with the code (unless you want to).

This is available in OH 3.2 M3 and later.

1 Like

Great!

I opened https://github.com/openhab/openhab-android/issues/2724 as a reminder to adapt the docs.

1 Like

Is it only me who doesn’t find it on the marketplace or am I looking at the wrong place? (I am on M6)

You don’t need it any more. You can use a Time is <Item> trigger instead. A little while after that new trigger was added I removed the “published” tag so it won’t show up in everyone’s list any more.

2 Likes