]> git.basschouten.com Git - openhab-addons.git/blob
dc894d07070f13241a258b4450e064677748c484
[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.remoteopenhab.internal.handler;
14
15 import java.net.MalformedURLException;
16 import java.net.URL;
17 import java.time.DateTimeException;
18 import java.time.ZonedDateTime;
19 import java.time.format.DateTimeFormatter;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28 import java.util.stream.Collectors;
29
30 import javax.ws.rs.client.ClientBuilder;
31
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.eclipse.jetty.client.HttpClient;
35 import org.openhab.binding.remoteopenhab.internal.RemoteopenhabChannelTypeProvider;
36 import org.openhab.binding.remoteopenhab.internal.RemoteopenhabStateDescriptionOptionProvider;
37 import org.openhab.binding.remoteopenhab.internal.config.RemoteopenhabServerConfiguration;
38 import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabItem;
39 import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStateDescription;
40 import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStateOption;
41 import org.openhab.binding.remoteopenhab.internal.discovery.RemoteopenhabDiscoveryService;
42 import org.openhab.binding.remoteopenhab.internal.exceptions.RemoteopenhabException;
43 import org.openhab.binding.remoteopenhab.internal.listener.RemoteopenhabItemsDataListener;
44 import org.openhab.binding.remoteopenhab.internal.listener.RemoteopenhabStreamingDataListener;
45 import org.openhab.binding.remoteopenhab.internal.rest.RemoteopenhabRestClient;
46 import org.openhab.core.library.CoreItemFactory;
47 import org.openhab.core.library.types.DateTimeType;
48 import org.openhab.core.library.types.DecimalType;
49 import org.openhab.core.library.types.HSBType;
50 import org.openhab.core.library.types.OnOffType;
51 import org.openhab.core.library.types.OpenClosedType;
52 import org.openhab.core.library.types.PercentType;
53 import org.openhab.core.library.types.PlayPauseType;
54 import org.openhab.core.library.types.PointType;
55 import org.openhab.core.library.types.QuantityType;
56 import org.openhab.core.library.types.RawType;
57 import org.openhab.core.library.types.StringType;
58 import org.openhab.core.net.NetUtil;
59 import org.openhab.core.thing.Bridge;
60 import org.openhab.core.thing.Channel;
61 import org.openhab.core.thing.ChannelUID;
62 import org.openhab.core.thing.ThingStatus;
63 import org.openhab.core.thing.ThingStatusDetail;
64 import org.openhab.core.thing.binding.BaseBridgeHandler;
65 import org.openhab.core.thing.binding.ThingHandlerService;
66 import org.openhab.core.thing.binding.builder.ChannelBuilder;
67 import org.openhab.core.thing.binding.builder.ThingBuilder;
68 import org.openhab.core.thing.type.AutoUpdatePolicy;
69 import org.openhab.core.thing.type.ChannelKind;
70 import org.openhab.core.thing.type.ChannelType;
71 import org.openhab.core.thing.type.ChannelTypeBuilder;
72 import org.openhab.core.thing.type.ChannelTypeUID;
73 import org.openhab.core.types.Command;
74 import org.openhab.core.types.RefreshType;
75 import org.openhab.core.types.State;
76 import org.openhab.core.types.StateDescriptionFragmentBuilder;
77 import org.openhab.core.types.StateOption;
78 import org.openhab.core.types.TypeParser;
79 import org.openhab.core.types.UnDefType;
80 import org.osgi.service.jaxrs.client.SseEventSourceFactory;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
83
84 import com.google.gson.Gson;
85
86 /**
87  * The {@link RemoteopenhabBridgeHandler} is responsible for handling commands and updating states
88  * using the REST API of the remote openHAB server.
89  *
90  * @author Laurent Garnier - Initial contribution
91  */
92 @NonNullByDefault
93 public class RemoteopenhabBridgeHandler extends BaseBridgeHandler
94         implements RemoteopenhabStreamingDataListener, RemoteopenhabItemsDataListener {
95
96     private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
97     private static final DateTimeFormatter FORMATTER_DATE = DateTimeFormatter.ofPattern(DATE_FORMAT_PATTERN);
98
99     private static final int MAX_STATE_SIZE_FOR_LOGGING = 50;
100
101     private final Logger logger = LoggerFactory.getLogger(RemoteopenhabBridgeHandler.class);
102
103     private final HttpClient httpClientTrustingCert;
104     private final RemoteopenhabChannelTypeProvider channelTypeProvider;
105     private final RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider;
106
107     private final Object updateThingLock = new Object();
108
109     private @NonNullByDefault({}) RemoteopenhabServerConfiguration config;
110
111     private @Nullable ScheduledFuture<?> checkConnectionJob;
112     private RemoteopenhabRestClient restClient;
113
114     private Map<ChannelUID, State> channelsLastStates = new HashMap<>();
115
116     public RemoteopenhabBridgeHandler(Bridge bridge, HttpClient httpClient, HttpClient httpClientTrustingCert,
117             ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory,
118             RemoteopenhabChannelTypeProvider channelTypeProvider,
119             RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider, final Gson jsonParser) {
120         super(bridge);
121         this.httpClientTrustingCert = httpClientTrustingCert;
122         this.channelTypeProvider = channelTypeProvider;
123         this.stateDescriptionProvider = stateDescriptionProvider;
124         this.restClient = new RemoteopenhabRestClient(httpClient, clientBuilder, eventSourceFactory, jsonParser);
125     }
126
127     @Override
128     public void initialize() {
129         logger.debug("Initializing remote openHAB handler for bridge {}", getThing().getUID());
130
131         config = getConfigAs(RemoteopenhabServerConfiguration.class);
132
133         String host = config.host.trim();
134         if (host.length() == 0) {
135             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
136                     "Undefined server address setting in the thing configuration");
137             return;
138         }
139         List<String> localIpAddresses = NetUtil.getAllInterfaceAddresses().stream()
140                 .filter(a -> !a.getAddress().isLinkLocalAddress())
141                 .map(a -> a.getAddress().getHostAddress().split("%")[0]).collect(Collectors.toList());
142         if (localIpAddresses.contains(host)) {
143             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
144                     "Do not use the local server as a remote server in the thing configuration");
145             return;
146         }
147         String path = config.restPath.trim();
148         if (path.length() == 0 || !path.startsWith("/")) {
149             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
150                     "Invalid REST API path setting in the thing configuration");
151             return;
152         }
153         URL url;
154         try {
155             url = new URL(config.useHttps ? "https" : "http", host, config.port, path);
156         } catch (MalformedURLException e) {
157             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
158                     "Invalid REST URL built from the settings in the thing configuration");
159             return;
160         }
161
162         String urlStr = url.toString();
163         if (urlStr.endsWith("/")) {
164             urlStr = urlStr.substring(0, urlStr.length() - 1);
165         }
166         logger.debug("REST URL = {}", urlStr);
167
168         restClient.setRestUrl(urlStr);
169         restClient.setAccessToken(config.token);
170         if (config.useHttps && config.trustedCertificate) {
171             restClient.setHttpClient(httpClientTrustingCert);
172             restClient.setTrustedCertificate(true);
173         }
174
175         updateStatus(ThingStatus.UNKNOWN);
176
177         scheduler.submit(() -> checkConnection(false));
178         if (config.accessibilityInterval > 0) {
179             startCheckConnectionJob(config.accessibilityInterval, config.aliveInterval, config.restartIfNoActivity);
180         }
181     }
182
183     @Override
184     public void dispose() {
185         logger.debug("Disposing remote openHAB handler for bridge {}", getThing().getUID());
186         stopStreamingUpdates(false);
187         stopCheckConnectionJob();
188         channelsLastStates.clear();
189     }
190
191     @Override
192     public void handleCommand(ChannelUID channelUID, Command command) {
193         if (getThing().getStatus() != ThingStatus.ONLINE) {
194             return;
195         }
196
197         try {
198             if (command instanceof RefreshType) {
199                 String state = restClient.getRemoteItemState(channelUID.getId());
200                 updateChannelState(channelUID.getId(), null, state, false);
201             } else if (isLinked(channelUID)) {
202                 restClient.sendCommandToRemoteItem(channelUID.getId(), command);
203                 String commandStr = command.toFullString();
204                 logger.debug("Sending command {} to remote item {} succeeded",
205                         commandStr.length() < MAX_STATE_SIZE_FOR_LOGGING ? commandStr
206                                 : commandStr.substring(0, MAX_STATE_SIZE_FOR_LOGGING) + "...",
207                         channelUID.getId());
208             }
209         } catch (RemoteopenhabException e) {
210             logger.debug("{}", e.getMessage());
211         }
212     }
213
214     private boolean createChannels(List<RemoteopenhabItem> items, boolean replace) {
215         synchronized (updateThingLock) {
216             try {
217                 int nbGroups = 0;
218                 int nbChannelTypesCreated = 0;
219                 List<Channel> channels = new ArrayList<>();
220                 for (RemoteopenhabItem item : items) {
221                     String itemType = item.type;
222                     boolean readOnly = false;
223                     if ("Group".equals(itemType)) {
224                         if (item.groupType.isEmpty()) {
225                             // Standard groups are ignored
226                             nbGroups++;
227                             continue;
228                         } else {
229                             itemType = item.groupType;
230                         }
231                     } else {
232                         if (item.stateDescription != null && item.stateDescription.readOnly) {
233                             readOnly = true;
234                         }
235                     }
236                     // Ignore pattern containing a transformation (detected by a parenthesis in the pattern)
237                     RemoteopenhabStateDescription stateDescription = item.stateDescription;
238                     String pattern = (stateDescription == null || stateDescription.pattern.contains("(")) ? ""
239                             : stateDescription.pattern;
240                     ChannelTypeUID channelTypeUID;
241                     ChannelType channelType = channelTypeProvider.getChannelType(itemType, readOnly, pattern);
242                     String label;
243                     String description;
244                     if (channelType == null) {
245                         channelTypeUID = channelTypeProvider.buildNewChannelTypeUID(itemType);
246                         logger.trace("Create the channel type {} for item type {} ({} and with pattern {})",
247                                 channelTypeUID, itemType, readOnly ? "read only" : "read write", pattern);
248                         label = String.format("Remote %s Item", itemType);
249                         description = String.format("An item of type %s from the remote server.", itemType);
250                         StateDescriptionFragmentBuilder stateDescriptionBuilder = StateDescriptionFragmentBuilder
251                                 .create().withReadOnly(readOnly);
252                         if (!pattern.isEmpty()) {
253                             stateDescriptionBuilder = stateDescriptionBuilder.withPattern(pattern);
254                         }
255                         channelType = ChannelTypeBuilder.state(channelTypeUID, label, itemType)
256                                 .withDescription(description)
257                                 .withStateDescriptionFragment(stateDescriptionBuilder.build())
258                                 .withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
259                         channelTypeProvider.addChannelType(itemType, channelType);
260                         nbChannelTypesCreated++;
261                     } else {
262                         channelTypeUID = channelType.getUID();
263                     }
264                     ChannelUID channelUID = new ChannelUID(getThing().getUID(), item.name);
265                     logger.trace("Create the channel {} of type {}", channelUID, channelTypeUID);
266                     label = "Item " + item.name;
267                     description = String.format("Item %s from the remote server.", item.name);
268                     channels.add(ChannelBuilder.create(channelUID, itemType).withType(channelTypeUID)
269                             .withKind(ChannelKind.STATE).withLabel(label).withDescription(description).build());
270                 }
271                 ThingBuilder thingBuilder = editThing();
272                 if (replace) {
273                     thingBuilder.withChannels(channels);
274                     updateThing(thingBuilder.build());
275                     logger.debug(
276                             "{} channels defined (with {} different channel types) for the thing {} (from {} items including {} groups)",
277                             channels.size(), nbChannelTypesCreated, getThing().getUID(), items.size(), nbGroups);
278                 } else if (channels.size() > 0) {
279                     int nbRemoved = 0;
280                     for (Channel channel : channels) {
281                         if (getThing().getChannel(channel.getUID()) != null) {
282                             thingBuilder.withoutChannel(channel.getUID());
283                             nbRemoved++;
284                         }
285                     }
286                     if (nbRemoved > 0) {
287                         logger.debug("{} channels removed for the thing {} (from {} items)", nbRemoved,
288                                 getThing().getUID(), items.size());
289                     }
290                     for (Channel channel : channels) {
291                         thingBuilder.withChannel(channel);
292                     }
293                     updateThing(thingBuilder.build());
294                     if (nbGroups > 0) {
295                         logger.debug("{} channels added for the thing {} (from {} items including {} groups)",
296                                 channels.size(), getThing().getUID(), items.size(), nbGroups);
297                     } else {
298                         logger.debug("{} channels added for the thing {} (from {} items)", channels.size(),
299                                 getThing().getUID(), items.size());
300                     }
301                 }
302                 return true;
303             } catch (IllegalArgumentException e) {
304                 logger.warn("An error occurred while creating the channels for the server {}: {}", getThing().getUID(),
305                         e.getMessage());
306                 return false;
307             }
308         }
309     }
310
311     private void removeChannels(List<RemoteopenhabItem> items) {
312         synchronized (updateThingLock) {
313             int nbRemoved = 0;
314             ThingBuilder thingBuilder = editThing();
315             for (RemoteopenhabItem item : items) {
316                 Channel channel = getThing().getChannel(item.name);
317                 if (channel != null) {
318                     thingBuilder.withoutChannel(channel.getUID());
319                     nbRemoved++;
320                 }
321             }
322             if (nbRemoved > 0) {
323                 updateThing(thingBuilder.build());
324                 logger.debug("{} channels removed for the thing {} (from {} items)", nbRemoved, getThing().getUID(),
325                         items.size());
326             }
327         }
328     }
329
330     private void setStateOptions(List<RemoteopenhabItem> items) {
331         for (RemoteopenhabItem item : items) {
332             Channel channel = getThing().getChannel(item.name);
333             RemoteopenhabStateDescription descr = item.stateDescription;
334             List<RemoteopenhabStateOption> options = descr == null ? null : descr.options;
335             if (channel != null && options != null && options.size() > 0) {
336                 List<StateOption> stateOptions = new ArrayList<>();
337                 for (RemoteopenhabStateOption option : options) {
338                     stateOptions.add(new StateOption(option.value, option.label));
339                 }
340                 stateDescriptionProvider.setStateOptions(channel.getUID(), stateOptions);
341                 logger.trace("{} options set for the channel {}", options.size(), channel.getUID());
342             }
343         }
344     }
345
346     public void checkConnection(boolean restartSse) {
347         logger.debug("Try the root REST API...");
348         try {
349             restClient.tryApi();
350             if (restClient.getRestApiVersion() == null) {
351                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
352                         "OH 1.x server not supported by the binding");
353             } else if (getThing().getStatus() != ThingStatus.ONLINE) {
354                 List<RemoteopenhabItem> items = restClient.getRemoteItems("name,type,groupType,state,stateDescription");
355
356                 if (createChannels(items, true)) {
357                     setStateOptions(items);
358                     for (RemoteopenhabItem item : items) {
359                         updateChannelState(item.name, null, item.state, false);
360                     }
361
362                     updateStatus(ThingStatus.ONLINE);
363
364                     restartStreamingUpdates();
365                 } else {
366                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
367                             "Dynamic creation of the channels for the remote server items failed");
368                     stopStreamingUpdates();
369                 }
370             } else if (restartSse) {
371                 logger.debug("The SSE connection is restarted because there was no recent event received");
372                 restartStreamingUpdates();
373             }
374         } catch (RemoteopenhabException e) {
375             logger.debug("{}", e.getMessage());
376             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
377             stopStreamingUpdates();
378         }
379     }
380
381     private void startCheckConnectionJob(int accessibilityInterval, int aliveInterval, boolean restartIfNoActivity) {
382         ScheduledFuture<?> localCheckConnectionJob = checkConnectionJob;
383         if (localCheckConnectionJob == null || localCheckConnectionJob.isCancelled()) {
384             checkConnectionJob = scheduler.scheduleWithFixedDelay(() -> {
385                 long millisSinceLastEvent = System.currentTimeMillis() - restClient.getLastEventTimestamp();
386                 if (getThing().getStatus() != ThingStatus.ONLINE || aliveInterval == 0
387                         || restClient.getLastEventTimestamp() == 0) {
388                     logger.debug("Time to check server accessibility");
389                     checkConnection(restartIfNoActivity && aliveInterval != 0);
390                 } else if (millisSinceLastEvent > (aliveInterval * 60000)) {
391                     logger.debug(
392                             "Time to check server accessibility (maybe disconnected from streaming events, millisSinceLastEvent={})",
393                             millisSinceLastEvent);
394                     checkConnection(restartIfNoActivity);
395                 } else {
396                     logger.debug(
397                             "Bypass server accessibility check (receiving streaming events, millisSinceLastEvent={})",
398                             millisSinceLastEvent);
399                 }
400             }, accessibilityInterval, accessibilityInterval, TimeUnit.MINUTES);
401         }
402     }
403
404     private void stopCheckConnectionJob() {
405         ScheduledFuture<?> localCheckConnectionJob = checkConnectionJob;
406         if (localCheckConnectionJob != null) {
407             localCheckConnectionJob.cancel(true);
408             checkConnectionJob = null;
409         }
410     }
411
412     private void restartStreamingUpdates() {
413         synchronized (restClient) {
414             stopStreamingUpdates();
415             startStreamingUpdates();
416         }
417     }
418
419     private void startStreamingUpdates() {
420         synchronized (restClient) {
421             restClient.addStreamingDataListener(this);
422             restClient.addItemsDataListener(this);
423             restClient.start();
424         }
425     }
426
427     private void stopStreamingUpdates() {
428         stopStreamingUpdates(true);
429     }
430
431     private void stopStreamingUpdates(boolean waitingForCompletion) {
432         synchronized (restClient) {
433             restClient.stop(waitingForCompletion);
434             restClient.removeStreamingDataListener(this);
435             restClient.removeItemsDataListener(this);
436         }
437     }
438
439     public RemoteopenhabRestClient gestRestClient() {
440         return restClient;
441     }
442
443     @Override
444     public void onConnected() {
445         updateStatus(ThingStatus.ONLINE);
446     }
447
448     @Override
449     public void onDisconnected() {
450         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Disconected from the remote server");
451     }
452
453     @Override
454     public void onError(String message) {
455         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
456     }
457
458     @Override
459     public void onItemStateEvent(String itemName, String stateType, String state, boolean onlyIfStateChanged) {
460         updateChannelState(itemName, stateType, state, onlyIfStateChanged);
461     }
462
463     @Override
464     public void onItemAdded(RemoteopenhabItem item) {
465         createChannels(List.of(item), false);
466     }
467
468     @Override
469     public void onItemRemoved(RemoteopenhabItem item) {
470         removeChannels(List.of(item));
471     }
472
473     @Override
474     public void onItemUpdated(RemoteopenhabItem newItem, RemoteopenhabItem oldItem) {
475         if (!newItem.type.equals(oldItem.type)) {
476             createChannels(List.of(newItem), false);
477         } else {
478             logger.trace("Updated remote item {} ignored because item type {} is unchanged", newItem.name,
479                     newItem.type);
480         }
481     }
482
483     private void updateChannelState(String itemName, @Nullable String stateType, String state,
484             boolean onlyIfStateChanged) {
485         Channel channel = getThing().getChannel(itemName);
486         if (channel == null) {
487             logger.trace("No channel for item {}", itemName);
488             return;
489         }
490         String acceptedItemType = channel.getAcceptedItemType();
491         if (acceptedItemType == null) {
492             logger.trace("Channel without accepted item type for item {}", itemName);
493             return;
494         }
495         if (!isLinked(channel.getUID())) {
496             logger.trace("Unlinked channel {}", channel.getUID());
497             return;
498         }
499         State channelState = null;
500         try {
501             if (stateType == null && "NULL".equals(state)) {
502                 channelState = UnDefType.NULL;
503             } else if (stateType == null && "UNDEF".equals(state)) {
504                 channelState = UnDefType.UNDEF;
505             } else if ("UnDef".equals(stateType)) {
506                 switch (state) {
507                     case "NULL":
508                         channelState = UnDefType.NULL;
509                         break;
510                     case "UNDEF":
511                         channelState = UnDefType.UNDEF;
512                         break;
513                     default:
514                         logger.debug("Invalid UnDef value {} for item {}", state, itemName);
515                         break;
516                 }
517             } else if (acceptedItemType.startsWith(CoreItemFactory.NUMBER + ":")) {
518                 // Item type Number with dimension
519                 if (stateType == null || "Quantity".equals(stateType)) {
520                     List<Class<? extends State>> stateTypes = Collections.singletonList(QuantityType.class);
521                     channelState = TypeParser.parseState(stateTypes, state);
522                 } else if ("Decimal".equals(stateType)) {
523                     channelState = new DecimalType(state);
524                 } else {
525                     logger.debug("Unexpected value type {} for item {}", stateType, itemName);
526                 }
527             } else {
528                 switch (acceptedItemType) {
529                     case CoreItemFactory.STRING:
530                         if (checkStateType(itemName, stateType, "String")) {
531                             channelState = new StringType(state);
532                         }
533                         break;
534                     case CoreItemFactory.NUMBER:
535                         if (checkStateType(itemName, stateType, "Decimal")) {
536                             channelState = new DecimalType(state);
537                         }
538                         break;
539                     case CoreItemFactory.SWITCH:
540                         if (checkStateType(itemName, stateType, "OnOff")) {
541                             channelState = "ON".equals(state) ? OnOffType.ON : OnOffType.OFF;
542                         }
543                         break;
544                     case CoreItemFactory.CONTACT:
545                         if (checkStateType(itemName, stateType, "OpenClosed")) {
546                             channelState = "OPEN".equals(state) ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
547                         }
548                         break;
549                     case CoreItemFactory.DIMMER:
550                         if (checkStateType(itemName, stateType, "Percent")) {
551                             channelState = new PercentType(state);
552                         }
553                         break;
554                     case CoreItemFactory.COLOR:
555                         if (checkStateType(itemName, stateType, "HSB")) {
556                             channelState = HSBType.valueOf(state);
557                         }
558                         break;
559                     case CoreItemFactory.DATETIME:
560                         if (checkStateType(itemName, stateType, "DateTime")) {
561                             channelState = new DateTimeType(ZonedDateTime.parse(state, FORMATTER_DATE));
562                         }
563                         break;
564                     case CoreItemFactory.LOCATION:
565                         if (checkStateType(itemName, stateType, "Point")) {
566                             channelState = new PointType(state);
567                         }
568                         break;
569                     case CoreItemFactory.IMAGE:
570                         if (checkStateType(itemName, stateType, "Raw")) {
571                             channelState = RawType.valueOf(state);
572                         }
573                         break;
574                     case CoreItemFactory.PLAYER:
575                         if (checkStateType(itemName, stateType, "PlayPause")) {
576                             switch (state) {
577                                 case "PLAY":
578                                     channelState = PlayPauseType.PLAY;
579                                     break;
580                                 case "PAUSE":
581                                     channelState = PlayPauseType.PAUSE;
582                                     break;
583                                 default:
584                                     logger.debug("Unexpected value {} for item {}", state, itemName);
585                                     break;
586                             }
587                         }
588                         break;
589                     case CoreItemFactory.ROLLERSHUTTER:
590                         if (checkStateType(itemName, stateType, "Percent")) {
591                             channelState = new PercentType(state);
592                         }
593                         break;
594                     default:
595                         logger.debug("Item type {} is not yet supported", acceptedItemType);
596                         break;
597                 }
598             }
599         } catch (IllegalArgumentException | DateTimeException e) {
600             logger.warn("Failed to parse state \"{}\" for item {}: {}", state, itemName, e.getMessage());
601             channelState = UnDefType.UNDEF;
602         }
603         if (channelState != null) {
604             if (onlyIfStateChanged && channelState.equals(channelsLastStates.get(channel.getUID()))) {
605                 logger.trace("ItemStateChangedEvent ignored for item {} as state is identical to the last state",
606                         itemName);
607                 return;
608             }
609             channelsLastStates.put(channel.getUID(), channelState);
610             updateState(channel.getUID(), channelState);
611             String channelStateStr = channelState.toFullString();
612             logger.debug("updateState {} with {}", channel.getUID(),
613                     channelStateStr.length() < MAX_STATE_SIZE_FOR_LOGGING ? channelStateStr
614                             : channelStateStr.substring(0, MAX_STATE_SIZE_FOR_LOGGING) + "...");
615         }
616     }
617
618     private boolean checkStateType(String itemName, @Nullable String stateType, String expectedType) {
619         if (stateType != null && !expectedType.equals(stateType)) {
620             logger.debug("Unexpected value type {} for item {}", stateType, itemName);
621             return false;
622         } else {
623             return true;
624         }
625     }
626
627     @Override
628     public Collection<Class<? extends ThingHandlerService>> getServices() {
629         return Collections.singleton(RemoteopenhabDiscoveryService.class);
630     }
631 }