[ipobserver] Weather station binding, Initial contribution. (#10567)
* Bulk updated to UOM. Signed-off-by: Matthew Skinner <matt@pcmus.com> * ipObserver creation Signed-off-by: Matthew Skinner <matt@pcmus.com> * Bulk updated to UOM. Signed-off-by: Matthew Skinner <matt@pcmus.com> * channel fixup for UOM. Signed-off-by: Matthew Skinner <matt@pcmus.com> * improve UOM. Signed-off-by: Matthew Skinner <matt@pcmus.com> * updates Signed-off-by: Matthew Skinner <matt@pcmus.com> * Battery ch fixed. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix time channels. Signed-off-by: Matthew Skinner <matt@pcmus.com> * readme update and remove %unit% from rain channels. Signed-off-by: Matthew Skinner <matt@pcmus.com> * readme fixup. Signed-off-by: Matthew Skinner <matt@pcmus.com> * edit global files. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix merge conflicts. Signed-off-by: Matthew Skinner <matt@pcmus.com> * fix up build issues. Signed-off-by: Matthew Skinner <matt@pcmus.com> * remove reboot channel. Signed-off-by: Matthew Skinner <matt@pcmus.com> * readme fixup. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Rename channels to put kind first. Signed-off-by: Matthew Skinner <matt@pcmus.com> * update to build on latest main. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add support for outBatt1 Signed-off-by: Matthew Skinner <matt@pcmus.com> * Added auto discovery. Signed-off-by: Matthew Skinner <matt@pcmus.com> * add bundle to POM. Signed-off-by: Matthew Skinner <matt@pcmus.com> * newline added. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix bug in discovery. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Added tags Signed-off-by: Matthew Skinner <matt@pcmus.com> * update to 3.2.0-SNAPSHOT Signed-off-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/thing/thing-types.xml Signed-off-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> * Update bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/thing/thing-types.xml Signed-off-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> * Clean up channels Signed-off-by: Matthew Skinner <matt@pcmus.com> * Update binding description. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix jsoup suggestions. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.ipobserver/src/main/java/org/openhab/binding/ipobserver/internal/IpObserverDiscoveryService.java Signed-off-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> * Update bundles/org.openhab.binding.ipobserver/src/main/resources/OH-INF/thing/thing-types.xml Signed-off-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> * Removed nullable. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Improvements Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix compiler warnings Signed-off-by: Matthew Skinner <matt@pcmus.com> * Change to datetime Signed-off-by: Matthew Skinner <matt@pcmus.com> * change to use system channels. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Move to Number:Intensity for solar Signed-off-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.ipobserver-${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-ipobserver" description="IpObserver Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle dependency="true">mvn:org.jsoup/jsoup/1.8.3</bundle>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.ipobserver/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.ipobserver.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link IpObserverBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IpObserverBindingConstants {
|
||||
public static final String BINDING_ID = "ipobserver";
|
||||
public static final String REBOOT_URL = "/msgreboot.htm";
|
||||
public static final String LIVE_DATA_URL = "/livedata.htm";
|
||||
public static final String STATION_SETTINGS_URL = "/station.htm";
|
||||
public static final int DISCOVERY_THREAD_POOL_SIZE = 15;
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_WEATHER_STATION = new ThingTypeUID(BINDING_ID, "weatherstation");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String TEMP_INDOOR = "temperatureIndoor";
|
||||
public static final String TEMP_OUTDOOR = "temperatureOutdoor";
|
||||
public static final String INDOOR_HUMIDITY = "humidityIndoor";
|
||||
public static final String OUTDOOR_HUMIDITY = "humidityOutdoor";
|
||||
public static final String ABS_PRESSURE = "pressureAbsolute";
|
||||
public static final String REL_PRESSURE = "pressureRelative";
|
||||
public static final String WIND_DIRECTION = "windDirection";
|
||||
public static final String WIND_AVERAGE_SPEED = "windAverageSpeed";
|
||||
public static final String WIND_SPEED = "windSpeed";
|
||||
public static final String WIND_GUST = "windGust";
|
||||
public static final String WIND_MAX_GUST = "windMaxGust";
|
||||
public static final String SOLAR_RADIATION = "solarRadiation";
|
||||
public static final String UV = "uv";
|
||||
public static final String UV_INDEX = "uvIndex";
|
||||
public static final String HOURLY_RAIN_RATE = "rainHourlyRate";
|
||||
public static final String DAILY_RAIN = "rainToday";
|
||||
public static final String WEEKLY_RAIN = "rainForWeek";
|
||||
public static final String MONTHLY_RAIN = "rainForMonth";
|
||||
public static final String YEARLY_RAIN = "rainForYear";
|
||||
public static final String INDOOR_BATTERY = "batteryIndoor";
|
||||
public static final String OUTDOOR_BATTERY = "batteryOutdoor";
|
||||
public static final String RESPONSE_TIME = "responseTime";
|
||||
public static final String LAST_UPDATED_TIME = "lastUpdatedTime";
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.ipobserver.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link IpObserverConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IpObserverConfiguration {
|
||||
public String address = "";
|
||||
public int pollTime = 20;
|
||||
public int autoReboot = 2000;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.ipobserver.internal;
|
||||
|
||||
import static org.openhab.binding.ipobserver.internal.IpObserverBindingConstants.LIVE_DATA_URL;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* The {@link IpObserverDiscoveryJob} class allows auto discovery of
|
||||
* devices for a single IP address. This is used
|
||||
* for threading to make discovery faster.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IpObserverDiscoveryJob implements Runnable {
|
||||
private IpObserverDiscoveryService discoveryClass;
|
||||
private String ipAddress;
|
||||
|
||||
public IpObserverDiscoveryJob(IpObserverDiscoveryService service, String ip) {
|
||||
this.discoveryClass = service;
|
||||
this.ipAddress = ip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (isIpObserverDevice(this.ipAddress)) {
|
||||
discoveryClass.submitDiscoveryResults(this.ipAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isIpObserverDevice(String ip) {
|
||||
Request request = discoveryClass.getHttpClient().newRequest("http://" + ip + LIVE_DATA_URL);
|
||||
request.method(HttpMethod.GET).timeout(5, TimeUnit.SECONDS).header(HttpHeader.ACCEPT_ENCODING, "gzip");
|
||||
ContentResponse contentResponse;
|
||||
try {
|
||||
contentResponse = request.send();
|
||||
if (contentResponse.getStatus() == 200 && contentResponse.getContentAsString().contains("livedata.htm")) {
|
||||
return true;
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.ipobserver.internal;
|
||||
|
||||
import static org.openhab.binding.ipobserver.internal.IpObserverBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
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 IpObserverDiscoveryService} is responsible for finding ipObserver devices.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution.
|
||||
*/
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.ipobserver")
|
||||
@NonNullByDefault
|
||||
public class IpObserverDiscoveryService extends AbstractDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_WEATHER_STATION);
|
||||
private ExecutorService discoverySearchPool = Executors.newFixedThreadPool(DISCOVERY_THREAD_POOL_SIZE);
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public IpObserverDiscoveryService(@Reference HttpClientFactory httpClientFactory) {
|
||||
super(SUPPORTED_THING_TYPES_UIDS, 240);
|
||||
httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
protected HttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public void submitDiscoveryResults(String ip) {
|
||||
ThingUID thingUID = new ThingUID(THING_WEATHER_STATION, ip.replace('.', '_'));
|
||||
HashMap<String, Object> properties = new HashMap<>();
|
||||
properties.put("address", ip);
|
||||
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel("Weather Station")
|
||||
.withRepresentationProperty("address").build());
|
||||
}
|
||||
|
||||
private void scanSingleSubnet(InterfaceAddress hostAddress) {
|
||||
byte[] broadcastAddress = hostAddress.getBroadcast().getAddress();
|
||||
// Create subnet mask from length
|
||||
int shft = 0xffffffff << (32 - hostAddress.getNetworkPrefixLength());
|
||||
byte oct1 = (byte) (((byte) ((shft & 0xff000000) >> 24)) & 0xff);
|
||||
byte oct2 = (byte) (((byte) ((shft & 0x00ff0000) >> 16)) & 0xff);
|
||||
byte oct3 = (byte) (((byte) ((shft & 0x0000ff00) >> 8)) & 0xff);
|
||||
byte oct4 = (byte) (((byte) (shft & 0x000000ff)) & 0xff);
|
||||
byte[] subnetMask = new byte[] { oct1, oct2, oct3, oct4 };
|
||||
// calc first IP to start scanning from on this subnet
|
||||
byte[] startAddress = new byte[4];
|
||||
startAddress[0] = (byte) (broadcastAddress[0] & subnetMask[0]);
|
||||
startAddress[1] = (byte) (broadcastAddress[1] & subnetMask[1]);
|
||||
startAddress[2] = (byte) (broadcastAddress[2] & subnetMask[2]);
|
||||
startAddress[3] = (byte) (broadcastAddress[3] & subnetMask[3]);
|
||||
// Loop from start of subnet to the broadcast address.
|
||||
for (int i = ByteBuffer.wrap(startAddress).getInt(); i < ByteBuffer.wrap(broadcastAddress).getInt(); i++) {
|
||||
try {
|
||||
InetAddress currentIP = InetAddress.getByAddress(ByteBuffer.allocate(4).putInt(i).array());
|
||||
// Try to reach each IP with a timeout of 500ms which is enough for local network
|
||||
if (currentIP.isReachable(500)) {
|
||||
String host = currentIP.getHostAddress().toString();
|
||||
logger.debug("Unknown device was found at: {}", host);
|
||||
discoverySearchPool.execute(new IpObserverDiscoveryJob(this, host));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
discoverySearchPool = Executors.newFixedThreadPool(DISCOVERY_THREAD_POOL_SIZE);
|
||||
try {
|
||||
ipAddressScan();
|
||||
} catch (Exception exp) {
|
||||
logger.debug("IpObserver discovery service encountered an error while scanning for devices: {}",
|
||||
exp.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopScan() {
|
||||
discoverySearchPool.shutdown();
|
||||
super.stopScan();
|
||||
}
|
||||
|
||||
private void ipAddressScan() {
|
||||
try {
|
||||
for (Enumeration<NetworkInterface> enumNetworks = NetworkInterface.getNetworkInterfaces(); enumNetworks
|
||||
.hasMoreElements();) {
|
||||
NetworkInterface networkInterface = enumNetworks.nextElement();
|
||||
List<InterfaceAddress> list = networkInterface.getInterfaceAddresses();
|
||||
for (InterfaceAddress hostAddress : list) {
|
||||
InetAddress inetAddress = hostAddress.getAddress();
|
||||
if (!inetAddress.isLoopbackAddress() && inetAddress.isSiteLocalAddress()) {
|
||||
logger.debug("Scanning all IP address's that IP {}/{} is on", hostAddress.getAddress(),
|
||||
hostAddress.getNetworkPrefixLength());
|
||||
scanSingleSubnet(hostAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.ipobserver.internal;
|
||||
|
||||
import static org.openhab.binding.ipobserver.internal.IpObserverBindingConstants.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.TypeParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link IpObserverHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Thomas Hentschel - Initial contribution.
|
||||
* @author Matthew Skinner - Full re-write for BND, V3.0 and UOM
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IpObserverHandler extends BaseThingHandler {
|
||||
private final HttpClient httpClient;
|
||||
private final Logger logger = LoggerFactory.getLogger(IpObserverHandler.class);
|
||||
private Map<String, ChannelHandler> channelHandlers = new HashMap<String, ChannelHandler>();
|
||||
private @Nullable ScheduledFuture<?> pollingFuture = null;
|
||||
private IpObserverConfiguration config = new IpObserverConfiguration();
|
||||
// Config settings parsed from weather station.
|
||||
private boolean imperialTemperature = false;
|
||||
private boolean imperialRain = false;
|
||||
// 0=lux, 1=w/m2, 2=fc
|
||||
private String solarUnit = "0";
|
||||
// 0=m/s, 1=km/h, 2=ft/s, 3=bft, 4=mph, 5=knot
|
||||
private String windUnit = "0";
|
||||
// 0=hpa, 1=inhg, 2=mmhg
|
||||
private String pressureUnit = "0";
|
||||
|
||||
private class ChannelHandler {
|
||||
private IpObserverHandler handler;
|
||||
private Channel channel;
|
||||
private String previousValue = "";
|
||||
private Unit<?> unit;
|
||||
private final ArrayList<Class<? extends State>> acceptedDataTypes = new ArrayList<Class<? extends State>>();
|
||||
|
||||
ChannelHandler(IpObserverHandler handler, Channel channel, Class<? extends State> acceptable, Unit<?> unit) {
|
||||
super();
|
||||
this.handler = handler;
|
||||
this.channel = channel;
|
||||
this.unit = unit;
|
||||
acceptedDataTypes.add(acceptable);
|
||||
}
|
||||
|
||||
public void processValue(String sensorValue) {
|
||||
if (!sensorValue.equals(previousValue)) {
|
||||
previousValue = sensorValue;
|
||||
switch (channel.getUID().getId()) {
|
||||
case LAST_UPDATED_TIME:
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm MM/dd/yyyy")
|
||||
.withZone(TimeZone.getDefault().toZoneId());
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.parse(sensorValue, formatter);
|
||||
this.handler.updateState(this.channel.getUID(), new DateTimeType(zonedDateTime));
|
||||
} catch (DateTimeParseException e) {
|
||||
logger.debug("Could not parse {} as a valid dateTime", sensorValue);
|
||||
}
|
||||
return;
|
||||
case INDOOR_BATTERY:
|
||||
case OUTDOOR_BATTERY:
|
||||
if ("1".equals(sensorValue)) {
|
||||
handler.updateState(this.channel.getUID(), OnOffType.ON);
|
||||
} else {
|
||||
handler.updateState(this.channel.getUID(), OnOffType.OFF);
|
||||
}
|
||||
return;
|
||||
}
|
||||
State state = TypeParser.parseState(this.acceptedDataTypes, sensorValue);
|
||||
if (state == null) {
|
||||
return;
|
||||
} else if (state instanceof QuantityType) {
|
||||
handler.updateState(this.channel.getUID(),
|
||||
QuantityType.valueOf(Double.parseDouble(sensorValue), unit));
|
||||
} else {
|
||||
handler.updateState(this.channel.getUID(), state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IpObserverHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
private void parseSettings(String html) {
|
||||
Document doc = Jsoup.parse(html);
|
||||
solarUnit = doc.select("select[name=unit_Solar] option[selected]").val();
|
||||
windUnit = doc.select("select[name=unit_Wind] option[selected]").val();
|
||||
pressureUnit = doc.select("select[name=unit_Pressure] option[selected]").val();
|
||||
// 0=degC, 1=degF
|
||||
if ("1".equals(doc.select("select[name=u_Temperature] option[selected]").val())) {
|
||||
imperialTemperature = true;
|
||||
} else {
|
||||
imperialTemperature = false;
|
||||
}
|
||||
// 0=mm, 1=in
|
||||
if ("1".equals(doc.select("select[name=u_Rainfall] option[selected]").val())) {
|
||||
imperialRain = true;
|
||||
} else {
|
||||
imperialRain = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseAndUpdate(String html) {
|
||||
Document doc = Jsoup.parse(html);
|
||||
String value = doc.select("select[name=inBattSta] option[selected]").val();
|
||||
ChannelHandler localUpdater = channelHandlers.get("inBattSta");
|
||||
if (localUpdater != null) {
|
||||
localUpdater.processValue(value);
|
||||
}
|
||||
value = doc.select("select[name=outBattSta] option[selected]").val();
|
||||
localUpdater = channelHandlers.get("outBattSta");
|
||||
if (localUpdater != null) {
|
||||
localUpdater.processValue(value);
|
||||
}
|
||||
|
||||
Elements elements = doc.select("input");
|
||||
for (Element element : elements) {
|
||||
String elementName = element.attr("name");
|
||||
value = element.attr("value");
|
||||
if (!value.isEmpty()) {
|
||||
logger.trace("Found element {}, value is {}", elementName, value);
|
||||
localUpdater = channelHandlers.get(elementName);
|
||||
if (localUpdater != null) {
|
||||
localUpdater.processValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendGetRequest(String url) {
|
||||
Request request = httpClient.newRequest("http://" + config.address + url);
|
||||
request.method(HttpMethod.GET).timeout(5, TimeUnit.SECONDS).header(HttpHeader.ACCEPT_ENCODING, "gzip");
|
||||
String errorReason = "";
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
ContentResponse contentResponse = request.send();
|
||||
if (contentResponse.getStatus() == 200) {
|
||||
long responseTime = (System.currentTimeMillis() - start);
|
||||
if (!this.getThing().getStatus().equals(ThingStatus.ONLINE)) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
logger.debug("Finding out which units of measurement the weather station is using.");
|
||||
sendGetRequest(STATION_SETTINGS_URL);
|
||||
}
|
||||
if (url == STATION_SETTINGS_URL) {
|
||||
parseSettings(contentResponse.getContentAsString());
|
||||
setupChannels();
|
||||
} else {
|
||||
updateState(RESPONSE_TIME, new QuantityType<>(responseTime, MetricPrefix.MILLI(Units.SECOND)));
|
||||
parseAndUpdate(contentResponse.getContentAsString());
|
||||
}
|
||||
if (config.autoReboot > 0 && responseTime > config.autoReboot) {
|
||||
logger.debug("An Auto reboot of the IP Observer unit has been triggered as the response was {}ms.",
|
||||
responseTime);
|
||||
sendGetRequest(REBOOT_URL);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
errorReason = String.format("IpObserver request failed with %d: %s", contentResponse.getStatus(),
|
||||
contentResponse.getReason());
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
errorReason = "TimeoutException: IpObserver was not reachable on your network";
|
||||
} catch (ExecutionException e) {
|
||||
errorReason = String.format("ExecutionException: %s", e.getMessage());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
errorReason = String.format("InterruptedException: %s", e.getMessage());
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorReason);
|
||||
}
|
||||
|
||||
private void pollStation() {
|
||||
sendGetRequest(LIVE_DATA_URL);
|
||||
}
|
||||
|
||||
private void createChannelHandler(String chanName, Class<? extends State> type, Unit<?> unit, String htmlName) {
|
||||
@Nullable
|
||||
Channel channel = this.getThing().getChannel(chanName);
|
||||
if (channel != null) {
|
||||
channelHandlers.put(htmlName, new ChannelHandler(this, channel, type, unit));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupChannels() {
|
||||
if (imperialTemperature) {
|
||||
logger.debug("Using imperial units of measurement for temperature.");
|
||||
createChannelHandler(TEMP_INDOOR, QuantityType.class, ImperialUnits.FAHRENHEIT, "inTemp");
|
||||
createChannelHandler(TEMP_OUTDOOR, QuantityType.class, ImperialUnits.FAHRENHEIT, "outTemp");
|
||||
} else {
|
||||
logger.debug("Using metric units of measurement for temperature.");
|
||||
createChannelHandler(TEMP_INDOOR, QuantityType.class, SIUnits.CELSIUS, "inTemp");
|
||||
createChannelHandler(TEMP_OUTDOOR, QuantityType.class, SIUnits.CELSIUS, "outTemp");
|
||||
}
|
||||
|
||||
if (imperialRain) {
|
||||
createChannelHandler(HOURLY_RAIN_RATE, QuantityType.class, ImperialUnits.INCH, "rainofhourly");
|
||||
createChannelHandler(DAILY_RAIN, QuantityType.class, ImperialUnits.INCH, "rainofdaily");
|
||||
createChannelHandler(WEEKLY_RAIN, QuantityType.class, ImperialUnits.INCH, "rainofweekly");
|
||||
createChannelHandler(MONTHLY_RAIN, QuantityType.class, ImperialUnits.INCH, "rainofmonthly");
|
||||
createChannelHandler(YEARLY_RAIN, QuantityType.class, ImperialUnits.INCH, "rainofyearly");
|
||||
} else {
|
||||
logger.debug("Using metric units of measurement for rain.");
|
||||
createChannelHandler(HOURLY_RAIN_RATE, QuantityType.class, MetricPrefix.MILLI(SIUnits.METRE),
|
||||
"rainofhourly");
|
||||
createChannelHandler(DAILY_RAIN, QuantityType.class, MetricPrefix.MILLI(SIUnits.METRE), "rainofdaily");
|
||||
createChannelHandler(WEEKLY_RAIN, QuantityType.class, MetricPrefix.MILLI(SIUnits.METRE), "rainofweekly");
|
||||
createChannelHandler(MONTHLY_RAIN, QuantityType.class, MetricPrefix.MILLI(SIUnits.METRE), "rainofmonthly");
|
||||
createChannelHandler(YEARLY_RAIN, QuantityType.class, MetricPrefix.MILLI(SIUnits.METRE), "rainofyearly");
|
||||
}
|
||||
|
||||
if ("5".equals(windUnit)) {
|
||||
createChannelHandler(WIND_AVERAGE_SPEED, QuantityType.class, Units.KNOT, "avgwind");
|
||||
createChannelHandler(WIND_SPEED, QuantityType.class, Units.KNOT, "windspeed");
|
||||
createChannelHandler(WIND_GUST, QuantityType.class, Units.KNOT, "gustspeed");
|
||||
createChannelHandler(WIND_MAX_GUST, QuantityType.class, Units.KNOT, "dailygust");
|
||||
} else if ("4".equals(windUnit)) {
|
||||
createChannelHandler(WIND_AVERAGE_SPEED, QuantityType.class, ImperialUnits.MILES_PER_HOUR, "avgwind");
|
||||
createChannelHandler(WIND_SPEED, QuantityType.class, ImperialUnits.MILES_PER_HOUR, "windspeed");
|
||||
createChannelHandler(WIND_GUST, QuantityType.class, ImperialUnits.MILES_PER_HOUR, "gustspeed");
|
||||
createChannelHandler(WIND_MAX_GUST, QuantityType.class, ImperialUnits.MILES_PER_HOUR, "dailygust");
|
||||
} else if ("1".equals(windUnit)) {
|
||||
createChannelHandler(WIND_AVERAGE_SPEED, QuantityType.class, SIUnits.KILOMETRE_PER_HOUR, "avgwind");
|
||||
createChannelHandler(WIND_SPEED, QuantityType.class, SIUnits.KILOMETRE_PER_HOUR, "windspeed");
|
||||
createChannelHandler(WIND_GUST, QuantityType.class, SIUnits.KILOMETRE_PER_HOUR, "gustspeed");
|
||||
createChannelHandler(WIND_MAX_GUST, QuantityType.class, SIUnits.KILOMETRE_PER_HOUR, "dailygust");
|
||||
} else if ("0".equals(windUnit)) {
|
||||
createChannelHandler(WIND_AVERAGE_SPEED, QuantityType.class, Units.METRE_PER_SECOND, "avgwind");
|
||||
createChannelHandler(WIND_SPEED, QuantityType.class, Units.METRE_PER_SECOND, "windspeed");
|
||||
createChannelHandler(WIND_GUST, QuantityType.class, Units.METRE_PER_SECOND, "gustspeed");
|
||||
createChannelHandler(WIND_MAX_GUST, QuantityType.class, Units.METRE_PER_SECOND, "dailygust");
|
||||
} else {
|
||||
logger.warn(
|
||||
"The IP Observer is sending a wind format the binding does not support. Select one of the other units.");
|
||||
}
|
||||
|
||||
if ("1".equals(solarUnit)) {
|
||||
createChannelHandler(SOLAR_RADIATION, QuantityType.class, Units.IRRADIANCE, "solarrad");
|
||||
} else if ("0".equals(solarUnit)) {
|
||||
createChannelHandler(SOLAR_RADIATION, QuantityType.class, Units.LUX, "solarrad");
|
||||
} else {
|
||||
logger.warn(
|
||||
"The IP Observer is sending fc (Foot Candles) for the solar radiation. Select one of the other units.");
|
||||
}
|
||||
|
||||
if ("0".equals(pressureUnit)) {
|
||||
createChannelHandler(ABS_PRESSURE, QuantityType.class, MetricPrefix.HECTO(SIUnits.PASCAL), "AbsPress");
|
||||
createChannelHandler(REL_PRESSURE, QuantityType.class, MetricPrefix.HECTO(SIUnits.PASCAL), "RelPress");
|
||||
} else if ("1".equals(pressureUnit)) {
|
||||
createChannelHandler(ABS_PRESSURE, QuantityType.class, ImperialUnits.INCH_OF_MERCURY, "AbsPress");
|
||||
createChannelHandler(REL_PRESSURE, QuantityType.class, ImperialUnits.INCH_OF_MERCURY, "RelPress");
|
||||
} else if ("2".equals(pressureUnit)) {
|
||||
createChannelHandler(ABS_PRESSURE, QuantityType.class, Units.MILLIMETRE_OF_MERCURY, "AbsPress");
|
||||
createChannelHandler(REL_PRESSURE, QuantityType.class, Units.MILLIMETRE_OF_MERCURY, "RelPress");
|
||||
}
|
||||
|
||||
createChannelHandler(WIND_DIRECTION, QuantityType.class, Units.DEGREE_ANGLE, "windir");
|
||||
createChannelHandler(INDOOR_HUMIDITY, DecimalType.class, Units.PERCENT, "inHumi");
|
||||
createChannelHandler(OUTDOOR_HUMIDITY, DecimalType.class, Units.PERCENT, "outHumi");
|
||||
// The units for the following are ignored as they are not a QuantityType.class
|
||||
createChannelHandler(UV, DecimalType.class, SIUnits.CELSIUS, "uv");
|
||||
createChannelHandler(UV_INDEX, DecimalType.class, SIUnits.CELSIUS, "uvi");
|
||||
// was outBattSta1 so some units may use this instead?
|
||||
createChannelHandler(OUTDOOR_BATTERY, StringType.class, Units.PERCENT, "outBattSta");
|
||||
createChannelHandler(OUTDOOR_BATTERY, StringType.class, Units.PERCENT, "outBattSta1");
|
||||
createChannelHandler(INDOOR_BATTERY, StringType.class, Units.PERCENT, "inBattSta");
|
||||
createChannelHandler(LAST_UPDATED_TIME, DateTimeType.class, SIUnits.CELSIUS, "CurrTime");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(IpObserverConfiguration.class);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
pollingFuture = scheduler.scheduleWithFixedDelay(this::pollStation, 1, config.pollTime, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
channelHandlers.clear();
|
||||
ScheduledFuture<?> localFuture = pollingFuture;
|
||||
if (localFuture != null) {
|
||||
localFuture.cancel(true);
|
||||
localFuture = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.ipobserver.internal;
|
||||
|
||||
import static org.openhab.binding.ipobserver.internal.IpObserverBindingConstants.THING_WEATHER_STATION;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
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 IpObserverHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.ipobserver", service = ThingHandlerFactory.class)
|
||||
public class IpObserverHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_WEATHER_STATION);
|
||||
protected final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public IpObserverHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
protected HttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_WEATHER_STATION.equals(thingTypeUID)) {
|
||||
return new IpObserverHandler(thing, httpClient);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="ipobserver" 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>IpObserver Binding</name>
|
||||
<description>This is the binding for weather stations marketed under many brands that come with or have an IpObserver
|
||||
station connected.</description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,249 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="ipobserver"
|
||||
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">
|
||||
|
||||
<thing-type id="weatherstation">
|
||||
<label>Weather Station</label>
|
||||
<description>Use for any weather station sold under multiple brands that come with an IP Observer unit.</description>
|
||||
<channels>
|
||||
<channel id="temperatureIndoor" typeId="temperatureIndoor"/>
|
||||
<channel id="temperatureOutdoor" typeId="system.outdoor-temperature"/>
|
||||
<channel id="humidityIndoor" typeId="humidityIndoor"/>
|
||||
<channel id="humidityOutdoor" typeId="system.atmospheric-humidity"/>
|
||||
<channel id="pressureAbsolute" typeId="pressureAbsolute"/>
|
||||
<channel id="pressureRelative" typeId="pressureRelative"/>
|
||||
<channel id="windDirection" typeId="system.wind-direction"/>
|
||||
<channel id="windAverageSpeed" typeId="windAverageSpeed"/>
|
||||
<channel id="windSpeed" typeId="windSpeed"/>
|
||||
<channel id="windGust" typeId="windGust"/>
|
||||
<channel id="windMaxGust" typeId="windMaxGust"/>
|
||||
<channel id="solarRadiation" typeId="solarRadiation"/>
|
||||
<channel id="uv" typeId="uv"/>
|
||||
<channel id="uvIndex" typeId="uvIndex"/>
|
||||
<channel id="rainHourlyRate" typeId="rainHourlyRate"/>
|
||||
<channel id="rainToday" typeId="rainToday"/>
|
||||
<channel id="rainForWeek" typeId="rainForWeek"/>
|
||||
<channel id="rainForMonth" typeId="rainForMonth"/>
|
||||
<channel id="rainForYear" typeId="rainForYear"/>
|
||||
<channel id="batteryOutdoor" typeId="system.low-battery"/>
|
||||
<channel id="batteryIndoor" typeId="system.low-battery"/>
|
||||
<channel id="responseTime" typeId="responseTime"/>
|
||||
<channel id="lastUpdatedTime" typeId="lastUpdatedTime"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Network Address</label>
|
||||
<description>Hostname or IP for the IP Observer</description>
|
||||
<default>192.168.1.243</default>
|
||||
</parameter>
|
||||
<parameter name="pollTime" type="integer" required="true" min="5" max="3600" unit="s">
|
||||
<label>Poll Time</label>
|
||||
<description>Time in seconds between each Scan of the livedata.htm from the ObserverIP</description>
|
||||
<default>20</default>
|
||||
</parameter>
|
||||
<parameter name="autoReboot" type="integer" required="true" min="0" max="20000" unit="ms">
|
||||
<label>Auto Reboot</label>
|
||||
<description>Time in milliseconds to wait for a reply before rebooting the IP Observer. A value of 0 disables this
|
||||
feature allowing you to manually trigger or use a rule to handle the reboots</description>
|
||||
<default>2000</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
<channel-type id="responseTime" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Response Time</label>
|
||||
<description>How many milliseconds it took to fetch the sensor readings from livedata.htm</description>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="temperatureIndoor">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Indoor Temperature</label>
|
||||
<description>Current Temperature Indoors</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="humidityIndoor">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Indoor Humidity</label>
|
||||
<description>Current Humidity Indoors</description>
|
||||
<category>Humidity</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Humidity</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f %%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="pressureAbsolute">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Pressure Absolute</label>
|
||||
<description>Absolute Current Pressure</description>
|
||||
<category>Pressure</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Pressure</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="pressureRelative">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Pressure Relative</label>
|
||||
<description>Relative Current Pressure</description>
|
||||
<category>Pressure</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Pressure</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="solarRadiation">
|
||||
<item-type>Number:Intensity</item-type>
|
||||
<label>Solar Radiation</label>
|
||||
<description>Solar Radiation</description>
|
||||
<category>Sun</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Light</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="uv" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>UV</label>
|
||||
<description>UV</description>
|
||||
<category>Sun</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Light</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="uvIndex" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>UV Index</label>
|
||||
<description>UV Index</description>
|
||||
<category>Sun</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Light</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="windAverageSpeed">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Average Speed</label>
|
||||
<description>Average Wind Speed</description>
|
||||
<category>Wind</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Wind</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="windSpeed" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Speed</label>
|
||||
<description>Wind Speed</description>
|
||||
<category>Wind</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Wind</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="windGust" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Gust</label>
|
||||
<description>Wind Gust</description>
|
||||
<category>Wind</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Wind</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="windMaxGust" advanced="true">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Wind Max Gust</label>
|
||||
<description>Max wind gust for today</description>
|
||||
<category>Wind</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Wind</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="rainHourlyRate">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain Hourly Rate</label>
|
||||
<description>How much rain will fall in an Hour if the rate continues</description>
|
||||
<category>Rain</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Rain</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="rainToday">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain Today</label>
|
||||
<description>Rain since Midnight</description>
|
||||
<category>Rain</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Rain</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="rainForWeek" advanced="true">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain for Week</label>
|
||||
<description>Weekly Rain</description>
|
||||
<category>Rain</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Rain</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="rainForMonth" advanced="true">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain for Month</label>
|
||||
<description>Rain since 12:00 on the 1st of this month</description>
|
||||
<category>Rain</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Rain</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="rainForYear">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Rain for Year</label>
|
||||
<description>Total rain since 12:00 on 1st Jan</description>
|
||||
<category>Rain</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Rain</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="lastUpdatedTime" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Updated Time</label>
|
||||
<description>Time of the last livedata scrape</description>
|
||||
<category>Time</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Timestamp</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user