added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true" />
<attribute name="maven.pomderived" value="true" />
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true" />
<attribute name="maven.pomderived" value="true" />
<attribute name="test" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="con"
path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes" />
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.ambientweather</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,40 @@
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
== Third-party Content
engine.io-client
* License: MIT License
* Project: https://socketio.github.io/engine.io-client-java/
* Source: https://github.com/socketio/engine.io-client-java
json
* License: JSON License
* Project: https://github.com/stleary/JSON-java
* Source: https://github.com/stleary/JSON-java
okhttp
* License: Apache License 2.0
* Project: https://square.github.io/okhttp/
* Source: https://github.com/square/okhttp
okio
* License: Apache License 2.0
* Project: https://github.com/square/okio
* Source: https://github.com/square/okio
socket.io-client
* License: MIT License
* Project: https://socketio.github.io/socket.io-client-java/
* Source: https://github.com/socketio/socket.io-client-java

View File

@@ -0,0 +1,259 @@
# Ambient Weather Binding
The Ambient Weather binding integrates weather station data that's stored on the Ambient Weather online service.
The binding uses Ambient Weather's real-time API, so updates from weather stations are delivered to the binding in near real-time.
## Supported Things
The binding currently supports weather data from these weather stations.
| Thing | ID |
|--------------------------|-----------|
| Account | bridge |
| WS-0900-IP | ws0900ip |
| WS-1400-IP / WS-1401-IP | ws1400ip |
| WS-2902A | ws2902a |
| WS-8482 | ws8482 |
| WS-0265 | ws0265 |
Other stations can be added relatively easily with changes in just several places in the source code.
## Discovery
Automatic discovery is currently not supported due to the lack of weather station model information in the Ambient Weather API.
## Thing Configuration
### Account
| Parameter | Parameter ID | Required/Optional | Description |
|------------------|-------------------|-------------------|-------------|
| API Key | apiKey | Required | Obtain the API key on the *My Account* page of your `ambientweather.net` dashboard. |
| Application Key | apiKey | Required | Obtain the Application key on the *My Account* page of your `ambientweather.net` dashboard. |
### Weather Station
| Parameter | Parameter ID | Required/Optional | Description |
|------------------|-------------------|-------------------|-------------|
| MAC Address | macAddress | Required | This is the weather station's MAC address. It must be configured in your `ambientweather.net` dashboard. |
## Channels
The following channels are supported by the binding. Note that specific weather station models may support only some or all of these channels.
| Channel Group ID | Channel ID | Item Type | Description |
|------------------------------|---------------------------------|-------------------------|---------------------------------------------------------------|
| station | name | String | Station name given by the user |
| station | location | String | Location of the station given by the user |
| | | | |
| weatherData\<station-type\> | observationTime | DateTime | Time that the conditions were observed |
| weatherData\<station-type\> | batteryIndicator | String | Battery condition indicator (1 Good, 0 Not good) |
| weatherData\<station-type\> | temperature | Number:Temperature | Actual observed temperature |
| weatherData\<station-type\> | feelingTemperature | Number:Temperature | "Real feel" temperature |
| weatherData\<station-type\> | dewPoint | Number:Temperature | Dew point |
| weatherData\<station-type\> | relativeHumidity | Number:Dimensionless | Relative humidity |
| weatherData\<station-type\> | pressureAbsolute | Number:Pressure | Absolute barometric pressure |
| weatherData\<station-type\> | pressureRelative | Number:Pressure | Relative barometric pressure |
| weatherData\<station-type\> | pressureTrend | String | 3-hour trend (FALLING RAPIDLY ... STEADY ... RISING RAPIDLY |
| weatherData\<station-type\> | windSpeed | Number:Speed | Wind speed |
| weatherData\<station-type\> | windDirectionDegrees | Number:Angle | Wind direction in degrees |
| weatherData\<station-type\> | windDirection | String | Wind direction |
| weatherData\<station-type\> | windSpeedMaxDaily | Number:Speed | Maximum daily wind speed |
| weatherData\<station-type\> | windGust | Number:Speed | Wind gust |
| weatherData\<station-type\> | windGustMaxDaily | Number:Speed | Maximum daily wind gust |
| weatherData\<station-type\> | windSpeedAvg2Minute | Number:Speed | Two-minute wind speed average |
| weatherData\<station-type\> | windDirectionDegreesAvg2Min | Number:Angle | Two-minute wind direction average |
| weatherData\<station-type\> | windDirectionAvg2Min | String | Two-minute wind direction average |
| weatherData\<station-type\> | windSpeedAvg10Minute | Number:Speed | Ten-minute wind speed average |
| weatherData\<station-type\> | windDirectionDegreesAvg10Min | Number:Angle | Ten-minute wind direction average |
| weatherData\<station-type\> | windDirectionAvg10Min | String | Ten-minute wind direction average |
| weatherData\<station-type\> | rainHourlyRate | Number:Speed | Hourly rate of rainfall |
| weatherData\<station-type\> | rainDay | Number:Length | Rainfall amount for the day |
| weatherData\<station-type\> | rainWeek | Number:Length | Rainfall amount for the week |
| weatherData\<station-type\> | rainMonth | Number:Length | Rainfall amount for the month |
| weatherData\<station-type\> | rainYear | Number:Length | Rainfall amount for the year |
| weatherData\<station-type\> | rainTotal | Number:Length | Rainfall amount since last weather station reset |
| weatherData\<station-type\> | rainEvent | Number:Length | Rainfall for most recent rain event |
| weatherData\<station-type\> | rainLastTime | DateTime | Last time it rained |
| weatherData\<station-type\> | solarRadiation | Number:Intensity | Solar radiation |
| weatherData\<station-type\> | uvIndex | Number:Dimensionless | UV index |
| | | | |
| indoorSensor | temperature | Number:Temperature | Temperature |
| indoorSensor | humidity | Number:Dimensionless | Humidity |
| indoorSensor | batteryIndicator | String | Battery indicator |
| | | | |
| remoteSensor\<1-10\> | temperature | Number:Temperature | Temperature |
| remoteSensor\<1-10\> | dewPoint | Number:Temperature | Dew Point |
| remoteSensor\<1-10\> | feelingTemperature | Number:Temperature | "Real feel" temperature |
| remoteSensor\<1-10\> | humidity | Number:Dimensionless | Humidity |
| remoteSensor\<1-10\> | batteryIndicator | String | Battery indicator |
| remoteSensor\<1-10\> | co2 | Number:Dimensionless | Carbon Dioxide level |
| remoteSensor\<1-10\> | relay | Switch | Relay |
| remoteSensor\<1-10\> | soilTemperature | Number:Temperature | Soil temperature |
| remoteSensor\<1-10\> | soilMoisture | Number:Dimensionless | Soil moisture |
| remoteSensor\<1-10\> | soilMoistureLevel | String | Soil moisture level (VERY DRY ... VERY WET) |
## Example
### Things
```
Bridge ambientweather:bridge:account "Ambient Weather Account" [ applicationKey="bd7eb3fe87f74e9.....", apiKey="efe88d6202be43e6a40....." ] {
Thing ws1400ip 1400 "Ambient Weather WS-1400-IP" [ macAddress="00:ab:cd:00:00:01" ]
Thing ws8482 8482 "Ambient Weather WS-8482" [ macAddress="00:ab:cd:00:00:02" ]
}
```
### Items
```
// WS-1400-IP Weather Station
String WS1400IP_StationName "Station Name [%s]" { channel="ambientweather:ws1400ip:account:1400:station#name" }
String WS1400IP_StationLocation "Station Location [%s]" { channel="ambientweather:ws1400ip:account:1400:station#location" }
DateTime WS1400IP_ObservationTime "Station Observation Time [%s]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#observationTime" }
String WS1400IP_StationBattery "Station Battery [MAP(ambient-battery.map):%s]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#batteryIndicator" }
Number:Temperature WS1400IP_Temperature "Temperature [%.1f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#temperature" }
Number:Temperature WS1400IP_DewPoint "Dew Point [%.1f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#dewPoint" }
Number:Temperature WS1400IP_RealFeel "RealFeel [%.1f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#feelingTemperature" }
Number:Dimensionless WS1400IP_Humidity "Humidity [%.1f %%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#relativeHumidity" }
Number:Pressure WS1400IP_PressureAbsolute "Pressure Absolute [%.1f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#pressureAbsolute" }
Number:Pressure WS1400IP_PressureRelative "Pressure Relative [%.1f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#pressureRelative" }
String WS1400IP_PressureTrend "Pressure Trend [%s]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#pressureTrend" }
Number:Speed WS1400IP_WindSpeed "Wind Speed [%.0f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#windSpeed" }
Number:Angle WS1400IP_WindDirectionDegrees "Wind Direction Degrees [%d %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#windDirectionDegrees" }
String WS1400IP_WindDirection "Wind Direction [%s]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#windDirection" }
Number:Speed WS1400IP_WindGust "Wind Gust [%.0f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#windGust" }
Number:Speed WS1400IP_WindGustDailyMax "Wind Gust Max Daily [%.0f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#windGustMaxDaily" }
// Use this if your units are SI
Number:Speed WS1400IP_RainHourlyRate "Rain Hourly Rate [%.1f mm/h]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#rainHourlyRate" }
// Use this if your units are Imperial
Number:Speed WS1400IP_RainHourlyRate "Rain Hourly Rate [%.2f in/h]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#rainHourlyRate" }
Number:Length WS1400IP_RainDaily "Rain Daily [%.2f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#rainDay" }
Number:Length WS1400IP_RainWeekly "Rain Weekly [%.2f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#rainWeek" }
Number:Length WS1400IP_RainMonthly "Rain Monthly [%.2f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#rainMonth" }
Number:Length WS1400IP_RainYearly "Rain Yearly [%.2f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#rainYear" }
Number:Length WS1400IP_RainTotal "Rain Total [%.2f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#rainTotal" }
Number:Length WS1400IP_RainEvent "Rain Event [%.2f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#rainEvent" }
DateTime WS1400IP_RainLastTime "Rain Last Time [%s]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#rainLastTime" }
Number:Intensity WS1400IP_SolarRadiation "Solar Radiation [%.0f %unit%]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#solarRadiation" }
Number WS1400IP_UVIndex "UV Index [%.0f]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#uvIndex" }
String WS1400IP_UVDanger "UV Danger Level [%s]" { channel="ambientweather:ws1400ip:account:1400:weatherDataWs1400ip#uvDanger" }
Number:Temperature WS1400IP_IndoorTemperature "Indoor Temperature [%.1f %unit%]" { channel="ambientweather:ws1400ip:account:1400:indoorSensor#temperature" }
Number:Dimensionless WS1400IP_IndoorHumidity "Indoor Humidity [%.1f %%]" { channel="ambientweather:ws1400ip:account:1400:indoorSensor#relativeHumidity" }
String WS1400IP_IndoorBattery "Indoor Battery [MAP(ambient-battery.map):%s]" { channel="ambientweather:ws1400ip:account:1400:indoorSensor#batteryIndicator" }
// WS-8482 Weather Station
String WS8482_StationName "Station Name [%s]" { channel="ambientweather:ws8482:ws8482:station#name" }
String WS8482_StationLocation "Station Location [%s]" { channel="ambientweather:ws8482:ws8482:station#location" }
DateTime WS8482_ObservationTime "Observation Time [%s]" { channel="ambientweather:ws8482:account:8482:weatherDataWs8482#observationTime" }
Number:Temperature WS8482_IndoorTemperature "Indoor Temperature [%.1f %unit%]" { channel="ambientweather:ws8482:account:8482:weatherDataWs8482#temperature" }
Number:Dimensionless WS8482_IndoorHumidity "Indoor Humidity [%.1f %%]" { channel="ambientweather:ws8482:account:8482:weatherDataWs8482#relativeHumidity" }
String WS8482_StationBattery "Station Battery [MAP(ambient-battery.map):%s]" { channel="ambientweather:ws8482:account:8482:weatherDataWs8482#batteryIndicator" }
Number:Temperature WS8482_RemoteTemperature "Remote Temperature [%.1f %unit%]" { channel="ambientweather:ws8482:account:8482:remoteSensor1#temperature" }
Number:Dimensionless WS8482_RemoteHumidity "Remote Humidity [%.1f %%]" { channel="ambientweather:ws8482:account:8482:remoteSensor1#relativeHumidity" }
String WS8482_RemoteBattery "Remote Battery [MAP(ambient-battery.map):%s]" { channel="ambientweather:ws8482:account:8482:remoteSensor1#batteryIndicator" }
Number:Temperature WS8482_SoilTemperature "Soil Temperature [%.1f %unit%]" { channel="ambientweather:ws8482:account:8482:remoteSensor2#soilTemperature" }
Number:Dimensionless WS8482_SoilMoisture "Soil Moisture [%.1f %%]" { channel="ambientweather:ws8482:account:8482:remoteSensor2#soilMoisture" }
String WS8482_SoilMoistureLevel "Soil Moisture Level [%s]" { channel="ambientweather:ws8482:account:8482:remoteSensor2#soilMoistureLevel" }
String WS8482_SoilSensorBattery "Remote Battery [MAP(ambient-battery.map):%s]" { channel="ambientweather:ws8482:account:8482:remoteSensor2#batteryIndicator" }
```
### Transforms
#### File ambient-battery.map
```
-=UNKNOWN
NULL=UNKNOWN
1=GOOD
0=REPLACE
```
### Sitemap
```
Text label="Weather Station" icon="sun_clouds" {
Frame {
Text item=WS1400IP_ObservationTime label="Observation Time [%1$tm/%1$td %1$tl:%1$tM %1$tp]"
}
Frame {
Text item=WS1400IP_Temperature
Text item=WS1400IP_RealFeel
Text item=WS1400IP_DewPoint
Text item=WS1400IP_Humidity
Text item=WS1400IP_PressureRelative
Text item=WS1400IP_PressureTrend
}
Frame {
Text item=WS1400IP_WindSpeed
Text item=WS1400IP_WindDirection
Text item=WS1400IP_WindGust
Text item=WS1400IP_WindGustDailyMax
}
Frame {
Text item=WS1400IP_SolarRadiation
Text item=WS1400IP_SolarRadiationDailyMax
Text item=WS1400IP_UVDanger
}
Frame {
Text item=WS1400IP_RainHourlyRate
Text item=WS1400IP_RainDaily
Text item=WS1400IP_RainWeekly
Text item=WS1400IP_RainMonthly
Text item=WS1400IP_RainYearly
Text item=WS1400IP_RainTotal
Text item=WS1400IP_RainEvent
Text item=WS1400IP_RainLastTime label="Last Rain [%1$tm/%1$td %1$tl:%1$tM %1$tp]"
}
Frame {
Text item=WS1400IP_IndoorTemperature
Text item=WS1400IP_IndoorHumidity
}
Frame {
Text item=WS1400IP_StationBattery
Text item=WS1400IP_IndoorBattery
}
}
```
## How To Add Another Weather Station Type
Adding support for a new weather station type involves changes to the source code in just a few places.
#### Add a New Thing Type in AmbientWeatherBindingConstants.java
Define a new `ThingTypeUID` for the new station and add it to the `SUPPORTED_THING_TYPES_UIDS` Collection.
#### Create OH-INF/thing/\<station-model\>.xml
Add thing type and channel group specific to the data elements supported by this weather station.
Modeling this after an existing thing type that shares many of the channels is the easiest starting point.
You can determine the weather data elements returned for the weather station by putting the binding into debug mode and reviewing the JSON object returned by the Ambient Weather API.
#### Create Processor Class AmbientWeather<StationModel>Processor
Add a class in `org.openhab.binding.ambientweather.internal.processor` that defines the channels supported by this station type.
Add the following two methods.
Again, the easiest approach is to model this class after a class for a similar weather station type.
##### Method: processInfoUpdate
Updates the channels for station name and location.
##### Method: processWeatherData
Updates channels for weather data.
#### Update AmbientWeatherProcessorFactory.java
Add new Processor class definition to `AmbientWeatherProcessorFactory.java`, and add a new case to the switch statement to return the new processor.

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.ambientweather</artifactId>
<name>openHAB Add-ons :: Bundles :: Ambient Weather Binding</name>
<dependencies>
<dependency>
<groupId>org.openhab.osgiify</groupId>
<artifactId>io.socket.socket.io-client</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.osgiify</groupId>
<artifactId>io.socket.engine.io-client</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.okhttp</artifactId>
<version>3.8.1_1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.okio</artifactId>
<version>1.13.0_1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180813</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>
<bnd.importpackage>android.*;resolution:=optional,com.android.org.*;resolution:=optional,dalvik.*;resolution:=optional,javax.annotation.meta.*;resolution:=optional,org.apache.harmony.*;resolution:=optional,org.conscrypt.*;resolution:=optional,sun.security.*;resolution:=optional</bnd.importpackage>
</properties>
</project>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.ambientweather-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-ambientweather" description="Ambient Weather Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle dependency="true">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.okhttp/3.8.1_1</bundle>
<bundle dependency="true">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.okio/1.13.0_1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/io.socket.socket.io-client/1.0.0</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/io.socket.engine.io-client/1.0.0</bundle>
<bundle dependency="true">mvn:org.json/json/20180813</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.ambientweather/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,128 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link AmbientWeatherBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class AmbientWeatherBindingConstants {
private static final String BINDING_ID = "ambientweather";
// Bridge
public static final String THING_TYPE_BRIDGE = "bridge";
public static final ThingTypeUID UID_BRIDGE = new ThingTypeUID(BINDING_ID, THING_TYPE_BRIDGE);
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(UID_BRIDGE).collect(Collectors.toSet()));
// WS-1400-IP series weather stations
public static final String THING_TYPE_WS1400IP = "ws1400ip";
public static final ThingTypeUID UID_WS1400IP = new ThingTypeUID(BINDING_ID, THING_TYPE_WS1400IP);
// WS-2902A series weather stations
public static final String THING_TYPE_WS2902A = "ws2902a";
public static final ThingTypeUID UID_WS2902A = new ThingTypeUID(BINDING_ID, THING_TYPE_WS2902A);
// WS-8482 weather station
public static final String THING_TYPE_WS8482 = "ws8482";
public static final ThingTypeUID UID_WS8482 = new ThingTypeUID(BINDING_ID, THING_TYPE_WS8482);
// WS-0900-IP series weather stations
public static final String THING_TYPE_WS0900IP = "ws0900ip";
public static final ThingTypeUID UID_WS0900IP = new ThingTypeUID(BINDING_ID, THING_TYPE_WS0900IP);
// WS-0265 weather station
public static final String THING_TYPE_WS0265 = "ws0265";
public static final ThingTypeUID UID_WS0265 = new ThingTypeUID(BINDING_ID, THING_TYPE_WS0265);
// Collection of weather station thing types
public static final Set<ThingTypeUID> SUPPORTED_STATION_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(UID_WS1400IP, UID_WS2902A, UID_WS8482, UID_WS0900IP, UID_WS0265).collect(Collectors.toSet()));
// Collection of all supported thing types
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.concat(SUPPORTED_BRIDGE_THING_TYPES_UIDS.stream(), SUPPORTED_STATION_THING_TYPES_UIDS.stream())
.collect(Collectors.toSet()));
// Channel groups for specific weather stations
public static final String CHGRP_WS1400IP = "weatherDataWs1400ip";
public static final String CHGRP_WS2902A = "weatherDataWs2902a";
public static final String CHGRP_WS8482 = "weatherDataWs8482";
public static final String CHGRP_WS0900IP = "weatherDataWs0900ip";
public static final String CHGRP_WS0265 = "weatherDataWs0265";
// Channel groups used across weather station types
public static final String CHGRP_STATION = "station";
public static final String CHGRP_INDOOR_SENSOR = "indoorSensor";
public static final String CHGRP_REMOTE_SENSOR = "remoteSensor";
// Channels Ids for data returned by the ambientweather.net API
public static final String CH_NAME = "name";
public static final String CH_LOCATION = "location";
public static final String CH_BATTERY_INDICATOR = "batteryIndicator";
public static final String CH_OBSERVATION_TIME = "observationTime";
public static final String CH_TEMPERATURE = "temperature";
public static final String CH_FEELING_TEMPERATURE = "feelingTemperature";
public static final String CH_DEW_POINT = "dewPoint";
public static final String CH_HUMIDITY = "relativeHumidity";
public static final String CH_PRESSURE_ABSOLUTE = "pressureAbsolute";
public static final String CH_PRESSURE_RELATIVE = "pressureRelative";
public static final String CH_WIND_SPEED = "windSpeed";
public static final String CH_WIND_DIRECTION_DEGREES = "windDirectionDegrees";
public static final String CH_WIND_SPEED_MAX_DAILY = "windSpeedMaxDaily";
public static final String CH_WIND_GUST = "windGust";
public static final String CH_WIND_GUST_MAX_DAILY = "windGustMaxDaily";
public static final String CH_WIND_SPEED_AVG_2MIN = "windSpeedAvg2Minute";
public static final String CH_WIND_DIRECTION_AVG_2MIN = "windDirectionDegreesAvg2Min";
public static final String CH_WIND_SPEED_AVG_10MIN = "windSpeedAvg10Minute";
public static final String CH_WIND_DIRECTION_AVG_10MIN = "windDirectionDegreesAvg10Min";
public static final String CH_RAIN_HOURLY_RATE = "rainHourlyRate";
public static final String CH_RAIN_DAY = "rainDay";
public static final String CH_RAIN_WEEK = "rainWeek";
public static final String CH_RAIN_MONTH = "rainMonth";
public static final String CH_RAIN_YEAR = "rainYear";
public static final String CH_RAIN_TOTAL = "rainTotal";
public static final String CH_RAIN_EVENT = "rainEvent";
public static final String CH_RAIN_LAST_TIME = "rainLastTime";
public static final String CH_SOLAR_RADIATION = "solarRadiation";
public static final String CH_UV_INDEX = "uvIndex";
public static final String CH_CO2 = "co2";
public static final String CH_SOIL_TEMPERATURE = "soilTemperature";
public static final String CH_SOIL_MOISTURE = "soilMoisture";
public static final String CH_RELAY = "relay";
// Channel Ids for calculated channels
public static final String CH_PRESSURE_TREND = "pressureTrend";
public static final String CH_WIND_DIRECTION = "windDirection";
public static final String CH_UV_DANGER = "uvDanger";
public static final String CH_SOIL_MOISTURE_LEVEL = "soilMoistureLevel";
// Bridge configuration parameters
public static final String CONFIG_API_KEY = "apiKey";
public static final String CONFIG_APPLICATION_KEY = "applicationKey";
// Weather station configuration parameters
public static final String CONFIG_MAC_ADDRESS = "macAddress";
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal;
import static org.openhab.binding.ambientweather.internal.AmbientWeatherBindingConstants.*;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ambientweather.internal.handler.AmbientWeatherBridgeHandler;
import org.openhab.binding.ambientweather.internal.handler.AmbientWeatherStationHandler;
import org.openhab.core.i18n.TimeZoneProvider;
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.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link AmbientWeatherHandlerFactory} is responsible for creating the
* Ambient Weather bridge and station thing handlers.
*
* @author Mark Hilbush - Initial contribution
*/
@Component(configurationPid = "binding.ambientweather", service = ThingHandlerFactory.class)
public class AmbientWeatherHandlerFactory extends BaseThingHandlerFactory {
// Needed for converting UTC time to local time
private final TimeZoneProvider timeZoneProvider;
@Activate
public AmbientWeatherHandlerFactory(@Reference TimeZoneProvider timeZoneProvider) {
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 (SUPPORTED_STATION_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new AmbientWeatherStationHandler(thing, timeZoneProvider);
}
if (SUPPORTED_BRIDGE_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new AmbientWeatherBridgeHandler((Bridge) thing);
}
return null;
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.config;
/**
* The {@link BridgeConfig} is responsible for storing the
* Ambient Weather bridge thing configuration.
*
* @author Mark Hilbush - Initial contribution
*/
public class BridgeConfig {
/**
* API key
*/
public String apiKey;
/**
* Application key
*/
public String applicationKey;
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.config;
/**
* The {@link StationConfig} is responsible for storing the
* Ambient Weather weather station thing configuration.
*
* @author Mark Hilbush - Initial contribution
*/
public class StationConfig {
/**
* MAC address of the device
*/
public String macAddress;
}

View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.handler;
import static org.openhab.binding.ambientweather.internal.AmbientWeatherBindingConstants.CONFIG_MAC_ADDRESS;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ambientweather.internal.config.BridgeConfig;
import org.openhab.binding.ambientweather.internal.model.DeviceJson;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.thing.Bridge;
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.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* The {@link AmbientWeatherBridgeHandler} is responsible for handling the
* bridge things created to use the Ambient Weather service.
*
* @author Mark Hilbush - Initial Contribution
*/
@NonNullByDefault
public class AmbientWeatherBridgeHandler extends BaseBridgeHandler {
// URL to retrieve device list from Ambient Weather
private static final String DEVICES_URL = "https://api.ambientweather.net/v1/devices?applicationKey=%APPKEY%&apiKey=%APIKEY%";
// Timeout of the call to the Ambient Weather devices API
public static final int DEVICES_API_TIMEOUT = 20000;
// Time to wait after failed key validation
public static final long KEY_VALIDATION_DELAY = 60L;
private final Logger logger = LoggerFactory.getLogger(AmbientWeatherBridgeHandler.class);
// Job to validate app and api keys
@Nullable
private ScheduledFuture<?> validateKeysJob;
// Application key is granted only by request from developer
@Nullable
private String applicationKey;
// API key assigned to user in ambientweather.net dashboard
@Nullable
private String apiKey;
// Used Ambient Weather real-time API to retrieve weather data
// for weather stations assigned to an API key
private AmbientWeatherEventListener listener;
private final Gson gson = new Gson();
private Runnable validateKeysRunnable = new Runnable() {
@Override
public void run() {
logger.debug("Validating application and API keys");
String response = null;
try {
// Query weather stations (devices) from Ambient Weather
String url = DEVICES_URL.replace("%APPKEY%", getApplicationKey()).replace("%APIKEY%", getApiKey());
logger.debug("Bridge: Querying list of devices from ambient weather service");
response = HttpUtil.executeUrl("GET", url, DEVICES_API_TIMEOUT);
logger.trace("Bridge: Response = {}", response);
// Got a response so the keys are good
DeviceJson[] stations = gson.fromJson(response, DeviceJson[].class);
logger.debug("Bridge: Application and API keys are valid with {} stations", stations.length);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Connecting to service");
// Start up the real-time API listener
listener.start(applicationKey, apiKey, gson);
} catch (IOException e) {
// executeUrl throws IOException when it gets a Not Authorized (401) response
logger.debug("Bridge: Got IOException: {}", e.getMessage());
setThingOfflineWithCommError(e.getMessage(), "Invalid API or application key");
rescheduleValidateKeysJob();
} catch (IllegalArgumentException e) {
logger.debug("Bridge: Got IllegalArgumentException: {}", e.getMessage());
setThingOfflineWithCommError(e.getMessage(), "Unable to get devices");
rescheduleValidateKeysJob();
} catch (JsonSyntaxException e) {
logger.debug("Bridge: Got JsonSyntaxException: {}", e.getMessage());
setThingOfflineWithCommError(e.getMessage(), "Error parsing json response");
rescheduleValidateKeysJob();
}
}
};
public AmbientWeatherBridgeHandler(Bridge bridge) {
super(bridge);
listener = new AmbientWeatherEventListener(this);
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
// If there are keys in the config, schedule the job to validate them
if (hasApplicationKey() && hasApiKey()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Awaiting key validation");
scheduleValidateKeysJob();
}
}
/*
* Check if an application key has been provided in the thing config
*/
private boolean hasApplicationKey() {
String configApplicationKey = getConfigAs(BridgeConfig.class).applicationKey;
if (StringUtils.isEmpty(configApplicationKey)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Missing application key");
return false;
}
applicationKey = configApplicationKey;
return true;
}
/*
* Check if an API key has been provided in the thing config
*/
private boolean hasApiKey() {
String configApiKey = getConfigAs(BridgeConfig.class).apiKey;
if (StringUtils.isEmpty(configApiKey)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Missing API key");
return false;
}
apiKey = configApiKey;
return true;
}
public void setThingOfflineWithCommError(String errorDetail, String statusDescription) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, statusDescription);
}
@Override
public void dispose() {
cancelValidateKeysJob();
listener.stop();
}
/**
* Start the job to validate the API and Application keys.
* A side-effect of this is that we will discover the devices
* MAC addresses that are associated with the API key.
*/
private void scheduleValidateKeysJob() {
if (validateKeysJob == null) {
validateKeysJob = scheduler.schedule(validateKeysRunnable, 5, TimeUnit.SECONDS);
}
}
private void cancelValidateKeysJob() {
if (validateKeysJob != null) {
validateKeysJob.cancel(true);
validateKeysJob = null;
}
}
public void rescheduleValidateKeysJob() {
logger.debug("Bridge: Key validation will run again in {} seconds", KEY_VALIDATION_DELAY);
validateKeysJob = scheduler.schedule(validateKeysRunnable, KEY_VALIDATION_DELAY, TimeUnit.SECONDS);
}
/*
* Keep track of the station handlers so that the listener can route data events
* to the correct handler.
*/
@Override
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
String macAddress = (String) childThing.getConfiguration().get(CONFIG_MAC_ADDRESS);
listener.addHandler((AmbientWeatherStationHandler) childHandler, macAddress);
logger.debug("Bridge: Station handler initialized for {} with MAC {}", childThing.getUID(), macAddress);
listener.resubscribe();
}
@Override
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
String macAddress = (String) childThing.getConfiguration().get(CONFIG_MAC_ADDRESS);
listener.removeHandler((AmbientWeatherStationHandler) childHandler, macAddress);
logger.debug("Bridge: Station handler disposed for {} with MAC {}", childThing.getUID(), macAddress);
}
// Callback used by EventListener to update bridge status
public void markBridgeOffline(String reason) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason);
}
// Callback used by EventListener to update bridge status
public void markBridgeOnline() {
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
}
public @Nullable String getApplicationKey() {
return applicationKey;
}
public @Nullable String getApiKey() {
return apiKey;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Handler doesn't support any commands
}
}

