Extending Blockly with new openHAB commands

Just to be clear, and because I promised you a GIF:

  • The days between now and waste on the left is made from the Marketplace (@deibich) block, using the now block from the same library. This creates the code:
var deibich_blockly_ChronoUnit = Java.type('java.time.temporal.ChronoUnit');
var deibich_blockly_ZonedDateTime = Java.type('java.time.ZonedDateTime');

deibich_blockly_ChronoUnit.DAYS.between(deibich_blockly_ZonedDateTime.now(), waste);
  • The days between now and waste on the right is made from the openHAB (3.3M6) block, using the now block from the same openHAB Dates & Times library. This creates the code:
var chronoUnit = Java.type("java.time.temporal.ChronoUnit");
var zdt = Java.type("java.time.ZonedDateTime");

chronoUnit.DAYS.between((zdt.now()),waste);
  • Save for the variable names, the code looks identical to me.
  • The block from the Marketplace is able to be inserted into the comparison block.
  • The block from openHAB is not able to be inserted into the comparison block

Peek 2022-06-15 17-58

So I’m still not quite sure why it’s not working. Is the Marketplace block identifying as a number, and the openHAB one isn’t?

Before I fixed my block it had the type “datetime” which is not equal to “number” on the right hand side. When I say type this is something that is defined in the code by the developer of the block (which is me). You can define what the output type of that block is and you can define what type is allowed as input for “holes” in a block. The library version is defined as “Number” (i double checked it), mine was mistakenly defined as datetime. The equals block makes sure that the left and the right hole get the same type (the blue “0” is a number type block), so it worked with the library block but not with the core block. This is why it it didn’t match the type in the comparison and it prevented it to connect.

However, I have fixed that and will provide the fix today (note that it needs to be reviewed and merged first before the fixes will become available)

2 Likes

That solves it then! Thanks for checking!

Pull request has been provided here: [blockly] fix dt block type, add missing options to dt block, add help urls by stefan-hoehn · Pull Request #1409 · openhab/openhab-webui · GitHub

… Already merged

Announcement: New Blockly Workspace Zooming

I provided another enhancement for blockly: From now on, you can zoom the Blockly Workspace:

image

You can find this in the bottom right corner. This also allows pinching on touch screens or touch pads.

Not to conflict with the “old” Code-Button, this was “moved” into a new version into the footer of the workspace (Kudos to @ysc):

5 Likes

I have a generic Blockly script that runs when a light group is triggered by a motion sensor. It then waits a certain amount of time to turn off the light or in the case of a retrigger of the motion sensor keep the lights on. I’ve tried to do this with the timers available in the OpenHAB section but that doesn’t work properly as timers (or probably the event context) is lost when a different light group is triggered.

I’m now using the common blocks from this community marketplace post: Collection of common blocks. Basically the timer block that keeps the triggering event context. That block actually works great and allows me to create the desired automation.

Is it possible to include the common blocks (or at least the specific timer one that keeps the event context) into the main OpenHAB section? It’s to avoid that I’m dependent on an ‘external’ block instead of what is available in OpenHAB out of the box. Thanks for considering!

As @rlkoshak is our genie on timers I loop him in here:

Rich, the first block is the one that Marco mentions, the second is ours

image

The code difference is minimal

if (typeof this.timers['MyTimer'] === 'undefined' || this.timers['MyTimer'].hasTerminated()) {
  this.timers['MyTimer'] = scriptExecution.createTimerWithArgument(zdt.now().plusSeconds(10),event, function (event) {
  })
} else {
  this.timers['MyTimer'].reschedule(zdt.now().plusSeconds(10));
}

if (typeof this.timers['MyTimer'] === 'undefined' || this.timers['MyTimer'].hasTerminated()) {
  this.timers['MyTimer'] = scriptExecution.createTimer(zdt.now().plusSeconds(10), function () {
    })
} else {
  this.timers['MyTimer'].reschedule(zdt.now().plusSeconds(10));
}

