]> git.basschouten.com Git - openhab-addons.git/blob
98123eddbf8ff5f2fdf32073ad9e00172baa1745
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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 javax.measure.quantity.Time;
27
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;
45
46 /**
47  * The {@link KaleidescapeMessageHandler} class processes all messages received
48  * by the event listener and then updates the appropriate states
49  *
50  * @author Michael Lobstein - Initial contribution
51  */
52 @NonNullByDefault
53 public enum KaleidescapeMessageHandler {
54     UI_STATE {
55         @Override
56         public void handleMessage(String message, KaleidescapeHandler handler) {
57             handler.updateChannel(KaleidescapeBindingConstants.UI_STATE, new StringType(message));
58         }
59     },
60     HIGHLIGHTED_SELECTION {
61         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
62
63         @Override
64         public void handleMessage(String message, KaleidescapeHandler handler) {
65             handler.updateChannel(KaleidescapeBindingConstants.HIGHLIGHTED_SELECTION, new StringType(message));
66
67             if (handler.isLoadHighlightedDetails) {
68                 try {
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);
72                 }
73             }
74         }
75     },
76     DEVICE_POWER_STATE {
77         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
78
79         // example: 1:1
80         // power_state, zone 1 state, zone n state
81         private final Pattern p = Pattern.compile("^(\\d{1}):(.*)$");
82
83         @Override
84         public void handleMessage(String message, KaleidescapeHandler handler) {
85             Matcher matcher = p.matcher(message);
86             if (matcher.find()) {
87                 handler.updateChannel(POWER, (ONE).equals(matcher.group(1)) ? OnOffType.ON : OnOffType.OFF);
88             } else {
89                 logger.debug("DEVICE_POWER_STATE - no match on message: {}", message);
90             }
91         }
92     },
93     TITLE_NAME {
94         @Override
95         public void handleMessage(String message, KaleidescapeHandler handler) {
96             handler.updateChannel(KaleidescapeBindingConstants.TITLE_NAME,
97                     new StringType(KaleidescapeFormatter.formatString(message)));
98         }
99     },
100     PLAY_STATUS {
101         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
102
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})$");
107
108         @Override
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))));
114
115                 handler.updateChannel(CONTROL, "2".equals(matcher.group(1)) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
116
117                 handler.updateChannel(PLAY_SPEED, new StringType(matcher.group(2)));
118
119                 handler.updateChannel(TITLE_NUM, new DecimalType(Integer.parseInt(matcher.group(3))));
120
121                 handler.updateChannel(TITLE_LENGTH,
122                         new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
123
124                 handler.updateChannel(TITLE_LOC,
125                         new QuantityType<Time>(Integer.parseInt(matcher.group(5)), handler.apiSecondUnit));
126
127                 handler.updateChannel(CHAPTER_NUM, new DecimalType(Integer.parseInt(matcher.group(6))));
128
129                 handler.updateChannel(CHAPTER_LENGTH,
130                         new QuantityType<Time>(Integer.parseInt(matcher.group(7)), handler.apiSecondUnit));
131
132                 handler.updateChannel(CHAPTER_LOC,
133                         new QuantityType<Time>(Integer.parseInt(matcher.group(8)), handler.apiSecondUnit));
134             } else {
135                 logger.debug("PLAY_STATUS - no match on message: {}", message);
136             }
137         }
138     },
139     MOVIE_MEDIA_TYPE {
140         @Override
141         public void handleMessage(String message, KaleidescapeHandler handler) {
142             handler.updateChannel(KaleidescapeBindingConstants.MOVIE_MEDIA_TYPE,
143                     new StringType(KaleidescapeStatusCodes.MEDIA_TYPE.get(message)));
144         }
145     },
146     MOVIE_LOCATION {
147         @Override
148         public void handleMessage(String message, KaleidescapeHandler handler) {
149             handler.updateChannel(KaleidescapeBindingConstants.MOVIE_LOCATION,
150                     new StringType(KaleidescapeStatusCodes.MOVIE_LOCATION.get(message)));
151         }
152     },
153     VIDEO_MODE {
154         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
155
156         // example: 00:00:00
157         // composite, component, hdmi
158         private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
159
160         @Override
161         public void handleMessage(String message, KaleidescapeHandler handler) {
162             handler.updateChannel(KaleidescapeBindingConstants.VIDEO_MODE, new StringType(message));
163
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))));
168
169                 handler.updateChannel(VIDEO_MODE_COMPONENT,
170                         new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(2))));
171
172                 handler.updateChannel(VIDEO_MODE_HDMI,
173                         new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(3))));
174             } else {
175                 logger.debug("VIDEO_MODE - no match on message: {}", message);
176             }
177         }
178     },
179     VIDEO_COLOR {
180         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
181
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})$");
185
186         @Override
187         public void handleMessage(String message, KaleidescapeHandler handler) {
188             handler.updateChannel(KaleidescapeBindingConstants.VIDEO_COLOR, new StringType(message));
189
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))));
194             } else {
195                 logger.debug("VIDEO_COLOR - no match on message: {}", message);
196             }
197         }
198     },
199     CONTENT_COLOR {
200         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
201
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})$");
205
206         @Override
207         public void handleMessage(String message, KaleidescapeHandler handler) {
208             handler.updateChannel(KaleidescapeBindingConstants.CONTENT_COLOR, new StringType(message));
209
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))));
214             } else {
215                 logger.debug("CONTENT_COLOR - no match on message: {}", message);
216             }
217         }
218     },
219     SCALE_MODE {
220         @Override
221         public void handleMessage(String message, KaleidescapeHandler handler) {
222             handler.updateChannel(KaleidescapeBindingConstants.SCALE_MODE, new StringType(message));
223         }
224     },
225     SCREEN_MASK {
226         @Override
227         public void handleMessage(String message, KaleidescapeHandler handler) {
228             handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK, new StringType(message));
229
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])));
236             }
237         }
238     },
239     SCREEN_MASK2 {
240         @Override
241         public void handleMessage(String message, KaleidescapeHandler handler) {
242             handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK2, new StringType(message));
243         }
244     },
245     CINEMASCAPE_MASK {
246         @Override
247         public void handleMessage(String message, KaleidescapeHandler handler) {
248             handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MASK, new StringType(message));
249         }
250     },
251     CINEMASCAPE_MODE {
252         @Override
253         public void handleMessage(String message, KaleidescapeHandler handler) {
254             handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MODE, new StringType(message));
255         }
256     },
257     CHILD_MODE_STATE {
258         @Override
259         public void handleMessage(String message, KaleidescapeHandler handler) {
260             handler.updateChannel(KaleidescapeBindingConstants.CHILD_MODE_STATE, new StringType(message));
261         }
262     },
263     MUSIC_TITLE {
264         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
265
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("^(.*):(.*):(.*):(.*):(.*):(.*)$");
269
270         @Override
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))));
277
278                 handler.updateChannel(MUSIC_ARTIST,
279                         new StringType(KaleidescapeFormatter.formatString(matcher.group(2))));
280
281                 handler.updateChannel(MUSIC_ALBUM,
282                         new StringType(KaleidescapeFormatter.formatString(matcher.group(3))));
283
284                 handler.updateChannel(MUSIC_TRACK_HANDLE, new StringType(matcher.group(4)));
285
286                 handler.updateChannel(MUSIC_ALBUM_HANDLE, new StringType(matcher.group(5)));
287
288                 handler.updateChannel(MUSIC_NOWPLAY_HANDLE, new StringType(matcher.group(6)));
289
290                 if (handler.isLoadAlbumDetails) {
291                     try {
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: {}",
295                                 matcher.group(5));
296                     }
297                 }
298             } else {
299                 logger.debug("MUSIC_TITLE - no match on message: {}", message);
300             }
301         }
302     },
303     MUSIC_PLAY_STATUS {
304         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
305
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})$");
310
311         @Override
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))));
317
318                 handler.updateChannel(MUSIC_CONTROL,
319                         "2".equals(matcher.group(1)) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
320
321                 handler.updateChannel(MUSIC_PLAY_SPEED, new StringType(matcher.group(2)));
322
323                 handler.updateChannel(MUSIC_TRACK_LENGTH,
324                         new QuantityType<Time>(Integer.parseInt(matcher.group(3)), handler.apiSecondUnit));
325
326                 handler.updateChannel(MUSIC_TRACK_POSITION,
327                         new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
328
329                 handler.updateChannel(MUSIC_TRACK_PROGRESS,
330                         new DecimalType(BigDecimal.valueOf(Math.round(Double.parseDouble(matcher.group(5))))));
331             } else {
332                 logger.debug("MUSIC_PLAY_STATUS - no match on message: {}", message);
333             }
334         }
335     },
336     MUSIC_NOW_PLAYING_STATUS {
337         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
338
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}):(.*)$");
343
344         @Override
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, (ONE).equals(matcher.group(3)) ? OnOffType.ON : OnOffType.OFF);
350
351                 // update RANDOM switch state
352                 handler.updateChannel(MUSIC_RANDOM, (ONE).equals(matcher.group(4)) ? OnOffType.ON : OnOffType.OFF);
353             } else {
354                 logger.debug("MUSIC_NOW_PLAYING_STATUS - no match on message: {}", message);
355             }
356         }
357     },
358     PLAYING_MUSIC_INFORMATION {
359         @Override
360         public void handleMessage(String message, KaleidescapeHandler handler) {
361             // example: R_1493833:Radiohead - Pablo Honey
362             // album handle, artist - album
363             // do nothing; redundant
364         }
365     },
366     CONTENT_DETAILS {
367         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
368
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}):([^:^/]*):(.*)$");
373
374         @Override
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));
380
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;
386
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);
391
392                     } else if ((ALBUM_CONTENT_HANDLE).equals(metaType)) {
393                         handler.updateDetailChannel(DETAIL_TYPE, new StringType(ALBUM));
394                         handler.metaRuntimeMultiple = 1;
395
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);
406
407                     } else {
408                         handler.updateDetailChannel(DETAIL_TYPE, UnDefType.UNDEF);
409                     }
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)) {
416                             try {
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"));
423                                 } else {
424                                     handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
425                                 }
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);
429                             }
430                         } else {
431                             handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
432                         }
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
438                     } else {
439                         handler.updateDetailChannel(metaType, new StringType(value));
440                     }
441                 }
442             } else {
443                 logger.debug("CONTENT_DETAILS - no match on message: {}", message);
444             }
445         }
446     },
447     TIME {
448         @Override
449         public void handleMessage(String message, KaleidescapeHandler handler) {
450             // do nothing
451         }
452     },
453     STATUS_CUE_PERIOD {
454         @Override
455         public void handleMessage(String message, KaleidescapeHandler handler) {
456             // do nothing
457         }
458     },
459     ASPECT_RATIO {
460         @Override
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
464         }
465     },
466     USER_DEFINED_EVENT {
467         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
468
469         @Override
470         public void handleMessage(String message, KaleidescapeHandler handler) {
471             // example: SELECT_KALEIDESCAPE_INPUT
472             try {
473                 switch (message) {
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
476                     case "VOLUME_QUERY":
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));
482                             }
483                         }
484                         break;
485                     case "VOLUME_UP":
486                         if (handler.volumeEnabled) {
487                             synchronized (handler.sequenceLock) {
488                                 handler.volume++;
489                                 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
490                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
491                             }
492                         }
493                         break;
494                     case "VOLUME_DOWN":
495                         if (handler.volumeEnabled) {
496                             synchronized (handler.sequenceLock) {
497                                 handler.volume--;
498                                 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
499                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
500                             }
501                         }
502                         break;
503                     case "TOGGLE_MUTE":
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;
510                                 } else {
511                                     state = OnOffType.ON;
512                                     handler.isMuted = true;
513                                 }
514                                 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
515                                 handler.updateChannel(MUTE, state);
516                             }
517                         }
518                         break;
519                     // the default is to just publish all other USER_DEFINED_EVENTs
520                     default:
521                         handler.updateChannel(KaleidescapeBindingConstants.USER_DEFINED_EVENT, new StringType(message));
522                 }
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 }