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