Airthings Hub implementation

Hi, was wondering if somebody has a solution to implement an Airthings Hub
or if someone already has found a way to connect to the Airthings API via OpenHAB.
Unfortunately the binding supports only Bluetooth which is no option in my infrastructure.

There are some Bluetooth bindings for OH. If the device only supports Bluetooth you would need something that uses Bluetooth. Since that is not an option for you, this is not a good solution to meet your requirements.

Hi Bruce,
The Sensor-Devices are Bluetooth only.
But the Hub is supposed to be implemented in Homeautomation. It’s connected via LAN and via API to the Airthings Cloud.

I don’t know thou, if the Manufacturer API is meant to be implemented.

HI all,

I just purchased an AirThings package as well. I am using 2 x Wave Plus’s, 2 x Wave Minis and a Hub. I too am very interested in trying to get this setup as well.

Thanks,
Pete

I asked Airthings about getting access to their API as a private person, but the answer was no.
They say a bit more about it here: Integrations: API

We can only hope that they will open up for it soon, as it was one of their original selling points for the hub.

Hello,

I did a little bit of research and found, that the Airthings Dashboard uses a simple RESt API. I would now like to write a binding to access this API from OpenHAB. As there is an existing Airthings Binding, I’m now wondering, if it would be wise to implement a new binding, or extend the already existing binding.

Maybe the original maintainers of the binding (@Kai and @pauli_anttila) could join the discussion. What would you think?

Best regards

Stefan

I think I would go for a separate binding, since the existing one is just an extension of the Bluetooth binding and coupled to that. And users that want to use the cloud api clearly have no interest in the bluetooth code on their installation. I’d suggest to go for airthingscloud as a binding id.

@stefanvogl & @nza: Feel free to mention to Airthings that you want to do the integration on behalf of the openHAB project - maybe this counts a bit more than “being a private person”, who knows!

I was wandering around the account today and it looks like there’s an API available for non-business customers: Getting Started with the Airthings API | Airthings

The documentation is at API Docs | Airthings and I was able to generate a token.

Hallo Kai,
probably it could make sense to bring different posts regarding airthings together? I also try since months… Here is Kris’ post who succeeded without any API issue:

It looks like it works easy. I ended up in the request of a PIN (Pairing mode)
https://community.openhab.org/t/re-airthings-wave-plus-how-to-get-bluetooth-bridge-id/120324?u=ro-smart
Would also be happy to get any help or if I can help to find a solution.
Dieter

Hi Stefan,

I’m still trying to figure out how to implement a solution utilizing the AirThings Hub and/or cloud services. Did you ever find a way to get this working? I installed Home Assistant just to see if I could get Airthings to work there and it does flawlessly. From there, I thought I’d be able to figure out how to make it work in OpenHAB but I guess I am not smart enough! Any help would be appreciated.

Thanks,

Hi,
unfortunately I didn’t have time to even start with implementing this. The Airthings API seems pretty straight forward. You have to log-in (using openid-connect) and get a token. With this token you can authenticate to the API. There you have devices with measurements. That’s basically it.

As I haven’t developed any OpenHAB addons, the setting up the boilerplate and everything, kept me from starting in the first place. So, if anyone (preferrably someone with some addon experience) would set this up, I’d be more than happy and willing to help and contribute.

1 Like

Thanks for responding. I think I am getting somewhere. I use Node-Red as my rules engine and since it is always running, I decided to give that a crack. I created a flow and sub-flow which work well to grab the sensor data from the cloud and push it to my mqtt broker. I know need to figure out how to get the mqtt data into OpenHAB. I’ve gotten as far as setting up a “Generic MQTT Thing” but am getting tripped up on how to set the channel and corresponding item to read the sensor data.

1 Like

Hi Pete,
would you share how you managed to get this done using NodeRed? I am not a coder and would really love to get airthings hub (api) data into my openhab environment.
Thanks.

Hi Stefan,
your post is a bit older now. Did you get forward with your idea of creating an API based binding in addition to the existing BLE binding?

Thanks and best regards
Dieter

Hi, no, I didn’t have the time to do it. I wrote a python script, that queries the sensors and sends it to an MQTT broker. That works for me at the moment. Of course, a binding would be the more convenient way.

If you like, I can share the script. Nothing fancy (and can be improved of course).

Best regards
Stefan

Hi @RO-Smart @jmarwil3 @artem

I created an AirthingsCloud Binding which uses the API of Airthings. As I only have 2 View Plus devices I would appriciate your feedback. I used the Binding now for 1.5 Months without any issue.It could be found here:
org.openhab.binding.airthingscloud-4.1.0-SNAPSHOT.jar

First create a bridge with your API credentials.
Then you can scan your devices to add them as Things.
My Items are configured as follows:

