[airthings] Add support for Airthings Wave Gen 1 (#11052)
Signed-off-by: dw-8 <davy.wong.on+github@gmail.com>
This commit is contained in:
parent
fa699b807e
commit
12dc0c37bb
|
@ -7,10 +7,10 @@ This extension adds support for [Airthings](https://www.airthings.com) indoor ai
|
||||||
Following thing types are supported by this extension:
|
Following thing types are supported by this extension:
|
||||||
|
|
||||||
| Thing Type ID | Description |
|
| Thing Type ID | Description |
|
||||||
| ------------------- | ------------------------- |
|
| ------------------- | -------------------------------------- |
|
||||||
| airthings_wave_plus | Airthings Wave Plus |
|
| airthings_wave_plus | Airthings Wave Plus |
|
||||||
| airthings_wave_mini | Airthings Wave Mini |
|
| airthings_wave_mini | Airthings Wave Mini |
|
||||||
|
| airthings_wave_gen1 | Airthings Wave 1st Gen (SN 2900xxxxxx) |
|
||||||
|
|
||||||
## Discovery
|
## 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_st_avg | Number:Density | The measured radon short term average level |
|
||||||
| radon_lt_avg | Number:Density | The measured radon long 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
|
## Example
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ import tech.units.indriya.unit.TransformedUnit;
|
||||||
*
|
*
|
||||||
* @author Pauli Anttila - Initial contribution
|
* @author Pauli Anttila - Initial contribution
|
||||||
* @author Kai Kreuzer - Added Airthings Wave Mini support
|
* @author Kai Kreuzer - Added Airthings Wave Mini support
|
||||||
|
* @author Davy Wong - Added Airthings Wave Gen 1 support
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class AirthingsBindingConstants {
|
public class AirthingsBindingConstants {
|
||||||
|
@ -45,9 +46,11 @@ public class AirthingsBindingConstants {
|
||||||
BluetoothBindingConstants.BINDING_ID, "airthings_wave_plus");
|
BluetoothBindingConstants.BINDING_ID, "airthings_wave_plus");
|
||||||
public static final ThingTypeUID THING_TYPE_AIRTHINGS_WAVE_MINI = new ThingTypeUID(
|
public static final ThingTypeUID THING_TYPE_AIRTHINGS_WAVE_MINI = new ThingTypeUID(
|
||||||
BluetoothBindingConstants.BINDING_ID, "airthings_wave_mini");
|
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,
|
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
|
// Channel IDs
|
||||||
public static final String CHANNEL_ID_HUMIDITY = "humidity";
|
public static final String CHANNEL_ID_HUMIDITY = "humidity";
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.osgi.service.component.annotations.Component;
|
||||||
*
|
*
|
||||||
* @author Pauli Anttila - Initial contribution
|
* @author Pauli Anttila - Initial contribution
|
||||||
* @author Kai Kreuzer - Added Airthings Wave Mini support
|
* @author Kai Kreuzer - Added Airthings Wave Mini support
|
||||||
|
* @author Davy Wong - Added Airthings Wave Gen 1 support
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
|
@ -43,6 +44,7 @@ public class AirthingsDiscoveryParticipant implements BluetoothDiscoveryParticip
|
||||||
|
|
||||||
private static final String WAVE_PLUS_MODEL = "2930";
|
private static final String WAVE_PLUS_MODEL = "2930";
|
||||||
private static final String WAVE_MINI_MODEL = "2920";
|
private static final String WAVE_MINI_MODEL = "2920";
|
||||||
|
private static final String WAVE_GEN1_MODEL = "2900"; // Wave 1st Gen SN 2900xxxxxx
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||||
|
@ -60,6 +62,10 @@ public class AirthingsDiscoveryParticipant implements BluetoothDiscoveryParticip
|
||||||
return new ThingUID(AirthingsBindingConstants.THING_TYPE_AIRTHINGS_WAVE_MINI,
|
return new ThingUID(AirthingsBindingConstants.THING_TYPE_AIRTHINGS_WAVE_MINI,
|
||||||
device.getAdapter().getUID(), device.getAddress().toString().toLowerCase().replace(":", ""));
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -79,6 +85,9 @@ public class AirthingsDiscoveryParticipant implements BluetoothDiscoveryParticip
|
||||||
if (WAVE_MINI_MODEL.equals(device.getModel())) {
|
if (WAVE_MINI_MODEL.equals(device.getModel())) {
|
||||||
return createResult(device, thingUID, "Airthings Wave Mini");
|
return createResult(device, thingUID, "Airthings Wave Mini");
|
||||||
}
|
}
|
||||||
|
if (WAVE_GEN1_MODEL.equals(device.getModel())) {
|
||||||
|
return createResult(device, thingUID, "Airthings Wave Gen 1");
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.osgi.service.component.annotations.Component;
|
||||||
*
|
*
|
||||||
* @author Pauli Anttila - Initial contribution
|
* @author Pauli Anttila - Initial contribution
|
||||||
* @author Kai Kreuzer - Added Airthings Wave Mini support
|
* @author Kai Kreuzer - Added Airthings Wave Mini support
|
||||||
|
* @author Davy Wong - Added Airthings Wave Gen 1 support
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.airthings")
|
@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)) {
|
if (thingTypeUID.equals(AirthingsBindingConstants.THING_TYPE_AIRTHINGS_WAVE_MINI)) {
|
||||||
return new AirthingsWaveMiniHandler(thing);
|
return new AirthingsWaveMiniHandler(thing);
|
||||||
}
|
}
|
||||||
|
if (thingTypeUID.equals(AirthingsBindingConstants.THING_TYPE_AIRTHINGS_WAVE_GEN1)) {
|
||||||
|
return new AirthingsWaveGen1Handler(thing);
|
||||||
|
}
|
||||||
return null;
|
return 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -69,6 +69,37 @@
|
||||||
</parameter>
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
</thing-type>
|
</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">
|
<channel-type id="airthings_humidity">
|
||||||
<item-type>Number:Dimensionless</item-type>
|
<item-type>Number:Dimensionless</item-type>
|
||||||
|
|
Loading…
Reference in New Issue