homebridge-bond
Version:
A homebridge plugin to control your Bond devices over the v2 API.
314 lines (313 loc) • 12.3 kB
JavaScript
;
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;