added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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