homebridge-elero-stick
Version:
Elero funkstick for homebridge: https://github.com/ma-ku/homebridge-elero-stick
205 lines • 9.12 kB
JavaScript
"use strict";
const elero_shutter_accessory_1 = require("./elero-shutter-accessory");
const elero_stick_1 = require("./usb/elero-stick");
const perf_hooks_1 = require("perf_hooks");
const PluginName = "homebridge-elero-stick";
const PLATFORM_NAME = "EleroStick";
const DEFAULT_UPDATEINTERVAL = 5000;
const DEFAULT_MOVINGUPDATEINTERVAL = 1500;
let hap;
class EleroStickPlatform {
constructor(log, config, api) {
this.eleroAccessories = new Map();
this.channelIds = [];
this.defaultUpdateInterval = 5000;
this.movingUpdateInterval = 1500;
this.updateInterval = 5000;
this.hasCallback = false;
this.log = log;
this.api = api;
// Dummy-Callback
this.accessoriesCallback = this.dummy;
this.config = this.checkConfig(config);
this.name = this.config['name'] || "";
this.port = this.config['port'];
// We will request an update from the stick every 5 seconds
this.defaultUpdateInterval = this.config["updateInterval"] || 5000;
this.movingUpdateInterval = this.config["movingUpdateInterval"] || 1500;
this.updateInterval = this.defaultUpdateInterval;
this.platformConfig = {
defaultUpdateInterval: this.defaultUpdateInterval,
movingUpdateInterval: this.movingUpdateInterval
};
this.lastStatusTimestamp = perf_hooks_1.performance.now();
// This will store the actual platformAccessory instances, indexed by their uuid
// this.accessories = {};
var stick = this;
this.serialConnection = new elero_stick_1.EleroStick(this.port, (config.debugSerial ? this.log : undefined));
this.serialConnection.sendInterval = this.config.sendInterval || 250;
if (api) {
this.api = api;
this.api.on('didFinishLaunching', () => {
this.log.info("Requesting learned channels from stick");
this.serialConnection.on('connect', (channels) => {
this.registerChannels(channels);
});
this.serialConnection.on('status', (channel, state) => {
stick.processState(channel, state);
});
this.serialConnection.easyCheck();
this.checkChannelStates();
});
}
}
/**
* This method is called to retrieve all accessories exposed by the platform.
* The Platform can delay the response my invoking the callback at a later time,
* it will delay the bridge startup though, so keep it to a minimum.
* The set of exposed accessories CANNOT change over the lifetime of the plugin!
*/
accessories(callback) {
this.accessoriesCallback = callback;
this.hasCallback = true;
this.publishAccessories();
}
dummy(foundAccessories) {
}
publishAccessories() {
if ((this.hasCallback) && (this.eleroAccessories.size > 0)) {
this.log.debug('>> Request for accessories');
this.log.debug('>> # of Elero accessories is ' + this.eleroAccessories.size);
let accessories = [];
this.eleroAccessories.forEach(value => {
accessories.push(value);
});
this.accessoriesCallback(accessories);
}
}
checkConfig(config) {
this.log.debug("User-Config:\n" + JSON.stringify(config));
let result = {
name: config['name'] || 'EleroStick',
port: config['port'] || '/dev/ttyUSB0',
debugSerial: config['debugSerial'],
updateInterval: config['updateInterval'] || DEFAULT_UPDATEINTERVAL,
movingUpdateInterval: config['movingUpdateInterval'] || DEFAULT_MOVINGUPDATEINTERVAL,
sendInterval: config['sendInterval'] || 250,
motors: {}
};
config.motors = config.motors || [];
config.motors.forEach((userConfig) => {
this.log.debug("Motor-User-Config:\n" + JSON.stringify(userConfig));
let motorConfig = {
type: userConfig['type'] || 'shutter',
channel: userConfig['channel'],
name: userConfig['name'] || "Channel " + userConfig['channel'],
displayName: userConfig['displayName'] || "Channel " + userConfig['channel'],
duration: userConfig['duration'] || 10000,
disabled: userConfig['disabled'] || false,
reverse: userConfig['reverse'] || false,
stopButton: userConfig['stopButton'] || false,
ventilationPositionButton: userConfig['ventilationPositionButton'] || false,
intermediatePositionButton: userConfig['intermediatePositionButton'] || false,
};
result.motors[motorConfig.channel] = motorConfig;
});
this.log.debug("Result-Config:\n" + JSON.stringify(result));
return result;
}
/**
* This is a callback from an easyInfo call to the EleroStick. Since other than described in the documentation,
* the stick is not requesting the state for each channel sent in the command, we are requesting the state
* individually for each channel and thus receive multiple responses.
*
* @param {int} channel
* @param {int} state
*/
processState(channel, state) {
var newTimestamp = perf_hooks_1.performance.now();
var newInterval = this.updateInterval;
this.eleroAccessories.forEach(accessory => {
if (accessory.channel == channel) {
accessory.processState(state, newTimestamp);
}
});
// Find minimum monitoring time
newInterval = this.defaultUpdateInterval;
this.eleroAccessories.forEach(accessory => {
newInterval = Math.min(newInterval, accessory.reportingInterval);
});
this.updateInterval = newInterval;
this.lastStatusTimestamp = newTimestamp;
}
channelUUID(channel) {
return hap.uuid.generate(this.port + ":" + channel);
}
checkChannelStates() {
var stick = this;
if (isNaN(this.updateInterval)) {
this.updateInterval = this.defaultUpdateInterval;
}
this.log.debug('checkChannelStates (%s ms)', this.updateInterval);
stick.serialConnection.easyInfo(stick.channelIds);
setTimeout(function () {
stick.checkChannelStates();
}, Math.max(500, this.updateInterval));
}
/**
* Receive the learned channels from the linked Elero Stick
* and creates the corresponding accessory for it, if not already
* defined. Furthermore will all registered accessories checked
* if they are still attached to a learned channel and eventually
* will get set to unreachable.
*
* @param {int[]} channels
*/
registerChannels(channels) {
this.log.info("Stick reported channels: ", channels);
this.channelIds = channels;
var stick = this;
// For each channel we will generate the corresponding EleroChannel that is
// then linked with a matching implementation (for now only Shutters)
channels.forEach((channel) => {
// Do we already have an accessory for this?
var uuid = stick.channelUUID(channel);
// Check if we have a corresponding accessory configuration
// for that channel that we can use to complement the accessory
// configuration
var channelConfig = Object.assign({
type: 'shutter',
channel: channel,
name: 'Channel ' + channel,
disabled: false
}, stick.config.motors[channel]);
// If this accessory is new, we will create a new instance
var accessory = undefined;
if (!(channelConfig.disabled || false)) {
switch (channelConfig.type) {
case 'shades':
case 'shutter':
case 'lights':
case 'heating':
accessory = new elero_shutter_accessory_1.EleroShutterAccessory(this.api.hap, this.log, this.platformConfig, channelConfig, uuid, this.serialConnection, channel);
break;
default:
stick.log.error("Cannot create accessory of type '%s' for channel: %s", channelConfig.type, channel);
}
if (accessory) {
this.eleroAccessories.set(accessory.uuid, accessory);
}
else {
stick.log.error("Cannot add accessory for channel: %s", channel);
}
}
else {
stick.log.info("Channel: %s is disabled, no accessory will get created", channel);
}
});
this.publishAccessories();
}
}
module.exports = (api) => {
hap = api.hap;
api.registerPlatform(PLATFORM_NAME, EleroStickPlatform);
};
//# sourceMappingURL=elero-stick-platform.js.map