UNPKG

homebridge-calypshome

Version:
171 lines 6.38 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CalypshomeAPI = exports.getObjectsSchema = void 0; const events_1 = require("events"); const undici_1 = require("undici"); const zod_1 = require("zod"); const rollingShutter_1 = require("./rollingShutter"); const ws_1 = __importDefault(require("ws")); // can be replaced with native node later exports.getObjectsSchema = zod_1.z.object({ objects: zod_1.z.array(zod_1.z.object({ room: zod_1.z.object({ id: zod_1.z.number(), name: zod_1.z.string(), }), id: zod_1.z.string(), status: zod_1.z.array(zod_1.z.object({ value: zod_1.z.string(), name: zod_1.z.string(), time: zod_1.z.string(), })), categories: zod_1.z.unknown(), name: zod_1.z.string(), type: zod_1.z.enum(['Rolling_Shutter', 'Composite', 'EZSP']), img: zod_1.z.string(), gw: zod_1.z.string(), eventId: zod_1.z.string(), connected: zod_1.z.boolean(), actions: zod_1.z.array(zod_1.z.enum(['OPEN', 'CLOSE', 'STOP', 'LEVEL', 'TILT', 'SCAN', 'JOIN'])), })), }); // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging class CalypshomeAPI extends events_1.EventEmitter { logger; url; wsUrl; inMemoryDevices = {}; static DEVICE_UPDATE = 'device_update'; constructor(config, logger) { super(); this.logger = logger; this.url = config.url; this.wsUrl = `${config.url.replace('http', 'ws')}/ws`; } close() { this.ws?.close(); } async devices() { return this.apiCall(`${this.url}/m?a=getObjects`, { headers: { Accept: 'application/json', }, }) .then((x) => x.body.json()) .then((data) => exports.getObjectsSchema .parse(data) .objects.filter((entry) => entry.type === 'Rolling_Shutter') .map((g) => new rollingShutter_1.RollingShutter(g))) .then((devices) => { this.inMemoryDevices = devices.reduce((acc, device) => { acc[device.id] = device; return acc; }, {}); return devices; }); } async action(id, action, args) { return this.apiCall(`${this.url}/m?a=command`, { body: new URLSearchParams({ id, action, args: args ? JSON.stringify(args) : '', }).toString(), }) .then((response) => response.statusCode === 200) .catch((e) => { this.logger.error('Error in action', { id, action, args, e }); return false; }); } apiCall(url, options) { const ac = new AbortController(); options.method ??= 'POST'; options.signal = ac.signal; options.dispatcher = new undici_1.Agent({ keepAliveTimeout: 10, keepAliveMaxTimeout: 10, }); this.logger.debug(`API call ${url}`, options); const timer = setTimeout(() => { ac.abort(); }, 5 * 1000); return (0, undici_1.request)(url, options) .then((response) => { clearTimeout(timer); this.logger.debug(`API call ${url} response`, { status: response.statusCode, }); return response; }) .catch((e) => { this.logger.error(`API call ${url}`, e); throw e; }); } device(id) { return this.inMemoryDevices[id]; } connectWebSocket() { this.logger.info('Connecting WebSocket'); // const START_TIMESTAMP = Math.round(new Date().getTime() / 1000); this.ws = new ws_1.default(this.wsUrl, 'lws-mirror-protocol'); this.ws.on('open', () => { this.logger.info('WebSocket connected'); this.ws.send('p1 1 _web / login'); }); this.ws.on('close', () => { this.logger.warn('WebSocket onclose() retrying in 30s'); setTimeout(() => { this.connectWebSocket(); }, 30000); }); this.ws.on('error', (event) => { this.logger.error('WebSocket error', event); }); this.ws.on('message', (event) => { this.handleWebSocketMessage(event.toString('utf-8')); }); } handleWebSocketMessage(data) { const [, , src, dest, cmd, rest, b64, value] = data.split(' '); // decode base64 const message = b64.startsWith('@') ? Buffer.from(b64.substring(1), 'base64').toString() : b64; const sysmatch = message.match(/^event\/system\/gateway\/dev-\d\/id-self\/(.*)/); if (message === 'event/ui/web/connect') { return; } if (sysmatch) { if (['uptime', 'cpu_idle', 'disk_free', 'memory_free', 'load_5', 'system_uptime'].includes(sysmatch[1]) || sysmatch[1].startsWith('gw_')) { return; } this.logger.info('WebSocket system message', data, { match: sysmatch[1], value }); return; } const devmatch = message.match(/^event\/io\/ezsp\/dev-\d\/([^/]+)\/(level|angle|status)/); if (devmatch) { const [, device, type] = devmatch; const matchedDevice = Object.values(this.inMemoryDevices).find((d) => d.id.includes(device)); if (matchedDevice && ['level', 'angle', 'status'].includes(type)) { this.update(matchedDevice, type, value); return; } this.logger.warn('WebSocket dev message', data, { device, type }); return; } this.logger.warn('WebSocket unknown message', data, { src, dest, cmd, rest, message, value }); } update(device, key, value) { if (key === 'angle') { device.angle = Number(value); } else if (key === 'level') { device.level = Number(value); } this.emit(CalypshomeAPI.DEVICE_UPDATE, device, key); } } exports.CalypshomeAPI = CalypshomeAPI; //# sourceMappingURL=calypshomeAPI.js.map