2 * Copyright (c) 2010-2020 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.kaleidescape.internal.handler;
15 import static org.eclipse.jetty.http.HttpMethod.GET;
16 import static org.eclipse.jetty.http.HttpStatus.OK_200;
17 import static org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants.*;
19 import java.math.BigDecimal;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
26 import javax.measure.quantity.Time;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jetty.client.api.ContentResponse;
30 import org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants;
31 import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
32 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeFormatter;
33 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeStatusCodes;
34 import org.openhab.core.library.types.DecimalType;
35 import org.openhab.core.library.types.OnOffType;
36 import org.openhab.core.library.types.PercentType;
37 import org.openhab.core.library.types.QuantityType;
38 import org.openhab.core.library.types.RawType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.types.State;
41 import org.openhab.core.types.UnDefType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link KaleidescapeMessageHandler} class processes all messages received
47 * by the event listener and then updates the appropriate states
49 * @author Michael Lobstein - Initial contribution
52 public enum KaleidescapeMessageHandler {
55 public void handleMessage(String message, KaleidescapeHandler handler) {
56 handler.updateChannel(KaleidescapeBindingConstants.UI_STATE, new StringType(message));
59 HIGHLIGHTED_SELECTION {
61 public void handleMessage(String message, KaleidescapeHandler handler) {
62 handler.updateChannel(KaleidescapeBindingConstants.HIGHLIGHTED_SELECTION, new StringType(message));
66 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
69 // power_state, zone 1 state, zone n state
70 private final Pattern p = Pattern.compile("^(\\d{1}):(.*)$");
73 public void handleMessage(String message, KaleidescapeHandler handler) {
74 Matcher matcher = p.matcher(message);
76 handler.updateChannel(POWER, (ONE).equals(matcher.group(1)) ? OnOffType.ON : OnOffType.OFF);
78 logger.debug("DEVICE_POWER_STATE - no match on message: {}", message);
84 public void handleMessage(String message, KaleidescapeHandler handler) {
85 handler.updateChannel(KaleidescapeBindingConstants.TITLE_NAME,
86 new StringType(KaleidescapeFormatter.formatString(message)));
90 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
92 // example: 0:0:00:00000:00000:000:00000:00000
93 // mode, speed, title_num, title_length, title_loc, chapter_num, chapter_length, chapter_loc
94 private final Pattern p = Pattern
95 .compile("^(\\d{1}):(\\d{1}):(\\d{2}):(\\d{5}):(\\d{5}):(\\d{3}):(\\d{5}):(\\d{5})$");
98 public void handleMessage(String message, KaleidescapeHandler handler) {
99 Matcher matcher = p.matcher(message);
100 if (matcher.find()) {
101 handler.updateChannel(PLAY_MODE,
102 new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
104 handler.updateChannel(PLAY_SPEED, new StringType(matcher.group(2)));
106 handler.updateChannel(TITLE_NUM, new DecimalType(Integer.parseInt(matcher.group(3))));
108 handler.updateChannel(TITLE_LENGTH,
109 new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
111 handler.updateChannel(TITLE_LOC,
112 new QuantityType<Time>(Integer.parseInt(matcher.group(5)), handler.apiSecondUnit));
114 handler.updateChannel(CHAPTER_NUM, new DecimalType(Integer.parseInt(matcher.group(6))));
116 handler.updateChannel(CHAPTER_LENGTH,
117 new QuantityType<Time>(Integer.parseInt(matcher.group(7)), handler.apiSecondUnit));
119 handler.updateChannel(CHAPTER_LOC,
120 new QuantityType<Time>(Integer.parseInt(matcher.group(8)), handler.apiSecondUnit));
122 logger.debug("PLAY_STATUS - no match on message: {}", message);
128 public void handleMessage(String message, KaleidescapeHandler handler) {
129 handler.updateChannel(KaleidescapeBindingConstants.MOVIE_MEDIA_TYPE,
130 new StringType(KaleidescapeStatusCodes.MEDIA_TYPE.get(message)));
135 public void handleMessage(String message, KaleidescapeHandler handler) {
136 handler.updateChannel(KaleidescapeBindingConstants.MOVIE_LOCATION,
137 new StringType(KaleidescapeStatusCodes.MOVIE_LOCATION.get(message)));
141 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
144 // composite, component, hdmi
145 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
148 public void handleMessage(String message, KaleidescapeHandler handler) {
149 handler.updateChannel(KaleidescapeBindingConstants.VIDEO_MODE, new StringType(message));
151 Matcher matcher = p.matcher(message);
152 if (matcher.find()) {
153 handler.updateChannel(VIDEO_MODE_COMPOSITE,
154 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(1))));
156 handler.updateChannel(VIDEO_MODE_COMPONENT,
157 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(2))));
159 handler.updateChannel(VIDEO_MODE_HDMI,
160 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(3))));
162 logger.debug("VIDEO_MODE - no match on message: {}", message);
167 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
169 // example: 02:01:24:01
170 // eotf, color_space, color_depth, color_sampling
171 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
174 public void handleMessage(String message, KaleidescapeHandler handler) {
175 handler.updateChannel(KaleidescapeBindingConstants.VIDEO_COLOR, new StringType(message));
177 Matcher matcher = p.matcher(message);
178 if (matcher.find()) {
179 handler.updateChannel(VIDEO_COLOR_EOTF,
180 new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
182 logger.debug("VIDEO_COLOR - no match on message: {}", message);
187 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
189 // example: 02:01:24:01
190 // eotf, color_space, color_depth, color_sampling
191 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
194 public void handleMessage(String message, KaleidescapeHandler handler) {
195 handler.updateChannel(KaleidescapeBindingConstants.CONTENT_COLOR, new StringType(message));
197 Matcher matcher = p.matcher(message);
198 if (matcher.find()) {
199 handler.updateChannel(CONTENT_COLOR_EOTF,
200 new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
202 logger.debug("CONTENT_COLOR - no match on message: {}", message);
208 public void handleMessage(String message, KaleidescapeHandler handler) {
209 handler.updateChannel(KaleidescapeBindingConstants.SCALE_MODE, new StringType(message));
214 public void handleMessage(String message, KaleidescapeHandler handler) {
215 handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK, new StringType(message));
217 // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
218 // the first element of SCREEN_MASK now provides this info
219 if (!message.equals(EMPTY)) {
220 String[] msgSplit = message.split(":", 2);
221 handler.updateChannel(KaleidescapeBindingConstants.ASPECT_RATIO,
222 new StringType(KaleidescapeStatusCodes.ASPECT_RATIO.get(msgSplit[0])));
228 public void handleMessage(String message, KaleidescapeHandler handler) {
229 handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK2, new StringType(message));
234 public void handleMessage(String message, KaleidescapeHandler handler) {
235 handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MASK, new StringType(message));
240 public void handleMessage(String message, KaleidescapeHandler handler) {
241 handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MODE, new StringType(message));
246 public void handleMessage(String message, KaleidescapeHandler handler) {
247 handler.updateChannel(KaleidescapeBindingConstants.CHILD_MODE_STATE, new StringType(message));
251 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
253 // example: You:Radiohead:Pablo Honey:1.9b5f4d786d7e2c49-t301_577:1.R_1493833:2.200c5
254 // track, artist, album, track handle, album handle, now playing handle
255 private final Pattern p = Pattern.compile("^(.*):(.*):(.*):(.*):(.*):(.*)$");
258 public void handleMessage(String message, KaleidescapeHandler handler) {
259 // first replace delimited : in track/artist/album name with ||, fix it later in formatString()
260 Matcher matcher = p.matcher(message.replace("\\:", "||"));
261 if (matcher.find()) {
262 handler.updateChannel(MUSIC_TRACK,
263 new StringType(KaleidescapeFormatter.formatString(matcher.group(1))));
265 handler.updateChannel(MUSIC_ARTIST,
266 new StringType(KaleidescapeFormatter.formatString(matcher.group(2))));
268 handler.updateChannel(MUSIC_ALBUM,
269 new StringType(KaleidescapeFormatter.formatString(matcher.group(3))));
271 handler.updateChannel(MUSIC_TRACK_HANDLE, new StringType(matcher.group(4)));
273 handler.updateChannel(MUSIC_ALBUM_HANDLE, new StringType(matcher.group(5)));
275 handler.updateChannel(MUSIC_NOWPLAY_HANDLE, new StringType(matcher.group(6)));
277 logger.debug("MUSIC_TITLE - no match on message: {}", message);
282 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
284 // example: 2:0:00207:+00000:000.00
285 // 2:0:00331:+00183:055.29
286 // mode, speed, track length, track position, track progress %
287 private final Pattern p = Pattern.compile("^(\\d{1}):(\\d{1}):(\\d{5}):(.\\d{5}):(\\d{3}\\.\\d{2})$");
290 public void handleMessage(String message, KaleidescapeHandler handler) {
291 Matcher matcher = p.matcher(message);
292 if (matcher.find()) {
293 handler.updateChannel(MUSIC_PLAY_MODE,
294 new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
296 handler.updateChannel(MUSIC_PLAY_SPEED, new StringType(matcher.group(2)));
298 handler.updateChannel(MUSIC_TRACK_LENGTH,
299 new QuantityType<Time>(Integer.parseInt(matcher.group(3)), handler.apiSecondUnit));
301 handler.updateChannel(MUSIC_TRACK_POSITION,
302 new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
304 handler.updateChannel(MUSIC_TRACK_PROGRESS,
305 new DecimalType(BigDecimal.valueOf(Math.round(Double.parseDouble(matcher.group(5))))));
307 logger.debug("MUSIC_PLAY_STATUS - no match on message: {}", message);
311 MUSIC_NOW_PLAYING_STATUS {
312 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
314 // example: 00013:00000:0:0:0000000238:2.200c5
315 // total # tracks in list, list index, repeat, random, generation, now_playing handle
316 // only using repeat & random right now
317 private final Pattern p = Pattern.compile("^(\\d{5}):(\\d{5}):(\\d{1}):(\\d{1}):(\\d{10}):(.*)$");
320 public void handleMessage(String message, KaleidescapeHandler handler) {
321 Matcher matcher = p.matcher(message);
322 if (matcher.find()) {
323 // update REPEAT switch state
324 handler.updateChannel(MUSIC_REPEAT, (ONE).equals(matcher.group(3)) ? OnOffType.ON : OnOffType.OFF);
326 // update RANDOM switch state
327 handler.updateChannel(MUSIC_RANDOM, (ONE).equals(matcher.group(4)) ? OnOffType.ON : OnOffType.OFF);
329 logger.debug("MUSIC_NOW_PLAYING_STATUS - no match on message: {}", message);
333 PLAYING_MUSIC_INFORMATION {
335 public void handleMessage(String message, KaleidescapeHandler handler) {
336 // example: R_1493833:Radiohead - Pablo Honey
337 // album handle, artist - album
338 // do nothing; redundant
342 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
344 // g1=meta id, g2=meta type, g3=data
345 // example: 6:Year:1995
346 // or: 10:Genres:Pop\/Rock
347 private final Pattern p = Pattern.compile("^(\\d{1,2}):([^:^/]*):(.*)$");
350 public void handleMessage(String message, KaleidescapeHandler handler) {
351 Matcher matcher = p.matcher(message);
352 if (matcher.find()) {
353 String metaType = matcher.group(2).toLowerCase();
354 String value = KaleidescapeFormatter.formatString(matcher.group(3));
356 // the CONTENT_DETAILS message with id=1 tells us what type of meta data is coming
357 if (ONE.equals(matcher.group(1))) {
358 if ((CONTENT_HANDLE).equals(metaType)) {
359 handler.updateDetailChannel(DETAIL_TYPE, new StringType(MOVIE));
360 handler.metaRuntimeMultiple = 60;
362 // null out album specific
363 handler.updateDetailChannel(DETAIL_ALBUM_TITLE, UnDefType.NULL);
364 handler.updateDetailChannel(DETAIL_ARTIST, UnDefType.NULL);
365 handler.updateDetailChannel(DETAIL_REVIEW, UnDefType.NULL);
367 } else if ((ALBUM_CONTENT_HANDLE).equals(metaType)) {
368 handler.updateDetailChannel(DETAIL_TYPE, new StringType(ALBUM));
369 handler.metaRuntimeMultiple = 1;
371 // null out movie specific
372 handler.updateDetailChannel(DETAIL_TITLE, UnDefType.NULL);
373 handler.updateDetailChannel(DETAIL_RATING, UnDefType.NULL);
374 handler.updateDetailChannel(DETAIL_ACTORS, UnDefType.NULL);
375 handler.updateDetailChannel(DETAIL_DIRECTORS, UnDefType.NULL);
376 handler.updateDetailChannel(DETAIL_RATING_REASON, UnDefType.NULL);
377 handler.updateDetailChannel(DETAIL_SYNOPSIS, UnDefType.NULL);
378 handler.updateDetailChannel(DETAIL_COLOR_DESCRIPTION, UnDefType.NULL);
379 handler.updateDetailChannel(DETAIL_COUNTRY, UnDefType.NULL);
380 handler.updateDetailChannel(DETAIL_ASPECT_RATIO, UnDefType.NULL);
383 handler.updateDetailChannel(DETAIL_TYPE, UnDefType.UNDEF);
385 // otherwise update the channel if it is one we care about
386 } else if (METADATA_CHANNELS.contains(metaType)) {
387 // special case for cover art image
388 if (DETAIL_COVER_URL.equals(metaType)) {
389 handler.updateDetailChannel(metaType, new StringType(value));
390 if (!value.isEmpty()) {
392 ContentResponse contentResponse = handler.httpClient.newRequest(value).method(GET)
393 .timeout(10, TimeUnit.SECONDS).send();
394 int httpStatus = contentResponse.getStatus();
395 if (httpStatus == OK_200) {
396 handler.updateDetailChannel(DETAIL_COVER_ART,
397 new RawType(contentResponse.getContent(), RawType.DEFAULT_MIME_TYPE));
399 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
401 } catch (InterruptedException | TimeoutException | ExecutionException e) {
402 logger.debug("Error updating Cover Art Image channel for url: {}", value);
403 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
406 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
408 // special case for running time to create a QuantityType<Time>
409 } else if (DETAIL_RUNNING_TIME.equals(metaType)) {
410 handler.updateDetailChannel(DETAIL_RUNNING_TIME, new QuantityType<Time>(
411 Integer.parseInt(value) * handler.metaRuntimeMultiple, handler.apiSecondUnit));
412 // everything else just send it as a string
414 handler.updateDetailChannel(metaType, new StringType(value));
418 logger.debug("CONTENT_DETAILS - no match on message: {}", message);
424 public void handleMessage(String message, KaleidescapeHandler handler) {
430 public void handleMessage(String message, KaleidescapeHandler handler) {
436 public void handleMessage(String message, KaleidescapeHandler handler) {
437 // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
438 // the first element of SCREEN_MASK now provides this info
442 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
445 public void handleMessage(String message, KaleidescapeHandler handler) {
446 // example: SELECT_KALEIDESCAPE_INPUT
449 // when the ipad or phone app is started up, it sends a VOLUME_QUERY,
450 // so we respond to enable volume controls and set the initial volume and mute
452 if (handler.volumeEnabled) {
453 synchronized (handler.sequenceLock) {
454 handler.connector.sendCommand(SEND_EVENT_VOLUME_CAPABILITIES_15);
455 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
456 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
461 if (handler.volumeEnabled) {
462 synchronized (handler.sequenceLock) {
464 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
465 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
470 if (handler.volumeEnabled) {
471 synchronized (handler.sequenceLock) {
473 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
474 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
479 if (handler.volumeEnabled) {
480 State state = UnDefType.UNDEF;
481 synchronized (handler.sequenceLock) {
482 if (handler.isMuted) {
483 state = OnOffType.OFF;
484 handler.isMuted = false;
486 state = OnOffType.ON;
487 handler.isMuted = true;
489 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
490 handler.updateChannel(MUTE, state);
494 // the default is to just publish all other USER_DEFINED_EVENTs
496 handler.updateChannel(KaleidescapeBindingConstants.USER_DEFINED_EVENT, new StringType(message));
498 } catch (KaleidescapeException e) {
499 logger.debug("USER_DEFINED_EVENT - exception on message: {}", message);
505 public void handleMessage(String message, KaleidescapeHandler handler) {
506 // example: 01:Search for title:ABC
507 handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
512 public void handleMessage(String message, KaleidescapeHandler handler) {
513 // example: 00:00::00:0:1
514 handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
517 SYSTEM_READINESS_STATE {
519 public void handleMessage(String message, KaleidescapeHandler handler) {
521 handler.updateChannel(KaleidescapeBindingConstants.SYSTEM_READINESS_STATE,
522 new StringType(KaleidescapeStatusCodes.READINESS_STATE.get(message)));
526 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
528 // example: 16:8.6.0-21023
529 // protocol version, kOS version
530 private final Pattern p = Pattern.compile("^(\\d{2}):(.*)$");
533 public void handleMessage(String message, KaleidescapeHandler handler) {
534 Matcher matcher = p.matcher(message);
535 if (matcher.find()) {
536 handler.updateThingProperty(PROPERTY_PROTOCOL_VERSION, matcher.group(1));
537 handler.updateThingProperty(PROPERTY_SYSTEM_VERSION, matcher.group(2));
539 logger.debug("SYSTEM_VERSION - no match on message: {}", message);
544 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
546 // example: 07:000000000000558F:00:192.168.001.100
547 // device type (deprecated), serial number, cpdid, ip address
548 private final Pattern p = Pattern.compile("^(\\d{2}):(.*):(\\d{2}):(.*)$");
551 public void handleMessage(String message, KaleidescapeHandler handler) {
552 Matcher matcher = p.matcher(message);
553 if (matcher.find()) {
554 // replaceFirst takes off leading zeros
555 handler.updateThingProperty(PROPERTY_SERIAL_NUMBER, matcher.group(2).replaceFirst("^0+(?!$)", EMPTY));
556 handler.updateThingProperty(PROPERTY_CONTROL_PROTOCOL_ID, matcher.group(3));
558 logger.debug("DEVICE_INFO - no match on message: {}", message);
564 public void handleMessage(String message, KaleidescapeHandler handler) {
565 // example: 'Player' or 'Strato'
566 handler.updateThingProperty(PROPERTY_COMPONENT_TYPE, message);
571 public void handleMessage(String message, KaleidescapeHandler handler) {
572 // example: 'Living Room'
573 handler.friendlyName = message;
574 handler.updateThingProperty(PROPERTY_FRIENDLY_NAME, message);
578 public abstract void handleMessage(String message, KaleidescapeHandler handler);