]> git.basschouten.com Git - openhab-addons.git/blob
6e64b5453c8efd6022536d43a13ecd8278966054
[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.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;
44
45 /**
46  * The {@link KaleidescapeMessageHandler} class processes all messages received
47  * by the event listener and then updates the appropriate states
48  *
49  * @author Michael Lobstein - Initial contribution
50  */
51 @NonNullByDefault
52 public enum KaleidescapeMessageHandler {
53     UI_STATE {
54         @Override
55         public void handleMessage(String message, KaleidescapeHandler handler) {
56             handler.updateChannel(KaleidescapeBindingConstants.UI_STATE, new StringType(message));
57         }
58     },
59     HIGHLIGHTED_SELECTION {
60         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
61
62         @Override
63         public void handleMessage(String message, KaleidescapeHandler handler) {
64             handler.updateChannel(KaleidescapeBindingConstants.HIGHLIGHTED_SELECTION, new StringType(message));
65
66             if (handler.isLoadHighlightedDetails) {
67                 try {
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);
71                 }
72             }
73         }
74     },
75     DEVICE_POWER_STATE {
76         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
77
78         // example: 1:1
79         // power_state, zone 1 state, zone n state
80         private final Pattern p = Pattern.compile("^(\\d{1}):(.*)$");
81
82         @Override
83         public void handleMessage(String message, KaleidescapeHandler handler) {
84             Matcher matcher = p.matcher(message);
85             if (matcher.find()) {
86                 handler.updateChannel(POWER, (ONE).equals(matcher.group(1)) ? OnOffType.ON : OnOffType.OFF);
87             } else {
88                 logger.debug("DEVICE_POWER_STATE - no match on message: {}", message);
89             }
90         }
91     },
92     TITLE_NAME {
93         @Override
94         public void handleMessage(String message, KaleidescapeHandler handler) {
95             handler.updateChannel(KaleidescapeBindingConstants.TITLE_NAME,
96                     new StringType(KaleidescapeFormatter.formatString(message)));
97         }
98     },
99     PLAY_STATUS {
100         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
101
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})$");
106
107         @Override
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))));
113
114                 handler.updateChannel(PLAY_SPEED, new StringType(matcher.group(2)));
115
116                 handler.updateChannel(TITLE_NUM, new DecimalType(Integer.parseInt(matcher.group(3))));
117
118                 handler.updateChannel(TITLE_LENGTH,
119                         new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
120
121                 handler.updateChannel(TITLE_LOC,
122                         new QuantityType<Time>(Integer.parseInt(matcher.group(5)), handler.apiSecondUnit));
123
124                 handler.updateChannel(CHAPTER_NUM, new DecimalType(Integer.parseInt(matcher.group(6))));
125
126                 handler.updateChannel(CHAPTER_LENGTH,
127                         new QuantityType<Time>(Integer.parseInt(matcher.group(7)), handler.apiSecondUnit));
128
129                 handler.updateChannel(CHAPTER_LOC,
130                         new QuantityType<Time>(Integer.parseInt(matcher.group(8)), handler.apiSecondUnit));
131             } else {
132                 logger.debug("PLAY_STATUS - no match on message: {}", message);
133             }
134         }
135     },
136     MOVIE_MEDIA_TYPE {
137         @Override
138         public void handleMessage(String message, KaleidescapeHandler handler) {
139             handler.updateChannel(KaleidescapeBindingConstants.MOVIE_MEDIA_TYPE,
140                     new StringType(KaleidescapeStatusCodes.MEDIA_TYPE.get(message)));
141         }
142     },
143     MOVIE_LOCATION {
144         @Override
145         public void handleMessage(String message, KaleidescapeHandler handler) {
146             handler.updateChannel(KaleidescapeBindingConstants.MOVIE_LOCATION,
147                     new StringType(KaleidescapeStatusCodes.MOVIE_LOCATION.get(message)));
148         }
149     },
150     VIDEO_MODE {
151         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
152
153         // example: 00:00:00
154         // composite, component, hdmi
155         private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
156
157         @Override
158         public void handleMessage(String message, KaleidescapeHandler handler) {
159             handler.updateChannel(KaleidescapeBindingConstants.VIDEO_MODE, new StringType(message));
160
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))));
165
166                 handler.updateChannel(VIDEO_MODE_COMPONENT,
167                         new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(2))));
168
169                 handler.updateChannel(VIDEO_MODE_HDMI,
170                         new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(3))));
171             } else {
172                 logger.debug("VIDEO_MODE - no match on message: {}", message);
173             }
174         }
175     },
176     VIDEO_COLOR {
177         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
178
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})$");
182
183         @Override
184         public void handleMessage(String message, KaleidescapeHandler handler) {
185             handler.updateChannel(KaleidescapeBindingConstants.VIDEO_COLOR, new StringType(message));
186
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))));
191             } else {
192                 logger.debug("VIDEO_COLOR - no match on message: {}", message);
193             }
194         }
195     },
196     CONTENT_COLOR {
197         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
198
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})$");
202
203         @Override
204         public void handleMessage(String message, KaleidescapeHandler handler) {
205             handler.updateChannel(KaleidescapeBindingConstants.CONTENT_COLOR, new StringType(message));
206
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))));
211             } else {
212                 logger.debug("CONTENT_COLOR - no match on message: {}", message);
213             }
214         }
215     },
216     SCALE_MODE {
217         @Override
218         public void handleMessage(String message, KaleidescapeHandler handler) {
219             handler.updateChannel(KaleidescapeBindingConstants.SCALE_MODE, new StringType(message));
220         }
221     },
222     SCREEN_MASK {
223         @Override
224         public void handleMessage(String message, KaleidescapeHandler handler) {
225             handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK, new StringType(message));
226
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])));
233             }
234         }
235     },
236     SCREEN_MASK2 {
237         @Override
238         public void handleMessage(String message, KaleidescapeHandler handler) {
239             handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK2, new StringType(message));
240         }
241     },
242     CINEMASCAPE_MASK {
243         @Override
244         public void handleMessage(String message, KaleidescapeHandler handler) {
245             handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MASK, new StringType(message));
246         }
247     },
248     CINEMASCAPE_MODE {
249         @Override
250         public void handleMessage(String message, KaleidescapeHandler handler) {
251             handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MODE, new StringType(message));
252         }
253     },
254     CHILD_MODE_STATE {
255         @Override
256         public void handleMessage(String message, KaleidescapeHandler handler) {
257             handler.updateChannel(KaleidescapeBindingConstants.CHILD_MODE_STATE, new StringType(message));
258         }
259     },
260     MUSIC_TITLE {
261         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
262
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("^(.*):(.*):(.*):(.*):(.*):(.*)$");
266
267         @Override
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))));
274
275                 handler.updateChannel(MUSIC_ARTIST,
276                         new StringType(KaleidescapeFormatter.formatString(matcher.group(2))));
277
278                 handler.updateChannel(MUSIC_ALBUM,
279                         new StringType(KaleidescapeFormatter.formatString(matcher.group(3))));
280
281                 handler.updateChannel(MUSIC_TRACK_HANDLE, new StringType(matcher.group(4)));
282
283                 handler.updateChannel(MUSIC_ALBUM_HANDLE, new StringType(matcher.group(5)));
284
285                 handler.updateChannel(MUSIC_NOWPLAY_HANDLE, new StringType(matcher.group(6)));
286
287                 if (handler.isLoadAlbumDetails) {
288                     try {
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: {}",
292                                 matcher.group(5));
293                     }
294                 }
295             } else {
296                 logger.debug("MUSIC_TITLE - no match on message: {}", message);
297             }
298         }
299     },
300     MUSIC_PLAY_STATUS {
301         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
302
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})$");
307
308         @Override
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))));
314
315                 handler.updateChannel(MUSIC_PLAY_SPEED, new StringType(matcher.group(2)));
316
317                 handler.updateChannel(MUSIC_TRACK_LENGTH,
318                         new QuantityType<Time>(Integer.parseInt(matcher.group(3)), handler.apiSecondUnit));
319
320                 handler.updateChannel(MUSIC_TRACK_POSITION,
321                         new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
322
323                 handler.updateChannel(MUSIC_TRACK_PROGRESS,
324                         new DecimalType(BigDecimal.valueOf(Math.round(Double.parseDouble(matcher.group(5))))));
325             } else {
326                 logger.debug("MUSIC_PLAY_STATUS - no match on message: {}", message);
327             }
328         }
329     },
330     MUSIC_NOW_PLAYING_STATUS {
331         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
332
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}):(.*)$");
337
338         @Override
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);
344
345                 // update RANDOM switch state
346                 handler.updateChannel(MUSIC_RANDOM, (ONE).equals(matcher.group(4)) ? OnOffType.ON : OnOffType.OFF);
347             } else {
348                 logger.debug("MUSIC_NOW_PLAYING_STATUS - no match on message: {}", message);
349             }
350         }
351     },
352     PLAYING_MUSIC_INFORMATION {
353         @Override
354         public void handleMessage(String message, KaleidescapeHandler handler) {
355             // example: R_1493833:Radiohead - Pablo Honey
356             // album handle, artist - album
357             // do nothing; redundant
358         }
359     },
360     CONTENT_DETAILS {
361         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
362
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}):([^:^/]*):(.*)$");
367
368         @Override
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));
374
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;
380
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);
385
386                     } else if ((ALBUM_CONTENT_HANDLE).equals(metaType)) {
387                         handler.updateDetailChannel(DETAIL_TYPE, new StringType(ALBUM));
388                         handler.metaRuntimeMultiple = 1;
389
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);
400
401                     } else {
402                         handler.updateDetailChannel(DETAIL_TYPE, UnDefType.UNDEF);
403                     }
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() && handler.isChannelLinked(DETAIL + DETAIL_COVER_ART)) {
410                             try {
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));
417                                 } else {
418                                     handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
419                                 }
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);
423                             }
424                         } else {
425                             handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
426                         }
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
432                     } else {
433                         handler.updateDetailChannel(metaType, new StringType(value));
434                     }
435                 }
436             } else {
437                 logger.debug("CONTENT_DETAILS - no match on message: {}", message);
438             }
439         }
440     },
441     TIME {
442         @Override
443         public void handleMessage(String message, KaleidescapeHandler handler) {
444             // do nothing
445         }
446     },
447     STATUS_CUE_PERIOD {
448         @Override
449         public void handleMessage(String message, KaleidescapeHandler handler) {
450             // do nothing
451         }
452     },
453     ASPECT_RATIO {
454         @Override
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
458         }
459     },
460     USER_DEFINED_EVENT {
461         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
462
463         @Override
464         public void handleMessage(String message, KaleidescapeHandler handler) {
465             // example: SELECT_KALEIDESCAPE_INPUT
466             try {
467                 switch (message) {
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
470                     case "VOLUME_QUERY":
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));
476                             }
477                         }
478                         break;
479                     case "VOLUME_UP":
480                         if (handler.volumeEnabled) {
481                             synchronized (handler.sequenceLock) {
482                                 handler.volume++;
483                                 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
484                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
485                             }
486                         }
487                         break;
488                     case "VOLUME_DOWN":
489                         if (handler.volumeEnabled) {
490                             synchronized (handler.sequenceLock) {
491                                 handler.volume--;
492                                 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
493                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
494                             }
495                         }
496                         break;
497                     case "TOGGLE_MUTE":
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;
504                                 } else {
505                                     state = OnOffType.ON;
506                                     handler.isMuted = true;
507                                 }
508                                 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
509                                 handler.updateChannel(MUTE, state);
510                             }
511                         }
512                         break;
513                     // the default is to just publish all other USER_DEFINED_EVENTs
514                     default:
515                         handler.updateChannel(KaleidescapeBindingConstants.USER_DEFINED_EVENT, new StringType(message));
516                 }
517             } catch (KaleidescapeException e) {
518                 logger.debug("USER_DEFINED_EVENT - exception on message: {}", message);
519             }
520         }
521     },
522     USER_INPUT {
523         @Override
524         public void handleMessage(String message, KaleidescapeHandler handler) {
525             // example: 01:Search for title:ABC
526             handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
527         }
528     },
529     USER_INPUT_PROMPT {
530         @Override
531         public void handleMessage(String message, KaleidescapeHandler handler) {
532             // example: 00:00::00:0:1
533             handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
534         }
535     },
536     SYSTEM_READINESS_STATE {
537         @Override
538         public void handleMessage(String message, KaleidescapeHandler handler) {
539             // example 1, 2 or 3
540             handler.updateChannel(KaleidescapeBindingConstants.SYSTEM_READINESS_STATE,
541                     new StringType(KaleidescapeStatusCodes.READINESS_STATE.get(message)));
542         }
543     },
544     SYSTEM_VERSION {
545         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
546
547         // example: 16:8.6.0-21023
548         // protocol version, kOS version
549         private final Pattern p = Pattern.compile("^(\\d{2}):(.*)$");
550
551         @Override
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));
557             } else {
558                 logger.debug("SYSTEM_VERSION - no match on message: {}", message);
559             }
560         }
561     },
562     DEVICE_INFO {
563         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
564
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}):(.*)$");
568
569         @Override
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));
576             } else {
577                 logger.debug("DEVICE_INFO - no match on message: {}", message);
578             }
579         }
580     },
581     DEVICE_TYPE_NAME {
582         @Override
583         public void handleMessage(String message, KaleidescapeHandler handler) {
584             // example: 'Player' or 'Strato'
585             handler.updateThingProperty(PROPERTY_COMPONENT_TYPE, message);
586         }
587     },
588     FRIENDLY_NAME {
589         @Override
590         public void handleMessage(String message, KaleidescapeHandler handler) {
591             // example: 'Living Room'
592             handler.friendlyName = message;
593             handler.updateThingProperty(PROPERTY_FRIENDLY_NAME, message);
594         }
595     };
596
597     public abstract void handleMessage(String message, KaleidescapeHandler handler);
598 }