The only difference is that it uses

  • createTimerWithArgument
  • add the event-variable as an additional parameter and forwards it to the function

Could we not just do that in general?
If not, we could give the timer-blocks an additional choice to choose from but I need to check of that would be backward compatible, so it doesn’t break old blocks.

What do you think?

There are subtitles involved here that probably needs to be explored before deciding how to proceed. The big concern is that

  1. sometimes you want to have the event and possibly other stuff as it was when the timer was created

  2. other times you want to have the event and other stuff as it is right now, possibly having been changed since the timer was created on a subsequent rule trigger.

The difference is subtle but important. Ideally both use cases should be supported. I personally prefer using function generators because it lets you “fix” multiple variables rather than just the one variable. For example:

var timerGenerator = (one, two) => {
  return function() {
    // function called by the timer which uses arguments one and two somehow
  }
}

...

scriptExecution.createTimer(zdt.now().plusSeconds(10), timerGenerator(event, foo));

We call a function passing the arguments and get back a function that will get called by the timer. The passed in arguments become fixed and won’t be overridden when the rule runs again. But that’s a pretty big lift for the typical blockly user (though I think it should be possible).

I think maybe splitting the difference would be be better. Rather than forcing the argument passed to the timer to be the event, why not let us pass our own argument, including event? But I think that would involve creating a new “timer with argument” block where one can select the event or any other variable defined.

The first Blockly video tutorial has been published on the 21st of June on the openHAB Video Channel.

The second Blockly video tutorial has been published on the 5 of July on the openHAB Video Channel.

See the announcement here for further information.

3 Likes

Hello All

Has anyone worked with location type items in Blockly? are there any speciality blocks that i could simply import?

I would like to be able to calculate the distance to a moving object (a car) and get a message from openHAB when the object (car) is closer to home than a certain distance.

I created this one for this usecase: Geographic information system - #5 by timl But the “location type” itself is not supported yet. I worked with native float (latitude and longitude).

Regards,
Tim

cool i will check it out

Hello Stefan

this is the example of trying to use the “state” of a location item, as a string, to split off the latitude and longitude for further use.

edit; i guess the reason for not being able to treat the state of the location item as a string is that it is returned as an object

Hey @joriskofman
I also love blockly… here a much smaller code. You can create a list from the location item and access directly the first (0) and second (1) item:

Regards,
Tim

1 Like

Good one @timl your code also works if there would be an elevation field in the location item, mine would break

I finally found the time to implement it.

There is a pull request to be merged:

If everything goes well, it will be available in the next release.

These are the blocks that will be available soon:

oh_text_replace: a new text replace block that is missing in the standard text block section

oh_taggeditems: retrieve a list of items identified by one or multiple tags

image

oh_list_concatenate: allows to concatenate two lists into a new lists

oh_get_persistvalue: add new previousState (new mutating block)

image

oh_zdt_fromText: add time support

image

7 Likes

I added some more functionality to allow skipping the same state until it finds a different one to the current state:

This for example allows to find out how long a door has been kept open and if it exceeds a certain time, a notification is sent to the user to close it

You can even go further and use the tag block to gather ALL windows that are tagged with OpenState by running this rule once a minute:

Note: to be able to use the skip=true, rrd4j isn’t sufficient as it does not support skipping. You may add influxdb for that purpose and set the default persistence to that.

2 Likes

@ysc @rlkoshak

I’d like to have your opinion on the following topic.
Since we have introduced our blocks it more and more became clear to me that the naming of the word “item” in the blocks is actually not really consistent and it can lead to the wrong usage of the blocks

note that I just yesterday implemented some more rigid type checking on these which was actually flawed over the past months but still type checking can be “circumvented” when introducing variables (type checking is then not applied).

So what is the problem - let’s have a look at these blocks:

This here is actually not an item but only an item name that is picked by the user - it is essentially a string

image

This will return an item object

image

This doesn’t send a command to an item but to an item name to be precise

