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