UNPKG

homebridge-bond

Version:

A homebridge plugin to control your Bond devices over the v2 API.

314 lines (313 loc) 12.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BondApi = void 0; const Action_1 = require("./enum/Action"); const BondUri_1 = require("./BondUri"); const axios_1 = __importDefault(require("axios")); const axios_retry_1 = __importDefault(require("axios-retry")); const flake_idgen_1 = __importDefault(require("flake-idgen")); // eslint-disable-next-line @typescript-eslint/no-var-requires const intformat = require('biguint-format'); var HTTPMethod; (function (HTTPMethod) { HTTPMethod["GET"] = "get"; HTTPMethod["PUT"] = "put"; HTTPMethod["PATCH"] = "patch"; })(HTTPMethod || (HTTPMethod = {})); const flakeIdGen = new flake_idgen_1.default(); class BondApi { constructor(platform, bondToken, ipAddress, ms_between_actions) { this.platform = platform; this.queueNextRequest = false; this.requestQueue = []; this.bondToken = bondToken; this.uri = new BondUri_1.BondUri(ipAddress); this.ms_between_actions = ms_between_actions; (0, axios_retry_1.default)(axios_1.default, { retries: 10, retryDelay: axios_retry_1.default.exponentialDelay, shouldResetTimeout: true, retryCondition: (error) => { var _a, _b, _c; const shouldRetry = axios_retry_1.default.isNetworkOrIdempotentRequestError(error) || error.code === 'ECONNABORTED'; this.platform.log.debug(`Retrying: ${shouldRetry ? 'YES' : 'NO'}`, { url: (_a = error.config) === null || _a === void 0 ? void 0 : _a.url, method: (_b = error.config) === null || _b === void 0 ? void 0 : _b.method, errorCode: error.code, responseStatus: (_c = error.response) === null || _c === void 0 ? void 0 : _c.status, }); return shouldRetry; }, }); } // Bond / Device Info getVersion() { return this.request(HTTPMethod.GET, this.uri.version()); } getState(id) { return this.request(HTTPMethod.GET, this.uri.state(id)); } getDeviceIds() { const req = this.request(HTTPMethod.GET, this.uri.deviceIds()); return req.then(json => Object.keys(json).filter(x => { // Ignore anything that is an empty string or starts with underscores return x.length > 0 && !/^_+/.test(x); })); } getDevices(ids) { const ps = []; ids.forEach(id => { ps.push(this.getDevice(id)); }); return Promise.all(ps); } getDevice(id) { const req = this.request(HTTPMethod.GET, this.uri.device(id)); return req.then(json => { // Set the id since it's not included in the response json.id = id; // get the properties of the device return this.getProperties(id).then(properties => { json.properties = properties; if (json.commands) { // commands are only present on Bridge devices. return this.getCommands(id).then(commands => { json.commands = commands; return json; }); } else { return json; } }); }); } // Actions action(device, action, callback, body = {}) { return (this.ms_between_actions ? this.queueRequest(device, action, body) : this.request(HTTPMethod.PUT, this.uri.action(device.id, action), body)) .then(() => { callback(null); }) .catch((error) => { callback(Error(error)); }); } queueRequest(device, action, body) { if (this.queueNextRequest) { this.requestQueue.push({ device, action, body }); } else { this.queueNextRequest = true; this.request(HTTPMethod.PUT, this.uri.action(device.id, action), body); setTimeout(() => this.unQueueRequest(), this.ms_between_actions); } return Promise.resolve(); } unQueueRequest() { const next = this.requestQueue.shift(); if (next) { this.request(HTTPMethod.PUT, this.uri.action(next.device.id, next.action), next.body); setTimeout(() => this.unQueueRequest(), this.ms_between_actions); } else { this.queueNextRequest = false; } } toggleLight(device, callback) { return this.action(device, Action_1.Action.ToggleLight, callback); } toggleUpLight(device, callback) { return this.action(device, Action_1.Action.ToggleUpLight, callback); } toggleDownLight(device, callback) { return this.action(device, Action_1.Action.ToggleDownLight, callback); } startDimmer(device, callback) { return this.action(device, Action_1.Action.StartDimmer, callback); } startUpLightDimmer(device, callback) { return this.action(device, Action_1.Action.StartUpLightDimmer, callback); } startDownLightDimmer(device, callback) { return this.action(device, Action_1.Action.StartDownLightDimmer, callback); } startIncreasingBrightness(device, callback) { return this.action(device, Action_1.Action.StartIncreasingBrightness, callback); } startDecreasingBrightness(device, callback) { return this.action(device, Action_1.Action.StartDecreasingBrightness, callback); } setBrightness(device, value, callback) { return this.action(device, Action_1.Action.SetBrightness, callback, { argument: value }); } setFlame(device, value, callback) { return this.action(device, Action_1.Action.SetFlame, callback, { argument: value }); } turnLightOff(device, callback) { return this.action(device, Action_1.Action.TurnLightOff, callback); } toggleFan(device, on, callback) { const action = on ? Action_1.Action.TurnOn : Action_1.Action.TurnOff; return this.action(device, action, callback); } toggleDirection(device, callback) { return this.action(device, Action_1.Action.ToggleDirection, callback); } togglePower(device, callback) { return this.action(device, Action_1.Action.TogglePower, callback); } toggleOpen(device, callback) { return this.action(device, Action_1.Action.ToggleOpen, callback); } preset(device, callback) { return this.action(device, Action_1.Action.Preset, callback); } stop(device, callback) { return this.request(HTTPMethod.PUT, this.uri.action(device.id, Action_1.Action.Stop)) .then(() => { if (callback) { callback(null); } }) .catch((error) => { if (callback) { callback(Error(error)); } }); } setPosition(device, position, callback) { return this.request(HTTPMethod.PUT, this.uri.action(device.id, Action_1.Action.SetPosition), { argument: position }) .then(() => { if (callback) { callback(null); } }) .catch((error) => { if (callback) { callback(Error(error)); } }); } setFanSpeed(device, speed, callback) { return this.action(device, Action_1.Action.SetSpeed, callback, { argument: speed }); } increaseSpeed(device, callback) { return this.action(device, Action_1.Action.IncreaseSpeed, callback, { argument: 1 }); } decreaseSpeed(device, callback) { return this.action(device, Action_1.Action.DecreaseSpeed, callback, { argument: 1 }); } // State updateState(device, state, callback) { return this.request(HTTPMethod.PATCH, this.uri.state(device.id), state) .then(() => { callback(null); }) .catch((error) => { callback(Error(error)); }); } // PATCH: Toggle state property for a device toggleState(device, property, callback) { return this.getState(device.id) .then(state => { if (property !== 'open' && property !== 'power' && property !== 'light') { callback(null); throw Error(`This device does not have ${property} in it's Bond state`); } if (state[property] !== undefined) { const newState = {}; newState[property] = state[property] === 1 ? 0 : 1; return this.updateState(device, newState, callback); } else { callback(null); throw Error(`This device does not have ${property} in it's Bond state`); } }); } // Commands getCommands(deviceId) { return this.getCommandIds(deviceId).then(ids => { const ps = []; ids.forEach(id => { ps.push(this.getCommand(deviceId, id)); }); return Promise.all(ps); }); } getCommandIds(id) { const req = this.request(HTTPMethod.GET, this.uri.commands(id)); return req.then(json => Object.keys(json).filter(x => { // Ignore anything that is an empty string or starts with underscores return x.length > 0 && !/^_+/.test(x); })); } getCommand(deviceId, commandId) { return this.request(HTTPMethod.GET, this.uri.command(deviceId, commandId)); } // Properties getProperties(id) { return this.request(HTTPMethod.GET, this.uri.properties(id)); } // Helpers ping() { const uuid = intformat(flakeIdGen.next(), 'hex', { prefix: '18', padstr: '0', size: 16 }); // avoid duplicate action const bondUuid = uuid.substring(0, 13) + uuid.substring(15); // remove '00' used for datacenter/worker in flakeIdGen return (0, axios_1.default)({ method: HTTPMethod.GET, url: this.uri.deviceIds(), headers: { 'BOND-Token': this.bondToken, 'Bond-UUID': bondUuid, }, timeout: 2000, }); } request(method, uri, body = {}) { const bodyStr = JSON.stringify(body); const uuid = intformat(flakeIdGen.next(), 'hex', { prefix: '18', padstr: '0', size: 16 }); // avoid duplicate action const bondUuid = uuid.substring(0, 13) + uuid.substring(15); // remove '00' used for datacenter/worker in flakeIdGen if (bodyStr !== '{}') { this.platform.log.debug(`Request (${bondUuid}) [${method} ${uri}] - body: ${bodyStr}`); } else { this.platform.log.debug(`Request (${bondUuid}) [${method} ${uri}]`); } return (0, axios_1.default)({ method, url: uri, headers: { 'BOND-Token': this.bondToken, 'Bond-UUID': bondUuid, }, data: body, timeout: 10000, }) .then(response => { this.platform.log.debug(`Response (${bondUuid}) [${method} ${uri}] - ${JSON.stringify(response.data)}`); return response.data; }) .catch((error) => { if (axios_1.default.isAxiosError(error) && error.response) { const response = error.response; switch (response.status) { case 401: this.platform.log.error('Unauthorized. Please check the `token` in your config to see if it is correct.'); return; default: this.platform.log.error(`A request error occurred: [status] ${response.status} [statusText] ${response.statusText}`); } } else { this.platform.log.error(`A request error occurred: ${JSON.stringify(error)}`); } }); } } exports.BondApi = BondApi;