diff --git a/bundles/org.openhab.binding.opensprinkler/README.md b/bundles/org.openhab.binding.opensprinkler/README.md
index 4d8ddf544..6ecd67925 100644
--- a/bundles/org.openhab.binding.opensprinkler/README.md
+++ b/bundles/org.openhab.binding.opensprinkler/README.md
@@ -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"] {
diff --git a/bundles/org.openhab.binding.opensprinkler/pom.xml b/bundles/org.openhab.binding.opensprinkler/pom.xml
index c7f33ad68..486f74bdd 100644
--- a/bundles/org.openhab.binding.opensprinkler/pom.xml
+++ b/bundles/org.openhab.binding.opensprinkler/pom.xml
@@ -14,17 +14,4 @@
openHAB Add-ons :: Bundles :: OpenSprinkler Binding
-
- commons-net
-
-
-
-
- commons-net
- commons-net
- ${commons.net.version}
- compile
-
-
-
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/feature/feature.xml b/bundles/org.openhab.binding.opensprinkler/src/main/feature/feature.xml
index 4a62ecf85..853e6eece 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/feature/feature.xml
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/feature/feature.xml
@@ -1,10 +1,8 @@
mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
-
openhab-runtime-base
- openhab.tp-commons-net
mvn:org.openhab.addons.bundles/org.openhab.binding.opensprinkler/${project.version}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerBindingConstants.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerBindingConstants.java
index 6cc890c5b..4a32278a7 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerBindingConstants.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerBindingConstants.java
@@ -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";
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerHandlerFactory.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerHandlerFactory.java
index 06dd806c3..9b1eb7b6a 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerHandlerFactory.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerHandlerFactory.java
@@ -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 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;
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerState.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerState.java
new file mode 100644
index 000000000..33e89c238
--- /dev/null
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerState.java
@@ -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 programs = new ArrayList<>();
+ public List 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> 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 snames = new ArrayList<>();
+ @SerializedName(value = "ignore_rain", alternate = "ignoreRain")
+ public byte[] ignoreRain = { 0 };
+ }
+}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerStateDescriptionProvider.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerStateDescriptionProvider.java
new file mode 100644
index 000000000..dd61584dc
--- /dev/null
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/OpenSprinklerStateDescriptionProvider.java
@@ -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;
+ }
+}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApi.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApi.java
index 36a7a7aa7..5f365299b 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApi.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApi.java
@@ -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
+ */
+ public abstract List getPrograms();
+
+ /**
+ * Return a list of all the stations the device has as List of StateOptions
+ *
+ * @return List
+ */
+ public abstract List 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.
+ * 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
+ */
+ public abstract QuantityType getRainDelay();
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApiConstants.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApiConstants.java
deleted file mode 100644
index 91620bc90..000000000
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApiConstants.java
+++ /dev/null
@@ -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";
-}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApiFactory.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApiFactory.java
index 88dfbb305..2dd677b03 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApiFactory.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerApiFactory.java
@@ -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;
}
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV100.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV100.java
index bba8ee6a2..6cfdf9d89 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV100.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV100.java
@@ -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 getPrograms() {
+ return state.programs;
+ }
- this.firmwareVersion = getFirmwareVersion();
- this.numberOfStations = getNumberOfStations();
+ @Override
+ public List 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 getRainDelay() {
+ if (state.jcReply.rdst == 0) {
+ return new QuantityType(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(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> 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();
}
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV210.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV210.java
index e46d35824..b6753dab7 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV210.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV210.java
@@ -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 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;
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV213.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV213.java
index a23638238..f1643c6ce 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV213.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV213.java
@@ -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();
}
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV217.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV217.java
new file mode 100644
index 000000000..aea385425
--- /dev/null
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV217.java
@@ -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);
+ }
+}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV219.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV219.java
new file mode 100644
index 000000000..c374bd6ce
--- /dev/null
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/OpenSprinklerHttpApiV219.java
@@ -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;
+ }
+}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/CommunicationApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/CommunicationApiException.java
index da0d7d2b8..f410ea393 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/CommunicationApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/CommunicationApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/DataFormatErrorApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/DataFormatErrorApiException.java
index a51335eaf..8bf60999b 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/DataFormatErrorApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/DataFormatErrorApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/DataMissingApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/DataMissingApiException.java
index b91ef80f7..12ff6ef7d 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/DataMissingApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/DataMissingApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/GeneralApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/GeneralApiException.java
index aa6ce59d4..67792b2b0 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/GeneralApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/GeneralApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/MismatchApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/MismatchApiException.java
index c40761f00..615767e27 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/MismatchApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/MismatchApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/NotPermittedApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/NotPermittedApiException.java
index 496dc7f93..75baf295a 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/NotPermittedApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/NotPermittedApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/OutOfRangeApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/OutOfRangeApiException.java
index cb59d7eb3..d74f8a02e 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/OutOfRangeApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/OutOfRangeApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/PageNotFoundApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/PageNotFoundApiException.java
index 22904386a..882101803 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/PageNotFoundApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/PageNotFoundApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/UnauthorizedApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/UnauthorizedApiException.java
index edd6047fc..ac89589eb 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/UnauthorizedApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/UnauthorizedApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/UnknownApiException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/UnknownApiException.java
index 3aa42d725..faefd85b5 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/UnknownApiException.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/api/exception/UnknownApiException.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerHttpInterfaceConfig.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerHttpInterfaceConfig.java
index 37f81e60a..09edc3dc0 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerHttpInterfaceConfig.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerHttpInterfaceConfig.java
@@ -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 = "";
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerPiConfig.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerPiConfig.java
index a94331dc7..eb6a86293 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerPiConfig.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerPiConfig.java
@@ -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.
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerStationConfig.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerStationConfig.java
index 32babbd69..238f6f048 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerStationConfig.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/config/OpenSprinklerStationConfig.java
@@ -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;
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/discovery/OpenSprinklerDiscoveryJob.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/discovery/OpenSprinklerDiscoveryJob.java
index da5e9e144..e0c7a915c 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/discovery/OpenSprinklerDiscoveryJob.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/discovery/OpenSprinklerDiscoveryJob.java
@@ -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;
}
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/discovery/OpenSprinklerDiscoveryService.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/discovery/OpenSprinklerDiscoveryService.java
index abe381780..664bc17d6 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/discovery/OpenSprinklerDiscoveryService.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/discovery/OpenSprinklerDiscoveryService.java
@@ -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 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 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 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 getIpAddressScanList() throws UnknownHostException, SocketException {
- List 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 enumNetworks = NetworkInterface.getNetworkInterfaces(); enumNetworks
+ .hasMoreElements();) {
+ NetworkInterface networkInterface = enumNetworks.nextElement();
+ List 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) {
+ }
}
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerBaseBridgeHandler.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerBaseBridgeHandler.java
deleted file mode 100644
index 177d7a7fa..000000000
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerBaseBridgeHandler.java
+++ /dev/null
@@ -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
- }
-}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerBaseHandler.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerBaseHandler.java
index 2eac9354f..aef0dda9c 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerBaseHandler.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerBaseHandler.java
@@ -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);
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerDeviceHandler.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerDeviceHandler.java
index fa9688e9d..b6e6ba662 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerDeviceHandler.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerDeviceHandler.java
@@ -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(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(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(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 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);
+ }
}
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerHttpBridgeHandler.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerHttpBridgeHandler.java
index 39715fa56..895945b19 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerHttpBridgeHandler.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerHttpBridgeHandler.java
@@ -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;
}
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerStationHandler.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerStationHandler.java
index b07a6b706..2388a0df0 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerStationHandler.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/handler/OpenSprinklerStationHandler.java
@@ -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 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;
- }
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/model/NoCurrentDrawSensorException.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/model/NoCurrentDrawSensorException.java
deleted file mode 100644
index 31fc7eb2b..000000000
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/model/NoCurrentDrawSensorException.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/model/StationProgram.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/model/StationProgram.java
index a7ea0aff4..a864fef3e 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/model/StationProgram.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/model/StationProgram.java
@@ -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;
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/util/Hash.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/util/Hash.java
index 52d7b2a78..7248c3ff3 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/util/Hash.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/util/Hash.java
@@ -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;
}
}
}
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/util/Parse.java b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/util/Parse.java
index 6fffab23f..bf142e2ec 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/util/Parse.java
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/java/org/openhab/binding/opensprinkler/internal/util/Parse.java
@@ -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();
}
/**
diff --git a/bundles/org.openhab.binding.opensprinkler/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.opensprinkler/src/main/resources/OH-INF/thing/thing-types.xml
index bea478715..8f7039036 100644
--- a/bundles/org.openhab.binding.opensprinkler/src/main/resources/OH-INF/thing/thing-types.xml
+++ b/bundles/org.openhab.binding.opensprinkler/src/main/resources/OH-INF/thing/thing-types.xml
@@ -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">
- OpenSprinkler HTTP Interface
+ OpenSprinkler HTTP Bridge
A connection to a stand alone OpenSprinkler device which communicates over HTTP.
-
+
Hostname
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://.
localhost
-
+
Port
Port of the OpenSprinkler Web API interface.
80
+ true
-
+
+ password
Password
The admin password used to access the Web API interface.
opendoor
@@ -55,12 +57,14 @@
+
Station Index
The index of the station, starting with 0, of the station.
+ 0
@@ -74,33 +78,87 @@
+
+
+
+
+
+
+
+
+
+
Switch
- Rain
+ Rain Sensor
Provides feedback on whether the OpenSprinkler device has detected rain or not.
Sensor
+
+ Switch
+ Sensor 2
+ Sensor 2 can be setup as a rain, flow or soil moisture sensor.
+ Sensor
+
+
Number:Dimensionless
Water Level
- The current water level in percent
+ The current watering level in percent
+
+
+
+
+ Number:Dimensionless
+ Flow Sensor Count
+ A count of how many pulses the water flow sensor has given.
+ Flow
+
+
+
+
+ Number:ElectricCurrent
+ Current Draw
+ The current draw in mA
+ Energy
Switch
- Station
+ Station State
Controls a station on the OpenSprinkler device.
Switch
+
+ Switch
+ Station Ignores Rain
+ The station will ignore forecasted rain.
+ Switch
+
+
+
+ Switch
+ Reset Stations
+ Resets all stations back to CLOSED.
+ Switch
+
+
+
+ Switch
+ Enable Programs
+ Allow programs to auto run, when OFF, manually started stations still work.
+ Switch
+
+
Switch
Queued
@@ -113,13 +171,39 @@
Number:Time
Remaining Water Time
Read-only property of the remaining water time of the station.
+ Time
Number:Time
- Next Open Duration
+ Next Duration
The duration the station will be opened the next time it is switched on.
+ Time
+
+
+ Number:Time
+ Rain Delay
+ The amount of time in hours to delay the running of any program.
+ Time
+
+
+
+
+ String
+ Run Program
+ Run a program that is saved inside the OpenSprinkler Device.
+
+
+
+
+
+ String
+ Open Station
+ Opens the solenoid of a single station.
+
+
+