home-assistant-matter-hub
Version:

1,397 lines (1,339 loc) • 157 kB
JavaScript
#!/usr/bin/env node
// src/cli.ts
import "./bootstrap.js";
import * as path4 from "node:path";
import * as url from "node:url";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
// src/commands/start/start-command.ts
import "@matter/nodejs";
// src/commands/start/start-handler.ts
import { Environment as Environment2, VendorId as VendorId2 } from "@matter/main";
import AsyncLock3 from "async-lock";
import * as ws from "ws";
// src/api/web-api.ts
import express3 from "express";
import basicAuth from "express-basic-auth";
import AccessControl from "express-ip-access-control";
import nocache from "nocache";
// src/environment/logger.ts
import { LogFormat, Logger, LogLevel as MatterLogLevel } from "@matter/general";
function logging(environment, level, disableColors) {
const loggerService = new LoggerService();
loggerService.level = logLevelFromString(level ?? "info");
if (disableColors) {
loggerService.format = LogFormat.PLAIN;
}
environment.set(LoggerService, loggerService);
}
function logLevelFromString(level) {
const customNames = {
SILLY: -1 /* SILLY */
};
if (level.toUpperCase() in customNames) {
return customNames[level.toUpperCase()];
}
return MatterLogLevel(level);
}
var LoggerService = class {
_level = MatterLogLevel.INFO;
customLogLevelMapping = {
[-1 /* SILLY */]: MatterLogLevel.DEBUG
};
set level(value) {
this._level = value ?? this._level;
Logger.level = this.customLogLevelMapping[this._level] ?? this._level;
}
set format(format) {
Logger.format = format;
}
get(name) {
return new BetterLogger(name, this._level);
}
};
var BetterLogger = class extends Logger {
constructor(name, _level) {
super(name);
this._level = _level;
}
silly(...values) {
if (this._level <= -1 /* SILLY */) {
this.debug(...["SILLY", ...values]);
}
}
};
// src/environment/register.ts
function register(environment, factory, service) {
const logger = environment.get(LoggerService).get("Environment");
environment.set(factory, service);
const close = async () => {
logger.debug(`Disposing ${factory.name}`);
await service[Symbol.asyncDispose]?.();
logger.debug(`Disposed ${factory.name}`);
};
environment.runtime.add({ [Symbol.asyncDispose]: close });
}
// src/api/access-log.ts
function accessLogger(logger) {
return (req, res, next) => {
res.on("finish", () => {
logger.debug(
`${req.method} ${decodeURI(req.originalUrl)} ${res.statusCode} ${res.statusMessage} from ${req.socket.remoteAddress}`
);
});
next();
};
}
// ../common/dist/bridge-data.js
var BridgeStatus;
(function(BridgeStatus2) {
BridgeStatus2["Starting"] = "starting";
BridgeStatus2["Running"] = "running";
BridgeStatus2["Stopped"] = "stopped";
BridgeStatus2["Failed"] = "failed";
})(BridgeStatus || (BridgeStatus = {}));
// ../common/dist/home-assistant-filter.js
var HomeAssistantMatcherType;
(function(HomeAssistantMatcherType2) {
HomeAssistantMatcherType2["Pattern"] = "pattern";
HomeAssistantMatcherType2["Domain"] = "domain";
HomeAssistantMatcherType2["Platform"] = "platform";
HomeAssistantMatcherType2["Label"] = "label";
HomeAssistantMatcherType2["Area"] = "area";
HomeAssistantMatcherType2["EntityCategory"] = "entity_category";
})(HomeAssistantMatcherType || (HomeAssistantMatcherType = {}));
// ../common/dist/home-assistant-domain.js
var HomeAssistantDomain;
(function(HomeAssistantDomain2) {
HomeAssistantDomain2["automation"] = "automation";
HomeAssistantDomain2["button"] = "button";
HomeAssistantDomain2["binary_sensor"] = "binary_sensor";
HomeAssistantDomain2["climate"] = "climate";
HomeAssistantDomain2["cover"] = "cover";
HomeAssistantDomain2["fan"] = "fan";
HomeAssistantDomain2["humidifier"] = "humidifier";
HomeAssistantDomain2["input_boolean"] = "input_boolean";
HomeAssistantDomain2["input_button"] = "input_button";
HomeAssistantDomain2["light"] = "light";
HomeAssistantDomain2["lock"] = "lock";
HomeAssistantDomain2["media_player"] = "media_player";
HomeAssistantDomain2["scene"] = "scene";
HomeAssistantDomain2["script"] = "script";
HomeAssistantDomain2["sensor"] = "sensor";
HomeAssistantDomain2["switch"] = "switch";
HomeAssistantDomain2["vacuum"] = "vacuum";
})(HomeAssistantDomain || (HomeAssistantDomain = {}));
// ../common/dist/domains/binary-sensor.js
var BinarySensorDeviceClass;
(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;
(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 = {}));
var ClimateHvacAction;
(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 = {}));
var ClimateDeviceFeature;
(function(ClimateDeviceFeature2) {
ClimateDeviceFeature2[ClimateDeviceFeature2["TARGET_TEMPERATURE"] = 1] = "TARGET_TEMPERATURE";
ClimateDeviceFeature2[ClimateDeviceFeature2["TARGET_TEMPERATURE_RANGE"] = 2] = "TARGET_TEMPERATURE_RANGE";
ClimateDeviceFeature2[ClimateDeviceFeature2["TARGET_HUMIDITY"] = 4] = "TARGET_HUMIDITY";
ClimateDeviceFeature2[ClimateDeviceFeature2["FAN_MODE"] = 8] = "FAN_MODE";
ClimateDeviceFeature2[ClimateDeviceFeature2["PRESET_MODE"] = 16] = "PRESET_MODE";
ClimateDeviceFeature2[ClimateDeviceFeature2["SWING_MODE"] = 32] = "SWING_MODE";
ClimateDeviceFeature2[ClimateDeviceFeature2["AUX_HEAT"] = 64] = "AUX_HEAT";
ClimateDeviceFeature2[ClimateDeviceFeature2["TURN_OFF"] = 128] = "TURN_OFF";
ClimateDeviceFeature2[ClimateDeviceFeature2["TURN_ON"] = 256] = "TURN_ON";
ClimateDeviceFeature2[ClimateDeviceFeature2["SWING_HORIZONTAL_MODE"] = 512] = "SWING_HORIZONTAL_MODE";
})(ClimateDeviceFeature || (ClimateDeviceFeature = {}));
// ../common/dist/domains/cover.js
var CoverDeviceState;
(function(CoverDeviceState2) {
CoverDeviceState2["closed"] = "closed";
CoverDeviceState2["open"] = "open";
CoverDeviceState2["closing"] = "closing";
CoverDeviceState2["opening"] = "opening";
})(CoverDeviceState || (CoverDeviceState = {}));
var CoverSupportedFeatures = {
support_open: 1,
support_close: 2,
support_set_position: 4,
support_stop: 8,
support_open_tilt: 16,
support_close_tilt: 32,
support_stop_tilt: 64,
support_set_tilt_position: 128
};
// ../common/dist/domains/fan.js
var FanDeviceDirection;
(function(FanDeviceDirection2) {
FanDeviceDirection2["FORWARD"] = "forward";
FanDeviceDirection2["REVERSE"] = "reverse";
})(FanDeviceDirection || (FanDeviceDirection = {}));
var FanDeviceFeature;
(function(FanDeviceFeature2) {
FanDeviceFeature2[FanDeviceFeature2["SET_SPEED"] = 1] = "SET_SPEED";
FanDeviceFeature2[FanDeviceFeature2["OSCILLATE"] = 2] = "OSCILLATE";
FanDeviceFeature2[FanDeviceFeature2["DIRECTION"] = 4] = "DIRECTION";
FanDeviceFeature2[FanDeviceFeature2["PRESET_MODE"] = 8] = "PRESET_MODE";
FanDeviceFeature2[FanDeviceFeature2["TURN_OFF"] = 16] = "TURN_OFF";
FanDeviceFeature2[FanDeviceFeature2["TURN_ON"] = 32] = "TURN_ON";
})(FanDeviceFeature || (FanDeviceFeature = {}));
// ../common/dist/domains/light.js
var LightDeviceColorMode;
(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;
(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/media-player.js
var MediaPlayerDeviceClass;
(function(MediaPlayerDeviceClass2) {
MediaPlayerDeviceClass2["Tv"] = "tv";
MediaPlayerDeviceClass2["Speaker"] = "speaker";
MediaPlayerDeviceClass2["Receiver"] = "receiver";
})(MediaPlayerDeviceClass || (MediaPlayerDeviceClass = {}));
var MediaPlayerDeviceFeature;
(function(MediaPlayerDeviceFeature2) {
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["PAUSE"] = 1] = "PAUSE";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["SEEK"] = 2] = "SEEK";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["VOLUME_SET"] = 4] = "VOLUME_SET";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["VOLUME_MUTE"] = 8] = "VOLUME_MUTE";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["PREVIOUS_TRACK"] = 16] = "PREVIOUS_TRACK";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["NEXT_TRACK"] = 32] = "NEXT_TRACK";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["TURN_ON"] = 128] = "TURN_ON";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["TURN_OFF"] = 256] = "TURN_OFF";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["PLAY_MEDIA"] = 512] = "PLAY_MEDIA";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["VOLUME_STEP"] = 1024] = "VOLUME_STEP";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["SELECT_SOURCE"] = 2048] = "SELECT_SOURCE";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["STOP"] = 4096] = "STOP";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["CLEAR_PLAYLIST"] = 8192] = "CLEAR_PLAYLIST";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["PLAY"] = 16384] = "PLAY";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["SHUFFLE_SET"] = 32768] = "SHUFFLE_SET";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["SELECT_SOUND_MODE"] = 65536] = "SELECT_SOUND_MODE";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["BROWSE_MEDIA"] = 131072] = "BROWSE_MEDIA";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["REPEAT_SET"] = 262144] = "REPEAT_SET";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["GROUPING"] = 524288] = "GROUPING";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["MEDIA_ANNOUNCE"] = 1048576] = "MEDIA_ANNOUNCE";
MediaPlayerDeviceFeature2[MediaPlayerDeviceFeature2["MEDIA_ENQUEUE"] = 2097152] = "MEDIA_ENQUEUE";
})(MediaPlayerDeviceFeature || (MediaPlayerDeviceFeature = {}));
// ../common/dist/domains/vacuum.js
var VacuumState;
(function(VacuumState2) {
VacuumState2["cleaning"] = "cleaning";
VacuumState2["docked"] = "docked";
VacuumState2["returning"] = "returning";
VacuumState2["error"] = "error";
VacuumState2["idle"] = "idle";
VacuumState2["paused"] = "paused";
})(VacuumState || (VacuumState = {}));
var VacuumDeviceFeature;
(function(VacuumDeviceFeature2) {
VacuumDeviceFeature2[VacuumDeviceFeature2["TURN_ON"] = 1] = "TURN_ON";
VacuumDeviceFeature2[VacuumDeviceFeature2["TURN_OFF"] = 2] = "TURN_OFF";
VacuumDeviceFeature2[VacuumDeviceFeature2["PAUSE"] = 4] = "PAUSE";
VacuumDeviceFeature2[VacuumDeviceFeature2["STOP"] = 8] = "STOP";
VacuumDeviceFeature2[VacuumDeviceFeature2["RETURN_HOME"] = 16] = "RETURN_HOME";
VacuumDeviceFeature2[VacuumDeviceFeature2["FAN_SPEED"] = 32] = "FAN_SPEED";
VacuumDeviceFeature2[VacuumDeviceFeature2["BATTERY"] = 64] = "BATTERY";
VacuumDeviceFeature2[VacuumDeviceFeature2["STATUS"] = 128] = "STATUS";
VacuumDeviceFeature2[VacuumDeviceFeature2["SEND_COMMAND"] = 256] = "SEND_COMMAND";
VacuumDeviceFeature2[VacuumDeviceFeature2["LOCATE"] = 512] = "LOCATE";
VacuumDeviceFeature2[VacuumDeviceFeature2["CLEAN_SPOT"] = 1024] = "CLEAN_SPOT";
VacuumDeviceFeature2[VacuumDeviceFeature2["MAP"] = 2048] = "MAP";
VacuumDeviceFeature2[VacuumDeviceFeature2["STATE"] = 4096] = "STATE";
VacuumDeviceFeature2[VacuumDeviceFeature2["START"] = 8192] = "START";
})(VacuumDeviceFeature || (VacuumDeviceFeature = {}));
var VacuumFanSpeed;
(function(VacuumFanSpeed2) {
VacuumFanSpeed2["off"] = "off";
VacuumFanSpeed2["low"] = "low";
VacuumFanSpeed2["medium"] = "medium";
VacuumFanSpeed2["high"] = "high";
VacuumFanSpeed2["turbo"] = "turbo";
VacuumFanSpeed2["auto"] = "auto";
VacuumFanSpeed2["max"] = "max";
})(VacuumFanSpeed || (VacuumFanSpeed = {}));
// ../common/dist/utils/color-converter.js
import Color from "color";
var ColorConverter = class _ColorConverter {
/**
* 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(Math.round(hue / 254 * 360), Math.round(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;
const g2 = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
const 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;
}
return (1 + 0.055) * 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 _ColorConverter.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);
}
/**
* Create a color object from `rgbw_color` value
* @param r Red, 0-255
* @param g Green, 0-255
* @param b Blue, 0-255
* @param w White, 0-255
* @return Color
*/
static fromRGBW(r, g, b, w) {
return _ColorConverter.fromRGB(Math.min(255, r + w), Math.min(255, g + w), Math.min(255, b + w));
}
/**
* Create a color object from `rgbww_color` value
* @param r Red, 0-255
* @param g Green, 0-255
* @param b Blue, 0-255
* @param cw Cold White, 0-255
* @param ww Warm White, 0-255
* @returns
*/
static fromRGBWW(r, g, b, cw, ww) {
return _ColorConverter.fromRGBW(r, g, b, (cw + ww) / 2);
}
/**
* 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 Hue and Saturation compatible with Matter
* @param color The Color
* @return [hue, saturation]
*/
static toMatterHS(color) {
const [h, s] = color.hsv().array();
return [Math.round(h / 360 * 254), Math.round(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
* @param rounding Whether to floor or to ceil after conversion
* @param boundaries Min and Max Boundaries to apply
* @return Temperature in Mireds
*/
static temperatureKelvinToMireds(temperatureKelvin, boundaries = [0, 65279]) {
const result = 1e6 / temperatureKelvin;
const [min, max] = boundaries;
return Math.min(Math.max(result, min), max);
}
};
// ../common/dist/clusters/door-lock.js
var DoorLockStatus;
(function(DoorLockStatus2) {
DoorLockStatus2[DoorLockStatus2["locked"] = 1] = "locked";
DoorLockStatus2[DoorLockStatus2["unlocked"] = 2] = "unlocked";
})(DoorLockStatus || (DoorLockStatus = {}));
// ../common/dist/clusters/fan-control.js
var FanControlFanMode;
(function(FanControlFanMode2) {
FanControlFanMode2[FanControlFanMode2["Off"] = 0] = "Off";
FanControlFanMode2[FanControlFanMode2["Low"] = 1] = "Low";
FanControlFanMode2[FanControlFanMode2["Medium"] = 2] = "Medium";
FanControlFanMode2[FanControlFanMode2["High"] = 3] = "High";
FanControlFanMode2[FanControlFanMode2["On"] = 4] = "On";
FanControlFanMode2[FanControlFanMode2["Auto"] = 5] = "Auto";
FanControlFanMode2[FanControlFanMode2["Smart"] = 6] = "Smart";
})(FanControlFanMode || (FanControlFanMode = {}));
var FanControlAirflowDirection;
(function(FanControlAirflowDirection2) {
FanControlAirflowDirection2[FanControlAirflowDirection2["Forward"] = 0] = "Forward";
FanControlAirflowDirection2[FanControlAirflowDirection2["Reverse"] = 1] = "Reverse";
})(FanControlAirflowDirection || (FanControlAirflowDirection = {}));
var FanControlFanModeSequence;
(function(FanControlFanModeSequence2) {
FanControlFanModeSequence2[FanControlFanModeSequence2["OffLowMedHigh"] = 0] = "OffLowMedHigh";
FanControlFanModeSequence2[FanControlFanModeSequence2["OffLowHigh"] = 1] = "OffLowHigh";
FanControlFanModeSequence2[FanControlFanModeSequence2["OffLowMedHighAuto"] = 2] = "OffLowMedHighAuto";
FanControlFanModeSequence2[FanControlFanModeSequence2["OffLowHighAuto"] = 3] = "OffLowHighAuto";
FanControlFanModeSequence2[FanControlFanModeSequence2["OffHighAuto"] = 4] = "OffHighAuto";
FanControlFanModeSequence2[FanControlFanModeSequence2["OffHigh"] = 5] = "OffHigh";
})(FanControlFanModeSequence || (FanControlFanModeSequence = {}));
// ../common/dist/clusters/thermostat.js
var ThermostatSystemMode;
(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/rvc-operational-state.js
var RvcOperationalState;
(function(RvcOperationalState4) {
RvcOperationalState4[RvcOperationalState4["Stopped"] = 0] = "Stopped";
RvcOperationalState4[RvcOperationalState4["Running"] = 1] = "Running";
RvcOperationalState4[RvcOperationalState4["Paused"] = 2] = "Paused";
RvcOperationalState4[RvcOperationalState4["Error"] = 3] = "Error";
RvcOperationalState4[RvcOperationalState4["SeekingCharger"] = 64] = "SeekingCharger";
RvcOperationalState4[RvcOperationalState4["Charging"] = 65] = "Charging";
RvcOperationalState4[RvcOperationalState4["Docked"] = 66] = "Docked";
})(RvcOperationalState || (RvcOperationalState = {}));
// ../common/dist/clusters/index.js
var ClusterId;
(function(ClusterId2) {
ClusterId2["homeAssistantEntity"] = "homeAssistantEntity";
ClusterId2["descriptor"] = "descriptor";
ClusterId2["identify"] = "identify";
ClusterId2["groups"] = "groups";
ClusterId2["bridgedDeviceBasicInformation"] = "bridgedDeviceBasicInformation";
ClusterId2["booleanState"] = "booleanState";
ClusterId2["colorControl"] = "colorControl";
ClusterId2["doorLock"] = "doorLock";
ClusterId2["levelControl"] = "levelControl";
ClusterId2["fanControl"] = "fanControl";
ClusterId2["occupancySensing"] = "occupancySensing";
ClusterId2["onOff"] = "onOff";
ClusterId2["relativeHumidityMeasurement"] = "relativeHumidityMeasurement";
ClusterId2["temperatureMeasurement"] = "temperatureMeasurement";
ClusterId2["thermostat"] = "thermostat";
ClusterId2["windowCovering"] = "windowCovering";
ClusterId2["mediaInput"] = "mediaInput";
ClusterId2["rvcRunMode"] = "rvcRunMode";
ClusterId2["rvcOperationalState"] = "rvcOperationalState";
})(ClusterId || (ClusterId = {}));
// ../common/dist/schemas/bridge-config-schema.js
var homeAssistantMatcherSchema = {
type: "object",
default: { type: "", value: "" },
properties: {
type: {
title: "Type",
type: "string",
enum: Object.values(HomeAssistantMatcherType)
},
value: {
title: "Value",
type: "string",
minLength: 1
}
},
required: ["type", "value"],
additionalProperties: false
};
var homeAssistantFilterSchema = {
title: "Include or exclude entities",
type: "object",
properties: {
include: {
title: "Include",
type: "array",
items: homeAssistantMatcherSchema
},
exclude: {
title: "Exclude",
type: "array",
items: homeAssistantMatcherSchema
}
},
required: ["include", "exclude"],
additionalProperties: false
};
var featureFlagSchema = {
title: "Feature Flags",
type: "object",
properties: {
coverDoNotInvertPercentage: {
title: "Do not invert Percentages for Covers",
description: "Do not invert the percentage of covers to match Home Assistant (not Matter compliant)",
type: "boolean",
default: false
},
includeHiddenEntities: {
title: "Include Hidden Entities",
description: "Include entities that are marked as hidden in Home Assistant",
type: "boolean",
default: false
}
},
additionalProperties: false
};
var bridgeConfigSchema = {
type: "object",
title: "Bridge Config",
properties: {
name: {
title: "Name",
type: "string",
minLength: 1,
maxLength: 32
},
port: {
title: "Port",
type: "number",
minimum: 1
},
countryCode: {
title: "Country Code",
type: "string",
description: "An ISO 3166-1 alpha-2 code to represent the country in which the Node is located. Only needed if the commissioning fails due to missing country code.",
minLength: 2,
maxLength: 3
},
filter: homeAssistantFilterSchema,
featureFlags: featureFlagSchema
},
required: ["name", "port", "filter"],
additionalProperties: false
};
// ../common/dist/schemas/create-bridge-request-schema.js
var createBridgeRequestSchema = bridgeConfigSchema;
// ../common/dist/schemas/update-bridge-request-schema.js
var updateBridgeRequestSchema = {
...bridgeConfigSchema,
properties: {
...bridgeConfigSchema.properties,
id: {
type: "string"
}
},
required: [...bridgeConfigSchema?.required ?? [], "id"]
};
// src/api/matter-api.ts
import { Ajv } from "ajv";
import express from "express";
// src/errors/port-already-in-use-error.ts
var PortAlreadyInUseError = class extends Error {
constructor(port) {
super(`Port ${port} is already in use`);
}
};
// src/matter/bridge-service.ts
import crypto4 from "node:crypto";
import { asyncNew } from "@matter/main";
// src/storage/bridge-storage.ts
import {
Environmental as Environmental2
} from "@matter/main";
// src/storage/app-storage.ts
import {
Environmental,
StorageService
} from "@matter/main";
var AppStorage = class _AppStorage {
constructor(environment) {
this.environment = environment;
register(environment, _AppStorage, this);
this.construction = this.initialize();
}
static [Environmental.create](environment) {
return new _AppStorage(environment);
}
construction;
storageManager;
async initialize() {
this.storageManager = await this.environment.load(StorageService).then((s) => s.open("app"));
}
async [Symbol.asyncDispose]() {
await this.storageManager.close();
}
createContext(context) {
return this.storageManager.createContext(context);
}
};
// src/storage/migrations/bridge/v1-to-v2.ts
async function migrateBridgeV1ToV2(storage2) {
const bridgeIds = JSON.parse(await storage2.get("ids", "[]"));
await storage2.set("ids", bridgeIds);
for (const bridgeId of bridgeIds) {
const bridgeValue = await storage2.get(bridgeId);
if (bridgeValue === void 0) {
continue;
}
const bridge = JSON.parse(bridgeValue);
bridge.compatibility = void 0;
await storage2.set(bridgeId, bridge);
}
return 2;
}
// src/storage/migrations/bridge/v2-to-v3.ts
async function migrateBridgeV2ToV3(storage2) {
const bridgeIdsValue = await storage2.get("ids", []);
let bridgeIds;
if (typeof bridgeIdsValue === "string") {
bridgeIds = JSON.parse(bridgeIdsValue);
await storage2.set("ids", bridgeIds);
} else {
bridgeIds = bridgeIdsValue;
}
for (const bridgeId of bridgeIds) {
const bridgeValue = await storage2.get(bridgeId);
if (bridgeValue === void 0) {
continue;
}
let bridge;
if (typeof bridgeValue === "string") {
bridge = JSON.parse(bridgeValue);
} else {
bridge = bridgeValue;
}
await storage2.set(bridgeId, bridge);
}
return 3;
}
// src/storage/migrations/bridge/v3-to-v4.ts
async function migrateBridgeV3ToV4(storage2) {
const bridgeIds = await storage2.get("ids", []);
for (const bridgeId of bridgeIds) {
const bridgeValue = await storage2.get(bridgeId);
if (bridgeValue === void 0) {
continue;
}
const bridge = bridgeValue;
const featureFlags = bridge.featureFlags;
if (featureFlags) {
featureFlags.coverDoNotInvertPercentage = featureFlags.mimicHaCoverPercentage ?? false;
featureFlags.coverSwapOpenClose = featureFlags.mimicHaCoverPercentage ?? false;
featureFlags.mimicHaCoverPercentage = void 0;
await storage2.set(bridgeId, bridgeValue);
}
}
return 4;
}
// src/storage/migrations/bridge/v4-to-v5.ts
async function migrateBridgeV4ToV5(storage2) {
const bridgeIds = await storage2.get("ids", []);
for (const bridgeId of bridgeIds) {
const bridgeValue = await storage2.get(bridgeId);
if (bridgeValue === void 0) {
continue;
}
const bridge = bridgeValue;
const featureFlags = bridge.featureFlags;
if (featureFlags) {
featureFlags.coverSwapOpenClose = void 0;
featureFlags.matterFans = void 0;
featureFlags.matterSpeakers = void 0;
featureFlags.useOnOffSensorAsDefaultForBinarySensors = void 0;
await storage2.set(bridgeId, bridgeValue);
}
}
return 5;
}
// src/storage/bridge-storage.ts
var BridgeStorage = class _BridgeStorage {
constructor(environment) {
this.environment = environment;
register(environment, _BridgeStorage, this);
this.construction = this.initialize();
}
static [Environmental2.create](environment) {
return new _BridgeStorage(environment);
}
construction;
storage;
_bridges = [];
async initialize() {
const appStorage = await this.environment.load(AppStorage);
this.storage = appStorage.createContext("bridges");
await this.migrate();
const bridgeIds = await this.storage.get("ids", []);
const bridges = await Promise.all(
bridgeIds.map(
async (bridgeId) => this.storage.get(bridgeId)
)
);
this._bridges = bridges.filter((b) => b !== void 0).map((bridge) => bridge);
}
get bridges() {
return this._bridges;
}
async add(bridge) {
const idx = this._bridges.findIndex((b) => b.id === bridge.id);
if (idx !== -1) {
this._bridges[idx] = bridge;
} else {
this._bridges.push(bridge);
}
await this.storage.set(bridge.id, bridge);
await this.persistIds();
}
async remove(bridgeId) {
const idx = this._bridges.findIndex((b) => b.id === bridgeId);
if (idx >= 0) {
this._bridges.splice(idx, 1);
}
await this.storage.delete(bridgeId);
await this.persistIds();
}
async persistIds() {
await this.storage.set(
"ids",
this._bridges.map((b) => b.id)
);
}
async migrate() {
const version = await this.storage.get("version", 1);
let migratedVersion = version;
if (version === 1) {
migratedVersion = await migrateBridgeV1ToV2(this.storage);
} else if (version === 2) {
migratedVersion = await migrateBridgeV2ToV3(this.storage);
} else if (version === 3) {
migratedVersion = await migrateBridgeV3ToV4(this.storage);
} else if (version === 4) {
migratedVersion = await migrateBridgeV4ToV5(this.storage);
}
if (migratedVersion !== version) {
await this.storage.set("version", migratedVersion);
return this.migrate();
}
}
};
// src/matter/bridge/bridge-server-node.ts
import { Endpoint as Endpoint2, Environment } from "@matter/main";
import { AggregatorEndpoint as AggregatorEndpoint2 } from "@matter/main/endpoints";
import { ServerNode as ServerNode2 } from "@matter/main/node";
import _4 from "lodash";
// src/utils/json/create-bridge-server-config.ts
import crypto from "node:crypto";
import { AggregatorEndpoint } from "@matter/main/endpoints";
import { ServerNode } from "@matter/main/node";
// src/utils/trim-to-length.ts
function trimToLength(value, maxLength, suffix) {
const stringValue = value?.toString();
if (!stringValue?.trim().length) {
return void 0;
}
if (stringValue.length <= maxLength) {
return stringValue;
}
return stringValue.substring(0, maxLength - suffix.length) + suffix;
}
// src/utils/json/create-bridge-server-config.ts
function createBridgeServerConfig(environment, data) {
return {
type: ServerNode.RootEndpoint,
environment,
id: data.id,
network: {
port: data.port
},
productDescription: {
name: data.name,
deviceType: AggregatorEndpoint.deviceType
},
basicInformation: {
uniqueId: data.id,
nodeLabel: trimToLength(data.name, 32, "..."),
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,
...data.countryCode ? { location: data.countryCode } : {}
}
};
}
// src/matter/bridge/bridge-data-provider.ts
var BridgeDataProvider = class _BridgeDataProvider {
constructor(environment, getBridgeData) {
this.getBridgeData = getBridgeData;
environment.set(_BridgeDataProvider, this);
}
get basicInformation() {
return this.getBridgeData().basicInformation;
}
get featureFlags() {
return this.getBridgeData().featureFlags ?? {};
}
};
// src/matter/bridge/bridge-device-manager.ts
import { Endpoint } from "@matter/main";
import AsyncLock2 from "async-lock";
import _3 from "lodash";
// src/home-assistant/home-assistant-registry.ts
import { Environmental as Environmental3 } from "@matter/main";
import { getStates } from "home-assistant-js-websocket";
import _ from "lodash";
// src/home-assistant/api/get-registry.ts
async function getRegistry(connection) {
return connection.sendMessagePromise({
type: "config/entity_registry/list"
});
}
async function getDeviceRegistry(connection) {
return connection.sendMessagePromise({
type: "config/device_registry/list"
});
}
// src/home-assistant/api/subscribe-entities.ts
import crypto2 from "node:crypto";
import {
getCollection
} from "home-assistant-js-websocket";
import { atLeastHaVersion } from "home-assistant-js-websocket/dist/util.js";
function processEvent(store, updates) {
const state = { ...store.state };
if (updates.a) {
for (const entityId in updates.a) {
const newState = updates.a[entityId];
const last_changed = new Date(newState.lc * 1e3).toISOString();
state[entityId] = {
entity_id: entityId,
state: newState.s,
attributes: newState.a,
context: typeof newState.c === "string" ? { id: newState.c, parent_id: null, user_id: null } : newState.c,
last_changed,
last_updated: newState.lu ? new Date(newState.lu * 1e3).toISOString() : last_changed
};
}
}
if (updates.r) {
for (const entityId of updates.r) {
delete state[entityId];
}
}
if (updates.c) {
for (const entityId in updates.c) {
let entityState = state[entityId];
if (!entityState) {
console.warn("Received state update for unknown entity", entityId);
continue;
}
entityState = { ...entityState };
const { "+": toAdd, "-": toRemove } = updates.c[entityId];
const attributesChanged = toAdd?.a || toRemove?.a;
const attributes4 = attributesChanged ? { ...entityState.attributes } : entityState.attributes;
if (toAdd) {
if (toAdd.s !== void 0) {
entityState.state = toAdd.s;
}
if (toAdd.c) {
if (typeof toAdd.c === "string") {
entityState.context = { ...entityState.context, id: toAdd.c };
} else {
entityState.context = { ...entityState.context, ...toAdd.c };
}
}
if (toAdd.lc) {
entityState.last_updated = entityState.last_changed = new Date(
toAdd.lc * 1e3
).toISOString();
} else if (toAdd.lu) {
entityState.last_updated = new Date(toAdd.lu * 1e3).toISOString();
}
if (toAdd.a) {
Object.assign(attributes4, toAdd.a);
}
}
if (toRemove?.a) {
for (const key of toRemove.a) {
delete attributes4[key];
}
}
if (attributesChanged) {
entityState.attributes = attributes4;
}
state[entityId] = entityState;
}
}
store.setState(state, true);
}
var subscribeUpdates = (conn, store, entityIds) => {
if (entityIds && entityIds.length === 0) {
return Promise.resolve(async () => {
});
}
return conn.subscribeMessage((ev) => processEvent(store, ev), {
type: "subscribe_entities",
entity_ids: entityIds
});
};
function createEntitiesHash(entityIds) {
return crypto2.createHash("sha256").update(entityIds.join(",")).digest("hex").substring(0, 16);
}
var entitiesColl = (conn, entityIds) => {
if (atLeastHaVersion(conn.haVersion, 2022, 4, 0)) {
return getCollection(
conn,
`_ent_${createEntitiesHash(entityIds)}`,
void 0,
(conn2, store) => subscribeUpdates(conn2, store, entityIds)
);
}
throw new Error(`Home Assistant version ${conn.haVersion} is not supported`);
};
var subscribeEntities = (conn, onChange, entityIds) => entitiesColl(conn, entityIds).subscribe(onChange);
// src/home-assistant/home-assistant-client.ts
import {
ERR_CANNOT_CONNECT,
ERR_INVALID_AUTH,
createConnection,
createLongLivedTokenAuth,
getConfig
} from "home-assistant-js-websocket";
var HomeAssistantClient = class _HomeAssistantClient {
constructor(environment, props) {
this.props = props;
register(environment, _HomeAssistantClient, this);
this.log = environment.get(LoggerService).get("HomeAssistantClient");
this.construction = this.initialize();
}
log;
construction;
connection;
disposed = false;
async initialize() {
if (this.disposed) {
return;
}
try {
this.connection?.close();
this.connection = await createConnection({
auth: createLongLivedTokenAuth(
this.props.url.replace(/\/$/, ""),
this.props.accessToken
)
});
await this.waitForHomeAssistantToBeUpAndRunning();
} catch (reason) {
return this.handleInitializationError(reason);
}
}
async handleInitializationError(reason) {
if (reason === ERR_CANNOT_CONNECT) {
this.log.error(
`Unable to connect to home assistant with url: ${this.props.url}. Retrying in 5 seconds...`
);
await new Promise((resolve) => setTimeout(resolve, 5e3));
return this.initialize();
}
if (reason === ERR_INVALID_AUTH) {
throw new Error(
"Authentication failed while connecting to home assistant"
);
}
throw new Error(`Unable to connect to home assistant: ${reason}`);
}
async waitForHomeAssistantToBeUpAndRunning() {
this.log.info("Waiting for Home Assistant to be up and running");
const getState = async () => {
const s = await getConfig(this.connection).then((config6) => config6.state);
this.log.debug(
`Got an update from Home Assistant. System state is '${s}'.`
);
return s;
};
let state = "NOT_RUNNING";
while (state !== "RUNNING") {
await new Promise((resolve) => setTimeout(resolve, 5e3));
state = await getState();
}
}
async [Symbol.asyncDispose]() {
this.disposed = true;
this.connection?.close();
}
};
// src/home-assistant/home-assistant-registry.ts
var HomeAssistantRegistry = class _HomeAssistantRegistry {
constructor(environment) {
this.environment = environment;
environment.set(_HomeAssistantRegistry, this);
}
static [Environmental3.create](environment) {
return new _HomeAssistantRegistry(environment);
}
async allEntities() {
const client = await this.environment.load(HomeAssistantClient);
const entityRegistry = _.keyBy(
await getRegistry(client.connection),
(r) => r.entity_id
);
const deviceRegistry = _.keyBy(
await getDeviceRegistry(client.connection),
(r) => r.id
);
const entityStates = _.keyBy(
await getStates(client.connection),
(s) => s.entity_id
);
const entityIds = _.uniq(
_.keys(entityRegistry).concat(_.keys(entityStates))
);
const deviceByEntityId = _.fromPairs(
entityIds.map((entityId) => [entityId, entityRegistry[entityId]?.device_id]).filter(([, deviceId]) => !!deviceId).map(([entityId, deviceId]) => [entityId, deviceRegistry[deviceId]])
);
return entityIds.map((entity_id) => ({
entity_id,
registry: entityRegistry[entity_id],
deviceRegistry: deviceByEntityId[entity_id],
state: entityStates[entity_id]
}));
}
async subscribeStates(entityIds, onChange) {
const client = await this.environment.load(HomeAssistantClient);
return subscribeEntities(client.connection, onChange, entityIds);
}
};
// src/utils/errors/invalid-device-error.ts
var InvalidDeviceError = class extends Error {
constructor(reason) {
super(`Invalid device detected. Reason: ${reason}`);
}
};
// src/matter/custom-behaviors/home-assistant-entity-behavior.ts
import { Behavior, EventEmitter } from "@matter/main";
import AsyncLock from "async-lock";
// src/home-assistant/home-assistant-actions.ts
import { Environmental as Environmental4 } from "@matter/main";
import { callService } from "home-assistant-js-websocket";
var HomeAssistantActions = class _HomeAssistantActions {
constructor(environment) {
this.environment = environment;
environment.set(_HomeAssistantActions, this);
this.log = environment.get(LoggerService).get("HomeAssistantActions");
}
log;
static [Environmental4.create](environment) {
return new _HomeAssistantActions(environment);
}
call(action, target, returnResponse) {
const [domain, actionName] = action.action.split(".");
return this.callAction(
domain,
actionName,
action.data,
target,
returnResponse
);
}
async callAction(domain, action, data, target, returnResponse) {
this.log.debug(
`Calling action '${domain}.${action}' for target ${JSON.stringify(target)} with data ${JSON.stringify(data ?? {})}`
);
const client = await this.environment.load(HomeAssistantClient);
const result = await callService(
client.connection,
domain,
action,
data,
target,
returnResponse
);
return result;
}
};
// src/home-assistant/home-assistant-config.ts
import { Environmental as Environmental5 } from "@matter/main";
import { getConfig as getConfig2 } from "home-assistant-js-websocket";
var HomeAssistantConfig = class _HomeAssistantConfig {
static [Environmental5.create](environment) {
return new _HomeAssistantConfig(environment);
}
environment;
logger;
construction;
config;
get unitSystem() {
return this.config.unit_system;
}
constructor(environment) {
register(environment, _HomeAssistantConfig, this);
this.environment = environment;
this.logger = environment.get(LoggerService).get("HomeAssistantConfig");
this.construction = this.initialize();
}
async initialize() {
const { connection } = await this.environment.load(HomeAssistantClient);
this.config = await getConfig2(connection);
}
};
// src/utils/async-observable.ts
import { AsyncObservable as Base } from "@matter/main";
var AsyncObservable = () => Base();
// src/matter/custom-behaviors/home-assistant-entity-behavior.ts
var HomeAssistantEntityBehavior = class extends Behavior {
static id = ClusterId.homeAssistantEntity;
async initialize() {
await this.env.load(HomeAssistantConfig);
this.internal.logger = this.env.get(LoggerService).get(`HomeAssistant / ${this.entityId}`);
}
get entityId() {
return this.entity.entity_id;
}
get entity() {
return this.state.entity;
}
get onChange() {
return this.events.entity$Changed;
}
get isAvailable() {
return this.entity.state.state !== "unavailable";
}
async callAction(action) {
const actions = this.env.get(HomeAssistantActions);
const lock = this.env.get(AsyncLock);
const lockKey2 = this.state.lockKey;
const log = this.internal.logger;
const target = {
entity_id: this.entityId
};
setTimeout(async () => {
await lock.acquire(
lockKey2,
async () => actions.call(action, target, false).catch((error) => log.error(error))
);
}, 0);
}
};
((HomeAssistantEntityBehavior2) => {
class Internal {
logger;
}
HomeAssistantEntityBehavior2.Internal = Internal;
class State {
lockKey;
entity;
}
HomeAssistantEntityBehavior2.State = State;
class Events extends EventEmitter {
entity$Changed = AsyncObservable();
}
HomeAssistantEntityBehavior2.Events = Events;
})(HomeAssistantEntityBehavior || (HomeAssistantEntityBehavior = {}));
// src/matter/devices/automation/index.ts
import { OnOffPlugInUnitDevice } from "@matter/main/devices";
// src/matter/behaviors/basic-information-server.ts
import crypto3 from "node:crypto";
import { VendorId } from "@matter/main";
import { BridgedDeviceBasicInformationServer as Base2 } from "@matter/main/behaviors";
// src/utils/apply-patch-state.ts
import _2 from "lodash";
function applyPatchState(state, patch) {
const actualPatch = {};
const keys = Object.keys(patch);
for (const key of keys) {
const patchValue = patch[key];
if (patchValue !== void 0 && !deepEqual(state[key], patchValue)) {
actualPatch[key] = patchValue;
}
}
if (_2.size(actualPatch) > 0) {
try {
Object.assign(state, actualPatch);
} catch (e) {
throw new Error(
`Failed to patch the following properties: ${JSON.stringify(actualPatch, null, 2)}`,
{ cause: e }
);
}
}
return actualPatch;
}
function deepEqual(a, b) {
if (a == null || b == null) {
return a === b;
}
if (typeof a !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
return false;
}
if (Array.isArray(a) && Array.isArray(b)) {
return a.length === b.length && a.every((vA, idx) => deepEqual(vA, b[idx]));
}
if (typeof a === "object" && typeof b === "object") {
const keys = Object.keys({ ...a, ...b });
return keys.every((key) => deepEqual(a[key], b[key]));
}
return a === b;
}
// src/matter/behaviors/basic-information-server.ts
var BasicInformationServer = class extends Base2 {
async initialize() {
await super.initialize();
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
this.update(homeAssistant.entity);
this.reactTo(homeAssistant.onChange, this.update);
}
update(entity) {
const { basicInformation: basicInformation2 } = this.env.get(BridgeDataProvider);
const device = entity.deviceRegistry;
applyPatchState(this.state, {
vendorId: VendorId(basicInformation2.vendorId),
vendorName: ellipse(32, device?.manufacturer) ?? hash(32, basicInformation2.vendorName),
productName: ellipse(32, device?.model_id) ?? ellipse(32, device?.model) ?? hash(32, basicInformation2.productName),
productLabel: ellipse(64, device?.model) ?? hash(64, basicInformation2.productLabel),
hardwareVersion: basicInformation2.hardwareVersion,
softwareVersion: basicInformation2.softwareVersion,
hardwareVersionString: ellipse(64, device?.hw_version),
softwareVersionString: ellipse(64, device?.sw_version),
nodeLabel: ellipse(32, e