homebridge-smartsystem
Version:
SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)
339 lines (294 loc) • 10.2 kB
text/typescript
import { err, log } from "../duotecno/logger";
import { Unit } from "../duotecno/protocol";
import { ContentType, httpRequest } from "./HA-API";
export interface HBAccessory {
name: string,
manufacturer: string,
id: string,
type: string,
service: Array<string>
// Dimmers: [On, Brightness],
// Switch: [On],
// WindowCovering: [TargetPosition, PositionState, ObstructionDetected, CurrentPosition]
}
let gAccessories: Array<HBAccessory> = [];
let timer = null;
export function hbName(id: string): string {
return gAccessories.find(item => item.id === id)?.name || "Unknown";
}
export function hbService(id: string): Array<string> {
return gAccessories.find(item => item.id === id)?.service || [];
}
export async function hbAccessories(server: string, token: string): Promise<Array<HBAccessory>> {
if (gAccessories.length === 0) {
const list = await httpRequest(server+'/api/accessories', 'GET', "", ContentType.json, {
Authorization: 'Bearer ' + token
});
gAccessories = list
.filter(item =>
item.accessoryInformation.Manufacturer != "homebridge.io")
.map(item => ({
name: item.accessoryInformation.Name,
manufacturer: item.accessoryInformation.Manufacturer,
id: item.uniqueId,
type: item.type,
service: Object.keys(item.values) // ["On", "Brightness"] // TargetPosition
})
);
}
return gAccessories;
// test list
// return [{name: "Somfy 1", manufacturer: "Tahoma", id: "somfy1"},
// {name: "Somfy 2", manufacturer: "Tahoma", id: "somfy2"},
// {name: "Shelly 1", manufacturer: "Shelly", id: "shelly1"},
// {name: "Shell 2", manufacturer: "Shelly", id: "shelly2"},
// ];
}
/*
const getAccessories: [
{
"name": "Living",
"manufacturer": "Somfy",
"id": "06e5b66f78fe0136ea075e742a7fb388587329a4717393b21aaa180083305704",
"type": "WindowCovering",
"service": [
"CurrentPosition",
"PositionState",
"TargetPosition"
]
},
{
"name": "Tim Klein",
"manufacturer": "Somfy",
"id": "986b901a30d92cb7bd0205be90f65786c2d87c9a95d8a0a60cb4c048deda5ce6",
"type": "WindowCovering",
"service": [
"CurrentPosition",
"PositionState",
"TargetPosition",
"ObstructionDetected"
]
},
{
"name": "Keuken",
"manufacturer": "Somfy",
"id": "f18e175bdc561a8e2318662ebd225c21338623668152b23bf383849b085f340b",
"type": "WindowCovering",
"service": [
"CurrentPosition",
"PositionState",
"TargetPosition"
]
},
{
"name": "Connectivity kit-ledBox",
"manufacturer": "Somfy",
"id": "04b9348dda8ef417605a8661c2850721975cfcbd1398c6425ab9824ec238bc96",
"type": "Lightbulb",
"service": [
"On",
"Brightness"
]
},
{
"name": "Tim Groot",
"manufacturer": "Somfy",
"id": "81892c3728bb9e401c915ec4be9cb6cff2de98a27b0573f8396b94d386db99b0",
"type": "WindowCovering",
"service": [
"CurrentPosition",
"PositionState",
"TargetPosition",
"ObstructionDetected"
]
},
{
"name": "Bottom-Left",
"manufacturer": "Duotecno-Coppieters",
"id": "5f7746ab9fdcab159ee4806750bfcac5539df233e9dafe7ed1a568deecceb8ca",
"type": "Switch",
"service": [
"On"
]
},
{
"name": "Somfy Dimmer",
"manufacturer": "Duotecno-Coppieters",
"id": "da32bb657ad68d3fd63004015406e0cbde28355fc950204cec2fcd90d835e044",
"type": "Lightbulb",
"service": [
"On",
"Brightness"
]
},
{
"name": "Alles uitje",
"manufacturer": "Duotecno-Coppieters",
"id": "f0645ea91ad7730f9c9115364ad8c3a2bd58d3fdce7bfcf00e84a7cc1017c217",
"type": "Switch",
"service": [
"On"
]
},
{
"name": "Alles aantje",
"manufacturer": "Duotecno-Coppieters",
"id": "cdcab0c257fcd190e31d3cc736419f9fc32c4ff6e406e2d4d2cc51727d323fe9",
"type": "Switch",
"service": [
"On"
]
}
]
*/
//
// Get a token for the Homebridge API
//
export async function hbLogin(server: string, username: string, password: string): Promise<string> {
if (server.indexOf("://") === -1) server = "http://" + server;
const header = {"Content-Type": "application/json", "accept": "*/*"};
const data = {username, password};
const respBody = await httpRequest(server + "/api/auth/login", 'POST', JSON.stringify(data), ContentType.json, JSON.stringify(header));
if (!respBody.access_token) {
err("hab", "hbLogin: failed -> " + JSON.stringify(respBody));
return "";
} else {
return respBody.access_token
}
}
function hbLoad(value: number | boolean, service: Array<string>): Record<string, number | boolean> {
function hbvalue(value: number | boolean): number {
if (typeof value === "boolean") {
return (value) ? 100 : 0;
} else if (typeof value === "number") {
value = Math.round(value);
if (value <= 1) {
return 0;
} else if (value >= 99) {
return 100;
} else {
return value;
}
}
}
const data = {};
if (service.includes("On")) {
data["value"] = value;
data["characteristicType"] = "On";
}
if (service.includes("Brightness")) {
data["value"] = hbvalue(value);
data["characteristicType"] = "Brightness";
}
if (service.includes("TargetPosition")) {
data["value"] = hbvalue(value);
data["characteristicType"] = "TargetPosition";
};
return data;
}
export function hb2dtvalue(unit: Unit, value: number | boolean): {dtValue: number, dtTarget: number} {
let dtTarget;
let dtValue;
if (typeof value === "boolean") value = (value) ? 99 : 1;
value = Math.round(value);
if (unit.isUpDown()) {
dtTarget = (value >= 50) ? 4 : 5; // 4 = up, 5 = down
dtValue = (value >= 50) ? 2 : 1; // 2 = idle-up, 1 = idle-down
}
if (unit.isDimmer()) dtTarget = dtValue = (value <= 1) ? 1 : (value >= 99) ? 100 : value;
if (unit.isSwitch() || unit.isMood()) dtTarget = dtValue = (value > 50) ? 1 : 0;
return {dtTarget, dtValue};
}
export function dt2hbvalue(unit: Unit, dontChangeValue: number, min: number, max: number): number {
// tunr into (rounded) number
let value = (typeof unit.value === "boolean") ? (unit.value ? max : min) : Math.round(unit.value);
// Virtual motor: state = 0 -> stop, state = 1 -> up, state = 2 -> down
// Real motor: 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
if (unit.isUpDown()) {
// don't send status of real motor
return (value == 10) ? -1 : // stop [ virtual ]
(value == 11) ? max : // up [ virtual ]
(value == 12) ? min : undefined; // down [ virtual ]
// return ((value === 1) || (value === 3)) ? min : // down [ motor ]
// ((value === 2) || (value === 4)) ? max : // up [ motor ]
// (value == 0) ? dontChangeValue : // no change [ motor ]
// (value == 10) ? -1 : // stop [ virtual ]
// (value == 11) ? max : min; // up/down [ virtual ]
} else if (unit.isDimmer()) {
return (value <= 1) ? 0 : (value >= 99) ? 100 : value;
} else if (unit.isSwitch() || unit.isMood()) {
return (value) ? max : min;
} else {
return value;
}
}
function hbValue(data: Record<string, any>, service: Array<string>): number {
if (service.includes("Brightness")) {
return parseInt(data["Brightness"]);
}
if (service.includes("On")) {
return parseInt(data["On"])
}
if (service.includes("TargetPosition")) {
return parseInt(data["TargetPosition"]);
}
return undefined
}
// send a status change to HB Accessory
export async function hbSetState(server: string, id: string, service: Array<string>, value: number | boolean, token: string) : Promise<void> {
const data = hbLoad(value, service);
log("HB", "hbSetState: " + id + " -> " + JSON.stringify(data));
const respBody = await httpRequest(server + "/api/accessories/"+id, 'PUT', JSON.stringify(data), ContentType.json, {
Authorization: 'Bearer ' + token
});
if (respBody && respBody.error) {
log("HB", "hbSetState: " + id + " -> error: " + JSON.stringify(respBody))
}
}
// send a status change to HB Accessory
export async function hbGetValue(server: string, id: string, service: Array<string>, token: string) : Promise<number> {
const respBody = await httpRequest(server + "/api/accessories/"+id, 'GET', "", ContentType.json, {
Authorization: 'Bearer ' + token
});
if (respBody && respBody.error) {
log("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
return undefined;
} else if (respBody && respBody.values) {
return hbValue(respBody.values, service);
} else {
log("HB", "hbGetState: " + id + " -> no values?? : " + JSON.stringify(respBody));
return undefined;
};
}
export async function hbStop(server: string, id: string, service: Array<string>, token: string) : Promise<number | undefined> {
// does not exist, but we'll do a getCurrentPosition + setTargetPosition to this value
const respBody = await httpRequest(server + "/api/accessories/"+id, 'GET', "", ContentType.json, {
Authorization: 'Bearer ' + token
});
if (respBody && respBody.error) {
log("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
return undefined;
} else if (respBody && respBody.values) {
log("HB", "hbStop GetState: " + id + " -> " + JSON.stringify(respBody.values));
const data = {value: respBody.values.CurrentPosition, characteristicType: "TargetPosition"};
log("HB", "hbStop SetState: " + id + " -> " + JSON.stringify(data));
await httpRequest(server + "/api/accessories/"+id, 'PUT', JSON.stringify(data), ContentType.json, {
Authorization: 'Bearer ' + token
});
return hbValue(respBody.values, service);
}
}
export function hbSubscribe(callback) {
if (timer) {
clearInterval(timer);
}
timer = setInterval(() => {
callback()
}, 10000);
}
export function hbUnsubscribe() {
if (timer) {
clearInterval(timer);
timer = null;
}
}