]> git.basschouten.com Git - openhab-addons.git/blob
2469d65d2d2aa5cdd3974abafaa163cdeb0432c0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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         @Override
61         public void handleMessage(String message, KaleidescapeHandler handler) {
62             handler.updateChannel(KaleidescapeBindingConstants.HIGHLIGHTED_SELECTION, new StringType(message));
63         }
64     },
65     DEVICE_POWER_STATE {
66         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
67
68         // example: 1:1
69         // power_state, zone 1 state, zone n state
70         private final Pattern p = Pattern.compile("^(\\d{1}):(.*)$");
71
72         @Override
73         public void handleMessage(String message, KaleidescapeHandler handler) {
74             Matcher matcher = p.matcher(message);
75             if (matcher.find()) {
76                 handler.updateChannel(POWER, (ONE).equals(matcher.group(1)) ? OnOffType.ON : OnOffType.OFF);
77             } else {
78                 logger.debug("DEVICE_POWER_STATE - no match on message: {}", message);
79             }
80         }
81     },
82     TITLE_NAME {
83         @Override
84         public void handleMessage(String message, KaleidescapeHandler handler) {
85             handler.updateChannel(KaleidescapeBindingConstants.TITLE_NAME,
86                     new StringType(KaleidescapeFormatter.formatString(message)));
87         }
88     },
89     PLAY_STATUS {
90         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
91
92         // example: 0:0:00:00000:00000:000:00000:00000
93         // mode, speed, title_num, title_length, title_loc, chapter_num, chapter_length, chapter_loc
94         private final Pattern p = Pattern
95                 .compile("^(\\d{1}):(\\d{1}):(\\d{2}):(\\d{5}):(\\d{5}):(\\d{3}):(\\d{5}):(\\d{5})$");
96
97         @Override
98         public void handleMessage(String message, KaleidescapeHandler handler) {
99             Matcher matcher = p.matcher(message);
100             if (matcher.find()) {
101                 handler.updateChannel(PLAY_MODE,
102                         new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
103
104                 handler.updateChannel(PLAY_SPEED, new StringType(matcher.group(2)));
105
106                 handler.updateChannel(TITLE_NUM, new DecimalType(Integer.parseInt(matcher.group(3))));
107
108                 handler.updateChannel(TITLE_LENGTH,
109                         new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
110
111                 handler.updateChannel(TITLE_LOC,
112                         new QuantityType<Time>(Integer.parseInt(matcher.group(5)), handler.apiSecondUnit));
113
114                 handler.updateChannel(CHAPTER_NUM, new DecimalType(Integer.parseInt(matcher.group(6))));
115
116                 handler.updateChannel(CHAPTER_LENGTH,
117                         new QuantityType<Time>(Integer.parseInt(matcher.group(7)), handler.apiSecondUnit));
118
119                 handler.updateChannel(CHAPTER_LOC,
120                         new QuantityType<Time>(Integer.parseInt(matcher.group(8)), handler.apiSecondUnit));
121             } else {
122                 logger.debug("PLAY_STATUS - no match on message: {}", message);
123             }
124         }
125     },
126     MOVIE_MEDIA_TYPE {
127         @Override
128         public void handleMessage(String message, KaleidescapeHandler handler) {
129             handler.updateChannel(KaleidescapeBindingConstants.MOVIE_MEDIA_TYPE,
130                     new StringType(KaleidescapeStatusCodes.MEDIA_TYPE.get(message)));
131         }
132     },
133     MOVIE_LOCATION {
134         @Override
135         public void handleMessage(String message, KaleidescapeHandler handler) {
136             handler.updateChannel(KaleidescapeBindingConstants.MOVIE_LOCATION,
137                     new StringType(KaleidescapeStatusCodes.MOVIE_LOCATION.get(message)));
138         }
139     },
140     VIDEO_MODE {
141         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
142
143         // example: 00:00:00
144         // composite, component, hdmi
145         private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
146
147         @Override
148         public void handleMessage(String message, KaleidescapeHandler handler) {
149             handler.updateChannel(KaleidescapeBindingConstants.VIDEO_MODE, new StringType(message));
150
151             Matcher matcher = p.matcher(message);
152             if (matcher.find()) {
153                 handler.updateChannel(VIDEO_MODE_COMPOSITE,
154                         new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(1))));
155
156                 handler.updateChannel(VIDEO_MODE_COMPONENT,
157                         new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(2))));
158
159                 handler.updateChannel(VIDEO_MODE_HDMI,
160                         new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(3))));
161             } else {
162                 logger.debug("VIDEO_MODE - no match on message: {}", message);
163             }
164         }
165     },
166     VIDEO_COLOR {
167         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
168
169         // example: 02:01:24:01
170         // eotf, color_space, color_depth, color_sampling
171         private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
172
173         @Override
174         public void handleMessage(String message, KaleidescapeHandler handler) {
175             handler.updateChannel(KaleidescapeBindingConstants.VIDEO_COLOR, new StringType(message));
176
177             Matcher matcher = p.matcher(message);
178             if (matcher.find()) {
179                 handler.updateChannel(VIDEO_COLOR_EOTF,
180                         new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
181             } else {
182                 logger.debug("VIDEO_COLOR - no match on message: {}", message);
183             }
184         }
185     },
186     CONTENT_COLOR {
187         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
188
189         // example: 02:01:24:01
190         // eotf, color_space, color_depth, color_sampling
191         private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
192
193         @Override
194         public void handleMessage(String message, KaleidescapeHandler handler) {
195             handler.updateChannel(KaleidescapeBindingConstants.CONTENT_COLOR, new StringType(message));
196
197             Matcher matcher = p.matcher(message);
198             if (matcher.find()) {
199                 handler.updateChannel(CONTENT_COLOR_EOTF,
200                         new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
201             } else {
202                 logger.debug("CONTENT_COLOR - no match on message: {}", message);
203             }
204         }
205     },
206     SCALE_MODE {
207         @Override
208         public void handleMessage(String message, KaleidescapeHandler handler) {
209             handler.updateChannel(KaleidescapeBindingConstants.SCALE_MODE, new StringType(message));
210         }
211     },
212     SCREEN_MASK {
213         @Override
214         public void handleMessage(String message, KaleidescapeHandler handler) {
215             handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK, new StringType(message));
216
217             // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
218             // the first element of SCREEN_MASK now provides this info
219             if (!message.equals(EMPTY)) {
220                 String[] msgSplit = message.split(":", 2);
221                 handler.updateChannel(KaleidescapeBindingConstants.ASPECT_RATIO,
222                         new StringType(KaleidescapeStatusCodes.ASPECT_RATIO.get(msgSplit[0])));
223             }
224         }
225     },
226     SCREEN_MASK2 {
227         @Override
228         public void handleMessage(String message, KaleidescapeHandler handler) {
229             handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK2, new StringType(message));
230         }
231     },
232     CINEMASCAPE_MASK {
233         @Override
234         public void handleMessage(String message, KaleidescapeHandler handler) {
235             handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MASK, new StringType(message));
236         }
237     },
238     CINEMASCAPE_MODE {
239         @Override
240         public void handleMessage(String message, KaleidescapeHandler handler) {
241             handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MODE, new StringType(message));
242         }
243     },
244     CHILD_MODE_STATE {
245         @Override
246         public void handleMessage(String message, KaleidescapeHandler handler) {
247             handler.updateChannel(KaleidescapeBindingConstants.CHILD_MODE_STATE, new StringType(message));
248         }
249     },
250     MUSIC_TITLE {
251         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
252
253         // example: You:Radiohead:Pablo Honey:1.9b5f4d786d7e2c49-t301_577:1.R_1493833:2.200c5
254         // track, artist, album, track handle, album handle, now playing handle
255         private final Pattern p = Pattern.compile("^(.*):(.*):(.*):(.*):(.*):(.*)$");
256
257         @Override
258         public void handleMessage(String message, KaleidescapeHandler handler) {
259             // first replace delimited : in track/artist/album name with ||, fix it later in formatString()
260             Matcher matcher = p.matcher(message.replace("\\:", "||"));
261             if (matcher.find()) {
262                 handler.updateChannel(MUSIC_TRACK,
263                         new StringType(KaleidescapeFormatter.formatString(matcher.group(1))));
264
265                 handler.updateChannel(MUSIC_ARTIST,
266                         new StringType(KaleidescapeFormatter.formatString(matcher.group(2))));
267
268                 handler.updateChannel(MUSIC_ALBUM,
269                         new StringType(KaleidescapeFormatter.formatString(matcher.group(3))));
270
271                 handler.updateChannel(MUSIC_TRACK_HANDLE, new StringType(matcher.group(4)));
272
273                 handler.updateChannel(MUSIC_ALBUM_HANDLE, new StringType(matcher.group(5)));
274
275                 handler.updateChannel(MUSIC_NOWPLAY_HANDLE, new StringType(matcher.group(6)));
276             } else {
277                 logger.debug("MUSIC_TITLE - no match on message: {}", message);
278             }
279         }
280     },
281     MUSIC_PLAY_STATUS {
282         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
283
284         // example: 2:0:00207:+00000:000.00
285         // 2:0:00331:+00183:055.29
286         // mode, speed, track length, track position, track progress %
287         private final Pattern p = Pattern.compile("^(\\d{1}):(\\d{1}):(\\d{5}):(.\\d{5}):(\\d{3}\\.\\d{2})$");
288
289         @Override
290         public void handleMessage(String message, KaleidescapeHandler handler) {
291             Matcher matcher = p.matcher(message);
292             if (matcher.find()) {
293                 handler.updateChannel(MUSIC_PLAY_MODE,
294                         new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
295
296                 handler.updateChannel(MUSIC_PLAY_SPEED, new StringType(matcher.group(2)));
297
298                 handler.updateChannel(MUSIC_TRACK_LENGTH,
299                         new QuantityType<Time>(Integer.parseInt(matcher.group(3)), handler.apiSecondUnit));
300
301                 handler.updateChannel(MUSIC_TRACK_POSITION,
302                         new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
303
304                 handler.updateChannel(MUSIC_TRACK_PROGRESS,
305                         new DecimalType(BigDecimal.valueOf(Math.round(Double.parseDouble(matcher.group(5))))));
306             } else {
307                 logger.debug("MUSIC_PLAY_STATUS - no match on message: {}", message);
308             }
309         }
310     },
311     MUSIC_NOW_PLAYING_STATUS {
312         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
313
314         // example: 00013:00000:0:0:0000000238:2.200c5
315         // total # tracks in list, list index, repeat, random, generation, now_playing handle
316         // only using repeat & random right now
317         private final Pattern p = Pattern.compile("^(\\d{5}):(\\d{5}):(\\d{1}):(\\d{1}):(\\d{10}):(.*)$");
318
319         @Override
320         public void handleMessage(String message, KaleidescapeHandler handler) {
321             Matcher matcher = p.matcher(message);
322             if (matcher.find()) {
323                 // update REPEAT switch state
324                 handler.updateChannel(MUSIC_REPEAT, (ONE).equals(matcher.group(3)) ? OnOffType.ON : OnOffType.OFF);
325
326                 // update RANDOM switch state
327                 handler.updateChannel(MUSIC_RANDOM, (ONE).equals(matcher.group(4)) ? OnOffType.ON : OnOffType.OFF);
328             } else {
329                 logger.debug("MUSIC_NOW_PLAYING_STATUS - no match on message: {}", message);
330             }
331         }
332     },
333     PLAYING_MUSIC_INFORMATION {
334         @Override
335         public void handleMessage(String message, KaleidescapeHandler handler) {
336             // example: R_1493833:Radiohead - Pablo Honey
337             // album handle, artist - album
338             // do nothing; redundant
339         }
340     },
341     CONTENT_DETAILS {
342         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
343
344         // g1=meta id, g2=meta type, g3=data
345         // example: 6:Year:1995
346         // or: 10:Genres:Pop\/Rock
347         private final Pattern p = Pattern.compile("^(\\d{1,2}):([^:^/]*):(.*)$");
348
349         @Override
350         public void handleMessage(String message, KaleidescapeHandler handler) {
351             Matcher matcher = p.matcher(message);
352             if (matcher.find()) {
353                 String metaType = matcher.group(2).toLowerCase();
354                 String value = KaleidescapeFormatter.formatString(matcher.group(3));
355
356                 // the CONTENT_DETAILS message with id=1 tells us what type of meta data is coming
357                 if (ONE.equals(matcher.group(1))) {
358                     if ((CONTENT_HANDLE).equals(metaType)) {
359                         handler.updateDetailChannel(DETAIL_TYPE, new StringType(MOVIE));
360                         handler.metaRuntimeMultiple = 60;
361
362                         // null out album specific
363                         handler.updateDetailChannel(DETAIL_ALBUM_TITLE, UnDefType.NULL);
364                         handler.updateDetailChannel(DETAIL_ARTIST, UnDefType.NULL);
365                         handler.updateDetailChannel(DETAIL_REVIEW, UnDefType.NULL);
366
367                     } else if ((ALBUM_CONTENT_HANDLE).equals(metaType)) {
368                         handler.updateDetailChannel(DETAIL_TYPE, new StringType(ALBUM));
369                         handler.metaRuntimeMultiple = 1;
370
371                         // null out movie specific
372                         handler.updateDetailChannel(DETAIL_TITLE, UnDefType.NULL);
373                         handler.updateDetailChannel(DETAIL_RATING, UnDefType.NULL);
374                         handler.updateDetailChannel(DETAIL_ACTORS, UnDefType.NULL);
375                         handler.updateDetailChannel(DETAIL_DIRECTORS, UnDefType.NULL);
376                         handler.updateDetailChannel(DETAIL_RATING_REASON, UnDefType.NULL);
377                         handler.updateDetailChannel(DETAIL_SYNOPSIS, UnDefType.NULL);
378                         handler.updateDetailChannel(DETAIL_COLOR_DESCRIPTION, UnDefType.NULL);
379                         handler.updateDetailChannel(DETAIL_COUNTRY, UnDefType.NULL);
380                         handler.updateDetailChannel(DETAIL_ASPECT_RATIO, UnDefType.NULL);
381
382                     } else {
383                         handler.updateDetailChannel(DETAIL_TYPE, UnDefType.UNDEF);
384                     }
385                     // otherwise update the channel if it is one we care about
386                 } else if (METADATA_CHANNELS.contains(metaType)) {
387                     // special case for cover art image
388                     if (DETAIL_COVER_URL.equals(metaType)) {
389                         handler.updateDetailChannel(metaType, new StringType(value));
390                         if (!value.isEmpty()) {
391                             try {
392                                 ContentResponse contentResponse = handler.httpClient.newRequest(value).method(GET)
393                                         .timeout(10, TimeUnit.SECONDS).send();
394                                 int httpStatus = contentResponse.getStatus();
395                                 if (httpStatus == OK_200) {
396                                     handler.updateDetailChannel(DETAIL_COVER_ART,
397                                             new RawType(contentResponse.getContent(), RawType.DEFAULT_MIME_TYPE));
398                                 } else {
399                                     handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
400                                 }
401                             } catch (InterruptedException | TimeoutException | ExecutionException e) {
402                                 logger.debug("Error updating Cover Art Image channel for url: {}", value);
403                                 handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
404                             }
405                         } else {
406                             handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
407                         }
408                         // special case for running time to create a QuantityType<Time>
409                     } else if (DETAIL_RUNNING_TIME.equals(metaType)) {
410                         handler.updateDetailChannel(DETAIL_RUNNING_TIME, new QuantityType<Time>(
411                                 Integer.parseInt(value) * handler.metaRuntimeMultiple, handler.apiSecondUnit));
412                         // everything else just send it as a string
413                     } else {
414                         handler.updateDetailChannel(metaType, new StringType(value));
415                     }
416                 }
417             } else {
418                 logger.debug("CONTENT_DETAILS - no match on message: {}", message);
419             }
420         }
421     },
422     TIME {
423         @Override
424         public void handleMessage(String message, KaleidescapeHandler handler) {
425             // do nothing
426         }
427     },
428     STATUS_CUE_PERIOD {
429         @Override
430         public void handleMessage(String message, KaleidescapeHandler handler) {
431             // do nothing
432         }
433     },
434     ASPECT_RATIO {
435         @Override
436         public void handleMessage(String message, KaleidescapeHandler handler) {
437             // per API reference rev 3.3.1, ASPECT_RATIO message should not be used
438             // the first element of SCREEN_MASK now provides this info
439         }
440     },
441     USER_DEFINED_EVENT {
442         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
443
444         @Override
445         public void handleMessage(String message, KaleidescapeHandler handler) {
446             // example: SELECT_KALEIDESCAPE_INPUT
447             try {
448                 switch (message) {
449                     // when the ipad or phone app is started up, it sends a VOLUME_QUERY,
450                     // so we respond to enable volume controls and set the initial volume and mute
451                     case "VOLUME_QUERY":
452                         if (handler.volumeEnabled) {
453                             synchronized (handler.sequenceLock) {
454                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_CAPABILITIES_15);
455                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
456                                 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
457                             }
458                         }
459                         break;
460                     case "VOLUME_UP":
461                         if (handler.volumeEnabled) {
462                             synchronized (handler.sequenceLock) {
463                                 handler.volume++;
464                                 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
465                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
466                             }
467                         }
468                         break;
469                     case "VOLUME_DOWN":
470                         if (handler.volumeEnabled) {
471                             synchronized (handler.sequenceLock) {
472                                 handler.volume--;
473                                 handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
474                                 handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
475                             }
476                         }
477                         break;
478                     case "TOGGLE_MUTE":
479                         if (handler.volumeEnabled) {
480                             State state = UnDefType.UNDEF;
481                             synchronized (handler.sequenceLock) {
482                                 if (handler.isMuted) {
483                                     state = OnOffType.OFF;
484                                     handler.isMuted = false;
485                                 } else {
486                                     state = OnOffType.ON;
487                                     handler.isMuted = true;
488                                 }
489                                 handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
490                                 handler.updateChannel(MUTE, state);
491                             }
492                         }
493                         break;
494                     // the default is to just publish all other USER_DEFINED_EVENTs
495                     default:
496                         handler.updateChannel(KaleidescapeBindingConstants.USER_DEFINED_EVENT, new StringType(message));
497                 }
498             } catch (KaleidescapeException e) {
499                 logger.debug("USER_DEFINED_EVENT - exception on message: {}", message);
500             }
501         }
502     },
503     USER_INPUT {
504         @Override
505         public void handleMessage(String message, KaleidescapeHandler handler) {
506             // example: 01:Search for title:ABC
507             handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
508         }
509     },
510     USER_INPUT_PROMPT {
511         @Override
512         public void handleMessage(String message, KaleidescapeHandler handler) {
513             // example: 00:00::00:0:1
514             handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
515         }
516     },
517     SYSTEM_READINESS_STATE {
518         @Override
519         public void handleMessage(String message, KaleidescapeHandler handler) {
520             // example 1, 2 or 3
521             handler.updateChannel(KaleidescapeBindingConstants.SYSTEM_READINESS_STATE,
522                     new StringType(KaleidescapeStatusCodes.READINESS_STATE.get(message)));
523         }
524     },
525     SYSTEM_VERSION {
526         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
527
528         // example: 16:8.6.0-21023
529         // protocol version, kOS version
530         private final Pattern p = Pattern.compile("^(\\d{2}):(.*)$");
531
532         @Override
533         public void handleMessage(String message, KaleidescapeHandler handler) {
534             Matcher matcher = p.matcher(message);
535             if (matcher.find()) {
536                 handler.updateThingProperty(PROPERTY_PROTOCOL_VERSION, matcher.group(1));
537                 handler.updateThingProperty(PROPERTY_SYSTEM_VERSION, matcher.group(2));
538             } else {
539                 logger.debug("SYSTEM_VERSION - no match on message: {}", message);
540             }
541         }
542     },
543     DEVICE_INFO {
544         private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
545
546         // example: 07:000000000000558F:00:192.168.001.100
547         // device type (deprecated), serial number, cpdid, ip address
548         private final Pattern p = Pattern.compile("^(\\d{2}):(.*):(\\d{2}):(.*)$");
549
550         @Override
551         public void handleMessage(String message, KaleidescapeHandler handler) {
552             Matcher matcher = p.matcher(message);
553             if (matcher.find()) {
554                 // replaceFirst takes off leading zeros
555                 handler.updateThingProperty(PROPERTY_SERIAL_NUMBER, matcher.group(2).replaceFirst("^0+(?!$)", EMPTY));
556                 handler.updateThingProperty(PROPERTY_CONTROL_PROTOCOL_ID, matcher.group(3));
557             } else {
558                 logger.debug("DEVICE_INFO - no match on message: {}", message);
559             }
560         }
561     },
562     DEVICE_TYPE_NAME {
563         @Override
564         public void handleMessage(String message, KaleidescapeHandler handler) {
565             // example: 'Player' or 'Strato'
566             handler.updateThingProperty(PROPERTY_COMPONENT_TYPE, message);
567         }
568     },
569     FRIENDLY_NAME {
570         @Override
571         public void handleMessage(String message, KaleidescapeHandler handler) {
572             // example: 'Living Room'
573             handler.friendlyName = message;
574             handler.updateThingProperty(PROPERTY_FRIENDLY_NAME, message);
575         }
576     };
577
578     public abstract void handleMessage(String message, KaleidescapeHandler handler);
579 }