Nibe uplink binding

Would love to test that. Do you have a link to the Dev. version? can I just install it over the existing one?

best regards
Mark

I have backported the changes to 2.4 as the 2.5 SNAPSHOT version is no longer compatible with 2.4.0 release of openhab.

2.4 backport: http://friese-de.eu/openhab2/org.openhab.binding.nibeuplink-2.4.0-SNAPSHOT.jar
2.5 snapshot: http://friese-de.eu/openhab2/org.openhab.binding.nibeuplink-2.5.0-SNAPSHOT.jar

Hi @AlexF, i hope you’re doing well after all this time.

i’m still a heavy and very happy user of your binding since the first beta version. now so glad it made its way through the official distrib.

I’m writing to you as, as you have probably noticed, the nibeuplink API itself has been suffering up and downs recently. i’ve been suffering its impact on the system after not being able to get records for relatively long periods, or even not having the machine able to push new measurements, in turn leaving the binding to return invariable measurements for long periods of time. In order to mitigate that, i’ve been looking for some measure of the reliability of the measurements themselves, even though the binding reports the thing ‘online’ where at the end it effectively is, but without taking into account the fact that the nibe itself may not be able to refresh the measurements on the API. For instance i’ve implemented a check that verifies whether the API point is responding over http (i.e. a pure connectivity test) and whether the nibe thing is effectively online (it means it could connect/authenticate), and that has already allowed me to implement appropriate measures for when typically the internet connectivity suffers problems or when the API does not accept logins. This is however proving not to be enough, as recently the API looks healthy and the binding is itself on-line and functioning but the measurements do not get updated or very sporadically.
On this, I remember from the times when i had implemented myself an interface with the API, that the date of the last update from the equipment is also published through the API. In fact, i guess it is what is being used by the nibeuplink web page to report on the on-line/off-line status of the equipment. Looking back at my old code, i was getting that info from the /api/v1/systems/ interface in the ‘LastActivityDate’ element of the returned JSON.
I thought it could be an idea to publish this date through the binding in a new generic channel (of type DateTimeType). I know it’s a bit of a ‘nice to have’ wrt to all what the binding already offers, but it would solve this last point about reliability, at least for me.
WHat do you think about this and could it be something you’d be willing to support in the binding?

cheers

Hi @AlexF!
I’m working on updating the nibe uplink binding to be able to use the official Nibe Uplink REST API. So far I have managed to get authentication working and some other calls to the api that can be used for thing discovery (and also implemented a discovery service, so things are addedd to the inbox once the bridge is set up). I have quite a bit of more work to do to implement more functionality and make it more stable, but I’m going to need your help in integrating it into the existing binding.

My work is currently done as a separate binding because it made it easier for me to learn all the internal workings (it’s my first attempt at creating a binding), but I hope it can be integrated with the existing one. My work so far is available at github. Do you have any idea what would be the easiest approach to integrate it? (I’m sorry if my code is a bit of a mess, I most noteably need do document it better :wink:)

Sounds to me more like it would actually be a replacement.

Is there any difference in features between REST and webinterface?

There are some differences. I wrote a short summary in this post.
If I would replace the current binding some users might lose functionality, most notably to change settings, so I’d rather just add it as a separate bridge to the existing binding.
But it can be hard to work with someone else’s code, especially since I’m not very experienced. That’s why it’s currently a separate project.

I will consider this for my next release. Currently there is a huge change waiting for approval to be merged. As long as this is not merged back I will not continue as the approval process might result in changes which could also affect my further adoptions.

Hi @pacive:
There are 2 main reasons why I did not implement the official API:

  1. There is (currently) no write access supported
  2. The official API uses OAuth and I did not manage to get my binding authenticated this way.

I had a short look at your code, it seems to me that you have chosen a different approach and thus it will be quite hard to integrate it into the existing binding. You decided to use a bridge + things, I did not use a bridge but just a simple thing which has all channels attached. I do not have auto discovery because I decided to focus on the core functionality.
I have created another binding (SolarEdge) which has a very similar approach. There I have implemented two different APIs the API can be chosen by configuration. Basically this approach could also be used to add another API to the NIBE binding.
While in case of SolarEdge there was a good reason for implementing two different APIs I do not see this reason here. The “private” API does not have any disadvantage compared to the official one.

