Skip to content

Commit

Permalink
fix(endpoint): handle frame should respond with cluster specific resp…
Browse files Browse the repository at this point in the history
…onse even if disable default response is set to true

Fixes: https://support.developer.homey.app/a/tickets/218
  • Loading branch information
RobinBol committed Jan 7, 2025
1 parent aa8cb1b commit 0160278
Showing 1 changed file with 61 additions and 44 deletions.
105 changes: 61 additions & 44 deletions lib/Endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,38 +106,49 @@ class Endpoint extends EventEmitter {
const rawFrame = frame;
frame = Endpoint.parseFrame(frame);

// NOTE: we do not respond with a default response if:
// 1. The frame we received is a default response (frame.cmdId = 11)
// 2. Another command is sent in response to the received frame
// 3. The frame has the disableDefaultResponse flag set
// See ZCL specification 2.5.12.2.
const response = (
frame.frameControl.disableDefaultResponse
|| (meta && meta.groupId)
|| frame.cmdId === 11
) ? null : this._makeErrorResponse(frame);

let clusterSpecificResponse = null;
let clusterSpecificError = null;
try {
const result = await this.handleZCLFrame(clusterId, frame, meta, rawFrame);
if (!response) return;
if (result) {
const [cmdId, data] = result;
response.data = data.toBuffer();
response.cmdId = cmdId;
} else {
// Set status to success
response.data[1] = 0;
}
} catch (e) {
debug(`${this.getLogId(clusterId)}, error while handling frame`, e.message, { meta, frame });
clusterSpecificResponse = await this.handleZCLFrame(clusterId, frame, meta, rawFrame);
} catch (err) {
clusterSpecificError = err;
debug(`${this.getLogId(clusterId)}, error while handling frame`, err.message, { meta, frame });
}
// If desired (disableDefaultResponse: false) try to respond to the incoming frame
if (response) {
try {
await this.sendFrame(clusterId, response.toBuffer());
} catch (err) {
debug(`${this.getLogId(clusterId)}, error while responding with \`send frame\` to \`handle frame\``, err, { response });
}

// Don't respond to this frame if it is a default response or a group cast (ZCL spec 2.5.12.2)
if (frame.cmdId === 11 || (meta && typeof meta.groupId === 'number')) return;

// If cluster specific error, respond with a default response error frame
if (clusterSpecificError) {
const defaultResponseErrorFrame = this.makeDefaultResponseFrame(frame, false);
this.sendFrame(clusterId, defaultResponseErrorFrame.toBuffer()).catch(err => {
debug(`${this.getLogId(clusterId)}, error while sending default error response`, err, { response: defaultResponseErrorFrame });
});

// No further handling for this frame
return;
}

// Create response frame and set status to success
const responseFrame = this.makeDefaultResponseFrame(frame, true);

// If a cluster specific response was generated, set the response data
// and cmdId in the response frame.
if (clusterSpecificResponse) {
const [cmdId, data] = clusterSpecificResponse;
responseFrame.data = data.toBuffer();
responseFrame.cmdId = cmdId;
}

// If there was no cluster specific response and the default response is disabled, don't
// send a response.
if (!clusterSpecificResponse && frame.frameControl.disableDefaultResponse) return;

// Send either cluster specific, or default response frame
try {
await this.sendFrame(clusterId, responseFrame.toBuffer());
} catch (err) {
debug(`${this.getLogId(clusterId)}, error while sending cluster specific or default success response`, err, { response: responseFrame });
}
}

Expand Down Expand Up @@ -170,25 +181,31 @@ class Endpoint extends EventEmitter {
return response;
}

_makeErrorResponse(frame) {
let result;
if (frame instanceof ZCLStandardHeader) {
result = new ZCLStandardHeader();
/**
* Returns a default response frame with an error status code.
* @param {*} receivedFrame
* @param {boolean} success
* @returns {ZCLStandardHeader|ZCLMfgSpecificHeader}
*/
makeDefaultResponseFrame(receivedFrame, success) {
let responseFrame;
if (receivedFrame instanceof ZCLStandardHeader) {
responseFrame = new ZCLStandardHeader();
} else {
result = new ZCLMfgSpecificHeader();
result.manufacturerId = frame.manufacturerId;
responseFrame = new ZCLMfgSpecificHeader();
responseFrame.manufacturerId = receivedFrame.manufacturerId;
}
// TODO: flip proper bits
result.frameControl = frame.frameControl.copy();
responseFrame.frameControl = receivedFrame.frameControl.copy();

result.frameControl.disableDefaultResponse = true;
result.frameControl.clusterSpecific = false;
result.frameControl.directionToClient = !frame.frameControl.directionToClient;
responseFrame.frameControl.disableDefaultResponse = true;
responseFrame.frameControl.clusterSpecific = false;
responseFrame.frameControl.directionToClient = !receivedFrame.frameControl.directionToClient;

result.trxSequenceNumber = frame.trxSequenceNumber;
result.cmdId = 0x0B;
result.data = Buffer.from([frame.cmdId, 0x01]);
return result;
responseFrame.trxSequenceNumber = receivedFrame.trxSequenceNumber;
responseFrame.cmdId = 0x0B;
responseFrame.data = Buffer.from([receivedFrame.cmdId, success ? 0 : 1]);
return responseFrame;
}

static parseFrame(frame) {
Expand Down

0 comments on commit 0160278

Please sign in to comment.