]> git.basschouten.com Git - openhab-addons.git/blob
fa822fd5abee4cce24c42eb19f5fa46895de5936
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.irobot.internal.dto;
14
15 import java.io.IOException;
16 import java.io.StringReader;
17 import java.net.DatagramPacket;
18 import java.net.DatagramSocket;
19 import java.net.InetAddress;
20 import java.nio.charset.StandardCharsets;
21
22 import com.google.gson.Gson;
23 import com.google.gson.JsonParseException;
24 import com.google.gson.stream.JsonReader;
25
26 /**
27  * iRobot discovery and identification protocol
28  *
29  * @author Pavel Fedin - Initial contribution
30  *
31  */
32 public class IdentProtocol {
33     private static final String UDP_PACKET_CONTENTS = "irobotmcs";
34     private static final int REMOTE_UDP_PORT = 5678;
35     private static final Gson gson = new Gson();
36
37     public static DatagramSocket sendRequest(InetAddress host) throws IOException {
38         DatagramSocket socket = new DatagramSocket();
39
40         socket.setBroadcast(true);
41         socket.setReuseAddress(true);
42
43         byte[] packetContents = UDP_PACKET_CONTENTS.getBytes(StandardCharsets.UTF_8);
44         DatagramPacket packet = new DatagramPacket(packetContents, packetContents.length, host, REMOTE_UDP_PORT);
45
46         socket.send(packet);
47         return socket;
48     }
49
50     public static DatagramPacket receiveResponse(DatagramSocket socket) throws IOException {
51         byte[] buffer = new byte[1024];
52         DatagramPacket incomingPacket = new DatagramPacket(buffer, buffer.length);
53
54         socket.setSoTimeout(1000 /* one second */);
55         socket.receive(incomingPacket);
56
57         return incomingPacket;
58     }
59
60     public static IdentData decodeResponse(DatagramPacket packet) throws JsonParseException {
61         return decodeResponse(new String(packet.getData(), StandardCharsets.UTF_8));
62     }
63
64     public static IdentData decodeResponse(String reply) throws JsonParseException {
65         /*
66          * packet is a JSON of the following contents (addresses are undisclosed):
67          * @formatter:off
68          * {
69          *   "ver":"3",
70          *   "hostname":"Roomba-3168820480607740",
71          *   "robotname":"Roomba",
72          *   "ip":"XXX.XXX.XXX.XXX",
73          *   "mac":"XX:XX:XX:XX:XX:XX",
74          *   "sw":"v2.4.6-3",
75          *   "sku":"R981040",
76          *   "nc":0,
77          *   "proto":"mqtt",
78          *   "cap":{
79          *     "pose":1,
80          *     "ota":2,
81          *     "multiPass":2,
82          *     "carpetBoost":1,
83          *     "pp":1,
84          *     "binFullDetect":1,
85          *     "langOta":1,
86          *     "maps":1,
87          *     "edge":1,
88          *     "eco":1,
89          *     "svcConf":1
90          *   }
91          * }
92          * @formatter:on
93          */
94         // We are not consuming all the fields, so we have to create the reader explicitly
95         // If we use fromJson(String) or fromJson(java.util.reader), it will throw
96         // "JSON not fully consumed" exception, because not all the reader's content has been
97         // used up. We want to avoid that for compatibility reasons because newer iRobot versions
98         // may add fields.
99         JsonReader jsonReader = new JsonReader(new StringReader(reply));
100         IdentData data = gson.fromJson(jsonReader, IdentData.class);
101
102         data.postParse();
103         return data;
104     }
105
106     public static class IdentData {
107         public static int MIN_SUPPORTED_VERSION = 2;
108         public static String PRODUCT_ROOMBA = "Roomba";
109
110         public int ver;
111         private String hostname;
112         public String robotname;
113
114         // These two fields are synthetic, they are not contained in JSON
115         public String product;
116         public String blid;
117
118         public void postParse() {
119             // Synthesize missing properties.
120             String[] hostparts = hostname.split("-");
121
122             // This also comes from Roomba980-Python. Comments there say that "iRobot"
123             // prefix is used by i7. We assume for other robots it would be product
124             // name, e. g. "Scooba"
125             if (hostparts[0].equals("iRobot")) {
126                 product = "Roomba";
127             } else {
128                 product = hostparts[0];
129             }
130
131             blid = hostparts[1];
132         }
133     }
134 }