View File

@@ -0,0 +1,362 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.handler;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.json.JSONObject;
import org.openhab.binding.ambientweather.internal.model.DeviceJson;
import org.openhab.binding.ambientweather.internal.model.EventDataGenericJson;
import org.openhab.binding.ambientweather.internal.model.EventSubscribedJson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;
/**
* The {@link AmbientWeatherEventListener} is responsible for establishing
* a socket.io connection with ambientweather.net, subscribing/unsubscribing
* to data events, receiving data events through the real-time socket.io API,
* and for routing the data events to a weather station thing handler for processing.
*
* @author Mark Hilbush - Initial contribution
*/
public class AmbientWeatherEventListener {
// URL used to get the realtime event stream
private static final String REALTIME_URL = "https://api.ambientweather.net/?api=1&applicationKey=%APPKEY%";
// JSON used to subscribe or unsubscribe from weather data events
private static final String SUB_UNSUB_JSON = "{ apiKeys: [ '%APIKEY%' ] }";
private final Logger logger = LoggerFactory.getLogger(AmbientWeatherEventListener.class);
// Maintain mapping of handler and weather station MAC address
private final Map<AmbientWeatherStationHandler, String> handlers = new ConcurrentHashMap<>();
private String apiKey;
private String applicationKey;
// Socket.io socket used to access Ambient Weather real-time API
private Socket socket;
// Identifies if connected to real-time API
private boolean isConnected;
private Gson gson;
private AmbientWeatherBridgeHandler bridgeHandler;
public AmbientWeatherEventListener(AmbientWeatherBridgeHandler bridgeHandler) {
this.bridgeHandler = bridgeHandler;
}
/*
* Update the list of handlers to include 'handler' at MAC address 'macAddress'
*/
public void addHandler(AmbientWeatherStationHandler handler, String macAddress) {
logger.debug("Listener: Add station handler to list: {}", handler.getThing().getUID());
handlers.put(handler, macAddress);
}
/*
* Update the list of handlers to remove 'handler' at MAC address 'macAddress'
*/
public void removeHandler(AmbientWeatherStationHandler handler, String macAddress) {
logger.debug("Listener: Remove station handler from list: {}", handler.getThing().getUID());
handlers.remove(handler);
}
/*
* Send weather station information (station name and location) to the
* thing handler associated with the MAC address
*/
private void sendStationInfoToHandler(String macAddress, String name, String location) {
AmbientWeatherStationHandler handler = getHandler(macAddress);
if (handler != null) {
handler.handleInfoEvent(macAddress, name, location);
}
}
/*
* Send an Ambient Weather data event to the station thing handler associated
* with the MAC address
*/
private void sendWeatherDataToHandler(String macAddress, String jsonData) {
AmbientWeatherStationHandler handler = getHandler(macAddress);
if (handler != null) {
handler.handleWeatherDataEvent(jsonData);
}
}
private AmbientWeatherStationHandler getHandler(String macAddress) {
logger.debug("Listener: Search for MAC {} in handlers list with {} entries", macAddress, handlers.size());
for (Map.Entry<AmbientWeatherStationHandler, String> device : handlers.entrySet()) {
AmbientWeatherStationHandler handler = device.getKey();
String mac = device.getValue();
if (mac.equals(macAddress)) {
logger.debug("Listener: Found handler for {} with MAC {}", handler.getThing().getUID(), macAddress);
return handler;
}
}
logger.debug("Listener: No handler available for event for station with MAC {}", macAddress);
return null;
}
/*
* Start the event listener for the Ambient Weather real-time API
*/
public void start(String applicationKey, String apiKey, Gson gson) {
logger.debug("Listener: Event listener starting");
this.applicationKey = applicationKey;
this.apiKey = apiKey;
this.gson = gson;
connectToService();
}
/*
* Stop the event listener for the Ambient Weather real-time API.
*/
public void stop() {
logger.debug("Listener: Event listener stopping");
sendUnsubscribe();
disconnectFromService();
handlers.clear();
}
/*
* Initiate the connection to the Ambient Weather real-time API
*/
private synchronized void connectToService() {
final String url = REALTIME_URL.replace("%APPKEY%", applicationKey);
try {
IO.Options options = new IO.Options();
options.forceNew = true;
options.transports = new String[] { "websocket" };
socket = IO.socket(url, options);
} catch (URISyntaxException e) {
logger.info("Listener: URISyntaxException getting IO socket: {}", e.getMessage());
return;
}
socket.on(Socket.EVENT_CONNECT, onEventConnect);
socket.on(Socket.EVENT_CONNECT_ERROR, onEventConnectError);
socket.on(Socket.EVENT_CONNECT_TIMEOUT, onEventConnectTimeout);
socket.on(Socket.EVENT_DISCONNECT, onEventDisconnect);
socket.on(Socket.EVENT_RECONNECT, onEventReconnect);
socket.on("data", onData);
socket.on("subscribed", onSubscribed);
logger.debug("Listener: Opening connection to ambient weather service with socket {}", socket.toString());
socket.connect();
}
/*
* Initiate a disconnect from the Ambient Weather real-time API
*/
private void disconnectFromService() {
if (socket != null) {
logger.debug("Listener: Disconnecting socket and removing event listeners for {}", socket.toString());
socket.disconnect();
socket.off();
socket = null;
}
}
/*
* Attempt to reconnect to the Ambient Weather real-time API
*/
private void reconnectToService() {
logger.debug("Listener: Attempting to reconnect to service");
disconnectFromService();
connectToService();
}
/*
* Socket.io event callbacks
*/
private Emitter.Listener onEventConnect = new Emitter.Listener() {
@Override
public void call(final Object... args) {
logger.debug("Listener: Connected! Subscribe to weather data events");
isConnected = true;
bridgeHandler.markBridgeOnline();
sendSubscribe();
}
};
private Emitter.Listener onEventDisconnect = new Emitter.Listener() {
@Override
public void call(final Object... args) {
logger.debug("Listener: Disconnected from the ambient weather service)");
handleError(Socket.EVENT_DISCONNECT, args);
isConnected = false;
}
};
private Emitter.Listener onEventConnectError = new Emitter.Listener() {
@Override
public void call(final Object... args) {
handleError(Socket.EVENT_CONNECT_ERROR, args);
}
};
private Emitter.Listener onEventConnectTimeout = new Emitter.Listener() {
@Override
public void call(final Object... args) {
handleError(Socket.EVENT_CONNECT_TIMEOUT, args);
}
};
private Emitter.Listener onEventReconnect = new Emitter.Listener() {
@Override
public void call(final Object... args) {
logger.debug("Listener: Received reconnect event from service");
reconnectToService();
}
};
private Emitter.Listener onSubscribed = new Emitter.Listener() {
@Override
public void call(final Object... args) {
logger.debug("Listener: Received SUBSCRIBED event");
// Got a response to a subscribe or unsubscribe command
if (args.length > 0) {
handleSubscribed(((JSONObject) args[0]).toString());
}
}
};
private Emitter.Listener onData = new Emitter.Listener() {
@Override
public void call(final Object... args) {
logger.debug("Listener: Received DATA event");
// Got a weather data event from ambientweather.net
if (args.length > 0) {
handleData(((JSONObject) args[0]).toString());
}
}
};
/*
* Handlers for events
*/
private void handleError(String event, Object... args) {
String reason = "Unknown";
if (args.length > 0) {
if (args[0] instanceof String) {
reason = (String) args[0];
} else if (args[0] instanceof Exception) {
reason = String.format("Exception=%s Message=%s", args[0].getClass(),
((Exception) args[0]).getMessage());
}
}
logger.debug("Listener: Received socket event: {}, Reason: {}", event, reason);
bridgeHandler.markBridgeOffline(reason);
}
/*
* Parse the subscribed event, then tell the handler to update the station info channels.
*/
private void handleSubscribed(String jsonData) {
logger.debug("Listener: subscribed={}", jsonData);
// Extract the station names and locations and give to handlers
try {
EventSubscribedJson subscribed = gson.fromJson(jsonData, EventSubscribedJson.class);
if (subscribed.invalidApiKeys != null) {
logger.info("Listener: Invalid keys!! invalidApiKeys={}", subscribed.invalidApiKeys);
bridgeHandler.markBridgeOffline("Invalid API keys");
return;
}
if (subscribed.devices != null && subscribed.devices instanceof ArrayList) {
// Convert the ArrayList back to JSON, then parse it
String innerJson = gson.toJson(subscribed.devices);
DeviceJson[] stations = gson.fromJson(innerJson, DeviceJson[].class);
// Inform handlers of their name and location
for (DeviceJson station : stations) {
logger.debug("Listener: Subscribed event has station: name = {}, location = {}, MAC = {}",
station.info.name, station.info.location, station.macAddress);
sendStationInfoToHandler(station.macAddress, station.info.name, station.info.location);
}
}
if (subscribed.isMethodSubscribe()) {
logger.debug("Listener: Subscribed to data events. Waiting for data...");
} else if (subscribed.isMethodUnsubscribe()) {
logger.debug("Listener: Unsubscribed from data events");
}
} catch (JsonSyntaxException e) {
logger.debug("Listener: Exception parsing subscribed.devices: {}", e.getMessage());
}
}
/*
* Parse the weather data event, then send to handler to update the channels
*/
private synchronized void handleData(String jsonData) {
logger.debug("Listener: Data: {}", jsonData);
try {
EventDataGenericJson data = gson.fromJson(jsonData, EventDataGenericJson.class);
if (StringUtils.isNotEmpty(data.macAddress)) {
sendWeatherDataToHandler(data.macAddress, jsonData);
}
} catch (JsonSyntaxException e) {
logger.info("Listener: Exception parsing subscribed event: {}", e.getMessage());
}
}
/*
* Subscribe to weather data events for stations associated with the API key
*/
private void sendSubscribe() {
if (apiKey == null) {
return;
}
final String sub = SUB_UNSUB_JSON.replace("%APIKEY%", apiKey);
if (isConnected && socket != null) {
logger.debug("Listener: Sending subscribe request");
socket.emit("subscribe", new JSONObject(sub));
}
}
/*
* Unsubscribe from weather data events for stations associated with the API key
*/
private void sendUnsubscribe() {
if (apiKey == null) {
return;
}
final String unsub = SUB_UNSUB_JSON.replace("%APIKEY%", apiKey);
if (isConnected && socket != null) {
logger.debug("Listener: Sending unsubscribe request");
socket.emit("unsubscribe", new JSONObject(unsub));
}
}
/*
* Resubscribe when a handler is initialized
*/
public synchronized void resubscribe() {
sendUnsubscribe();
sendSubscribe();
}
}

