]> git.basschouten.com Git - openhab-addons.git/blob
b6535a5fb56e41be1ea0b2fc0abdd6bd27a73e37
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mynice.internal.handler;
14
15 import static org.openhab.core.thing.Thing.*;
16 import static org.openhab.core.types.RefreshType.REFRESH;
17
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.concurrent.CopyOnWriteArrayList;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
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;
48
49 /**
50  * The {@link It4WifiHandler} is responsible for handling commands, which are
51  * sent to one of the channels.
52  *
53  * @author GaĆ«l L'hopital - Initial contribution
54  */
55 @NonNullByDefault
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
59
60     private final Logger logger = LoggerFactory.getLogger(It4WifiHandler.class);
61     private final List<MyNiceDataListener> dataListeners = new CopyOnWriteArrayList<>();
62     private final MyNiceXStream xstream = new MyNiceXStream();
63
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;
69
70     public It4WifiHandler(Bridge thing) {
71         super(thing);
72     }
73
74     @Override
75     public Collection<Class<? extends ThingHandlerService>> getServices() {
76         return Set.of(MyNiceDiscoveryService.class);
77     }
78
79     public void registerDataListener(MyNiceDataListener dataListener) {
80         dataListeners.add(dataListener);
81         notifyListeners(devices);
82     }
83
84     public void unregisterDataListener(MyNiceDataListener dataListener) {
85         dataListeners.remove(dataListener);
86     }
87
88     @Override
89     public void handleCommand(ChannelUID channelUID, Command command) {
90         if (REFRESH.equals(command)) {
91             sendCommand(CommandType.INFO);
92         }
93     }
94
95     @Override
96     public void initialize() {
97         if (getConfigAs(It4WifiConfiguration.class).username.isBlank()) {
98             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-username");
99         } else {
100             updateStatus(ThingStatus.UNKNOWN);
101             scheduler.execute(() -> startConnector());
102         }
103     }
104
105     @Override
106     public void dispose() {
107         It4WifiConnector localConnector = connector;
108         if (localConnector != null) {
109             localConnector.dispose();
110         }
111         freeKeepAlive();
112     }
113
114     private void startConnector() {
115         It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
116         freeKeepAlive();
117         reqBuilder = new RequestBuilder(config.macAddress, config.username);
118         It4WifiConnector localConnector = new It4WifiConnector(config.hostname, this);
119         localConnector.start();
120         connector = localConnector;
121     }
122
123     private void freeKeepAlive() {
124         ScheduledFuture<?> keepAlive = keepAliveJob;
125         if (keepAlive != null) {
126             keepAlive.cancel(true);
127         }
128         keepAliveJob = null;
129     }
130
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);
136         } else {
137             if (event instanceof Response) {
138                 handleResponse((Response) event);
139             } else {
140                 notifyListeners(event.getDevices());
141             }
142         }
143     }
144
145     private void handleResponse(Response response) {
146         switch (response.type) {
147             case PAIR:
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);
153                 return;
154             case VERIFY:
155                 if (keepAliveJob != null) { // means we are connected
156                     return;
157                 }
158                 switch (response.authentication.perm) {
159                     case admin:
160                     case user:
161                         sendCommand(CommandType.CONNECT);
162                         return;
163                     case wait:
164                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
165                                 "@text/conf-pending-validation");
166                         scheduler.schedule(() -> handShaked(), 15, TimeUnit.SECONDS);
167                         return;
168                     default:
169                         return;
170                 }
171             case CONNECT:
172                 String sc = response.authentication.sc;
173                 It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
174                 if (sc != null) {
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);
179                 }
180                 return;
181             case 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);
189                 }
190                 notifyListeners(response.getDevices());
191                 return;
192             case STATUS:
193                 notifyListeners(response.getDevices());
194                 return;
195             case CHANGE:
196                 logger.debug("Change command accepted");
197                 return;
198             default:
199                 logger.warn("Unhandled response type : {}", response.type);
200         }
201     }
202
203     public void handShaked() {
204         handshakeAttempts = 0;
205         It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
206         sendCommand(config.password.isBlank() ? CommandType.PAIR : CommandType.VERIFY);
207     }
208
209     private void notifyListeners(List<Device> list) {
210         devices = list;
211         dataListeners.forEach(listener -> listener.onDataFetched(devices));
212     }
213
214     private void sendCommand(String command) {
215         It4WifiConnector localConnector = connector;
216         if (localConnector != null) {
217             localConnector.sendCommand(command);
218         } else {
219             logger.warn("Tried to send a command when IT4WifiConnector is not initialized.");
220         }
221     }
222
223     public void sendCommand(CommandType command) {
224         sendCommand(reqBuilder.buildMessage(command));
225     }
226
227     public void sendCommand(String id, String command) {
228         sendCommand(reqBuilder.buildMessage(id, command.toLowerCase()));
229     }
230
231     public void sendCommand(String id, T4Command t4) {
232         sendCommand(reqBuilder.buildMessage(id, t4));
233     }
234
235     public void connectorInterrupted(@Nullable String message) {
236         if (handshakeAttempts++ <= MAX_HANDSHAKE_ATTEMPTS) {
237             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
238             startConnector();
239         } else {
240             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error-handshake-limit");
241             connector = null;
242         }
243     }
244 }