knxnetjs
Version:
A TypeScript library for KNXnet/IP communication
640 lines (632 loc) • 25 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("./index");
const constants_1 = require("./constants");
function parseArgs() {
const args = process.argv.slice(2);
const options = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case "-h":
case "--help":
options.help = true;
break;
case "-a":
case "--address":
if (i + 1 < args.length) {
const addr = args[++i];
if (addr) {
options.multicastAddress = addr;
}
}
break;
case "-p":
case "--port":
if (i + 1 < args.length) {
const portStr = args[++i];
if (portStr) {
const port = parseInt(portStr, 10);
if (!isNaN(port)) {
options.port = port;
}
}
}
break;
case "-t":
case "--tunnel":
if (i + 1 < args.length) {
const tunnelAddr = args[++i];
if (tunnelAddr) {
options.tunnel = tunnelAddr;
}
}
break;
case "--timeout":
if (i + 1 < args.length) {
const timeoutStr = args[++i];
if (timeoutStr) {
const timeout = parseInt(timeoutStr, 10);
if (!isNaN(timeout)) {
options.timeout = timeout;
}
}
}
break;
case "--busmonitor":
options.busmonitor = true;
break;
case "-u":
case "--usb":
options.usb = true;
break;
case "--usb-device":
if (i + 1 < args.length) {
const usbDevice = args[++i];
if (usbDevice) {
options.usbDevice = usbDevice;
}
}
break;
case "--interface-object":
if (i + 1 < args.length) {
const interfaceObjectStr = args[++i];
if (interfaceObjectStr) {
const interfaceObject = parseInt(interfaceObjectStr, 16);
if (!isNaN(interfaceObject)) {
options.interfaceObject = interfaceObject;
}
}
}
break;
case "--object-instance":
if (i + 1 < args.length) {
const objectInstanceStr = args[++i];
if (objectInstanceStr) {
const objectInstance = parseInt(objectInstanceStr, 10);
if (!isNaN(objectInstance)) {
options.objectInstance = objectInstance;
}
}
}
break;
case "--property-id":
if (i + 1 < args.length) {
const propertyIdStr = args[++i];
if (propertyIdStr) {
const propertyId = parseInt(propertyIdStr, 10);
if (!isNaN(propertyId)) {
options.propertyId = propertyId;
}
}
}
break;
case "--elements":
if (i + 1 < args.length) {
const elementsStr = args[++i];
if (elementsStr) {
const elements = parseInt(elementsStr, 10);
if (!isNaN(elements)) {
options.numberOfElements = elements;
}
}
}
break;
case "--start-index":
if (i + 1 < args.length) {
const startIndexStr = args[++i];
if (startIndexStr) {
const startIndex = parseInt(startIndexStr, 10);
if (!isNaN(startIndex)) {
options.startIndex = startIndex;
}
}
}
break;
case "--data":
if (i + 1 < args.length) {
const data = args[++i];
if (data) {
options.data = data;
}
}
break;
case "dump":
case "discover":
case "writeProperty":
case "readProperty":
break;
default:
if (arg && arg.startsWith("-")) {
console.error(`Unknown option: ${arg}`);
process.exit(1);
}
break;
}
}
return options;
}
function printHelp() {
console.log(`
knxnetjs - KNXnet/IP CLI Tool
Usage: knxnetjs <command> [options]
Commands:
dump Connect to KNX network and dump all received frames
discover Discover KNXnet/IP endpoints on the network
writeProperty Write a property to a KNX interface object
readProperty Read a property from a KNX interface object
Options:
-a, --address <addr> Multicast address for routing (default: 224.0.23.12)
-p, --port <port> Port number (default: 3671)
-t, --tunnel <addr> Use tunneling to specified server address instead of routing
-u, --usb Use USB KNX interface instead of network
--usb-device <path> Specify USB device path (auto-detect if not provided)
--busmonitor Enable busmonitor mode (read-only)
--timeout <ms> Discovery timeout in milliseconds (default: 3000)
-h, --help Show this help message
Property Write Options:
--interface-object <hex> Interface object type (hex format, e.g., 0x0008)
--object-instance <num> Object instance (default: 1)
--property-id <id> Property ID (e.g., 52 for PID_COMM_MODE)
--elements <count> Number of elements (default: 1)
--start-index <idx> Start index (default: 1)
--data <hex> Data to write in hex format (e.g., 00 or 00FF)
Examples:
# Frame dumping
knxnetjs dump # Dump via routing (multicast)
knxnetjs dump -a 224.0.23.12 -p 3671 # Custom routing address and port
knxnetjs dump -t 192.168.1.100 # Dump via tunneling to server
knxnetjs dump -t 192.168.1.100:3672 # Tunneling with custom port
knxnetjs dump -t 192.168.1.100 --busmonitor # Busmonitor mode (read-only)
knxnetjs dump -u # Dump via USB interface
knxnetjs dump -u --usb-device /dev/hidraw0 # Dump via specific USB device
knxnetjs dump -u --busmonitor # USB busmonitor mode (read-only)
# Discovery
knxnetjs discover # Discover KNX devices
knxnetjs discover --timeout 5000 # Discover with 5 second timeout
# Property writing
knxnetjs writeProperty -u --interface-object 0x0008 --property-id 52 --data 00
knxnetjs writeProperty -t 192.168.1.100 --interface-object 0x0008 --object-instance 1 --property-id 52 --data 01
# Property reading
knxnetjs readProperty -u --interface-object 0x0008 --property-id 52
knxnetjs readProperty -t 192.168.1.100 --interface-object 0x0008 --object-instance 1 --property-id 52 --elements 2
`);
}
async function startFrameDump(options) {
let connection;
if (options.usb) {
// USB KNX interface connection
const usbOptions = {
...(options.usbDevice && { devicePath: options.usbDevice }),
...(options.busmonitor !== undefined && {
busmonitorMode: options.busmonitor,
}),
};
if (options.busmonitor) {
console.log("Starting USB KNX busmonitor connection...");
console.log(`Device: ${options.usbDevice || "Auto-detect"}`);
console.log(`Mode: Busmonitor (read-only)`);
console.log("Press Ctrl+C to stop\n");
connection = (0, index_1.createUSBBusmonitor)(usbOptions);
}
else {
console.log("Starting USB KNX connection...");
console.log(`Device: ${options.usbDevice || "Auto-detect"}`);
console.log("Press Ctrl+C to stop\n");
connection = (0, index_1.createUSB)(usbOptions);
}
}
else if (options.tunnel) {
// Parse tunnel address (support both "ip" and "ip:port" formats)
const tunnelParts = options.tunnel.split(":");
const serverAddress = tunnelParts[0];
if (!serverAddress) {
throw new Error("Invalid tunnel address format");
}
const serverPort = tunnelParts[1]
? parseInt(tunnelParts[1], 10)
: undefined;
if (options.busmonitor) {
console.log("Starting KNXnet/IP busmonitor connection...");
console.log(`Server Address: ${serverAddress}`);
console.log(`Server Port: ${serverPort || 3671}`);
console.log(`Mode: Busmonitor (read-only)`);
console.log("Press Ctrl+C to stop\n");
connection = (0, index_1.createBusmonitor)(serverAddress, serverPort);
}
else {
console.log("Starting KNXnet/IP tunneling connection...");
console.log(`Server Address: ${serverAddress}`);
console.log(`Server Port: ${serverPort || 3671}`);
console.log("Press Ctrl+C to stop\n");
connection = (0, index_1.createTunneling)(serverAddress, serverPort);
}
}
else {
if (options.busmonitor) {
console.error("Error: --busmonitor option requires tunneling mode (-t/--tunnel) or USB mode (-u/--usb)");
console.error("Busmonitor mode is only available with tunneling or USB connections");
process.exit(1);
}
console.log("Starting KNXnet/IP routing connection...");
console.log(`Multicast Address: ${options.multicastAddress || "224.0.23.12"}`);
console.log(`Port: ${options.port || 3671}`);
console.log("Press Ctrl+C to stop\n");
connection = (0, index_1.createRouting)(options.multicastAddress, options.port);
}
connection.on("recv", (frame) => {
console.log(frame.toFormattedString());
});
connection.on("error", (error) => {
console.error("Connection error:", error.message);
});
if (options.usb) {
connection.on("reset", () => {
console.log("Interface reset");
});
}
process.on("SIGINT", async () => {
console.log("\nShutting down...");
await connection.close();
process.exit(0);
});
try {
await connection.open();
if (options.usb) {
if (options.busmonitor) {
console.log("USB KNX busmonitor connection established! Monitoring KNX traffic...\n");
}
else {
console.log("USB KNX connection established! Listening for KNX frames...\n");
}
}
else if (options.tunnel) {
if (options.busmonitor) {
console.log("Busmonitor connection established! Monitoring KNX traffic...\n");
}
else {
console.log("Tunneling connection established! Listening for KNX frames...\n");
}
}
else {
console.log("Routing connection established! Listening for KNX frames...\n");
}
}
catch (error) {
console.error("Failed to connect:", error.message);
process.exit(1);
}
}
function formatCapabilities(capabilities) {
const caps = [];
if (capabilities & constants_1.KNX_CONSTANTS.DEVICE_CAPABILITIES.DEVICE_MANAGEMENT) {
caps.push("Device Management");
}
if (capabilities & constants_1.KNX_CONSTANTS.DEVICE_CAPABILITIES.TUNNELLING) {
caps.push("Tunnelling");
}
if (capabilities & constants_1.KNX_CONSTANTS.DEVICE_CAPABILITIES.ROUTING) {
caps.push("Routing");
}
if (capabilities & constants_1.KNX_CONSTANTS.DEVICE_CAPABILITIES.REMOTE_LOGGING) {
caps.push("Remote Logging");
}
if (capabilities & constants_1.KNX_CONSTANTS.DEVICE_CAPABILITIES.REMOTE_CONFIGURATION) {
caps.push("Remote Config");
}
if (capabilities & constants_1.KNX_CONSTANTS.DEVICE_CAPABILITIES.OBJECT_SERVER) {
caps.push("Object Server");
}
return caps.length > 0 ? caps.join(", ") : "None";
}
function formatInterfaceResult(interfaceInfo) {
let output = `\n┌─ ${interfaceInfo.name}\n`;
output += `├─ Type: ${interfaceInfo.type.toUpperCase()}\n`;
if (interfaceInfo.description) {
output += `├─ Description: ${interfaceInfo.description}\n`;
}
// Network interface details
let protocol;
if (interfaceInfo.protocol) {
switch (interfaceInfo.protocol) {
case 1:
protocol = "UDP";
break;
case 2:
protocol = "TCP";
break;
}
}
if (interfaceInfo.address) {
output += `├─ Address: ${interfaceInfo.address}:${interfaceInfo.port || 3671}${protocol ? " (" + protocol + ")" : ""}\n`;
}
if (interfaceInfo.capabilities !== undefined) {
output += `├─ Capabilities: ${formatCapabilities(interfaceInfo.capabilities)}\n`;
}
if (interfaceInfo.knxAddress) {
output += `├─ KNX Address: ${interfaceInfo.knxAddress}\n`;
}
if (interfaceInfo.macAddress) {
output += `├─ MAC Address: ${interfaceInfo.macAddress}\n`;
}
if (interfaceInfo.serialNumber) {
output += `├─ Serial Number: ${interfaceInfo.serialNumber}\n`;
}
if (interfaceInfo.friendlyName &&
interfaceInfo.friendlyName !== interfaceInfo.name) {
output += `├─ Friendly Name: ${interfaceInfo.friendlyName}\n`;
}
// USB interface details
if (interfaceInfo.devicePath) {
output += `├─ Device Path: ${interfaceInfo.devicePath}\n`;
}
if (interfaceInfo.vendorId !== undefined &&
interfaceInfo.productId !== undefined) {
output += `├─ USB ID: ${interfaceInfo.vendorId
.toString(16)
.padStart(4, "0")}:${interfaceInfo.productId
.toString(16)
.padStart(4, "0")}\n`;
}
if (interfaceInfo.manufacturer) {
output += `├─ Manufacturer: ${interfaceInfo.manufacturer}\n`;
}
if (interfaceInfo.product) {
output += `├─ Product: ${interfaceInfo.product}\n`;
}
// Show supported features
const features = [];
if (interfaceInfo.supportsRouting())
features.push("Routing");
if (interfaceInfo.supportsTunneling())
features.push("Tunneling");
if (interfaceInfo.supportsBusmonitor())
features.push("Busmonitor");
if (features.length > 0) {
output += `├─ Supported: ${features.join(", ")}\n`;
}
output += `└─ Status: Available`;
return output;
}
async function startDiscovery(options) {
console.log("Starting KNX interface discovery...");
console.log(`Timeout: ${options.timeout || constants_1.KNX_CONSTANTS.DISCOVERY.DEFAULT_SEARCH_TIMEOUT}ms\n`);
let interfaceCount = 0;
try {
await (0, index_1.discoverInterfaces)((interfaceInfo) => {
interfaceCount++;
console.log(formatInterfaceResult(interfaceInfo));
}, {
timeout: options.timeout || constants_1.KNX_CONSTANTS.DISCOVERY.DEFAULT_SEARCH_TIMEOUT,
includeUSB: true,
});
console.log(`\n┌────────────────────────────────────┐`);
console.log(`│ Discovery completed │`);
console.log(`│ Found ${interfaceCount
.toString()
.padStart(2, " ")} interface(s) │`);
console.log(`└────────────────────────────────────┘\n`);
if (interfaceCount === 0) {
console.log("No KNX interfaces found.");
console.log("Make sure:");
console.log("- KNX devices are connected and powered on");
console.log("- Your network allows UDP multicast traffic");
console.log("- USB KNX interfaces are properly connected");
console.log("- Devices are on the same network segment");
}
}
catch (error) {
console.error("Failed to discover interfaces:", error.message);
process.exit(1);
}
}
async function writeProperty(options) {
// Validate required options
if (options.interfaceObject === undefined) {
console.error("Error: --interface-object is required");
process.exit(1);
}
if (options.propertyId === undefined) {
console.error("Error: --property-id is required");
process.exit(1);
}
if (!options.data) {
console.error("Error: --data is required");
process.exit(1);
}
// Set defaults
const objectInstance = options.objectInstance ?? 1;
const numberOfElements = options.numberOfElements ?? 1;
const startIndex = options.startIndex ?? 1;
// Parse hex data
let dataBuffer;
try {
const hexData = options.data.replace(/\s/g, ""); // Remove spaces
dataBuffer = Buffer.from(hexData, "hex");
}
catch (error) {
console.error("Error: Invalid hex data format");
process.exit(1);
}
console.log("Property Write Operation");
console.log(`Interface Object: 0x${options.interfaceObject
.toString(16)
.padStart(4, "0")
.toUpperCase()}`);
console.log(`Object Instance: ${objectInstance}`);
console.log(`Property ID: ${options.propertyId}`);
console.log(`Number of Elements: ${numberOfElements}`);
console.log(`Start Index: ${startIndex}`);
console.log(`Data: ${dataBuffer.toString("hex").toUpperCase()}`);
let connection;
try {
if (options.usb) {
// USB KNX interface connection
const usbOptions = {
...(options.usbDevice && { devicePath: options.usbDevice }),
};
console.log("\nConnecting to USB KNX interface...");
connection = (0, index_1.createUSB)(usbOptions);
}
else if (options.tunnel) {
// Parse tunnel address
const tunnelParts = options.tunnel.split(":");
const serverAddress = tunnelParts[0];
if (!serverAddress) {
throw new Error("Invalid tunnel address format");
}
const serverPort = tunnelParts[1]
? parseInt(tunnelParts[1], 10)
: undefined;
console.log(`\nConnecting to tunneling server ${serverAddress}:${serverPort || 3671}...`);
connection = (0, index_1.createManagement)(serverAddress, serverPort);
}
else {
console.error("Error: Property write requires either USB (-u) or tunneling (-t) connection");
console.error("Routing connections do not support property write operations");
process.exit(1);
}
// Set up error handling
connection.on("error", (error) => {
console.error("Connection error:", error.message);
process.exit(1);
});
// Open connection
await connection.open();
console.log("Connected successfully!");
// Write property
console.log("\nWriting property...");
await connection.writeProperty(options.interfaceObject, objectInstance, options.propertyId, numberOfElements, startIndex, dataBuffer);
console.log("Property write completed successfully!");
}
catch (error) {
console.error("Failed to write property:", error.message);
}
finally {
// Always close connection
if (connection) {
try {
await connection.close();
console.log("Connection closed.");
}
catch (closeError) {
console.error("Error closing connection:", closeError.message);
}
}
}
}
async function readProperty(options) {
// Validate required options
if (options.interfaceObject === undefined) {
console.error("Error: --interface-object is required");
process.exit(1);
}
if (options.propertyId === undefined) {
console.error("Error: --property-id is required");
process.exit(1);
}
// Set defaults
const objectInstance = options.objectInstance ?? 1;
const numberOfElements = options.numberOfElements ?? 1;
const startIndex = options.startIndex ?? 1;
console.log("Property Read Operation");
console.log(`Interface Object: 0x${options.interfaceObject
.toString(16)
.padStart(4, "0")
.toUpperCase()}`);
console.log(`Object Instance: ${objectInstance}`);
console.log(`Property ID: ${options.propertyId}`);
console.log(`Number of Elements: ${numberOfElements}`);
console.log(`Start Index: ${startIndex}`);
let connection;
try {
if (options.usb) {
// USB KNX interface connection
const usbOptions = {
...(options.usbDevice && { devicePath: options.usbDevice }),
};
console.log("\nConnecting to USB KNX interface...");
connection = (0, index_1.createUSB)(usbOptions);
}
else if (options.tunnel) {
// Parse tunnel address
const tunnelParts = options.tunnel.split(":");
const serverAddress = tunnelParts[0];
if (!serverAddress) {
throw new Error("Invalid tunnel address format");
}
const serverPort = tunnelParts[1]
? parseInt(tunnelParts[1], 10)
: undefined;
console.log(`\nConnecting to tunneling server ${serverAddress}:${serverPort || 3671}...`);
connection = (0, index_1.createManagement)(serverAddress, serverPort);
}
else {
console.error("Error: Property read requires either USB (-u) or tunneling (-t) connection");
console.error("Routing connections do not support property read operations");
process.exit(1);
}
// Set up error handling
connection.on("error", (error) => {
console.error("Connection error:", error.message);
process.exit(1);
});
// Open connection
await connection.open();
console.log("Connected successfully!");
// Read property
console.log("\nReading property...");
const result = await connection.readProperty(options.interfaceObject, objectInstance, options.propertyId, numberOfElements, startIndex);
console.log("Property read completed successfully!");
console.log(`Data: ${result.toString("hex").toUpperCase()}`);
console.log(`Data (decimal): [${Array.from(result).join(", ")}]`);
}
catch (error) {
console.error("Failed to read property:", error.message);
}
finally {
// Always close connection
if (connection) {
try {
await connection.close();
console.log("Connection closed.");
}
catch (closeError) {
console.error("Error closing connection:", closeError.message);
}
}
}
}
async function main() {
const options = parseArgs();
const args = process.argv.slice(2);
if (options.help || args.length === 0) {
printHelp();
return;
}
if (args[0] === "dump") {
await startFrameDump(options);
}
else if (args[0] === "discover") {
await startDiscovery(options);
}
else if (args[0] === "writeProperty") {
await writeProperty(options);
}
else if (args[0] === "readProperty") {
await readProperty(options);
}
else {
console.error(`Unknown command: ${args[0]}`);
console.error('Use "knxnetjs --help" for usage information');
process.exit(1);
}
}
if (require.main === module) {
main().catch((error) => {
console.error("Fatal error:", error.message);
process.exit(1);
});
}
//# sourceMappingURL=cli.js.map
;