I agree that write access is a big advantage with your approach, which is why I definitely think it should still be an option, but I wouldn’t say it doesn’t have any disadvantages:

  1. Since it’s a private API, Nibe could change it at any moment without notice, which would break the binding. The public API promises to always be backwards compatible, and developers are notified in advance of any changes.
  2. The public API supports virtual thermostats, that can control the heating demand of the system, just by using a temp sensor already connected to OH. Some other settings are also available, such as vacation mode, temporary lux, ventilation boost and temperature setpoint/parallel adjustment of the heating curve. Though not as many as the private API.
  3. OAuth is more secure since no user credentials need to be stored in plain text. I found OH’s implementation really easy to work with, but I believe it’s quite new and might not have been available when you started your work.
  4. After adding the bridge, auto-discovery can be used to add any connected system, and also dynamically add channels (I still have to work more on that).

It might be possible to have both approaches living side by side, by just modifying the Handler Factory and adding my classes as-is. We just have to make sure there aren’t any naming collisions. WDYT?

The public API supports virtual thermostats, that can control the heating demand of the system, just by using a temp sensor already connected to OH

Didn’t you say right before it doesn’t have any write access?

It doesn’t have write access for most settings. With the current implementation, any setting that’s available on the nibeuplink.com website could potentially be changed. Not sure how many are implemented in the binding though. With the public API, some settings can be altered, those are transient ones that gets reset to default after 30 min (I guess so the system won’t be in a bad state if the connection should be lost).

But thermostat support is IMO one of the biggest ups with the public API, I have a personal implementation and use it extensively. I can, for example, lower the setpoint when it’s sunny outside to save energy and reduce the temperature at night or when nobody’s home. You can also start vacation mode, to lower both temperature and hot water (to a predefined value set in the heatpump) when you’re away for a longer period (should be possible to implement in the current binding as well, if not already), temporarily increase the hot water temperature, and boost the ventilation. But that’s about all the public API allows. And the values need to be posted with regular intervals, otherwise it’s reset to default. But that’s no problem to implement.

Sure, but as the public API is still in development it could also change. The private API is now stable for about 3 years so I would say it is quite stable. Openhab itself is less stable.

Sorry to say that but adjusting the heating temperatures will not help to save any money. If you are using “floor heating” you should optimize your heating as follows:

  1. set the room thermostat to max in the room which should be the warmest one in your house (typically the bath room)
  2. adjust the heating curve and parallel shift of your nibe until the bathroom has the desired temperature.
  3. adjust the room thermostats in all other rooms if those are warmer than desired.
  4. steps 2+3 need to be repeated several times

I ended up with heating curve 4 and shift 0. The temperature in all rooms is very constant. If the weather is very sunny the bathroom might be a little bit warmer than usual but in general the system works very efficiently and my heating costs are very very low so no need for any micro management.

Could you explain how this works in details. I used the public API manually several times I had to enter my credentials, afterwards some kind of token was generated. If I remember correctly. I used Insomnia Rest client for that purpose. But however after a few minutes inactivity the token became invalid and I had to authenticate again. This is not very convenient if there is a power failure or internet connection failure or maintenance of NIBE web service the binding will lose authentication.

Autodiscovery is really nice but as my approach only uses one thing I think it is not a must have. My latest DEV version which is not yet merged contains a lot of simplification + support for dynamic channels which means you could add any number of custom channels. This was really missing my binding.

It could make sense to use public API for data retrieval and private API for write access. In fact the private write API is completly separated from the private read API except that both use the same authentication/session.
But in my opinion it does not make any sense to have two binding implementations within one binding. In order to integrate your and my binding both bindings should share a common code base. Only the code which is used to access the API should be different. As I said, my binding in general allows to use different implementations as I have implemented the “Command” pattern. I already used it to access different APIs that use different types of authentication in my Solaredge. So there is already a proof of concept that it could be used. But it will still be quite a lot of work to integrate it.

I don’t think it’s likely to change, but there’s a small risk.

I have done that as well, but we have radiators on our upper floor that are quicker to adjust. Even in winter the heatpump can allow the compressor to turn off completely for long periods on a sunny day, or run at a low frequency, and the electric addon (which are the biggest source of power consumption) don’t have to run as much. Money-wise it doesn’t add up to much, but it’s satisfying to see :wink:.

