How to get and use Astro sunrise and sunset via OH3 Javascript

  • Platform information:
    openHAB 3.0.1 using Javascript and access via MainUI
    Astro binding added for sunrise and sunset.

I want a rule/script that ensures some lights are ON or OFF after openHab restarts or after the lights’ smart switch comes back ONLINE. My issues have to do with using the Astro binding add-on programmatically from Javascript. Alas, the only way I have figured out how to do this is to “Create Points from Thing” using the Astro’s “Local Sun” thing’s Sunrise “Start Time” and Sunset “End Time”.

Then I have my rule trigger’s like:


and the given script as

var restarted = restarted || (function() {
  /* Hardwired item names... */
  var switchName = "ssE_Power";
  var sunset = "LocalSun_Set_End";
  var sunrise = "LocalSun_Rise_Start";
  var myItemNames = [ switchName, sunset, sunrise ];
  
  var OPENHAB_CONF = Java.type("java.lang.System").getenv("OPENHAB_CONF");
  var JS_CORE = OPENHAB_CONF + "/automation/lib/javascript/core/";
  load(JS_CORE + "rules.js");
  var logger = getLogger("restarted");
  
  /*
   * Array reduction function: Validate name as an item's name.
   * If not valid, allGood is set to false.
   */
  var knownItem = function(allGood, name) {
    if (! items[name]) {
      logger.error(name + " is not a valid item name.");
      allGood = false;
    }
    return allGood;
  };
  
  /* Return true iff all the elements in myItemNames seem to be item names. */
  var confirmKnownItems = function() {
    return myItemNames.reduce(knownItem, true);
  };
  
  /*
   * "Main script".
   * Send a command to switchName reflecting "known reality" -
   * Lights ON between sunset and sunrise; OFF otherwise.
   */
  var restarted = function() {
    if (confirmKnownItems()) {
      var riseItem = items[sunrise];
      var setItem = items[sunset];
      
      /*
       * Yucky, but at least the Javascript programmer
       * now has some absolute time values.
       */
      var riseTime = (new Date("" + riseItem)).getTime();
      var setTime = (new Date("" + setItem)).getTime();
      var nowTime = (new Date()).getTime();
      
      /* Assumes that instances before & after midnight always corresponds to ON... */
      var state = (nowTime >= setTime || nowTime <= riseTime ) ? ON : OFF;
      logger.warn(switchName + " state=" + state);
      events.sendCommand(switchName, state);
    }
  };
  
  return restarted;
})();

restarted();
  1. Is there a way get current Astro sunrise and sunset information without resorting to creating points and hardwiring their item names into the script? I see DSL examples but they have no apparent translation into Javascript.

  2. A similar question for time. The script succeeds using the Javascript Date constructor and luck. I can find DSL examples that barely make sense to me but again, they have no apparent translation into Javascript.

Any insight or direction is appreciated. Even if the entire rule can be done better, I still would appreciate responses to the two issues.

Thanks!

I’m not sure why you want to get away from using items for this. Items are the backbone of OH and the more you struggle to work around that the more you make your own life more difficult.

In this case, however, I think there’s a single item that makes your life much easier. The Astro binding also has a Sun Phase channel which gives you a string value from the list SUN_RISE, ASTRO_DAWN, NAUTIC_DAWN, CIVIL_DAWN, CIVIL_DUSK, NAUTIC_DUSK, ASTRO_DUSK, SUN_SET, DAYLIGHT, NIGHT. As you are interested in just two states, night (between sunset and sunrise) and not night (everything else) this item is a perfect fit. Create this item (you can do it from the Astro thing page itself if you don’t want to go through the semantic model’s equipment and points). Then your rule can simply use:

var state = (items[new_sun_phase_item].state=="NIGHT" ) ? ON : OFF;

without all the time conversions and tests.

3 Likes

Whilst I sympathise with wanting to get away from items for things like this, I also use an item in my JS rules. I do however create the item from JS and wrap access to it in a library, so at least I’m not hardcoding item names and all the functionality is encapsulated in one place.

