knxnetjs
Version:
A TypeScript library for KNXnet/IP communication
178 lines • 6.92 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KNXNetRoutingImpl = void 0;
const events_1 = require("events");
const dgram_1 = require("dgram");
const constants_1 = require("../constants");
const frames_1 = require("../frames");
class KNXNetRoutingImpl extends events_1.EventEmitter {
constructor(multicastAddress, port) {
super();
this.isConnected = false;
this.busyCounter = 0;
this.lastBusyTime = 0;
this.options = {
multicastAddress: multicastAddress || constants_1.KNX_CONSTANTS.DEFAULT_MULTICAST_ADDRESS,
port: port || constants_1.KNX_CONSTANTS.DEFAULT_PORT,
ttl: 16,
};
}
async open() {
if (this.isConnected) {
return;
}
return new Promise((resolve, reject) => {
this.socket = (0, dgram_1.createSocket)({ type: "udp4", reuseAddr: true });
this.socket.on("message", (msg) => {
this.handleIncomingMessage(msg);
});
this.socket.on("error", (err) => {
this.emit("error", err);
reject(err);
});
this.socket.bind(this.options.port, () => {
if (!this.socket)
return;
this.socket.addMembership(this.options.multicastAddress);
this.socket.setMulticastTTL(this.options.ttl);
this.isConnected = true;
resolve();
});
});
}
async send(frame) {
if (!this.isConnected || !this.socket) {
throw new Error("Not connected");
}
const routingFrame = this.createRoutingIndicationFrame(frame.toBuffer());
return new Promise((resolve, reject) => {
this.socket.send(routingFrame, this.options.port, this.options.multicastAddress, (err) => {
if (err) {
reject(err);
}
else {
resolve();
}
});
});
}
async close() {
if (this.socket && this.isConnected) {
this.socket.dropMembership(this.options.multicastAddress);
this.socket.close();
this.isConnected = false;
}
}
async writeProperty(interfaceObject, objectInstance, propertyId, numberOfElements, startIndex, data) {
throw new Error("writeProperty is not implemented for KNX routing connections");
}
async readProperty(interfaceObject, objectInstance, propertyId, numberOfElements, startIndex) {
throw new Error("readProperty is not implemented for KNX routing connections");
}
on(event, listener) {
return super.on(event, listener);
}
handleIncomingMessage(msg) {
try {
const frame = this.parseKNXNetFrame(msg);
switch (frame.serviceType) {
case constants_1.KNX_CONSTANTS.SERVICE_TYPES.ROUTING_INDICATION:
this.handleRoutingIndication(frame);
break;
case constants_1.KNX_CONSTANTS.SERVICE_TYPES.ROUTING_LOST_MESSAGE:
this.handleRoutingLostMessage(frame);
break;
case constants_1.KNX_CONSTANTS.SERVICE_TYPES.ROUTING_BUSY:
this.handleRoutingBusy(frame);
break;
}
}
catch (error) {
this.emit("error", error);
}
}
parseKNXNetFrame(buffer) {
const frame = frames_1.KNXnetIPFrame.fromBuffer(buffer);
const data = frame.payload;
switch (frame.service) {
case constants_1.KNX_CONSTANTS.SERVICE_TYPES.ROUTING_INDICATION:
return {
serviceType: frame.service,
cemiFrame: data,
};
case constants_1.KNX_CONSTANTS.SERVICE_TYPES.ROUTING_LOST_MESSAGE:
return {
serviceType: frame.service,
deviceState: data.readUInt8(1),
numberOfLostMessages: data.readUInt16BE(2),
};
case constants_1.KNX_CONSTANTS.SERVICE_TYPES.ROUTING_BUSY:
return {
serviceType: frame.service,
deviceState: data.readUInt8(1),
waitTime: data.readUInt16BE(2),
controlField: data.readUInt16BE(4),
};
default:
throw new Error(`Unknown service type: 0x${frame.service.toString(16)}`);
}
}
createRoutingIndicationFrame(cemiFrame) {
const frame = new frames_1.KNXnetIPFrame(constants_1.KNX_CONSTANTS.SERVICE_TYPES.ROUTING_INDICATION, cemiFrame);
return frame.toBuffer();
}
handleRoutingIndication(frame) {
if (frames_1.CEMIFrame.isValidBuffer(frame.cemiFrame)) {
const cemiFrame = frames_1.CEMIFrame.fromBuffer(frame.cemiFrame);
const routingCounter = cemiFrame.routingCounter;
if (routingCounter === constants_1.KNX_CONSTANTS.ROUTING_COUNTER.DONT_ROUTE) {
return;
}
this.emit("recv", cemiFrame);
}
else {
// Fallback to old extraction method for invalid frames
const routingCounter = this.extractRoutingCounter(frame.cemiFrame);
if (routingCounter === constants_1.KNX_CONSTANTS.ROUTING_COUNTER.DONT_ROUTE) {
return;
}
// Try to create a CEMIFrame from invalid buffer, emit error if it fails
try {
const cemiFrame = frames_1.CEMIFrame.fromBuffer(frame.cemiFrame);
this.emit("recv", cemiFrame);
}
catch (error) {
this.emit("error", new Error(`Invalid cEMI frame received: ${error.message}`));
}
}
}
handleRoutingLostMessage(frame) {
this.emit("lostMessage", {
deviceState: frame.deviceState,
numberOfLostMessages: frame.numberOfLostMessages,
});
}
handleRoutingBusy(frame) {
const now = Date.now();
if (now - this.lastBusyTime >
constants_1.KNX_CONSTANTS.FLOW_CONTROL.BUSY_DETECTION_THRESHOLD) {
this.busyCounter = 0;
}
this.busyCounter++;
this.lastBusyTime = now;
this.emit("busy", {
waitTime: frame.waitTime,
controlField: frame.controlField,
busyCounter: this.busyCounter,
});
}
extractRoutingCounter(cemiFrame) {
if (cemiFrame.length < 8) {
return constants_1.KNX_CONSTANTS.ROUTING_COUNTER.DONT_ROUTE;
}
const ctrl1 = cemiFrame.readUInt8(1);
return (ctrl1 >> 4) & 0x07;
}
}
exports.KNXNetRoutingImpl = KNXNetRoutingImpl;
//# sourceMappingURL=routing.js.map