]> git.basschouten.com Git - openhab-addons.git/commitdiff
[airthings] Add support for Airthings Wave Gen 1 (#11052)
authordw-8 <87277011+dw-8@users.noreply.github.com>
Fri, 30 Jul 2021 20:12:35 +0000 (16:12 -0400)
committerGitHub <noreply@github.com>
Fri, 30 Jul 2021 20:12:35 +0000 (22:12 +0200)
Signed-off-by: dw-8 <davy.wong.on+github@gmail.com>
bundles/org.openhab.binding.bluetooth.airthings/README.md
bundles/org.openhab.binding.bluetooth.airthings/src/main/java/org/openhab/binding/bluetooth/airthings/internal/AirthingsBindingConstants.java
bundles/org.openhab.binding.bluetooth.airthings/src/main/java/org/openhab/binding/bluetooth/airthings/internal/AirthingsDiscoveryParticipant.java
bundles/org.openhab.binding.bluetooth.airthings/src/main/java/org/openhab/binding/bluetooth/airthings/internal/AirthingsHandlerFactory.java
bundles/org.openhab.binding.bluetooth.airthings/src/main/java/org/openhab/binding/bluetooth/airthings/internal/AirthingsWaveGen1Handler.java [new file with mode: 0644]
bundles/org.openhab.binding.bluetooth.airthings/src/main/resources/OH-INF/thing/airthings.xml

index fe97bbc46917f14717cd554938677b50340cedae..1f85091762d19e34bac60be956b6c66e6eb19019 100644 (file)
@@ -6,11 +6,11 @@ This extension adds support for [Airthings](https://www.airthings.com) indoor ai
 
 Following thing types are supported by this extension:
 
-| Thing Type ID       | Description               |
-| ------------------- | ------------------------- |
-| airthings_wave_plus | Airthings Wave Plus       |
-| airthings_wave_mini | Airthings Wave Mini       |
-
+| Thing Type ID       | Description                            |
+| ------------------- | -------------------------------------- |
+| airthings_wave_plus | Airthings Wave Plus                    |
+| airthings_wave_mini | Airthings Wave Mini                    |
+| airthings_wave_gen1 | Airthings Wave 1st Gen (SN 2900xxxxxx) |
 
 ## Discovery
 
@@ -44,6 +44,16 @@ The `Airthings Wave Plus` thing has additionally the following channels:
 | radon_st_avg       | Number:Density           | The measured radon short term average level |
 | radon_lt_avg       | Number:Density           | The measured radon long term average level  |
 
+The `Airthings Wave Gen 1` thing has the following channels:
+
+| Channel ID         | Item Type                | Description                                 |
+| ------------------ | ------------------------ | ------------------------------------------- |
+| radon_st_avg       | Number:Density           | The measured radon short term average level |
+| radon_lt_avg       | Number:Density           | The measured radon long term average level  |
+| temperature        | Number:Temperature       | The measured temperature                    |
+| humidity           | Number:Dimensionless     | The measured humidity                       |
+
+Note: For the `Airthings Wave Gen 1`, only one channel can be updated at each refreshInterval, so it will take refreshInterval x 4 cycles to sequentially update all 4 channels  
 
 ## Example
 
index de8a43c8f0ae75c7a9849e1beae87d7ef675729d..74345b604e0708bde8c13c1f1c795e1d4a84bdc4 100644 (file)
@@ -36,6 +36,7 @@ import tech.units.indriya.unit.TransformedUnit;
  *
  * @author Pauli Anttila - Initial contribution
  * @author Kai Kreuzer - Added Airthings Wave Mini support
+ * @author Davy Wong - Added Airthings Wave Gen 1 support
  */
 @NonNullByDefault
 public class AirthingsBindingConstants {
@@ -45,9 +46,11 @@ public class AirthingsBindingConstants {
             BluetoothBindingConstants.BINDING_ID, "airthings_wave_plus");
     public static final ThingTypeUID THING_TYPE_AIRTHINGS_WAVE_MINI = new ThingTypeUID(
             BluetoothBindingConstants.BINDING_ID, "airthings_wave_mini");
+    public static final ThingTypeUID THING_TYPE_AIRTHINGS_WAVE_GEN1 = new ThingTypeUID(
+            BluetoothBindingConstants.BINDING_ID, "airthings_wave_gen1");
 
     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIRTHINGS_WAVE_PLUS,
-            THING_TYPE_AIRTHINGS_WAVE_MINI);
+            THING_TYPE_AIRTHINGS_WAVE_MINI, THING_TYPE_AIRTHINGS_WAVE_GEN1);
 
     // Channel IDs
     public static final String CHANNEL_ID_HUMIDITY = "humidity";
index 91eb0ee9558c2e2b7a9f9d7da7760948d8c228dd..99c640dbca99352c83dbc4dbb21b9167af28bb30 100644 (file)
@@ -33,6 +33,7 @@ import org.osgi.service.component.annotations.Component;
  *
  * @author Pauli Anttila - Initial contribution
  * @author Kai Kreuzer - Added Airthings Wave Mini support
+ * @author Davy Wong - Added Airthings Wave Gen 1 support
  *
  */
 @NonNullByDefault
@@ -43,6 +44,7 @@ public class AirthingsDiscoveryParticipant implements BluetoothDiscoveryParticip
 
     private static final String WAVE_PLUS_MODEL = "2930";
     private static final String WAVE_MINI_MODEL = "2920";
+    private static final String WAVE_GEN1_MODEL = "2900"; // Wave 1st Gen SN 2900xxxxxx
 
     @Override
     public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
@@ -60,6 +62,10 @@ public class AirthingsDiscoveryParticipant implements BluetoothDiscoveryParticip
                 return new ThingUID(AirthingsBindingConstants.THING_TYPE_AIRTHINGS_WAVE_MINI,
                         device.getAdapter().getUID(), device.getAddress().toString().toLowerCase().replace(":", ""));
             }
