[fineoffsetweatherstation] Add support for ELV protocol (#13138)
* [fineoffsetweatherstation] add support for ELV protocol * [fineoffsetweatherstation] remove german translations * [fineoffsetweatherstation] add representationProperty for gateway, fix unit of irradiation-uv * [fineoffsetweatherstation] add representationProperties in descriptor xml file * [fineoffsetweatherstation] set channels values to undef in case of communication errors * [fineoffsetweatherstation] use lock for request synchronisation * [fineoffsetweatherstation] marke sensors as gone, if they got unpaired resolves #12763 Signed-off-by: Andreas Berger <andreas@berger-freelancer.com>
This commit is contained in:
parent
9ec6f7d12e
commit
84ea355e76
|
@ -7,6 +7,7 @@ Some of these brands are e.g.:
|
||||||
* Aercus
|
* Aercus
|
||||||
* Ambient Weather
|
* Ambient Weather
|
||||||
* Ecowitt
|
* Ecowitt
|
||||||
|
* ELV
|
||||||
* Frogitt
|
* Frogitt
|
||||||
* Misol
|
* Misol
|
||||||
* Pantech
|
* Pantech
|
||||||
|
@ -40,6 +41,7 @@ This binding works offline by [implementing the wire protocol](https://osswww.ec
|
||||||
- WH2680
|
- WH2680
|
||||||
- WH2900
|
- WH2900
|
||||||
- WH2950
|
- WH2950
|
||||||
|
- WS980 ELV (tested)
|
||||||
- `sensor`: A Fine Offset sensor which is connected to the bridge with the ThingTypeUID `fineoffsetweatherstation:sensor`.
|
- `sensor`: A Fine Offset sensor which is connected to the bridge with the ThingTypeUID `fineoffsetweatherstation:sensor`.
|
||||||
Since the gateway collects all the sensor data and harmonizes them, the sensor thing itself will only hold information about the signal and battery status.
|
Since the gateway collects all the sensor data and harmonizes them, the sensor thing itself will only hold information about the signal and battery status.
|
||||||
This is a list of sensors supported by the protocol:
|
This is a list of sensors supported by the protocol:
|
||||||
|
@ -68,12 +70,13 @@ This binding support discovery of Fine Offset gateway devices by sending a broad
|
||||||
|
|
||||||
### `gateway` Thing Configuration
|
### `gateway` Thing Configuration
|
||||||
|
|
||||||
| Name | Type | Description | Default | Required | Advanced |
|
| Name | Type | Description | Default | Required | Advanced |
|
||||||
|-------------------|---------|-------------------------------------------------------------------------------------|---------|----------|----------|
|
|------------------|---------|----------------------------------------------------------------------------------------------|---------|----------|----------|
|
||||||
| ip | text | The Hostname or IP address of the device | N/A | yes | no |
|
| ip | text | The Hostname or IP address of the device | N/A | yes | no |
|
||||||
| port | integer | The network port of the gateway | 45000 | yes | no |
|
| port | integer | The network port of the gateway | 45000 | yes | no |
|
||||||
| pollingInterval | integer | Polling period for refreshing the data in seconds | 16 | yes | yes |
|
| protocol | text | The protocol to use for communicating with the gateway, valid values are: `DEFAULT` or `ELV` | DEFAULT | no | no |
|
||||||
| discoverInterval | integer | Interval in seconds to fetch registered sensors, battery status and signal strength | 900 | yes | yes |
|
| pollingInterval | integer | Polling period for refreshing the data in seconds | 16 | yes | yes |
|
||||||
|
| discoverInterval | integer | Interval in seconds to fetch registered sensors, battery status and signal strength | 900 | yes | yes |
|
||||||
|
|
||||||
### `sensor` Thing Configuration
|
### `sensor` Thing Configuration
|
||||||
|
|
||||||
|
@ -267,7 +270,13 @@ This is an example configuration for the WH2650 gateway
|
||||||
_weatherstation.things_:
|
_weatherstation.things_:
|
||||||
|
|
||||||
```xtend
|
```xtend
|
||||||
Bridge fineoffsetweatherstation:gateway:3906700515 "Weather station" [ip="192.168.1.42", port="45000", discoverInterval="900", pollingInterval="16"] {
|
Bridge fineoffsetweatherstation:gateway:3906700515 "Weather station" [
|
||||||
|
ip="192.168.1.42",
|
||||||
|
port="45000",
|
||||||
|
discoverInterval="900",
|
||||||
|
pollingInterval="16",
|
||||||
|
protocol="DEFAULT"
|
||||||
|
] {
|
||||||
Thing sensor WH25 "WH25" [sensor="WH25"]
|
Thing sensor WH25 "WH25" [sensor="WH25"]
|
||||||
Thing sensor WH65 "WH65" [sensor="WH65"]
|
Thing sensor WH65 "WH65" [sensor="WH65"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,11 @@
|
||||||
<name>openHAB Add-ons :: Bundles :: Fine Offset Weather Station</name>
|
<name>openHAB Add-ons :: Bundles :: Fine Offset Weather Station</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-csv</artifactId>
|
||||||
|
<version>1.9.0</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.assertj</groupId>
|
||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>assertj-core</artifactId>
|
||||||
|
|
|
@ -14,6 +14,7 @@ package org.openhab.binding.fineoffsetweatherstation.internal;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link FineOffsetGatewayConfiguration} class contains fields mapping thing configuration parameters.
|
* The {@link FineOffsetGatewayConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
@ -26,8 +27,12 @@ public class FineOffsetGatewayConfiguration {
|
||||||
public static final String IP = "ip";
|
public static final String IP = "ip";
|
||||||
public static final String PORT = "port";
|
public static final String PORT = "port";
|
||||||
|
|
||||||
|
public static final String PROTOCOL = "protocol";
|
||||||
|
|
||||||
public @Nullable String ip;
|
public @Nullable String ip;
|
||||||
public int port = 45000;
|
public int port = 45000;
|
||||||
public int pollingInterval = 16;
|
public int pollingInterval = 16;
|
||||||
public int discoverInterval = 900;
|
public int discoverInterval = 900;
|
||||||
|
|
||||||
|
public Protocol protocol = Protocol.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,12 @@ import java.net.DatagramSocket;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -36,7 +38,12 @@ import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetSensorCon
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants;
|
import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
|
import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.service.GatewayQueryService;
|
||||||
|
import org.openhab.core.config.core.Configuration;
|
||||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
|
@ -60,13 +67,13 @@ import org.slf4j.LoggerFactory;
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = { DiscoveryService.class, FineOffsetGatewayDiscoveryService.class }, immediate = true)
|
@Component(service = { DiscoveryService.class, FineOffsetGatewayDiscoveryService.class }, immediate = true)
|
||||||
public class FineOffsetGatewayDiscoveryService extends AbstractDiscoveryService {
|
public class FineOffsetGatewayDiscoveryService extends AbstractDiscoveryService {
|
||||||
public static final int DISCOVERY_PORT = 46000;
|
private static final int DISCOVERY_PORT = 46000;
|
||||||
private static final int BUFFER_LENGTH = 255;
|
private static final int BUFFER_LENGTH = 255;
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayDiscoveryService.class);
|
private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayDiscoveryService.class);
|
||||||
|
|
||||||
private static final long REFRESH_INTERVAL = 600;
|
private static final long REFRESH_INTERVAL = 600;
|
||||||
private static final int DISCOVERY_TIME = 5;
|
private static final int DISCOVERY_TIME = 10;
|
||||||
private final TranslationProvider translationProvider;
|
private final TranslationProvider translationProvider;
|
||||||
private final LocaleProvider localeProvider;
|
private final LocaleProvider localeProvider;
|
||||||
private final @Nullable Bundle bundle;
|
private final @Nullable Bundle bundle;
|
||||||
|
@ -124,7 +131,11 @@ public class FineOffsetGatewayDiscoveryService extends AbstractDiscoveryService
|
||||||
|
|
||||||
private void discover() {
|
private void discover() {
|
||||||
startReceiverThread();
|
startReceiverThread();
|
||||||
NetUtil.getAllBroadcastAddresses().forEach(this::sendDiscoveryRequest);
|
NetUtil.getAllBroadcastAddresses().forEach(broadcastAddress -> {
|
||||||
|
sendBroadcastPacket(broadcastAddress, Command.CMD_BROADCAST.getPayloadAlternative());
|
||||||
|
scheduler.schedule(() -> sendBroadcastPacket(broadcastAddress, Command.CMD_BROADCAST.getPayload()), 5,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSensors(ThingUID bridgeUID, Collection<SensorDevice> sensorDevices) {
|
public void addSensors(ThingUID bridgeUID, Collection<SensorDevice> sensorDevices) {
|
||||||
|
@ -168,15 +179,39 @@ public class FineOffsetGatewayDiscoveryService extends AbstractDiscoveryService
|
||||||
properties.put(Thing.PROPERTY_MAC_ADDRESS, Utils.toHexString(macAddr, macAddr.length, ":"));
|
properties.put(Thing.PROPERTY_MAC_ADDRESS, Utils.toHexString(macAddr, macAddr.length, ":"));
|
||||||
properties.put(FineOffsetGatewayConfiguration.IP, ip);
|
properties.put(FineOffsetGatewayConfiguration.IP, ip);
|
||||||
properties.put(FineOffsetGatewayConfiguration.PORT, port);
|
properties.put(FineOffsetGatewayConfiguration.PORT, port);
|
||||||
|
FineOffsetGatewayConfiguration config = new Configuration(properties).as(FineOffsetGatewayConfiguration.class);
|
||||||
|
Protocol protocol = determineProtocol(config);
|
||||||
|
if (protocol != null) {
|
||||||
|
properties.put(FineOffsetGatewayConfiguration.PROTOCOL, protocol.name());
|
||||||
|
}
|
||||||
|
|
||||||
ThingUID uid = new ThingUID(THING_TYPE_GATEWAY, id);
|
ThingUID uid = new ThingUID(THING_TYPE_GATEWAY, id);
|
||||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
|
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
|
||||||
|
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS)
|
||||||
.withLabel(translationProvider.getText(bundle, "thing.gateway.label", name, localeProvider.getLocale()))
|
.withLabel(translationProvider.getText(bundle, "thing.gateway.label", name, localeProvider.getLocale()))
|
||||||
.build();
|
.build();
|
||||||
thingDiscovered(result);
|
thingDiscovered(result);
|
||||||
logger.debug("Thing discovered '{}'", result);
|
logger.debug("Thing discovered '{}'", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Protocol determineProtocol(FineOffsetGatewayConfiguration config) {
|
||||||
|
ConversionContext conversionContext = new ConversionContext(ZoneOffset.UTC);
|
||||||
|
for (Protocol protocol : Protocol.values()) {
|
||||||
|
try (GatewayQueryService gatewayQueryService = protocol.getGatewayQueryService(config, null,
|
||||||
|
conversionContext)) {
|
||||||
|
List<MeasuredValue> result = gatewayQueryService.getMeasuredValues();
|
||||||
|
logger.trace("found {} measured values via protocol {}", result.size(), protocol);
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
synchronized @Nullable DatagramSocket getSocket() {
|
synchronized @Nullable DatagramSocket getSocket() {
|
||||||
DatagramSocket clientSocket = this.clientSocket;
|
DatagramSocket clientSocket = this.clientSocket;
|
||||||
if (clientSocket != null && clientSocket.isBound()) {
|
if (clientSocket != null && clientSocket.isBound()) {
|
||||||
|
@ -205,12 +240,13 @@ public class FineOffsetGatewayDiscoveryService extends AbstractDiscoveryService
|
||||||
this.clientSocket = null;
|
this.clientSocket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendDiscoveryRequest(String broadcastAddress) {
|
private void sendBroadcastPacket(String broadcastAddress, byte[] requestMessage) {
|
||||||
final @Nullable DatagramSocket socket = getSocket();
|
final @Nullable DatagramSocket socket = getSocket();
|
||||||
if (socket != null) {
|
if (socket != null) {
|
||||||
byte[] requestMessage = Command.CMD_BROADCAST.getPayload();
|
|
||||||
InetSocketAddress addr = new InetSocketAddress(broadcastAddress, DISCOVERY_PORT);
|
InetSocketAddress addr = new InetSocketAddress(broadcastAddress, DISCOVERY_PORT);
|
||||||
DatagramPacket datagramPacket = new DatagramPacket(requestMessage, requestMessage.length, addr);
|
DatagramPacket datagramPacket = new DatagramPacket(requestMessage, requestMessage.length, addr);
|
||||||
|
logger.trace("sendBroadcastPacket: send request: {}",
|
||||||
|
Utils.toHexString(requestMessage, requestMessage.length, ""));
|
||||||
try {
|
try {
|
||||||
socket.send(datagramPacket);
|
socket.send(datagramPacket);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
|
@ -12,10 +12,7 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.fineoffsetweatherstation.internal.domain;
|
package org.openhab.binding.fineoffsetweatherstation.internal.domain;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
|
import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,6 +22,12 @@ import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public enum Command {
|
public enum Command {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read current data,reply data size is 2bytes.
|
||||||
|
*/
|
||||||
|
CMD_WS980_LIVEDATA((byte) 0x0b, 2),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* send SSID and Password to WIFI module
|
* send SSID and Password to WIFI module
|
||||||
*/
|
*/
|
||||||
|
@ -238,21 +241,18 @@ public enum Command {
|
||||||
this.sizeBytes = sizeBytes;
|
this.sizeBytes = sizeBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSizeBytes() {
|
|
||||||
return sizeBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPayload() {
|
public byte[] getPayload() {
|
||||||
byte size = 3; // + rest of payload / not yet implemented
|
byte size = 3; // + rest of payload / not yet implemented
|
||||||
return new byte[] { (byte) 0xff, (byte) 0xff, code, size, (byte) (code + size) };
|
return new byte[] { (byte) 0xff, (byte) 0xff, code, size, (byte) (code + size) };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @Nullable Command findByCode(byte code) {
|
public byte[] getPayloadAlternative() {
|
||||||
return Arrays.stream(values()).filter(command -> command.getCode() == code).findFirst().orElse(null);
|
if (sizeBytes == 2) {
|
||||||
|
return new byte[] { (byte) 0xff, (byte) 0xff, code, 0, (byte) (sizeBytes + 2),
|
||||||
|
(byte) ((code + sizeBytes + 2) % 0xff) };
|
||||||
|
}
|
||||||
|
byte size = 3;
|
||||||
|
return new byte[] { (byte) 0xff, (byte) 0xff, code, size, (byte) ((code + size) % 0xff) };
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHeaderValid(byte[] data) {
|
public boolean isHeaderValid(byte[] data) {
|
||||||
|
|
|
@ -1,391 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2022 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.fineoffsetweatherstation.internal.domain;
|
|
||||||
|
|
||||||
import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_MAX_WIND_SPEED;
|
|
||||||
import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_MOISTURE;
|
|
||||||
import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_UV_INDEX;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
|
||||||
import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
|
|
||||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
|
||||||
import org.openhab.core.types.State;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The measurands of supported by the gateway.
|
|
||||||
*
|
|
||||||
* @author Andreas Berger - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public enum Measurand {
|
|
||||||
|
|
||||||
INTEMP("temperature-indoor", (byte) 0x01, "Indoor Temperature", MeasureType.TEMPERATURE,
|
|
||||||
DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_INDOOR_TEMPERATURE),
|
|
||||||
|
|
||||||
OUTTEMP("temperature-outdoor", (byte) 0x02, "Outdoor Temperature", MeasureType.TEMPERATURE,
|
|
||||||
DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_OUTDOOR_TEMPERATURE),
|
|
||||||
|
|
||||||
DEWPOINT("temperature-dew-point", (byte) 0x03, "Dew point", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
WINDCHILL("temperature-wind-chill", (byte) 0x04, "Wind chill", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
HEATINDEX("temperature-heat-index", (byte) 0x05, "Heat index", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
INHUMI("humidity-indoor", (byte) 0x06, "Indoor Humidity", MeasureType.PERCENTAGE),
|
|
||||||
|
|
||||||
OUTHUMI("humidity-outdoor", (byte) 0x07, "Outdoor Humidity", MeasureType.PERCENTAGE,
|
|
||||||
DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_ATMOSPHERIC_HUMIDITY),
|
|
||||||
|
|
||||||
ABSBARO("pressure-absolute", (byte) 0x08, "Absolutely pressure", MeasureType.PRESSURE),
|
|
||||||
|
|
||||||
RELBARO("pressure-relative", (byte) 0x09, "Relative pressure", MeasureType.PRESSURE,
|
|
||||||
DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_BAROMETRIC_PRESSURE),
|
|
||||||
|
|
||||||
WINDDIRECTION("direction-wind", (byte) 0x0A, "Wind Direction", MeasureType.DEGREE,
|
|
||||||
DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_WIND_DIRECTION),
|
|
||||||
|
|
||||||
WINDSPEED("speed-wind", (byte) 0x0B, "Wind Speed", MeasureType.SPEED,
|
|
||||||
DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_WIND_SPEED),
|
|
||||||
|
|
||||||
GUSTSPEED("speed-gust", (byte) 0x0C, "Gust Speed", MeasureType.SPEED,
|
|
||||||
DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_WIND_SPEED),
|
|
||||||
|
|
||||||
RAINEVENT("rain-event", (byte) 0x0D, "Rain Event", MeasureType.HEIGHT),
|
|
||||||
|
|
||||||
RAINRATE("rain-rate", (byte) 0x0E, "Rain Rate", MeasureType.HEIGHT_PER_HOUR),
|
|
||||||
|
|
||||||
RAINHOUR("rain-hour", (byte) 0x0F, "Rain hour", MeasureType.HEIGHT),
|
|
||||||
|
|
||||||
RAINDAY("rain-day", (byte) 0x10, "Rain Day", MeasureType.HEIGHT),
|
|
||||||
|
|
||||||
RAINWEEK("rain-week", (byte) 0x11, "Rain Week", MeasureType.HEIGHT),
|
|
||||||
|
|
||||||
RAINMONTH("rain-month", (byte) 0x12, "Rain Month", MeasureType.HEIGHT_BIG),
|
|
||||||
|
|
||||||
RAINYEAR("rain-year", (byte) 0x13, "Rain Year", MeasureType.HEIGHT_BIG),
|
|
||||||
|
|
||||||
RAINTOTALS("rain-total", (byte) 0x14, "Rain Totals", MeasureType.HEIGHT_BIG),
|
|
||||||
|
|
||||||
LIGHT("illumination", (byte) 0x15, "Light", MeasureType.LUX),
|
|
||||||
|
|
||||||
UV("irradiation-uv", (byte) 0x16, "UV", MeasureType.MICROWATT_PER_SQUARE_CENTIMETRE),
|
|
||||||
|
|
||||||
UVI("uv-index", (byte) 0x17, "UV index", MeasureType.BYTE, CHANNEL_TYPE_UV_INDEX),
|
|
||||||
|
|
||||||
TIME("time", (byte) 0x18, "Date and time", MeasureType.DATE_TIME2),
|
|
||||||
|
|
||||||
DAYLWINDMAX("wind-max-day", (byte) 0X19, "Day max wind", MeasureType.SPEED, CHANNEL_TYPE_MAX_WIND_SPEED),
|
|
||||||
|
|
||||||
TEMP1("temperature-channel-1", (byte) 0x1A, "Temperature 1", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TEMP2("temperature-channel-2", (byte) 0x1B, "Temperature 2", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TEMP3("temperature-channel-3", (byte) 0x1C, "Temperature 3", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TEMP4("temperature-channel-4", (byte) 0x1D, "Temperature 4", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TEMP5("temperature-channel-5", (byte) 0x1E, "Temperature 5", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TEMP6("temperature-channel-6", (byte) 0x1F, "Temperature 6", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TEMP7("temperature-channel-7", (byte) 0x20, "Temperature 7", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TEMP8("temperature-channel-8", (byte) 0x21, "Temperature 8", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
HUMI1("humidity-channel-1", (byte) 0x22, "Humidity 1", MeasureType.PERCENTAGE),
|
|
||||||
|
|
||||||
HUMI2("humidity-channel-2", (byte) 0x23, "Humidity 2", MeasureType.PERCENTAGE),
|
|
||||||
|
|
||||||
HUMI3("humidity-channel-3", (byte) 0x24, "Humidity 3", MeasureType.PERCENTAGE),
|
|
||||||
|
|
||||||
HUMI4("humidity-channel-4", (byte) 0x25, "Humidity 4", MeasureType.PERCENTAGE),
|
|
||||||
|
|
||||||
HUMI5("humidity-channel-5", (byte) 0x26, "Humidity 5", MeasureType.PERCENTAGE),
|
|
||||||
|
|
||||||
HUMI6("humidity-channel-6", (byte) 0x27, "Humidity 6", MeasureType.PERCENTAGE),
|
|
||||||
|
|
||||||
HUMI7("humidity-channel-7", (byte) 0x28, "Humidity 7", MeasureType.PERCENTAGE),
|
|
||||||
|
|
||||||
HUMI8("humidity-channel-8", (byte) 0x29, "Humidity 8", MeasureType.PERCENTAGE),
|
|
||||||
|
|
||||||
SOILTEMP1("temperature-soil-channel-1", (byte) 0x2B, "Soil Temperature 1", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP2("temperature-soil-channel-2", (byte) 0x2D, "Soil Temperature 2", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP3("temperature-soil-channel-3", (byte) 0x2F, "Soil Temperature 3", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP4("temperature-soil-channel-4", (byte) 0x31, "Soil Temperature 4", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP5("temperature-soil-channel-5", (byte) 0x33, "Soil Temperature 5", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP6("temperature-soil-channel-6", (byte) 0x35, "Soil Temperature 6", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP7("temperature-soil-channel-7", (byte) 0x37, "Soil Temperature 7", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP8("temperature-soil-channel-8", (byte) 0x39, "Soil Temperature 8", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP9("temperature-soil-channel-9", (byte) 0x3B, "Soil Temperature 9", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP10("temperature-soil-channel-10", (byte) 0x3D, "Soil Temperature 10", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP11("temperature-soil-channel-11", (byte) 0x3F, "Soil Temperature 11", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP12("temperature-soil-channel-12", (byte) 0x41, "Soil Temperature 12", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP13("temperature-soil-channel-13", (byte) 0x43, "Soil Temperature 13", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP14("temperature-soil-channel-14", (byte) 0x45, "Soil Temperature 14", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP15("temperature-soil-channel-15", (byte) 0x47, "Soil Temperature 15", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILTEMP16("temperature-soil-channel-16", (byte) 0x49, "Soil Temperature 16", MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
SOILMOISTURE1("moisture-soil-channel-1", (byte) 0x2C, "Soil Moisture 1", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE2("moisture-soil-channel-2", (byte) 0x2E, "Soil Moisture 2", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE3("moisture-soil-channel-3", (byte) 0x30, "Soil Moisture 3", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE4("moisture-soil-channel-4", (byte) 0x32, "Soil Moisture 4", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE5("moisture-soil-channel-5", (byte) 0x34, "Soil Moisture 5", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE6("moisture-soil-channel-6", (byte) 0x36, "Soil Moisture 6", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE7("moisture-soil-channel-7", (byte) 0x38, "Soil Moisture 7", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE8("moisture-soil-channel-8", (byte) 0x3A, "Soil Moisture 8", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE9("moisture-soil-channel-9", (byte) 0x3C, "Soil Moisture 9", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE10("moisture-soil-channel-10", (byte) 0x3E, "Soil Moisture 10", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE11("moisture-soil-channel-11", (byte) 0x40, "Soil Moisture 11", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE12("moisture-soil-channel-12", (byte) 0x42, "Soil Moisture 12", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE13("moisture-soil-channel-13", (byte) 0x44, "Soil Moisture 13", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE14("moisture-soil-channel-14", (byte) 0x46, "Soil Moisture 14", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE15("moisture-soil-channel-15", (byte) 0x48, "Soil Moisture 15", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
SOILMOISTURE16("moisture-soil-channel-16", (byte) 0x4A, "Soil Moisture 16", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
// will no longer be used
|
|
||||||
// skip battery-level, since it is read via Command.CMD_READ_SENSOR_ID_NEW
|
|
||||||
LOWBATT((byte) 0x4C, new Skip(1)),
|
|
||||||
|
|
||||||
PM25_24HAVG1("air-quality-24-hour-average-channel-1", (byte) 0x4D, "PM2.5 Air Quality 24 hour average channel 1",
|
|
||||||
MeasureType.PM25),
|
|
||||||
|
|
||||||
PM25_24HAVG2("air-quality-24-hour-average-channel-2", (byte) 0x4E, "PM2.5 Air Quality 24 hour average channel 2",
|
|
||||||
MeasureType.PM25),
|
|
||||||
|
|
||||||
PM25_24HAVG3("air-quality-24-hour-average-channel-3", (byte) 0x4F, "PM2.5 Air Quality 24 hour average channel 3",
|
|
||||||
MeasureType.PM25),
|
|
||||||
|
|
||||||
PM25_24HAVG4("air-quality-24-hour-average-channel-4", (byte) 0x50, "PM2.5 Air Quality 24 hour average channel 4",
|
|
||||||
MeasureType.PM25),
|
|
||||||
|
|
||||||
PM25_CH1("air-quality-channel-1", (byte) 0x2A, "PM2.5 Air Quality channel 1", MeasureType.PM25),
|
|
||||||
|
|
||||||
PM25_CH2("air-quality-channel-2", (byte) 0x51, "PM2.5 Air Quality channel 2", MeasureType.PM25),
|
|
||||||
|
|
||||||
PM25_CH3("air-quality-channel-3", (byte) 0x52, "PM2.5 Air Quality channel 3", MeasureType.PM25),
|
|
||||||
|
|
||||||
PM25_CH4("air-quality-channel-4", (byte) 0x53, "PM2.5 Air Quality channel 4", MeasureType.PM25),
|
|
||||||
|
|
||||||
LEAK_CH1("water-leak-channel-1", (byte) 0x58, "Leak channel 1", MeasureType.WATER_LEAK_DETECTION),
|
|
||||||
|
|
||||||
LEAK_CH2("water-leak-channel-2", (byte) 0x59, "Leak channel 2", MeasureType.WATER_LEAK_DETECTION),
|
|
||||||
|
|
||||||
LEAK_CH3("water-leak-channel-3", (byte) 0x5A, "Leak channel 3", MeasureType.WATER_LEAK_DETECTION),
|
|
||||||
|
|
||||||
LEAK_CH4("water-leak-channel-4", (byte) 0x5B, "Leak channel 4", MeasureType.WATER_LEAK_DETECTION),
|
|
||||||
|
|
||||||
// `LIGHTNING` is the name in the spec, so we keep it here as it
|
|
||||||
LIGHTNING("lightning-distance", (byte) 0x60, "lightning distance 1~40KM", MeasureType.LIGHTNING_DISTANCE),
|
|
||||||
|
|
||||||
LIGHTNING_TIME("lightning-time", (byte) 0x61, "lightning happened time", MeasureType.LIGHTNING_TIME),
|
|
||||||
|
|
||||||
// `LIGHTNING_POWER` is the name in the spec, so we keep it here as it
|
|
||||||
LIGHTNING_POWER("lightning-counter", (byte) 0x62, "lightning counter for the day", MeasureType.LIGHTNING_COUNTER),
|
|
||||||
|
|
||||||
TF_USR1("temperature-external-channel-1", (byte) 0x63, "Soil or Water temperature channel 1",
|
|
||||||
MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TF_USR2("temperature-external-channel-2", (byte) 0x64, "Soil or Water temperature channel 2",
|
|
||||||
MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TF_USR3("temperature-external-channel-3", (byte) 0x65, "Soil or Water temperature channel 3",
|
|
||||||
MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TF_USR4("temperature-external-channel-4", (byte) 0x66, "Soil or Water temperature channel 4",
|
|
||||||
MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TF_USR5("temperature-external-channel-5", (byte) 0x67, "Soil or Water temperature channel 5",
|
|
||||||
MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TF_USR6("temperature-external-channel-6", (byte) 0x68, "Soil or Water temperature channel 6",
|
|
||||||
MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TF_USR7("temperature-external-channel-7", (byte) 0x69, "Soil or Water temperature channel 7",
|
|
||||||
MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
TF_USR8("temperature-external-channel-8", (byte) 0x6A, "Soil or Water temperature channel 8",
|
|
||||||
MeasureType.TEMPERATURE),
|
|
||||||
|
|
||||||
ITEM_SENSOR_CO2((byte) 0x70,
|
|
||||||
new MeasurandParser("sensor-co2-temperature", "Temperature (CO₂-Sensor)", MeasureType.TEMPERATURE),
|
|
||||||
new MeasurandParser("sensor-co2-humidity", "Humidity (CO₂-Sensor)", MeasureType.PERCENTAGE),
|
|
||||||
new MeasurandParser("sensor-co2-pm10", "PM10 Air Quality (CO₂-Sensor)", MeasureType.PM10),
|
|
||||||
new MeasurandParser("sensor-co2-pm10-24-hour-average", "PM10 Air Quality 24 hour average (CO₂-Sensor)",
|
|
||||||
MeasureType.PM10),
|
|
||||||
new MeasurandParser("sensor-co2-pm25", "PM2.5 Air Quality (CO₂-Sensor)", MeasureType.PM25),
|
|
||||||
new MeasurandParser("sensor-co2-pm25-24-hour-average", "PM2.5 Air Quality 24 hour average (CO₂-Sensor)",
|
|
||||||
MeasureType.PM25),
|
|
||||||
new MeasurandParser("sensor-co2-co2", "CO₂", MeasureType.CO2),
|
|
||||||
new MeasurandParser("sensor-co2-co2-24-hour-average", "CO₂ 24 hour average", MeasureType.CO2),
|
|
||||||
// skip battery-level, since it is read via Command.CMD_READ_SENSOR_ID_NEW
|
|
||||||
new Skip(1)),
|
|
||||||
|
|
||||||
ITEM_LEAF_WETNESS_CH1("leaf-wetness-channel-1", (byte) 0x72, "Leaf Moisture channel 1", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
ITEM_LEAF_WETNESS_CH2("leaf-wetness-channel-2", (byte) 0x73, "Leaf Moisture channel 2", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
ITEM_LEAF_WETNESS_CH3("leaf-wetness-channel-3", (byte) 0x74, "Leaf Moisture channel 3", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
ITEM_LEAF_WETNESS_CH4("leaf-wetness-channel-4", (byte) 0x75, "Leaf Moisture channel 4", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
ITEM_LEAF_WETNESS_CH5("leaf-wetness-channel-5", (byte) 0x76, "Leaf Moisture channel 5", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
ITEM_LEAF_WETNESS_CH6("leaf-wetness-channel-6", (byte) 0x77, "Leaf Moisture channel 6", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
ITEM_LEAF_WETNESS_CH7("leaf-wetness-channel-7", (byte) 0x78, "Leaf Moisture channel 7", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),
|
|
||||||
|
|
||||||
ITEM_LEAF_WETNESS_CH8("leaf-wetness-channel-8", (byte) 0x79, "Leaf Moisture channel 8", MeasureType.PERCENTAGE,
|
|
||||||
CHANNEL_TYPE_MOISTURE),;
|
|
||||||
|
|
||||||
private static final Map<Byte, Measurand> MEASURANDS = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (Measurand value : values()) {
|
|
||||||
MEASURANDS.put(value.code, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final byte code;
|
|
||||||
private final Parser[] parsers;
|
|
||||||
|
|
||||||
Measurand(String channelId, byte code, String name, MeasureType measureType) {
|
|
||||||
this(channelId, code, name, measureType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Measurand(String channelId, byte code, String name, MeasureType measureType,
|
|
||||||
@Nullable ChannelTypeUID channelTypeUID) {
|
|
||||||
this(code, new MeasurandParser(channelId, name, measureType, channelTypeUID));
|
|
||||||
}
|
|
||||||
|
|
||||||
Measurand(byte code, Parser... parsers) {
|
|
||||||
this.code = code;
|
|
||||||
this.parsers = parsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @Nullable Measurand getByCode(byte code) {
|
|
||||||
return MEASURANDS.get(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int extractMeasuredValues(byte[] data, int offset, ConversionContext context, List<MeasuredValue> result) {
|
|
||||||
int subOffset = 0;
|
|
||||||
for (Parser parser : parsers) {
|
|
||||||
subOffset += parser.extractMeasuredValues(data, offset + subOffset, context, result);
|
|
||||||
}
|
|
||||||
return subOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface Parser {
|
|
||||||
int extractMeasuredValues(byte[] data, int offset, ConversionContext context, List<MeasuredValue> result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Skip implements Parser {
|
|
||||||
private final int skip;
|
|
||||||
|
|
||||||
public Skip(int skip) {
|
|
||||||
this.skip = skip;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
|
|
||||||
List<MeasuredValue> result) {
|
|
||||||
return skip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MeasurandParser implements Parser {
|
|
||||||
private final String name;
|
|
||||||
private final String channelId;
|
|
||||||
private final MeasureType measureType;
|
|
||||||
private final @Nullable ChannelTypeUID channelTypeUID;
|
|
||||||
|
|
||||||
MeasurandParser(String channelId, String name, MeasureType measureType) {
|
|
||||||
this(channelId, name, measureType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
MeasurandParser(String channelId, String name, MeasureType measureType,
|
|
||||||
@Nullable ChannelTypeUID channelTypeUID) {
|
|
||||||
this.channelId = channelId;
|
|
||||||
this.name = name;
|
|
||||||
this.measureType = measureType;
|
|
||||||
this.channelTypeUID = channelTypeUID == null ? measureType.getChannelTypeId() : channelTypeUID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
|
|
||||||
List<MeasuredValue> result) {
|
|
||||||
State state = measureType.toState(data, offset, context);
|
|
||||||
if (state != null) {
|
|
||||||
result.add(new MeasuredValue(measureType, channelId, channelTypeUID, state, name));
|
|
||||||
}
|
|
||||||
return measureType.getByteSize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.fineoffsetweatherstation.internal.domain;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.apache.commons.csv.CSVFormat;
|
||||||
|
import org.apache.commons.csv.CSVParser;
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
||||||
|
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds all the measurands supported by the gateway.
|
||||||
|
*
|
||||||
|
* @author Andreas Berger - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Measurands {
|
||||||
|
|
||||||
|
private static final Map<Protocol, Measurands> INSTANCES = new HashMap<>();
|
||||||
|
private final Map<Byte, List<Parser>> parsersPerCode = new HashMap<>();
|
||||||
|
|
||||||
|
private Measurands(Protocol protocol) {
|
||||||
|
try (InputStream data = Measurands.class.getResourceAsStream("/measurands.csv")) {
|
||||||
|
if (data == null) {
|
||||||
|
throw new IllegalStateException("Missing measurands.csv");
|
||||||
|
}
|
||||||
|
CSVFormat csvFormat = CSVFormat.Builder.create().setHeader().setSkipHeaderRecord(true).build();
|
||||||
|
CSVParser.parse(new InputStreamReader(data), csvFormat).forEach(row -> {
|
||||||
|
|
||||||
|
byte code = Byte.valueOf(row.get("Code").replace("0x", ""), 16);
|
||||||
|
Optional<Integer> skip = Optional.ofNullable(row.get("Skip")).filter(Predicate.not(String::isBlank))
|
||||||
|
.map(Integer::valueOf);
|
||||||
|
int index = Optional.ofNullable(row.get("Index")).filter(Predicate.not(String::isBlank))
|
||||||
|
.map(Integer::valueOf).orElse(0);
|
||||||
|
|
||||||
|
Parser parser;
|
||||||
|
if (skip.isPresent()) {
|
||||||
|
parser = new Skip(skip.get(), index);
|
||||||
|
} else {
|
||||||
|
String name = row.get("Name");
|
||||||
|
String channel = row.get("Channel");
|
||||||
|
|
||||||
|
ChannelTypeUID channelType = Optional.ofNullable(row.get("ChannelType"))
|
||||||
|
.filter(Predicate.not(String::isBlank)).map(s -> {
|
||||||
|
if (s.contains(":")) {
|
||||||
|
return new ChannelTypeUID(s);
|
||||||
|
} else {
|
||||||
|
return new ChannelTypeUID(FineOffsetWeatherStationBindingConstants.BINDING_ID, s);
|
||||||
|
}
|
||||||
|
}).orElse(null);
|
||||||
|
String measurandString = protocol == Protocol.DEFAULT ? row.get("MeasureType_DEFAULT")
|
||||||
|
: Optional.ofNullable(row.get("MeasureType_" + protocol.name()))
|
||||||
|
.filter(Predicate.not(String::isBlank))
|
||||||
|
.orElseGet(() -> row.get("MeasureType_DEFAULT"));
|
||||||
|
parser = new MeasurandParser(channel, name, MeasureType.valueOf(measurandString), index,
|
||||||
|
channelType);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Parser> parsers = parsersPerCode.computeIfAbsent(code, aByte -> new ArrayList<>());
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
if (parsers != null) {
|
||||||
|
parsers.add(parser);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (List<Parser> parsers : parsersPerCode.values()) {
|
||||||
|
parsers.sort(Comparator.comparing(Parser::getIndex));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Failed to read measurands.csv", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Measurands getInstance(Protocol protocol) {
|
||||||
|
synchronized (INSTANCES) {
|
||||||
|
return Objects.requireNonNull(INSTANCES.computeIfAbsent(protocol, Measurands::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class Parser {
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
public Parser(int index) {
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
|
||||||
|
List<MeasuredValue> result);
|
||||||
|
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Skip extends Parser {
|
||||||
|
private final int skip;
|
||||||
|
|
||||||
|
public Skip(int skip, int index) {
|
||||||
|
super(index);
|
||||||
|
this.skip = skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
|
||||||
|
List<MeasuredValue> result) {
|
||||||
|
return skip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MeasurandParser extends Parser {
|
||||||
|
private final String name;
|
||||||
|
private final String channelId;
|
||||||
|
private final MeasureType measureType;
|
||||||
|
private final @Nullable ChannelTypeUID channelTypeUID;
|
||||||
|
|
||||||
|
MeasurandParser(String channelId, String name, MeasureType measureType, int index,
|
||||||
|
@Nullable ChannelTypeUID channelTypeUID) {
|
||||||
|
super(index);
|
||||||
|
this.channelId = channelId;
|
||||||
|
this.name = name;
|
||||||
|
this.measureType = measureType;
|
||||||
|
this.channelTypeUID = channelTypeUID == null ? measureType.getChannelTypeId() : channelTypeUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int extractMeasuredValues(byte[] data, int offset, ConversionContext context,
|
||||||
|
List<MeasuredValue> result) {
|
||||||
|
State state = measureType.toState(data, offset, context);
|
||||||
|
if (state != null) {
|
||||||
|
result.add(new MeasuredValue(measureType, channelId, channelTypeUID, state, name));
|
||||||
|
}
|
||||||
|
return measureType.getByteSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int extractMeasuredValues(byte code, byte[] data, int offset, ConversionContext context,
|
||||||
|
List<MeasuredValue> result) {
|
||||||
|
List<Parser> parsers = parsersPerCode.get(code);
|
||||||
|
if (parsers == null) {
|
||||||
|
throw new IllegalArgumentException("No measurement for code 0x" + Integer.toHexString(code) + " defined");
|
||||||
|
}
|
||||||
|
int subOffset = 0;
|
||||||
|
for (Parser parser : parsers) {
|
||||||
|
subOffset += parser.extractMeasuredValues(data, offset + subOffset, context, result);
|
||||||
|
}
|
||||||
|
return subOffset;
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt
|
||||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
||||||
import static org.openhab.core.library.unit.SIUnits.METRE;
|
import static org.openhab.core.library.unit.SIUnits.METRE;
|
||||||
import static org.openhab.core.library.unit.SIUnits.PASCAL;
|
import static org.openhab.core.library.unit.SIUnits.PASCAL;
|
||||||
|
import static org.openhab.core.library.unit.SIUnits.SQUARE_METRE;
|
||||||
import static org.openhab.core.library.unit.Units.DEGREE_ANGLE;
|
import static org.openhab.core.library.unit.Units.DEGREE_ANGLE;
|
||||||
import static org.openhab.core.library.unit.Units.METRE_PER_SECOND;
|
import static org.openhab.core.library.unit.Units.METRE_PER_SECOND;
|
||||||
import static org.openhab.core.library.unit.Units.MICROGRAM_PER_CUBICMETRE;
|
import static org.openhab.core.library.unit.Units.MICROGRAM_PER_CUBICMETRE;
|
||||||
|
@ -84,6 +85,8 @@ public enum MeasureType {
|
||||||
|
|
||||||
HEIGHT_PER_HOUR(MILLIMETRE_PER_HOUR, 2, CHANNEL_TYPE_RAIN_RATE, (data, offset) -> toUInt16(data, offset) / 10.),
|
HEIGHT_PER_HOUR(MILLIMETRE_PER_HOUR, 2, CHANNEL_TYPE_RAIN_RATE, (data, offset) -> toUInt16(data, offset) / 10.),
|
||||||
|
|
||||||
|
HEIGHT_PER_HOUR_BIG(MILLIMETRE_PER_HOUR, 4, CHANNEL_TYPE_RAIN_RATE, (data, offset) -> toUInt32(data, offset) / 10.),
|
||||||
|
|
||||||
LUX(Units.LUX, 4, CHANNEL_TYPE_ILLUMINATION, (data, offset) -> toUInt32(data, offset) / 10.),
|
LUX(Units.LUX, 4, CHANNEL_TYPE_ILLUMINATION, (data, offset) -> toUInt32(data, offset) / 10.),
|
||||||
|
|
||||||
PM25(MICROGRAM_PER_CUBICMETRE, 2, CHANNEL_TYPE_PM25, (data, offset) -> toUInt16(data, offset) / 10.),
|
PM25(MICROGRAM_PER_CUBICMETRE, 2, CHANNEL_TYPE_PM25, (data, offset) -> toUInt16(data, offset) / 10.),
|
||||||
|
@ -104,8 +107,8 @@ public enum MeasureType {
|
||||||
(data, offset, context) -> new DateTimeType(
|
(data, offset, context) -> new DateTimeType(
|
||||||
ZonedDateTime.ofInstant(Instant.ofEpochSecond(toUInt32(data, offset)), context.getZoneId()))),
|
ZonedDateTime.ofInstant(Instant.ofEpochSecond(toUInt32(data, offset)), context.getZoneId()))),
|
||||||
|
|
||||||
MICROWATT_PER_SQUARE_CENTIMETRE(Units.MICROWATT_PER_SQUARE_CENTIMETRE, 2, CHANNEL_TYPE_UV_RADIATION,
|
MILLIWATT_PER_SQUARE_METRE(MILLI(Units.WATT).divide(SQUARE_METRE), 2, CHANNEL_TYPE_UV_RADIATION,
|
||||||
Utils::toUInt16),
|
(data, offset) -> Utils.toUInt16(data, offset) / 10.),
|
||||||
|
|
||||||
BYTE(1, null, (data, offset, context) -> new DecimalType(toUInt8(data[offset]))),
|
BYTE(1, null, (data, offset, context) -> new DecimalType(toUInt8(data[offset]))),
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.fineoffsetweatherstation.internal.domain;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetGatewayConfiguration;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.handler.ThingStatusListener;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.service.ELVGatewayQueryService;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.service.FineOffsetGatewayQueryService;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.service.GatewayQueryService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The protocol defining the way the data is parsed
|
||||||
|
*
|
||||||
|
* @author Andreas Berger - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum Protocol {
|
||||||
|
DEFAULT(FineOffsetGatewayQueryService::new),
|
||||||
|
ELV(ELVGatewayQueryService::new);
|
||||||
|
|
||||||
|
private final GatewayQueryServiceFactory queryServiceFactory;
|
||||||
|
|
||||||
|
Protocol(GatewayQueryServiceFactory queryServiceFactory) {
|
||||||
|
this.queryServiceFactory = queryServiceFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GatewayQueryService getGatewayQueryService(FineOffsetGatewayConfiguration config,
|
||||||
|
@Nullable ThingStatusListener thingStatusListener, ConversionContext conversionContext) {
|
||||||
|
return queryServiceFactory.newInstance(config, thingStatusListener, conversionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface GatewayQueryServiceFactory {
|
||||||
|
GatewayQueryService newInstance(FineOffsetGatewayConfiguration config,
|
||||||
|
@Nullable ThingStatusListener thingStatusListener, ConversionContext conversionContext);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
@ -36,7 +37,7 @@ import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewa
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SystemInfo;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SystemInfo;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.service.FineOffsetGatewayQueryService;
|
import org.openhab.binding.fineoffsetweatherstation.internal.service.GatewayQueryService;
|
||||||
import org.openhab.core.i18n.LocaleProvider;
|
import org.openhab.core.i18n.LocaleProvider;
|
||||||
import org.openhab.core.i18n.TimeZoneProvider;
|
import org.openhab.core.i18n.TimeZoneProvider;
|
||||||
import org.openhab.core.i18n.TranslationProvider;
|
import org.openhab.core.i18n.TranslationProvider;
|
||||||
|
@ -56,6 +57,7 @@ import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
import org.osgi.framework.FrameworkUtil;
|
import org.osgi.framework.FrameworkUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -76,7 +78,7 @@ public class FineOffsetGatewayHandler extends BaseBridgeHandler {
|
||||||
private final Bundle bundle;
|
private final Bundle bundle;
|
||||||
private final ConversionContext conversionContext;
|
private final ConversionContext conversionContext;
|
||||||
|
|
||||||
private @Nullable FineOffsetGatewayQueryService gatewayQueryService;
|
private @Nullable GatewayQueryService gatewayQueryService;
|
||||||
|
|
||||||
private final FineOffsetGatewayDiscoveryService gatewayDiscoveryService;
|
private final FineOffsetGatewayDiscoveryService gatewayDiscoveryService;
|
||||||
private final ChannelTypeRegistry channelTypeRegistry;
|
private final ChannelTypeRegistry channelTypeRegistry;
|
||||||
|
@ -94,7 +96,7 @@ public class FineOffsetGatewayHandler extends BaseBridgeHandler {
|
||||||
ChannelTypeRegistry channelTypeRegistry, TranslationProvider translationProvider,
|
ChannelTypeRegistry channelTypeRegistry, TranslationProvider translationProvider,
|
||||||
LocaleProvider localeProvider, TimeZoneProvider timeZoneProvider) {
|
LocaleProvider localeProvider, TimeZoneProvider timeZoneProvider) {
|
||||||
super(bridge);
|
super(bridge);
|
||||||
bridgeUID = bridge.getUID();
|
this.bridgeUID = bridge.getUID();
|
||||||
this.gatewayDiscoveryService = gatewayDiscoveryService;
|
this.gatewayDiscoveryService = gatewayDiscoveryService;
|
||||||
this.channelTypeRegistry = channelTypeRegistry;
|
this.channelTypeRegistry = channelTypeRegistry;
|
||||||
this.translationProvider = translationProvider;
|
this.translationProvider = translationProvider;
|
||||||
|
@ -110,7 +112,7 @@ public class FineOffsetGatewayHandler extends BaseBridgeHandler {
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
FineOffsetGatewayConfiguration config = getConfigAs(FineOffsetGatewayConfiguration.class);
|
FineOffsetGatewayConfiguration config = getConfigAs(FineOffsetGatewayConfiguration.class);
|
||||||
gatewayQueryService = new FineOffsetGatewayQueryService(config, this::updateStatus, conversionContext);
|
gatewayQueryService = config.protocol.getGatewayQueryService(config, this::updateStatus, conversionContext);
|
||||||
|
|
||||||
updateStatus(ThingStatus.UNKNOWN);
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
fetchAndUpdateSensors();
|
fetchAndUpdateSensors();
|
||||||
|
@ -122,7 +124,7 @@ public class FineOffsetGatewayHandler extends BaseBridgeHandler {
|
||||||
|
|
||||||
private void fetchAndUpdateSensors() {
|
private void fetchAndUpdateSensors() {
|
||||||
@Nullable
|
@Nullable
|
||||||
Map<SensorGatewayBinding, SensorDevice> deviceMap = query(FineOffsetGatewayQueryService::getRegisteredSensors);
|
Map<SensorGatewayBinding, SensorDevice> deviceMap = query(GatewayQueryService::getRegisteredSensors);
|
||||||
sensorDeviceMap = deviceMap;
|
sensorDeviceMap = deviceMap;
|
||||||
updateSensors();
|
updateSensors();
|
||||||
if (deviceMap != null) {
|
if (deviceMap != null) {
|
||||||
|
@ -154,8 +156,9 @@ public class FineOffsetGatewayHandler extends BaseBridgeHandler {
|
||||||
if (disposed) {
|
if (disposed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<MeasuredValue> data = query(FineOffsetGatewayQueryService::getLiveData);
|
List<MeasuredValue> data = query(GatewayQueryService::getMeasuredValues);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.UNDEF));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +177,7 @@ public class FineOffsetGatewayHandler extends BaseBridgeHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!channels.isEmpty()) {
|
if (!channels.isEmpty()) {
|
||||||
updateThing(editThing().withChannels(channels).build());
|
updateBridgeThing(bridgeBuilder -> bridgeBuilder.withChannels(channels));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,27 +211,31 @@ public class FineOffsetGatewayHandler extends BaseBridgeHandler {
|
||||||
|
|
||||||
private void updateBridgeInfo() {
|
private void updateBridgeInfo() {
|
||||||
@Nullable
|
@Nullable
|
||||||
String firmware = query(FineOffsetGatewayQueryService::getFirmwareVersion);
|
String firmware = query(GatewayQueryService::getFirmwareVersion);
|
||||||
Map<String, String> properties = new HashMap<>(thing.getProperties());
|
Map<String, String> properties = new HashMap<>(thing.getProperties());
|
||||||
if (firmware != null) {
|
if (firmware != null) {
|
||||||
var fwString = firmware.split("_V");
|
var fwString = firmware.split("_?V");
|
||||||
if (fwString.length > 1) {
|
if (fwString.length > 1) {
|
||||||
properties.put(Thing.PROPERTY_MODEL_ID, fwString[0]);
|
properties.put(Thing.PROPERTY_MODEL_ID, fwString[0]);
|
||||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, fwString[1]);
|
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, fwString[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemInfo systemInfo = query(FineOffsetGatewayQueryService::fetchSystemInfo);
|
SystemInfo systemInfo = query(GatewayQueryService::fetchSystemInfo);
|
||||||
if (systemInfo != null && systemInfo.getFrequency() != null) {
|
if (systemInfo != null && systemInfo.getFrequency() != null) {
|
||||||
properties.put(PROPERTY_FREQUENCY, systemInfo.getFrequency() + " MHz");
|
properties.put(PROPERTY_FREQUENCY, systemInfo.getFrequency() + " MHz");
|
||||||
}
|
}
|
||||||
if (!thing.getProperties().equals(properties)) {
|
if (!thing.getProperties().equals(properties)) {
|
||||||
BridgeBuilder bridge = editThing();
|
updateBridgeThing(bridgeBuilder -> bridgeBuilder.withProperties(properties));
|
||||||
bridge.withProperties(properties);
|
|
||||||
updateThing(bridge.build());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateBridgeThing(Consumer<BridgeBuilder> customizer) {
|
||||||
|
BridgeBuilder bridge = editThing();
|
||||||
|
customizer.accept(bridge);
|
||||||
|
updateThing(bridge.build());
|
||||||
|
}
|
||||||
|
|
||||||
private void startDiscoverJob() {
|
private void startDiscoverJob() {
|
||||||
ScheduledFuture<?> job = discoverJob;
|
ScheduledFuture<?> job = discoverJob;
|
||||||
if (job == null || job.isCancelled()) {
|
if (job == null || job.isCancelled()) {
|
||||||
|
@ -262,9 +269,9 @@ public class FineOffsetGatewayHandler extends BaseBridgeHandler {
|
||||||
this.pollingJob = null;
|
this.pollingJob = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> @Nullable T query(Function<FineOffsetGatewayQueryService, T> delegate) {
|
private <T> @Nullable T query(Function<GatewayQueryService, T> delegate) {
|
||||||
@Nullable
|
@Nullable
|
||||||
FineOffsetGatewayQueryService queryService = this.gatewayQueryService;
|
GatewayQueryService queryService = this.gatewayQueryService;
|
||||||
if (queryService == null) {
|
if (queryService == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -275,7 +282,7 @@ public class FineOffsetGatewayHandler extends BaseBridgeHandler {
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
@Nullable
|
@Nullable
|
||||||
FineOffsetGatewayQueryService queryService = this.gatewayQueryService;
|
GatewayQueryService queryService = this.gatewayQueryService;
|
||||||
if (queryService != null) {
|
if (queryService != null) {
|
||||||
try {
|
try {
|
||||||
queryService.close();
|
queryService.close();
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link FineOffsetSensorHandler} keeps track of the signal and battery of the sensor attached to the gateway.
|
* The {@link FineOffsetSensorHandler} keeps track of the signal and battery of the sensor attached to the gateway.
|
||||||
|
@ -61,7 +62,10 @@ public class FineOffsetSensorHandler extends BaseThingHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sensorDevice == null) {
|
if (sensorDevice == null) {
|
||||||
updateStatus(ThingStatus.OFFLINE);
|
// this only happens, if sensor data was read out correctly from the gateway, but the things' device
|
||||||
|
// (sensor) is no longer part of the paired sensors
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE);
|
||||||
|
getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.UNDEF));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sensorDevice.getSignal() == 0) {
|
if (sensorDevice.getSignal() == 0) {
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.fineoffsetweatherstation.internal.service;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetGatewayConfiguration;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SystemInfo;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.handler.ThingStatusListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to query an ELV gateway device.
|
||||||
|
*
|
||||||
|
* @author Andreas Berger - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ELVGatewayQueryService extends GatewayQueryService {
|
||||||
|
|
||||||
|
private final FineOffsetDataParser fineOffsetDataParser;
|
||||||
|
|
||||||
|
private final ConversionContext conversionContext;
|
||||||
|
|
||||||
|
public ELVGatewayQueryService(FineOffsetGatewayConfiguration config,
|
||||||
|
@Nullable ThingStatusListener thingStatusListener, ConversionContext conversionContext) {
|
||||||
|
super(config, thingStatusListener);
|
||||||
|
this.fineOffsetDataParser = new FineOffsetDataParser(Protocol.ELV);
|
||||||
|
this.conversionContext = conversionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getFirmwareVersion() {
|
||||||
|
Command command = Command.CMD_READ_FIRMWARE_VERSION;
|
||||||
|
var data = executeCommand(command.name(), command.getPayloadAlternative(), bytes -> true);
|
||||||
|
if (null != data) {
|
||||||
|
return fineOffsetDataParser.getFirmwareVersion(data);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<SensorGatewayBinding, SensorDevice> getRegisteredSensors() {
|
||||||
|
// not supported by ELV device
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable SystemInfo fetchSystemInfo() {
|
||||||
|
// not supported by ELV device
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MeasuredValue> getMeasuredValues() {
|
||||||
|
Command command = Command.CMD_WS980_LIVEDATA;
|
||||||
|
// since this request has 2 checksums we shortcut it here and provide the concrete payload directly
|
||||||
|
byte[] payload = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0x0b, (byte) 0x00, (byte) 0x06, (byte) 0x04,
|
||||||
|
(byte) 0x04, (byte) 0x19 };
|
||||||
|
byte[] data = executeCommand(command.name(), payload, command::isResponseValid);
|
||||||
|
if (data == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return fineOffsetDataParser.getMeasuredValues(data, conversionContext);
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
|
import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Measurand;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Measurands;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.BatteryStatus;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.BatteryStatus;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
||||||
|
@ -44,6 +45,11 @@ import org.slf4j.LoggerFactory;
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class FineOffsetDataParser {
|
public class FineOffsetDataParser {
|
||||||
private final Logger logger = LoggerFactory.getLogger(FineOffsetDataParser.class);
|
private final Logger logger = LoggerFactory.getLogger(FineOffsetDataParser.class);
|
||||||
|
private final Protocol protocol;
|
||||||
|
|
||||||
|
public FineOffsetDataParser(Protocol protocol) {
|
||||||
|
this.protocol = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable String getFirmwareVersion(byte[] data) {
|
public @Nullable String getFirmwareVersion(byte[] data) {
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
|
@ -145,7 +151,7 @@ public class FineOffsetDataParser {
|
||||||
return new SystemInfo(frequency, date, dst, useWh24);
|
return new SystemInfo(frequency, date, dst, useWh24);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MeasuredValue> getLiveData(byte[] data, ConversionContext context) {
|
List<MeasuredValue> getMeasuredValues(byte[] data, ConversionContext context) {
|
||||||
/*
|
/*
|
||||||
* Pos| Length | Description
|
* Pos| Length | Description
|
||||||
* -------------------------------------------------
|
* -------------------------------------------------
|
||||||
|
@ -165,16 +171,20 @@ public class FineOffsetDataParser {
|
||||||
* | 1 | checksum
|
* | 1 | checksum
|
||||||
*/
|
*/
|
||||||
var idx = 5;
|
var idx = 5;
|
||||||
|
if (protocol == Protocol.ELV) {
|
||||||
|
idx++; // at index 5 there is an additional Byte being set to 0x04
|
||||||
|
}
|
||||||
var size = toUInt16(data, 3);
|
var size = toUInt16(data, 3);
|
||||||
List<MeasuredValue> result = new ArrayList<>();
|
List<MeasuredValue> result = new ArrayList<>();
|
||||||
|
Measurands measurands = Measurands.getInstance(protocol);
|
||||||
while (idx < size) {
|
while (idx < size) {
|
||||||
byte code = data[idx++];
|
byte code = data[idx++];
|
||||||
Measurand measurand = Measurand.getByCode(code);
|
try {
|
||||||
if (measurand == null) {
|
idx += measurands.extractMeasuredValues(code, data, idx, context, result);
|
||||||
logger.warn("failed to get measurand 0x{}", Integer.toHexString(code));
|
} catch (IllegalArgumentException e) {
|
||||||
|
logger.warn("", e);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
idx += measurand.extractMeasuredValues(data, idx, context, result);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,6 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.fineoffsetweatherstation.internal.service;
|
package org.openhab.binding.fineoffsetweatherstation.internal.service;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -23,16 +19,14 @@ import java.util.Map;
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetGatewayConfiguration;
|
import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetGatewayConfiguration;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
|
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SystemInfo;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SystemInfo;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.handler.ThingStatusListener;
|
import org.openhab.binding.fineoffsetweatherstation.internal.handler.ThingStatusListener;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -42,24 +36,21 @@ import org.slf4j.LoggerFactory;
|
||||||
* @author Andreas Berger - Initial contribution
|
* @author Andreas Berger - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class FineOffsetGatewayQueryService implements AutoCloseable {
|
public class FineOffsetGatewayQueryService extends GatewayQueryService {
|
||||||
private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayQueryService.class);
|
private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayQueryService.class);
|
||||||
|
|
||||||
private @Nullable Socket socket;
|
|
||||||
private final FineOffsetGatewayConfiguration config;
|
|
||||||
private final ThingStatusListener thingStatusListener;
|
|
||||||
private final FineOffsetDataParser fineOffsetDataParser;
|
private final FineOffsetDataParser fineOffsetDataParser;
|
||||||
|
|
||||||
private final ConversionContext conversionContext;
|
private final ConversionContext conversionContext;
|
||||||
|
|
||||||
public FineOffsetGatewayQueryService(FineOffsetGatewayConfiguration config, ThingStatusListener thingStatusListener,
|
public FineOffsetGatewayQueryService(FineOffsetGatewayConfiguration config,
|
||||||
ConversionContext conversionContext) {
|
@Nullable ThingStatusListener thingStatusListener, ConversionContext conversionContext) {
|
||||||
this.config = config;
|
super(config, thingStatusListener);
|
||||||
this.thingStatusListener = thingStatusListener;
|
this.fineOffsetDataParser = new FineOffsetDataParser(Protocol.DEFAULT);
|
||||||
this.fineOffsetDataParser = new FineOffsetDataParser();
|
|
||||||
this.conversionContext = conversionContext;
|
this.conversionContext = conversionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public @Nullable String getFirmwareVersion() {
|
public @Nullable String getFirmwareVersion() {
|
||||||
var data = executeCommand(Command.CMD_READ_FIRMWARE_VERSION);
|
var data = executeCommand(Command.CMD_READ_FIRMWARE_VERSION);
|
||||||
if (null != data) {
|
if (null != data) {
|
||||||
|
@ -68,6 +59,7 @@ public class FineOffsetGatewayQueryService implements AutoCloseable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Map<SensorGatewayBinding, SensorDevice> getRegisteredSensors() {
|
public Map<SensorGatewayBinding, SensorDevice> getRegisteredSensors() {
|
||||||
var data = executeCommand(Command.CMD_READ_SENSOR_ID_NEW);
|
var data = executeCommand(Command.CMD_READ_SENSOR_ID_NEW);
|
||||||
if (null == data) {
|
if (null == data) {
|
||||||
|
@ -83,6 +75,7 @@ public class FineOffsetGatewayQueryService implements AutoCloseable {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public @Nullable SystemInfo fetchSystemInfo() {
|
public @Nullable SystemInfo fetchSystemInfo() {
|
||||||
var data = executeCommand(Command.CMD_READ_SSSS);
|
var data = executeCommand(Command.CMD_READ_SSSS);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
@ -92,80 +85,16 @@ public class FineOffsetGatewayQueryService implements AutoCloseable {
|
||||||
return fineOffsetDataParser.fetchSystemInfo(data);
|
return fineOffsetDataParser.fetchSystemInfo(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MeasuredValue> getLiveData() {
|
@Override
|
||||||
|
public List<MeasuredValue> getMeasuredValues() {
|
||||||
byte[] data = executeCommand(Command.CMD_GW1000_LIVEDATA);
|
byte[] data = executeCommand(Command.CMD_GW1000_LIVEDATA);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return fineOffsetDataParser.getLiveData(data, conversionContext);
|
return fineOffsetDataParser.getMeasuredValues(data, conversionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized byte @Nullable [] executeCommand(Command command) {
|
protected byte @Nullable [] executeCommand(Command command) {
|
||||||
byte[] buffer = new byte[2028];
|
return executeCommand(command.name(), command.getPayload(), command::isResponseValid);
|
||||||
int bytesRead;
|
|
||||||
byte[] request = command.getPayload();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Socket socket = getConnection();
|
|
||||||
if (socket == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
InputStream in = socket.getInputStream();
|
|
||||||
socket.getOutputStream().write(request);
|
|
||||||
if ((bytesRead = in.read(buffer)) == -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!command.isResponseValid(buffer)) {
|
|
||||||
if (bytesRead > 0) {
|
|
||||||
logger.debug("executeCommand({}), invalid response: {}", command,
|
|
||||||
Utils.toHexString(buffer, bytesRead, ""));
|
|
||||||
} else {
|
|
||||||
logger.debug("executeCommand({}): no response", command);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException ex) {
|
|
||||||
thingStatusListener.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
|
||||||
ex.getMessage());
|
|
||||||
try {
|
|
||||||
close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.warn("executeCommand({})", command, ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = Arrays.copyOfRange(buffer, 0, bytesRead);
|
|
||||||
logger.trace("executeCommand({}): received: {}", command, Utils.toHexString(data, data.length, ""));
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized @Nullable Socket getConnection() {
|
|
||||||
Socket socket = this.socket;
|
|
||||||
if (socket == null) {
|
|
||||||
try {
|
|
||||||
socket = new Socket(config.ip, config.port);
|
|
||||||
socket.setSoTimeout(5000);
|
|
||||||
this.socket = socket;
|
|
||||||
thingStatusListener.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
|
|
||||||
} catch (IOException e) {
|
|
||||||
thingStatusListener.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
|
||||||
e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
Socket socket = this.socket;
|
|
||||||
this.socket = null;
|
|
||||||
if (socket != null) {
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.fineoffsetweatherstation.internal.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetGatewayConfiguration;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.Utils;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SystemInfo;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.handler.ThingStatusListener;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface defining the API for querying a gateway device.
|
||||||
|
*
|
||||||
|
* @author Andreas Berger - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public abstract class GatewayQueryService implements AutoCloseable {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
private static final Lock REQUEST_LOCK = new ReentrantLock();
|
||||||
|
|
||||||
|
private @Nullable Socket socket;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final ThingStatusListener thingStatusListener;
|
||||||
|
|
||||||
|
private final FineOffsetGatewayConfiguration config;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract String getFirmwareVersion();
|
||||||
|
|
||||||
|
public abstract Map<SensorGatewayBinding, SensorDevice> getRegisteredSensors();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract SystemInfo fetchSystemInfo();
|
||||||
|
|
||||||
|
public abstract List<MeasuredValue> getMeasuredValues();
|
||||||
|
|
||||||
|
public GatewayQueryService(FineOffsetGatewayConfiguration config,
|
||||||
|
@Nullable ThingStatusListener thingStatusListener) {
|
||||||
|
this.config = config;
|
||||||
|
this.thingStatusListener = thingStatusListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte @Nullable [] executeCommand(String command, byte[] request,
|
||||||
|
Function<byte[], Boolean> validateResponse) {
|
||||||
|
byte[] buffer = new byte[2028];
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!REQUEST_LOCK.tryLock(30, TimeUnit.SECONDS)) {
|
||||||
|
logger.trace("executeCommand({}): time out while getting lock", command);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Socket socket = getConnection();
|
||||||
|
if (socket == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
logger.trace("executeCommand({}): send request: {}", command,
|
||||||
|
Utils.toHexString(request, request.length, ""));
|
||||||
|
InputStream in = socket.getInputStream();
|
||||||
|
socket.getOutputStream().write(request);
|
||||||
|
if ((bytesRead = in.read(buffer)) == -1) {
|
||||||
|
logger.trace("executeCommand({}): data exceeded buffer length ({})", command, buffer.length);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!validateResponse.apply(buffer)) {
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
logger.debug("executeCommand({}), invalid response: {}", command,
|
||||||
|
Utils.toHexString(buffer, bytesRead, ""));
|
||||||
|
} else {
|
||||||
|
logger.debug("executeCommand({}): no response", command);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException ex) {
|
||||||
|
@Nullable
|
||||||
|
ThingStatusListener statusListener = thingStatusListener;
|
||||||
|
if (statusListener != null) {
|
||||||
|
statusListener.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
ex.getMessage());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.warn("executeCommand({})", command, ex);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
REQUEST_LOCK.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = Arrays.copyOfRange(buffer, 0, bytesRead);
|
||||||
|
logger.trace("executeCommand({}): received: {}", command, Utils.toHexString(data, data.length, ""));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized @Nullable Socket getConnection() {
|
||||||
|
Socket socket = this.socket;
|
||||||
|
if (socket == null) {
|
||||||
|
@Nullable
|
||||||
|
ThingStatusListener statusListener = thingStatusListener;
|
||||||
|
try {
|
||||||
|
socket = new Socket(config.ip, config.port);
|
||||||
|
socket.setSoTimeout(5000);
|
||||||
|
this.socket = socket;
|
||||||
|
if (statusListener != null) {
|
||||||
|
statusListener.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (statusListener != null) {
|
||||||
|
statusListener.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
Socket socket = this.socket;
|
||||||
|
this.socket = null;
|
||||||
|
if (socket != null) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,11 @@
|
||||||
<description>The network port of the gateway</description>
|
<description>The network port of the gateway</description>
|
||||||
<default>45000</default>
|
<default>45000</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="protocol" type="text" required="false">
|
||||||
|
<label>Protocol</label>
|
||||||
|
<description>The protocol to use for communicating with the gateway, valid values are: `DEFAULT` or `ELV`</description>
|
||||||
|
<default>DEFAULT</default>
|
||||||
|
</parameter>
|
||||||
<parameter name="pollingInterval" type="integer" required="true">
|
<parameter name="pollingInterval" type="integer" required="true">
|
||||||
<label>Polling Interval</label>
|
<label>Polling Interval</label>
|
||||||
<description>Polling interval for refreshing the data in seconds</description>
|
<description>Polling interval for refreshing the data in seconds</description>
|
||||||
|
|
|
@ -20,6 +20,8 @@ thing-type.config.fineoffsetweatherstation.gateway.pollingInterval.label = Polli
|
||||||
thing-type.config.fineoffsetweatherstation.gateway.pollingInterval.description = Polling interval for refreshing the data in seconds
|
thing-type.config.fineoffsetweatherstation.gateway.pollingInterval.description = Polling interval for refreshing the data in seconds
|
||||||
thing-type.config.fineoffsetweatherstation.gateway.port.label = Port
|
thing-type.config.fineoffsetweatherstation.gateway.port.label = Port
|
||||||
thing-type.config.fineoffsetweatherstation.gateway.port.description = The network port of the gateway
|
thing-type.config.fineoffsetweatherstation.gateway.port.description = The network port of the gateway
|
||||||
|
thing-type.config.fineoffsetweatherstation.gateway.protocol.label = Protocol
|
||||||
|
thing-type.config.fineoffsetweatherstation.gateway.protocol.description = The protocol to use for communicating with the gateway, valid values are: `DEFAULT` or `ELV`
|
||||||
thing-type.config.fineoffsetweatherstation.sensor.sensor.label = Sensor
|
thing-type.config.fineoffsetweatherstation.sensor.sensor.label = Sensor
|
||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<label>Gateway Device</label>
|
<label>Gateway Device</label>
|
||||||
<description>A WiFi connected gateway device (WN1900, GW1000, GW1100, WH2680, WH2650) to bridge Sensors</description>
|
<description>A WiFi connected gateway device (WN1900, GW1000, GW1100, WH2680, WH2650) to bridge Sensors</description>
|
||||||
<category>NetworkAppliance</category>
|
<category>NetworkAppliance</category>
|
||||||
|
<representation-property>macAddress</representation-property>
|
||||||
<config-description-ref uri="thing-type:fineoffsetweatherstation:gateway"/>
|
<config-description-ref uri="thing-type:fineoffsetweatherstation:gateway"/>
|
||||||
</bridge-type>
|
</bridge-type>
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||||
</channels>
|
</channels>
|
||||||
|
|
||||||
|
<representation-property>sensor</representation-property>
|
||||||
<config-description-ref uri="thing-type:fineoffsetweatherstation:sensor"/>
|
<config-description-ref uri="thing-type:fineoffsetweatherstation:sensor"/>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
ManufacturerName,Name,Channel,Code,Index,Skip,ChannelType,MeasureType_DEFAULT,MeasureType_ELV
|
||||||
|
INTEMP,Indoor Temperature,temperature-indoor,0x1,,,system:indoor-temperature,TEMPERATURE,
|
||||||
|
OUTTEMP,Outdoor Temperature,temperature-outdoor,0x2,,,system:outdoor-temperature,TEMPERATURE,
|
||||||
|
DEWPOINT,Dew point,temperature-dew-point,0x3,,,,TEMPERATURE,
|
||||||
|
WINDCHILL,Wind chill,temperature-wind-chill,0x4,,,,TEMPERATURE,
|
||||||
|
HEATINDEX,Heat index,temperature-heat-index,0x5,,,,TEMPERATURE,
|
||||||
|
INHUMI,Indoor Humidity,humidity-indoor,0x6,,,,PERCENTAGE,
|
||||||
|
OUTHUMI,Outdoor Humidity,humidity-outdoor,0x7,,,system:atmospheric-humidity,PERCENTAGE,
|
||||||
|
ABSBARO,Absolutely pressure,pressure-absolute,0x8,,,,PRESSURE,
|
||||||
|
RELBARO,Relative pressure,pressure-relative,0x9,,,system:barometric-pressure,PRESSURE,
|
||||||
|
WINDDIRECTION,Wind Direction,direction-wind,0xa,,,system:wind-direction,DEGREE,
|
||||||
|
WINDSPEED,Wind Speed,speed-wind,0xb,,,system:wind-speed,SPEED,
|
||||||
|
GUSTSPEED,Gust Speed,speed-gust,0xc,,,system:wind-speed,SPEED,
|
||||||
|
RAINEVENT,Rain Event,rain-event,0xd,,,,HEIGHT,HEIGHT_BIG
|
||||||
|
RAINRATE,Rain Rate,rain-rate,0xe,,,,HEIGHT_PER_HOUR,HEIGHT_PER_HOUR_BIG
|
||||||
|
RAINHOUR,Rain hour,rain-hour,0xf,,,,HEIGHT,HEIGHT_BIG
|
||||||
|
RAINDAY,Rain Day,rain-day,0x10,,,,HEIGHT,HEIGHT_BIG
|
||||||
|
RAINWEEK,Rain Week,rain-week,0x11,,,,HEIGHT,HEIGHT_BIG
|
||||||
|
RAINMONTH,Rain Month,rain-month,0x12,,,,HEIGHT_BIG,
|
||||||
|
RAINYEAR,Rain Year,rain-year,0x13,,,,HEIGHT_BIG,
|
||||||
|
RAINTOTALS,Rain Totals,rain-total,0x14,,,,HEIGHT_BIG,
|
||||||
|
LIGHT,Light,illumination,0x15,,,,LUX,
|
||||||
|
UV,UV,irradiation-uv,0x16,,,,MILLIWATT_PER_SQUARE_METRE,
|
||||||
|
UVI,UV index,uv-index,0x17,,,uv-index,BYTE,
|
||||||
|
TIME,Date and time,time,0x18,,,,DATE_TIME2,
|
||||||
|
DAYLWINDMAX,Day max wind,wind-max-day,0x19,,,max-wind-speed,SPEED,
|
||||||
|
TEMP1,Temperature 1,temperature-channel-1,0x1a,,,,TEMPERATURE,
|
||||||
|
TEMP2,Temperature 2,temperature-channel-2,0x1b,,,,TEMPERATURE,
|
||||||
|
TEMP3,Temperature 3,temperature-channel-3,0x1c,,,,TEMPERATURE,
|
||||||
|
TEMP4,Temperature 4,temperature-channel-4,0x1d,,,,TEMPERATURE,
|
||||||
|
TEMP5,Temperature 5,temperature-channel-5,0x1e,,,,TEMPERATURE,
|
||||||
|
TEMP6,Temperature 6,temperature-channel-6,0x1f,,,,TEMPERATURE,
|
||||||
|
TEMP7,Temperature 7,temperature-channel-7,0x20,,,,TEMPERATURE,
|
||||||
|
TEMP8,Temperature 8,temperature-channel-8,0x21,,,,TEMPERATURE,
|
||||||
|
HUMI1,Humidity 1,humidity-channel-1,0x22,,,,PERCENTAGE,
|
||||||
|
HUMI2,Humidity 2,humidity-channel-2,0x23,,,,PERCENTAGE,
|
||||||
|
HUMI3,Humidity 3,humidity-channel-3,0x24,,,,PERCENTAGE,
|
||||||
|
HUMI4,Humidity 4,humidity-channel-4,0x25,,,,PERCENTAGE,
|
||||||
|
HUMI5,Humidity 5,humidity-channel-5,0x26,,,,PERCENTAGE,
|
||||||
|
HUMI6,Humidity 6,humidity-channel-6,0x27,,,,PERCENTAGE,
|
||||||
|
HUMI7,Humidity 7,humidity-channel-7,0x28,,,,PERCENTAGE,
|
||||||
|
HUMI8,Humidity 8,humidity-channel-8,0x29,,,,PERCENTAGE,
|
||||||
|
SOILTEMP1,Soil Temperature 1,temperature-soil-channel-1,0x2b,,,,TEMPERATURE,
|
||||||
|
SOILTEMP2,Soil Temperature 2,temperature-soil-channel-2,0x2d,,,,TEMPERATURE,
|
||||||
|
SOILTEMP3,Soil Temperature 3,temperature-soil-channel-3,0x2f,,,,TEMPERATURE,
|
||||||
|
SOILTEMP4,Soil Temperature 4,temperature-soil-channel-4,0x31,,,,TEMPERATURE,
|
||||||
|
SOILTEMP5,Soil Temperature 5,temperature-soil-channel-5,0x33,,,,TEMPERATURE,
|
||||||
|
SOILTEMP6,Soil Temperature 6,temperature-soil-channel-6,0x35,,,,TEMPERATURE,
|
||||||
|
SOILTEMP7,Soil Temperature 7,temperature-soil-channel-7,0x37,,,,TEMPERATURE,
|
||||||
|
SOILTEMP8,Soil Temperature 8,temperature-soil-channel-8,0x39,,,,TEMPERATURE,
|
||||||
|
SOILTEMP9,Soil Temperature 9,temperature-soil-channel-9,0x3b,,,,TEMPERATURE,
|
||||||
|
SOILTEMP10,Soil Temperature 10,temperature-soil-channel-10,0x3d,,,,TEMPERATURE,
|
||||||
|
SOILTEMP11,Soil Temperature 11,temperature-soil-channel-11,0x3f,,,,TEMPERATURE,
|
||||||
|
SOILTEMP12,Soil Temperature 12,temperature-soil-channel-12,0x41,,,,TEMPERATURE,
|
||||||
|
SOILTEMP13,Soil Temperature 13,temperature-soil-channel-13,0x43,,,,TEMPERATURE,
|
||||||
|
SOILTEMP14,Soil Temperature 14,temperature-soil-channel-14,0x45,,,,TEMPERATURE,
|
||||||
|
SOILTEMP15,Soil Temperature 15,temperature-soil-channel-15,0x47,,,,TEMPERATURE,
|
||||||
|
SOILTEMP16,Soil Temperature 16,temperature-soil-channel-16,0x49,,,,TEMPERATURE,
|
||||||
|
SOILMOISTURE1,Soil Moisture 1,moisture-soil-channel-1,0x2c,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE2,Soil Moisture 2,moisture-soil-channel-2,0x2e,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE3,Soil Moisture 3,moisture-soil-channel-3,0x30,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE4,Soil Moisture 4,moisture-soil-channel-4,0x32,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE5,Soil Moisture 5,moisture-soil-channel-5,0x34,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE6,Soil Moisture 6,moisture-soil-channel-6,0x36,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE7,Soil Moisture 7,moisture-soil-channel-7,0x38,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE8,Soil Moisture 8,moisture-soil-channel-8,0x3a,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE9,Soil Moisture 9,moisture-soil-channel-9,0x3c,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE10,Soil Moisture 10,moisture-soil-channel-10,0x3e,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE11,Soil Moisture 11,moisture-soil-channel-11,0x40,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE12,Soil Moisture 12,moisture-soil-channel-12,0x42,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE13,Soil Moisture 13,moisture-soil-channel-13,0x44,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE14,Soil Moisture 14,moisture-soil-channel-14,0x46,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE15,Soil Moisture 15,moisture-soil-channel-15,0x48,,,moisture,PERCENTAGE,
|
||||||
|
SOILMOISTURE16,Soil Moisture 16,moisture-soil-channel-16,0x4a,,,moisture,PERCENTAGE,
|
||||||
|
LOWBATT,Low Battery,,0x4c,,1,,,
|
||||||
|
PM25_24HAVG1,PM2.5 Air Quality 24 hour average channel 1,air-quality-24-hour-average-channel-1,0x4d,,,,PM25,
|
||||||
|
PM25_24HAVG2,PM2.5 Air Quality 24 hour average channel 2,air-quality-24-hour-average-channel-2,0x4e,,,,PM25,
|
||||||
|
PM25_24HAVG3,PM2.5 Air Quality 24 hour average channel 3,air-quality-24-hour-average-channel-3,0x4f,,,,PM25,
|
||||||
|
PM25_24HAVG4,PM2.5 Air Quality 24 hour average channel 4,air-quality-24-hour-average-channel-4,0x50,,,,PM25,
|
||||||
|
PM25_CH1,PM2.5 Air Quality channel 1,air-quality-channel-1,0x2a,,,,PM25,
|
||||||
|
PM25_CH2,PM2.5 Air Quality channel 2,air-quality-channel-2,0x51,,,,PM25,
|
||||||
|
PM25_CH3,PM2.5 Air Quality channel 3,air-quality-channel-3,0x52,,,,PM25,
|
||||||
|
PM25_CH4,PM2.5 Air Quality channel 4,air-quality-channel-4,0x53,,,,PM25,
|
||||||
|
LEAK_CH1,Leak channel 1,water-leak-channel-1,0x58,,,,WATER_LEAK_DETECTION,
|
||||||
|
LEAK_CH2,Leak channel 2,water-leak-channel-2,0x59,,,,WATER_LEAK_DETECTION,
|
||||||
|
LEAK_CH3,Leak channel 3,water-leak-channel-3,0x5a,,,,WATER_LEAK_DETECTION,
|
||||||
|
LEAK_CH4,Leak channel 4,water-leak-channel-4,0x5b,,,,WATER_LEAK_DETECTION,
|
||||||
|
LIGHTNING,lightning distance 1~40KM,lightning-distance,0x60,,,,LIGHTNING_DISTANCE,
|
||||||
|
LIGHTNING_TIME,lightning happened time,lightning-time,0x61,,,,LIGHTNING_TIME,
|
||||||
|
LIGHTNING_POWER,lightning counter for the day,lightning-counter,0x62,,,,LIGHTNING_COUNTER,
|
||||||
|
TF_USR1,Soil or Water temperature channel 1,temperature-external-channel-1,0x63,,,,TEMPERATURE,
|
||||||
|
TF_USR2,Soil or Water temperature channel 2,temperature-external-channel-2,0x64,,,,TEMPERATURE,
|
||||||
|
TF_USR3,Soil or Water temperature channel 3,temperature-external-channel-3,0x65,,,,TEMPERATURE,
|
||||||
|
TF_USR4,Soil or Water temperature channel 4,temperature-external-channel-4,0x66,,,,TEMPERATURE,
|
||||||
|
TF_USR5,Soil or Water temperature channel 5,temperature-external-channel-5,0x67,,,,TEMPERATURE,
|
||||||
|
TF_USR6,Soil or Water temperature channel 6,temperature-external-channel-6,0x68,,,,TEMPERATURE,
|
||||||
|
TF_USR7,Soil or Water temperature channel 7,temperature-external-channel-7,0x69,,,,TEMPERATURE,
|
||||||
|
TF_USR8,Soil or Water temperature channel 8,temperature-external-channel-8,0x6a,,,,TEMPERATURE,
|
||||||
|
ITEM_SENSOR_CO2,Temperature (CO₂-Sensor),sensor-co2-temperature,0x70,0,,,TEMPERATURE,
|
||||||
|
ITEM_SENSOR_CO2,Humidity (CO₂-Sensor),sensor-co2-humidity,0x70,1,,,PERCENTAGE,
|
||||||
|
ITEM_SENSOR_CO2,PM10 Air Quality (CO₂-Sensor),sensor-co2-pm10,0x70,2,,,PM10,
|
||||||
|
ITEM_SENSOR_CO2,PM10 Air Quality 24 hour average (CO₂-Sensor),sensor-co2-pm10-24-hour-average,0x70,3,,,PM10,
|
||||||
|
ITEM_SENSOR_CO2,PM2.5 Air Quality (CO₂-Sensor),sensor-co2-pm25,0x70,4,,,PM25,
|
||||||
|
ITEM_SENSOR_CO2,PM2.5 Air Quality 24 hour average (CO₂-Sensor),sensor-co2-pm25-24-hour-average,0x70,5,,,PM25,
|
||||||
|
ITEM_SENSOR_CO2,CO₂,sensor-co2-co2,0x70,6,,,CO2,
|
||||||
|
ITEM_SENSOR_CO2,CO₂ 24 hour average,sensor-co2-co2-24-hour-average,0x70,7,,,CO2,
|
||||||
|
ITEM_SENSOR_CO2,Battery Level,,0x70,8,1,,,
|
||||||
|
ITEM_LEAF_WETNESS_CH1,Leaf Moisture channel 1,leaf-wetness-channel-1,0x72,,,moisture,PERCENTAGE,
|
||||||
|
ITEM_LEAF_WETNESS_CH2,Leaf Moisture channel 2,leaf-wetness-channel-2,0x73,,,moisture,PERCENTAGE,
|
||||||
|
ITEM_LEAF_WETNESS_CH3,Leaf Moisture channel 3,leaf-wetness-channel-3,0x74,,,moisture,PERCENTAGE,
|
||||||
|
ITEM_LEAF_WETNESS_CH4,Leaf Moisture channel 4,leaf-wetness-channel-4,0x75,,,moisture,PERCENTAGE,
|
||||||
|
ITEM_LEAF_WETNESS_CH5,Leaf Moisture channel 5,leaf-wetness-channel-5,0x76,,,moisture,PERCENTAGE,
|
||||||
|
ITEM_LEAF_WETNESS_CH6,Leaf Moisture channel 6,leaf-wetness-channel-6,0x77,,,moisture,PERCENTAGE,
|
||||||
|
ITEM_LEAF_WETNESS_CH7,Leaf Moisture channel 7,leaf-wetness-channel-7,0x78,,,moisture,PERCENTAGE,
|
||||||
|
ITEM_LEAF_WETNESS_CH8,Leaf Moisture channel 8,leaf-wetness-channel-8,0x79,,,moisture,PERCENTAGE,
|
|
|
@ -21,6 +21,7 @@ import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext;
|
||||||
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.Protocol;
|
||||||
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,11 +29,9 @@ import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.Mea
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
class FineOffsetDataParserTest {
|
class FineOffsetDataParserTest {
|
||||||
private final FineOffsetDataParser parser = new FineOffsetDataParser();
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLiveDataWH45() {
|
void testLiveDataWH45() {
|
||||||
List<MeasuredValue> data = parser.getLiveData(Hex.decode(
|
List<MeasuredValue> data = new FineOffsetDataParser(Protocol.DEFAULT).getMeasuredValues(Hex.decode(
|
||||||
"FFFF2700510100D306280827EF0927EF020045074F0A00150B00000C0000150000000016000117001900000E0000100000110021120000002113000005850D00007000D12E0060005A005B005502AE028F0633"),
|
"FFFF2700510100D306280827EF0927EF020045074F0A00150B00000C0000150000000016000117001900000E0000100000110021120000002113000005850D00007000D12E0060005A005B005502AE028F0633"),
|
||||||
new ConversionContext(ZoneOffset.UTC));
|
new ConversionContext(ZoneOffset.UTC));
|
||||||
Assertions.assertThat(data)
|
Assertions.assertThat(data)
|
||||||
|
@ -42,7 +41,7 @@ class FineOffsetDataParserTest {
|
||||||
new Tuple("temperature-outdoor", "6.9 °C"), new Tuple("humidity-outdoor", "79 %"),
|
new Tuple("temperature-outdoor", "6.9 °C"), new Tuple("humidity-outdoor", "79 %"),
|
||||||
new Tuple("direction-wind", "21 °"), new Tuple("speed-wind", "0 m/s"),
|
new Tuple("direction-wind", "21 °"), new Tuple("speed-wind", "0 m/s"),
|
||||||
new Tuple("speed-gust", "0 m/s"), new Tuple("illumination", "0 lx"),
|
new Tuple("speed-gust", "0 m/s"), new Tuple("illumination", "0 lx"),
|
||||||
new Tuple("irradiation-uv", "1 µW/cm²"), new Tuple("uv-index", "0"),
|
new Tuple("irradiation-uv", "0.1 mW/m²"), new Tuple("uv-index", "0"),
|
||||||
new Tuple("wind-max-day", "0 m/s"), new Tuple("rain-rate", "0 mm/h"),
|
new Tuple("wind-max-day", "0 m/s"), new Tuple("rain-rate", "0 mm/h"),
|
||||||
new Tuple("rain-day", "0 mm"), new Tuple("rain-week", "3.3 mm"),
|
new Tuple("rain-day", "0 mm"), new Tuple("rain-week", "3.3 mm"),
|
||||||
new Tuple("rain-month", "3.3 mm"), new Tuple("rain-year", "141.3 mm"),
|
new Tuple("rain-month", "3.3 mm"), new Tuple("rain-year", "141.3 mm"),
|
||||||
|
@ -53,4 +52,32 @@ class FineOffsetDataParserTest {
|
||||||
new Tuple("sensor-co2-pm25-24-hour-average", "8.5 µg/m³"),
|
new Tuple("sensor-co2-pm25-24-hour-average", "8.5 µg/m³"),
|
||||||
new Tuple("sensor-co2-co2", "686 ppm"), new Tuple("sensor-co2-co2-24-hour-average", "655 ppm"));
|
new Tuple("sensor-co2-co2", "686 ppm"), new Tuple("sensor-co2-co2-24-hour-average", "655 ppm"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLiveDataELV() {
|
||||||
|
byte[] data = Hex.decode(
|
||||||
|
"FFFF0B00500401010B0201120300620401120501120629072108254B09254B0A01480B00040C000A0E000000001000000021110000002E120000014F130000100714000012FD15000B4BB816086917056D35");
|
||||||
|
List<MeasuredValue> measuredValues = new FineOffsetDataParser(Protocol.ELV).getMeasuredValues(data,
|
||||||
|
new ConversionContext(ZoneOffset.UTC));
|
||||||
|
Assertions.assertThat(measuredValues)
|
||||||
|
.extracting(MeasuredValue::getChannelId, measuredValue -> measuredValue.getState().toString())
|
||||||
|
.containsExactly(new Tuple("temperature-indoor", "26.7 °C"),
|
||||||
|
new Tuple("temperature-outdoor", "27.4 °C"), new Tuple("temperature-dew-point", "9.8 °C"),
|
||||||
|
new Tuple("temperature-wind-chill", "27.4 °C"), new Tuple("temperature-heat-index", "27.4 °C"),
|
||||||
|
new Tuple("humidity-indoor", "41 %"), new Tuple("humidity-outdoor", "33 %"),
|
||||||
|
new Tuple("pressure-absolute", "954.7 hPa"), new Tuple("pressure-relative", "954.7 hPa"),
|
||||||
|
new Tuple("direction-wind", "328 °"), new Tuple("speed-wind", "0.4 m/s"),
|
||||||
|
new Tuple("speed-gust", "1 m/s"), new Tuple("rain-rate", "0 mm/h"),
|
||||||
|
new Tuple("rain-day", "3.3 mm"), new Tuple("rain-week", "4.6 mm"),
|
||||||
|
new Tuple("rain-month", "33.5 mm"), new Tuple("rain-year", "410.3 mm"),
|
||||||
|
new Tuple("rain-total", "486.1 mm"), new Tuple("illumination", "74028 lx"),
|
||||||
|
new Tuple("irradiation-uv", "215.3 mW/m²"), new Tuple("uv-index", "5"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFirmware() {
|
||||||
|
byte[] data = Hex.decode("FFFF501511456173795765617468657256312E362E3400");
|
||||||
|
String firmware = new FineOffsetDataParser(Protocol.ELV).getFirmwareVersion(data);
|
||||||
|
Assertions.assertThat(firmware).isEqualTo("EasyWeatherV1.6.4");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue