New feature: globally provisioned widgets (i.e. with OSGi bundles)

Hi all,

I would like to announce a new feature merged yesterday into HABPanel: the ability to provision widgets through HABPanel’s configuration, instead of manually uploading .json to individual panel configurations files using the GUI.

What this feature notably allows is that from now on, widgets can easily be distributed as bundles (or part of bundles) installed to your openHAB installation like any another - for instance, by dropping them into the addons directory or perhaps using the Eclipse IoT Marketplace. And since a few widgets need additional assets to work (stylesheets, iconsets, even scripts), they can also be distributed and registered in the bundle as a single “professional” package suited for more complex widgets.

I prepared an example implementation to showcase a typical scenario; this example widget controls a control for a rollershutter, which comes with:

  • some icons;
  • a stylesheet (so the template can use CSS classes instead of inline styling);
  • the widget itself: this file is actually the .json file you would export from HABPanel’s custom widget list.

The OSGi component (RollershutterWidgetProvider.java) will then, upon activation:

  1. register the icons in icons/ as a standard iconset (it extends AbstractResourceIconProvider);
  2. map the static resources in the static/ folder to a dedicated path on the HttpService in activate() line 127;
  3. read the widget’s .json file from the bundle and add its contents as a new property to HABPanel’s configuration, also in activate() line 134 - I chose this approach instead of a dedicated class because it’s simple enough and in order to avoid any depedency to the HABPanel bundle.

deactivate() will unregister everything - the static resources and the configuration variable.

The name of these configuration properties must be widget.<your_widget_id> i.e. they should begin with “widget.”, followed by an unique id (be careful, there are no id collision protection measures).

Once the bundle is installed:

openhab> bundle:list
...
246 | Active   |  80 | 2.1.0.201704182210    | Rollershutter widget example for HABPanel

It will simply pick up the configuration proprties; the widgets will show up in HABPanel’s widget list and “Add widget” menu:

There are icons and tooltips to make the distinction between “globally provisioned” and “user-defined” custom widgets. As indicated, a globally provisioned widget cannot be modified within the GUI, but can be cloned to a user-defined widget you can then modify.

Regarding usage, it behaves like any other widget. Simply drop it into a dashboard:

You might notice the new icons - they actually come from the iconset registered by the bundle along with the widget. Here’s the widget’s template:

<div class="example-rollershutter" oc-lazy-load="['/habpanel-resources/example-widget/rollershutter.css']">
  <div class="rollershutter-header">{{ngModel.name}}</div>

  <button class="btn btn-default up-button" ng-click="sendCmd(config.item, 100)">
    <img src="/icon/fts_shutter_up?iconset=knx-rollershutter&format=svg" class="invert"></img>
  </button>

  <div class="middle-buttons">
    <button class="btn btn-primary button-left" ng-click="sendCmd(config.item, 30)">
      <img src="/icon/fts_shutter_30?iconset=knx-rollershutter&format=svg"></img>
    </button>
    <button class="btn btn-primary button-middle" ng-click="sendCmd(config.item, 50)">
      <img src="/icon/fts_shutter_50?iconset=knx-rollershutter&format=svg"></img>
    </button>
    <button class="btn btn-primary button-right" ng-click="sendCmd(config.item, 70)">
      <img src="/icon/fts_shutter_70?iconset=knx-rollershutter&format=svg"></img>
    </button>
  </div>

  <button class="btn btn-default down-button" ng-click="sendCmd(config.item, 0)">
    <img src="/icon/fts_shutter_down?iconset=knx-rollershutter&format=svg" class="invert"></img>
  </button>
</div>