+            if (WAVE_GEN1_MODEL.equals(device.getModel())) {
+                return new ThingUID(AirthingsBindingConstants.THING_TYPE_AIRTHINGS_WAVE_GEN1,
+                        device.getAdapter().getUID(), device.getAddress().toString().toLowerCase().replace(":", ""));
+            }
         }
         return null;
     }
@@ -79,6 +85,9 @@ public class AirthingsDiscoveryParticipant implements BluetoothDiscoveryParticip
         if (WAVE_MINI_MODEL.equals(device.getModel())) {
             return createResult(device, thingUID, "Airthings Wave Mini");
         }
+        if (WAVE_GEN1_MODEL.equals(device.getModel())) {
+            return createResult(device, thingUID, "Airthings Wave Gen 1");
+        }
         return null;
     }
 
index 1b1c2b5b0c42ba06c5b436a8bf563e1f66d65907..6c5a4701712c22852395ae633b386affb5b57be8 100644 (file)
@@ -26,6 +26,7 @@ import org.osgi.service.component.annotations.Component;
  *
  * @author Pauli Anttila - Initial contribution
  * @author Kai Kreuzer - Added Airthings Wave Mini support
+ * @author Davy Wong - Added Airthings Wave Gen 1 support
  */
 @NonNullByDefault
 @Component(service = ThingHandlerFactory.class, configurationPid = "binding.airthings")
@@ -45,6 +46,9 @@ public class AirthingsHandlerFactory extends BaseThingHandlerFactory {
         if (thingTypeUID.equals(AirthingsBindingConstants.THING_TYPE_AIRTHINGS_WAVE_MINI)) {
             return new AirthingsWaveMiniHandler(thing);
         }
+        if (thingTypeUID.equals(AirthingsBindingConstants.THING_TYPE_AIRTHINGS_WAVE_GEN1)) {
+            return new AirthingsWaveGen1Handler(thing);
+        }
         return null;
     }
 }
