2
0

doc(main): update documentation and flows

This commit is contained in:
Pierre CLEMENT
2018-03-21 23:29:36 +01:00
parent c850ca9536
commit d04f5c1196
65 changed files with 2990 additions and 4 deletions

113
dist/devices/gateway/Gateway.js vendored Normal file
View File

@@ -0,0 +1,113 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events = require("events");
const crypto = require("crypto");
const GatewayServer_1 = require("./GatewayServer");
const _1 = require("./");
const Color_1 = require("../../utils/Color");
class Gateway extends events.EventEmitter {
constructor(sid, ip) {
super();
this.sid = sid;
this.ip = ip;
this._subdevices = {};
}
set password(password) {
this._password = password;
}
get key() {
if (!this.lastToken || !this._password)
return null;
var cipher = crypto.createCipheriv('aes-128-cbc', this._password, Gateway.iv);
var key = cipher.update(Buffer.from(this.lastToken), "ascii", "hex");
cipher.final('hex');
return key;
}
handleMessage(msg) {
if (msg.data) {
if (msg.model === "gateway" && msg.sid === this.sid && msg.token) {
this.lastToken = msg.token;
}
}
if (msg.isGetIdListAck()) {
msg.data.forEach((sid) => {
this.read(sid);
});
}
if (msg.isReadAck() || msg.isReport()) {
if (!this._subdevices[msg.sid]) {
for (let SubDeviceClass of [_1.Magnet, _1.Motion, _1.Switch, _1.Weather]) {
if (SubDeviceClass.acceptedModels.indexOf(msg.model) >= 0) {
this._subdevices[msg.sid] = new SubDeviceClass(msg.sid, msg.model);
this._subdevices[msg.sid].on('values-updated', (sidOrMessage) => {
this.emit("subdevice-values-updated", sidOrMessage);
});
this.emit("subdevice-found", msg.sid);
}
}
}
if (this._subdevices[msg.sid]) {
this._subdevices[msg.sid].handleMessage(msg);
}
}
}
getSubdevice(sid) {
return this._subdevices[sid] || null;
}
hasSubdevice(sid) {
return !!this._subdevices[sid];
}
getIdList() {
this.send({
cmd: "get_id_list",
});
}
read(sid) {
this.send({ cmd: "read", sid: sid || this.sid });
}
setLight(brightness, rgb) {
this.send({
cmd: "write",
data: {
rgb: Color_1.Color.toValue(rgb.red, rgb.green, rgb.blue, brightness),
sid: this.sid
}
});
}
playSound(musicId, volume) {
this.send({
cmd: "write",
data: {
mid: musicId,
volume: volume,
sid: this.sid
}
});
}
send(message) {
let msg = Object.assign({}, message.payload || message);
if (msg.cmd) {
msg.sid = message.sid || this.sid;
if (msg.gateway) {
delete msg.gateway;
}
if (msg.cmd === "write") {
msg.data.key = this.key;
}
GatewayServer_1.GatewayServer.getInstance().sendToGateway(this.sid, msg);
}
}
get subdevices() {
return this._subdevices;
}
toJSON() {
return {
sid: this.sid,
ip: this.ip,
key: this.password,
subdevices: this.subdevices
};
}
}
Gateway.iv = Buffer.from([0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e]);
exports.Gateway = Gateway;

30
dist/devices/gateway/GatewayMessage.js vendored Normal file
View File

@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class GatewayMessage {
constructor(raw) {
Object.assign(this, raw);
if (raw.port) {
this.port = parseInt(raw.port);
}
if (raw.data) {
this.data = JSON.parse(raw.data) || raw.data;
}
this.timestamp = +new Date;
}
isHeartbeat() {
return this.cmd === "heartbeat";
}
isIam() {
return this.cmd === "iam";
}
isGetIdListAck() {
return this.cmd === "get_id_list_ack";
}
isReadAck() {
return this.cmd === "read_ack";
}
isReport() {
return this.cmd === "report";
}
}
exports.GatewayMessage = GatewayMessage;

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

121
dist/devices/gateway/GatewayServer.js vendored Normal file
View File