Again, in 4.0 I have implemented checks, that make sure that the blocks cannot be attached to the wrong place (that is still possible in 3.4) but with vars it can be pretty confusing and to be honest I myself fell intro that trap:

Looks right, doesn’t it?

Unfortunately it isn’t and it crashes. Let me show why:

“get state of item” expects an item-name and not an item which the variable actually contains.

Here are proposals

  1. It isn’t that big of an issue and we just have to document it well enough
  2. We rename the green item to become more precise

image

which then would become the following for all the other blocks (which might be a bit lengthy)

  1. We could try to detect in jsscripting (graalvm) if a name or an item object is given and therefore become more forgiving and resilient. However, that doesn’t improve the current implementation based on Nashorn.

So what’s your vote or do we have an option 4?

@ysc @rlkoshak

I‘d also like to have your opinion on a Blockly topic.
During the portation of Blockly to JS Scripting, @stefan.hoehn and I found out, that the Date & Time parsing Blocks for Nashorn generate ZonedDateTimes in the UTC time zone (indicated by a Z at the end of the ZonedDateTime‘s string representation).

In JS Scripting, the ISO parsing is using the system‘s time zone, e.g. for me Europe/Berlin, which is +01:00 from UTC.

We now have to discuss how to proceed: Should Blockly stay with using the UTC zone or use the system time zone?

IMO, we should use the system time zone, because:

  • openHAB DateTime Items also use the local system time zone (I found this out when I retrieved the state of one of my DateTime Items: REST API returned 2022-12- 27T19:59:16.696974+0100
  • Using UTC can lead to serious problems when comparing with DateTime Items, because they are in the system time zone:
    Imagine you want to check whether a DateTime Item is after 13:04. You create a Block, put 13:04 into it (you wouldn‘t think of: which zone am I, which Zone requires the Block, what do I have to input). Your Item has 13:05 as state, but because it is in your time zone +01:00, it is UTC 12:05. So you end up with a check, that says: 13:05 is not after 13:04, because you did not expect that they have different time zones. The check would only work if it was: 13:05 (+01:00) is after 12:04 (UTC).

IMO using UTC as time zone for user inputted times is a bug that breaks comparisons.
I propose to accept the system time zone that is used in JS Scripting‘s ISO parsing, even though the behaviour of some Blocks will be slightly different, e.g. when calculating the hours between a ZonedDateTime in winter and summer, because in Europe we have summer and winter time and the system time zone seems to respect that. But in that case as well, the system time zone IMO is the better solution, as it returns the correct value of hours between those two DateTimes in your zone.

  1. If even you fell into this trap I’m certain it will become a significant issue among others.

  2. I like this as the bare minimum thing to do. One of the strengths of Blockly is it’s self describing. Being explicit here helps with that.

But, what happens if you have the following?

get state of item -> get item -> item name [MyItem]

Does get state of item only support the Item name or can I also pass it the Item Object?

Could it make sense that we only have a get item and adjust the other blocks to always expect an Item Object?

Or the converse, always expect an Item name and the other blocks adjusted to convert to an Item Object?

I’m nervous about both ideas, especially when you start working with Group members.

And this begs the question, why do we have both get state of item and item name in addition to get x of item (which presumably can get both the state and Item name)? Is it because we get the name and state so often that we created a separate block for convenience or is there a deeper reason?

  1. Why would this be limited to graalvm? It seems possible to do with Nashorn too. I like this idea but it will make the underlying code a tiny bit more complicated.

  2. Ignoring the chaos and breaking changes, what if we did away with item name and instead we get the Item Object in that block? Adjust the other blocks to always want an Item Object instead of just the name. We’d need to keep the get item block for cases where an Item’s name is constructed in the code of course.

There’s a third option, change to use LocalDateTime. I’m not pushing that, just mentioning that it could be an option.

I would tend to agree. We need to use what ever gets stored in DateTimeTypes, which I believe uses the system time zone. If we are consistent with that we should have a smoother overall experience.