UNPKG

homebridge-smartsystem

Version:

SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)

339 lines (294 loc) 10.2 kB
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; } }