Extending Blockly with new openHAB commands

Hi Yannick,

is there a way to build the openhab-docs locally, so I can check how my my writing would look like before I commit and push it.

I looked at your block post about vuepress and also the ruby files in the repo but I wasn’'t sure what has to be run (I checked the README, the files in .vuepress and also tried to figure out the build process online but couldn’t). Sorry if overlooked something.

UPDATE (SOLVED):

  • I installed vuepress via npm install vuepress
  • I then ran npm run build-preview

It runs fine and creates all the files in the vuepress folder.

There is a downside with that method though because as it runs “ruby prepare-docs.rb --delete-sources” it deletes a lot of files in the git repos which appears to become lots of deleted file. The alternative would be to do the following two step individually

  • npm run prepare-docs
  • npm run build-only

which doesn’t delete the files and does render the html but in the end ends with an error like

error Error rendering /addons/actions.html: false
undefined
error Error rendering /addons/bindings.html: false
undefined
error Error rendering /addons/io.html: false
undefined
error Error rendering /addons/transformations.html: false
undefined
error Error rendering /addons/persistence.html: false
undefined
error Error rendering /addons/voices.html: false
undefined
TypeError: Cannot read property 'label' of undefined
    at Proxy.render (addons/actions.md?547e:1:72156)
    at VueComponent.Vue._render (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue/dist/vue.runtime.common.dev.js:3559:22)
    at resolve (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8444:27)
    at waitForServerPrefetch (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8316:3)
    at renderComponentInner (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8455:3)
    at renderComponent (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8412:5)
    at renderNode (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8323:5)
    at resolve (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8450:5)
    at waitForServerPrefetch (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8316:3)
    at renderComponentInner (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8455:3)
    at renderComponent (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8412:5)
    at RenderContext.renderNode (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8323:5)
    at RenderContext.next (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:2611:23)
    at cachedWrite (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:2464:9)
    at renderStringNode$1 (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8530:5)
    at RenderContext.renderNode (/Users/xxxx.xxxx/Development/checkout/openhab-docs/node_modules/vue-server-renderer/build.dev.js:8321:5)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! openhab-docs@3.0.0 build-only: `vuepress build .`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the openhab-docs@3.0.0 build-only script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:

Can you explain that?

Wouldn’t it be a good idea to add that to the README.md?

Building the docs is a mess, yes. And it’s not well documented.

I wouldn’t even try to build them locally, just create a PR and you’ll get a preview built for you.
It’s updated every time you push new commits.

1 Like

Just wanted to say thanks for the work done on Blockly! I managed to migrate a number of old rules in jython easily to Blockly with only very limited programming knowledge. Very pleased with the result!

I was wondering if Blockly would ever support reading item metadata? That would allow for more flexibility in my rules. Or could I use the inline script option for that for now? In case of the latter would somebody have an example that reads item metadata in an inline script I can study?

Thanks, I appreciate it.

What exactly are you missing besides these?

So basically a way to access (read) custom metadata namespaces from the item.

For example; I would like to define for a dimmer light item a default delay (how many seconds) before it is turned off again when the light is triggered by a motion sensor. Or the actual brightness for a given time of day.

With the old jython rule I used each light item had meta data like this (in a custom namespace):


value: ""
config:
  light_action:
    active:
      modes:
        DAY:
          low_lux:
            brightness: 98
          lux_trigger: 0
        EVENING:
          low_lux:
            brightness: 98
          lux_trigger: 0
        MORNING:
          low_lux:
            brightness: 75
          lux_trigger: 0
        NIGHT:
          low_lux:
            brightness: 5
          lux_trigger: 0
        PARTY:
          low_lux:
            brightness: 98
          lux_trigger: 0
    inactive:
      delay: 180
    lux_item_name: LocalSun_DiffuseRadiation

I’m probably going to simplify this configuration but I guess it’s a good example. I could then create very generic Blockly rules that take the meta data as input for specific actions.

But perhaps it’s not really part of the standard Blockly journey and requires actual programming. I don’t know what the consensus is what should or should not be part of Blockly…

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