View File

@@ -0,0 +1,190 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.handler;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ambientweather.internal.config.StationConfig;
import org.openhab.binding.ambientweather.internal.processor.ProcessorFactory;
import org.openhab.binding.ambientweather.internal.processor.ProcessorNotFoundException;
import org.openhab.core.common.AbstractUID;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
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.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AmbientWeatherStationHandler} is responsible for processing
* info and weather data updates.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class AmbientWeatherStationHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(AmbientWeatherStationHandler.class);
// MAC address for weather station handled by this thing handler
private @Nullable String macAddress;
// Short name for logging station type
private String station;
// Time zone provider representing time zone configured in openHAB config
TimeZoneProvider timeZoneProvider;
public AmbientWeatherStationHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
super(thing);
this.timeZoneProvider = timeZoneProvider;
// Name of station thing type used in logging
String s = thing.getThingTypeUID().getAsString();
station = s.substring(s.indexOf(AbstractUID.SEPARATOR) + 1).toUpperCase();
}
@Override
public void initialize() {
macAddress = getConfigAs(StationConfig.class).macAddress;
logger.debug("Station {}: Initializing station handler for MAC {}", station, macAddress);
try {
ProcessorFactory.getProcessor(thing).setChannelGroupId();
ProcessorFactory.getProcessor(thing).setNumberOfSensors();
} catch (ProcessorNotFoundException e) {
logger.warn("Station {}: Unable to set channel group Id and/or number of sensors: {}", station,
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, e.getMessage());
return;
}
Thing bridge = getBridge();
if (bridge != null) {
logger.debug("Station {}: Set station status to match bridge status: {}", station, bridge.getStatus());
updateStatus(bridge.getStatus());
}
}
@Override
public void dispose() {
macAddress = getConfigAs(StationConfig.class).macAddress;
logger.debug("Station {}: Disposing station handler for MAC {}", station, macAddress);
updateStatus(ThingStatus.OFFLINE);
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
logger.debug("Station {}: Detected bridge status changed to '{}', Update my status", station, bridgeStatus);
if (bridgeStatus == ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else if (bridgeStatus == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}
/*
* Handle an update to the station name and location
*/
public void handleInfoEvent(String mac, String name, String location) {
logger.debug("Station {}: Update name={} and location={} for MAC {}", station, name, location, macAddress);
try {
ProcessorFactory.getProcessor(thing).processInfoUpdate(this, station, name, location);
} catch (ProcessorNotFoundException e) {
logger.debug("Unable to process info event: {}", e.getMessage());
}
}
/*
* Handle an update to the weather data.
*/
public void handleWeatherDataEvent(String jsonData) {
logger.debug("Station {}: Processing data event for MAC {}", station, macAddress);
try {
ProcessorFactory.getProcessor(thing).processWeatherData(this, station, jsonData);
} catch (ProcessorNotFoundException e) {
logger.debug("Unable to process weather data event: {}", e.getMessage());
}
}
public void updateQuantity(String groupId, String channelId, @Nullable Number value, Unit<?> unit) {
String channel = groupId + "#" + channelId;
if (value != null && isLinked(channel)) {
updateState(channel, new QuantityType<>(value, unit));
}
}
public void updateString(String groupId, String channelId, @Nullable String value) {
String channel = groupId + "#" + channelId;
if (value != null && isLinked(channel)) {
updateState(channel, new StringType(value));
}
}
public void updateNumber(String groupId, String channelId, @Nullable Number value) {
String channel = groupId + "#" + channelId;
if (value != null && isLinked(channel)) {
if (value instanceof Integer) {
updateState(channel, new DecimalType(value.intValue()));
} else if (value instanceof Double) {
updateState(channel, new DecimalType(value.doubleValue()));
}
}
}
public void updateDate(String groupId, String channelId, @Nullable String date) {
String channel = groupId + "#" + channelId;
if (date != null && isLinked(channel)) {
updateState(channel, getLocalDateTimeType(date, getZoneId()));
}
}
private DateTimeType getLocalDateTimeType(String dateTimeString, ZoneId zoneId) {
DateTimeType dateTimeType;
try {
Instant instant = Instant.parse(dateTimeString);
ZonedDateTime localDateTime = instant.atZone(zoneId);
dateTimeType = new DateTimeType(localDateTime);
} catch (DateTimeParseException e) {
logger.debug("Error parsing date/time string: {}", e.getMessage());
dateTimeType = new DateTimeType();
} catch (IllegalArgumentException e) {
logger.debug("Error converting to DateTimeType: {}", e.getMessage());
dateTimeType = new DateTimeType();
}
return dateTimeType;
}
private ZoneId getZoneId() {
return timeZoneProvider.getTimeZone();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Handler doesn't support any commands
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.model;
/**
* The {@link DeviceJson} is the JSON object returned
* by the Ambient Weather '/v1/devices' command.
* Note that there is nothing in the JSON that defines the type (e.g.
* WS-1400-IP, WS-8482, etc.) of weather station.
*
* @author Mark Hilbush - Initial Contribution
*/
public class DeviceJson {
/*
* The weather station's MAC address
*/
public String macAddress;
/*
* API key to which this device is associated
*/
public String apiKey;
/*
* User-supplied information about the station, such as name and location
*/
public StationInfoJson info;
/*
* Placeholder for weather data, which varies by station.
*/
public Object lastData;
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.model;
/**
* The {@link EventDataGenericJson} is the JSOn object
* returned by the Ambient Weather real-time API.
* The contents of the object varies by weather station, and
* all we care about here is getting the MAC address. The MAC
* address is used to decide which Station Thing Handler
* instance gets the weather data object.
*
* @author Mark Hilbush - Initial Contribution
*/
public class EventDataGenericJson {
/*
* The weather station's MAC address
*/
public String macAddress;
/*
* Placeholder for the rest of the JSON object.
*/
public Object data;
}

View File

@@ -0,0 +1,293 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.model;
/**
* The {@link EventDataJson} is the JSON object returned
* from the Ambient Weather real-time API for the weather stations
* whose weather data is hosted on ambientweather.net. This object is
* generated whenever the data for the station changes on
* ambientweather.net.
*
* @author Mark Hilbush - Initial Contribution
*/
public class EventDataJson {
/*
* The weather station's MAC address
*/
public String macAddress;
/*
* Date of the weather observation in UTC format
*/
public long dateutc;
/*
* Date of the weather observation as a string
*/
public String date;
/*
* Temperature from station in degrees F
*/
public Double tempf;
/*
* Real-feel temperature
*/
public Double feelsLike;
/*
* Dew point
*/
public Double dewPoint;
/*
* Relative humidity from station
*/
public Double humidity;
/*
* Relative barometric pressure in inches
*/
public Double baromrelin;
/*
* Absolute barometric pressure in inches
*/
public Double baromabsin;
/*
* Temperature from indoor sensor
*/
public Double tempinf;
/*
* Humidity from indoor sensor
*/
public Double humidityin;
/*
* Battery from indoor sensor
*/
public String battin;
/*
* Solar radiation
*/
public Double solarradiation;
/*
* UV Index
*/
public Integer uv;
/*
* Carbon dioxide
*/
public Double co;
/*
* Battery level
*/
public String battout;
/*
* Wind speed
* Instantaneous wind speed, mph
*/
public Double windspeedmph;
/*
* Wind direction
* Instantaneous wind direction, 0-360 degrees
*/
public Integer winddir;
/*
* Wind gust speed
* Max wind speed in the last 10 minutes, mph
*/
public Double windgustmph;
/*
* Wind gust direction
* Wind direction at which the wind gust occurred, 0-360 degrees
*/
public Integer windgustdir;
/*
* Maximum daily wind gust speed
* Maximum wind speed in last day, mph
*/
public Double maxdailygust;
/*
* Wind speed 2-minute average
* Average wind speed, 2 minute average, mph
* Name must contain underscore
*/
public Double windspdmph_avg2m;
/*
* Wind direction 2 minute average
* Average wind direction, 2 minute average, 0-360 degrees
* Name must contain underscore
*/
public Integer winddir_avg2m;
/*
* Wind speed 10-minute average
* Average wind speed, 10 minute average, mph
* Name must contain underscore
*/
public Double windspdmph_avg10m;
/*
* Wind direction 10 minute average
* Average wind direction, 10 minute average, 0-360 degrees
* Name must contain underscore
*/
public Integer winddir_avg10m;
/*
* Hourly rain rate in inches/hour
*/
public Double hourlyrainin;
/*
* Daily rain, inches
*/
public Double dailyrainin;
/*
* Weekly rain, inches
*/
public Double weeklyrainin;
/*
* Monthly rain, inches
*/
public Double monthlyrainin;
/*
* Yearly rain, inches
*/
public Double yearlyrainin;
/*
* Event rain, inches
*/
public Double eventrainin;
/*
* Rain in past 24 hours, inches
* Can't use since it starts with numbers
*/
// public double 24hourrainin;
/*
* Total rain since last factory reset, inches
*/
public Double totalrainin;
/*
* lastRain - Last time hourlyrainin > 0,
*/
public String lastRain;
/*
* Temperature from remote sensors 1-10
*/
public Double temp1f;
public Double temp2f;
public Double temp3f;
public Double temp4f;
public Double temp5f;
public Double temp6f;
public Double temp7f;
public Double temp8f;
public Double temp9f;
public Double temp10f;
/*
* Relative humidity from remote sensors 1-10
*/
public Double humidity1;
public Double humidity2;
public Double humidity3;
public Double humidity4;
public Double humidity5;
public Double humidity6;
public Double humidity7;
public Double humidity8;
public Double humidity9;
public Double humidity10;
/*
* Battery status of remote sensors 1-10
* Good/Bad indication, String, 1=Good, 0=Bad
*/
public String batt1;
public String batt2;
public String batt3;
public String batt4;
public String batt5;
public String batt6;
public String batt7;
public String batt8;
public String batt9;
public String batt10;
/*
* Relay 1-10
*/
public String relay1;
public String relay2;
public String relay3;
public String relay4;
public String relay5;
public String relay6;
public String relay7;
public String relay8;
public String relay9;
public String relay10;
/*
* Soil temperature from remote sensors 1-10
*/
public Double soiltemp1;
public Double soiltemp2;
public Double soiltemp3;
public Double soiltemp4;
public Double soiltemp5;
public Double soiltemp6;
public Double soiltemp7;
public Double soiltemp8;
public Double soiltemp9;
public Double soiltemp10;
/*
* Soil moisture from remote sensors 1-10
*/
public Double soilhum1;
public Double soilhum2;
public Double soilhum3;
public Double soilhum4;
public Double soilhum5;
public Double soilhum6;
public Double soilhum7;
public Double soilhum8;
public Double soilhum9;
public Double soilhum10;
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.model;
/**
* The {@link EventSubscribedJson} is the JSON object
* returned by the Ambient Weather real-time API in response to
* the 'subscribe' and 'unsubscribe' commands.
*
* @author Mark Hilbush - Initial Contribution
*/
public class EventSubscribedJson {
/*
* method is "subscribed"
*/
public String method;
/*
* Array of devices
*/
public Object devices;
/*
* Array of invalid API Keys
*/
public Object invalidApiKeys;
public String getMethod() {
return method;
}
public boolean isMethodSubscribe() {
return "subscribe".equals(method);
}
public boolean isMethodUnsubscribe() {
return "unsubscribe".equals(method);
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.model;
/**
* The {@link StationInfoJson} is the JSON object
* returned by the Ambient Weather API that describes the
* user-provided name and location of the weather station.
*
* @author Mark Hilbush - Initial Contribution
*/
public class StationInfoJson {
/*
* The name given to the station by the user
*/
public String name;
/*
* The location given to the station by the user
*/
public String location;
}

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ambientweather.internal.model.EventDataJson;
import org.openhab.binding.ambientweather.internal.util.PressureTrend;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonSyntaxException;
/**
* The {@link AbstractProcessor} is the generic/error processor
* for info and weather updates from weather stations that are currently
* not supported by this binding.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public abstract class AbstractProcessor implements ProcessorInterface {
// @formatter:off
private static final String[] UV_INDEX = {
"LOW",
"LOW",
"LOW",
"MODERATE",
"MODERATE",
"MODERATE",
"HIGH",
"HIGH",
"VERY HIGH",
"VERY HIGH",
"VERY HIGH",
"EXTREME",
"EXTREME",
"EXTREME",
"EXTREME",
"EXTREME"
};
// @formatter:on
private static final String[] WIND_DIRECTIONS = new String[] { "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
"S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW" };
protected static final String NOT_APPLICABLE = "N/A";
protected final Logger logger = LoggerFactory.getLogger(getClass());
// Used to calculate barometric pressure trend
protected PressureTrend pressureTrend = new PressureTrend();
/*
* The channel group Id for this processor
*/
protected String channelGroupId = "";
/*
* Used to extract remote sensor data from the data event Json
*/
protected RemoteSensor remoteSensor = new RemoteSensor();
/*
* Parse the event data json string
*/
protected @Nullable EventDataJson parseEventData(String station, String jsonData) {
EventDataJson data = null;
try {
logger.debug("Station {}: Parsing weather data event json", station);
data = ProcessorFactory.getGson().fromJson(jsonData, EventDataJson.class);
} catch (JsonSyntaxException e) {
logger.info("Station {}: Data event cannot be parsed: {}", station, e.getMessage());
}
return data;
}
/*
* Convert the UV Index integer value to a string representation
*/
protected String convertUVIndexToString(int uvIndex) {
if (uvIndex < 0 || uvIndex >= UV_INDEX.length) {
return "UNKNOWN";
}
return UV_INDEX[uvIndex];
}
/*
* Convert the wind directions in degrees to a string representation
*/
protected String convertWindDirectionToString(double windDirectionDegrees) {
double step = 360.0 / WIND_DIRECTIONS.length;
double b = Math.floor((windDirectionDegrees + (step / 2.0)) / step);
return WIND_DIRECTIONS[(int) (b % WIND_DIRECTIONS.length)];
}
}

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
import com.google.gson.Gson;
/**
* The {@link ProcessorFactory} is responsible for returning the right
* processor for handling info update and weather data events.
*
* There is one processor for each supported station type.
* To add support for a new station type, this class needs to be modified to
* return a processor for the new thing type. In addition, an
* AmbientWeatherXXXXXProcessor class needs to be created for the new station.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class ProcessorFactory {
// Single Gson instance shared by processors
private static final Gson GSON = new Gson();
// Supported weather stations
private @Nullable static Ws1400ipProcessor WS1400IP_PROCESSOR;
private @Nullable static Ws2902aProcessor WS2902A_PROCESSOR;
private @Nullable static Ws8482Processor WS8482_PROCESSOR;
private @Nullable static Ws0900ipProcessor WS0900IP_PROCESSOR;
private @Nullable static Ws0265Processor WS0265_PROCESSOR;
/**
* Individual weather station processors use this one Gson instance,
* rather than create their own.
*
* @return instance of a Gson object
*/
public static Gson getGson() {
return (GSON);
}
/**
* Get a processor for a specific weather station type.
*
* @param thing
* @return instance of a weather station processor
* @throws ProcessorNotFoundException
*/
public static AbstractProcessor getProcessor(Thing thing) throws ProcessorNotFoundException {
// Return the processor for this thing type
String thingType = thing.getThingTypeUID().getAsString().toLowerCase();
switch (thingType) {
case "ambientweather:ws1400ip": {
Ws1400ipProcessor processor = WS1400IP_PROCESSOR;
if (processor == null) {
processor = new Ws1400ipProcessor();
WS1400IP_PROCESSOR = processor;
}
return processor;
}
case "ambientweather:ws2902a": {
Ws2902aProcessor processor = WS2902A_PROCESSOR;
if (processor == null) {
processor = new Ws2902aProcessor();
WS2902A_PROCESSOR = processor;
}
return processor;
}
case "ambientweather:ws8482": {
Ws8482Processor processor = WS8482_PROCESSOR;
if (processor == null) {
processor = new Ws8482Processor();
WS8482_PROCESSOR = processor;
}
return processor;
}
case "ambientweather:ws0900ip": {
Ws0900ipProcessor processor = WS0900IP_PROCESSOR;
if (processor == null) {
processor = new Ws0900ipProcessor();
WS0900IP_PROCESSOR = processor;
}
return processor;
}
case "ambientweather:ws0265": {
Ws0265Processor processor = WS0265_PROCESSOR;
if (processor == null) {
processor = new Ws0265Processor();
WS0265_PROCESSOR = processor;
}
return processor;
}
}
throw new ProcessorNotFoundException("No processor for thing type " + thingType);
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ambientweather.internal.handler.AmbientWeatherStationHandler;
/**
* The {@link ProcessorInterface} is responsible for
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public interface ProcessorInterface {
/*
* Set the channel group Id for the station
*/
public void setChannelGroupId();
/*
* Set the number of remote sensors supported by the station
*/
public void setNumberOfSensors();
/*
* Updates the info channels (i.e. name and location) for a station
*/
public void processInfoUpdate(AmbientWeatherStationHandler handler, String station, String name, String location);
/*
* Updates the weather data channels for a station
*/
public void processWeatherData(AmbientWeatherStationHandler handler, String station, String jsonData);
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
/**
* The {@link ProcessorNotFoundException} is thrown when a data event is
* received for which there's no processor that can process the event.
*
* @author Mark Hilbush - Initial contribution
*/
public class ProcessorNotFoundException extends Exception {
private static final long serialVersionUID = -7522973666620330850L;
public ProcessorNotFoundException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,137 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
import static org.openhab.binding.ambientweather.internal.AmbientWeatherBindingConstants.*;
import java.io.IOException;
import java.io.StringReader;
import java.util.TreeMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ambientweather.internal.handler.AmbientWeatherStationHandler;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.stream.JsonReader;
/**
* The {@link RemoteSensor} is responsible for updating the remote sensor
* channels for the remote sensors attached to a weather station.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class RemoteSensor {
// Maximum number of remote sensors that can be supported by a station
private static final int MAX_SENSORS = 10;
private final Logger logger = LoggerFactory.getLogger(RemoteSensor.class);
private final TreeMap<Double, String> soilMoistureMap = new TreeMap<>();
private int numberOfSensors;
public RemoteSensor() {
initializeSoilMoistureMap();
}
public void setNumberOfSensors(int numberOfSensors) {
if (numberOfSensors < 1 || numberOfSensors > MAX_SENSORS) {
throw new IllegalArgumentException("Invalid number of sensors");
}
this.numberOfSensors = numberOfSensors;
}
public void updateChannels(AmbientWeatherStationHandler handler, final @Nullable String jsonData) {
if (jsonData == null) {
throw new IllegalArgumentException("Json data is null");
}
if (numberOfSensors < 1 || numberOfSensors > MAX_SENSORS) {
throw new IllegalStateException("Number of sensors has not been set");
}
for (int sensorNumber = 1; sensorNumber <= numberOfSensors; sensorNumber++) {
updateSensorChannels(handler, sensorNumber, jsonData);
}
}
/*
* Iterate through the JSON object and update the channels for which
* there are remote sensor values
*/
private void updateSensorChannels(AmbientWeatherStationHandler handler, int i, final @Nullable String jsonData) {
String sensorNumber = String.valueOf(i);
StringReader stringReader = new StringReader(jsonData);
JsonReader reader = new JsonReader(stringReader);
try {
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (("temp" + sensorNumber + "f").equals(name)) {
handler.updateQuantity(CHGRP_REMOTE_SENSOR + sensorNumber, CH_TEMPERATURE, reader.nextDouble(),
ImperialUnits.FAHRENHEIT);
} else if (("dewPoint" + sensorNumber).equals(name)) {
handler.updateQuantity(CHGRP_REMOTE_SENSOR + sensorNumber, CH_DEW_POINT, reader.nextDouble(),
ImperialUnits.FAHRENHEIT);
} else if (("feelsLike" + sensorNumber).equals(name)) {
handler.updateQuantity(CHGRP_REMOTE_SENSOR + sensorNumber, CH_FEELING_TEMPERATURE,
reader.nextDouble(), ImperialUnits.FAHRENHEIT);
} else if (("humidity" + sensorNumber).equals(name)) {
handler.updateQuantity(CHGRP_REMOTE_SENSOR + sensorNumber, CH_HUMIDITY, reader.nextDouble(),
SmartHomeUnits.PERCENT);
} else if (("soiltemp" + sensorNumber).equals(name)) {
handler.updateQuantity(CHGRP_REMOTE_SENSOR + sensorNumber, CH_SOIL_TEMPERATURE, reader.nextDouble(),
ImperialUnits.FAHRENHEIT);
} else if (("soilhum" + sensorNumber).equals(name)) {
Double soilMoisture = reader.nextDouble();
handler.updateQuantity(CHGRP_REMOTE_SENSOR + sensorNumber, CH_SOIL_MOISTURE, soilMoisture,
SmartHomeUnits.PERCENT);
handler.updateString(CHGRP_REMOTE_SENSOR + sensorNumber, CH_SOIL_MOISTURE_LEVEL,
convertSoilMoistureToString(soilMoisture));
} else if (("batt" + sensorNumber).equals(name)) {
handler.updateString(CHGRP_REMOTE_SENSOR + sensorNumber, CH_BATTERY_INDICATOR, reader.nextString());
} else {
reader.skipValue();
}
}
} catch (IOException e) {
logger.debug("IOException from JsonReader: {}", e.getMessage(), e);
} finally {
try {
reader.close();
stringReader.close();
} catch (IOException e) {
// Eat the exception
}
}
}
private void initializeSoilMoistureMap() {
soilMoistureMap.put(33.0, "VERY DRY");
soilMoistureMap.put(60.0, "DRY");
soilMoistureMap.put(80.0, "MOIST");
soilMoistureMap.put(93.0, "WET");
soilMoistureMap.put(100.0, "VERY WET");
}
/*
* Convert the soil moisture to a string representation
*/
private String convertSoilMoistureToString(double soilMoisture) {
Double key = soilMoistureMap.ceilingKey(soilMoisture);
return key == null ? "UNKNOWN" : soilMoistureMap.get(key);
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
import static org.openhab.binding.ambientweather.internal.AmbientWeatherBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ambientweather.internal.handler.AmbientWeatherStationHandler;
import org.openhab.binding.ambientweather.internal.model.EventDataJson;
import org.openhab.core.library.unit.ImperialUnits;
/**
* The {@link Ws0265Processor} is responsible for updating
* the channels associated with the WS-0265 series weather stations in
* response to the receipt of a weather data update from the Ambient
* Weather real-time API.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class Ws0265Processor extends AbstractProcessor {
@Override
public void setChannelGroupId() {
channelGroupId = CHGRP_WS0265;
}
@Override
public void setNumberOfSensors() {
remoteSensor.setNumberOfSensors(7);
}
@Override
public void processInfoUpdate(AmbientWeatherStationHandler handler, String station, String name, String location) {
// Update name and location channels
handler.updateString(CHGRP_STATION, CH_NAME, name);
handler.updateString(CHGRP_STATION, CH_LOCATION, location);
}
@Override
public void processWeatherData(AmbientWeatherStationHandler handler, String station, String jsonData) {
EventDataJson data = parseEventData(station, jsonData);
if (data == null) {
return;
}
// Update the weather data channels
handler.updateDate(channelGroupId, CH_OBSERVATION_TIME, data.date);
handler.updateQuantity(channelGroupId, CH_TEMPERATURE, data.tempinf, ImperialUnits.FAHRENHEIT);
// Update the remote sensor channels
remoteSensor.updateChannels(handler, jsonData);
}
}

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
import static org.openhab.binding.ambientweather.internal.AmbientWeatherBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ambientweather.internal.handler.AmbientWeatherStationHandler;
import org.openhab.binding.ambientweather.internal.model.EventDataJson;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* The {@link Ws0900ipProcessor} is responsible for updating
* the channels associated with the WS-0900-IP weather stations in
* response to the receipt of a weather data update from the Ambient
* Weather real-time API.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class Ws0900ipProcessor extends AbstractProcessor {
@Override
public void setChannelGroupId() {
channelGroupId = CHGRP_WS0900IP;
}
@Override
public void setNumberOfSensors() {
// This station doesn't support remote sensors
}
@Override
public void processInfoUpdate(AmbientWeatherStationHandler handler, String station, String name, String location) {
// Update name and location channels
handler.updateString(CHGRP_STATION, CH_NAME, name);
handler.updateString(CHGRP_STATION, CH_LOCATION, location);
}
@Override
public void processWeatherData(AmbientWeatherStationHandler handler, String station, String jsonData) {
EventDataJson data = parseEventData(station, jsonData);
if (data == null) {
return;
}
// Update the weather data channels
handler.updateDate(channelGroupId, CH_OBSERVATION_TIME, data.date);
handler.updateString(channelGroupId, CH_BATTERY_INDICATOR, NOT_APPLICABLE);
handler.updateQuantity(channelGroupId, CH_TEMPERATURE, data.tempf, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(channelGroupId, CH_DEW_POINT, data.dewPoint, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(channelGroupId, CH_HUMIDITY, data.humidity, SmartHomeUnits.PERCENT);
handler.updateQuantity(channelGroupId, CH_PRESSURE_ABSOLUTE, data.baromabsin, ImperialUnits.INCH_OF_MERCURY);
handler.updateQuantity(channelGroupId, CH_PRESSURE_RELATIVE, data.baromrelin, ImperialUnits.INCH_OF_MERCURY);
handler.updateQuantity(channelGroupId, CH_WIND_SPEED, data.windspeedmph, ImperialUnits.MILES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_WIND_DIRECTION_DEGREES, data.winddir, SmartHomeUnits.DEGREE_ANGLE);
handler.updateQuantity(channelGroupId, CH_WIND_GUST, data.windgustmph, ImperialUnits.MILES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_WIND_GUST_MAX_DAILY, data.maxdailygust, ImperialUnits.MILES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_RAIN_HOURLY_RATE, data.hourlyrainin, SmartHomeUnits.INCHES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_RAIN_DAY, data.dailyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_WEEK, data.weeklyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_MONTH, data.monthlyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_YEAR, data.yearlyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_TOTAL, data.totalrainin, ImperialUnits.INCH);
handler.updateDate(channelGroupId, CH_RAIN_LAST_TIME, data.lastRain);
// Update calculated channels
if (data.baromrelin != null) {
pressureTrend.put(data.baromrelin);
handler.updateString(channelGroupId, CH_PRESSURE_TREND, pressureTrend.getPressureTrend());
}
if (data.winddir != null) {
handler.updateString(channelGroupId, CH_WIND_DIRECTION, convertWindDirectionToString(data.winddir));
}
// Update indoor sensor channels
handler.updateQuantity(CHGRP_INDOOR_SENSOR, CH_TEMPERATURE, data.tempinf, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(CHGRP_INDOOR_SENSOR, CH_HUMIDITY, data.humidityin, SmartHomeUnits.PERCENT);
handler.updateString(CHGRP_INDOOR_SENSOR, CH_BATTERY_INDICATOR, NOT_APPLICABLE);
}
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
import static org.openhab.binding.ambientweather.internal.AmbientWeatherBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ambientweather.internal.handler.AmbientWeatherStationHandler;
import org.openhab.binding.ambientweather.internal.model.EventDataJson;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* The {@link Ws1400ipProcessor} is responsible for updating
* the channels associated with the WS-1400-IP series weather stations in
* response to the receipt of a weather data update from the Ambient
* Weather real-time API.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class Ws1400ipProcessor extends AbstractProcessor {
@Override
public void setChannelGroupId() {
channelGroupId = CHGRP_WS1400IP;
}
@Override
public void setNumberOfSensors() {
remoteSensor.setNumberOfSensors(8);
}
@Override
public void processInfoUpdate(AmbientWeatherStationHandler handler, String station, String name, String location) {
// Update name and location channels
handler.updateString(CHGRP_STATION, CH_NAME, name);
handler.updateString(CHGRP_STATION, CH_LOCATION, location);
}
@Override
public void processWeatherData(AmbientWeatherStationHandler handler, String station, String jsonData) {
EventDataJson data = parseEventData(station, jsonData);
if (data == null) {
return;
}
// Update the weather data channels
handler.updateDate(channelGroupId, CH_OBSERVATION_TIME, data.date);
handler.updateString(channelGroupId, CH_BATTERY_INDICATOR, data.battout);
handler.updateQuantity(channelGroupId, CH_TEMPERATURE, data.tempf, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(channelGroupId, CH_FEELING_TEMPERATURE, data.feelsLike, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(channelGroupId, CH_DEW_POINT, data.dewPoint, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(channelGroupId, CH_HUMIDITY, data.humidity, SmartHomeUnits.PERCENT);
handler.updateQuantity(channelGroupId, CH_PRESSURE_ABSOLUTE, data.baromabsin, ImperialUnits.INCH_OF_MERCURY);
handler.updateQuantity(channelGroupId, CH_PRESSURE_RELATIVE, data.baromrelin, ImperialUnits.INCH_OF_MERCURY);
handler.updateQuantity(channelGroupId, CH_WIND_SPEED, data.windspeedmph, ImperialUnits.MILES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_WIND_DIRECTION_DEGREES, data.winddir, SmartHomeUnits.DEGREE_ANGLE);
handler.updateQuantity(channelGroupId, CH_WIND_GUST, data.windgustmph, ImperialUnits.MILES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_WIND_GUST_MAX_DAILY, data.maxdailygust, ImperialUnits.MILES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_SOLAR_RADIATION, data.solarradiation, SmartHomeUnits.IRRADIANCE);
handler.updateNumber(channelGroupId, CH_UV_INDEX, data.uv);
handler.updateQuantity(channelGroupId, CH_RAIN_HOURLY_RATE, data.hourlyrainin, SmartHomeUnits.INCHES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_RAIN_DAY, data.dailyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_WEEK, data.weeklyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_MONTH, data.monthlyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_YEAR, data.yearlyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_TOTAL, data.totalrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_EVENT, data.eventrainin, ImperialUnits.INCH);
handler.updateDate(channelGroupId, CH_RAIN_LAST_TIME, data.lastRain);
// Update calculated channels
if (data.baromrelin != null) {
pressureTrend.put(data.baromrelin);
handler.updateString(channelGroupId, CH_PRESSURE_TREND, pressureTrend.getPressureTrend());
}
if (data.winddir != null) {
handler.updateString(channelGroupId, CH_WIND_DIRECTION, convertWindDirectionToString(data.winddir));
}
if (data.uv != null) {
handler.updateString(channelGroupId, CH_UV_DANGER, convertUVIndexToString(data.uv));
}
// Update indoor sensor channels
handler.updateQuantity(CHGRP_INDOOR_SENSOR, CH_TEMPERATURE, data.tempinf, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(CHGRP_INDOOR_SENSOR, CH_HUMIDITY, data.humidityin, SmartHomeUnits.PERCENT);
handler.updateString(CHGRP_INDOOR_SENSOR, CH_BATTERY_INDICATOR, data.battin);
// Update channels for the remote sensors
remoteSensor.updateChannels(handler, jsonData);
}
}

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
import static org.openhab.binding.ambientweather.internal.AmbientWeatherBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ambientweather.internal.handler.AmbientWeatherStationHandler;
import org.openhab.binding.ambientweather.internal.model.EventDataJson;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* The {@link Ws2902aProcessor} is responsible for updating
* the channels associated with the WS-2902A weather stations in
* response to the receipt of a weather data update from the Ambient
* Weather real-time API.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class Ws2902aProcessor extends AbstractProcessor {
@Override
public void setChannelGroupId() {
channelGroupId = CHGRP_WS2902A;
}
@Override
public void setNumberOfSensors() {
// This station doesn't support remote sensors
}
@Override
public void processInfoUpdate(AmbientWeatherStationHandler handler, String station, String name, String location) {
// Update name and location channels
handler.updateString(CHGRP_STATION, CH_NAME, name);
handler.updateString(CHGRP_STATION, CH_LOCATION, location);
}
@Override
public void processWeatherData(AmbientWeatherStationHandler handler, String station, String jsonData) {
EventDataJson data = parseEventData(station, jsonData);
if (data == null) {
return;
}
// Update the weather data channels
handler.updateDate(channelGroupId, CH_OBSERVATION_TIME, data.date);
handler.updateString(channelGroupId, CH_BATTERY_INDICATOR, NOT_APPLICABLE);
handler.updateQuantity(channelGroupId, CH_TEMPERATURE, data.tempf, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(channelGroupId, CH_FEELING_TEMPERATURE, data.feelsLike, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(channelGroupId, CH_DEW_POINT, data.dewPoint, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(channelGroupId, CH_HUMIDITY, data.humidity, SmartHomeUnits.PERCENT);
handler.updateQuantity(channelGroupId, CH_PRESSURE_ABSOLUTE, data.baromabsin, ImperialUnits.INCH_OF_MERCURY);
handler.updateQuantity(channelGroupId, CH_PRESSURE_RELATIVE, data.baromrelin, ImperialUnits.INCH_OF_MERCURY);
handler.updateQuantity(channelGroupId, CH_WIND_SPEED, data.windspeedmph, ImperialUnits.MILES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_WIND_DIRECTION_DEGREES, data.winddir, SmartHomeUnits.DEGREE_ANGLE);
handler.updateQuantity(channelGroupId, CH_WIND_GUST, data.windgustmph, ImperialUnits.MILES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_WIND_GUST_MAX_DAILY, data.maxdailygust, ImperialUnits.MILES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_SOLAR_RADIATION, data.solarradiation, SmartHomeUnits.IRRADIANCE);
handler.updateNumber(channelGroupId, CH_UV_INDEX, data.uv);
handler.updateQuantity(channelGroupId, CH_RAIN_HOURLY_RATE, data.hourlyrainin, SmartHomeUnits.INCHES_PER_HOUR);
handler.updateQuantity(channelGroupId, CH_RAIN_DAY, data.dailyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_WEEK, data.weeklyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_MONTH, data.monthlyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_YEAR, data.yearlyrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_TOTAL, data.totalrainin, ImperialUnits.INCH);
handler.updateQuantity(channelGroupId, CH_RAIN_EVENT, data.eventrainin, ImperialUnits.INCH);
handler.updateDate(channelGroupId, CH_RAIN_LAST_TIME, data.lastRain);
// Update calculated channels
if (data.baromrelin != null) {
pressureTrend.put(data.baromrelin);
handler.updateString(channelGroupId, CH_PRESSURE_TREND, pressureTrend.getPressureTrend());
}
if (data.winddir != null) {
handler.updateString(channelGroupId, CH_WIND_DIRECTION, convertWindDirectionToString(data.winddir));
}
if (data.uv != null) {
handler.updateString(channelGroupId, CH_UV_DANGER, convertUVIndexToString(data.uv));
}
// Update indoor sensor channels
handler.updateQuantity(CHGRP_INDOOR_SENSOR, CH_TEMPERATURE, data.tempinf, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(CHGRP_INDOOR_SENSOR, CH_HUMIDITY, data.humidityin, SmartHomeUnits.PERCENT);
handler.updateString(CHGRP_INDOOR_SENSOR, CH_BATTERY_INDICATOR, NOT_APPLICABLE);
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.processor;
import static org.openhab.binding.ambientweather.internal.AmbientWeatherBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ambientweather.internal.handler.AmbientWeatherStationHandler;
import org.openhab.binding.ambientweather.internal.model.EventDataJson;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* The {@link Ws8482Processor} is responsible for updating
* the channels associated with the WS-1400-IP series weather stations in
* response to the receipt of a weather data update from the Ambient
* Weather real-time API.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class Ws8482Processor extends AbstractProcessor {
@Override
public void setChannelGroupId() {
channelGroupId = CHGRP_WS8482;
}
@Override
public void setNumberOfSensors() {
remoteSensor.setNumberOfSensors(7);
}
@Override
public void processInfoUpdate(AmbientWeatherStationHandler handler, String station, String name, String location) {
// Update name and location channels
handler.updateString(CHGRP_STATION, CH_NAME, name);
handler.updateString(CHGRP_STATION, CH_LOCATION, location);
}
@Override
public void processWeatherData(AmbientWeatherStationHandler handler, String station, String jsonData) {
EventDataJson data = parseEventData(station, jsonData);
if (data == null) {
return;
}
// Update the weather data channels
handler.updateDate(channelGroupId, CH_OBSERVATION_TIME, data.date);
handler.updateString(channelGroupId, CH_BATTERY_INDICATOR, data.battout);
handler.updateQuantity(channelGroupId, CH_TEMPERATURE, data.tempinf, ImperialUnits.FAHRENHEIT);
handler.updateQuantity(channelGroupId, CH_HUMIDITY, data.humidityin, SmartHomeUnits.PERCENT);
// Update the remote sensor channels
remoteSensor.updateChannels(handler, jsonData);
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.util;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PressureTrend} is responsible for determining the 3 hour
* barometric pressure trend. All calculations are in inches of Mercury.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class PressureTrend extends SlidingTimeWindow<Double> {
private final Logger logger = LoggerFactory.getLogger(PressureTrend.class);
// Pressure trend is established after 3 hours
private static final long PRESSURE_TREND_PERIOD = 1000 * 60 * 60 * 3;
// Thresholds used to determine pressure trends
private static final double RAPIDLY_THRESHOLD = 0.06;
private static final double STEADY_THRESHOLD = 0.02;
// Pressure trends
private static final String RISING_RAPIDLY = "RISING RAPIDLY";
private static final String RISING = "RISING";
private static final String FALLING_RAPIDLY = "FALLING RAPIDLY";
private static final String FALLING = "FALLING";
private static final String STEADY = "STEADY";
private static final String UNKNOWN = "UNKNOWN";
public PressureTrend() {
super(PRESSURE_TREND_PERIOD);
}
public String getPressureTrend() {
long firstTime;
long lastTime;
double firstValue;
double lastValue;
if (storage.isEmpty()) {
return UNKNOWN;
}
synchronized (storage) {
firstTime = storage.firstKey();
lastTime = storage.lastKey();
firstValue = storage.get(storage.firstKey()).doubleValue();
lastValue = storage.get(storage.lastKey()).doubleValue();
}
if (lastTime - firstTime < period * 0.99) {
// Not within 1% of time period
return UNKNOWN;
}
double pressureDifference = lastValue - firstValue;
String pressureTrend;
if (pressureDifference > RAPIDLY_THRESHOLD) {
pressureTrend = RISING_RAPIDLY;
} else if (pressureDifference > STEADY_THRESHOLD && pressureDifference <= RAPIDLY_THRESHOLD) {
pressureTrend = RISING;
} else if (pressureDifference > -STEADY_THRESHOLD && pressureDifference <= STEADY_THRESHOLD) {
pressureTrend = STEADY;
} else if (pressureDifference > -RAPIDLY_THRESHOLD && pressureDifference <= -STEADY_THRESHOLD) {
pressureTrend = FALLING;
} else {
pressureTrend = FALLING_RAPIDLY;
}
removeOldEntries();
return pressureTrend;
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 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.ambientweather.internal.util;
import java.util.Collections;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SlidingTimeWindow} is responsible for managing a set of
* time-based values. This class is used to calculate trends over time.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
class SlidingTimeWindow<T> {
protected long period = 0;
protected final SortedMap<Long, T> storage = Collections.synchronizedSortedMap(new TreeMap<>());
/**
* Create a sliding time window for the provided time period
*/
public SlidingTimeWindow(long period) {
this.period = period;
}
public void put(T value) {
storage.put(System.currentTimeMillis(), value);
}
public void removeOldEntries() {
long old = System.currentTimeMillis() - period;
synchronized (storage) {
for (Iterator<Long> iterator = storage.keySet().iterator(); iterator.hasNext();) {
long time = iterator.next();
if (time < old) {
iterator.remove();
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="ambientweather" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>AmbientWeather Binding</name>
<description>This binding supports weather stations from Ambient Weather</description>
<author>Mark Hilbush</author>
</binding:binding>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:ambientweather:bridge">
<parameter name="applicationKey" type="text" required="true">
<label>Application Key</label>
<description>Enter the application key</description>
</parameter>
<parameter name="apiKey" type="text" required="true">
<label>API Key</label>
<description>Enter the API key</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:ambientweather:station">
<parameter name="macAddress" type="text" required="true">
<label>MAC Address</label>
<description>Enter the MAC address of the weather station</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,15 @@
# binding
binding.ambientweather.name = Ambient Weather Binding
binding.ambientweather.description = Binding supporting weather stations from Ambient Weather
# thing types
thing-type.ambientweather.bridge.label = Ambient Weather Bridge
thing-type.ambientweather.bridge.description = Ambient Weather Bridge
thing-type.ambientweather.ws1400ip.label = Ambient Weather WS-1400-IP
thing-type.ambientweather.ws1400ip.description = Ambient Weather WS-1400-IP Weather Station
thing-type.ambientweather.ws8482.label = Ambient Weather WS-8482
thing-type.ambientweather.ws8482.description = Ambient Weather WS-8482 Weather Station
thing-type.ambientweather.ws2902a.label = Ambient Weather WS-2902A
thing-type.ambientweather.ws2902a.description = Ambient Weather WS-2902A Weather Station
thing-type.ambientweather.ws0900ip.label = Ambient Weather WS-0900-IP
thing-type.ambientweather.ws0900ip.description = Ambient Weather WS-0900-IP Weather Station

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="ambientweather"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Ambient Weather Account -->
<bridge-type id="bridge">
<label>Ambient Weather Account</label>
<description>Represents an account at Ambient Weather (ambientweather.net)</description>
<config-description-ref uri="thing-type:ambientweather:bridge"/>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="ambientweather"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Channel group types in common for all weather stations -->
<!-- The channel group type for a specific station is in the xml file for that station -->
<channel-group-type id="station">
<label>Station Information</label>
<description>Station Information</description>
<channels>
<channel id="name" typeId="stationName"/>
<channel id="location" typeId="location"/>
</channels>
</channel-group-type>
<channel-group-type id="indoorSensor">
<label>Indoor Sensor</label>
<description>Indoor Sensor</description>
<channels>
<channel id="temperature" typeId="temperature"/>
<channel id="relativeHumidity" typeId="relativeHumidity"/>
<channel id="batteryIndicator" typeId="batteryIndicator"/>
</channels>
</channel-group-type>
<channel-group-type id="remoteSensor">
<label>Remote Sensor</label>
<description>Remote Sensor</description>
<channels>
<channel id="temperature" typeId="temperature"/>
<channel id="dewPoint" typeId="dewPoint"/>
<channel id="feelingTemperature" typeId="feelingTemperature"/>
<channel id="relativeHumidity" typeId="relativeHumidity"/>
<channel id="soilTemperature" typeId="soilTemperature"/>
<channel id="soilMoisture" typeId="soilMoisture"/>
<channel id="soilMoistureLevel" typeId="soilMoistureLevel"/>
<channel id="batteryIndicator" typeId="batteryIndicator"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,320 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="ambientweather"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="stationName">
<item-type>String</item-type>
<label>Name</label>
<description>Weather station name</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="location">
<item-type>String</item-type>
<label>Location</label>
<description>Weather station location</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="observationTime">
<item-type>DateTime</item-type>
<label>Observation Time</label>
<description>Observation date and time</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="dewPoint">
<item-type>Number:Temperature</item-type>
<label>Dew Point Temperature</label>
<description>Dew Point temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="feelingTemperature">
<item-type>Number:Temperature</item-type>
<label>Feeling Temperature</label>
<description>Feeling temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="relativeHumidity">
<item-type>Number:Dimensionless</item-type>
<label>Relative Humidity</label>
<description>Forecast relative humidity</description>
<category>Humidity</category>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="pressure">
<item-type>Number:Pressure</item-type>
<label>Pressure</label>
<description>Barometric Pressure</description>
<category>Pressure</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="pressureTrend">
<item-type>String</item-type>
<label>Pressure Trend</label>
<description>Pressure evolution trend (up, down, stable)</description>
<category>Pressure</category>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="rainHourlyRate">
<item-type>Number:Speed</item-type>
<label>Rain Fall Hourly Rate</label>
<description>Rain fall hourly rate</description>
<category>Rain</category>
<state readOnly="true" pattern="%.7f %unit%"/>
</channel-type>
<channel-type id="rainDay">
<item-type>Number:Length</item-type>
<label>Rain Fall Day</label>
<description>Rain fall during the day</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="rainWeek">
<item-type>Number:Length</item-type>
<label>Rain Fall Week</label>
<description>Rain fall amount for the week</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="rainMonth">
<item-type>Number:Length</item-type>
<label>Rain Fall Month</label>
<description>Rain fall amount for the month</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="rainYear">
<item-type>Number:Length</item-type>
<label>Rain Fall Year</label>
<description>Rain fall amount for the year</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="rainTotal">
<item-type>Number:Length</item-type>
<label>Rain Fall Total</label>
<description>Rain fall total amount since factpry reset</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="rain24Hour">
<item-type>Number:Length</item-type>
<label>Rain Fall 24 Hours</label>
<description>Rain fall in last 24 hours</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="rainEvent">
<item-type>Number:Length</item-type>
<label>Rain Fall Event</label>
<description>Rain fall total for last rain event</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="rainLastTime">
<item-type>DateTime</item-type>
<label>Rain Last Time</label>
<description>Date and time of last rainfall</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="windSpeed">
<item-type>Number:Speed</item-type>
<label>Wind Speed</label>
<description>Wind speed</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="windSpeedMaxDaily">
<item-type>Number:Speed</item-type>
<label>Maximum Daily Wind Speed</label>
<description>Maximum daily wind speed</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="windDirectionDegrees">
<item-type>Number:Angle</item-type>
<label>Wind Direction Degrees</label>
<description>Wind direction in degrees</description>
<state readOnly="true" min="0" max="360" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="windDirection">
<item-type>String</item-type>
<label>Wind Direction</label>
<description>Wind direction</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="windGust">
<item-type>Number:Speed</item-type>
<label>Wind Gust</label>
<description>Wind gust</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="windGustDirectionDegrees">
<item-type>Number:Angle</item-type>
<label>Wind Gust Direction Degrees</label>
<description>Wind gust direction in degrees</description>
<state readOnly="true" min="0" max="360" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="windGustDirection">
<item-type>String</item-type>
<label>Wind Gust Direction</label>
<description>Wind gust direction</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="windGustMaxDaily">
<item-type>Number:Speed</item-type>
<label>Maximum Daily Wind Gust</label>
<description>Maximum daily wind gust</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="windSpeedAvg2Minute">
<item-type>Number:Speed</item-type>
<label>Wind Speed 2-Minute</label>
<description>Wind speed 2-minute average</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="windDirectionDegreesAvg2Minute">
<item-type>Number:Angle</item-type>
<label>Wind Direction Degrees 2-minute</label>
<description>Wind direction 2-minute average in degrees</description>
<state readOnly="true" min="0" max="360" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="windDirectionAvg2Minute">
<item-type>String</item-type>
<label>Wind Direction 2-minute</label>
<description>Wind direction 2-minute Average</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="windSpeedAvg10Minute">
<item-type>Number:Speed</item-type>
<label>Wind Speed 10-Minute</label>
<description>Wind speed 10-minute average</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="windDirectionDegreesAvg10Minute">
<item-type>Number:Angle</item-type>
<label>Wind Direction Degrees 10-minute</label>
<description>Wind direction 10-minute average in degrees</description>
<state readOnly="true" min="0" max="360" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="windDirectionAvg10Minute">
<item-type>String</item-type>
<label>Wind Direction 10-minute</label>
<description>Wind direction 10-minute Average</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="solarRadiation">
<item-type>Number:Intensity</item-type>
<label>Solar Radiation</label>
<description>Solar radiation</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="uvIndex">
<item-type>Number:Dimensionless</item-type>
<label>UV Index</label>
<description>Ultraviolet radiation index</description>
<state readOnly="true" pattern="%.1f"/>
</channel-type>
<channel-type id="uvDanger">
<item-type>String</item-type>
<label>UV Danger</label>
<description>Ultraviolet radiation danger level</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="soilTemperature">
<item-type>Number:Temperature</item-type>
<label>Soil Temperature</label>
<description>Temperature of the soil</description>
<category>Sensor</category>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="soilMoisture">
<item-type>Number:Dimensionless</item-type>
<label>Soil Moisture</label>
<description>Amount of moisture in the soil</description>
<category>Sensor</category>
<state readOnly="true" min="0" max="100" pattern="%d"/>
</channel-type>
<channel-type id="soilMoistureLevel">
<item-type>String</item-type>
<label>Soil Moisture Level</label>
<description>Soil moisture level from Very Dry to Very Wet</description>
<category>Sensor</category>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="co2">
<item-type>Number:Dimensionless</item-type>
<label>Carbon Dioxide</label>
<description>Amount of carbon dioxide</description>
<category>Sensor</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="relay">
<item-type>Switch</item-type>
<label>Relay</label>
<description>Relay</description>
<category>Switch</category>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="batteryIndicator">
<item-type>String</item-type>
<label>Battery Indicator</label>
<description>Indicates if battery is good or needs replacing</description>
<category>Battery</category>
<state readOnly="true" pattern="%s"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="ambientweather"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Ambient Weather WS-0265 -->
<thing-type id="ws0265">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>WS-0265</label>
<description>Ambient Weather Station WS-0265</description>
<channel-groups>
<channel-group id="station" typeId="station">
<label>Weather Station</label>
<description></description>
</channel-group>
<channel-group id="weatherDataWs0265" typeId="weatherDataWs0265">
<label>Weather Data</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor1" typeId="remoteSensor">
<label>Remote Sensor 1</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor2" typeId="remoteSensor">
<label>Remote Sensor 2</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor3" typeId="remoteSensor">
<label>Remote Sensor 3</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor4" typeId="remoteSensor">
<label>Remote Sensor 4</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor5" typeId="remoteSensor">
<label>Remote Sensor 5</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor6" typeId="remoteSensor">
<label>Remote Sensor 6</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor7" typeId="remoteSensor">
<label>Remote Sensor 7</label>
<description></description>
</channel-group>
</channel-groups>
<config-description-ref uri="thing-type:ambientweather:station"/>
</thing-type>
<channel-group-type id="weatherDataWs0265">
<label>Weather Data</label>
<description>Weather Data</description>
<channels>
<channel id="observationTime" typeId="observationTime"/>
<channel id="temperature" typeId="temperature"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="ambientweather"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Ambient Weather WS-0900-IP -->
<thing-type id="ws0900ip">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>WS-0900-IP</label>
<description>Ambient Weather Station WS-0900-IP</description>
<channel-groups>
<channel-group id="station" typeId="station">
<label>Weather Station</label>
<description></description>
</channel-group>
<channel-group id="weatherDataWs0900ip" typeId="weatherDataWs0900ip">
<label>Weather Data</label>
<description></description>
</channel-group>
<channel-group id="indoorSensor" typeId="indoorSensor">
<label>Indoor Sensor</label>
<description></description>
</channel-group>
</channel-groups>
<config-description-ref uri="thing-type:ambientweather:station"/>
</thing-type>
<!-- Channel group type specific to WS-0900-IP -->
<channel-group-type id="weatherDataWs0900ip">
<label>Weather Data</label>
<description>Weather Data</description>
<channels>
<channel id="observationTime" typeId="observationTime"/>
<channel id="temperature" typeId="temperature"/>
<channel id="dewPoint" typeId="dewPoint"/>
<channel id="relativeHumidity" typeId="relativeHumidity"/>
<channel id="pressureAbsolute" typeId="pressure"/>
<channel id="pressureRelative" typeId="pressure"/>
<channel id="pressureTrend" typeId="pressureTrend"/>
<channel id="windDirectionDegrees" typeId="windDirectionDegrees"/>
<channel id="windDirection" typeId="windDirection"/>
<channel id="windSpeed" typeId="windSpeed"/>
<channel id="windGust" typeId="windGust"/>
<channel id="windGustMaxDaily" typeId="windGustMaxDaily"/>
<channel id="rainHourlyRate" typeId="rainHourlyRate"/>
<channel id="rainDay" typeId="rainDay"/>
<channel id="rainWeek" typeId="rainWeek"/>
<channel id="rainMonth" typeId="rainMonth"/>
<channel id="rainYear" typeId="rainYear"/>
<channel id="rainTotal" typeId="rainTotal"/>
<channel id="rainLastTime" typeId="rainLastTime"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="ambientweather"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Ambient Weather WS-1400-IP -->
<thing-type id="ws1400ip">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>WS-1400-IP</label>
<description>Ambient Weather Station WS-1400-IP</description>
<channel-groups>
<channel-group id="station" typeId="station">
<label>Weather Station</label>
<description></description>
</channel-group>
<channel-group id="weatherDataWs1400ip" typeId="weatherDataWs1400ip">
<label>Weather Data</label>
<description></description>
</channel-group>
<channel-group id="indoorSensor" typeId="indoorSensor">
<label>Indoor Sensor</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor1" typeId="remoteSensor">
<label>Remote Sensor 1</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor2" typeId="remoteSensor">
<label>Remote Sensor 2</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor3" typeId="remoteSensor">
<label>Remote Sensor 3</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor4" typeId="remoteSensor">
<label>Remote Sensor 4</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor5" typeId="remoteSensor">
<label>Remote Sensor 5</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor6" typeId="remoteSensor">
<label>Remote Sensor 6</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor7" typeId="remoteSensor">
<label>Remote Sensor 7</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor8" typeId="remoteSensor">
<label>Remote Sensor 8</label>
<description></description>
</channel-group>
</channel-groups>
<config-description-ref uri="thing-type:ambientweather:station"/>
</thing-type>
<!-- Channel group type specific to WS-1400-IP -->
<channel-group-type id="weatherDataWs1400ip">
<label>Weather Data</label>
<description>Weather Data</description>
<channels>
<channel id="observationTime" typeId="observationTime"/>
<channel id="batteryIndicator" typeId="batteryIndicator"/>
<channel id="temperature" typeId="temperature"/>
<channel id="dewPoint" typeId="dewPoint"/>
<channel id="feelingTemperature" typeId="feelingTemperature"/>
<channel id="relativeHumidity" typeId="relativeHumidity"/>
<channel id="pressureAbsolute" typeId="pressure"/>
<channel id="pressureRelative" typeId="pressure"/>
<channel id="pressureTrend" typeId="pressureTrend"/>
<channel id="windDirectionDegrees" typeId="windDirectionDegrees"/>
<channel id="windDirection" typeId="windDirection"/>
<channel id="windSpeed" typeId="windSpeed"/>
<channel id="windGust" typeId="windGust"/>
<channel id="windGustMaxDaily" typeId="windGustMaxDaily"/>
<channel id="rainHourlyRate" typeId="rainHourlyRate"/>
<channel id="rainDay" typeId="rainDay"/>
<channel id="rainWeek" typeId="rainWeek"/>
<channel id="rainMonth" typeId="rainMonth"/>
<channel id="rainYear" typeId="rainYear"/>
<channel id="rainTotal" typeId="rainTotal"/>
<channel id="rainEvent" typeId="rainEvent"/>
<channel id="rainLastTime" typeId="rainLastTime"/>
<channel id="solarRadiation" typeId="solarRadiation"/>
<channel id="uvIndex" typeId="uvIndex"/>
<channel id="uvDanger" typeId="uvDanger"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="ambientweather"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Ambient Weather WS-2902A -->
<thing-type id="ws2902a">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>WS-2902A</label>
<description>Ambient Weather Station WS-2902A</description>
<channel-groups>
<channel-group id="station" typeId="station">
<label>Weather Station</label>
<description></description>
</channel-group>
<channel-group id="weatherDataWs2902a" typeId="weatherDataWs2902a">
<label>Weather Data</label>
<description></description>
</channel-group>
<channel-group id="indoorSensor" typeId="indoorSensor">
<label>Indoor Sensor</label>
<description></description>
</channel-group>
</channel-groups>
<config-description-ref uri="thing-type:ambientweather:station"/>
</thing-type>
<!-- Channel group type specific to WS-2902A -->
<channel-group-type id="weatherDataWs2902a">
<label>Weather Data</label>
<description>Weather Data</description>
<channels>
<channel id="observationTime" typeId="observationTime"/>
<channel id="batteryIndicator" typeId="batteryIndicator"/>
<channel id="temperature" typeId="temperature"/>
<channel id="dewPoint" typeId="dewPoint"/>
<channel id="feelingTemperature" typeId="feelingTemperature"/>
<channel id="relativeHumidity" typeId="relativeHumidity"/>
<channel id="pressureAbsolute" typeId="pressure"/>
<channel id="pressureRelative" typeId="pressure"/>
<channel id="pressureTrend" typeId="pressureTrend"/>
<channel id="windDirectionDegrees" typeId="windDirectionDegrees"/>
<channel id="windDirection" typeId="windDirection"/>
<channel id="windSpeed" typeId="windSpeed"/>
<channel id="windGust" typeId="windGust"/>
<channel id="windGustMaxDaily" typeId="windGustMaxDaily"/>
<channel id="rainHourlyRate" typeId="rainHourlyRate"/>
<channel id="rainDay" typeId="rainDay"/>
<channel id="rainWeek" typeId="rainWeek"/>
<channel id="rainMonth" typeId="rainMonth"/>
<channel id="rainYear" typeId="rainYear"/>
<channel id="rainTotal" typeId="rainTotal"/>
<channel id="rainEvent" typeId="rainEvent"/>
<channel id="rainLastTime" typeId="rainLastTime"/>
<channel id="solarRadiation" typeId="solarRadiation"/>
<channel id="uvIndex" typeId="uvIndex"/>
<channel id="uvDanger" typeId="uvDanger"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="ambientweather"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Ambient Weather WS-8482 -->
<thing-type id="ws8482">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>WS-8482</label>
<description>Ambient Weather Station WS-8482</description>
<channel-groups>
<channel-group id="station" typeId="station">
<label>Weather Station</label>
<description></description>
</channel-group>
<channel-group id="weatherDataWs8482" typeId="weatherDataWs8482">
<label>Weather Data</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor1" typeId="remoteSensor">
<label>Remote Sensor 1</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor2" typeId="remoteSensor">
<label>Remote Sensor 2</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor3" typeId="remoteSensor">
<label>Remote Sensor 3</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor4" typeId="remoteSensor">
<label>Remote Sensor 4</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor5" typeId="remoteSensor">
<label>Remote Sensor 5</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor6" typeId="remoteSensor">
<label>Remote Sensor 6</label>
<description></description>
</channel-group>
<channel-group id="remoteSensor7" typeId="remoteSensor">
<label>Remote Sensor 7</label>
<description></description>
</channel-group>
</channel-groups>
<config-description-ref uri="thing-type:ambientweather:station"/>
</thing-type>
<channel-group-type id="weatherDataWs8482">
<label>Weather Data</label>
<description>Weather Data</description>
<channels>
<channel id="observationTime" typeId="observationTime"/>
<channel id="batteryIndicator" typeId="batteryIndicator"/>
<channel id="temperature" typeId="temperature"/>
<channel id="relativeHumidity" typeId="relativeHumidity"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>