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.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 {
60 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
63 public void handleMessage(String message, KaleidescapeHandler handler) {
64 handler.updateChannel(KaleidescapeBindingConstants.HIGHLIGHTED_SELECTION, new StringType(message));
66 if (handler.isLoadHighlightedDetails) {
68 handler.connector.sendCommand(GET_CONTENT_DETAILS + message + ":");
69 } catch (KaleidescapeException e) {
70 logger.debug("GET_CONTENT_DETAILS - exception loading content details for handle: {}", message);
76 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
79 // power_state, zone 1 state, zone n state
80 private final Pattern p = Pattern.compile("^(\\d{1}):(.*)$");
83 public void handleMessage(String message, KaleidescapeHandler handler) {
84 Matcher matcher = p.matcher(message);
86 handler.updateChannel(POWER, (ONE).equals(matcher.group(1)) ? OnOffType.ON : OnOffType.OFF);
88 logger.debug("DEVICE_POWER_STATE - no match on message: {}", message);
94 public void handleMessage(String message, KaleidescapeHandler handler) {
95 handler.updateChannel(KaleidescapeBindingConstants.TITLE_NAME,
96 new StringType(KaleidescapeFormatter.formatString(message)));
100 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
102 // example: 0:0:00:00000:00000:000:00000:00000
103 // mode, speed, title_num, title_length, title_loc, chapter_num, chapter_length, chapter_loc
104 private final Pattern p = Pattern
105 .compile("^(\\d{1}):(\\d{1}):(\\d{2}):(\\d{5}):(\\d{5}):(\\d{3}):(\\d{5}):(\\d{5})$");
108 public void handleMessage(String message, KaleidescapeHandler handler) {
109 Matcher matcher = p.matcher(message);
110 if (matcher.find()) {
111 handler.updateChannel(PLAY_MODE,
112 new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
114 handler.updateChannel(PLAY_SPEED, new StringType(matcher.group(2)));
116 handler.updateChannel(TITLE_NUM, new DecimalType(Integer.parseInt(matcher.group(3))));
118 handler.updateChannel(TITLE_LENGTH,
119 new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
121 handler.updateChannel(TITLE_LOC,
122 new QuantityType<Time>(Integer.parseInt(matcher.group(5)), handler.apiSecondUnit));
124 handler.updateChannel(CHAPTER_NUM, new DecimalType(Integer.parseInt(matcher.group(6))));
126 handler.updateChannel(CHAPTER_LENGTH,
127 new QuantityType<Time>(Integer.parseInt(matcher.group(7)), handler.apiSecondUnit));
129 handler.updateChannel(CHAPTER_LOC,
130 new QuantityType<Time>(Integer.parseInt(matcher.group(8)), handler.apiSecondUnit));
132 logger.debug("PLAY_STATUS - no match on message: {}", message);
138 public void handleMessage(String message, KaleidescapeHandler handler) {
139 handler.updateChannel(KaleidescapeBindingConstants.MOVIE_MEDIA_TYPE,
140 new StringType(KaleidescapeStatusCodes.MEDIA_TYPE.get(message)));
145 public void handleMessage(String message, KaleidescapeHandler handler) {
146 handler.updateChannel(KaleidescapeBindingConstants.MOVIE_LOCATION,
147 new StringType(KaleidescapeStatusCodes.MOVIE_LOCATION.get(message)));
151 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
154 // composite, component, hdmi
155 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
158 public void handleMessage(String message, KaleidescapeHandler handler) {
159 handler.updateChannel(KaleidescapeBindingConstants.VIDEO_MODE, new StringType(message));
161 Matcher matcher = p.matcher(message);
162 if (matcher.find()) {
163 handler.updateChannel(VIDEO_MODE_COMPOSITE,
164 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(1))));
166 handler.updateChannel(VIDEO_MODE_COMPONENT,
167 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(2))));
169 handler.updateChannel(VIDEO_MODE_HDMI,
170 new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(3))));
172 logger.debug("VIDEO_MODE - no match on message: {}", message);
177 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
179 // example: 02:01:24:01
180 // eotf, color_space, color_depth, color_sampling
181 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
184 public void handleMessage(String message, KaleidescapeHandler handler) {
185 handler.updateChannel(KaleidescapeBindingConstants.VIDEO_COLOR, new StringType(message));
187 Matcher matcher = p.matcher(message);
188 if (matcher.find()) {
189 handler.updateChannel(VIDEO_COLOR_EOTF,
190 new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
192 logger.debug("VIDEO_COLOR - no match on message: {}", message);
197 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
199 // example: 02:01:24:01
200 // eotf, color_space, color_depth, color_sampling
201 private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
204 public void handleMessage(String message, KaleidescapeHandler handler) {
205 handler.updateChannel(KaleidescapeBindingConstants.CONTENT_COLOR, new StringType(message));
207 Matcher matcher = p.matcher(message);
208 if (matcher.find()) {
209 handler.updateChannel(CONTENT_COLOR_EOTF,
210 new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
212 logger.debug("CONTENT_COLOR - no match on message: {}", message);
218 public void handleMessage(String message, KaleidescapeHandler handler) {
219 handler.updateChannel(KaleidescapeBindingConstants.SCALE_MODE, new StringType(message));
224 public void handleMessage(String message, KaleidescapeHandler handler) {
225 handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK, new StringType(message));
227 // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
228 // the first element of SCREEN_MASK now provides this info
229 if (!message.equals(EMPTY)) {
230 String[] msgSplit = message.split(":", 2);
231 handler.updateChannel(KaleidescapeBindingConstants.ASPECT_RATIO,
232 new StringType(KaleidescapeStatusCodes.ASPECT_RATIO.get(msgSplit[0])));
238 public void handleMessage(String message, KaleidescapeHandler handler) {
239 handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK2, new StringType(message));
244 public void handleMessage(String message, KaleidescapeHandler handler) {
245 handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MASK, new StringType(message));
250 public void handleMessage(String message, KaleidescapeHandler handler) {
251 handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MODE, new StringType(message));
256 public void handleMessage(String message, KaleidescapeHandler handler) {
257 handler.updateChannel(KaleidescapeBindingConstants.CHILD_MODE_STATE, new StringType(message));
261 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
263 // example: You:Radiohead:Pablo Honey:1.9b5f4d786d7e2c49-t301_577:1.R_1493833:2.200c5
264 // track, artist, album, track handle, album handle, now playing handle
265 private final Pattern p = Pattern.compile("^(.*):(.*):(.*):(.*):(.*):(.*)$");
268 public void handleMessage(String message, KaleidescapeHandler handler) {
269 // first replace delimited : in track/artist/album name with ||, fix it later in formatString()
270 Matcher matcher = p.matcher(message.replace("\\:", "||"));
271 if (matcher.find()) {
272 handler.updateChannel(MUSIC_TRACK,
273 new StringType(KaleidescapeFormatter.formatString(matcher.group(1))));
275 handler.updateChannel(MUSIC_ARTIST,
276 new StringType(KaleidescapeFormatter.formatString(matcher.group(2))));
278 handler.updateChannel(MUSIC_ALBUM,
279 new StringType(KaleidescapeFormatter.formatString(matcher.group(3))));
281 handler.updateChannel(MUSIC_TRACK_HANDLE, new StringType(matcher.group(4)));
283 handler.updateChannel(MUSIC_ALBUM_HANDLE, new StringType(matcher.group(5)));
285 handler.updateChannel(MUSIC_NOWPLAY_HANDLE, new StringType(matcher.group(6)));
287 if (handler.isLoadAlbumDetails) {
289 handler.connector.sendCommand(GET_CONTENT_DETAILS + matcher.group(5) + ":");
290 } catch (KaleidescapeException e) {
291 logger.debug("GET_CONTENT_DETAILS - exception loading album details for handle: {}",
296 logger.debug("MUSIC_TITLE - no match on message: {}", message);
301 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
303 // example: 2:0:00207:+00000:000.00
304 // 2:0:00331:+00183:055.29
305 // mode, speed, track length, track position, track progress %
306 private final Pattern p = Pattern.compile("^(\\d{1}):(\\d{1}):(\\d{5}):(.\\d{5}):(\\d{3}\\.\\d{2})$");
309 public void handleMessage(String message, KaleidescapeHandler handler) {
310 Matcher matcher = p.matcher(message);
311 if (matcher.find()) {
312 handler.updateChannel(MUSIC_PLAY_MODE,
313 new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
315 handler.updateChannel(MUSIC_PLAY_SPEED, new StringType(matcher.group(2)));
317 handler.updateChannel(MUSIC_TRACK_LENGTH,
318 new QuantityType<Time>(Integer.parseInt(matcher.group(3)), handler.apiSecondUnit));
320 handler.updateChannel(MUSIC_TRACK_POSITION,
321 new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
323 handler.updateChannel(MUSIC_TRACK_PROGRESS,
324 new DecimalType(BigDecimal.valueOf(Math.round(Double.parseDouble(matcher.group(5))))));
326 logger.debug("MUSIC_PLAY_STATUS - no match on message: {}", message);
330 MUSIC_NOW_PLAYING_STATUS {
331 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
333 // example: 00013:00000:0:0:0000000238:2.200c5
334 // total # tracks in list, list index, repeat, random, generation, now_playing handle
335 // only using repeat & random right now
336 private final Pattern p = Pattern.compile("^(\\d{5}):(\\d{5}):(\\d{1}):(\\d{1}):(\\d{10}):(.*)$");
339 public void handleMessage(String message, KaleidescapeHandler handler) {
340 Matcher matcher = p.matcher(message);
341 if (matcher.find()) {
342 // update REPEAT switch state
343 handler.updateChannel(MUSIC_REPEAT, (ONE).equals(matcher.group(3)) ? OnOffType.ON : OnOffType.OFF);
345 // update RANDOM switch state
346 handler.updateChannel(MUSIC_RANDOM, (ONE).equals(matcher.group(4)) ? OnOffType.ON : OnOffType.OFF);
348 logger.debug("MUSIC_NOW_PLAYING_STATUS - no match on message: {}", message);
352 PLAYING_MUSIC_INFORMATION {
354 public void handleMessage(String message, KaleidescapeHandler handler) {
355 // example: R_1493833:Radiohead - Pablo Honey
356 // album handle, artist - album
357 // do nothing; redundant
361 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
363 // g1=meta id, g2=meta type, g3=data
364 // example: 6:Year:1995
365 // or: 10:Genres:Pop\/Rock
366 private final Pattern p = Pattern.compile("^(\\d{1,2}):([^:^/]*):(.*)$");
369 public void handleMessage(String message, KaleidescapeHandler handler) {
370 Matcher matcher = p.matcher(message);
371 if (matcher.find()) {
372 String metaType = matcher.group(2).toLowerCase();
373 String value = KaleidescapeFormatter.formatString(matcher.group(3));
375 // the CONTENT_DETAILS message with id=1 tells us what type of meta data is coming
376 if (ONE.equals(matcher.group(1))) {
377 if ((CONTENT_HANDLE).equals(metaType)) {
378 handler.updateDetailChannel(DETAIL_TYPE, new StringType(MOVIE));
379 handler.metaRuntimeMultiple = 60;
381 // null out album specific
382 handler.updateDetailChannel(DETAIL_ALBUM_TITLE, UnDefType.NULL);
383 handler.updateDetailChannel(DETAIL_ARTIST, UnDefType.NULL);
384 handler.updateDetailChannel(DETAIL_REVIEW, UnDefType.NULL);
386 } else if ((ALBUM_CONTENT_HANDLE).equals(metaType)) {
387 handler.updateDetailChannel(DETAIL_TYPE, new StringType(ALBUM));
388 handler.metaRuntimeMultiple = 1;
390 // null out movie specific
391 handler.updateDetailChannel(DETAIL_TITLE, UnDefType.NULL);
392 handler.updateDetailChannel(DETAIL_RATING, UnDefType.NULL);
393 handler.updateDetailChannel(DETAIL_ACTORS, UnDefType.NULL);
394 handler.updateDetailChannel(DETAIL_DIRECTORS, UnDefType.NULL);
395 handler.updateDetailChannel(DETAIL_RATING_REASON, UnDefType.NULL);
396 handler.updateDetailChannel(DETAIL_SYNOPSIS, UnDefType.NULL);
397 handler.updateDetailChannel(DETAIL_COLOR_DESCRIPTION, UnDefType.NULL);
398 handler.updateDetailChannel(DETAIL_COUNTRY, UnDefType.NULL);
399 handler.updateDetailChannel(DETAIL_ASPECT_RATIO, UnDefType.NULL);
402 handler.updateDetailChannel(DETAIL_TYPE, UnDefType.UNDEF);
404 // otherwise update the channel if it is one we care about
405 } else if (METADATA_CHANNELS.contains(metaType)) {
406 // special case for cover art image
407 if (DETAIL_COVER_URL.equals(metaType)) {
408 handler.updateDetailChannel(metaType, new StringType(value));
409 if (!value.isEmpty()) {
411 ContentResponse contentResponse = handler.httpClient.newRequest(value).method(GET)
412 .timeout(10, TimeUnit.SECONDS).send();
413 int httpStatus = contentResponse.getStatus();
414 if (httpStatus == OK_200) {
415 handler.updateDetailChannel(DETAIL_COVER_ART,
416 new RawType(contentResponse.getContent(), RawType.DEFAULT_MIME_TYPE));
418 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
420 } catch (InterruptedException | TimeoutException | ExecutionException e) {
421 logger.debug("Error updating Cover Art Image channel for url: {}", value);
422 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
425 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
427 // special case for running time to create a QuantityType<Time>
428 } else if (DETAIL_RUNNING_TIME.equals(metaType)) {
429 handler.updateDetailChannel(DETAIL_RUNNING_TIME, new QuantityType<Time>(
430 Integer.parseInt(value) * handler.metaRuntimeMultiple, handler.apiSecondUnit));
431 // everything else just send it as a string
433 handler.updateDetailChannel(metaType, new StringType(value));
437 logger.debug("CONTENT_DETAILS - no match on message: {}", message);
443 public void handleMessage(String message, KaleidescapeHandler handler) {
449 public void handleMessage(String message, KaleidescapeHandler handler) {
455 public void handleMessage(String message, KaleidescapeHandler handler) {
456 // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
457 // the first element of SCREEN_MASK now provides this info
461 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
464 public void handleMessage(String message, KaleidescapeHandler handler) {
465 // example: SELECT_KALEIDESCAPE_INPUT
468 // when the ipad or phone app is started up, it sends a VOLUME_QUERY,
469 // so we respond to enable volume controls and set the initial volume and mute
471 if (handler.volumeEnabled) {
472 synchronized (handler.sequenceLock) {
473 handler.connector.sendCommand(SEND_EVENT_VOLUME_CAPABILITIES_15);
474 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
475 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
480 if (handler.volumeEnabled) {
481 synchronized (handler.sequenceLock) {
483 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
484 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
489 if (handler.volumeEnabled) {
490 synchronized (handler.sequenceLock) {
492 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
493 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
498 if (handler.volumeEnabled) {
499 State state = UnDefType.UNDEF;
500 synchronized (handler.sequenceLock) {
501 if (handler.isMuted) {
502 state = OnOffType.OFF;
503 handler.isMuted = false;
505 state = OnOffType.ON;
506 handler.isMuted = true;
508 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
509 handler.updateChannel(MUTE, state);
513 // the default is to just publish all other USER_DEFINED_EVENTs
515 handler.updateChannel(KaleidescapeBindingConstants.USER_DEFINED_EVENT, new StringType(message));
517 } catch (KaleidescapeException e) {
518 logger.debug("USER_DEFINED_EVENT - exception on message: {}", message);
524 public void handleMessage(String message, KaleidescapeHandler handler) {
525 // example: 01:Search for title:ABC
526 handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
531 public void handleMessage(String message, KaleidescapeHandler handler) {
532 // example: 00:00::00:0:1
533 handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
536 SYSTEM_READINESS_STATE {
538 public void handleMessage(String message, KaleidescapeHandler handler) {
540 handler.updateChannel(KaleidescapeBindingConstants.SYSTEM_READINESS_STATE,
541 new StringType(KaleidescapeStatusCodes.READINESS_STATE.get(message)));
545 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
547 // example: 16:8.6.0-21023
548 // protocol version, kOS version
549 private final Pattern p = Pattern.compile("^(\\d{2}):(.*)$");
552 public void handleMessage(String message, KaleidescapeHandler handler) {
553 Matcher matcher = p.matcher(message);
554 if (matcher.find()) {
555 handler.updateThingProperty(PROPERTY_PROTOCOL_VERSION, matcher.group(1));
556 handler.updateThingProperty(PROPERTY_SYSTEM_VERSION, matcher.group(2));
558 logger.debug("SYSTEM_VERSION - no match on message: {}", message);
563 private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
565 // example: 07:000000000000558F:00:192.168.001.100
566 // device type (deprecated), serial number, cpdid, ip address
567 private final Pattern p = Pattern.compile("^(\\d{2}):(.*):(\\d{2}):(.*)$");
570 public void handleMessage(String message, KaleidescapeHandler handler) {
571 Matcher matcher = p.matcher(message);
572 if (matcher.find()) {
573 // replaceFirst takes off leading zeros
574 handler.updateThingProperty(PROPERTY_SERIAL_NUMBER, matcher.group(2).replaceFirst("^0+(?!$)", EMPTY));
575 handler.updateThingProperty(PROPERTY_CONTROL_PROTOCOL_ID, matcher.group(3));
577 logger.debug("DEVICE_INFO - no match on message: {}", message);
583 public void handleMessage(String message, KaleidescapeHandler handler) {
584 // example: 'Player' or 'Strato'
585 handler.updateThingProperty(PROPERTY_COMPONENT_TYPE, message);
590 public void handleMessage(String message, KaleidescapeHandler handler) {
591 // example: 'Living Room'
592 handler.friendlyName = message;
593 handler.updateThingProperty(PROPERTY_FRIENDLY_NAME, message);
597 public abstract void handleMessage(String message, KaleidescapeHandler handler);