]> git.basschouten.com Git - openhab-addons.git/blob
947164cd4bf904d22c59fe27b9cd289c9672d42b
[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.silvercrestwifisocket.internal.handler;
14
15 import static org.openhab.binding.silvercrestwifisocket.internal.SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_CHANNEL_ID;
16
17 import java.math.BigDecimal;
18 import java.net.DatagramPacket;
19 import java.net.DatagramSocket;
20 import java.net.InetAddress;
21 import java.net.UnknownHostException;
22 import java.util.HashMap;
23 import java.util.Map;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.openhab.binding.silvercrestwifisocket.internal.SilvercrestWifiSocketBindingConstants;
28 import org.openhab.binding.silvercrestwifisocket.internal.entities.SilvercrestWifiSocketRequest;
29 import org.openhab.binding.silvercrestwifisocket.internal.entities.SilvercrestWifiSocketResponse;
30 import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketRequestType;
31 import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketVendor;
32 import org.openhab.binding.silvercrestwifisocket.internal.exceptions.MacAddressNotValidException;
33 import org.openhab.binding.silvercrestwifisocket.internal.utils.NetworkUtils;
34 import org.openhab.binding.silvercrestwifisocket.internal.utils.ValidationUtils;
35 import org.openhab.binding.silvercrestwifisocket.internal.utils.WifiSocketPacketConverter;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * The {@link SilvercrestWifiSocketHandler} is responsible for handling commands, which are
50  * sent to one of the channels.
51  *
52  * @author Jaime Vaz - Initial contribution
53  * @author Christian Heimerl - for integration of EasyHome
54  */
55 public class SilvercrestWifiSocketHandler extends BaseThingHandler {
56
57     private final Logger logger = LoggerFactory.getLogger(SilvercrestWifiSocketHandler.class);
58
59     private String hostAddress;
60     private String macAddress;
61     private SilvercrestWifiSocketVendor vendor = SilvercrestWifiSocketBindingConstants.DEFAULT_VENDOR;
62     private Long updateInterval = SilvercrestWifiSocketBindingConstants.DEFAULT_REFRESH_INTERVAL;
63
64     private final WifiSocketPacketConverter converter = new WifiSocketPacketConverter();
65     private ScheduledFuture<?> keepAliveJob;
66     private long latestUpdate = -1;
67
68     /**
69      * Default constructor.
70      *
71      * @param thing the thing of the handler.
72      * @throws MacAddressNotValidException if the mac address isn't valid.
73      */
74     public SilvercrestWifiSocketHandler(final Thing thing) throws MacAddressNotValidException {
75         super(thing);
76         this.saveMacAddressFromConfiguration(this.getConfig());
77         this.saveHostAddressFromConfiguration(this.getConfig());
78         this.saveUpdateIntervalFromConfiguration(this.getConfig());
79         this.saveVendorFromConfiguration(this.getConfig());
80     }
81
82     @Override
83     public void handleCommand(final ChannelUID channelUID, final Command command) {
84         if (channelUID.getId().equals(WIFI_SOCKET_CHANNEL_ID)) {
85             logger.debug("Silvercrest socket command received: {}", command);
86
87             if (command == OnOffType.ON) {
88                 this.sendCommand(SilvercrestWifiSocketRequestType.ON);
89             } else if (command == OnOffType.OFF) {
90                 this.sendCommand(SilvercrestWifiSocketRequestType.OFF);
91             } else if (command == RefreshType.REFRESH) {
92                 this.sendCommand(SilvercrestWifiSocketRequestType.GPIO_STATUS);
93             }
94         }
95     }
96
97     @Override
98     public void handleRemoval() {
99         // stop update thread
100         this.keepAliveJob.cancel(true);
101         super.handleRemoval();
102     }
103
104     /**
105      * Starts one thread that querys the state of the socket, after the defined refresh interval.
106      */
107     private void initGetStatusAndKeepAliveThread() {
108         if (this.keepAliveJob != null) {
109             this.keepAliveJob.cancel(true);
110         }
111         // try with handler port if is null
112         Runnable runnable = () -> {
113             logger.debug(
114                     "Begin of Socket keep alive thread routine. Current configuration update interval: {} seconds.",
115                     SilvercrestWifiSocketHandler.this.updateInterval);
116
117             long now = System.currentTimeMillis();
118             long timePassedFromLastUpdateInSeconds = (now - SilvercrestWifiSocketHandler.this.latestUpdate) / 1000;
119
120             logger.trace("Latest Update: {} Now: {} Delta: {} seconds", SilvercrestWifiSocketHandler.this.latestUpdate,
121                     now, timePassedFromLastUpdateInSeconds);
122
123             logger.debug("It has been passed {} seconds since the last update on socket with mac address {}.",
124                     timePassedFromLastUpdateInSeconds, SilvercrestWifiSocketHandler.this.macAddress);
125
126             boolean mustUpdateHostAddress = timePassedFromLastUpdateInSeconds > (SilvercrestWifiSocketHandler.this.updateInterval
127                     * 2);
128
129             if (mustUpdateHostAddress) {
130                 logger.debug("No updates have been received for a long time, search the mac address {} in network...",
131                         SilvercrestWifiSocketHandler.this.getMacAddress());
132                 SilvercrestWifiSocketHandler.this.lookupForSocketHostAddress();
133             }
134
135             boolean considerThingOffline = (SilvercrestWifiSocketHandler.this.latestUpdate < 0)
136                     || (timePassedFromLastUpdateInSeconds > (SilvercrestWifiSocketHandler.this.updateInterval * 4));
137             if (considerThingOffline) {
138                 logger.debug(
139                         "No updates have been received for a long long time will put the thing with mac address {} OFFLINE.",
140                         SilvercrestWifiSocketHandler.this.getMacAddress());
141                 SilvercrestWifiSocketHandler.this.updateStatus(ThingStatus.OFFLINE);
142             }
143
144             // request gpio status
145             SilvercrestWifiSocketHandler.this.sendCommand(SilvercrestWifiSocketRequestType.GPIO_STATUS);
146         };
147         this.keepAliveJob = this.scheduler.scheduleWithFixedDelay(runnable, 1,
148                 SilvercrestWifiSocketHandler.this.updateInterval, TimeUnit.SECONDS);
149     }
150
151     @Override
152     public void initialize() {
153         this.initGetStatusAndKeepAliveThread();
154         updateStatus(ThingStatus.ONLINE);
155         this.saveConfigurationsUsingCurrentStates();
156     }
157
158     /**
159      * Lookup for socket host address, by sending one broadcast discovery message. Eventually the socket will respond to
160      * the message. When the mediator receives the message, it will set the host address in this handler, for future
161      * communications.
162      */
163     private void lookupForSocketHostAddress() {
164         SilvercrestWifiSocketRequest requestPacket = new SilvercrestWifiSocketRequest(this.macAddress,
165                 SilvercrestWifiSocketRequestType.DISCOVERY, this.vendor);
166
167         for (InetAddress broadcastAddressFound : NetworkUtils.getAllBroadcastAddresses()) {
168             logger.debug("Will query for device with mac address {} in network with broadcast address {}",
169                     this.macAddress, broadcastAddressFound);
170             this.sendRequestPacket(requestPacket, broadcastAddressFound);
171         }
172     }
173
174     /**
175      * Method called by {@link SilvercrestWifiSocketMediator} when one new message has been received for this handler.
176      *
177      * @param receivedMessage the received {@link SilvercrestWifiSocketResponse}.
178      */
179     public void newReceivedResponseMessage(final SilvercrestWifiSocketResponse receivedMessage) {
180         // if the host of the packet is different from the host address set in handler, update the host
181         // address.
182         if (!receivedMessage.getHostAddress().equals(this.hostAddress)) {
183             logger.debug("""
184                     The host of the packet is different from the host address set in handler. \
185                     Will update the host address. handler of mac: {}. \
186                     Old host address: '{}' -> new host address: '{}'\
187                     """, this.macAddress, this.hostAddress, receivedMessage.getHostAddress());
188
189             this.hostAddress = receivedMessage.getHostAddress();
190             this.saveConfigurationsUsingCurrentStates();
191         }
192
193         switch (receivedMessage.getType()) {
194             case ACK:
195                 break;
196             case DISCOVERY:
197                 break;
198             case OFF:
199                 this.updateState(SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_CHANNEL_ID, OnOffType.OFF);
200                 break;
201             case ON:
202                 this.updateState(SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_CHANNEL_ID, OnOffType.ON);
203                 break;
204             default:
205                 logger.debug("Command not found!");
206                 break;
207         }
208
209         this.updateStatus(ThingStatus.ONLINE);
210         this.latestUpdate = System.currentTimeMillis();
211     }
212
213     /**
214      * Saves the host address from configuration in field.
215      *
216      * @param configuration The {@link Configuration}
217      */
218     private void saveHostAddressFromConfiguration(final Configuration configuration) {
219         if ((configuration != null)
220                 && (configuration.get(SilvercrestWifiSocketBindingConstants.HOST_ADDRESS_ARG) != null)) {
221             this.hostAddress = String
222                     .valueOf(configuration.get(SilvercrestWifiSocketBindingConstants.HOST_ADDRESS_ARG));
223         }
224     }
225
226     /**
227      * Saves the host address from configuration in field.
228      *
229      * @param configuration The {@link Configuration}
230      */
231     private void saveUpdateIntervalFromConfiguration(final Configuration configuration) {
232         this.updateInterval = SilvercrestWifiSocketBindingConstants.DEFAULT_REFRESH_INTERVAL;
233         if ((configuration != null)
234                 && (configuration.get(SilvercrestWifiSocketBindingConstants.UPDATE_INTERVAL_ARG) instanceof BigDecimal)
235                 && (((BigDecimal) configuration.get(SilvercrestWifiSocketBindingConstants.UPDATE_INTERVAL_ARG))
236                         .longValue() > 0)) {
237             this.updateInterval = ((BigDecimal) configuration
238                     .get(SilvercrestWifiSocketBindingConstants.UPDATE_INTERVAL_ARG)).longValue();
239         }
240     }
241
242     /**
243      * Saves the mac address from configuration in field.
244      *
245      * @param configuration The {@link Configuration}
246      */
247     private void saveMacAddressFromConfiguration(final Configuration configuration) throws MacAddressNotValidException {
248         if ((configuration != null)
249                 && (configuration.get(SilvercrestWifiSocketBindingConstants.MAC_ADDRESS_ARG) != null)) {
250             String macAddress = String
251                     .valueOf(configuration.get(SilvercrestWifiSocketBindingConstants.MAC_ADDRESS_ARG));
252             if (ValidationUtils.isMacNotValid(macAddress)) {
253                 throw new MacAddressNotValidException("Mac address is not valid", macAddress);
254             }
255             this.macAddress = macAddress.replace(":", "").toUpperCase();
256         }
257         if (this.macAddress == null) {
258             throw new MacAddressNotValidException("Mac address is not valid", this.macAddress);
259         }
260     }
261
262     /**
263      * Saves the vendor from configuration in field.
264      *
265      * @param configuration The {@link Configuration}
266      */
267     private void saveVendorFromConfiguration(final Configuration configuration) {
268         if ((configuration != null) && (configuration.get(SilvercrestWifiSocketBindingConstants.VENDOR_ARG) != null)) {
269             this.vendor = SilvercrestWifiSocketVendor
270                     .valueOf(String.valueOf(configuration.get(SilvercrestWifiSocketBindingConstants.VENDOR_ARG)));
271         }
272     }
273
274     /**
275      * Sends one command to the Wifi Socket. If the host address is not set, it will trigger the lookup of the
276      * host address and discard the command queried.
277      *
278      * @param type the {@link SilvercrestWifiSocketRequestType} of the command.
279      */
280     private void sendCommand(final SilvercrestWifiSocketRequestType type) {
281         logger.debug("Send command for mac addr: {} with type: {} with hostaddress: {}", this.getMacAddress(),
282                 type.name(), this.hostAddress);
283         if (this.hostAddress == null) {
284             logger.debug(
285                     "Send command cannot proceed until one Host Address is set for mac address: {} Will invoke one mac address lookup!",
286                     this.macAddress);
287             this.lookupForSocketHostAddress();
288         } else {
289             InetAddress address;
290             try {
291                 address = InetAddress.getByName(this.hostAddress);
292                 this.sendRequestPacket(new SilvercrestWifiSocketRequest(this.macAddress, type, this.vendor), address);
293             } catch (UnknownHostException e) {
294                 logger.debug("Host Address not found: {}. Will lookup Mac address.", this.hostAddress);
295                 this.hostAddress = null;
296                 this.lookupForSocketHostAddress();
297             }
298         }
299     }
300
301     /**
302      * Sends {@link SilvercrestWifiSocketRequest} to the passed {@link InetAddress}.
303      *
304      * @param requestPacket the {@link SilvercrestWifiSocketRequest}.
305      * @param address the {@link InetAddress}.
306      */
307     private void sendRequestPacket(final SilvercrestWifiSocketRequest requestPacket, final InetAddress address) {
308         DatagramSocket dsocket = null;
309         try {
310             if (address != null) {
311                 byte[] message = this.converter.transformToByteMessage(requestPacket);
312                 logger.trace("Preparing packet to send...");
313                 // Initialize a datagram packet with data and address
314                 DatagramPacket packet = new DatagramPacket(message, message.length, address,
315                         SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_DEFAULT_UDP_PORT);
316
317                 // Create a datagram socket, send the packet through it, close it.
318                 dsocket = new DatagramSocket();
319
320                 dsocket.send(packet);
321                 logger.debug("Sent packet to address: {} and port {}", address,
322                         SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_DEFAULT_UDP_PORT);
323             }
324         } catch (Exception exception) {
325             logger.debug("Something wrong happen sending the packet to address: {} and port {}... msg: {}", address,
326                     SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_DEFAULT_UDP_PORT, exception.getMessage());
327         } finally {
328             if (dsocket != null) {
329                 dsocket.close();
330             }
331
332         }
333     }
334
335     @Override
336     protected void updateConfiguration(final Configuration configuration) {
337         try {
338             this.latestUpdate = -1;
339
340             this.saveMacAddressFromConfiguration(configuration);
341
342             this.hostAddress = null;
343             this.saveHostAddressFromConfiguration(configuration);
344             if (this.hostAddress == null) {
345                 this.lookupForSocketHostAddress();
346             }
347             this.saveUpdateIntervalFromConfiguration(configuration);
348             this.saveVendorFromConfiguration(configuration);
349
350             this.initGetStatusAndKeepAliveThread();
351             this.saveConfigurationsUsingCurrentStates();
352         } catch (MacAddressNotValidException e) {
353             logger.error("The Mac address passed is not valid! {}", e.getMacAddress());
354             this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
355         }
356     }
357
358     /**
359      * Save the current runtime configuration of the handler in configuration mechanism.
360      */
361     private void saveConfigurationsUsingCurrentStates() {
362         Map<String, Object> map = new HashMap<>();
363         map.put(SilvercrestWifiSocketBindingConstants.MAC_ADDRESS_ARG, this.macAddress);
364         map.put(SilvercrestWifiSocketBindingConstants.HOST_ADDRESS_ARG, this.hostAddress);
365         map.put(SilvercrestWifiSocketBindingConstants.VENDOR_ARG, this.vendor.toString());
366         map.put(SilvercrestWifiSocketBindingConstants.UPDATE_INTERVAL_ARG, this.updateInterval);
367
368         Configuration newConfiguration = new Configuration(map);
369         super.updateConfiguration(newConfiguration);
370     }
371
372     // SETTERS AND GETTERS
373     public String getHostAddress() {
374         return this.hostAddress;
375     }
376
377     public String getMacAddress() {
378         return this.macAddress;
379     }
380
381     public SilvercrestWifiSocketVendor getVendor() {
382         return this.vendor;
383     }
384 }