home-assistant-matter-hub
Version:

1,467 lines (1,410 loc) • 164 kB
JavaScript
#!/usr/bin/env node
// src/cli.ts
import "./bootstrap.js";
// src/polyfills.ts
var JSON_SPECIAL_KEY_TYPE = "__object__";
var JSON_SPECIAL_KEY_VALUE = "__value__";
BigInt.prototype.toJSON = function() {
return `{"${JSON_SPECIAL_KEY_TYPE}":"BigInt","${JSON_SPECIAL_KEY_VALUE}":"${this.toString()}"}`;
};
// src/cli.ts
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 * 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/core/ioc/service.ts
var Service = class {
constructor(serviceName) {
this.serviceName = serviceName;
}
construction = new Promise((resolve) => {
setImmediate(() => {
const init = this.initialize?.bind(this) ?? (async () => {
});
init().then(resolve);
});
});
};
// 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/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/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/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/index.js
var ClusterId;
(function(ClusterId2) {
ClusterId2["homeAssistantEntity"] = "homeAssistantEntity";
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["illuminanceMeasurement"] = "illuminanceMeasurement";
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/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/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/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/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/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/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/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"]
};
// ../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);
}
};
// src/api/matter-api.ts
import { Ajv } from "ajv";
import express from "express";
// src/utils/json/endpoint-to-json.ts
function endpointToJson(endpoint, parentId) {
const globalId = [parentId, endpoint.id].filter((i) => !!i).join(".");
return {
id: {
global: globalId,
local: endpoint.id
},
type: {
name: endpoint.type.name,
id: `0x${endpoint.type.deviceType.toString(16).padStart(4, "0")}`
},
endpoint: endpoint.number,
state: endpoint.state,
parts: endpoint.parts.map((p) => endpointToJson(p, globalId))
};
}
// src/api/matter-api.ts
var ajv = new Ajv();
function matterApi(bridgeService) {
const router = express.Router();
router.get("/", (_, res) => {
res.status(200).json({});
});
router.get("/bridges", async (_, res) => {
res.status(200).json(bridgeService.bridges.map((b) => b.data));
});
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 {
const bridge = await bridgeService.create(body);
res.status(200).json(bridge.data);
}
});
router.get("/bridges/:bridgeId", async (req, res) => {
const bridgeId = req.params.bridgeId;
const bridge = bridgeService.get(bridgeId);
if (bridge) {
res.status(200).json(bridge.data);
} 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 {
const bridge = await bridgeService.update(body);
if (!bridge) {
res.status(404).send("Not Found");
} else {
res.status(200).json(bridge.data);
}
}
});
router.delete("/bridges/:bridgeId", async (req, res) => {
const bridgeId = req.params.bridgeId;
await bridgeService.delete(bridgeId);
res.status(204).send();
});
router.get("/bridges/:bridgeId/actions/factory-reset", async (req, res) => {
const bridgeId = req.params.bridgeId;
const bridge = bridgeService.bridges.find((b) => b.id === bridgeId);
if (bridge) {
await bridge.factoryReset();
await bridge.start();
res.status(200).json(bridge.data);
} else {
res.status(404).send("Not Found");
}
});
router.get("/bridges/:bridgeId/devices", async (req, res) => {
const bridgeId = req.params.bridgeId;
const bridge = bridgeService.bridges.find((b) => b.id === bridgeId);
if (bridge) {
res.status(200).json(endpointToJson(bridge.server));
} else {
res.status(404).send("Not Found");
}
});
return router;
}
// src/api/proxy-support.ts
import path from "node:path";
var ingressPath = "x-ingress-path";
var forwardedPrefix = "x-forwarded-prefix";
function supportIngress(req, _, next) {
if (!(ingressPath in req.headers)) {
return next();
}
const prefix = req.header(ingressPath);
if (!prefix) {
return next();
}
const baseUrl = buildPath(prefix, req.baseUrl);
req.baseUrl = baseUrl;
req.originalUrl = buildPath(baseUrl, req.url);
req.url = buildPath(req.url);
next();
}
function supportProxyLocation(req, res, next) {
if (!(forwardedPrefix in req.headers)) {
return next();
}
const prefix = req.header(forwardedPrefix);
if (!prefix) {
return next();
}
let baseUrl = buildPath(prefix, req.baseUrl);
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}
if (!req.url.startsWith(`${baseUrl}/`)) {
res.status(400).contentType("text/plain").send(`URL ${req.url} does not match base url ${baseUrl}`);
return;
}
req.baseUrl = baseUrl;
req.originalUrl = req.url;
req.url = req.url.slice(baseUrl.length);
next();
}
function buildPath(...paths) {
let result = path.posix.join(...paths);
if (!result.startsWith("/")) {
result = `/${result}`;
}
return result;
}
// src/api/web-ui.ts
import fs from "node:fs";
import path2 from "node:path";
import express2 from "express";
function webUi(dist) {
const router = express2.Router();
if (dist) {
const index = replaceBase(dist);
router.get("/", index);
router.get("/index.html", index);
router.use(express2.static(dist));
router.get(/.*/, index);
}
return router;
}
function replaceBase(dist) {
return (req, res) => {
let baseUrl = req.baseUrl;
if (!baseUrl.endsWith("/")) {
baseUrl += "/";
}
const content = fs.readFileSync(path2.join(dist, "index.html"), "utf8").replace(
/<!-- BASE -->[\s\S]*<!-- \/BASE -->/,
`<base href='${baseUrl}' />`
);
res.status(200).contentType("text/html").send(content);
};
}
// src/api/web-api.ts
var WebApi = class extends Service {
constructor(logger, bridgeService, props) {
super("WebApi");
this.bridgeService = bridgeService;
this.props = props;
this.log = logger.get(this);
this.accessLogger = accessLogger(this.log.createChild("Access Log"));
}
log;
accessLogger;
app;
server;
async initialize() {
const api = express3.Router();
api.use(express3.json()).use(nocache()).use("/matter", matterApi(this.bridgeService));
const middlewares = [
this.accessLogger,
supportIngress,
supportProxyLocation
];
if (this.props.auth) {
middlewares.push(
basicAuth({
users: { [this.props.auth.username]: this.props.auth.password },
challenge: true,
realm: "Home Assistant Matter Hub"
})
);
this.log.info("Basic authentication enabled");
}
if (this.props.whitelist && this.props.whitelist.length > 0) {
middlewares.push(
AccessControl({
log: (clientIp, access) => {
this.log.silly(
`Client ${clientIp} was ${access ? "granted" : "denied"}`
);
},
mode: "allow",
allows: this.props.whitelist
})
);
}
this.app = express3().use(...middlewares).use("/api", api).use(webUi(this.props.webUiDist));
}
async dispose() {
await new Promise((resolve, reject) => {
this.server?.close((error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
async start() {
if (this.server) {
return;
}
this.server = await new Promise((resolve) => {
const server = this.app.listen(this.props.port, () => {
this.log.info(
`HTTP server (API ${this.props.webUiDist ? "& Web App" : "only"}) listening on port ${this.props.port}`
);
resolve(server);
});
});
}
};
// src/core/app/configure-default-environment.ts
import { Environment, VariableService } from "@matter/main";
import AsyncLock from "async-lock";
// src/core/app/logger.ts
import { LogFormat, Logger, LogLevel as MatterLogLevel } from "@matter/general";
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
};
constructor(options) {
this._level = logLevelFromString(options.level ?? "info");
Logger.level = this.customLogLevelMapping[this._level] ?? this._level;
Logger.format = options.disableColors ? LogFormat.PLAIN : LogFormat.ANSI;
}
get(nameOrService) {
let name;
if (typeof nameOrService === "string") {
name = nameOrService;
} else {
name = nameOrService.serviceName;
}
return new BetterLogger(name, this._level);
}
};
var BetterLogger = class _BetterLogger extends Logger {
constructor(name, _level) {
super(name);
this.name = name;
this._level = _level;
}
createChild(name) {
return new _BetterLogger(`${this.name} / ${name}`, this._level);
}
silly(...values4) {
if (this._level <= -1 /* SILLY */) {
this.debug(...["SILLY", ...values4]);
}
}
};
// src/core/app/mdns.ts
import { MdnsService } from "@matter/main/protocol";
function mdns(env, options) {
new MdnsService(env, {
ipv4: options.ipv4,
networkInterface: options.networkInterface
});
}
// src/core/app/storage.ts
import fs3 from "node:fs";
import os from "node:os";
import path3 from "node:path";
import { StorageService } from "@matter/main";
// src/core/app/storage/custom-storage.ts
import fs2 from "node:fs";
import { StorageBackendDisk } from "@matter/nodejs";
import { forEach } from "lodash-es";
// src/core/app/storage/legacy-custom-storage.ts
import { StorageBackendJsonFile } from "@matter/nodejs";
import { pickBy } from "lodash-es";
var LegacyCustomStorage = class extends StorageBackendJsonFile {
constructor(log, path5) {
super(path5);
this.log = log;
const parser = this;
const serialize = parser.toJson.bind(parser);
const deserialize = parser.fromJson.bind(parser);
parser.fromJson = (json) => {
if (json.trim().length === 0) {
return {};
}
try {
const object = deserialize(json);
return this.removeClusters(object, Object.values(ClusterId));
} catch (e) {
this.log.error(
`Failed to parse json file '${path5}' with content:
${json}
`
);
throw e;
}
};
parser.toJson = (object) => {
const json = serialize(
this.removeClusters(object, [ClusterId.homeAssistantEntity])
);
if (json.trim().length === 0) {
throw new Error(`Tried to write empty storage to ${path5}`);
}
return json;
};
}
removeClusters(object, clusters) {
if (clusters.length === 0) {
return object;
}
const keys3 = Object.keys(object).filter(
(key) => key.startsWith("root.parts.") && clusters.some((cluster) => key.endsWith(`.${cluster}`))
);
return pickBy(object, (_, key) => !keys3.includes(key));
}
};
// src/core/app/storage/custom-storage.ts
var CustomStorage = class extends StorageBackendDisk {
constructor(log, path5) {
super(path5);
this.log = log;
this.path = path5;
}
async initialize() {
await super.initialize();
if (fs2.existsSync(`${this.path}.json`)) {
await this.migrateLegacyStorage();
}
}
async keys(contexts) {
const key = this.getContextBaseKey(contexts);
const clusters = Object.values(ClusterId);
if (key.startsWith("root.parts.aggregator.parts.") && clusters.some((cluster) => key.endsWith(cluster))) {
return [];
}
return await super.keys(contexts);
}
async migrateLegacyStorage() {
const path5 = this.path;
this.log.warn(
`Migrating legacy storage (JSON file) to new storage (directory): ${path5}`
);
const legacyStorage = new LegacyCustomStorage(this.log, `${path5}.json`);
legacyStorage.initialize();
forEach(legacyStorage.data, (values4, context) => {
forEach(values4, (value, key) => {
this.set([context], key, value);
});
});
await legacyStorage.close();
fs2.renameSync(`${path5}.json`, `${path5}/backup.alpha-69.json`);
}
};
// src/core/app/storage.ts
function storage(environment, options) {
const logger = environment.get(LoggerService).get("CustomStorage");
const location = resolveStorageLocation(options.location);
fs3.mkdirSync(location, { recursive: true });
const storageService = environment.get(StorageService);
storageService.location = location;
storageService.factory = (ns) => new CustomStorage(logger, path3.resolve(location, ns));
}
function resolveStorageLocation(storageLocation) {
const homedir = os.homedir();
return storageLocation ? path3.resolve(storageLocation.replace(/^~\//, `${homedir}/`)) : path3.join(homedir, ".home-assistant-matter-hub");
}
// src/core/app/configure-default-environment.ts
function configureDefaultEnvironment(options) {
const env = Environment.default;
env.runtime;
new VariableService(env);
env.set(AsyncLock, new AsyncLock());
env.set(LoggerService, new LoggerService(options.logging));
mdns(env, options.mdns);
storage(env, options.storage);
return env;
}
// src/core/app/options.ts
import { VendorId } from "@matter/main";
var Options = class {
constructor(startOptions) {
this.startOptions = startOptions;
}
get mdns() {
return {
ipv4: true,
networkInterface: notEmpty(this.startOptions.mdnsNetworkInterface)
};
}
get logging() {
return {
level: this.startOptions.logLevel,
disableColors: this.startOptions.disableLogColors ?? false
};
}
get storage() {
return {
location: notEmpty(this.startOptions.storageLocation)
};
}
get homeAssistant() {
return {
url: this.startOptions.homeAssistantUrl,
accessToken: this.startOptions.homeAssistantAccessToken,
refreshInterval: this.startOptions.homeAssistantRefreshInterval
};
}
get webApi() {
const auth = this.startOptions.httpAuthUsername && this.startOptions.httpAuthPassword ? {
username: this.startOptions.httpAuthUsername,
password: this.startOptions.httpAuthPassword
} : void 0;
return {
port: this.startOptions.httpPort,
whitelist: this.startOptions.httpIpWhitelist?.map(
(item) => item.toString()
),
webUiDist: this.startOptions.webUiDist,
auth
};
}
get bridgeService() {
return {
basicInformation: {
vendorId: VendorId(65521),
vendorName: "t0bst4r",
productId: 32768,
productName: "MatterHub",
productLabel: "Home Assistant Matter Hub",
hardwareVersion: (/* @__PURE__ */ new Date()).getFullYear(),
softwareVersion: (/* @__PURE__ */ new Date()).getFullYear()
}
};
}
};
function notEmpty(val) {
const value = val?.trim();
if (value == null || value.length === 0) {
return void 0;
}
return value;
}
// src/core/ioc/app-environment.ts
import { StorageService as StorageService2 } from "@matter/main";
// src/services/bridges/bridge-factory.ts
var BridgeFactory = class extends Service {
};
// src/services/bridges/bridge-service.ts
import crypto from "node:crypto";
var BridgeService = class extends Service {
constructor(bridgeStorage, bridgeFactory, props) {
super("BridgeService");
this.bridgeStorage = bridgeStorage;
this.bridgeFactory = bridgeFactory;
this.props = props;
}
bridges = [];
async initialize() {
for (const data of this.bridgeStorage.bridges) {
await this.addBridge(data);
}
}
async dispose() {
await Promise.all(this.bridges.map((bridge) => bridge.dispose()));
}
async startAll() {
for (const bridge of this.bridges) {
await bridge.start();
}
}
async refreshAll() {
for (const bridge of this.bridges) {
await bridge.refreshDevices();
}
}
get(id) {
return this.bridges.find((bridge) => bridge.id === id);
}
async create(request) {
if (this.portUsed(request.port)) {
throw new Error(`Port already in use: ${request.port}`);
}
const bridge = await this.addBridge({
...request,
id: crypto.randomUUID().replace(/-/g, ""),
basicInformation: this.props.basicInformation
});
await this.bridgeStorage.add(bridge.data);
await bridge.start();
return bridge;
}
async update(request) {
if (this.portUsed(request.port, [request.id])) {
throw new Error(`Port already in use: ${request.port}`);
}
const bridge = this.get(request.id);
if (!bridge) {
return;
}
await bridge.update(request);
await this.bridgeStorage.add(bridge.data);
return bridge;
}
async delete(bridgeId) {
const bridge = this.bridges.find((bridge2) => bridge2.id === bridgeId);
if (!bridge) {
return;
}
await bridge.stop();
await bridge.delete();
await bridge.dispose();
this.bridges.splice(this.bridges.indexOf(bridge), 1);
await this.bridgeStorage.remove(bridgeId);
}
async addBridge(bridgeData) {
const bridge = await this.bridgeFactory.create(bridgeData);
this.bridges.push(bridge);
return bridge;
}
portUsed(port, notId) {
return this.bridges.filter((bridge) => notId == null || !notId.includes(bridge.id)).some((bridge) => bridge.data.port === port);
}
};
// src/services/home-assistant/home-assistant-actions.ts
import { callService } from "home-assistant-js-websocket";
var HomeAssistantActions = class extends Service {
constructor(logger, client) {
super("HomeAssistantActions");
this.client = client;
this.log = logger.get(this);
}
log;
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 result = await callService(
this.client.connection,
domain,
action,
data,
target,
returnResponse
);
return result;
}
};
// src/services/home-assistant/home-assistant-client.ts
import {
createConnection,
createLongLivedTokenAuth,
ERR_CANNOT_CONNECT,
ERR_INVALID_AUTH,
getConfig
} from "home-assistant-js-websocket";
var HomeAssistantClient = class extends Service {
constructor(logger, options) {
super("HomeAssistantClient");
this.options = options;
this.log = logger.get(this);
}
static Options = Symbol.for("HomeAssistantClientProps");
_connection;
log;
get connection() {
return this._connection;
}
async initialize() {
this._connection = await this.createConnection(this.options);
}
async dispose() {
this.connection?.close();
}
async createConnection(props) {
try {
const connection = await createConnection({
auth: createLongLivedTokenAuth(
props.url.replace(/\/$/, ""),
props.accessToken
)
});
await this.waitForHomeAssistantToBeUpAndRunning(connection);
return connection;
} catch (reason) {
return this.handleInitializationError(reason, props);
}
}
async handleInitializationError(reason, props) {
if (reason === ERR_CANNOT_CONNECT) {
this.log.error(
`Unable to connect to home assistant with url: ${props.url}. Retrying in 5 seconds...`
);
await new Promise((resolve) => setTimeout(resolve, 5e3));
return this.createConnection(props);
}
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(connection) {
this.log.info(
"Waiting for Home Assistant to be up and running - the application will be available once a connection to Home Assistant could be established."
);
const getState = async () => {
const s = await getConfig(connection).then((config6) => config6.state);
this.log.debug(
`Got an update from Home Assistant. System state is '${s}'.`
);
return s;
};
let state;
while (state !== "RUNNING") {
await new Promise((resolve) => setTimeout(resolve, 5e3));
state = await getState();
}
this.log.info("Home assistant reported to be up and running");
}
};
// src/services/home-assistant/home-assistant-config.ts
import { getConfig as getConfig2 } from "home-assistant-js-websocket";
var HomeAssistantConfig = class extends Service {
constructor(client) {
super("HomeAssistantConfig");
this.client = client;
}
config;
get unitSystem() {
return this.config.unit_system;
}
async initialize() {
this.config = await getConfig2(this.client.connection);
}
};
// src/services/home-assistant/home-assistant-registry.ts
import { createHash } from "node:crypto";
import { getStates } from "home-assistant-js-websocket";
import { fromPairs, keyBy, keys, uniq, values } from "lodash-es";
// src/services/home-assistant/api/get-registry.ts
async function getRegistry(connection) {
return await connection.sendMessagePromise({
type: "config/entity_registry/list"
});
}
async function getDeviceRegistry(connection) {
return connection.sendMessagePromise({
type: "config/device_registry/list"
});
}
// src/services/home-assistant/home-assistant-registry.ts
var HomeAssistantRegistry = class extends Service {
constructor(client, options) {
super("HomeAssistantRegistry");
this.client = client;
this.options = options;
}
autoRefresh;
_devices = {};
get devices() {
return this._devices;
}
_entities = {};
get entities() {
return this._entities;
}
_states = {};
get states() {
return this._states;
}
async initialize() {
await this.reload();
}
async dispose() {
this.disableAutoRefresh();
}
enableAutoRefresh(onRefresh) {
this.disableAutoRefresh();
this.autoRefresh = setInterval(async () => {
await this.reload();
onRefresh();
}, this.options.refreshInterval * 1e3);
}
disableAutoRefresh() {
if (this.autoRefresh != null) {
clearInterval(this.autoRefresh);
}
this.autoRefresh = void 0;
}
async reload() {
const connection = this.client.connection;
const entityRegistry = await getRegistry(connection);
entityRegistry.forEach((e) => {
e.device_id = e.device_id ?? mockDeviceId(e.entity_id);
});
const entities = keyBy(entityRegistry, "entity_id");
const states = keyBy(
await getStates(connection),
"entity_id"
);
const entityIds = uniq(keys(entities).concat(keys(states)));
const allEntities = keyBy(
entityIds.map((id) => entities[id] ?? { entity_id: id, device_id: id }),
"entity_id"
);
const deviceIds = values(allEntities).map(
(e) => e.device_id ?? e.entity_id
);
const realDevices = keyBy(await getDeviceRegistry(connection), "id");
const missingDeviceIds = uniq(deviceIds.filter((d) => !realDevices[d]));
const missingDevices = fromPairs(missingDeviceIds.map((d) => [d, { id: d }]));
this._devices = { ...missingDevices, ...realDevices };
this._entities = entities;
this._states = states;
}
};
function mockDeviceId(entityId) {
const hash2 = createHash("sha256").update(entityId).digest("hex").substring(0, 29);
return `e__${hash2}`;
}
// src/services/storage/app-storage.ts
var AppStorage = class extends Service {
constructor(storageService) {
super("AppStorage");
this.storageService = storageService;
}
storageManager;
async initialize() {
this.storageManager = await this.storageService.open("app");
}
async dispose() {
await this.storageManager.close();
}
createContext(context) {
return this.storageManager.createContext(context);
}
};
// src/services/storage/m