UNPKG

knxnetjs

Version:

A TypeScript library for KNXnet/IP communication

304 lines 11.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.KNXUSBImpl = void 0; const events_1 = require("events"); const HID = __importStar(require("node-hid")); const frames_1 = require("../frames"); const knx_hid_report_1 = require("../frames/knx-hid-report"); const knx_usb_transfer_1 = require("../frames/knx-usb-transfer"); const cemi_properties_1 = require("../frames/cemi-properties"); class KNXUSBImpl extends events_1.EventEmitter { constructor(options = {}) { super(); this.isConnected = false; this.buffer = Buffer.alloc(0); this.options = { devicePath: options.devicePath || "", baudRate: options.baudRate || 0, // Not used for HID autoConnect: options.autoConnect ?? true, busmonitorMode: options.busmonitorMode ?? false, }; } async open() { if (this.isConnected) { return; } const device = this.findKNXDevice(); if (!device) { throw new Error("No USB KNX HID device found. Please connect a USB KNX interface and ensure proper drivers are installed."); } try { this.hidDevice = new HID.HID(device.path); this.hidDevice.on("data", (data) => { this.handleIncomingData(data); }); this.hidDevice.on("error", (error) => { this.emit("error", error); }); // Initialize the device await this.initializeDevice(); this.isConnected = true; } catch (error) { throw new Error(`Failed to open USB KNX HID device: ${error.message}`); } } async send(frame) { if (!this.isConnected || !this.hidDevice) { throw new Error("USB KNX device not connected"); } if (this.options.busmonitorMode) { throw new Error("Cannot send frames in busmonitor mode - this is a monitor-only connection"); } const hidFrame = this.createHIDFrame(frame.toBuffer()); return new Promise((resolve, reject) => { try { this.hidDevice.write(hidFrame); resolve(); } catch (error) { reject(error); } }); } async close() { if (this.hidDevice && this.isConnected) { try { this.hidDevice.close(); } catch (error) { this.emit("error", error); } this.isConnected = false; } } /** * Check if this connection is in busmonitor mode */ isBusmonitorMode() { return this.options.busmonitorMode; } /** * Get available KNX USB devices */ static getAvailableDevices() { const devices = HID.devices(); // Filter for known KNX USB interface vendor/product IDs return devices.filter((device) => { // Common KNX USB interface vendors const knownVendors = [ 0x147b, // Weinzierl Engineering (KNX USB interface) 0x16d0, // MCS Electronics (various KNX interfaces) 0x0e77, // Siemens (some KNX products) 0x0403, // FTDI (used by some KNX manufacturers) ]; // Known product IDs for KNX interfaces const knownProducts = [ 0x0001, // Weinzierl KNX USB Interface 0x0002, // Weinzierl KNX USB Interface 810 0x6001, // FTDI-based interfaces ]; return (knownVendors.includes(device.vendorId || 0) || knownProducts.includes(device.productId || 0) || (device.product && device.product.toLowerCase().includes("knx"))); }); } on(event, listener) { return super.on(event, listener); } findKNXDevice() { if (this.options.devicePath) { // If specific path provided, try to use it const devices = HID.devices(); const device = devices.find((d) => d.path === this.options.devicePath); return device || null; } // Auto-detect KNX devices const knxDevices = KNXUSBImpl.getAvailableDevices(); if (knxDevices.length === 0) { return null; } // Return the first available KNX device return knxDevices[0] || null; } async initializeDevice() { if (!this.hidDevice) { throw new Error("HID device not available"); } // Send initialization command to the USB interface // This varies by manufacturer but typically involves: // 1. Reset command // 2. Set mode (normal vs busmonitor) const initCommands = []; // Create reset command using M_RESET_REQ message code initCommands.push(frames_1.KNXUSBTransferFrame.createForCEMI(Buffer.from([frames_1.CEMIMessageCode.M_RESET_REQ]))); // Set active emi service initCommands.push(frames_1.KNXUSBTransferFrame.createForBusAccess(0x03, // service device feature set 0x05, // feature active emi type Buffer.from([knx_usb_transfer_1.KNXUSBTransferEMIId.cEMI]))); initCommands.push(frames_1.KNXUSBTransferFrame.createForCEMI(new cemi_properties_1.CEMIPropertyWrite(0x0008, 1, cemi_properties_1.Properties.PID_COMM_MODE, 1, 1, Buffer.from([ this.options.busmonitorMode ? cemi_properties_1.DPT_CommMode.DataLinkLayerBusmonitor : cemi_properties_1.DPT_CommMode.DataLinkLayer, ])).toBuffer())); // Send initialization commands with delays for (const frame of initCommands) { try { // Wrap USB Transfer Frame in HID Report const hidReport = new knx_hid_report_1.KNXHIDReport(frame.toBuffer()); this.hidDevice.write(hidReport.toBuffer()); // Wait between commands to allow device processing await new Promise((resolve) => setTimeout(resolve, 100)); } catch (error) { throw new Error(`Failed to initialize USB device: ${error.message}`); } } } handleIncomingData(data) { const reportId = data[0]; // check if report id is 1 if (reportId === 0x01) { const packetType = data[1]; const dataLength = data[2]; if (packetType & 0x01) { // start of packet, start with new buffer this.buffer = Buffer.from(data, 0, dataLength); } else { // Append new data to buffer this.buffer = Buffer.concat([ this.buffer, data.subarray(0, dataLength), ]); } if (packetType & 0x02) { // Process complete frames from buffer this.processBuffer(); } } } processBuffer() { if (this.buffer.length > 3) { const dataLength = this.buffer[2]; const frame = this.buffer.subarray(3, 3 + dataLength); if (!frame) { return; // Not enough data for a complete frame } try { this.handleFrame(frame); } catch (error) { this.emit("error", new Error(`Frame processing error: ${error.message}`)); } } } handleFrame(hidFrame) { try { const frame = frames_1.KNXUSBTransferFrame.fromBuffer(hidFrame); switch (frame.body.emiMessageCode) { case frames_1.CEMIMessageCode.L_DATA_IND: const cemiFrame = frames_1.CEMIFrame.fromBuffer(Buffer.concat([ Buffer.from([frame.body.emiMessageCode]), frame.body.data, ])); if (cemiFrame.isValid()) { this.emit("recv", cemiFrame); } break; case frames_1.CEMIMessageCode.L_BUSMON_IND: const busmonFrame = frames_1.CEMIFrame.fromBuffer(Buffer.concat([ Buffer.from([frame.body.emiMessageCode]), frame.body.data, ])); if (busmonFrame.isValid()) { this.emit("recv", busmonFrame); } break; case frames_1.CEMIMessageCode.M_RESET_IND: this.emit("reset"); break; default: break; } } catch (error) { this.emit("error", new Error(`Frame conversion error: ${error.message}`)); } } convertHIDToCEMI(hidFrame) { // Convert USB HID frame to standard cEMI frame using KNX USB Transfer Protocol // Skip HID report header (first 2 bytes: report ID and packet length) if (hidFrame.length < 2) { return null; } const packetLength = hidFrame[1]; if (!packetLength || packetLength === 0 || hidFrame.length < packetLength + 2) { return null; } // Extract KNX USB Transfer Protocol frame (skip HID header) const transferProtocolData = hidFrame.subarray(2, packetLength + 2); try { if (!frames_1.KNXUSBTransferFrame.isValid(transferProtocolData)) { return null; // Not a valid KNX USB Transfer Protocol frame } const transferFrame = frames_1.KNXUSBTransferFrame.fromBuffer(transferProtocolData); return transferFrame.getCEMIData(); } catch (error) { return null; // Invalid frame } } createHIDFrame(cemiData) { // Create KNX USB Transfer Protocol frame for cEMI data const transferFrame = frames_1.KNXUSBTransferFrame.createForCEMI(cemiData); return new knx_hid_report_1.KNXHIDReport(transferFrame.toBuffer()).toBuffer(); } padHIDFrame(frame) { // Many HID devices expect fixed-size reports (typically 64 bytes) const reportSize = 64; if (frame.length >= reportSize) { return frame; } const paddedFrame = Buffer.alloc(reportSize); frame.copy(paddedFrame); return paddedFrame; } } exports.KNXUSBImpl = KNXUSBImpl; //# sourceMappingURL=usb.js.map