diff --git a/bundles/org.openhab.binding.opengarage/README.md b/bundles/org.openhab.binding.opengarage/README.md
index 2fc30e1e1..155372a0f 100644
--- a/bundles/org.openhab.binding.opengarage/README.md
+++ b/bundles/org.openhab.binding.opengarage/README.md
@@ -4,7 +4,7 @@ The OpenGarage binding allows you to control an OpenGarage controller ( are supported.
+OpenGarage controllers from are supported.
## Discovery
@@ -19,6 +19,13 @@ As a minimum, the IP address is needed:
- `port` - the port the OpenGarage is listening on. Defaults to port 80
- `refresh` - The frequency with which to refresh information from the OpenGarage controller specified in seconds. Defaults to 10 seconds.
- `password` - The password to send commands to the OpenGarage. Defaults to "opendoor"
+- `doorTransitionTimeSeconds` - Specifies how long it takes the garage door
+to fully open / close after triggering it from OpenGarage, including auditory
+beeps. Recommend to round up or pad by a second or two.
+- `doorOpeningState` - Text state to report when garage is opening. Defaults to "OPENING".
+- `doorOpenState` - Text state to report when garage is open (and not in transition). Defaults to "OPEN".
+- `doorClosingState` - Text state to report when garage is closing. Defaults to "CLOSING".
+- `doorClosedState` - Text state to report when garage is closed (and not in transition). Defaults to "CLOSED".
## Channels
@@ -26,6 +33,7 @@ As a minimum, the IP address is needed:
|----------------------|---------------|---------------------------------------------------------------------------------------|
| distance | Number:Length | Distance reading from the OpenGarage controller (default in cm) |
| status-switch | Switch | Door status (OFF = Closed, ON = Open), set "invert=true" on channel to invert switch |
+| status-text | String | Text status of the current door state, including transition, using values from configuration: doorOpeningState, doorOpenState, doorClosingState, doorClosedState. |
| status-contact | Contact | Door status (Open or Closed) |
| status-rollershutter | Rollershutter | Door status (DOWN = Closed, UP = Open) |
| vehicle-status | Number | Report vehicle presence (0=Not Detected, 1=Detected, 2=Unknown) |
@@ -46,11 +54,13 @@ Contact OpenGarage_Status_Contact { channel="opengarage:opengarage:OpenGarage:st
Rollershutter OpenGarage_Status_Rollershutter { channel="opengarage:opengarage:OpenGarage:status-rollershutter" }
Number:Length OpenGarage_Distance { channel="opengarage:opengarage:OpenGarage:setpoint" }
String OpenGarage_Vehicle { channel="opengarage:opengarage:OpenGarage:vehicle" }
+String OpenGarage_StatusText { channel="opengarage:opengarage:OpenGarage:status-text" }
```
opengarage.sitemap:
```perl
+Text item=OpenGarage_StatusText label="Status"
Switch item=OpenGarage_Status icon="garagedoorclosed" mappings=[ON=Open] visibility=[OpenGarage_Status == OFF]
Switch item=OpenGarage_Status icon="garagedooropen" mappings=[OFF=Close] visibility=[OpenGarage_Status == ON]
Switch item=OpenGarage_Status icon="garage"
@@ -58,4 +68,26 @@ Contact item=OpenGarage_Status_Contact icon="garage"
Rollershutter item=OpenGarage_Status_Rollershutter icon="garage"
Text item=OpenGarage_Distance label="OG distance"
Text item=OpenGarage_Vehicle label="Vehicle Presence"
+
```
+
+## Adding to HomeKit
+
+If you have the HomeKit extension installed, you can control your OpenGarage instance via your iPhone.
+To wire it up to HomeKit, you might specify the following:
+
+opengarage.items
+
+```
+Group gOpenGarage "OpenGarage Door" {homekit="GarageDoorOpener"}
+Switch OpenGarage_TargetState "Target state" (gOpenGarage) {homekit="GarageDoorOpener.TargetDoorState", channel="opengarage:opengarage:deadbeef:status-switch"}
+String OpenGarage_CurrentState "Current state" (gOpenGarage) {homekit="GarageDoorOpener.CurrentDoorState", channel="opengarage:opengarage:deadbeef:status-text"}
+Switch OpenGarage_xxObstruction "Obstruction (do not use)" (gOpenGarage) {homekit="GarageDoorOpener.ObstructionStatus"}
+```
+
+The obstruction channel is not bound to any channel.
+It's needed because HomeKit requires it, and OpenGarage does not provide it.
+HomeKit requires a status for the garage door of `OPEN`, `CLOSED`, `CLOSING`, `OPENING`.
+In order to report that, we must provide state transition information.
+State transition information is inferred when the garage door state is changed.
+For `doorTransitionTimeSeconds` since the last open/close command was issued, the binding reports the state as either "closing" or "opening".
diff --git a/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageBindingConstants.java b/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageBindingConstants.java
index 02704100d..e1a9267fc 100644
--- a/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageBindingConstants.java
+++ b/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageBindingConstants.java
@@ -34,6 +34,7 @@ public class OpenGarageBindingConstants {
// List of all Channel ids
public static final String CHANNEL_OG_DISTANCE = "distance";
public static final String CHANNEL_OG_STATUS = "status"; // now deprecated
+ public static final String CHANNEL_OG_STATUS_TEXT = "status-text";
public static final String CHANNEL_OG_STATUS_SWITCH = "status-switch";
public static final String CHANNEL_OG_STATUS_CONTACT = "status-contact";
public static final String CHANNEL_OG_STATUS_ROLLERSHUTTER = "status-rollershutter";
diff --git a/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageConfiguration.java b/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageConfiguration.java
index 6f1473e8f..84ae82771 100644
--- a/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageConfiguration.java
+++ b/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageConfiguration.java
@@ -12,14 +12,23 @@
*/
package org.openhab.binding.opengarage.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* The OpenGarageConfiguration class contains fields mapping thing configuration parameters.
*
* @author Paul Smedley - Initial contribution
*/
+@NonNullByDefault
public class OpenGarageConfiguration {
- public String hostname;
- public long port = 80;
+ public String hostname = "";
+ public int port = 80;
public String password = "opendoor";
- public long refresh = 10;
+ public int refresh = 10;
+
+ public String doorOpeningState = "OPENING";
+ public String doorOpenState = "OPEN";
+ public String doorClosedState = "CLOSED";
+ public String doorClosingState = "CLOSING";
+ public int doorTransitionTimeSeconds = 17;
}
diff --git a/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageHandler.java b/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageHandler.java
index ce0ca8752..dab22dbce 100644
--- a/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageHandler.java
+++ b/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageHandler.java
@@ -13,12 +13,14 @@
package org.openhab.binding.opengarage.internal;
import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.opengarage.internal.api.ControllerVariables;
import org.openhab.binding.opengarage.internal.api.Enums.OpenGarageCommand;
import org.openhab.core.library.types.DecimalType;
@@ -52,34 +54,47 @@ public class OpenGarageHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenGarageHandler.class);
- private long refreshInterval;
-
private @NonNullByDefault({}) OpenGarageWebTargets webTargets;
- private @Nullable ScheduledFuture> pollFuture;
+
+ // reference to periodically scheduled poll task
+ private Future> pollScheduledFuture = CompletableFuture.completedFuture(null);
+
+ // reference to one-shot poll task which gets scheduled after a garage state change command
+ private Future> pollScheduledFutureTransition = CompletableFuture.completedFuture(null);
+ private Instant lastTransition;
+ private String lastTransitionText;
+
+ private OpenGarageConfiguration config = new OpenGarageConfiguration();
public OpenGarageHandler(Thing thing) {
super(thing);
+ this.lastTransition = Instant.MIN;
+ this.lastTransitionText = "";
}
@Override
- public void handleCommand(ChannelUID channelUID, Command command) {
+ public synchronized void handleCommand(ChannelUID channelUID, Command command) {
try {
logger.debug("Received command {} for thing '{}' on channel {}", command, thing.getUID().getAsString(),
channelUID.getId());
- boolean invert = isChannelInverted(channelUID.getId());
+ Function maybeInvert = getInverter(channelUID.getId());
switch (channelUID.getId()) {
case OpenGarageBindingConstants.CHANNEL_OG_STATUS:
case OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH:
case OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER:
- if (command.equals(OnOffType.ON) || command.equals(UpDownType.UP)) {
- changeStatus(invert ? OpenGarageCommand.CLOSE : OpenGarageCommand.OPEN);
- return;
- } else if (command.equals(OnOffType.OFF) || command.equals(UpDownType.DOWN)) {
- changeStatus(invert ? OpenGarageCommand.OPEN : OpenGarageCommand.CLOSE);
- return;
- } else if (command.equals(StopMoveType.STOP) || command.equals(StopMoveType.MOVE)) {
+ if (command.equals(StopMoveType.STOP) || command.equals(StopMoveType.MOVE)) {
changeStatus(OpenGarageCommand.CLICK);
- return;
+ } else {
+ boolean doorOpen = command.equals(OnOffType.ON) || command.equals(UpDownType.UP);
+ changeStatus(maybeInvert.apply(doorOpen) ? OpenGarageCommand.OPEN : OpenGarageCommand.CLOSE);
+ this.lastTransition = Instant.now();
+ this.lastTransitionText = doorOpen ? this.config.doorOpeningState
+ : this.config.doorClosingState;
+
+ this.poll(); // invoke poll directly to communicate the door transition state
+ this.pollScheduledFutureTransition.cancel(false);
+ this.pollScheduledFutureTransition = this.scheduler.schedule(this::poll,
+ this.config.doorTransitionTimeSeconds, TimeUnit.SECONDS);
}
break;
default:
@@ -91,107 +106,111 @@ public class OpenGarageHandler extends BaseThingHandler {
@Override
public void initialize() {
- OpenGarageConfiguration config = getConfigAs(OpenGarageConfiguration.class);
+ this.config = getConfigAs(OpenGarageConfiguration.class);
logger.debug("config.hostname = {}, refresh = {}, port = {}", config.hostname, config.refresh, config.port);
- if (config.hostname == null) {
+ if (config.hostname.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hostname/IP address must be set");
} else {
- webTargets = new OpenGarageWebTargets(config.hostname, config.port, config.password);
- refreshInterval = config.refresh;
-
- schedulePoll();
+ updateStatus(ThingStatus.UNKNOWN);
+ int requestTimeout = Math.max(OpenGarageWebTargets.DEFAULT_TIMEOUT_MS, config.refresh * 1000);
+ webTargets = new OpenGarageWebTargets(config.hostname, config.port, config.password, requestTimeout);
+ this.pollScheduledFuture = this.scheduler.scheduleWithFixedDelay(this::poll, 1, config.refresh,
+ TimeUnit.SECONDS);
}
}
@Override
public void dispose() {
+ this.pollScheduledFuture.cancel(true);
+ this.pollScheduledFutureTransition.cancel(true);
super.dispose();
- stopPoll();
}
- private void schedulePoll() {
- if (pollFuture != null) {
- pollFuture.cancel(false);
- }
- logger.debug("Scheduling poll for 1 second out, then every {} s", refreshInterval);
- pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, refreshInterval, TimeUnit.SECONDS);
- }
-
- private void poll() {
+ /**
+ * Update the state of the controller.
+ *
+ *
+ */
+ private synchronized void poll() {
try {
logger.debug("Polling for state");
- pollStatus();
+ ControllerVariables controllerVariables = webTargets.getControllerVariables();
+ long lastTransitionAgoSecs = Duration.between(lastTransition, Instant.now()).getSeconds();
+ boolean inTransition = lastTransitionAgoSecs < this.config.doorTransitionTimeSeconds;
+ if (controllerVariables != null) {
+ updateStatus(ThingStatus.ONLINE);
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_DISTANCE,
+ new QuantityType<>(controllerVariables.dist, MetricPrefix.CENTI(SIUnits.METRE)));
+ Function maybeInvert = getInverter(
+ OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH);
+
+ if ((controllerVariables.door != 0) && (controllerVariables.door != 1)) {
+ logger.debug("Received unknown door value: {}", controllerVariables.door);
+ } else {
+ boolean doorOpen = controllerVariables.door == 1;
+ OnOffType onOff = maybeInvert.apply(doorOpen) ? OnOffType.ON : OnOffType.OFF;
+ UpDownType upDown = doorOpen ? UpDownType.UP : UpDownType.DOWN;
+ OpenClosedType contact = doorOpen ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
+
+ String transitionText;
+ if (inTransition) {
+ transitionText = this.lastTransitionText;
+ } else {
+ transitionText = doorOpen ? this.config.doorOpenState : this.config.doorClosedState;
+ }
+ if (!inTransition) {
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS, onOff); // deprecated channel
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH, onOff);
+ }
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER, upDown);
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_CONTACT, contact);
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_TEXT, new StringType(transitionText));
+ }
+
+ switch (controllerVariables.vehicle) {
+ case 0:
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
+ new StringType("No vehicle detected"));
+ break;
+ case 1:
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("Vehicle detected"));
+ break;
+ case 2:
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
+ new StringType("Vehicle status unknown"));
+ break;
+ case 3:
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
+ new StringType("Vehicle status not available"));
+ break;
+
+ default:
+ logger.debug("Received unknown vehicle value: {}", controllerVariables.vehicle);
+ }
+ updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE_STATUS,
+ new DecimalType(controllerVariables.vehicle));
+ }
} catch (IOException e) {
logger.debug("Could not connect to OpenGarage controller", e);
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Could not connect to OpenGarage controller");
} catch (RuntimeException e) {
- logger.warn("Unexpected error connecting to OpenGarage controller", e);
+ logger.debug("Unexpected error connecting to OpenGarage controller", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
- private void stopPoll() {
- final Future> future = pollFuture;
- if (future != null && !future.isCancelled()) {
- future.cancel(true);
- pollFuture = null;
- }
- }
-
- private void pollStatus() throws IOException {
- ControllerVariables controllerVariables = webTargets.getControllerVariables();
- updateStatus(ThingStatus.ONLINE);
- if (controllerVariables != null) {
- updateState(OpenGarageBindingConstants.CHANNEL_OG_DISTANCE,
- new QuantityType<>(controllerVariables.dist, MetricPrefix.CENTI(SIUnits.METRE)));
- boolean invert = isChannelInverted(OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH);
- switch (controllerVariables.door) {
- case 0:
- updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS, invert ? OnOffType.ON : OnOffType.OFF);
- updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH,
- invert ? OnOffType.ON : OnOffType.OFF);
- updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER, UpDownType.DOWN);
- updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_CONTACT, OpenClosedType.CLOSED);
- break;
- case 1:
- updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS, invert ? OnOffType.OFF : OnOffType.ON);
- updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH,
- invert ? OnOffType.OFF : OnOffType.ON);
- updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER, UpDownType.UP);
- updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_CONTACT, OpenClosedType.OPEN);
- break;
- default:
- logger.warn("Received unknown door value: {}", controllerVariables.door);
- }
- switch (controllerVariables.vehicle) {
- case 0:
- updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("No vehicle detected"));
- break;
- case 1:
- updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("Vehicle detected"));
- break;
- case 2:
- updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
- new StringType("Vehicle status unknown"));
- break;
- case 3:
- updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
- new StringType("Vehicle status not available"));
- break;
- default:
- logger.warn("Received unknown vehicle value: {}", controllerVariables.vehicle);
- }
- updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE_STATUS,
- new DecimalType(controllerVariables.vehicle));
- }
- }
-
private void changeStatus(OpenGarageCommand status) throws OpenGarageCommunicationException {
webTargets.setControllerVariables(status);
}
- private boolean isChannelInverted(String channelUID) {
+ private Function getInverter(String channelUID) {
Channel channel = getThing().getChannel(channelUID);
- return channel != null && channel.getConfiguration().as(OpenGarageChannelConfiguration.class).invert;
+ boolean invert = channel != null && channel.getConfiguration().as(OpenGarageChannelConfiguration.class).invert;
+ if (invert) {
+ return onOff -> !onOff;
+ } else {
+ return Function.identity();
+ }
}
}
diff --git a/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageWebTargets.java b/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageWebTargets.java
index feb9c195c..ea313e771 100644
--- a/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageWebTargets.java
+++ b/bundles/org.openhab.binding.opengarage/src/main/java/org/openhab/binding/opengarage/internal/OpenGarageWebTargets.java
@@ -27,16 +27,18 @@ import org.slf4j.LoggerFactory;
*
*/
public class OpenGarageWebTargets {
- private static final int TIMEOUT_MS = 30000;
+ public static int DEFAULT_TIMEOUT_MS = 30000;
private String getControllerVariablesUri;
private String changeControllerVariablesUri;
private final Logger logger = LoggerFactory.getLogger(OpenGarageWebTargets.class);
+ private int timeoutMs;
- public OpenGarageWebTargets(String ipAddress, long port, String password) {
+ public OpenGarageWebTargets(String ipAddress, long port, String password, int timeoutMs) {
String baseUri = "http://" + ipAddress + ":" + port + "/";
- getControllerVariablesUri = baseUri + "jc";
- changeControllerVariablesUri = baseUri + "cc?dkey=" + password;
+ this.timeoutMs = timeoutMs;
+ this.getControllerVariablesUri = baseUri + "jc";
+ this.changeControllerVariablesUri = baseUri + "cc?dkey=" + password;
}
public ControllerVariables getControllerVariables() throws OpenGarageCommunicationException {
@@ -73,7 +75,7 @@ public class OpenGarageWebTargets {
String response;
synchronized (this) {
try {
- response = HttpUtil.executeUrl("GET", uriWithParams, TIMEOUT_MS);
+ response = HttpUtil.executeUrl("GET", uriWithParams, this.timeoutMs);
} catch (IOException ex) {
logger.debug("{}", ex.getLocalizedMessage(), ex);
// Response will also be set to null if parsing in executeUrl fails so we use null here to make the
diff --git a/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/i18n/opengarage.properties b/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/i18n/opengarage.properties
index a83666c9c..5bf5c729d 100644
--- a/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/i18n/opengarage.properties
+++ b/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/i18n/opengarage.properties
@@ -18,6 +18,16 @@ thing-type.config.opengarage.opengarage.port.label = Port
thing-type.config.opengarage.opengarage.port.description = Port of the OpenGarage Web API interface.
thing-type.config.opengarage.opengarage.refresh.label = Refresh Interval
thing-type.config.opengarage.opengarage.refresh.description = Specifies the refresh interval in seconds.
+thing-type.config.opengarage.opengarage.doorTransitionTimeSeconds.label = Door Transition Time
+thing-type.config.opengarage.opengarage.doorTransitionTimeSeconds.description = Specifies number of seconds that it takes for the garage door to fully open / close, including the time it takes for OpenHab to emit beeps. Round up.
+thing-type.config.opengarage.opengarage.doorOpeningState.label = Door Opening State
+thing-type.config.opengarage.opengarage.doorOpeningState.description = Text state to report when garage is opening. Defaults to "OPENING".
+thing-type.config.opengarage.opengarage.doorOpenState.label = Door Open State
+thing-type.config.opengarage.opengarage.doorOpenState.description = Text state to report when garage is open (and not in transition). Defaults to "OPEN".
+thing-type.config.opengarage.opengarage.doorClosingState.label = Door Closing State
+thing-type.config.opengarage.opengarage.doorClosingState.description = Text state to report when garage is closing. Defaults to "CLOSING".
+thing-type.config.opengarage.opengarage.doorClosedState.label = Door Closed State
+thing-type.config.opengarage.opengarage.doorClosedState.description = Text state to report when garage is closed (and not in transition). Defaults to "CLOSED".
# channel types
@@ -31,6 +41,8 @@ channel-type.opengarage.opengarage-status-switch.label = Status
channel-type.opengarage.opengarage-status-switch.description = On/Off Status of the OG unit
channel-type.opengarage.opengarage-status.label = Status
channel-type.opengarage.opengarage-status.description = On/Off Status of the OG unit (now deprecated, use status-switch instead)
+channel-type.opengarage.opengarage-status-text.label = Text status
+channel-type.opengarage.opengarage-status-text.description = Text status of the current door state, including transition, using values from configuration: doorOpeningState, doorOpenState, doorClosingState, doorClosedState.
channel-type.opengarage.opengarage-vehicle-status.label = Vehicle Presence
channel-type.opengarage.opengarage-vehicle-status.description = Vehicle presence detection
channel-type.opengarage.opengarage-vehicle-status.state.option.0 = No vehicle detected
diff --git a/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/thing/thing-types.xml
index 9395f6f0c..7cde3d295 100644
--- a/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/thing/thing-types.xml
+++ b/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/thing/thing-types.xml
@@ -16,8 +16,13 @@
+
+
+ 1
+
+
@@ -34,13 +39,38 @@
passwordopendoor
-
+ Specifies the refresh interval in seconds.60
+
+
+ Specifies number of seconds that it takes for the garage door to fully open / close, including the time
+ it takes for OpenHab to emit beeps. Round up.
+ 17
+
+
+
+ Text state to report when garage is opening
+ OPENING
+
+
+
+ Text state to report when garage is open (and not in transition)
+ OPEN
+
+
+
+ Text state to report when garage is closing
+ CLOSING
+
+
+
+ Text state to report when garage is closed (and not in transition)
+ CLOSED
+
-
@@ -113,4 +143,11 @@
+
+ String
+
+ Text status of the current door state, including transition, using values from configuration:
+ doorOpeningState, doorOpenState, doorClosingState, doorClosedState.
+
+
diff --git a/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/update/instructions.xml
new file mode 100644
index 000000000..65663e15a
--- /dev/null
+++ b/bundles/org.openhab.binding.opengarage/src/main/resources/OH-INF/update/instructions.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ opengarage:opengarage-status-text
+
+
+
+
+
+