tuwien.auto.calimero.tools.ProgMode Maven / Gradle / Ivy
Show all versions of calimero-tools Show documentation
/*
Calimero 2 - A library for KNX network access
Copyright (c) 2015, 2018 B. Malinowsky
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under terms
of your choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module. An
independent module is a module which is not derived from or based on
this library. If you modify this library, you may extend this exception
to your version of the library, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from your
version.
*/
package tuwien.auto.calimero.tools;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TreeSet;
import java.util.stream.Collectors;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.KNXFormatException;
import tuwien.auto.calimero.KNXIllegalArgumentException;
import tuwien.auto.calimero.Settings;
import tuwien.auto.calimero.knxnetip.KNXnetIPConnection;
import tuwien.auto.calimero.link.KNXNetworkLink;
import tuwien.auto.calimero.link.KNXNetworkLinkFT12;
import tuwien.auto.calimero.link.KNXNetworkLinkIP;
import tuwien.auto.calimero.link.KNXNetworkLinkTpuart;
import tuwien.auto.calimero.link.KNXNetworkLinkUsb;
import tuwien.auto.calimero.link.medium.KNXMediumSettings;
import tuwien.auto.calimero.link.medium.TPSettings;
import tuwien.auto.calimero.mgmt.ManagementProcedures;
import tuwien.auto.calimero.mgmt.ManagementProceduresImpl;
/**
* ProgMode lists the current KNX devices in programming mode, and allows to set the programming mode of a device. The
* tool supports network access using KNXnet/IP, KNX IP, USB, FT1.2, and TP-UART.
*
* Run the tool by using either {@link ProgMode#main(String[])} or {@link Runnable#run()}.
*/
public class ProgMode implements Runnable
{
private static final String tool = "ProgMode";
private static final String sep = System.getProperty("line.separator");
/** Contains tool options after parsing command line. */
private final Map options = new HashMap<>();
/**
* Creates a new ProgMode object using the supplied arguments.
*
* @param args options for the tool, see {@link #main(String[])}
* @throws KNXIllegalArgumentException on missing or wrong formatted option value
*/
public ProgMode(final String[] args)
{
try {
parseOptions(args);
}
catch (final KNXIllegalArgumentException e) {
throw e;
}
catch (KNXFormatException | RuntimeException e) {
throw new KNXIllegalArgumentException(e.getMessage(), e);
}
}
/**
* Entry point for running the ProgMode tool from the command line.
*
* A communication device, host, or port identifier has to be supplied to specify the endpoint for KNX network
* access.
* To show the usage message of this tool, use the command line option --help (or -h).
* Command line options are treated case sensitive. Available options are:
*
* --help -h
show help message
* --version
show tool/library version and exit
* --localhost
id local IP/host name
* --localport
number local UDP port (default system assigned)
* --port -p
number UDP port on host (default 3671)
* --nat -n
enable Network Address Translation
* --ft12 -f
use FT1.2 serial communication
* --usb -u
use KNX USB communication
* --tpuart
use TP-UART communication
* --medium -m
id KNX medium [tp1|p110|rf|knxip] (defaults to tp1)
* --domain
address domain address on open KNX medium (PL or RF)
* --knx-address -k
KNX address KNX device address of local endpoint
*
* The --knx-address
option is only necessary if an access protocol is selected that directly
* communicates with the KNX network, i.e., KNX IP or TP-UART. The selected KNX individual address shall be unique
* in a network, and the subnetwork address (area and line) should be set to match the network configuration.
*
* Supported commands:
*
* on
device switch programming mode on
* off
device switch programming mode off
*
*
* @param args command line arguments for the tool
*/
public static void main(final String[] args)
{
try {
new ProgMode(args).run();
}
catch (final Throwable t) {
out("error parsing arguments (use --help): " + t);
}
}
@Override
public void run()
{
if (options.containsKey("about")) {
((Runnable) options.get("about")).run();
return;
}
Exception thrown = null;
boolean canceled = false;
try (KNXNetworkLink link = createLink(); ManagementProcedures mgmt = new ManagementProceduresImpl(link)) {
final String cmd = (String) options.get("command");
if ("status".equals(cmd)) {
System.out.print("Device(s) in programming mode ...");
System.out.flush();
while (true)
devicesInProgMode(mgmt.readAddress());
}
else
mgmt.setProgrammingMode((IndividualAddress) options.get("device"), "on".equals(cmd));
}
catch (KNXException | RuntimeException e) {
thrown = e;
}
catch (final InterruptedException e) {
canceled = true;
Thread.currentThread().interrupt();
}
finally {
onCompletion(thrown, canceled);
}
}
protected void devicesInProgMode(final IndividualAddress... devices)
{
final String output = devices.length == 0 ? "none"
: new TreeSet<>(Arrays.asList(devices)).stream().map(Objects::toString).collect(Collectors.joining(", "));
System.out.print("\33[2K\rDevice(s) in programming mode: " + output);
System.out.flush();
}
/**
* Called by this tool on completion.
*
* @param thrown the thrown exception if operation completed due to a raised exception, null
otherwise
* @param canceled whether the operation got canceled before its planned end
*/
protected void onCompletion(final Exception thrown, final boolean canceled)
{
if (canceled)
out(tool + " got canceled");
if (thrown != null)
out(tool + " error", thrown);
}
private KNXNetworkLink createLink() throws KNXException, InterruptedException
{
final String host = (String) options.get("host");
final KNXMediumSettings medium = (KNXMediumSettings) options.get("medium");
if (options.containsKey("ft12")) {
// create FT1.2 network link
try {
return new KNXNetworkLinkFT12(Integer.parseInt(host), medium);
}
catch (final NumberFormatException e) {
return new KNXNetworkLinkFT12(host, medium);
}
}
if (options.containsKey("usb")) {
// create KNX USB HID network link
return new KNXNetworkLinkUsb(host, medium);
}
if (options.containsKey("tpuart")) {
// create TP-UART link
final IndividualAddress device = (IndividualAddress) options.get("knx-address");
medium.setDeviceAddress(device);
return new KNXNetworkLinkTpuart(host, medium, Collections.emptyList());
}
// create IP link
final InetSocketAddress local = Main.createLocalSocket((InetAddress) options.get("localhost"),
(Integer) options.get("localport"));
final InetAddress addr = Main.parseHost(host);
if (addr.isMulticastAddress())
return KNXNetworkLinkIP.newRoutingLink(local.getAddress(), addr, medium);
final InetSocketAddress remote = new InetSocketAddress(addr, (Integer) options.get("port"));
return KNXNetworkLinkIP.newTunnelingLink(local, remote, options.containsKey("nat"), medium);
}
private void parseOptions(final String[] args) throws KNXFormatException
{
if (args.length == 0) {
options.put("about", (Runnable) ProgMode::showToolInfo);
return;
}
// add defaults
options.put("port", KNXnetIPConnection.DEFAULT_PORT);
options.put("medium", TPSettings.TP1);
// default subnetwork address for TP1 and unregistered device
options.put("knx-address", new IndividualAddress(0, 0x02, 0xff));
options.put("command", "status");
boolean setmode = false;
int i = 0;
for (; i < args.length; i++) {
final String arg = args[i];
if (Main.isOption(arg, "help", "h")) {
options.put("about", (Runnable) ProgMode::showUsage);
return;
}
if (Main.isOption(arg, "version", null)) {
options.put("about", (Runnable) ProgMode::showVersion);
return;
}
else if (Main.isOption(arg, "verbose", "v"))
options.put("verbose", null);
else if (Main.isOption(arg, "localhost", null))
options.put("localhost", Main.parseHost(args[++i]));
else if (Main.isOption(arg, "localport", null))
options.put("localport", Integer.decode(args[++i]));
else if (Main.isOption(arg, "port", "p"))
options.put("port", Integer.decode(args[++i]));
else if (Main.isOption(arg, "nat", "n"))
options.put("nat", null);
else if (Main.isOption(arg, "ft12", "f"))
options.put("ft12", null);
else if (Main.isOption(arg, "usb", "u"))
options.put("usb", null);
else if (Main.isOption(arg, "tpuart", null))
options.put("tpuart", null);
else if (Main.isOption(arg, "medium", "m"))
options.put("medium", Main.getMedium(args[++i]));
else if (Main.isOption(arg, "domain", null))
options.put("domain", Long.decode(args[++i]));
else if (Main.isOption(arg, "knx-address", "k"))
options.put("knx-address", Main.getAddress(args[++i]));
else if (arg.equals("on") || arg.equals("off")) {
options.put("command", arg);
setmode = true;
}
else if (setmode)
options.put("device", new IndividualAddress(arg));
else if (!options.containsKey("host"))
options.put("host", arg);
else
options.put("device", new IndividualAddress(arg));
}
if (!options.containsKey("host"))
throw new KNXIllegalArgumentException("no communication device/host specified");
if (options.containsKey("ft12") && !options.containsKey("remote"))
throw new KNXIllegalArgumentException("--remote option is mandatory with --ft12");
if (setmode != options.containsKey("device"))
throw new KNXIllegalArgumentException("setting programming mode requires mode and KNX device address");
Main.setDomainAddress(options);
}
private static void showToolInfo()
{
out(tool + " - Check/set device(s) in programming mode");
showVersion();
out("Use --help for help message");
}
private static void showUsage()
{
final StringBuilder sb = new StringBuilder();
sb.append("Usage: ").append(tool).append(" [options] [on|off ]").append(sep);
sb.append("Options:").append(sep);
sb.append(" --help -h show this help message").append(sep);
sb.append(" --version show tool/library version and exit").append(sep);
sb.append(" --localhost local IP/host name").append(sep);
sb.append(" --localport local UDP port (default system assigned)").append(sep);
sb.append(" --port -p UDP port on (default ").append(KNXnetIPConnection.DEFAULT_PORT)
.append(")").append(sep);
sb.append(" --nat -n enable Network Address Translation").append(sep);
sb.append(" --ft12 -f use FT1.2 serial communication").append(sep);
sb.append(" --usb -u use KNX USB communication").append(sep);
sb.append(" --tpuart use TP-UART communication").append(sep);
sb.append(" --medium -m KNX medium [tp1|p110|rf|knxip] (default tp1)").append(sep);
sb.append(" --domain domain address on KNX PL/RF medium (defaults to broadcast domain)")
.append(sep);
sb.append("Commands:").append(sep);
sb.append(" on switch programming mode on").append(sep);
sb.append(" off switch programming mode off").append(sep);
out(sb);
}
private static void showVersion()
{
out(Settings.getLibraryHeader(false));
}
private static void out(final CharSequence s, final Throwable... t)
{
if (t.length > 0 && t[0] != null) {
System.out.print(s + ": ");
t[0].printStackTrace();
}
else
System.out.println(s);
}
}