diff --git a/CODEOWNERS b/CODEOWNERS index 110ba738a..76785db77 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -95,6 +95,7 @@ /bundles/org.openhab.binding.exec/ @kgoderis /bundles/org.openhab.binding.feed/ @svilenvul /bundles/org.openhab.binding.feican/ @Hilbrand +/bundles/org.openhab.binding.fineoffsetweatherstation/ @Andy2003 /bundles/org.openhab.binding.flicbutton/ @pfink /bundles/org.openhab.binding.fmiweather/ @ssalonen /bundles/org.openhab.binding.folderwatcher/ @goopilot diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 3218cb784..155250edf 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -466,6 +466,11 @@ org.openhab.binding.feican ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.fineoffsetweatherstation + ${project.version} + org.openhab.addons.bundles org.openhab.binding.flicbutton diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/NOTICE b/bundles/org.openhab.binding.fineoffsetweatherstation/NOTICE new file mode 100644 index 000000000..38d625e34 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/README.md b/bundles/org.openhab.binding.fineoffsetweatherstation/README.md new file mode 100644 index 000000000..7fc57f126 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/README.md @@ -0,0 +1,307 @@ +# Fine Offset Weather Station Binding + +This binding is for weather stations manufactured by [Fine Offset](http://www.foshk.com/). +These weather stations are white labeled products which are re-branded by many distribution companies around the world. +Some of these brands are e.g.: + +* Aercus +* Ambient Weather +* Ecowitt +* Frogitt +* Misol +* Pantech +* Sainlogic +* Steinberg Systems +* Waldbeck Halley + +Here is a product picture of how this Weather Station looks like: + +![WH2650](doc/WH2650.png) + +This binding works offline by [implementing the wire protocol](https://osswww.ecowitt.net/uploads/20210716/WN1900%20GW1000,1100%20WH2680,2650%20telenet%20v1.6.0%20.pdf) of the WiFi gateway device. + +## Supported Things + +- `weatherstation`: A Fine Offset gateway device with the ThingTypeUID `fineoffsetweatherstation:weatherstation` wich supports the [wire protocol](https://osswww.ecowitt.net/uploads/20210716/WN1900%20GW1000,1100%20WH2680,2650%20telenet%20v1.6.0%20.pdf) e.g.: + - HP2550 + - HP3500 + - GW1000 + - GW1001 + - GW1002 + - GW1003 + - GW1100 + - WN1900 + - WN1910 + - WH2350 + - WH2600 + - WH2610 + - WH2620 + - WH2650 (tested) + - WH2680 + - WH2900 + - WH2950 +- `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. + This is a list of sensors supported by the protocol: + - WH24 - 7-in-1 weather station, Sensor for wind speed & direction, solar radiation & light, temperature, humidity, rainfall + - WH25 - 3-in-1 sensor temperature, humidity, pressure + - WH26 - 2-in-1 sensor temperature, humidity + - WH31 - 2-in-1 sensor temperature, humidity + - WH34 - External temperature sensor + - WH35 - Leaf wetness sensor + - WH40 - Rainfall sensor + - WH41 - Outdoor air quality sensor + - WH45 - Air quality sensor + - WH51 - Soil moisture sensor + - WH55 - Water leak detection sensor + - WH57 - Lightning detection sensor + - WH65 - 7-in-1 weather station for wind speed & direction, solar radiation & light, temperature, humidity and rainfall + - WH68 - 4-in-1 weather station - Solar-powered sensor for wind speed & direction, solar radiation & light + - WH80 - 6-in-1 weather station - Ultrasonic sensor for wind speed & direction, solar radiation & light, temperature & humidity + - WH90 - A new weather station + +## Discovery + +This binding support discovery of Fine Offset gateway devices by sending a broadcast message. + +## Thing Configuration + +### `gateway` Thing Configuration + +| Name | Type | Description | Default | Required | Advanced | +|-------------------|---------|-------------------------------------------------------------------------------------|---------|----------|----------| +| 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 | +| 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 + +| Name | Type | Description | Default | Required | Advanced | +|------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------|---------|----------|----------| +| sensor | text | The name of sensor attached to the gateway (multiple sensors of the same type may have different names according to the bound channel) | N/A | yes | no | + +Valid sensors: + +- WH24 +- WH65 +- WH68 +- WH80 +- WH40 +- WH25 +- WH26 +- WH31_CH1 +- WH31_CH2 +- WH31_CH3 +- WH31_CH4 +- WH31_CH5 +- WH31_CH6 +- WH31_CH7 +- WH31_CH8 +- WH51_CH1 +- WH51_CH2 +- WH51_CH3 +- WH51_CH4 +- WH51_CH5 +- WH51_CH6 +- WH51_CH7 +- WH51_CH8 +- WH41_CH1 +- WH41_CH2 +- WH41_CH3 +- WH41_CH4 +- WH57 +- WH55_CH1 +- WH55_CH2 +- WH55_CH3 +- WH55_CH4 +- WH34_CH1 +- WH34_CH2 +- WH34_CH3 +- WH34_CH4 +- WH34_CH5 +- WH34_CH6 +- WH34_CH7 +- WH34_CH8 +- WH45 +- WH35_CH1 +- WH35_CH2 +- WH35_CH3 +- WH35_CH4 +- WH35_CH5 +- WH35_CH6 +- WH35_CH7 +- WH35_CH8 +- WH90 + +## Channels + +### `gateway` Channels + +| Channel | Type | Read/Write | Description | +|---------------------------------------|-------------------------------|------------|------------------------------------------------| +| temperature-indoor | Number:Temperature | R | Indoor Temperature | +| temperature-outdoor | Number:Temperature | R | Outdoor Temperature | +| temperature-dew-point | Number:Temperature | R | Dew Point | +| temperature-wind-chill | Number:Temperature | R | Perceived Temperature | +| temperature-heat-index | Number:Temperature | R | Heat Index | +| humidity-indoor | Number:Dimensionless | R | Humidity Inside | +| humidity-outdoor | Number:Dimensionless | R | Humidity Outside | +| pressure-absolute | Number:Pressure | R | Absolute Pressure | +| pressure-relative | Number:Pressure | R | Relative Pressure | +| direction-wind | Number:Angle | R | Wind Direction | +| speed-wind | Number:Speed | R | Wind Speed | +| speed-gust | Number:Speed | R | Gust Speed | +| rain-event | Number:Length | R | Amount of Rainfall At the last Rain | +| rain-rate | Number:VolumetricFlowRate | R | Rainfall Rate | +| rain-hour | Number:Length | R | Rainfall Current Hour | +| rain-day | Number:Length | R | Rainfall Today | +| rain-week | Number:Length | R | Rainfall this Week | +| rain-month | Number:Length | R | Rainfall this Month | +| rain-year | Number:Length | R | Rainfall this Year | +| rain-total | Number:Length | R | Rainfall Total | +| illumination | Number:Illuminance | R | Light Intensity | +| irradiation-uv | Number:Intensity | R | UV Irradiation | +| uv-index | Number:Dimensionless | R | UV Index | +| wind-max-day | Number:Speed | R | Maximum Wind Speed Today | +| temperature-channel-1 | Number:Temperature | R | Temperature Channel 1 | +| temperature-channel-2 | Number:Temperature | R | Temperature Channel 2 | +| temperature-channel-3 | Number:Temperature | R | Temperature Channel 3 | +| temperature-channel-4 | Number:Temperature | R | Temperature Channel 4 | +| temperature-channel-5 | Number:Temperature | R | Temperature Channel 5 | +| temperature-channel-6 | Number:Temperature | R | Temperature Channel 6 | +| temperature-channel-7 | Number:Temperature | R | Temperature Channel 7 | +| temperature-channel-8 | Number:Temperature | R | Temperature Channel 8 | +| humidity-channel-1 | Number:Dimensionless | R | Humidity Channel 1 | +| humidity-channel-2 | Number:Dimensionless | R | Humidity Channel 2 | +| humidity-channel-3 | Number:Dimensionless | R | Humidity Channel 3 | +| humidity-channel-4 | Number:Dimensionless | R | Humidity Channel 4 | +| humidity-channel-5 | Number:Dimensionless | R | Humidity Channel 5 | +| humidity-channel-6 | Number:Dimensionless | R | Humidity Channel 6 | +| humidity-channel-7 | Number:Dimensionless | R | Humidity Channel 7 | +| humidity-channel-8 | Number:Dimensionless | R | Humidity Channel 8 | +| temperature-soil-channel-1 | Number:Temperature | R | Soil Temperature Channel 1 | +| temperature-soil-channel-2 | Number:Temperature | R | Soil Temperature Channel 2 | +| temperature-soil-channel-3 | Number:Temperature | R | Soil Temperature Channel 3 | +| temperature-soil-channel-4 | Number:Temperature | R | Soil Temperature Channel 4 | +| temperature-soil-channel-5 | Number:Temperature | R | Soil Temperature Channel 5 | +| temperature-soil-channel-6 | Number:Temperature | R | Soil Temperature Channel 6 | +| temperature-soil-channel-7 | Number:Temperature | R | Soil Temperature Channel 7 | +| temperature-soil-channel-8 | Number:Temperature | R | Soil Temperature Channel 8 | +| temperature-soil-channel-9 | Number:Temperature | R | Soil Temperature Channel 9 | +| temperature-soil-channel-10 | Number:Temperature | R | Soil Temperature Channel 10 | +| temperature-soil-channel-11 | Number:Temperature | R | Soil Temperature Channel 11 | +| temperature-soil-channel-12 | Number:Temperature | R | Soil Temperature Channel 12 | +| temperature-soil-channel-13 | Number:Temperature | R | Soil Temperature Channel 13 | +| temperature-soil-channel-14 | Number:Temperature | R | Soil Temperature Channel 14 | +| temperature-soil-channel-15 | Number:Temperature | R | Soil Temperature Channel 15 | +| temperature-soil-channel-16 | Number:Temperature | R | Soil Temperature Channel 16 | +| moisture-soil-channel-1 | Number:Dimensionless | R | Soil Moisture Channel 1 | +| moisture-soil-channel-2 | Number:Dimensionless | R | Soil Moisture Channel 2 | +| moisture-soil-channel-3 | Number:Dimensionless | R | Soil Moisture Channel 3 | +| moisture-soil-channel-4 | Number:Dimensionless | R | soil Moisture Channel 4 | +| moisture-soil-channel-5 | Number:Dimensionless | R | Soil Moisture Channel 5 | +| moisture-soil-channel-6 | Number:Dimensionless | R | Soil Moisture Channel 6 | +| moisture-soil-channel-7 | Number:Dimensionless | R | Soil Moisture Channel 7 | +| moisture-soil-channel-8 | Number:Dimensionless | R | Soil Moisture Channel 8 | +| moisture-soil-channel-9 | Number:Dimensionless | R | Soil Moisture Channel 9 | +| moisture-soil-channel-10 | Number:Dimensionless | R | Soil Moisture Channel 10 | +| moisture-soil-channel-11 | Number:Dimensionless | R | Soil Moisture Channel 11 | +| moisture-soil-channel-12 | Number:Dimensionless | R | Soil Moisture Channel 12 | +| moisture-soil-channel-13 | Number:Dimensionless | R | soil Moisture Channel 13 | +| moisture-soil-channel-14 | Number:Dimensionless | R | Soil Moisture Channel 14 | +| moisture-soil-channel-15 | Number:Dimensionless | R | Soil Moisture Channel 15 | +| moisture-soil-channel-16 | Number:Dimensionless | R | Soil Moisture Channel 16 | +| air-quality-24-hour-average-channel-1 | Number:Density | R | PM2.5 Air Quality 24 Hour Average Channel 1 | +| air-quality-24-hour-average-channel-2 | Number:Density | R | PM2.5 Air Quality 24 Hour Average Channel 2 | +| air-quality-24-hour-average-channel-3 | Number:Density | R | PM2.5 Air Quality 24 Hour Average Channel 3 | +| air-quality-24-hour-average-channel-4 | Number:Density | R | PM2.5 Air Quality 24 Hour Average Channel 4 | +| air-quality-channel-1 | Number:Density | R | PM2.5 Air Quality Channel 1 | +| air-quality-channel-2 | Number:Density | R | PM2.5 Air Quality Channel 2 | +| air-quality-channel-3 | Number:Density | R | PM2.5 Air Quality Channel 3 | +| air-quality-channel-4 | Number:Density | R | PM2.5 Air Quality Channel 4 | +| water-leak-channel-1 | Switch | R | Water Leak Detection Channel 1 | +| water-leak-channel-2 | Switch | R | Water Leak Detection Channel 2 | +| water-leak-channel-3 | Switch | R | Water Leak Detection Channel 3 | +| water-leak-channel-4 | Switch | R | Water Leak Detection Channel 4 | +| lightning-distance | Number:Length | R | Lightning Distance | +| lightning-time | DateTime | R | Time of last Lightning Strike | +| lightning-counter | Number | R | Lightning Strikes Today | +| temperature-external-channel-1 | Number:Temperature | R | External Temperature Sensor Channel 1 | +| temperature-external-channel-2 | Number:Temperature | R | External Temperature Sensor Channel 2 | +| temperature-external-channel-3 | Number:Temperature | R | External Temperature Sensor Channel 3 | +| temperature-external-channel-4 | Number:Temperature | R | External Temperature Sensor Channel 4 | +| temperature-external-channel-5 | Number:Temperature | R | External Temperature Sensor Channel 5 | +| temperature-external-channel-6 | Number:Temperature | R | External Temperature Sensor Channel 6 | +| temperature-external-channel-7 | Number:Temperature | R | External Temperature Sensor Channel 7 | +| temperature-external-channel-8 | Number:Temperature | R | External Temperature Sensor Channel 8 | +| sensor-co2-temperature | Number:Temperature | R | Temperature (CO2-Sensor) | +| sensor-co2-humidity | Number:Dimensionless | R | Humidity (CO2-Sensor) | +| sensor-co2-pm10 | Number:Density | R | PM10 Air Quality (CO2-Sensor) | +| sensor-co2-pm10-24-hour-average | Number:Density | R | PM10 Air Quality 24 Hour Average (CO2-Sensor) | +| sensor-co2-pm25 | Number:Density | R | PM2.5 Air Quality (CO2-Sensor) | +| sensor-co2-pm25-24-hour-average | Number:Density | R | PM2.5 Air Quality 24 Hour Average (CO2-Sensor) | +| sensor-co2-co2 | Number:Dimensionless | R | CO2 | +| sensor-co2-co2-24-hour-average | Number:Dimensionless | R | CO2 24 Hour Average | +| leaf-wetness-channel-1 | Number:Dimensionless | R | Leaf Moisture Channel 1 | +| leaf-wetness-channel-2 | Number:Dimensionless | R | Leaf Moisture Channel 2 | +| leaf-wetness-channel-3 | Number:Dimensionless | R | Leaf Moisture Channel 3 | +| leaf-wetness-channel-4 | Number:Dimensionless | R | Leaf Moisture Channel 4 | +| leaf-wetness-channel-5 | Number:Dimensionless | R | Leaf Moisture Channel 5 | +| leaf-wetness-channel-6 | Number:Dimensionless | R | Leaf Moisture Channel 6 | +| leaf-wetness-channel-7 | Number:Dimensionless | R | Leaf Moisture Channel 7 | +| leaf-wetness-channel-8 | Number:Dimensionless | R | Leaf Moisture Channel 8 | + +### `sensor` Channels + +| Channel | Type | Read/Write | Description | +|--------------|--------|------------|-----------------------------| +| signal | Number | R | The sensors signal strenght | +| batteryLevel | Number | R | The sensors battery level | +| lowBattery | Switch | R | The sensors battery status | + +## Full Example + +This is an example configuration for the WH2650 gateway + +_weatherstation.things_: + +```xtend +Bridge fineoffsetweatherstation:gateway:3906700515 "Weather station" [ip="192.168.1.42", port="45000", discoverInterval="900", pollingInterval="16"] { + Thing sensor WH25 "WH25" [sensor="WH25"] + Thing sensor WH65 "WH65" [sensor="WH65"] +} +``` + +_weatherstation.items_: + +```xtend +Group WH25 "WH25" ["Sensor"] +Number SignalWH25 "Signal WH25" (WH25) ["Measurement", "Level"] { channel="fineoffsetweatherstation:sensor:3906700515:WH25:signal" } +Switch BatteryStatusWH25 "Low Battery WH25" (WH25) ["Energy", "LowBattery"] { channel="fineoffsetweatherstation:sensor:3906700515:WH25:lowBattery" } + +Group WH65 "WH65" ["Sensor"] +Number SignalWH65 "Signal WH65" (WH65) ["Measurement", "Level"] { channel="fineoffsetweatherstation:sensor:3906700515:WH65:signal" } +Switch BatteryStatusWH65 "Low Battery WH65" (WH65) ["Energy", "LowBattery"] { channel="fineoffsetweatherstation:sensor:3906700515:WH65:lowBattery" } + +Group gOutdoor "Outdoor" ["Location"] +Number:Temperature weather_temperature_outdoor "Outdoor Temperature" (gOutdoor) ["Measurement", "Temperature"] { channel="fineoffsetweatherstation:gateway:3906700515:temperature-outdoor" } +Number:Temperature weather_temperature_indoor "Inside temperature" ["Measurement", "Temperature"] { channel="fineoffsetweatherstation:gateway:3906700515:temperature-indoor" } +Number:Dimensionless weather_humidity_indoor "Humidity inside" ["Measurement", "Humidity"] { channel="fineoffsetweatherstation:gateway:3906700515:humidity-indoor" } +Number:Pressure weather_pressure_absolute "Absolute pressure" (gOutdoor) ["Measurement", "Pressure"] { channel="fineoffsetweatherstation:gateway:3906700515:pressure-absolute" } +Number:Pressure weather_pressure_relative "Relative pressure" (gOutdoor) ["Measurement", "Pressure"] { channel="fineoffsetweatherstation:gateway:3906700515:pressure-relative" } +Number:Dimensionless weather_humidity_outdoor "Humidity outside" (gOutdoor) ["Measurement", "Humidity"] { channel="fineoffsetweatherstation:gateway:3906700515:humidity-outdoor" } +Number:Angle weather_direction_wind "Wind direction" (gOutdoor) ["Measurement", "Wind"] { channel="fineoffsetweatherstation:gateway:3906700515:direction-wind" } +Number:Speed weather_speed_wind "Wind speed" (gOutdoor) ["Measurement", "Wind"] { channel="fineoffsetweatherstation:gateway:3906700515:speed-wind" } +Number:Speed weather_speed_gust "Gust speed" (gOutdoor) ["Measurement", "Wind"] { channel="fineoffsetweatherstation:gateway:3906700515:speed-gust" } +Number:Illuminance weather_illumination "Light intensity" (gOutdoor) ["Measurement", "Light"] { channel="fineoffsetweatherstation:gateway:3906700515:illumination" } +Number:Intensity weather_irradiation_uv "UV radiation" (gOutdoor) ["Measurement", "Light"] { channel="fineoffsetweatherstation:gateway:3906700515:irradiation-uv" } +Number:Dimensionless weather_uv_index "UV Index" (gOutdoor) ["Measurement", "Light"] { channel="fineoffsetweatherstation:gateway:3906700515:uv-index" } +Number:Speed weather_max_day "Maximum wind speed today" (gOutdoor) ["Measurement", "Wind"] { channel="fineoffsetweatherstation:gateway:3906700515:wind-max-day" } +Number:VolumetricFlowRate weather_rain_rate "Rainfall rate" (gOutdoor) ["Measurement", "Rain"] { channel="fineoffsetweatherstation:gateway:3906700515:rain-rate" } +Number:Length weather_rain_day "Rainfall today" (gOutdoor) ["Measurement", "Rain"] { channel="fineoffsetweatherstation:gateway:3906700515:rain-day" } +Number:Length weather_rain_week "Rainfall this week" (gOutdoor) ["Measurement", "Rain"] { channel="fineoffsetweatherstation:gateway:3906700515:rain-week" } +Number:Length weather_rain_month "Rainfall this month" (gOutdoor) ["Measurement", "Rain"] { channel="fineoffsetweatherstation:gateway:3906700515:rain-month" } +Number:Length weather_rain_year "Rainfall this year " (gOutdoor) ["Measurement", "Rain"] { channel="fineoffsetweatherstation:gateway:3906700515:rain-year" } +Number:Length weather_rain_event "Amount of rainfall at the last rain" (gOutdoor) ["Measurement", "Rain"] { channel="fineoffsetweatherstation:gateway:3906700515:rain-event" } +``` diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/doc/WH2650.png b/bundles/org.openhab.binding.fineoffsetweatherstation/doc/WH2650.png new file mode 100644 index 000000000..965345243 Binary files /dev/null and b/bundles/org.openhab.binding.fineoffsetweatherstation/doc/WH2650.png differ diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/pom.xml b/bundles/org.openhab.binding.fineoffsetweatherstation/pom.xml new file mode 100644 index 000000000..26139fd97 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/pom.xml @@ -0,0 +1,38 @@ + + + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.3.0-SNAPSHOT + + + org.openhab.binding.fineoffsetweatherstation + + openHAB Add-ons :: Bundles :: Fine Offset Weather Station + + + + org.assertj + assertj-core + 3.22.0 + test + + + diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/feature/feature.xml b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/feature/feature.xml new file mode 100644 index 000000000..9a2de9a94 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.fineoffsetweatherstation/${project.version} + + diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetGatewayConfiguration.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetGatewayConfiguration.java new file mode 100644 index 000000000..6c8e43f02 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetGatewayConfiguration.java @@ -0,0 +1,33 @@ +/** + * 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; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link FineOffsetGatewayConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class FineOffsetGatewayConfiguration { + + public static final String IP = "ip"; + public static final String PORT = "port"; + + public @Nullable String ip; + public int port = 45000; + public int pollingInterval = 16; + public int discoverInterval = 900; +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetSensorConfiguration.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetSensorConfiguration.java new file mode 100644 index 000000000..3767f75a5 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetSensorConfiguration.java @@ -0,0 +1,30 @@ +/** + * 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; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding; + +/** + * The {@link FineOffsetSensorConfiguration} class contains the fields mapping thing configuration parameters. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class FineOffsetSensorConfiguration { + + public static final String SENSOR = "sensor"; + + public @Nullable SensorGatewayBinding sensor; +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetWeatherStationBindingConstants.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetWeatherStationBindingConstants.java new file mode 100644 index 000000000..6c5950667 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetWeatherStationBindingConstants.java @@ -0,0 +1,62 @@ +/** + * 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; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.type.ChannelTypeUID; + +/** + * The {@link FineOffsetWeatherStationBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class FineOffsetWeatherStationBindingConstants { + + public static final String BINDING_ID = "fineoffsetweatherstation"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_GATEWAY = new ThingTypeUID(BINDING_ID, "gateway"); + public static final ThingTypeUID THING_TYPE_SENSOR = new ThingTypeUID(BINDING_ID, "sensor"); + + public static final ChannelTypeUID CHANNEL_TYPE_TEMPERATURE = new ChannelTypeUID(BINDING_ID, "temperature"); + public static final ChannelTypeUID CHANNEL_TYPE_HUMIDITY = new ChannelTypeUID(BINDING_ID, "humidity"); + public static final ChannelTypeUID CHANNEL_TYPE_MOISTURE = new ChannelTypeUID(BINDING_ID, "moisture"); + public static final ChannelTypeUID CHANNEL_TYPE_MAX_WIND_SPEED = new ChannelTypeUID(BINDING_ID, "max-wind-speed"); + public static final ChannelTypeUID CHANNEL_TYPE_RAIN = new ChannelTypeUID(BINDING_ID, "rain"); + public static final ChannelTypeUID CHANNEL_TYPE_PRESSURE = new ChannelTypeUID(BINDING_ID, "pressure"); + public static final ChannelTypeUID CHANNEL_TYPE_ILLUMINATION = new ChannelTypeUID(BINDING_ID, "illumination"); + public static final ChannelTypeUID CHANNEL_TYPE_UV_INDEX = new ChannelTypeUID(BINDING_ID, "uv-index"); + public static final ChannelTypeUID CHANNEL_TYPE_UV_RADIATION = new ChannelTypeUID(BINDING_ID, "uv-radiation"); + public static final ChannelTypeUID CHANNEL_TYPE_RAIN_RATE = new ChannelTypeUID(BINDING_ID, "rain-rate"); + public static final ChannelTypeUID CHANNEL_TYPE_PM25 = new ChannelTypeUID(BINDING_ID, "pm25"); + public static final ChannelTypeUID CHANNEL_TYPE_PM10 = new ChannelTypeUID(BINDING_ID, "pm10"); + public static final ChannelTypeUID CHANNEL_TYPE_CO2 = new ChannelTypeUID(BINDING_ID, "co2"); + public static final ChannelTypeUID CHANNEL_TYPE_WATER_LEAK_DETECTION = new ChannelTypeUID(BINDING_ID, + "water-leak-detection"); + public static final ChannelTypeUID CHANNEL_TYPE_LIGHTNING_COUNTER = new ChannelTypeUID(BINDING_ID, + "lightning-counter"); + public static final ChannelTypeUID CHANNEL_TYPE_LIGHTNING_TIME = new ChannelTypeUID(BINDING_ID, "lightning-time"); + public static final ChannelTypeUID CHANNEL_TYPE_LIGHTNING_DISTANCE = new ChannelTypeUID(BINDING_ID, + "lightning-distance"); + + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GATEWAY, THING_TYPE_SENSOR); + + public static final String SENSOR_CHANNEL_SIGNAL = "signal"; + public static final String SENSOR_CHANNEL_BATTERY_LEVEL = "batteryLevel"; + public static final String SENSOR_CHANNEL_LOW_BATTERY = "lowBattery"; +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetWeatherStationHandlerFactory.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetWeatherStationHandlerFactory.java new file mode 100644 index 000000000..de54049bf --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/FineOffsetWeatherStationHandlerFactory.java @@ -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; + +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.SUPPORTED_THING_TYPES_UIDS; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.THING_TYPE_GATEWAY; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.THING_TYPE_SENSOR; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.fineoffsetweatherstation.internal.discovery.FineOffsetGatewayDiscoveryService; +import org.openhab.binding.fineoffsetweatherstation.internal.handler.FineOffsetGatewayHandler; +import org.openhab.binding.fineoffsetweatherstation.internal.handler.FineOffsetSensorHandler; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.openhab.core.thing.type.ChannelTypeRegistry; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link FineOffsetWeatherStationHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.fineoffsetweatherstation", service = ThingHandlerFactory.class) +public class FineOffsetWeatherStationHandlerFactory extends BaseThingHandlerFactory { + + private final FineOffsetGatewayDiscoveryService gatewayDiscoveryService; + private final ChannelTypeRegistry channelTypeRegistry; + private final TranslationProvider translationProvider; + private final LocaleProvider localeProvider; + private final TimeZoneProvider timeZoneProvider; + + @Activate + public FineOffsetWeatherStationHandlerFactory(@Reference FineOffsetGatewayDiscoveryService gatewayDiscoveryService, + @Reference ChannelTypeRegistry channelTypeRegistry, @Reference TranslationProvider translationProvider, + @Reference LocaleProvider localeProvider, @Reference TimeZoneProvider timeZoneProvider) { + this.gatewayDiscoveryService = gatewayDiscoveryService; + this.channelTypeRegistry = channelTypeRegistry; + this.translationProvider = translationProvider; + this.localeProvider = localeProvider; + this.timeZoneProvider = timeZoneProvider; + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_GATEWAY.equals(thingTypeUID) && thing instanceof Bridge) { + return new FineOffsetGatewayHandler((Bridge) thing, gatewayDiscoveryService, channelTypeRegistry, + translationProvider, localeProvider, timeZoneProvider); + } + if (THING_TYPE_SENSOR.equals(thingTypeUID)) { + return new FineOffsetSensorHandler(thing); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/Utils.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/Utils.java new file mode 100644 index 000000000..c93d1d6dd --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/Utils.java @@ -0,0 +1,88 @@ +/** + * 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; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Utility class. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class Utils { + + public static String toHexString(byte[] hex, int length, String delimiter) { + String[] hexadecimal = new String[length]; + for (int i = 0; i < length; i++) { + hexadecimal[i] = String.format("%02X", hex[i]); + } + return String.join(delimiter, hexadecimal); + } + + public static boolean validateChecksum(byte[] data, int sizeBytes) { + int size; + + // general response + // | 1 byte size | 2 byte size + // | -----------------------------|-------------------- + // | 0 - 0xff - header | 0 - 0xff - header + // | 1 - 0xff | 1 - 0xff + // | 2 - command | 2 - command + // | 3 - total size of response | 3 - size1 + // | 4-X - data | 4 - size2 + // | X+1 - checksum | 5-X - data + // | | X+1 - checksum + + if (sizeBytes == 1) { + size = Utils.toUInt8(data[3]); + } else { + size = toUInt16(data, 3); + } + + byte checksum = sum(data, 2, size); + return checksum == data[size + 1]; + } + + private static byte sum(byte[] data, int start, int end) { + byte checksum = 0; + for (var i = start; i <= end; i++) { + checksum += data[i]; + } + return checksum; + } + + public static int toUInt8(byte data) { + return Byte.toUnsignedInt(data); + } + + public static int toInt16(byte[] array, int start) { + int result = ((int) array[start]) << 24; + result |= Utils.toUInt8(array[start + 1]) << 16; + return result >> 16; + } + + public static int toUInt16(byte[] array, int start) { + return (Utils.toUInt8(array[start]) << 8 | Utils.toUInt8(array[start + 1])); + } + + public static int toUInt32(byte[] array, int start) { + return (Utils.toUInt8(array[start++]) << 24 | Utils.toUInt8(array[start++]) << 16 + | Utils.toUInt8(array[start++]) << 8 | Utils.toUInt8(array[start])); + } + + public static long toUInt64(byte[] array, int start) { + return ((long) Utils.toUInt8(array[start++]) << 24 | (long) toUInt8(array[start++]) << 16 + | (long) toUInt8(array[start++]) << 8 | Utils.toUInt8(array[start])); + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/discovery/FineOffsetGatewayDiscoveryService.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/discovery/FineOffsetGatewayDiscoveryService.java new file mode 100644 index 000000000..10289e095 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/discovery/FineOffsetGatewayDiscoveryService.java @@ -0,0 +1,308 @@ +/** + * 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.discovery; + +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.THING_TYPE_GATEWAY; +import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt16; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +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.FineOffsetSensorConfiguration; +import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants; +import org.openhab.binding.fineoffsetweatherstation.internal.Utils; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.net.NetUtil; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingUID; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +@Component(service = { DiscoveryService.class, FineOffsetGatewayDiscoveryService.class }, immediate = true) +public class FineOffsetGatewayDiscoveryService extends AbstractDiscoveryService { + public static final int DISCOVERY_PORT = 46000; + private static final int BUFFER_LENGTH = 255; + + private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayDiscoveryService.class); + + private static final long REFRESH_INTERVAL = 600; + private static final int DISCOVERY_TIME = 5; + private final TranslationProvider translationProvider; + private final LocaleProvider localeProvider; + private final @Nullable Bundle bundle; + private @Nullable DatagramSocket clientSocket; + private @Nullable Thread socketReceiveThread; + private @Nullable ScheduledFuture discoveryJob; + + @Activate + public FineOffsetGatewayDiscoveryService(@Reference TranslationProvider translationProvider, + @Reference LocaleProvider localeProvider) throws IllegalArgumentException { + super(Collections.singleton(THING_TYPE_GATEWAY), DISCOVERY_TIME, true); + this.translationProvider = translationProvider; + this.localeProvider = localeProvider; + this.bundle = FrameworkUtil.getBundle(FineOffsetGatewayDiscoveryService.class); + } + + @Override + protected void startBackgroundDiscovery() { + final @Nullable ScheduledFuture discoveryJob = this.discoveryJob; + if (discoveryJob == null || discoveryJob.isCancelled()) { + this.discoveryJob = scheduler.scheduleWithFixedDelay(this::discover, 0, REFRESH_INTERVAL, TimeUnit.SECONDS); + } + } + + @Override + protected void stopBackgroundDiscovery() { + final @Nullable ScheduledFuture discoveryJob = this.discoveryJob; + if (discoveryJob != null) { + discoveryJob.cancel(true); + this.discoveryJob = null; + } + } + + @Override + public void deactivate() { + stopReceiverThreat(); + final DatagramSocket clientSocket = this.clientSocket; + if (clientSocket != null) { + clientSocket.close(); + } + this.clientSocket = null; + super.deactivate(); + } + + @Override + protected void startScan() { + final DatagramSocket clientSocket = getSocket(); + if (clientSocket != null) { + logger.debug("Discovery using socket on port {}", clientSocket.getLocalPort()); + discover(); + } else { + logger.debug("Discovery not started. Client DatagramSocket null"); + } + } + + private void discover() { + startReceiverThread(); + NetUtil.getAllBroadcastAddresses().forEach(this::sendDiscoveryRequest); + } + + public void addSensors(ThingUID bridgeUID, Collection sensorDevices) { + for (SensorDevice sensorDevice : sensorDevices) { + ThingUID uid = new ThingUID(FineOffsetWeatherStationBindingConstants.THING_TYPE_SENSOR, bridgeUID, + sensorDevice.getSensorGatewayBinding().name()); + + String model = sensorDevice.getSensorGatewayBinding().getSensor().name(); + String prefix = "thing.sensor." + model; + @Nullable + String name = translationProvider.getText(bundle, prefix + ".label", model, localeProvider.getLocale()); + DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID) + .withProperty(FineOffsetSensorConfiguration.SENSOR, sensorDevice.getSensorGatewayBinding().name()) + .withProperty(Thing.PROPERTY_MODEL_ID, model) + .withRepresentationProperty(FineOffsetSensorConfiguration.SENSOR); + + @Nullable + Integer channel = sensorDevice.getSensorGatewayBinding().getChannel(); + if (channel != null) { + builder.withProperty("channel", channel); + name += " " + translationProvider.getText(bundle, "channel", "channel", localeProvider.getLocale()) + + " " + channel; + } + builder.withLabel(name); + @Nullable + String description = translationProvider.getText(bundle, prefix + ".description", model, + localeProvider.getLocale()); + if (description != null) { + builder.withProperty("description", description); + } + + DiscoveryResult result = builder.build(); + thingDiscovered(result); + } + } + + private void discovered(String ip, int port, byte[] macAddr, String name) { + String id = String.valueOf(Utils.toUInt64(macAddr, 0)); + + Map properties = new HashMap<>(); + properties.put(Thing.PROPERTY_MAC_ADDRESS, Utils.toHexString(macAddr, macAddr.length, ":")); + properties.put(FineOffsetGatewayConfiguration.IP, ip); + properties.put(FineOffsetGatewayConfiguration.PORT, port); + + ThingUID uid = new ThingUID(THING_TYPE_GATEWAY, id); + DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties) + .withLabel(translationProvider.getText(bundle, "thing.gateway.label", name, localeProvider.getLocale())) + .build(); + thingDiscovered(result); + logger.debug("Thing discovered '{}'", result); + } + + synchronized @Nullable DatagramSocket getSocket() { + DatagramSocket clientSocket = this.clientSocket; + if (clientSocket != null && clientSocket.isBound()) { + return clientSocket; + } + try { + logger.debug("Getting new socket for discovery"); + clientSocket = new DatagramSocket(); + clientSocket.setReuseAddress(true); + clientSocket.setBroadcast(true); + this.clientSocket = clientSocket; + return clientSocket; + } catch (SocketException | SecurityException e) { + logger.debug("Error getting socket for discovery: {}", e.getMessage()); + } + return null; + } + + private void closeSocket() { + final @Nullable DatagramSocket clientSocket = this.clientSocket; + if (clientSocket != null) { + clientSocket.close(); + } else { + return; + } + this.clientSocket = null; + } + + private void sendDiscoveryRequest(String broadcastAddress) { + final @Nullable DatagramSocket socket = getSocket(); + if (socket != null) { + byte[] requestMessage = Command.CMD_BROADCAST.getPayload(); + InetSocketAddress addr = new InetSocketAddress(broadcastAddress, DISCOVERY_PORT); + DatagramPacket datagramPacket = new DatagramPacket(requestMessage, requestMessage.length, addr); + try { + socket.send(datagramPacket); + } catch (IOException e) { + logger.trace("Discovery on {} error: {}", broadcastAddress, e.getMessage()); + } + } + } + + /** + * starts the {@link ReceiverThread} thread + */ + private synchronized void startReceiverThread() { + final Thread srt = socketReceiveThread; + if (srt != null) { + if (srt.isAlive() && !srt.isInterrupted()) { + return; + } + } + stopReceiverThreat(); + Thread socketReceiveThread = new ReceiverThread(); + socketReceiveThread.start(); + this.socketReceiveThread = socketReceiveThread; + } + + /** + * Stops the {@link ReceiverThread} thread + */ + private synchronized void stopReceiverThreat() { + final Thread socketReceiveThread = this.socketReceiveThread; + if (socketReceiveThread != null) { + socketReceiveThread.interrupt(); + this.socketReceiveThread = null; + } + closeSocket(); + } + + /** + * The thread, which waits for data and submits the unique results addresses to the discovery results + */ + private class ReceiverThread extends Thread { + @Override + public void run() { + DatagramSocket socket = getSocket(); + if (socket != null) { + logger.debug("Starting discovery receiver thread for socket on port {}", socket.getLocalPort()); + receiveData(socket); + } + } + + /** + * This method waits for data and submits the unique results addresses to the discovery results + * + * @param socket - The multicast socket to (re)use + */ + private void receiveData(DatagramSocket socket) { + DatagramPacket receivePacket = new DatagramPacket(new byte[BUFFER_LENGTH], BUFFER_LENGTH); + try { + while (!interrupted()) { + logger.trace("Thread {} waiting for data on port {}", this, socket.getLocalPort()); + socket.receive(receivePacket); + String hostAddress = receivePacket.getAddress().getHostAddress(); + logger.trace("Received {} bytes response from {}:{} on Port {}", receivePacket.getLength(), + hostAddress, receivePacket.getPort(), socket.getLocalPort()); + + byte[] messageBuf = Arrays.copyOfRange(receivePacket.getData(), receivePacket.getOffset(), + receivePacket.getOffset() + receivePacket.getLength()); + if (logger.isTraceEnabled()) { + logger.trace("Discovery response received: {}", + Utils.toHexString(messageBuf, messageBuf.length, "")); + } + + if (Command.CMD_BROADCAST.isHeaderValid(messageBuf)) { + String ip = InetAddress.getByAddress(Arrays.copyOfRange(messageBuf, 11, 15)).getHostAddress(); + var macAddr = Arrays.copyOfRange(messageBuf, 5, 5 + 6); + var port = toUInt16(messageBuf, 15); + var len = Utils.toUInt8(messageBuf[17]); + String name = new String(messageBuf, 18, len); + scheduler.schedule(() -> { + try { + discovered(ip, port, macAddr, name); + } catch (Exception e) { + logger.debug("Error submitting discovered device at {}", ip, e); + } + }, 0, TimeUnit.SECONDS); + } + } + } catch (SocketException e) { + logger.debug("Receiver thread received SocketException: {}", e.getMessage()); + } catch (IOException e) { + logger.trace("Receiver thread was interrupted"); + } + logger.debug("Receiver thread ended"); + } + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Command.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Command.java new file mode 100644 index 000000000..2d47269a5 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Command.java @@ -0,0 +1,268 @@ +/** + * 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.util.Arrays; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.fineoffsetweatherstation.internal.Utils; + +/** + * The Commands supported by the gateway. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public enum Command { + /** + * send SSID and Password to WIFI module + */ + CMD_WRITE_SSID((byte) 0x11, 1), + + /** + * UDP cast for device echo,answer back data size is 2 Bytes + */ + CMD_BROADCAST((byte) 0x12, 2), + + /** + * read aw.net setting + */ + CMD_READ_ECOWITT((byte) 0x1E, 1), + + /** + * write back awt.net setting + */ + CMD_WRITE_ECOWITT((byte) 0x1F, 1), + + /** + * read Wunderground setting + */ + CMD_READ_WUNDERGROUND((byte) 0x20, 1), + + /** + * write back Wunderground setting + */ + CMD_WRITE_WUNDERGROUND((byte) 0x21, 1), + + /** + * read WeatherObservationsWebsite setting + */ + CMD_READ_WOW((byte) 0x22, 1), + + /** + * write back WeatherObservationsWebsite setting + */ + CMD_WRITE_WOW((byte) 0x23, 1), + + /** + * read Weathercloud setting + */ + CMD_READ_WEATHERCLOUD((byte) 0x24, 1), + + /** + * write back Weathercloud setting + */ + CMD_WRITE_WEATHERCLOUD((byte) 0x25, 1), + + /** + * read MAC address + */ + CMD_READ_SATION_MAC((byte) 0x26, 1), + + /** + * read Customized sever setting + */ + CMD_READ_CUSTOMIZED((byte) 0x2A, 1), + + /** + * write back Customized sever setting + */ + CMD_WRITE_CUSTOMIZED((byte) 0x2B, 1), + + /** + * firmware upgrade + */ + CMD_WRITE_UPDATE((byte) 0x43, 1), + + /** + * read current firmware version number + */ + CMD_READ_FIRMWARE_VERSION((byte) 0x50, 1), + + CMD_READ_USR_PATH((byte) 0x51, 1), + + CMD_WRITE_USR_PATH((byte) 0x52, 1), + + // the following command is only valid for GW1000, WH2650 and wn1900 + + /** + * read current data,reply data size is 2bytes. + */ + CMD_GW1000_LIVEDATA((byte) 0x27, 2), + + /** + * read Soilmoisture Sensor calibration parameters + */ + CMD_GET_SOILHUMIAD((byte) 0x28, 1), + + /** + * write back Soilmoisture Sensor calibration parameters + */ + CMD_SET_SOILHUMIAD((byte) 0x29, 1), + + /** + * read multi channel sensor offset value + */ + CMD_GET_MulCH_OFFSET((byte) 0x2C, 1), + + /** + * write back multi channel sensor OFFSET value + */ + CMD_SET_MulCH_OFFSET((byte) 0x2D, 1), + + /** + * read PM2.5OFFSET calibration data + */ + CMD_GET_PM25_OFFSET((byte) 0x2E, 1), + + /** + * writeback PM2.5OFFSET calibration data + */ + CMD_SET_PM25_OFFSET((byte) 0x2F, 1), + + /** + * read system info + */ + CMD_READ_SSSS((byte) 0x30, 1), + + /** + * write back system info + */ + CMD_WRITE_SSSS((byte) 0x31, 1), + + /** + * read rain data + */ + CMD_READ_RAINDATA((byte) 0x34, 1), + + /** + * write back rain data + */ + CMD_WRITE_RAINDATA((byte) 0x35, 1), + + /** + * read rain gain + */ + CMD_READ_GAIN((byte) 0x36, 1), + + /** + * write back rain gain + */ + CMD_WRITE_GAIN((byte) 0x37, 1), + + /** + * read sensor set offset calibration value + */ + CMD_READ_CALIBRATION((byte) 0x38, 1), + + /** + * write back sensor set offset value + */ + CMD_WRITE_CALIBRATION((byte) 0x39, 1), + + /** + * read Sensors ID + */ + CMD_READ_SENSOR_ID((byte) 0x3A, 1), + + /** + * write back Sensors ID + */ + CMD_WRITE_SENSOR_ID((byte) 0x3B, 1), + + /** + * this is reserved for newly added sensors + */ + CMD_READ_SENSOR_ID_NEW((byte) 0x3C, 2), + + /** + * system restart + */ + CMD_WRITE_REBOOT((byte) 0x40, 1), + + /** + * reset to default + */ + CMD_WRITE_RESET((byte) 0x41, 1), + + CMD_READ_CUSTOMIZED_PATH((byte) 0x51, 1), + + CMD_WRITE_CUSTOMIZED_PATH((byte) 0x52, 1), + + /** + * CO2 OFFSET + */ + CMD_GET_CO2_OFFSET((byte) 0x53, 1), + + /** + * CO2 OFFSET + */ + CMD_SET_CO2_OFFSET((byte) 0x54, 1), + + /** + * read rain reset time + */ + CMD_READ_RSTRAIN_TIME((byte) 0x55, 1), + + /** + * write back rain reset time + */ + CMD_WRITE_RSTRAIN_TIME((byte) 0x56, 1); + + private final byte code; + private final int sizeBytes; + + Command(byte code, int sizeBytes) { + this.code = code; + this.sizeBytes = sizeBytes; + } + + public byte getCode() { + return code; + } + + public int getSizeBytes() { + return sizeBytes; + } + + public byte[] getPayload() { + byte size = 3; // + rest of payload / not yet implemented + return new byte[] { (byte) 0xff, (byte) 0xff, code, size, (byte) (code + size) }; + } + + public static @Nullable Command findByCode(byte code) { + return Arrays.stream(values()).filter(command -> command.getCode() == code).findFirst().orElse(null); + } + + public boolean isHeaderValid(byte[] data) { + if (data.length < 4 + sizeBytes) { + return false; + } + return data[0] == (byte) 0xff && data[1] == (byte) 0xff && data[2] == code; + } + + public boolean isResponseValid(byte[] data) { + return isHeaderValid(data) && Utils.validateChecksum(data, sizeBytes); + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/ConversionContext.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/ConversionContext.java new file mode 100644 index 000000000..195a9bcc1 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/ConversionContext.java @@ -0,0 +1,34 @@ +/** + * 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.time.ZoneId; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class ConversionContext { + + private final ZoneId zoneId; + + public ConversionContext(ZoneId zoneId) { + this.zoneId = zoneId; + } + + public ZoneId getZoneId() { + return zoneId; + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Measurand.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Measurand.java new file mode 100644 index 000000000..a1b2dc2b1 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Measurand.java @@ -0,0 +1,391 @@ +/** + * 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 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 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 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 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 result) { + State state = measureType.toState(data, offset, context); + if (state != null) { + result.add(new MeasuredValue(measureType, channelId, channelTypeUID, state, name)); + } + return measureType.getByteSize(); + } + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/MeasureType.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/MeasureType.java new file mode 100644 index 000000000..075c34c9e --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/MeasureType.java @@ -0,0 +1,160 @@ +/** + * 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 javax.measure.MetricPrefix.HECTO; +import static javax.measure.MetricPrefix.KILO; +import static javax.measure.MetricPrefix.MILLI; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_CO2; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_HUMIDITY; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_ILLUMINATION; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_LIGHTNING_COUNTER; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_LIGHTNING_DISTANCE; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_LIGHTNING_TIME; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_PM10; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_PM25; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_PRESSURE; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_RAIN; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_RAIN_RATE; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_TEMPERATURE; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_UV_RADIATION; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.CHANNEL_TYPE_WATER_LEAK_DETECTION; +import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toInt16; +import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt16; +import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt32; +import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt8; +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.PASCAL; +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.MICROGRAM_PER_CUBICMETRE; +import static org.openhab.core.library.unit.Units.MILLIMETRE_PER_HOUR; +import static org.openhab.core.library.unit.Units.PARTS_PER_MILLION; +import static org.openhab.core.library.unit.Units.PERCENT; + +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.function.BiFunction; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.fineoffsetweatherstation.internal.Utils; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.State; + +/** + * Represents the measured type with conversion from the sensors' bytes to the openHAB state. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public enum MeasureType { + + TEMPERATURE(CELSIUS, 2, CHANNEL_TYPE_TEMPERATURE, (data, offset) -> toInt16(data, offset) / 10.), + + PERCENTAGE(PERCENT, 1, CHANNEL_TYPE_HUMIDITY, (data, offset) -> toUInt8(data[offset])), + + PRESSURE(HECTO(PASCAL), 2, CHANNEL_TYPE_PRESSURE, Utils::toUInt16), + + DEGREE(DEGREE_ANGLE, 2, null, Utils::toUInt16), + + SPEED(METRE_PER_SECOND, 2, null, (data, offset) -> toUInt16(data, offset) / 10.), + + HEIGHT(MILLI(METRE), 2, CHANNEL_TYPE_RAIN, (data, offset) -> toUInt16(data, offset) / 10.), + + HEIGHT_BIG(MILLI(METRE), 4, CHANNEL_TYPE_RAIN, (data, offset) -> toUInt32(data, offset) / 10.), + + HEIGHT_PER_HOUR(MILLIMETRE_PER_HOUR, 2, CHANNEL_TYPE_RAIN_RATE, (data, offset) -> toUInt16(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.), + + PM10(MICROGRAM_PER_CUBICMETRE, 2, CHANNEL_TYPE_PM10, (data, offset) -> toUInt16(data, offset) / 10.), + + CO2(PARTS_PER_MILLION, 2, CHANNEL_TYPE_CO2, Utils::toUInt16), + + WATER_LEAK_DETECTION(1, CHANNEL_TYPE_WATER_LEAK_DETECTION, + (data, offset, context) -> toUInt8(data[offset]) != 0 ? OnOffType.ON : OnOffType.OFF), + + LIGHTNING_DISTANCE(KILO(METRE), 1, CHANNEL_TYPE_LIGHTNING_DISTANCE, (data, offset) -> toUInt8(data[offset])), + + LIGHTNING_COUNTER(4, CHANNEL_TYPE_LIGHTNING_COUNTER, + (data, offset, context) -> new DecimalType(toUInt32(data, offset))), + + LIGHTNING_TIME(4, CHANNEL_TYPE_LIGHTNING_TIME, + (data, offset, context) -> new DateTimeType( + ZonedDateTime.ofInstant(Instant.ofEpochSecond(toUInt32(data, offset)), context.getZoneId()))), + + MICROWATT_PER_SQUARE_CENTIMETRE(Units.MICROWATT_PER_SQUARE_CENTIMETRE, 2, CHANNEL_TYPE_UV_RADIATION, + Utils::toUInt16), + + BYTE(1, null, (data, offset, context) -> new DecimalType(toUInt8(data[offset]))), + + DATE_TIME2(6, null, (data, offset, context) -> new DateTimeType( + ZonedDateTime.ofInstant(Instant.ofEpochSecond(toUInt32(data, offset)), context.getZoneId()))); + + private final int byteSize; + private final @Nullable ChannelTypeUID channelTypeUID; + private final StateConverter stateConverter; + + /** + * @param unit the unit + * @param byteSize the size in the sensors' payload + * @param channelTypeUID the channel type + * @param valueExtractor a function to extract the sensor data into a number of the dimension defined by the unit + */ + MeasureType(Unit unit, int byteSize, @Nullable ChannelTypeUID channelTypeUID, + BiFunction valueExtractor) { + this(byteSize, channelTypeUID, (bytes, offset, context) -> { + Number value = valueExtractor.apply(bytes, offset); + return value == null ? null : new QuantityType<>(value, unit); + }); + } + + /** + * @param byteSize the size in the sensors' payload + * @param channelTypeUID the channel type + * @param stateConverter a function to extract the sensor data into the openHAB's state + */ + MeasureType(int byteSize, @Nullable ChannelTypeUID channelTypeUID, StateConverter stateConverter) { + this.byteSize = byteSize; + this.channelTypeUID = channelTypeUID; + this.stateConverter = stateConverter; + } + + public int getByteSize() { + return byteSize; + } + + public @Nullable ChannelTypeUID getChannelTypeId() { + return channelTypeUID; + } + + public @Nullable State toState(byte[] data, int offset, ConversionContext context) { + return stateConverter.toState(data, offset, context); + } + + private interface StateConverter { + @Nullable + State toState(byte[] data, int offset, ConversionContext context); + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Sensor.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Sensor.java new file mode 100644 index 000000000..4392d6b05 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/Sensor.java @@ -0,0 +1,57 @@ +/** + * 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.domain.response.BatteryStatus.Type.LEVEL; +import static org.openhab.binding.fineoffsetweatherstation.internal.domain.response.BatteryStatus.Type.LEVEL_OR_DC; +import static org.openhab.binding.fineoffsetweatherstation.internal.domain.response.BatteryStatus.Type.LOW_HIGH; +import static org.openhab.binding.fineoffsetweatherstation.internal.domain.response.BatteryStatus.Type.VOLTAGE_BROAD_STEPS; +import static org.openhab.binding.fineoffsetweatherstation.internal.domain.response.BatteryStatus.Type.VOLTAGE_FINE_STEPS; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.BatteryStatus; + +/** + * The Sensors supported by the gateway. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public enum Sensor { + WH24(LOW_HIGH), + WH25(LOW_HIGH), + WH26(LOW_HIGH), + WH31(LOW_HIGH), + WH34(VOLTAGE_FINE_STEPS), + WH35(VOLTAGE_FINE_STEPS), + WH40(VOLTAGE_BROAD_STEPS), + WH41(LEVEL_OR_DC), + WH45(LEVEL_OR_DC), + WH51(VOLTAGE_BROAD_STEPS), + WH55(LEVEL), + WH57(LEVEL), + WH65(LOW_HIGH), + WH68(VOLTAGE_FINE_STEPS), + WH80(VOLTAGE_FINE_STEPS), + WH90(VOLTAGE_FINE_STEPS); + + private final BatteryStatus.Type batteryStatusTpe; + + Sensor(BatteryStatus.Type batteryStatusTpe) { + this.batteryStatusTpe = batteryStatusTpe; + } + + public BatteryStatus getBatteryStatus(byte data) { + return new BatteryStatus(batteryStatusTpe, data); + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/SensorGatewayBinding.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/SensorGatewayBinding.java new file mode 100644 index 000000000..380c4ab66 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/SensorGatewayBinding.java @@ -0,0 +1,124 @@ +/** + * 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.util.ArrayList; +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.BatteryStatus; + +/** + * The binding of a sensor to the gateway. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public enum SensorGatewayBinding { + /** + * wh24 + wh65 share the same id, they are distinguished by user set flag, see also {@link Command#CMD_READ_SSSS} + */ + WH24((byte) 0, Sensor.WH24, null), + WH65((byte) 0, Sensor.WH65, null), + // also wh69 + WH68((byte) 1, Sensor.WH68, null), + WH80((byte) 2, Sensor.WH80, null), + WH40((byte) 3, Sensor.WH40, null), + WH25((byte) 4, Sensor.WH25, null), + WH26((byte) 5, Sensor.WH26, null), + WH31_CH1((byte) 6, Sensor.WH31, 1), + WH31_CH2((byte) 7, Sensor.WH31, 2), + WH31_CH3((byte) 8, Sensor.WH31, 3), + WH31_CH4((byte) 9, Sensor.WH31, 4), + WH31_CH5((byte) 10, Sensor.WH31, 5), + WH31_CH6((byte) 11, Sensor.WH31, 6), + WH31_CH7((byte) 12, Sensor.WH31, 7), + WH31_CH8((byte) 13, Sensor.WH31, 8), + WH51_CH1((byte) 14, Sensor.WH51, 1), + WH51_CH2((byte) 15, Sensor.WH51, 2), + WH51_CH3((byte) 16, Sensor.WH51, 3), + WH51_CH4((byte) 17, Sensor.WH51, 4), + WH51_CH5((byte) 18, Sensor.WH51, 5), + WH51_CH6((byte) 19, Sensor.WH51, 6), + WH51_CH7((byte) 20, Sensor.WH51, 7), + WH51_CH8((byte) 21, Sensor.WH51, 8), + WH41_CH1((byte) 22, Sensor.WH41, 1), + WH41_CH2((byte) 23, Sensor.WH41, 2), + WH41_CH3((byte) 24, Sensor.WH41, 3), + WH41_CH4((byte) 25, Sensor.WH41, 4), + WH57((byte) 26, Sensor.WH57, null), + WH55_CH1((byte) 27, Sensor.WH55, 1), + WH55_CH2((byte) 28, Sensor.WH55, 2), + WH55_CH3((byte) 29, Sensor.WH55, 3), + WH55_CH4((byte) 30, Sensor.WH55, 4), + WH34_CH1((byte) 31, Sensor.WH34, 1), + WH34_CH2((byte) 32, Sensor.WH34, 2), + WH34_CH3((byte) 33, Sensor.WH34, 3), + WH34_CH4((byte) 34, Sensor.WH34, 4), + WH34_CH5((byte) 35, Sensor.WH34, 5), + WH34_CH6((byte) 36, Sensor.WH34, 6), + WH34_CH7((byte) 37, Sensor.WH34, 7), + WH34_CH8((byte) 38, Sensor.WH34, 8), + WH45((byte) 39, Sensor.WH45, null), + WH35_CH1((byte) 40, Sensor.WH35, 1), + WH35_CH2((byte) 41, Sensor.WH35, 2), + WH35_CH3((byte) 42, Sensor.WH35, 3), + WH35_CH4((byte) 43, Sensor.WH35, 4), + WH35_CH5((byte) 44, Sensor.WH35, 5), + WH35_CH6((byte) 45, Sensor.WH35, 6), + WH35_CH7((byte) 46, Sensor.WH35, 7), + WH35_CH8((byte) 47, Sensor.WH35, 8), + WH90((byte) 48, Sensor.WH90, null); + + private static final Map> SENSOR_LOOKUP = new HashMap<>(); + + static { + for (SensorGatewayBinding sensorGatewayBinding : values()) { + List bindings = SENSOR_LOOKUP.computeIfAbsent(sensorGatewayBinding.id, + ArrayList::new); + // noinspection ConstantConditions + if (bindings != null) { + bindings.add(sensorGatewayBinding); + } + } + } + + private final byte id; + private final Sensor sensor; + private final @Nullable Integer channel; + + SensorGatewayBinding(byte id, Sensor sensor, @Nullable Integer channel) { + this.id = id; + this.sensor = sensor; + this.channel = channel; + } + + public static @Nullable List forIndex(byte idx) { + return SENSOR_LOOKUP.get(idx); + } + + public BatteryStatus getBatteryStatus(byte data) { + return sensor.getBatteryStatus(data); + } + + public Sensor getSensor() { + return sensor; + } + + public @Nullable Integer getChannel() { + return channel; + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/BatteryStatus.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/BatteryStatus.java new file mode 100644 index 000000000..97ab2c81c --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/BatteryStatus.java @@ -0,0 +1,142 @@ +/** + * 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.response; + +import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt8; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The status of the sensors' battery. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class BatteryStatus { + + public enum Type { + /** + * 1: BATT low, 0: normal + */ + LOW_HIGH, + + /** + * level0~5,<=1 for BATT low + */ + LEVEL, + + /** + * level0~6,<=1 for BATT low, 6 = dc power supply + */ + LEVEL_OR_DC, + + /** + * val * 0.1v + */ + VOLTAGE_BROAD_STEPS, + + /** + * val*0.02V if v<=1.2V BATT low + */ + VOLTAGE_FINE_STEPS + } + + private @Nullable Integer level; + private @Nullable Double voltage; + private final boolean low; + private boolean dc; + + public BatteryStatus(Type type, byte data) { + int value = toUInt8(data); + double voltage; + switch (type) { + case LOW_HIGH: + low = value == 1; + break; + case LEVEL: + level = value; + low = value <= 1; + break; + case LEVEL_OR_DC: + dc = value == 6; + level = value; + low = value <= 1; + break; + case VOLTAGE_BROAD_STEPS: + this.voltage = voltage = value * 0.1; + low = voltage <= 1.2; + break; + case VOLTAGE_FINE_STEPS: + this.voltage = voltage = value * 0.02; + low = voltage <= 1.2; + break; + default: + throw new IllegalArgumentException("Unsupported type " + type); + } + } + + /** + * @return level 0 - 5 or null f not available + */ + public @Nullable Integer getLevel() { + return level; + } + + /** + * @return voltage of the battery or null if not available + */ + public @Nullable Double getVoltage() { + return voltage; + } + + /** + * @return true, if the battery is low + */ + public boolean isLow() { + return low; + } + + /** + * @return true, if device is DC connected + */ + public boolean isDc() { + return dc; + } + + public @Nullable Integer getPercentage() { + if (dc) { + return 100; + } + Integer currentLevel = level; + if (currentLevel != null) { + return (currentLevel * 100 / 5); + } + return null; + } + + @Override + public String toString() { + String status = low ? "LOW" : "OK"; + if (dc) { + return "DC connected"; + } + if (voltage != null) { + return "Battery " + voltage + " V " + status; + } + if (level != null) { + return "Battery " + level + "/ 5" + " " + status; + } + return "Battery " + status; + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/MeasuredValue.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/MeasuredValue.java new file mode 100644 index 000000000..ba42b3c0a --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/MeasuredValue.java @@ -0,0 +1,68 @@ +/** + * 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.response; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.MeasureType; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.State; + +/** + * A certain measured value. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class MeasuredValue { + private final MeasureType measureType; + private final String channelId; + private final @Nullable ChannelTypeUID channelTypeUID; + private final State state; + private final String debugName; + + public MeasuredValue(MeasureType measureType, String channelId, @Nullable ChannelTypeUID channelTypeUID, + State state, String debugName) { + this.measureType = measureType; + this.channelId = channelId; + this.channelTypeUID = channelTypeUID; + this.state = state; + this.debugName = debugName; + } + + public MeasureType getMeasureType() { + return measureType; + } + + public String getChannelId() { + return channelId; + } + + public @Nullable ChannelTypeUID getChannelTypeUID() { + return channelTypeUID; + } + + public State getState() { + return state; + } + + public String getDebugName() { + return debugName; + } + + @Override + public String toString() { + return "MeasuredValue{" + "measureType=" + measureType + ", channelId='" + channelId + '\'' + + ", channelTypeUID=" + channelTypeUID + ", state=" + state + ", debugName='" + debugName + '\'' + '}'; + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/SensorDevice.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/SensorDevice.java new file mode 100644 index 000000000..e593084be --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/SensorDevice.java @@ -0,0 +1,58 @@ +/** + * 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.response; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.SensorGatewayBinding; + +/** + * HHolds all available information of a sensor device. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class SensorDevice { + private final int id; + private final SensorGatewayBinding sensorGatewayBinding; + private final BatteryStatus batteryStatus; + private final int signal; + + public SensorDevice(int id, SensorGatewayBinding sensorGatewayBinding, BatteryStatus batteryStatus, int signal) { + this.id = id; + this.sensorGatewayBinding = sensorGatewayBinding; + this.batteryStatus = batteryStatus; + this.signal = signal; + } + + public int getId() { + return id; + } + + public SensorGatewayBinding getSensorGatewayBinding() { + return sensorGatewayBinding; + } + + public BatteryStatus getBatteryStatus() { + return batteryStatus; + } + + public int getSignal() { + return signal; + } + + @Override + public String toString() { + return "SensorDevice{" + "id=" + id + ", sensor=" + sensorGatewayBinding + ", batteryStatus=" + batteryStatus + + ", signal=" + signal + '}'; + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/SystemInfo.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/SystemInfo.java new file mode 100644 index 000000000..8b326b5dc --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/domain/response/SystemInfo.java @@ -0,0 +1,66 @@ +/** + * 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.response; + +import java.time.LocalDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Information about the gateway + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class SystemInfo { + /** + * in MHz + */ + private final @Nullable Integer frequency; + private final LocalDateTime dateTime; + /** + * Daylight saving time + */ + private final boolean dst; + private final boolean useWh24; + + public SystemInfo(@Nullable Integer frequency, LocalDateTime dateTime, boolean dst, boolean useWh24) { + this.frequency = frequency; + this.dateTime = dateTime; + this.dst = dst; + this.useWh24 = useWh24; + } + + public @Nullable Integer getFrequency() { + return frequency; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public boolean isDst() { + return dst; + } + + public boolean isUseWh24() { + return useWh24; + } + + @Override + public String toString() { + return "SystemInfo{" + "frequency=" + frequency + " MHz" + ", dateTime=" + dateTime + ", dst=" + dst + + ", useWh24=" + useWh24 + '}'; + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/handler/FineOffsetGatewayHandler.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/handler/FineOffsetGatewayHandler.java new file mode 100644 index 000000000..1d37544f0 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/handler/FineOffsetGatewayHandler.java @@ -0,0 +1,291 @@ +/** + * 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.handler; + +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.THING_TYPE_GATEWAY; +import static org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants.THING_TYPE_SENSOR; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +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.FineOffsetSensorConfiguration; +import org.openhab.binding.fineoffsetweatherstation.internal.FineOffsetWeatherStationBindingConstants; +import org.openhab.binding.fineoffsetweatherstation.internal.discovery.FineOffsetGatewayDiscoveryService; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext; +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.service.FineOffsetGatewayQueryService; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.builder.BridgeBuilder; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.type.ChannelKind; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeRegistry; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link FineOffsetGatewayHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class FineOffsetGatewayHandler extends BaseBridgeHandler { + + private static final String PROPERTY_FREQUENCY = "frequency"; + + private final Logger logger = LoggerFactory.getLogger(FineOffsetGatewayHandler.class); + private final Bundle bundle; + private final ConversionContext conversionContext; + + private @Nullable FineOffsetGatewayQueryService gatewayQueryService; + + private final FineOffsetGatewayDiscoveryService gatewayDiscoveryService; + private final ChannelTypeRegistry channelTypeRegistry; + private final TranslationProvider translationProvider; + private final LocaleProvider localeProvider; + + private final ThingUID bridgeUID; + + private @Nullable Map sensorDeviceMap; + private @Nullable ScheduledFuture pollingJob; + private @Nullable ScheduledFuture discoverJob; + private boolean disposed; + + public FineOffsetGatewayHandler(Bridge bridge, FineOffsetGatewayDiscoveryService gatewayDiscoveryService, + ChannelTypeRegistry channelTypeRegistry, TranslationProvider translationProvider, + LocaleProvider localeProvider, TimeZoneProvider timeZoneProvider) { + super(bridge); + bridgeUID = bridge.getUID(); + this.gatewayDiscoveryService = gatewayDiscoveryService; + this.channelTypeRegistry = channelTypeRegistry; + this.translationProvider = translationProvider; + this.localeProvider = localeProvider; + this.bundle = FrameworkUtil.getBundle(FineOffsetGatewayDiscoveryService.class); + this.conversionContext = new ConversionContext(timeZoneProvider.getTimeZone()); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } + + @Override + public void initialize() { + FineOffsetGatewayConfiguration config = getConfigAs(FineOffsetGatewayConfiguration.class); + gatewayQueryService = new FineOffsetGatewayQueryService(config, this::updateStatus, conversionContext); + + updateStatus(ThingStatus.UNKNOWN); + fetchAndUpdateSensors(); + disposed = false; + updateBridgeInfo(); + startDiscoverJob(); + startPollingJob(); + } + + private void fetchAndUpdateSensors() { + @Nullable + Map deviceMap = query(FineOffsetGatewayQueryService::getRegisteredSensors); + sensorDeviceMap = deviceMap; + updateSensors(); + if (deviceMap != null) { + gatewayDiscoveryService.addSensors(bridgeUID, deviceMap.values()); + } + } + + private void updateSensors() { + ((Bridge) thing).getThings().forEach(this::updateSensorThing); + } + + private void updateSensorThing(Thing thing) { + Map sensorMap = sensorDeviceMap; + if (!THING_TYPE_SENSOR.equals(thing.getThingTypeUID()) || sensorMap == null) { + return; + } + SensorGatewayBinding sensor = thing.getConfiguration().as(FineOffsetSensorConfiguration.class).sensor; + Optional.ofNullable(thing.getHandler()).filter(FineOffsetSensorHandler.class::isInstance) + .map(FineOffsetSensorHandler.class::cast) + .ifPresent(sensorHandler -> sensorHandler.updateSensorState(sensorMap.get(sensor))); + } + + @Override + public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) { + updateSensorThing(childThing); + } + + private void updateLiveData() { + if (disposed) { + return; + } + List data = query(FineOffsetGatewayQueryService::getLiveData); + if (data == null) { + return; + } + + List channels = new ArrayList<>(); + for (MeasuredValue measuredValue : data) { + @Nullable + Channel channel = thing.getChannel(measuredValue.getChannelId()); + if (channel == null) { + channel = createChannel(measuredValue); + if (channel != null) { + channels.add(channel); + } + } else { + State state = measuredValue.getState(); + updateState(channel.getUID(), state); + } + } + if (!channels.isEmpty()) { + updateThing(editThing().withChannels(channels).build()); + } + } + + private @Nullable Channel createChannel(MeasuredValue measuredValue) { + ChannelTypeUID channelTypeId = measuredValue.getChannelTypeUID(); + if (channelTypeId == null) { + logger.debug("cannot create channel for {}", measuredValue.getDebugName()); + return null; + } + ChannelBuilder builder = ChannelBuilder.create(new ChannelUID(thing.getUID(), measuredValue.getChannelId())) + .withKind(ChannelKind.STATE).withType(channelTypeId); + String channelKey = "thing-type." + FineOffsetWeatherStationBindingConstants.BINDING_ID + "." + + THING_TYPE_GATEWAY.getId() + ".channel." + measuredValue.getChannelId(); + String label = translationProvider.getText(bundle, channelKey + ".label", measuredValue.getDebugName(), + localeProvider.getLocale()); + if (label != null) { + builder.withLabel(label); + } + String description = translationProvider.getText(bundle, channelKey + ".description", null, + localeProvider.getLocale()); + if (description != null) { + builder.withDescription(description); + } + @Nullable + ChannelType type = channelTypeRegistry.getChannelType(channelTypeId); + if (type != null) { + builder.withAcceptedItemType(type.getItemType()); + } + return builder.build(); + } + + private void updateBridgeInfo() { + @Nullable + String firmware = query(FineOffsetGatewayQueryService::getFirmwareVersion); + Map properties = new HashMap<>(thing.getProperties()); + if (firmware != null) { + var fwString = firmware.split("_V"); + if (fwString.length > 1) { + properties.put(Thing.PROPERTY_MODEL_ID, fwString[0]); + properties.put(Thing.PROPERTY_FIRMWARE_VERSION, fwString[1]); + } + } + + SystemInfo systemInfo = query(FineOffsetGatewayQueryService::fetchSystemInfo); + if (systemInfo != null && systemInfo.getFrequency() != null) { + properties.put(PROPERTY_FREQUENCY, systemInfo.getFrequency() + " MHz"); + } + if (!thing.getProperties().equals(properties)) { + BridgeBuilder bridge = editThing(); + bridge.withProperties(properties); + updateThing(bridge.build()); + } + } + + private void startDiscoverJob() { + ScheduledFuture job = discoverJob; + if (job == null || job.isCancelled()) { + int discoverInterval = thing.getConfiguration().as(FineOffsetGatewayConfiguration.class).discoverInterval; + discoverJob = scheduler.scheduleWithFixedDelay(this::fetchAndUpdateSensors, 0, discoverInterval, + TimeUnit.SECONDS); + } + } + + private void stopDiscoverJob() { + ScheduledFuture job = this.discoverJob; + if (job != null) { + job.cancel(true); + } + this.discoverJob = null; + } + + private void startPollingJob() { + ScheduledFuture job = pollingJob; + if (job == null || job.isCancelled()) { + int pollingInterval = thing.getConfiguration().as(FineOffsetGatewayConfiguration.class).pollingInterval; + pollingJob = scheduler.scheduleWithFixedDelay(this::updateLiveData, 5, pollingInterval, TimeUnit.SECONDS); + } + } + + private void stopPollingJob() { + ScheduledFuture job = this.pollingJob; + if (job != null) { + job.cancel(true); + } + this.pollingJob = null; + } + + private @Nullable T query(Function delegate) { + @Nullable + FineOffsetGatewayQueryService queryService = this.gatewayQueryService; + if (queryService == null) { + return null; + } + return delegate.apply(queryService); + } + + @Override + public void dispose() { + disposed = true; + @Nullable + FineOffsetGatewayQueryService queryService = this.gatewayQueryService; + if (queryService != null) { + try { + queryService.close(); + } catch (IOException e) { + logger.debug("failed to close queryService", e); + } + } + this.gatewayQueryService = null; + this.sensorDeviceMap = null; + stopPollingJob(); + stopDiscoverJob(); + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/handler/FineOffsetSensorHandler.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/handler/FineOffsetSensorHandler.java new file mode 100644 index 000000000..b81894314 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/handler/FineOffsetSensorHandler.java @@ -0,0 +1,88 @@ +/** + * 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.handler; + +import java.math.BigDecimal; + +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.SensorDevice; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; + +/** + * The {@link FineOffsetSensorHandler} keeps track of the signal and battery of the sensor attached to the gateway. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class FineOffsetSensorHandler extends BaseThingHandler { + private boolean disposed; + + public FineOffsetSensorHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } + + @Override + public void initialize() { + updateStatus(ThingStatus.ONLINE); + disposed = false; + } + + @Override + public void dispose() { + disposed = true; + } + + public void updateSensorState(@Nullable SensorDevice sensorDevice) { + if (disposed) { + return; + } + if (sensorDevice == null) { + updateStatus(ThingStatus.OFFLINE); + return; + } + if (sensorDevice.getSignal() == 0) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + } else { + updateStatus(ThingStatus.ONLINE); + } + updateState(FineOffsetWeatherStationBindingConstants.SENSOR_CHANNEL_SIGNAL, + new DecimalType(sensorDevice.getSignal())); + updateState(FineOffsetWeatherStationBindingConstants.SENSOR_CHANNEL_LOW_BATTERY, + sensorDevice.getBatteryStatus().isLow() ? OnOffType.ON : OnOffType.OFF); + Integer percentage = sensorDevice.getBatteryStatus().getPercentage(); + if (percentage != null) { + updateState(FineOffsetWeatherStationBindingConstants.SENSOR_CHANNEL_BATTERY_LEVEL, + new DecimalType(new BigDecimal(percentage))); + } else { + @Nullable + Channel channel = thing.getChannel(FineOffsetWeatherStationBindingConstants.SENSOR_CHANNEL_BATTERY_LEVEL); + if (channel != null) { + updateThing(editThing().withoutChannels(channel).build()); + } + } + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/handler/ThingStatusListener.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/handler/ThingStatusListener.java new file mode 100644 index 000000000..039bbea14 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/handler/ThingStatusListener.java @@ -0,0 +1,27 @@ +/** + * 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.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; + +/** + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public interface ThingStatusListener { + + void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description); +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParser.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParser.java new file mode 100644 index 000000000..5bcfcd8b7 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParser.java @@ -0,0 +1,181 @@ +/** + * 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 static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt16; +import static org.openhab.binding.fineoffsetweatherstation.internal.Utils.toUInt32; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +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.domain.ConversionContext; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.Measurand; +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.MeasuredValue; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SensorDevice; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.SystemInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class to Convert the protocol data + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class FineOffsetDataParser { + private final Logger logger = LoggerFactory.getLogger(FineOffsetDataParser.class); + + public @Nullable String getFirmwareVersion(byte[] data) { + if (data.length > 0) { + return new String(data, 5, data[4]); + } + return null; + } + + public Map getRegisteredSensors(byte[] data, + Supplier<@Nullable Boolean> isUseWh24) { + /* + * Pos | Length | Description + * ------------------------------------------------- + * 0 | 2 | fixed header (0xffff) + * 2 | 1 | command (0x3c) + * 3 | 2 | size + * ------------------------------------------------- + * (n * 7) + 5 | 1 | index of sensor n + * (n * 7) + 6 | 4 | id of sensor n + * (n * 7) + 10 | 1 | battery status of sensor n + * (n * 7) + 11 | 1 | signal of sensor n + * ------------------------------------------------- + * (n * 7) + 12 | 1 | checksum + */ + + Map result = new HashMap<>(); + var len = toUInt16(data, 3); + int entry = 0; + int entrySize = 7; + while (entry * entrySize + 11 <= len) { + int idx = entry++ * entrySize + 5; + int id = toUInt32(data, idx + 1); + List sensorCandidates = SensorGatewayBinding.forIndex(data[idx]); + if (sensorCandidates == null || sensorCandidates.isEmpty()) { + logger.debug("unknown sensor (id={}) for index {}", id, data[idx]); + continue; + } + SensorGatewayBinding sensorGatewayBinding = null; + if (sensorCandidates.size() == 1) { + sensorGatewayBinding = sensorCandidates.get(0); + } else if (sensorCandidates.size() == 2 && data[idx] == 0) { + sensorGatewayBinding = Boolean.TRUE.equals(isUseWh24.get()) ? SensorGatewayBinding.WH24 + : SensorGatewayBinding.WH65; + } + if (sensorGatewayBinding == null) { + logger.debug("too many sensor candidates for (id={}) and index {}: {}", id, data[idx], + sensorCandidates); + continue; + } + switch (id) { + case 0xFFFFFFFE: + logger.trace("sensor {} = disabled", sensorGatewayBinding); + continue; + case 0xFFFFFFFF: + logger.trace("sensor {} = registering", sensorGatewayBinding); + continue; + } + + BatteryStatus batteryStatus = sensorGatewayBinding.getBatteryStatus(data[idx + 5]); + int signal = Utils.toUInt8(data[idx + 6]); + + result.put(sensorGatewayBinding, new SensorDevice(id, sensorGatewayBinding, batteryStatus, signal)); + } + return result; + } + + public @Nullable SystemInfo fetchSystemInfo(byte[] data) { + // expected response + // 0 - 0xff - header + // 1 - 0xff - header + // 2 - 0x30 - system info + // 3 - 0x?? - size of response + // 4 - frequency - 0=433, 1=868MHz, 2=915MHz, 3=920MHz + // 5 - sensor type - 0=WH24, 1=WH65 + // 6-9 - UTC time + // 10 - time zone index (?) + // 11 - DST 0-1 - false/true + // 12 - 0x?? - checksum + Integer frequency = null; + switch (data[4]) { + case 0: + frequency = 433; + break; + case 1: + frequency = 868; + break; + case 2: + frequency = 915; + break; + case 3: + frequency = 920; + break; + + } + boolean useWh24 = data[5] == 0; + var unix = toUInt32(data, 6); + var date = LocalDateTime.ofEpochSecond(unix, 0, ZoneOffset.UTC); + var dst = data[11] != 0; + return new SystemInfo(frequency, date, dst, useWh24); + } + + List getLiveData(byte[] data, ConversionContext context) { + /* + * Pos| Length | Description + * ------------------------------------------------- + * 0 | 2 | fixed header (0xffff) + * 2 | 1 | command (0x27) + * 3 | 2 | size + * ------------------------------------------------- + * 5 | 1 | code of item (item defines n) + * 6 | n | value of item + * ------------------------------------------------- + * 6 + n | 1 | code of item (item defines m) + * 7 + n | m | value of item + * ------------------------------------------------- + * ... + * ------------------------------------------------- + * + * | 1 | checksum + */ + var idx = 5; + var size = toUInt16(data, 3); + List result = new ArrayList<>(); + while (idx < size) { + byte code = data[idx++]; + Measurand measurand = Measurand.getByCode(code); + if (measurand == null) { + logger.warn("failed to get measurand 0x{}", Integer.toHexString(code)); + return result; + } + idx += measurand.extractMeasuredValues(data, idx, context, result); + } + return result; + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetGatewayQueryService.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetGatewayQueryService.java new file mode 100644 index 000000000..32950d16b --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetGatewayQueryService.java @@ -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.service; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.util.Arrays; +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.Utils; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.Command; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext; +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; + +/** + * Service to query the gateway device. + * + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +public class FineOffsetGatewayQueryService implements AutoCloseable { + 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 ConversionContext conversionContext; + + public FineOffsetGatewayQueryService(FineOffsetGatewayConfiguration config, ThingStatusListener thingStatusListener, + ConversionContext conversionContext) { + this.config = config; + this.thingStatusListener = thingStatusListener; + this.fineOffsetDataParser = new FineOffsetDataParser(); + this.conversionContext = conversionContext; + } + + public @Nullable String getFirmwareVersion() { + var data = executeCommand(Command.CMD_READ_FIRMWARE_VERSION); + if (null != data) { + return fineOffsetDataParser.getFirmwareVersion(data); + } + return null; + } + + public Map getRegisteredSensors() { + var data = executeCommand(Command.CMD_READ_SENSOR_ID_NEW); + if (null == data) { + return Map.of(); + } + return fineOffsetDataParser.getRegisteredSensors(data, () -> { + @Nullable + SystemInfo systemInfo = fetchSystemInfo(); + if (systemInfo != null) { + return systemInfo.isUseWh24(); + } + return null; + }); + } + + public @Nullable SystemInfo fetchSystemInfo() { + var data = executeCommand(Command.CMD_READ_SSSS); + if (data == null) { + logger.debug("Unexpected response to System Info!"); + return null; + } + return fineOffsetDataParser.fetchSystemInfo(data); + } + + public List getLiveData() { + byte[] data = executeCommand(Command.CMD_GW1000_LIVEDATA); + if (data == null) { + return Collections.emptyList(); + } + return fineOffsetDataParser.getLiveData(data, conversionContext); + } + + private synchronized byte @Nullable [] executeCommand(Command command) { + byte[] buffer = new byte[2028]; + 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(); + } + } +} diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 000000000..09d1eb7ea --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,9 @@ + + + + Fine Offset Weather Station + A binding for the Weather Stations Manufactured by Fine Offset + + diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/config/config-descriptions.xml b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/config/config-descriptions.xml new file mode 100644 index 000000000..72f0ba4c6 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/config/config-descriptions.xml @@ -0,0 +1,40 @@ + + + + + + + The Hostname or IP address of the device + network-address + + + + The network port of the gateway + 45000 + + + + Polling interval for refreshing the data in seconds + 16 + s + true + + + + Interval in seconds to fetch registered sensors, battery status and signal strength + s + 900 + true + + + + + + + + + diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/i18n/fineoffsetweatherstation.properties b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/i18n/fineoffsetweatherstation.properties new file mode 100644 index 000000000..46b30c518 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/i18n/fineoffsetweatherstation.properties @@ -0,0 +1,187 @@ +# binding + +binding.fineoffsetweatherstation.name = Fine Offset Weather Station +binding.fineoffsetweatherstation.description = A binding for the Weather Stations Manufactured by Fine Offset + +# thing types + +thing-type.fineoffsetweatherstation.gateway.label = Gateway Device +thing-type.fineoffsetweatherstation.gateway.description = A WiFi connected gateway device (WN1900, GW1000, GW1100, WH2680, WH2650) to bridge Sensors +thing-type.fineoffsetweatherstation.sensor.label = Sensor Device +thing-type.fineoffsetweatherstation.sensor.description = A Sensor connected to the gateway (WN1900, GW1000, GW1100, WH2680, WH2650) + +# thing types config + +thing-type.config.fineoffsetweatherstation.gateway.discoverInterval.label = Discover Interval +thing-type.config.fineoffsetweatherstation.gateway.discoverInterval.description = Interval in seconds to fetch registered sensors, battery status and signal strength +thing-type.config.fineoffsetweatherstation.gateway.ip.label = IP Address +thing-type.config.fineoffsetweatherstation.gateway.ip.description = The Hostname or IP address of the device +thing-type.config.fineoffsetweatherstation.gateway.pollingInterval.label = Polling Interval +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.description = The network port of the gateway +thing-type.config.fineoffsetweatherstation.sensor.sensor.label = Sensor + +# channel types + +channel-type.fineoffsetweatherstation.co2.label = COâ‚‚ +channel-type.fineoffsetweatherstation.co2.description = Air quality indicator +channel-type.fineoffsetweatherstation.humidity.label = Humidity +channel-type.fineoffsetweatherstation.illumination.label = Illumination +channel-type.fineoffsetweatherstation.lightning-counter.label = Lightning Counter +channel-type.fineoffsetweatherstation.lightning-distance.label = Lightning Distance +channel-type.fineoffsetweatherstation.lightning-time.label = Lightning Time +channel-type.fineoffsetweatherstation.max-wind-speed.label = Maximum Wind Speed +channel-type.fineoffsetweatherstation.moisture.label = Moisture +channel-type.fineoffsetweatherstation.pm10.label = PM10 Air Quality +channel-type.fineoffsetweatherstation.pm25.label = PM2.5 Air Quality +channel-type.fineoffsetweatherstation.pressure.label = Pressure +channel-type.fineoffsetweatherstation.rain-rate.label = Rain Rate +channel-type.fineoffsetweatherstation.rain.label = Rain +channel-type.fineoffsetweatherstation.temperature.label = Temperature +channel-type.fineoffsetweatherstation.uv-index.label = UV-Index +channel-type.fineoffsetweatherstation.uv-radiation.label = UV-Irradiation +channel-type.fineoffsetweatherstation.water-leak-detection.label = Water Leak Detection + +channel = Channel +thing.gateway.label = Weather Station +thing.sensor.WH24.label = Weather Station - Outdoor Unit +thing.sensor.WH24.description = Sensor for Wind Speed & Direction, Solar Radiation & Light, Temperature, Humidity, Rainfall +thing.sensor.WH25.label = Sensor for Temperature, Humidity and Pressure +thing.sensor.WH25.description = Multi-Channel Sensor for Temperature, Humidity and pressure +thing.sensor.WH26.label = Sensor for Temperature and Humidity +thing.sensor.WH26.description = Multi-Channel Sensor for Temperature and Humidity +thing.sensor.WH31.label = Sensor for Temperature and Humidity +thing.sensor.WH31.description = Multi-Channel Sensor for Temperature and Humidity +thing.sensor.WH34.label = Temperature Sensor with external probe +thing.sensor.WH35.label = Leaf Wetness Sensor +thing.sensor.WH40.label = Rainfall Sensor +thing.sensor.WH41.label = Air Quality Sensor - outdoor +thing.sensor.WH41.description = An outdoor PM2.5 Air Quality Sensor +thing.sensor.WH45.label = Air Quality Sensor +thing.sensor.WH45.description = 5-in-1 Air Quality Sensor for CO2, PM2.5, PM10, Temperature and Humidity +thing.sensor.WH51.label = Soil Moisture Sensor +thing.sensor.WH55.label = Water Leak Detection Sensor +thing.sensor.WH57.label = Lightning Detection Sensor +thing.sensor.WH65.label = Weather Station - outdoor unit +thing.sensor.WH65.description = Sensor for Wind Speed & Direction, Solar Radiation & Light, Temperature, Humidity and Rainfall +thing.sensor.WH68.label = Weather Station - outdoor unit +thing.sensor.WH68.description = Solar-powered Sensor for Wind Speed & Direction, Solar Radiation & Light +thing.sensor.WH80.label = Weather Station - outdoor unit +thing.sensor.WH80.description = Ultrasonic Sensor for Wind Speed & Direction, Solar Radiation & Light, Temperature & Humidity +thing.sensor.WH90.label = Weather Station - outdoor unit + +# channels + +thing-type.fineoffsetweatherstation.gateway.channel.temperature-indoor.label = Indoor Temperature +thing-type.fineoffsetweatherstation.gateway.channel.temperature-outdoor.label = Outdoor Temperature +thing-type.fineoffsetweatherstation.gateway.channel.temperature-dew-point.label = Dew Point +thing-type.fineoffsetweatherstation.gateway.channel.temperature-wind-chill.label = Perceived Temperature +thing-type.fineoffsetweatherstation.gateway.channel.temperature-heat-index.label = Heat Index +thing-type.fineoffsetweatherstation.gateway.channel.humidity-indoor.label = Humidity Inside +thing-type.fineoffsetweatherstation.gateway.channel.humidity-outdoor.label = Humidity Outside +thing-type.fineoffsetweatherstation.gateway.channel.pressure-absolute.label = Absolute Pressure +thing-type.fineoffsetweatherstation.gateway.channel.pressure-relative.label = Relative Pressure +thing-type.fineoffsetweatherstation.gateway.channel.direction-wind.label = Wind Direction +thing-type.fineoffsetweatherstation.gateway.channel.speed-wind.label = Wind Speed +thing-type.fineoffsetweatherstation.gateway.channel.speed-gust.label = Gust Speed +thing-type.fineoffsetweatherstation.gateway.channel.rain-event.label = Amount of Rainfall At the last Rain +thing-type.fineoffsetweatherstation.gateway.channel.rain-rate.label = Rainfall Rate +thing-type.fineoffsetweatherstation.gateway.channel.rain-hour.label = Rainfall Current Hour +thing-type.fineoffsetweatherstation.gateway.channel.rain-day.label = Rainfall Today +thing-type.fineoffsetweatherstation.gateway.channel.rain-week.label = Rainfall this Week +thing-type.fineoffsetweatherstation.gateway.channel.rain-month.label = Rainfall this Month +thing-type.fineoffsetweatherstation.gateway.channel.rain-year.label = Rainfall this Year +thing-type.fineoffsetweatherstation.gateway.channel.rain-total.label = Rainfall Total +thing-type.fineoffsetweatherstation.gateway.channel.illumination.label = Light Intensity +thing-type.fineoffsetweatherstation.gateway.channel.irradiation-uv.label = UV Irradiation +thing-type.fineoffsetweatherstation.gateway.channel.uv-index.label = UV Index +thing-type.fineoffsetweatherstation.gateway.channel.time.label = Date and Time +thing-type.fineoffsetweatherstation.gateway.channel.wind-max-day.label = Maximum Wind Speed Today +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-1.label = Temperature Channel 1 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-2.label = Temperature Channel 2 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-3.label = Temperature Channel 3 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-4.label = Temperature Channel 4 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-5.label = Temperature Channel 5 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-6.label = Temperature Channel 6 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-7.label = Temperature Channel 7 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-8.label = Temperature Channel 8 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-1.label = Humidity Channel 1 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-2.label = Humidity Channel 2 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-3.label = Humidity Channel 3 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-4.label = Humidity Channel 4 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-5.label = Humidity Channel 5 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-6.label = Humidity Channel 6 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-7.label = Humidity Channel 7 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-8.label = Humidity Channel 8 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-1.label = Soil Temperature Channel 1 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-2.label = Soil Temperature Channel 2 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-3.label = Soil Temperature Channel 3 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-4.label = Soil Temperature Channel 4 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-5.label = Soil Temperature Channel 5 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-6.label = Soil Temperature Channel 6 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-7.label = Soil Temperature Channel 7 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-8.label = Soil Temperature Channel 8 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-9.label = Soil Temperature Channel 9 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-10.label = Soil Temperature Channel 10 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-11.label = Soil Temperature Channel 11 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-12.label = Soil Temperature Channel 12 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-13.label = Soil Temperature Channel 13 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-14.label = Soil Temperature Channel 14 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-15.label = Soil Temperature Channel 15 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-16.label = Soil Temperature Channel 16 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-1.label = Soil Moisture Channel 1 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-2.label = Soil Moisture Channel 2 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-3.label = Soil Moisture Channel 3 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-4.label = soil Moisture Channel 4 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-5.label = Soil Moisture Channel 5 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-6.label = Soil Moisture Channel 6 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-7.label = Soil Moisture Channel 7 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-8.label = Soil Moisture Channel 8 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-9.label = Soil Moisture Channel 9 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-10.label = Soil Moisture Channel 10 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-11.label = Soil Moisture Channel 11 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-12.label = Soil Moisture Channel 12 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-13.label = soil Moisture Channel 13 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-14.label = Soil Moisture Channel 14 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-15.label = Soil Moisture Channel 15 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-16.label = Soil Moisture Channel 16 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-24-hour-average-channel-1.label = PM2.5 Air Quality 24 Hour Average Channel 1 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-24-hour-average-channel-2.label = PM2.5 Air Quality 24 Hour Average Channel 2 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-24-hour-average-channel-3.label = PM2.5 Air Quality 24 Hour Average Channel 3 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-24-hour-average-channel-4.label = PM2.5 Air Quality 24 Hour Average Channel 4 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-channel-1.label = PM2.5 Air Quality Channel 1 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-channel-2.label = PM2.5 Air Quality Channel 2 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-channel-3.label = PM2.5 Air Quality Channel 3 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-channel-4.label = PM2.5 Air Quality Channel 4 +thing-type.fineoffsetweatherstation.gateway.channel.water-leak-channel-1.label = Water Leak Detection Channel 1 +thing-type.fineoffsetweatherstation.gateway.channel.water-leak-channel-2.label = Water Leak Detection Channel 2 +thing-type.fineoffsetweatherstation.gateway.channel.water-leak-channel-3.label = Water Leak Detection Channel 3 +thing-type.fineoffsetweatherstation.gateway.channel.water-leak-channel-4.label = Water Leak Detection Channel 4 +thing-type.fineoffsetweatherstation.gateway.channel.lightning-distance.label = Lightning Distance +thing-type.fineoffsetweatherstation.gateway.channel.lightning-time.label = Time of last Lightning Strike +thing-type.fineoffsetweatherstation.gateway.channel.lightning-counter.label = Lightning Strikes Today +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-1.label = External Temperature Sensor Channel 1 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-2.label = External Temperature Sensor Channel 2 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-3.label = External Temperature Sensor Channel 3 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-4.label = External Temperature Sensor Channel 4 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-5.label = External Temperature Sensor Channel 5 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-6.label = External Temperature Sensor Channel 6 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-7.label = External Temperature Sensor Channel 7 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-8.label = External Temperature Sensor Channel 8 +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-temperature.label = Temperature (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-humidity.label = Humidity (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-pm10.label = PM10 Air Quality (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-pm10-24-hour-average.label = PM10 Air Quality 24 Hour Average (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-pm25.label = PM2.5 Air Quality (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-pm25-24-hour-average.label = PM2.5 Air Quality 24 Hour Average (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-co2.label = CO2 +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-co2-24-hour-average.label = CO2 24 Hour Average +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-1.label = Leaf Moisture Channel 1 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-2.label = Leaf Moisture Channel 2 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-3.label = Leaf Moisture Channel 3 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-4.label = Leaf Moisture Channel 4 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-5.label = Leaf Moisture Channel 5 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-6.label = Leaf Moisture Channel 6 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-7.label = Leaf Moisture Channel 7 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-8.label = Leaf Moisture Channel 8 diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/i18n/fineoffsetweatherstation_de.properties b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/i18n/fineoffsetweatherstation_de.properties new file mode 100644 index 000000000..0013e70e9 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/i18n/fineoffsetweatherstation_de.properties @@ -0,0 +1,183 @@ +# binding +binding.fineoffsetweatherstation.name = Fine Offset Wetterstation +binding.fineoffsetweatherstation.description = Eine Anbindung für die von Fine Offset hergestellten Wetterstationen + +# thing types +thing-type.fineoffsetweatherstation.gateway.label = Wetterstation +thing-type.fineoffsetweatherstation.gateway.description = Ein mit WiFi verbundenes Gateway-Gerät (WN1900, GW1000, GW1100, WH2680, WH2650) zur Verbindung von Sensoren +thing-type.fineoffsetweatherstation.sensor.label = Sensor +thing-type.fineoffsetweatherstation.sensor.description = Ein mit dem Gateway verbundener Sensor (WN1900, GW1000, GW1100, WH2680, WH2650) + +# thing types config +thing-type.config.fineoffsetweatherstation.gateway.discoverInterval.label = Intervall in Sekunden zum Abrufen von registrierten Sensoren, Batteriestatus und Signalstärke +thing-type.config.fineoffsetweatherstation.gateway.ip.label = IP-Adresse +thing-type.config.fineoffsetweatherstation.gateway.ip.description = Der Hostname oder die IP-Adresse des Gateways +thing-type.config.fineoffsetweatherstation.gateway.pollingInterval.label = Abfragezeitraum +thing-type.config.fineoffsetweatherstation.gateway.pollingInterval.description = Abfragezeitraum für die Auffrischung der Daten in Sekunden +thing-type.config.fineoffsetweatherstation.gateway.port.label = Port +thing-type.config.fineoffsetweatherstation.gateway.port.description = Der Netzwerk-Port des Gateways +thing-type.config.fineoffsetweatherstation.sensor.sensor.label = Sensor + +# channel types + +channel-type.fineoffsetweatherstation.co2.label = CO2 +channel-type.fineoffsetweatherstation.co2.description = Indikator für die Luftqualität +channel-type.fineoffsetweatherstation.humidity.label = Luftfeuchtigkeit +channel-type.fineoffsetweatherstation.illumination.label = Lichtstärke +channel-type.fineoffsetweatherstation.lightning-counter.label = Anzahl Blitzschläge +channel-type.fineoffsetweatherstation.lightning-distance.label = Gewitter Entfernung +channel-type.fineoffsetweatherstation.lightning-time.label = Zeitpunkt des letzten Blitzschlags +channel-type.fineoffsetweatherstation.max-wind-speed.label = Maximale Windgeschwindigkeit +channel-type.fineoffsetweatherstation.moisture.label = Feuchtigkeit +channel-type.fineoffsetweatherstation.pm10.label = PM10 Luftqualität +channel-type.fineoffsetweatherstation.pm25.label = PM2.5 Luftqualität +channel-type.fineoffsetweatherstation.pressure.label = Druck +channel-type.fineoffsetweatherstation.rain-rate.label = Niederschlagsrate +channel-type.fineoffsetweatherstation.rain.label = Niederschlagsmenge +channel-type.fineoffsetweatherstation.temperature.label = Temperatur +channel-type.fineoffsetweatherstation.uv-index.label = UV-Index +channel-type.fineoffsetweatherstation.uv-radiation.label = UV-Strahlung +channel-type.fineoffsetweatherstation.water-leak-detection.label = Wasserleckerkennung + +channel=Kanal +thing.gateway.label=Wetterstation +thing.sensor.WH24.label=Wetterstation - Außeneinheit +thing.sensor.WH24.description=Sensor für Windgeschwindigkeit & Richtung, Sonneneinstrahlung & Licht, Temperatur, Luftfeuchtigkeit, Niederschlag +thing.sensor.WH25.label=Sensor für Temperatur, Luftfeuchtigkeit und Druck +thing.sensor.WH25.description=Mehrkanalsensor für Temperatur, Feuchte und Druck +thing.sensor.WH26.label=Sensor für Temperatur und Luftfeuchtigkeit +thing.sensor.WH26.description=Mehrkanalsensor für Temperatur und Feuchte +thing.sensor.WH31.label=Sensor für Temperatur und Feuchte +thing.sensor.WH31.description=Mehrkanalsensor für Temperatur und Feuchte +thing.sensor.WH34.label=Temperatursensor mit externem Fühler +thing.sensor.WH35.label=Blattnässe-Sensor +thing.sensor.WH40.label=Niederschlagssensor +thing.sensor.WH41.label=Außenluftqualitätssensor +thing.sensor.WH41.description=Ein Außenluftqualitätssensor für PM2,5 +thing.sensor.WH45.label=Luftqualitätssensor +thing.sensor.WH45.description=5-in-1-Luftqualitätssensor für CO2, PM2.5, PM10, Temperatur und Feuchtigkeit +thing.sensor.WH51.label=Bodenfeuchte-Sensor +thing.sensor.WH55.label=Wasserleck-Sensor +thing.sensor.WH57.label=Blitzerkennungs-Sensor +thing.sensor.WH65.label=Wetterstation - Außeneinheit +thing.sensor.WH65.description=Sensor für Windgeschwindigkeit & -richtung, Sonneneinstrahlung & Licht, Temperatur, Luftfeuchtigkeit und Niederschlag +thing.sensor.WH68.label=Wetterstation - Außengerät +thing.sensor.WH68.description=Solarbetriebener Sensor für Windgeschwindigkeit und -richtung, Sonneneinstrahlung und Licht +thing.sensor.WH80.label=Wetterstation - Außengerät +thing.sensor.WH80.description=Ultraschallsensor für Windgeschwindigkeit & -richtung, Sonneneinstrahlung & Licht, Temperatur & Feuchtigkeit +thing.sensor.WH90.label=Wetterstation - Außengerät + +# channels + +thing-type.fineoffsetweatherstation.gateway.channel.temperature-indoor.label = Innentemperatur +thing-type.fineoffsetweatherstation.gateway.channel.temperature-outdoor.label = Außentemperatur +thing-type.fineoffsetweatherstation.gateway.channel.temperature-dew-point.label = Taupunkt +thing-type.fineoffsetweatherstation.gateway.channel.temperature-wind-chill.label = Gefühlte Temperatur +thing-type.fineoffsetweatherstation.gateway.channel.temperature-heat-index.label = Wärmeindex +thing-type.fineoffsetweatherstation.gateway.channel.humidity-indoor.label = Luftfeuchtigkeit innen +thing-type.fineoffsetweatherstation.gateway.channel.humidity-outdoor.label = Luftfeuchtigkeit außen +thing-type.fineoffsetweatherstation.gateway.channel.pressure-absolute.label = Absoluter Druck +thing-type.fineoffsetweatherstation.gateway.channel.pressure-relative.label = Relativer Druck +thing-type.fineoffsetweatherstation.gateway.channel.direction-wind.label = Windrichtung +thing-type.fineoffsetweatherstation.gateway.channel.speed-wind.label = Windgeschwindigkeit +thing-type.fineoffsetweatherstation.gateway.channel.speed-gust.label = Böengeschwindigkeit +thing-type.fineoffsetweatherstation.gateway.channel.rain-event.label = Niederschlagsmenge beim letzten Regen +thing-type.fineoffsetweatherstation.gateway.channel.rain-rate.label = Niederschlagsrate +thing-type.fineoffsetweatherstation.gateway.channel.rain-hour.label = Niederschlagsmenge aktuelle Stunde +thing-type.fineoffsetweatherstation.gateway.channel.rain-day.label = Niederschlagsmenge Heute +thing-type.fineoffsetweatherstation.gateway.channel.rain-week.label = Niederschlagsmenge diese Woche +thing-type.fineoffsetweatherstation.gateway.channel.rain-month.label = Niederschlagsmenge diesen Monat +thing-type.fineoffsetweatherstation.gateway.channel.rain-year.label = Niederschlagsmenge dieses Jahr +thing-type.fineoffsetweatherstation.gateway.channel.rain-total.label = Niederschlagsmenge gesamt +thing-type.fineoffsetweatherstation.gateway.channel.illumination.label = Lichtstärke +thing-type.fineoffsetweatherstation.gateway.channel.irradiation-uv.label = UV-Strahlung +thing-type.fineoffsetweatherstation.gateway.channel.uv-index.label = UV-Index +thing-type.fineoffsetweatherstation.gateway.channel.time.label = Datum und Uhrzeit +thing-type.fineoffsetweatherstation.gateway.channel.wind-max-day.label = Maximaler Windgeschwindigkeit heute +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-1.label = Temperatur Kanal 1 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-2.label = Temperatur Kanal 2 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-3.label = Temperatur Kanal 3 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-4.label = Temperatur Kanal 4 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-5.label = Temperatur Kanal 5 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-6.label = Temperatur Kanal 6 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-7.label = Temperatur Kanal 7 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-channel-8.label = Temperatur Kanal 8 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-1.label = Luftfeuchtigkeit Kanal 1 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-2.label = Luftfeuchtigkeit Kanal 2 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-3.label = Luftfeuchtigkeit Kanal 3 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-4.label = Luftfeuchtigkeit Kanal 4 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-5.label = Luftfeuchtigkeit Kanal 5 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-6.label = Luftfeuchtigkeit Kanal 6 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-7.label = Luftfeuchtigkeit Kanal 7 +thing-type.fineoffsetweatherstation.gateway.channel.humidity-channel-8.label = Luftfeuchtigkeit Kanal 8 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-1.label = Bodentemperatur Kanal 1 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-2.label = Bodentemperatur Kanal 2 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-3.label = Bodentemperatur Kanal 3 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-4.label = Bodentemperatur Kanal 4 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-5.label = Bodentemperatur Kanal 5 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-6.label = Bodentemperatur Kanal 6 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-7.label = Bodentemperatur Kanal 7 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-8.label = Bodentemperatur Kanal 8 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-9.label = Bodentemperatur Kanal 9 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-10.label = Bodentemperatur Kanal 10 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-11.label = Bodentemperatur Kanal 11 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-12.label = Bodentemperatur Kanal 12 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-13.label = Bodentemperatur Kanal 13 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-14.label = Bodentemperatur Kanal 14 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-15.label = Bodentemperatur Kanal 15 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-soil-channel-16.label = Bodentemperatur Kanal 16 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-1.label = Bodenfeuchtigkeit Kanal 1 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-2.label = Bodenfeuchtigkeit Kanal 2 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-3.label = Bodenfeuchtigkeit Kanal 3 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-4.label = Bodenfeuchtigkeit Kanal 4 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-5.label = Bodenfeuchtigkeit Kanal 5 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-6.label = Bodenfeuchtigkeit Kanal 6 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-7.label = Bodenfeuchtigkeit Kanal 7 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-8.label = Bodenfeuchtigkeit Kanal 8 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-9.label = Bodenfeuchtigkeit Kanal 9 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-10.label = Bodenfeuchtigkeit Kanal 10 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-11.label = Bodenfeuchtigkeit Kanal 11 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-12.label = Bodenfeuchtigkeit Kanal 12 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-13.label = Bodenfeuchtigkeit Kanal 13 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-14.label = Bodenfeuchtigkeit Kanal 14 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-15.label = Bodenfeuchtigkeit Kanal 15 +thing-type.fineoffsetweatherstation.gateway.channel.moisture-soil-channel-16.label = Bodenfeuchtigkeit Kanal 16 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-24-hour-average-channel-1.label = PM2.5 Luftqualität 24 Stunden Durchschnitt Kanal 1 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-24-hour-average-channel-2.label = PM2.5 Luftqualität 24 Stunden Durchschnitt Kanal 2 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-24-hour-average-channel-3.label = PM2.5 Luftqualität 24 Stunden Durchschnitt Kanal 3 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-24-hour-average-channel-4.label = PM2.5 Luftqualität 24 Stunden Durchschnitt Kanal 4 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-channel-1.label = PM2.5 Luftqualität Kanal 1 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-channel-2.label = PM2.5 Luftqualität Kanal 2 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-channel-3.label = PM2.5 Luftqualität Kanal 3 +thing-type.fineoffsetweatherstation.gateway.channel.air-quality-channel-4.label = PM2.5 Luftqualität Kanal 4 +thing-type.fineoffsetweatherstation.gateway.channel.water-leak-channel-1.label = Wasserleckerkennung Kanal 1 +thing-type.fineoffsetweatherstation.gateway.channel.water-leak-channel-2.label = Wasserleckerkennung Kanal 2 +thing-type.fineoffsetweatherstation.gateway.channel.water-leak-channel-3.label = Wasserleckerkennung Kanal 3 +thing-type.fineoffsetweatherstation.gateway.channel.water-leak-channel-4.label = Wasserleckerkennung Kanal 4 +thing-type.fineoffsetweatherstation.gateway.channel.lightning-distance.label = Gewitter Entfernung +thing-type.fineoffsetweatherstation.gateway.channel.lightning-time.label = Zeitpunkt des letzten Blitzschlags +thing-type.fineoffsetweatherstation.gateway.channel.lightning-counter.label = Blitzschläge heute +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-1.label = Externer Temperaturfühler Kanal 1 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-2.label = Externer Temperaturfühler Kanal 2 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-3.label = Externer Temperaturfühler Kanal 3 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-4.label = Externer Temperaturfühler Kanal 4 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-5.label = Externer Temperaturfühler Kanal 5 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-6.label = Externer Temperaturfühler Kanal 6 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-7.label = Externer Temperaturfühler Kanal 7 +thing-type.fineoffsetweatherstation.gateway.channel.temperature-external-channel-8.label = Externer Temperaturfühler Kanal 8 +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-temperature.label=Temperatur (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-humidity.label=Luftfeuchtigkeit (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-pm10.label=PM10 Luftqualität (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-pm10-24-hour-average.label=PM10 Luftqualität 24-Stunden-Mittelwert (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-pm25.label=PM2.5 Luftqualität (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-pm25-24-hour-average.label=PM2.5 Luftqualität 24-Stunden-Mittelwert (CO2-Sensor) +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-co2.label=CO2 +thing-type.fineoffsetweatherstation.gateway.channel.sensor-co2-co2-24-hour-average.label=CO2 24-Stunden-Mittelwert +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-1.label=Blattfeuchtigkeit Kanal 1 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-2.label=Blattfeuchtigkeit Kanal 2 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-3.label=Blattfeuchtigkeit Kanal 3 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-4.label=Blattfeuchtigkeit Kanal 4 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-5.label=Blattfeuchtigkeit Kanal 5 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-6.label=Blattfeuchtigkeit Kanal 6 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-7.label=Blattfeuchtigkeit Kanal 7 +thing-type.fineoffsetweatherstation.gateway.channel.leaf-wetness-channel-8.label=Blattfeuchtigkeit Kanal 8 diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/thing/gateway.xml b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/thing/gateway.xml new file mode 100644 index 000000000..53576ae5c --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/thing/gateway.xml @@ -0,0 +1,181 @@ + + + + + + NetworkAppliance + + + + + Number:Temperature + + Temperature + + Measurement + Temperature + + + + + + Number:Speed + + Wind + + + + + Number:Length + + Rain + + Measurement + Rain + + + + + + Number:Pressure + + Pressure + + Measurement + Pressure + + + + + + Number:Dimensionless + + Humidity + + Measurement + Humidity + + + + + + Number:Dimensionless + + Moisture + + Measurement + Moisture + + + + + + Number:Illuminance + + Sun + + Measurement + Light + + + + + + Number:Intensity + + Sun + + Measurement + Light + + + + + + Number:Dimensionless + + Sun + + Measurement + Light + + + + + + Number:VolumetricFlowRate + + Rain + + Measurement + Rain + + + + + + Number:Density + + AirQuality + + Measurement + AirQuality + + + + + + Number:Density + + AirQuality + + Measurement + AirQuality + + + + + + Number:Dimensionless + + Air quality indicator + CarbonDioxide + + Measurement + CO2 + + + + + + Switch + + Alarm + + Alarm + + + + + + Number + + + + + + DateTime + + Time + + + + + Number:Length + + + + diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/thing/sensor.xml b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/thing/sensor.xml new file mode 100644 index 000000000..8e3668dbc --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/main/resources/OH-INF/thing/sensor.xml @@ -0,0 +1,24 @@ + + + + + + + + + + A Sensor connected to the gateway (WN1900, GW1000, GW1100, WH2680, WH2650) + Sensor + + + + + + + + + + diff --git a/bundles/org.openhab.binding.fineoffsetweatherstation/src/test/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParserTest.java b/bundles/org.openhab.binding.fineoffsetweatherstation/src/test/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParserTest.java new file mode 100644 index 000000000..269e5d4f2 --- /dev/null +++ b/bundles/org.openhab.binding.fineoffsetweatherstation/src/test/java/org/openhab/binding/fineoffsetweatherstation/internal/service/FineOffsetDataParserTest.java @@ -0,0 +1,56 @@ +/** + * 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.time.ZoneOffset; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.assertj.core.groups.Tuple; +import org.bouncycastle.util.encoders.Hex; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.ConversionContext; +import org.openhab.binding.fineoffsetweatherstation.internal.domain.response.MeasuredValue; + +/** + * @author Andreas Berger - Initial contribution + */ +@NonNullByDefault +class FineOffsetDataParserTest { + private final FineOffsetDataParser parser = new FineOffsetDataParser(); + + @Test + void testLiveDataWH45() { + List data = parser.getLiveData(Hex.decode( + "FFFF2700510100D306280827EF0927EF020045074F0A00150B00000C0000150000000016000117001900000E0000100000110021120000002113000005850D00007000D12E0060005A005B005502AE028F0633"), + new ConversionContext(ZoneOffset.UTC)); + Assertions.assertThat(data) + .extracting(MeasuredValue::getChannelId, measuredValue -> measuredValue.getState().toString()) + .containsExactly(new Tuple("temperature-indoor", "21.1 °C"), new Tuple("humidity-indoor", "40 %"), + new Tuple("pressure-absolute", "10223 hPa"), new Tuple("pressure-relative", "10223 hPa"), + 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("speed-gust", "0 m/s"), new Tuple("illumination", "0 lx"), + new Tuple("irradiation-uv", "1 µW/cm²"), new Tuple("uv-index", "0"), + 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-month", "3.3 mm"), new Tuple("rain-year", "141.3 mm"), + new Tuple("rain-event", "0 mm"), new Tuple("sensor-co2-temperature", "20.9 °C"), + new Tuple("sensor-co2-humidity", "46 %"), new Tuple("sensor-co2-pm10", "9.6 µg/m³"), + new Tuple("sensor-co2-pm10-24-hour-average", "9 µg/m³"), + new Tuple("sensor-co2-pm25", "9.1 µ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")); + } +} diff --git a/bundles/pom.xml b/bundles/pom.xml index 348736683..61d421499 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -127,6 +127,7 @@ org.openhab.binding.exec org.openhab.binding.feed org.openhab.binding.feican + org.openhab.binding.fineoffsetweatherstation org.openhab.binding.flicbutton org.openhab.binding.fmiweather org.openhab.binding.folderwatcher