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.androidtv.internal.protocol.googletv;
15 import static org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConstants.*;
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
22 * Class responsible for parsing incoming GoogleTV messages. Calls back to an object implementing the
23 * GoogleTVMessageParserCallbacks interface.
25 * Adapted from Lutron Leap binding
27 * @author Ben Rosenblum - Initial contribution
31 public class GoogleTVMessageParser {
32 private final Logger logger = LoggerFactory.getLogger(GoogleTVMessageParser.class);
34 private final GoogleTVConnectionManager callback;
36 public GoogleTVMessageParser(GoogleTVConnectionManager callback) {
37 this.callback = callback;
40 public void handleMessage(String msg) {
41 if (msg.trim().isEmpty()) {
42 return; // Ignore empty lines
45 String thingId = callback.getThingID();
46 char[] charArray = msg.toCharArray();
47 String lenString = "" + charArray[0] + charArray[1];
48 int len = Integer.parseInt(lenString, 16);
49 msg = msg.substring(2);
50 charArray = msg.toCharArray();
52 logger.trace("{} - Received GoogleTV message - Length: {} Message: {}", thingId, len, msg);
54 callback.validMessageReceived();
57 if (msg.startsWith(DELIMITER_1A)) {
58 logger.warn("{} - GoogleTV Error Message: {}", thingId, msg);
59 callback.getHandler().dispose();
60 } else if (msg.startsWith(DELIMITER_0A)) {
61 // First message on connection from GTV
63 // 0a 5b 08 ff 04 12 56 0a 11 534849454c4420416e64726f6964205456 12 06 4e5649444941 18 01 22 02 3131 2a
64 // ---LEN------------LEN---LEN-SHIELD Android TV--------------------LEN-NVIDIA---------LEN---LEN-Android
65 // 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
66 // LEN-com.google.android.tv.remote.service
67 // 0d 352e322e343733323534313333
70 // 0a 53 08 ff 04 12 4e 0a 0c 42524156494120344b204742 12 04 536f6e79 18 01 22 01 39 2a
71 // ---LEN------------LEN---LEN-BRAVIA 4K GB---------------LEN-Sony-------LEN---LEN-Android Version
72 // 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
73 // 0d 352e322e343733323534313333
75 // 0a 54 08 ff 04 12 4f 0a 0a 4368726f6d6563617374 12 06 476f6f676c65 18 01 22 02 3132 2a
76 // ---LEN------------LEN---LEN-Chromecast-------------LEN-Google---------LEN---LEN-Android Version
77 // 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
78 // 0d 352e322e343733323534313333
80 // 0a 57 08 ff 04 12 52 0a 0d 4368726f6d6563617374204844 12 06 476f6f676c65 18 01 22 02 3132 2a
81 // ---LEN------------LEN---LEN-Chromecast HD----------------LEN-Google---------LEN---LEN-Android Version
82 // 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
83 // 0d352e322e343733323534313333
85 // 0a 55 08 ef 04 12 50 0a 09 535754562d32304145 12 08 736b79776f727468 18 01 22 02 3130 2a
86 // ---LEN------------LEN---LEN-SWTV-20AE------------LEN-skyworth-----------LEN---LEN-Android
87 // 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
88 // LEN-com.google.android.tv.remote.service
89 // 0d 352e322e343733323534313333
92 // 0a 5b 08 fd 04 12 56 0a 11 534849454c4420416e64726f6964205456 12 06 4e5649444941 18 01 22 02 3131 2a
93 // ---LEN------------LEN---LEN-SHIELD Android TV--------------------LEN-NVIDIA---------LEN---LEN-Android
94 // 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
95 // LEN-com.google.android.tv.remote.service
96 // 0d 352e322e343733323534313333
99 if (callback.getLoggedIn()) {
100 logger.warn("{} - Unexpected Login Message: {}", thingId, msg);
102 String flag = "" + charArray[6] + charArray[7];
103 logger.trace("{} - Encoding Flag Data: {}", thingId, flag);
104 callback.sendCommand(
105 new GoogleTVCommand(GoogleTVRequest.encodeMessage(GoogleTVRequest.loginRequest(4, flag))));
110 StringBuilder preambleSb = new StringBuilder();
111 StringBuilder manufacturerSb = new StringBuilder();
112 StringBuilder modelSb = new StringBuilder();
113 StringBuilder androidVersionSb = new StringBuilder();
114 StringBuilder remoteServerSb = new StringBuilder();
115 StringBuilder remoteServerVersionSb = new StringBuilder();
120 for (; i < 14; i++) {
121 preambleSb.append(charArray[i]);
124 i += 2; // 0a delimiter
126 st = "" + charArray[i] + charArray[i + 1];
127 length = Integer.parseInt(st, 16) * 2;
131 for (; i < current + length; i++) {
132 modelSb.append(charArray[i]);
135 i += 2; // 12 delimiter
137 st = "" + charArray[i] + charArray[i + 1];
138 length = Integer.parseInt(st, 16) * 2;
142 for (; i < current + length; i++) {
143 manufacturerSb.append(charArray[i]);
148 st = "" + charArray[i] + charArray[i + 1];
149 length = Integer.parseInt(st, 16) * 2;
153 for (; i < current + length; i++) {
154 androidVersionSb.append(charArray[i]);
157 i += 2; // 2a delimiter
159 st = "" + charArray[i] + charArray[i + 1];
160 length = Integer.parseInt(st, 16) * 2;
164 for (; i < current + length; i++) {
165 remoteServerSb.append(charArray[i]);
168 i += 2; // 32 delimiter
170 st = "" + charArray[i] + charArray[i + 1];
171 length = Integer.parseInt(st, 16) * 2;
175 for (; i < current + length; i++) {
176 remoteServerVersionSb.append(charArray[i]);
179 String preamble = preambleSb.toString();
180 String model = GoogleTVRequest.encodeMessage(modelSb.toString());
181 String manufacturer = GoogleTVRequest.encodeMessage(manufacturerSb.toString());
182 String androidVersion = GoogleTVRequest.encodeMessage(androidVersionSb.toString());
183 String remoteServer = GoogleTVRequest.encodeMessage(remoteServerSb.toString());
184 String remoteServerVersion = GoogleTVRequest.encodeMessage(remoteServerVersionSb.toString());
186 logger.debug("{} - {} \"{}\" \"{}\" {} {} {}", thingId, preamble, model, manufacturer, androidVersion,
187 remoteServer, remoteServerVersion);
189 callback.setModel(model);
190 callback.setManufacturer(manufacturer);
191 callback.setAndroidVersion(androidVersion);
192 callback.setRemoteServer(remoteServer);
193 callback.setRemoteServerVersion(remoteServerVersion);
195 } else if (msg.startsWith(DELIMITER_12)) {
196 // Second message on connection from GTV
198 callback.sendCommand(
199 new GoogleTVCommand(GoogleTVRequest.encodeMessage(GoogleTVRequest.loginRequest(5))));
200 logger.info("{} - Login Successful", thingId);
201 callback.setLoggedIn(true);
202 } else if (msg.startsWith(DELIMITER_92)) {
203 // Third message on connection from GTV
204 // Also sent on power state change (to ON only unless keypress triggers)i
205 // 9203 21 08 02 10 02 1a 11 534849454c4420416e64726f6964205456 20 02 2800 30 0f 38 0e 40 00
206 // --------DD----DD----DD-LEN-SHIELD Android TV
207 // 9203 1e 08 9610 10 09 1a 0d 4368726f6d6563617374204844 20 02 2800 30 19 38 0a 40 00
208 // --------DD------DD----DD-LEN-Chromecast HD
209 // 9203 1a 08 f304 10 09 1a 11 534849454c4420416e64726f6964205456 20 01
210 // 9203 1a 08 8205 10 09 1a 11 534849454c4420416e64726f6964205456 20 01
211 // --------DD------DD----DD-LEN-SHIELD Android TV
214 // ---------------DD----DD----DD-LEN-BRAVIA 4K GB------------DD---------DD-MAX---VOL---MUTE
215 // 00 --- 9203 1c 08 03 10 06 1a 0c 42524156494120344b204742 20 02 2800 30 64 38 00 40 00
216 // 01 --- 9203 1c 08 03 10 06 1a 0c 42524156494120344b204742 20 02 2800 30 64 38 01 40 00
217 // 100 -- 9203 1c 08 03 10 06 1a 0c 42524156494120344b204742 20 02 2800 30 64 38 64 40 00
218 // MUTE - 9203 1c 08 03 10 06 1a 0c 42524156494120344b204742 20 02 2800 30 64 38 00 40 01
223 StringBuilder preambleSb = new StringBuilder();
224 StringBuilder modelSb = new StringBuilder();
228 String audioMode = "";
233 for (; i < 12; i++) {
234 preambleSb.append(charArray[i]);
237 st = "" + charArray[i] + charArray[i + 1];
239 if (!DELIMITER_1A.equals(st)) {
240 preambleSb.append(st);
242 st = "" + charArray[i] + charArray[i + 1];
244 } while (!DELIMITER_1A.equals(st));
246 i += 2; // 1a delimiter
248 st = "" + charArray[i] + charArray[i + 1];
249 length = Integer.parseInt(st, 16) * 2;
253 for (; i < current + length; i++) {
254 modelSb.append(charArray[i]);
257 i += 2; // 20 delimiter
259 st = "" + charArray[i] + charArray[i + 1];
261 audioMode = st; // 01 remote audio - 02 local audio
263 if (DELIMITER_02.equals(st)) {
264 i += 2; // 02 longer message
265 i += 4; // Unknown 2800 message
266 i += 2; // 30 delimiter
267 volMax = "" + charArray[i] + charArray[i + 1];
268 i += 4; // volMax + 38 delimiter
269 volCurr = "" + charArray[i] + charArray[i + 1];
270 i += 4; // volCurr + 40 delimiter
271 volMute = "" + charArray[i] + charArray[i + 1];
273 callback.setVolMax(volMax);
274 callback.setVolCurr(volCurr);
275 callback.setVolMute(volMute);
278 String preamble = preambleSb.toString();
279 String model = GoogleTVRequest.encodeMessage(modelSb.toString());
280 logger.debug("{} - Device Update: {} \"{}\" {} {} {} {}", thingId, preamble, model, audioMode, volMax,
282 callback.setAudioMode(audioMode);
284 } else if (msg.startsWith(DELIMITER_08)) {
285 // PIN Process Messages. Only used on 6467.
286 if (msg.startsWith(MESSAGE_PINSUCCESS)) {
287 // PIN Process Successful
288 logger.debug("{} - PIN Process Successful!", thingId);
289 callback.finishPinProcess();
291 // 080210c801a201081204080310061801
293 logger.debug("{} - PIN Intermediary Message: {}", thingId, msg);
295 } else if (msg.startsWith(DELIMITER_C2)) {
299 if (MESSAGE_POWEROFF.equals(msg)) {
300 callback.setPower(false);
301 } else if (MESSAGE_POWERON.equals(msg)) {
302 callback.setPower(true);
304 logger.info("{} - Unknown power state received. {}", thingId, msg);
306 } else if (msg.startsWith(DELIMITER_42)) {
308 callback.sendKeepAlive(msg);
309 } else if (msg.startsWith(DELIMITER_A2)) {
310 // Current app name. Sent on keypress and power change.
311 // a201 21 0a 1f 62 1d 636f6d2e676f6f676c652e616e64726f69642e796f75747562652e7476
312 // -----------------LEN-com.google.android.youtube.tv
313 // a201 21 0a 1f 62 1d 636f6d2e676f6f676c652e616e64726f69642e74766c61756e63686572
314 // -----------------LEN-com.google.android.tvlauncher
315 // a201 14 0a 12 62 10 636f6d2e736f6e792e6474762e747678
316 // -----------------LEN-com.sony.dtv.tvx
317 // a201 15 0a 13 62 11 636f6d2e6e6574666c69782e6e696e6a61
318 // -----------------LEN-com.netflix.ninja
320 StringBuilder preambleSb = new StringBuilder();
321 StringBuilder appNameSb = new StringBuilder();
325 for (; i < 10; i++) {
326 preambleSb.append(charArray[i]);
329 i += 2; // 62 delimiter
331 String st = "" + charArray[i] + charArray[i + 1];
332 int length = Integer.parseInt(st, 16) * 2;
336 for (; i < current + length; i++) {
337 appNameSb.append(charArray[i]);
340 String preamble = preambleSb.toString();
341 String appName = GoogleTVRequest.encodeMessage(appNameSb.toString());
343 logger.debug("{} - Current App: {} {}", thingId, preamble, appName);
344 callback.setCurrentApp(appName);
346 logger.info("{} - Unknown payload received. {} {}", thingId, len, msg);
348 } catch (Exception e) {
349 logger.warn("{} - Message Parser Exception on {}", thingId, msg);
350 logger.warn("{} - Message Parser Caught Exception", thingId, e);