]> git.basschouten.com Git - openhab-addons.git/blob
d4046b66bf7b6374f7db35e49f8675a0fc1db27b
[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.insteon.internal.handler;
14
15 import java.lang.reflect.Type;
16 import java.math.BigDecimal;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Objects;
24 import java.util.Set;
25 import java.util.stream.Collectors;
26 import java.util.stream.Stream;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.insteon.internal.InsteonBinding;
31 import org.openhab.binding.insteon.internal.InsteonBindingConstants;
32 import org.openhab.binding.insteon.internal.config.InsteonChannelConfiguration;
33 import org.openhab.binding.insteon.internal.config.InsteonDeviceConfiguration;
34 import org.openhab.binding.insteon.internal.device.DeviceFeature;
35 import org.openhab.binding.insteon.internal.device.DeviceTypeLoader;
36 import org.openhab.binding.insteon.internal.device.InsteonAddress;
37 import org.openhab.binding.insteon.internal.device.InsteonDevice;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.Channel;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.binding.BaseThingHandler;
45 import org.openhab.core.thing.binding.ThingHandlerCallback;
46 import org.openhab.core.thing.type.ChannelTypeUID;
47 import org.openhab.core.types.Command;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 import com.google.gson.Gson;
52 import com.google.gson.JsonParseException;
53 import com.google.gson.reflect.TypeToken;
54
55 /**
56  * The {@link InsteonDeviceHandler} is responsible for handling commands, which are
57  * sent to one of the channels.
58  *
59  * @author Rob Nielsen - Initial contribution
60  */
61 @NonNullByDefault
62 public class InsteonDeviceHandler extends BaseThingHandler {
63
64     private static final Set<String> ALL_CHANNEL_IDS = Collections.unmodifiableSet(Stream.of(
65             InsteonBindingConstants.AC_DELAY, InsteonBindingConstants.BACKLIGHT_DURATION,
66             InsteonBindingConstants.BATTERY_LEVEL, InsteonBindingConstants.BATTERY_PERCENT,
67             InsteonBindingConstants.BATTERY_WATERMARK_LEVEL, InsteonBindingConstants.BEEP,
68             InsteonBindingConstants.BOTTOM_OUTLET, InsteonBindingConstants.BUTTON_A, InsteonBindingConstants.BUTTON_B,
69             InsteonBindingConstants.BUTTON_C, InsteonBindingConstants.BUTTON_D, InsteonBindingConstants.BUTTON_E,
70             InsteonBindingConstants.BUTTON_F, InsteonBindingConstants.BUTTON_G, InsteonBindingConstants.BUTTON_H,
71             InsteonBindingConstants.BROADCAST_ON_OFF, InsteonBindingConstants.CONTACT,
72             InsteonBindingConstants.COOL_SET_POINT, InsteonBindingConstants.DIMMER, InsteonBindingConstants.FAN,
73             InsteonBindingConstants.FAN_MODE, InsteonBindingConstants.FAST_ON_OFF,
74             InsteonBindingConstants.FAST_ON_OFF_BUTTON_A, InsteonBindingConstants.FAST_ON_OFF_BUTTON_B,
75             InsteonBindingConstants.FAST_ON_OFF_BUTTON_C, InsteonBindingConstants.FAST_ON_OFF_BUTTON_D,
76             InsteonBindingConstants.FAST_ON_OFF_BUTTON_E, InsteonBindingConstants.FAST_ON_OFF_BUTTON_F,
77             InsteonBindingConstants.FAST_ON_OFF_BUTTON_G, InsteonBindingConstants.FAST_ON_OFF_BUTTON_H,
78             InsteonBindingConstants.HEAT_SET_POINT, InsteonBindingConstants.HUMIDITY,
79             InsteonBindingConstants.HUMIDITY_HIGH, InsteonBindingConstants.HUMIDITY_LOW,
80             InsteonBindingConstants.IS_COOLING, InsteonBindingConstants.IS_HEATING,
81             InsteonBindingConstants.KEYPAD_BUTTON_A, InsteonBindingConstants.KEYPAD_BUTTON_B,
82             InsteonBindingConstants.KEYPAD_BUTTON_C, InsteonBindingConstants.KEYPAD_BUTTON_D,
83             InsteonBindingConstants.KEYPAD_BUTTON_E, InsteonBindingConstants.KEYPAD_BUTTON_F,
84             InsteonBindingConstants.KEYPAD_BUTTON_G, InsteonBindingConstants.KEYPAD_BUTTON_H,
85             InsteonBindingConstants.KWH, InsteonBindingConstants.LAST_HEARD_FROM,
86             InsteonBindingConstants.LED_BRIGHTNESS, InsteonBindingConstants.LED_ONOFF,
87             InsteonBindingConstants.LIGHT_DIMMER, InsteonBindingConstants.LIGHT_LEVEL,
88             InsteonBindingConstants.LIGHT_LEVEL_ABOVE_THRESHOLD, InsteonBindingConstants.LOAD_DIMMER,
89             InsteonBindingConstants.LOAD_SWITCH, InsteonBindingConstants.LOAD_SWITCH_FAST_ON_OFF,
90             InsteonBindingConstants.LOAD_SWITCH_MANUAL_CHANGE, InsteonBindingConstants.LOWBATTERY,
91             InsteonBindingConstants.MANUAL_CHANGE, InsteonBindingConstants.MANUAL_CHANGE_BUTTON_A,
92             InsteonBindingConstants.MANUAL_CHANGE_BUTTON_B, InsteonBindingConstants.MANUAL_CHANGE_BUTTON_C,
93             InsteonBindingConstants.MANUAL_CHANGE_BUTTON_D, InsteonBindingConstants.MANUAL_CHANGE_BUTTON_E,
94             InsteonBindingConstants.MANUAL_CHANGE_BUTTON_F, InsteonBindingConstants.MANUAL_CHANGE_BUTTON_G,
95             InsteonBindingConstants.MANUAL_CHANGE_BUTTON_H, InsteonBindingConstants.NOTIFICATION,
96             InsteonBindingConstants.ON_LEVEL, InsteonBindingConstants.RAMP_DIMMER, InsteonBindingConstants.RAMP_RATE,
97             InsteonBindingConstants.RESET, InsteonBindingConstants.STAGE1_DURATION, InsteonBindingConstants.SWITCH,
98             InsteonBindingConstants.SYSTEM_MODE, InsteonBindingConstants.TAMPER_SWITCH,
99             InsteonBindingConstants.TEMPERATURE, InsteonBindingConstants.TEMPERATURE_LEVEL,
100             InsteonBindingConstants.TOP_OUTLET, InsteonBindingConstants.UPDATE, InsteonBindingConstants.WATTS)
101             .collect(Collectors.toSet()));
102
103     public static final String BROADCAST_GROUPS = "broadcastGroups";
104     public static final String BROADCAST_ON_OFF = "broadcastonoff";
105     public static final String CMD = "cmd";
106     public static final String CMD_RESET = "reset";
107     public static final String CMD_UPDATE = "update";
108     public static final String DATA = "data";
109     public static final String FIELD = "field";
110     public static final String FIELD_BATTERY_LEVEL = "battery_level";
111     public static final String FIELD_BATTERY_PERCENTAGE = "battery_percentage";
112     public static final String FIELD_BATTERY_WATERMARK_LEVEL = "battery_watermark_level";
113     public static final String FIELD_KWH = "kwh";
114     public static final String FIELD_LIGHT_LEVEL = "light_level";
115     public static final String FIELD_TEMPERATURE_LEVEL = "temperature_level";
116     public static final String FIELD_WATTS = "watts";
117     public static final String GROUP = "group";
118     public static final String METER = "meter";
119
120     public static final String HIDDEN_DOOR_SENSOR_PRODUCT_KEY = "F00.00.03";
121     public static final String MOTION_SENSOR_II_PRODUCT_KEY = "F00.00.24";
122     public static final String MOTION_SENSOR_PRODUCT_KEY = "0x00004A";
123     public static final String PLM_PRODUCT_KEY = "0x000045";
124     public static final String POWER_METER_PRODUCT_KEY = "F00.00.17";
125
126     private final Logger logger = LoggerFactory.getLogger(InsteonDeviceHandler.class);
127
128     private @Nullable InsteonDeviceConfiguration config;
129
130     public InsteonDeviceHandler(Thing thing) {
131         super(thing);
132     }
133
134     @Override
135     public void initialize() {
136         config = getConfigAs(InsteonDeviceConfiguration.class);
137
138         scheduler.execute(() -> {
139             final Bridge bridge = getBridge();
140             if (bridge == null) {
141                 String msg = "An Insteon network bridge has not been selected for this device.";
142                 logger.warn("{} {}", thing.getUID().getAsString(), msg);
143
144                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
145                 return;
146             }
147
148             InsteonDeviceConfiguration config = this.config;
149             if (config == null) {
150                 String msg = "Insteon device configuration is null.";
151                 logger.warn("{} {}", thing.getUID().getAsString(), msg);
152
153                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
154                 return;
155             }
156             String address = config.getAddress();
157             if (!InsteonAddress.isValid(address)) {
158                 String msg = "Unable to start Insteon device, the insteon or X10 address '" + address
159                         + "' is invalid. It must be in the format 'AB.CD.EF' or 'H.U' (X10).";
160                 logger.warn("{} {}", thing.getUID().getAsString(), msg);
161
162                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
163                 return;
164             }
165
166             DeviceTypeLoader instance = DeviceTypeLoader.instance();
167             if (instance == null) {
168                 String msg = "Device type loader is null.";
169                 logger.warn("{} {}", thing.getUID().getAsString(), msg);
170
171                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
172                 return;
173             }
174
175             String productKey = config.getProductKey();
176             if (instance.getDeviceType(productKey) == null) {
177                 String msg = "Unable to start Insteon device, invalid product key '" + productKey + "'.";
178                 logger.warn("{} {}", thing.getUID().getAsString(), msg);
179
180                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
181                 return;
182             }
183
184             String deviceConfig = config.getDeviceConfig();
185             Map<String, Object> deviceConfigMap;
186             if (deviceConfig != null) {
187                 Type mapType = new TypeToken<Map<String, Object>>() {
188                 }.getType();
189                 try {
190                     deviceConfigMap = Objects.requireNonNull(new Gson().fromJson(deviceConfig, mapType));
191                 } catch (JsonParseException e) {
192                     String msg = "The device configuration parameter is not valid JSON.";
193                     logger.warn("{} {}", thing.getUID().getAsString(), msg);
194
195                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
196                     return;
197                 }
198             } else {
199                 deviceConfigMap = Collections.emptyMap();
200             }
201
202             InsteonBinding insteonBinding = getInsteonBinding();
203             InsteonAddress insteonAddress = new InsteonAddress(address);
204             if (insteonBinding.getDevice(insteonAddress) != null) {
205                 String msg = "A device already exists with the address '" + address + "'.";
206                 logger.warn("{} {}", thing.getUID().getAsString(), msg);
207
208                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
209                 return;
210             }
211
212             InsteonDevice device = insteonBinding.makeNewDevice(insteonAddress, productKey, deviceConfigMap);
213             if (device == null) {
214                 String msg = "Unable to create a device with the product key '" + productKey + "' with the address'"
215                         + address + "'.";
216                 logger.warn("{} {}", thing.getUID().getAsString(), msg);
217
218                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
219                 return;
220             }
221
222             ThingHandlerCallback callback = getCallback();
223             if (callback == null) {
224                 String msg = "Unable to get thing handler callback.";
225                 logger.warn("{} {}", thing.getUID().getAsString(), msg);
226
227                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
228                 return;
229             }
230
231             Map<String, Channel> channelMap = new HashMap<>();
232             String thingId = getThing().getUID().getAsString();
233             for (String channelId : ALL_CHANNEL_IDS) {
234                 String feature = channelId.toLowerCase();
235
236                 if (productKey.equals(HIDDEN_DOOR_SENSOR_PRODUCT_KEY)) {
237                     if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)
238                             || feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_WATERMARK_LEVEL)) {
239                         feature = DATA;
240                     }
241                 } else if (productKey.equals(MOTION_SENSOR_PRODUCT_KEY)) {
242                     if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)
243                             || feature.equalsIgnoreCase(InsteonBindingConstants.LIGHT_LEVEL)) {
244                         feature = DATA;
245                     }
246                 } else if (productKey.equals(MOTION_SENSOR_II_PRODUCT_KEY)) {
247                     if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)
248                             || feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_PERCENT)
249                             || feature.equalsIgnoreCase(InsteonBindingConstants.LIGHT_LEVEL)
250                             || feature.equalsIgnoreCase(InsteonBindingConstants.TEMPERATURE_LEVEL)) {
251                         feature = DATA;
252                     }
253                 } else if (productKey.equals(PLM_PRODUCT_KEY)) {
254                     String parts[] = feature.split("#");
255                     if (parts.length == 2 && parts[0].equalsIgnoreCase(InsteonBindingConstants.BROADCAST_ON_OFF)
256                             && parts[1].matches("^\\d+$")) {
257                         feature = BROADCAST_ON_OFF;
258                     }
259                 } else if (productKey.equals(POWER_METER_PRODUCT_KEY)) {
260                     if (feature.equalsIgnoreCase(InsteonBindingConstants.KWH)
261                             || feature.equalsIgnoreCase(InsteonBindingConstants.RESET)
262                             || feature.equalsIgnoreCase(InsteonBindingConstants.UPDATE)
263                             || feature.equalsIgnoreCase(InsteonBindingConstants.WATTS)) {
264                         feature = METER;
265                     }
266                 }
267
268                 DeviceFeature f = device.getFeature(feature);
269                 if (f != null) {
270                     if (!f.isFeatureGroup()) {
271                         if (channelId.equals(InsteonBindingConstants.BROADCAST_ON_OFF)) {
272                             Set<String> broadcastChannels = new HashSet<>();
273                             for (Channel channel : thing.getChannels()) {
274                                 String id = channel.getUID().getId();
275                                 if (id.startsWith(InsteonBindingConstants.BROADCAST_ON_OFF)) {
276                                     channelMap.put(id, channel);
277                                     broadcastChannels.add(id);
278                                 }
279                             }
280
281                             Object groups = deviceConfigMap.get(BROADCAST_GROUPS);
282                             if (groups != null) {
283                                 boolean valid = false;
284                                 if (groups instanceof List<?>) {
285                                     valid = true;
286                                     for (Object o : (List<?>) groups) {
287                                         if (o instanceof Double && (Double) o % 1 == 0) {
288                                             String id = InsteonBindingConstants.BROADCAST_ON_OFF + "#"
289                                                     + ((Double) o).intValue();
290                                             if (!broadcastChannels.contains(id)) {
291                                                 ChannelUID channelUID = new ChannelUID(thing.getUID(), id);
292                                                 ChannelTypeUID channelTypeUID = new ChannelTypeUID(
293                                                         InsteonBindingConstants.BINDING_ID,
294                                                         InsteonBindingConstants.SWITCH);
295                                                 Channel channel = callback
296                                                         .createChannelBuilder(channelUID, channelTypeUID).withLabel(id)
297                                                         .build();
298
299                                                 channelMap.put(id, channel);
300                                                 broadcastChannels.add(id);
301                                             }
302                                         } else {
303                                             valid = false;
304                                             break;
305                                         }
306                                     }
307                                 }
308
309                                 if (!valid) {
310                                     String msg = "The value for key " + BROADCAST_GROUPS
311                                             + " must be an array of integers in the device configuration parameter.";
312                                     logger.warn("{} {}", thing.getUID().getAsString(), msg);
313
314                                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
315                                     return;
316                                 }
317                             }
318                         } else {
319                             ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
320                             ChannelTypeUID channelTypeUID = new ChannelTypeUID(InsteonBindingConstants.BINDING_ID,
321                                     channelId);
322                             Channel channel = thing.getChannel(channelUID);
323                             if (channel == null) {
324                                 channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
325                             }
326
327                             channelMap.put(channelId, channel);
328                         }
329                     } else {
330                         logger.debug("{} is a feature group for {}. It will not be added as a channel.", feature,
331                                 productKey);
332                     }
333                 }
334             }
335
336             if (!channelMap.isEmpty() || device.isModem()) {
337                 List<Channel> channels = new ArrayList<>();
338                 StringBuilder channelList = new StringBuilder();
339                 if (!channelMap.isEmpty()) {
340                     List<String> channelIds = new ArrayList<>(channelMap.keySet());
341                     Collections.sort(channelIds);
342                     channelIds.forEach(channelId -> {
343                         Channel channel = channelMap.get(channelId);
344                         if (channel != null) {
345                             channels.add(channel);
346
347                             if (channelList.length() > 0) {
348                                 channelList.append(", ");
349                             }
350                             channelList.append(channelId);
351                         }
352                     });
353
354                     updateThing(editThing().withChannels(channels).build());
355                 }
356
357                 StringBuilder builder = new StringBuilder(thingId);
358                 builder.append(" address = ");
359                 builder.append(address);
360                 builder.append(" productKey = ");
361                 builder.append(productKey);
362                 builder.append(" channels = ");
363                 builder.append(channelList.toString());
364                 String msg = builder.toString();
365                 logger.debug("{}", msg);
366
367                 getInsteonNetworkHandler().initialized(getThing().getUID(), msg);
368
369                 channels.forEach(channel -> {
370                     if (isLinked(channel.getUID())) {
371                         channelLinked(channel.getUID());
372                     }
373                 });
374
375                 if (ThingStatus.ONLINE == bridge.getStatus()) {
376                     updateStatus(ThingStatus.ONLINE);
377                 } else {
378                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
379                 }
380             } else {
381                 String msg = "Product key '" + productKey
382                         + "' does not have any features that match existing channels.";
383
384                 logger.warn("{} {}", thing.getUID().getAsString(), msg);
385
386                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
387             }
388         });
389     }
390
391     @Override
392     public void dispose() {
393         InsteonDeviceConfiguration config = this.config;
394         if (config != null) {
395             String address = config.getAddress();
396             if (getBridge() != null && InsteonAddress.isValid(address)) {
397                 getInsteonBinding().removeDevice(new InsteonAddress(address));
398
399                 logger.debug("removed {} address = {}", getThing().getUID().getAsString(), address);
400             }
401
402             InsteonNetworkHandler handler = null;
403             try {
404                 handler = getInsteonNetworkHandler();
405             } catch (IllegalArgumentException e) {
406             }
407
408             if (handler != null) {
409                 handler.disposed(getThing().getUID());
410             }
411         }
412
413         super.dispose();
414     }
415
416     @Override
417     public void handleCommand(ChannelUID channelUID, Command command) {
418         if (ThingStatus.ONLINE.equals(getThing().getStatus())) {
419             logger.debug("channel {} was triggered with the command {}", channelUID.getAsString(), command);
420
421             getInsteonBinding().sendCommand(channelUID.getAsString(), command);
422         } else {
423             logger.debug("the command {} for channel {} was ignored because the thing is not ONLINE", command,
424                     channelUID.getAsString());
425         }
426     }
427
428     @Override
429     public void channelLinked(ChannelUID channelUID) {
430         if (getInsteonNetworkHandler().isChannelLinked(channelUID)) {
431             return;
432         }
433
434         Map<String, String> params = new HashMap<>();
435         Channel channel = getThing().getChannel(channelUID.getId());
436         if (channel == null) {
437             logger.warn("channel is null");
438             return;
439         }
440
441         Map<String, Object> channelProperties = channel.getConfiguration().getProperties();
442         for (String key : channelProperties.keySet()) {
443             Object value = channelProperties.get(key);
444             if (value instanceof String) {
445                 params.put(key, (String) value);
446             } else if (value instanceof BigDecimal) {
447                 String s = ((BigDecimal) value).toPlainString();
448                 params.put(key, s);
449             } else {
450                 logger.warn("not a string or big decimal value key '{}' value '{}' {}", key, value,
451                         value != null ? value.getClass().getName() : "unknown");
452             }
453         }
454
455         String feature = channelUID.getId().toLowerCase();
456         InsteonDeviceConfiguration config = this.config;
457         if (config == null) {
458             logger.warn("insteon device config is null");
459             return;
460         }
461         String productKey = config.getProductKey();
462         if (productKey.equals(HIDDEN_DOOR_SENSOR_PRODUCT_KEY)) {
463             if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)) {
464                 params.put(FIELD, FIELD_BATTERY_LEVEL);
465                 feature = DATA;
466             } else if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_WATERMARK_LEVEL)) {
467                 params.put(FIELD, FIELD_BATTERY_WATERMARK_LEVEL);
468                 feature = DATA;
469             }
470         } else if (productKey.equals(MOTION_SENSOR_PRODUCT_KEY)) {
471             if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)) {
472                 params.put(FIELD, FIELD_BATTERY_LEVEL);
473                 feature = DATA;
474             } else if (feature.equalsIgnoreCase(InsteonBindingConstants.LIGHT_LEVEL)) {
475                 params.put(FIELD, FIELD_LIGHT_LEVEL);
476                 feature = DATA;
477             }
478         } else if (productKey.equals(MOTION_SENSOR_II_PRODUCT_KEY)) {
479             if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)) {
480                 params.put(FIELD, FIELD_BATTERY_LEVEL);
481                 feature = DATA;
482             } else if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_PERCENT)) {
483                 params.put(FIELD, FIELD_BATTERY_PERCENTAGE);
484                 feature = DATA;
485             } else if (feature.equalsIgnoreCase(InsteonBindingConstants.LIGHT_LEVEL)) {
486                 params.put(FIELD, FIELD_LIGHT_LEVEL);
487                 feature = DATA;
488             } else if (feature.equalsIgnoreCase(InsteonBindingConstants.TEMPERATURE_LEVEL)) {
489                 params.put(FIELD, FIELD_TEMPERATURE_LEVEL);
490                 feature = DATA;
491             }
492         } else if (productKey.equals(PLM_PRODUCT_KEY)) {
493             String parts[] = feature.split("#");
494             if (parts.length == 2 && parts[0].equalsIgnoreCase(InsteonBindingConstants.BROADCAST_ON_OFF)
495                     && parts[1].matches("^\\d+$")) {
496                 params.put(GROUP, parts[1]);
497                 feature = BROADCAST_ON_OFF;
498             }
499         } else if (productKey.equals(POWER_METER_PRODUCT_KEY)) {
500             if (feature.equalsIgnoreCase(InsteonBindingConstants.KWH)) {
501                 params.put(FIELD, FIELD_KWH);
502             } else if (feature.equalsIgnoreCase(InsteonBindingConstants.WATTS)) {
503                 params.put(FIELD, FIELD_WATTS);
504             } else if (feature.equalsIgnoreCase(InsteonBindingConstants.RESET)) {
505                 params.put(CMD, CMD_RESET);
506             } else if (feature.equalsIgnoreCase(InsteonBindingConstants.UPDATE)) {
507                 params.put(CMD, CMD_UPDATE);
508             }
509
510             feature = METER;
511         }
512
513         InsteonChannelConfiguration bindingConfig = new InsteonChannelConfiguration(channelUID, feature,
514                 new InsteonAddress(config.getAddress()), productKey, params);
515         getInsteonBinding().addFeatureListener(bindingConfig);
516
517         StringBuilder builder = new StringBuilder(channelUID.getAsString());
518         builder.append(" feature = ");
519         builder.append(feature);
520         builder.append(" parameters = ");
521         builder.append(params);
522         String msg = builder.toString();
523         logger.debug("{}", msg);
524
525         getInsteonNetworkHandler().linked(channelUID, msg);
526     }
527
528     @Override
529     public void channelUnlinked(ChannelUID channelUID) {
530         getInsteonBinding().removeFeatureListener(channelUID);
531         getInsteonNetworkHandler().unlinked(channelUID);
532
533         logger.debug("channel {} unlinked ", channelUID.getAsString());
534     }
535
536     private InsteonNetworkHandler getInsteonNetworkHandler() {
537         Bridge bridge = getBridge();
538         if (bridge == null) {
539             throw new IllegalArgumentException("insteon network bridge is null");
540         }
541         InsteonNetworkHandler handler = (InsteonNetworkHandler) bridge.getHandler();
542         if (handler == null) {
543             throw new IllegalArgumentException("insteon network handler is null");
544         }
545         return handler;
546     }
547
548     private InsteonBinding getInsteonBinding() {
549         return getInsteonNetworkHandler().getInsteonBinding();
550     }
551 }