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.silvercrestwifisocket.internal.handler;
15 import static org.openhab.binding.silvercrestwifisocket.internal.SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_CHANNEL_ID;
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;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
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;
49 * The {@link SilvercrestWifiSocketHandler} is responsible for handling commands, which are
50 * sent to one of the channels.
52 * @author Jaime Vaz - Initial contribution
53 * @author Christian Heimerl - for integration of EasyHome
55 public class SilvercrestWifiSocketHandler extends BaseThingHandler {
57 private final Logger logger = LoggerFactory.getLogger(SilvercrestWifiSocketHandler.class);
59 private String hostAddress;
60 private String macAddress;
61 private SilvercrestWifiSocketVendor vendor = SilvercrestWifiSocketBindingConstants.DEFAULT_VENDOR;
62 private Long updateInterval = SilvercrestWifiSocketBindingConstants.DEFAULT_REFRESH_INTERVAL;
64 private final WifiSocketPacketConverter converter = new WifiSocketPacketConverter();
65 private ScheduledFuture<?> keepAliveJob;
66 private long latestUpdate = -1;
69 * Default constructor.
71 * @param thing the thing of the handler.
72 * @throws MacAddressNotValidException if the mac address isn't valid.
74 public SilvercrestWifiSocketHandler(final Thing thing) throws MacAddressNotValidException {
76 this.saveMacAddressFromConfiguration(this.getConfig());
77 this.saveHostAddressFromConfiguration(this.getConfig());
78 this.saveUpdateIntervalFromConfiguration(this.getConfig());
79 this.saveVendorFromConfiguration(this.getConfig());
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);
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);
98 public void handleRemoval() {
100 this.keepAliveJob.cancel(true);
101 super.handleRemoval();
105 * Starts one thread that querys the state of the socket, after the defined refresh interval.
107 private void initGetStatusAndKeepAliveThread() {
108 if (this.keepAliveJob != null) {
109 this.keepAliveJob.cancel(true);
111 // try with handler port if is null
112 Runnable runnable = () -> {
114 "Begin of Socket keep alive thread routine. Current configuration update interval: {} seconds.",
115 SilvercrestWifiSocketHandler.this.updateInterval);
117 long now = System.currentTimeMillis();
118 long timePassedFromLastUpdateInSeconds = (now - SilvercrestWifiSocketHandler.this.latestUpdate) / 1000;
120 logger.trace("Latest Update: {} Now: {} Delta: {} seconds", SilvercrestWifiSocketHandler.this.latestUpdate,
121 now, timePassedFromLastUpdateInSeconds);
123 logger.debug("It has been passed {} seconds since the last update on socket with mac address {}.",
124 timePassedFromLastUpdateInSeconds, SilvercrestWifiSocketHandler.this.macAddress);
126 boolean mustUpdateHostAddress = timePassedFromLastUpdateInSeconds > (SilvercrestWifiSocketHandler.this.updateInterval
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();
135 boolean considerThingOffline = (SilvercrestWifiSocketHandler.this.latestUpdate < 0)
136 || (timePassedFromLastUpdateInSeconds > (SilvercrestWifiSocketHandler.this.updateInterval * 4));
137 if (considerThingOffline) {
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);
144 // request gpio status
145 SilvercrestWifiSocketHandler.this.sendCommand(SilvercrestWifiSocketRequestType.GPIO_STATUS);
147 this.keepAliveJob = this.scheduler.scheduleWithFixedDelay(runnable, 1,
148 SilvercrestWifiSocketHandler.this.updateInterval, TimeUnit.SECONDS);
152 public void initialize() {
153 this.initGetStatusAndKeepAliveThread();
154 updateStatus(ThingStatus.ONLINE);
155 this.saveConfigurationsUsingCurrentStates();
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
163 private void lookupForSocketHostAddress() {
164 SilvercrestWifiSocketRequest requestPacket = new SilvercrestWifiSocketRequest(this.macAddress,
165 SilvercrestWifiSocketRequestType.DISCOVERY, this.vendor);
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);
175 * Method called by {@link SilvercrestWifiSocketMediator} when one new message has been received for this handler.
177 * @param receivedMessage the received {@link SilvercrestWifiSocketResponse}.
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
182 if (!receivedMessage.getHostAddress().equals(this.hostAddress)) {
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());
189 this.hostAddress = receivedMessage.getHostAddress();
190 this.saveConfigurationsUsingCurrentStates();
193 switch (receivedMessage.getType()) {
199 this.updateState(SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_CHANNEL_ID, OnOffType.OFF);
202 this.updateState(SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_CHANNEL_ID, OnOffType.ON);
205 logger.debug("Command not found!");
209 this.updateStatus(ThingStatus.ONLINE);
210 this.latestUpdate = System.currentTimeMillis();
214 * Saves the host address from configuration in field.
216 * @param configuration The {@link Configuration}
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));
227 * Saves the host address from configuration in field.
229 * @param configuration The {@link Configuration}
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))
237 this.updateInterval = ((BigDecimal) configuration
238 .get(SilvercrestWifiSocketBindingConstants.UPDATE_INTERVAL_ARG)).longValue();
243 * Saves the mac address from configuration in field.
245 * @param configuration The {@link Configuration}
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);
255 this.macAddress = macAddress.replaceAll(":", "").toUpperCase();
257 if (this.macAddress == null) {
258 throw new MacAddressNotValidException("Mac address is not valid", this.macAddress);
263 * Saves the vendor from configuration in field.
265 * @param configuration The {@link Configuration}
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)));
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.
278 * @param type the {@link SilvercrestWifiSocketRequestType} of the command.
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) {
285 "Send command cannot proceed until one Host Address is set for mac address: {} Will invoke one mac address lookup!",
287 this.lookupForSocketHostAddress();
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();
302 * Sends {@link SilvercrestWifiSocketRequest} to the passed {@link InetAddress}.
304 * @param requestPacket the {@link SilvercrestWifiSocketRequest}.
305 * @param address the {@link InetAddress}.
307 private void sendRequestPacket(final SilvercrestWifiSocketRequest requestPacket, final InetAddress address) {
308 DatagramSocket dsocket = null;
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);
317 // Create a datagram socket, send the packet through it, close it.
318 dsocket = new DatagramSocket();
320 dsocket.send(packet);
321 logger.debug("Sent packet to address: {} and port {}", address,
322 SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_DEFAULT_UDP_PORT);
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());
328 if (dsocket != null) {
336 protected void updateConfiguration(final Configuration configuration) {
338 this.latestUpdate = -1;
340 this.saveMacAddressFromConfiguration(configuration);
342 this.hostAddress = null;
343 this.saveHostAddressFromConfiguration(configuration);
344 if (this.hostAddress == null) {
345 this.lookupForSocketHostAddress();
347 this.saveUpdateIntervalFromConfiguration(configuration);
348 this.saveVendorFromConfiguration(configuration);
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);
359 * Save the current runtime configuration of the handler in configuration mechanism.
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);
368 Configuration newConfiguration = new Configuration(map);
369 super.updateConfiguration(newConfiguration);
372 // SETTERS AND GETTERS
373 public String getHostAddress() {
374 return this.hostAddress;
377 public String getMacAddress() {
378 return this.macAddress;
381 public SilvercrestWifiSocketVendor getVendor() {