zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
184 lines • 8 kB
JavaScript
import { isEncapsulatingCommandClass, isMultiEncapsulatingCommandClass, } from "@zwave-js/cc";
import { MessagePriority, ZWaveLoggerBase, getDirectionPrefix, messageRecordToLines, tagify, } from "@zwave-js/core";
import { FunctionType, MessageType, } from "@zwave-js/serial";
import { containsCC } from "@zwave-js/serial/serialapi";
import { getEnumMemberName } from "@zwave-js/shared";
import { NodeStatus } from "../node/_Types.js";
export const DRIVER_LABEL = "DRIVER";
const DRIVER_LOGLEVEL = "verbose";
const SENDQUEUE_LOGLEVEL = "debug";
export class DriverLogger extends ZWaveLoggerBase {
driver;
constructor(driver, loggers) {
super(loggers, DRIVER_LABEL);
this.driver = driver;
}
isDriverLogVisible() {
return this.container.isLoglevelVisible(DRIVER_LOGLEVEL);
}
isSendQueueLogVisible() {
return this.container.isLoglevelVisible(SENDQUEUE_LOGLEVEL);
}
/**
* Logs a message
* @param msg The message to output
*/
print(message, level) {
const actualLevel = level || DRIVER_LOGLEVEL;
if (!this.container.isLoglevelVisible(actualLevel))
return;
this.logger.log({
level: actualLevel,
message,
direction: getDirectionPrefix("none"),
context: { source: "driver", direction: "none" },
});
}
/**
* Serializes a message that starts a transaction, i.e. a message that is sent and may expect a response
*/
transaction(transaction) {
if (!this.isDriverLogVisible())
return;
const { message } = transaction;
// On the first attempt, we print the basic information about the transaction
const secondaryTags = [];
// TODO: restore logging
// if (transaction.sendAttempts === 1) {
secondaryTags.push(`P: ${MessagePriority[transaction.priority]}`);
// } else {
// // On later attempts, we print the send attempts
// secondaryTags.push(
// `attempt ${transaction.sendAttempts}/${transaction.maxSendAttempts}`,
// );
// }
this.logMessage(message, {
secondaryTags,
// Since we are programming a controller, the first message of a transaction is always outbound
// (not to confuse with the message type, which may be Request or Response)
direction: "outbound",
});
}
/** Logs information about a message that is received as a response to a transaction */
transactionResponse(message, originalTransaction, role) {
if (!this.isDriverLogVisible())
return;
this.logMessage(message, {
nodeId: originalTransaction?.message?.getNodeId(),
secondaryTags: [role],
direction: "inbound",
});
}
logMessage(message, {
// Used to relate this log message to a node
nodeId, secondaryTags, direction = "none", } = {}) {
if (!this.isDriverLogVisible())
return;
if (nodeId == undefined)
nodeId = message.getNodeId();
if (nodeId != undefined && !this.container.isNodeLoggingVisible(nodeId)) {
return;
}
const isCCContainer = containsCC(message);
const logEntry = message.toLogEntry();
if (this.driver.options.logConfig?.raw) {
// In raw mode, we just print the JSON representation of the message
this.logger.log({
level: DRIVER_LOGLEVEL,
primaryTags: tagify(logEntry.tags),
secondaryTags: secondaryTags && secondaryTags.length > 0
? tagify(secondaryTags)
: undefined,
message: JSON.stringify(logEntry),
// Since we are programming a controller, responses are always inbound
// (not to confuse with the message type, which may be Request or Response)
direction: getDirectionPrefix(direction),
context: { source: "driver", direction },
});
return;
}
// Otherwise, render the entire command tree
let msg = [tagify(logEntry.tags)];
if (logEntry.message) {
msg.push(...messageRecordToLines(logEntry.message).map((line) => (isCCContainer ? "│ " : " ") + line));
}
try {
// If possible, include information about the CCs
if (isCCContainer) {
// Remove the default payload message and draw a bracket
msg = msg.filter((line) => !line.startsWith("│ payload:"));
const logCC = (cc, indent = 0) => {
const isEncapCC = isEncapsulatingCommandClass(cc)
|| isMultiEncapsulatingCommandClass(cc);
const loggedCC = cc.toLogEntry(this.driver);
msg.push(" ".repeat(indent * 2)
+ "└─"
+ tagify(loggedCC.tags));
indent++;
if (loggedCC.message) {
msg.push(...messageRecordToLines(loggedCC.message).map((line) => `${" ".repeat(indent * 2)}${isEncapCC ? "│ " : " "}${line}`));
}
// If this is an encap CC, continue
if (isEncapsulatingCommandClass(cc)) {
logCC(cc.encapsulated, indent);
}
else if (isMultiEncapsulatingCommandClass(cc)) {
for (const encap of cc.encapsulated) {
logCC(encap, indent);
}
}
};
logCC(message.command);
}
this.logger.log({
level: DRIVER_LOGLEVEL,
secondaryTags: secondaryTags && secondaryTags.length > 0
? tagify(secondaryTags)
: undefined,
message: msg,
// Since we are programming a controller, responses are always inbound
// (not to confuse with the message type, which may be Request or Response)
direction: getDirectionPrefix(direction),
context: { source: "driver", direction },
});
}
catch { }
}
/** Logs what's currently in the driver's send queue */
sendQueue(...queues) {
if (!this.isSendQueueLogVisible())
return;
let message = "Send queue:";
let length = 0;
for (const queue of queues) {
length += queue.length;
if (queue.length > 0) {
for (const trns of queue.transactions) {
// TODO: This formatting should be shared with the other logging methods
const node = trns.message.tryGetNode(this.driver);
const prefix = trns.message.type === MessageType.Request
? "[REQ]"
: "[RES]";
const postfix = node != undefined
? ` [Node ${node.id}, ${getEnumMemberName(NodeStatus, node.status)}]`
: "";
const command = containsCC(trns.message)
? `: ${trns.message.command.constructor.name}`
: "";
message += `\n· ${prefix} ${FunctionType[trns.message.functionType]}${command}${postfix} (P: ${getEnumMemberName(MessagePriority, trns.priority)})`;
}
}
else {
message += " (empty)";
}
}
this.logger.log({
level: SENDQUEUE_LOGLEVEL,
message,
secondaryTags: `(${length} message${length === 1 ? "" : "s"})`,
direction: getDirectionPrefix("none"),
context: { source: "driver", direction: "none" },
});
}
}
//# sourceMappingURL=Driver.js.map