matterbridge-shelly
Version:
Matterbridge shelly plugin
132 lines (131 loc) • 5.99 kB
JavaScript
import { AnsiLogger, CYAN, db, er, hk, rs, wr, zb } from 'matterbridge/logger';
import WebSocket, { WebSocketServer } from 'ws';
import EventEmitter from 'node:events';
import { createServer } from 'node:http';
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 });
}
emit(eventName, ...args) {
return super.emit(eventName, ...args);
}
on(eventName, listener) {
return super.on(eventName, listener);
}
get isListening() {
return this._isListening;
}
async listenForStatusUpdates(port = 8485) {
try {
this.httpServer = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('WebSocketServer is running\n');
});
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.debug(`WebSocketServer incoming client connection from ${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', () => {
this.log.debug('WebSocketServer client disconnected');
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.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.`);
});
}
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._isListening = false;
this.wsServer?.close();
this.wsServer?.removeAllListeners();
this.wsServer = undefined;
this.httpServer?.close();
this.httpServer?.removeAllListeners();
this.httpServer = undefined;
this.log.info(`Stopped WebSocket server for shelly devices...`);
}
}