]> git.basschouten.com Git - openhab-addons.git/blob
1f2da882e8f51a68ec828bf326f08df05ad88370
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.km200.internal.handler;
14
15 import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.net.InetAddress;
19 import java.net.UnknownHostException;
20 import java.security.NoSuchAlgorithmException;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.Iterator;
24 import java.util.LinkedHashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Set;
29 import java.util.concurrent.CopyOnWriteArrayList;
30 import java.util.concurrent.Executors;
31 import java.util.concurrent.ScheduledExecutorService;
32 import java.util.concurrent.TimeUnit;
33
34 import javax.crypto.Cipher;
35
36 import org.eclipse.jdt.annotation.NonNullByDefault;
37 import org.eclipse.jdt.annotation.Nullable;
38 import org.eclipse.jetty.client.HttpClient;
39 import org.openhab.binding.km200.internal.KM200Device;
40 import org.openhab.binding.km200.internal.KM200ServiceObject;
41 import org.openhab.binding.km200.internal.KM200ThingType;
42 import org.openhab.binding.km200.internal.KM200Utils;
43 import org.openhab.core.common.NamedThreadFactory;
44 import org.openhab.core.config.core.Configuration;
45 import org.openhab.core.library.types.DateTimeType;
46 import org.openhab.core.library.types.DecimalType;
47 import org.openhab.core.library.types.StringType;
48 import org.openhab.core.thing.Bridge;
49 import org.openhab.core.thing.Channel;
50 import org.openhab.core.thing.ChannelUID;
51 import org.openhab.core.thing.Thing;
52 import org.openhab.core.thing.ThingStatus;
53 import org.openhab.core.thing.ThingStatusDetail;
54 import org.openhab.core.thing.ThingStatusInfo;
55 import org.openhab.core.thing.ThingTypeUID;
56 import org.openhab.core.thing.binding.BaseBridgeHandler;
57 import org.openhab.core.types.Command;
58 import org.openhab.core.types.State;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import com.google.gson.JsonObject;
63 import com.google.gson.JsonParseException;
64
65 /**
66  * The {@link KM200GatewayHandler} is responsible for handling commands, which are
67  * sent to one of the channels.
68  *
69  * @author Markus Eckhardt - Initial contribution
70  */
71 @NonNullByDefault
72 public class KM200GatewayHandler extends BaseBridgeHandler {
73
74     private final Logger logger = LoggerFactory.getLogger(KM200GatewayHandler.class);
75
76     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_KMDEVICE);
77
78     private final Map<Channel, JsonObject> sendMap = Collections.synchronizedMap(new LinkedHashMap<>());
79
80     private List<KM200GatewayStatusListener> listeners = new CopyOnWriteArrayList<>();
81
82     /**
83      * shared instance of HTTP client for (a)synchronous calls
84      */
85     private ScheduledExecutorService executor;
86     private final KM200Device remoteDevice;
87     private final KM200DataHandler dataHandler;
88     private int readDelay;
89     private int refreshInterval;
90
91     public KM200GatewayHandler(Bridge bridge, HttpClient httpClient) {
92         super(bridge);
93         refreshInterval = 120;
94         readDelay = 100;
95         remoteDevice = new KM200Device(httpClient);
96         dataHandler = new KM200DataHandler(remoteDevice);
97         executor = Executors.newScheduledThreadPool(2, new NamedThreadFactory("org.openhab.binding.km200", true));
98     }
99
100     @Override
101     public void handleCommand(ChannelUID channelUID, Command command) {
102         Channel channel = getThing().getChannel(channelUID.getId());
103         if (null != channel) {
104             if (command instanceof DateTimeType || command instanceof DecimalType || command instanceof StringType) {
105                 prepareMessage(thing, channel, command);
106             } else {
107                 logger.warn("Unsupported Command: {} Class: {}", command.toFullString(), command.getClass());
108             }
109         }
110     }
111
112     @Override
113     public void initialize() {
114         try {
115             int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES/ECB/NoPadding");
116             if (maxKeyLen <= 128) {
117                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
118                         "Java Cryptography Extension (JCE) have to be installed");
119                 return;
120             }
121         } catch (NoSuchAlgorithmException e) {
122             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "AES encoding not supported");
123             return;
124         }
125         if (!getDevice().getInited()) {
126             logger.info("Update KM50/100/200 gateway configuration, it takes a minute....");
127             getConfiguration();
128             if (getDevice().isConfigured()) {
129                 if (!checkConfiguration()) {
130                     return;
131                 }
132                 /* configuration and communication seems to be ok */
133                 readCapabilities();
134                 updateStatus(ThingStatus.ONLINE);
135             } else {
136                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
137                 logger.debug("The KM50/100/200 gateway configuration is not complete");
138                 return;
139             }
140
141             SendKM200Runnable sendRunnable = new SendKM200Runnable(sendMap, getDevice());
142             GetKM200Runnable receivingRunnable = new GetKM200Runnable(sendMap, this, getDevice());
143             if (!executor.isTerminated()) {
144                 executor = Executors.newScheduledThreadPool(2,
145                         new NamedThreadFactory("org.openhab.binding.km200", true));
146                 executor.scheduleWithFixedDelay(receivingRunnable, 30, refreshInterval, TimeUnit.SECONDS);
147                 executor.scheduleWithFixedDelay(sendRunnable, 60, refreshInterval * 2, TimeUnit.SECONDS);
148             }
149         }
150     }
151
152     @Override
153     public void dispose() {
154         executor.shutdown();
155         try {
156             if (!executor.awaitTermination(60000, TimeUnit.SECONDS)) {
157                 logger.debug("Services didn't finish in 60000 seconds!");
158             }
159         } catch (InterruptedException e) {
160             executor.shutdownNow();
161         }
162         synchronized (getDevice()) {
163             getDevice().setInited(false);
164             getDevice().setIP4Address("");
165             getDevice().setCryptKeyPriv("");
166             getDevice().setMD5Salt("");
167             getDevice().setGatewayPassword("");
168             getDevice().setPrivatePassword("");
169             getDevice().serviceTreeMap.clear();
170         }
171         updateStatus(ThingStatus.OFFLINE);
172     }
173
174     @Override
175     public void handleRemoval() {
176         for (Thing actThing : getThing().getThings()) {
177             actThing.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, ""));
178         }
179         this.updateStatus(ThingStatus.REMOVED);
180     }
181
182     /**
183      * Gets bridges configuration
184      */
185     private void getConfiguration() {
186         Configuration configuration = getConfig();
187         for (String key : configuration.keySet()) {
188             logger.trace("initialize Key: {} Value: {}", key, configuration.get(key));
189             switch (key) {
190                 case "ip4Address":
191                     String ip = (String) configuration.get("ip4Address");
192                     if (ip != null && !ip.isBlank()) {
193                         try {
194                             InetAddress.getByName(ip);
195                         } catch (UnknownHostException e) {
196                             logger.warn("IP4_address is not valid!: {}", ip);
197                         }
198                         getDevice().setIP4Address(ip);
199                     } else {
200                         logger.debug("No ip4_address configured!");
201                     }
202                     break;
203                 case "privateKey":
204                     String privateKey = (String) configuration.get("privateKey");
205                     if (privateKey != null && !privateKey.isBlank()) {
206                         getDevice().setCryptKeyPriv(privateKey);
207                     }
208                     break;
209                 case "md5Salt":
210                     String md5Salt = (String) configuration.get("md5Salt");
211                     if (md5Salt != null && !md5Salt.isBlank()) {
212                         getDevice().setMD5Salt(md5Salt);
213                     }
214                     break;
215                 case "gatewayPassword":
216                     String gatewayPassword = (String) configuration.get("gatewayPassword");
217                     if (gatewayPassword != null && !gatewayPassword.isBlank()) {
218                         getDevice().setGatewayPassword(gatewayPassword);
219                     }
220                     break;
221                 case "privatePassword":
222                     String privatePassword = (String) configuration.get("privatePassword");
223                     if (privatePassword != null && !privatePassword.isBlank()) {
224                         getDevice().setPrivatePassword(privatePassword);
225                     }
226                     break;
227                 case "refreshInterval":
228                     refreshInterval = ((BigDecimal) configuration.get("refreshInterval")).intValue();
229                     logger.debug("Set refresh interval to: {} seconds.", refreshInterval);
230                     break;
231                 case "readDelay":
232                     readDelay = ((BigDecimal) configuration.get("readDelay")).intValue();
233                     logger.debug("Set read delay to: {} seconds.", readDelay);
234                     break;
235                 case "maxNbrRepeats":
236                     Integer maxNbrRepeats = ((BigDecimal) configuration.get("maxNbrRepeats")).intValue();
237                     logger.debug("Set max. number of repeats to: {} seconds.", maxNbrRepeats);
238                     remoteDevice.setMaxNbrRepeats(maxNbrRepeats);
239                     break;
240             }
241         }
242     }
243
244     /**
245      * Checks bridges configuration
246      */
247     private boolean checkConfiguration() {
248         /* Get HTTP Data from device */
249         JsonObject nodeRoot = remoteDevice.getServiceNode("/gateway/DateTime");
250         if (nodeRoot == null || nodeRoot.isJsonNull()) {
251             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
252                     "No communication possible with gateway");
253             return false;
254         }
255         logger.debug("Test of the communication to the gateway was successful..");
256
257         /* Testing the received data, is decryption working? */
258         try {
259             nodeRoot.get("type").getAsString();
260             nodeRoot.get("id").getAsString();
261         } catch (JsonParseException e) {
262             logger.debug("The data is not readable, check the key and password configuration! {}", e.getMessage());
263             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Wrong gateway configuration");
264             return false;
265         }
266         return true;
267     }
268
269     /**
270      * Reads the devices capabilities and sets the data structures
271      */
272     private void readCapabilities() {
273         KM200VirtualServiceHandler virtualServiceHandler;
274         /* Checking of the device specific services and creating of a service list */
275         for (KM200ThingType thing : KM200ThingType.values()) {
276             String rootPath = thing.getRootPath();
277             if (!rootPath.isEmpty() && (rootPath.indexOf("/", 0) == rootPath.lastIndexOf("/", rootPath.length() - 1))) {
278                 if (remoteDevice.getBlacklistMap().contains(thing.getRootPath())) {
279                     logger.debug("Service on blacklist: {}", thing.getRootPath());
280                     return;
281                 }
282                 KM200ServiceHandler serviceHandler = new KM200ServiceHandler(thing.getRootPath(), null, remoteDevice);
283                 serviceHandler.initObject();
284             }
285         }
286         /* Now init the virtual services */
287         virtualServiceHandler = new KM200VirtualServiceHandler(remoteDevice);
288         virtualServiceHandler.initVirtualObjects();
289         /* Output all available services in the log file */
290         getDevice().listAllServices();
291         updateBridgeProperties();
292         getDevice().setInited(true);
293     }
294
295     /**
296      * Adds a GatewayConnectedListener
297      */
298     public void addGatewayStatusListener(KM200GatewayStatusListener listener) {
299         listeners.add(listener);
300         listener.gatewayStatusChanged(getThing().getStatus());
301     }
302
303     /**
304      * Removes a GatewayConnectedListener
305      */
306     public void removeHubStatusListener(KM200GatewayStatusListener listener) {
307         listeners.remove(listener);
308     }
309
310     /**
311      * Refreshes a channel
312      */
313     public void refreshChannel(Channel channel) {
314         GetSingleKM200Runnable runnable = new GetSingleKM200Runnable(sendMap, this, getDevice(), channel);
315         logger.debug("starting single runnable.");
316         scheduler.submit(runnable);
317     }
318
319     /**
320      * Updates bridges properties
321      */
322     private void updateBridgeProperties() {
323         List<String> propertyServices = new ArrayList<>();
324         propertyServices.add(KM200ThingType.GATEWAY.getRootPath());
325         propertyServices.add(KM200ThingType.SYSTEM.getRootPath());
326         Map<String, String> bridgeProperties = editProperties();
327
328         for (KM200ThingType tType : KM200ThingType.values()) {
329             List<String> asProperties = tType.asBridgeProperties();
330             String rootPath = tType.getRootPath();
331             if (rootPath.isEmpty()) {
332                 continue;
333             }
334             KM200ServiceObject serObj = getDevice().getServiceObject(rootPath);
335             if (null != serObj) {
336                 for (String subKey : asProperties) {
337                     if (serObj.serviceTreeMap.containsKey(subKey)) {
338                         KM200ServiceObject subKeyObj = serObj.serviceTreeMap.get(subKey);
339                         if (subKeyObj != null) {
340                             String subKeyType = subKeyObj.getServiceType();
341                             if (!DATA_TYPE_STRING_VALUE.equals(subKeyType)
342                                     && !DATA_TYPE_FLOAT_VALUE.equals(subKeyType)) {
343                                 continue;
344                             }
345                             if (bridgeProperties.containsKey(subKey)) {
346                                 bridgeProperties.remove(subKey);
347                             }
348                             Object value = subKeyObj.getValue();
349                             logger.trace("Add Property: {}  :{}", subKey, value);
350                             if (null != value) {
351                                 bridgeProperties.put(subKey, value.toString());
352                             }
353                         }
354                     }
355                 }
356             }
357         }
358         updateProperties(bridgeProperties);
359     }
360
361     /**
362      * Prepares a message for sending
363      */
364     public void prepareMessage(Thing thing, Channel channel, Command command) {
365         if (getDevice().getInited()) {
366             JsonObject newObject = null;
367             State state = null;
368             String service = KM200Utils.checkParameterReplacement(channel, getDevice());
369             String chTypes = channel.getAcceptedItemType();
370             if (null == chTypes) {
371                 logger.warn("Channel {} has not accepted item types", channel.getLabel());
372                 return;
373             }
374             logger.trace("handleCommand channel: {} service: {}", channel.getLabel(), service);
375             newObject = dataHandler.sendProvidersState(service, command, chTypes,
376                     KM200Utils.getChannelConfigurationStrings(channel));
377             synchronized (getDevice()) {
378                 KM200ServiceObject serObjekt = getDevice().getServiceObject(service);
379                 if (null != serObjekt) {
380                     if (newObject != null) {
381                         sendMap.put(channel, newObject);
382                     } else if (getDevice().containsService(service) && serObjekt.getVirtual() == 1) {
383                         String parent = serObjekt.getParent();
384                         for (Thing actThing : getThing().getThings()) {
385                             logger.trace("Checking: {}", actThing.getUID().getAsString());
386                             for (Channel tmpChannel : actThing.getChannels()) {
387                                 String tmpChTypes = tmpChannel.getAcceptedItemType();
388                                 if (null == tmpChTypes) {
389                                     logger.warn("Channel {} has not accepted item types", tmpChannel.getLabel());
390                                     return;
391                                 }
392                                 String actService = KM200Utils.checkParameterReplacement(tmpChannel, getDevice());
393                                 KM200ServiceObject actSerObjekt = getDevice().getServiceObject(actService);
394                                 if (null != actSerObjekt) {
395                                     String actParent = actSerObjekt.getParent();
396                                     if (actParent != null && actParent.equals(parent)) {
397                                         state = dataHandler.getProvidersState(actService, tmpChTypes,
398                                                 KM200Utils.getChannelConfigurationStrings(tmpChannel));
399                                         if (state != null) {
400                                             try {
401                                                 updateState(tmpChannel.getUID(), state);
402                                             } catch (IllegalStateException e) {
403                                                 logger.warn("Could not get updated item state", e);
404                                             }
405                                         }
406                                     }
407                                 }
408                             }
409                         }
410                     } else {
411                         logger.debug("Service is not availible: {}", service);
412                     }
413                 }
414             }
415         }
416     }
417
418     /**
419      * Update the children
420      */
421     // Every thing has here a handler
422     private void updateChildren(Map<Channel, JsonObject> sendMap, KM200GatewayHandler gatewayHandler,
423             KM200Device remoteDevice, @Nullable String parent) {
424         State state;
425         synchronized (remoteDevice) {
426             if (parent != null) {
427                 KM200ServiceObject serParObjekt = remoteDevice.getServiceObject(parent);
428                 if (null != serParObjekt) {
429                     serParObjekt.setUpdated(false);
430                 }
431             }
432             for (Thing actThing : gatewayHandler.getThing().getThings()) {
433                 for (Channel actChannel : actThing.getChannels()) {
434                     String actChTypes = actChannel.getAcceptedItemType();
435                     if (null == actChTypes) {
436                         logger.warn("Channel {} has not accepted item types", actChannel.getLabel());
437                         return;
438                     }
439                     logger.trace("Checking: {} Root: {}", actChannel.getUID().getAsString(),
440                             actChannel.getProperties().get("root"));
441                     KM200ThingHandler actHandler = (KM200ThingHandler) actThing.getHandler();
442                     if (actHandler != null) {
443                         if (!actHandler.checkLinked(actChannel)) {
444                             continue;
445                         }
446                     } else {
447                         continue;
448                     }
449                     String tmpService = KM200Utils.checkParameterReplacement(actChannel, remoteDevice);
450                     KM200ServiceObject tmpSerObjekt = remoteDevice.getServiceObject(tmpService);
451                     if (null != tmpSerObjekt) {
452                         if (parent == null || parent.equals(tmpSerObjekt.getParent())) {
453                             synchronized (sendMap) {
454                                 JsonObject obj = sendMap.get(actChannel);
455                                 if (obj != null) {
456                                     state = dataHandler.parseJSONData(obj, tmpSerObjekt.getServiceType(), tmpService,
457                                             actChTypes, KM200Utils.getChannelConfigurationStrings(actChannel));
458                                 } else {
459                                     state = dataHandler.getProvidersState(tmpService, actChTypes,
460                                             KM200Utils.getChannelConfigurationStrings(actChannel));
461                                 }
462                             }
463                             if (state != null) {
464                                 try {
465                                     gatewayHandler.updateState(actChannel.getUID(), state);
466                                 } catch (IllegalStateException e) {
467                                     logger.warn("Could not get updated item state", e);
468                                 }
469                             }
470                         }
471                         try {
472                             Thread.sleep(readDelay);
473                         } catch (InterruptedException e) {
474                             continue;
475                         }
476                     }
477                 }
478             }
479         }
480     }
481
482     /**
483      * Return the device instance.
484      */
485     public KM200Device getDevice() {
486         return remoteDevice;
487     }
488
489     /**
490      * The GetKM200Runnable class get the data from device to all items.
491      */
492     private class GetKM200Runnable implements Runnable {
493
494         private final KM200GatewayHandler gatewayHandler;
495         private final KM200Device remoteDevice;
496         private final Logger logger = LoggerFactory.getLogger(GetKM200Runnable.class);
497         private final Map<Channel, JsonObject> sendMap;
498
499         public GetKM200Runnable(Map<Channel, JsonObject> sendMap, KM200GatewayHandler gatewayHandler,
500                 KM200Device remoteDevice) {
501             this.sendMap = sendMap;
502             this.gatewayHandler = gatewayHandler;
503             this.remoteDevice = remoteDevice;
504         }
505
506         @Override
507         public void run() {
508             logger.debug("GetKM200Runnable");
509             synchronized (remoteDevice) {
510                 if (remoteDevice.getInited()) {
511                     remoteDevice.resetAllUpdates(remoteDevice.serviceTreeMap);
512                     updateChildren(sendMap, gatewayHandler, remoteDevice, null);
513                 }
514             }
515         }
516     }
517
518     /**
519      * The GetKM200Runnable class get the data from device for one channel.
520      */
521     private class GetSingleKM200Runnable implements Runnable {
522
523         private final Logger logger = LoggerFactory.getLogger(GetSingleKM200Runnable.class);
524         private final KM200GatewayHandler gatewayHandler;
525         private final KM200Device remoteDevice;
526         private final Channel channel;
527         private final Map<Channel, JsonObject> sendMap;
528
529         public GetSingleKM200Runnable(Map<Channel, JsonObject> sendMap, KM200GatewayHandler gatewayHandler,
530                 KM200Device remoteDevice, Channel channel) {
531             this.gatewayHandler = gatewayHandler;
532             this.remoteDevice = remoteDevice;
533             this.channel = channel;
534             this.sendMap = sendMap;
535         }
536
537         @Override
538         public void run() {
539             logger.debug("GetKM200Runnable");
540             State state = null;
541             synchronized (remoteDevice) {
542                 synchronized (sendMap) {
543                     if (sendMap.containsKey(channel)) {
544                         return;
545                     }
546                 }
547                 if (remoteDevice.getInited()) {
548                     logger.trace("Checking: {} Root: {}", channel.getUID().getAsString(),
549                             channel.getProperties().get("root"));
550                     String chTypes = channel.getAcceptedItemType();
551                     if (null == chTypes) {
552                         logger.warn("Channel {} has not accepted item types", channel.getLabel());
553                         return;
554                     }
555                     String service = KM200Utils.checkParameterReplacement(channel, remoteDevice);
556                     KM200ServiceObject object = remoteDevice.getServiceObject(service);
557                     if (null != object) {
558                         if (object.getVirtual() == 1) {
559                             String parent = object.getParent();
560                             updateChildren(sendMap, gatewayHandler, remoteDevice, parent);
561                         } else {
562                             object.setUpdated(false);
563                             synchronized (sendMap) {
564                                 KM200ServiceObject serObjekt = remoteDevice.getServiceObject(service);
565                                 if (null != serObjekt) {
566                                     JsonObject obj = sendMap.get(channel);
567                                     if (obj != null) {
568                                         state = dataHandler.parseJSONData(obj, serObjekt.getServiceType(), service,
569                                                 chTypes, KM200Utils.getChannelConfigurationStrings(channel));
570                                     } else {
571                                         state = dataHandler.getProvidersState(service, chTypes,
572                                                 KM200Utils.getChannelConfigurationStrings(channel));
573                                     }
574                                 }
575                                 if (state != null) {
576                                     try {
577                                         gatewayHandler.updateState(channel.getUID(), state);
578                                     } catch (IllegalStateException e) {
579                                         logger.warn("Could not get updated item state", e);
580                                     }
581                                 }
582                             }
583                         }
584                     }
585                 }
586             }
587         }
588     }
589
590     /**
591      * The sendKM200Thread class sends the data to the device.
592      */
593     private class SendKM200Runnable implements Runnable {
594
595         private final Logger logger = LoggerFactory.getLogger(SendKM200Runnable.class);
596         private final Map<Channel, JsonObject> newObject;
597         private final KM200Device remoteDevice;
598
599         public SendKM200Runnable(Map<Channel, JsonObject> newObject, KM200Device remoteDevice) {
600             this.newObject = newObject;
601             this.remoteDevice = remoteDevice;
602         }
603
604         @Override
605         public void run() {
606             logger.debug("Send-Executor started");
607             Map.Entry<Channel, JsonObject> nextEntry;
608             /* Check whether a new entry is availible, if yes then take and remove it */
609             do {
610                 nextEntry = null;
611                 synchronized (remoteDevice) {
612                     synchronized (newObject) {
613                         Iterator<Entry<Channel, JsonObject>> i = newObject.entrySet().iterator();
614                         if (i.hasNext()) {
615                             nextEntry = i.next();
616                             i.remove();
617                         }
618                     }
619                     if (nextEntry != null) {
620                         /* Now send the data to the device */
621                         Channel channel = nextEntry.getKey();
622                         JsonObject newObject = nextEntry.getValue();
623
624                         String service = KM200Utils.checkParameterReplacement(channel, remoteDevice);
625                         KM200ServiceObject object = remoteDevice.getServiceObject(service);
626                         if (null != object) {
627                             if (object.getVirtual() == 0) {
628                                 remoteDevice.setServiceNode(service, newObject);
629                             } else {
630                                 String parent = object.getParent();
631                                 if (null != parent) {
632                                     logger.trace("Sending: {} to : {}", newObject, service);
633                                     remoteDevice.setServiceNode(parent, newObject);
634                                 }
635                             }
636                         }
637                     }
638                 }
639             } while (nextEntry != null);
640         }
641     }
642 }