[opengarage] Add door transition status support (#14028)
* Add support for garage door transition status 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 door_transition_time_seconds since the last open/close command was issued, the binding reports the state as either "closing" or "opening". --------- Signed-off-by: Tim Harper <timcharper@gmail.com> Co-authored-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
parent
4a8cb5fac9
commit
9ebb203d58
|
@ -4,7 +4,7 @@ The OpenGarage binding allows you to control an OpenGarage controller (<https://
|
||||||
|
|
||||||
## Supported Things
|
## Supported Things
|
||||||
|
|
||||||
Opengarage controllers from <https://opensprinkler.com/product/opengarage/> are supported.
|
OpenGarage controllers from <https://opensprinkler.com/product/opengarage/> are supported.
|
||||||
|
|
||||||
## Discovery
|
## 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
|
- `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.
|
- `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"
|
- `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
|
## 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) |
|
| 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-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-contact | Contact | Door status (Open or Closed) |
|
||||||
| status-rollershutter | Rollershutter | Door status (DOWN = Closed, UP = Open) |
|
| status-rollershutter | Rollershutter | Door status (DOWN = Closed, UP = Open) |
|
||||||
| vehicle-status | Number | Report vehicle presence (0=Not Detected, 1=Detected, 2=Unknown) |
|
| 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" }
|
Rollershutter OpenGarage_Status_Rollershutter { channel="opengarage:opengarage:OpenGarage:status-rollershutter" }
|
||||||
Number:Length OpenGarage_Distance { channel="opengarage:opengarage:OpenGarage:setpoint" }
|
Number:Length OpenGarage_Distance { channel="opengarage:opengarage:OpenGarage:setpoint" }
|
||||||
String OpenGarage_Vehicle { channel="opengarage:opengarage:OpenGarage:vehicle" }
|
String OpenGarage_Vehicle { channel="opengarage:opengarage:OpenGarage:vehicle" }
|
||||||
|
String OpenGarage_StatusText { channel="opengarage:opengarage:OpenGarage:status-text" }
|
||||||
```
|
```
|
||||||
|
|
||||||
opengarage.sitemap:
|
opengarage.sitemap:
|
||||||
|
|
||||||
```perl
|
```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="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="garagedooropen" mappings=[OFF=Close] visibility=[OpenGarage_Status == ON]
|
||||||
Switch item=OpenGarage_Status icon="garage"
|
Switch item=OpenGarage_Status icon="garage"
|
||||||
|
@ -58,4 +68,26 @@ Contact item=OpenGarage_Status_Contact icon="garage"
|
||||||
Rollershutter item=OpenGarage_Status_Rollershutter icon="garage"
|
Rollershutter item=OpenGarage_Status_Rollershutter icon="garage"
|
||||||
Text item=OpenGarage_Distance label="OG distance"
|
Text item=OpenGarage_Distance label="OG distance"
|
||||||
Text item=OpenGarage_Vehicle label="Vehicle Presence"
|
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".
|
||||||
|
|
|
@ -34,6 +34,7 @@ public class OpenGarageBindingConstants {
|
||||||
// List of all Channel ids
|
// List of all Channel ids
|
||||||
public static final String CHANNEL_OG_DISTANCE = "distance";
|
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 = "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_SWITCH = "status-switch";
|
||||||
public static final String CHANNEL_OG_STATUS_CONTACT = "status-contact";
|
public static final String CHANNEL_OG_STATUS_CONTACT = "status-contact";
|
||||||
public static final String CHANNEL_OG_STATUS_ROLLERSHUTTER = "status-rollershutter";
|
public static final String CHANNEL_OG_STATUS_ROLLERSHUTTER = "status-rollershutter";
|
||||||
|
|
|
@ -12,14 +12,23 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.opengarage.internal;
|
package org.openhab.binding.opengarage.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The OpenGarageConfiguration class contains fields mapping thing configuration parameters.
|
* The OpenGarageConfiguration class contains fields mapping thing configuration parameters.
|
||||||
*
|
*
|
||||||
* @author Paul Smedley - Initial contribution
|
* @author Paul Smedley - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class OpenGarageConfiguration {
|
public class OpenGarageConfiguration {
|
||||||
public String hostname;
|
public String hostname = "";
|
||||||
public long port = 80;
|
public int port = 80;
|
||||||
public String password = "opendoor";
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,14 @@
|
||||||
package org.openhab.binding.opengarage.internal;
|
package org.openhab.binding.opengarage.internal;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.Future;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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.ControllerVariables;
|
||||||
import org.openhab.binding.opengarage.internal.api.Enums.OpenGarageCommand;
|
import org.openhab.binding.opengarage.internal.api.Enums.OpenGarageCommand;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
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 final Logger logger = LoggerFactory.getLogger(OpenGarageHandler.class);
|
||||||
|
|
||||||
private long refreshInterval;
|
|
||||||
|
|
||||||
private @NonNullByDefault({}) OpenGarageWebTargets webTargets;
|
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) {
|
public OpenGarageHandler(Thing thing) {
|
||||||
super(thing);
|
super(thing);
|
||||||
|
this.lastTransition = Instant.MIN;
|
||||||
|
this.lastTransitionText = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public synchronized void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
try {
|
try {
|
||||||
logger.debug("Received command {} for thing '{}' on channel {}", command, thing.getUID().getAsString(),
|
logger.debug("Received command {} for thing '{}' on channel {}", command, thing.getUID().getAsString(),
|
||||||
channelUID.getId());
|
channelUID.getId());
|
||||||
boolean invert = isChannelInverted(channelUID.getId());
|
Function<Boolean, Boolean> maybeInvert = getInverter(channelUID.getId());
|
||||||
switch (channelUID.getId()) {
|
switch (channelUID.getId()) {
|
||||||
case OpenGarageBindingConstants.CHANNEL_OG_STATUS:
|
case OpenGarageBindingConstants.CHANNEL_OG_STATUS:
|
||||||
case OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH:
|
case OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH:
|
||||||
case OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER:
|
case OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER:
|
||||||
if (command.equals(OnOffType.ON) || command.equals(UpDownType.UP)) {
|
if (command.equals(StopMoveType.STOP) || command.equals(StopMoveType.MOVE)) {
|
||||||
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)) {
|
|
||||||
changeStatus(OpenGarageCommand.CLICK);
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -91,81 +106,71 @@ public class OpenGarageHandler extends BaseThingHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
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);
|
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");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hostname/IP address must be set");
|
||||||
} else {
|
} else {
|
||||||
webTargets = new OpenGarageWebTargets(config.hostname, config.port, config.password);
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
refreshInterval = config.refresh;
|
int requestTimeout = Math.max(OpenGarageWebTargets.DEFAULT_TIMEOUT_MS, config.refresh * 1000);
|
||||||
|
webTargets = new OpenGarageWebTargets(config.hostname, config.port, config.password, requestTimeout);
|
||||||
schedulePoll();
|
this.pollScheduledFuture = this.scheduler.scheduleWithFixedDelay(this::poll, 1, config.refresh,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
this.pollScheduledFuture.cancel(true);
|
||||||
|
this.pollScheduledFutureTransition.cancel(true);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
stopPoll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void schedulePoll() {
|
/**
|
||||||
if (pollFuture != null) {
|
* Update the state of the controller.
|
||||||
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 synchronized void poll() {
|
||||||
}
|
|
||||||
|
|
||||||
private void poll() {
|
|
||||||
try {
|
try {
|
||||||
logger.debug("Polling for state");
|
logger.debug("Polling for state");
|
||||||
pollStatus();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.debug("Could not connect to OpenGarage controller", e);
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
logger.warn("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();
|
ControllerVariables controllerVariables = webTargets.getControllerVariables();
|
||||||
updateStatus(ThingStatus.ONLINE);
|
long lastTransitionAgoSecs = Duration.between(lastTransition, Instant.now()).getSeconds();
|
||||||
|
boolean inTransition = lastTransitionAgoSecs < this.config.doorTransitionTimeSeconds;
|
||||||
if (controllerVariables != null) {
|
if (controllerVariables != null) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_DISTANCE,
|
updateState(OpenGarageBindingConstants.CHANNEL_OG_DISTANCE,
|
||||||
new QuantityType<>(controllerVariables.dist, MetricPrefix.CENTI(SIUnits.METRE)));
|
new QuantityType<>(controllerVariables.dist, MetricPrefix.CENTI(SIUnits.METRE)));
|
||||||
boolean invert = isChannelInverted(OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH);
|
Function<Boolean, Boolean> maybeInvert = getInverter(
|
||||||
switch (controllerVariables.door) {
|
OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH);
|
||||||
case 0:
|
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS, invert ? OnOffType.ON : OnOffType.OFF);
|
if ((controllerVariables.door != 0) && (controllerVariables.door != 1)) {
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH,
|
logger.debug("Received unknown door value: {}", controllerVariables.door);
|
||||||
invert ? OnOffType.ON : OnOffType.OFF);
|
} else {
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER, UpDownType.DOWN);
|
boolean doorOpen = controllerVariables.door == 1;
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_CONTACT, OpenClosedType.CLOSED);
|
OnOffType onOff = maybeInvert.apply(doorOpen) ? OnOffType.ON : OnOffType.OFF;
|
||||||
break;
|
UpDownType upDown = doorOpen ? UpDownType.UP : UpDownType.DOWN;
|
||||||
case 1:
|
OpenClosedType contact = doorOpen ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS, invert ? OnOffType.OFF : OnOffType.ON);
|
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH,
|
String transitionText;
|
||||||
invert ? OnOffType.OFF : OnOffType.ON);
|
if (inTransition) {
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER, UpDownType.UP);
|
transitionText = this.lastTransitionText;
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_STATUS_CONTACT, OpenClosedType.OPEN);
|
} else {
|
||||||
break;
|
transitionText = doorOpen ? this.config.doorOpenState : this.config.doorClosedState;
|
||||||
default:
|
|
||||||
logger.warn("Received unknown door value: {}", controllerVariables.door);
|
|
||||||
}
|
}
|
||||||
|
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) {
|
switch (controllerVariables.vehicle) {
|
||||||
case 0:
|
case 0:
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("No vehicle detected"));
|
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
|
||||||
|
new StringType("No vehicle detected"));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("Vehicle detected"));
|
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("Vehicle detected"));
|
||||||
|
@ -178,20 +183,34 @@ public class OpenGarageHandler extends BaseThingHandler {
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
|
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
|
||||||
new StringType("Vehicle status not available"));
|
new StringType("Vehicle status not available"));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.warn("Received unknown vehicle value: {}", controllerVariables.vehicle);
|
logger.debug("Received unknown vehicle value: {}", controllerVariables.vehicle);
|
||||||
}
|
}
|
||||||
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE_STATUS,
|
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE_STATUS,
|
||||||
new DecimalType(controllerVariables.vehicle));
|
new DecimalType(controllerVariables.vehicle));
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("Could not connect to OpenGarage controller", e);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"Could not connect to OpenGarage controller");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
logger.debug("Unexpected error connecting to OpenGarage controller", e);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeStatus(OpenGarageCommand status) throws OpenGarageCommunicationException {
|
private void changeStatus(OpenGarageCommand status) throws OpenGarageCommunicationException {
|
||||||
webTargets.setControllerVariables(status);
|
webTargets.setControllerVariables(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isChannelInverted(String channelUID) {
|
private Function<Boolean, Boolean> getInverter(String channelUID) {
|
||||||
Channel channel = getThing().getChannel(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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,18 @@ import org.slf4j.LoggerFactory;
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class OpenGarageWebTargets {
|
public class OpenGarageWebTargets {
|
||||||
private static final int TIMEOUT_MS = 30000;
|
public static int DEFAULT_TIMEOUT_MS = 30000;
|
||||||
|
|
||||||
private String getControllerVariablesUri;
|
private String getControllerVariablesUri;
|
||||||
private String changeControllerVariablesUri;
|
private String changeControllerVariablesUri;
|
||||||
private final Logger logger = LoggerFactory.getLogger(OpenGarageWebTargets.class);
|
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 + "/";
|
String baseUri = "http://" + ipAddress + ":" + port + "/";
|
||||||
getControllerVariablesUri = baseUri + "jc";
|
this.timeoutMs = timeoutMs;
|
||||||
changeControllerVariablesUri = baseUri + "cc?dkey=" + password;
|
this.getControllerVariablesUri = baseUri + "jc";
|
||||||
|
this.changeControllerVariablesUri = baseUri + "cc?dkey=" + password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControllerVariables getControllerVariables() throws OpenGarageCommunicationException {
|
public ControllerVariables getControllerVariables() throws OpenGarageCommunicationException {
|
||||||
|
@ -73,7 +75,7 @@ public class OpenGarageWebTargets {
|
||||||
String response;
|
String response;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
try {
|
try {
|
||||||
response = HttpUtil.executeUrl("GET", uriWithParams, TIMEOUT_MS);
|
response = HttpUtil.executeUrl("GET", uriWithParams, this.timeoutMs);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
logger.debug("{}", ex.getLocalizedMessage(), 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
|
// Response will also be set to null if parsing in executeUrl fails so we use null here to make the
|
||||||
|
|
|
@ -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.port.description = Port of the OpenGarage Web API interface.
|
||||||
thing-type.config.opengarage.opengarage.refresh.label = Refresh Interval
|
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.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
|
# 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-switch.description = On/Off Status of the OG unit
|
||||||
channel-type.opengarage.opengarage-status.label = Status
|
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.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.label = Vehicle Presence
|
||||||
channel-type.opengarage.opengarage-vehicle-status.description = Vehicle presence detection
|
channel-type.opengarage.opengarage-vehicle-status.description = Vehicle presence detection
|
||||||
channel-type.opengarage.opengarage-vehicle-status.state.option.0 = No vehicle detected
|
channel-type.opengarage.opengarage-vehicle-status.state.option.0 = No vehicle detected
|
||||||
|
|
|
@ -16,8 +16,13 @@
|
||||||
<channel id="status-rollershutter" typeId="opengarage-status-rollershutter"/>
|
<channel id="status-rollershutter" typeId="opengarage-status-rollershutter"/>
|
||||||
<channel id="vehicle" typeId="opengarage-vehicle"/>
|
<channel id="vehicle" typeId="opengarage-vehicle"/>
|
||||||
<channel id="vehicle-status" typeId="opengarage-vehicle-status"/>
|
<channel id="vehicle-status" typeId="opengarage-vehicle-status"/>
|
||||||
|
<channel id="status-text" typeId="opengarage-status-text"/>
|
||||||
</channels>
|
</channels>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<property name="thingTypeVersion">1</property>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<config-description>
|
<config-description>
|
||||||
<parameter name="hostname" type="text" required="true">
|
<parameter name="hostname" type="text" required="true">
|
||||||
<label>Hostname/IP Address</label>
|
<label>Hostname/IP Address</label>
|
||||||
|
@ -34,13 +39,38 @@
|
||||||
<context>password</context>
|
<context>password</context>
|
||||||
<default>opendoor</default>
|
<default>opendoor</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="refresh" type="integer">
|
<parameter name="refresh" type="integer" unit="s">
|
||||||
<label>Refresh Interval</label>
|
<label>Refresh Interval</label>
|
||||||
<description>Specifies the refresh interval in seconds.</description>
|
<description>Specifies the refresh interval in seconds.</description>
|
||||||
<default>60</default>
|
<default>60</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="doorTransitionTimeSeconds" type="integer" unit="s">
|
||||||
|
<label>Door Transition Time</label>
|
||||||
|
<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.</description>
|
||||||
|
<default>17</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="doorOpeningState" type="text">
|
||||||
|
<label>Door Opening State</label>
|
||||||
|
<description>Text state to report when garage is opening</description>
|
||||||
|
<default>OPENING</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="doorOpenState" type="text">
|
||||||
|
<label>Door Open State</label>
|
||||||
|
<description>Text state to report when garage is open (and not in transition)</description>
|
||||||
|
<default>OPEN</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="doorClosingState" type="text">
|
||||||
|
<label>Door Closing State</label>
|
||||||
|
<description>Text state to report when garage is closing</description>
|
||||||
|
<default>CLOSING</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="doorClosedState" type="text">
|
||||||
|
<label>Door Closed State</label>
|
||||||
|
<description>Text state to report when garage is closed (and not in transition)</description>
|
||||||
|
<default>CLOSED</default>
|
||||||
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
|
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<channel-type id="opengarage-distance">
|
<channel-type id="opengarage-distance">
|
||||||
|
@ -113,4 +143,11 @@
|
||||||
</options>
|
</options>
|
||||||
</state>
|
</state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
<channel-type id="opengarage-status-text">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Text status</label>
|
||||||
|
<description>Text status of the current door state, including transition, using values from configuration:
|
||||||
|
doorOpeningState, doorOpenState, doorClosingState, doorClosedState.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||||
|
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
|
||||||
|
|
||||||
|
<thing-type uid="opengarage:opengarage">
|
||||||
|
|
||||||
|
<instruction-set targetVersion="1">
|
||||||
|
<add-channel id="status-text">
|
||||||
|
<type>opengarage:opengarage-status-text</type>
|
||||||
|
</add-channel>
|
||||||
|
</instruction-set>
|
||||||
|
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
</update:update-descriptions>
|
Loading…
Reference in New Issue