2 * Copyright (c) 2010-2020 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.command;
15 import java.text.SimpleDateFormat;
16 import java.util.Arrays;
17 import java.util.Date;
18 import java.util.HashSet;
19 import java.util.List;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.insteon.internal.InsteonBinding;
25 import org.openhab.binding.insteon.internal.device.DeviceFeature;
26 import org.openhab.binding.insteon.internal.device.InsteonAddress;
27 import org.openhab.binding.insteon.internal.device.InsteonDevice;
28 import org.openhab.binding.insteon.internal.handler.InsteonNetworkHandler;
29 import org.openhab.binding.insteon.internal.message.FieldException;
30 import org.openhab.binding.insteon.internal.message.InvalidMessageTypeException;
31 import org.openhab.binding.insteon.internal.message.Msg;
32 import org.openhab.binding.insteon.internal.message.MsgListener;
33 import org.openhab.binding.insteon.internal.utils.Utils;
34 import org.openhab.core.io.console.Console;
35 import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
36 import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
37 import org.osgi.service.component.annotations.Component;
38 import org.osgi.service.component.annotations.Reference;
39 import org.osgi.service.component.annotations.ReferenceCardinality;
40 import org.osgi.service.component.annotations.ReferencePolicy;
41 import org.osgi.service.component.annotations.ReferencePolicyOption;
45 * Console commands for the Insteon binding
47 * @author Rob Nielsen - Initial contribution
50 @Component(service = ConsoleCommandExtension.class)
51 public class InsteonCommandExtension extends AbstractConsoleCommandExtension implements MsgListener {
52 private static final String DISPLAY_DEVICES = "display_devices";
53 private static final String DISPLAY_CHANNELS = "display_channels";
54 private static final String DISPLAY_LOCAL_DATABASE = "display_local_database";
55 private static final String DISPLAY_MONITORED = "display_monitored";
56 private static final String START_MONITORING = "start_monitoring";
57 private static final String STOP_MONITORING = "stop_monitoring";
58 private static final String SEND_STANDARD_MESSAGE = "send_standard_message";
59 private static final String SEND_EXTENDED_MESSAGE = "send_extended_message";
60 private static final String SEND_EXTENDED_MESSAGE_2 = "send_extended_message_2";
62 private enum MessageType {
69 private InsteonNetworkHandler handler;
71 private Console console;
72 private boolean monitoring = false;
73 private boolean monitorAllDevices = false;
74 private Set<InsteonAddress> monitoredAddresses = new HashSet<>();
76 public InsteonCommandExtension() {
77 super("insteon", "Interact with the Insteon integration.");
81 public void execute(String[] args, Console console) {
82 if (args.length > 0) {
83 InsteonNetworkHandler handler = this.handler; // fix eclipse warnings about nullable
84 if (handler == null) {
85 console.println("No Insteon network bridge configured.");
89 if (args.length == 1) {
90 handler.displayDevices(console);
95 case DISPLAY_CHANNELS:
96 if (args.length == 1) {
97 handler.displayChannels(console);
102 case DISPLAY_LOCAL_DATABASE:
103 if (args.length == 1) {
104 handler.displayLocalDatabase(console);
109 case DISPLAY_MONITORED:
110 if (args.length == 1) {
111 displayMonitoredDevices(console);
116 case START_MONITORING:
117 if (args.length == 2) {
118 startMonitoring(console, args[1]);
123 case STOP_MONITORING:
124 if (args.length == 2) {
125 stopMonitoring(console, args[1]);
130 case SEND_STANDARD_MESSAGE:
131 if (args.length == 5) {
132 sendMessage(console, MessageType.STANDARD, args);
137 case SEND_EXTENDED_MESSAGE:
138 if (args.length >= 5 && args.length <= 18) {
139 sendMessage(console, MessageType.EXTENDED, args);
144 case SEND_EXTENDED_MESSAGE_2:
145 if (args.length >= 5 && args.length <= 17) {
146 sendMessage(console, MessageType.EXTENDED_2, args);
152 console.println("Unknown command '" + args[0] + "'");
163 public List<String> getUsages() {
164 return Arrays.asList(new String[] {
165 buildCommandUsage(DISPLAY_DEVICES, "display devices that are online, along with available channels"),
166 buildCommandUsage(DISPLAY_CHANNELS,
167 "display channels that are linked, along with configuration information"),
168 buildCommandUsage(DISPLAY_LOCAL_DATABASE, "display Insteon PLM or hub database details"),
169 buildCommandUsage(DISPLAY_MONITORED, "display monitored device(s)"),
170 buildCommandUsage(START_MONITORING + " all|address",
171 "start displaying messages received from device(s)"),
172 buildCommandUsage(STOP_MONITORING + " all|address", "stop displaying messages received from device(s)"),
173 buildCommandUsage(SEND_STANDARD_MESSAGE + " address flags cmd1 cmd2",
174 "send standard message to a device"),
175 buildCommandUsage(SEND_EXTENDED_MESSAGE + " address flags cmd1 cmd2 [up to 13 bytes]",
176 "send extended message to a device"),
177 buildCommandUsage(SEND_EXTENDED_MESSAGE_2 + " address flags cmd1 cmd2 [up to 12 bytes]",
178 "send extended message with a two byte crc to a device") });
182 public void msg(Msg msg) {
183 if (monitorAllDevices || monitoredAddresses.contains(msg.getAddr("fromAddress"))) {
184 String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
185 if (console != null) {
186 console.println(date + " " + msg.toString());
191 @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
192 public void setInsteonNetworkHandler(InsteonNetworkHandler handler) {
193 this.handler = handler;
196 public void unsetInsteonNetworkHandler(InsteonNetworkHandler handler) {
200 private void displayMonitoredDevices(Console console) {
201 if (!monitoredAddresses.isEmpty()) {
202 StringBuilder builder = new StringBuilder();
203 for (InsteonAddress insteonAddress : monitoredAddresses) {
204 if (builder.length() == 0) {
205 builder = new StringBuilder("The individual device(s) ");
207 builder.append(", ");
209 builder.append(insteonAddress);
211 console.println(builder.append(" are monitored").toString());
212 } else if (monitorAllDevices) {
213 console.println("All devices are monitored.");
215 console.println("Not mointoring any devices.");
219 private void startMonitoring(Console console, String addr) {
220 if (addr.equalsIgnoreCase("all")) {
221 if (monitorAllDevices != true) {
222 monitorAllDevices = true;
223 monitoredAddresses.clear();
224 console.println("Started monitoring all devices.");
226 console.println("Already monitoring all devices.");
230 if (monitorAllDevices) {
231 console.println("Already monitoring all devices.");
232 } else if (monitoredAddresses.add(new InsteonAddress(addr))) {
233 console.println("Started monitoring the device " + addr + ".");
235 console.println("Already monitoring the device " + addr + ".");
237 } catch (IllegalArgumentException e) {
238 console.println("Invalid device address" + addr + ".");
243 if (monitoring == false) {
244 getInsteonBinding().getDriver().addMsgListener(this);
246 this.console = console;
251 private void stopMonitoring(Console console, String addr) {
252 if (monitoring == false) {
253 console.println("Not mointoring any devices.");
257 if (addr.equalsIgnoreCase("all")) {
258 if (monitorAllDevices) {
259 monitorAllDevices = false;
260 console.println("Stopped monitoring all devices.");
262 console.println("Not monitoring all devices.");
266 if (monitorAllDevices) {
267 console.println("Not monitoring individual devices.");
268 } else if (monitoredAddresses.remove(new InsteonAddress(addr))) {
269 console.println("Stopped monitoring the device " + addr + ".");
271 console.println("Not monitoring the device " + addr + ".");
274 } catch (IllegalArgumentException e) {
275 console.println("Invalid address device address " + addr + ".");
280 if (monitorAllDevices == false && monitoredAddresses.isEmpty()) {
281 getInsteonBinding().getDriver().removeListener(this);
287 private void sendMessage(Console console, MessageType messageType, String[] args) {
288 InsteonDevice device = new InsteonDevice();
289 device.setDriver(getInsteonBinding().getDriver());
292 device.setAddress(new InsteonAddress(args[1]));
293 } catch (IllegalArgumentException e) {
294 console.println("Invalid device address" + args[1] + ".");
298 StringBuilder builder = new StringBuilder();
299 for (int i = 2; i < args.length; i++) {
300 if (!args[i].matches("\\p{XDigit}{1,2}")) {
301 if (builder.length() > 0) {
302 builder.append(", ");
304 builder.append(args[i]);
307 if (builder.length() != 0) {
308 builder.append(" is not a valid hexadecimal byte.");
309 console.print(builder.toString());
314 byte flags = (byte) Utils.fromHexString(args[2]);
315 byte cmd1 = (byte) Utils.fromHexString(args[3]);
316 byte cmd2 = (byte) Utils.fromHexString(args[4]);
318 if (messageType == MessageType.STANDARD) {
319 msg = device.makeStandardMessage(flags, cmd1, cmd2);
321 byte[] data = new byte[args.length - 5];
322 for (int i = 0; i + 5 < args.length; i++) {
323 data[i] = (byte) Utils.fromHexString(args[i + 5]);
326 if (messageType == MessageType.EXTENDED) {
327 msg = device.makeExtendedMessage(flags, cmd1, cmd2, data);
329 msg = device.makeExtendedMessageCRC2(flags, cmd1, cmd2, data);
332 device.enqueueMessage(msg, new DeviceFeature(device, "console"));
333 } catch (FieldException | InvalidMessageTypeException e) {
334 console.println("Error while trying to create message.");
338 @SuppressWarnings("null")
339 private InsteonBinding getInsteonBinding() {
340 if (handler == null) {
341 throw new IllegalArgumentException("No Insteon network bridge configured.");
345 InsteonBinding insteonBinding = handler.getInsteonBinding();
346 if (insteonBinding == null) {
347 throw new IllegalArgumentException("Insteon binding is null.");
350 return insteonBinding;