[webhook] New, very simple binding for listening incomming http requests

Hello,

I have scratched a simple binding for listening to incoming http requests.

What problem does it solve?
There are quite a few devices which are able to perform http requests on a given webhook (like Synology Surveillance Station in rules or some intercoms/ip doorphones like akuvox). Those httprequest cannot be simple utilized in openhab straight away. You could translate it at nginx level from the simple form of http get/post those devices can perform to openhab REST APIs or create a simple server which passes mentioned request to mqtt.
However those methods requires third party software to be involved. With this binding you can expose an url to be called by your devices and handle it in the openhab directly.

How this binding works?
When you create a thing you can configure it with only one parameter - JSON like string. Example

{
  "channels": [
    {
      "key":"action",
      "kind": "STATE",
      "state": "req.parameters.action[0]"
    },{
      "key": "card",
      "kind": "STATE",
      "state": "req.parameters.card[0]"
    }
  ]
}

In this parameter you define channels that will be exposed by the thing. key means the id of the channel. Kind is either STATE or TRIGGER (both are implemented) type of a channel. state is an expression based on apache jexl expression language.

As for now in the expression there is only a simple object req which has only two properties:

  1. parameters - under this property all query parameters and form-encoded post parameters are placed. So you can define in your http requests query params and extract it to channels.
    2 body - unders this property the body of the POST requests is stored. body has two properties text and json. With this you can post a json file to you openhab and use an expression to extract an value from the json and place it into channel. See further examples.

Context for expressions can be enriched with more than one req object in future.

Examples

Handling an card input to my doorphone.
I have configured my doorphone to call following url when someone use the mifare tag on it.

http://oh/httplistener/intercomhook?action=cardaction&card=$card_sn

Then I have configured a thing with following json snippet

{
  "channels": [
    {
      "key":"action",
      "kind": "STATE",
      "state": "req.parameters.action[0]"
    },{
      "key": "card",
      "kind": "STATE",
      "state": "req.parameters.card[0]"
    }
  ]
}

Result:

Extracting posted JSON
Following call

curl -H "Content-Type: application/json" -X POST -d '{"username":"test-username"}' http://localhost:8080/httplistener/af71afd830/

With configured thing:

