Help making a JavaScript example

I’m working on documenting YAML rules and rule templates, and I think I’m closed to finished.

However, I thought that it would be nice to include a JavaScript rule template example, but I have no idea how to actually write valid JavaScript rules.

I’m worried that the example is a bit terrible to, because it will check the light status every minute, but I also want to keep it reasonably simple.

Anyway, here goes what I have with Qwen-Coder generated JavaScript:

version: 1
rules:
  "3699580b09":
    template: light-control
    label: Light Control Test
    description: Controls lights based on time.
    config:
      light_item: DemoSwitch
      end_time: 01:00
      start_time: 19:00
    triggers:
      - id: time_trigger
        config:
          cronExpression: 0 0/1 * 1/1 * ? *
        type: Cron
    actions:
      - id: light_action
        config:
          type: JavaScript
          script: |
            var item = items.getItem("DemoSwitch");
            const now = time.ZonedDateTime.now();
            if (now.isAfter(now.withTimeAtStartOfDay().plusHours(parseInt(config.start.split(':')[0])).plusMinutes(parseInt(config.start.split(':')[1]))) &&
                now.isBefore(now.withTimeAtStartOfDay().plusHours(parseInt(config.end.split(':')[0])).plusMinutes(parseInt(config.end.split(':')[1])))) {
              item.sendCommand(ON);
            } else {
              item.sendCommand(OFF);
            }
        type: Script

The script part obviously fails as it is now, it calls non-existing methods. However, instead of me trying to figure out how to do this in JavaScript, I was hoping that some of you that use this could chip in and help me.

I’d also only want the command to be sent if the state isn’t already correct, to not hammer the Item with commands.

Examples should be exemplary, which is why I think it’s a bad idea that I try to figure this out anyway.

edit: Please ignore config.start etc. suggested by Qwen, it doesn’t properly understand rule templates. Instead, the string should be used directly, e.g. 19:00.

@rlkoshak I have a feeling that you’re one of the potential people that could help out here.

With some help from Gemini, I got to this, which at least runs without errors.

version: 1
rules:
  "3699580b09":
    template: light-control
    label: Light Control Test
    description: Controls lights based on time.
    config:
      light_item: DemoSwitch
      end_time: 01:00
      start_time: 19:00
    triggers:
      - id: time_trigger
        config:
          cronExpression: 0 0/1 * 1/1 * ? *
        type: Cron
    actions:
      - id: light_action
        config:
          type: JavaScript
          script: |
            var item = items.getItem("DemoSwitch");
            const now = time.ZonedDateTime.now();

            // Build absolute ZonedDateTime objects for today using the raw string fragments
            let startTime = time.toZDT("19:00");
            let endTime = time.toZDT("01:00");

            // Handle time windows that cross midnight (e.g., 19:00 to 01:00)
            if (endTime.isBefore(startTime)) {
              if (now.isBefore(endTime)) {
                // If it is early morning (e.g., 00:30), move the start boundary to yesterday
                startTime = startTime.minusDays(1);
              } else {
                // If it is evening (e.g., 20:00), move the end boundary to tomorrow
                endTime = endTime.plusDays(1);
              }
            }

            // Verify if 'now' sits inside our execution window
            if (now.isAfter(startTime) && now.isBefore(endTime)) {
              // Only send command if the light isn't already ON
              if (item.state !== "ON") {
                item.sendCommand("ON");
              }
            } else {
              // Only send command if the light isn't already OFF
              if (item.state !== "OFF") {
                item.sendCommand("OFF");
              }
            }
        type: Script

I still don’t know if this is “good example material” though. Is this written reasonably, or should things be done differently in an example?

JSScripting library has a built-in option for this: sendCommandIfDifferent

The only thing about it seems a little off is the every minute cron trigger. I would think that a more common example for beginners would be connecting a light to a sensor event, especially a motion sensor, or to an astro event.

I have a nice simple rule that turns on the light in my pantry when the door is open and turns it off again when the door is closed which has the advantage over a motion control example of not getting into the issue of accurate presence detection.

Looks pretty good to me. I would tweak only two minor things:

Since you are using const and let everywhere else, this should probably be let instead of var.

Between sendCommandIfDifferent and a ternary expression, I would condense this to

let turnOn = now.isAfter(startTime) && now.isBefore(endTime);
item.sendCommandIfDifferent(turnOn ? "ON" : "OFF");

Thanks, I’ll see if I can figure that out.

The point is that it’s an example of how to use a rule template, so I want it to be simple to not “take focus away” from the basics. Running a cron every minute might be excessive, but given that it is precompiled, it should actually be a very light load on the system. What is “worse” is that there will be constant “rule executed” events, but again, I need to keep the example simple.

Good point.

I’ll give it a try.

To demonstrate how the examples are being used, here’s a preview:

I want the focus to be on the placeholders and how they are substituted.

Linking the trigger to a property of the template is a pretty basic thing for a template.

What I would do with this example:

