* 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>
| 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
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"}
```
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");
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;
/**
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) {
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)) {
--- /dev/null
+/**
+ * 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;
+}
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;
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) {
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));
}
}
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;
*/
@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);
@Override
public void dispose() {
+ id = "";
+ gateStatus = Optional.empty();
+ t4Allowed = List.of();
getBridgeHandler().ifPresent(h -> h.unregisterDataListener(this));
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
+ String channelId = channelUID.getId();
+
if (command instanceof RefreshType) {
- return;
+ 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 {
+ 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 {
- handleCommand(channelUID.getId(), command.toString());
+ logger.warn("Unable to handle command {} on channel {}", command, channelId);
}
}
- 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 -> {
- try {
- T4Command t4 = T4Command.fromCode(command);
- handler.sendCommand(id, t4);
- } catch (IllegalArgumentException e) {
- logger.warn("{} is not a valid T4 command", command);
- }
- });
+ private void handleStopMove(DoorStatus status, StopMoveType stopMoveCommand) {
+ if (stopMoveCommand == StopMoveType.STOP) {
+ if (status == DoorStatus.STOPPED) {
+ logger.info("The gate is already stopped.");
} else {
- logger.warn("This thing does not accept the T4 command '{}'", command);
+ 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);
}
}
public void onDataFetched(List<Device> devices) {
devices.stream().filter(d -> id.equals(d.id)).findFirst().map(device -> {
updateStatus(ThingStatus.ONLINE);
- 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 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()) {
+ 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.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;
});
*/
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);
- setDaemon(true);
- } catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
- throw new IllegalArgumentException(e);
- }
+ this.in = new InputStreamReader(sslSocket.getInputStream());
+ this.out = new OutputStreamWriter(sslSocket.getOutputStream());
+ setDaemon(true);
}
@Override
public void run() {
String buffer = "";
- try {
- connect();
- while (!interrupted()) {
- int data;
+ int data;
+
+ while (!interrupted()) {
+ try {
while ((data = in.read()) != -1) {
if (data == STX) {
buffer = "";
buffer += (char) data;
}
}
+ } catch (IOException e) {
+ handler.communicationError(e.toString());
+ interrupt();
}
- handler.connectorInterrupted("IT4WifiConnector interrupted");
- dispose();
- } catch (IOException e) {
- handler.connectorInterrupted(e.getMessage());
}
}
+ @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);
try {
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) {
- 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();
+ private void tryClose(Closeable closeable) {
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();
- }
}
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;
*/
@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
public void initialize() {
if (getConfigAs(It4WifiConfiguration.class).username.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-username");
- } else {
- updateStatus(ThingStatus.UNKNOWN);
- scheduler.execute(() -> startConnector());
+ 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);
- localConnector.start();
- connector = localConnector;
+ 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();
+
+ 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) {
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());
}
sendCommand(CommandType.VERIFY);
return;
case VERIFY:
- if (keepAliveJob != null) { // means we are connected
- return;
- }
- switch (response.authentication.perm) {
- case admin:
- case user:
- sendCommand(CommandType.CONNECT);
- return;
- case wait:
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
- "@text/conf-pending-validation");
- scheduler.schedule(() -> handShaked(), 15, TimeUnit.SECONDS);
- return;
- default:
- return;
+ if (keepAliveJob.isEmpty()) { // means we are not connected
+ switch (response.authentication.perm) {
+ case admin, user:
+ sendCommand(CommandType.CONNECT);
+ return;
+ case wait:
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+ "@text/conf-pending-validation");
+ scheduler.schedule(() -> handShaked(), 15, TimeUnit.SECONDS);
+ 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;
}
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) {
sendCommand(reqBuilder.buildMessage(id, t4));
}
- public void connectorInterrupted(@Nullable String message) {
- 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;
+ 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");
+ }
}
}
}
*/
@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());
+ }
}
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;
}
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();
}
}
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
<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>
<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>
<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">
<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>
<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>
<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>
--- /dev/null
+<?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>