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