added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.meteostick/.classpath
Normal file
32
bundles/org.openhab.binding.meteostick/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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>
|
||||
23
bundles/org.openhab.binding.meteostick/.project
Normal file
23
bundles/org.openhab.binding.meteostick/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.meteostick</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>
|
||||
13
bundles/org.openhab.binding.meteostick/NOTICE
Normal file
13
bundles/org.openhab.binding.meteostick/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
179
bundles/org.openhab.binding.meteostick/README.md
Normal file
179
bundles/org.openhab.binding.meteostick/README.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Meteostick Binding
|
||||
|
||||
This is the binding for the [Meteostick](https://www.smartbedded.com/wiki/index.php/Meteostick) weather receiver dongle.
|
||||
This is an RF receiver that can receive data directly from Davis weather devices (and others).
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding support 2 different things types
|
||||
|
||||
| Thing | Type | Description |
|
||||
|----------------------|--------|-----------------------------------|
|
||||
| meteostick_bridge | Bridge | This is the Meteostick USB stick |
|
||||
| meteostick_davis_iss | Thing | This is the Davis Vue ISS |
|
||||
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The Meteostick things need to be manually added - there is no discovery in the Meteostick binding.
|
||||
|
||||
First add and configure the Meteostick bridge - the port and frequency band for your region need to be set.
|
||||
Next add the sensor and configure the channel number.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
### meteostick_bridge Configuration Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|----------------------------------------------------|
|
||||
| port | Sets the serial port to be used for the stick |
|
||||
| mode | Sets the mode (frequency band) |
|
||||
|
||||
Set mode to one of the following depending on your device and region:
|
||||
|
||||
| Mode | Device | Region | Frequency |
|
||||
|-------|--------------|------------------|-----------|
|
||||
| 0 | Davis | North America | 915 Mhz |
|
||||
| 1 | Davis | Europe | 868 Mhz |
|
||||
| 2 | Davis | Australia | 915 Mhz |
|
||||
| 3 | Fine Offset | North America | 915 Mhz |
|
||||
| 4 | Fine Offset | Europe | 868 Mhz |
|
||||
| 5 | Davis | New Zealand | 931.5 Mhz |
|
||||
|
||||
### meteostick_davis_iss Configuration Options
|
||||
|
||||
| Option | Description |
|
||||
|---------|-------------------------------------------|
|
||||
| channel | Sets the RF channel used for this sensor |
|
||||
| spoon | Size of rain spoon assembly for this sensor in mm. Default value is 0.254 (0.01") for use with Davis part number 7345.280. Set to 0.2 for use with Davis part number 7345.319 |
|
||||
|
||||
## Channels
|
||||
|
||||
### Meteostick
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|--------------------|--------------------|--------------------|
|
||||
| pressure | Number:Pressure | Air pressure |
|
||||
| indoor-temperature | Number:Temperature | Indoor temperature |
|
||||
|
||||
### Davis ISS
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|---------------------|-----------------------|-------------------------------------------------|
|
||||
| outdoor-temperature | Number:Temperature | Outside temperature |
|
||||
| humidity | Number | Humidity |
|
||||
| wind-direction | Number:Angle | Wind direction |
|
||||
| wind-direction-last2min-average | Number:Angle | Wind direction average over last 2 minutes |
|
||||
| wind-speed | Number:Speed | Wind speed |
|
||||
| wind-speed-last2min-average | Number:Speed | Wind speed average over last 2 minutes |
|
||||
| wind-speed-last2min-maximum | Number:Speed | Wind speed maximum over last 2 minutes |
|
||||
| rain-raw | Number | Raw rain counter from the tipping spoon sensor |
|
||||
| rain-currenthour | Number:Length | The rainfall in the last 60 minutes |
|
||||
| rain-lasthour | Number:Length | The rainfall in the previous hour |
|
||||
| solar-power | Number | Solar power from the sensor station |
|
||||
| signal-strength | Number | Received signal strength |
|
||||
| low-battery | Switch | Low battery warning |
|
||||
|
||||
#### Rainfall
|
||||
|
||||
There are three channels associated with rainfall.
|
||||
The raw counter from the tipping bucket is provided, the rainfall in the last 60 minutes is updated on each received rainfall and provides the past 60 minutes of rainfall.
|
||||
The rainfall in the previous hour is the rainfall for each hour of the day and is updated on the hour.
|
||||
|
||||
## Full Example
|
||||
|
||||
This example uploads weather data to for your personal weather station at Weather Underground every two minutes.
|
||||
|
||||
Steps:
|
||||
|
||||
1. Install the [MeteoStick](https://www.smartbedded.com/wiki/index.php/Meteostick) binding for use with your [Davis Vantage Vue Integrated Sensor Suite (ISS)](https://www.davisnet.com/solution/vantage-vue/).
|
||||
1. [Register](https://www.wunderground.com/personal-weather-station/signup.asp) your personal weather station with Weather Underground and make note of the station ID and password issued.
|
||||
1. Add the following files to your openHAB configuration:
|
||||
|
||||
### things/meteostick.things
|
||||
|
||||
Things can be defined in the .things file as follows:
|
||||
|
||||
```
|
||||
meteostick:meteostick_bridge:receiver [ port="/dev/tty.usbserial-AI02XA60", mode=1 ]
|
||||
meteostick:meteostick_davis_iss:iss (meteostick:meteostick_bridge:receiver) [ channel=1, spoon=0.2 ]
|
||||
```
|
||||
|
||||
Note the configuration options for `port`, `mode`, `channel` and `spoon` above and adjust as needed for your specific hardware.
|
||||
|
||||
### items/meteostick.items
|
||||
|
||||
```
|
||||
Number:Pressure MeteoStickPressure "Meteostick Pressure [%.1f hPa]"{ channel="meteostick:meteostick_bridge:receiver:pressure" }
|
||||
Number:Temperature DavisVantageVueOutdoorTemperature "ISS Outdoor Temp [%.1f °C]" { channel="meteostick:meteostick_davis_iss:iss:outdoor-temperature" }
|
||||
Number DavisVantageVueHumidity "ISS Humidity [%.0f %%]" { channel="meteostick:meteostick_davis_iss:iss:humidity" }
|
||||
Number:Angle DavisVantageVueWindDirection "ISS Wind Direction [%.0f °]" { channel="meteostick:meteostick_davis_iss:iss:wind-direction" }
|
||||
Number:Angle DavisVantageVueWindDirectionAverage "ISS Average Wind Direction [%.0f °]" { channel="meteostick:meteostick_davis_iss:iss:wind-direction-last2min-average" }
|
||||
Number:Speed DavisVantageVueWindSpeed "ISS Wind Speed [%.1f m/s]" { channel="meteostick:meteostick_davis_iss:iss:wind-speed" }
|
||||
Number:Speed DavisVantageVueWindSpeedAverage "ISS Average Wind Speed [%.1f m/s]" { channel="meteostick:meteostick_davis_iss:iss:wind-speed-last2min-average" }
|
||||
Number:Speed DavisVantageVueWindSpeedMaximum "ISS Maximum Wind Speed [%.1f m/s]" { channel="meteostick:meteostick_davis_iss:iss:wind-speed-last2min-maximum" }
|
||||
Number:Length DavisVantageVueRainCurrentHour "ISS Rain Current Hour [%.1f mm]" { channel="meteostick:meteostick_davis_iss:iss:rain-currenthour" }
|
||||
```
|
||||
|
||||
### rules/meteostick.rules
|
||||
|
||||
Replace `YOUR_ID` and `your_password` below with the values from the the Weather Underground registration process.
|
||||
|
||||
```
|
||||
import java.net.URLEncoder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Map
|
||||
import java.util.TimeZone
|
||||
|
||||
/* Uploads weather station data using the format documented here:
|
||||
|
||||
https://feedback.weather.com/customer/en/portal/articles/2924682-pws-upload-protocol?b_id=17298
|
||||
*/
|
||||
|
||||
rule PWS
|
||||
when
|
||||
Item DavisVantageVueWindDirectionAverage received update
|
||||
then
|
||||
val id = 'YOUR_ID'
|
||||
val pw = 'your_password'
|
||||
val sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss')
|
||||
sdf.setTimeZone(TimeZone.getTimeZone('UTC'))
|
||||
val double rh = DavisVantageVueHumidity.getStateAs(DecimalType).doubleValue
|
||||
val double tempc = DavisVantageVueOutdoorTemperature.getStateAs(QuantityType).toUnit('°C').doubleValue
|
||||
val double dewptc = 243.04 * (Math.log(rh/100) + ((17.625 * tempc) / (243.04 + tempc))) / (17.625 - Math.log(rh/100) - ((17.625 * tempc) / (243.04 + tempc)))
|
||||
val double dewptf = new QuantityType(dewptc, CELSIUS).toUnit('°F').doubleValue
|
||||
val Map<String, Object> params = newLinkedHashMap(
|
||||
'action' -> 'updateraw',
|
||||
'ID' -> id,
|
||||
'PASSWORD' -> pw,
|
||||
'dateutc' -> sdf.format(new Date()),
|
||||
'winddir' -> DavisVantageVueWindDirection.getStateAs(QuantityType).toUnit('°').intValue,
|
||||
'windspeedmph' -> DavisVantageVueWindSpeed.getStateAs(QuantityType).toUnit('mph').doubleValue,
|
||||
'windgustmph' -> DavisVantageVueWindSpeedMaximum.getStateAs(QuantityType).toUnit('mph').doubleValue,
|
||||
'windgustdir' -> DavisVantageVueWindDirectionAverage.getStateAs(QuantityType).toUnit('°').intValue,
|
||||
'windspdmph_avg2m' -> DavisVantageVueWindSpeedAverage.getStateAs(QuantityType).toUnit('mph').doubleValue,
|
||||
'winddir_avg2m' -> DavisVantageVueWindDirectionAverage.getStateAs(QuantityType).toUnit('°').intValue,
|
||||
'humidity' -> DavisVantageVueHumidity.state,
|
||||
'dewptf' -> dewptf,
|
||||
'tempf' -> DavisVantageVueOutdoorTemperature.getStateAs(QuantityType).toUnit('°F').doubleValue,
|
||||
'rainin' -> DavisVantageVueRainCurrentHour.getStateAs(QuantityType).toUnit('in').doubleValue,
|
||||
'baromin' -> MeteoStickPressure.getStateAs(QuantityType).toUnit('inHg').doubleValue,
|
||||
'softwaretype' -> 'openHAB 2.4')
|
||||
|
||||
var url = 'https://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?'
|
||||
var first = true
|
||||
for (key : params.keySet()) {
|
||||
if (!first) {
|
||||
url += '&'
|
||||
}
|
||||
url += key + '=' + URLEncoder::encode(params.get(key).toString, 'UTF-8')
|
||||
first = false
|
||||
}
|
||||
|
||||
logDebug('PWS', 'url is {}', url)
|
||||
sendHttpGetRequest(url)
|
||||
end
|
||||
```
|
||||
|
||||
openHAB will now report your weather station data to Weather Underground every two minutes.
|
||||
17
bundles/org.openhab.binding.meteostick/pom.xml
Normal file
17
bundles/org.openhab.binding.meteostick/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?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.meteostick</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: meteostick Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.meteostick-${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-meteostick" description="Meteostick Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-serial</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.meteostick/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.meteostick.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link meteostickBinding} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Chris Jackson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MeteostickBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "meteostick";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "meteostick_bridge");
|
||||
public static final ThingTypeUID THING_TYPE_DAVIS = new ThingTypeUID(BINDING_ID, "meteostick_davis_iss");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_INDOOR_TEMPERATURE = "indoor-temperature";
|
||||
public static final String CHANNEL_OUTDOOR_TEMPERATURE = "outdoor-temperature";
|
||||
public static final String CHANNEL_HUMIDITY = "humidity";
|
||||
public static final String CHANNEL_PRESSURE = "pressure";
|
||||
public static final String CHANNEL_RAIN_RAW = "rain-raw";
|
||||
public static final String CHANNEL_RAIN_CURRENTHOUR = "rain-currenthour";
|
||||
public static final String CHANNEL_RAIN_LASTHOUR = "rain-lasthour";
|
||||
public static final String CHANNEL_WIND_SPEED = "wind-speed";
|
||||
public static final String CHANNEL_WIND_DIRECTION = "wind-direction";
|
||||
public static final String CHANNEL_WIND_SPEED_LAST2MIN_AVERAGE = "wind-speed-last2min-average";
|
||||
public static final String CHANNEL_WIND_SPEED_LAST2MIN_MAXIMUM = "wind-speed-last2min-maximum";
|
||||
public static final String CHANNEL_WIND_DIRECTION_LAST2MIN_AVERAGE = "wind-direction-last2min-average";
|
||||
public static final String CHANNEL_SOLAR_POWER = "solar-power";
|
||||
public static final String CHANNEL_SIGNAL_STRENGTH = "signal-strength";
|
||||
public static final String CHANNEL_LOW_BATTERY = "low-battery";
|
||||
|
||||
// List of parameters
|
||||
public static final String PARAMETER_CHANNEL = "channel";
|
||||
public static final String PARAMETER_SPOON = "spoon";
|
||||
public static final String PARAMETER_SPOON_DEFAULT = "0.254";
|
||||
|
||||
// Miscellaneous constants
|
||||
public static final long HOUR_IN_SEC = 60 * 60;
|
||||
public static final long HOUR_IN_MSEC = HOUR_IN_SEC * 1000;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.meteostick.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.meteostick.internal.handler.MeteostickBridgeHandler;
|
||||
import org.openhab.binding.meteostick.internal.handler.MeteostickSensorHandler;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MeteostickHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Chris Jackson - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.meteostick")
|
||||
public class MeteostickHandlerFactory extends BaseThingHandlerFactory {
|
||||
private Logger logger = LoggerFactory.getLogger(MeteostickHandlerFactory.class);
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
@Activate
|
||||
public MeteostickHandlerFactory(final @Reference SerialPortManager serialPortManager) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return MeteostickBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)
|
||||
| MeteostickSensorHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
logger.debug("MeteoStick thing factory: createHandler {} of type {}", thing.getThingTypeUID(), thing.getUID());
|
||||
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (MeteostickBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new MeteostickBridgeHandler((Bridge) thing, serialPortManager);
|
||||
}
|
||||
|
||||
if (MeteostickSensorHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new MeteostickSensorHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
/**
|
||||
* 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.meteostick.internal.handler;
|
||||
|
||||
import static org.openhab.binding.meteostick.internal.MeteostickBindingConstants.*;
|
||||
import static org.openhab.core.library.unit.MetricPrefix.HECTO;
|
||||
import static org.openhab.core.library.unit.SIUnits.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.TooManyListenersException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEvent;
|
||||
import org.openhab.core.io.transport.serial.SerialPortEventListener;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MeteostickBridgeHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Chris Jackson - Initial contribution
|
||||
*/
|
||||
public class MeteostickBridgeHandler extends BaseBridgeHandler {
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MeteostickBridgeHandler.class);
|
||||
|
||||
private static final int RECEIVE_TIMEOUT = 3000;
|
||||
|
||||
private SerialPort serialPort;
|
||||
private final SerialPortManager serialPortManager;
|
||||
private ReceiveThread receiveThread;
|
||||
|
||||
private ScheduledFuture<?> offlineTimerJob;
|
||||
|
||||
private String meteostickMode = "m1";
|
||||
private final String meteostickFormat = "o1";
|
||||
|
||||
private Date lastData;
|
||||
|
||||
private ConcurrentMap<Integer, MeteostickEventListener> eventListeners = new ConcurrentHashMap<>();
|
||||
|
||||
public MeteostickBridgeHandler(Bridge thing, SerialPortManager serialPortManager) {
|
||||
super(thing);
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing MeteoStick Bridge handler.");
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
Configuration config = getThing().getConfiguration();
|
||||
|
||||
final String port = (String) config.get("port");
|
||||
|
||||
final BigDecimal mode = (BigDecimal) config.get("mode");
|
||||
if (mode != null) {
|
||||
meteostickMode = "m" + mode.toString();
|
||||
}
|
||||
|
||||
Runnable pollingRunnable = () -> {
|
||||
if (connectPort(port)) {
|
||||
offlineTimerJob.cancel(true);
|
||||
}
|
||||
};
|
||||
|
||||
// Scheduling a job on each hour to update the last hour rainfall
|
||||
offlineTimerJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
disconnect();
|
||||
if (offlineTimerJob != null) {
|
||||
offlineTimerJob.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
private void resetMeteoStick() {
|
||||
sendToMeteostick("r");
|
||||
}
|
||||
|
||||
protected void subscribeEvents(int channel, MeteostickEventListener handler) {
|
||||
logger.debug("MeteoStick bridge: subscribeEvents to channel {} with {}", channel, handler);
|
||||
|
||||
if (eventListeners.containsKey(channel)) {
|
||||
logger.debug("MeteoStick bridge: subscribeEvents to channel {} already registered", channel);
|
||||
return;
|
||||
}
|
||||
eventListeners.put(channel, handler);
|
||||
|
||||
resetMeteoStick();
|
||||
}
|
||||
|
||||
protected void unsubscribeEvents(int channel, MeteostickEventListener handler) {
|
||||
logger.debug("MeteoStick bridge: unsubscribeEvents to channel {} with {}", channel, handler);
|
||||
|
||||
eventListeners.remove(channel, handler);
|
||||
|
||||
resetMeteoStick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the comm port and starts send and receive threads.
|
||||
*
|
||||
* @param serialPortName the port name to open
|
||||
* @throws SerialInterfaceException when a connection error occurs.
|
||||
*/
|
||||
private boolean connectPort(final String serialPortName) {
|
||||
logger.debug("MeteoStick Connecting to serial port {}", serialPortName);
|
||||
|
||||
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
|
||||
if (portIdentifier == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Serial Error: Port " + serialPortName + " does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
serialPort = portIdentifier.open("org.openhab.binding.meteostick", 2000);
|
||||
serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
|
||||
SerialPort.PARITY_NONE);
|
||||
serialPort.enableReceiveThreshold(1);
|
||||
serialPort.enableReceiveTimeout(RECEIVE_TIMEOUT);
|
||||
|
||||
receiveThread = new ReceiveThread();
|
||||
receiveThread.start();
|
||||
|
||||
// RXTX serial port library causes high CPU load
|
||||
// Start event listener, which will just sleep and slow down event loop
|
||||
serialPort.addEventListener(this.receiveThread);
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
|
||||
logger.debug("Serial port is initialized");
|
||||
|
||||
success = true;
|
||||
} catch (PortInUseException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Serial Error: Port " + serialPortName + " in use");
|
||||
} catch (UnsupportedCommOperationException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Serial Error: Unsupported comm operation on port " + serialPortName);
|
||||
} catch (TooManyListenersException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Serial Error: Too many listeners on port " + serialPortName);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the serial interface and stops send and receive threads.
|
||||
*/
|
||||
private void disconnect() {
|
||||
if (receiveThread != null) {
|
||||
receiveThread.interrupt();
|
||||
try {
|
||||
receiveThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
receiveThread = null;
|
||||
}
|
||||
|
||||
if (this.serialPort != null) {
|
||||
this.serialPort.close();
|
||||
this.serialPort = null;
|
||||
}
|
||||
logger.debug("Disconnected from serial port");
|
||||
}
|
||||
|
||||
private void sendToMeteostick(String string) {
|
||||
try {
|
||||
synchronized (serialPort.getOutputStream()) {
|
||||
serialPort.getOutputStream().write(string.getBytes());
|
||||
serialPort.getOutputStream().write(13);
|
||||
serialPort.getOutputStream().flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Got I/O exception {} during sending. exiting thread.", e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private class ReceiveThread extends Thread implements SerialPortEventListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(ReceiveThread.class);
|
||||
|
||||
@Override
|
||||
public void serialEvent(SerialPortEvent arg0) {
|
||||
try {
|
||||
logger.trace("RXTX library CPU load workaround, sleep forever");
|
||||
Thread.sleep(Long.MAX_VALUE);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run method. Runs the actual receiving process.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("Starting MeteoStick Receive Thread");
|
||||
byte[] rxPacket = new byte[100];
|
||||
int rxCnt = 0;
|
||||
int rxByte;
|
||||
while (!interrupted()) {
|
||||
try {
|
||||
rxByte = serialPort.getInputStream().read();
|
||||
|
||||
if (rxByte == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lastData = new Date();
|
||||
startTimeoutCheck();
|
||||
|
||||
// Check for end of line
|
||||
if (rxByte == 13 && rxCnt > 0) {
|
||||
String inputString = new String(rxPacket, 0, rxCnt);
|
||||
logger.debug("MeteoStick received: {}", inputString);
|
||||
String p[] = inputString.split("\\s+");
|
||||
|
||||
switch (p[0]) {
|
||||
case "B": // Barometer
|
||||
BigDecimal temperature = new BigDecimal(p[1]);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_INDOOR_TEMPERATURE),
|
||||
new QuantityType<>(temperature.setScale(1), CELSIUS));
|
||||
|
||||
BigDecimal pressure = new BigDecimal(p[2]);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_PRESSURE),
|
||||
new QuantityType<>(pressure.setScale(1, RoundingMode.HALF_UP), HECTO(PASCAL)));
|
||||
break;
|
||||
case "#":
|
||||
break;
|
||||
case "?":
|
||||
// Create the channel command
|
||||
int channels = 0;
|
||||
for (int channel : eventListeners.keySet()) {
|
||||
channels += Math.pow(2, channel - 1);
|
||||
}
|
||||
|
||||
// Device has been reset - reconfigure
|
||||
sendToMeteostick(meteostickFormat);
|
||||
sendToMeteostick(meteostickMode);
|
||||
sendToMeteostick("t" + channels);
|
||||
break;
|
||||
default:
|
||||
if (p.length < 3) {
|
||||
logger.debug("MeteoStick bridge: short data ({})", p.length);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
MeteostickEventListener listener = eventListeners.get(Integer.parseInt(p[1]));
|
||||
if (listener != null) {
|
||||
listener.onDataReceived(p);
|
||||
} else {
|
||||
logger.debug("MeteoStick bridge: data from channel {} with no handler",
|
||||
Integer.parseInt(p[1]));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
rxCnt = 0;
|
||||
} else if (rxByte != 10) {
|
||||
// Ignore line feed
|
||||
rxPacket[rxCnt] = (byte) rxByte;
|
||||
|
||||
if (rxCnt < rxPacket.length) {
|
||||
rxCnt++;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
rxCnt = 0;
|
||||
logger.error("Exception during MeteoStick receive thread", e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Stopping MeteoStick Receive Thread");
|
||||
serialPort.removeEventListener();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void startTimeoutCheck() {
|
||||
Runnable pollingRunnable = () -> {
|
||||
String detail;
|
||||
if (lastData == null) {
|
||||
detail = "No data received";
|
||||
} else {
|
||||
detail = "No data received since " + lastData.toString();
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, detail);
|
||||
};
|
||||
|
||||
if (offlineTimerJob != null) {
|
||||
offlineTimerJob.cancel(true);
|
||||
}
|
||||
|
||||
// Scheduling a job on each hour to update the last hour rainfall
|
||||
offlineTimerJob = scheduler.schedule(pollingRunnable, 90, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.meteostick.internal.handler;
|
||||
|
||||
/**
|
||||
* This interface provides notifications between the bridge and the sensors.
|
||||
*
|
||||
* @author Chris Jackson - Initial contribution
|
||||
*
|
||||
*/
|
||||
public interface MeteostickEventListener {
|
||||
/**
|
||||
* Called each time a new line of data is received
|
||||
*
|
||||
* @param data a line of data from the meteoStick
|
||||
*/
|
||||
public void onDataReceived(String data[]);
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
/**
|
||||
* 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.meteostick.internal.handler;
|
||||
|
||||
import static org.openhab.binding.meteostick.internal.MeteostickBindingConstants.*;
|
||||
import static org.openhab.core.library.unit.MetricPrefix.MILLI;
|
||||
import static org.openhab.core.library.unit.SIUnits.*;
|
||||
import static org.openhab.core.library.unit.SmartHomeUnits.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.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.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MeteostickSensorHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Chris Jackson - Initial contribution
|
||||
* @author John Cocula - Added variable spoon size, UoM, wind stats, bug fixes
|
||||
*/
|
||||
public class MeteostickSensorHandler extends BaseThingHandler implements MeteostickEventListener {
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DAVIS);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MeteostickSensorHandler.class);
|
||||
|
||||
private int channel = 0;
|
||||
private BigDecimal spoon = new BigDecimal(PARAMETER_SPOON_DEFAULT);
|
||||
private MeteostickBridgeHandler bridgeHandler;
|
||||
private RainHistory rainHistory = new RainHistory(HOUR_IN_MSEC);
|
||||
private WindHistory windHistory = new WindHistory(2 * 60 * 1000); // 2 minutes
|
||||
private ScheduledFuture<?> rainHourlyJob;
|
||||
private ScheduledFuture<?> wind2MinJob;
|
||||
private ScheduledFuture<?> offlineTimerJob;
|
||||
|
||||
private Date lastData;
|
||||
|
||||
public MeteostickSensorHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing MeteoStick handler.");
|
||||
|
||||
channel = ((BigDecimal) getConfig().get(PARAMETER_CHANNEL)).intValue();
|
||||
|
||||
spoon = (BigDecimal) getConfig().get(PARAMETER_SPOON);
|
||||
if (spoon == null) {
|
||||
spoon = new BigDecimal(PARAMETER_SPOON_DEFAULT);
|
||||
}
|
||||
logger.debug("Initializing MeteoStick handler - Channel {}, Spoon size {} mm.", channel, spoon);
|
||||
|
||||
Runnable rainRunnable = () -> {
|
||||
BigDecimal rainfall = rainHistory.getTotal(spoon);
|
||||
rainfall.setScale(1, RoundingMode.DOWN);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_RAIN_LASTHOUR),
|
||||
new QuantityType<>(rainfall, MILLI(METRE)));
|
||||
};
|
||||
|
||||
// Scheduling a job on each hour to update the last hour rainfall
|
||||
long start = HOUR_IN_SEC - ((System.currentTimeMillis() % HOUR_IN_MSEC) / 1000);
|
||||
rainHourlyJob = scheduler.scheduleWithFixedDelay(rainRunnable, start, HOUR_IN_SEC, TimeUnit.SECONDS);
|
||||
|
||||
Runnable windRunnable = () -> {
|
||||
WindStats stats = windHistory.getStats();
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_WIND_SPEED_LAST2MIN_AVERAGE),
|
||||
new QuantityType<>(stats.averageSpeed, METRE_PER_SECOND));
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_WIND_SPEED_LAST2MIN_MAXIMUM),
|
||||
new QuantityType<>(stats.maxSpeed, METRE_PER_SECOND));
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_WIND_DIRECTION_LAST2MIN_AVERAGE),
|
||||
new QuantityType<>(stats.averageDirection, DEGREE_ANGLE));
|
||||
};
|
||||
|
||||
// Scheduling a job to run every two minutes to update wind statistics
|
||||
wind2MinJob = scheduler.scheduleWithFixedDelay(windRunnable, 2, 2, TimeUnit.MINUTES);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (rainHourlyJob != null) {
|
||||
rainHourlyJob.cancel(true);
|
||||
}
|
||||
|
||||
if (wind2MinJob != null) {
|
||||
wind2MinJob.cancel(true);
|
||||
}
|
||||
|
||||
if (offlineTimerJob != null) {
|
||||
offlineTimerJob.cancel(true);
|
||||
}
|
||||
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.unsubscribeEvents(channel, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("MeteoStick handler {}: bridgeStatusChanged to {}", channel, bridgeStatusInfo);
|
||||
if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
|
||||
logger.debug("MeteoStick handler {}: bridgeStatusChanged but bridge offline", channel);
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
bridgeHandler = (MeteostickBridgeHandler) getBridge().getHandler();
|
||||
|
||||
if (channel != 0) {
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.subscribeEvents(channel, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Put the thing online and start our "no data" timer
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
startTimeoutCheck();
|
||||
}
|
||||
|
||||
private void processSignalStrength(String dbmString) {
|
||||
double dbm = Double.parseDouble(dbmString);
|
||||
int strength;
|
||||
|
||||
if (dbm > -60) {
|
||||
strength = 4;
|
||||
} else if (dbm > -70) {
|
||||
strength = 3;
|
||||
} else if (dbm > -80) {
|
||||
strength = 2;
|
||||
} else if (dbm > -90) {
|
||||
strength = 1;
|
||||
} else {
|
||||
strength = 0;
|
||||
}
|
||||
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_SIGNAL_STRENGTH), new DecimalType(strength));
|
||||
}
|
||||
|
||||
private void processBattery(boolean batteryLow) {
|
||||
OnOffType state = batteryLow ? OnOffType.ON : OnOffType.OFF;
|
||||
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_LOW_BATTERY), state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataReceived(String[] data) {
|
||||
logger.debug("MeteoStick received channel {}: {}", channel, data);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
lastData = new Date();
|
||||
|
||||
startTimeoutCheck();
|
||||
|
||||
switch (data[0]) {
|
||||
case "R": // Rain
|
||||
int rain = Integer.parseInt(data[2]);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_RAIN_RAW), new DecimalType(rain));
|
||||
processSignalStrength(data[3]);
|
||||
processBattery(data.length == 5);
|
||||
|
||||
rainHistory.put(rain);
|
||||
|
||||
BigDecimal rainfall = rainHistory.getTotal(spoon);
|
||||
rainfall.setScale(1, RoundingMode.DOWN);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_RAIN_CURRENTHOUR),
|
||||
new QuantityType<>(rainfall, MILLI(METRE)));
|
||||
break;
|
||||
case "W": // Wind
|
||||
BigDecimal windSpeed = new BigDecimal(data[2]);
|
||||
int windDirection = Integer.parseInt(data[3]);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_WIND_SPEED),
|
||||
new QuantityType<>(windSpeed, METRE_PER_SECOND));
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_WIND_DIRECTION),
|
||||
new QuantityType<>(windDirection, DEGREE_ANGLE));
|
||||
|
||||
windHistory.put(windSpeed, windDirection);
|
||||
|
||||
processSignalStrength(data[4]);
|
||||
processBattery(data.length == 6);
|
||||
break;
|
||||
case "T": // Temperature
|
||||
BigDecimal temperature = new BigDecimal(data[2]);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_OUTDOOR_TEMPERATURE),
|
||||
new QuantityType<>(temperature.setScale(1), CELSIUS));
|
||||
|
||||
BigDecimal humidity = new BigDecimal(data[3]);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_HUMIDITY),
|
||||
new DecimalType(humidity.setScale(1)));
|
||||
|
||||
processSignalStrength(data[4]);
|
||||
processBattery(data.length == 6);
|
||||
break;
|
||||
case "P": // Solar panel power
|
||||
BigDecimal power = new BigDecimal(data[2]);
|
||||
updateState(new ChannelUID(getThing().getUID(), CHANNEL_SOLAR_POWER),
|
||||
new DecimalType(power.setScale(1)));
|
||||
|
||||
processSignalStrength(data[3]);
|
||||
processBattery(data.length == 5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class SlidingTimeWindow<T> {
|
||||
private long period = 0;
|
||||
protected final SortedMap<Long, T> storage = Collections.synchronizedSortedMap(new TreeMap<>());
|
||||
|
||||
/**
|
||||
*
|
||||
* @param period window period in milliseconds
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RainHistory extends SlidingTimeWindow<Integer> {
|
||||
|
||||
public RainHistory(long period) {
|
||||
super(period);
|
||||
}
|
||||
|
||||
public BigDecimal getTotal(BigDecimal spoon) {
|
||||
removeOldEntries();
|
||||
|
||||
int least = -1;
|
||||
int total = 0;
|
||||
|
||||
synchronized (storage) {
|
||||
for (int value : storage.values()) {
|
||||
|
||||
/*
|
||||
* Rain counters have been seen to wrap at 127 and also at 255.
|
||||
* The Meteostick documentation only mentions 255 at the time of
|
||||
* this writing. This potential difference is solved by having
|
||||
* all rain counters wrap at 127 (0x7F) by removing the high bit.
|
||||
*/
|
||||
value &= 0x7F;
|
||||
|
||||
if (least == -1) {
|
||||
least = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value < least) {
|
||||
total = 128 - least + value;
|
||||
} else {
|
||||
total = value - least;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BigDecimal.valueOf(total).multiply(spoon);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the wind direction as an east-west vector and a north-south vector
|
||||
* so that an average direction can be calculated based on the wind speed
|
||||
* at the time of the direction sample.
|
||||
*/
|
||||
class WindSample {
|
||||
double speed;
|
||||
double ewVector;
|
||||
double nsVector;
|
||||
|
||||
public WindSample(BigDecimal speed, int directionDegrees) {
|
||||
this.speed = speed.doubleValue();
|
||||
double direction = Math.toRadians(directionDegrees);
|
||||
this.ewVector = this.speed * Math.sin(direction);
|
||||
this.nsVector = this.speed * Math.cos(direction);
|
||||
}
|
||||
}
|
||||
|
||||
class WindStats {
|
||||
BigDecimal averageSpeed;
|
||||
int averageDirection;
|
||||
BigDecimal maxSpeed;
|
||||
}
|
||||
|
||||
class WindHistory extends SlidingTimeWindow<WindSample> {
|
||||
|
||||
public WindHistory(long period) {
|
||||
super(period);
|
||||
}
|
||||
|
||||
public void put(BigDecimal speed, int directionDegrees) {
|
||||
put(new WindSample(speed, directionDegrees));
|
||||
}
|
||||
|
||||
public WindStats getStats() {
|
||||
removeOldEntries();
|
||||
|
||||
double ewSum = 0;
|
||||
double nsSum = 0;
|
||||
double totalSpeed = 0;
|
||||
double maxSpeed = 0;
|
||||
int size = 0;
|
||||
synchronized (storage) {
|
||||
size = storage.size();
|
||||
for (WindSample sample : storage.values()) {
|
||||
ewSum += sample.ewVector;
|
||||
nsSum += sample.nsVector;
|
||||
totalSpeed += sample.speed;
|
||||
if (sample.speed > maxSpeed) {
|
||||
maxSpeed = sample.speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WindStats stats = new WindStats();
|
||||
|
||||
stats.averageDirection = (int) Math.toDegrees(Math.atan2(ewSum, nsSum));
|
||||
if (stats.averageDirection < 0) {
|
||||
stats.averageDirection += 360;
|
||||
}
|
||||
|
||||
stats.averageSpeed = new BigDecimal(size > 0 ? totalSpeed / size : 0).setScale(3, RoundingMode.HALF_DOWN);
|
||||
|
||||
stats.maxSpeed = new BigDecimal(maxSpeed).setScale(3, RoundingMode.HALF_DOWN);
|
||||
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void startTimeoutCheck() {
|
||||
Runnable pollingRunnable = () -> {
|
||||
String detail;
|
||||
if (lastData == null) {
|
||||
detail = "No data received";
|
||||
} else {
|
||||
detail = "No data received since " + lastData.toString();
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, detail);
|
||||
};
|
||||
|
||||
if (offlineTimerJob != null) {
|
||||
offlineTimerJob.cancel(true);
|
||||
}
|
||||
|
||||
// Scheduling a job on each hour to update the last hour rainfall
|
||||
offlineTimerJob = scheduler.schedule(pollingRunnable, 90, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="meteostick" 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>MeteoStick Binding</name>
|
||||
<description>This is the binding for MeteoStick USB weather station receiver.</description>
|
||||
<author>Chris Jackson</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,202 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="meteostick"
|
||||
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">
|
||||
|
||||
<bridge-type id="meteostick_bridge">
|
||||
<label>Meteostick USB Receiver</label>
|
||||
<description>Meteostick USB Receiver</description>
|
||||
<channels>
|
||||
<channel id="pressure" typeId="pressure"/>
|
||||
<channel id="indoor-temperature" typeId="indoor-temperature"/>
|
||||
</channels>
|
||||
<config-description>
|
||||
<parameter name="port" type="text" required="true">
|
||||
<label>Serial Port</label>
|
||||
<context>serial-port</context>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
<description>Serial port that the Meteostick is plugged into</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="integer" required="true">
|
||||
<label>Frequency Band</label>
|
||||
<description>Specifies the frequency mode including device, region and frequency that Meteostick will use</description>
|
||||
<options>
|
||||
<option value="0">Davis - North America (915MHz)</option>
|
||||
<option value="1">Davis - Europe (868MHz)</option>
|
||||
<option value="2">Davis - Australia (915Mhz)</option>
|
||||
<option value="3">Fine Offset - North America (915MHz)</option>
|
||||
<option value="4">Fine Offset - Europe (868MHz)</option>
|
||||
<option value="5">Davis - New Zealand (931.5Mhz)</option>
|
||||
</options>
|
||||
<default>1</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="meteostick_davis_iss">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="meteostick_bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Davis Vantage Vue ISS</label>
|
||||
<description>Davis Integrated Sensor Suite</description>
|
||||
<channels>
|
||||
<channel id="outdoor-temperature" typeId="outdoor-temperature"/>
|
||||
<channel id="humidity" typeId="humidity"/>
|
||||
<channel id="wind-direction" typeId="wind-direction"/>
|
||||
<channel id="wind-direction-last2min-average" typeId="wind-direction-last2min-average"/>
|
||||
<channel id="wind-speed" typeId="wind-speed"/>
|
||||
<channel id="wind-speed-last2min-average" typeId="wind-speed-last2min-average"/>
|
||||
<channel id="wind-speed-last2min-maximum" typeId="wind-speed-last2min-maximum"/>
|
||||
<channel id="rain-raw" typeId="rain-raw"/>
|
||||
<channel id="rain-currenthour" typeId="rain-currenthour"/>
|
||||
<channel id="rain-lasthour" typeId="rain-lasthour"/>
|
||||
<channel id="solar-power" typeId="solar-power"/>
|
||||
<channel id="signal-strength" typeId="system.signal-strength"/>
|
||||
<channel id="low-battery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="channel" type="integer" required="true">
|
||||
<label>Channel</label>
|
||||
<description>Specifies the channel the sensor is using</description>
|
||||
<options>
|
||||
<option value="1">Channel 1</option>
|
||||
<option value="2">Channel 2</option>
|
||||
<option value="3">Channel 3</option>
|
||||
<option value="4">Channel 4</option>
|
||||
<option value="5">Channel 5</option>
|
||||
<option value="6">Channel 6</option>
|
||||
<option value="7">Channel 7</option>
|
||||
<option value="8">Channel 8</option>
|
||||
</options>
|
||||
</parameter>
|
||||
|
||||
<parameter name="spoon" type="decimal" required="false">
|
||||
<label>Spoon</label>
|
||||
<description>Specifies the amount of rain needed to tip spoon</description>
|
||||
<default>0.254</default>
|
||||
<unitLabel>mm</unitLabel>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="indoor-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Indoor Temperature</label>
|
||||
<description>Current indoor temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="outdoor-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Outdoor Temperature</label>
|
||||
<description>Current outdoor temperature</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity">
|
||||
<item-type>Number</item-type>
|
||||
<label>Outdoor Humidity</label>
|
||||
<description>Current humidity in percent</description>
|
||||
<category>Humidity</category>
|
||||
<state readOnly="true" pattern="%d %%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pressure">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Pressure</label>
|
||||
<description>Current atmospheric pressure</description>
|
||||
<category>Pressure</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="wind-direction">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Wind Direction</label>
|
||||
<description>Wind direction</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="wind-direction-last2min-average" advanced="true">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Wind Direction (Average)</label>
|
||||
<description>Wind direction average over last two minutes</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="wind-speed">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Speed</label>
|
||||
<description>Wind speed</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="wind-speed-last2min-average" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Speed (Average)</label>
|
||||
<description>Wind speed average over last two minutes</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="wind-speed-last2min-maximum" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Speed (Maximum)</label>
|
||||
<description>Wind speed maximum over last two minutes</description>
|
||||
<category>Wind</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rain-raw" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Rainfall (Raw)</label>
|
||||
<description>A counter between 0 and 255 in spoon-sized steps</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.1f">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rain-lasthour">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rainfall (previous Hour)</label>
|
||||
<description>Rainfall in the previous hour</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rain-currenthour">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rainfall (60 Minutes)</label>
|
||||
<description>Rainfall in the last 60 minutes</description>
|
||||
<category>Rain</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="solar-power">
|
||||
<item-type>Number</item-type>
|
||||
<label>Solar Power</label>
|
||||
<description>Solar panel power percentage</description>
|
||||
<category>Light</category>
|
||||
<state readOnly="true" pattern="%.1f %%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user