Extending Blockly with new openHAB commands

Metadata gets a little complicated in Blockly because it’s not just a name/value pair but name/value/array of more name/values. Sometimes you just need the value, other times you just need the array of name/values, sometimes you need both.

You could use the inline script for this I think. But it’s awkward. First you need to get the MetadataRegistry and some other classes you’ll need.

    var FrameworkUtil = Java.type("org.osgi.framework.FrameworkUtil");
    var _bundle = FrameworkUtil.getBundle(scriptExtension.class);
    var bundle_context = _bundle.getBundleContext()
    var MetadataRegistry_Ref = bundle_context.getServiceReference("org.openhab.core.items.MetadataRegistry");
    var MetadataRegistry = bundle_context.getService(MetadataRegistry_Ref);
    var MetadataKey = Java.type("org.openhab.core.items.MetadataKey");

Only then can you query for metadata on an Item:

var metadata = MetadataRegistry.get(new MetadataKey(namespace, itemName));
var value = metadata.getValue();
var config = metadata.getConfiguration();
var myEle = config.get('myElement');

In JS Scripting it’s much easier.

var value = items.getItem(item).getMetadataValue(namespace);

Thanks! I’ll try the inline script option first. One of these days I need to start with JS Scripting!

Just to let you know, David, I have started working on bringing these over to the core. It will probably take some time until I have done everything because I need to make sure it is all (backward) compatible and working well with the current blocks. Eventually when I do the Pull-Request to have these merged I will attribute it as contribution from you.

The first two are already working

As you can see I intentionally use input fields which is a bit less compact but allows to use computed values as well.

1 Like

Thank you for letting me know.

I think it’s good how you did it, since variables can also be used in this way. Due to the lack of custom validators I used number_field for the input. They allowed me to restrict the user’s input, but that may not be necessary.

yep, I am also using number fields and I may add validators for value checking later.

edit: I am actually using input fields that allow adding number fields which is different from number fields like you do. The latter would allow a validator (I just did that) but have the disadvantage that variables cannot be input there. The fields that allow variables to be added however do not allow validations to be done.

@rlkoshak Do you also agree we should have generic input fields (that allow variables to be used) over number fields (which could be validated but do not take computed variable fields)?

Ok, now I’m confused. Can we clarify the terminology for the inputs?!

A field allows to enter some data manually but can’t use variables as input. There are several predefined fields like “field_number”, for example.

On the other hand there are input/output values (jigsaw-like connections).

Outputs connect to value inputs. Blocks with an output are usually called value blocks.

I would describe the input for the block as “input value of type number, shadowed with the default(?) ‘math_number’ block”.

True, I was a bit lax with my comment above - sorry about that.

What your block used is called a “field” within Blockly, whereas where another block would fit it in is an “input” as you described above.

I am actually currently working on a mutating date block that let’s you convert back and forth. Stay tuned. I already have a prototype working :smiley:

Variables would be more flexible. There is a whole host of use cases that would be impossible if we can’t use variables I think. When using something that doesn’t make sense in a variable do we get back meaningful errors in the logs?

Yes, most likely: Imagine we would expect a number (for the year) and someone provided “ABC” then he/she would would most likely see an exception.

Hi Yannick,

two things: Do you mind merging the following PR?

I also have another problem, which you may have a solution for:

I try to create a block by adding a math_number block (you could imagine any block type e.g. the dictionary block) to it (not via the toolbox but programmatically).

This is the code that I am using which is working but unfortunately it has a catch:

Blockly.Blocks['number_example'] = {
    init: function () {

      console.log("init")
      let theNumber = this.appendValueInput("theNumber")
        .setCheck('Number')
        .appendField('Enter the number)
      addNumberBlock(this, theNumber, 2040)

with the following function

  function addNumberBlock(context, block, value) {
            let num = context.workspace.newBlock('math_number')
            num.inputList[0].fieldRow[0].setValue(value)
            block.connection.connect(num.outputConnection)
  }

So this actually works but what happens is when I reload the rule in the editor it always creates duplicates of the number block (again and again and again). So somehow I am lacking a way to find out if there is already an existing block. I tried a lot of things and investigated the “parent” block when I am adding the new block(e.g. I checked if there might already be a block as a connection) but after several hours of research and trying out, I basically gave up as I am running out of ideas.

Initially
image

I save the rule

after reloading the page
image

I save the rule

another reload

image

Every reload hits the init()-method and creates a new block

Do you have an idea how to solve that problem?

@ysc

This seems to be an even a more general problem with our setup. I copied a mutator example from a blockly forum because I thought my implementation may be flawed

Blockly.Blocks['oh_mut'] = {
  init: function() {
    var checkbox = new Blockly.FieldCheckbox("TRUE", function(pxchecked) {
      this.sourceBlock_.updateShape_(pxchecked);
    });
    this.setInputsInline(false);
    this.appendDummyInput()
       .appendField("Unit")
       .appendField(new Blockly.FieldTextInput("C01"), "ENName");
    this.appendDummyInput()
       .appendField("PX")
       .appendField(checkbox, 'HasPX');
    this.setPreviousStatement(true, ["C", "EN"]);
    this.setNextStatement(true, ["C", "EN"]);
    this.setColour(40);
  },
  /**
   * Create XML to represent whether the 'pxchecked' should be present.
   * @return {Element} XML storage element.
   * @this Blockly.Block
   */
  mutationToDom: function() {
    var container = document.createElement('mutation');
    var pxchecked = (this.getFieldValue('HasPX') == 'TRUE');
    container.setAttribute('pxchecked', pxchecked);
    return container;
  },
  /**
   * Parse XML to restore the 'pxchecked'.
   * @param {!Element} xmlElement XML storage element.
   * @this Blockly.Block
   */
  domToMutation: function(xmlElement) {
    var pxchecked = (xmlElement.getAttribute('pxchecked') == 'true');
    this.updateShape_(pxchecked);
  },
  /**
   * Modify this block to have (or not have) an input for 'HasPX'.
   * @param {boolean} pxchecked True if this block HasPX is pxchecked.
   * @private
   * @this Blockly.Block
   */
  updateShape_: function(pxchecked) {
    // Add or remove a Value Input.
    if (pxchecked) {
      this.appendValueInput("PX")
          .setCheck('Number');
    } else {
      if (this.childBlocks_.length > 0) {
        for (var i = 0; i < this.childBlocks_.length; i++) {
          if (this.childBlocks_[i].type == 'Number') {
            this.childBlocks_[i].unplug();
            break;
          }
        }
      }
      this.removeInput('PX');
    }
  }
}

However, it turns out that when saving a block with Numbers on it and reloading it we have duplicates as well:

image

Reminder of new user target audience, and the difficulties they experience with their first simple rules

when it happens to involve a Quantity

Do you refer to this topic?

and a few lines below the quote of Yannick

So in the end we might indeed have to provide a comparison block explicitely for QuantityTypes which would use “compareTo” under the hood - even though I’m not very happy about it as it adds to the confusion.

and Rich further below saying that the new Javascript libraries could simplify that in the future even more.

Is that the topic you are referring to?

If yes, it would maybe make sense to add something to the blockly documentation as a hint when using comparison as I agree this happens to a lot of people.

Yes, I have no suggestion for the best solution but this is “pitfall number 2” for new users. A great number of sensor readings come with units in OH3. I’m not sure a note in docs will do.

In other rule systems we can, say, easily compare some temperature sensor with 20°C without even knowing or caring what units the sensor comes in.
It should be just as easy in Blockly, to my mind.

DSL example
if (mysensor.state > 20|°C) ...
The Item state knows it is a Quantity type and what to do, the trickiest part for the DSL user is making the constant a Quantity too.

1 Like

I looked into this a little bit already in hopes of adding it to the JS Scripting helper library but am working on some time utils first.

Unfortunately JavaScript doesn’t really have operator overloading so I can’t just overload + and write some code to check to see if the two operands have units and do something reasonable in both cases. So now I need to find some other approach. I’ve been thinking on it to come up with a good approach but have failed so far. Obviously I can add “add/subtract/multiply/divide/gt/lt/eq…” functions that can work with both but you’ll end up with something only slightly less annoying than what we have now.

I’m definitely open to ideas.

BTW: with my utilities we’ll be able to do stuff like

if(time.toZDT().betweenTimes(items.getItem('Sunset'), '05:00')) { // is now between sunset and 5am

This is all way out of my league, but doesn’t the Java QuantityType object come with all the necessary .multiply , .equals etc.methods? Suggesting an alternative of coercing the ‘operand’ into a Java type as well, instead of trying to make the QT javascript-y.

Type problems are the number one problem among OH rules writers, be they advanced and experienced or new comers. Anything we can do to manage type problems is a good thing. I’m adding the time utils stuff so no matter what, you are one function call away from having a time.ZonedDateTime so anything, be it the state of an Item, now, or just a disconnected “13:54:00” string, can be used without worrying about type.

On top of that, the openhab-js library has a policy, maybe goal is a better word, to present the users with as pure of an environment as possible. This means converting stuff to JavaScript as much as possible. Given how JS works, just about everything except date times and QuantityType can be handled just by converting the stuff to a String.

However, as soon as you hit a QuantityType, you have to:

  1. know that you have actually encountered a QuantityType which requires knowledge outside of the rule
  2. Only use QuantityTypes when working with that value or
  3. Normalize everything to primitive floats/string

Currently the openhab-js library basically strips the units off the QuantityTypes by default, giving you just a string of the number. That is not a satisfactory solution. Forcing the end users to have to use QuantityTypes directly every time they encounter one to even do a comparison is also not satisfactory. I’d prefer to get to a point that the library makes reasonable assumptions when it encounters a QuantityType in one operand and a primitive JS float or string in the other.

For example, if I compare a QuantityType kWh to a "naked’ JS number, automatically assume that the naked number is the same units as the QuantityType and do all the conversion in the background.

As another example, provide a syntax where one can more easily define a constant QuantityType like is available in Rules DSL (e.g. items.getItem('MyKwhItem').state < "123 kWh"; (unfortunately that specific syntax isn’t possible so I’m looking for alternatives) as opposed to items.getItem('MyKwhItem).rawState < new QuantityType('123 kWh')); with the caveat that QuantityType would need to be imported separately).

We both know QuantityTypes are both awesome and super powerful and at the same time a huge pain to work with and a major hangup for users experienced and new. Heck, it’s even a pain for some of the binding developers to deal with. My hope is to find a way to ease some of that pain similarly to how I’m doing for DateTime types.

If I can free the end users from needing to worry too much about type with QuantityTypes, then I’ll have made it so that the end users don’t usually need to worry about type at all any more. The library will make reasonable assumptions or provide dead simple conversion functions for every case. Docs will become simpler and end users will encounter fewer surprises. Win win win! :smiley:

2 Likes

hello is there a way to include these blocks now and test them and if so where can I find the code for the block libraries

What block libraries are you referring to?

In general Just go the Blockly documentation on openHAB docs. At the very end there is a link to a tutorial that Rich wrote which also explains how to include a library.

See Blockly Docs → Tutorials or other useful information

1 Like

@ysc I have provided another PR to redirect all help URLs to the official openhab blockly rules documentation. It should be straight forward to merge as it does not contain any real code changes.