There’s different types of OAuth authentication. A simpler one, where the user logs in, and then the application (often browser-based) can get access to the data. This is called “implicit grant”. But if it’s meant for server-server communication the “authorization grant flow” is used, where the user authenticates, giving OH in this instance an authorization code. OH can then trade this for an access-token that can be used when querying the API. The token is only valid for a short time (30 minutes), but with it also comes a “refresh token” that can be user to obtain a new token when it has expired. This makes it able to continue without user interaction indefinitely, even if OH would be offline for several hours, the refresh token it has saved can be used to get a new access token. The token renewal is handled automatically by the framework. And since it’s a well defined standard, it works the same for many different services. The spotify binding uses it as well. I have had my own implementation running for over a year without having to do any re-authentication, even though both my server and Nibe’s have been offline from time to time.

It would be my preference as well to have as much as possible in common. I have made my binding the way I felt was easiest, just to get it working and to learn all the ins and outs of OH, but I can certainly try to adapt it more to your modelling. Do you know if it’s possible to have the same Thing working both with and without a bridge?

Ok, if you have radiators this is of course a different story. However my eletric addon was never activated in heating mode since I have the NIBE (about 4 years now).

Well, this is great. When I started development the framwork did not support OAuth and I am not so familiar with the standard. But you are right this seems to be the better option from a security perspective.

I am afraid this will not work. I think supporting both Thing-only and Bridge-based object model will not work. So either my code needs to be adopted to the Bridge based approach or your code should be adopted to the Thing only approach.

Either way, this would mean breaking changes for the users, since the existing thing-types would have to be altered, either to work with a bridge or to be able to select the type of connection and having two sets of fields for credential input (or having two versions of each thing, which would be the worst option, in which case we might as well have two entirely different approaches). I think in that case going with the bridge model would be the easiest for the users, to just move their user credentials to the bridge and then recreate the thing (and perhaps re-link all items). They could then easily just swap the bridge to the other type should they wish later on.

In my opinion it does not make much difference if a bridge or a thing will be used. Maybe a bridge is slightly better. But anyway you will need the OAuth token to store for read access + (optional) username + password to be able to use write access.
Otherwise you will end up in different things for read and write access and thus you will have duplicated channels. This will confuse end users and this implementation details should definetely be hidden from end users. Thus I strongly recommend to implement both APIs within the same bridge/thing. And of course it should work the way that public read API is activated by default and private write API could be activated in addition.

BTW: This is my pull request with latest changes / simplifications: [Nibeuplink] enhanced custom channel feature by alexf2015 · Pull Request #6696 · openhab/openhab-addons · GitHub

That sounds like a good approach, even though it’s a bit more difficult to set up, than just filling in username and password in a field. The spotify binding use the same approach (but registering an application at the nibe uplink developer site is simpler, than with spotify). Your code for updating settings could be left almost just as it is.

Getting the data would require a different model though I’m afraid. When fetching parameters from the api you are only allowed to request 15 at a time. With your model this would require several different classes that override the prepareRequest() method to create requests with different query strings (where a list of parameter to get is provided), and then the poller would have to put all of them in the queue. This in turn means that all parameters must be statically defined in the code and requested, even if there’s no item linked to that channel, and it might require even more different classes to cover the different combination of parameters for different models.

It would be easier to define a common interface with something like getParameters(), setParameters(), pollParameters() etc, and then the implementing class(es) can decide how to construct the requests and where to send them, and then report back to the handler’s updateChannelStatus() method.

Finally my latest enhancement of the binding was merged and will be available in 2.5.6:

This change will break the existing custom channel definition but will add this:

  • unlimited number of custom channels can be defined
  • scaling of values will be possible (unscaled, div by 10, div by 100)
1 Like

After the update to 2.5.6 I receive warnings on all channels like this one:

[WARN ] [ternal.model.DataResponseTransformer] - could not handle unknown type base-type-40004, channel nibeuplink:f1145:44a25413:base#40004, value 211

Should I completely remove and re-add the F1145 Thing and all it’s items?

UPDATE: Yes I had to remove the Thing and re-add it. Everything works perfect again now. Thanks for the nice update! Being more flexible in the custom channels is very nice :slight_smile:

Somehow the conversion of “degree minutes” seems to make some problems:

2020-12-07 15:52:44.359 [WARN ] [pse.smarthome.core.items.GenericItem] - failed notifying listener 'org.eclipse.smarthome.io.rest.sitemap.internal.PageChangeListener@c43c6e' about state update of item NIBE_DEG_MIN: null