version: 1
rules:
  "light-control-template":
    template: light-control
    label: Light Control Template
    description: Controls lights based on a light sensor
    config:
      light_item: DemoSwitch
      sensor_item: DemoSensor
      end_time: 01:00
      start_time: 19:00
    triggers:
      - id: item_trigger
        config:
          item: {{sensor_item}}
        type: ItemChanged
    actions:
      - id: light_action
        config:
          type: JavaScript
            if(time.toZDT().isBetweenTimes("{{start_time}", "{{end_time}}")){
              items["{{light_item}}"].sendCommandIfDifferent("ON");
            }
            else {
              items["{{light_item}}"].sendCommandIfDifferent("OFF");
            }
        type: Script
  • give it a meaningful UID and name
  • an Item trigger is going to be much more commonly used than a cron trigger so that would make a better trigger for the example
  • isBetweenTimes() handles the case when the start time is after the end time for you and it parses the times for you (i.e. calls time.toZDT() on the arguments)

If I were writing for me instead of for an example I’d use this one liner:

  items["{{light_item}}"].sendCommandIfDifferent((time.toZDT().isBetweenTimes("{{start_time}}","{{end_time}}")) ? "ON" : "OFF");

This was a generated rule, the UID and labels aren’t the ones used in the documentation.

That depends I guess, but I agree that it’s nice to not have the cron timer in the example, so I can change that. That said, if I wanted to have a simple template for switching lights on and off at specific times, I’m not sure that I wouldn’t just use a cron timer. What would the Item be?

I’ll take your word for it, which simplifies everything a whole lot.

In that case just trigger at the start time and the end time

version: 1
rules:
  "light-control-template":
    template: light-control
    label: Light Control Template
    description: Controls lights based on a light sensor
    config:
      light_item: DemoSwitch
      end_time: 01:00
      start_time: 19:00
    triggers:
      - id: start_trigger
        config:
          time: {{start_time}}
        type: TimeOfDay
      - id: end_trigger
        config:
          time: {{end_time}}
        type: TimeOfDay
    actions:
      - id: light_action
        config:
          type: JavaScript
            if(time.toZDT().isBetweenTimes("{{start_time}", "{{end_time}}")){
              items["{{light_item}}"].sendCommandIfDifferent("ON");
            }
            else {
              items["{{light_item}}"].sendCommandIfDifferent("OFF");
            }
        type: Script

But an example that does use an Item trigger and the Item is chosen by a configuration property will be the most common use case.

The rule will actually run after the trigger by few milliseconds so the isBetweenTimes() comparison should still work.

The point isn’t if the rules are useful, but if the mechanisms of using templates are clear. So, I’ve modified it to use the sensor item.

I actually have a somewhat similar rule in use (except that it gradually adjusts the light levels), and I think it’s actually run by a cron trigger. The reason for that, is that I don’t want it to only control the lights “at the thresholds”, but in between as well. In my “real” case, there’s an Item that enables or disables “automatic lighting”, and if it’s enabled, it will start controlling the lights - and override any manual adjustments made (it’s a bit more complicated, it will only override it if the level is set to low when it’s increasing or too high when it’s descending). For such a scenario, I do a check every 2 minutes I think.

Anyway, using the “sensor Item” has revealed a bug I think, the rule won’t initialize because:

Getting handler 'core.ItemStateChangeTrigger' for module 'item_trigger' failed: Cannot invoke "String.contains(java.lang.CharSequence)" because "this.itemName" is null

I have no suitable “sensor Item”, so I just set it to a string Item, but I can’t see why the rule shouldn’t still be accepted. This is the trigger:

    triggers:
      - id: item_trigger
        config:
          item: DemoString
        type: ItemChanged

Did I do something obviously wrong here, or is there something fishy going on?

edit: Forget the error, it was just a wrong property name:

    triggers:
      - id: item_trigger
        config:
          itemName: DemoString
        type: ItemChanged

..works.

Updated preview is ready:

That makes sense to use a cron trigger for that because the light needs to change brightness/color over time. But this is a simple ON/OFF at a certain time. So unless there is some reason you want the make sure the light is ON or OFF every minute, it’s better to use discrete triggers rather than a cron trigger. But in your use case, you do have a reason to check every minute or so which makes a cron trigger very appropriate.

Stuff like this all depends on the details.

Looking at the latest lighting example, it doesn’t actually make much sense. We should use the state of the sensor somehow in the action too. Something like:

            if(time.toZDT().isBetweenTimes("{{start_time}", "{{end_time}}") 
               && items["sensor_item"].quantityState.lessThan("1400 lx")){
              items["{{light_item}}"].sendCommandIfDifferent("ON");
            }
            else {
              items["{{light_item}}"].sendCommandIfDifferent("OFF");
            }

Otherwise the sensor isn’t actually doing anything useful here.

Anybody that wants to help review this so that we can get it into the documentation, the PR is here:

Except for that, thank you both for the help, I consider this “resolved”.