As for time, I expect that you will need to defer to native Java objects. Personally I use GitHub - js-joda/js-joda: 🕑 Immutable date and time library for javascript (as the OH2.5 used joda-time, and the new Java classes mostly copied it, so this is a good pure-JS option), although if you want to use JS modules you’d need to upgrade your JS runtime (e.g. [jsscripting] ES6+ Support by jpg0 · Pull Request #8516 · openhab/openhab-addons · GitHub).

3 Likes
  1. As others have said, using an Item for this is the most straight forward and is closer to the “openHAB way” of doing things. So it’s probably worth exploring the trouble you’ve had with the creation of the Items. One thing I note is that you refer to the Items a Points. Perhaps you are unaware that Items need not be a part of the model. In fact, I’d expect a large portion of you Items to not be in the model. There are lots of ways to create an Item that is not part of the model: create the Item from the Items settings page, create the Item from the Things’ Channel’s page, create the Item from the Model settings page and remove the semantic tag. As Justin point out, there is a Channel that will tell you the current phase of the sun.

Having said all that, there is in fact an Action provided by the Astro binding which lets you query for various bits of information from Astro on demand in your rule. See Astro - Bindings | openHAB.

  1. Internally and in Rules DSL all Date Time stuff is handled by the Java java.time.ZonedDateTime (and other classes in java.time). If you are coming from Rules DSL or trying to adapt a Rules DSL example you would do well to also use these classes since they are what will be give to you when you ask for a date time from OH and it is what will be expected when you need to give a date time to OH (e.g. creating a Timer). Unfortunately you’ll have to import it in JavaScript (and Python and probably Groovy).
var ZonedDateTime = Java.type("java.time.ZonedDateTime");

Full documentation for ZonedDateTime can be found at ZonedDateTime (Java SE 11 & JDK 11 ).

To get the ZonedDateTime value from a DateTimeType (state carried by a DateTimeItem) you would use

var sunrise = items[sunrise].getZonedDateTime();

To compare ZonedDateTimes you use isBefore and isAfter just like in Joda. In fact ZonedDateTime is very similar to Joda so most of the Rules DSL examples will work as written once you have the ZonedDateTime. However, there are some differences in function names. See the link above for what ZonedDateTime supports.

It is not at all clear what this rule is for. It looks like it’s supposed to trigger when OH restarts or it gets some command from a TP Link. As far as I can tell it spends a whole lot of effort to see if it’s between sunRise and sunSet and turns on a switch accordingly. I guess I’d have the following comments on how the rule could be made better:

  • You seem to go out of your way to convert the Item names to variables. I’m sure you are a programmer at least a little bit and it feels icky to hard code stuff like that. All I can say is all your rules will be much more complicated and much longer if you continue to do that as opposed to just accepting the fact that the stuff your rule needs to interact with and get information from is going to be Items and you’ll have to reference those Items by name. Often the name is hard coded, other times they are constructed based on other information the rule knows. Also note that you cannot change the name of an Item once it’s created. To change the name you have to delete the Item and recreate it with a new name. In short, changing an Item’s name is a big deal. So it is unlikely that these Items will be changing name much anyway and when they do, you have a lot of checking all over OH to make sure it changes everywhere. IMHO adding checks to your rule in case the Item changes it’s name is simply not worth the effort, especially since a reasonable error will be thrown if you try to access an Item that doesn’t exist anyway.

  • The JavaScript Helper Libraries GitHub - CrazyIvan359/openhab-helper-libraries: JSR223-Jython scripts and modules for use with openHAB have a number of functions and utilities for working with time to make it easier to handle without being all that familiar with ZonedDateTime. In addition, my own GitHub - rkoshak/openhab-rules-tools: Library functions, classes, and examples to reuse in the development of new Rules. has some additional time utils mostly focused on creating timers and managing timers. With libraries like these and @jpg0’s one is able to avoid needing to actually work with ZonedDateTimes directly most of the time. But if you are migrating from Rules DSL or adapting a Rules DSL example, you will probably need to use ZonedDateTime directly. It does look like you might be using the Helper Library (loading rules.js). If that is indeed the case, look at openhab-helper-libraries/utils.js at ivans-updates · CrazyIvan359/openhab-helper-libraries · GitHub which has some date time utilities and I know that @CrazyIvan359 is working (as able) on normalizing the JavaScript and Python Helper libraries so a lot of openhab-helper-libraries/date.py at ivans-updates · CrazyIvan359/openhab-helper-libraries · GitHub should hopefully become ported to JavaScript at some point. (Note: Once I’m doing with the Getting Started Tutorial I hope to contribute to the effort there a lot more.)

Anyway, if my understanding of what the rule does is correct, I’d implement it as follows:

No Helper Libraries using OH provided actions as much as possible

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

var sunriseTime = items["LocalSun_Rise_Start"].getZonedDateTime();
var sunsetTime = items["LocalSun_Set_End"].getZonedDateTime();
var now = java.time.ZonedDateTime.now();

var state = (now.isAfter(sunsetTime) || now.isBefore(sunriseTime)) ? ON : OFF;
Log.logWarn("restarted", "ssE_Power state=" + state);
events.sendCommand("ssE_Power", state);

As I said, there’s no need to check for the existence of the Items only to log a message they no longer exist. You’ll get such a message anyway. Also, this whole Script Action will be running in isolation from all your other Script Actions. So defining and calling the function like this doesn’t really do much for you except make the Script Action more complicated than it needs to be.

All you seem to be using from rules.js is the logger so I just access the logger directly.

2 Likes

As a new comer, I am still developing a mental model of openHAB. So I am not necessarily trying to get away from items, rather questioning if they are the right approach. Indeed, it is comforting to know that if everything is a nail, then I only need a hammer, a crowbar, and a lot of band-aids.

Thank you for example. I’ll pursue and see if I can succeed with what you have demonstrated.

Thank you for your insight. Is it possible to provide an example, either meta or real code? A lot of my exploratory scripts use hardcoded item names, and for obvious reasons, I’d like to get away from that.

I’ll need to cogitate on time more… Thanks again.

To clarify where I am coming from: I am brand new to openHAB; I know C and Javascript ES5 (but not the DOM or any other interesting environment); I have zero experience with Java and Python. I have never heard of Joda (but will pursue). So, DSL and openHAB 2.x migration discussions and examples befuddle me as I try to grasp OH3. Alas, most google results that I see usually involve those two topics…

I thank you for you response. There is a lot to digest! But I gather that items are great, names are permanent, and that I had better learn some Java.

Your past experience will benefit you greatly. Java is a lot like C in structure, but you don’t really need to learn it to use OH, just know that you are interacting with Java objects. Python is not so far from JS, just remove all the brackets and keep the indents.

Do not spend time learning about Joda, it is gone in OH3, learn about java.time as Rich indicates. You may find examples that use Joda which you will need to translate to using Java Time, the differences are few for most things.

As Rich indicated, if you need to manipulate time objects you can refer to date.py. I have not had time to translate it to JavaScript, but it mostly deals with Java objects so the translation will largely be 1:1. If you get into it and don’t understand something feel free to ask.

2 Likes

Sure! Typically the pattern I use is to “create or replace” items in my scripts, using a function replaceItem to do this. Once I have the item object, I use item.name elsewhere to refer to it’s name. Here is an example of where I refer to an item name for a created item.

Ok JustinG, I used your one line example,

var state = (items[new_sun_phase_item].state=="NIGHT" ) ? ON : OFF;

to come up with my own two line script:

var state = (items.LocalSun_SunPhaseName === "NIGHT") ? ON : OFF;
events.sendCommand("ssE_Power", state);

and I want to see if I am on the right track. First, I navigated the Main UI “Local Sun” “Channels” tab, and “Add Points to Model” check-boxing the same “Sun Phase Name” menu item. I kept the same name that it presented: “LocalSun_SunPhaseName”.

I then used that name, “LocalSun_SunPhaseName” to index the items array. The resulting Javascript object had no property called “state”. Indeed, it appears entirely opaque (having no properties) to me. But letting Javascript and the logger coerce it to a string showed “DAYLIGHT”.

So, a beginner might know to compare the (stringified) object to “NIGHT” by consulting the Astro binding documentation, and noticing “DAYLIGHT” and “NIGHT” are two of ten documented string values for the “group” “phase” “channel”. Right? Do we believe that the documentation is up to date for OH3?

Or, did I entirely miss the gist of your one liner?

Again, I thank you for your time and patience.

Yes, sorry about that I was typing quickly and conflated the two different options: items and ir.getItem.

  1. items.item_name just gets you the state of the item.
  2. ir.getItem('item_name') returns a more detailed object from the item registry with several properties including state.

Either items.LocalSun_SunPhaseName or ir.getItem('LocalSun_SunPhaseName').state would work, but for this case I think you’ve settled on the clearer option.

I think that is a reasonable expectation (perhaps with some gentle nudging).

I have no reason to suspect otherwise.

This may or may not be an issue but you are using === where Justin’s example is using ==. === will return false if the Strings are the same but of different types. A String Item’s state is of type org.openhab.core.library.types.StringType. “NIGHT” is going to be the JavaScript type string. == will be smart enough to coerce the operands and determine if they are equivalent. But because you are using strict equals it will always return false because the two operands are of different types.

I don’t want to make a blanket rule of thumb for JavaScript until I get some more experience, but in Rules DSL, where === means something slightly different but has the same problem, the rule is to only ever use === and !== when one of the operands is literally null. In all other cases one should use ==.

Why did you choose to use strict equals in the first place?

1 Like

“Experience”, or lack thereof. In my ten years of playing with Javascript, this would have been the first time I would have written ==. I have always interpreted that operator as “I don’t know what I’m doing, but see if the operands are kinda alike”. And indeed, (1) I don’t know what I am doing, and (2) you say that the operands are kinda alike. My == blind-spot also includes not knowing the rules for the operand coercion…

Regardless of org.openhab.core.library.types.StringType (I have yet to figure out where to go, or (obviously) what to do when I get there; it sounds like I might find some Java code), and Rules DSL (that is before my time), I am ready to use the fuzzier operators in my openHAB dealings.

Meanwhile,

Oh dear. My mental model was that items[x] and ir.getItem(x) produced the same thing. Thank you for the reality check.