diff --git a/bundles/org.openhab.binding.opensprinkler/README.md b/bundles/org.openhab.binding.opensprinkler/README.md
index 4d8ddf544830b..6ecd679252aca 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.
-## 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.
+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.
-## Thing Configuration
+## Discovery
-OpenSprinkler using the HTTP interface
+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.
-```
-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. |
-
-## Example
-
-demo.Things:
+| 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. |
+
+## Textual Example
+
+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 c7f33ad6825c8..486f74bdd357d 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 4a62ecf8542f9..853e6eece343d 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 6cc890c5b4b7d..4a32278a7a27a 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 @@
@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 06dd806c35371..9b1eb7b6ae5b9 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.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 @@
* @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 boolean supportsThingType(ThingTypeUID thingTypeUID) {
}
@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 @@ protected ThingHandler createHandler(Thing thing) {
} 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 0000000000000..33e89c238b77b
--- /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 0000000000000..dd61584dc1a90
--- /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 36a7a7aa7bb1d..5f365299b9171 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 abstract void openStation(int station, BigDecimal duration)
* @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 abstract void openStation(int station, BigDecimal duration)
* @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();
+
+ /**
+ * 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 int currentDraw() throws CommunicationApiException, NoCurrentDrawSensorException;
+ 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 abstract void openStation(int station, BigDecimal duration)
* @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 91620bc903266..0000000000000
--- 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 88dfbb30585ec..2dd677b03c796 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.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 @@
* @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 OpenSprinklerApi getHttpApi(OpenSprinklerHttpInterfaceConfig config)
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 OpenSprinklerApi getHttpApi(OpenSprinklerHttpInterfaceConfig config)
"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 bba8ee6a283da..6cfdf9d891c41 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 @@
* @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 @@ public boolean isManualModeEnabled() {
}
@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;
+ public boolean isStationOpen(int station) throws CommunicationApiException, GeneralApiException {
+ String returnContent = http.sendHttpGet(getBaseUrl() + "sn" + station, null);
+ return "1".equals(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.");
- }
+ @Override
+ public void ignoreRain(int station, boolean command) throws CommunicationApiException, UnauthorizedApiException {
+ }
- 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());
- }
+ @Override
+ public boolean isIgnoringRain(int station) {
+ return false;
+ }
- return returnContent != null && returnContent.equals("1");
+ @Override
+ public boolean isRainDetected() {
+ return state.jcReply.rs == 1;
}
@Override
- public boolean isRainDetected() throws CommunicationApiException {
- if (statusInfo().rs == 1) {
- return true;
- } else {
- return false;
- }
+ public int getSensor2State() {
+ return state.jcReply.sn2;
}
@Override
- public int currentDraw() throws CommunicationApiException, NoCurrentDrawSensorException {
- JcResponse info = statusInfo();
- if (info.curr == null) {
- throw new NoCurrentDrawSensorException();
- }
- return info.curr;
+ public int currentDraw() {
+ return state.jcReply.curr;
}
@Override
- public int waterLevel() throws CommunicationApiException {
- JoResponse info = getOptions();
- return info.wl;
+ public int flowSensorCount() {
+ return state.jcReply.flcrt;
}
@Override
- public int getNumberOfStations() throws CommunicationApiException {
- String returnContent;
+ public int signalStrength() {
+ return state.jcReply.rssi;
+ }
- 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());
- }
+ @Override
+ public boolean getIsEnabled() {
+ return state.jcReply.en == 1;
+ }
+
+ @Override
+ public int waterLevel() {
+ return state.joReply.wl;
+ }
- this.numberOfStations = Parse.jsonInt(returnContent, JSON_OPTION_STATION_COUNT);
+ @Override
+ public int getNumberOfStations() {
+ numberOfStations = state.jsReply.nstations;
+ return numberOfStations;
+ }
- return this.numberOfStations;
+ @Override
+ public int getFirmwareVersion() throws CommunicationApiException, UnauthorizedApiException {
+ state.joReply = getOptions();
+ return state.joReply.fwv;
}
@Override
- public int getFirmwareVersion() throws CommunicationApiException {
+ public void runProgram(Command command) throws CommunicationApiException, UnauthorizedApiException {
+ logger.warn("OpenSprinkler requires at least firmware 217 for the runProgram feature to work");
+ }
- try {
- JoResponse info = getOptions();
- this.firmwareVersion = info.fwv;
- } catch (Exception exp) {
- this.firmwareVersion = -1;
+ @Override
+ public void enablePrograms(Command command) throws UnauthorizedApiException, CommunicationApiException {
+ if (command == OnOffType.ON) {
+ http.sendHttpGet(getBaseUrl() + "cv", getRequestRequiredOptions() + "&en=1");
+ } else {
+ http.sendHttpGet(getBaseUrl() + "cv", getRequestRequiredOptions() + "&en=0");
}
+ }
- return this.firmwareVersion;
+ @Override
+ public void resetStations() throws UnauthorizedApiException, CommunicationApiException {
+ http.sendHttpGet(getBaseUrl() + "cv", getRequestRequiredOptions() + "&rsn=1");
+ }
+
+ @Override
+ public void setRainDelay(int hours) throws UnauthorizedApiException, CommunicationApiException {
+ http.sendHttpGet(getBaseUrl() + "cv", getRequestRequiredOptions() + "&rd=" + hours);
+ }
+
+ @Override
+ public QuantityType getRainDelay() {
+ if (state.jcReply.rdst == 0) {
+ return new QuantityType(0, Units.SECOND);
+ }
+ long remainingTime = state.jcReply.rdst - state.jcReply.devt;
+ return new QuantityType(remainingTime, Units.SECOND);
}
/**
@@ -232,7 +245,7 @@ public int getFirmwareVersion() throws CommunicationApiException {
* @return String representation of the OpenSprinkler API URL.
*/
protected String getBaseUrl() {
- return hostname + ":" + port + "/";
+ return hostname + ":" + config.port + "/";
}
/**
@@ -246,49 +259,89 @@ protected String getRequestRequiredOptions() {
@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());
}
+ return resp;
+ }
- JoResponse resp = gson.fromJson(returnContent, JoResponse.class);
+ 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;
}
- private static class JoResponse {
- public int wl;
- public int fwv;
+ @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 @@ public HttpRequestSender(HttpClient httpClient) {
* @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 @@ public String sendHttpPost(String url, String urlParameters) throws Communicatio
} 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 e46d3582418aa..b6753dab75030 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.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 @@
* @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());
}
+ 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));
+ }
+ }
+ }
- 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());
+ @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;
+ }
- if (stationStatus == 1) {
- return true;
+ @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 @@ public boolean isStationOpen(int station) throws GeneralApiException, Communicat
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 @@ public void openStation(int station, BigDecimal duration) throws CommunicationAp
throw new CommunicationApiException(
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
}
-
resultParser(returnContent);
}
@@ -108,11 +118,6 @@ public void openStation(int station, BigDecimal duration) throws CommunicationAp
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 @@ public void closeStation(int station) throws CommunicationApiException, GeneralA
throw new CommunicationApiException(
"There was a problem in the HTTP communication with the OpenSprinkler API: " + exp.getMessage());
}
-
resultParser(returnContent);
}
@@ -130,10 +134,8 @@ public void closeStation(int station) throws CommunicationApiException, GeneralA
* @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 a23638238b992..f1643c6ce89f2 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 @@
*
* @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 0000000000000..aea385425d063
--- /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 0000000000000..c374bd6ce7abd
--- /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 da0d7d2b8a79f..f410ea393d24e 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 a51335eaf141f..8bf60999babc8 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 b91ef80f75008..12ff6ef7d2a19 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 aa6ce59d4ca00..67792b2b00707 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 c40761f004907..615767e272526 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 496dc7f935278..75baf295a60b1 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 cb59d7eb34799..d74f8a02e081b 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 22904386a8b44..8821018032d1c 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 edd6047fc6a46..ac89589ebaf46 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 3aa42d725f84a..faefd85b5a806 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 37f81e60ad7d8..09edc3dc0dd1f 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 @@
*
* @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 a94331dc772b1..eb6a862939080 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 @@
*
* @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 32babbd697f76..238f6f0486e9e 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 da5e9e144103f..e0c7a915cf514 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 @@
*
* @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 void run() {
*/
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 abe3817807ced..664bc17d600c4 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.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 @@
* @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 @@ OpenSprinklerApiFactory getApiFactory() {
@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 @@ protected void startScan() {
* @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 177d7a7fa1a9f..0000000000000
--- 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 2eac9354f4729..aef0dda9cda08 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 {
- public OpenSprinklerBaseHandler(Thing thing) {
- super(thing);
- }
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+ protected BigDecimal nextDurationTime = MAX_TIME_SECONDS;
- @Override
- public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
- super.bridgeStatusChanged(bridgeStatusInfo);
+ @Nullable
+ OpenSprinklerHttpBridgeHandler bridgeHandler;
- if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
- updateStatus(ThingStatus.UNKNOWN);
- }
+ public OpenSprinklerBaseHandler(Thing thing) {
+ super(thing);
}
- @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 void updateChannels() {
}
}
+ 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 fa9688e9dc7c4..b6e6ba662c964 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.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);
- }
+ 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());
- } catch (CommunicationApiException e) {
- logger.debug("Could not query current draw. Not removing channel as it could be temporary.", e);
}
+ 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 39715fa5638a5..895945b1900d4 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.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 OpenSprinklerHttpBridgeHandler(Bridge bridge, OpenSprinklerApiFactory api
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;
- }
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ // Nothing to do for the bridge handler
+ }
- super.initialize();
+ @Override
+ public void initialize() {
+ openSprinklerConfig = getConfig().as(OpenSprinklerHttpInterfaceConfig.class);
+ pollingJob = scheduler.scheduleWithFixedDelay(this::refreshStations, 2, openSprinklerConfig.refresh,
+ TimeUnit.SECONDS);
}
@Override
- protected long getRefreshInterval() {
- OpenSprinklerHttpInterfaceConfig openSprinklerConfig = this.openSprinklerConfig;
- if (openSprinklerConfig == null) {
- return DEFAULT_REFRESH_RATE;
+ 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 b07a6b706753c..2388a0df01559 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 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.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 OpenSprinklerStationHandler(Thing thing) {
@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 void handleCommand(ChannelUID channelUID, Command command) {
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 @@ private OnOffType getStationState(int stationId) {
}
@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 @@ protected void updateChannel(@NonNull ChannelUID channel) {
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 @@ protected void updateChannel(@NonNull ChannelUID channel) {
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 31fc7eb2bdd44..0000000000000
--- 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 a7ea0aff47cb2..a864fef3ed2de 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 52d7b2a78967b..7248c3ff3dc87 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 @@
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 static String getMD5Hash(String unhashed) {
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 6fffab23fe03e..bf142e2ec4d49 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 @@
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 @@
*
* @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 bea478715c365..8f7039036cce4 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.
+
+
+