mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[opensprinkler] Improvements: Remove apache.commons, fix bugs and warnings (#9869)
* Fix: current channel would not get added. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix: online and offline now detect correctly. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Improve discovery. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Bug fixes. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Misc Improvements and log cleanup. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Make current sensor channel dynamically get removed if not supported. Signed-off-by: Matthew Skinner <matt@pcmus.com> * fix compiler warning. Signed-off-by: Matthew Skinner <matt@pcmus.com> * readme updates. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Shift refresh() Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add RSSI and water flow meter count channels. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Change to flowSensorCount Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix bug Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix NPE on parsing jsReplies. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add new programs channel. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix wrong api use. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Change to using max time if the time is null. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add device withRepresentationProperty Signed-off-by: Matthew Skinner <matt@pcmus.com> * update readme Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix rain sensor not working in 219 firmware. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Spotless fixes. Signed-off-by: Matthew Skinner <matt@pcmus.com> * fix gson double up error. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add more channels Signed-off-by: Matthew Skinner <matt@pcmus.com> * refresh completed before bridge goes online. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Simplify. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Allow non default password to not stop discovery. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add ignoresRain channel Signed-off-by: Matthew Skinner <matt@pcmus.com> * Simplify commands in station handler. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add new rainDelay channel. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Make constants consistent. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Add categories. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Changes requested by FlorianSW Signed-off-by: Matthew Skinner <matt@pcmus.com> * Stop catching and throwing new exp. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Create a state class. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/discovery/OpenSprinklerDiscoveryService.java Signed-off-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Connor Petty <mistercpp2000@gmail.com> * Fix for EOF exception that stops polling with bad wifi. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Change signal strength over to 0-4 range Signed-off-by: Matthew Skinner <matt@pcmus.com> * Handle toUnit null returns. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Support discovery on subnets that are not /24 Signed-off-by: Matthew Skinner <matt@pcmus.com> * update readme for recent changes to signalStrength channel. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Fix code anal issue and readme. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Remove info logging. Signed-off-by: Matthew Skinner <matt@pcmus.com> * remove traces of old dependancy. Signed-off-by: Matthew Skinner <matt@pcmus.com> * Changes for fwolter Signed-off-by: Matthew Skinner <matt@pcmus.com> * support RSSI and rssi Signed-off-by: Matthew Skinner <matt@pcmus.com> * Update after new command sent. Signed-off-by: Matthew Skinner <matt@pcmus.com> * improve delayedRefresh Signed-off-by: Matthew Skinner <matt@pcmus.com> * Remove supressWarnings Signed-off-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Connor Petty <mistercpp2000@gmail.com>
This commit is contained in:
parent
1e8be24fce
commit
6fc24e4aa4
@ -1,31 +1,28 @@
|
||||
# OpenSprinkler Binding
|
||||
|
||||
This binding allows allows basic control of the OpenSprinkler devices.
|
||||
Stations can be controlled to be turned on or off and rain sensor state can be read.
|
||||
This binding allows good and flexible control over your OpenSprinkler devices.
|
||||
You can choose to manually start, stop or delay the stand alone watering programs that are stored and run fully from the OpenSprinkler device.
|
||||
Alternatively you can setup openHAB rules to control everything in more depth by setting up multiple `station` things for each watering zone to gain more in depth control.
|
||||
By using the internal programs and sensors of the OpenSprinkler device, it can remove the complexity of what happens to the watering if openHAB crashes, is rebooted, or drops out of WiFi range in the middle of your watering rules.
|
||||
Mixing the two concepts can also be done, the choice is yours.
|
||||
|
||||
## Supported Bridges
|
||||
|
||||
* HTTP (`http`) - The http bridge allows to communicate with an OpenSprinkler device through the network
|
||||
* `OpenSprinkler HTTP Bridge` is required to communicate with an OpenSprinkler device through the network and should be added first.
|
||||
|
||||
## Supported Things
|
||||
|
||||
* OpenSprinkler Station (`station`) - to control a single station of a device, e.g. to turn it on or off
|
||||
* OpenSprinkler Device (`device`) - for getting device-specific infos, e.g. if rain was detected
|
||||
* `OpenSprinkler Station` is for gaining advanced controls and status information over a single station (zone) of a device, e.g. to turn it on or off, or the time remaining.
|
||||
* `OpenSprinkler Device` is for device-specific controls that usually apply to multiple stations or main unit sensors, e.g. if rain was detected.
|
||||
|
||||
Recommend that you first add a single `device` thing and then if you need the extra controls, add as many of the `station` things as you wish.
|
||||
|
||||
## Discovery
|
||||
|
||||
OpenSprinkler devices can be manually discovered by sending a request to every IP on the network.
|
||||
Discovery needs to be run manually as this is a brute force method of finding devices that can saturate network or device available bandwidth.
|
||||
OpenSprinkler devices can be discovered by the binding sending requests to every IP on your network.
|
||||
Due to this method used, it is very slow at finding devices and can saturate network bandwidth.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
OpenSprinkler using the HTTP interface
|
||||
|
||||
```
|
||||
Bridge opensprinkler:http:http [hostname="127.0.0.1", port=80, password="opendoor", refresh=60] {
|
||||
Thing station 01 [stationIndex=1]
|
||||
}
|
||||
```
|
||||
## Bridge ('http') Configuration
|
||||
|
||||
- hostname: Hostname or IP address of the OpenSprinkler HTTP API.
|
||||
- port: Port the OpenSprinkler device is listening on. Usually 80.
|
||||
@ -36,42 +33,54 @@ Bridge opensprinkler:http:http [hostname="127.0.0.1", port=80, password="opendoo
|
||||
|
||||
### Station Thing Configuration
|
||||
|
||||
The `station` thing can be used with both bridge and has the following configuration properties:
|
||||
The `station` thing must be used with a `http` bridge and has the following configuration properties:
|
||||
|
||||
- stationIndex: The index of the station to communicate with, starting with 0 for the first station
|
||||
|
||||
## Channels
|
||||
|
||||
The following channel is supported by the `station` thing.
|
||||
The following channels are supported by the `station` thing.
|
||||
|
||||
| Channel Type ID | Item Type | | Description |
|
||||
|--------------------|-------------|----|----------------------------------------------------------|
|
||||
| stationState | Switch | RW | This channel indicates whether station 01 is on or off. |
|
||||
| stationState | Switch | RW | This channel indicates whether the station is on or off. |
|
||||
| remainingWaterTime | Number:Time | R | The time the station remains to be open. |
|
||||
| nextDuration | Number:Time | RW | A configuration item, which time, if linked, will be |
|
||||
| | | | used as the time the station will be kept open when |
|
||||
| | | | switched on. It is advised to add persistence for items |
|
||||
| | | | linked to this channel, the binding does not persist |
|
||||
| | | | values of it. |
|
||||
| nextDuration | Number:Time | RW | The amount of time that will be used to keep the station |
|
||||
| | | | open when next manually switched on. If not set, this |
|
||||
| | | | value will default to 18 hours which is the maximum time |
|
||||
| | | | supported. |
|
||||
| queued | Switch | RW | Indicates that the station is queued to be turned on. |
|
||||
| | | | The channel cannot be turned on, only turning it off is |
|
||||
| | | | supported (which removes the station from the queue). |
|
||||
| ignoreRain | Switch | RW | This channel makes the station ignore the rain delay. |
|
||||
|
||||
When using the `nextDuration` channel, it is advised to setup persistence (e.g. MapDB) in order to persist the value through restarts.
|
||||
|
||||
The following is supported by the `device` thing, but only when connected using the http interface.
|
||||
The following channels are supported by the `device` thing.
|
||||
NOTE: Some channels will only show up if the hardware has the required sensor and is setup correctly.
|
||||
|
||||
| Channel Type ID | Item Type | | Description |
|
||||
|-----------------|------------------------|----|------------------------------------------------------------------------------------|
|
||||
| rainsensor | Switch | RO | This channel indicates whether rain is detected by the device or not. |
|
||||
| currentDraw | Number:ElectricCurrent | RO | Shows the current draw of the device. If the device does not have sensors |
|
||||
| | | | for this metric, the channel will not be available. |
|
||||
| sensor2 | Switch | RO | This channel is for the second sensor (if your hardware supports it). |
|
||||
| currentDraw | Number:ElectricCurrent | RO | Shows the current draw of the device. |
|
||||
| waterlevel | Number:Dimensionless | RO | This channel shows the current water level in percent (0-250%). The water level is |
|
||||
| | | | calculated based on the weather and influences the duration of the water programs. |
|
||||
| signalStrength | Number | RO | Shows how strong the WiFi Signal is. |
|
||||
| flowSensorCount | Number:Dimensionless | RO | Shows the number of pulses the optional water flow sensor has reported. |
|
||||
| programs | String | RW | Displays a list of the programs that are setup in your OpenSprinkler and when |
|
||||
| | | | selected will start that program for you. |
|
||||
| stations | String | RW | Display a list of stations that can be run when selected to the length of time set |
|
||||
| | | | in the `nextDuration` channel. |
|
||||
| nextDuration | Number:Time | RW | The time the station will open for when any stations are selected from the |
|
||||
| | | | `stations` channel. Defaults to 30 minutes if not set. |
|
||||
| resetStations | Switch | RW | The ON command will stop all stations immediately, including those waiting to run. |
|
||||
| enablePrograms | Switch | RW | Allow programs to auto run. When OFF, manually started stations will still work. |
|
||||
| rainDelay | Number:Time | RW | Sets/Shows the amount of time (hours) that rain has caused programs to be delayed. |
|
||||
|
||||
## Example
|
||||
## Textual Example
|
||||
|
||||
demo.Things:
|
||||
demo.things:
|
||||
|
||||
```
|
||||
Bridge opensprinkler:http:http [hostname="127.0.0.1", port=81, password="opendoor"] {
|
||||
|
@ -14,17 +14,4 @@
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: OpenSprinkler Binding</name>
|
||||
|
||||
<properties>
|
||||
<dep.noembedding>commons-net</dep.noembedding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>${commons.net.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -1,10 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.opensprinkler-${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-opensprinkler" description="OpenSprinkler Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature dependency="true">openhab.tp-commons-net</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.opensprinkler/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
|
@ -12,6 +12,8 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
@ -25,6 +27,32 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerBindingConstants {
|
||||
public static final String BINDING_ID = "opensprinkler";
|
||||
public static final String DEFAULT_ADMIN_PASSWORD = "opendoor";
|
||||
public static final int DEFAULT_STATION_COUNT = 8;
|
||||
public static final String HTTP_REQUEST_URL_PREFIX = "http://";
|
||||
public static final String HTTPS_REQUEST_URL_PREFIX = "https://";
|
||||
public static final String CMD_ENABLE_MANUAL_MODE = "mm=1";
|
||||
public static final String CMD_DISABLE_MANUAL_MODE = "mm=0";
|
||||
public static final String CMD_PASSWORD = "pw=";
|
||||
public static final String CMD_STATION = "sid=";
|
||||
public static final String CMD_STATION_ENABLE = "en=1";
|
||||
public static final String CMD_STATION_DISABLE = "en=0";
|
||||
public static final String CMD_STATUS_INFO = "jc";
|
||||
public static final String CMD_OPTIONS_INFO = "jo";
|
||||
public static final String CMD_STATION_INFO = "js";
|
||||
public static final String CMD_PROGRAM_DATA = "jp";
|
||||
public static final String CMD_STATION_CONTROL = "cm";
|
||||
public static final String JSON_OPTION_FIRMWARE_VERSION = "fwv";
|
||||
public static final String JSON_OPTION_RAINSENSOR = "rs";
|
||||
public static final String JSON_OPTION_STATION = "sn";
|
||||
public static final String JSON_OPTION_STATION_COUNT = "nstations";
|
||||
public static final String JSON_OPTION_RESULT = "result";
|
||||
public static final int DEFAULT_REFRESH_RATE = 60;
|
||||
public static final int DISCOVERY_THREAD_POOL_SIZE = 15;
|
||||
public static final boolean DISCOVERY_DEFAULT_AUTO_DISCOVER = false;
|
||||
public static final int DISCOVERY_DEFAULT_TIMEOUT_RATE = 500;
|
||||
public static final int DISCOVERY_DEFAULT_IP_TIMEOUT_RATE = 750;
|
||||
public static final BigDecimal MAX_TIME_SECONDS = new BigDecimal(64800);
|
||||
|
||||
// List of all Thing ids
|
||||
public static final String HTTP_BRIDGE = "http";
|
||||
@ -37,19 +65,21 @@ public class OpenSprinklerBindingConstants {
|
||||
public static final ThingTypeUID OPENSPRINKLER_STATION = new ThingTypeUID(BINDING_ID, STATION_THING);
|
||||
public static final ThingTypeUID OPENSPRINKLER_DEVICE = new ThingTypeUID(BINDING_ID, DEVICE_THING);
|
||||
|
||||
public static final int DEFAULT_WAIT_BEFORE_INITIAL_REFRESH = 30;
|
||||
public static final int DEFAULT_REFRESH_RATE = 60;
|
||||
public static final int DISCOVERY_THREAD_POOL_SIZE = 15;
|
||||
public static final boolean DISCOVERY_DEFAULT_AUTO_DISCOVER = false;
|
||||
public static final int DISCOVERY_DEFAULT_TIMEOUT_RATE = 500;
|
||||
public static final int DISCOVERY_DEFAULT_IP_TIMEOUT_RATE = 750;
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String SENSOR_SIGNAL_STRENGTH = "signalStrength";
|
||||
public static final String SENSOR_FLOW_COUNT = "flowSensorCount";
|
||||
public static final String SENSOR_RAIN = "rainsensor";
|
||||
public static final String SENSOR_2 = "sensor2";
|
||||
public static final String SENSOR_WATERLEVEL = "waterlevel";
|
||||
public static final String SENSOR_CURRENT_DRAW = "currentDraw";
|
||||
public static final String CHANNEL_PROGRAMS = "programs";
|
||||
public static final String CHANNEL_ENABLE_PROGRAMS = "enablePrograms";
|
||||
public static final String CHANNEL_STATIONS = "stations";
|
||||
public static final String CHANNEL_RESET_STATIONS = "resetStations";
|
||||
public static final String STATION_STATE = "stationState";
|
||||
public static final String STATION_QUEUED = "queued";
|
||||
public static final String REMAINING_WATER_TIME = "remainingWaterTime";
|
||||
public static final String NEXT_DURATION = "nextDuration";
|
||||
public static final String CHANNEL_IGNORE_RAIN = "ignoreRain";
|
||||
public static final String CHANNEL_RAIN_DELAY = "rainDelay";
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiFactory;
|
||||
import org.openhab.binding.opensprinkler.internal.handler.OpenSprinklerDeviceHandler;
|
||||
import org.openhab.binding.opensprinkler.internal.handler.OpenSprinklerHttpBridgeHandler;
|
||||
@ -40,14 +42,18 @@ import org.osgi.service.component.annotations.Reference;
|
||||
* @author Florian Schmidt - Split channels to their own things
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.opensprinkler")
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>(
|
||||
Arrays.asList(OPENSPRINKLER_HTTP_BRIDGE, OPENSPRINKLER_STATION, OPENSPRINKLER_DEVICE));
|
||||
private final OpenSprinklerStateDescriptionProvider stateDescriptionProvider;
|
||||
private OpenSprinklerApiFactory apiFactory;
|
||||
|
||||
@Activate
|
||||
public OpenSprinklerHandlerFactory(@Reference OpenSprinklerApiFactory apiFactory) {
|
||||
public OpenSprinklerHandlerFactory(@Reference OpenSprinklerApiFactory apiFactory,
|
||||
final @Reference OpenSprinklerStateDescriptionProvider stateDescriptionProvider) {
|
||||
this.apiFactory = apiFactory;
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -56,7 +62,7 @@ public class OpenSprinklerHandlerFactory extends BaseThingHandlerFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(OPENSPRINKLER_HTTP_BRIDGE)) {
|
||||
@ -64,7 +70,7 @@ public class OpenSprinklerHandlerFactory extends BaseThingHandlerFactory {
|
||||
} else if (thingTypeUID.equals(OPENSPRINKLER_STATION)) {
|
||||
return new OpenSprinklerStationHandler(thing);
|
||||
} else if (thingTypeUID.equals(OPENSPRINKLER_DEVICE)) {
|
||||
return new OpenSprinklerDeviceHandler(thing);
|
||||
return new OpenSprinklerDeviceHandler(thing, stateDescriptionProvider);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.opensprinkler.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.types.StateOption;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerState} class holds the state and replies for an OpenSprinkler device.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerState {
|
||||
public JcResponse jcReply = new JcResponse();
|
||||
public JoResponse joReply = new JoResponse();
|
||||
public JsResponse jsReply = new JsResponse();
|
||||
public JpResponse jpReply = new JpResponse();
|
||||
public JnResponse jnReply = new JnResponse();
|
||||
public List<StateOption> programs = new ArrayList<>();
|
||||
public List<StateOption> stations = new ArrayList<>();
|
||||
|
||||
public static class JsResponse {
|
||||
public int sn[] = new int[8];
|
||||
public int nstations = 8;
|
||||
}
|
||||
|
||||
public static class JpResponse {
|
||||
public int nprogs = 0;
|
||||
public Object[] pd = {};
|
||||
}
|
||||
|
||||
public static class JoResponse {
|
||||
public int wl;
|
||||
public int fwv = -1;
|
||||
}
|
||||
|
||||
public static class JcResponse {
|
||||
public @Nullable List<List<Integer>> ps;
|
||||
@SerializedName(value = "sn1", alternate = "rs")
|
||||
public int rs;
|
||||
public long devt = 0;
|
||||
public long rdst = 0;
|
||||
public int en = 1;
|
||||
public int sn2 = -1;
|
||||
@SerializedName(value = "RSSI", alternate = "rssi") // json reply uses all uppercase
|
||||
public int rssi = 1;
|
||||
public int flcrt = -1;
|
||||
public int curr = -1;
|
||||
}
|
||||
|
||||
public static class JnResponse {
|
||||
public List<String> snames = new ArrayList<>();
|
||||
@SerializedName(value = "ignore_rain", alternate = "ignoreRain")
|
||||
public byte[] ignoreRain = { 0 };
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.opensprinkler.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerStateDescriptionProvider} Allows the dynamic updating of Programs that can be run from an
|
||||
* Opensprinkler Device
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, OpenSprinklerStateDescriptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
@Activate
|
||||
public OpenSprinklerStateDescriptionProvider(
|
||||
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
}
|
@ -13,19 +13,30 @@
|
||||
package org.openhab.binding.opensprinkler.internal.api;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.opensprinkler.internal.OpenSprinklerState.JnResponse;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.GeneralApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.model.NoCurrentDrawSensorException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.UnauthorizedApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.model.StationProgram;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.StateOption;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerApi} interface defines the functions which are
|
||||
* controllable on the OpenSprinkler API interface.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
* @author Florian Schmidt - Refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface OpenSprinklerApi {
|
||||
|
||||
/**
|
||||
* Whether the device entered manual mode and accepts API requests to control the stations.
|
||||
*
|
||||
@ -38,14 +49,14 @@ public interface OpenSprinklerApi {
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract void enterManualMode() throws CommunicationApiException;
|
||||
public abstract void enterManualMode() throws CommunicationApiException, UnauthorizedApiException;
|
||||
|
||||
/**
|
||||
* Disables the manual mode, if it is enabled.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract void leaveManualMode() throws CommunicationApiException;
|
||||
public abstract void leaveManualMode() throws CommunicationApiException, UnauthorizedApiException;
|
||||
|
||||
/**
|
||||
* Starts a station on the OpenSprinkler device for the specified duration.
|
||||
@ -72,7 +83,7 @@ public interface OpenSprinklerApi {
|
||||
* @return True if the station is open, false if it is closed or cannot determine.
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract boolean isStationOpen(int station) throws GeneralApiException, CommunicationApiException;
|
||||
public abstract boolean isStationOpen(int station) throws CommunicationApiException, GeneralApiException;
|
||||
|
||||
/**
|
||||
* Returns the current program data of the requested station.
|
||||
@ -89,34 +100,63 @@ public interface OpenSprinklerApi {
|
||||
* @return True if rain is detected, false if not or cannot determine.
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract boolean isRainDetected() throws CommunicationApiException;
|
||||
public abstract boolean isRainDetected();
|
||||
|
||||
/**
|
||||
* Returns the current draw of all connected zones of the OpenSprinkler device in milliamperes.
|
||||
*
|
||||
* @return current draw in milliamperes
|
||||
* @throws CommunicationApiException
|
||||
* @throws
|
||||
* @return current draw in milliamperes or -1 if sensor not supported
|
||||
*/
|
||||
public abstract int currentDraw() throws CommunicationApiException, NoCurrentDrawSensorException;
|
||||
public abstract int currentDraw();
|
||||
|
||||
/**
|
||||
* Returns the state of the second sensor.
|
||||
*
|
||||
* @return 1: sensor is active; 0: sensor is inactive; -1: no sensor.
|
||||
*/
|
||||
public abstract int getSensor2State();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The Wifi signal strength in -dB or 0 if not supported by firmware
|
||||
*/
|
||||
public abstract int signalStrength();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The pulses that the flow sensor has given in the last time period, -1 if not supported.
|
||||
*/
|
||||
public abstract int flowSensorCount();
|
||||
|
||||
/**
|
||||
* CLOSES all stations turning them all off.
|
||||
*
|
||||
*/
|
||||
public abstract void resetStations() throws UnauthorizedApiException, CommunicationApiException;
|
||||
|
||||
/**
|
||||
* Returns true if the internal programs are allowed to auto start.
|
||||
*
|
||||
* @return true if enabled
|
||||
*/
|
||||
public abstract boolean getIsEnabled();
|
||||
|
||||
public abstract void enablePrograms(Command command) throws UnauthorizedApiException, CommunicationApiException;
|
||||
|
||||
/**
|
||||
* Returns the water level in %.
|
||||
*
|
||||
* @return waterLevel in %
|
||||
* @throws CommunicationApiException
|
||||
* @throws
|
||||
*/
|
||||
public abstract int waterLevel() throws CommunicationApiException;
|
||||
public abstract int waterLevel();
|
||||
|
||||
/**
|
||||
* Returns the number of total stations that are controllable from the OpenSprinkler
|
||||
* device.
|
||||
*
|
||||
* @return Number of stations as an int.
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract int getNumberOfStations() throws Exception;
|
||||
public abstract int getNumberOfStations();
|
||||
|
||||
/**
|
||||
* Returns the firmware version number.
|
||||
@ -124,5 +164,89 @@ public interface OpenSprinklerApi {
|
||||
* @return The firmware version of the OpenSprinkler device as an int.
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract int getFirmwareVersion() throws CommunicationApiException;
|
||||
public abstract int getFirmwareVersion() throws CommunicationApiException, UnauthorizedApiException;
|
||||
|
||||
/**
|
||||
* Sends all the GET requests and stores/cache the responses for use by the API to prevent the need for multiple
|
||||
* requests.
|
||||
*
|
||||
* @throws CommunicationApiException
|
||||
* @throws UnauthorizedApiException
|
||||
*/
|
||||
public abstract void refresh() throws CommunicationApiException, UnauthorizedApiException;
|
||||
|
||||
/**
|
||||
* Ask the OpenSprinkler for the program names and store these for future use in a List.
|
||||
*
|
||||
* @throws CommunicationApiException
|
||||
* @throws UnauthorizedApiException
|
||||
*/
|
||||
public abstract void getProgramData() throws CommunicationApiException, UnauthorizedApiException;
|
||||
|
||||
/**
|
||||
* Returns a list of all internal programs as a list of StateOptions.
|
||||
*
|
||||
* @return List<StateOption>
|
||||
*/
|
||||
public abstract List<StateOption> getPrograms();
|
||||
|
||||
/**
|
||||
* Return a list of all the stations the device has as List of StateOptions
|
||||
*
|
||||
* @return List<StateOption>
|
||||
*/
|
||||
public abstract List<StateOption> getStations();
|
||||
|
||||
/**
|
||||
* Runs a Program that is setup and stored inside the OpenSprinkler
|
||||
*
|
||||
* @param Program index number that you wish to run.
|
||||
*
|
||||
* @throws CommunicationApiException
|
||||
* @throws UnauthorizedApiException
|
||||
*/
|
||||
public abstract void runProgram(Command command) throws CommunicationApiException, UnauthorizedApiException;
|
||||
|
||||
/**
|
||||
* Fetch the station names and place them in a list of List<StateOption>.
|
||||
* Use getStations() to retrieve this list.
|
||||
*
|
||||
* @throws CommunicationApiException
|
||||
* @throws UnauthorizedApiException
|
||||
*/
|
||||
public abstract JnResponse getStationNames() throws CommunicationApiException, UnauthorizedApiException;
|
||||
|
||||
/**
|
||||
* Tells a single station to ignore the rain delay.
|
||||
*
|
||||
* @param station
|
||||
* @param command
|
||||
* @throws CommunicationApiException
|
||||
* @throws UnauthorizedApiException
|
||||
*/
|
||||
public void ignoreRain(int station, boolean command) throws CommunicationApiException, UnauthorizedApiException;
|
||||
|
||||
/**
|
||||
* Asks if a single station is set to ignore rain delays.
|
||||
*
|
||||
* @param station
|
||||
* @return
|
||||
*/
|
||||
public abstract boolean isIgnoringRain(int station);
|
||||
|
||||
/**
|
||||
* Sets how long the OpenSprinkler device will stop running programs for.
|
||||
*
|
||||
* @param hours
|
||||
* @throws UnauthorizedApiException
|
||||
* @throws CommunicationApiException
|
||||
*/
|
||||
public abstract void setRainDelay(int hours) throws UnauthorizedApiException, CommunicationApiException;
|
||||
|
||||
/**
|
||||
* Gets the rain delay in hours from the OpenSprinkler device.
|
||||
*
|
||||
* @return QuantityType<Time>
|
||||
*/
|
||||
public abstract QuantityType<Time> getRainDelay();
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
/**
|
||||
* 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.opensprinkler.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerApiContents} class defines common constants, which are
|
||||
* used across OpenSprinkler API classes.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerApiConstants {
|
||||
public static final String HTTP_REQUEST_URL_PREFIX = "http://";
|
||||
public static final String HTTPS_REQUEST_URL_PREFIX = "https://";
|
||||
|
||||
public static final String DEFAULT_ADMIN_PASSWORD = "opendoor";
|
||||
public static final int DEFAULT_API_PORT = 80;
|
||||
public static final int DEFAULT_STATION_COUNT = 8;
|
||||
|
||||
public static final String CMD_ENABLE_MANUAL_MODE = "mm=1";
|
||||
public static final String CMD_DISABLE_MANUAL_MODE = "mm=0";
|
||||
public static final String CMD_PASSWORD = "pw=";
|
||||
public static final String CMD_STATION = "sid=";
|
||||
public static final String CMD_STATION_ENABLE = "en=1";
|
||||
public static final String CMD_STATION_DISABLE = "en=0";
|
||||
|
||||
public static final String CMD_STATUS_INFO = "jc";
|
||||
public static final String CMD_OPTIONS_INFO = "jo";
|
||||
public static final String CMD_STATION_INFO = "js";
|
||||
public static final String CMD_STATION_CONTROL = "cm";
|
||||
|
||||
public static final String JSON_OPTION_FIRMWARE_VERSION = "fwv";
|
||||
public static final String JSON_OPTION_RAINSENSOR = "rs";
|
||||
public static final String JSON_OPTION_STATION = "sn";
|
||||
public static final String JSON_OPTION_STATION_COUNT = "nstations";
|
||||
|
||||
public static final String JSON_OPTION_RESULT = "result";
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.GeneralApiException;
|
||||
@ -21,6 +21,8 @@ import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
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 OpenSprinklerApiFactory} class is used for creating instances of
|
||||
@ -31,9 +33,10 @@ import org.osgi.service.component.annotations.Reference;
|
||||
* @author Florian Schmidt - Refactoring
|
||||
*/
|
||||
@Component(service = OpenSprinklerApiFactory.class)
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerApiFactory {
|
||||
|
||||
private @NonNull HttpClient httpClient;
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public OpenSprinklerApiFactory(@Reference HttpClientFactory httpClientFactory) {
|
||||
@ -61,13 +64,17 @@ public class OpenSprinklerApiFactory {
|
||||
version = lowestSupportedApi.getFirmwareVersion();
|
||||
} catch (CommunicationApiException exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
"Problem fetching the firmware version from the OpenSprinkler: " + exp.getMessage());
|
||||
}
|
||||
|
||||
logger.debug("Firmware was reported as {}", version);
|
||||
if (version >= 210 && version < 213) {
|
||||
return new OpenSprinklerHttpApiV210(this.httpClient, config);
|
||||
} else if (version >= 213) {
|
||||
} else if (version >= 213 && version < 217) {
|
||||
return new OpenSprinklerHttpApiV213(this.httpClient, config);
|
||||
} else if (version >= 217 && version < 219) {
|
||||
return new OpenSprinklerHttpApiV217(this.httpClient, config);
|
||||
} else if (version >= 219) {
|
||||
return new OpenSprinklerHttpApiV219(this.httpClient, config);
|
||||
} else {
|
||||
/* Need to make sure we have an older OpenSprinkler device by checking the first station. */
|
||||
try {
|
||||
@ -77,7 +84,6 @@ public class OpenSprinklerApiFactory {
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: "
|
||||
+ exp.getMessage());
|
||||
}
|
||||
|
||||
return lowestSupportedApi;
|
||||
}
|
||||
}
|
||||
|
@ -12,31 +12,47 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api;
|
||||
|
||||
import static org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiConstants.*;
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
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.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.opensprinkler.internal.OpenSprinklerState;
|
||||
import org.openhab.binding.opensprinkler.internal.OpenSprinklerState.JcResponse;
|
||||
import org.openhab.binding.opensprinkler.internal.OpenSprinklerState.JnResponse;
|
||||
import org.openhab.binding.opensprinkler.internal.OpenSprinklerState.JoResponse;
|
||||
import org.openhab.binding.opensprinkler.internal.OpenSprinklerState.JsResponse;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.GeneralApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.UnauthorizedApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.config.OpenSprinklerHttpInterfaceConfig;
|
||||
import org.openhab.binding.opensprinkler.internal.model.NoCurrentDrawSensorException;
|
||||
import org.openhab.binding.opensprinkler.internal.model.StationProgram;
|
||||
import org.openhab.binding.opensprinkler.internal.util.Parse;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerHttpApiV100} class is used for communicating with the
|
||||
@ -45,19 +61,16 @@ import com.google.gson.annotations.SerializedName;
|
||||
* @author Chris Graham - Initial contribution
|
||||
* @author Florian Schmidt - Allow https URLs and basic auth
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class OpenSprinklerHttpApiV100 implements OpenSprinklerApi {
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
protected final String hostname;
|
||||
protected final int port;
|
||||
protected final String password;
|
||||
protected final String basicUsername;
|
||||
protected final String basicPassword;
|
||||
|
||||
protected int firmwareVersion = -1;
|
||||
protected final OpenSprinklerHttpInterfaceConfig config;
|
||||
protected String password;
|
||||
protected OpenSprinklerState state = new OpenSprinklerState();
|
||||
protected int numberOfStations = DEFAULT_STATION_COUNT;
|
||||
|
||||
protected boolean isInManualMode = false;
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
protected final Gson gson = new Gson();
|
||||
protected HttpRequestSender http;
|
||||
|
||||
/**
|
||||
@ -73,27 +86,15 @@ class OpenSprinklerHttpApiV100 implements OpenSprinklerApi {
|
||||
* @throws Exception
|
||||
*/
|
||||
OpenSprinklerHttpApiV100(final HttpClient httpClient, final OpenSprinklerHttpInterfaceConfig config)
|
||||
throws GeneralApiException {
|
||||
if (config.hostname == null) {
|
||||
throw new GeneralApiException("The given url is null.");
|
||||
}
|
||||
if (config.port < 1 || config.port > 65535) {
|
||||
throw new GeneralApiException("The given port is invalid.");
|
||||
}
|
||||
if (config.password == null) {
|
||||
throw new GeneralApiException("The given password is null.");
|
||||
}
|
||||
|
||||
throws CommunicationApiException, UnauthorizedApiException {
|
||||
if (config.hostname.startsWith(HTTP_REQUEST_URL_PREFIX)
|
||||
|| config.hostname.startsWith(HTTPS_REQUEST_URL_PREFIX)) {
|
||||
this.hostname = config.hostname;
|
||||
} else {
|
||||
this.hostname = HTTP_REQUEST_URL_PREFIX + config.hostname;
|
||||
}
|
||||
this.port = config.port;
|
||||
this.config = config;
|
||||
this.password = config.password;
|
||||
this.basicUsername = config.basicUsername;
|
||||
this.basicPassword = config.basicPassword;
|
||||
this.http = new HttpRequestSender(httpClient);
|
||||
}
|
||||
|
||||
@ -103,127 +104,139 @@ class OpenSprinklerHttpApiV100 implements OpenSprinklerApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterManualMode() throws CommunicationApiException {
|
||||
try {
|
||||
http.sendHttpGet(getBaseUrl(), getRequestRequiredOptions() + "&" + CMD_ENABLE_MANUAL_MODE);
|
||||
} catch (Exception exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
}
|
||||
public List<StateOption> getPrograms() {
|
||||
return state.programs;
|
||||
}
|
||||
|
||||
this.firmwareVersion = getFirmwareVersion();
|
||||
this.numberOfStations = getNumberOfStations();
|
||||
@Override
|
||||
public List<StateOption> getStations() {
|
||||
return state.stations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() throws CommunicationApiException, UnauthorizedApiException {
|
||||
state.joReply = getOptions();
|
||||
state.jsReply = getStationStatus();
|
||||
state.jcReply = statusInfo();
|
||||
state.jnReply = getStationNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterManualMode() throws CommunicationApiException, UnauthorizedApiException {
|
||||
http.sendHttpGet(getBaseUrl(), getRequestRequiredOptions() + "&" + CMD_ENABLE_MANUAL_MODE);
|
||||
numberOfStations = getNumberOfStations();
|
||||
isInManualMode = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveManualMode() throws CommunicationApiException {
|
||||
public void leaveManualMode() throws CommunicationApiException, UnauthorizedApiException {
|
||||
http.sendHttpGet(getBaseUrl(), getRequestRequiredOptions() + "&" + CMD_DISABLE_MANUAL_MODE);
|
||||
isInManualMode = false;
|
||||
|
||||
try {
|
||||
http.sendHttpGet(getBaseUrl(), getRequestRequiredOptions() + "&" + CMD_DISABLE_MANUAL_MODE);
|
||||
} catch (Exception exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openStation(int station, BigDecimal duration) throws CommunicationApiException, GeneralApiException {
|
||||
if (station < 0 || station >= numberOfStations) {
|
||||
throw new GeneralApiException("This OpenSprinkler device only has " + this.numberOfStations
|
||||
+ " but station " + station + " was requested to be opened.");
|
||||
}
|
||||
|
||||
try {
|
||||
http.sendHttpGet(getBaseUrl() + "sn" + station + "=1&t=" + duration, null);
|
||||
} catch (Exception exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
}
|
||||
http.sendHttpGet(getBaseUrl() + "sn" + station + "=1&t=" + duration, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeStation(int station) throws CommunicationApiException, GeneralApiException {
|
||||
if (station < 0 || station >= numberOfStations) {
|
||||
throw new GeneralApiException("This OpenSprinkler device only has " + this.numberOfStations
|
||||
+ " but station " + station + " was requested to be closed.");
|
||||
}
|
||||
|
||||
http.sendHttpGet(getBaseUrl() + "sn" + station + "=0", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStationOpen(int station) throws GeneralApiException, CommunicationApiException {
|
||||
String returnContent;
|
||||
|
||||
if (station < 0 || station >= numberOfStations) {
|
||||
throw new GeneralApiException("This OpenSprinkler device only has " + this.numberOfStations
|
||||
+ " but station " + station + " was requested for a status update.");
|
||||
}
|
||||
|
||||
try {
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + "sn" + station, null);
|
||||
} catch (Exception exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
}
|
||||
|
||||
return returnContent != null && returnContent.equals("1");
|
||||
public boolean isStationOpen(int station) throws CommunicationApiException, GeneralApiException {
|
||||
String returnContent = http.sendHttpGet(getBaseUrl() + "sn" + station, null);
|
||||
return "1".equals(returnContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRainDetected() throws CommunicationApiException {
|
||||
if (statusInfo().rs == 1) {
|
||||
return true;
|
||||
public void ignoreRain(int station, boolean command) throws CommunicationApiException, UnauthorizedApiException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIgnoringRain(int station) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRainDetected() {
|
||||
return state.jcReply.rs == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSensor2State() {
|
||||
return state.jcReply.sn2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int currentDraw() {
|
||||
return state.jcReply.curr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int flowSensorCount() {
|
||||
return state.jcReply.flcrt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int signalStrength() {
|
||||
return state.jcReply.rssi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getIsEnabled() {
|
||||
return state.jcReply.en == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int waterLevel() {
|
||||
return state.joReply.wl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumberOfStations() {
|
||||
numberOfStations = state.jsReply.nstations;
|
||||
return numberOfStations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirmwareVersion() throws CommunicationApiException, UnauthorizedApiException {
|
||||
state.joReply = getOptions();
|
||||
return state.joReply.fwv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runProgram(Command command) throws CommunicationApiException, UnauthorizedApiException {
|
||||
logger.warn("OpenSprinkler requires at least firmware 217 for the runProgram feature to work");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enablePrograms(Command command) throws UnauthorizedApiException, CommunicationApiException {
|
||||
if (command == OnOffType.ON) {
|
||||
http.sendHttpGet(getBaseUrl() + "cv", getRequestRequiredOptions() + "&en=1");
|
||||
} else {
|
||||
return false;
|
||||
http.sendHttpGet(getBaseUrl() + "cv", getRequestRequiredOptions() + "&en=0");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int currentDraw() throws CommunicationApiException, NoCurrentDrawSensorException {
|
||||
JcResponse info = statusInfo();
|
||||
if (info.curr == null) {
|
||||
throw new NoCurrentDrawSensorException();
|
||||
}
|
||||
return info.curr;
|
||||
public void resetStations() throws UnauthorizedApiException, CommunicationApiException {
|
||||
http.sendHttpGet(getBaseUrl() + "cv", getRequestRequiredOptions() + "&rsn=1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int waterLevel() throws CommunicationApiException {
|
||||
JoResponse info = getOptions();
|
||||
return info.wl;
|
||||
public void setRainDelay(int hours) throws UnauthorizedApiException, CommunicationApiException {
|
||||
http.sendHttpGet(getBaseUrl() + "cv", getRequestRequiredOptions() + "&rd=" + hours);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumberOfStations() throws CommunicationApiException {
|
||||
String returnContent;
|
||||
|
||||
try {
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + CMD_STATION_INFO, getRequestRequiredOptions());
|
||||
} catch (Exception exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
public QuantityType<Time> getRainDelay() {
|
||||
if (state.jcReply.rdst == 0) {
|
||||
return new QuantityType<Time>(0, Units.SECOND);
|
||||
}
|
||||
|
||||
this.numberOfStations = Parse.jsonInt(returnContent, JSON_OPTION_STATION_COUNT);
|
||||
|
||||
return this.numberOfStations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirmwareVersion() throws CommunicationApiException {
|
||||
|
||||
try {
|
||||
JoResponse info = getOptions();
|
||||
this.firmwareVersion = info.fwv;
|
||||
} catch (Exception exp) {
|
||||
this.firmwareVersion = -1;
|
||||
}
|
||||
|
||||
return this.firmwareVersion;
|
||||
long remainingTime = state.jcReply.rdst - state.jcReply.devt;
|
||||
return new QuantityType<Time>(remainingTime, Units.SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -232,7 +245,7 @@ class OpenSprinklerHttpApiV100 implements OpenSprinklerApi {
|
||||
* @return String representation of the OpenSprinkler API URL.
|
||||
*/
|
||||
protected String getBaseUrl() {
|
||||
return hostname + ":" + port + "/";
|
||||
return hostname + ":" + config.port + "/";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,49 +259,89 @@ class OpenSprinklerHttpApiV100 implements OpenSprinklerApi {
|
||||
|
||||
@Override
|
||||
public StationProgram retrieveProgram(int station) throws CommunicationApiException {
|
||||
JcResponse resp = statusInfo();
|
||||
return resp.ps.stream().map(values -> new StationProgram(values.get(1))).collect(Collectors.toList())
|
||||
.get(station);
|
||||
if (state.jcReply.ps != null) {
|
||||
return state.jcReply.ps.stream().map(values -> new StationProgram(values.get(1)))
|
||||
.collect(Collectors.toList()).get(station);
|
||||
}
|
||||
return new StationProgram(0);
|
||||
}
|
||||
|
||||
private JcResponse statusInfo() throws CommunicationApiException {
|
||||
private JcResponse statusInfo() throws CommunicationApiException, UnauthorizedApiException {
|
||||
String returnContent;
|
||||
|
||||
JcResponse resp;
|
||||
try {
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + CMD_STATUS_INFO, getRequestRequiredOptions());
|
||||
} catch (CommunicationApiException exp) {
|
||||
resp = gson.fromJson(returnContent, JcResponse.class);
|
||||
if (resp == null) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication: jcReply was empty.");
|
||||
}
|
||||
} catch (JsonSyntaxException exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
"There was a JSON syntax problem in the HTTP communication with the OpenSprinkler API: "
|
||||
+ exp.getMessage());
|
||||
}
|
||||
|
||||
JcResponse resp = gson.fromJson(returnContent, JcResponse.class);
|
||||
return resp;
|
||||
}
|
||||
|
||||
private static class JcResponse {
|
||||
public List<List<Integer>> ps;
|
||||
@SerializedName(value = "sn1", alternate = "rs")
|
||||
public int rs;
|
||||
public Integer curr;
|
||||
}
|
||||
|
||||
private JoResponse getOptions() throws CommunicationApiException {
|
||||
private JoResponse getOptions() throws CommunicationApiException, UnauthorizedApiException {
|
||||
String returnContent;
|
||||
|
||||
JoResponse resp;
|
||||
try {
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + CMD_OPTIONS_INFO, getRequestRequiredOptions());
|
||||
} catch (CommunicationApiException exp) {
|
||||
resp = gson.fromJson(returnContent, JoResponse.class);
|
||||
if (resp == null) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication: joReply was empty.");
|
||||
}
|
||||
} catch (JsonSyntaxException exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
"There was a JSON syntax problem in the HTTP communication with the OpenSprinkler API: "
|
||||
+ exp.getMessage());
|
||||
}
|
||||
|
||||
JoResponse resp = gson.fromJson(returnContent, JoResponse.class);
|
||||
return resp;
|
||||
}
|
||||
|
||||
private static class JoResponse {
|
||||
public int wl;
|
||||
public int fwv;
|
||||
protected JsResponse getStationStatus() throws CommunicationApiException, UnauthorizedApiException {
|
||||
String returnContent;
|
||||
JsResponse resp;
|
||||
try {
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + CMD_STATION_INFO, getRequestRequiredOptions());
|
||||
resp = gson.fromJson(returnContent, JsResponse.class);
|
||||
if (resp == null) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication: jsReply was empty.");
|
||||
}
|
||||
} catch (JsonSyntaxException exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a JSON syntax problem in the HTTP communication with the OpenSprinkler API: "
|
||||
+ exp.getMessage());
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getProgramData() throws CommunicationApiException, UnauthorizedApiException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JnResponse getStationNames() throws CommunicationApiException, UnauthorizedApiException {
|
||||
String returnContent;
|
||||
JnResponse resp;
|
||||
try {
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + "jn", getRequestRequiredOptions());
|
||||
resp = gson.fromJson(returnContent, JnResponse.class);
|
||||
if (resp == null) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication: jnReply was empty.");
|
||||
}
|
||||
} catch (JsonSyntaxException exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a JSON syntax problem in the HTTP communication with the OpenSprinkler API: "
|
||||
+ exp.getMessage());
|
||||
}
|
||||
state.jnReply = resp;
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -318,35 +371,37 @@ class OpenSprinklerHttpApiV100 implements OpenSprinklerApi {
|
||||
* @return String contents of the response for the GET request.
|
||||
* @throws Exception
|
||||
*/
|
||||
public String sendHttpGet(String url, String urlParameters) throws CommunicationApiException {
|
||||
public String sendHttpGet(String url, @Nullable String urlParameters)
|
||||
throws CommunicationApiException, UnauthorizedApiException {
|
||||
String location = null;
|
||||
|
||||
if (urlParameters != null) {
|
||||
location = url + "?" + urlParameters;
|
||||
} else {
|
||||
location = url;
|
||||
}
|
||||
|
||||
ContentResponse response;
|
||||
try {
|
||||
response = withGeneralProperties(httpClient.newRequest(location)).method(HttpMethod.GET).send();
|
||||
response = withGeneralProperties(httpClient.newRequest(location)).timeout(5, TimeUnit.SECONDS)
|
||||
.method(HttpMethod.GET).send();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new CommunicationApiException("Request to OpenSprinkler device failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (response.getStatus() != HTTP_OK_CODE) {
|
||||
throw new CommunicationApiException(
|
||||
"Error sending HTTP GET request to " + url + ". Got response code: " + response.getStatus());
|
||||
}
|
||||
|
||||
return response.getContentAsString();
|
||||
String content = response.getContentAsString();
|
||||
if ("{\"result\":2}".equals(content)) {
|
||||
throw new UnauthorizedApiException("Unauthorized, check your password is correct");
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private Request withGeneralProperties(Request request) {
|
||||
request.header(HttpHeader.USER_AGENT, USER_AGENT);
|
||||
if (basicUsername != null && basicPassword != null) {
|
||||
String encoded = Base64.getEncoder()
|
||||
.encodeToString((basicUsername + ":" + basicPassword).getBytes(StandardCharsets.UTF_8));
|
||||
if (!config.basicUsername.isEmpty() && !config.basicPassword.isEmpty()) {
|
||||
String encoded = Base64.getEncoder().encodeToString(
|
||||
(config.basicUsername + ":" + config.basicPassword).getBytes(StandardCharsets.UTF_8));
|
||||
request.header(HttpHeader.AUTHORIZATION, "Basic " + encoded);
|
||||
}
|
||||
return request;
|
||||
@ -370,12 +425,10 @@ class OpenSprinklerHttpApiV100 implements OpenSprinklerApi {
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new CommunicationApiException("Request to OpenSprinkler device failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (response.getStatus() != HTTP_OK_CODE) {
|
||||
throw new CommunicationApiException(
|
||||
"Error sending HTTP POST request to " + url + ". Got response code: " + response.getStatus());
|
||||
}
|
||||
|
||||
return response.getContentAsString();
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api;
|
||||
|
||||
import static org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiConstants.*;
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.opensprinkler.internal.OpenSprinklerState.JpResponse;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.DataFormatErrorApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.DataMissingApiException;
|
||||
@ -29,6 +33,7 @@ import org.openhab.binding.opensprinkler.internal.api.exception.UnauthorizedApiE
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.UnknownApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.config.OpenSprinklerHttpInterfaceConfig;
|
||||
import org.openhab.binding.opensprinkler.internal.util.Parse;
|
||||
import org.openhab.core.types.StateOption;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerHttpApiV210} class is used for communicating with
|
||||
@ -37,6 +42,7 @@ import org.openhab.binding.opensprinkler.internal.util.Parse;
|
||||
* @author Chris Graham - Initial contribution
|
||||
* @author Florian Schmidt - Refactor class visibility
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class OpenSprinklerHttpApiV210 extends OpenSprinklerHttpApiV100 {
|
||||
/**
|
||||
* Constructor for the OpenSprinkler API class to create a connection to the OpenSprinkler
|
||||
@ -47,40 +53,50 @@ class OpenSprinklerHttpApiV210 extends OpenSprinklerHttpApiV100 {
|
||||
* @param password Admin password for the OpenSprinkler device.
|
||||
* @param basicUsername only needed if basic auth is required
|
||||
* @param basicPassword only needed if basic auth is required
|
||||
* @throws CommunicationApiException
|
||||
* @throws Exception
|
||||
*/
|
||||
OpenSprinklerHttpApiV210(final HttpClient httpClient, final OpenSprinklerHttpInterfaceConfig config)
|
||||
throws GeneralApiException {
|
||||
throws GeneralApiException, CommunicationApiException {
|
||||
super(httpClient, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStationOpen(int station) throws GeneralApiException, CommunicationApiException {
|
||||
public void getProgramData() throws CommunicationApiException, UnauthorizedApiException {
|
||||
String returnContent;
|
||||
int stationStatus = -1;
|
||||
|
||||
if (station < 0 || station >= numberOfStations) {
|
||||
throw new GeneralApiException("This OpenSprinkler device only has " + this.numberOfStations
|
||||
+ " but station " + station + " was requested for a status update.");
|
||||
}
|
||||
try {
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + CMD_STATION_INFO, getRequestRequiredOptions());
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + CMD_PROGRAM_DATA, getRequestRequiredOptions());
|
||||
} catch (CommunicationApiException exp) {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
stationStatus = Parse.jsonIntAtArrayIndex(returnContent, JSON_OPTION_STATION, station);
|
||||
} catch (Exception exp) {
|
||||
throw new GeneralApiException("There was a problem parsing the station status for station " + station
|
||||
+ ". Got the error: " + exp.getMessage());
|
||||
JpResponse resp = gson.fromJson(returnContent, JpResponse.class);
|
||||
if (resp != null && resp.pd.length > 0) {
|
||||
state.programs = new ArrayList<>();
|
||||
int counter = 0;
|
||||
for (Object x : resp.pd) {
|
||||
String temp = x.toString();
|
||||
temp = temp.substring(temp.lastIndexOf(',') + 2, temp.length() - 1);
|
||||
state.programs.add(new StateOption(Integer.toString(counter++), temp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stationStatus == 1) {
|
||||
return true;
|
||||
@Override
|
||||
public List<StateOption> getStations() {
|
||||
int counter = 0;
|
||||
for (String x : state.jnReply.snames) {
|
||||
state.stations.add(new StateOption(Integer.toString(counter++), x));
|
||||
}
|
||||
return state.stations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStationOpen(int station) throws GeneralApiException, CommunicationApiException {
|
||||
if (state.jsReply.sn.length > 0) {
|
||||
return state.jsReply.sn[station] == 1;
|
||||
} else {
|
||||
return false;
|
||||
throw new GeneralApiException("There was a problem parsing the station status for the sn value.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,11 +104,6 @@ class OpenSprinklerHttpApiV210 extends OpenSprinklerHttpApiV100 {
|
||||
public void openStation(int station, BigDecimal duration) throws CommunicationApiException, GeneralApiException {
|
||||
String returnContent;
|
||||
|
||||
if (station < 0 || station >= numberOfStations) {
|
||||
throw new GeneralApiException("This OpenSprinkler device only has " + this.numberOfStations
|
||||
+ " but station " + station + " was requested to be opened.");
|
||||
}
|
||||
|
||||
try {
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + CMD_STATION_CONTROL, getRequestRequiredOptions() + "&"
|
||||
+ CMD_STATION + station + "&" + CMD_STATION_ENABLE + "&t=" + duration);
|
||||
@ -100,7 +111,6 @@ class OpenSprinklerHttpApiV210 extends OpenSprinklerHttpApiV100 {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
}
|
||||
|
||||
resultParser(returnContent);
|
||||
}
|
||||
|
||||
@ -108,11 +118,6 @@ class OpenSprinklerHttpApiV210 extends OpenSprinklerHttpApiV100 {
|
||||
public void closeStation(int station) throws CommunicationApiException, GeneralApiException {
|
||||
String returnContent;
|
||||
|
||||
if (station < 0 || station > numberOfStations) {
|
||||
throw new GeneralApiException("This OpenSprinkler device only has " + this.numberOfStations
|
||||
+ " but station " + station + " was requested to be closed.");
|
||||
}
|
||||
|
||||
try {
|
||||
returnContent = http.sendHttpGet(getBaseUrl() + CMD_STATION_CONTROL,
|
||||
getRequestRequiredOptions() + "&" + CMD_STATION + station + "&" + CMD_STATION_DISABLE);
|
||||
@ -120,7 +125,6 @@ class OpenSprinklerHttpApiV210 extends OpenSprinklerHttpApiV100 {
|
||||
throw new CommunicationApiException(
|
||||
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
|
||||
}
|
||||
|
||||
resultParser(returnContent);
|
||||
}
|
||||
|
||||
@ -130,10 +134,8 @@ class OpenSprinklerHttpApiV210 extends OpenSprinklerHttpApiV100 {
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void enterManualMode() throws CommunicationApiException {
|
||||
this.firmwareVersion = getFirmwareVersion();
|
||||
this.numberOfStations = getNumberOfStations();
|
||||
|
||||
public void enterManualMode() throws CommunicationApiException, UnauthorizedApiException {
|
||||
numberOfStations = getNumberOfStations();
|
||||
isInManualMode = true;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,9 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.GeneralApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.config.OpenSprinklerHttpInterfaceConfig;
|
||||
import org.openhab.binding.opensprinkler.internal.util.Hash;
|
||||
@ -23,6 +25,7 @@ import org.openhab.binding.opensprinkler.internal.util.Hash;
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class OpenSprinklerHttpApiV213 extends OpenSprinklerHttpApiV210 {
|
||||
/**
|
||||
* Constructor for the OpenSprinkler API class to create a connection to the OpenSprinkler
|
||||
@ -33,15 +36,14 @@ class OpenSprinklerHttpApiV213 extends OpenSprinklerHttpApiV210 {
|
||||
* @param password Admin password for the OpenSprinkler device.
|
||||
* @param basicUsername only needed if basic auth is required
|
||||
* @param basicPassword only needed if basic auth is required
|
||||
* @throws CommunicationApiException
|
||||
* @throws Exception
|
||||
*/
|
||||
OpenSprinklerHttpApiV213(final HttpClient httpClient, final OpenSprinklerHttpInterfaceConfig config)
|
||||
throws GeneralApiException {
|
||||
super(httpClient, withHashedPassword(config));
|
||||
}
|
||||
|
||||
private static OpenSprinklerHttpInterfaceConfig withHashedPassword(final OpenSprinklerHttpInterfaceConfig config) {
|
||||
config.password = Hash.getMD5Hash(config.password);
|
||||
return config;
|
||||
throws GeneralApiException, CommunicationApiException {
|
||||
super(httpClient, config);
|
||||
password = Hash.getMD5Hash(password);
|
||||
getProgramData();
|
||||
getStationNames();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.opensprinkler.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.GeneralApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.UnauthorizedApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.config.OpenSprinklerHttpInterfaceConfig;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerHttpApiV217} class is used for communicating with
|
||||
* the OpenSprinkler API for firmware versions 2.1.7 and up.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerHttpApiV217 extends OpenSprinklerHttpApiV213 {
|
||||
|
||||
OpenSprinklerHttpApiV217(final HttpClient httpClient, final OpenSprinklerHttpInterfaceConfig config)
|
||||
throws GeneralApiException, CommunicationApiException {
|
||||
super(httpClient, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runProgram(Command command) throws UnauthorizedApiException, CommunicationApiException {
|
||||
http.sendHttpGet(getBaseUrl() + "mp", getRequestRequiredOptions() + "&pid=" + command);
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.opensprinkler.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.GeneralApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.UnauthorizedApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.config.OpenSprinklerHttpInterfaceConfig;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerHttpApiV219} class is used for communicating with
|
||||
* the firmware versions 2.1.9 and up.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerHttpApiV219 extends OpenSprinklerHttpApiV217 {
|
||||
|
||||
OpenSprinklerHttpApiV219(final HttpClient httpClient, final OpenSprinklerHttpInterfaceConfig config)
|
||||
throws GeneralApiException, CommunicationApiException {
|
||||
super(httpClient, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ignoreRain(int station, boolean command) throws CommunicationApiException, UnauthorizedApiException {
|
||||
int arrayIndex = station / 8;
|
||||
int bit = station % 8;
|
||||
logger.debug("Ignore Rain for Station:{} is being looked in index: {} and bit:{}", station, arrayIndex, bit);
|
||||
byte status = state.jnReply.ignoreRain[arrayIndex];
|
||||
if (command) {
|
||||
status |= 1 << bit;
|
||||
} else {
|
||||
status &= ~(1 << bit);
|
||||
}
|
||||
http.sendHttpGet(getBaseUrl() + "cs", getRequestRequiredOptions() + "&i" + arrayIndex + "=" + status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIgnoringRain(int station) {
|
||||
int arrayIndex = station / 8;
|
||||
int bit = station % 8;
|
||||
byte status = state.jnReply.ignoreRain[arrayIndex];
|
||||
return (status & (1 << bit)) != 0;
|
||||
}
|
||||
}
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link CommunicationApiException} exception is thrown when result from the OpenSprinkler
|
||||
* API is problems communicating with the controller.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CommunicationApiException extends Exception {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link DataFormatErrorApiException} exception is thrown when result from the OpenSprinkler
|
||||
* API is "result" : 18.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DataFormatErrorApiException extends GeneralApiException {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link DataMissingApiException} exception is thrown when result from the OpenSprinkler
|
||||
* API is "result" : 16.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DataMissingApiException extends GeneralApiException {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link GeneralApiException} exception is thrown when problems
|
||||
* working with the OpenSprinkler API arise.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GeneralApiException extends Exception {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link MismatchApiException} exception is thrown when result from the OpenSprinkler
|
||||
* API is "result" : 3.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MismatchApiException extends GeneralApiException {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link NotPermittedApiException} exception is thrown when result from the OpenSprinkler
|
||||
* API is "result" : 48.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NotPermittedApiException extends GeneralApiException {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link OutOfRangeApiException} exception is thrown when result from the OpenSprinkler
|
||||
* API is "result" : 17.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OutOfRangeApiException extends GeneralApiException {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PageNotFoundApiException} exception is thrown when result from the OpenSprinkler
|
||||
* API is "result" : 32.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PageNotFoundApiException extends GeneralApiException {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link UnauthorizedApiException} exception is thrown when result from the OpenSprinkler
|
||||
* API is "result" : 2.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UnauthorizedApiException extends GeneralApiException {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,12 +12,15 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.api.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link UnknownApiException} exception is thrown when result from the OpenSprinkler
|
||||
* API returns an unknown result.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UnknownApiException extends GeneralApiException {
|
||||
/**
|
||||
* Serial ID of this error class.
|
||||
|
@ -12,8 +12,9 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.config;
|
||||
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.DEFAULT_REFRESH_RATE;
|
||||
import static org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiConstants.*;
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.DEFAULT_ADMIN_PASSWORD;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerHttpInterfaceConfig} class defines the configuration options
|
||||
@ -21,16 +22,17 @@ import static org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiCon
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerHttpInterfaceConfig {
|
||||
/**
|
||||
* Hostname of the OpenSprinkler API.
|
||||
*/
|
||||
public String hostname = null;
|
||||
public String hostname = "";
|
||||
|
||||
/**
|
||||
* The port the OpenSprinkler API is listening on.
|
||||
*/
|
||||
public int port = DEFAULT_API_PORT;
|
||||
public int port = 80;
|
||||
|
||||
/**
|
||||
* The password to connect to the OpenSprinkler API.
|
||||
@ -40,13 +42,13 @@ public class OpenSprinklerHttpInterfaceConfig {
|
||||
/**
|
||||
* Number of seconds in between refreshes from the OpenSprinkler device.
|
||||
*/
|
||||
public int refresh = DEFAULT_REFRESH_RATE;
|
||||
public int refresh = 60;
|
||||
/**
|
||||
* The basic auth username to use when the OpenSprinkler device is behind a reverse proxy with basic auth enabled.
|
||||
*/
|
||||
public String basicUsername = null;
|
||||
public String basicUsername = "";
|
||||
/**
|
||||
* The basic auth password to use when the OpenSprinkler device is behind a reverse proxy with basic auth enabled.
|
||||
*/
|
||||
public String basicPassword = null;
|
||||
public String basicPassword = "";
|
||||
}
|
||||
|
@ -12,8 +12,9 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.config;
|
||||
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.DEFAULT_REFRESH_RATE;
|
||||
import static org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiConstants.DEFAULT_STATION_COUNT;
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerPiConfig} class defines the configuration options
|
||||
@ -21,6 +22,7 @@ import static org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiCon
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerPiConfig {
|
||||
/**
|
||||
* Number of stations to control.
|
||||
|
@ -12,15 +12,18 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link OpenSprinklerStationConfig} class defines the configuration options
|
||||
* for the OpenSprinkler Thing.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerStationConfig {
|
||||
/**
|
||||
* The index of the station the thing is configured to control, starting with 0.
|
||||
*/
|
||||
public int stationIndex = -1;
|
||||
public int stationIndex = 0;
|
||||
}
|
||||
|
@ -12,15 +12,10 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.DISCOVERY_DEFAULT_IP_TIMEOUT_RATE;
|
||||
import static org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApi;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.GeneralApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.UnauthorizedApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.config.OpenSprinklerHttpInterfaceConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -32,11 +27,10 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerDiscoveryJob implements Runnable {
|
||||
private final Logger logger = LoggerFactory.getLogger(OpenSprinklerDiscoveryJob.class);
|
||||
|
||||
private OpenSprinklerDiscoveryService discoveryClass;
|
||||
|
||||
private String ipAddress;
|
||||
|
||||
public OpenSprinklerDiscoveryJob(OpenSprinklerDiscoveryService service, String ip) {
|
||||
@ -59,46 +53,15 @@ public class OpenSprinklerDiscoveryJob implements Runnable {
|
||||
*/
|
||||
private boolean hasOpenSprinklerDevice(String ip) {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(ip);
|
||||
|
||||
if (canEstablishConnection(address, DEFAULT_API_PORT)) {
|
||||
OpenSprinklerHttpInterfaceConfig config = new OpenSprinklerHttpInterfaceConfig();
|
||||
config.hostname = ip;
|
||||
config.port = DEFAULT_API_PORT;
|
||||
config.password = DEFAULT_ADMIN_PASSWORD;
|
||||
OpenSprinklerApi openSprinkler = discoveryClass.getApiFactory().getHttpApi(config);
|
||||
|
||||
return (openSprinkler != null);
|
||||
} else {
|
||||
logger.trace("No OpenSprinkler device found at IP address ({})", ip);
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (Exception exp) {
|
||||
OpenSprinklerHttpInterfaceConfig config = new OpenSprinklerHttpInterfaceConfig();
|
||||
config.hostname = ip;
|
||||
discoveryClass.getApiFactory().getHttpApi(config);
|
||||
} catch (UnauthorizedApiException e) {
|
||||
return true;
|
||||
} catch (CommunicationApiException | GeneralApiException exp) {
|
||||
logger.debug("No OpenSprinkler device found at IP address ({}) because of error: {}", ip, exp.getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to establish a connection to a hostname and port.
|
||||
*
|
||||
* @param host Hostname or IP address to connect to.
|
||||
* @param port Port to attempt to connect to.
|
||||
* @return True if a connection can be established, false if not.
|
||||
*/
|
||||
private boolean canEstablishConnection(InetAddress host, int port) {
|
||||
boolean reachable = false;
|
||||
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.connect(new InetSocketAddress(host, port), DISCOVERY_DEFAULT_IP_TIMEOUT_RATE);
|
||||
|
||||
reachable = true;
|
||||
} catch (IOException e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return reachable;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,15 @@
|
||||
package org.openhab.binding.opensprinkler.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.*;
|
||||
import static org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiConstants.*;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -31,7 +29,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.apache.commons.net.util.SubnetUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiFactory;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
@ -51,12 +49,12 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.opensprinkler")
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerDiscoveryService extends AbstractDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(OpenSprinklerDiscoveryService.class);
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>(
|
||||
Arrays.asList(OPENSPRINKLER_HTTP_BRIDGE));
|
||||
|
||||
private ExecutorService discoverySearchPool;
|
||||
private ExecutorService discoverySearchPool = scheduler;
|
||||
private OpenSprinklerApiFactory apiFactory;
|
||||
|
||||
@Activate
|
||||
@ -76,23 +74,13 @@ public class OpenSprinklerDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting discovery of OpenSprinkler devices.");
|
||||
|
||||
discoverySearchPool = Executors.newFixedThreadPool(DISCOVERY_THREAD_POOL_SIZE);
|
||||
try {
|
||||
List<String> ipList = getIpAddressScanList();
|
||||
|
||||
discoverySearchPool = Executors.newFixedThreadPool(DISCOVERY_THREAD_POOL_SIZE);
|
||||
|
||||
for (String ip : ipList) {
|
||||
discoverySearchPool.execute(new OpenSprinklerDiscoveryJob(this, ip));
|
||||
}
|
||||
|
||||
discoverySearchPool.shutdown();
|
||||
ipAddressScan();
|
||||
} catch (Exception exp) {
|
||||
logger.debug("OpenSprinkler discovery service encountered an error while scanning for devices: {}",
|
||||
exp.getMessage());
|
||||
}
|
||||
|
||||
logger.debug("Completed discovery of OpenSprinkler devices.");
|
||||
}
|
||||
|
||||
@ -102,52 +90,68 @@ public class OpenSprinklerDiscoveryService extends AbstractDiscoveryService {
|
||||
* @param ip IP address of the OpenSprinkler device as a string.
|
||||
*/
|
||||
public void submitDiscoveryResults(String ip) {
|
||||
ThingUID uid = new ThingUID(OPENSPRINKLER_HTTP_BRIDGE, ip.replace('.', '_'));
|
||||
|
||||
ThingUID bridgeUID = new ThingUID(OPENSPRINKLER_HTTP_BRIDGE, ip.replace('.', '_'));
|
||||
HashMap<String, Object> properties = new HashMap<>();
|
||||
|
||||
properties.put("hostname", ip);
|
||||
properties.put("port", DEFAULT_API_PORT);
|
||||
properties.put("port", 80);
|
||||
properties.put("password", DEFAULT_ADMIN_PASSWORD);
|
||||
properties.put("refresh", DEFAULT_REFRESH_RATE);
|
||||
|
||||
thingDiscovered(
|
||||
DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel("OpenSprinkler").build());
|
||||
properties.put("refresh", 60);
|
||||
thingDiscovered(DiscoveryResultBuilder.create(bridgeUID).withProperties(properties)
|
||||
.withLabel("OpenSprinkler HTTP Bridge").withRepresentationProperty("hostname").build());
|
||||
// Now create the Device thing
|
||||
properties.clear();
|
||||
properties.put("hostname", ip);
|
||||
ThingUID uid = new ThingUID(OPENSPRINKLER_DEVICE, bridgeUID, ip.replace('.', '_'));
|
||||
thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withProperties(properties)
|
||||
.withRepresentationProperty("hostname").withLabel("OpenSprinkler Device").build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a string list of all the IP addresses associated with the network interfaces on
|
||||
* this machine.
|
||||
*
|
||||
* @return String list of IP addresses.
|
||||
* @throws UnknownHostException
|
||||
* @throws SocketException
|
||||
*/
|
||||
private List<String> getIpAddressScanList() throws UnknownHostException, SocketException {
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localHost);
|
||||
|
||||
for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
|
||||
InetAddress ipAddress = address.getAddress();
|
||||
|
||||
String cidrSubnet = ipAddress.getHostAddress() + "/" + address.getNetworkPrefixLength();
|
||||
|
||||
/* Apache Subnet Utils only supports IP v4 for creating string list of IP's */
|
||||
if (ipAddress instanceof Inet4Address) {
|
||||
logger.debug("Found interface IPv4 address to scan: {}", cidrSubnet);
|
||||
|
||||
SubnetUtils utils = new SubnetUtils(cidrSubnet);
|
||||
|
||||
results.addAll(Arrays.asList(utils.getInfo().getAllAddresses()));
|
||||
} else if (ipAddress instanceof Inet6Address) {
|
||||
logger.debug("Found interface IPv6 address to scan: {}", cidrSubnet);
|
||||
} else {
|
||||
logger.debug("Found interface unknown IP type address to scan: {}", cidrSubnet);
|
||||
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 OpenSprinklerDiscoveryJob(this, host));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,105 +0,0 @@
|
||||
/**
|
||||
* 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.opensprinkler.internal.handler;
|
||||
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.DEFAULT_WAIT_BEFORE_INITIAL_REFRESH;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApi;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
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.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Florian Schmidt - Refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class OpenSprinklerBaseBridgeHandler extends BaseBridgeHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(OpenSprinklerBaseBridgeHandler.class);
|
||||
|
||||
@Nullable
|
||||
private ScheduledFuture<?> pollingJob;
|
||||
@Nullable
|
||||
protected OpenSprinklerApi openSprinklerDevice;
|
||||
|
||||
public OpenSprinklerBaseBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
public OpenSprinklerApi getApi() {
|
||||
OpenSprinklerApi api = openSprinklerDevice;
|
||||
if (api == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::refreshStations, DEFAULT_WAIT_BEFORE_INITIAL_REFRESH,
|
||||
getRefreshInterval(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
protected abstract long getRefreshInterval();
|
||||
|
||||
private void refreshStations() {
|
||||
if (openSprinklerDevice != null) {
|
||||
if (openSprinklerDevice.isManualModeEnabled()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
this.getThing().getThings().forEach(thing -> {
|
||||
OpenSprinklerBaseHandler handler = (OpenSprinklerBaseHandler) thing.getHandler();
|
||||
if (handler != null) {
|
||||
handler.updateChannels();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Could not sync status with the OpenSprinkler.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (openSprinklerDevice != null) {
|
||||
try {
|
||||
openSprinklerDevice.leaveManualMode();
|
||||
} catch (CommunicationApiException e) {
|
||||
logger.error("Could not close connection on teardown.", e);
|
||||
}
|
||||
openSprinklerDevice = null;
|
||||
}
|
||||
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Nothing to do for the bridge handler
|
||||
}
|
||||
}
|
@ -12,50 +12,50 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.handler;
|
||||
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.MAX_TIME_SECONDS;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApi;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Chris Graham - Initial contribution
|
||||
* @author Florian Schmidt - Refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class OpenSprinklerBaseHandler extends BaseThingHandler {
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
protected BigDecimal nextDurationTime = MAX_TIME_SECONDS;
|
||||
|
||||
@Nullable
|
||||
OpenSprinklerHttpBridgeHandler bridgeHandler;
|
||||
|
||||
public OpenSprinklerBaseHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
super.bridgeStatusChanged(bridgeStatusInfo);
|
||||
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected OpenSprinklerApi getApi() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
return null;
|
||||
}
|
||||
BridgeHandler handler = bridge.getHandler();
|
||||
if (!(handler instanceof OpenSprinklerBaseBridgeHandler)) {
|
||||
protected @Nullable OpenSprinklerApi getApi() {
|
||||
OpenSprinklerHttpBridgeHandler localBridge = bridgeHandler;
|
||||
if (localBridge == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return ((OpenSprinklerBaseBridgeHandler) handler).getApi();
|
||||
return localBridge.getApi();
|
||||
} catch (IllegalStateException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -69,5 +69,33 @@ public abstract class OpenSprinklerBaseHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleNextDurationCommand(ChannelUID channelUID, Command command) {
|
||||
if (!(command instanceof QuantityType<?>)) {
|
||||
logger.warn("Ignoring implausible non-QuantityType command for NEXT_DURATION");
|
||||
return;
|
||||
}
|
||||
QuantityType<?> quantity = (QuantityType<?>) command;
|
||||
quantity = quantity.toUnit(Units.SECOND);
|
||||
if (quantity != null) {
|
||||
nextDurationTime = quantity.toBigDecimal();
|
||||
updateState(channelUID, quantity);
|
||||
}
|
||||
}
|
||||
|
||||
protected BigDecimal nextDurationValue() {
|
||||
return nextDurationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "No HTTP Bridge thing selected");
|
||||
return;
|
||||
}
|
||||
bridgeHandler = (OpenSprinklerHttpBridgeHandler) bridge.getHandler();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
protected abstract void updateChannel(ChannelUID uid);
|
||||
}
|
||||
|
@ -16,90 +16,228 @@ import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingCon
|
||||
import static org.openhab.core.library.unit.MetricPrefix.MILLI;
|
||||
import static org.openhab.core.library.unit.Units.PERCENT;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
import javax.measure.quantity.ElectricCurrent;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.opensprinkler.internal.OpenSprinklerStateDescriptionProvider;
|
||||
import org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApi;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.model.NoCurrentDrawSensorException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.UnauthorizedApiException;
|
||||
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.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.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
|
||||
/**
|
||||
* @author Chris Graham - Initial contribution
|
||||
* @author Florian Schmidt - Refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerDeviceHandler extends OpenSprinklerBaseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(OpenSprinklerDeviceHandler.class);
|
||||
public final OpenSprinklerStateDescriptionProvider stateDescriptionProvider;
|
||||
|
||||
public OpenSprinklerDeviceHandler(Thing thing) {
|
||||
public OpenSprinklerDeviceHandler(Thing thing, OpenSprinklerStateDescriptionProvider stateDescriptionProvider) {
|
||||
super(thing);
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChannel(ChannelUID channel) {
|
||||
try {
|
||||
switch (channel.getIdWithoutGroup()) {
|
||||
case SENSOR_RAIN:
|
||||
if (getApi().isRainDetected()) {
|
||||
updateState(channel, OnOffType.ON);
|
||||
} else {
|
||||
updateState(channel, OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
case SENSOR_WATERLEVEL:
|
||||
updateState(channel, QuantityType.valueOf(getApi().waterLevel(), PERCENT));
|
||||
break;
|
||||
case SENSOR_CURRENT_DRAW:
|
||||
updateState(channel,
|
||||
new QuantityType<ElectricCurrent>(getApi().currentDraw(), MILLI(Units.AMPERE)));
|
||||
break;
|
||||
default:
|
||||
logger.debug("Not updating unknown channel {}", channel);
|
||||
}
|
||||
} catch (CommunicationApiException | NoCurrentDrawSensorException e) {
|
||||
logger.debug("Could not update {}", channel, e);
|
||||
OpenSprinklerApi localAPI = getApi();
|
||||
if (localAPI == null) {
|
||||
return;
|
||||
}
|
||||
switch (channel.getIdWithoutGroup()) {
|
||||
case SENSOR_RAIN:
|
||||
if (localAPI.isRainDetected()) {
|
||||
updateState(channel, OnOffType.ON);
|
||||
} else {
|
||||
updateState(channel, OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_RAIN_DELAY:
|
||||
updateState(channel, localAPI.getRainDelay());
|
||||
break;
|
||||
case SENSOR_2:
|
||||
if (localAPI.getSensor2State() == 1) {
|
||||
updateState(channel, OnOffType.ON);
|
||||
} else {
|
||||
updateState(channel, OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
case SENSOR_WATERLEVEL:
|
||||
updateState(channel, QuantityType.valueOf(localAPI.waterLevel(), PERCENT));
|
||||
break;
|
||||
case SENSOR_CURRENT_DRAW:
|
||||
updateState(channel, new QuantityType<ElectricCurrent>(localAPI.currentDraw(), MILLI(Units.AMPERE)));
|
||||
break;
|
||||
case SENSOR_SIGNAL_STRENGTH:
|
||||
int rssiValue = localAPI.signalStrength();
|
||||
if (rssiValue < -80) {
|
||||
updateState(channel, DecimalType.ZERO);
|
||||
} else if (rssiValue < -70) {
|
||||
updateState(channel, new DecimalType(1));
|
||||
} else if (rssiValue < -60) {
|
||||
updateState(channel, new DecimalType(2));
|
||||
} else if (rssiValue < -40) {
|
||||
updateState(channel, new DecimalType(3));
|
||||
} else if (rssiValue >= -40) {
|
||||
updateState(channel, new DecimalType(4));
|
||||
}
|
||||
break;
|
||||
case SENSOR_FLOW_COUNT:
|
||||
updateState(channel, new QuantityType<Dimensionless>(localAPI.flowSensorCount(), Units.ONE));
|
||||
break;
|
||||
case CHANNEL_PROGRAMS:
|
||||
break;
|
||||
case CHANNEL_ENABLE_PROGRAMS:
|
||||
if (localAPI.getIsEnabled()) {
|
||||
updateState(channel, OnOffType.ON);
|
||||
} else {
|
||||
updateState(channel, OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_STATIONS:
|
||||
break;
|
||||
case NEXT_DURATION:
|
||||
break;
|
||||
case CHANNEL_RESET_STATIONS:
|
||||
break;
|
||||
default:
|
||||
logger.debug("Can not update the unknown channel {}", channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
ChannelUID currentDraw = new ChannelUID(thing.getUID(), "currentDraw");
|
||||
if (thing.getChannel(currentDraw) == null) {
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
try {
|
||||
getApi().currentDraw();
|
||||
|
||||
Channel currentDrawChannel = ChannelBuilder.create(currentDraw, "Number:ElectricCurrent")
|
||||
.withType(new ChannelTypeUID(BINDING_ID, SENSOR_CURRENT_DRAW)).withLabel("Current Draw")
|
||||
.withDescription("Provides the current draw.").build();
|
||||
thingBuilder.withChannel(currentDrawChannel);
|
||||
|
||||
updateThing(thingBuilder.build());
|
||||
} catch (NoCurrentDrawSensorException e) {
|
||||
if (thing.getChannel(currentDraw) != null) {
|
||||
thingBuilder.withoutChannel(currentDraw);
|
||||
}
|
||||
updateThing(thingBuilder.build());
|
||||
} catch (CommunicationApiException e) {
|
||||
logger.debug("Could not query current draw. Not removing channel as it could be temporary.", e);
|
||||
super.initialize();
|
||||
OpenSprinklerApi localAPI = getApi();
|
||||
// Remove channels due to missing sensors or old firmware
|
||||
if (localAPI != null) {
|
||||
ArrayList<Channel> removeChannels = new ArrayList<>();
|
||||
Channel channel = thing.getChannel(SENSOR_CURRENT_DRAW);
|
||||
if (localAPI.currentDraw() == -1 && channel != null) {
|
||||
logger.debug("No current sensor detected, removing channel.");
|
||||
removeChannels.add(channel);
|
||||
}
|
||||
channel = thing.getChannel(SENSOR_SIGNAL_STRENGTH);
|
||||
if (localAPI.signalStrength() == 1 && channel != null) {
|
||||
removeChannels.add(channel);
|
||||
}
|
||||
channel = thing.getChannel(SENSOR_FLOW_COUNT);
|
||||
if (localAPI.flowSensorCount() == -1 && channel != null) {
|
||||
removeChannels.add(channel);
|
||||
}
|
||||
channel = thing.getChannel(SENSOR_2);
|
||||
if (localAPI.getSensor2State() == -1 && channel != null) {
|
||||
removeChannels.add(channel);
|
||||
}
|
||||
if (!removeChannels.isEmpty()) {
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
thingBuilder.withoutChannels(removeChannels);
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
updateProgramsChanOptions(localAPI);
|
||||
updateStationsChanOptions(localAPI);
|
||||
nextDurationTime = new BigDecimal(1800);
|
||||
updateState(NEXT_DURATION, new QuantityType<>(nextDurationTime, Units.SECOND));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the stored Program list and update the StateOptions on the channel so they match.
|
||||
*
|
||||
* @param api
|
||||
*/
|
||||
private void updateProgramsChanOptions(OpenSprinklerApi api) {
|
||||
stateDescriptionProvider.setStateOptions(new ChannelUID(this.getThing().getUID(), CHANNEL_PROGRAMS),
|
||||
api.getPrograms());
|
||||
}
|
||||
|
||||
private void updateStationsChanOptions(OpenSprinklerApi api) {
|
||||
stateDescriptionProvider.setStateOptions(new ChannelUID(this.getThing().getUID(), CHANNEL_STATIONS),
|
||||
api.getStations());
|
||||
}
|
||||
|
||||
protected void handleRainDelayCommand(ChannelUID channelUID, Command command, OpenSprinklerApi api)
|
||||
throws UnauthorizedApiException, CommunicationApiException {
|
||||
if (!(command instanceof QuantityType<?>)) {
|
||||
logger.warn("Ignoring implausible non-QuantityType command for rainDelay.");
|
||||
return;
|
||||
}
|
||||
QuantityType<?> quantity = (QuantityType<?>) command;
|
||||
quantity = quantity.toUnit(Units.HOUR);
|
||||
if (quantity != null) {
|
||||
api.setRainDelay(quantity.intValue());
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// nothing to do here
|
||||
OpenSprinklerApi api = getApi();
|
||||
if (api == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "OpenSprinkler bridge returned no API.");
|
||||
return;
|
||||
}
|
||||
OpenSprinklerHttpBridgeHandler localBridge = bridgeHandler;
|
||||
if (localBridge == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (command instanceof RefreshType) {
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
case CHANNEL_PROGRAMS:
|
||||
api.getProgramData();
|
||||
updateProgramsChanOptions(api);
|
||||
break;
|
||||
case CHANNEL_STATIONS:
|
||||
api.getStationNames();
|
||||
updateStationsChanOptions(api);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
case CHANNEL_PROGRAMS:
|
||||
api.runProgram(command);
|
||||
break;
|
||||
case CHANNEL_ENABLE_PROGRAMS:
|
||||
api.enablePrograms(command);
|
||||
break;
|
||||
case NEXT_DURATION:
|
||||
handleNextDurationCommand(channelUID, command);
|
||||
break;
|
||||
case CHANNEL_RESET_STATIONS:
|
||||
if (command == OnOffType.ON) {
|
||||
api.resetStations();
|
||||
}
|
||||
break;
|
||||
case CHANNEL_STATIONS:
|
||||
if (command instanceof StringType) {
|
||||
BigDecimal temp = new BigDecimal(command.toString());
|
||||
api.openStation(temp.intValue(), nextDurationValue());
|
||||
}
|
||||
break;
|
||||
case CHANNEL_RAIN_DELAY:
|
||||
handleRainDelayCommand(channelUID, command, api);
|
||||
break;
|
||||
}
|
||||
localBridge.delayedRefresh();// update sensors and controls after command is sent
|
||||
}
|
||||
} catch (Exception e) {
|
||||
localBridge.communicationError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,8 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.handler;
|
||||
|
||||
import static org.openhab.binding.opensprinkler.internal.OpenSprinklerBindingConstants.DEFAULT_REFRESH_RATE;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -20,22 +21,28 @@ import org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApi;
|
||||
import org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApiFactory;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.CommunicationApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.GeneralApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.api.exception.UnauthorizedApiException;
|
||||
import org.openhab.binding.opensprinkler.internal.config.OpenSprinklerHttpInterfaceConfig;
|
||||
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.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Chris Graham - Initial contribution
|
||||
* @author Florian Schmidt - Refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerHttpBridgeHandler extends OpenSprinklerBaseBridgeHandler {
|
||||
public class OpenSprinklerHttpBridgeHandler extends BaseBridgeHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(OpenSprinklerHttpBridgeHandler.class);
|
||||
|
||||
@Nullable
|
||||
private OpenSprinklerHttpInterfaceConfig openSprinklerConfig;
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
private @Nullable ScheduledFuture<?> delayedJob;
|
||||
private @Nullable OpenSprinklerApi openSprinklerDevice;
|
||||
private OpenSprinklerHttpInterfaceConfig openSprinklerConfig = new OpenSprinklerHttpInterfaceConfig();
|
||||
private OpenSprinklerApiFactory apiFactory;
|
||||
|
||||
public OpenSprinklerHttpBridgeHandler(Bridge bridge, OpenSprinklerApiFactory apiFactory) {
|
||||
@ -43,52 +50,105 @@ public class OpenSprinklerHttpBridgeHandler extends OpenSprinklerBaseBridgeHandl
|
||||
this.apiFactory = apiFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
OpenSprinklerHttpInterfaceConfig openSprinklerConfig = getConfig().as(OpenSprinklerHttpInterfaceConfig.class);
|
||||
this.openSprinklerConfig = openSprinklerConfig;
|
||||
public OpenSprinklerApi getApi() {
|
||||
OpenSprinklerApi api = openSprinklerDevice;
|
||||
if (api == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
public void communicationError(Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Communication Error with the OpenSprinkler: " + e.getMessage());
|
||||
}
|
||||
|
||||
public void refreshStations() {
|
||||
OpenSprinklerApi localApi = openSprinklerDevice;
|
||||
if (localApi == null || !localApi.isManualModeEnabled()) {
|
||||
setupAPI();
|
||||
localApi = openSprinklerDevice;
|
||||
}
|
||||
if (localApi != null) {
|
||||
try {
|
||||
localApi.refresh();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
this.getThing().getThings().forEach(thing -> {
|
||||
OpenSprinklerBaseHandler handler = (OpenSprinklerBaseHandler) thing.getHandler();
|
||||
if (handler != null) {
|
||||
handler.updateChannels();
|
||||
}
|
||||
});
|
||||
} catch (CommunicationApiException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Could not sync status with the OpenSprinkler. " + e.getMessage());
|
||||
} catch (UnauthorizedApiException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Unauthorized, check your password is correct");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void delayedRefresh() {
|
||||
ScheduledFuture<?> localFuture = delayedJob;
|
||||
if (localFuture == null || localFuture.isDone()) {
|
||||
delayedJob = scheduler.schedule(this::refreshStations, 3, TimeUnit.SECONDS);
|
||||
} else {// User has sent multiple commands quickly, only need to update the controls once.
|
||||
localFuture.cancel(true);
|
||||
delayedJob = scheduler.schedule(this::refreshStations, 3, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupAPI() {
|
||||
logger.debug("Initializing OpenSprinkler with config (Hostname: {}, Port: {}, Refresh: {}).",
|
||||
openSprinklerConfig.hostname, openSprinklerConfig.port, openSprinklerConfig.refresh);
|
||||
|
||||
OpenSprinklerApi openSprinklerDevice;
|
||||
try {
|
||||
openSprinklerDevice = apiFactory.getHttpApi(openSprinklerConfig);
|
||||
this.openSprinklerDevice = openSprinklerDevice;
|
||||
OpenSprinklerApi localApi = openSprinklerDevice;
|
||||
localApi.enterManualMode();
|
||||
if (!localApi.isManualModeEnabled()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Could not initialize the connection to the OpenSprinkler.");
|
||||
}
|
||||
} catch (CommunicationApiException | GeneralApiException exp) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Could not create API connection to the OpenSprinkler device. Error received: " + exp);
|
||||
|
||||
"Could not create an API connection to the OpenSprinkler. Error received: " + exp);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Successfully created API connection to the OpenSprinkler device.");
|
||||
|
||||
try {
|
||||
openSprinklerDevice.enterManualMode();
|
||||
} catch (CommunicationApiException exp) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Could not open API connection to the OpenSprinkler device. Error received: " + exp);
|
||||
}
|
||||
|
||||
if (openSprinklerDevice.isManualModeEnabled()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Could not initialize the connection to the OpenSprinkler.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getRefreshInterval() {
|
||||
OpenSprinklerHttpInterfaceConfig openSprinklerConfig = this.openSprinklerConfig;
|
||||
if (openSprinklerConfig == null) {
|
||||
return DEFAULT_REFRESH_RATE;
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Nothing to do for the bridge handler
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
openSprinklerConfig = getConfig().as(OpenSprinklerHttpInterfaceConfig.class);
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::refreshStations, 2, openSprinklerConfig.refresh,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
OpenSprinklerApi localApi = openSprinklerDevice;
|
||||
if (localApi != null) {
|
||||
try {
|
||||
localApi.leaveManualMode();
|
||||
} catch (CommunicationApiException | UnauthorizedApiException e) {
|
||||
logger.warn("Could not close connection on teardown.");
|
||||
}
|
||||
openSprinklerDevice = null;
|
||||
}
|
||||
ScheduledFuture<?> localFuture = pollingJob;
|
||||
if (localFuture != null) {
|
||||
localFuture.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
localFuture = delayedJob;
|
||||
if (localFuture != null) {
|
||||
localFuture.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
return openSprinklerConfig.refresh;
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import java.math.BigDecimal;
|
||||
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.opensprinkler.internal.api.OpenSprinklerApi;
|
||||
@ -28,27 +27,20 @@ import org.openhab.binding.opensprinkler.internal.config.OpenSprinklerStationCon
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Chris Graham - Initial contribution
|
||||
* @author Florian Schmidt - Refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenSprinklerStationHandler extends OpenSprinklerBaseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(OpenSprinklerStationHandler.class);
|
||||
|
||||
@Nullable
|
||||
private OpenSprinklerStationConfig config;
|
||||
@Nullable
|
||||
private BigDecimal nextDurationTime;
|
||||
private OpenSprinklerStationConfig config = new OpenSprinklerStationConfig();
|
||||
|
||||
public OpenSprinklerStationHandler(Thing thing) {
|
||||
super(thing);
|
||||
@ -56,7 +48,13 @@ public class OpenSprinklerStationHandler extends OpenSprinklerBaseHandler {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
config = getConfig().as(OpenSprinklerStationConfig.class);
|
||||
OpenSprinklerApi api = getApi();
|
||||
if (api != null && config.stationIndex >= api.getNumberOfStations()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Station Index is higher than the number of stations that the OpenSprinkler is reporting. Make sure your Station Index is correct.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,68 +64,46 @@ public class OpenSprinklerStationHandler extends OpenSprinklerBaseHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "OpenSprinkler bridge has no initialized API.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (command != RefreshType.REFRESH) {
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
case NEXT_DURATION:
|
||||
handleNextDurationCommand(channelUID, command);
|
||||
break;
|
||||
case STATION_STATE:
|
||||
handleStationStateCommand(api, command);
|
||||
break;
|
||||
case STATION_QUEUED:
|
||||
handleQueuedCommand(api, command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateChannels();
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private void handleNextDurationCommand(ChannelUID channelUID, Command command) {
|
||||
if (!(command instanceof QuantityType<?>)) {
|
||||
logger.info("Ignoring implausible non-QuantityType command for NEXT_DURATION");
|
||||
return;
|
||||
}
|
||||
QuantityType<?> quantity = (QuantityType<?>) command;
|
||||
this.nextDurationTime = quantity.toUnit(Units.SECOND).toBigDecimal();
|
||||
updateState(channelUID, quantity);
|
||||
}
|
||||
|
||||
private void handleStationStateCommand(OpenSprinklerApi api, Command command) {
|
||||
if (!(command instanceof OnOffType)) {
|
||||
logger.error("Received invalid command type for OpenSprinkler station ({}).", command);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (command == OnOffType.ON) {
|
||||
api.openStation(this.getStationIndex(), nextStationDuration());
|
||||
} else {
|
||||
api.closeStation(this.getStationIndex());
|
||||
if (command != RefreshType.REFRESH) {
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
case NEXT_DURATION:
|
||||
handleNextDurationCommand(channelUID, command);
|
||||
break;
|
||||
case STATION_STATE:
|
||||
if (!(command instanceof OnOffType)) {
|
||||
logger.warn("Received invalid command type for OpenSprinkler station ({}).", command);
|
||||
return;
|
||||
}
|
||||
if (command == OnOffType.ON) {
|
||||
api.openStation(config.stationIndex, nextDurationValue());
|
||||
} else {
|
||||
api.closeStation(config.stationIndex);
|
||||
}
|
||||
break;
|
||||
case STATION_QUEUED:
|
||||
if (command == OnOffType.OFF) {
|
||||
api.closeStation(config.stationIndex);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_IGNORE_RAIN:
|
||||
api.ignoreRain(config.stationIndex, command == OnOffType.ON);
|
||||
break;
|
||||
}
|
||||
OpenSprinklerHttpBridgeHandler localBridge = bridgeHandler;
|
||||
if (localBridge == null) {
|
||||
return;
|
||||
}
|
||||
// update all controls after a command is sent in case a long poll time is set.
|
||||
localBridge.delayedRefresh();
|
||||
}
|
||||
} catch (CommunicationApiException | GeneralApiException exp) {
|
||||
} catch (GeneralApiException | CommunicationApiException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"Could not control the station channel " + (this.getStationIndex() + 1)
|
||||
+ " for the OpenSprinkler. Error: " + exp.getMessage());
|
||||
"Could not control the station channel " + (config.stationIndex + 1)
|
||||
+ " for the OpenSprinkler. Error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleQueuedCommand(OpenSprinklerApi api, Command command) {
|
||||
if (command == OnOffType.ON) {
|
||||
return;
|
||||
}
|
||||
handleStationStateCommand(api, command);
|
||||
}
|
||||
|
||||
private BigDecimal nextStationDuration() {
|
||||
BigDecimal nextDurationItemValue = nextDurationValue();
|
||||
Channel nextDuration = getThing().getChannel(NEXT_DURATION);
|
||||
if (nextDuration != null && isLinked(nextDuration.getUID()) && nextDurationItemValue != null) {
|
||||
return nextDurationItemValue;
|
||||
}
|
||||
return new BigDecimal(64800);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles determining a channel's current state from the OpenSprinkler device.
|
||||
*
|
||||
@ -186,9 +162,10 @@ public class OpenSprinklerStationHandler extends OpenSprinklerBaseHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChannel(@NonNull ChannelUID channel) {
|
||||
OnOffType currentDeviceState = getStationState(this.getStationIndex());
|
||||
protected void updateChannel(ChannelUID channel) {
|
||||
OnOffType currentDeviceState = getStationState(config.stationIndex);
|
||||
QuantityType<Time> remainingWaterTime = getRemainingWaterTime(config.stationIndex);
|
||||
OpenSprinklerApi api = getApi();
|
||||
switch (channel.getIdWithoutGroup()) {
|
||||
case STATION_STATE:
|
||||
if (currentDeviceState != null) {
|
||||
@ -202,9 +179,7 @@ public class OpenSprinklerStationHandler extends OpenSprinklerBaseHandler {
|
||||
break;
|
||||
case NEXT_DURATION:
|
||||
BigDecimal duration = nextDurationValue();
|
||||
if (duration != null) {
|
||||
updateState(channel, new QuantityType<>(duration, Units.SECOND));
|
||||
}
|
||||
updateState(channel, new QuantityType<>(duration, Units.SECOND));
|
||||
break;
|
||||
case STATION_QUEUED:
|
||||
if (remainingWaterTime != null && currentDeviceState != null && currentDeviceState == OnOffType.OFF
|
||||
@ -214,20 +189,15 @@ public class OpenSprinklerStationHandler extends OpenSprinklerBaseHandler {
|
||||
updateState(channel, OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_IGNORE_RAIN:
|
||||
if (api != null && api.isIgnoringRain(config.stationIndex)) {
|
||||
updateState(channel, OnOffType.ON);
|
||||
} else {
|
||||
updateState(channel, OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Not updating unknown channel {}", channel);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable BigDecimal nextDurationValue() {
|
||||
return nextDurationTime;
|
||||
}
|
||||
|
||||
private int getStationIndex() {
|
||||
OpenSprinklerStationConfig config = this.config;
|
||||
if (config == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return config.stationIndex;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* 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.opensprinkler.internal.model;
|
||||
|
||||
/**
|
||||
* Indicates, that a device is missing a sensor to measure the current draw of itself.
|
||||
*
|
||||
* @author Florian Schmidt - Initial contribution
|
||||
*/
|
||||
public class NoCurrentDrawSensorException extends Exception {
|
||||
private static final long serialVersionUID = 2251925316743442346L;
|
||||
}
|
@ -12,11 +12,14 @@
|
||||
*/
|
||||
package org.openhab.binding.opensprinkler.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link StationProgram} class corresponds to the program set in the station.
|
||||
*
|
||||
* @author Florian Schmidt - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StationProgram {
|
||||
public final long remainingWaterTime;
|
||||
|
||||
|
@ -14,12 +14,15 @@ package org.openhab.binding.opensprinkler.internal.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Hash} class contains static methods for creating hashes
|
||||
* of strings. Usually for password hashing.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Hash {
|
||||
private static final String MD5_HASH_ALGORITHM = "MD5";
|
||||
private static final String UTF8_CHAR_SET = "UTF-8";
|
||||
@ -48,7 +51,8 @@ public class Hash {
|
||||
|
||||
return digest;
|
||||
} catch (Exception exp) {
|
||||
return null;
|
||||
// Instead of null we return the unhashed password.
|
||||
return unhashed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ package org.openhab.binding.opensprinkler.internal.util;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
@ -26,6 +28,7 @@ import com.google.gson.JsonParser;
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Parse {
|
||||
/**
|
||||
* Parses an integer from a JSON string given its key name.
|
||||
@ -37,7 +40,11 @@ public class Parse {
|
||||
public static int jsonInt(String jsonData, String keyName) {
|
||||
JsonElement jelement = JsonParser.parseString(jsonData);
|
||||
JsonObject jobject = jelement.getAsJsonObject();
|
||||
return jobject.get(keyName).getAsInt();
|
||||
jelement = jobject.get(keyName);
|
||||
if (jelement == null) {
|
||||
return 0;// prevents a NPE if the key does not exist.
|
||||
}
|
||||
return jelement.getAsInt();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,22 +5,24 @@
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="http">
|
||||
<label>OpenSprinkler HTTP Interface</label>
|
||||
<label>OpenSprinkler HTTP Bridge</label>
|
||||
<description>A connection to a stand alone OpenSprinkler device which communicates over HTTP.</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text">
|
||||
<parameter name="hostname" type="text" required="true">
|
||||
<label>Hostname</label>
|
||||
<description>The host name or IP address of the OpenSprinkler Web API interface. It may or may not start with the
|
||||
protocol, e.g. in order to use https:// instead of the default http://.</description>
|
||||
<default>localhost</default>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" min="1" max="65535">
|
||||
<parameter name="port" type="integer" min="1" max="65535" required="true">
|
||||
<label>Port</label>
|
||||
<description>Port of the OpenSprinkler Web API interface.</description>
|
||||
<default>80</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="password" type="text">
|
||||
<parameter name="password" type="text" required="true">
|
||||
<context>password</context>
|
||||
<label>Password</label>
|
||||
<description>The admin password used to access the Web API interface.</description>
|
||||
<default>opendoor</default>
|
||||
@ -55,12 +57,14 @@
|
||||
<channel id="queued" typeId="queued"></channel>
|
||||
<channel id="remainingWaterTime" typeId="remainingWaterTime"></channel>
|
||||
<channel id="nextDuration" typeId="nextDuration"></channel>
|
||||
<channel id="ignoreRain" typeId="ignoreRain"></channel>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="stationIndex" type="integer" required="true">
|
||||
<label>Station Index</label>
|
||||
<description>The index of the station, starting with 0, of the station.</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
@ -74,33 +78,87 @@
|
||||
|
||||
<channels>
|
||||
<channel id="rainsensor" typeId="rainsensor"></channel>
|
||||
<channel id="sensor2" typeId="sensor2"></channel>
|
||||
<channel id="waterlevel" typeId="waterlevel"></channel>
|
||||
<channel id="currentDraw" typeId="currentDraw"></channel>
|
||||
<channel id="signalStrength" typeId="system.signal-strength"></channel>
|
||||
<channel id="flowSensorCount" typeId="flowSensorCount"></channel>
|
||||
<channel id="programs" typeId="programs"></channel>
|
||||
<channel id="stations" typeId="stations"></channel>
|
||||
<channel id="nextDuration" typeId="nextDuration"></channel>
|
||||
<channel id="resetStations" typeId="resetStations"></channel>
|
||||
<channel id="enablePrograms" typeId="enablePrograms"></channel>
|
||||
<channel id="rainDelay" typeId="rainDelay"></channel>
|
||||
</channels>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="rainsensor">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Rain</label>
|
||||
<label>Rain Sensor</label>
|
||||
<description>Provides feedback on whether the OpenSprinkler device has detected rain or not.</description>
|
||||
<category>Sensor</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sensor2">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Sensor 2</label>
|
||||
<description>Sensor 2 can be setup as a rain, flow or soil moisture sensor.</description>
|
||||
<category>Sensor</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="waterlevel">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Water Level</label>
|
||||
<description>The current water level in percent</description>
|
||||
<description>The current watering level in percent</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="flowSensorCount">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Flow Sensor Count</label>
|
||||
<description>A count of how many pulses the water flow sensor has given.</description>
|
||||
<category>Flow</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="currentDraw">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Current Draw</label>
|
||||
<description>The current draw in mA</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="stationState">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Station</label>
|
||||
<label>Station State</label>
|
||||
<description>Controls a station on the OpenSprinkler device.</description>
|
||||
<category>Switch</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ignoreRain">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Station Ignores Rain</label>
|
||||
<description>The station will ignore forecasted rain.</description>
|
||||
<category>Switch</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="resetStations">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Reset Stations</label>
|
||||
<description>Resets all stations back to CLOSED.</description>
|
||||
<category>Switch</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="enablePrograms">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Enable Programs</label>
|
||||
<description>Allow programs to auto run, when OFF, manually started stations still work.</description>
|
||||
<category>Switch</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="queued">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Queued</label>
|
||||
@ -113,13 +171,39 @@
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Remaining Water Time</label>
|
||||
<description>Read-only property of the remaining water time of the station.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="true" pattern="%.0f min"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nextDuration">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Next Open Duration</label>
|
||||
<label>Next Duration</label>
|
||||
<description>The duration the station will be opened the next time it is switched on.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="false" pattern="%.0f min"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rainDelay">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Rain Delay</label>
|
||||
<description>The amount of time in hours to delay the running of any program.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="false" pattern="%.0f h"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="programs">
|
||||
<item-type>String</item-type>
|
||||
<label>Run Program</label>
|
||||
<description>Run a program that is saved inside the OpenSprinkler Device.</description>
|
||||
<state readOnly="false">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="stations">
|
||||
<item-type>String</item-type>
|
||||
<label>Open Station</label>
|
||||
<description>Opens the solenoid of a single station.</description>
|
||||
<state readOnly="false">
|
||||
</state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
|
Loading…
Reference in New Issue
Block a user