zigbee-herdsman-zigate
Version:
An open source ZigBee gateway solution with node.js.
324 lines • 14.7 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const serialport_1 = __importDefault(require("serialport"));
const frame_1 = __importDefault(require("./frame"));
const events_1 = require("events");
const debug_1 = require("../debug");
const serialPortUtils_1 = __importDefault(require("../../serialPortUtils"));
const socketPortUtils_1 = __importDefault(require("../../socketPortUtils"));
const net_1 = __importDefault(require("net"));
const utils_1 = require("../../../utils");
const constants_1 = require("./constants");
const ziGateObject_1 = __importDefault(require("./ziGateObject"));
const zcl_1 = require("../../../zcl");
const waitress_1 = __importDefault(require("../../../utils/waitress"));
const debug = debug_1.Debug('driver');
const autoDetectDefinitions = [
{ manufacturer: 'zz', vendorId: 'id', productId: '00' },
];
const timeouts = {
reset: 30000,
default: 3000,
};
function zeroPad(number, size) {
return (number).toString(16).padStart(size || 4, '0');
}
function resolve(path, obj, separator = '.') {
const properties = Array.isArray(path) ? path : path.split(separator);
return properties.reduce((prev, curr) => prev && prev[curr], obj);
}
class ZiGate extends events_1.EventEmitter {
constructor(path, serialPortOptions) {
super();
// eslint-disable-next-line prefer-rest-params
debug.log('construct', arguments);
this.path = path;
this.baudRate = typeof serialPortOptions.baudRate === 'number' ? serialPortOptions.baudRate : 115200;
this.rtscts = typeof serialPortOptions.rtscts === 'boolean' ? serialPortOptions.rtscts : false;
this.portType = socketPortUtils_1.default.isTcpPath(path) ? 'socket' : 'serial';
this.initialized = false;
this.queue = new utils_1.Queue(1);
this.waitress = new waitress_1.default(this.waitressValidator, this.waitressTimeoutFormatter);
}
static isValidPath(path) {
return __awaiter(this, void 0, void 0, function* () {
return serialPortUtils_1.default.is(path, autoDetectDefinitions);
});
}
static autoDetectPath() {
return __awaiter(this, void 0, void 0, function* () {
const paths = yield serialPortUtils_1.default.find(autoDetectDefinitions);
return paths.length > 0 ? paths[0] : null;
});
}
open() {
return this.portType === 'serial' ? this.openSerialPort() : this.openSocketPort();
}
close() {
debug.error('close');
return new Promise((resolve, reject) => {
if (this.initialized) {
this.initialized = false;
this.portWrite = null;
if (this.portType === 'serial') {
this.serialPort.flush(() => {
this.serialPort.close((error) => {
this.serialPort = null;
error == null ?
resolve() :
reject(new Error(`Error while closing serialPort '${error}'`));
this.emit('close');
});
});
}
else {
// @ts-ignore
this.socketPort.destroy((error) => {
this.socketPort = null;
error == null ?
resolve() :
reject(new Error(`Error while closing serialPort '${error}'`));
this.emit('close');
});
}
}
else {
resolve();
this.emit('close');
}
});
}
sendCommand(code, payload) {
return __awaiter(this, void 0, void 0, function* () {
// const argument = arguments;
return this.queue.execute(() => __awaiter(this, void 0, void 0, function* () {
try {
debug.log('Send command \x1b[42m>>>> '
+ constants_1.ZiGateCommandCode[code]
+ ' 0x' + zeroPad(code)
+ ' <<<<\x1b[0m ');
debug.log('payload: ', payload);
const ziGateObject = ziGateObject_1.default.createRequest(code, payload);
const frame = ziGateObject.toZiGateFrame();
const sendBuffer = frame.toBuffer();
debug.log('Send command buff: ', sendBuffer);
const waiters = [];
ziGateObject.command.response.forEach((rules) => {
waiters.push(this.waitress.waitFor({ ziGateObject, rules }, timeouts.default).start().promise);
});
// if (ziGateObject.command.response) {
// waiter = this.waitress.waitFor({
// responseType: ziGateObject.command.wait_response,
// commandCode: ziGateObject.code
// }, timeouts.default);
// } else if (ziGateObject.command.wait_status) {
// waiter = this.waitress.waitFor(
// {responseType: 0x8000, commandCode: ziGateObject.code},
// timeouts.default
// );
// }
this.portWrite.write(sendBuffer);
return Promise.race(waiters);
// return new Promise(async (resolve, reject) => {
// const firstResult = await Promise.race(waiters);
//
// if (firstResult.code === 0x8000 && firstResult.payload.status !== 0) {
// reject();
// }
// if (firstResult.code === 0x8000
// && firstResult.payload.status === 0
// &&
// ) { // && count promiss >0
// const dataResult = await Promise.race(waiters);
// } //status
// });
}
catch (e) {
debug.error(e);
return new Promise((resolve, reject) => {
reject();
});
}
}));
});
}
waitFor(matcher, timeout = timeouts.default) {
return this.waitress.waitFor(matcher, timeout);
}
openSerialPort() {
return __awaiter(this, void 0, void 0, function* () {
this.serialPort = new serialport_1.default(this.path, {
baudRate: this.baudRate,
dataBits: 8,
parity: 'none',
stopBits: 1,
lock: false,
autoOpen: false
});
this.parser = this.serialPort.pipe(new serialport_1.default.parsers.Delimiter({ delimiter: [frame_1.default.STOP_BYTE], includeDelimiter: true }));
this.parser.on('data', this.onSerialData.bind(this));
this.portWrite = this.serialPort;
return new Promise((resolve, reject) => {
this.serialPort.open((err) => __awaiter(this, void 0, void 0, function* () {
if (err) {
this.serialPort = null;
this.parser = null;
this.path = null;
this.initialized = false;
const error = `Error while opening serialPort '${err}'`;
debug.error(error);
reject(new Error(error));
}
else {
debug.log('Successfully connected ZiGate port \'' + this.path + '\'');
this.serialPort.on('error', (error) => {
debug.error(`serialPort error: ${error}`);
});
this.serialPort.on('close', this.onPortClose.bind(this));
this.initialized = true;
resolve();
}
}));
});
});
}
openSocketPort() {
return __awaiter(this, void 0, void 0, function* () {
const info = socketPortUtils_1.default.parseTcpPath(this.path);
debug.log(`Opening TCP socket with ${info.host}:${info.port}`);
this.socketPort = new net_1.default.Socket();
this.socketPort.setNoDelay(true);
this.socketPort.setKeepAlive(true, 15000);
this.parser = this.socketPort.pipe(new serialport_1.default.parsers.Delimiter({ delimiter: [frame_1.default.STOP_BYTE], includeDelimiter: true }));
this.parser.on('data', this.onSerialData.bind(this));
this.portWrite = this.socketPort;
return new Promise((resolve, reject) => {
this.socketPort.on('connect', function () {
debug.log('Socket connected');
});
// eslint-disable-next-line
const self = this;
this.socketPort.on('ready', function () {
return __awaiter(this, void 0, void 0, function* () {
debug.log('Socket ready');
self.initialized = true;
resolve();
});
});
this.socketPort.once('close', this.onPortClose);
this.socketPort.on('error', (error) => {
debug.log('Socket error', error);
// reject(new Error(`Error while opening socket`));
reject();
self.initialized = false;
});
this.socketPort.connect(info.port, info.host);
});
});
}
onSerialError(err) {
debug.error('serial error: ', err);
}
onPortClose() {
debug.log('serial closed');
this.initialized = false;
this.emit('close');
}
onSerialData(buffer) {
try {
debug.log(`--- parseNext `, buffer);
const frame = new frame_1.default(buffer);
const code = frame.readMsgCode();
let msgName;
try {
msgName = constants_1.ZiGateMessageCode[code] ? constants_1.ZiGateMessageCode[code] : '';
msgName += ' 0x' + zeroPad(code);
}
catch (e) {
}
debug.log(`--> parsed frame \x1b[1;34m>>>> ${msgName} <<<<`);
// debug.log(frame);
try {
const ziGateObject = ziGateObject_1.default.fromZiGateFrame(frame);
if (ziGateObject === undefined)
return;
// debug.log(ziGateObject.payload, frame.readRSSI());
this.waitress.resolve(ziGateObject);
if (code !== constants_1.ZiGateMessageCode.Status || ziGateObject.payload.status !== 0) {
debug.info(`--> frame to object `, ziGateObject.payload);
}
if (code === constants_1.ZiGateMessageCode.DataIndication) {
debug.info('raw');
let zclFrame = null;
switch (ziGateObject.payload.clusterID) {
case 0x8005:
// @ts-ignore
zclFrame = ziGateObject_1.default.fromBufer(0x8005, ziGateObject.payload.payload);
break;
default:
try {
// @ts-ignore
zclFrame = zcl_1.ZclFrame.fromBuffer(ziGateObject.payload.clusterID, ziGateObject.payload.payload);
}
catch (e) {
zclFrame = null;
}
}
this.emit('received', { ziGateObject, zclFrame });
}
else if (code === constants_1.ZiGateMessageCode.LeaveIndication) {
debug.log('raw leave');
this.emit('LeaveIndication', { ziGateObject });
}
else if (code === constants_1.ZiGateMessageCode.DeviceAnnounce) {
debug.log('raw announce');
this.emit('DeviceAnnounce', { ziGateObject });
}
}
catch (error) {
this.emit('receivedRaw', { error, frame });
// debug.error(`'${error.stack}'`);
}
}
catch (error) {
debug.error(`Error while parsing to ZiGateObject '${error.stack}'`);
}
}
waitressTimeoutFormatter(matcher, timeout) {
return `${matcher} after ${timeout}ms`;
}
waitressValidator(ziGateObject, matcher) {
const validator = (rule) => {
try {
let expectedValue;
if (typeof rule.value === "undefined" && typeof rule.expectedProperty !== "undefined") {
expectedValue = resolve(rule.expectedProperty, matcher.ziGateObject);
}
else {
expectedValue = rule.value;
}
const receivedValue = resolve(rule.receivedProperty, ziGateObject);
// debug.info(expectedValue, receivedValue, rule);
return rule.matcher(expectedValue, receivedValue);
}
catch (e) {
return false;
}
};
return matcher.rules.every(validator);
}
}
exports.default = ZiGate;
//# sourceMappingURL=zigate.js.map