@@ -0,0 +1,121 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events = require("events");
const dgram = require("dgram");
const _1 = require("./");
const GatewayMessage_1 = require("./GatewayMessage");
class GatewayServer extends events.EventEmitter {
constructor() {
super(...arguments);
this._gateways = {};
this._gatewaysPing = {};
}
static getInstance() {
if (!this.instance) {
this.instance = new GatewayServer();
}
return this.instance;
}
discover(ipv = 4) {
if (this.server) {
return;
}
this.server = dgram.createSocket({
type: `udp${ipv}`,
reuseAddr: true
});
this.server.on('listening', () => {
var address = this.server.address();
//this.log(RED._("udp.status.listener-at",{host:address.address,port:address.port}));
this.server.setBroadcast(true);
try {
this.server.setMulticastTTL(128);
this.server.addMembership(GatewayServer.MULTICAST_ADDRESS, null);
}
catch (e) {
/*if (e.errno == "EINVAL") {
this.error(RED._("udp.errors.bad-mcaddress"));
} else if (e.errno == "ENODEV") {
this.error(RED._("udp.errors.interface"));
} else {
this.error(RED._("udp.errors.error",{error:e.errno}));
}*/
}
});
this.server.on("error", (err) => {
/*if ((err.code == "EACCES") && (this.port < 1024)) {
this.error(RED._("udp.errors.access-error"));
} else {
this.error(RED._("udp.errors.error",{error:err.code}));
}*/
this.server.close();
delete this.server;
});
this.server.on('message', (message, remote) => {
let msg = new GatewayMessage_1.GatewayMessage(JSON.parse(message.toString('utf8')));
//console.log(msg);
let gatewaySid = null;
if ((msg.isHeartbeat() || msg.isIam()) && msg.model === "gateway") {
if (!this._gateways[msg.sid]) {
this._gateways[msg.sid] = new _1.Gateway(msg.sid, remote.address);
this._gateways[msg.sid].getIdList();
this.emit("gateway-online", msg.sid);
}
else {
// Any IP update?
this._gateways[msg.sid].ip = remote.address;
}
if (this._gatewaysPing[msg.sid]) {
clearTimeout(this._gatewaysPing[msg.sid]);
delete this._gatewaysPing[msg.sid];
}
// Consider the gateway as unreachable after 2 heartbeats missed (1 heartbeat every 10s)
this._gatewaysPing[msg.sid] = setTimeout(() => {
this.emit("gateway-offline", msg.sid);
delete this._gateways[msg.sid];
}, 25 * 1000);
gatewaySid = msg.sid;
}
if (!gatewaySid) {
gatewaySid = Object.keys(this._gateways).filter((gatewaySid) => this._gateways[gatewaySid].ip === remote.address)[0];
}
gatewaySid && this._gateways[gatewaySid] && this._gateways[gatewaySid].handleMessage(msg);
});
return new Promise((resolve, reject) => {
try {
this.server.bind(GatewayServer.SERVER_PORT, null);
let msg = Buffer.from(JSON.stringify({ cmd: "whois" }));
this.server.send(msg, 0, msg.length, GatewayServer.MULTICAST_PORT, GatewayServer.MULTICAST_ADDRESS);
resolve(this.server);
}
catch (e) {
reject();
}
});
}
stop() {
if (this.server) {
this.server.close();
delete this.server;
}
}
getGateway(sid) {
return this._gateways[sid] || null;
}
hasGateway(sid) {
return !!this._gateways[sid];
}
get gateways() {
return this._gateways;
}
sendToGateway(sid, message) {
if (this.server && this._gateways[sid]) {
let msg = Buffer.from(JSON.stringify(message));
this.server.send(msg, 0, msg.length, GatewayServer.SERVER_PORT, this._gateways[sid].ip);
}
}
}
GatewayServer.MULTICAST_ADDRESS = '224.0.0.50';
GatewayServer.MULTICAST_PORT = 4321;
GatewayServer.SERVER_PORT = 9898;
exports.GatewayServer = GatewayServer;

View File

@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events = require("events");
class GatewaySubdevice extends events.EventEmitter {
constructor(sid, model) {
super();
this.sid = sid;
this.model = model;
}
get batteryLevel() {
/*
When full, CR2032 batteries are between 3 and 3.4V
http://farnell.com/datasheets/1496885.pdf
*/
return this.voltage ? Math.min(Math.round((this.voltage - 2200) / 10), 100) : -1;
}
handleMessage(msg) {
if (msg.data.voltage) {
this.voltage = msg.data.voltage;
}
this.message = msg;
}
static get acceptedModels() {
return [];
}
;
toJSON() {
let json = {};
for (let prop of Object.keys(this)) {
json[prop] = this[prop];
}
delete json._events;
delete json._eventsCount;
delete json._maxListeners;
json.batteryLevel = this.batteryLevel;
json.internalModel = this.internalModel;
return json;
}
}
exports.GatewaySubdevice = GatewaySubdevice;

32
dist/devices/gateway/Magnet.js vendored Normal file
View File