java.lang.NullPointerException: null

	at org.eclipse.smarthome.ui.internal.items.ItemUIRegistryImpl.getLabel(ItemUIRegistryImpl.java:435) ~[?:?]

	at org.eclipse.smarthome.io.rest.sitemap.internal.PageChangeListener.constructSitemapEvents(PageChangeListener.java:228) ~[?:?]

	at org.eclipse.smarthome.io.rest.sitemap.internal.PageChangeListener.constructSitemapEvents(PageChangeListener.java:214) ~[?:?]

	at org.eclipse.smarthome.io.rest.sitemap.internal.PageChangeListener.constructAndSendEvents(PageChangeListener.java:172) ~[?:?]

	at org.eclipse.smarthome.io.rest.sitemap.internal.PageChangeListener.stateChanged(PageChangeListener.java:186) ~[?:?]

	at org.eclipse.smarthome.core.items.GenericItem$1.run(GenericItem.java:261) [bundleFile:?]

	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_265]

	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_265]

	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_265]

2020-12-07 15:52:44.366 [WARN ] [pse.smarthome.core.items.GenericItem] - failed notifying listener 'org.eclipse.smarthome.io.rest.sitemap.internal.PageChangeListener@34fadf' about state update of item NIBE_DEG_MIN: Can not compare incompatible units.

java.lang.IllegalArgumentException: Can not compare incompatible units.

	at org.eclipse.smarthome.core.library.types.QuantityType.compareTo(QuantityType.java:174) ~[bundleFile:?]

	at org.eclipse.smarthome.core.library.types.QuantityType.equals(QuantityType.java:156) ~[bundleFile:?]

	at org.eclipse.smarthome.core.items.GenericItem$1.run(GenericItem.java:260) [bundleFile:?]

	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_265]

	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_265]

	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_265]

2020-12-07 15:52:44.366 [ERROR] [me.core.internal.events.EventHandler] - Dispatching/filtering event for subscriber 'org.eclipse.smarthome.core.events.EventSubscriber' failed: Can not compare incompatible units.

java.lang.IllegalArgumentException: Can not compare incompatible units.

	at org.eclipse.smarthome.core.library.types.QuantityType.compareTo(QuantityType.java:174) ~[?:?]

	at org.eclipse.smarthome.core.library.types.QuantityType.equals(QuantityType.java:156) ~[?:?]

	at org.eclipse.smarthome.core.items.GenericItem.applyState(GenericItem.java:234) ~[?:?]

	at org.eclipse.smarthome.core.items.GenericItem.setState(GenericItem.java:219) ~[?:?]

	at org.eclipse.smarthome.core.library.items.NumberItem.setState(NumberItem.java:121) ~[?:?]

	at org.eclipse.smarthome.core.internal.items.ItemUpdater.receiveUpdate(ItemUpdater.java:75) ~[?:?]

	at org.eclipse.smarthome.core.items.events.AbstractItemEventSubscriber.receive(AbstractItemEventSubscriber.java:52) ~[?:?]

	at org.eclipse.smarthome.core.internal.events.EventHandler.lambda$0(EventHandler.java:155) ~[?:?]

	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_265]

	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_265]

	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_265]

	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_265]

	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_265]

2020-12-07 16:09:44.554 [WARN ] [pse.smarthome.core.items.GenericItem] - failed notifying listener 'org.eclipse.smarthome.model.rule.runtime.internal.engine.RuleEngineImpl@1cf4a2c' about state update of item NIBE_DEG_MIN: Unable to convert to system unit during compare.

java.lang.IllegalArgumentException: Unable to convert to system unit during compare.

	at org.eclipse.smarthome.core.library.types.QuantityType.compareTo(QuantityType.java:171) ~[bundleFile:?]

	at org.eclipse.smarthome.core.library.types.QuantityType.equals(QuantityType.java:156) ~[bundleFile:?]

	at org.eclipse.smarthome.core.items.GenericItem$1.run(GenericItem.java:260) [bundleFile:?]

	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_265]

	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_265]

	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_265]

2020-12-07 16:22:44.699 [WARN ] [pse.smarthome.core.items.GenericItem] - failed notifying listener 'org.eclipse.smarthome.io.rest.sitemap.internal.PageChangeListener@34fadf' about state update of item NIBE_DEG_MIN: Unable to convert to system unit during compare.

java.lang.IllegalArgumentException: Unable to convert to system unit during compare.

	at org.eclipse.smarthome.core.library.types.QuantityType.compareTo(QuantityType.java:171) ~[bundleFile:?]

	at org.eclipse.smarthome.core.library.types.QuantityType.equals(QuantityType.java:156) ~[bundleFile:?]

	at org.eclipse.smarthome.core.items.GenericItem$1.run(GenericItem.java:260) [bundleFile:?]

	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_265]

	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_265]

	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_265]

Any idea why this is happening?