matterbridge-shelly
Version:
Matterbridge shelly plugin
135 lines (134 loc) • 6.46 kB
JavaScript
import EventEmitter from 'node:events';
import { createServer } from 'node:http';
import { AnsiLogger, CYAN, db, er, hk, rs, wr, zb } from 'matterbridge/logger';
import WebSocket, { WebSocketServer } from 'ws';
import { ShellyDevice } from './shellyDevice.js';
export class WsServer extends EventEmitter {
log;
httpServer;
wsServer;
pingPeriod = 30000;
pongPeriod = 30000;
_isListening = false;
constructor(logLevel = "info") {
super();
this.log = new AnsiLogger({ logName: 'ShellyWsServer', logTimestampFormat: 4, logLevel });
}
get isListening() {
return this._isListening;
}
async listenForStatusUpdates(port = 8485) {
try {
this.httpServer = createServer();
this.wsServer = new WebSocketServer({ server: this.httpServer });
}
catch (error) {
this.log.error(`Failed to create the HttpServer and WebSocketServer: ${error}`);
return;
}
this.wsServer.on('connection', (ws, req) => {
const clientAddress = req.socket.remoteAddress;
this.log.info(`WebSocketServer client connected host ${zb}${clientAddress}${db}.`);
this.log.debug(`Start WebSocketServer PingPong.`);
let pongTimeout = undefined;
let pingInterval = undefined;
ws.ping();
pingInterval = setInterval(() => {
if (ws?.readyState === WebSocket.OPEN) {
ws.ping();
pongTimeout = setTimeout(() => {
this.log.error(`WebSocketServer pong not received.`);
}, this.pongPeriod);
}
}, this.pingPeriod);
ws.on('message', (data) => {
this.log.debug(`Received message from WebSocketServer client ${zb}${clientAddress}${db}.`);
try {
const message = JSON.parse(data.toString());
if (message.method && (message.method === 'NotifyStatus' || message.method === 'NotifyFullStatus') && message.src && message.dst === 'ws') {
message.src = ShellyDevice.normalizeId(message.src).id;
this.log.debug(`Received ${CYAN}${message.method}${db} from ${hk}${message.src}${db} host ${zb}${clientAddress}${db}:${rs}\n`, message.params);
this.emit('wssupdate', message.src, message.params);
}
else if (message.method && message.method === 'NotifyEvent' && message.src && message.dst === 'ws') {
message.src = ShellyDevice.normalizeId(message.src).id;
this.log.debug(`Received ${CYAN}NotifyEvent${db} from ${hk}${message.src}${db} host ${zb}${clientAddress}${db}:${rs}\n`, message.params);
this.emit('wssevent', message.src, message.params);
}
else {
this.log.debug(`WebSocketServer received an unknown message from ${hk}${message.src}${db} host ${zb}${clientAddress}${wr}:${rs}\n`, message);
}
}
catch (error) {
this.log.error(`WebSocketServer error parsing message from ${zb}${clientAddress}${er}: ${error instanceof Error ? error.message : error}`);
}
});
ws.on('pong', (_data) => {
this.log.debug('WebSocketServer client sent a pong');
clearTimeout(pongTimeout);
pongTimeout = undefined;
});
ws.on('ping', (_data) => {
this.log.debug('WebSocketServer client sent a ping');
ws.pong();
});
ws.on('close', (code, reason) => {
this.log.info(`WebSocketServer client disconnected: code ${code} ${reason.toString('utf-8') === '' ? '' : 'reason ' + reason.toString('utf-8')}`);
clearInterval(pingInterval);
pingInterval = undefined;
clearTimeout(pongTimeout);
pongTimeout = undefined;
});
ws.on('error', (error) => {
this.log.error('WebSocketServer client error:', error);
});
});
this.wsServer.on('error', (error) => {
this.log.error(`WebSocketServer error: ${error instanceof Error ? error.message : error}`);
this._isListening = false;
});
this.wsServer.on('close', () => {
this.log.debug(`WebSocketServer connection closed.`);
this._isListening = false;
});
this.httpServer.on('error', (error) => {
this.log.error(`HttpServer error: ${error instanceof Error ? error.message : error}`);
this.emit('error', error);
this._isListening = false;
});
this.httpServer.listen(port, () => {
this._isListening = true;
this.log.debug(`HttpServer for WebSocketServer is listening on port ${port}`);
this.log.info(`Started WebSocket server for shelly devices.`);
this.log.info(`WebSocket server for shelly devices is listening on port ${port}...`);
this.emit('started');
});
}
start(port = 8485) {
if (this._isListening) {
this.log.debug(`WebSocketServer is already listening.`);
return;
}
this.log.info(`Starting WebSocket server for shelly devices...`);
this.listenForStatusUpdates(port);
}
stop() {
this.log.info(`Stopping WebSocket server (listening ${this._isListening}) for shelly devices...`);
for (const client of this.wsServer?.clients || []) {
client.terminate();
}
this.wsServer?.close((err) => {
this.log.debug(`WebSocket server for shelly devices stopped${err ? ' with error ' + err.message : ''}.`);
this.wsServer?.removeAllListeners();
this.wsServer = undefined;
});
this.httpServer?.close((err) => {
this.log.debug(`HttpServer for WebSocketServer stopped${err ? ' with error ' + err.message : ''}.`);
this.httpServer?.removeAllListeners();
this.httpServer = undefined;
});
this._isListening = false;
this.log.info(`Stopped WebSocket server for shelly devices...`);
this.emit('stopped', undefined);
}
}