]> git.basschouten.com Git - openhab-addons.git/blob
95119b780d9f1d7fab37d8fa39275d3a66bc84c6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.kaleidescape.internal.handler;
14
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.*;
18
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;
25
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;
43
44 /**
45  * The {@link KaleidescapeMessageHandler} class processes all messages received
46  * by the event listener and then updates the appropriate states
47  *
48  * @author Michael Lobstein - Initial contribution
49  */
50 @NonNullByDefault
51 public enum KaleidescapeMessageHandler {
52     UI_STATE {
53         @Override
54         public void handleMessage(String message, KaleidescapeHandler handler) {
55             handler.updateChannel(KaleidescapeBindingConstants.UI_STATE, new StringType(message));
56         }
57     },
58     HIGHLIGHTED_SELECTION {
59         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
60
61         @Override
62         public void handleMessage(String message, KaleidescapeHandler handler) {
63             handler.updateChannel(KaleidescapeBindingConstants.HIGHLIGHTED_SELECTION, new StringType(message));
64
65             if (handler.isLoadHighlightedDetails) {
66                 try {
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);
70                 }
71             }
72         }
73     },
74     DEVICE_POWER_STATE {
75         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
76
77         // example: 1:1
78         // power_state, zone 1 state, zone n state
79         private final Pattern p = Pattern.compile("^(\\d{1}):(.*)$");
80
81         @Override
82         public void handleMessage(String message, KaleidescapeHandler handler) {
83             Matcher matcher = p.matcher(message);
84             if (matcher.find()) {
85                 handler.updateChannel(POWER, OnOffType.from(ONE.equals(matcher.group(1))));
86             } else {
87                 logger.debug("DEVICE_POWER_STATE - no match on message: {}", message);
88             }
89         }
90     },
91     TITLE_NAME {
92         @Override
93         public void handleMessage(String message, KaleidescapeHandler handler) {
94             handler.updateChannel(KaleidescapeBindingConstants.TITLE_NAME,
95                     new StringType(KaleidescapeFormatter.formatString(message)));
96         }
97     },
98     PLAY_STATUS {
99         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
100
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})$");
105
106         @Override
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))));
112
113                 handler.updateChannel(CONTROL, "2".equals(matcher.group(1)) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
114
115                 handler.updateChannel(PLAY_SPEED, new StringType(matcher.group(2)));
116
117                 handler.updateChannel(TITLE_NUM, new DecimalType(Integer.parseInt(matcher.group(3))));
118
119                 handler.updateChannel(TITLE_LENGTH,
120                         new QuantityType<>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
121
122                 handler.updateChannel(TITLE_LOC,
123                         new QuantityType<>(Integer.parseInt(matcher.group(5)), handler.apiSecondUnit));
124
125                 handler.updateChannel(CHAPTER_NUM, new DecimalType(Integer.parseInt(matcher.group(6))));
126
127                 handler.updateChannel(CHAPTER_LENGTH,
128                         new QuantityType<>(Integer.parseInt(matcher.group(7)), handler.apiSecondUnit));
129
130                 handler.updateChannel(CHAPTER_LOC,
131                         new QuantityType<>(Integer.parseInt(matcher.group(8)), handler.apiSecondUnit));
132             } else {
133                 logger.debug("PLAY_STATUS - no match on message: {}", message);
134             }
135         }
136     },
137     MOVIE_MEDIA_TYPE {
138         @Override
139         public void handleMessage(String message, KaleidescapeHandler handler) {
140             handler.updateChannel(KaleidescapeBindingConstants.MOVIE_MEDIA_TYPE,
141                     new StringType(KaleidescapeStatusCodes.MEDIA_TYPE.get(message)));
142         }
143     },
144     MOVIE_LOCATION {
145         @Override
146         public void handleMessage(String message, KaleidescapeHandler handler) {
147             handler.updateChannel(KaleidescapeBindingConstants.MOVIE_LOCATION,
148                     new StringType(KaleidescapeStatusCodes.MOVIE_LOCATION.get(message)));
149         }
150     },
151     VIDEO_MODE {
152         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
153
154         // example: 00:00:00
155         // composite, component, hdmi
156         private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
157
158         @Override
159         public void handleMessage(String message, KaleidescapeHandler handler) {
160             handler.updateChannel(KaleidescapeBindingConstants.VIDEO_MODE, new StringType(message));
161
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))));
166
167                 handler.updateChannel(VIDEO_MODE_COMPONENT,
168                         new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(2))));
169
170                 handler.updateChannel(VIDEO_MODE_HDMI,
171                         new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(3))));
172             } else {
173                 logger.debug("VIDEO_MODE - no match on message: {}", message);
174             }
175         }
176     },
177     VIDEO_COLOR {
178         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
179
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})$");
183
184         @Override
185         public void handleMessage(String message, KaleidescapeHandler handler) {
186             handler.updateChannel(KaleidescapeBindingConstants.VIDEO_COLOR, new StringType(message));
187
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))));
192             } else {
193                 logger.debug("VIDEO_COLOR - no match on message: {}", message);
194             }
195         }
196     },
197     CONTENT_COLOR {
198         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
199
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})$");
203
204         @Override
205         public void handleMessage(String message, KaleidescapeHandler handler) {
206             handler.updateChannel(KaleidescapeBindingConstants.CONTENT_COLOR, new StringType(message));
207
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))));
212             } else {
213                 logger.debug("CONTENT_COLOR - no match on message: {}", message);
214             }
215         }
216     },
217     SCALE_MODE {
218         @Override
219         public void handleMessage(String message, KaleidescapeHandler handler) {
220             handler.updateChannel(KaleidescapeBindingConstants.SCALE_MODE, new StringType(message));
221         }
222     },
223     SCREEN_MASK {
224         @Override
225         public void handleMessage(String message, KaleidescapeHandler handler) {
226             handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK, new StringType(message));
227
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])));
234             }
235         }
236     },
237     SCREEN_MASK2 {
238         @Override
239         public void handleMessage(String message, KaleidescapeHandler handler) {
240             handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK2, new StringType(message));
241         }
242     },
243     CINEMASCAPE_MASK {
244         @Override
245         public void handleMessage(String message, KaleidescapeHandler handler) {
246             handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MASK, new StringType(message));
247         }
248     },
249     CINEMASCAPE_MODE {
250         @Override
251         public void handleMessage(String message, KaleidescapeHandler handler) {
252             handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MODE, new StringType(message));
253         }
254     },
255     CHILD_MODE_STATE {
256         @Override
257         public void handleMessage(String message, KaleidescapeHandler handler) {
258             handler.updateChannel(KaleidescapeBindingConstants.CHILD_MODE_STATE, new StringType(message));
259         }
260     },
261     MUSIC_TITLE {
262         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
263
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("^(.*):(.*):(.*):(.*):(.*):(.*)$");
267
268         @Override
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))));
275
276                 handler.updateChannel(MUSIC_ARTIST,
277                         new StringType(KaleidescapeFormatter.formatString(matcher.group(2))));
278
279                 handler.updateChannel(MUSIC_ALBUM,
280                         new StringType(KaleidescapeFormatter.formatString(matcher.group(3))));
281
282                 handler.updateChannel(MUSIC_TRACK_HANDLE, new StringType(matcher.group(4)));
283
284                 handler.updateChannel(MUSIC_ALBUM_HANDLE, new StringType(matcher.group(5)));
285
286                 handler.updateChannel(MUSIC_NOWPLAY_HANDLE, new StringType(matcher.group(6)));
287
288                 if (handler.isLoadAlbumDetails) {
289                     try {
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: {}",
293                                 matcher.group(5));
294                     }
295                 }
296             } else {
297                 logger.debug("MUSIC_TITLE - no match on message: {}", message);
298             }
299         }
300     },
301     MUSIC_PLAY_STATUS {
302         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
303
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})$");
308
309         @Override
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))));
315
316                 handler.updateChannel(MUSIC_CONTROL,
317                         "2".equals(matcher.group(1)) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
318
319                 handler.updateChannel(MUSIC_PLAY_SPEED, new StringType(matcher.group(2)));
320
321                 handler.updateChannel(MUSIC_TRACK_LENGTH,
322                         new QuantityType<>(Integer.parseInt(matcher.group(3)), handler.apiSecondUnit));
323
324                 handler.updateChannel(MUSIC_TRACK_POSITION,
325                         new QuantityType<>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
326
327                 handler.updateChannel(MUSIC_TRACK_PROGRESS,
328                         new DecimalType(BigDecimal.valueOf(Math.round(Double.parseDouble(matcher.group(5))))));
329             } else {
330                 logger.debug("MUSIC_PLAY_STATUS - no match on message: {}", message);
331             }
332         }
333     },
334     MUSIC_NOW_PLAYING_STATUS {
335         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
336
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}):(.*)$");
341
342         @Override
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))));
348
349                 // update RANDOM switch state
350                 handler.updateChannel(MUSIC_RANDOM, OnOffType.from(ONE.equals(matcher.group(4))));
351             } else {
352                 logger.debug("MUSIC_NOW_PLAYING_STATUS - no match on message: {}", message);
353             }
354         }
355     },
356     PLAYING_MUSIC_INFORMATION {
357         @Override
358         public void handleMessage(String message, KaleidescapeHandler handler) {
359             // example: R_1493833:Radiohead - Pablo Honey
360             // album handle, artist - album
361             // do nothing; redundant
362         }
363     },
364     CONTENT_DETAILS {
365         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
366
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}):([^:^/]*):(.*)$");
371
372         @Override
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));
378
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;
384
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);
389
390                     } else if ((ALBUM_CONTENT_HANDLE).equals(metaType)) {
391                         handler.updateDetailChannel(DETAIL_TYPE, new StringType(ALBUM));
392                         handler.metaRuntimeMultiple = 1;
393
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);
404
405                     } else {
406                         handler.updateDetailChannel(DETAIL_TYPE, UnDefType.UNDEF);
407                     }
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)) {
414                             try {
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"));
421                                 } else {
422                                     handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
423                                 }
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);
427                             }
428                         } else {
429                             handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
430                         }
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
436                     } else {
437                         handler.updateDetailChannel(metaType, new StringType(value));
438                     }
439                 }
440             } else {
441                 logger.debug("CONTENT_DETAILS - no match on message: {}", message);
442             }
443         }
444     },
445     TIME {
446         @Override
447         public void handleMessage(String message, KaleidescapeHandler handler) {
448             // do nothing
449         }
450     },
451     STATUS_CUE_PERIOD {
452         @Override
453         public void handleMessage(String message, KaleidescapeHandler handler) {
454             // do nothing
455         }
456     },
457     ASPECT_RATIO {
458         @Override
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
462         }
463     },
464     USER_DEFINED_EVENT {
465         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
466
467         @Override
468         public void handleMessage(String message, KaleidescapeHandler handler) {
469             // example: SELECT_KALEIDESCAPE_INPUT
470             try {
471                 switch (message) {
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
474                     case "VOLUME_QUERY":
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));
480                             }
481                         } else if (handler.volumeBasicEnabled) {
482                             synchronized (handler.sequenceLock) {
483                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_CAPABILITIES_3);
484                             }
485                         }
486                         break;
487                     case "VOLUME_UP":
488                         if (handler.volumeEnabled) {
489                             synchronized (handler.sequenceLock) {
490                                 handler.volume++;
491                                 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
492                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
493                             }
494                         }
495                         break;
496                     case "VOLUME_DOWN":
497                         if (handler.volumeEnabled) {
498                             synchronized (handler.sequenceLock) {
499                                 handler.volume--;
500                                 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
501                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
502                             }
503                         }
504                         break;
505                     case "TOGGLE_MUTE":
506                         if (handler.volumeEnabled) {
507                             State state = UnDefType.UNDEF;
508                             synchronized (handler.sequenceLock) {
509                                 if (handler.isMuted) {
510                                     state = OnOffType.OFF;
511                                     handler.isMuted = false;
512                                 } else {
513                                     state = OnOffType.ON;
514                                     handler.isMuted = true;
515                                 }
516                                 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
517                                 handler.updateChannel(MUTE, state);
518                             }
519                         }
520                         break;
521                 }
522                 handler.updateChannel(KaleidescapeBindingConstants.USER_DEFINED_EVENT, new StringType(message));
523             } catch (KaleidescapeException e) {
524                 logger.debug("USER_DEFINED_EVENT - exception on message: {}", message);
525             }
526         }
527     },
528     USER_INPUT {
529         @Override
530         public void handleMessage(String message, KaleidescapeHandler handler) {
531             // example: 01:Search for title:ABC
532             handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
533         }
534     },
535     USER_INPUT_PROMPT {
536         @Override
537         public void handleMessage(String message, KaleidescapeHandler handler) {
538             // example: 00:00::00:0:1
539             handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
540         }
541     },
542     SYSTEM_READINESS_STATE {
543         @Override
544         public void handleMessage(String message, KaleidescapeHandler handler) {
545             // example 1, 2 or 3
546             handler.updateChannel(KaleidescapeBindingConstants.SYSTEM_READINESS_STATE,
547                     new StringType(KaleidescapeStatusCodes.READINESS_STATE.get(message)));
548         }
549     },
550     SYSTEM_VERSION {
551         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
552
553         // example: 16:8.6.0-21023
554         // protocol version, kOS version
555         private final Pattern p = Pattern.compile("^(\\d{2}):(.*)$");
556
557         @Override
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));
563             } else {
564                 logger.debug("SYSTEM_VERSION - no match on message: {}", message);
565             }
566         }
567     },
568     DEVICE_INFO {
569         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
570
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}):(.*)$");
574
575         @Override
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));
582             } else {
583                 logger.debug("DEVICE_INFO - no match on message: {}", message);
584             }
585         }
586     },
587     DEVICE_TYPE_NAME {
588         @Override
589         public void handleMessage(String message, KaleidescapeHandler handler) {
590             // example: 'Player' or 'Strato'
591             handler.updateThingProperty(PROPERTY_COMPONENT_TYPE, message);
592         }
593     },
594     FRIENDLY_NAME {
595         @Override
596         public void handleMessage(String message, KaleidescapeHandler handler) {
597             // example: 'Living Room'
598             handler.friendlyName = message;
599             handler.updateThingProperty(PROPERTY_FRIENDLY_NAME, message);
600         }
601     };
602
603     public abstract void handleMessage(String message, KaleidescapeHandler handler);
604 }