Also note how the stylesheet is lazy-loaded from the registered static resources path, then the classes from it simply applied to the HTML elements. Next I’ll try to come up with another example including custom scripting (https://jsfiddle.net/jbf2f7k7/2/)

My hope is that this new feature will pave the way to a better distribution and provisioning mechanism for widgets; I invite you to play with the sample project and don’t hesitate to ask questions or provide feedback!

Cheers :slight_smile:

9 Likes

whoaw, seems cool ! (but maybe more diffucult to handle at first) ! You say “even scrips” ? Like real scripts in the “static” folder of this maven-widget-folder-tree, not requiring using the oc-lazy-load directive (that seems not beeing able to console.log) ?

Eventually, is it possible to update only habpanel and not the rest of openhab2 ? (as “when it works don’t touch”, first thing I did with the new repo system is download everything and disable distant repository to prevent auto-magically-breaking-things like I had once )

No you still need to use the oc-lazy-load directive (example), but during the bundle activation this line:

httpService.registerResources("/habpanel-resources/example-widget", "static", null);

will make files in the static/ folder (in the bundle) e.g. static/script.js accessible at http://<your-openhab>:8080/habpanel-resources/example-widget/script.js.
You can lazy load JS libraries, controllers, directives and so on - as well as stylesheets.

My advice would then be to design your widgets by putting additional resources in your conf/html directory until it works well, then make the bundle and change the instances of /static in the template (the conf/html directory is mapped to /static by openHAB) by your custom mapping path (what you provide as 1st argument to HttpService.registerResources()).

To upgrade HABPanel from the stable version uninstall it first then drop the .jar from https://openhab.ci.cloudbees.com/job/openHAB2-Bundles/org.openhab.ui$org.openhab.ui.habpanel/lastSuccessfulBuild/ to your addons directory.

Done: Gauge widget :wink:

Hey Yannik, this is really excellent!
I always had a UI in mind, which should be extensible through custom widgets (possibly even coming with specific bindings for certain devices).

Just some remarks:

register the icons in icons/ as a standard iconset

Not sure this is a good idea - icon sets will appear in the selection options for Basic UI and they are expected to be “complete” - we should thus probably treat the icons for custom widgets differently.

map the static resources in the static/ folder to a dedicated path on the HttpService

It probably should not be the widget bundle, which decides for (any) uri here - I’d rather think this should be derived from a unique widget id.

The OSGi component (RollershutterWidgetProvider.java2) will then … register … unregister …

I wonder if it is at all necessary to implement an OSGi service with all its complexity. Wouldn’t it be theoretically enough to treat all files as data that should be put into a pre-defined folder structure and the HABPanel bundle itself could scan other bundles for their content and do the registration etc. on their behalf? It would make writing custom widgets probably much easier, don’t you think?

Hi @Kai, nice to see some feedback from you - it’s a fresh new feature, my approach was to put it out there maybe a bit boldly as an experiment, and discuss the design afterwards; it’s not too late to make adjustments while there aren’t any widgets making use of it.

The reason was indeed to improve the visibility of some of the great widgets that are already out there, notably those tied to particular hardware or a particular binding (Netatmo, Ecobee, Nuki lock, Onkyo, Hue, Russound, Weather… just to name a few), people using this hardware or these bindings as well as HABPanel might be interested in them but don’t know they exist. They should IMHO be presented to the user and installed very easily or even automatically if possible. Moreover, some of those widgets use external resources (CSS, JS, images, icons…), usually there are instructions provided to put them in conf/html/etc so they’re accessible at static/etc - this is too complex; a bundle automating everything is way easier.

I had the idea to allow certain bindings to optionally (un)provision the widgets at their leisure if they detect HABPanel (for instance only when adding a new Thing of a certain type), so honestly I must say I feel a bit dirty with my solution, but the goal was to keep things simple and avoid at all costs a dependency to the HABPanel bundle, which wouldn’t make sense in this case. Then I realized bindings themselves should perhaps not deal with particular UI concerns at all :slight_smile:

This is very interesting and would indeed probably be enough, I didn’t think it was possible to scan other bundles like you suggest. I also agree with your concern about the “pollution” of the URL namespace with arbitrary mappings by widget bundles, those mappings (as well as the registration of the widgets themselves) should be properly handled centrally. I’ll have to wrap my head around all this - I’m naturally open to further suggestions.

Didn’t realize this either… Thanks for pointing that out. The icon sets are great for providing an item’s state and get the right icon in return, but of course other solutions would work too.

Sorry, I’m afraid I don’t understand.

@ysc : Where we can find jar file for rollershutter ? I can’t find it in marketplace.

I haven’t released a .jar for this one. This project was only meant to be an example for widget developers.

@ysc @Kai

I eventually wanted to encapsulate the Russound widget into a bundle as this thread describes. However, it brings up the question - where to store the code. While I can certainly host it in my own github repository - it doesn’t feel ‘right’ to me that something in my repository depends on something in the openhab repository and how closely tied they are (if I make changes to the binding code, it will always come with changes to this widget).

I’d like to add it to the openhab tree - but mixing it in with the binding code doesn’t feel ‘right’ either - especially since habpanel is in a different project itself.

What are your feeling if we created a ‘org.openhab.ui.habpanel.widgets’ in the openhab/org.openhab.ui.habpanel project that people can fork and put their widgets in?

So for the russound, I’d have the org.openhab.binding.russound in the oh2 addons project and an ‘org.openhab.ui.habpanel.widgets.russound’ in the habpanel project. That to me feels more ‘natural’ way of handling complex widgets.

Hi @tmrobert8,
Thanks for following up on this; admittedly this method of packaging widgets never took off (it’s a little bit more involved to build bundles and the way it’s been designed has its shortcomings…). The initial goal was to:

  1. allow the distribution of widgets’ related assets;
  2. explore the possibility of distributing widgets as part of bundles unrelated to HABPanel, like bindings: this is why I tried to avoid bundle imports, even dynamic or optional, or another dependencies to HABPanel (since it’s a separate project not related to ESH but openHAB only) and still be able to provision a widget, however there is a clear lack of separation of concerns in this approach, and that might be seen as an architectural flaw;
  3. explore the possibility of distributing widgets - among other UI components - through the Eclipse IoT Marketplace, since it’s easier than making PRs to official repositories and allows to license your work differently. Unfortunately the HABPanel-not-being-an-ESH-component issue remains, on top of that the Marketplace add-on is only able to install bindings (and maybe rule templates) currently. See https://github.com/eclipse/smarthome/issues/3320 for a discussion on this subject. The suggestion that followed there was, we should try to define some kind of standard for UI widgets, possibly based on Web Components, so that the widgets could be reused in other web UIs like Paper UI or web views in native apps - and to make them hopefully easier to package. This is a significant amount of planning and work though, so honestly I don’t know when (if ever) this idea will materialize.

To answer your question, your proposal is interesting, but since the HABPanel repository is for building the HABPanel bundle only, I’m not sure how to include specialized widgets as well that shouldn’t be part of it. For now, and until we find a steady course of action, I think it’s better to include your widget in the widget gallery with clear instructions on how to update the static assets - like you did!
Maybe use working sets in Eclipse to work in parallel on the binding and the widget in different Git repositories?

If something new comes up, this thread will be updated - please share your thoughts though!

Thanks!
Yannick