]> git.basschouten.com Git - openhab-addons.git/blob
463068dcd9129e2fc6c6efd24087cb8a8ea424da
[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.etherrain.internal.api;
14
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.StringReader;
18 import java.net.DatagramPacket;
19 import java.net.DatagramSocket;
20 import java.net.HttpURLConnection;
21 import java.net.InetAddress;
22 import java.net.SocketTimeoutException;
23 import java.util.Collections;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.concurrent.ExecutionException;
27 import java.util.concurrent.TimeUnit;
28 import java.util.concurrent.TimeoutException;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import java.util.stream.Collectors;
32
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jetty.client.HttpClient;
35 import org.eclipse.jetty.client.api.ContentResponse;
36 import org.openhab.binding.etherrain.internal.EtherRainException;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
41  * The {@link EtherRainCommunication} handles communication with EtherRain
42  * controllers so that the API is all in one place
43  *
44  * @author Joe Inkenbrandt - Initial contribution
45  */
46
47 @NonNullByDefault
48 public class EtherRainCommunication {
49
50     private static final String BROADCAST_DISCOVERY_MESSAGE = "eviro_id_request1";
51     private static final int BROADCAST_DISCOVERY_PORT = 8089;
52
53     private static final String ETHERRAIN_USERNAME = "admin";
54
55     private static final int HTTP_TIMEOUT = 3;
56
57     private static final int BROADCAST_TIMEOUT = 80;
58
59     private static final String RESPONSE_STATUS_PATTERN = "^\\s*(un|ma|ac|os|cs|rz|ri|rn):\\s*([a-zA-Z0-9\\.]*)(\\s*<br>)?";
60     private static final String BROADCAST_RESPONSE_DISCOVER_PATTERN = "eviro t=(\\S*) n=(\\S*) p=(\\S*) a=(\\S*)";
61
62     private static final Pattern broadcastResponseDiscoverPattern = Pattern
63             .compile(BROADCAST_RESPONSE_DISCOVER_PATTERN);
64     private static final Pattern responseStatusPattern = Pattern.compile(RESPONSE_STATUS_PATTERN);
65
66     private final Logger logger = LoggerFactory.getLogger(EtherRainCommunication.class);
67     private final HttpClient httpClient;
68
69     private final String address;
70     private final int port;
71     private final String password;
72
73     public EtherRainCommunication(String address, int port, String password, HttpClient httpClient) {
74         this.address = address;
75         this.port = port;
76         this.password = password;
77         this.httpClient = httpClient;
78     }
79
80     public static List<EtherRainUdpResponse> autoDiscover() {
81         return updBroadcast();
82     }
83
84     public EtherRainStatusResponse commandStatus() throws EtherRainException, IOException {
85         commandLogin();
86
87         List<String> responseList = sendGet("result.cgi?xs");
88
89         if (responseList.isEmpty()) {
90             throw new EtherRainException("Empty Response");
91         }
92
93         EtherRainStatusResponse response = new EtherRainStatusResponse();
94
95         for (String line : responseList) {
96
97             Matcher m = responseStatusPattern.matcher(line);
98
99             if (m.matches()) {
100                 String command = m.replaceAll("$1");
101                 String status = m.replaceAll("$2");
102
103                 switch (command) {
104                     case "un":
105                         response.setUniqueName(status);
106                         break;
107                     case "ma":
108                         response.setMacAddress(status);
109                         break;
110                     case "ac":
111                         response.setServiceAccount(status);
112                         break;
113                     case "os":
114                         response.setOperatingStatus(EtherRainOperatingStatus.valueOf(status.toUpperCase()));
115                         break;
116                     case "cs":
117                         response.setLastCommandStatus(EtherRainCommandStatus.valueOf(status.toUpperCase()));
118                         break;
119                     case "rz":
120                         response.setLastCommandResult(EtherRainCommandResult.valueOf(status.toUpperCase()));
121                         break;
122                     case "ri":
123                         response.setLastActiveValue(Integer.parseInt(status));
124                         break;
125                     case "rn":
126                         response.setRainSensor(Integer.parseInt(status) == 1);
127                         break;
128                     default:
129                         logger.debug("Unknown response: {}", command);
130                 }
131             }
132         }
133
134         return response;
135     }
136
137     public synchronized boolean commandIrrigate(int delay, int zone1, int zone2, int zone3, int zone4, int zone5,
138             int zone6, int zone7, int zone8) {
139         try {
140             sendGet("result.cgi?xi=" + delay + ":" + zone1 + ":" + zone2 + ":" + zone3 + ":" + zone4 + ":" + zone5 + ":"
141                     + zone6 + ":" + zone7 + ":" + zone8);
142         } catch (IOException e) {
143             logger.warn("Could not send irrigate command to etherrain: {}", e.getMessage());
144             return false;
145         }
146
147         return true;
148     }
149
150     public synchronized boolean commandClear() {
151         try {
152             sendGet("/result.cgi?xr");
153         } catch (IOException e) {
154             logger.warn("Could not send clear command to etherrain: {}", e.getMessage());
155             return false;
156         }
157
158         return true;
159     }
160
161     public synchronized boolean commandLogin() throws EtherRainException {
162         try {
163             sendGet("/ergetcfg.cgi?lu=" + ETHERRAIN_USERNAME + "&lp=" + password);
164         } catch (IOException e) {
165             logger.warn("Could not send clear command to etherrain: {}", e.getMessage());
166             return false;
167         }
168
169         return true;
170     }
171
172     public synchronized boolean commandLogout() {
173         try {
174             sendGet("/ergetcfg.cgi?m=o");
175         } catch (IOException e) {
176             logger.warn("Could not send logout command to etherrain: {}", e.getMessage());
177             return false;
178         }
179
180         return true;
181     }
182
183     private synchronized List<String> sendGet(String command) throws IOException {
184         String url = "http://" + address + ":" + port + "/" + command;
185
186         ContentResponse response;
187
188         try {
189             response = httpClient.newRequest(url).timeout(HTTP_TIMEOUT, TimeUnit.SECONDS).send();
190             if (response.getStatus() != HttpURLConnection.HTTP_OK) {
191                 logger.warn("Etherrain return status other than HTTP_OK : {}", response.getStatus());
192                 return Collections.emptyList();
193             }
194         } catch (TimeoutException | ExecutionException e) {
195             logger.warn("Could not connect to Etherrain with exception: {}", e.getMessage());
196             return Collections.emptyList();
197         } catch (InterruptedException e) {
198             logger.warn("Connect to Etherrain interrupted: {}", e.getMessage());
199             Thread.currentThread().interrupt();
200             return Collections.emptyList();
201         }
202
203         return new BufferedReader(new StringReader(response.getContentAsString())).lines().collect(Collectors.toList());
204     }
205
206     private static List<EtherRainUdpResponse> updBroadcast() {
207         List<EtherRainUdpResponse> rList = new LinkedList<>();
208
209         // Find the server using UDP broadcast
210
211         try (DatagramSocket c = new DatagramSocket()) {
212             c.setSoTimeout(BROADCAST_TIMEOUT);
213             c.setBroadcast(true);
214
215             byte[] sendData = BROADCAST_DISCOVERY_MESSAGE.getBytes("UTF-8");
216
217             // Try the 255.255.255.255 first
218             DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length,
219                     InetAddress.getByName("255.255.255.255"), BROADCAST_DISCOVERY_PORT);
220             c.send(sendPacket);
221
222             while (true) {
223                 // Wait for a response
224                 byte[] recvBuf = new byte[15000];
225                 DatagramPacket receivePacket;
226                 try {
227                     receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
228                     c.receive(receivePacket);
229                 } catch (SocketTimeoutException e) {
230                     return rList;
231                 }
232
233                 // Check if the message is correct
234                 String message = new String(receivePacket.getData(), "UTF-8").trim();
235
236                 if (message.length() == 0) {
237                     return rList;
238                 }
239
240                 String addressBC = receivePacket.getAddress().getHostAddress();
241
242                 Matcher matcher = broadcastResponseDiscoverPattern.matcher(message);
243                 String deviceTypeBC = matcher.replaceAll("$1");
244                 String unqiueNameBC = matcher.replaceAll("$2");
245                 int portBC = Integer.parseInt(matcher.replaceAll("$3"));
246
247                 // NOTE: Version 3.77 of API states that Additional Parameters (a=) are not used
248                 // on EtherRain
249                 rList.add(new EtherRainUdpResponse(deviceTypeBC, addressBC, portBC, unqiueNameBC, ""));
250             }
251         } catch (IOException ex) {
252             return rList;
253         }
254     }
255 }