[MyNice] Addition of Courtesy Light Channel (#14797)
* Solving activation / deactivation of IT4Wifi thing glitches. * Adding Courtesy light Added command capability of Stop / Move * Changed misplaced handling of RefreshType --------- Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
parent
80eeba48ce
commit
41e4cc4545
|
@ -46,14 +46,18 @@ Channels available for the gates are :
|
|||
|
||||
| Channel | Type | Read/Write | Description |
|
||||
|-----------|--------|------------|----------------------------------------------------------|
|
||||
| status | String | R | Description of the current status of the door (1) |
|
||||
| status | String | R/W (1) | Description of the current status of the door (2) |
|
||||
| obstruct | Switch | R | Flags an obstruction, blocking the door |
|
||||
| moving | Switch | R | Indicates if the device is currently operating a command |
|
||||
| command | String | W | Send a given command to the gate (2) |
|
||||
| command | String | W | Send a given command to the gate (3) |
|
||||
| t4command | String | W | Send a T4 Command to the gate |
|
||||
| courtesy | Switch | R/W | Status of the courtesy light (4) |
|
||||
|
||||
(1) : can be open, closed, opening, closing, stopped.
|
||||
(2) : must be "stop","open","close"
|
||||
(1) : Accepted commands are : STOP, MOVE
|
||||
(2) : Valid status are : OPEN, CLOSED, OPENING, CLOSING, STOPPED
|
||||
(3) : Accepted commands are : "stop","open","close"
|
||||
(4) : There is no way to retrieve the current status of the courtesy light. It is supposed to be ON when the gate is moving and turned OFF once done.
|
||||
The delay between the moving end and light being turned off is a configuration parameter of the `courtesy` channel.
|
||||
|
||||
### T4 Commands
|
||||
|
||||
|
@ -110,5 +114,6 @@ String NiceIT4WIFI_GateStatus "Gate Status" <gate> (gMyniceSwing) ["Statu
|
|||
String NiceIT4WIFI_Obstruction "Obstruction" <none> (gMyniceSwing) {channel="mynice:swing:83eef09166:1:obstruct"}
|
||||
Switch NiceIT4WIFI_Moving "Moving" <motion> (gMyniceSwing) ["Status","Vibration"] {channel="mynice:swing:83eef09166:1:moving"}
|
||||
String NiceIT4WIFI_Command "Command" <none> (gMyniceSwing) {channel="mynice:swing:83eef09166:1:command"}
|
||||
Switch NiceIT4WIFI_Command "Courtesy Light" <light> (gMyniceSwing) {channel="mynice:swing:83eef09166:1:courtesy"}
|
||||
|
||||
```
|
||||
|
|
|
@ -25,11 +25,12 @@ public class MyNiceBindingConstants {
|
|||
private static final String BINDING_ID = "mynice";
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String DOOR_STATUS = "status";
|
||||
public static final String DOOR_OBSTRUCTED = "obstruct";
|
||||
public static final String DOOR_MOVING = "moving";
|
||||
public static final String DOOR_COMMAND = "command";
|
||||
public static final String DOOR_T4_COMMAND = "t4command";
|
||||
public static final String CHANNEL_STATUS = "status";
|
||||
public static final String CHANNEL_OBSTRUCTED = "obstruct";
|
||||
public static final String CHANNEL_MOVING = "moving";
|
||||
public static final String CHANNEL_COMMAND = "command";
|
||||
public static final String CHANNEL_T4_COMMAND = "t4command";
|
||||
public static final String CHANNEL_COURTESY = "courtesy";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID BRIDGE_TYPE_IT4WIFI = new ThingTypeUID(BINDING_ID, "it4wifi");
|
||||
|
|
|
@ -14,18 +14,26 @@ package org.openhab.binding.mynice.internal;
|
|||
|
||||
import static org.openhab.binding.mynice.internal.MyNiceBindingConstants.*;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mynice.internal.handler.GateHandler;
|
||||
import org.openhab.binding.mynice.internal.handler.It4WifiHandler;
|
||||
import org.openhab.core.io.net.http.TrustAllTrustManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
|
@ -38,6 +46,18 @@ import org.osgi.service.component.annotations.Component;
|
|||
public class MyNiceHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(BRIDGE_TYPE_IT4WIFI, THING_TYPE_SWING,
|
||||
THING_TYPE_SLIDING);
|
||||
private final SSLSocketFactory socketFactory;
|
||||
|
||||
@Activate
|
||||
public MyNiceHandlerFactory() {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(null, new TrustManager[] { TrustAllTrustManager.getInstance() }, null);
|
||||
socketFactory = sslContext.getSocketFactory();
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
|
@ -49,7 +69,7 @@ public class MyNiceHandlerFactory extends BaseThingHandlerFactory {
|
|||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (BRIDGE_TYPE_IT4WIFI.equals(thingTypeUID)) {
|
||||
return new It4WifiHandler((Bridge) thing);
|
||||
return new It4WifiHandler((Bridge) thing, socketFactory);
|
||||
} else if (THING_TYPE_SWING.equals(thingTypeUID)) {
|
||||
return new GateHandler(thing);
|
||||
} else if (THING_TYPE_SLIDING.equals(thingTypeUID)) {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2023 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.mynice.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link CourtesyConfiguration} class contains fields mapping courtesy channel configuration parameters.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CourtesyConfiguration {
|
||||
public int duration = 60;
|
||||
}
|
|
@ -15,6 +15,7 @@ package org.openhab.binding.mynice.internal.discovery;
|
|||
import static org.openhab.binding.mynice.internal.MyNiceBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
@ -45,49 +46,43 @@ public class MyNiceDiscoveryService extends AbstractDiscoveryService
|
|||
private static final int SEARCH_TIME = 5;
|
||||
private final Logger logger = LoggerFactory.getLogger(MyNiceDiscoveryService.class);
|
||||
|
||||
private @Nullable It4WifiHandler bridgeHandler;
|
||||
private Optional<It4WifiHandler> bridgeHandler = Optional.empty();
|
||||
|
||||
/**
|
||||
* Creates a MyNiceDiscoveryService with background discovery disabled.
|
||||
*/
|
||||
public MyNiceDiscoveryService() {
|
||||
super(Set.of(THING_TYPE_SWING), SEARCH_TIME, false);
|
||||
super(Set.of(THING_TYPE_SWING, THING_TYPE_SLIDING), SEARCH_TIME, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
if (handler instanceof It4WifiHandler it4Handler) {
|
||||
bridgeHandler = it4Handler;
|
||||
bridgeHandler = Optional.of(it4Handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
return bridgeHandler.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
super.activate(null);
|
||||
It4WifiHandler handler = bridgeHandler;
|
||||
if (handler != null) {
|
||||
handler.registerDataListener(this);
|
||||
}
|
||||
bridgeHandler.ifPresent(h -> h.registerDataListener(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
It4WifiHandler handler = bridgeHandler;
|
||||
if (handler != null) {
|
||||
handler.unregisterDataListener(this);
|
||||
}
|
||||
bridgeHandler.ifPresent(h -> h.unregisterDataListener(this));
|
||||
bridgeHandler = Optional.empty();
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataFetched(List<Device> devices) {
|
||||
It4WifiHandler handler = bridgeHandler;
|
||||
if (handler != null) {
|
||||
bridgeHandler.ifPresent(handler -> {
|
||||
ThingUID bridgeUID = handler.getThing().getUID();
|
||||
devices.stream().filter(device -> device.type != null).forEach(device -> {
|
||||
ThingUID thingUID = switch (device.type) {
|
||||
|
@ -105,14 +100,11 @@ public class MyNiceDiscoveryService extends AbstractDiscoveryService
|
|||
logger.info("`{}` type of device is not yet supported", device.type);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
It4WifiHandler handler = bridgeHandler;
|
||||
if (handler != null) {
|
||||
handler.sendCommand(CommandType.INFO);
|
||||
}
|
||||
bridgeHandler.ifPresent(h -> h.sendCommand(CommandType.INFO));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,20 @@ import static org.openhab.core.thing.Thing.*;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mynice.internal.config.CourtesyConfiguration;
|
||||
import org.openhab.binding.mynice.internal.xml.dto.CommandType;
|
||||
import org.openhab.binding.mynice.internal.xml.dto.Device;
|
||||
import org.openhab.binding.mynice.internal.xml.dto.Properties.DoorStatus;
|
||||
import org.openhab.binding.mynice.internal.xml.dto.Property;
|
||||
import org.openhab.binding.mynice.internal.xml.dto.T4Command;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
|
@ -42,12 +48,12 @@ import org.slf4j.LoggerFactory;
|
|||
*/
|
||||
@NonNullByDefault
|
||||
public class GateHandler extends BaseThingHandler implements MyNiceDataListener {
|
||||
private static final String OPENING = "opening";
|
||||
private static final String CLOSING = "closing";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GateHandler.class);
|
||||
|
||||
private String id = "";
|
||||
private Optional<DoorStatus> gateStatus = Optional.empty();
|
||||
private List<T4Command> t4Allowed = List.of();
|
||||
|
||||
public GateHandler(Thing thing) {
|
||||
super(thing);
|
||||
|
@ -61,6 +67,9 @@ public class GateHandler extends BaseThingHandler implements MyNiceDataListener
|
|||
|
||||
@Override
|
||||
public void dispose() {
|
||||
id = "";
|
||||
gateStatus = Optional.empty();
|
||||
t4Allowed = List.of();
|
||||
getBridgeHandler().ifPresent(h -> h.unregisterDataListener(this));
|
||||
}
|
||||
|
||||
|
@ -77,51 +86,101 @@ public class GateHandler extends BaseThingHandler implements MyNiceDataListener
|
|||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
return;
|
||||
} else {
|
||||
handleCommand(channelUID.getId(), command.toString());
|
||||
}
|
||||
}
|
||||
String channelId = channelUID.getId();
|
||||
|
||||
private void handleCommand(String channelId, String command) {
|
||||
if (DOOR_COMMAND.equals(channelId)) {
|
||||
getBridgeHandler().ifPresent(handler -> handler.sendCommand(id, command));
|
||||
} else if (DOOR_T4_COMMAND.equals(channelId)) {
|
||||
String allowed = thing.getProperties().get(ALLOWED_T4);
|
||||
if (allowed != null && allowed.contains(command)) {
|
||||
getBridgeHandler().ifPresent(handler -> {
|
||||
if (command instanceof RefreshType) {
|
||||
getBridgeHandler().ifPresent(handler -> handler.sendCommand(CommandType.INFO));
|
||||
} else if (CHANNEL_COURTESY.equals(channelId) && command instanceof OnOffType) {
|
||||
handleT4Command(T4Command.MDEy);
|
||||
} else if (CHANNEL_STATUS.equals(channelId)) {
|
||||
gateStatus.ifPresentOrElse(status -> {
|
||||
if (command instanceof StopMoveType stopMoveCommand) {
|
||||
handleStopMove(status, stopMoveCommand);
|
||||
} else {
|
||||
try {
|
||||
T4Command t4 = T4Command.fromCode(command);
|
||||
handler.sendCommand(id, t4);
|
||||
handleStopMove(status, StopMoveType.valueOf(command.toString()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Invalid StopMoveType command received : {}", command);
|
||||
}
|
||||
}
|
||||
}, () -> logger.info("Current status of the gate unknown, can not send {} command", command));
|
||||
} else if (CHANNEL_COMMAND.equals(channelId)) {
|
||||
getBridgeHandler().ifPresent(handler -> handler.sendCommand(id, command.toString()));
|
||||
} else if (CHANNEL_T4_COMMAND.equals(channelId)) {
|
||||
try {
|
||||
T4Command t4 = T4Command.fromCode(command.toString());
|
||||
handleT4Command(t4);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("{} is not a valid T4 command", command);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logger.warn("This thing does not accept the T4 command '{}'", command);
|
||||
logger.warn("Unable to handle command {} on channel {}", command, channelId);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStopMove(DoorStatus status, StopMoveType stopMoveCommand) {
|
||||
if (stopMoveCommand == StopMoveType.STOP) {
|
||||
if (status == DoorStatus.STOPPED) {
|
||||
logger.info("The gate is already stopped.");
|
||||
} else {
|
||||
handleT4Command(T4Command.MDAy);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// It's a move Command
|
||||
if (status == DoorStatus.OPEN) {
|
||||
handleT4Command(T4Command.MDA0);
|
||||
} else if (status == DoorStatus.CLOSED) {
|
||||
handleT4Command(T4Command.MDAz);
|
||||
} else if (status.moving) {
|
||||
logger.info("The gate is already currently moving.");
|
||||
} else { // it is closed
|
||||
handleT4Command(T4Command.MDAx);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleT4Command(T4Command t4Command) {
|
||||
if (t4Allowed.contains(t4Command)) {
|
||||
getBridgeHandler().ifPresent(handler -> handler.sendCommand(id, t4Command));
|
||||
} else {
|
||||
logger.warn("This gate does not accept the T4 command '{}'", t4Command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataFetched(List<Device> devices) {
|
||||
devices.stream().filter(d -> id.equals(d.id)).findFirst().map(device -> {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
Property t4list = device.properties.t4allowed;
|
||||
if (t4Allowed.isEmpty() && t4list != null) {
|
||||
int value = Integer.parseInt(t4list.values, 16);
|
||||
t4Allowed = T4Command.fromBitmask(value).stream().toList();
|
||||
if (thing.getProperties().isEmpty()) {
|
||||
int value = Integer.parseInt(device.properties.t4allowed.values, 16);
|
||||
List<String> t4Allowed = T4Command.fromBitmask(value).stream().map(Enum::name).toList();
|
||||
updateProperties(Map.of(PROPERTY_VENDOR, device.manuf, PROPERTY_MODEL_ID, device.prod,
|
||||
PROPERTY_SERIAL_NUMBER, device.serialNr, PROPERTY_HARDWARE_VERSION, device.versionHW,
|
||||
PROPERTY_FIRMWARE_VERSION, device.versionFW, ALLOWED_T4, String.join(",", t4Allowed)));
|
||||
PROPERTY_FIRMWARE_VERSION, device.versionFW, ALLOWED_T4,
|
||||
String.join(",", t4Allowed.stream().map(Enum::name).toList())));
|
||||
}
|
||||
}
|
||||
if (device.prod != null) {
|
||||
getBridgeHandler().ifPresent(h -> h.sendCommand(CommandType.STATUS));
|
||||
} else {
|
||||
String status = device.properties.doorStatus;
|
||||
updateState(DOOR_STATUS, new StringType(status));
|
||||
updateState(DOOR_OBSTRUCTED, OnOffType.from("1".equals(device.properties.obstruct)));
|
||||
updateState(DOOR_MOVING, OnOffType.from(status.equals(CLOSING) || status.equals(OPENING)));
|
||||
DoorStatus status = device.properties.status();
|
||||
|
||||
updateState(CHANNEL_STATUS, new StringType(status.name()));
|
||||
updateState(CHANNEL_OBSTRUCTED, OnOffType.from(device.properties.obstructed()));
|
||||
updateState(CHANNEL_MOVING, OnOffType.from(status.moving));
|
||||
if (status.moving && isLinked(CHANNEL_COURTESY)) {
|
||||
Channel courtesy = getThing().getChannel(CHANNEL_COURTESY);
|
||||
if (courtesy != null) {
|
||||
updateState(CHANNEL_COURTESY, OnOffType.ON);
|
||||
CourtesyConfiguration config = courtesy.getConfiguration().as(CourtesyConfiguration.class);
|
||||
scheduler.schedule(() -> updateState(CHANNEL_COURTESY, OnOffType.OFF), config.duration,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
gateStatus = Optional.of(status);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
|
|
@ -12,59 +12,47 @@
|
|||
*/
|
||||
package org.openhab.binding.mynice.internal.handler;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.TrustManager;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.net.http.TrustAllTrustManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link It4WifiConnector} is responsible for connecting reading, writing and disconnecting from the It4Wifi.
|
||||
* The {@link It4WifiConnector} is responsible for reading and writing to the It4Wifi.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class It4WifiConnector extends Thread {
|
||||
private static final int SERVER_PORT = 443;
|
||||
private static final char ETX = '\u0003';
|
||||
private static final char STX = '\u0002';
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(It4WifiConnector.class);
|
||||
private final It4WifiHandler handler;
|
||||
private final SSLSocket sslsocket;
|
||||
private final InputStreamReader in;
|
||||
private final OutputStreamWriter out;
|
||||
|
||||
private @NonNullByDefault({}) InputStreamReader in;
|
||||
private @NonNullByDefault({}) OutputStreamWriter out;
|
||||
|
||||
public It4WifiConnector(String hostname, It4WifiHandler handler) {
|
||||
public It4WifiConnector(It4WifiHandler handler, SSLSocket sslSocket) throws IOException {
|
||||
super(It4WifiConnector.class.getName());
|
||||
this.handler = handler;
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(null, new TrustManager[] { TrustAllTrustManager.getInstance() }, null);
|
||||
sslsocket = (SSLSocket) sslContext.getSocketFactory().createSocket(hostname, SERVER_PORT);
|
||||
this.in = new InputStreamReader(sslSocket.getInputStream());
|
||||
this.out = new OutputStreamWriter(sslSocket.getOutputStream());
|
||||
setDaemon(true);
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String buffer = "";
|
||||
try {
|
||||
connect();
|
||||
while (!interrupted()) {
|
||||
int data;
|
||||
|
||||
while (!interrupted()) {
|
||||
try {
|
||||
while ((data = in.read()) != -1) {
|
||||
if (data == STX) {
|
||||
buffer = "";
|
||||
|
@ -74,13 +62,21 @@ public class It4WifiConnector extends Thread {
|
|||
buffer += (char) data;
|
||||
}
|
||||
}
|
||||
}
|
||||
handler.connectorInterrupted("IT4WifiConnector interrupted");
|
||||
dispose();
|
||||
} catch (IOException e) {
|
||||
handler.connectorInterrupted(e.getMessage());
|
||||
handler.communicationError(e.toString());
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
logger.debug("Closing streams");
|
||||
tryClose(in);
|
||||
tryClose(out);
|
||||
|
||||
super.interrupt();
|
||||
}
|
||||
|
||||
public synchronized void sendCommand(String command) {
|
||||
logger.debug("Sending ItT4Wifi :{}", command);
|
||||
|
@ -88,54 +84,15 @@ public class It4WifiConnector extends Thread {
|
|||
out.write(STX + command + ETX);
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
handler.connectorInterrupted(e.getMessage());
|
||||
handler.communicationError(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
logger.debug("Disconnecting");
|
||||
|
||||
if (in != null) {
|
||||
private void tryClose(Closeable closeable) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
in = null;
|
||||
out = null;
|
||||
|
||||
logger.debug("Disconnected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the device thread
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void dispose() {
|
||||
interrupt();
|
||||
disconnect();
|
||||
try {
|
||||
sslsocket.close();
|
||||
closeable.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error closing sslsocket : {}", e.getMessage());
|
||||
logger.debug("Exception closing stream : {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void connect() throws IOException {
|
||||
disconnect();
|
||||
logger.debug("Initiating connection to IT4Wifi on port {}...", SERVER_PORT);
|
||||
|
||||
sslsocket.startHandshake();
|
||||
in = new InputStreamReader(sslsocket.getInputStream());
|
||||
out = new OutputStreamWriter(sslsocket.getOutputStream());
|
||||
handler.handShaked();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,17 +15,22 @@ package org.openhab.binding.mynice.internal.handler;
|
|||
import static org.openhab.core.thing.Thing.*;
|
||||
import static org.openhab.core.types.RefreshType.REFRESH;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mynice.internal.config.It4WifiConfiguration;
|
||||
import org.openhab.binding.mynice.internal.discovery.MyNiceDiscoveryService;
|
||||
import org.openhab.binding.mynice.internal.xml.MyNiceXStream;
|
||||
|
@ -54,21 +59,25 @@ import org.slf4j.LoggerFactory;
|
|||
*/
|
||||
@NonNullByDefault
|
||||
public class It4WifiHandler extends BaseBridgeHandler {
|
||||
private static final int SERVER_PORT = 443;
|
||||
private static final int MAX_HANDSHAKE_ATTEMPTS = 3;
|
||||
private static final int KEEPALIVE_DELAY_S = 235; // Timeout seems to be at 6 min
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(It4WifiHandler.class);
|
||||
private final List<MyNiceDataListener> dataListeners = new CopyOnWriteArrayList<>();
|
||||
private final MyNiceXStream xstream = new MyNiceXStream();
|
||||
private final SSLSocketFactory socketFactory;
|
||||
|
||||
private @NonNullByDefault({}) RequestBuilder reqBuilder;
|
||||
private @Nullable It4WifiConnector connector;
|
||||
private @Nullable ScheduledFuture<?> keepAliveJob;
|
||||
private List<Device> devices = new ArrayList<>();
|
||||
private int handshakeAttempts = 0;
|
||||
private Optional<ScheduledFuture<?>> keepAliveJob = Optional.empty();
|
||||
private Optional<It4WifiConnector> connector = Optional.empty();
|
||||
private Optional<SSLSocket> sslSocket = Optional.empty();
|
||||
|
||||
public It4WifiHandler(Bridge thing) {
|
||||
public It4WifiHandler(Bridge thing, SSLSocketFactory socketFactory) {
|
||||
super(thing);
|
||||
this.socketFactory = socketFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -96,36 +105,57 @@ public class It4WifiHandler extends BaseBridgeHandler {
|
|||
public void initialize() {
|
||||
if (getConfigAs(It4WifiConfiguration.class).username.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-username");
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
scheduler.execute(() -> startConnector());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
It4WifiConnector localConnector = connector;
|
||||
if (localConnector != null) {
|
||||
localConnector.dispose();
|
||||
}
|
||||
dataListeners.clear();
|
||||
|
||||
freeKeepAlive();
|
||||
|
||||
sslSocket.ifPresent(socket -> {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error closing sslsocket : {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
sslSocket = Optional.empty();
|
||||
|
||||
connector.ifPresent(c -> scheduler.execute(() -> c.interrupt()));
|
||||
connector = Optional.empty();
|
||||
}
|
||||
|
||||
private void startConnector() {
|
||||
It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
|
||||
freeKeepAlive();
|
||||
reqBuilder = new RequestBuilder(config.macAddress, config.username);
|
||||
It4WifiConnector localConnector = new It4WifiConnector(config.hostname, this);
|
||||
try {
|
||||
logger.debug("Initiating connection to IT4Wifi {} on port {}...", config.hostname, SERVER_PORT);
|
||||
|
||||
SSLSocket localSocket = (SSLSocket) socketFactory.createSocket(config.hostname, SERVER_PORT);
|
||||
sslSocket = Optional.of(localSocket);
|
||||
localSocket.startHandshake();
|
||||
|
||||
It4WifiConnector localConnector = new It4WifiConnector(this, localSocket);
|
||||
connector = Optional.of(localConnector);
|
||||
localConnector.start();
|
||||
connector = localConnector;
|
||||
|
||||
reqBuilder = new RequestBuilder(config.macAddress, config.username);
|
||||
handShaked();
|
||||
} catch (UnknownHostException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-hostname");
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error-handshake-init");
|
||||
}
|
||||
}
|
||||
|
||||
private void freeKeepAlive() {
|
||||
ScheduledFuture<?> keepAlive = keepAliveJob;
|
||||
if (keepAlive != null) {
|
||||
keepAlive.cancel(true);
|
||||
}
|
||||
keepAliveJob = null;
|
||||
keepAliveJob.ifPresent(job -> job.cancel(true));
|
||||
keepAliveJob = Optional.empty();
|
||||
}
|
||||
|
||||
public void received(String command) {
|
||||
|
@ -134,8 +164,8 @@ public class It4WifiHandler extends BaseBridgeHandler {
|
|||
if (event.error != null) {
|
||||
logger.warn("Error code {} received : {}", event.error.code, event.error.info);
|
||||
} else {
|
||||
if (event instanceof Response) {
|
||||
handleResponse((Response) event);
|
||||
if (event instanceof Response responseEvent) {
|
||||
handleResponse(responseEvent);
|
||||
} else {
|
||||
notifyListeners(event.getDevices());
|
||||
}
|
||||
|
@ -152,12 +182,9 @@ public class It4WifiHandler extends BaseBridgeHandler {
|
|||
sendCommand(CommandType.VERIFY);
|
||||
return;
|
||||
case VERIFY:
|
||||
if (keepAliveJob != null) { // means we are connected
|
||||
return;
|
||||
}
|
||||
if (keepAliveJob.isEmpty()) { // means we are not connected
|
||||
switch (response.authentication.perm) {
|
||||
case admin:
|
||||
case user:
|
||||
case admin, user:
|
||||
sendCommand(CommandType.CONNECT);
|
||||
return;
|
||||
case wait:
|
||||
|
@ -165,27 +192,25 @@ public class It4WifiHandler extends BaseBridgeHandler {
|
|||
"@text/conf-pending-validation");
|
||||
scheduler.schedule(() -> handShaked(), 15, TimeUnit.SECONDS);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
case CONNECT:
|
||||
String sc = response.authentication.sc;
|
||||
It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
|
||||
if (sc != null) {
|
||||
It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
|
||||
reqBuilder.setChallenges(sc, response.authentication.id, config.password);
|
||||
keepAliveJob = scheduler.scheduleWithFixedDelay(() -> sendCommand(CommandType.VERIFY),
|
||||
KEEPALIVE_DELAY_S, KEEPALIVE_DELAY_S, TimeUnit.SECONDS);
|
||||
keepAliveJob = Optional.of(scheduler.scheduleWithFixedDelay(() -> sendCommand(CommandType.VERIFY),
|
||||
KEEPALIVE_DELAY_S, KEEPALIVE_DELAY_S, TimeUnit.SECONDS));
|
||||
sendCommand(CommandType.INFO);
|
||||
}
|
||||
return;
|
||||
case INFO:
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
if (thing.getProperties().isEmpty()) {
|
||||
Map<String, String> properties = Map.of(PROPERTY_VENDOR, response.intf.manuf, PROPERTY_MODEL_ID,
|
||||
response.intf.prod, PROPERTY_SERIAL_NUMBER, response.intf.serialNr,
|
||||
PROPERTY_HARDWARE_VERSION, response.intf.versionHW, PROPERTY_FIRMWARE_VERSION,
|
||||
response.intf.versionFW);
|
||||
updateProperties(properties);
|
||||
updateProperties(Map.of(PROPERTY_VENDOR, response.intf.manuf, PROPERTY_MODEL_ID, response.intf.prod,
|
||||
PROPERTY_SERIAL_NUMBER, response.intf.serialNr, PROPERTY_HARDWARE_VERSION,
|
||||
response.intf.versionHW, PROPERTY_FIRMWARE_VERSION, response.intf.versionFW));
|
||||
}
|
||||
notifyListeners(response.getDevices());
|
||||
return;
|
||||
|
@ -212,12 +237,8 @@ public class It4WifiHandler extends BaseBridgeHandler {
|
|||
}
|
||||
|
||||
private void sendCommand(String command) {
|
||||
It4WifiConnector localConnector = connector;
|
||||
if (localConnector != null) {
|
||||
localConnector.sendCommand(command);
|
||||
} else {
|
||||
logger.warn("Tried to send a command when IT4WifiConnector is not initialized.");
|
||||
}
|
||||
connector.ifPresentOrElse(c -> c.sendCommand(command),
|
||||
() -> logger.warn("Tried to send a command when IT4WifiConnector is not initialized."));
|
||||
}
|
||||
|
||||
public void sendCommand(CommandType command) {
|
||||
|
@ -232,13 +253,16 @@ public class It4WifiHandler extends BaseBridgeHandler {
|
|||
sendCommand(reqBuilder.buildMessage(id, t4));
|
||||
}
|
||||
|
||||
public void connectorInterrupted(@Nullable String message) {
|
||||
public void communicationError(String message) {
|
||||
// avoid a status update that would generates a WARN while we're already disconnecting
|
||||
if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
|
||||
dispose();
|
||||
if (handshakeAttempts++ <= MAX_HANDSHAKE_ATTEMPTS) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
|
||||
startConnector();
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error-handshake-limit");
|
||||
connector = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,10 +20,32 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
|||
*/
|
||||
@XStreamAlias("Properties")
|
||||
public class Properties {
|
||||
public static enum DoorStatus {
|
||||
OPEN(false),
|
||||
CLOSED(false),
|
||||
OPENING(true),
|
||||
CLOSING(true),
|
||||
STOPPED(false);
|
||||
|
||||
public final boolean moving;
|
||||
|
||||
DoorStatus(boolean moving) {
|
||||
this.moving = moving;
|
||||
}
|
||||
}
|
||||
|
||||
@XStreamAlias("DoorStatus")
|
||||
public String doorStatus;
|
||||
private String doorStatus;
|
||||
@XStreamAlias("Obstruct")
|
||||
public String obstruct;
|
||||
private String obstruct;
|
||||
@XStreamAlias("T4_allowed")
|
||||
public Property t4allowed;
|
||||
|
||||
public boolean obstructed() {
|
||||
return "1".equals(obstruct);
|
||||
}
|
||||
|
||||
public DoorStatus status() {
|
||||
return DoorStatus.valueOf(doorStatus.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
package org.openhab.binding.mynice.internal.xml.dto;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
@ -61,7 +60,6 @@ public enum T4Command {
|
|||
}
|
||||
|
||||
public static List<T4Command> fromBitmask(int bitmask) {
|
||||
return Stream.of(T4Command.values()).filter(command -> ((1 << command.bitPosition) & bitmask) != 0)
|
||||
.collect(Collectors.toList());
|
||||
return Stream.of(T4Command.values()).filter(command -> ((1 << command.bitPosition) & bitmask) != 0).toList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,48 +31,59 @@ thing-type.config.mynice.swing.id.description = ID of the gate on the TP4 bus co
|
|||
|
||||
channel-type.mynice.command.label = Command
|
||||
channel-type.mynice.command.description = Send a given command to the gate
|
||||
channel-type.mynice.command.state.option.stop = Stop
|
||||
channel-type.mynice.command.state.option.open = Open
|
||||
channel-type.mynice.command.state.option.close = Close
|
||||
channel-type.mynice.command.command.option.stop = Stop
|
||||
channel-type.mynice.command.command.option.open = Open
|
||||
channel-type.mynice.command.command.option.close = Close
|
||||
channel-type.mynice.courtesy.label = Courtesy Light
|
||||
channel-type.mynice.courtesy.description = Courtesy Light illuminates the area around your gates.
|
||||
channel-type.mynice.doorstatus.label = Gate Status
|
||||
channel-type.mynice.doorstatus.description = Position of the gate or state if moving
|
||||
channel-type.mynice.doorstatus.state.option.open = Open
|
||||
channel-type.mynice.doorstatus.state.option.closed = Closed
|
||||
channel-type.mynice.doorstatus.state.option.opening = Opening
|
||||
channel-type.mynice.doorstatus.state.option.closing = Closing
|
||||
channel-type.mynice.doorstatus.state.option.stopped = Stopped
|
||||
channel-type.mynice.doorstatus.state.option.OPEN = Open
|
||||
channel-type.mynice.doorstatus.state.option.CLOSED = Closed
|
||||
channel-type.mynice.doorstatus.state.option.OPENING = Opening
|
||||
channel-type.mynice.doorstatus.state.option.CLOSING = Closing
|
||||
channel-type.mynice.doorstatus.state.option.STOPPED = Stopped
|
||||
channel-type.mynice.doorstatus.command.option.STOP = Stop
|
||||
channel-type.mynice.doorstatus.command.option.MOVE = Move
|
||||
channel-type.mynice.moving.label = Moving
|
||||
channel-type.mynice.moving.description = Indicates if the device is currently operating a command
|
||||
channel-type.mynice.obstruct.label = Obstruction
|
||||
channel-type.mynice.obstruct.description = Something prevented normal operation of the gate by crossing the infra-red barrier
|
||||
channel-type.mynice.t4command.label = T4 Command
|
||||
channel-type.mynice.t4command.description = Send a T4 Command to the gate
|
||||
channel-type.mynice.t4command.state.option.MDAx = Step by Step
|
||||
channel-type.mynice.t4command.state.option.MDAy = Stop (as remote control)
|
||||
channel-type.mynice.t4command.state.option.MDAz = Open (as remote control)
|
||||
channel-type.mynice.t4command.state.option.MDA0 = Close (as remote control)
|
||||
channel-type.mynice.t4command.state.option.MDA1 = Partial opening 1
|
||||
channel-type.mynice.t4command.state.option.MDA2 = Partial opening 2
|
||||
channel-type.mynice.t4command.state.option.MDA3 = Partial opening 3
|
||||
channel-type.mynice.t4command.state.option.MDBi = Apartment Step by Step
|
||||
channel-type.mynice.t4command.state.option.MDBj = Step by Step high priority
|
||||
channel-type.mynice.t4command.state.option.MDBk = Open and block
|
||||
channel-type.mynice.t4command.state.option.MDBl = Close and block
|
||||
channel-type.mynice.t4command.state.option.MDBm = Block
|
||||
channel-type.mynice.t4command.state.option.MDEw = Release
|
||||
channel-type.mynice.t4command.state.option.MDEx = Courtesy light timer on
|
||||
channel-type.mynice.t4command.state.option.MDEy = Courtesy light on-off
|
||||
channel-type.mynice.t4command.state.option.MDEz = Step by Step master door
|
||||
channel-type.mynice.t4command.state.option.MDE0 = Open master door
|
||||
channel-type.mynice.t4command.state.option.MDE1 = Close master door
|
||||
channel-type.mynice.t4command.state.option.MDE2 = Step by Step slave door
|
||||
channel-type.mynice.t4command.state.option.MDE3 = Open slave door
|
||||
channel-type.mynice.t4command.state.option.MDE4 = Close slave door
|
||||
channel-type.mynice.t4command.state.option.MDE5 = Release and Open
|
||||
channel-type.mynice.t4command.state.option.MDFh = Release and Close
|
||||
channel-type.mynice.t4command.command.option.MDAx = Step by Step
|
||||
channel-type.mynice.t4command.command.option.MDAy = Stop (as remote control)
|
||||
channel-type.mynice.t4command.command.option.MDAz = Open (as remote control)
|
||||
channel-type.mynice.t4command.command.option.MDA0 = Close (as remote control)
|
||||
channel-type.mynice.t4command.command.option.MDA1 = Partial opening 1
|
||||
channel-type.mynice.t4command.command.option.MDA2 = Partial opening 2
|
||||
channel-type.mynice.t4command.command.option.MDA3 = Partial opening 3
|
||||
channel-type.mynice.t4command.command.option.MDBi = Apartment Step by Step
|
||||
channel-type.mynice.t4command.command.option.MDBj = Step by Step high priority
|
||||
channel-type.mynice.t4command.command.option.MDBk = Open and block
|
||||
channel-type.mynice.t4command.command.option.MDBl = Close and block
|
||||
channel-type.mynice.t4command.command.option.MDBm = Block
|
||||
channel-type.mynice.t4command.command.option.MDEw = Release
|
||||
channel-type.mynice.t4command.command.option.MDEx = Courtesy light timer on
|
||||
channel-type.mynice.t4command.command.option.MDEy = Courtesy light on-off
|
||||
channel-type.mynice.t4command.command.option.MDEz = Step by Step master door
|
||||
channel-type.mynice.t4command.command.option.MDE0 = Open master door
|
||||
channel-type.mynice.t4command.command.option.MDE1 = Close master door
|
||||
channel-type.mynice.t4command.command.option.MDE2 = Step by Step slave door
|
||||
channel-type.mynice.t4command.command.option.MDE3 = Open slave door
|
||||
channel-type.mynice.t4command.command.option.MDE4 = Close slave door
|
||||
channel-type.mynice.t4command.command.option.MDE5 = Release and Open
|
||||
channel-type.mynice.t4command.command.option.MDFh = Release and Close
|
||||
|
||||
# channel types config
|
||||
|
||||
channel-type.config.mynice.courtesy.duration.label = Duration
|
||||
channel-type.config.mynice.courtesy.duration.description = Duration the lamp stays on
|
||||
|
||||
# error messages
|
||||
|
||||
conf-error-no-username = Please define a username for this thing
|
||||
conf-pending-validation = Please validate the user on the MyNice application
|
||||
conf-error-hostname = Unable to reach the configured hostname
|
||||
error-handshake-limit = Maximum handshake attempts reached
|
||||
error-handshake-init = Error initializing communication with IT4Wifi
|
||||
|
|
|
@ -47,8 +47,13 @@
|
|||
<channel id="moving" typeId="moving"/>
|
||||
<channel id="command" typeId="command"/>
|
||||
<channel id="t4command" typeId="t4command"/>
|
||||
<channel id="courtesy" typeId="courtesy"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="thingTypeVersion">1</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description>
|
||||
|
@ -73,8 +78,13 @@
|
|||
<channel id="moving" typeId="moving"/>
|
||||
<channel id="command" typeId="command"/>
|
||||
<channel id="t4command" typeId="t4command"/>
|
||||
<channel id="courtesy" typeId="courtesy"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="thingTypeVersion">1</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description>
|
||||
|
@ -90,15 +100,23 @@
|
|||
<item-type>String</item-type>
|
||||
<label>Gate Status</label>
|
||||
<description>Position of the gate or state if moving</description>
|
||||
<state readOnly="true">
|
||||
<category>door</category>
|
||||
<state>
|
||||
<options>
|
||||
<option value="open">Open</option>
|
||||
<option value="closed">Closed</option>
|
||||
<option value="opening">Opening</option>
|
||||
<option value="closing">Closing</option>
|
||||
<option value="stopped">Stopped</option>
|
||||
<option value="OPEN">Open</option>
|
||||
<option value="CLOSED">Closed</option>
|
||||
<option value="OPENING">Opening</option>
|
||||
<option value="CLOSING">Closing</option>
|
||||
<option value="STOPPED">Stopped</option>
|
||||
</options>
|
||||
</state>
|
||||
<command>
|
||||
<options>
|
||||
<option value="STOP">Stop</option>
|
||||
<option value="MOVE">Move</option>
|
||||
</options>
|
||||
</command>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="moving">
|
||||
|
@ -115,17 +133,17 @@
|
|||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="command">
|
||||
<channel-type id="command" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Command</label>
|
||||
<description>Send a given command to the gate</description>
|
||||
<state readOnly="false">
|
||||
<command>
|
||||
<options>
|
||||
<option value="stop">Stop</option>
|
||||
<option value="open">Open</option>
|
||||
<option value="close">Close</option>
|
||||
</options>
|
||||
</state>
|
||||
</command>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
|
||||
|
@ -133,7 +151,7 @@
|
|||
<item-type>String</item-type>
|
||||
<label>T4 Command</label>
|
||||
<description>Send a T4 Command to the gate</description>
|
||||
<state readOnly="false">
|
||||
<command>
|
||||
<options>
|
||||
<option value="MDAx">Step by Step</option>
|
||||
<option value="MDAy">Stop (as remote control)</option>
|
||||
|
@ -159,8 +177,22 @@
|
|||
<option value="MDE5">Release and Open</option>
|
||||
<option value="MDFh">Release and Close</option>
|
||||
</options>
|
||||
</state>
|
||||
</command>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="courtesy">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Courtesy Light</label>
|
||||
<description>Courtesy Light illuminates the area around your gates.</description>
|
||||
<category>lightbulb</category>
|
||||
<config-description>
|
||||
<parameter name="duration" type="integer" min="0" unit="s" step="1">
|
||||
<label>Duration</label>
|
||||
<description>Duration the lamp stays on</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?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="mynice:swing">
|
||||
|
||||
<instruction-set targetVersion="1">
|
||||
<add-channel id="courtesy">
|
||||
<type>mynice:courtesy</type>
|
||||
</add-channel>
|
||||
</instruction-set>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type uid="mynice:sliding">
|
||||
|
||||
<instruction-set targetVersion="1">
|
||||
<add-channel id="courtesy">
|
||||
<type>mynice:courtesy</type>
|
||||
</add-channel>
|
||||
</instruction-set>
|
||||
|
||||
</thing-type>
|
||||
|
||||
</update:update-descriptions>
|
Loading…
Reference in New Issue