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 org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jetty.client.api.ContentResponse;
28 import org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants;
29 import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
30 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeFormatter;
31 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeStatusCodes;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.PercentType;
35 import org.openhab.core.library.types.PlayPauseType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.library.types.RawType;
38 import org.openhab.core.library.types.StringType;
39 import org.openhab.core.types.State;
40 import org.openhab.core.types.UnDefType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link KaleidescapeMessageHandler} class processes all messages received
46 * by the event listener and then updates the appropriate states
48 * @author Michael Lobstein - Initial contribution
51 public enum KaleidescapeMessageHandler {
54 public void handleMessage(String message, KaleidescapeHandler handler) {
55 handler.updateChannel(KaleidescapeBindingConstants.UI_STATE, new StringType(message));
58 HIGHLIGHTED_SELECTION {
59 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
62 public void handleMessage(String message, KaleidescapeHandler handler) {
63 handler.updateChannel(KaleidescapeBindingConstants.HIGHLIGHTED_SELECTION, new StringType(message));
65 if (handler.isLoadHighlightedDetails) {
67 handler.connector.sendCommand(GET_CONTENT_DETAILS + message + ":");
68 } catch (KaleidescapeException e) {
69 logger.debug("GET_CONTENT_DETAILS - exception loading content details for handle: {}", message);
75 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
78 // power_state, zone 1 state, zone n state
79 private final Pattern p = Pattern.compile("^(\\d{1}):(.*)$");
82 public void handleMessage(String message, KaleidescapeHandler handler) {
83 Matcher matcher = p.matcher(message);
85 handler.updateChannel(POWER, OnOffType.from(ONE.equals(matcher.group(1))));
87 logger.debug("DEVICE_POWER_STATE - no match on message: {}", message);
93 public void handleMessage(String message, KaleidescapeHandler handler) {
94 handler.updateChannel(KaleidescapeBindingConstants.TITLE_NAME,
95 new StringType(KaleidescapeFormatter.formatString(message)));
99 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
101 // example: 0:0:00:00000:00000:000:00000:00000
102 // mode, speed, title_num, title_length, title_loc, chapter_num, chapter_length, chapter_loc
103 private final Pattern p = Pattern
104 .compile("^(\\d{1}):(\\d{1}):(\\d{2}):(\\d{5}):(\\d{5}):(\\d{3}):(\\d{5}):(\\d{5})$");
107 public void handleMessage(String message, KaleidescapeHandler handler) {
108 Matcher matcher = p.matcher(message);
109 if (matcher.find()) {
110 handler.updateChannel(PLAY_MODE,
111 new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
113 handler.updateChannel(CONTROL, "2".equals(matcher.group(1)) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
115 handler.updateChannel(PLAY_SPEED, new StringType(matcher.group(2)));
117 handler.updateChannel(TITLE_NUM, new DecimalType(Integer.parseInt(matcher.group(3))));
119 handler.updateChannel(TITLE_LENGTH,
120 new QuantityType<>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
122 handler.updateChannel(TITLE_LOC,
123 new QuantityType<>(Integer.parseInt(matcher.group(5)), handler.apiSecondUnit));
125 handler.updateChannel(CHAPTER_NUM, new DecimalType(Integer.parseInt(matcher.group(6))));
127 handler.updateChannel(CHAPTER_LENGTH,
128 new QuantityType<>(Integer.parseInt(matcher.group(7)), handler.apiSecondUnit));
130 handler.updateChannel(CHAPTER_LOC,
131 new QuantityType<>(Integer.parseInt(matcher.group(8)), handler.apiSecondUnit));
133 logger.debug("PLAY_STATUS - no match on message: {}", message);
139 public void handleMessage(String message, KaleidescapeHandler handler) {
140 handler.updateChannel(KaleidescapeBindingConstants.MOVIE_MEDIA_TYPE,
141 new StringType(KaleidescapeStatusCodes.MEDIA_TYPE.get(message)));
146 public void handleMessage(String message, KaleidescapeHandler handler) {
147 handler.updateChannel(KaleidescapeBindingConstants.MOVIE_LOCATION,
148 new StringType(KaleidescapeStatusCodes.MOVIE_LOCATION.get(message)));
152 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
155 // composite, component, hdmi
156 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
159 public void handleMessage(String message, KaleidescapeHandler handler) {
160 handler.updateChannel(KaleidescapeBindingConstants.VIDEO_MODE, new StringType(message));
162 Matcher matcher = p.matcher(message);
163 if (matcher.find()) {
164 handler.updateChannel(VIDEO_MODE_COMPOSITE,
165 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(1))));
167 handler.updateChannel(VIDEO_MODE_COMPONENT,
168 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(2))));
170 handler.updateChannel(VIDEO_MODE_HDMI,
171 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(3))));
173 logger.debug("VIDEO_MODE - no match on message: {}", message);
178 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
180 // example: 02:01:24:01
181 // eotf, color_space, color_depth, color_sampling
182 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
185 public void handleMessage(String message, KaleidescapeHandler handler) {
186 handler.updateChannel(KaleidescapeBindingConstants.VIDEO_COLOR, new StringType(message));
188 Matcher matcher = p.matcher(message);
189 if (matcher.find()) {
190 handler.updateChannel(VIDEO_COLOR_EOTF,
191 new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
193 logger.debug("VIDEO_COLOR - no match on message: {}", message);
198 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
200 // example: 02:01:24:01
201 // eotf, color_space, color_depth, color_sampling
202 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
205 public void handleMessage(String message, KaleidescapeHandler handler) {
206 handler.updateChannel(KaleidescapeBindingConstants.CONTENT_COLOR, new StringType(message));
208 Matcher matcher = p.matcher(message);
209 if (matcher.find()) {
210 handler.updateChannel(CONTENT_COLOR_EOTF,
211 new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
213 logger.debug("CONTENT_COLOR - no match on message: {}", message);
219 public void handleMessage(String message, KaleidescapeHandler handler) {
220 handler.updateChannel(KaleidescapeBindingConstants.SCALE_MODE, new StringType(message));
225 public void handleMessage(String message, KaleidescapeHandler handler) {
226 handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK, new StringType(message));
228 // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
229 // the first element of SCREEN_MASK now provides this info
230 if (!message.equals(EMPTY)) {
231 String[] msgSplit = message.split(":", 2);
232 handler.updateChannel(KaleidescapeBindingConstants.ASPECT_RATIO,
233 new StringType(KaleidescapeStatusCodes.ASPECT_RATIO.get(msgSplit[0])));
239 public void handleMessage(String message, KaleidescapeHandler handler) {
240 handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK2, new StringType(message));
245 public void handleMessage(String message, KaleidescapeHandler handler) {
246 handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MASK, new StringType(message));
251 public void handleMessage(String message, KaleidescapeHandler handler) {
252 handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MODE, new StringType(message));
257 public void handleMessage(String message, KaleidescapeHandler handler) {
258 handler.updateChannel(KaleidescapeBindingConstants.CHILD_MODE_STATE, new StringType(message));
262 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
264 // example: You:Radiohead:Pablo Honey:1.9b5f4d786d7e2c49-t301_577:1.R_1493833:2.200c5
265 // track, artist, album, track handle, album handle, now playing handle
266 private final Pattern p = Pattern.compile("^(.*):(.*):(.*):(.*):(.*):(.*)$");
269 public void handleMessage(String message, KaleidescapeHandler handler) {
270 // first replace delimited : in track/artist/album name with ||, fix it later in formatString()
271 Matcher matcher = p.matcher(message.replace("\\:", "||"));
272 if (matcher.find()) {
273 handler.updateChannel(MUSIC_TRACK,
274 new StringType(KaleidescapeFormatter.formatString(matcher.group(1))));
276 handler.updateChannel(MUSIC_ARTIST,
277 new StringType(KaleidescapeFormatter.formatString(matcher.group(2))));
279 handler.updateChannel(MUSIC_ALBUM,
280 new StringType(KaleidescapeFormatter.formatString(matcher.group(3))));
282 handler.updateChannel(MUSIC_TRACK_HANDLE, new StringType(matcher.group(4)));
284 handler.updateChannel(MUSIC_ALBUM_HANDLE, new StringType(matcher.group(5)));
286 handler.updateChannel(MUSIC_NOWPLAY_HANDLE, new StringType(matcher.group(6)));
288 if (handler.isLoadAlbumDetails) {
290 handler.connector.sendCommand(GET_CONTENT_DETAILS + matcher.group(5) + ":");
291 } catch (KaleidescapeException e) {
292 logger.debug("GET_CONTENT_DETAILS - exception loading album details for handle: {}",
297 logger.debug("MUSIC_TITLE - no match on message: {}", message);
302 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
304 // example: 2:0:00207:+00000:000.00
305 // 2:0:00331:+00183:055.29
306 // mode, speed, track length, track position, track progress %
307 private final Pattern p = Pattern.compile("^(\\d{1}):(\\d{1}):(\\d{5}):(.\\d{5}):(\\d{3}\\.\\d{2})$");
310 public void handleMessage(String message, KaleidescapeHandler handler) {
311 Matcher matcher = p.matcher(message);
312 if (matcher.find()) {
313 handler.updateChannel(MUSIC_PLAY_MODE,
314 new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
316 handler.updateChannel(MUSIC_CONTROL,
317 "2".equals(matcher.group(1)) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
319 handler.updateChannel(MUSIC_PLAY_SPEED, new StringType(matcher.group(2)));
321 handler.updateChannel(MUSIC_TRACK_LENGTH,
322 new QuantityType<>(Integer.parseInt(matcher.group(3)), handler.apiSecondUnit));
324 handler.updateChannel(MUSIC_TRACK_POSITION,
325 new QuantityType<>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
327 handler.updateChannel(MUSIC_TRACK_PROGRESS,
328 new DecimalType(BigDecimal.valueOf(Math.round(Double.parseDouble(matcher.group(5))))));
330 logger.debug("MUSIC_PLAY_STATUS - no match on message: {}", message);
334 MUSIC_NOW_PLAYING_STATUS {
335 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
337 // example: 00013:00000:0:0:0000000238:2.200c5
338 // total # tracks in list, list index, repeat, random, generation, now_playing handle
339 // only using repeat & random right now
340 private final Pattern p = Pattern.compile("^(\\d{5}):(\\d{5}):(\\d{1}):(\\d{1}):(\\d{10}):(.*)$");
343 public void handleMessage(String message, KaleidescapeHandler handler) {
344 Matcher matcher = p.matcher(message);
345 if (matcher.find()) {
346 // update REPEAT switch state
347 handler.updateChannel(MUSIC_REPEAT, OnOffType.from(ONE.equals(matcher.group(3))));
349 // update RANDOM switch state
350 handler.updateChannel(MUSIC_RANDOM, OnOffType.from(ONE.equals(matcher.group(4))));
352 logger.debug("MUSIC_NOW_PLAYING_STATUS - no match on message: {}", message);
356 PLAYING_MUSIC_INFORMATION {
358 public void handleMessage(String message, KaleidescapeHandler handler) {
359 // example: R_1493833:Radiohead - Pablo Honey
360 // album handle, artist - album
361 // do nothing; redundant
365 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
367 // g1=meta id, g2=meta type, g3=data
368 // example: 6:Year:1995
369 // or: 10:Genres:Pop\/Rock
370 private final Pattern p = Pattern.compile("^(\\d{1,2}):([^:^/]*):(.*)$");
373 public void handleMessage(String message, KaleidescapeHandler handler) {
374 Matcher matcher = p.matcher(message);
375 if (matcher.find()) {
376 String metaType = matcher.group(2).toLowerCase();
377 String value = KaleidescapeFormatter.formatString(matcher.group(3));
379 // the CONTENT_DETAILS message with id=1 tells us what type of meta data is coming
380 if (ONE.equals(matcher.group(1))) {
381 if ((CONTENT_HANDLE).equals(metaType)) {
382 handler.updateDetailChannel(DETAIL_TYPE, new StringType(MOVIE));
383 handler.metaRuntimeMultiple = 60;
385 // null out album specific
386 handler.updateDetailChannel(DETAIL_ALBUM_TITLE, UnDefType.NULL);
387 handler.updateDetailChannel(DETAIL_ARTIST, UnDefType.NULL);
388 handler.updateDetailChannel(DETAIL_REVIEW, UnDefType.NULL);
390 } else if ((ALBUM_CONTENT_HANDLE).equals(metaType)) {
391 handler.updateDetailChannel(DETAIL_TYPE, new StringType(ALBUM));
392 handler.metaRuntimeMultiple = 1;
394 // null out movie specific
395 handler.updateDetailChannel(DETAIL_TITLE, UnDefType.NULL);
396 handler.updateDetailChannel(DETAIL_RATING, UnDefType.NULL);
397 handler.updateDetailChannel(DETAIL_ACTORS, UnDefType.NULL);
398 handler.updateDetailChannel(DETAIL_DIRECTORS, UnDefType.NULL);
399 handler.updateDetailChannel(DETAIL_RATING_REASON, UnDefType.NULL);
400 handler.updateDetailChannel(DETAIL_SYNOPSIS, UnDefType.NULL);
401 handler.updateDetailChannel(DETAIL_COLOR_DESCRIPTION, UnDefType.NULL);
402 handler.updateDetailChannel(DETAIL_COUNTRY, UnDefType.NULL);
403 handler.updateDetailChannel(DETAIL_ASPECT_RATIO, UnDefType.NULL);
406 handler.updateDetailChannel(DETAIL_TYPE, UnDefType.UNDEF);
408 // otherwise update the channel if it is one we care about
409 } else if (METADATA_CHANNELS.contains(metaType)) {
410 // special case for cover art image
411 if (DETAIL_COVER_URL.equals(metaType)) {
412 handler.updateDetailChannel(metaType, new StringType(value));
413 if (!value.isEmpty() && handler.isChannelLinked(DETAIL + DETAIL_COVER_ART)) {
415 ContentResponse contentResponse = handler.httpClient.newRequest(value).method(GET)
416 .timeout(10, TimeUnit.SECONDS).send();
417 int httpStatus = contentResponse.getStatus();
418 if (httpStatus == OK_200) {
419 handler.updateDetailChannel(DETAIL_COVER_ART,
420 new RawType(contentResponse.getContent(), "image/jpeg"));
422 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
424 } catch (InterruptedException | TimeoutException | ExecutionException e) {
425 logger.debug("Error updating Cover Art Image channel for url: {}", value);
426 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
429 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
431 // special case for running time to create a QuantityType<Time>
432 } else if (DETAIL_RUNNING_TIME.equals(metaType)) {
433 handler.updateDetailChannel(DETAIL_RUNNING_TIME, new QuantityType<>(
434 Integer.parseInt(value) * handler.metaRuntimeMultiple, handler.apiSecondUnit));
435 // everything else just send it as a string
437 handler.updateDetailChannel(metaType, new StringType(value));
441 logger.debug("CONTENT_DETAILS - no match on message: {}", message);
447 public void handleMessage(String message, KaleidescapeHandler handler) {
453 public void handleMessage(String message, KaleidescapeHandler handler) {
459 public void handleMessage(String message, KaleidescapeHandler handler) {
460 // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
461 // the first element of SCREEN_MASK now provides this info
465 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
468 public void handleMessage(String message, KaleidescapeHandler handler) {
469 // example: SELECT_KALEIDESCAPE_INPUT
472 // when the ipad or phone app is started up, it sends a VOLUME_QUERY,
473 // so we respond to enable volume controls and set the initial volume and mute
475 if (handler.volumeEnabled) {
476 synchronized (handler.sequenceLock) {
477 handler.connector.sendCommand(SEND_EVENT_VOLUME_CAPABILITIES_15);
478 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
479 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
484 if (handler.volumeEnabled) {
485 synchronized (handler.sequenceLock) {
487 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
488 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
493 if (handler.volumeEnabled) {
494 synchronized (handler.sequenceLock) {
496 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
497 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
502 if (handler.volumeEnabled) {
503 State state = UnDefType.UNDEF;
504 synchronized (handler.sequenceLock) {
505 if (handler.isMuted) {
506 state = OnOffType.OFF;
507 handler.isMuted = false;
509 state = OnOffType.ON;
510 handler.isMuted = true;
512 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
513 handler.updateChannel(MUTE, state);
517 // the default is to just publish all other USER_DEFINED_EVENTs
519 handler.updateChannel(KaleidescapeBindingConstants.USER_DEFINED_EVENT, new StringType(message));
521 } catch (KaleidescapeException e) {
522 logger.debug("USER_DEFINED_EVENT - exception on message: {}", message);
528 public void handleMessage(String message, KaleidescapeHandler handler) {
529 // example: 01:Search for title:ABC
530 handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
535 public void handleMessage(String message, KaleidescapeHandler handler) {
536 // example: 00:00::00:0:1
537 handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
540 SYSTEM_READINESS_STATE {
542 public void handleMessage(String message, KaleidescapeHandler handler) {
544 handler.updateChannel(KaleidescapeBindingConstants.SYSTEM_READINESS_STATE,
545 new StringType(KaleidescapeStatusCodes.READINESS_STATE.get(message)));
549 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
551 // example: 16:8.6.0-21023
552 // protocol version, kOS version
553 private final Pattern p = Pattern.compile("^(\\d{2}):(.*)$");
556 public void handleMessage(String message, KaleidescapeHandler handler) {
557 Matcher matcher = p.matcher(message);
558 if (matcher.find()) {
559 handler.updateThingProperty(PROPERTY_PROTOCOL_VERSION, matcher.group(1));
560 handler.updateThingProperty(PROPERTY_SYSTEM_VERSION, matcher.group(2));
562 logger.debug("SYSTEM_VERSION - no match on message: {}", message);
567 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
569 // example: 07:000000000000558F:00:192.168.001.100
570 // device type (deprecated), serial number, cpdid, ip address
571 private final Pattern p = Pattern.compile("^(\\d{2}):(.*):(\\d{2}):(.*)$");
574 public void handleMessage(String message, KaleidescapeHandler handler) {
575 Matcher matcher = p.matcher(message);
576 if (matcher.find()) {
577 // replaceFirst takes off leading zeros
578 handler.updateThingProperty(PROPERTY_SERIAL_NUMBER, matcher.group(2).replaceFirst("^0+(?!$)", EMPTY));
579 handler.updateThingProperty(PROPERTY_CONTROL_PROTOCOL_ID, matcher.group(3));
581 logger.debug("DEVICE_INFO - no match on message: {}", message);
587 public void handleMessage(String message, KaleidescapeHandler handler) {
588 // example: 'Player' or 'Strato'
589 handler.updateThingProperty(PROPERTY_COMPONENT_TYPE, message);
594 public void handleMessage(String message, KaleidescapeHandler handler) {
595 // example: 'Living Room'
596 handler.friendlyName = message;
597 handler.updateThingProperty(PROPERTY_FRIENDLY_NAME, message);
601 public abstract void handleMessage(String message, KaleidescapeHandler handler);