@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const GatewaySubdevice_1 = require("./GatewaySubdevice");
class Magnet extends GatewaySubdevice_1.GatewaySubdevice {
static get acceptedModels() {
return ['magnet', 'sensor_magnet.aq2'];
}
get internalModel() {
return 'mi.magnet';
}
isClosed() {
return this.status === "close";
}
isOpened() {
return this.status === "open";
}
isUnkownState() {
return this.status === "unkown";
}
handleMessage(msg) {
super.handleMessage(msg);
if (msg.isReadAck() || msg.isReport()) {
let data = msg.data;
// mintime
if (this.status !== data.status) {
this.status = data.status;
this.emit('values-updated', this.sid);
}
}
}
}
exports.Magnet = Magnet;

29
dist/devices/gateway/Motion.js vendored Normal file
View File

@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const GatewaySubdevice_1 = require("./GatewaySubdevice");
class Motion extends GatewaySubdevice_1.GatewaySubdevice {
constructor() {
super(...arguments);
this.lux = 0;
}
static get acceptedModels() {
return ['motion', 'sensor_motion.aq2'];
}
get internalModel() {
return 'mi.motion';
}
handleMessage(msg) {
super.handleMessage(msg);
if (msg.isReadAck() || msg.isReport()) {
let data = msg.data;
if (data.lux) {
this.lux = parseInt(data.lux);
}
if (data.status === "motion") {
this.lastMotionTimestamp = data.timestamp;
this.emit('values-updated', { sid: this.sid, data: { hasMotion: true } });
}
}
}
}
exports.Motion = Motion;

15
dist/devices/gateway/Switch.js vendored Normal file
View File

@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const GatewaySubdevice_1 = require("./GatewaySubdevice");
class Switch extends GatewaySubdevice_1.GatewaySubdevice {
static get acceptedModels() {
return ['switch', 'sensor_switch.aq2'];
}
get internalModel() {
return 'mi.switch';
}
handleMessage(msg) {
super.handleMessage(msg);
}
}
exports.Switch = Switch;

36
dist/devices/gateway/Weather.js vendored Normal file
View File

@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const GatewaySubdevice_1 = require("./GatewaySubdevice");
class Weather extends GatewaySubdevice_1.GatewaySubdevice {
static get acceptedModels() {
return ['sensor_ht', 'weather.v1'];
}
get internalModel() {
return 'mi.weather';
}
get temperatureInDegrees() {
return this.temperature / 100;
}
get humidityInPercent() {
return this.humidity / 100;
}
get pressureInBar() {
return this.pressure / 100000;
}
get pressureInhPa() {
return this.pressure / 100;
}
handleMessage(msg) {
super.handleMessage(msg);
if (msg.isReadAck() || msg.isReport()) {
let data = msg.data;
['temperature', 'humidity', 'pressure'].forEach((dataType) => {
if (data[dataType]) {
this[dataType] = parseInt(data[dataType]);
}
});
this.emit('values-updated', this.sid);
}
}
}
exports.Weather = Weather;

14
dist/devices/gateway/index.js vendored Normal file
View File

@@ -0,0 +1,14 @@
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./"));
__export(require("./Gateway"));
__export(require("./GatewayMessage"));
__export(require("./GatewayServer"));
__export(require("./GatewaySubdevice"));
__export(require("./Magnet"));
__export(require("./Motion"));
__export(require("./Switch"));
__export(require("./Weather"));

45
dist/devices/yeelight/YeelightServer.js vendored Normal file
View File

@@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events = require("events");
const YeelightSearch = require("yeelight-wifi");
class YeelightServer extends events.EventEmitter {
constructor() {
super(...arguments);
this._bulbs = {};
this._bulbsJson = {};
}
static getInstance() {
if (!this.instance) {
this.instance = new YeelightServer();
}
return this.instance;
}
get bulbs() {
return this._bulbsJson;
}
getBulb(sid) {
return this._bulbs[sid];
}
discover() {
new Promise(() => {
(new YeelightSearch()).on('found', (bulb) => {
bulb.sid = parseInt(bulb.id);
if (!this._bulbs[bulb.sid]) {
this._bulbs[bulb.sid] = bulb;
this._bulbsJson[bulb.sid] = YeelightServer.bulbToJSON(bulb);
this.emit("yeelight-online", bulb.sid);
}
});
});
// TODO: disconected ?
}
static bulbToJSON(bulb) {
return {
sid: bulb.sid,
ip: bulb.hostname,
name: bulb.name,
model: bulb.model
};
}
}
exports.YeelightServer = YeelightServer;

0
dist/devices/yeelight/index.js vendored Normal file
View File