UNPKG

@vincentpeyrouse/home-assistant-matter-hub

Version:

This project simulates bridges to publish your entities from Home Assistant to any Matter-compatible controller like Alexa, Apple Home or Google Home. Using Matter, those can be connected easily using local communication without the need of port forwardin

1,519 lines (1,463 loc) 113 kB
#!/usr/bin/env node var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // ../common/dist/bridge-data.js var init_bridge_data = __esm({ "../common/dist/bridge-data.js"() { "use strict"; } }); // ../common/dist/home-assistant-filter.js var HomeAssistantMatcherType; var init_home_assistant_filter = __esm({ "../common/dist/home-assistant-filter.js"() { "use strict"; (function(HomeAssistantMatcherType2) { HomeAssistantMatcherType2["Pattern"] = "pattern"; HomeAssistantMatcherType2["Domain"] = "domain"; HomeAssistantMatcherType2["Platform"] = "platform"; HomeAssistantMatcherType2["Label"] = "label"; })(HomeAssistantMatcherType || (HomeAssistantMatcherType = {})); } }); // ../common/dist/home-assistant-entity-registry.js var init_home_assistant_entity_registry = __esm({ "../common/dist/home-assistant-entity-registry.js"() { "use strict"; } }); // ../common/dist/home-assistant-entity-state.js var init_home_assistant_entity_state = __esm({ "../common/dist/home-assistant-entity-state.js"() { "use strict"; } }); // ../common/dist/home-assistant-domain.js var HomeAssistantDomain; var init_home_assistant_domain = __esm({ "../common/dist/home-assistant-domain.js"() { "use strict"; (function(HomeAssistantDomain2) { HomeAssistantDomain2["light"] = "light"; HomeAssistantDomain2["switch"] = "switch"; HomeAssistantDomain2["lock"] = "lock"; HomeAssistantDomain2["fan"] = "fan"; HomeAssistantDomain2["binary_sensor"] = "binary_sensor"; HomeAssistantDomain2["sensor"] = "sensor"; HomeAssistantDomain2["cover"] = "cover"; HomeAssistantDomain2["climate"] = "climate"; HomeAssistantDomain2["input_boolean"] = "input_boolean"; HomeAssistantDomain2["script"] = "script"; HomeAssistantDomain2["automation"] = "automation"; HomeAssistantDomain2["scene"] = "scene"; })(HomeAssistantDomain || (HomeAssistantDomain = {})); } }); // ../common/dist/device-data.js var init_device_data = __esm({ "../common/dist/device-data.js"() { "use strict"; } }); // ../common/dist/domains/binary-sensor.js var BinarySensorDeviceClass; var init_binary_sensor = __esm({ "../common/dist/domains/binary-sensor.js"() { "use strict"; (function(BinarySensorDeviceClass2) { BinarySensorDeviceClass2["Battery"] = "battery"; BinarySensorDeviceClass2["BatteryCharging"] = "battery_charging"; BinarySensorDeviceClass2["CarbonMonoxide"] = "carbon_monoxide"; BinarySensorDeviceClass2["Cold"] = "cold"; BinarySensorDeviceClass2["Connectivity"] = "connectivity"; BinarySensorDeviceClass2["Door"] = "door"; BinarySensorDeviceClass2["GarageDoor"] = "garage_door"; BinarySensorDeviceClass2["Gas"] = "gas"; BinarySensorDeviceClass2["Heat"] = "heat"; BinarySensorDeviceClass2["Light"] = "light"; BinarySensorDeviceClass2["Lock"] = "lock"; BinarySensorDeviceClass2["Moisture"] = "moisture"; BinarySensorDeviceClass2["Motion"] = "motion"; BinarySensorDeviceClass2["Moving"] = "moving"; BinarySensorDeviceClass2["Occupancy"] = "occupancy"; BinarySensorDeviceClass2["Opening"] = "opening"; BinarySensorDeviceClass2["Plug"] = "plug"; BinarySensorDeviceClass2["Power"] = "power"; BinarySensorDeviceClass2["Presence"] = "presence"; BinarySensorDeviceClass2["Problem"] = "problem"; BinarySensorDeviceClass2["Running"] = "running"; BinarySensorDeviceClass2["Safety"] = "safety"; BinarySensorDeviceClass2["Smoke"] = "smoke"; BinarySensorDeviceClass2["Sound"] = "sound"; BinarySensorDeviceClass2["Tamper"] = "tamper"; BinarySensorDeviceClass2["Update"] = "update"; BinarySensorDeviceClass2["Vibration"] = "vibration"; BinarySensorDeviceClass2["Window"] = "window"; })(BinarySensorDeviceClass || (BinarySensorDeviceClass = {})); } }); // ../common/dist/domains/climate.js var ClimateHvacMode, ClimateHvacAction; var init_climate = __esm({ "../common/dist/domains/climate.js"() { "use strict"; (function(ClimateHvacMode2) { ClimateHvacMode2["off"] = "off"; ClimateHvacMode2["heat"] = "heat"; ClimateHvacMode2["cool"] = "cool"; ClimateHvacMode2["heat_cool"] = "heat_cool"; ClimateHvacMode2["auto"] = "auto"; ClimateHvacMode2["dry"] = "dry"; ClimateHvacMode2["fan_only"] = "fan_only"; })(ClimateHvacMode || (ClimateHvacMode = {})); (function(ClimateHvacAction2) { ClimateHvacAction2["off"] = "off"; ClimateHvacAction2["preheating"] = "preheating"; ClimateHvacAction2["heating"] = "heating"; ClimateHvacAction2["cooling"] = "cooling"; ClimateHvacAction2["drying"] = "drying"; ClimateHvacAction2["fan"] = "fan"; ClimateHvacAction2["idle"] = "idle"; ClimateHvacAction2["defrosting"] = "defrosting"; })(ClimateHvacAction || (ClimateHvacAction = {})); } }); // ../common/dist/domains/cover.js var init_cover = __esm({ "../common/dist/domains/cover.js"() { "use strict"; } }); // ../common/dist/domains/fan.js var init_fan = __esm({ "../common/dist/domains/fan.js"() { "use strict"; } }); // ../common/dist/domains/light.js var LightDeviceColorMode; var init_light = __esm({ "../common/dist/domains/light.js"() { "use strict"; (function(LightDeviceColorMode2) { LightDeviceColorMode2["UNKNOWN"] = "unknown"; LightDeviceColorMode2["ONOFF"] = "onoff"; LightDeviceColorMode2["BRIGHTNESS"] = "brightness"; LightDeviceColorMode2["COLOR_TEMP"] = "color_temp"; LightDeviceColorMode2["HS"] = "hs"; LightDeviceColorMode2["XY"] = "xy"; LightDeviceColorMode2["RGB"] = "rgb"; LightDeviceColorMode2["RGBW"] = "rgbw"; LightDeviceColorMode2["RGBWW"] = "rgbww"; LightDeviceColorMode2["WHITE"] = "white"; })(LightDeviceColorMode || (LightDeviceColorMode = {})); } }); // ../common/dist/domains/sensor.js var SensorDeviceClass; var init_sensor = __esm({ "../common/dist/domains/sensor.js"() { "use strict"; (function(SensorDeviceClass2) { SensorDeviceClass2["None"] = "None"; SensorDeviceClass2["apparent_power"] = "apparent_power"; SensorDeviceClass2["aqi"] = "aqi"; SensorDeviceClass2["atmospheric_pressure"] = "atmospheric_pressure"; SensorDeviceClass2["battery"] = "battery"; SensorDeviceClass2["carbon_dioxide"] = "carbon_dioxide"; SensorDeviceClass2["carbon_monoxide"] = "carbon_monoxide"; SensorDeviceClass2["current"] = "current"; SensorDeviceClass2["data_rate"] = "data_rate"; SensorDeviceClass2["data_size"] = "data_size"; SensorDeviceClass2["date"] = "date"; SensorDeviceClass2["distance"] = "distance"; SensorDeviceClass2["duration"] = "duration"; SensorDeviceClass2["energy"] = "energy"; SensorDeviceClass2["energy_storage"] = "energy_storage"; SensorDeviceClass2["enum"] = "enum"; SensorDeviceClass2["frequency"] = "frequency"; SensorDeviceClass2["gas"] = "gas"; SensorDeviceClass2["humidity"] = "humidity"; SensorDeviceClass2["illuminance"] = "illuminance"; SensorDeviceClass2["irradiance"] = "irradiance"; SensorDeviceClass2["moisture"] = "moisture"; SensorDeviceClass2["monetary"] = "monetary"; SensorDeviceClass2["nitrogen_dioxide"] = "nitrogen_dioxide"; SensorDeviceClass2["nitrogen_monoxide"] = "nitrogen_monoxide"; SensorDeviceClass2["nitrous_oxide"] = "nitrous_oxide"; SensorDeviceClass2["ozone"] = "ozone"; SensorDeviceClass2["ph"] = "ph"; SensorDeviceClass2["pm1"] = "pm1"; SensorDeviceClass2["pm25"] = "pm25"; SensorDeviceClass2["pm10"] = "pm10"; SensorDeviceClass2["power_factor"] = "power_factor"; SensorDeviceClass2["power"] = "power"; SensorDeviceClass2["precipitation"] = "precipitation"; SensorDeviceClass2["precipitation_intensity"] = "precipitation_intensity"; SensorDeviceClass2["pressure"] = "pressure"; SensorDeviceClass2["reactive_power"] = "reactive_power"; SensorDeviceClass2["signal_strength"] = "signal_strength"; SensorDeviceClass2["sound_pressure"] = "sound_pressure"; SensorDeviceClass2["speed"] = "speed"; SensorDeviceClass2["sulphur_dioxide"] = "sulphur_dioxide"; SensorDeviceClass2["temperature"] = "temperature"; SensorDeviceClass2["timestamp"] = "timestamp"; SensorDeviceClass2["volatile_organic_compounds"] = "volatile_organic_compounds"; SensorDeviceClass2["volatile_organic_compounds_parts"] = "volatile_organic_compounds_parts"; SensorDeviceClass2["voltage"] = "voltage"; SensorDeviceClass2["volume"] = "volume"; SensorDeviceClass2["volume_flow_rate"] = "volume_flow_rate"; SensorDeviceClass2["volume_storage"] = "volume_storage"; SensorDeviceClass2["water"] = "water"; SensorDeviceClass2["weight"] = "weight"; SensorDeviceClass2["wind_speed"] = "wind_speed"; })(SensorDeviceClass || (SensorDeviceClass = {})); } }); // ../common/dist/domains/index.js var init_domains = __esm({ "../common/dist/domains/index.js"() { "use strict"; init_binary_sensor(); init_climate(); init_cover(); init_fan(); init_light(); init_sensor(); } }); // ../common/dist/utils/type.js var init_type = __esm({ "../common/dist/utils/type.js"() { "use strict"; } }); // ../common/dist/utils/color-converter.js import Color from "color"; var ColorConverter; var init_color_converter = __esm({ "../common/dist/utils/color-converter.js"() { "use strict"; ColorConverter = class { /** * Create a color object from `hs_color` value * @param hue Hue, Values between 0 and 360 * @param saturation Saturation, Values between 0 and 100 * @return Color */ static fromHomeAssistantHS(hue, saturation) { return Color.hsv(hue, saturation, 100); } /** * Create a color object from `hue` and `saturation` values set via Matter * @param hue Hue, Values between 0 and 255 * @param saturation Saturation, Values between 0 and 255 * @return Color */ static fromMatterHS(hue, saturation) { return Color.hsv(hue / 254 * 360, saturation / 254 * 100, 100); } /** * Create a color object from `x` and `y` values set via Matter. * This function was inspired by color utils of Home Assistant Core (`homeassistant.util.color.color_xy_brightness_to_RGB`). * @param x X, Values between 0 and 1 * @param y Y, Values between 0 and 1 * @return Color */ static fromXY(x, y) { function toXYZ(x2, y2) { const Y = 1; const X = Y / y2 * x2; const Z = Y / y2 * (1 - x2 - y2); return [X, Y, Z]; } function toRGB_D65(X, Y, Z) { const r2 = X * 1.656492 - Y * 0.354851 - Z * 0.255038, g2 = -X * 0.707196 + Y * 1.655397 + Z * 0.036152, b2 = X * 0.051713 - Y * 0.121364 + Z * 1.01153; return [r2, g2, b2]; } function applyReverseGammaCorrection(x2) { if (x2 <= 31308e-7) { return 12.92 * x2; } else { return (1 + 0.055) * Math.pow(x2, 1 / 2.4) - 0.055; } } const XYZ = toXYZ(x, y); let rgb = toRGB_D65(...XYZ).map(applyReverseGammaCorrection).map((v) => Math.max(v, 0)); const maxValue = Math.max(...rgb); if (maxValue > 1) { rgb = rgb.map((v) => v / maxValue); } const [r, g, b] = rgb.map((v) => Math.round(v * 255)); return this.fromRGB(r, g, b); } /** * Create a color object from `rgb_color` value * @param r Red, 0-255 * @param g Green, 0-255 * @param b Blue, 0-255 * @return Color */ static fromRGB(r, g, b) { return Color.rgb(r, g, b); } /** * Extract Hue and Saturation compatible with Home Assistant * @param color The Color * @return [hue, saturation] */ static toHomeAssistantHS(color) { const [h, s] = color.hsv().array(); return [h, s]; } /** * Extract R, G, B values from a color * @param color The color * @return [r, g, b] */ static toRGB(color) { const [r, g, b] = color.rgb().array(); return [r, g, b]; } /** * Extract Hue and Saturation compatible with Matter * @param color The Color * @return [hue, saturation] */ static toMatterHS(color) { const [h, s] = color.hsv().array(); return [h / 360 * 254, s / 100 * 254]; } /** * Convert Color Tempareture from Mireds to Kelvin * @param temperatureMireds Temperature in Mireds * @return Temperature in Kelvin */ static temperatureMiredsToKelvin(temperatureMireds) { return 1e6 / temperatureMireds; } /** * Convert Color Tempareture from Kelvin to Mireds * @param temperatureKelvin Temperature in Kelvin * @return Temperature in Mireds */ static temperatureKelvinToMireds(temperatureKelvin) { return 1e6 / temperatureKelvin; } static temperatureKelvinToColor(temperatureKelvin) { const temp = temperatureKelvin / 100; let red, green, blue; if (temp <= 66) { red = 255; green = temp; green = 99.4708025861 * Math.log(green) - 161.1195681661; if (temp <= 19) { blue = 0; } else { blue = temp - 10; blue = 138.5177312231 * Math.log(blue) - 305.0447927307; } } else { red = temp - 60; red = 329.698727446 * Math.pow(red, -0.1332047592); green = temp - 60; green = 288.1221695283 * Math.pow(green, -0.0755148492); blue = 255; } function boundary(value) { return Math.min(Math.max(value, 0), 255); } return this.fromRGB(boundary(red), boundary(green), boundary(blue)); } static temperatureMiredsToColor(temperatureMireds) { return this.temperatureKelvinToColor(this.temperatureMiredsToKelvin(temperatureMireds)); } }; } }); // ../common/dist/utils/index.js var init_utils = __esm({ "../common/dist/utils/index.js"() { "use strict"; init_type(); init_color_converter(); } }); // ../common/dist/clusters/boolean-state.js var init_boolean_state = __esm({ "../common/dist/clusters/boolean-state.js"() { "use strict"; } }); // ../common/dist/clusters/color-control.js var init_color_control = __esm({ "../common/dist/clusters/color-control.js"() { "use strict"; } }); // ../common/dist/clusters/door-lock.js var DoorLockStatus; var init_door_lock = __esm({ "../common/dist/clusters/door-lock.js"() { "use strict"; (function(DoorLockStatus2) { DoorLockStatus2[DoorLockStatus2["locked"] = 1] = "locked"; DoorLockStatus2[DoorLockStatus2["unlocked"] = 2] = "unlocked"; })(DoorLockStatus || (DoorLockStatus = {})); } }); // ../common/dist/clusters/level-control.js var init_level_control = __esm({ "../common/dist/clusters/level-control.js"() { "use strict"; } }); // ../common/dist/clusters/occupancy-sensing.js var init_occupancy_sensing = __esm({ "../common/dist/clusters/occupancy-sensing.js"() { "use strict"; } }); // ../common/dist/clusters/on-off.js var init_on_off = __esm({ "../common/dist/clusters/on-off.js"() { "use strict"; } }); // ../common/dist/clusters/relative-humidity-measurement.js var init_relative_humidity_measurement = __esm({ "../common/dist/clusters/relative-humidity-measurement.js"() { "use strict"; } }); // ../common/dist/clusters/temperature-measurement.js var init_temperature_measurement = __esm({ "../common/dist/clusters/temperature-measurement.js"() { "use strict"; } }); // ../common/dist/clusters/thermostat.js var ThermostatSystemMode; var init_thermostat = __esm({ "../common/dist/clusters/thermostat.js"() { "use strict"; (function(ThermostatSystemMode2) { ThermostatSystemMode2[ThermostatSystemMode2["Off"] = 0] = "Off"; ThermostatSystemMode2[ThermostatSystemMode2["Auto"] = 1] = "Auto"; ThermostatSystemMode2[ThermostatSystemMode2["Cool"] = 3] = "Cool"; ThermostatSystemMode2[ThermostatSystemMode2["Heat"] = 4] = "Heat"; ThermostatSystemMode2[ThermostatSystemMode2["EmergencyHeat"] = 5] = "EmergencyHeat"; ThermostatSystemMode2[ThermostatSystemMode2["Precooling"] = 6] = "Precooling"; ThermostatSystemMode2[ThermostatSystemMode2["FanOnly"] = 7] = "FanOnly"; ThermostatSystemMode2[ThermostatSystemMode2["Dry"] = 8] = "Dry"; ThermostatSystemMode2[ThermostatSystemMode2["Sleep"] = 9] = "Sleep"; })(ThermostatSystemMode || (ThermostatSystemMode = {})); } }); // ../common/dist/clusters/window-covering.js var init_window_covering = __esm({ "../common/dist/clusters/window-covering.js"() { "use strict"; } }); // ../common/dist/clusters/index.js var ClusterId; var init_clusters = __esm({ "../common/dist/clusters/index.js"() { "use strict"; init_boolean_state(); init_color_control(); init_door_lock(); init_level_control(); init_occupancy_sensing(); init_on_off(); init_relative_humidity_measurement(); init_temperature_measurement(); init_thermostat(); init_window_covering(); (function(ClusterId2) { ClusterId2["descriptor"] = "descriptor"; ClusterId2["bridgedDeviceBasicInformation"] = "bridgedDeviceBasicInformation"; ClusterId2["identify"] = "identify"; ClusterId2["groups"] = "groups"; ClusterId2["booleanState"] = "booleanState"; ClusterId2["colorControl"] = "colorControl"; ClusterId2["doorLock"] = "doorLock"; ClusterId2["levelControl"] = "levelControl"; ClusterId2["occupancySensing"] = "occupancySensing"; ClusterId2["onOff"] = "onOff"; ClusterId2["relativeHumidityMeasurement"] = "relativeHumidityMeasurement"; ClusterId2["temperatureMeasurement"] = "temperatureMeasurement"; ClusterId2["thermostat"] = "thermostat"; ClusterId2["windowCovering"] = "windowCovering"; })(ClusterId || (ClusterId = {})); } }); // ../common/dist/schemas/bridge-config-schema.js var homeAssistantMatcherSchema, homeAssistantFilterSchema, bridgeConfigSchema; var init_bridge_config_schema = __esm({ "../common/dist/schemas/bridge-config-schema.js"() { "use strict"; init_home_assistant_filter(); homeAssistantMatcherSchema = { type: "object", properties: { type: { type: "string", enum: Object.values(HomeAssistantMatcherType) }, value: { type: "string", minLength: 1 } }, required: ["type", "value"], additionalProperties: false }; homeAssistantFilterSchema = { type: "object", properties: { include: { type: "array", items: homeAssistantMatcherSchema }, exclude: { type: "array", items: homeAssistantMatcherSchema } }, required: ["include", "exclude"], additionalProperties: false }; bridgeConfigSchema = { type: "object", properties: { name: { type: "string", minLength: 1 }, port: { type: "number", minimum: 1 }, filter: homeAssistantFilterSchema }, required: ["name", "port", "filter"], additionalProperties: false }; } }); // ../common/dist/schemas/create-bridge-request-schema.js var createBridgeRequestSchema; var init_create_bridge_request_schema = __esm({ "../common/dist/schemas/create-bridge-request-schema.js"() { "use strict"; init_bridge_config_schema(); createBridgeRequestSchema = bridgeConfigSchema; } }); // ../common/dist/schemas/update-bridge-request-schema.js var updateBridgeRequestSchema; var init_update_bridge_request_schema = __esm({ "../common/dist/schemas/update-bridge-request-schema.js"() { "use strict"; init_bridge_config_schema(); updateBridgeRequestSchema = { ...bridgeConfigSchema, properties: { ...bridgeConfigSchema.properties, id: { type: "string" } }, required: [...bridgeConfigSchema?.required ?? [], "id"] }; } }); // ../common/dist/schemas/index.js var init_schemas = __esm({ "../common/dist/schemas/index.js"() { "use strict"; init_bridge_config_schema(); init_create_bridge_request_schema(); init_update_bridge_request_schema(); } }); // ../common/dist/index.js var init_dist = __esm({ "../common/dist/index.js"() { "use strict"; init_bridge_data(); init_home_assistant_filter(); init_home_assistant_entity_registry(); init_home_assistant_entity_state(); init_home_assistant_domain(); init_device_data(); init_domains(); init_utils(); init_clusters(); init_schemas(); } }); // src/errors/port-already-in-use-error.ts var PortAlreadyInUseError; var init_port_already_in_use_error = __esm({ "src/errors/port-already-in-use-error.ts"() { "use strict"; PortAlreadyInUseError = class extends Error { constructor(port) { super(`Port ${port} is already in use`); } }; } }); // src/utils/json/bridge-to-json.ts import _ from "lodash"; function bridgeToJson(bridge) { return { id: bridge.id, name: bridge.name, port: bridge.port, filter: bridge.filter, basicInformation: bridge.basicInformation, deviceCount: bridge.devices.length, commissioning: bridge.commissioning ? { isCommissioned: bridge.commissioning.commissioned, passcode: bridge.commissioning.passcode, discriminator: bridge.commissioning.discriminator, manualPairingCode: bridge.commissioning.pairingCodes.manualPairingCode, qrPairingCode: bridge.commissioning.pairingCodes.qrPairingCode, fabrics: _.values(bridge.commissioning.fabrics).map((fabric) => ({ fabricIndex: fabric.fabricIndex, fabricId: Number(fabric.fabricId), nodeId: Number(fabric.nodeId), rootNodeId: Number(fabric.rootNodeId), rootVendorId: fabric.rootVendorId, label: fabric.label })) } : void 0 }; } var init_bridge_to_json = __esm({ "src/utils/json/bridge-to-json.ts"() { "use strict"; } }); // src/utils/async-observable.ts import { AsyncObservable as Base } from "@matter/main"; var AsyncObservable; var init_async_observable = __esm({ "src/utils/async-observable.ts"() { "use strict"; AsyncObservable = () => Base(); } }); // src/matter/custom-behaviors/home-assistant-behavior.ts import { Behavior } from "@matter/main"; import { EventEmitter } from "@matter/main"; var HomeAssistantBehavior; var init_home_assistant_behavior = __esm({ "src/matter/custom-behaviors/home-assistant-behavior.ts"() { "use strict"; init_async_observable(); HomeAssistantBehavior = class extends Behavior { static id = "homeAssistant"; get entity() { return this.state.entity; } onUpdate(callback) { this.events.entity$Changed.on(callback); } async callAction(domain, action, data, target, returnResponse) { return this.state.actions.callAction( domain, action, data, target, returnResponse ); } }; ((HomeAssistantBehavior2) => { class State { actions; basicInformation; registry; entity; } HomeAssistantBehavior2.State = State; class Events extends EventEmitter { entity$Changed = AsyncObservable(); } HomeAssistantBehavior2.Events = Events; })(HomeAssistantBehavior || (HomeAssistantBehavior = {})); } }); // src/utils/json/device-to-json.ts import _2 from "lodash"; function deviceToJson(device) { const behaviors = _2.pickBy( device.behaviors.supported, (b) => b.id !== HomeAssistantBehavior.id ); const state = _2.mapValues(behaviors, (b) => device.stateOf(b)); return { entityId: device.entityId, endpointCode: device.type.deviceType.toString(16), endpointType: device.type.name, state }; } var init_device_to_json = __esm({ "src/utils/json/device-to-json.ts"() { "use strict"; init_home_assistant_behavior(); } }); // src/api/matter-api.ts import express from "express"; import { Ajv } from "ajv"; function matterApi(matterServer) { const router = express.Router(); router.get("/", (req, res) => { res.status(200).json(matterServer); }); router.get("/bridges", (_7, res) => { res.status(200).json(matterServer.bridges.map(bridgeToJson)); }); router.post("/bridges", async (req, res) => { const body = req.body; const isValid = ajv.validate(createBridgeRequestSchema, body); if (!isValid) { res.status(400).json(ajv.errors); } else { try { const bridge = await matterServer.create(body); res.status(200).json(bridgeToJson(bridge)); } catch (error) { if (error instanceof PortAlreadyInUseError) { res.status(400).json({ error: error.message }); } throw error; } } }); router.get("/bridges/:bridgeId", async (req, res) => { const bridgeId = req.params.bridgeId; const bridge = matterServer.bridges.find((b) => b.id === bridgeId); if (bridge) { res.status(200).json(bridgeToJson(bridge)); } else { res.status(404).send("Not Found"); } }); router.put("/bridges/:bridgeId", async (req, res) => { const bridgeId = req.params.bridgeId; const body = req.body; const isValid = ajv.validate(updateBridgeRequestSchema, body); if (!isValid) { res.status(400).json(ajv.errors); } else if (bridgeId !== body.id) { res.status(400).send("Path variable `bridgeId` does not match `body.id`"); } else { try { const bridge = await matterServer.update(body); if (!bridge) { res.status(404).send("Not Found"); } else { res.status(200).json(bridgeToJson(bridge)); } } catch (error) { if (error instanceof PortAlreadyInUseError) { res.status(400).json({ error: error.message }); } throw error; } } }); router.delete("/bridges/:bridgeId", async (req, res) => { const bridgeId = req.params.bridgeId; await matterServer.delete(bridgeId); res.status(204).send(); }); router.get("/bridges/:bridgeId/devices", async (req, res) => { const bridgeId = req.params.bridgeId; const bridge = matterServer.bridges.find((b) => b.id === bridgeId); if (bridge) { res.status(200).json(bridge.devices.map(deviceToJson)); } else { res.status(404).send("Not Found"); } }); return router; } var ajv; var init_matter_api = __esm({ "src/api/matter-api.ts"() { "use strict"; init_dist(); init_port_already_in_use_error(); init_bridge_to_json(); init_device_to_json(); ajv = new Ajv(); } }); // src/api/access-log.ts function accessLogger(logger) { return (req, res, next) => { res.on("finish", function() { logger.debug( "%s %s %s %s", req.method, decodeURI(req.originalUrl), res.statusCode, res.statusMessage ); }); next(); }; } var init_access_log = __esm({ "src/api/access-log.ts"() { "use strict"; } }); // src/api/web-ui.ts import express2 from "express"; import fs from "node:fs"; import path from "node:path"; function webUi(dist) { const router = express2.Router(); if (dist) { router.get("*", (req, res) => { if (fs.existsSync(path.join(dist, req.path))) { res.sendFile(path.join(dist, req.path)); } else { res.sendFile(path.join(dist, "index.html")); } }); } return router; } var init_web_ui = __esm({ "src/api/web-ui.ts"() { "use strict"; } }); // src/logging/create-child-logger.ts function createChildLogger(logger, name) { const loggerName = [logger.defaultMeta?.loggerName, name].filter((it) => !!it).join(" / "); const log = logger.child({ loggerName }); log.defaultMeta = { ...logger.defaultMeta, loggerName }; return log; } var init_create_child_logger = __esm({ "src/logging/create-child-logger.ts"() { "use strict"; } }); // src/utils/service.ts var ServiceBase; var init_service = __esm({ "src/utils/service.ts"() { "use strict"; init_create_child_logger(); ServiceBase = class { constructor(serviceName, logger) { this.serviceName = serviceName; this.log = createChildLogger(logger, serviceName); } log; }; } }); // src/api/web-api.ts import express3 from "express"; var WebApi; var init_web_api = __esm({ "src/api/web-api.ts"() { "use strict"; init_matter_api(); init_access_log(); init_web_ui(); init_service(); init_create_child_logger(); WebApi = class extends ServiceBase { port; includesWebUi; app; server; constructor(props) { super("WebApi", props.logger); const api = express3.Router(); api.use(express3.json()).use("/matter", matterApi(props.bridgeService)); let app = express3().use(accessLogger(createChildLogger(this.log, "access-log"))).use("/api", api); if (props.webUiDist) { app = app.use(webUi(props.webUiDist)); } this.app = app; this.port = props.port; this.includesWebUi = !!props.webUiDist; } async start() { this.server = await new Promise((resolve) => { const server = this.app.listen(this.port, () => { this.log.info( "HTTP server (API %s) listening on port %s", this.includesWebUi ? "& Web App" : "only", this.port ); resolve(server); }); }); } async close() { await new Promise((resolve, reject) => { this.server?.close((error) => { if (error) { reject(error); } else { resolve(); } }); }); } }; } }); // src/utils/detach.ts function detach(cb) { return new Promise((resolve) => { setTimeout(async () => { resolve(); await cb(); }, 10); }); } var init_detach = __esm({ "src/utils/detach.ts"() { "use strict"; } }); // src/utils/json/bridge-from-json.ts import { ServerNode } from "@matter/main/node"; import { FabricId, FabricIndex, NodeId, VendorId } from "@matter/main"; import _3 from "lodash"; import { AggregatorEndpoint } from "@matter/main/endpoints"; import crypto from "node:crypto"; function bridgeFromJson(environment, data) { const commissioning = data.commissioning ? { commissioned: data.commissioning.isCommissioned, passcode: data.commissioning.passcode, discriminator: data.commissioning.discriminator, pairingCodes: { manualPairingCode: data.commissioning.manualPairingCode, qrPairingCode: data.commissioning.qrPairingCode }, fabrics: _3.fromPairs( data.commissioning.fabrics.map((fabric) => ({ fabricIndex: FabricIndex(fabric.fabricIndex), fabricId: FabricId(fabric.fabricId), nodeId: NodeId(fabric.nodeId), rootNodeId: NodeId(fabric.rootNodeId), rootVendorId: VendorId(fabric.rootVendorId), label: fabric.label })).map((fabric) => [fabric.fabricIndex, fabric]) ) } : void 0; return { type: ServerNode.RootEndpoint, environment, id: data.id, network: { port: data.port }, commissioning, productDescription: { name: data.name, deviceType: AggregatorEndpoint.deviceType }, basicInformation: { uniqueId: data.id, nodeLabel: data.name, vendorId: data.basicInformation.vendorId, vendorName: data.basicInformation.vendorName, productId: data.basicInformation.productId, productName: data.basicInformation.productName, productLabel: data.basicInformation.productLabel, serialNumber: crypto.createHash("md5").update(`serial-${data.id}`).digest("hex").substring(0, 32), hardwareVersion: data.basicInformation.hardwareVersion, softwareVersion: data.basicInformation.softwareVersion } }; } var init_bridge_from_json = __esm({ "src/utils/json/bridge-from-json.ts"() { "use strict"; } }); // src/matter/matter-device.ts import { Endpoint } from "@matter/main"; var MatterDevice; var init_matter_device = __esm({ "src/matter/matter-device.ts"() { "use strict"; init_home_assistant_behavior(); MatterDevice = class extends Endpoint { entityId; constructor(type, homeAssistant, options) { const entityId = homeAssistant.registry.entity_id; if (!(HomeAssistantBehavior.id in type.behaviors)) { throw new Error( `${type.name} does not utilize HomeAssistantBehavior (${entityId})` ); } const newOptions = { id: entityId.replace(/\./g, "_"), homeAssistant, ...options }; super(type, newOptions); this.entityId = entityId; } async update(state) { await this.setStateOf(HomeAssistantBehavior, { entity: state }); } }; } }); // src/matter/behaviors/identify-server.ts import { IdentifyServer as Base2 } from "@matter/main/behaviors"; var IdentifyServer; var init_identify_server = __esm({ "src/matter/behaviors/identify-server.ts"() { "use strict"; IdentifyServer = class extends Base2 { }; } }); // src/matter/behaviors/basic-information-server.ts import { BridgedDeviceBasicInformationServer as Base3 } from "@matter/main/behaviors"; import crypto2 from "node:crypto"; import { VendorId as VendorId2 } from "@matter/main"; function maxLengthOrHash(value, maxLength) { if (maxLength < 16) { throw new Error("MaxLength cannot be shorter than 16"); } if (value.length < maxLength) { return value; } else { const hash = crypto2.createHash("md5").update(value).digest("hex").substring(0, 8); return value.substring(0, maxLength - 8) + hash; } } var BasicInformationServer; var init_basic_information_server = __esm({ "src/matter/behaviors/basic-information-server.ts"() { "use strict"; init_home_assistant_behavior(); BasicInformationServer = class extends Base3 { async initialize() { await super.initialize(); const homeAssistant = await this.agent.load(HomeAssistantBehavior); const homeAssistantInfo = homeAssistant.state; this.state.vendorId = VendorId2(homeAssistantInfo.basicInformation.vendorId); this.state.vendorName = maxLengthOrHash( homeAssistantInfo.basicInformation.vendorName, 32 ); this.state.productName = maxLengthOrHash( homeAssistantInfo.basicInformation.productName, 32 ); this.state.productLabel = maxLengthOrHash( homeAssistantInfo.basicInformation.productLabel, 64 ); this.state.hardwareVersion = homeAssistantInfo.basicInformation.hardwareVersion; this.state.softwareVersion = homeAssistantInfo.basicInformation.softwareVersion; this.state.nodeLabel = maxLengthOrHash( homeAssistantInfo.entity.attributes.friendly_name ?? "Unknown Entity", 32 ); this.state.reachable = true; } }; } }); // src/matter/behaviors/on-off-server.ts import { OnOffServer as Base4 } from "@matter/main/behaviors"; var OnOffServer; var init_on_off_server = __esm({ "src/matter/behaviors/on-off-server.ts"() { "use strict"; init_home_assistant_behavior(); OnOffServer = class _OnOffServer extends Base4 { async initialize() { super.initialize(); const homeAssistant = await this.agent.load(HomeAssistantBehavior); this.state.onOff = homeAssistant.state.entity.state !== "off"; homeAssistant.onUpdate((s) => this.update(s)); } async update(state) { const current = this.endpoint.stateOf(_OnOffServer); const isOn = state.state !== "off"; if (isOn !== current.onOff) { await this.endpoint.setStateOf(_OnOffServer, { onOff: isOn }); } } async on() { await super.on(); const homeAssistant = this.agent.get(HomeAssistantBehavior); await homeAssistant.callAction("homeassistant", "turn_on", void 0, { entity_id: homeAssistant.state.entity.entity_id }); } async off() { await super.off(); const homeAssistant = this.agent.get(HomeAssistantBehavior); await homeAssistant.callAction("homeassistant", "turn_off", void 0, { entity_id: homeAssistant.state.entity.entity_id }); } }; } }); // src/matter/behaviors/extended-color-control-server.ts import { ColorControlServer as Base5 } from "@matter/main/behaviors/color-control"; var ExtendedColorControlServer; var init_extended_color_control_server = __esm({ "src/matter/behaviors/extended-color-control-server.ts"() { "use strict"; init_dist(); init_home_assistant_behavior(); ExtendedColorControlServer = class _ExtendedColorControlServer extends Base5.with( "ColorTemperature", "HueSaturation" ) { async initialize() { await super.initialize(); const homeAssistant = await this.agent.load(HomeAssistantBehavior); const state = homeAssistant.state.entity; const minKelvin = state.attributes.min_color_temp_kelvin ?? 1500; const maxKelvin = state.attributes.max_color_temp_kelvin ?? 8e3; this.state.coupleColorTempToLevelMinMireds = Math.floor( ColorConverter.temperatureKelvinToMireds(maxKelvin) ); this.state.colorTempPhysicalMinMireds = Math.floor( ColorConverter.temperatureKelvinToMireds(maxKelvin) ); this.state.colorTempPhysicalMaxMireds = Math.ceil( ColorConverter.temperatureKelvinToMireds(minKelvin) ); this.state.startUpColorTemperatureMireds = ColorConverter.temperatureKelvinToMireds( state.attributes.color_temp_kelvin ?? maxKelvin ); if (state.attributes.color_temp_kelvin) { this.state.colorTemperatureMireds = ColorConverter.temperatureKelvinToMireds( state.attributes.color_temp_kelvin ); } homeAssistant.onUpdate((s) => this.update(s)); } async update(state) { const current = this.endpoint.stateOf(_ExtendedColorControlServer); const color = this.getMatterColor(state); if (state.attributes.color_mode === LightDeviceColorMode.COLOR_TEMP) { let kelvin = state.attributes.color_temp_kelvin; const minKelvin = state.attributes.min_color_temp_kelvin ?? 1500; const maxKelvin = state.attributes.max_color_temp_kelvin ?? 8e3; if (kelvin != null) { kelvin = Math.max(Math.min(kelvin, maxKelvin), minKelvin); const mireds = ColorConverter.temperatureKelvinToMireds(kelvin); if (mireds != current.colorTemperatureMireds) { await this.endpoint.setStateOf(_ExtendedColorControlServer, { colorTemperatureMireds: mireds }); } } } else { if (color != null) { const [hue, saturation] = color; if (current.currentHue !== hue || current.currentSaturation !== saturation) { await this.endpoint.setStateOf(_ExtendedColorControlServer, { currentHue: hue, currentSaturation: saturation }); } } } } async moveToColorTemperature(request) { const homeAssistant = this.agent.get(HomeAssistantBehavior); const targetKelvin = ColorConverter.temperatureMiredsToKelvin( request.colorTemperatureMireds ); await super.moveToColorTemperature({ ...request, transitionTime: request.transitionTime ?? 1 }); await homeAssistant.callAction( "light", "turn_on", { color_temp_kelvin: targetKelvin }, { entity_id: homeAssistant.state.entity.entity_id } ); } async moveToHueAndSaturation(request) { const homeAssistant = this.agent.get(HomeAssistantBehavior); await super.moveToHueAndSaturation({ ...request, transitionTime: request.transitionTime ?? 1 }); const color = ColorConverter.fromMatterHS(request.hue, request.saturation); const [hue, saturation] = ColorConverter.toHomeAssistantHS(color); await homeAssistant.callAction( "light", "turn_on", { hs_color: [hue, saturation] }, { entity_id: homeAssistant.state.entity.entity_id } ); } getMatterColor(entity) { const hsColor = entity.attributes.hs_color; const xyColor = entity.attributes.xy_color; const rgbColor = entity.attributes.rgb_color; if (hsColor != null) { const [hue, saturation] = hsColor; return ColorConverter.toMatterHS( ColorConverter.fromHomeAssistantHS(hue, saturation) ); } else if (rgbColor != null) { const [r, g, b] = rgbColor; return ColorConverter.toMatterHS(ColorConverter.fromRGB(r, g, b)); } else if (xyColor != null) { const [x, y] = xyColor; return ColorConverter.toMatterHS(ColorConverter.fromXY(x, y)); } return void 0; } }; } }); // src/matter/behaviors/level-control-server.ts import { LevelControlServer as Base6 } from "@matter/main/behaviors"; var LevelControlServer; var init_level_control_server = __esm({ "src/matter/behaviors/level-control-server.ts"() { "use strict"; init_home_assistant_behavior(); LevelControlServer = class _LevelControlServer extends Base6 { async initialize() { super.initialize(); const homeAssistant = await this.agent.load(HomeAssistantBehavior); const state = homeAssistant.state.entity; this.state.currentLevel = this.state.config.getValue(state); this.state.minLevel = this.state.config.getMinValue?.(state); this.state.maxLevel = this.state.config.getMaxValue?.(state); this.state.managedTransitionTimeHandling = false; this.state.onTransitionTime = 1; this.state.offTransitionTime = 1; this.state.onOffTransitionTime = 1; homeAssistant.onUpdate((s) => this.update(s)); } async update(state) { const current = this.endpoint.stateOf(_LevelControlServer); const level = current.config.getValue(state); if (level != null && !isNaN(level) && level != current.currentLevel) { await this.endpoint.setStateOf(_LevelControlServer, { currentLevel: level }); } } async moveToLevel(request) { await super.moveToLevel({ ...request, transitionTime: request.transitionTime ?? 1 }); await this.handleMoveToLevel(request); } async moveToLevelWithOnOff(request) { await super.moveToLevelWithOnOff({ ...request, transitionTime: request.transitionTime ?? 1 }); await this.handleMoveToLevel(request); } async handleMoveToLevel(request) { const homeAssistant = this.agent.get(HomeAssistantBehavior); const [domain, action] = this.state.config.moveToLevel.action.split("."); await homeAssistant.callAction( domain, action, this.state.config.moveToLevel.data(request.level), { entity_id: homeAssistant.state.entity.entity_id } ); } }; ((LevelControlServer2) => { class State extends Base6.State { config; } LevelControlServer2.State = State; })(LevelControlServer || (LevelControlServer = {})); } }); // src/matter/devices/light/extended-color-light.ts import { ExtendedColorLightDevice as Device } from "@matter/main/devices"; var ExtendedColorLightType; var init_extended_color_light = __esm({ "src/matter/devices/light/extended-color-light.ts"() { "use strict"; init_identify_server(); init_basic_information_server(); init_on_off_server(); init_extended_color_control_server(); init_home_assistant_behavior(); init_level_control_server(); ExtendedColorLightType = Device.with( IdentifyServer, BasicInformationServer, HomeAssistantBehavior, OnOffServer, LevelControlServer, ExtendedColorControlServer ); } }); // src/matter/behaviors/color-temperature-control-server.ts import { ColorControlServer as Base7 } from "@matter/main/behaviors/color-control"; var ColorTemperatureControlServer; var init_color_temperature_control_server = __esm({ "src/matter/behaviors/color-temperature-control-server.ts"() { "use strict"; init_dist(); init_home_assistant_behavior(); ColorTemperatureControlServer = class _ColorTemperatureControlServer extends Base7.with( "ColorTemperature" ) { async initialize() { await super.initialize(); const homeAssistant = await this.agent.load(HomeAssistantBehavior); const state = homeAssistant.state.entity; const minKelvin = state.attributes.min_color_temp_kelvin ?? 1500; const maxKelvin = state.attributes.max_color_temp_kelvin ?? 8e3; this.state.coupleColorTempToLevelMinMireds = Math.floor( ColorConverter.temperatureKelvinToMireds(maxKelvin) ); this.state.colorTempPhysicalMinMireds = Math.floor( ColorConverter.temperatureKelvinToMireds(maxKelvin) ); this.state.colorTempPhysicalMaxMireds = Math.ceil( ColorConverter.temperatureKelvinToMireds(minKelvin) ); this.state.startUpColorTemperatureMireds = ColorConverter.temperatureKelvinToMireds( state.attributes.color_temp_kelvin ?? maxKelvin ); if (state.attributes.color_temp_kelvin) { this.state.colorTemperatureMireds = ColorConverter.temperatureKelvinToMireds( state.attributes.color_temp_kelvin ); } homeAssistant.onUpdate((s) => this.update(s)); } async update(state) { const current = this.endpoint.stateOf(_ColorTemperatureControlServer); if (state.attributes.color_mode === LightDeviceColorMode.COLOR_TEMP) { let kelvin = state.attributes.color_temp_kelvin; const minKelvin = state.attributes.min_color_temp_kelvin ?? 1500; const maxKelvin = state.attributes.max_color_temp_kelvin ?? 8e3; if (kelvin != null) { kelvin = Math.max(Math.min(kelvin, maxKelvin), minKelvin); const mireds = ColorConverter.temperatureKelvinToMireds(kelvin); if (mireds != current.colorTemperatureMireds) { await this.endpoint.setStateOf(_ColorTemperatureControlServer, { colorTemperatureMireds: mireds }); } } } } async moveToColorTemperature(request) { const homeAssistant = this.agent.get(HomeAssistantBehavior); const targetKelvin = ColorConverter.temperatureMiredsToKelvin( request.colorTemperatureMireds ); await super.moveToColorTemperature({ ...request, transitionTime: request.transitionTime ?? 1 }); await homeAssistant.callAction( "light", "turn_on", { color_temp_kelvin: targetKelvin }, { entity_id: homeAssistant.state.entity.entity_id } ); } }; } }); // src/matter/devices/light/color-temperature-light.ts import { ColorTemperatureLightDevice as Device2 } from "@matter/main/devices"; var ColorTemperatureLightType; var init_color_temperature_light = __esm({ "src/matter/devices/light/color-temperature-light.ts"() { "use strict"; init_identify_server(); init_basic_information_server(); init_on_off_server(); init_color_temperature_control_server(); init_home_assistant_behavior(); init_level_control_server(); ColorTemperatureLightType = Device2.with( IdentifyServer, BasicInformationServer, HomeAssistantBehavior, OnOffServer, LevelControlServer, ColorTemperatureControlServer ); } }); // src/matter/devices/light/dimmable-light.ts import { DimmableLightDevice as Device3 } from "@matter/main/devices"; var DimmableLightType; var init_dimmable_light = __esm({ "src/matter/devices/light/dimmable-light.ts"() { "use strict"; init_identify_server(); init_basic_information_server(); init_on_off_server(); init_home_assistant_behavior(); init_level_control_server(); DimmableLightType = Device3.with( IdentifyServer, BasicInformationServer, HomeAssistantBehavior, OnOffServer, LevelControlServer ); } }); // src/matter/devices/light/on-off-light-device.ts import {