]> git.basschouten.com Git - openhab-addons.git/blob
502680e58beabe8841e6f52bafc69c8c6f42e493
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.bosesoundtouch.internal;
14
15 import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
16 import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION;
17 import static org.openhab.core.thing.Thing.PROPERTY_HARDWARE_VERSION;
18 import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID;
19
20 import java.util.HashMap;
21 import java.util.Map;
22 import java.util.Objects;
23 import java.util.Stack;
24
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
27 import org.openhab.core.io.net.http.HttpUtil;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.PercentType;
31 import org.openhab.core.library.types.PlayPauseType;
32 import org.openhab.core.library.types.RawType;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.types.State;
35 import org.openhab.core.types.UnDefType;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38 import org.xml.sax.Attributes;
39 import org.xml.sax.SAXException;
40 import org.xml.sax.helpers.DefaultHandler;
41
42 /**
43  * The {@link XMLResponseHandler} class handles the XML communication with the Soundtouch
44  *
45  * @author Christian Niessner - Initial contribution
46  * @author Thomas Traunbauer - Initial contribution
47  * @author Kai Kreuzer - code clean up
48  */
49 public class XMLResponseHandler extends DefaultHandler {
50
51     private final Logger logger = LoggerFactory.getLogger(XMLResponseHandler.class);
52
53     private BoseSoundTouchHandler handler;
54     private CommandExecutor commandExecutor;
55
56     private Map<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap;
57
58     private final Stack<XMLHandlerState> states = new Stack<>();
59     private XMLHandlerState state = XMLHandlerState.INIT;
60     private boolean msgHeaderWasValid;
61
62     private ContentItem contentItem;
63     private boolean volumeMuteEnabled;
64     private OnOffType rateEnabled;
65     private OnOffType skipEnabled;
66     private OnOffType skipPreviousEnabled;
67     private State nowPlayingSource;
68
69     private BoseSoundTouchConfiguration masterDeviceId;
70
71     String deviceId;
72
73     private Map<Integer, ContentItem> playerPresets;
74
75     /**
76      * Creates a new instance of this class
77      *
78      * @param handler
79      * @param stateSwitchingMap the stateSwitchingMap is the XMLState Map, that says which Flags are computed
80      */
81     public XMLResponseHandler(BoseSoundTouchHandler handler,
82             Map<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap) {
83         this.handler = handler;
84         this.commandExecutor = handler.getCommandExecutor();
85         this.stateSwitchingMap = stateSwitchingMap;
86     }
87
88     @Override
89     public void startElement(@Nullable String uri, @Nullable String localName, @Nullable String qName,
90             @Nullable Attributes attributes) throws SAXException {
91         super.startElement(uri, localName, qName, attributes);
92         logger.trace("{}: startElement('{}'; state: {})", handler.getDeviceName(), localName, state);
93         states.push(state);
94         XMLHandlerState curState = state; // save for switch statement
95         Map<String, XMLHandlerState> stateMap = stateSwitchingMap.get(state);
96         state = XMLHandlerState.Unprocessed; // set default value; we avoid default in select to have the compiler
97                                              // showing a
98                                              // warning for unhandled states
99
100         XMLHandlerState localState = null;
101         if (stateMap != null) {
102             localState = stateMap.get(localName);
103         }
104
105         switch (curState) {
106             case INIT:
107                 if ("updates".equals(localName)) {
108                     // it just seems to be a ping - havn't seen any data on it..
109                     if (checkDeviceId(localName, attributes, false)) {
110                         state = XMLHandlerState.Updates;
111                     } else {
112                         state = XMLHandlerState.Unprocessed;
113                     }
114                 } else {
115                     if (localState == null) {
116                         logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
117                                 localName);
118                         state = XMLHandlerState.Unprocessed;
119                     }
120                 }
121                 break;
122             case Msg:
123                 if ("header".equals(localName)) {
124                     // message
125                     if (checkDeviceId(localName, attributes, false)) {
126                         state = XMLHandlerState.MsgHeader;
127                         msgHeaderWasValid = true;
128                     } else {
129                         state = XMLHandlerState.Unprocessed;
130                     }
131                 } else if ("body".equals(localName)) {
132                     if (msgHeaderWasValid) {
133                         state = XMLHandlerState.MsgBody;
134                     } else {
135                         state = XMLHandlerState.Unprocessed;
136                     }
137                 } else {
138                     logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
139                             localName);
140
141                     state = XMLHandlerState.Unprocessed;
142                 }
143                 break;
144             case MsgHeader:
145                 if ("request".equals(localName)) {
146                     state = XMLHandlerState.Unprocessed; // TODO implement request id / response tracking...
147                 } else {
148                     logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
149                             localName);
150                     state = XMLHandlerState.Unprocessed;
151                 }
152                 break;
153             case MsgBody:
154                 if ("nowPlaying".equals(localName)) {
155                     /*
156                      * if (!checkDeviceId(localName, attributes, true)) {
157                      * state = XMLHandlerState.Unprocessed;
158                      * break;
159                      * }
160                      */
161                     rateEnabled = OnOffType.OFF;
162                     skipEnabled = OnOffType.OFF;
163                     skipPreviousEnabled = OnOffType.OFF;
164                     state = XMLHandlerState.NowPlaying;
165                     String source = "";
166                     if (attributes != null) {
167                         source = attributes.getValue("source");
168                     }
169                     if (nowPlayingSource == null || !nowPlayingSource.toString().equals(source)) {
170                         // source changed
171                         nowPlayingSource = new StringType(source);
172                         // reset enabled states
173                         updateRateEnabled(OnOffType.OFF);
174                         updateSkipEnabled(OnOffType.OFF);
175                         updateSkipPreviousEnabled(OnOffType.OFF);
176
177                         // clear all "nowPlaying" details on source change...
178                         updateNowPlayingAlbum(UnDefType.NULL);
179                         updateNowPlayingArtwork(UnDefType.NULL);
180                         updateNowPlayingArtist(UnDefType.NULL);
181                         updateNowPlayingDescription(UnDefType.NULL);
182                         updateNowPlayingGenre(UnDefType.NULL);
183                         updateNowPlayingItemName(UnDefType.NULL);
184                         updateNowPlayingStationLocation(UnDefType.NULL);
185                         updateNowPlayingStationName(UnDefType.NULL);
186                         updateNowPlayingTrack(UnDefType.NULL);
187                     }
188                 } else if ("zone".equals(localName)) {
189                     state = XMLHandlerState.Zone;
190                 } else if ("presets".equals(localName)) {
191                     // reset the current playerPrests
192                     playerPresets = new HashMap<>();
193                     for (int i = 1; i <= 6; i++) {
194                         playerPresets.put(i, null);
195                     }
196                     state = XMLHandlerState.Presets;
197                 } else if ("group".equals(localName)) {
198                     this.masterDeviceId = new BoseSoundTouchConfiguration();
199                 } else {
200                     if (localState == null) {
201                         logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
202                                 localName);
203
204                         state = XMLHandlerState.Unprocessed;
205                     } else if (state != XMLHandlerState.Volume && state != XMLHandlerState.Presets
206                             && state != XMLHandlerState.Group && state != XMLHandlerState.Unprocessed) {
207                         if (!checkDeviceId(localName, attributes, false)) {
208                             state = XMLHandlerState.Unprocessed;
209                             break;
210                         }
211                     }
212                 }
213                 break;
214             case Presets:
215                 if ("preset".equals(localName)) {
216                     state = XMLHandlerState.Preset;
217                     String id = "0";
218                     if (attributes != null) {
219                         id = attributes.getValue("id");
220                     }
221                     if (contentItem == null) {
222                         contentItem = new ContentItem();
223                     }
224                     contentItem.setPresetID(Integer.parseInt(id));
225                 } else {
226                     logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
227                             localName);
228
229                     state = XMLHandlerState.Unprocessed;
230                 }
231                 break;
232             case Sources:
233                 if ("sourceItem".equals(localName)) {
234                     state = XMLHandlerState.Unprocessed;
235                     String source = "";
236                     String status = "";
237                     String sourceAccount = "";
238                     if (attributes != null) {
239                         source = attributes.getValue("source");
240                         sourceAccount = attributes.getValue("sourceAccount");
241                         status = attributes.getValue("status");
242                     }
243                     if ("READY".equals(status)) {
244                         switch (source) {
245                             case "AUX":
246                                 if ("AUX".equals(sourceAccount)) {
247                                     commandExecutor.setAUXAvailable(true);
248                                 }
249                                 if ("AUX1".equals(sourceAccount)) {
250                                     commandExecutor.setAUX1Available(true);
251                                 }
252                                 if ("AUX2".equals(sourceAccount)) {
253                                     commandExecutor.setAUX2Available(true);
254                                 }
255                                 if ("AUX3".equals(sourceAccount)) {
256                                     commandExecutor.setAUX3Available(true);
257                                 }
258                                 break;
259                             case "STORED_MUSIC":
260                                 commandExecutor.setStoredMusicAvailable(true);
261                                 break;
262                             case "INTERNET_RADIO":
263                                 commandExecutor.setInternetRadioAvailable(true);
264                                 break;
265                             case "BLUETOOTH":
266                                 commandExecutor.setBluetoothAvailable(true);
267                                 break;
268                             case "PRODUCT":
269                                 switch (sourceAccount) {
270                                     case "TV":
271                                         commandExecutor.setTVAvailable(true);
272                                         break;
273                                     case "HDMI_1":
274                                         commandExecutor.setHDMI1Available(true);
275                                         break;
276                                     default:
277                                         logger.debug("{}: has an unknown source account: '{}'", handler.getDeviceName(),
278                                                 sourceAccount);
279                                         break;
280                                 }
281                             default:
282                                 logger.debug("{}: has an unknown source: '{}'", handler.getDeviceName(), source);
283                                 break;
284                         }
285                     }
286                 } else {
287                     logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
288                             localName);
289                     state = XMLHandlerState.Unprocessed;
290                 }
291                 break;
292             // auto go trough the state map
293             case Group:
294             case Zone:
295             case Bass:
296             case ContentItem:
297             case MasterDeviceId:
298             case GroupName:
299             case DeviceId:
300             case DeviceIp:
301             case Info:
302             case NowPlaying:
303             case Preset:
304             case Updates:
305             case Volume:
306             case Components:
307             case Component:
308                 state = nextState(stateMap, curState, localName);
309                 break;
310             case BassCapabilities:
311                 state = nextState(stateMap, curState, localName);
312                 break;
313             // all entities without any children expected..
314             case BassTarget:
315             case BassActual:
316             case BassUpdated:
317             case BassMin:
318             case BassMax:
319             case BassDefault:
320             case ContentItemItemName:
321             case ContentItemContainerArt:
322             case InfoName:
323             case InfoType:
324             case InfoFirmwareVersion:
325             case InfoModuleType:
326             case NowPlayingAlbum:
327             case NowPlayingArt:
328             case NowPlayingArtist:
329             case NowPlayingGenre:
330             case NowPlayingDescription:
331             case NowPlayingPlayStatus:
332             case NowPlayingRateEnabled:
333             case NowPlayingSkipEnabled:
334             case NowPlayingSkipPreviousEnabled:
335             case NowPlayingStationLocation:
336             case NowPlayingStationName:
337             case NowPlayingTrack:
338             case VolumeTarget:
339             case VolumeActual:
340             case VolumeUpdated:
341             case VolumeMuteEnabled:
342             case ZoneMember:
343             case ZoneUpdated: // currently this dosn't provide any zone details..
344                 if (logger.isDebugEnabled()) {
345                     logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
346                             localName);
347                 }
348                 state = XMLHandlerState.Unprocessed;
349                 break;
350             case BassAvailable:
351                 if (logger.isDebugEnabled()) {
352                     logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
353                             localName);
354                 }
355                 state = XMLHandlerState.Unprocessed;
356                 break;
357             case Unprocessed:
358                 // all further things are also unprocessed
359                 state = XMLHandlerState.Unprocessed;
360                 break;
361             case UnprocessedNoTextExpected:
362                 state = XMLHandlerState.UnprocessedNoTextExpected;
363                 break;
364         }
365         if (state == XMLHandlerState.ContentItem) {
366             if (contentItem == null) {
367                 contentItem = new ContentItem();
368             }
369             String source = "";
370             String location = "";
371             String sourceAccount = "";
372             Boolean isPresetable = false;
373
374             if (attributes != null) {
375                 source = attributes.getValue("source");
376                 sourceAccount = attributes.getValue("sourceAccount");
377                 location = attributes.getValue("location");
378                 isPresetable = Boolean.parseBoolean(attributes.getValue("isPresetable"));
379
380                 if (source != null) {
381                     contentItem.setSource(source);
382                 }
383                 if (sourceAccount != null) {
384                     contentItem.setSourceAccount(sourceAccount);
385                 }
386                 if (location != null) {
387                     contentItem.setLocation(location);
388                 }
389                 contentItem.setPresetable(isPresetable);
390
391                 for (int attrId = 0; attrId < attributes.getLength(); attrId++) {
392                     String attrName = attributes.getLocalName(attrId);
393                     if ("source".equalsIgnoreCase(attrName)) {
394                         continue;
395                     }
396                     if ("location".equalsIgnoreCase(attrName)) {
397                         continue;
398                     }
399                     if ("sourceAccount".equalsIgnoreCase(attrName)) {
400                         continue;
401                     }
402                     if ("isPresetable".equalsIgnoreCase(attrName)) {
403                         continue;
404                     }
405                     if (attrName != null) {
406                         contentItem.setAdditionalAttribute(attrName, attributes.getValue(attrId));
407                     }
408                 }
409             }
410         }
411     }
412
413     @Override
414     public void endElement(String uri, String localName, String qName) throws SAXException {
415         super.endElement(uri, localName, qName);
416         logger.trace("{}: endElement('{}')", handler.getDeviceName(), localName);
417         final XMLHandlerState prevState = state;
418         state = states.pop();
419         switch (prevState) {
420             case Info:
421                 commandExecutor.getInformations(APIRequest.VOLUME);
422                 commandExecutor.getInformations(APIRequest.PRESETS);
423                 commandExecutor.getInformations(APIRequest.NOW_PLAYING);
424                 commandExecutor.getInformations(APIRequest.GET_ZONE);
425                 commandExecutor.getInformations(APIRequest.BASS);
426                 commandExecutor.getInformations(APIRequest.SOURCES);
427                 commandExecutor.getInformations(APIRequest.BASSCAPABILITIES);
428                 commandExecutor.getInformations(APIRequest.GET_GROUP);
429                 break;
430             case ContentItem:
431                 if (state == XMLHandlerState.NowPlaying) {
432                     // update now playing name...
433                     updateNowPlayingItemName(new StringType(contentItem.getItemName()));
434                     commandExecutor.setCurrentContentItem(contentItem);
435                 }
436                 break;
437             case Preset:
438                 if (state == XMLHandlerState.Presets) {
439                     playerPresets.put(contentItem.getPresetID(), contentItem);
440                     contentItem = null;
441                 }
442                 break;
443             case NowPlaying:
444                 if (state == XMLHandlerState.MsgBody) {
445                     updateRateEnabled(rateEnabled);
446                     updateSkipEnabled(skipEnabled);
447                     updateSkipPreviousEnabled(skipPreviousEnabled);
448                 }
449                 break;
450             // handle special tags..
451             case BassUpdated:
452                 // request current bass level
453                 commandExecutor.getInformations(APIRequest.BASS);
454                 break;
455             case VolumeUpdated:
456                 commandExecutor.getInformations(APIRequest.VOLUME);
457                 break;
458             case NowPlayingRateEnabled:
459                 rateEnabled = OnOffType.ON;
460                 break;
461             case NowPlayingSkipEnabled:
462                 skipEnabled = OnOffType.ON;
463                 break;
464             case NowPlayingSkipPreviousEnabled:
465                 skipPreviousEnabled = OnOffType.ON;
466                 break;
467             case Volume:
468                 OnOffType muted = volumeMuteEnabled ? OnOffType.ON : OnOffType.OFF;
469                 commandExecutor.setCurrentMuted(volumeMuteEnabled);
470                 commandExecutor.postVolumeMuted(muted);
471                 break;
472             case ZoneUpdated:
473                 commandExecutor.getInformations(APIRequest.GET_ZONE);
474                 break;
475             case Presets:
476                 commandExecutor.updatePresetContainerFromPlayer(playerPresets);
477                 playerPresets = null;
478                 break;
479             case Group:
480                 handler.handleGroupUpdated(masterDeviceId);
481                 break;
482             default:
483                 // no actions...
484                 break;
485         }
486     }
487
488     @Override
489     public void characters(char[] ch, int start, int length) throws SAXException {
490         logger.trace("{}: Text data during {}: '{}'", handler.getDeviceName(), state, new String(ch, start, length));
491         super.characters(ch, start, length);
492         switch (state) {
493             case INIT:
494             case Msg:
495             case MsgHeader:
496             case MsgBody:
497             case Bass:
498             case BassUpdated:
499             case Updates:
500             case Volume:
501             case VolumeUpdated:
502             case Info:
503             case Preset:
504             case Presets:
505             case NowPlaying:
506             case NowPlayingRateEnabled:
507             case NowPlayingSkipEnabled:
508             case NowPlayingSkipPreviousEnabled:
509             case ContentItem:
510             case UnprocessedNoTextExpected:
511             case Zone:
512             case ZoneUpdated:
513             case Sources:
514                 logger.debug("{}: Unexpected text data during {}: '{}'", handler.getDeviceName(), state,
515                         new String(ch, start, length));
516                 break;
517             case BassMin: // @TODO - find out how to dynamically change "channel-type" bass configuration
518             case BassMax: // based on these values...
519             case BassDefault:
520             case BassTarget:
521             case VolumeTarget:
522                 // this are currently unprocessed values.
523                 break;
524             case BassCapabilities:
525                 logger.debug("{}: Unexpected text data during {}: '{}'", handler.getDeviceName(), state,
526                         new String(ch, start, length));
527                 break;
528             case Unprocessed:
529                 // drop quietly..
530                 break;
531             case BassActual:
532                 commandExecutor.updateBassLevelGUIState(new DecimalType(new String(ch, start, length)));
533                 break;
534             case InfoName:
535                 setConfigOption(DEVICE_INFO_NAME, new String(ch, start, length));
536                 break;
537             case InfoType:
538                 setConfigOption(DEVICE_INFO_TYPE, new String(ch, start, length));
539                 setConfigOption(PROPERTY_MODEL_ID, new String(ch, start, length));
540                 break;
541             case InfoModuleType:
542                 setConfigOption(PROPERTY_HARDWARE_VERSION, new String(ch, start, length));
543                 break;
544             case InfoFirmwareVersion:
545                 String[] fwVersion = new String(ch, start, length).split(" ");
546                 setConfigOption(PROPERTY_FIRMWARE_VERSION, fwVersion[0]);
547                 break;
548             case BassAvailable:
549                 boolean bassAvailable = Boolean.parseBoolean(new String(ch, start, length));
550                 commandExecutor.setBassAvailable(bassAvailable);
551                 break;
552             case NowPlayingAlbum:
553                 updateNowPlayingAlbum(new StringType(new String(ch, start, length)));
554                 break;
555             case NowPlayingArt:
556                 String url = new String(ch, start, length);
557                 if (url.startsWith("http")) {
558                     // We download the cover art in a different thread to not delay the other operations
559                     handler.getScheduler().submit(() -> {
560                         RawType image = HttpUtil.downloadImage(url, true, 500000);
561                         if (image != null) {
562                             updateNowPlayingArtwork(image);
563                         } else {
564                             updateNowPlayingArtwork(UnDefType.UNDEF);
565                         }
566                     });
567                 } else {
568                     updateNowPlayingArtwork(UnDefType.UNDEF);
569                 }
570                 break;
571             case NowPlayingArtist:
572                 updateNowPlayingArtist(new StringType(new String(ch, start, length)));
573                 break;
574             case ContentItemItemName:
575                 contentItem.setItemName(new String(ch, start, length));
576                 break;
577             case ContentItemContainerArt:
578                 contentItem.setContainerArt(new String(ch, start, length));
579                 break;
580             case NowPlayingDescription:
581                 updateNowPlayingDescription(new StringType(new String(ch, start, length)));
582                 break;
583             case NowPlayingGenre:
584                 updateNowPlayingGenre(new StringType(new String(ch, start, length)));
585                 break;
586             case NowPlayingPlayStatus:
587                 String playPauseState = new String(ch, start, length);
588                 if ("PLAY_STATE".equals(playPauseState) || "BUFFERING_STATE".equals(playPauseState)) {
589                     commandExecutor.updatePlayerControlGUIState(PlayPauseType.PLAY);
590                 } else if ("STOP_STATE".equals(playPauseState) || "PAUSE_STATE".equals(playPauseState)) {
591                     commandExecutor.updatePlayerControlGUIState(PlayPauseType.PAUSE);
592                 }
593                 break;
594             case NowPlayingStationLocation:
595                 updateNowPlayingStationLocation(new StringType(new String(ch, start, length)));
596                 break;
597             case NowPlayingStationName:
598                 updateNowPlayingStationName(new StringType(new String(ch, start, length)));
599                 break;
600             case NowPlayingTrack:
601                 updateNowPlayingTrack(new StringType(new String(ch, start, length)));
602                 break;
603             case VolumeActual:
604                 commandExecutor.updateVolumeGUIState(new PercentType(Integer.parseInt(new String(ch, start, length))));
605                 break;
606             case VolumeMuteEnabled:
607                 volumeMuteEnabled = Boolean.parseBoolean(new String(ch, start, length));
608                 commandExecutor.setCurrentMuted(volumeMuteEnabled);
609                 break;
610             case MasterDeviceId:
611                 if (masterDeviceId != null) {
612                     masterDeviceId.macAddress = new String(ch, start, length);
613                 }
614                 break;
615             case GroupName:
616                 if (masterDeviceId != null) {
617                     masterDeviceId.groupName = new String(ch, start, length);
618                 }
619                 break;
620             case DeviceId:
621                 deviceId = new String(ch, start, length);
622                 break;
623             case DeviceIp:
624                 if (masterDeviceId != null && Objects.equals(masterDeviceId.macAddress, deviceId)) {
625                     masterDeviceId.host = new String(ch, start, length);
626                 }
627                 break;
628             default:
629                 // do nothing
630                 break;
631         }
632     }
633
634     @Override
635     public void skippedEntity(String name) throws SAXException {
636         super.skippedEntity(name);
637     }
638
639     private boolean checkDeviceId(@Nullable String localName, @Nullable Attributes attributes,
640             boolean allowFromMaster) {
641         String deviceID = (attributes != null) ? attributes.getValue("deviceID") : null;
642         if (deviceID == null) {
643             logger.warn("{}: No device-ID in entity {}", handler.getDeviceName(), localName);
644             return false;
645         }
646         if (deviceID.equals(handler.getMacAddress())) {
647             return true;
648         }
649         logger.warn("{}: Wrong device-ID in entity '{}': Got: '{}', expected: '{}'", handler.getDeviceName(), localName,
650                 deviceID, handler.getMacAddress());
651         return false;
652     }
653
654     private XMLHandlerState nextState(Map<String, XMLHandlerState> stateMap, XMLHandlerState curState,
655             String localName) {
656         XMLHandlerState state = stateMap.get(localName);
657         if (state == null) {
658             if (logger.isDebugEnabled()) {
659                 logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, localName);
660             }
661             state = XMLHandlerState.Unprocessed;
662         }
663         return state;
664     }
665
666     private void setConfigOption(String option, String value) {
667         if (option != null) {
668             Map<String, String> prop = handler.getThing().getProperties();
669             String cur = prop.get(option);
670             if (cur == null || !cur.equals(value)) {
671                 logger.debug("{}: Option '{}' updated: From '{}' to '{}'", handler.getDeviceName(), option, cur, value);
672                 handler.getThing().setProperty(option, value);
673             }
674         }
675     }
676
677     private void updateNowPlayingAlbum(State state) {
678         handler.updateState(CHANNEL_NOWPLAYING_ALBUM, state);
679     }
680
681     private void updateNowPlayingArtwork(State state) {
682         handler.updateState(CHANNEL_NOWPLAYING_ARTWORK, state);
683     }
684
685     private void updateNowPlayingArtist(State state) {
686         handler.updateState(CHANNEL_NOWPLAYING_ARTIST, state);
687     }
688
689     private void updateNowPlayingDescription(State state) {
690         handler.updateState(CHANNEL_NOWPLAYING_DESCRIPTION, state);
691     }
692
693     private void updateNowPlayingGenre(State state) {
694         handler.updateState(CHANNEL_NOWPLAYING_GENRE, state);
695     }
696
697     private void updateNowPlayingItemName(State state) {
698         handler.updateState(CHANNEL_NOWPLAYING_ITEMNAME, state);
699     }
700
701     private void updateNowPlayingStationLocation(State state) {
702         handler.updateState(CHANNEL_NOWPLAYING_STATIONLOCATION, state);
703     }
704
705     private void updateNowPlayingStationName(State state) {
706         handler.updateState(CHANNEL_NOWPLAYING_STATIONNAME, state);
707     }
708
709     private void updateNowPlayingTrack(State state) {
710         handler.updateState(CHANNEL_NOWPLAYING_TRACK, state);
711     }
712
713     private void updateRateEnabled(OnOffType state) {
714         handler.updateState(CHANNEL_RATEENABLED, state);
715     }
716
717     private void updateSkipEnabled(OnOffType state) {
718         handler.updateState(CHANNEL_SKIPENABLED, state);
719     }
720
721     private void updateSkipPreviousEnabled(OnOffType state) {
722         handler.updateState(CHANNEL_SKIPPREVIOUSENABLED, state);
723     }
724 }