2 * Copyright (c) 2010-2024 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.PlayPauseType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.RawType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.types.State;
42 import org.openhab.core.types.UnDefType;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * The {@link KaleidescapeMessageHandler} class processes all messages received
48 * by the event listener and then updates the appropriate states
50 * @author Michael Lobstein - Initial contribution
53 public enum KaleidescapeMessageHandler {
56 public void handleMessage(String message, KaleidescapeHandler handler) {
57 handler.updateChannel(KaleidescapeBindingConstants.UI_STATE, new StringType(message));
60 HIGHLIGHTED_SELECTION {
61 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
64 public void handleMessage(String message, KaleidescapeHandler handler) {
65 handler.updateChannel(KaleidescapeBindingConstants.HIGHLIGHTED_SELECTION, new StringType(message));
67 if (handler.isLoadHighlightedDetails) {
69 handler.connector.sendCommand(GET_CONTENT_DETAILS + message + ":");
70 } catch (KaleidescapeException e) {
71 logger.debug("GET_CONTENT_DETAILS - exception loading content details for handle: {}", message);
77 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
80 // power_state, zone 1 state, zone n state
81 private final Pattern p = Pattern.compile("^(\\d{1}):(.*)$");
84 public void handleMessage(String message, KaleidescapeHandler handler) {
85 Matcher matcher = p.matcher(message);
87 handler.updateChannel(POWER, OnOffType.from(ONE.equals(matcher.group(1))));
89 logger.debug("DEVICE_POWER_STATE - no match on message: {}", message);
95 public void handleMessage(String message, KaleidescapeHandler handler) {
96 handler.updateChannel(KaleidescapeBindingConstants.TITLE_NAME,
97 new StringType(KaleidescapeFormatter.formatString(message)));
101 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
103 // example: 0:0:00:00000:00000:000:00000:00000
104 // mode, speed, title_num, title_length, title_loc, chapter_num, chapter_length, chapter_loc
105 private final Pattern p = Pattern
106 .compile("^(\\d{1}):(\\d{1}):(\\d{2}):(\\d{5}):(\\d{5}):(\\d{3}):(\\d{5}):(\\d{5})$");
109 public void handleMessage(String message, KaleidescapeHandler handler) {
110 Matcher matcher = p.matcher(message);
111 if (matcher.find()) {
112 handler.updateChannel(PLAY_MODE,
113 new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
115 handler.updateChannel(CONTROL, "2".equals(matcher.group(1)) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
117 handler.updateChannel(PLAY_SPEED, new StringType(matcher.group(2)));
119 handler.updateChannel(TITLE_NUM, new DecimalType(Integer.parseInt(matcher.group(3))));
121 handler.updateChannel(TITLE_LENGTH,
122 new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
124 handler.updateChannel(TITLE_LOC,
125 new QuantityType<Time>(Integer.parseInt(matcher.group(5)), handler.apiSecondUnit));
127 handler.updateChannel(CHAPTER_NUM, new DecimalType(Integer.parseInt(matcher.group(6))));
129 handler.updateChannel(CHAPTER_LENGTH,
130 new QuantityType<Time>(Integer.parseInt(matcher.group(7)), handler.apiSecondUnit));
132 handler.updateChannel(CHAPTER_LOC,
133 new QuantityType<Time>(Integer.parseInt(matcher.group(8)), handler.apiSecondUnit));
135 logger.debug("PLAY_STATUS - no match on message: {}", message);
141 public void handleMessage(String message, KaleidescapeHandler handler) {
142 handler.updateChannel(KaleidescapeBindingConstants.MOVIE_MEDIA_TYPE,
143 new StringType(KaleidescapeStatusCodes.MEDIA_TYPE.get(message)));
148 public void handleMessage(String message, KaleidescapeHandler handler) {
149 handler.updateChannel(KaleidescapeBindingConstants.MOVIE_LOCATION,
150 new StringType(KaleidescapeStatusCodes.MOVIE_LOCATION.get(message)));
154 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
157 // composite, component, hdmi
158 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
161 public void handleMessage(String message, KaleidescapeHandler handler) {
162 handler.updateChannel(KaleidescapeBindingConstants.VIDEO_MODE, new StringType(message));
164 Matcher matcher = p.matcher(message);
165 if (matcher.find()) {
166 handler.updateChannel(VIDEO_MODE_COMPOSITE,
167 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(1))));
169 handler.updateChannel(VIDEO_MODE_COMPONENT,
170 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(2))));
172 handler.updateChannel(VIDEO_MODE_HDMI,
173 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(3))));
175 logger.debug("VIDEO_MODE - no match on message: {}", message);
180 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
182 // example: 02:01:24:01
183 // eotf, color_space, color_depth, color_sampling
184 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
187 public void handleMessage(String message, KaleidescapeHandler handler) {
188 handler.updateChannel(KaleidescapeBindingConstants.VIDEO_COLOR, new StringType(message));
190 Matcher matcher = p.matcher(message);
191 if (matcher.find()) {
192 handler.updateChannel(VIDEO_COLOR_EOTF,
193 new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
195 logger.debug("VIDEO_COLOR - no match on message: {}", message);
200 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
202 // example: 02:01:24:01
203 // eotf, color_space, color_depth, color_sampling
204 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
207 public void handleMessage(String message, KaleidescapeHandler handler) {
208 handler.updateChannel(KaleidescapeBindingConstants.CONTENT_COLOR, new StringType(message));
210 Matcher matcher = p.matcher(message);
211 if (matcher.find()) {
212 handler.updateChannel(CONTENT_COLOR_EOTF,
213 new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
215 logger.debug("CONTENT_COLOR - no match on message: {}", message);
221 public void handleMessage(String message, KaleidescapeHandler handler) {
222 handler.updateChannel(KaleidescapeBindingConstants.SCALE_MODE, new StringType(message));
227 public void handleMessage(String message, KaleidescapeHandler handler) {
228 handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK, new StringType(message));
230 // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
231 // the first element of SCREEN_MASK now provides this info
232 if (!message.equals(EMPTY)) {
233 String[] msgSplit = message.split(":", 2);
234 handler.updateChannel(KaleidescapeBindingConstants.ASPECT_RATIO,
235 new StringType(KaleidescapeStatusCodes.ASPECT_RATIO.get(msgSplit[0])));
241 public void handleMessage(String message, KaleidescapeHandler handler) {
242 handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK2, new StringType(message));
247 public void handleMessage(String message, KaleidescapeHandler handler) {
248 handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MASK, new StringType(message));
253 public void handleMessage(String message, KaleidescapeHandler handler) {
254 handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MODE, new StringType(message));
259 public void handleMessage(String message, KaleidescapeHandler handler) {
260 handler.updateChannel(KaleidescapeBindingConstants.CHILD_MODE_STATE, new StringType(message));
264 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
266 // example: You:Radiohead:Pablo Honey:1.9b5f4d786d7e2c49-t301_577:1.R_1493833:2.200c5
267 // track, artist, album, track handle, album handle, now playing handle
268 private final Pattern p = Pattern.compile("^(.*):(.*):(.*):(.*):(.*):(.*)$");
271 public void handleMessage(String message, KaleidescapeHandler handler) {
272 // first replace delimited : in track/artist/album name with ||, fix it later in formatString()
273 Matcher matcher = p.matcher(message.replace("\\:", "||"));
274 if (matcher.find()) {
275 handler.updateChannel(MUSIC_TRACK,
276 new StringType(KaleidescapeFormatter.formatString(matcher.group(1))));
278 handler.updateChannel(MUSIC_ARTIST,
279 new StringType(KaleidescapeFormatter.formatString(matcher.group(2))));
281 handler.updateChannel(MUSIC_ALBUM,
282 new StringType(KaleidescapeFormatter.formatString(matcher.group(3))));
284 handler.updateChannel(MUSIC_TRACK_HANDLE, new StringType(matcher.group(4)));
286 handler.updateChannel(MUSIC_ALBUM_HANDLE, new StringType(matcher.group(5)));
288 handler.updateChannel(MUSIC_NOWPLAY_HANDLE, new StringType(matcher.group(6)));
290 if (handler.isLoadAlbumDetails) {
292 handler.connector.sendCommand(GET_CONTENT_DETAILS + matcher.group(5) + ":");
293 } catch (KaleidescapeException e) {
294 logger.debug("GET_CONTENT_DETAILS - exception loading album details for handle: {}",
299 logger.debug("MUSIC_TITLE - no match on message: {}", message);
304 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
306 // example: 2:0:00207:+00000:000.00
307 // 2:0:00331:+00183:055.29
308 // mode, speed, track length, track position, track progress %
309 private final Pattern p = Pattern.compile("^(\\d{1}):(\\d{1}):(\\d{5}):(.\\d{5}):(\\d{3}\\.\\d{2})$");
312 public void handleMessage(String message, KaleidescapeHandler handler) {
313 Matcher matcher = p.matcher(message);
314 if (matcher.find()) {
315 handler.updateChannel(MUSIC_PLAY_MODE,
316 new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
318 handler.updateChannel(MUSIC_CONTROL,
319 "2".equals(matcher.group(1)) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
321 handler.updateChannel(MUSIC_PLAY_SPEED, new StringType(matcher.group(2)));
323 handler.updateChannel(MUSIC_TRACK_LENGTH,
324 new QuantityType<Time>(Integer.parseInt(matcher.group(3)), handler.apiSecondUnit));
326 handler.updateChannel(MUSIC_TRACK_POSITION,
327 new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
329 handler.updateChannel(MUSIC_TRACK_PROGRESS,
330 new DecimalType(BigDecimal.valueOf(Math.round(Double.parseDouble(matcher.group(5))))));
332 logger.debug("MUSIC_PLAY_STATUS - no match on message: {}", message);
336 MUSIC_NOW_PLAYING_STATUS {
337 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
339 // example: 00013:00000:0:0:0000000238:2.200c5
340 // total # tracks in list, list index, repeat, random, generation, now_playing handle
341 // only using repeat & random right now
342 private final Pattern p = Pattern.compile("^(\\d{5}):(\\d{5}):(\\d{1}):(\\d{1}):(\\d{10}):(.*)$");
345 public void handleMessage(String message, KaleidescapeHandler handler) {
346 Matcher matcher = p.matcher(message);
347 if (matcher.find()) {
348 // update REPEAT switch state
349 handler.updateChannel(MUSIC_REPEAT, OnOffType.from(ONE.equals(matcher.group(3))));
351 // update RANDOM switch state
352 handler.updateChannel(MUSIC_RANDOM, OnOffType.from(ONE.equals(matcher.group(4))));
354 logger.debug("MUSIC_NOW_PLAYING_STATUS - no match on message: {}", message);
358 PLAYING_MUSIC_INFORMATION {
360 public void handleMessage(String message, KaleidescapeHandler handler) {
361 // example: R_1493833:Radiohead - Pablo Honey
362 // album handle, artist - album
363 // do nothing; redundant
367 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
369 // g1=meta id, g2=meta type, g3=data
370 // example: 6:Year:1995
371 // or: 10:Genres:Pop\/Rock
372 private final Pattern p = Pattern.compile("^(\\d{1,2}):([^:^/]*):(.*)$");
375 public void handleMessage(String message, KaleidescapeHandler handler) {
376 Matcher matcher = p.matcher(message);
377 if (matcher.find()) {
378 String metaType = matcher.group(2).toLowerCase();
379 String value = KaleidescapeFormatter.formatString(matcher.group(3));
381 // the CONTENT_DETAILS message with id=1 tells us what type of meta data is coming
382 if (ONE.equals(matcher.group(1))) {
383 if ((CONTENT_HANDLE).equals(metaType)) {
384 handler.updateDetailChannel(DETAIL_TYPE, new StringType(MOVIE));
385 handler.metaRuntimeMultiple = 60;
387 // null out album specific
388 handler.updateDetailChannel(DETAIL_ALBUM_TITLE, UnDefType.NULL);
389 handler.updateDetailChannel(DETAIL_ARTIST, UnDefType.NULL);
390 handler.updateDetailChannel(DETAIL_REVIEW, UnDefType.NULL);
392 } else if ((ALBUM_CONTENT_HANDLE).equals(metaType)) {
393 handler.updateDetailChannel(DETAIL_TYPE, new StringType(ALBUM));
394 handler.metaRuntimeMultiple = 1;
396 // null out movie specific
397 handler.updateDetailChannel(DETAIL_TITLE, UnDefType.NULL);
398 handler.updateDetailChannel(DETAIL_RATING, UnDefType.NULL);
399 handler.updateDetailChannel(DETAIL_ACTORS, UnDefType.NULL);
400 handler.updateDetailChannel(DETAIL_DIRECTORS, UnDefType.NULL);
401 handler.updateDetailChannel(DETAIL_RATING_REASON, UnDefType.NULL);
402 handler.updateDetailChannel(DETAIL_SYNOPSIS, UnDefType.NULL);
403 handler.updateDetailChannel(DETAIL_COLOR_DESCRIPTION, UnDefType.NULL);
404 handler.updateDetailChannel(DETAIL_COUNTRY, UnDefType.NULL);
405 handler.updateDetailChannel(DETAIL_ASPECT_RATIO, UnDefType.NULL);
408 handler.updateDetailChannel(DETAIL_TYPE, UnDefType.UNDEF);
410 // otherwise update the channel if it is one we care about
411 } else if (METADATA_CHANNELS.contains(metaType)) {
412 // special case for cover art image
413 if (DETAIL_COVER_URL.equals(metaType)) {
414 handler.updateDetailChannel(metaType, new StringType(value));
415 if (!value.isEmpty() && handler.isChannelLinked(DETAIL + DETAIL_COVER_ART)) {
417 ContentResponse contentResponse = handler.httpClient.newRequest(value).method(GET)
418 .timeout(10, TimeUnit.SECONDS).send();
419 int httpStatus = contentResponse.getStatus();
420 if (httpStatus == OK_200) {
421 handler.updateDetailChannel(DETAIL_COVER_ART,
422 new RawType(contentResponse.getContent(), "image/jpeg"));
424 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
426 } catch (InterruptedException | TimeoutException | ExecutionException e) {
427 logger.debug("Error updating Cover Art Image channel for url: {}", value);
428 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
431 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
433 // special case for running time to create a QuantityType<Time>
434 } else if (DETAIL_RUNNING_TIME.equals(metaType)) {
435 handler.updateDetailChannel(DETAIL_RUNNING_TIME, new QuantityType<Time>(
436 Integer.parseInt(value) * handler.metaRuntimeMultiple, handler.apiSecondUnit));
437 // everything else just send it as a string
439 handler.updateDetailChannel(metaType, new StringType(value));
443 logger.debug("CONTENT_DETAILS - no match on message: {}", message);
449 public void handleMessage(String message, KaleidescapeHandler handler) {
455 public void handleMessage(String message, KaleidescapeHandler handler) {
461 public void handleMessage(String message, KaleidescapeHandler handler) {
462 // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
463 // the first element of SCREEN_MASK now provides this info
467 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
470 public void handleMessage(String message, KaleidescapeHandler handler) {
471 // example: SELECT_KALEIDESCAPE_INPUT
474 // when the ipad or phone app is started up, it sends a VOLUME_QUERY,
475 // so we respond to enable volume controls and set the initial volume and mute
477 if (handler.volumeEnabled) {
478 synchronized (handler.sequenceLock) {
479 handler.connector.sendCommand(SEND_EVENT_VOLUME_CAPABILITIES_15);
480 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
481 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
486 if (handler.volumeEnabled) {
487 synchronized (handler.sequenceLock) {
489 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
490 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
495 if (handler.volumeEnabled) {
496 synchronized (handler.sequenceLock) {
498 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
499 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
504 if (handler.volumeEnabled) {
505 State state = UnDefType.UNDEF;
506 synchronized (handler.sequenceLock) {
507 if (handler.isMuted) {
508 state = OnOffType.OFF;
509 handler.isMuted = false;
511 state = OnOffType.ON;
512 handler.isMuted = true;
514 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
515 handler.updateChannel(MUTE, state);
519 // the default is to just publish all other USER_DEFINED_EVENTs
521 handler.updateChannel(KaleidescapeBindingConstants.USER_DEFINED_EVENT, new StringType(message));
523 } catch (KaleidescapeException e) {
524 logger.debug("USER_DEFINED_EVENT - exception on message: {}", message);
530 public void handleMessage(String message, KaleidescapeHandler handler) {
531 // example: 01:Search for title:ABC
532 handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
537 public void handleMessage(String message, KaleidescapeHandler handler) {
538 // example: 00:00::00:0:1
539 handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
542 SYSTEM_READINESS_STATE {
544 public void handleMessage(String message, KaleidescapeHandler handler) {
546 handler.updateChannel(KaleidescapeBindingConstants.SYSTEM_READINESS_STATE,
547 new StringType(KaleidescapeStatusCodes.READINESS_STATE.get(message)));
551 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
553 // example: 16:8.6.0-21023
554 // protocol version, kOS version
555 private final Pattern p = Pattern.compile("^(\\d{2}):(.*)$");
558 public void handleMessage(String message, KaleidescapeHandler handler) {
559 Matcher matcher = p.matcher(message);
560 if (matcher.find()) {
561 handler.updateThingProperty(PROPERTY_PROTOCOL_VERSION, matcher.group(1));
562 handler.updateThingProperty(PROPERTY_SYSTEM_VERSION, matcher.group(2));
564 logger.debug("SYSTEM_VERSION - no match on message: {}", message);
569 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
571 // example: 07:000000000000558F:00:192.168.001.100
572 // device type (deprecated), serial number, cpdid, ip address
573 private final Pattern p = Pattern.compile("^(\\d{2}):(.*):(\\d{2}):(.*)$");
576 public void handleMessage(String message, KaleidescapeHandler handler) {
577 Matcher matcher = p.matcher(message);
578 if (matcher.find()) {
579 // replaceFirst takes off leading zeros
580 handler.updateThingProperty(PROPERTY_SERIAL_NUMBER, matcher.group(2).replaceFirst("^0+(?!$)", EMPTY));
581 handler.updateThingProperty(PROPERTY_CONTROL_PROTOCOL_ID, matcher.group(3));
583 logger.debug("DEVICE_INFO - no match on message: {}", message);
589 public void handleMessage(String message, KaleidescapeHandler handler) {
590 // example: 'Player' or 'Strato'
591 handler.updateThingProperty(PROPERTY_COMPONENT_TYPE, message);
596 public void handleMessage(String message, KaleidescapeHandler handler) {
597 // example: 'Living Room'
598 handler.friendlyName = message;
599 handler.updateThingProperty(PROPERTY_FRIENDLY_NAME, message);
603 public abstract void handleMessage(String message, KaleidescapeHandler handler);