{
  "channels": [
    {
      "key":"testowy",
      "kind": "STATE",
      "state": "req.parameters.ekologia[0]"
    },{
     "key": "testjson",
     "kind": "STATE",
     "state": "req.body.json.username"
    }
  ]

And result:

That’s all. :slight_smile: Any suggestions are welcomed. I do not call it early alpha or something - I wouldn’t recommend to use it on production (though I have deployed in on my openhab). However maybe some of you would be interested and have some hints how evolve it to a useful binding.

Binaries and a source code is here.

Regards,
Piotr

21 Likes

This is really interesting. I might be missing something, but how do you expose the /httplistener URL? Does the binding do this automatically?

I’m also guessing that this is only available on the local network, and that devices posting to it will also have to be on that network (or on a VPN/reverse proxy). I think that’s worth clarifying before people start getting ideas about exposing their OH servers to the Internet. :wink:

Each thing is exposed under /httplistener/[thindid] url automatically and every call to http://youropenhab/httplistener/thingid/any-further-url-path is redirected to mapping you describe as simple json config.

And yeah, it works only locally on your local openhab - in fact this is based on a servlet deployed on a openhab.

2 Likes

This looks like exactly what is required to support the Ecowitt GW1000 weather station.
The weather station supports doing a post to a custom URL, so I created a listener with

{
  "channels": [
    {
     "key": "body",
     "kind": "STATE",
     "state": "req.body.text"
    }
  ]
}

And sure enough the channel gets populated with the posted text body:

PASSKEY=XXXXXXXXXXXXXXXXXXX&stationtype=GW1000_V1.5.4&dateutc=2021-11-02+09:54:58&tempinf=76.5&humidityin=52&baromrelin=30.345&baromabsin=29.902&tempf=66.0&humidity=72&wh26batt=0&freq=433M&model=GW1000_Pro

Now what? :slight_smile:

As it is not JSON, what is easiest way to use this data? Can I somehow configure and parse it in the binding?
Or I will need transform on the item? or script?

The easiest is probably to use the regex transformation profile, to get the tempinf you would use .*tempinf=(\d+\.?\d*)&.* in the configuraton (not tested, might need tweaking). As long as you get the number and nothing else you should be able to link it to a Number Item.

Thanks, Anders - but I discovered that the answer is actually in one of the examples that author gave above, and you can parse each parameter into separate channel. So the config I posted earlier is useful, because you get all the parameters (which are different pending what sensors you have). The full binding config for my GW1000 is therefore:

{
  "channels": [
    {
     "key": "tempinf",
     "kind": "STATE",
     "state": "req.parameters.tempinf[0]"
    },
     {
     "key": "humidityin",
     "kind": "STATE",
     "state": "req.parameters.humidityin[0]"
    },
    {
     "key": "baromrelin",
     "kind": "STATE",
     "state": "req.parameters.baromrelin[0]"
    },
    {
     "key": "baromabsin",
     "kind": "STATE",
     "state": "req.parameters.baromabsin[0]"
    },
    {
     "key": "humidity",
     "kind": "STATE",
     "state": "req.parameters.humidity[0]"
    }
  ]
}

@Piotr_Bojko This binding is really useful, and very easy to configure - especially compared to setting up some other listener and then pushing to OH some other way MQTT or to REST API - there are lots of devices that can POST, where this would be useful and no extra bits - I hope other’s realise this and it gets some traction - I would love to see this added as an official binding. Well done and thanks for sharing.

Hello,

It should be possible on the channel configuration level.

For accessing body as JSON you may try:

req.body.json.channels[1].state //.. etc

Example conditional channel configuration:

I may prepare you more direct and comprehensive example if you would paste complete request from the weather station.

Regards,
Piotr

I have it all working now - so for anybody else that ended up here trying to get GW1000 gateway working with OpenHAB.

The last thing to solve was Units - the Weather Station sends out temp in F, but I am using C - the device has no setting to change the units in the protocol.
I have never mixed units previously, with OH. The binding has no unit awareness (QuantityType), but is not needed - the easiest way I found is to ensure that the Item you are linking the channel to is correct type eg Number:Temperature, and then in the binding, configure the channel so the value has the correct unit. So my final config simply adds the units.

{
  "channels": [
    {
     "key": "tempinf",
     "kind": "STATE",
     "state": "req.parameters.tempinf[0] + ' °F'"
    },
     {
     "key": "tempf",
     "kind": "STATE",
     "state": "req.parameters.tempf[0] + ' °F'"
    },
    {
     "key": "humidityin",
     "kind": "STATE",
     "state": "req.parameters.humidityin[0]"
    },
     {
     "key": "humidity",
     "kind": "STATE",
     "state": "req.parameters.humidity[0]" 
    },
    {
     "key": "baromrelin",
     "kind": "STATE",
     "state": "req.parameters.baromrelin[0] + ' inHg'"
    },
    {
     "key": "baromabsin",
     "kind": "STATE",
     "state": "req.parameters.baromabsin[0] + ' inHg'"
    }
   
  ]
}

If your system default is metric, OH will then automatically convert the values, and you will end up with correct value with lots of numbers after the decimal point, so on your Item - in the Metadata section, add State Description and Pattern and format string

%.1f %unit%

This binding is perfect. Please add it to the normal bindings.
With this binding it was very easy to connect my Bresser 7in1 WeatherStation.

In case that someone has the same weather station:

{
  "channels": [
    {
     "key": "BaromIn",
     "kind": "STATE",
     "state": "req.parameters.baromin[0]  + ' inHg'"
    },
    {
     "key": "TempF",
     "kind": "STATE",
     "state": "req.parameters.tempf[0] + ' °F'"
    },
    {
     "key": "DewPointF",
     "kind": "STATE",
     "state": "req.parameters.dewptf[0] + ' °F'"
    },
    {
     "key": "Humidity",
     "kind": "STATE",
     "state": "req.parameters.humidity[0]"
    },
    {
     "key": "WindSpeedMph",
     "kind": "STATE",
     "state": "req.parameters.windspeedmph[0] + ' mph'"
    },
    {
     "key": "WindGustMph",
     "kind": "STATE",
     "state": "req.parameters.windgustmph[0] + ' mph'"
    },
    {
     "key": "WindDir",
     "kind": "STATE",
     "state": "req.parameters.winddir[0] + ' °'"
    },
    {
     "key": "RainIn",
     "kind": "STATE",
     "state": "req.parameters.rainin[0] + ' in'"
    },
    {
     "key": "DailyRainIn",
     "kind": "STATE",
     "state": "req.parameters.dailyrainin[0] + ' in'"
    },
    {
     "key": "SolarRadiation",
     "kind": "STATE",
     "state": "req.parameters.solarradiation[0]"
    },
    {
     "key": "UV",
     "kind": "STATE",
     "state": "req.parameters.UV[0]"
    },
    {
     "key": "IndoorTempF",
     "kind": "STATE",
     "state": "req.parameters.indoortempf[0] + ' °F'"
    },
     {
     "key": "IndoorHumidity",
     "kind": "STATE",
     "state": "req.parameters.indoorhumidity[0]"
    }
  ]
}
2 Likes

easy to implement and works perfectly!
every OH user should know about this binding

Im new to all this HTTP and your binding is great .

But your post got me stuck for an half an hour debugging until i found whats wrong because of yours obviously unintended error and my noob status of not thinking but copy/pasting… in your URL example

http://youropenhab/httplistener/thingid/any-further-url-path

should be

http://youropenhab/httplistener/thingid**?**any-further-url-path

for it to work.

1 Like

Hi!

If it is communicating through the openhab built in web server it should be available through the cloud myopenhab.org. You just need to adjust your URL-s and auth headers accordingly.

I have just checked this and you may be right. I’ve succeeded with a call through myopenhab to the webhook from the binding. You must be logged into myopenhab and the http request should contain a valid myopenhab session cookie for the call.

1 Like

I love your webhook binding.
I managed to control/get data from my Netatmo doorbell via the nginx proxy.
Thanks for sharing !

1 Like

Thank you for this binding.
Up until now I’ve been using a proxy server (written in Python) to get data from WirelessTags (Cao Gadgets) but as my Python skills are not the best, it was never really robust. Now I can get rid of it.

One difficulty was to link a webhook channel to an existing item which is not a string. The UI only offers items matching the channel type.

I got around this by temporarily changing the item to a string, linking it, then changing it back.

It would be nice if the binding offered a choice of data type for the channel. But hey, great work so far.

Will it be added to the marketplace anytime soon?

Also thank you very much for this great binding from my side!

I tested it on openHAB 3.2.0, it was working nicely.
Personally I prefer configuration files over the GUI due to the flexibility. But unfortunately I failed using a things file instead of the GUI.

gw1100.things:

Thing httplistener:HttpListener:ecowittGateway {
 Channels:
  State String : tempinf "Temperature" [
   configParameter="req.parameters.tempinf[0]"
  ]
}

The code above does not work. In the GUI the status shows “UNINITIALIZED, HANDLER_CONFIGURATION_PENDING”.

Any idea how the configuration can be applied to a .things file?

Yes, as mentioned in the thread you started on the very same topic. But you need to provide the information requested.

Thank you, my intention was not a double-post, but since @opus told me that the configuration is a binding specific question, I asked here for the binding specific answer.
My other thread was about how to add the configuration to the things file in general.

Many thanks to @hilbrand, this way seems to be the proper configuration when using .things files:

Thing httplistener:HttpListener:ecowittGateway [jsonConfig="{ \"channels\": [ { \"key\": \"tempinf\", \"kind\": \"STATE\", \"state\": \"req.parameters.tempinf[0]\" }, { \"key\": \"humidityin\", \"kind\": \"STATE\", \"state\": \"req.parameters.humidityin[0]\" } ] }"]

The status of the thing is now ONLINE, but unfortunately it still does not work.
Are there any debug capabilities (log files)?

For the moment using the GUI for configuration will be the best way. But is there a way to list the items pushed to the binding? I tried unsuccessfully with “req.body.text” (was empty).

In the repository hilbrand has also seen that that the httplistener binding was renamed to webhook and uses a different way of configuring the channels now. Are there any release plans?

Sounds great.
Would you mind sharing more information what you can control / read and how your configuration looks like?
Thank you.