UNPKG

freeathome-local-api-client

Version:

A client library for the BUSCH-JAEGER free@home local API implemented in TypeScript

731 lines (711 loc) 24.7 kB
// src/model/validation/channel-ti.ts import { iface, indexKey, opt } from "ts-interface-checker"; var Channel = iface([], { displayName: opt("string"), functionID: opt("string"), room: opt("string"), floor: opt("string"), inputs: opt( iface([], { [indexKey]: "InOutPut" }) ), outputs: opt( iface([], { [indexKey]: "InOutPut" }) ), parameters: opt( iface([], { [indexKey]: "string" }) ), type: opt("string") }); var ChannelTypeSuite = { Channel }; // src/model/validation/configuration-ti.ts import { iface as iface2, indexKey as indexKey2 } from "ts-interface-checker"; var Configuration = iface2([], { [indexKey2]: "SysAP" }); var ConfigurationTypeSuite = { Configuration }; // src/model/validation/device-ti.ts import { iface as iface3, indexKey as indexKey3, opt as opt2 } from "ts-interface-checker"; var Device = iface3([], { displayName: opt2("string"), room: opt2("string"), floor: opt2("string"), interface: opt2("string"), nativeId: opt2("string"), channels: opt2( iface3([], { [indexKey3]: "Channel" }) ), parameters: opt2( iface3([], { [indexKey3]: "string" }) ) }); var DeviceTypeSuite = { Device }; // src/model/validation/device-list-ti.ts import { array, iface as iface4, indexKey as indexKey4 } from "ts-interface-checker"; var DeviceList = iface4([], { [indexKey4]: array("string") }); var DeviceListTypeSuite = { DeviceList }; // src/model/validation/device-response-ti.ts import { iface as iface5, indexKey as indexKey5 } from "ts-interface-checker"; var DeviceResponse = iface5([], { [indexKey5]: iface5([], { devices: "Devices" }) }); var DeviceResponseTypeSuite = { DeviceResponse }; // src/model/validation/devices-ti.ts import { iface as iface6, indexKey as indexKey6 } from "ts-interface-checker"; var Devices = iface6([], { [indexKey6]: "Device" }); var DevicesTypeSuite = { Devices }; // src/model/validation/error-ti.ts import { iface as iface7 } from "ts-interface-checker"; var Error2 = iface7([], { code: "string", detail: "string", title: "string" }); var ErrorTypeSuite = { Error: Error2 }; // src/model/validation/floors-ti.ts import { iface as iface8, indexKey as indexKey7 } from "ts-interface-checker"; var Floors = iface8([], { [indexKey7]: iface8([], { name: "string", rooms: "Rooms" }) }); var FloorsTypeSuite = { Floors }; // src/model/validation/get-data-point-response-ti.ts import { array as array2, iface as iface9, indexKey as indexKey8 } from "ts-interface-checker"; var GetDataPointResponse = iface9([], { [indexKey8]: iface9([], { values: array2("string") }) }); var GetDataPointResponseTypeSuite = { GetDataPointResponse }; // src/model/validation/in-out-put-ti.ts import { iface as iface10, opt as opt3 } from "ts-interface-checker"; var InOutPut = iface10([], { value: opt3("string"), pairingID: opt3("number") }); var InOutPutTypeSuite = { InOutPut }; // src/model/validation/rooms-ti.ts import { iface as iface11, indexKey as indexKey9 } from "ts-interface-checker"; var Rooms = iface11([], { [indexKey9]: iface11([], { name: "string" }) }); var RoomsTypeSuite = { Rooms }; // src/model/validation/scenes-triggered-ti.ts import { iface as iface12, indexKey as indexKey10 } from "ts-interface-checker"; var ScenesTriggered = iface12([], { [indexKey10]: iface12([], { channels: iface12([], { [indexKey10]: iface12([], { outputs: iface12([], { [indexKey10]: iface12([], { value: "string", pairingID: "number" }) }) }) }) }) }); var ScenesTriggeredTypeSuite = { ScenesTriggered }; // src/model/validation/set-data-point-response-ti.ts import { iface as iface13, indexKey as indexKey11 } from "ts-interface-checker"; var SetDataPointResponse = iface13([], { [indexKey11]: iface13([], { [indexKey11]: "string" }) }); var SetDataPointResponseTypeSuite = { SetDataPointResponse }; // src/model/validation/sys-ap-ti.ts import { iface as iface14, opt as opt4 } from "ts-interface-checker"; var SysAP = iface14([], { devices: "Devices", floorplan: iface14([], { floors: "Floors" }), sysapName: "string", users: "Users", error: opt4("Error") }); var SysApTypeSuite = { SysAP }; // src/model/validation/users-ti.ts import { array as array3, iface as iface15, indexKey as indexKey12 } from "ts-interface-checker"; var Users = iface15([], { [indexKey12]: iface15([], { enabled: "boolean", flags: array3("string"), grantedPermissions: array3("string"), jid: "string", name: "string", requestedPermissions: array3("string"), role: "string" }) }); var UsersTypeSuite = { Users }; // src/model/validation/virtual-device-ti.ts import { array as array4, enumtype, iface as iface16, opt as opt5 } from "ts-interface-checker"; var VirtualDevice = iface16([], { type: "VirtualDeviceType", properties: opt5( iface16([], { ttl: opt5("string"), displayname: opt5("string"), flavor: opt5("string"), capabilities: opt5(array4("number")) }) ) }); var VirtualDeviceType = enumtype({ BinarySensor: "BinarySensor", BlindActuator: "BlindActuator", SwitchingActuator: "SwitchingActuator", CeilingFanActuator: "CeilingFanActuator", RTC: "RTC", DimActuator: "DimActuator", EVCharging: "evcharging", WindowSensor: "WindowSensor", SimpleDoorlock: "simple_doorlock", ShutterActuator: "ShutterActuator", WeatherStation: "WeatherStation", WeatherTemperatureSensor: "Weather-TemperatureSensor", WeatherWindSensor: "Weather-WindSensor", WeatherBrightnessSensor: "Weather-BrightnessSensor", WeatherRainSensor: "Weather-RainSensor", WindowActuator: "WindowActuator", CODetector: "CODetector", FireDetector: "FireDetector", KNXSwitchSensor: "KNX-SwitchSensor", MediaPlayer: "MediaPlayer", EnergyBattery: "EnergyBattery", EnergyInverter: "EnergyInverter", EnergyMeter: "EnergyMeter", EnergyInverterBattery: "EnergyInverterBattery", EnergyInverterMeter: "EnergyInverterMeter", EnergyInverterMeterBattery: "EnergyInverterMeterBattery", EnergyMeterBattery: "EnergyMeterBattery", AirQualityCO2: "AirQualityCO2", AirQualityCO: "AirQualityCO", AirQualityFull: "AirQualityFull", AirQualityHumidity: "AirQualityHumidity", AirQualityNO2: "AirQualityNO2", AirQualityO3: "AirQualityO3", AirQualityPM10: "AirQualityPM10", AirQualityPM25: "AirQualityPM25", AirQualityPressure: "AirQualityPressure", AirQualityTemperature: "AirQualityTemperature", AirQualityVOC: "AirQualityVOC", EnergyMeterV2: "EnergyMeterv2", HomeApplianceLaundry: "HomeAppliance-Laundry", HVAC: "HVAC", SplitUnit: "SplitUnit" }); var VirtualDeviceTypeSuite = { VirtualDevice, VirtualDeviceType }; // src/model/validation/virtual-device-response-ti.ts import { iface as iface17, indexKey as indexKey13 } from "ts-interface-checker"; var VirtualDeviceResponse = iface17([], { [indexKey13]: iface17([], { devices: iface17([], { [indexKey13]: iface17([], { serial: "string" }) }) }) }); var VirtualDeviceResponseTypeSuite = { VirtualDeviceResponse }; // src/model/validation/websocket-message-ti.ts import { array as array5, iface as iface18, indexKey as indexKey14, opt as opt6 } from "ts-interface-checker"; var WebSocketMessage = iface18([], { [indexKey14]: iface18([], { datapoints: iface18([], { [indexKey14]: "string" }), devices: "Devices", devicesAdded: array5("string"), devicesRemoved: array5("string"), scenesTriggered: "ScenesTriggered", parameters: opt6( iface18([], { [indexKey14]: "string" }) ) }) }); var WebSocketMessageTypeSuite = { WebSocketMessage }; // src/model/validator.ts import { createCheckers } from "ts-interface-checker"; var { Channel: Channel2 } = createCheckers(ChannelTypeSuite, InOutPutTypeSuite); var { Configuration: Configuration2 } = createCheckers( ConfigurationTypeSuite, DevicesTypeSuite, DeviceTypeSuite, ChannelTypeSuite, InOutPutTypeSuite, FloorsTypeSuite, RoomsTypeSuite, SysApTypeSuite, UsersTypeSuite, ErrorTypeSuite ); var { Device: Device2 } = createCheckers( DeviceTypeSuite, ChannelTypeSuite, InOutPutTypeSuite ); var { DeviceList: DeviceList2 } = createCheckers(DeviceListTypeSuite); var { DeviceResponse: DeviceResponse2 } = createCheckers( DeviceResponseTypeSuite, DevicesTypeSuite, DeviceTypeSuite, ChannelTypeSuite, InOutPutTypeSuite ); var { GetDataPointResponse: GetDataPointResponse2 } = createCheckers(GetDataPointResponseTypeSuite); var { SetDataPointResponse: SetDataPointResponse2 } = createCheckers(SetDataPointResponseTypeSuite); var { VirtualDevice: VirtualDevice2 } = createCheckers(VirtualDeviceTypeSuite); var { VirtualDeviceResponse: VirtualDeviceResponse2 } = createCheckers( VirtualDeviceResponseTypeSuite ); var { WebSocketMessage: WebSocketMessage2 } = createCheckers( WebSocketMessageTypeSuite, DevicesTypeSuite, DeviceTypeSuite, ChannelTypeSuite, InOutPutTypeSuite, ScenesTriggeredTypeSuite ); function check(obj, checker, logger, verbose) { if (verbose) { try { checker.check(obj); return true; } catch (error) { logger.error("Object validation failed!", error); return false; } } return checker.test(obj); } function isWebSocketMessage(obj, logger, verbose = false) { return check(obj, WebSocketMessage2, logger, verbose); } function isConfiguration(obj, logger, verbose = false) { return check(obj, Configuration2, logger, verbose); } function isDeviceList(obj, logger, verbose = false) { return check(obj, DeviceList2, logger, verbose); } function isDeviceResponse(obj, logger, verbose = false) { return check(obj, DeviceResponse2, logger, verbose); } function isGetDataPointResponse(obj, logger, verbose = false) { return check(obj, GetDataPointResponse2, logger, verbose); } function isSetDataPointResponse(obj, logger, verbose = false) { return check(obj, SetDataPointResponse2, logger, verbose); } function isVirtualDevice(obj, logger, verbose = false) { return check(obj, VirtualDevice2, logger, verbose); } function isVirtualDeviceResponse(obj, logger, verbose = false) { return check(obj, VirtualDeviceResponse2, logger, verbose); } function isChannel(obj, logger, verbose = false) { return check(obj, Channel2, logger, verbose); } function isDevice(obj, logger, verbose = false) { return check(obj, Device2, logger, verbose); } // src/model/virtual-device.ts var VirtualDeviceType2 = /* @__PURE__ */ ((VirtualDeviceType3) => { VirtualDeviceType3["BinarySensor"] = "BinarySensor"; VirtualDeviceType3["BlindActuator"] = "BlindActuator"; VirtualDeviceType3["SwitchingActuator"] = "SwitchingActuator"; VirtualDeviceType3["CeilingFanActuator"] = "CeilingFanActuator"; VirtualDeviceType3["RTC"] = "RTC"; VirtualDeviceType3["DimActuator"] = "DimActuator"; VirtualDeviceType3["EVCharging"] = "evcharging"; VirtualDeviceType3["WindowSensor"] = "WindowSensor"; VirtualDeviceType3["SimpleDoorlock"] = "simple_doorlock"; VirtualDeviceType3["ShutterActuator"] = "ShutterActuator"; VirtualDeviceType3["WeatherStation"] = "WeatherStation"; VirtualDeviceType3["WeatherTemperatureSensor"] = "Weather-TemperatureSensor"; VirtualDeviceType3["WeatherWindSensor"] = "Weather-WindSensor"; VirtualDeviceType3["WeatherBrightnessSensor"] = "Weather-BrightnessSensor"; VirtualDeviceType3["WeatherRainSensor"] = "Weather-RainSensor"; VirtualDeviceType3["WindowActuator"] = "WindowActuator"; VirtualDeviceType3["CODetector"] = "CODetector"; VirtualDeviceType3["FireDetector"] = "FireDetector"; VirtualDeviceType3["KNXSwitchSensor"] = "KNX-SwitchSensor"; VirtualDeviceType3["MediaPlayer"] = "MediaPlayer"; VirtualDeviceType3["EnergyBattery"] = "EnergyBattery"; VirtualDeviceType3["EnergyInverter"] = "EnergyInverter"; VirtualDeviceType3["EnergyMeter"] = "EnergyMeter"; VirtualDeviceType3["EnergyInverterBattery"] = "EnergyInverterBattery"; VirtualDeviceType3["EnergyInverterMeter"] = "EnergyInverterMeter"; VirtualDeviceType3["EnergyInverterMeterBattery"] = "EnergyInverterMeterBattery"; VirtualDeviceType3["EnergyMeterBattery"] = "EnergyMeterBattery"; VirtualDeviceType3["AirQualityCO2"] = "AirQualityCO2"; VirtualDeviceType3["AirQualityCO"] = "AirQualityCO"; VirtualDeviceType3["AirQualityFull"] = "AirQualityFull"; VirtualDeviceType3["AirQualityHumidity"] = "AirQualityHumidity"; VirtualDeviceType3["AirQualityNO2"] = "AirQualityNO2"; VirtualDeviceType3["AirQualityO3"] = "AirQualityO3"; VirtualDeviceType3["AirQualityPM10"] = "AirQualityPM10"; VirtualDeviceType3["AirQualityPM25"] = "AirQualityPM25"; VirtualDeviceType3["AirQualityPressure"] = "AirQualityPressure"; VirtualDeviceType3["AirQualityTemperature"] = "AirQualityTemperature"; VirtualDeviceType3["AirQualityVOC"] = "AirQualityVOC"; VirtualDeviceType3["EnergyMeterV2"] = "EnergyMeterv2"; VirtualDeviceType3["HomeApplianceLaundry"] = "HomeAppliance-Laundry"; VirtualDeviceType3["HVAC"] = "HVAC"; VirtualDeviceType3["SplitUnit"] = "SplitUnit"; return VirtualDeviceType3; })(VirtualDeviceType2 || {}); // src/system-access-point.ts import { Subject } from "rxjs"; import { WebSocket } from "ws"; import { EventEmitter } from "events"; var SystemAccessPoint = class extends EventEmitter { /** The basic authentication key used for requests. */ basicAuthKey; /** The host name of the system access point. */ hostName; /** Determines whether requests to the system access point will use TLS. */ tlsEnabled; logger; verboseErrors; webSocket; webSocketMessageSubject = new Subject(); /** * Constructs a new SystemAccessPoint instance * * @constructor * @param hostName {string} The system access point host name. * @param userName {string} The user name that shall be used to authenticate with the system access point. * @param password {string} The password that shall be used to authenticate with the system access point. * @param tlsEnabled {boolean} Determines whether the communication with the system access point shall be protected by TLS. * @param verboseErrors {boolean} Determines whether verbose error messages shall be used, for example for message validation. * @param logger {Logger} The logger instance to be used. If no explicit implementation is provided, the this.logger will be used for logging. */ constructor(hostName, userName, password, tlsEnabled = true, verboseErrors = false, logger) { super(); this.logger = logger ?? console; this.basicAuthKey = Buffer.from(`${userName}:${password}`, "utf8").toString( "base64" ); this.hostName = hostName; this.tlsEnabled = tlsEnabled; this.verboseErrors = verboseErrors; } /** * Connects to the System Access Point web socket. * @param certificateVerification {boolean} Determines whether the TLS certificate presented by the server will be verified. */ connectWebSocket(certificateVerification = true) { if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { throw new Error("Web socket is already connected"); } this.webSocket = this.createWebSocket(certificateVerification); } /** * Creates a new virtual device. * @param sysApUuid {string} The UUID identifying the system access point. * @param deviceSerial {string} The serial number to be assigned to the device. * @param virtualDevice {VirtualDevice} The virtual device to be created. * @returns {Promise.<VirtualDeviceResponse>} The response to the virtual device request. */ async createVirtualDevice(sysApUuid, deviceSerial, virtualDevice) { const response = await this.fetchDataViaRest( "PUT", `virtualdevice/${sysApUuid}/${deviceSerial}`, JSON.stringify(virtualDevice) ); return this.processRestResponse(response, isVirtualDeviceResponse); } createWebSocket(certificateVerification) { if (this.tlsEnabled && !certificateVerification) { this.logger.warn( "TLS certificate verification is disabled! This poses a security risk, activating certificate verification is strictly recommended." ); } const url = `${this.tlsEnabled ? "wss" : "ws"}://${this.hostName}/fhapi/v1/api/ws`; const options = { rejectUnauthorized: this.tlsEnabled && certificateVerification, headers: { Authorization: `Basic ${this.basicAuthKey}` } }; const webSocket = new WebSocket(url, options); webSocket.on("error", (error) => { this.emit("websocket-error", error); this.logger.error("Error received", error); }); webSocket.on("ping", (data) => { this.emit("websocket-ping", data); this.logger.debug("Ping received", data.toString("ascii")); }); webSocket.on("pong", (data) => { this.emit("websocket-pong", data); this.logger.debug("Pong received", data.toString("ascii")); }); webSocket.on("unexpected-response", (request, response) => { this.emit("websocket-unexpected-response", request, response); this.logger.error("Unexpected response received"); }); webSocket.on("upgrade", (request) => { this.emit("websocket-upgrade", request); this.logger.debug("Upgrade request received"); }); webSocket.on("open", () => { this.emit("websocket-open"); this.logger.log("Connection opened"); }); webSocket.on("close", (code, reason) => { this.emit("websocket-close", code, reason); this.logger.log("Connection closed"); }); webSocket.on("message", (data, isBinary) => { this.emit("websocket-message", data, isBinary); this.processWebSocketMessage(data, isBinary); }); return webSocket; } /** * Disconnects from the System Access Point web socket. * @param force {boolean} Determines whether or not the connection will be closed forcibly. */ disconnectWebSocket(force = false) { if (!this.webSocket || this.webSocket.readyState === WebSocket.CLOSED) { throw new Error("Web socket is not open"); } if (force) { this.webSocket.terminate(); } else { this.webSocket.close(); } } /** * Gets the configuration from the system access point. * @returns {Promise.<Configuration>} The system access point configuration. */ async getConfiguration() { const response = await this.fetchDataViaRest( "GET", "configuration" ); return this.processRestResponse(response, isConfiguration); } /** * Gets the device list from the system access point. * @returns {Promise.<DeviceList>} The requested device list. */ async getDeviceList() { const response = await this.fetchDataViaRest("GET", "devicelist"); return this.processRestResponse(response, isDeviceList); } /** * Gets the specified device from the system access point. * @param sysApUuid {string} The UUID identifying the system access point. * @param deviceSerial {string} The device serial number. * @returns {Promise.<DeviceResponse>} The response to the device request. */ async getDevice(sysApUuid, deviceSerial) { const response = await this.fetchDataViaRest( "GET", `device/${sysApUuid}/${deviceSerial}` ); return this.processRestResponse(response, isDeviceResponse); } /** * Gets the specified data point from the system access point. * @param sysApUuid {string} The UUID idenfifying the system access point. * @param deviceSerial {string} The device serial number. * @param channel {string} The channel identifier. * @param dataPoint {string} The datapoint identifier. * @returns {Promise.<GetDataPointResponse>} The response to the get data point request. */ async getDatapoint(sysApUuid, deviceSerial, channel, dataPoint) { const response = await this.fetchDataViaRest( "GET", `datapoint/${sysApUuid}/${deviceSerial}.${channel}.${dataPoint}` ); return this.processRestResponse(response, isGetDataPointResponse); } /** * Gets the web socket messages. * @returns {Observable.<WebSocketMessage>} An observable that is updated with the messages received from the web socket. */ getWebSocketMessages() { return this.webSocketMessageSubject.asObservable(); } /** * Sets a new value for the specificed data point. * @param sysApUuid {string} The UUID idenfifying the system access point. * @param deviceSerial {string} The device serial number. * @param channel {string} The channel identifier. * @param dataPoint {string} The datapoint identifier. * @param value {string} The new value to be set. * @returns {Promise.<SetDataPointResponse>} The response to the set data point request. */ async setDatapoint(sysApUuid, deviceSerial, channel, dataPoint, value) { const response = await this.fetchDataViaRest( "PUT", `datapoint/${sysApUuid}/${deviceSerial}.${channel}.${dataPoint}`, value ); return this.processRestResponse(response, isSetDataPointResponse); } /** * Triggeres the given action for the specified proxy device. Please note that this method is part of the experimental API! * @param sysApUuid {string} The UUID idenfifying the system access point. * @param deviceClass {string} The device class. * @param deviceSerial {string} The device serial number. * @param action {string} The action to be triggered. * @returns {Promise.<DeviceResponse>} The response to the request. */ async triggerProxyDevice(sysApUuid, deviceClass, deviceSerial, action) { const response = await this.fetchDataViaRest( "GET", `proxydevice/${sysApUuid}/${deviceClass}/${deviceSerial}/action/${action}` ); return this.processRestResponse(response, isDeviceResponse); } /** * Sets the given value for the specified proxy device. Please note that this method is part of the experimental API! * @param sysApUuid {string} The UUID idenfifying the system access point. * @param deviceClass {string} The device class. * @param deviceSerial {string} The device serial number. * @param value {string} The value to be set. * @returns {Promise.<DeviceResponse>} The response to the request. */ async setProxyDeviceValue(sysApUuid, deviceClass, deviceSerial, value) { const response = await this.fetchDataViaRest( "PUT", `proxydevice/${sysApUuid}/${deviceClass}/${deviceSerial}/value/${value}` ); return this.processRestResponse(response, isDeviceResponse); } async fetchDataViaRest(method, route, body = void 0) { const info = `${this.tlsEnabled ? "https" : "http"}://${this.hostName}/fhapi/v1/api/rest/${route}`; const init = { method, headers: { Authorization: `Basic ${this.basicAuthKey}` }, body }; return fetch(info, init); } async processRestResponse(response, typeGuard) { let body; let message; switch (response.status) { case 200: body = await response.json(); if (!typeGuard(body, this.logger, this.verboseErrors)) { message = "Received message has an unexpected type!"; this.logger.error(message, body); throw new Error(message); } return body; case 401: message = "Authentication information is missing or invalid."; this.logger.error(message); throw new Error(message); case 502: message = await response.text(); this.logger.error(message); throw new Error(message); default: message = `Received HTTP ${response.status} status code unexpectedly: ${await response.text()}`; this.logger.error(message); throw new Error(message); } } processWebSocketMessage(data, isBinary) { if (isBinary) { this.logger.warn( "Binary message received. Binary messages are not processed." ); return; } this.logger.debug("Message received"); const serialized = data.toString(); const message = JSON.parse(serialized); if (isWebSocketMessage(message, this.logger, this.verboseErrors)) { this.webSocketMessageSubject.next(message); return; } this.logger.error("Received message has an unexpected type!", serialized); } }; export { SystemAccessPoint, VirtualDeviceType2 as VirtualDeviceType, isChannel, isConfiguration, isDevice, isDeviceList, isDeviceResponse, isGetDataPointResponse, isSetDataPointResponse, isVirtualDevice, isVirtualDeviceResponse, isWebSocketMessage }; //# sourceMappingURL=index.mjs.map