knxnetjs
Version:
A TypeScript library for KNXnet/IP communication
304 lines • 11.9 kB
JavaScript
"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