]> git.basschouten.com Git - openhab-addons.git/blob
f5d672d01bb406f7d7b29e419234f37039e5b63d
[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.milight.internal.handler;
14
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.DatagramSocket;
18 import java.net.InetAddress;
19 import java.net.InetSocketAddress;
20 import java.net.SocketException;
21 import java.net.SocketTimeoutException;
22 import java.net.UnknownHostException;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.milight.internal.MilightBindingConstants;
29 import org.openhab.core.config.core.Configuration;
30 import org.openhab.core.thing.Bridge;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34
35 /**
36  * The {@link BridgeV3Handler} is responsible for handling commands, which are
37  * sent to one of the channels.
38  *
39  * @author David Graeff - Initial contribution
40  */
41 @NonNullByDefault
42 public class BridgeV3Handler extends AbstractBridgeHandler {
43     protected final DatagramPacket discoverPacketV3;
44     protected final byte[] buffer = new byte[1024];
45     protected final DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
46     private @Nullable ScheduledFuture<?> running;
47
48     public BridgeV3Handler(Bridge bridge, int bridgeOffset) {
49         super(bridge, bridgeOffset);
50         discoverPacketV3 = new DatagramPacket(MilightBindingConstants.DISCOVER_MSG_V3,
51                 MilightBindingConstants.DISCOVER_MSG_V3.length);
52     }
53
54     @Override
55     public void thingUpdated(Thing thing) {
56         super.thingUpdated(thing);
57     }
58
59     /**
60      * Creates a discovery object and the send queue. The initial IP address may be null
61      * or is not matching with the real IP address of the bridge. The discovery class will send
62      * a broadcast packet to find the bridge with the respective bridge ID. The response in bridgeDetected()
63      * may lead to a recreation of the send queue object.
64      *
65      * The keep alive timer that is also setup here, will send keep alive packets periodically.
66      * If the bridge doesn't respond anymore (e.g. DHCP IP change), the initial session handshake
67      * starts all over again.
68      */
69     @Override
70     protected void startConnectAndKeepAlive() {
71         if (address == null) {
72             if (!config.bridgeid.matches("^([0-9A-Fa-f]{12})$")) {
73                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridgeID invalid!");
74                 return;
75             }
76             try {
77                 address = InetAddress.getByAddress(new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255 });
78             } catch (UnknownHostException neverHappens) {
79             }
80         }
81
82         if (config.port == 0) {
83             config.port = MilightBindingConstants.PORT_VER3;
84         }
85
86         try {
87             socket = new DatagramSocket(null);
88             socket.setReuseAddress(true);
89             socket.setBroadcast(true);
90             socket.bind(null);
91         } catch (SocketException e) {
92             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
93         }
94
95         running = scheduler.scheduleWithFixedDelay(this::receive, 0, config.refreshTime, TimeUnit.SECONDS);
96     }
97
98     protected void stopKeepAlive() {
99         if (running != null) {
100             running.cancel(false);
101             running = null;
102         }
103         if (socket != null) {
104             socket.close();
105         }
106     }
107
108     @Override
109     public void dispose() {
110         stopKeepAlive();
111     }
112
113     private void receive() {
114         try {
115             discoverPacketV3.setAddress(address);
116             discoverPacketV3.setPort(MilightBindingConstants.PORT_DISCOVER);
117
118             final int attempts = 5;
119             int timeoutsCounter = 0;
120             for (timeoutsCounter = 1; timeoutsCounter <= attempts; ++timeoutsCounter) {
121                 try {
122                     packet.setLength(buffer.length);
123                     socket.setSoTimeout(500 * timeoutsCounter);
124                     socket.send(discoverPacketV3);
125                     socket.receive(packet);
126                 } catch (SocketTimeoutException e) {
127                     continue;
128                 }
129                 // We expect packets with a format like this: 10.1.1.27,ACCF23F57AD4,HF-LPB100
130                 final String received = new String(packet.getData());
131                 final String[] msg = received.split(",");
132
133                 if (msg.length != 2 && msg.length != 3) {
134                     // That data packet does not belong to a Milight bridge. Just ignore it.
135                     continue;
136                 }
137
138                 // First argument is the IP
139                 try {
140                     InetAddress.getByName(msg[0]);
141                 } catch (UnknownHostException ignored) {
142                     // That data packet does not belong to a Milight bridge, we expect an IP address as first
143                     // argument. Just ignore it.
144                     continue;
145                 }
146
147                 // Second argument is the MAC address
148                 if (msg[1].length() != 12) {
149                     // That data packet does not belong to a Milight bridge, we expect a MAC address as second
150                     // argument.
151                     // Just ignore it.
152                     continue;
153                 }
154
155                 final InetAddress addressOfBridge = ((InetSocketAddress) packet.getSocketAddress()).getAddress();
156                 final String bridgeID = msg[1];
157
158                 if (!config.bridgeid.isEmpty() && !bridgeID.equals(config.bridgeid)) {
159                     // We found a bridge, but it is not the one that is handled by this handler
160                     if (!config.host.isEmpty()) { // The user has set a host address -> but wrong bridge found!
161                         stopKeepAlive();
162                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
163                                 "Wrong bridge found on host address. Change bridgeid or host configuration.");
164                         break;
165                     }
166                     continue;
167                 }
168
169                 // IP address has changed, reestablish communication
170                 if (!addressOfBridge.equals(this.address)) {
171                     this.address = addressOfBridge;
172                     Configuration c = editConfiguration();
173                     c.put(BridgeHandlerConfig.CONFIG_HOST_NAME, addressOfBridge.getHostAddress());
174                     preventReinit = true;
175                     updateConfiguration(c);
176                     preventReinit = false;
177                 } else if (config.bridgeid.isEmpty()) { // bridge id was not set and is now known. Store it.
178                     config.bridgeid = bridgeID;
179                     Configuration c = editConfiguration();
180                     c.put(BridgeHandlerConfig.CONFIG_BRIDGE_ID, bridgeID);
181                     preventReinit = true;
182                     updateConfiguration(c);
183                     preventReinit = false;
184                 }
185
186                 updateStatus(ThingStatus.ONLINE);
187                 break;
188             }
189             if (timeoutsCounter > attempts) {
190                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Bridge did not respond!");
191             }
192         } catch (IOException e) {
193             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
194         }
195     }
196 }