diff --git a/bundles/org.openhab.binding.bluetooth.airthings/src/main/java/org/openhab/binding/bluetooth/airthings/internal/AirthingsWaveGen1Handler.java b/bundles/org.openhab.binding.bluetooth.airthings/src/main/java/org/openhab/binding/bluetooth/airthings/internal/AirthingsWaveGen1Handler.java
new file mode 100644 (file)
index 0000000..8ad7324
--- /dev/null
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth.airthings.internal;
+
+import static org.openhab.binding.bluetooth.airthings.internal.AirthingsBindingConstants.*;
+
+import java.util.UUID;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Thing;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AirthingsWaveGen1Handler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Davy Wong - Added Airthings Wave Gen 1 support
+ */
+@NonNullByDefault
+public class AirthingsWaveGen1Handler extends AbstractAirthingsHandler {
+
+    private static final String HUMIDITY_UUID = "00002a6f-0000-1000-8000-00805f9b34fb"; // 0x2A6F
+    private static final String TEMPERATURE_UUID = "00002a6e-0000-1000-8000-00805f9b34fb"; // 0x2A6E
+    private static final String RADON_STA_UUID = "b42e01aa-ade7-11e4-89d3-123b93f75cba";
+    private static final String RADON_LTA_UUID = "b42e0a4c-ade7-11e4-89d3-123b93f75cba";
+
+    private int intResult;
+    private double dblResult;
+    private volatile ReadSensor readSensor = ReadSensor.RADON_STA;
+
+    private enum ReadSensor {
+        TEMPERATURE,
+        HUMIDITY,
+        RADON_STA,
+        RADON_LTA,
+    }
+
+    public AirthingsWaveGen1Handler(Thing thing) {
+        super(thing);
+    }
+
+    private final Logger logger = LoggerFactory.getLogger(AirthingsWaveGen1Handler.class);
+
+    @Override
+    protected void updateChannels(int[] is) {
+        int[] rawdata;
+        rawdata = is;
+        if (rawdata.length == 2) {
+            switch (readSensor) {
+                case TEMPERATURE:
+                    dblResult = intFromBytes(rawdata[0], rawdata[1]) / 100D;
+                    logger.debug("Parsed data 1: {}", String.format("[temperature=%.1f °C]", dblResult));
+                    readSensor = ReadSensor.HUMIDITY;
+                    logger.debug("Change next readSensor to: {}", readSensor);
+                    logger.debug("Update channel 1");
+                    updateState(CHANNEL_ID_TEMPERATURE,
+                            QuantityType.valueOf(Double.valueOf(dblResult), SIUnits.CELSIUS));
+                    logger.debug("Update channel 1 done");
+                    break;
+                case HUMIDITY:
+                    dblResult = intFromBytes(rawdata[0], rawdata[1]) / 100D;
+                    logger.debug("Parsed data 2: {}", String.format("[humidity=%.1f %%rH]", dblResult));
+                    readSensor = ReadSensor.RADON_STA;
+                    logger.debug("Change next readSensor to: {}", readSensor);
+                    logger.debug("Update channel 2");
+                    updateState(CHANNEL_ID_HUMIDITY, QuantityType.valueOf(Double.valueOf(dblResult), Units.PERCENT));
+                    logger.debug("Update channel 2 done");
+                    break;
+                case RADON_STA:
+                    intResult = intFromBytes(rawdata[0], rawdata[1]);
+                    logger.debug("Parsed data 3: {}", String.format("[radonShortTermAvg=%d Bq/m3]", intResult));
+                    readSensor = ReadSensor.RADON_LTA;
+                    logger.debug("Change next readSensor to: {}", readSensor);
+                    logger.debug("Update channel 3");
+                    updateState(CHANNEL_ID_RADON_ST_AVG,
+                            QuantityType.valueOf(Double.valueOf(intResult), BECQUEREL_PER_CUBIC_METRE));
+                    logger.debug("Update channel 3 done");
+                    break;
+                case RADON_LTA:
+                    intResult = intFromBytes(rawdata[0], rawdata[1]);
+                    logger.debug("Parsed data 4: {}", String.format("[radonLongTermAvg=%d Bq/m3]", intResult));
+                    readSensor = ReadSensor.TEMPERATURE;
+                    logger.debug("Change next readSensor to: {}", readSensor);
+                    logger.debug("Update channel 4");
+                    updateState(CHANNEL_ID_RADON_LT_AVG,
+                            QuantityType.valueOf(Double.valueOf(intResult), BECQUEREL_PER_CUBIC_METRE));
+                    logger.debug("Update channel 4 done");
+                    break;
+            }
+        } else {
+            logger.debug("Illegal data structure length '%d'", String.valueOf(rawdata).length());
+        }
+    }
+
+    @Override
+    protected UUID getDataUUID() {
+        switch (readSensor) {
+            case TEMPERATURE:
+                logger.debug("Return UUID Temperature");
+                return UUID.fromString(TEMPERATURE_UUID);
+            case HUMIDITY:
+                logger.debug("Return UUID Humidity");
+                return UUID.fromString(HUMIDITY_UUID);
+            case RADON_STA:
+                logger.debug("Return UUID Radon STA");
+                return UUID.fromString(RADON_STA_UUID);
+            case RADON_LTA:
+                logger.debug("Return UUID Radon LTA");
+                return UUID.fromString(RADON_LTA_UUID);
+            default:
+                logger.debug("Return UUID Default");
+                return UUID.fromString(RADON_STA_UUID);
+        }
+    }
+
+    private int intFromBytes(int lowByte, int highByte) {
+        return (highByte & 0xFF) << 8 | (lowByte & 0xFF);
+    }
+
+}
index 3f7419ac5f0d4cb33bf3c1a3dea1b1272eea1389..03d7156d4b9b428284e09442c117eb9d0ad9080e 100644 (file)
                        </parameter>
                </config-description>
        </thing-type>
+       <thing-type id="airthings_wave_gen1">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="roaming" />
+                       <bridge-type-ref id="bluegiga" />
+                       <bridge-type-ref id="bluez" />
+               </supported-bridge-type-refs>
+
+               <label>Airthings Wave Gen 1</label>
+               <description>Smart Radon Monitor</description>
+
+               <channels>
+                       <channel id="rssi" typeId="rssi" />
+
+                       <channel id="humidity" typeId="airthings_humidity" />
+                       <channel id="temperature" typeId="airthings_temperature" />
+                       <channel id="radon_st_avg" typeId="airthings_radon_st_avg" />
+                       <channel id="radon_lt_avg" typeId="airthings_radon_lt_avg" />
+               </channels>
+
+               <config-description>
+                       <parameter name="address" type="text">
+                               <label>Address</label>
+                               <description>Bluetooth address in XX:XX:XX:XX:XX:XX format</description>
+                       </parameter>
+                       <parameter name="refreshInterval" type="integer" min="10">
+                               <label>Refresh Interval</label>
+                               <description>States how often a refresh shall occur in seconds. This could have impact to battery lifetime</description>
+                               <default>300</default>
+                       </parameter>
+               </config-description>
+       </thing-type>
 
        <channel-type id="airthings_humidity">
                <item-type>Number:Dimensionless</item-type>