2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.insteon.internal.handler;
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;
23 import java.util.Objects;
25 import java.util.stream.Collectors;
26 import java.util.stream.Stream;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
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;
50 import com.google.gson.Gson;
51 import com.google.gson.JsonParseException;
52 import com.google.gson.reflect.TypeToken;
55 * The {@link InsteonDeviceHandler} is responsible for handling commands, which are
56 * sent to one of the channels.
58 * @author Rob Nielsen - Initial contribution
61 public class InsteonDeviceHandler extends BaseThingHandler {
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()));
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";
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";
125 private final Logger logger = LoggerFactory.getLogger(InsteonDeviceHandler.class);
127 private @NonNullByDefault({}) InsteonDeviceConfiguration config;
128 private boolean deviceLinked = true;
130 public InsteonDeviceHandler(Thing thing) {
135 public void initialize() {
136 config = getConfigAs(InsteonDeviceConfiguration.class);
139 scheduler.execute(() -> {
140 final Bridge bridge = getBridge();
141 if (bridge == null) {
142 String msg = "An Insteon network bridge has not been selected for this device.";
143 logger.warn("{} {}", thing.getUID().getAsString(), msg);
145 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
149 InsteonDeviceConfiguration config = this.config;
150 if (config == null) {
151 String msg = "Insteon device configuration is null.";
152 logger.warn("{} {}", thing.getUID().getAsString(), msg);
154 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
157 String address = config.getAddress();
158 if (!InsteonAddress.isValid(address)) {
159 String msg = "Unable to start Insteon device, the insteon or X10 address '" + address
160 + "' is invalid. It must be in the format 'AB.CD.EF' or 'H.U' (X10).";
161 logger.warn("{} {}", thing.getUID().getAsString(), msg);
163 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
167 DeviceTypeLoader instance = DeviceTypeLoader.instance();
168 if (instance == null) {
169 String msg = "Device type loader is null.";
170 logger.warn("{} {}", thing.getUID().getAsString(), msg);
172 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
176 String productKey = config.getProductKey();
177 if (instance.getDeviceType(productKey) == null) {
178 String msg = "Unable to start Insteon device, invalid product key '" + productKey + "'.";
179 logger.warn("{} {}", thing.getUID().getAsString(), msg);
181 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
185 String deviceConfig = config.getDeviceConfig();
186 Map<String, Object> deviceConfigMap;
187 if (deviceConfig != null) {
188 Type mapType = new TypeToken<Map<String, Object>>() {
191 deviceConfigMap = Objects.requireNonNull(new Gson().fromJson(deviceConfig, mapType));
192 } catch (JsonParseException e) {
193 String msg = "The device configuration parameter is not valid JSON.";
194 logger.warn("{} {}", thing.getUID().getAsString(), msg);
196 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
200 deviceConfigMap = Collections.emptyMap();
203 InsteonBinding insteonBinding = getInsteonBinding();
204 InsteonAddress insteonAddress = new InsteonAddress(address);
205 if (insteonBinding.getDevice(insteonAddress) != null) {
206 String msg = "A device already exists with the address '" + address + "'.";
207 logger.warn("{} {}", thing.getUID().getAsString(), msg);
209 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
213 InsteonDevice device = insteonBinding.makeNewDevice(insteonAddress, productKey, deviceConfigMap);
214 if (device == null) {
215 String msg = "Unable to create a device with the product key '" + productKey + "' with the address'"
217 logger.warn("{} {}", thing.getUID().getAsString(), msg);
219 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
223 ThingHandlerCallback callback = getCallback();
224 if (callback == null) {
225 String msg = "Unable to get thing handler callback.";
226 logger.warn("{} {}", thing.getUID().getAsString(), msg);
228 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
232 Map<String, Channel> channelMap = new HashMap<>();
233 String thingId = getThing().getUID().getAsString();
234 for (String channelId : ALL_CHANNEL_IDS) {
235 String feature = channelId.toLowerCase();
237 if (productKey.equals(HIDDEN_DOOR_SENSOR_PRODUCT_KEY)) {
238 if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)
239 || feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_WATERMARK_LEVEL)) {
242 } else if (productKey.equals(MOTION_SENSOR_PRODUCT_KEY)) {
243 if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)
244 || feature.equalsIgnoreCase(InsteonBindingConstants.LIGHT_LEVEL)) {
247 } else if (productKey.equals(MOTION_SENSOR_II_PRODUCT_KEY)) {
248 if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)
249 || feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_PERCENT)
250 || feature.equalsIgnoreCase(InsteonBindingConstants.LIGHT_LEVEL)
251 || feature.equalsIgnoreCase(InsteonBindingConstants.TEMPERATURE_LEVEL)) {
254 } else if (productKey.equals(PLM_PRODUCT_KEY)) {
255 String[] parts = feature.split("#");
256 if (parts.length == 2 && parts[0].equalsIgnoreCase(InsteonBindingConstants.BROADCAST_ON_OFF)
257 && parts[1].matches("^\\d+$")) {
258 feature = BROADCAST_ON_OFF;
260 } else if (productKey.equals(POWER_METER_PRODUCT_KEY)) {
261 if (feature.equalsIgnoreCase(InsteonBindingConstants.KWH)
262 || feature.equalsIgnoreCase(InsteonBindingConstants.RESET)
263 || feature.equalsIgnoreCase(InsteonBindingConstants.UPDATE)
264 || feature.equalsIgnoreCase(InsteonBindingConstants.WATTS)) {
269 DeviceFeature f = device.getFeature(feature);
271 if (!f.isFeatureGroup()) {
272 if (channelId.equals(InsteonBindingConstants.BROADCAST_ON_OFF)) {
273 Set<String> broadcastChannels = new HashSet<>();
274 for (Channel channel : thing.getChannels()) {
275 String id = channel.getUID().getId();
276 if (id.startsWith(InsteonBindingConstants.BROADCAST_ON_OFF)) {
277 channelMap.put(id, channel);
278 broadcastChannels.add(id);
282 Object groups = deviceConfigMap.get(BROADCAST_GROUPS);
283 if (groups != null) {
284 boolean valid = false;
285 if (groups instanceof List<?> list) {
287 for (Object o : list) {
288 if (o instanceof Double && (Double) o % 1 == 0) {
289 String id = InsteonBindingConstants.BROADCAST_ON_OFF + "#"
290 + ((Double) o).intValue();
291 if (!broadcastChannels.contains(id)) {
292 ChannelUID channelUID = new ChannelUID(thing.getUID(), id);
293 ChannelTypeUID channelTypeUID = new ChannelTypeUID(
294 InsteonBindingConstants.BINDING_ID,
295 InsteonBindingConstants.SWITCH);
296 Channel channel = callback
297 .createChannelBuilder(channelUID, channelTypeUID).withLabel(id)
300 channelMap.put(id, channel);
301 broadcastChannels.add(id);
311 String msg = "The value for key " + BROADCAST_GROUPS
312 + " must be an array of integers in the device configuration parameter.";
313 logger.warn("{} {}", thing.getUID().getAsString(), msg);
315 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
320 ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
321 ChannelTypeUID channelTypeUID = new ChannelTypeUID(InsteonBindingConstants.BINDING_ID,
323 Channel channel = thing.getChannel(channelUID);
324 if (channel == null) {
325 channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
328 channelMap.put(channelId, channel);
331 logger.debug("{} is a feature group for {}. It will not be added as a channel.", feature,
337 if (!channelMap.isEmpty() || device.isModem()) {
338 List<Channel> channels = new ArrayList<>();
339 StringBuilder channelList = new StringBuilder();
340 if (!channelMap.isEmpty()) {
341 List<String> channelIds = new ArrayList<>(channelMap.keySet());
342 Collections.sort(channelIds);
343 channelIds.forEach(channelId -> {
344 Channel channel = channelMap.get(channelId);
345 if (channel != null) {
346 channels.add(channel);
348 if (channelList.length() > 0) {
349 channelList.append(", ");
351 channelList.append(channelId);
355 updateThing(editThing().withChannels(channels).build());
358 StringBuilder builder = new StringBuilder(thingId);
359 builder.append(" address = ");
360 builder.append(address);
361 builder.append(" productKey = ");
362 builder.append(productKey);
363 builder.append(" channels = ");
364 builder.append(channelList.toString());
365 String msg = builder.toString();
366 logger.debug("{}", msg);
368 getInsteonNetworkHandler().initialized(getThing().getUID(), msg);
370 channels.forEach(channel -> {
371 if (isLinked(channel.getUID())) {
372 channelLinked(channel.getUID());
376 if (ThingStatus.ONLINE == bridge.getStatus()) {
378 updateStatus(ThingStatus.ONLINE);
381 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
384 String msg = "Product key '" + productKey
385 + "' does not have any features that match existing channels.";
387 logger.warn("{} {}", thing.getUID().getAsString(), msg);
389 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
395 public void dispose() {
396 InsteonDeviceConfiguration config = this.config;
397 if (config != null) {
398 String address = config.getAddress();
399 if (getBridge() != null && InsteonAddress.isValid(address)) {
400 getInsteonBinding().removeDevice(new InsteonAddress(address));
402 logger.debug("removed {} address = {}", getThing().getUID().getAsString(), address);
405 InsteonNetworkHandler handler = null;
407 handler = getInsteonNetworkHandler();
408 } catch (IllegalArgumentException e) {
411 if (handler != null) {
412 handler.disposed(getThing().getUID());
420 public void handleCommand(ChannelUID channelUID, Command command) {
421 if (ThingStatus.ONLINE == getThing().getStatus()) {
422 logger.debug("channel {} was triggered with the command {}", channelUID.getAsString(), command);
424 getInsteonBinding().sendCommand(channelUID.getAsString(), command);
426 logger.debug("the command {} for channel {} was ignored because the thing is not ONLINE", command,
427 channelUID.getAsString());
432 public void channelLinked(ChannelUID channelUID) {
433 if (getInsteonNetworkHandler().isChannelLinked(channelUID)) {
437 Map<String, String> params = new HashMap<>();
438 Channel channel = getThing().getChannel(channelUID.getId());
439 if (channel == null) {
440 logger.warn("channel is null");
444 Map<String, Object> channelProperties = channel.getConfiguration().getProperties();
445 for (String key : channelProperties.keySet()) {
446 Object value = channelProperties.get(key);
447 if (value instanceof String stringValue) {
448 params.put(key, stringValue);
449 } else if (value instanceof BigDecimal decimalValue) {
450 String s = decimalValue.toPlainString();
453 logger.warn("not a string or big decimal value key '{}' value '{}' {}", key, value,
454 value != null ? value.getClass().getName() : "unknown");
458 String feature = channelUID.getId().toLowerCase();
459 InsteonDeviceConfiguration config = this.config;
460 if (config == null) {
461 logger.warn("insteon device config is null");
464 String productKey = config.getProductKey();
465 if (productKey.equals(HIDDEN_DOOR_SENSOR_PRODUCT_KEY)) {
466 if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)) {
467 params.put(FIELD, FIELD_BATTERY_LEVEL);
469 } else if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_WATERMARK_LEVEL)) {
470 params.put(FIELD, FIELD_BATTERY_WATERMARK_LEVEL);
473 } else if (productKey.equals(MOTION_SENSOR_PRODUCT_KEY)) {
474 if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)) {
475 params.put(FIELD, FIELD_BATTERY_LEVEL);
477 } else if (feature.equalsIgnoreCase(InsteonBindingConstants.LIGHT_LEVEL)) {
478 params.put(FIELD, FIELD_LIGHT_LEVEL);
481 } else if (productKey.equals(MOTION_SENSOR_II_PRODUCT_KEY)) {
482 if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_LEVEL)) {
483 params.put(FIELD, FIELD_BATTERY_LEVEL);
485 } else if (feature.equalsIgnoreCase(InsteonBindingConstants.BATTERY_PERCENT)) {
486 params.put(FIELD, FIELD_BATTERY_PERCENTAGE);
488 } else if (feature.equalsIgnoreCase(InsteonBindingConstants.LIGHT_LEVEL)) {
489 params.put(FIELD, FIELD_LIGHT_LEVEL);
491 } else if (feature.equalsIgnoreCase(InsteonBindingConstants.TEMPERATURE_LEVEL)) {
492 params.put(FIELD, FIELD_TEMPERATURE_LEVEL);
495 } else if (productKey.equals(PLM_PRODUCT_KEY)) {
496 String[] parts = feature.split("#");
497 if (parts.length == 2 && parts[0].equalsIgnoreCase(InsteonBindingConstants.BROADCAST_ON_OFF)
498 && parts[1].matches("^\\d+$")) {
499 params.put(GROUP, parts[1]);
500 feature = BROADCAST_ON_OFF;
502 } else if (productKey.equals(POWER_METER_PRODUCT_KEY)) {
503 if (feature.equalsIgnoreCase(InsteonBindingConstants.KWH)) {
504 params.put(FIELD, FIELD_KWH);
505 } else if (feature.equalsIgnoreCase(InsteonBindingConstants.WATTS)) {
506 params.put(FIELD, FIELD_WATTS);
507 } else if (feature.equalsIgnoreCase(InsteonBindingConstants.RESET)) {
508 params.put(CMD, CMD_RESET);
509 } else if (feature.equalsIgnoreCase(InsteonBindingConstants.UPDATE)) {
510 params.put(CMD, CMD_UPDATE);
516 InsteonChannelConfiguration bindingConfig = new InsteonChannelConfiguration(channelUID, feature,
517 new InsteonAddress(config.getAddress()), productKey, params);
518 getInsteonBinding().addFeatureListener(bindingConfig);
520 StringBuilder builder = new StringBuilder(channelUID.getAsString());
521 builder.append(" feature = ");
522 builder.append(feature);
523 builder.append(" parameters = ");
524 builder.append(params);
525 String msg = builder.toString();
526 logger.debug("{}", msg);
528 getInsteonNetworkHandler().linked(channelUID, msg);
532 public void channelUnlinked(ChannelUID channelUID) {
533 getInsteonBinding().removeFeatureListener(channelUID);
534 getInsteonNetworkHandler().unlinked(channelUID);
536 logger.debug("channel {} unlinked ", channelUID.getAsString());
539 public InsteonAddress getInsteonAddress() {
540 return new InsteonAddress(config.getAddress());
543 public void deviceNotLinked() {
544 String msg = "device with the address '" + config.getAddress()
545 + "' was not found in the modem database. Did you forget to link?";
546 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
548 deviceLinked = false;
551 private InsteonNetworkHandler getInsteonNetworkHandler() {
552 Bridge bridge = getBridge();
553 if (bridge == null) {
554 throw new IllegalArgumentException("insteon network bridge is null");
556 InsteonNetworkHandler handler = (InsteonNetworkHandler) bridge.getHandler();
557 if (handler == null) {
558 throw new IllegalArgumentException("insteon network handler is null");
563 private InsteonBinding getInsteonBinding() {
564 return getInsteonNetworkHandler().getInsteonBinding();