Number:Temperature ATKtemperature “Temperature [%.1f °C ]” { channel=“airthingscloud:airthings_thing:xxxxxx:yyyyyy:temp” }
Number:Dimensionless ATKhumidity “Humidity [%.0f %%]” { channel=“airthingscloud:airthings_thing:xxxxxx:yyyyyy:humidity” }
Number:Pressure ATKpressure “Air Pressure [%d %unit%]” { channel=“airthingscloud:airthings_thing:xxxxxx:yyyyyy:pressure” }
Number:Dimensionless ATKco2 “CO2 level [%.0f ppm]” { channel=“airthingscloud:airthings_thing:xxxxxx:yyyyyy:co2” }
Number:RadiationSpecificActivity ATKradon_st_avg “Radon short term average level [%d %unit%]” { channel=“airthingscloud:airthings_thing:xxxxxx:yyyyyy:radonShortTermAvg” }
Number:Dimensionless ATKbattery “Battery [%.0f %%]” { channel=“airthingscloud:airthings_thing:xxxxxx:yyyyyy:battery” }
Number:Density ATKpm1 “PM1 [%d µg/m³]” { channel=“airthingscloud:airthings_thing:xxxxxx:yyyyyy:pm1” }
Number:Density ATKpm25 “PM25 [%d µg/m³]” { channel=“airthingscloud:airthings_thing:xxxxxx:yyyyyy:pm25” }
Number:Dimensionless ATKvoc “Voc [%.0f ppb]” { channel=“airthingscloud:airthings_thing:xxxxxx:yyyyyy:voc” }

Thanks a lot for any feedback!
Frieso

2 Likes

Hi Frieso,

thanks for your time to develop and share this binding!!

I just installed the jar-File, added the Bridge and the Plus-Devices.
I guess I can skip the Hub, as it has no properties itself.

What I discovered at first glance:
Radon Long Term Average Level stays NULL.
Don’t know if this has to be configured in the Airthings-Dashboard itself?

Will have a look at it the next couple of days and report back! :slight_smile:

Regards
Bopp

Cool,

I’ve installed and set this up. It’s missing the long term average for Radon as well, but I seem to remember the same when initially setting these up.
I’ll keep an eye on it.
Great job!

Just created a custom widget (based on the “open_weather_api_air_quality” widget)

uid: airthings_plus_widget
tags:
  - custom
  - airthings
  - based_on_open_weather_api_air_quality
props:
  parameters:
    - description: Label for widget
      name: label
      required: false
      type: TEXT
    - context: item
      description: Airthings Wave Plus Item
      label: Item
      name: plusitem
      required: true
      type: TEXT
    - default: "#ffd555"
      description: Background color
      name: bg
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 28, 2023, 4:47:38 PM
component: f7-card
config:
  expandable: false
  style:
    background-color: = props.bg
    border-radius: 15px
    box-shadow: '= themeOptions.dark === "light" ? "5px 5px 10px #dcdcdb" : "0"'
    margin: 6px
slots:
  default:
    - component: f7-card-content
      config:
        style:
          width: 100%
      slots:
        default:
          - component: f7-row
            config:
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.3rem
                      font-weight: 700
                      padding-top: .1rem
                      width: 100%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: '= (props.label != undefined) ? props.label : "Airthings Wave Plus Report"'
                      - component: f7-list-item
                        config:
                          divider: true
                          style:
                            background-color: black
                            color: black
                            height: 1px
                            margin-top: .5rem
                            width: calc(100% - 2rem)
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_Temperature"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: Temperature
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_Temperature"].state
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_Humidity"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: Humidity
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_Humidity"].displayState
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_Pressure"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: Pressure
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_Pressure"].state
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_CO2_Level"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: CO2-Level
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_CO2_Level"].displayState
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_PM1"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: PM10
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_PM1"].state
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_PM25"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: PM2.5
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_PM25"].state
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_TVOC_Level"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: VOC
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_TVOC_Level"].displayState
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_Radon_Short_Term_Average_Level"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: Radon Short Term
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_Radon_Short_Term_Average_Level"].state
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_Radon_Long_Term_Average_Level"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: Radon Long Term
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_Radon_Long_Term_Average_Level"].state
          - component: f7-row
            config:
              visible: =(Number.parseFloat(items[props.plusitem + "_Battery"].state) > 0)
              style:
                margin-top: .3rem
                width: 100%
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      font-weight: 600
                      padding-top: .1rem
                      width: 40%
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: Battery
                - component: f7-col
                  config:
                    style:
                      font-size: 1.1rem
                      padding-top: .1rem
                      width: calc(60% - 65px)
                  slots:
                    default:
                      - component: Label
                        config:
                          style:
                            color: black
                          text: = items[props.plusitem + "_Battery"].displayState

Hi Jos and @Bopp

the radon long term avg values are not submitted by airthings in their Webservice. I just mailed them to get these values too. In the “business”-Version it is available. I’ll keep you updated. Thanks so far for reporting this :slight_smile:

Regards
Frieso

1 Like