2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mynice.internal.handler;
15 import static org.openhab.core.thing.Thing.*;
16 import static org.openhab.core.types.RefreshType.REFRESH;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.List;
23 import java.util.concurrent.CopyOnWriteArrayList;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.mynice.internal.config.It4WifiConfiguration;
30 import org.openhab.binding.mynice.internal.discovery.MyNiceDiscoveryService;
31 import org.openhab.binding.mynice.internal.xml.MyNiceXStream;
32 import org.openhab.binding.mynice.internal.xml.RequestBuilder;
33 import org.openhab.binding.mynice.internal.xml.dto.CommandType;
34 import org.openhab.binding.mynice.internal.xml.dto.Device;
35 import org.openhab.binding.mynice.internal.xml.dto.Event;
36 import org.openhab.binding.mynice.internal.xml.dto.Response;
37 import org.openhab.binding.mynice.internal.xml.dto.T4Command;
38 import org.openhab.core.config.core.Configuration;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.binding.BaseBridgeHandler;
44 import org.openhab.core.thing.binding.ThingHandlerService;
45 import org.openhab.core.types.Command;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * The {@link It4WifiHandler} is responsible for handling commands, which are
51 * sent to one of the channels.
53 * @author Gaƫl L'hopital - Initial contribution
56 public class It4WifiHandler extends BaseBridgeHandler {
57 private static final int MAX_HANDSHAKE_ATTEMPTS = 3;
58 private static final int KEEPALIVE_DELAY_S = 235; // Timeout seems to be at 6 min
60 private final Logger logger = LoggerFactory.getLogger(It4WifiHandler.class);
61 private final List<MyNiceDataListener> dataListeners = new CopyOnWriteArrayList<>();
62 private final MyNiceXStream xstream = new MyNiceXStream();
64 private @NonNullByDefault({}) RequestBuilder reqBuilder;
65 private @Nullable It4WifiConnector connector;
66 private @Nullable ScheduledFuture<?> keepAliveJob;
67 private List<Device> devices = new ArrayList<>();
68 private int handshakeAttempts = 0;
70 public It4WifiHandler(Bridge thing) {
75 public Collection<Class<? extends ThingHandlerService>> getServices() {
76 return Set.of(MyNiceDiscoveryService.class);
79 public void registerDataListener(MyNiceDataListener dataListener) {
80 dataListeners.add(dataListener);
81 notifyListeners(devices);
84 public void unregisterDataListener(MyNiceDataListener dataListener) {
85 dataListeners.remove(dataListener);
89 public void handleCommand(ChannelUID channelUID, Command command) {
90 if (REFRESH.equals(command)) {
91 sendCommand(CommandType.INFO);
96 public void initialize() {
97 if (getConfigAs(It4WifiConfiguration.class).username.isBlank()) {
98 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-username");
100 updateStatus(ThingStatus.UNKNOWN);
101 scheduler.execute(() -> startConnector());
106 public void dispose() {
107 It4WifiConnector localConnector = connector;
108 if (localConnector != null) {
109 localConnector.dispose();
114 private void startConnector() {
115 It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
117 reqBuilder = new RequestBuilder(config.macAddress, config.username);
118 It4WifiConnector localConnector = new It4WifiConnector(config.hostname, this);
119 localConnector.start();
120 connector = localConnector;
123 private void freeKeepAlive() {
124 ScheduledFuture<?> keepAlive = keepAliveJob;
125 if (keepAlive != null) {
126 keepAlive.cancel(true);
131 public void received(String command) {
132 logger.debug("Received : {}", command);
133 Event event = xstream.deserialize(command);
134 if (event.error != null) {
135 logger.warn("Error code {} received : {}", event.error.code, event.error.info);
137 if (event instanceof Response) {
138 handleResponse((Response) event);
140 notifyListeners(event.getDevices());
145 private void handleResponse(Response response) {
146 switch (response.type) {
148 Configuration thingConfig = editConfiguration();
149 thingConfig.put(It4WifiConfiguration.PASSWORD, response.authentication.pwd);
150 updateConfiguration(thingConfig);
151 logger.info("Pairing key updated in Configuration.");
152 sendCommand(CommandType.VERIFY);
155 if (keepAliveJob != null) { // means we are connected
158 switch (response.authentication.perm) {
161 sendCommand(CommandType.CONNECT);
164 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
165 "@text/conf-pending-validation");
166 scheduler.schedule(() -> handShaked(), 15, TimeUnit.SECONDS);
172 String sc = response.authentication.sc;
173 It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
175 reqBuilder.setChallenges(sc, response.authentication.id, config.password);
176 keepAliveJob = scheduler.scheduleWithFixedDelay(() -> sendCommand(CommandType.VERIFY),
177 KEEPALIVE_DELAY_S, KEEPALIVE_DELAY_S, TimeUnit.SECONDS);
178 sendCommand(CommandType.INFO);
182 updateStatus(ThingStatus.ONLINE);
183 if (thing.getProperties().isEmpty()) {
184 Map<String, String> properties = Map.of(PROPERTY_VENDOR, response.intf.manuf, PROPERTY_MODEL_ID,
185 response.intf.prod, PROPERTY_SERIAL_NUMBER, response.intf.serialNr,
186 PROPERTY_HARDWARE_VERSION, response.intf.versionHW, PROPERTY_FIRMWARE_VERSION,
187 response.intf.versionFW);
188 updateProperties(properties);
190 notifyListeners(response.getDevices());
193 notifyListeners(response.getDevices());
196 logger.debug("Change command accepted");
199 logger.warn("Unhandled response type : {}", response.type);
203 public void handShaked() {
204 handshakeAttempts = 0;
205 It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
206 sendCommand(config.password.isBlank() ? CommandType.PAIR : CommandType.VERIFY);
209 private void notifyListeners(List<Device> list) {
211 dataListeners.forEach(listener -> listener.onDataFetched(devices));
214 private void sendCommand(String command) {
215 It4WifiConnector localConnector = connector;
216 if (localConnector != null) {
217 localConnector.sendCommand(command);
219 logger.warn("Tried to send a command when IT4WifiConnector is not initialized.");
223 public void sendCommand(CommandType command) {
224 sendCommand(reqBuilder.buildMessage(command));
227 public void sendCommand(String id, String command) {
228 sendCommand(reqBuilder.buildMessage(id, command.toLowerCase()));
231 public void sendCommand(String id, T4Command t4) {
232 sendCommand(reqBuilder.buildMessage(id, t4));
235 public void connectorInterrupted(@Nullable String message) {
236 if (handshakeAttempts++ <= MAX_HANDSHAKE_ATTEMPTS) {
237 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
240 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error-handshake-limit");