@riddix/hamh 2.1.0-alpha.856 → 2.1.0-alpha.858
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/backend/cli.js
CHANGED
|
@@ -133321,12 +133321,93 @@ function accessLogger(logger253) {
|
|
|
133321
133321
|
}
|
|
133322
133322
|
|
|
133323
133323
|
// src/api/backup-api.ts
|
|
133324
|
-
import
|
|
133325
|
-
import
|
|
133324
|
+
import fs2 from "node:fs";
|
|
133325
|
+
import path2 from "node:path";
|
|
133326
133326
|
import archiver from "archiver";
|
|
133327
133327
|
import express from "express";
|
|
133328
133328
|
import multer from "multer";
|
|
133329
133329
|
import unzipper from "unzipper";
|
|
133330
|
+
|
|
133331
|
+
// src/plugins/plugin-storage.ts
|
|
133332
|
+
init_esm();
|
|
133333
|
+
import * as fs from "node:fs";
|
|
133334
|
+
import * as path from "node:path";
|
|
133335
|
+
var logger177 = Logger.get("PluginStorage");
|
|
133336
|
+
var SAVE_DEBOUNCE_MS = 500;
|
|
133337
|
+
var safe = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
133338
|
+
function pluginStorageFilePath(storageDir, bridgeId, pluginName) {
|
|
133339
|
+
return path.join(
|
|
133340
|
+
storageDir,
|
|
133341
|
+
`plugin-${safe(bridgeId)}-${safe(pluginName)}.json`
|
|
133342
|
+
);
|
|
133343
|
+
}
|
|
133344
|
+
function pluginStateFilePath(storageDir, bridgeId) {
|
|
133345
|
+
return path.join(storageDir, `plugin-state-${safe(bridgeId)}.json`);
|
|
133346
|
+
}
|
|
133347
|
+
var FilePluginStorage = class {
|
|
133348
|
+
data = {};
|
|
133349
|
+
dirty = false;
|
|
133350
|
+
filePath;
|
|
133351
|
+
saveTimer;
|
|
133352
|
+
constructor(storageDir, bridgeId, pluginName) {
|
|
133353
|
+
this.filePath = pluginStorageFilePath(storageDir, bridgeId, pluginName);
|
|
133354
|
+
this.load();
|
|
133355
|
+
}
|
|
133356
|
+
async get(key, defaultValue) {
|
|
133357
|
+
const value = this.data[key];
|
|
133358
|
+
return value ?? defaultValue;
|
|
133359
|
+
}
|
|
133360
|
+
async set(key, value) {
|
|
133361
|
+
this.data[key] = value;
|
|
133362
|
+
this.dirty = true;
|
|
133363
|
+
this.scheduleSave();
|
|
133364
|
+
}
|
|
133365
|
+
async delete(key) {
|
|
133366
|
+
delete this.data[key];
|
|
133367
|
+
this.dirty = true;
|
|
133368
|
+
this.scheduleSave();
|
|
133369
|
+
}
|
|
133370
|
+
async keys() {
|
|
133371
|
+
return Object.keys(this.data);
|
|
133372
|
+
}
|
|
133373
|
+
load() {
|
|
133374
|
+
try {
|
|
133375
|
+
if (fs.existsSync(this.filePath)) {
|
|
133376
|
+
const raw = fs.readFileSync(this.filePath, "utf-8");
|
|
133377
|
+
this.data = JSON.parse(raw);
|
|
133378
|
+
}
|
|
133379
|
+
} catch (e) {
|
|
133380
|
+
logger177.warn(`Failed to load plugin storage from ${this.filePath}:`, e);
|
|
133381
|
+
this.data = {};
|
|
133382
|
+
}
|
|
133383
|
+
}
|
|
133384
|
+
scheduleSave() {
|
|
133385
|
+
if (this.saveTimer) clearTimeout(this.saveTimer);
|
|
133386
|
+
this.saveTimer = setTimeout(() => this.save(), SAVE_DEBOUNCE_MS);
|
|
133387
|
+
}
|
|
133388
|
+
save() {
|
|
133389
|
+
if (!this.dirty) return;
|
|
133390
|
+
if (this.saveTimer) {
|
|
133391
|
+
clearTimeout(this.saveTimer);
|
|
133392
|
+
this.saveTimer = void 0;
|
|
133393
|
+
}
|
|
133394
|
+
try {
|
|
133395
|
+
const dir = path.dirname(this.filePath);
|
|
133396
|
+
if (!fs.existsSync(dir)) {
|
|
133397
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
133398
|
+
}
|
|
133399
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
|
|
133400
|
+
this.dirty = false;
|
|
133401
|
+
} catch (e) {
|
|
133402
|
+
logger177.warn(`Failed to save plugin storage to ${this.filePath}:`, e);
|
|
133403
|
+
}
|
|
133404
|
+
}
|
|
133405
|
+
flush() {
|
|
133406
|
+
this.save();
|
|
133407
|
+
}
|
|
133408
|
+
};
|
|
133409
|
+
|
|
133410
|
+
// src/api/backup-api.ts
|
|
133330
133411
|
var upload = multer({ storage: multer.memoryStorage() });
|
|
133331
133412
|
function backupApi(bridgeStorage, mappingStorage, storageLocation, backupService, settingsStorage, _bridgeService) {
|
|
133332
133413
|
const router = express.Router();
|
|
@@ -133342,9 +133423,9 @@ function backupApi(bridgeStorage, mappingStorage, storageLocation, backupService
|
|
|
133342
133423
|
}
|
|
133343
133424
|
}
|
|
133344
133425
|
let includesIcons = false;
|
|
133345
|
-
const iconsDir =
|
|
133346
|
-
if (includeIdentity &&
|
|
133347
|
-
const iconFiles =
|
|
133426
|
+
const iconsDir = path2.join(storageLocation, "bridge-icons");
|
|
133427
|
+
if (includeIdentity && fs2.existsSync(iconsDir)) {
|
|
133428
|
+
const iconFiles = fs2.readdirSync(iconsDir);
|
|
133348
133429
|
includesIcons = iconFiles.some((iconFile) => {
|
|
133349
133430
|
const bridgeId = iconFile.split(".")[0];
|
|
133350
133431
|
return bridges.some((b) => b.id === bridgeId);
|
|
@@ -133381,19 +133462,25 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133381
133462
|
`,
|
|
133382
133463
|
{ name: "README.txt" }
|
|
133383
133464
|
);
|
|
133465
|
+
for (const bridge of bridges) {
|
|
133466
|
+
const pluginState = pluginStateFilePath(storageLocation, bridge.id);
|
|
133467
|
+
if (fs2.existsSync(pluginState)) {
|
|
133468
|
+
archive.file(pluginState, { name: `plugin-state/${bridge.id}.json` });
|
|
133469
|
+
}
|
|
133470
|
+
}
|
|
133384
133471
|
if (includeIdentity) {
|
|
133385
133472
|
for (const bridge of bridges) {
|
|
133386
|
-
const bridgeStoragePath =
|
|
133387
|
-
if (
|
|
133473
|
+
const bridgeStoragePath = path2.join(storageLocation, bridge.id);
|
|
133474
|
+
if (fs2.existsSync(bridgeStoragePath)) {
|
|
133388
133475
|
archive.directory(bridgeStoragePath, `identity/${bridge.id}`);
|
|
133389
133476
|
}
|
|
133390
133477
|
}
|
|
133391
133478
|
if (includesIcons) {
|
|
133392
|
-
const iconFiles =
|
|
133479
|
+
const iconFiles = fs2.readdirSync(iconsDir);
|
|
133393
133480
|
for (const iconFile of iconFiles) {
|
|
133394
133481
|
const bridgeId = iconFile.split(".")[0];
|
|
133395
133482
|
if (bridges.some((b) => b.id === bridgeId)) {
|
|
133396
|
-
const iconPath =
|
|
133483
|
+
const iconPath = path2.join(iconsDir, iconFile);
|
|
133397
133484
|
archive.file(iconPath, { name: `bridge-icons/${iconFile}` });
|
|
133398
133485
|
}
|
|
133399
133486
|
}
|
|
@@ -133515,7 +133602,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133515
133602
|
fanRestoreSpeedOnPowerOn: config8.fanRestoreSpeedOnPowerOn,
|
|
133516
133603
|
composedEntities: config8.composedEntities,
|
|
133517
133604
|
disableMomentaryFlip: config8.disableMomentaryFlip,
|
|
133518
|
-
vacuumAscendingRoomOrder: config8.vacuumAscendingRoomOrder
|
|
133605
|
+
vacuumAscendingRoomOrder: config8.vacuumAscendingRoomOrder,
|
|
133606
|
+
vacuumRoomSwitches: config8.vacuumRoomSwitches
|
|
133519
133607
|
});
|
|
133520
133608
|
mappingsRestored++;
|
|
133521
133609
|
}
|
|
@@ -133541,6 +133629,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133541
133629
|
iconsRestored++;
|
|
133542
133630
|
}
|
|
133543
133631
|
}
|
|
133632
|
+
await restorePluginState(zipDirectory, bridge.id, storageLocation);
|
|
133544
133633
|
} catch (e) {
|
|
133545
133634
|
errors.push({
|
|
133546
133635
|
bridgeId: bridge.id,
|
|
@@ -133599,7 +133688,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133599
133688
|
"Content-Disposition",
|
|
133600
133689
|
`attachment; filename="${req.params.filename}"`
|
|
133601
133690
|
);
|
|
133602
|
-
const stream =
|
|
133691
|
+
const stream = fs2.createReadStream(filepath);
|
|
133603
133692
|
stream.pipe(res);
|
|
133604
133693
|
} catch (error) {
|
|
133605
133694
|
const message = error instanceof Error ? error.message : "Failed to download backup";
|
|
@@ -133613,7 +133702,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133613
133702
|
res.status(404).json({ error: "Backup not found" });
|
|
133614
133703
|
return;
|
|
133615
133704
|
}
|
|
133616
|
-
const buffer =
|
|
133705
|
+
const buffer = fs2.readFileSync(filepath);
|
|
133617
133706
|
const options = req.body || {};
|
|
133618
133707
|
const { backupData, zipDirectory } = await extractBackupData(buffer);
|
|
133619
133708
|
const existingIds = new Set(bridgeStorage.bridges.map((b) => b.id));
|
|
@@ -133681,7 +133770,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133681
133770
|
fanWindPresets: config8.fanWindPresets,
|
|
133682
133771
|
composedEntities: config8.composedEntities,
|
|
133683
133772
|
disableMomentaryFlip: config8.disableMomentaryFlip,
|
|
133684
|
-
vacuumAscendingRoomOrder: config8.vacuumAscendingRoomOrder
|
|
133773
|
+
vacuumAscendingRoomOrder: config8.vacuumAscendingRoomOrder,
|
|
133774
|
+
vacuumRoomSwitches: config8.vacuumRoomSwitches
|
|
133685
133775
|
});
|
|
133686
133776
|
mappingsRestored++;
|
|
133687
133777
|
}
|
|
@@ -133707,6 +133797,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133707
133797
|
iconsRestored++;
|
|
133708
133798
|
}
|
|
133709
133799
|
}
|
|
133800
|
+
await restorePluginState(zipDirectory, bridge.id, storageLocation);
|
|
133710
133801
|
} catch (e) {
|
|
133711
133802
|
errors.push({
|
|
133712
133803
|
bridgeId: bridge.id,
|
|
@@ -133774,12 +133865,12 @@ async function extractBackupData(buffer) {
|
|
|
133774
133865
|
return { backupData: data, zipDirectory: directory };
|
|
133775
133866
|
}
|
|
133776
133867
|
function resolveWithin(baseDir, relative) {
|
|
133777
|
-
if (relative.length === 0 ||
|
|
133868
|
+
if (relative.length === 0 || path2.isAbsolute(relative)) {
|
|
133778
133869
|
return null;
|
|
133779
133870
|
}
|
|
133780
|
-
const resolvedBase =
|
|
133781
|
-
const resolvedTarget =
|
|
133782
|
-
if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase +
|
|
133871
|
+
const resolvedBase = path2.resolve(baseDir);
|
|
133872
|
+
const resolvedTarget = path2.resolve(resolvedBase, relative);
|
|
133873
|
+
if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase + path2.sep)) {
|
|
133783
133874
|
return null;
|
|
133784
133875
|
}
|
|
133785
133876
|
return resolvedTarget;
|
|
@@ -133792,8 +133883,8 @@ async function restoreIdentityFiles(zipDirectory, bridgeId, storageLocation) {
|
|
|
133792
133883
|
if (identityFiles.length === 0) {
|
|
133793
133884
|
return false;
|
|
133794
133885
|
}
|
|
133795
|
-
const targetDir =
|
|
133796
|
-
|
|
133886
|
+
const targetDir = path2.join(storageLocation, bridgeId);
|
|
133887
|
+
fs2.mkdirSync(targetDir, { recursive: true });
|
|
133797
133888
|
for (const file of identityFiles) {
|
|
133798
133889
|
const relativePath = file.path.substring(identityPrefix.length);
|
|
133799
133890
|
const targetPath = resolveWithin(targetDir, relativePath);
|
|
@@ -133802,13 +133893,24 @@ async function restoreIdentityFiles(zipDirectory, bridgeId, storageLocation) {
|
|
|
133802
133893
|
`Refusing to restore identity file with unsafe path: ${file.path}`
|
|
133803
133894
|
);
|
|
133804
133895
|
}
|
|
133805
|
-
const targetDirPath =
|
|
133806
|
-
|
|
133896
|
+
const targetDirPath = path2.dirname(targetPath);
|
|
133897
|
+
fs2.mkdirSync(targetDirPath, { recursive: true });
|
|
133807
133898
|
const content = await file.buffer();
|
|
133808
|
-
|
|
133899
|
+
fs2.writeFileSync(targetPath, content);
|
|
133809
133900
|
}
|
|
133810
133901
|
return true;
|
|
133811
133902
|
}
|
|
133903
|
+
async function restorePluginState(zipDirectory, bridgeId, storageLocation) {
|
|
133904
|
+
const entry = zipDirectory.files.find(
|
|
133905
|
+
(f) => f.path === `plugin-state/${bridgeId}.json` && f.type === "File"
|
|
133906
|
+
);
|
|
133907
|
+
if (!entry) {
|
|
133908
|
+
return false;
|
|
133909
|
+
}
|
|
133910
|
+
const content = await entry.buffer();
|
|
133911
|
+
fs2.writeFileSync(pluginStateFilePath(storageLocation, bridgeId), content);
|
|
133912
|
+
return true;
|
|
133913
|
+
}
|
|
133812
133914
|
async function restoreBridgeIcon(zipDirectory, bridgeId, storageLocation) {
|
|
133813
133915
|
const iconPrefix = "bridge-icons/";
|
|
133814
133916
|
const iconFiles = zipDirectory.files.filter(
|
|
@@ -133817,8 +133919,8 @@ async function restoreBridgeIcon(zipDirectory, bridgeId, storageLocation) {
|
|
|
133817
133919
|
if (iconFiles.length === 0) {
|
|
133818
133920
|
return false;
|
|
133819
133921
|
}
|
|
133820
|
-
const iconsDir =
|
|
133821
|
-
|
|
133922
|
+
const iconsDir = path2.join(storageLocation, "bridge-icons");
|
|
133923
|
+
fs2.mkdirSync(iconsDir, { recursive: true });
|
|
133822
133924
|
for (const file of iconFiles) {
|
|
133823
133925
|
const fileName = file.path.substring(iconPrefix.length);
|
|
133824
133926
|
const targetPath = resolveWithin(iconsDir, fileName);
|
|
@@ -133828,7 +133930,7 @@ async function restoreBridgeIcon(zipDirectory, bridgeId, storageLocation) {
|
|
|
133828
133930
|
);
|
|
133829
133931
|
}
|
|
133830
133932
|
const content = await file.buffer();
|
|
133831
|
-
|
|
133933
|
+
fs2.writeFileSync(targetPath, content);
|
|
133832
133934
|
}
|
|
133833
133935
|
return true;
|
|
133834
133936
|
}
|
|
@@ -133837,7 +133939,7 @@ async function restoreBridgeIcon(zipDirectory, bridgeId, storageLocation) {
|
|
|
133837
133939
|
init_dist();
|
|
133838
133940
|
init_esm();
|
|
133839
133941
|
import express2 from "express";
|
|
133840
|
-
var
|
|
133942
|
+
var logger178 = Logger.get("BridgeExportApi");
|
|
133841
133943
|
function migrateFilter(legacyFilter) {
|
|
133842
133944
|
if (!legacyFilter) {
|
|
133843
133945
|
return { include: [], exclude: [] };
|
|
@@ -133988,7 +134090,7 @@ function bridgeExportApi(bridgeStorage) {
|
|
|
133988
134090
|
res.json(result);
|
|
133989
134091
|
} catch (e) {
|
|
133990
134092
|
const message = e instanceof Error ? e.message : String(e);
|
|
133991
|
-
|
|
134093
|
+
logger178.warn(`Failed to import bridges: ${message}`, e);
|
|
133992
134094
|
res.status(400).json({ error: `Failed to import bridges: ${message}` });
|
|
133993
134095
|
}
|
|
133994
134096
|
});
|
|
@@ -133996,16 +134098,16 @@ function bridgeExportApi(bridgeStorage) {
|
|
|
133996
134098
|
}
|
|
133997
134099
|
|
|
133998
134100
|
// src/api/bridge-icon-api.ts
|
|
133999
|
-
import
|
|
134000
|
-
import
|
|
134101
|
+
import fs3 from "node:fs";
|
|
134102
|
+
import path3 from "node:path";
|
|
134001
134103
|
import express3 from "express";
|
|
134002
134104
|
import multer2 from "multer";
|
|
134003
134105
|
var ALLOWED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
|
|
134004
134106
|
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
134005
134107
|
function bridgeIconApi(storageLocation) {
|
|
134006
|
-
const iconsDir =
|
|
134007
|
-
if (!
|
|
134008
|
-
|
|
134108
|
+
const iconsDir = path3.join(storageLocation, "bridge-icons");
|
|
134109
|
+
if (!fs3.existsSync(iconsDir)) {
|
|
134110
|
+
fs3.mkdirSync(iconsDir, { recursive: true });
|
|
134009
134111
|
}
|
|
134010
134112
|
const storage2 = multer2.diskStorage({
|
|
134011
134113
|
destination: (_req, _file, cb) => {
|
|
@@ -134013,12 +134115,12 @@ function bridgeIconApi(storageLocation) {
|
|
|
134013
134115
|
},
|
|
134014
134116
|
filename: (req, file, cb) => {
|
|
134015
134117
|
const bridgeId = req.params.bridgeId;
|
|
134016
|
-
const ext =
|
|
134118
|
+
const ext = path3.extname(file.originalname).toLowerCase();
|
|
134017
134119
|
cb(null, `${bridgeId}${ext}`);
|
|
134018
134120
|
}
|
|
134019
134121
|
});
|
|
134020
134122
|
const fileFilter = (_req, file, cb) => {
|
|
134021
|
-
const ext =
|
|
134123
|
+
const ext = path3.extname(file.originalname).toLowerCase();
|
|
134022
134124
|
if (ALLOWED_EXTENSIONS.includes(ext)) {
|
|
134023
134125
|
cb(null, true);
|
|
134024
134126
|
} else {
|
|
@@ -134051,40 +134153,40 @@ function bridgeIconApi(storageLocation) {
|
|
|
134051
134153
|
"/:bridgeId/exists",
|
|
134052
134154
|
(req, res) => {
|
|
134053
134155
|
const bridgeId = req.params.bridgeId;
|
|
134054
|
-
const files =
|
|
134156
|
+
const files = fs3.readdirSync(iconsDir);
|
|
134055
134157
|
const exists = files.some((f) => f.startsWith(`${bridgeId}.`));
|
|
134056
134158
|
res.json({ exists });
|
|
134057
134159
|
}
|
|
134058
134160
|
);
|
|
134059
134161
|
router.get("/:bridgeId", (req, res) => {
|
|
134060
134162
|
const bridgeId = req.params.bridgeId;
|
|
134061
|
-
const files =
|
|
134163
|
+
const files = fs3.readdirSync(iconsDir);
|
|
134062
134164
|
const iconFile = files.find((f) => f.startsWith(`${bridgeId}.`));
|
|
134063
134165
|
if (!iconFile) {
|
|
134064
134166
|
res.status(404).json({ error: "Icon not found" });
|
|
134065
134167
|
return;
|
|
134066
134168
|
}
|
|
134067
|
-
const filePath =
|
|
134169
|
+
const filePath = path3.join(iconsDir, iconFile);
|
|
134068
134170
|
res.sendFile(filePath);
|
|
134069
134171
|
});
|
|
134070
134172
|
router.delete("/:bridgeId", (req, res) => {
|
|
134071
134173
|
const bridgeId = req.params.bridgeId;
|
|
134072
|
-
const files =
|
|
134174
|
+
const files = fs3.readdirSync(iconsDir);
|
|
134073
134175
|
const iconFile = files.find((f) => f.startsWith(`${bridgeId}.`));
|
|
134074
134176
|
if (!iconFile) {
|
|
134075
134177
|
res.status(404).json({ error: "Icon not found" });
|
|
134076
134178
|
return;
|
|
134077
134179
|
}
|
|
134078
|
-
const filePath =
|
|
134079
|
-
|
|
134180
|
+
const filePath = path3.join(iconsDir, iconFile);
|
|
134181
|
+
fs3.unlinkSync(filePath);
|
|
134080
134182
|
res.json({ success: true });
|
|
134081
134183
|
});
|
|
134082
134184
|
return router;
|
|
134083
134185
|
}
|
|
134084
134186
|
|
|
134085
134187
|
// src/api/device-image-api.ts
|
|
134086
|
-
import
|
|
134087
|
-
import
|
|
134188
|
+
import fs4 from "node:fs";
|
|
134189
|
+
import path4 from "node:path";
|
|
134088
134190
|
import express4 from "express";
|
|
134089
134191
|
import multer3 from "multer";
|
|
134090
134192
|
var ALLOWED_EXTENSIONS2 = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
|
|
@@ -134095,10 +134197,10 @@ function sanitizeEntityId(entityId) {
|
|
|
134095
134197
|
}
|
|
134096
134198
|
function findCustomImage(imagesDir, entityId) {
|
|
134097
134199
|
const sanitized = sanitizeEntityId(entityId);
|
|
134098
|
-
if (!
|
|
134099
|
-
const files =
|
|
134100
|
-
const imageFile = files.find((f) =>
|
|
134101
|
-
return imageFile ?
|
|
134200
|
+
if (!fs4.existsSync(imagesDir)) return void 0;
|
|
134201
|
+
const files = fs4.readdirSync(imagesDir);
|
|
134202
|
+
const imageFile = files.find((f) => path4.parse(f).name === sanitized);
|
|
134203
|
+
return imageFile ? path4.join(imagesDir, imageFile) : void 0;
|
|
134102
134204
|
}
|
|
134103
134205
|
function resolveZ2mImageUrl(haRegistry, entityId) {
|
|
134104
134206
|
const entity = haRegistry.entities[entityId];
|
|
@@ -134108,9 +134210,9 @@ function resolveZ2mImageUrl(haRegistry, entityId) {
|
|
|
134108
134210
|
return `${Z2M_IMAGE_BASE}/${encodeURIComponent(device.model)}.png`;
|
|
134109
134211
|
}
|
|
134110
134212
|
function deviceImageApi(storageLocation, haRegistry) {
|
|
134111
|
-
const imagesDir =
|
|
134112
|
-
if (!
|
|
134113
|
-
|
|
134213
|
+
const imagesDir = path4.join(storageLocation, "device-images");
|
|
134214
|
+
if (!fs4.existsSync(imagesDir)) {
|
|
134215
|
+
fs4.mkdirSync(imagesDir, { recursive: true });
|
|
134114
134216
|
}
|
|
134115
134217
|
const storage2 = multer3.diskStorage({
|
|
134116
134218
|
destination: (_req, _file, cb) => {
|
|
@@ -134118,12 +134220,12 @@ function deviceImageApi(storageLocation, haRegistry) {
|
|
|
134118
134220
|
},
|
|
134119
134221
|
filename: (req, file, cb) => {
|
|
134120
134222
|
const entityId = sanitizeEntityId(req.params.entityId);
|
|
134121
|
-
const ext =
|
|
134223
|
+
const ext = path4.extname(file.originalname).toLowerCase();
|
|
134122
134224
|
cb(null, `${entityId}${ext}`);
|
|
134123
134225
|
}
|
|
134124
134226
|
});
|
|
134125
134227
|
const fileFilter = (_req, file, cb) => {
|
|
134126
|
-
const ext =
|
|
134228
|
+
const ext = path4.extname(file.originalname).toLowerCase();
|
|
134127
134229
|
if (ALLOWED_EXTENSIONS2.includes(ext)) {
|
|
134128
134230
|
cb(null, true);
|
|
134129
134231
|
} else {
|
|
@@ -134171,10 +134273,10 @@ function deviceImageApi(storageLocation, haRegistry) {
|
|
|
134171
134273
|
return;
|
|
134172
134274
|
}
|
|
134173
134275
|
const sanitized = sanitizeEntityId(req.params.entityId);
|
|
134174
|
-
const files =
|
|
134276
|
+
const files = fs4.readdirSync(imagesDir);
|
|
134175
134277
|
for (const f of files) {
|
|
134176
|
-
if (
|
|
134177
|
-
|
|
134278
|
+
if (path4.parse(f).name === sanitized && f !== req.file.filename) {
|
|
134279
|
+
fs4.unlinkSync(path4.join(imagesDir, f));
|
|
134178
134280
|
}
|
|
134179
134281
|
}
|
|
134180
134282
|
res.json({ success: true });
|
|
@@ -134201,7 +134303,7 @@ function deviceImageApi(storageLocation, haRegistry) {
|
|
|
134201
134303
|
res.status(404).json({ error: "No custom image found" });
|
|
134202
134304
|
return;
|
|
134203
134305
|
}
|
|
134204
|
-
|
|
134306
|
+
fs4.unlinkSync(customImage);
|
|
134205
134307
|
res.json({ success: true });
|
|
134206
134308
|
});
|
|
134207
134309
|
router.head("/:entityId", (req, res) => {
|
|
@@ -134533,6 +134635,7 @@ function entityMappingApi(mappingStorage, identityStorage) {
|
|
|
134533
134635
|
currentRoomEntity: body.currentRoomEntity,
|
|
134534
134636
|
cleanedAreaEntity: body.cleanedAreaEntity,
|
|
134535
134637
|
vacuumAscendingRoomOrder: body.vacuumAscendingRoomOrder,
|
|
134638
|
+
vacuumRoomSwitches: body.vacuumRoomSwitches,
|
|
134536
134639
|
disableCustomAreaRoomModes: body.disableCustomAreaRoomModes,
|
|
134537
134640
|
valetudoIdentifier: body.valetudoIdentifier,
|
|
134538
134641
|
coverSwapOpenClose: body.coverSwapOpenClose,
|
|
@@ -135045,7 +135148,8 @@ function configToProfileEntry(config8) {
|
|
|
135045
135148
|
climateExposeFan: config8.climateExposeFan,
|
|
135046
135149
|
climateAutoMode: config8.climateAutoMode,
|
|
135047
135150
|
disableMomentaryFlip: config8.disableMomentaryFlip,
|
|
135048
|
-
vacuumAscendingRoomOrder: config8.vacuumAscendingRoomOrder
|
|
135151
|
+
vacuumAscendingRoomOrder: config8.vacuumAscendingRoomOrder,
|
|
135152
|
+
vacuumRoomSwitches: config8.vacuumRoomSwitches
|
|
135049
135153
|
};
|
|
135050
135154
|
}
|
|
135051
135155
|
function mappingProfileApi(mappingStorage) {
|
|
@@ -135189,7 +135293,8 @@ function mappingProfileApi(mappingStorage) {
|
|
|
135189
135293
|
climateExposeFan: entry.climateExposeFan,
|
|
135190
135294
|
climateAutoMode: entry.climateAutoMode,
|
|
135191
135295
|
disableMomentaryFlip: entry.disableMomentaryFlip,
|
|
135192
|
-
vacuumAscendingRoomOrder: entry.vacuumAscendingRoomOrder
|
|
135296
|
+
vacuumAscendingRoomOrder: entry.vacuumAscendingRoomOrder,
|
|
135297
|
+
vacuumRoomSwitches: entry.vacuumRoomSwitches
|
|
135193
135298
|
});
|
|
135194
135299
|
applied++;
|
|
135195
135300
|
} catch (e) {
|
|
@@ -136593,26 +136698,26 @@ var BUILTIN_PLUGIN_NAMES = ["camera", "security"];
|
|
|
136593
136698
|
// src/plugins/plugin-installer.ts
|
|
136594
136699
|
init_esm();
|
|
136595
136700
|
import { execFile } from "node:child_process";
|
|
136596
|
-
import * as
|
|
136597
|
-
import * as
|
|
136598
|
-
var
|
|
136701
|
+
import * as fs5 from "node:fs";
|
|
136702
|
+
import * as path5 from "node:path";
|
|
136703
|
+
var logger179 = Logger.get("PluginInstaller");
|
|
136599
136704
|
var VALID_PACKAGE_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(@[^@\s]+)?$/;
|
|
136600
136705
|
var PluginInstaller = class {
|
|
136601
136706
|
pluginDir;
|
|
136602
136707
|
constructor(storageLocation) {
|
|
136603
|
-
this.pluginDir =
|
|
136708
|
+
this.pluginDir = path5.join(storageLocation, "plugin-packages");
|
|
136604
136709
|
this.ensurePluginDir();
|
|
136605
136710
|
}
|
|
136606
136711
|
get installDir() {
|
|
136607
136712
|
return this.pluginDir;
|
|
136608
136713
|
}
|
|
136609
136714
|
ensurePluginDir() {
|
|
136610
|
-
if (!
|
|
136611
|
-
|
|
136715
|
+
if (!fs5.existsSync(this.pluginDir)) {
|
|
136716
|
+
fs5.mkdirSync(this.pluginDir, { recursive: true });
|
|
136612
136717
|
}
|
|
136613
|
-
const pkgJson =
|
|
136614
|
-
if (!
|
|
136615
|
-
|
|
136718
|
+
const pkgJson = path5.join(this.pluginDir, "package.json");
|
|
136719
|
+
if (!fs5.existsSync(pkgJson)) {
|
|
136720
|
+
fs5.writeFileSync(
|
|
136616
136721
|
pkgJson,
|
|
136617
136722
|
JSON.stringify(
|
|
136618
136723
|
{
|
|
@@ -136635,7 +136740,7 @@ var PluginInstaller = class {
|
|
|
136635
136740
|
error: `Invalid package name: "${packageName}"`
|
|
136636
136741
|
};
|
|
136637
136742
|
}
|
|
136638
|
-
|
|
136743
|
+
logger179.info(`Installing plugin: ${packageName}`);
|
|
136639
136744
|
return new Promise((resolve11) => {
|
|
136640
136745
|
execFile(
|
|
136641
136746
|
"npm",
|
|
@@ -136647,7 +136752,7 @@ var PluginInstaller = class {
|
|
|
136647
136752
|
},
|
|
136648
136753
|
(error, _stdout, stderr) => {
|
|
136649
136754
|
if (error) {
|
|
136650
|
-
|
|
136755
|
+
logger179.error(
|
|
136651
136756
|
`Failed to install ${packageName}:`,
|
|
136652
136757
|
stderr || error.message
|
|
136653
136758
|
);
|
|
@@ -136659,7 +136764,7 @@ var PluginInstaller = class {
|
|
|
136659
136764
|
return;
|
|
136660
136765
|
}
|
|
136661
136766
|
const version2 = this.getInstalledVersion(packageName);
|
|
136662
|
-
|
|
136767
|
+
logger179.info(`Installed ${packageName}@${version2 || "unknown"}`);
|
|
136663
136768
|
resolve11({
|
|
136664
136769
|
success: true,
|
|
136665
136770
|
packageName,
|
|
@@ -136677,7 +136782,7 @@ var PluginInstaller = class {
|
|
|
136677
136782
|
error: `Invalid package name: "${packageName}"`
|
|
136678
136783
|
};
|
|
136679
136784
|
}
|
|
136680
|
-
|
|
136785
|
+
logger179.info(`Uninstalling plugin: ${packageName}`);
|
|
136681
136786
|
return new Promise((resolve11) => {
|
|
136682
136787
|
execFile(
|
|
136683
136788
|
"npm",
|
|
@@ -136688,7 +136793,7 @@ var PluginInstaller = class {
|
|
|
136688
136793
|
},
|
|
136689
136794
|
(error, _stdout, stderr) => {
|
|
136690
136795
|
if (error) {
|
|
136691
|
-
|
|
136796
|
+
logger179.error(
|
|
136692
136797
|
`Failed to uninstall ${packageName}:`,
|
|
136693
136798
|
stderr || error.message
|
|
136694
136799
|
);
|
|
@@ -136699,7 +136804,7 @@ var PluginInstaller = class {
|
|
|
136699
136804
|
});
|
|
136700
136805
|
return;
|
|
136701
136806
|
}
|
|
136702
|
-
|
|
136807
|
+
logger179.info(`Uninstalled ${packageName}`);
|
|
136703
136808
|
resolve11({ success: true, packageName });
|
|
136704
136809
|
}
|
|
136705
136810
|
);
|
|
@@ -136710,16 +136815,16 @@ var PluginInstaller = class {
|
|
|
136710
136815
|
* This is used by PluginManager.loadExternal() to import the plugin.
|
|
136711
136816
|
*/
|
|
136712
136817
|
getPluginPath(packageName) {
|
|
136713
|
-
return
|
|
136818
|
+
return path5.join(this.pluginDir, "node_modules", packageName);
|
|
136714
136819
|
}
|
|
136715
136820
|
/**
|
|
136716
136821
|
* List all installed plugin packages from the plugin directory's package.json.
|
|
136717
136822
|
*/
|
|
136718
136823
|
listInstalled() {
|
|
136719
136824
|
try {
|
|
136720
|
-
const pkgJson =
|
|
136721
|
-
if (!
|
|
136722
|
-
const pkg = JSON.parse(
|
|
136825
|
+
const pkgJson = path5.join(this.pluginDir, "package.json");
|
|
136826
|
+
if (!fs5.existsSync(pkgJson)) return [];
|
|
136827
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgJson, "utf-8"));
|
|
136723
136828
|
const deps = pkg.dependencies ?? {};
|
|
136724
136829
|
return Object.entries(deps).map(([name, ver]) => ({
|
|
136725
136830
|
name,
|
|
@@ -136730,9 +136835,9 @@ var PluginInstaller = class {
|
|
|
136730
136835
|
}
|
|
136731
136836
|
}
|
|
136732
136837
|
async installFromTgz(tgzBuffer) {
|
|
136733
|
-
const tgzPath =
|
|
136838
|
+
const tgzPath = path5.join(this.pluginDir, `.upload-${Date.now()}.tgz`);
|
|
136734
136839
|
try {
|
|
136735
|
-
|
|
136840
|
+
fs5.writeFileSync(tgzPath, tgzBuffer);
|
|
136736
136841
|
const depsBefore = new Set(Object.keys(this.readDeps()));
|
|
136737
136842
|
const result = await this.installFromNpm(tgzPath);
|
|
136738
136843
|
if (!result.success) return result;
|
|
@@ -136749,11 +136854,11 @@ var PluginInstaller = class {
|
|
|
136749
136854
|
return result;
|
|
136750
136855
|
} catch (e) {
|
|
136751
136856
|
const msg = e instanceof Error ? e.message : String(e);
|
|
136752
|
-
|
|
136857
|
+
logger179.error("Failed to install from tgz:", msg);
|
|
136753
136858
|
return { success: false, packageName: "unknown", error: msg };
|
|
136754
136859
|
} finally {
|
|
136755
136860
|
try {
|
|
136756
|
-
if (
|
|
136861
|
+
if (fs5.existsSync(tgzPath)) fs5.unlinkSync(tgzPath);
|
|
136757
136862
|
} catch {
|
|
136758
136863
|
}
|
|
136759
136864
|
}
|
|
@@ -136772,14 +136877,14 @@ var PluginInstaller = class {
|
|
|
136772
136877
|
if (error) {
|
|
136773
136878
|
resolve11({
|
|
136774
136879
|
success: false,
|
|
136775
|
-
packageName:
|
|
136880
|
+
packageName: path5.basename(target),
|
|
136776
136881
|
error: stderr || error.message
|
|
136777
136882
|
});
|
|
136778
136883
|
return;
|
|
136779
136884
|
}
|
|
136780
136885
|
resolve11({
|
|
136781
136886
|
success: true,
|
|
136782
|
-
packageName:
|
|
136887
|
+
packageName: path5.basename(target)
|
|
136783
136888
|
});
|
|
136784
136889
|
}
|
|
136785
136890
|
);
|
|
@@ -136787,25 +136892,25 @@ var PluginInstaller = class {
|
|
|
136787
136892
|
}
|
|
136788
136893
|
readDeps() {
|
|
136789
136894
|
try {
|
|
136790
|
-
const pkgJson =
|
|
136791
|
-
if (!
|
|
136792
|
-
const pkg = JSON.parse(
|
|
136895
|
+
const pkgJson = path5.join(this.pluginDir, "package.json");
|
|
136896
|
+
if (!fs5.existsSync(pkgJson)) return {};
|
|
136897
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgJson, "utf-8"));
|
|
136793
136898
|
return pkg.dependencies ?? {};
|
|
136794
136899
|
} catch {
|
|
136795
136900
|
return {};
|
|
136796
136901
|
}
|
|
136797
136902
|
}
|
|
136798
136903
|
installFromLocal(localPath) {
|
|
136799
|
-
const resolvedPath =
|
|
136800
|
-
if (!
|
|
136904
|
+
const resolvedPath = path5.resolve(localPath);
|
|
136905
|
+
if (!fs5.existsSync(resolvedPath)) {
|
|
136801
136906
|
return {
|
|
136802
136907
|
success: false,
|
|
136803
136908
|
packageName: "unknown",
|
|
136804
136909
|
error: `Path does not exist: ${resolvedPath}`
|
|
136805
136910
|
};
|
|
136806
136911
|
}
|
|
136807
|
-
const pkgJsonPath =
|
|
136808
|
-
if (!
|
|
136912
|
+
const pkgJsonPath = path5.join(resolvedPath, "package.json");
|
|
136913
|
+
if (!fs5.existsSync(pkgJsonPath)) {
|
|
136809
136914
|
return {
|
|
136810
136915
|
success: false,
|
|
136811
136916
|
packageName: "unknown",
|
|
@@ -136814,7 +136919,7 @@ var PluginInstaller = class {
|
|
|
136814
136919
|
}
|
|
136815
136920
|
let pkg;
|
|
136816
136921
|
try {
|
|
136817
|
-
pkg = JSON.parse(
|
|
136922
|
+
pkg = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
|
|
136818
136923
|
} catch {
|
|
136819
136924
|
return {
|
|
136820
136925
|
success: false,
|
|
@@ -136830,13 +136935,13 @@ var PluginInstaller = class {
|
|
|
136830
136935
|
error: "Invalid package.json: missing 'name' field"
|
|
136831
136936
|
};
|
|
136832
136937
|
}
|
|
136833
|
-
const targetLink =
|
|
136834
|
-
if (
|
|
136835
|
-
|
|
136938
|
+
const targetLink = path5.join(this.pluginDir, "node_modules", packageName);
|
|
136939
|
+
if (fs5.existsSync(targetLink)) {
|
|
136940
|
+
fs5.rmSync(targetLink, { recursive: true, force: true });
|
|
136836
136941
|
}
|
|
136837
|
-
|
|
136838
|
-
|
|
136839
|
-
|
|
136942
|
+
fs5.mkdirSync(path5.dirname(targetLink), { recursive: true });
|
|
136943
|
+
fs5.symlinkSync(resolvedPath, targetLink, "dir");
|
|
136944
|
+
logger179.info(
|
|
136840
136945
|
`Linked local plugin: ${packageName}@${pkg.version || "unknown"} \u2192 ${resolvedPath}`
|
|
136841
136946
|
);
|
|
136842
136947
|
return {
|
|
@@ -136847,14 +136952,14 @@ var PluginInstaller = class {
|
|
|
136847
136952
|
}
|
|
136848
136953
|
getInstalledVersion(packageName) {
|
|
136849
136954
|
try {
|
|
136850
|
-
const pkgPath =
|
|
136955
|
+
const pkgPath = path5.join(
|
|
136851
136956
|
this.pluginDir,
|
|
136852
136957
|
"node_modules",
|
|
136853
136958
|
packageName,
|
|
136854
136959
|
"package.json"
|
|
136855
136960
|
);
|
|
136856
|
-
if (
|
|
136857
|
-
const pkg = JSON.parse(
|
|
136961
|
+
if (fs5.existsSync(pkgPath)) {
|
|
136962
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
136858
136963
|
return pkg.version ?? null;
|
|
136859
136964
|
}
|
|
136860
136965
|
} catch {
|
|
@@ -136865,40 +136970,40 @@ var PluginInstaller = class {
|
|
|
136865
136970
|
|
|
136866
136971
|
// src/plugins/plugin-registry.ts
|
|
136867
136972
|
init_esm();
|
|
136868
|
-
import * as
|
|
136869
|
-
import * as
|
|
136870
|
-
var
|
|
136973
|
+
import * as fs6 from "node:fs";
|
|
136974
|
+
import * as path6 from "node:path";
|
|
136975
|
+
var logger180 = Logger.get("PluginRegistry");
|
|
136871
136976
|
var PluginRegistry = class {
|
|
136872
136977
|
plugins = [];
|
|
136873
136978
|
filePath;
|
|
136874
136979
|
constructor(storageLocation) {
|
|
136875
|
-
this.filePath =
|
|
136980
|
+
this.filePath = path6.join(storageLocation, "installed-plugins.json");
|
|
136876
136981
|
this.load();
|
|
136877
136982
|
}
|
|
136878
136983
|
load() {
|
|
136879
136984
|
try {
|
|
136880
|
-
if (
|
|
136881
|
-
const raw =
|
|
136985
|
+
if (fs6.existsSync(this.filePath)) {
|
|
136986
|
+
const raw = fs6.readFileSync(this.filePath, "utf-8");
|
|
136882
136987
|
this.plugins = JSON.parse(raw);
|
|
136883
136988
|
}
|
|
136884
136989
|
} catch (e) {
|
|
136885
|
-
|
|
136990
|
+
logger180.warn("Failed to load plugin registry:", e);
|
|
136886
136991
|
this.plugins = [];
|
|
136887
136992
|
}
|
|
136888
136993
|
}
|
|
136889
136994
|
save() {
|
|
136890
136995
|
try {
|
|
136891
|
-
const dir =
|
|
136892
|
-
if (!
|
|
136893
|
-
|
|
136996
|
+
const dir = path6.dirname(this.filePath);
|
|
136997
|
+
if (!fs6.existsSync(dir)) {
|
|
136998
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
136894
136999
|
}
|
|
136895
|
-
|
|
137000
|
+
fs6.writeFileSync(
|
|
136896
137001
|
this.filePath,
|
|
136897
137002
|
JSON.stringify(this.plugins, null, 2),
|
|
136898
137003
|
"utf-8"
|
|
136899
137004
|
);
|
|
136900
137005
|
} catch (e) {
|
|
136901
|
-
|
|
137006
|
+
logger180.error("Failed to save plugin registry:", e);
|
|
136902
137007
|
}
|
|
136903
137008
|
}
|
|
136904
137009
|
getAll() {
|
|
@@ -137031,19 +137136,35 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
137031
137136
|
}
|
|
137032
137137
|
return bridge;
|
|
137033
137138
|
}
|
|
137034
|
-
router.post("/:bridgeId/:pluginName/enable", (req, res) => {
|
|
137139
|
+
router.post("/:bridgeId/:pluginName/enable", async (req, res) => {
|
|
137035
137140
|
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
137036
137141
|
if (!bridge) return;
|
|
137037
137142
|
const { pluginName } = req.params;
|
|
137038
|
-
bridge.enablePlugin(pluginName);
|
|
137039
|
-
|
|
137143
|
+
const metadata = await bridge.enablePlugin(pluginName);
|
|
137144
|
+
if (!metadata) {
|
|
137145
|
+
res.status(404).json({ error: "Plugin not found" });
|
|
137146
|
+
return;
|
|
137147
|
+
}
|
|
137148
|
+
res.json({
|
|
137149
|
+
success: metadata.enabled === true,
|
|
137150
|
+
pluginName,
|
|
137151
|
+
enabled: metadata.enabled
|
|
137152
|
+
});
|
|
137040
137153
|
});
|
|
137041
|
-
router.post("/:bridgeId/:pluginName/disable", (req, res) => {
|
|
137154
|
+
router.post("/:bridgeId/:pluginName/disable", async (req, res) => {
|
|
137042
137155
|
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
137043
137156
|
if (!bridge) return;
|
|
137044
137157
|
const { pluginName } = req.params;
|
|
137045
|
-
bridge.disablePlugin(pluginName);
|
|
137046
|
-
|
|
137158
|
+
const metadata = await bridge.disablePlugin(pluginName);
|
|
137159
|
+
if (!metadata) {
|
|
137160
|
+
res.status(404).json({ error: "Plugin not found" });
|
|
137161
|
+
return;
|
|
137162
|
+
}
|
|
137163
|
+
res.json({
|
|
137164
|
+
success: metadata.enabled === false,
|
|
137165
|
+
pluginName,
|
|
137166
|
+
enabled: metadata.enabled
|
|
137167
|
+
});
|
|
137047
137168
|
});
|
|
137048
137169
|
router.get("/:bridgeId/:pluginName/config-schema", (req, res) => {
|
|
137049
137170
|
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
@@ -137244,7 +137365,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
137244
137365
|
}
|
|
137245
137366
|
|
|
137246
137367
|
// src/api/proxy-support.ts
|
|
137247
|
-
import
|
|
137368
|
+
import path7 from "node:path";
|
|
137248
137369
|
var ingressPath = "x-ingress-path";
|
|
137249
137370
|
var forwardedPrefix = "x-forwarded-prefix";
|
|
137250
137371
|
function supportIngress(req, _, next) {
|
|
@@ -137283,7 +137404,7 @@ function supportProxyLocation(req, res, next) {
|
|
|
137283
137404
|
next();
|
|
137284
137405
|
}
|
|
137285
137406
|
function buildPath(...paths) {
|
|
137286
|
-
let result =
|
|
137407
|
+
let result = path7.posix.join(...paths);
|
|
137287
137408
|
if (!result.startsWith("/")) {
|
|
137288
137409
|
result = `/${result}`;
|
|
137289
137410
|
}
|
|
@@ -137555,7 +137676,7 @@ import { promisify } from "node:util";
|
|
|
137555
137676
|
import v8 from "node:v8";
|
|
137556
137677
|
import express15 from "express";
|
|
137557
137678
|
var execAsync = promisify(exec);
|
|
137558
|
-
var
|
|
137679
|
+
var logger181 = Logger.get("SystemApi");
|
|
137559
137680
|
function detectEnvironment2() {
|
|
137560
137681
|
if (process.env.SUPERVISOR_TOKEN || process.env.HASSIO_TOKEN) {
|
|
137561
137682
|
return "Home Assistant Add-on";
|
|
@@ -137602,7 +137723,7 @@ function systemApi(version2) {
|
|
|
137602
137723
|
const data = await response.json();
|
|
137603
137724
|
res.json(toUpdateCheckResponse(version2, data, detectEnvironment2()));
|
|
137604
137725
|
} catch (error) {
|
|
137605
|
-
|
|
137726
|
+
logger181.error("Failed to check for updates:", error);
|
|
137606
137727
|
res.status(500).json({ error: "Failed to check for updates" });
|
|
137607
137728
|
}
|
|
137608
137729
|
});
|
|
@@ -137651,7 +137772,7 @@ function systemApi(version2) {
|
|
|
137651
137772
|
};
|
|
137652
137773
|
res.json(systemInfo);
|
|
137653
137774
|
} catch (error) {
|
|
137654
|
-
|
|
137775
|
+
logger181.error("Failed to get system info:", error);
|
|
137655
137776
|
res.status(500).json({ error: "Failed to get system info" });
|
|
137656
137777
|
}
|
|
137657
137778
|
});
|
|
@@ -137694,7 +137815,7 @@ async function getStorageInfo() {
|
|
|
137694
137815
|
return await getUnixStorageInfo(pathToCheck);
|
|
137695
137816
|
}
|
|
137696
137817
|
} catch (error) {
|
|
137697
|
-
|
|
137818
|
+
logger181.error("Failed to get storage info:", error);
|
|
137698
137819
|
return { total: 0, used: 0, free: 0 };
|
|
137699
137820
|
}
|
|
137700
137821
|
}
|
|
@@ -137763,8 +137884,8 @@ async function getUnixStorageInfo(path14) {
|
|
|
137763
137884
|
}
|
|
137764
137885
|
|
|
137765
137886
|
// src/api/web-ui.ts
|
|
137766
|
-
import
|
|
137767
|
-
import
|
|
137887
|
+
import fs7 from "node:fs";
|
|
137888
|
+
import path8 from "node:path";
|
|
137768
137889
|
import express16 from "express";
|
|
137769
137890
|
function webUi(dist) {
|
|
137770
137891
|
const router = express16.Router();
|
|
@@ -137783,7 +137904,7 @@ function replaceBase(dist) {
|
|
|
137783
137904
|
if (!baseUrl.endsWith("/")) {
|
|
137784
137905
|
baseUrl += "/";
|
|
137785
137906
|
}
|
|
137786
|
-
const content =
|
|
137907
|
+
const content = fs7.readFileSync(path8.join(dist, "index.html"), "utf8").replace(
|
|
137787
137908
|
/<!-- BASE -->[\s\S]*<!-- \/BASE -->/,
|
|
137788
137909
|
`<base href='${baseUrl}' />`
|
|
137789
137910
|
);
|
|
@@ -138379,7 +138500,7 @@ var FilteredNetwork = class extends NodeJsNetwork {
|
|
|
138379
138500
|
};
|
|
138380
138501
|
|
|
138381
138502
|
// src/core/app/mdns.ts
|
|
138382
|
-
var
|
|
138503
|
+
var logger182 = Logger.get("Mdns");
|
|
138383
138504
|
function mdns(env, options) {
|
|
138384
138505
|
if (options.stripGlobalIpv6) {
|
|
138385
138506
|
env.set(Network, new FilteredNetwork());
|
|
@@ -138394,7 +138515,7 @@ function mdns(env, options) {
|
|
|
138394
138515
|
function warnAboutAdvertising(options) {
|
|
138395
138516
|
const choice = selectMdnsInterface(os5.networkInterfaces());
|
|
138396
138517
|
if (choice.hasGlobalIpv6) {
|
|
138397
|
-
|
|
138518
|
+
logger182.warn(
|
|
138398
138519
|
"Matter mDNS is advertising a global IPv6 address that controllers may not reach on the LAN, so devices can show No Response (#361). Set mdns-strip-global-ipv6 if devices stay unreachable."
|
|
138399
138520
|
);
|
|
138400
138521
|
}
|
|
@@ -138403,7 +138524,7 @@ function warnAboutAdvertising(options) {
|
|
|
138403
138524
|
}
|
|
138404
138525
|
const suggestion = choice.selected ? ` Likely LAN interface: ${choice.selected}.` : "";
|
|
138405
138526
|
if (choice.hasThreadInterface) {
|
|
138406
|
-
|
|
138527
|
+
logger182.warn(
|
|
138407
138528
|
`Matter mDNS is advertising on an OpenThread/OTBR interface (wpan/otbr) whose mesh-local address controllers cannot reach, so devices may show offline (#388). Set mdns-network-interface to your LAN interface.${suggestion}`
|
|
138408
138529
|
);
|
|
138409
138530
|
}
|
|
@@ -138411,16 +138532,16 @@ function warnAboutAdvertising(options) {
|
|
|
138411
138532
|
return;
|
|
138412
138533
|
}
|
|
138413
138534
|
const list3 = choice.external.map((i) => `${i.name} (${i.ipv4[0] ?? i.ipv6[0] ?? "?"})`).join(", ");
|
|
138414
|
-
|
|
138535
|
+
logger182.warn(
|
|
138415
138536
|
`Matter mDNS is advertising on several interfaces including likely Docker-internal ones, so controllers may show devices as offline (#361). Set mdns-network-interface to your LAN interface.${suggestion} Interfaces: ${list3}.`
|
|
138416
138537
|
);
|
|
138417
138538
|
}
|
|
138418
138539
|
|
|
138419
138540
|
// src/core/app/storage.ts
|
|
138420
138541
|
init_esm7();
|
|
138421
|
-
import
|
|
138542
|
+
import fs8 from "node:fs";
|
|
138422
138543
|
import os6 from "node:os";
|
|
138423
|
-
import
|
|
138544
|
+
import path9 from "node:path";
|
|
138424
138545
|
|
|
138425
138546
|
// src/core/app/storage/custom-storage.ts
|
|
138426
138547
|
init_dist();
|
|
@@ -138440,7 +138561,7 @@ var CustomStorage = class extends FileStorageDriver {
|
|
|
138440
138561
|
// src/core/app/storage.ts
|
|
138441
138562
|
function storage(environment, options) {
|
|
138442
138563
|
const location = resolveStorageLocation(options.location);
|
|
138443
|
-
|
|
138564
|
+
fs8.mkdirSync(location, { recursive: true });
|
|
138444
138565
|
environment.get(VariableService).set("storage.path", location);
|
|
138445
138566
|
const storageService = environment.get(StorageService);
|
|
138446
138567
|
storageService.registerDriver({
|
|
@@ -138455,7 +138576,7 @@ function storage(environment, options) {
|
|
|
138455
138576
|
}
|
|
138456
138577
|
function resolveStorageLocation(storageLocation) {
|
|
138457
138578
|
const homedir = os6.homedir();
|
|
138458
|
-
return storageLocation ?
|
|
138579
|
+
return storageLocation ? path9.resolve(storageLocation.replace(/^~\//, `${homedir}/`)) : path9.join(homedir, ".home-assistant-matter-hub");
|
|
138459
138580
|
}
|
|
138460
138581
|
|
|
138461
138582
|
// src/core/app/configure-default-environment.ts
|
|
@@ -138475,7 +138596,7 @@ function configureDefaultEnvironment(options) {
|
|
|
138475
138596
|
init_esm7();
|
|
138476
138597
|
import { createRequire } from "node:module";
|
|
138477
138598
|
import os7 from "node:os";
|
|
138478
|
-
import
|
|
138599
|
+
import path10 from "node:path";
|
|
138479
138600
|
function resolveAppVersion() {
|
|
138480
138601
|
try {
|
|
138481
138602
|
const require2 = createRequire(import.meta.url);
|
|
@@ -138545,7 +138666,7 @@ var Options = class {
|
|
|
138545
138666
|
resolveStorageLocation() {
|
|
138546
138667
|
const storageLocation = notEmpty(this.startOptions.storageLocation);
|
|
138547
138668
|
const homedir = os7.homedir();
|
|
138548
|
-
return storageLocation ?
|
|
138669
|
+
return storageLocation ? path10.resolve(storageLocation.replace(/^~\//, `${homedir}/`)) : path10.join(homedir, ".home-assistant-matter-hub");
|
|
138549
138670
|
}
|
|
138550
138671
|
get bridgeService() {
|
|
138551
138672
|
return {
|
|
@@ -138585,8 +138706,8 @@ init_esm7();
|
|
|
138585
138706
|
|
|
138586
138707
|
// src/services/backup/backup-service.ts
|
|
138587
138708
|
init_esm();
|
|
138588
|
-
import
|
|
138589
|
-
import
|
|
138709
|
+
import fs9 from "node:fs";
|
|
138710
|
+
import path11 from "node:path";
|
|
138590
138711
|
import archiver2 from "archiver";
|
|
138591
138712
|
var BackupService = class {
|
|
138592
138713
|
constructor(bridgeStorage, mappingStorage, settingsStorage, props) {
|
|
@@ -138594,8 +138715,8 @@ var BackupService = class {
|
|
|
138594
138715
|
this.mappingStorage = mappingStorage;
|
|
138595
138716
|
this.settingsStorage = settingsStorage;
|
|
138596
138717
|
this.props = props;
|
|
138597
|
-
this.backupDir =
|
|
138598
|
-
|
|
138718
|
+
this.backupDir = path11.join(props.storageLocation, "backups");
|
|
138719
|
+
fs9.mkdirSync(this.backupDir, { recursive: true });
|
|
138599
138720
|
}
|
|
138600
138721
|
bridgeStorage;
|
|
138601
138722
|
mappingStorage;
|
|
@@ -138609,7 +138730,7 @@ var BackupService = class {
|
|
|
138609
138730
|
const dateStr = now.toISOString().replace(/T/, "_").replace(/:/g, "-").replace(/\.\d+Z$/, "");
|
|
138610
138731
|
const prefix = auto ? "auto" : "manual";
|
|
138611
138732
|
const filename = `hamh-${prefix}-${version2}-${dateStr}.zip`;
|
|
138612
|
-
const filepath =
|
|
138733
|
+
const filepath = path11.join(this.backupDir, filename);
|
|
138613
138734
|
const bridges = this.bridgeStorage.bridges;
|
|
138614
138735
|
const entityMappings = {};
|
|
138615
138736
|
for (const bridge of bridges) {
|
|
@@ -138619,9 +138740,9 @@ var BackupService = class {
|
|
|
138619
138740
|
}
|
|
138620
138741
|
}
|
|
138621
138742
|
let includesIcons = false;
|
|
138622
|
-
const iconsDir =
|
|
138623
|
-
if (
|
|
138624
|
-
const iconFiles =
|
|
138743
|
+
const iconsDir = path11.join(this.props.storageLocation, "bridge-icons");
|
|
138744
|
+
if (fs9.existsSync(iconsDir)) {
|
|
138745
|
+
const iconFiles = fs9.readdirSync(iconsDir);
|
|
138625
138746
|
includesIcons = iconFiles.some((f) => {
|
|
138626
138747
|
const bridgeId = f.split(".")[0];
|
|
138627
138748
|
return bridges.some((b) => b.id === bridgeId);
|
|
@@ -138638,7 +138759,7 @@ var BackupService = class {
|
|
|
138638
138759
|
auto
|
|
138639
138760
|
};
|
|
138640
138761
|
await new Promise((resolve11, reject) => {
|
|
138641
|
-
const output =
|
|
138762
|
+
const output = fs9.createWriteStream(filepath);
|
|
138642
138763
|
const archive = archiver2("zip", { zlib: { level: 9 } });
|
|
138643
138764
|
output.on("close", () => resolve11());
|
|
138644
138765
|
archive.on("error", (err) => reject(err));
|
|
@@ -138662,20 +138783,27 @@ var BackupService = class {
|
|
|
138662
138783
|
{ name: "README.txt" }
|
|
138663
138784
|
);
|
|
138664
138785
|
for (const bridge of bridges) {
|
|
138665
|
-
const bridgeStoragePath =
|
|
138786
|
+
const bridgeStoragePath = path11.join(
|
|
138666
138787
|
this.props.storageLocation,
|
|
138667
138788
|
bridge.id
|
|
138668
138789
|
);
|
|
138669
|
-
if (
|
|
138790
|
+
if (fs9.existsSync(bridgeStoragePath)) {
|
|
138670
138791
|
archive.directory(bridgeStoragePath, `identity/${bridge.id}`);
|
|
138671
138792
|
}
|
|
138793
|
+
const pluginState = pluginStateFilePath(
|
|
138794
|
+
this.props.storageLocation,
|
|
138795
|
+
bridge.id
|
|
138796
|
+
);
|
|
138797
|
+
if (fs9.existsSync(pluginState)) {
|
|
138798
|
+
archive.file(pluginState, { name: `plugin-state/${bridge.id}.json` });
|
|
138799
|
+
}
|
|
138672
138800
|
}
|
|
138673
138801
|
if (includesIcons) {
|
|
138674
|
-
const iconFiles =
|
|
138802
|
+
const iconFiles = fs9.readdirSync(iconsDir);
|
|
138675
138803
|
for (const iconFile of iconFiles) {
|
|
138676
138804
|
const bridgeId = iconFile.split(".")[0];
|
|
138677
138805
|
if (bridges.some((b) => b.id === bridgeId)) {
|
|
138678
|
-
archive.file(
|
|
138806
|
+
archive.file(path11.join(iconsDir, iconFile), {
|
|
138679
138807
|
name: `bridge-icons/${iconFile}`
|
|
138680
138808
|
});
|
|
138681
138809
|
}
|
|
@@ -138683,7 +138811,7 @@ var BackupService = class {
|
|
|
138683
138811
|
}
|
|
138684
138812
|
archive.finalize();
|
|
138685
138813
|
});
|
|
138686
|
-
const stat4 =
|
|
138814
|
+
const stat4 = fs9.statSync(filepath);
|
|
138687
138815
|
const metadata = {
|
|
138688
138816
|
filename,
|
|
138689
138817
|
version: version2,
|
|
@@ -138711,13 +138839,13 @@ var BackupService = class {
|
|
|
138711
138839
|
}
|
|
138712
138840
|
}
|
|
138713
138841
|
listBackups() {
|
|
138714
|
-
if (!
|
|
138842
|
+
if (!fs9.existsSync(this.backupDir)) {
|
|
138715
138843
|
return [];
|
|
138716
138844
|
}
|
|
138717
|
-
const files =
|
|
138845
|
+
const files = fs9.readdirSync(this.backupDir).filter((f) => f.startsWith("hamh-") && f.endsWith(".zip"));
|
|
138718
138846
|
return files.map((filename) => {
|
|
138719
138847
|
try {
|
|
138720
|
-
const stat4 =
|
|
138848
|
+
const stat4 = fs9.statSync(path11.join(this.backupDir, filename));
|
|
138721
138849
|
const parsed = this.parseFilename(filename);
|
|
138722
138850
|
return {
|
|
138723
138851
|
filename,
|
|
@@ -138737,8 +138865,8 @@ var BackupService = class {
|
|
|
138737
138865
|
if (filename.includes("..") || filename.includes("/")) {
|
|
138738
138866
|
return null;
|
|
138739
138867
|
}
|
|
138740
|
-
const filepath =
|
|
138741
|
-
if (!
|
|
138868
|
+
const filepath = path11.join(this.backupDir, filename);
|
|
138869
|
+
if (!fs9.existsSync(filepath)) {
|
|
138742
138870
|
return null;
|
|
138743
138871
|
}
|
|
138744
138872
|
return filepath;
|
|
@@ -138747,7 +138875,7 @@ var BackupService = class {
|
|
|
138747
138875
|
const filepath = this.getBackupPath(filename);
|
|
138748
138876
|
if (!filepath) return false;
|
|
138749
138877
|
try {
|
|
138750
|
-
|
|
138878
|
+
fs9.unlinkSync(filepath);
|
|
138751
138879
|
this.log.info(`Backup deleted: ${filename}`);
|
|
138752
138880
|
return true;
|
|
138753
138881
|
} catch (e) {
|
|
@@ -139327,7 +139455,7 @@ async function getAreaRegistry(connection, timeoutMs) {
|
|
|
139327
139455
|
}
|
|
139328
139456
|
|
|
139329
139457
|
// src/services/home-assistant/home-assistant-registry.ts
|
|
139330
|
-
var
|
|
139458
|
+
var logger183 = Logger.get("HomeAssistantRegistry");
|
|
139331
139459
|
var HomeAssistantRegistry = class extends Service {
|
|
139332
139460
|
constructor(client, options) {
|
|
139333
139461
|
super("HomeAssistantRegistry");
|
|
@@ -139362,7 +139490,7 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
139362
139490
|
try {
|
|
139363
139491
|
await this.reload();
|
|
139364
139492
|
} catch (e) {
|
|
139365
|
-
|
|
139493
|
+
logger183.warn(
|
|
139366
139494
|
"Initial registry fetch failed, starting empty and relying on auto-refresh:",
|
|
139367
139495
|
e
|
|
139368
139496
|
);
|
|
@@ -139376,7 +139504,7 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
139376
139504
|
let refreshing = false;
|
|
139377
139505
|
this.autoRefresh = setInterval(async () => {
|
|
139378
139506
|
if (refreshing) {
|
|
139379
|
-
|
|
139507
|
+
logger183.debug("Skipping registry refresh, previous tick still running");
|
|
139380
139508
|
return;
|
|
139381
139509
|
}
|
|
139382
139510
|
refreshing = true;
|
|
@@ -139386,7 +139514,7 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
139386
139514
|
await onRefresh();
|
|
139387
139515
|
}
|
|
139388
139516
|
} catch (e) {
|
|
139389
|
-
|
|
139517
|
+
logger183.warn("Failed to refresh registry, will retry next interval:", e);
|
|
139390
139518
|
} finally {
|
|
139391
139519
|
refreshing = false;
|
|
139392
139520
|
}
|
|
@@ -139404,7 +139532,7 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
139404
139532
|
baseDelayMs: 2e3,
|
|
139405
139533
|
maxDelayMs: 3e4,
|
|
139406
139534
|
onRetry: (attempt, error, delayMs) => {
|
|
139407
|
-
|
|
139535
|
+
logger183.warn(
|
|
139408
139536
|
`Registry fetch failed (attempt ${attempt}), retrying in ${delayMs}ms:`,
|
|
139409
139537
|
error
|
|
139410
139538
|
);
|
|
@@ -139417,7 +139545,7 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
139417
139545
|
return await this.runRegistryQueries();
|
|
139418
139546
|
} catch (e) {
|
|
139419
139547
|
if (!isConnectionLost(e)) throw e;
|
|
139420
|
-
|
|
139548
|
+
logger183.debug("Registry fetch hit connection drop, waiting for reconnect");
|
|
139421
139549
|
await this.waitForConnection(6e4);
|
|
139422
139550
|
return await this.runRegistryQueries();
|
|
139423
139551
|
}
|
|
@@ -139425,7 +139553,7 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
139425
139553
|
async waitForConnection(timeoutMs) {
|
|
139426
139554
|
const connection = this.client.connection;
|
|
139427
139555
|
if (connection.connected) return;
|
|
139428
|
-
|
|
139556
|
+
logger183.debug("Connection not ready, waiting for reconnect...");
|
|
139429
139557
|
await new Promise((resolve11) => {
|
|
139430
139558
|
const timeout = setTimeout(() => {
|
|
139431
139559
|
connection.removeEventListener("ready", onReady);
|
|
@@ -139486,7 +139614,7 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
139486
139614
|
const fingerprint = hash2.digest("hex");
|
|
139487
139615
|
this._states = keyBy(statesList, "entity_id");
|
|
139488
139616
|
if (fingerprint === this.lastRegistryFingerprint) {
|
|
139489
|
-
|
|
139617
|
+
logger183.debug("Registry unchanged, skipping full refresh");
|
|
139490
139618
|
return false;
|
|
139491
139619
|
}
|
|
139492
139620
|
this.lastRegistryFingerprint = fingerprint;
|
|
@@ -139507,10 +139635,10 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
139507
139635
|
const missingDevices = fromPairs(missingDeviceIds.map((d) => [d, { id: d }]));
|
|
139508
139636
|
this._devices = { ...missingDevices, ...realDevices };
|
|
139509
139637
|
this._entities = allEntities;
|
|
139510
|
-
|
|
139638
|
+
logger183.debug(
|
|
139511
139639
|
`Loaded HA registry: ${keys(allEntities).length} entities, ${keys(realDevices).length} devices, ${keys(this._states).length} states`
|
|
139512
139640
|
);
|
|
139513
|
-
logMemoryUsage(
|
|
139641
|
+
logMemoryUsage(logger183, "after HA registry load");
|
|
139514
139642
|
this._labels = labels;
|
|
139515
139643
|
this._areas = new Map(areas.map((a) => [a.area_id, a.name]));
|
|
139516
139644
|
return true;
|
|
@@ -139966,6 +140094,7 @@ var EntityMappingStorage = class extends Service {
|
|
|
139966
140094
|
currentRoomEntity: request.currentRoomEntity?.trim() || void 0,
|
|
139967
140095
|
cleanedAreaEntity: request.cleanedAreaEntity?.trim() || void 0,
|
|
139968
140096
|
vacuumAscendingRoomOrder: request.vacuumAscendingRoomOrder || void 0,
|
|
140097
|
+
vacuumRoomSwitches: request.vacuumRoomSwitches || void 0,
|
|
139969
140098
|
disableCustomAreaRoomModes: request.disableCustomAreaRoomModes || void 0,
|
|
139970
140099
|
valetudoIdentifier: request.valetudoIdentifier?.trim() || void 0,
|
|
139971
140100
|
coverSwapOpenClose: request.coverSwapOpenClose || void 0,
|
|
@@ -139983,7 +140112,7 @@ var EntityMappingStorage = class extends Service {
|
|
|
139983
140112
|
composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0,
|
|
139984
140113
|
disableMomentaryFlip: request.disableMomentaryFlip || void 0
|
|
139985
140114
|
};
|
|
139986
|
-
if (!config8.matterDeviceType && !config8.customName && !config8.customProductName && !config8.customVendorName && !config8.customSerialNumber && config8.customVendorId === void 0 && config8.disabled !== true && !config8.filterLifeEntity && !config8.cleaningModeEntity && !config8.temperatureEntity && !config8.humidityEntity && !config8.batteryEntity && !config8.disableBatteryMapping && !config8.chargingStateEntity && !config8.roomEntities && !config8.disableLockPin && !config8.lockUsercodeService && config8.lockUsercodeSlot === void 0 && config8.lockPinMinLength === void 0 && config8.lockPinMaxLength === void 0 && !config8.powerEntity && !config8.energyEntity && !config8.meterSerialNumber && !config8.pointOfDelivery && !config8.voltageEntity && !config8.currentEntity && !config8.batteryPowerEntity && !config8.batteryEnergyEntity && !config8.chargingSwitchEntity && !config8.currentLimitEntity && !config8.pressureEntity && !config8.suctionLevelEntity && !config8.mopIntensityEntity && (!config8.customServiceAreas || config8.customServiceAreas.length === 0) && (!config8.customFanSpeedTags || Object.keys(config8.customFanSpeedTags).length === 0) && (!config8.fanWindPresets || (config8.fanWindPresets.natural?.length ?? 0) === 0 && (config8.fanWindPresets.sleep?.length ?? 0) === 0) && !config8.fanRestoreSpeedOnPowerOn && !config8.currentRoomEntity && !config8.cleanedAreaEntity && !config8.vacuumAscendingRoomOrder && !config8.disableCustomAreaRoomModes && !config8.valetudoIdentifier && !config8.coverSwapOpenClose && !config8.coverExposeAsDimmableLight && !config8.selectExposeAsSwitch && !config8.selectSwitchOnOption && !config8.selectSwitchOffOption && !config8.coverSliderDebounceMs && !config8.updateThrottleMs && !config8.disableClimateOnOff && !config8.disableClimateFanControl && !config8.climateKeepModeOnIdle && !config8.climateExposeFan && !config8.climateAutoMode && (!config8.composedEntities || config8.composedEntities.length === 0) && !config8.disableMomentaryFlip) {
|
|
140115
|
+
if (!config8.matterDeviceType && !config8.customName && !config8.customProductName && !config8.customVendorName && !config8.customSerialNumber && config8.customVendorId === void 0 && config8.disabled !== true && !config8.filterLifeEntity && !config8.cleaningModeEntity && !config8.temperatureEntity && !config8.humidityEntity && !config8.batteryEntity && !config8.disableBatteryMapping && !config8.chargingStateEntity && !config8.roomEntities && !config8.disableLockPin && !config8.lockUsercodeService && config8.lockUsercodeSlot === void 0 && config8.lockPinMinLength === void 0 && config8.lockPinMaxLength === void 0 && !config8.powerEntity && !config8.energyEntity && !config8.meterSerialNumber && !config8.pointOfDelivery && !config8.voltageEntity && !config8.currentEntity && !config8.batteryPowerEntity && !config8.batteryEnergyEntity && !config8.chargingSwitchEntity && !config8.currentLimitEntity && !config8.pressureEntity && !config8.suctionLevelEntity && !config8.mopIntensityEntity && (!config8.customServiceAreas || config8.customServiceAreas.length === 0) && (!config8.customFanSpeedTags || Object.keys(config8.customFanSpeedTags).length === 0) && (!config8.fanWindPresets || (config8.fanWindPresets.natural?.length ?? 0) === 0 && (config8.fanWindPresets.sleep?.length ?? 0) === 0) && !config8.fanRestoreSpeedOnPowerOn && !config8.currentRoomEntity && !config8.cleanedAreaEntity && !config8.vacuumAscendingRoomOrder && !config8.vacuumRoomSwitches && !config8.disableCustomAreaRoomModes && !config8.valetudoIdentifier && !config8.coverSwapOpenClose && !config8.coverExposeAsDimmableLight && !config8.selectExposeAsSwitch && !config8.selectSwitchOnOption && !config8.selectSwitchOffOption && !config8.coverSliderDebounceMs && !config8.updateThrottleMs && !config8.disableClimateOnOff && !config8.disableClimateFanControl && !config8.climateKeepModeOnIdle && !config8.climateExposeFan && !config8.climateAutoMode && (!config8.composedEntities || config8.composedEntities.length === 0) && !config8.disableMomentaryFlip) {
|
|
139987
140116
|
bridgeMap.delete(request.entityId);
|
|
139988
140117
|
} else {
|
|
139989
140118
|
bridgeMap.set(request.entityId, config8);
|
|
@@ -140382,7 +140511,7 @@ var __privateIn5 = (member, obj) => Object(obj) !== obj ? __typeError50('Cannot
|
|
|
140382
140511
|
var __privateGet5 = (obj, member, getter) => (__accessCheck5(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
140383
140512
|
var __privateSet5 = (obj, member, value, setter) => (__accessCheck5(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
140384
140513
|
var __privateMethod5 = (obj, member, method) => (__accessCheck5(obj, member, "access private method"), method);
|
|
140385
|
-
var
|
|
140514
|
+
var logger184 = Logger.get("ScenesManagementServer");
|
|
140386
140515
|
var UNDEFINED_SCENE_ID = 255;
|
|
140387
140516
|
function constraintErrorWithSceneId(groupId22, sceneId) {
|
|
140388
140517
|
const response = { status: Status2.ConstraintError, groupId: groupId22, sceneId };
|
|
@@ -140509,11 +140638,11 @@ var ScenesManagementServer = class extends ScenesManagementBase {
|
|
|
140509
140638
|
return { status: Status2.ResourceExhausted, groupId: groupId22, sceneId };
|
|
140510
140639
|
}
|
|
140511
140640
|
this.state.sceneTable.push(sceneData);
|
|
140512
|
-
|
|
140641
|
+
logger184.debug(`Added scene ${sceneId} in group ${groupId22} for fabric ${fabricIndex}`);
|
|
140513
140642
|
this.#updateFabricSceneInfoCountsForFabric(fabricIndex);
|
|
140514
140643
|
} else {
|
|
140515
140644
|
this.state.sceneTable[existingSceneIndex] = sceneData;
|
|
140516
|
-
|
|
140645
|
+
logger184.debug(`Updated scene ${sceneId} in group ${groupId22} for fabric ${fabricIndex}`);
|
|
140517
140646
|
}
|
|
140518
140647
|
return { status: Status2.Success, groupId: groupId22, sceneId };
|
|
140519
140648
|
}
|
|
@@ -140816,20 +140945,20 @@ var ScenesManagementServer = class extends ScenesManagementBase {
|
|
|
140816
140945
|
}
|
|
140817
140946
|
}
|
|
140818
140947
|
if (fieldCount !== 2) {
|
|
140819
|
-
|
|
140948
|
+
logger184.warn(
|
|
140820
140949
|
`AttributeValuePair has invalid number (${fieldCount}) of fields (${serialize(attributeValuePair)})`
|
|
140821
140950
|
);
|
|
140822
140951
|
return void 0;
|
|
140823
140952
|
}
|
|
140824
140953
|
const value = attributeValuePair[mappedType];
|
|
140825
140954
|
if (value === void 0) {
|
|
140826
|
-
|
|
140955
|
+
logger184.warn(
|
|
140827
140956
|
`AttributeValuePair missing value for mappedType ${mappedType} (${serialize(attributeValuePair)})`
|
|
140828
140957
|
);
|
|
140829
140958
|
return void 0;
|
|
140830
140959
|
}
|
|
140831
140960
|
if (typeof value !== "number" && typeof value !== "bigint") {
|
|
140832
|
-
|
|
140961
|
+
logger184.warn(
|
|
140833
140962
|
`AttributeValuePair has invalid non-numeric value for mappedType ${mappedType} (${serialize(attributeValuePair)})`
|
|
140834
140963
|
// Should never happen
|
|
140835
140964
|
);
|
|
@@ -140927,7 +141056,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
|
|
|
140927
141056
|
} else if (schema6.schema.baseTypeMin < 0 && schema6.schema.min > schema6.schema.baseTypeMin) {
|
|
140928
141057
|
return { attributeId, [mappedType]: schema6.schema.baseTypeMin };
|
|
140929
141058
|
} else {
|
|
140930
|
-
|
|
141059
|
+
logger184.warn(
|
|
140931
141060
|
`Cannot determine out-of-bounds value for attribute schema, returning min value of datatype schema`
|
|
140932
141061
|
);
|
|
140933
141062
|
}
|
|
@@ -140948,7 +141077,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
|
|
|
140948
141077
|
}
|
|
140949
141078
|
}
|
|
140950
141079
|
});
|
|
140951
|
-
|
|
141080
|
+
logger184.debug(`Collected scene attribute values on Endpoint ${this.endpoint.id}: ${serialize(sceneValues)}`);
|
|
140952
141081
|
return sceneValues;
|
|
140953
141082
|
}
|
|
140954
141083
|
/**
|
|
@@ -140987,7 +141116,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
|
|
|
140987
141116
|
}
|
|
140988
141117
|
const attrType = attribute.primitiveBase?.name;
|
|
140989
141118
|
if (attrType === void 0 || DataTypeToSceneAttributeDataMap[attrType] === void 0) {
|
|
140990
|
-
|
|
141119
|
+
logger184.warn(
|
|
140991
141120
|
`Scene Attribute ${attribute.name} on Cluster ${clusterName} has unsupported datatype ${attrType} for scene management on Endpoint ${this.endpoint.id}`
|
|
140992
141121
|
);
|
|
140993
141122
|
continue;
|
|
@@ -141002,7 +141131,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
|
|
|
141002
141131
|
});
|
|
141003
141132
|
}
|
|
141004
141133
|
if (sceneClusterDetails) {
|
|
141005
|
-
|
|
141134
|
+
logger184.info(
|
|
141006
141135
|
`Registered ${sceneClusterDetails.attributes.size} scene attributes for Cluster ${clusterName} on Endpoint ${this.endpoint.id}`
|
|
141007
141136
|
);
|
|
141008
141137
|
this.internal.endpointSceneableBehaviors.add(sceneClusterDetails);
|
|
@@ -141010,7 +141139,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
|
|
|
141010
141139
|
}
|
|
141011
141140
|
/** Apply scene attribute values in the various clusters on the endpoint. */
|
|
141012
141141
|
#applySceneAttributeValues(sceneValues, transitionTime = null) {
|
|
141013
|
-
|
|
141142
|
+
logger184.debug(`Recalling scene on Endpoint ${this.endpoint.id} with values: ${serialize(sceneValues)}`);
|
|
141014
141143
|
const agent = this.endpoint.agentFor(this.context);
|
|
141015
141144
|
const promises = [];
|
|
141016
141145
|
for (const [clusterName, clusterAttributes] of Object.entries(sceneValues)) {
|
|
@@ -141021,7 +141150,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
|
|
|
141021
141150
|
promises.push(result);
|
|
141022
141151
|
}
|
|
141023
141152
|
} else {
|
|
141024
|
-
|
|
141153
|
+
logger184.warn(
|
|
141025
141154
|
`No scenes implementation found for cluster ${clusterName} on Endpoint ${this.endpoint.id} during scene recall. Values are ignored`
|
|
141026
141155
|
);
|
|
141027
141156
|
}
|
|
@@ -141029,7 +141158,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
|
|
|
141029
141158
|
if (promises.length) {
|
|
141030
141159
|
return Promise.all(promises).then(
|
|
141031
141160
|
() => void 0,
|
|
141032
|
-
(error) =>
|
|
141161
|
+
(error) => logger184.warn(`Error applying scene attribute values on Endpoint ${this.endpoint.id}:`, error)
|
|
141033
141162
|
);
|
|
141034
141163
|
}
|
|
141035
141164
|
}
|
|
@@ -141222,7 +141351,7 @@ var GroupsBehaviorConstructor = ClusterBehavior.for(Groups4);
|
|
|
141222
141351
|
var GroupsBehavior = GroupsBehaviorConstructor;
|
|
141223
141352
|
|
|
141224
141353
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/groups/GroupsServer.js
|
|
141225
|
-
var
|
|
141354
|
+
var logger185 = Logger.get("GroupsServer");
|
|
141226
141355
|
var { commands: commands3 } = Groups4.schema;
|
|
141227
141356
|
var addGroup = commands3.require("AddGroup");
|
|
141228
141357
|
var addGroupIfIdentifying = commands3.require("AddGroupIfIdentifying");
|
|
@@ -141301,7 +141430,7 @@ var GroupsServer = class extends GroupsBase {
|
|
|
141301
141430
|
(fabric2, gkm) => gkm.addEndpointForGroup(fabric2, groupId3, endpointNumber, groupName)
|
|
141302
141431
|
);
|
|
141303
141432
|
} catch (error) {
|
|
141304
|
-
|
|
141433
|
+
logger185.debug("Could not add group", error);
|
|
141305
141434
|
StatusResponseError.accept(error);
|
|
141306
141435
|
return { status: error.code, groupId: groupId3 };
|
|
141307
141436
|
}
|
|
@@ -141978,7 +142107,7 @@ var SwitchBehavior = SwitchBehaviorConstructor;
|
|
|
141978
142107
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/switch/SwitchServer.js
|
|
141979
142108
|
var DEFAULT_MULTIPRESS_DELAY = Millis(300);
|
|
141980
142109
|
var DEFAULT_LONG_PRESS_DELAY = Seconds(2);
|
|
141981
|
-
var
|
|
142110
|
+
var logger186 = Logger.get("SwitchServer");
|
|
141982
142111
|
var SwitchServerBase = SwitchBehavior.with(
|
|
141983
142112
|
Switch3.Feature.LatchingSwitch,
|
|
141984
142113
|
Switch3.Feature.MomentarySwitch,
|
|
@@ -142031,7 +142160,7 @@ var SwitchBaseServer = class extends SwitchServerBase {
|
|
|
142031
142160
|
this.internal.currentIsLongPress = false;
|
|
142032
142161
|
this.internal.multiPressTimer?.stop();
|
|
142033
142162
|
this.internal.longPressTimer?.stop();
|
|
142034
|
-
|
|
142163
|
+
logger186.info("State of Switch got reset");
|
|
142035
142164
|
}
|
|
142036
142165
|
// TODO remove when Validator logic can assess that with 1.3 introduction
|
|
142037
142166
|
#assertPositionInRange(position) {
|
|
@@ -142269,11 +142398,11 @@ var WebRtcTransportRequestorBehaviorConstructor = ClusterBehavior.for(WebRtcTran
|
|
|
142269
142398
|
var WebRtcTransportRequestorBehavior = WebRtcTransportRequestorBehaviorConstructor;
|
|
142270
142399
|
|
|
142271
142400
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/web-rtc-transport-requestor/WebRtcTransportRequestorServer.js
|
|
142272
|
-
var
|
|
142401
|
+
var logger187 = Logger.get("WebRtcTransportRequestorServer");
|
|
142273
142402
|
var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehavior {
|
|
142274
142403
|
async initialize() {
|
|
142275
142404
|
const node = Node.forEndpoint(this.endpoint);
|
|
142276
|
-
|
|
142405
|
+
logger187.info(
|
|
142277
142406
|
`WebRtcTransportRequestor initialized on endpoint=${this.endpoint.number} (id="${this.endpoint.id}")`
|
|
142278
142407
|
);
|
|
142279
142408
|
this.reactTo(node.lifecycle.online, this.#nodeOnline);
|
|
@@ -142339,7 +142468,7 @@ var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehav
|
|
|
142339
142468
|
* `offer` event.
|
|
142340
142469
|
*/
|
|
142341
142470
|
async offer(request) {
|
|
142342
|
-
|
|
142471
|
+
logger187.debug(`incoming Offer webRtcSessionId=${request.webRtcSessionId} sdpLen=${request.sdp.length}`);
|
|
142343
142472
|
const session = this.#findSessionStrict(request.webRtcSessionId);
|
|
142344
142473
|
this.events.offer.emit(session, request);
|
|
142345
142474
|
}
|
|
@@ -142348,7 +142477,7 @@ var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehav
|
|
|
142348
142477
|
* `answer` event.
|
|
142349
142478
|
*/
|
|
142350
142479
|
async answer(request) {
|
|
142351
|
-
|
|
142480
|
+
logger187.debug(`incoming Answer webRtcSessionId=${request.webRtcSessionId} sdpLen=${request.sdp.length}`);
|
|
142352
142481
|
const session = this.#findSessionStrict(request.webRtcSessionId);
|
|
142353
142482
|
this.events.answer.emit(session, request.sdp);
|
|
142354
142483
|
}
|
|
@@ -142356,7 +142485,7 @@ var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehav
|
|
|
142356
142485
|
* `iceCandidates` event.
|
|
142357
142486
|
*/
|
|
142358
142487
|
async iceCandidates(request) {
|
|
142359
|
-
|
|
142488
|
+
logger187.debug(
|
|
142360
142489
|
`incoming ICECandidates webRtcSessionId=${request.webRtcSessionId} count=${request.iceCandidates.length}`
|
|
142361
142490
|
);
|
|
142362
142491
|
if (request.iceCandidates.length === 0) {
|
|
@@ -142369,7 +142498,7 @@ var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehav
|
|
|
142369
142498
|
* {@link WebRtcTransportRequestorServer.Events} `end` event.
|
|
142370
142499
|
*/
|
|
142371
142500
|
async end(request) {
|
|
142372
|
-
|
|
142501
|
+
logger187.debug(`incoming End webRtcSessionId=${request.webRtcSessionId} reason=${request.reason}`);
|
|
142373
142502
|
const session = this.#findSessionStrict(request.webRtcSessionId);
|
|
142374
142503
|
this.removeSession(request.webRtcSessionId);
|
|
142375
142504
|
this.events.end.emit(session, request.reason);
|
|
@@ -142814,11 +142943,11 @@ var OccupancySensingBehaviorConstructor = ClusterBehavior.for(OccupancySensing3)
|
|
|
142814
142943
|
var OccupancySensingBehavior = OccupancySensingBehaviorConstructor;
|
|
142815
142944
|
|
|
142816
142945
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/occupancy-sensing/OccupancySensingServer.js
|
|
142817
|
-
var
|
|
142946
|
+
var logger188 = Logger.get("OccupancySensingServer");
|
|
142818
142947
|
var OccupancySensingServer = class extends OccupancySensingBehavior {
|
|
142819
142948
|
initialize() {
|
|
142820
142949
|
if (!Object.values(this.features).some((feature) => feature)) {
|
|
142821
|
-
|
|
142950
|
+
logger188.error(
|
|
142822
142951
|
`OccupancySensingServer: Since revision 5 of the cluster features need to be set based on the detector type. Currently no features are enabled.`
|
|
142823
142952
|
);
|
|
142824
142953
|
} else if (!Object.values(this.state.occupancySensorTypeBitmap).some((feature) => feature) || this.state.occupancySensorType === void 0) {
|
|
@@ -142845,7 +142974,7 @@ var OccupancySensingServer = class extends OccupancySensingBehavior {
|
|
|
142845
142974
|
} else if (this.state.occupancySensorTypeBitmap.physicalContact) {
|
|
142846
142975
|
this.state.occupancySensorType = OccupancySensing3.OccupancySensorType.PhysicalContact;
|
|
142847
142976
|
}
|
|
142848
|
-
|
|
142977
|
+
logger188.debug(
|
|
142849
142978
|
"Sync occupancySensorType to",
|
|
142850
142979
|
OccupancySensing3.OccupancySensorType[this.state.occupancySensorType],
|
|
142851
142980
|
"and occupancySensorTypeBitmap to",
|
|
@@ -142858,7 +142987,7 @@ var OccupancySensingServer = class extends OccupancySensingBehavior {
|
|
|
142858
142987
|
if (this.features.occupancyEvent) {
|
|
142859
142988
|
this.reactTo(this.events.occupancy$Changed, this.#emitOccupancyChanged);
|
|
142860
142989
|
} else {
|
|
142861
|
-
|
|
142990
|
+
logger188.info(
|
|
142862
142991
|
'OccupancySensingServer: enable the OccupancyEvent feature (e.g. OccupancySensingServer.with("<DetectorType>", "OccupancyEvent")) to emit the OccupancyChanged event.'
|
|
142863
142992
|
);
|
|
142864
142993
|
}
|
|
@@ -145454,7 +145583,7 @@ function miredsToXy(mireds) {
|
|
|
145454
145583
|
}
|
|
145455
145584
|
|
|
145456
145585
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/color-control/ColorControlServer.js
|
|
145457
|
-
var
|
|
145586
|
+
var logger189 = Logger.get("ColorControlServer");
|
|
145458
145587
|
var ColorControlBase = ColorControlBehavior.with(
|
|
145459
145588
|
ColorControl3.Feature.HueSaturation,
|
|
145460
145589
|
ColorControl3.Feature.EnhancedHue,
|
|
@@ -146635,7 +146764,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
|
|
|
146635
146764
|
switch (oldMode) {
|
|
146636
146765
|
case ColorControl3.ColorMode.CurrentHueAndCurrentSaturation:
|
|
146637
146766
|
if (this.state.currentHue === void 0 || this.state.currentSaturation === void 0) {
|
|
146638
|
-
|
|
146767
|
+
logger189.warn("Could not convert from hue/saturation because one of them is undefined");
|
|
146639
146768
|
break;
|
|
146640
146769
|
}
|
|
146641
146770
|
switch (newMode) {
|
|
@@ -146647,7 +146776,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
|
|
|
146647
146776
|
case ColorControl3.ColorMode.ColorTemperatureMireds:
|
|
146648
146777
|
const mireds = hsvToMireds(this.hue, this.saturation);
|
|
146649
146778
|
if (mireds === void 0) {
|
|
146650
|
-
|
|
146779
|
+
logger189.warn(
|
|
146651
146780
|
`Could not convert hue/saturation (${this.hue}/${this.saturation}) to color temperature`
|
|
146652
146781
|
);
|
|
146653
146782
|
} else {
|
|
@@ -146658,7 +146787,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
|
|
|
146658
146787
|
break;
|
|
146659
146788
|
case ColorControl3.ColorMode.CurrentXAndCurrentY:
|
|
146660
146789
|
if (this.state.currentX === void 0 || this.state.currentY === void 0) {
|
|
146661
|
-
|
|
146790
|
+
logger189.warn("Could not convert from xy because one of them is undefined");
|
|
146662
146791
|
break;
|
|
146663
146792
|
}
|
|
146664
146793
|
switch (newMode) {
|
|
@@ -146670,7 +146799,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
|
|
|
146670
146799
|
case ColorControl3.ColorMode.ColorTemperatureMireds:
|
|
146671
146800
|
const mireds = xyToMireds(this.x, this.y);
|
|
146672
146801
|
if (mireds === void 0) {
|
|
146673
|
-
|
|
146802
|
+
logger189.warn(`Could not convert xy ${this.x / this.y} to color temperature`);
|
|
146674
146803
|
} else {
|
|
146675
146804
|
this.mireds = mireds;
|
|
146676
146805
|
}
|
|
@@ -146679,14 +146808,14 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
|
|
|
146679
146808
|
break;
|
|
146680
146809
|
case ColorControl3.ColorMode.ColorTemperatureMireds:
|
|
146681
146810
|
if (this.state.colorTemperatureMireds === void 0) {
|
|
146682
|
-
|
|
146811
|
+
logger189.warn("Could not convert from color temperature because it is undefined");
|
|
146683
146812
|
break;
|
|
146684
146813
|
}
|
|
146685
146814
|
switch (newMode) {
|
|
146686
146815
|
case ColorControl3.ColorMode.CurrentHueAndCurrentSaturation:
|
|
146687
146816
|
const hsvResult = miredsToHsv(this.mireds);
|
|
146688
146817
|
if (hsvResult === void 0) {
|
|
146689
|
-
|
|
146818
|
+
logger189.warn(`Could not convert color temperature ${this.mireds} to hue/saturation`);
|
|
146690
146819
|
} else {
|
|
146691
146820
|
const [hue, saturation] = hsvResult;
|
|
146692
146821
|
this.hue = hue;
|
|
@@ -146696,7 +146825,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
|
|
|
146696
146825
|
case ColorControl3.ColorMode.CurrentXAndCurrentY:
|
|
146697
146826
|
const xyResult = miredsToXy(this.mireds);
|
|
146698
146827
|
if (xyResult === void 0) {
|
|
146699
|
-
|
|
146828
|
+
logger189.warn("Could not convert color temperature to xy");
|
|
146700
146829
|
} else {
|
|
146701
146830
|
const [x, y] = xyResult;
|
|
146702
146831
|
this.x = x;
|
|
@@ -146745,7 +146874,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
|
|
|
146745
146874
|
);
|
|
146746
146875
|
newColorTemp = tempPhysMax - tempDelta;
|
|
146747
146876
|
}
|
|
146748
|
-
|
|
146877
|
+
logger189.debug(`Synced color temperature with level: ${level}, new color temperature: ${newColorTemp}`);
|
|
146749
146878
|
return this.moveToColorTemperatureLogic(newColorTemp, 0);
|
|
146750
146879
|
}
|
|
146751
146880
|
#assertRate(mode, rate) {
|
|
@@ -146949,7 +147078,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
|
|
|
146949
147078
|
targetEnhancedColorMode = values4.enhancedColorMode;
|
|
146950
147079
|
}
|
|
146951
147080
|
if (!this.#supportsColorMode(targetEnhancedColorMode)) {
|
|
146952
|
-
|
|
147081
|
+
logger189.info(
|
|
146953
147082
|
`Can not apply scene with unsupported color mode: ${ColorControl3.EnhancedColorMode[targetEnhancedColorMode]} (${targetEnhancedColorMode})`
|
|
146954
147083
|
);
|
|
146955
147084
|
}
|
|
@@ -146991,7 +147120,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
|
|
|
146991
147120
|
}
|
|
146992
147121
|
break;
|
|
146993
147122
|
default:
|
|
146994
|
-
|
|
147123
|
+
logger189.info(
|
|
146995
147124
|
`No supported color mode found to apply scene: ${ColorControl3.EnhancedColorMode[targetEnhancedColorMode]} (${targetEnhancedColorMode})`
|
|
146996
147125
|
);
|
|
146997
147126
|
break;
|
|
@@ -147086,7 +147215,7 @@ var LevelControlBehaviorConstructor = ClusterBehavior.for(LevelControl3);
|
|
|
147086
147215
|
var LevelControlBehavior = LevelControlBehaviorConstructor;
|
|
147087
147216
|
|
|
147088
147217
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/level-control/LevelControlServer.js
|
|
147089
|
-
var
|
|
147218
|
+
var logger190 = Logger.get("LevelControlServer");
|
|
147090
147219
|
var LevelControlBase = LevelControlBehavior.with(LevelControl3.Feature.OnOff, LevelControl3.Feature.Lighting);
|
|
147091
147220
|
var LevelControlBaseServer = class _LevelControlBaseServer extends LevelControlBase {
|
|
147092
147221
|
/** Returns the minimum level, including feature specific fallback value handling. */
|
|
@@ -147179,17 +147308,17 @@ var LevelControlBaseServer = class _LevelControlBaseServer extends LevelControlB
|
|
|
147179
147308
|
*/
|
|
147180
147309
|
initializeLighting() {
|
|
147181
147310
|
if (this.state.currentLevel === 0) {
|
|
147182
|
-
|
|
147311
|
+
logger190.warn(
|
|
147183
147312
|
`The currentLevel value of ${this.state.currentLevel} is invalid according to Matter specification. The value must not be 0.`
|
|
147184
147313
|
);
|
|
147185
147314
|
}
|
|
147186
147315
|
if (this.minLevel !== 1) {
|
|
147187
|
-
|
|
147316
|
+
logger190.warn(
|
|
147188
147317
|
`The minLevel value of ${this.minLevel} is invalid according to Matter specification. The value should be 1.`
|
|
147189
147318
|
);
|
|
147190
147319
|
}
|
|
147191
147320
|
if (this.maxLevel !== 254) {
|
|
147192
|
-
|
|
147321
|
+
logger190.warn(
|
|
147193
147322
|
`The maxLevel value of ${this.maxLevel} is invalid according to Matter specification. The value should be 254.`
|
|
147194
147323
|
);
|
|
147195
147324
|
}
|
|
@@ -147500,7 +147629,7 @@ var LevelControlBaseServer = class _LevelControlBaseServer extends LevelControlB
|
|
|
147500
147629
|
if (!onOff || this.state.onLevel === null) {
|
|
147501
147630
|
return;
|
|
147502
147631
|
}
|
|
147503
|
-
|
|
147632
|
+
logger190.debug(`OnOff changed to ON, setting level to onLevel value of ${this.state.onLevel}`);
|
|
147504
147633
|
this.state.currentLevel = this.state.onLevel;
|
|
147505
147634
|
}
|
|
147506
147635
|
#calculateEffectiveOptions(optionsMask, optionsOverride) {
|
|
@@ -150563,7 +150692,7 @@ var ModeSelectBehaviorConstructor = ClusterBehavior.for(ModeSelect3);
|
|
|
150563
150692
|
var ModeSelectBehavior = ModeSelectBehaviorConstructor;
|
|
150564
150693
|
|
|
150565
150694
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/mode-select/ModeSelectServer.js
|
|
150566
|
-
var
|
|
150695
|
+
var logger191 = Logger.get("ModeSelectServer");
|
|
150567
150696
|
var ModeSelectBase = ModeSelectBehavior.with(ModeSelect3.Feature.OnOff);
|
|
150568
150697
|
var ModeSelectBaseServer = class extends ModeSelectBase {
|
|
150569
150698
|
initialize() {
|
|
@@ -150580,7 +150709,7 @@ var ModeSelectBaseServer = class extends ModeSelectBase {
|
|
|
150580
150709
|
}
|
|
150581
150710
|
this.reactTo(onOffServer.events.onOff$Changed, this.#handleOnOffDependency);
|
|
150582
150711
|
} else {
|
|
150583
|
-
|
|
150712
|
+
logger191.warn("OnOffServer not found on endpoint, but OnMode is set.");
|
|
150584
150713
|
}
|
|
150585
150714
|
}
|
|
150586
150715
|
if (!currentModeOverridden && this.state.startUpMode !== void 0 && this.state.startUpMode !== null && this.#getBootReason() !== GeneralDiagnostics3.BootReason.SoftwareUpdateCompleted) {
|
|
@@ -151781,7 +151910,7 @@ init_esm3();
|
|
|
151781
151910
|
|
|
151782
151911
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/thermostat/AtomicWriteState.js
|
|
151783
151912
|
init_esm();
|
|
151784
|
-
var
|
|
151913
|
+
var logger192 = Logger.get("AtomicWriteState");
|
|
151785
151914
|
var MAXIMUM_ALLOWED_TIMEOUT = Seconds(9);
|
|
151786
151915
|
var AtomicWriteState = class {
|
|
151787
151916
|
peerAddress;
|
|
@@ -151816,19 +151945,19 @@ var AtomicWriteState = class {
|
|
|
151816
151945
|
});
|
|
151817
151946
|
}
|
|
151818
151947
|
start() {
|
|
151819
|
-
|
|
151948
|
+
logger192.debug(
|
|
151820
151949
|
`Starting atomic write state for peer ${this.peerAddress.toString()} on endpoint ${this.endpoint.id}`
|
|
151821
151950
|
);
|
|
151822
151951
|
this.#timer.start();
|
|
151823
151952
|
}
|
|
151824
151953
|
#timeoutTriggered() {
|
|
151825
|
-
|
|
151954
|
+
logger192.debug(
|
|
151826
151955
|
`Atomic write state for peer ${this.peerAddress.toString()} on endpoint ${this.endpoint.id} timed out`
|
|
151827
151956
|
);
|
|
151828
151957
|
this.close();
|
|
151829
151958
|
}
|
|
151830
151959
|
close() {
|
|
151831
|
-
|
|
151960
|
+
logger192.debug(
|
|
151832
151961
|
`Closing atomic write state for peer ${this.peerAddress.toString()} on endpoint ${this.endpoint.id}`
|
|
151833
151962
|
);
|
|
151834
151963
|
if (this.#timer.isRunning) {
|
|
@@ -151839,7 +151968,7 @@ var AtomicWriteState = class {
|
|
|
151839
151968
|
};
|
|
151840
151969
|
|
|
151841
151970
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/thermostat/AtomicWriteHandler.js
|
|
151842
|
-
var
|
|
151971
|
+
var logger193 = Logger.get("AtomicWriteHandler");
|
|
151843
151972
|
var AtomicWriteHandler = class _AtomicWriteHandler {
|
|
151844
151973
|
#observers = new ObserverGroup();
|
|
151845
151974
|
#pendingWrites = new BasicSet();
|
|
@@ -151901,7 +152030,7 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
|
|
|
151901
152030
|
this.#pendingWrites.add(state);
|
|
151902
152031
|
state.closed.on(() => void this.#pendingWrites.delete(state));
|
|
151903
152032
|
state.start();
|
|
151904
|
-
|
|
152033
|
+
logger193.debug("Added atomic write state:", state);
|
|
151905
152034
|
return state;
|
|
151906
152035
|
}
|
|
151907
152036
|
if (existingState === void 0) {
|
|
@@ -151976,14 +152105,14 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
|
|
|
151976
152105
|
writeAttribute(context, endpoint, cluster2, attribute, value) {
|
|
151977
152106
|
const state = this.#assertPendingWriteForAttributeAndPeer(context, endpoint, cluster2, attribute);
|
|
151978
152107
|
const attributeName = state.attributeNames.get(attribute);
|
|
151979
|
-
|
|
152108
|
+
logger193.debug(`Writing pending value for attribute ${attributeName}, ${attribute} in atomic write`, value);
|
|
151980
152109
|
endpoint.eventsOf(cluster2.id)[`${attributeName}$AtomicChanging`]?.emit(
|
|
151981
152110
|
value,
|
|
151982
152111
|
state.pendingAttributeValues[attribute] !== void 0 ? state.pendingAttributeValues[attribute] : state.initialValues[attribute],
|
|
151983
152112
|
context
|
|
151984
152113
|
);
|
|
151985
152114
|
state.pendingAttributeValues[attribute] = value;
|
|
151986
|
-
|
|
152115
|
+
logger193.debug("Atomic write state after current write:", state);
|
|
151987
152116
|
}
|
|
151988
152117
|
/**
|
|
151989
152118
|
* Implements the commit logic for an atomic write.
|
|
@@ -152002,7 +152131,7 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
|
|
|
152002
152131
|
await context.transaction?.commit();
|
|
152003
152132
|
} catch (error) {
|
|
152004
152133
|
await context.transaction?.rollback();
|
|
152005
|
-
|
|
152134
|
+
logger193.info(
|
|
152006
152135
|
`Failed to write attribute ${attr} during atomic write commit:`,
|
|
152007
152136
|
Diagnostic.errorMessage(asError(error))
|
|
152008
152137
|
);
|
|
@@ -152041,7 +152170,7 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
|
|
|
152041
152170
|
const fabricIndex = fabric.fabricIndex;
|
|
152042
152171
|
for (const writeState of Array.from(this.#pendingWrites)) {
|
|
152043
152172
|
if (writeState.peerAddress.fabricIndex === fabricIndex) {
|
|
152044
|
-
|
|
152173
|
+
logger193.debug(
|
|
152045
152174
|
`Closing atomic write state for peer ${writeState.peerAddress.toString()} on endpoint ${writeState.endpoint.id} due to fabric removal`
|
|
152046
152175
|
);
|
|
152047
152176
|
writeState.close();
|
|
@@ -152090,7 +152219,7 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
|
|
|
152090
152219
|
if (!PeerAddress.is(attrWriteState.peerAddress, peerAddress)) {
|
|
152091
152220
|
return void 0;
|
|
152092
152221
|
}
|
|
152093
|
-
|
|
152222
|
+
logger193.debug(
|
|
152094
152223
|
`Found pending value for attribute ${attribute} for peer ${peerAddress.nodeId}`,
|
|
152095
152224
|
serialize(attrWriteState.pendingAttributeValues[attribute])
|
|
152096
152225
|
);
|
|
@@ -152131,7 +152260,7 @@ var ThermostatBehaviorConstructor = ClusterBehavior.for(Thermostat3);
|
|
|
152131
152260
|
var ThermostatBehavior = ThermostatBehaviorConstructor;
|
|
152132
152261
|
|
|
152133
152262
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/thermostat/ThermostatServer.js
|
|
152134
|
-
var
|
|
152263
|
+
var logger194 = Logger.get("ThermostatServer");
|
|
152135
152264
|
var ThermostatBehaviorLogicBase = ThermostatBehavior.with(
|
|
152136
152265
|
Thermostat3.Feature.Heating,
|
|
152137
152266
|
Thermostat3.Feature.Cooling,
|
|
@@ -152160,7 +152289,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
152160
152289
|
throw new ImplementationError("Setback feature is deprecated and not allowed to be enabled");
|
|
152161
152290
|
}
|
|
152162
152291
|
if (this.features.matterScheduleConfiguration) {
|
|
152163
|
-
|
|
152292
|
+
logger194.warn("MatterScheduleConfiguration feature is not yet implemented. Please do not activate it");
|
|
152164
152293
|
}
|
|
152165
152294
|
if (!this.features.presets && !this.features.matterScheduleConfiguration) {
|
|
152166
152295
|
this.atomicRequest = Behavior.unimplemented;
|
|
@@ -152281,7 +152410,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
152281
152410
|
throw new StatusResponse.InvalidCommandError("Requested PresetHandle not found");
|
|
152282
152411
|
}
|
|
152283
152412
|
}
|
|
152284
|
-
|
|
152413
|
+
logger194.info(`Setting active preset handle to`, presetHandle);
|
|
152285
152414
|
this.state.activePresetHandle = presetHandle;
|
|
152286
152415
|
return preset;
|
|
152287
152416
|
}
|
|
@@ -152351,7 +152480,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
152351
152480
|
}
|
|
152352
152481
|
if (this.state.setpointHoldExpiryTimestamp === void 0) {
|
|
152353
152482
|
} else {
|
|
152354
|
-
|
|
152483
|
+
logger194.warn(
|
|
152355
152484
|
"Handling for setpointHoldExpiryTimestamp is not yet implemented. To use this attribute you need to install the needed logic yourself"
|
|
152356
152485
|
);
|
|
152357
152486
|
}
|
|
@@ -152424,7 +152553,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
152424
152553
|
"RemoteSensing cannot be set to LocalTemperature when LocalTemperatureNotExposed feature is enabled"
|
|
152425
152554
|
);
|
|
152426
152555
|
}
|
|
152427
|
-
|
|
152556
|
+
logger194.debug("LocalTemperatureNotExposed feature is enabled, ignoring local temperature measurement");
|
|
152428
152557
|
this.state.localTemperature = null;
|
|
152429
152558
|
}
|
|
152430
152559
|
let localTemperature = null;
|
|
@@ -152433,11 +152562,11 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
152433
152562
|
const endpoints = this.env.get(ServerNode).endpoints;
|
|
152434
152563
|
const endpoint = endpoints.has(localTempEndpoint) ? endpoints.for(localTempEndpoint) : void 0;
|
|
152435
152564
|
if (endpoint !== void 0 && endpoint.behaviors.has(TemperatureMeasurementServer)) {
|
|
152436
|
-
|
|
152565
|
+
logger194.debug(
|
|
152437
152566
|
`Using existing TemperatureMeasurement cluster on endpoint #${localTempEndpoint} for local temperature measurement`
|
|
152438
152567
|
);
|
|
152439
152568
|
if (this.state.externalMeasuredIndoorTemperature !== void 0) {
|
|
152440
|
-
|
|
152569
|
+
logger194.warn(
|
|
152441
152570
|
"Both local TemperatureMeasurement cluster and externalMeasuredIndoorTemperature state are set, using local cluster"
|
|
152442
152571
|
);
|
|
152443
152572
|
}
|
|
@@ -152447,19 +152576,19 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
152447
152576
|
);
|
|
152448
152577
|
localTemperature = endpoint.stateOf(TemperatureMeasurementServer).measuredValue;
|
|
152449
152578
|
} else {
|
|
152450
|
-
|
|
152579
|
+
logger194.warn(
|
|
152451
152580
|
`No TemperatureMeasurement cluster found on endpoint #${localTempEndpoint}, falling back to externalMeasuredIndoorTemperature state if set`
|
|
152452
152581
|
);
|
|
152453
152582
|
}
|
|
152454
152583
|
} else {
|
|
152455
152584
|
if (this.state.externalMeasuredIndoorTemperature === void 0) {
|
|
152456
152585
|
if (this.state.localTemperatureCalibration !== void 0) {
|
|
152457
|
-
|
|
152586
|
+
logger194.warn(
|
|
152458
152587
|
"No local TemperatureMeasurement cluster available, externalMeasuredIndoorTemperature state not set but localTemperatureCalibration is used: Ensure to correctly consider the calibration when updating the localTemperature value"
|
|
152459
152588
|
);
|
|
152460
152589
|
}
|
|
152461
152590
|
} else {
|
|
152462
|
-
|
|
152591
|
+
logger194.info("Using measured temperature via externalMeasuredIndoorTemperature state");
|
|
152463
152592
|
localTemperature = this.state.externalMeasuredIndoorTemperature ?? null;
|
|
152464
152593
|
}
|
|
152465
152594
|
this.reactTo(this.events.externalMeasuredIndoorTemperature$Changed, this.#handleMeasuredTemperatureChange);
|
|
@@ -152499,28 +152628,28 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
152499
152628
|
const endpoints = this.env.get(ServerNode).endpoints;
|
|
152500
152629
|
const endpoint = endpoints.has(localOccupancyEndpoint) ? endpoints.for(localOccupancyEndpoint) : void 0;
|
|
152501
152630
|
if (endpoint !== void 0 && endpoint.behaviors.has(OccupancySensingServer)) {
|
|
152502
|
-
|
|
152631
|
+
logger194.debug(
|
|
152503
152632
|
`Using existing OccupancySensing cluster on endpoint ${localOccupancyEndpoint} for local occupancy sensing`
|
|
152504
152633
|
);
|
|
152505
152634
|
if (this.state.externallyMeasuredOccupancy !== void 0) {
|
|
152506
|
-
|
|
152635
|
+
logger194.warn(
|
|
152507
152636
|
"Both local OccupancySensing cluster and externallyMeasuredOccupancy state are set, using local cluster"
|
|
152508
152637
|
);
|
|
152509
152638
|
}
|
|
152510
152639
|
this.reactTo(endpoint.eventsOf(OccupancySensingServer).occupancy$Changed, this.#handleOccupancyChange);
|
|
152511
152640
|
currentOccupancy = !!endpoint.stateOf(OccupancySensingServer).occupancy.occupied;
|
|
152512
152641
|
} else {
|
|
152513
|
-
|
|
152642
|
+
logger194.warn(
|
|
152514
152643
|
`No OccupancySensing cluster found on endpoint ${localOccupancyEndpoint}, falling back to externallyMeasuredOccupancy state if set`
|
|
152515
152644
|
);
|
|
152516
152645
|
}
|
|
152517
152646
|
} else {
|
|
152518
152647
|
if (this.state.externallyMeasuredOccupancy === void 0) {
|
|
152519
|
-
|
|
152648
|
+
logger194.warn(
|
|
152520
152649
|
"No local OccupancySensing cluster available and externallyMeasuredOccupancy state not set"
|
|
152521
152650
|
);
|
|
152522
152651
|
} else {
|
|
152523
|
-
|
|
152652
|
+
logger194.info("Using occupancy via externallyMeasuredOccupancy state");
|
|
152524
152653
|
currentOccupancy = this.state.externallyMeasuredOccupancy;
|
|
152525
152654
|
}
|
|
152526
152655
|
this.reactTo(this.events.externallyMeasuredOccupancy$Changed, this.#handleExternalOccupancyChange);
|
|
@@ -152763,7 +152892,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
152763
152892
|
max = this.state[`max${scope}`] ?? defaults.absMax,
|
|
152764
152893
|
absMax = this.state[`absMax${scope}`] ?? defaults.absMax
|
|
152765
152894
|
} = details;
|
|
152766
|
-
|
|
152895
|
+
logger194.debug(
|
|
152767
152896
|
`Validating user setpoint limits for ${scope}: absMin=${absMin}, min=${min}, max=${max}, absMax=${absMax}`
|
|
152768
152897
|
);
|
|
152769
152898
|
if (absMin > min) {
|
|
@@ -152810,7 +152939,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
152810
152939
|
const limitMax = scope === "Heat" ? this.heatSetpointMaximum : this.coolSetpointMaximum;
|
|
152811
152940
|
const result = cropValueRange(setpoint, limitMin, limitMax);
|
|
152812
152941
|
if (result !== setpoint) {
|
|
152813
|
-
|
|
152942
|
+
logger194.debug(
|
|
152814
152943
|
`${scope} setpoint (${setpoint}) is out of limits [${limitMin}, ${limitMax}], clamping to ${result}`
|
|
152815
152944
|
);
|
|
152816
152945
|
}
|
|
@@ -153290,7 +153419,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
153290
153419
|
*/
|
|
153291
153420
|
#handlePersistedPresetsChanged(newPresets, oldPresets) {
|
|
153292
153421
|
if (oldPresets === void 0) {
|
|
153293
|
-
|
|
153422
|
+
logger194.debug(
|
|
153294
153423
|
"Old presets is undefined, skipping some checks. This should only happen on setup of the behavior."
|
|
153295
153424
|
);
|
|
153296
153425
|
}
|
|
@@ -153299,7 +153428,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
153299
153428
|
const newPresetHandles = /* @__PURE__ */ new Set();
|
|
153300
153429
|
for (const preset of newPresets) {
|
|
153301
153430
|
if (preset.presetHandle === null) {
|
|
153302
|
-
|
|
153431
|
+
logger194.debug("Preset is missing presetHandle, generating a new one");
|
|
153303
153432
|
preset.presetHandle = entropy.randomBytes(16);
|
|
153304
153433
|
changed = true;
|
|
153305
153434
|
}
|
|
@@ -153348,7 +153477,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
|
|
|
153348
153477
|
throw new StatusResponse.InvalidInStateError(`ActivePresetHandle references non-existing presetHandle`);
|
|
153349
153478
|
}
|
|
153350
153479
|
if (changed) {
|
|
153351
|
-
|
|
153480
|
+
logger194.debug("PresetHandles or BuiltIn flags were updated, updating persistedPresets");
|
|
153352
153481
|
this.state.persistedPresets = newPresets;
|
|
153353
153482
|
}
|
|
153354
153483
|
}
|
|
@@ -154437,7 +154566,7 @@ var WindowCoveringBehaviorConstructor = ClusterBehavior.for(WindowCovering3);
|
|
|
154437
154566
|
var WindowCoveringBehavior = WindowCoveringBehaviorConstructor;
|
|
154438
154567
|
|
|
154439
154568
|
// ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/window-covering/WindowCoveringServer.js
|
|
154440
|
-
var
|
|
154569
|
+
var logger195 = Logger.get("WindowCoveringServer");
|
|
154441
154570
|
var WindowCoveringBase = WindowCoveringBehavior.with(
|
|
154442
154571
|
WindowCovering3.Feature.Lift,
|
|
154443
154572
|
WindowCovering3.Feature.Tilt,
|
|
@@ -154522,7 +154651,7 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
|
|
|
154522
154651
|
this.state.configStatus = configStatus;
|
|
154523
154652
|
});
|
|
154524
154653
|
}
|
|
154525
|
-
|
|
154654
|
+
logger195.debug(
|
|
154526
154655
|
`Mode changed to ${Diagnostic.json(mode)} and config status to ${Diagnostic.json(configStatus)} and internal calibration mode to ${this.internal.calibrationMode}`
|
|
154527
154656
|
);
|
|
154528
154657
|
}
|
|
@@ -154530,7 +154659,7 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
|
|
|
154530
154659
|
#handleOperationalStatusChanging(operationalStatus) {
|
|
154531
154660
|
const globalStatus = operationalStatus.lift !== WindowCovering3.MovementStatus.Stopped ? operationalStatus.lift : operationalStatus.tilt;
|
|
154532
154661
|
operationalStatus.global = globalStatus;
|
|
154533
|
-
|
|
154662
|
+
logger195.debug(
|
|
154534
154663
|
`Operational status changed to ${Diagnostic.json(operationalStatus)} with new global status ${globalStatus}`
|
|
154535
154664
|
);
|
|
154536
154665
|
this.state.operationalStatus = operationalStatus;
|
|
@@ -154559,10 +154688,10 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
|
|
|
154559
154688
|
this.state.currentPositionLiftPercentage = percent100ths3 === null ? percent100ths3 : Math.floor(percent100ths3 / WC_PERCENT100THS_COEFFICIENT);
|
|
154560
154689
|
if (this.state.operationalStatus.lift !== WindowCovering3.MovementStatus.Stopped && percent100ths3 === this.state.targetPositionLiftPercent100ths) {
|
|
154561
154690
|
this.state.operationalStatus.lift = WindowCovering3.MovementStatus.Stopped;
|
|
154562
|
-
|
|
154691
|
+
logger195.debug("Lift movement stopped, target value reached");
|
|
154563
154692
|
}
|
|
154564
154693
|
}
|
|
154565
|
-
|
|
154694
|
+
logger195.debug(
|
|
154566
154695
|
`Syncing lift position ${this.state.currentPositionLiftPercent100ths === null ? null : (this.state.currentPositionLiftPercent100ths / 100).toFixed(2)} to ${this.state.currentPositionLiftPercentage}%`
|
|
154567
154696
|
);
|
|
154568
154697
|
}
|
|
@@ -154572,10 +154701,10 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
|
|
|
154572
154701
|
this.state.currentPositionTiltPercentage = percent100ths3 === null ? percent100ths3 : Math.floor(percent100ths3 / WC_PERCENT100THS_COEFFICIENT);
|
|
154573
154702
|
if (this.state.operationalStatus.tilt !== WindowCovering3.MovementStatus.Stopped && percent100ths3 === this.state.targetPositionTiltPercent100ths) {
|
|
154574
154703
|
this.state.operationalStatus.tilt = WindowCovering3.MovementStatus.Stopped;
|
|
154575
|
-
|
|
154704
|
+
logger195.debug("Tilt movement stopped, target value reached");
|
|
154576
154705
|
}
|
|
154577
154706
|
}
|
|
154578
|
-
|
|
154707
|
+
logger195.debug(
|
|
154579
154708
|
`Syncing tilt position ${this.state.currentPositionTiltPercent100ths === null ? null : (this.state.currentPositionTiltPercent100ths / 100).toFixed(2)} to ${this.state.currentPositionTiltPercentage}%`
|
|
154580
154709
|
);
|
|
154581
154710
|
}
|
|
@@ -154663,7 +154792,7 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
|
|
|
154663
154792
|
}
|
|
154664
154793
|
const directionInfo = direction === 2 ? ` in direction by position` : ` in direction ${direction === 1 ? "Close" : "Open"}`;
|
|
154665
154794
|
const targetInfo = targetPercent100ths === void 0 ? "" : ` to target position ${(targetPercent100ths / 100).toFixed(2)}`;
|
|
154666
|
-
|
|
154795
|
+
logger195.debug(
|
|
154667
154796
|
`Moving the device ${type === 0 ? "Lift" : "Tilt"}${directionInfo} (reversed=${reversed})${targetInfo}`
|
|
154668
154797
|
);
|
|
154669
154798
|
}
|
|
@@ -154685,7 +154814,7 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
|
|
|
154685
154814
|
);
|
|
154686
154815
|
}
|
|
154687
154816
|
if (type === 0 && this.state.configStatus.liftMovementReversed) {
|
|
154688
|
-
|
|
154817
|
+
logger195.debug("Lift movement is reversed");
|
|
154689
154818
|
}
|
|
154690
154819
|
switch (type) {
|
|
154691
154820
|
case 0:
|
|
@@ -154941,7 +155070,7 @@ function matterSubscriptionOptions() {
|
|
|
154941
155070
|
}
|
|
154942
155071
|
|
|
154943
155072
|
// src/matter/endpoints/server-mode-server-node.ts
|
|
154944
|
-
var
|
|
155073
|
+
var logger196 = Logger.get("ServerModeServerNode");
|
|
154945
155074
|
var ServerModeServerNode = class extends ServerNode {
|
|
154946
155075
|
deviceEndpoints = /* @__PURE__ */ new Map();
|
|
154947
155076
|
featureFlags;
|
|
@@ -155038,7 +155167,7 @@ var ServerModeServerNode = class extends ServerNode {
|
|
|
155038
155167
|
await this.set({ basicInformation });
|
|
155039
155168
|
} catch (e) {
|
|
155040
155169
|
const msg = e instanceof Error ? e.message : String(e);
|
|
155041
|
-
|
|
155170
|
+
logger196.warn(
|
|
155042
155171
|
`Failed to apply server-mode identity for ${entityId}: ${msg}`
|
|
155043
155172
|
);
|
|
155044
155173
|
}
|
|
@@ -155049,7 +155178,7 @@ var ServerModeServerNode = class extends ServerNode {
|
|
|
155049
155178
|
await this.set({ productDescription: { deviceType } });
|
|
155050
155179
|
} catch (e) {
|
|
155051
155180
|
const msg = e instanceof Error ? e.message : String(e);
|
|
155052
|
-
|
|
155181
|
+
logger196.warn(`Failed to set server-mode device type: ${msg}`);
|
|
155053
155182
|
}
|
|
155054
155183
|
}
|
|
155055
155184
|
async factoryReset() {
|
|
@@ -155069,84 +155198,6 @@ function dropUndefined(obj) {
|
|
|
155069
155198
|
|
|
155070
155199
|
// src/plugins/builtin/camera/camera-tcp-requirement.ts
|
|
155071
155200
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
155072
|
-
|
|
155073
|
-
// src/plugins/plugin-storage.ts
|
|
155074
|
-
init_esm();
|
|
155075
|
-
import * as fs9 from "node:fs";
|
|
155076
|
-
import * as path11 from "node:path";
|
|
155077
|
-
var logger196 = Logger.get("PluginStorage");
|
|
155078
|
-
var SAVE_DEBOUNCE_MS = 500;
|
|
155079
|
-
function pluginStorageFilePath(storageDir, bridgeId, pluginName) {
|
|
155080
|
-
const safe = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
155081
|
-
return path11.join(
|
|
155082
|
-
storageDir,
|
|
155083
|
-
`plugin-${safe(bridgeId)}-${safe(pluginName)}.json`
|
|
155084
|
-
);
|
|
155085
|
-
}
|
|
155086
|
-
var FilePluginStorage = class {
|
|
155087
|
-
data = {};
|
|
155088
|
-
dirty = false;
|
|
155089
|
-
filePath;
|
|
155090
|
-
saveTimer;
|
|
155091
|
-
constructor(storageDir, bridgeId, pluginName) {
|
|
155092
|
-
this.filePath = pluginStorageFilePath(storageDir, bridgeId, pluginName);
|
|
155093
|
-
this.load();
|
|
155094
|
-
}
|
|
155095
|
-
async get(key, defaultValue) {
|
|
155096
|
-
const value = this.data[key];
|
|
155097
|
-
return value ?? defaultValue;
|
|
155098
|
-
}
|
|
155099
|
-
async set(key, value) {
|
|
155100
|
-
this.data[key] = value;
|
|
155101
|
-
this.dirty = true;
|
|
155102
|
-
this.scheduleSave();
|
|
155103
|
-
}
|
|
155104
|
-
async delete(key) {
|
|
155105
|
-
delete this.data[key];
|
|
155106
|
-
this.dirty = true;
|
|
155107
|
-
this.scheduleSave();
|
|
155108
|
-
}
|
|
155109
|
-
async keys() {
|
|
155110
|
-
return Object.keys(this.data);
|
|
155111
|
-
}
|
|
155112
|
-
load() {
|
|
155113
|
-
try {
|
|
155114
|
-
if (fs9.existsSync(this.filePath)) {
|
|
155115
|
-
const raw = fs9.readFileSync(this.filePath, "utf-8");
|
|
155116
|
-
this.data = JSON.parse(raw);
|
|
155117
|
-
}
|
|
155118
|
-
} catch (e) {
|
|
155119
|
-
logger196.warn(`Failed to load plugin storage from ${this.filePath}:`, e);
|
|
155120
|
-
this.data = {};
|
|
155121
|
-
}
|
|
155122
|
-
}
|
|
155123
|
-
scheduleSave() {
|
|
155124
|
-
if (this.saveTimer) clearTimeout(this.saveTimer);
|
|
155125
|
-
this.saveTimer = setTimeout(() => this.save(), SAVE_DEBOUNCE_MS);
|
|
155126
|
-
}
|
|
155127
|
-
save() {
|
|
155128
|
-
if (!this.dirty) return;
|
|
155129
|
-
if (this.saveTimer) {
|
|
155130
|
-
clearTimeout(this.saveTimer);
|
|
155131
|
-
this.saveTimer = void 0;
|
|
155132
|
-
}
|
|
155133
|
-
try {
|
|
155134
|
-
const dir = path11.dirname(this.filePath);
|
|
155135
|
-
if (!fs9.existsSync(dir)) {
|
|
155136
|
-
fs9.mkdirSync(dir, { recursive: true });
|
|
155137
|
-
}
|
|
155138
|
-
fs9.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
|
|
155139
|
-
this.dirty = false;
|
|
155140
|
-
} catch (e) {
|
|
155141
|
-
logger196.warn(`Failed to save plugin storage to ${this.filePath}:`, e);
|
|
155142
|
-
}
|
|
155143
|
-
}
|
|
155144
|
-
flush() {
|
|
155145
|
-
this.save();
|
|
155146
|
-
}
|
|
155147
|
-
};
|
|
155148
|
-
|
|
155149
|
-
// src/plugins/builtin/camera/camera-tcp-requirement.ts
|
|
155150
155201
|
var CAMERA_TCP_CONFIG = { incoming: true, outgoing: false };
|
|
155151
155202
|
function parseCameraList(cameras) {
|
|
155152
155203
|
if (typeof cameras !== "string") return [];
|
|
@@ -156830,6 +156881,28 @@ var SafePluginRunner = class {
|
|
|
156830
156881
|
return void 0;
|
|
156831
156882
|
}
|
|
156832
156883
|
}
|
|
156884
|
+
/**
|
|
156885
|
+
* Run a shutdown-class hook with timeout only. An open breaker never blocks
|
|
156886
|
+
* it and its failures never count toward the breaker: cleanup has to run
|
|
156887
|
+
* exactly when a plugin is at its most broken, or timers and sockets leak.
|
|
156888
|
+
*/
|
|
156889
|
+
async runCleanup(pluginName, operation, fn, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
156890
|
+
const timeout = this.createTimeout(pluginName, operation, timeoutMs);
|
|
156891
|
+
try {
|
|
156892
|
+
const result = await Promise.race([
|
|
156893
|
+
Promise.resolve().then(fn),
|
|
156894
|
+
timeout.promise
|
|
156895
|
+
]);
|
|
156896
|
+
timeout.clear();
|
|
156897
|
+
return result;
|
|
156898
|
+
} catch (error) {
|
|
156899
|
+
timeout.clear();
|
|
156900
|
+
logger201.error(
|
|
156901
|
+
`Plugin "${pluginName}" failed during ${operation}: ${error instanceof Error ? error.message : String(error)}`
|
|
156902
|
+
);
|
|
156903
|
+
return void 0;
|
|
156904
|
+
}
|
|
156905
|
+
}
|
|
156833
156906
|
/**
|
|
156834
156907
|
* Run a synchronous plugin function with try/catch + circuit breaker.
|
|
156835
156908
|
*/
|
|
@@ -156880,6 +156953,8 @@ var SafePluginRunner = class {
|
|
|
156880
156953
|
var logger202 = Logger.get("PluginManager");
|
|
156881
156954
|
var PLUGIN_API_VERSION = 1;
|
|
156882
156955
|
var MAX_PLUGIN_DEVICE_ID_LENGTH = 100;
|
|
156956
|
+
var LEGACY_ENABLED_KEY = "__enabled";
|
|
156957
|
+
var PLUGIN_CONFIG_KEY = "config";
|
|
156883
156958
|
function validatePluginDevice(device) {
|
|
156884
156959
|
if (!device || typeof device !== "object") return "device must be an object";
|
|
156885
156960
|
const d = device;
|
|
@@ -156913,18 +156988,23 @@ var PluginManager = class {
|
|
|
156913
156988
|
domainMappingOwners = /* @__PURE__ */ new Map();
|
|
156914
156989
|
storageDir;
|
|
156915
156990
|
bridgeId;
|
|
156991
|
+
stateFile;
|
|
156916
156992
|
homeAssistant;
|
|
156917
156993
|
runner = new SafePluginRunner();
|
|
156918
156994
|
registry;
|
|
156919
156995
|
/** Callback invoked when a plugin registers a new device */
|
|
156920
156996
|
onDeviceRegistered;
|
|
156921
|
-
/**
|
|
156997
|
+
/**
|
|
156998
|
+
* Callback invoked when a plugin removes a device. keepIdentity marks a
|
|
156999
|
+
* reversible stop: the endpoint closes but keeps its persisted number.
|
|
157000
|
+
*/
|
|
156922
157001
|
onDeviceUnregistered;
|
|
156923
157002
|
/** Callback invoked when a plugin updates device state */
|
|
156924
157003
|
onDeviceStateUpdated;
|
|
156925
157004
|
constructor(bridgeId, storageDir, homeAssistant) {
|
|
156926
157005
|
this.bridgeId = bridgeId;
|
|
156927
157006
|
this.storageDir = storageDir;
|
|
157007
|
+
this.stateFile = pluginStateFilePath(storageDir, bridgeId);
|
|
156928
157008
|
this.homeAssistant = homeAssistant;
|
|
156929
157009
|
}
|
|
156930
157010
|
setRegistry(registry3) {
|
|
@@ -157009,35 +157089,59 @@ var PluginManager = class {
|
|
|
157009
157089
|
this.bridgeId,
|
|
157010
157090
|
plugin.name
|
|
157011
157091
|
);
|
|
157092
|
+
let enabled = this.readEnabledState()[plugin.name];
|
|
157093
|
+
const legacy = await storage2.get(LEGACY_ENABLED_KEY);
|
|
157094
|
+
if (typeof legacy === "boolean") {
|
|
157095
|
+
if (enabled === void 0) {
|
|
157096
|
+
enabled = legacy;
|
|
157097
|
+
this.persistEnabled(plugin.name, legacy);
|
|
157098
|
+
}
|
|
157099
|
+
await storage2.delete(LEGACY_ENABLED_KEY);
|
|
157100
|
+
storage2.flush();
|
|
157101
|
+
}
|
|
157102
|
+
if (enabled === false) {
|
|
157103
|
+
metadata.enabled = false;
|
|
157104
|
+
logger202.info(`Plugin "${plugin.name}" stays disabled (persisted)`);
|
|
157105
|
+
}
|
|
157012
157106
|
const devices = /* @__PURE__ */ new Map();
|
|
157013
157107
|
const pluginLogger = Logger.get(`Plugin:${plugin.name}`);
|
|
157108
|
+
const registerDeviceAt = (epoch) => async (device) => {
|
|
157109
|
+
const live = this.instances.get(plugin.name);
|
|
157110
|
+
if (!live || !live.metadata.enabled || live.epoch !== epoch) {
|
|
157111
|
+
pluginLogger.warn(
|
|
157112
|
+
`Dropped a device registration from a disabled or superseded start of "${plugin.name}"`
|
|
157113
|
+
);
|
|
157114
|
+
return;
|
|
157115
|
+
}
|
|
157116
|
+
const validationError = validatePluginDevice(device);
|
|
157117
|
+
if (validationError) {
|
|
157118
|
+
pluginLogger.warn(`Rejected device registration: ${validationError}`);
|
|
157119
|
+
return;
|
|
157120
|
+
}
|
|
157121
|
+
if (devices.has(device.id)) {
|
|
157122
|
+
pluginLogger.warn(
|
|
157123
|
+
`Device "${device.id}" already registered, updating`
|
|
157124
|
+
);
|
|
157125
|
+
}
|
|
157126
|
+
devices.set(device.id, device);
|
|
157127
|
+
await this.onDeviceRegistered?.(plugin.name, device);
|
|
157128
|
+
pluginLogger.debug(`Registered device: ${device.name} (${device.id})`);
|
|
157129
|
+
};
|
|
157014
157130
|
const context = {
|
|
157015
157131
|
bridgeId: this.bridgeId,
|
|
157016
157132
|
storage: storage2,
|
|
157017
157133
|
log: pluginLogger,
|
|
157018
157134
|
homeAssistant: this.homeAssistant,
|
|
157019
|
-
registerDevice:
|
|
157020
|
-
const validationError = validatePluginDevice(device);
|
|
157021
|
-
if (validationError) {
|
|
157022
|
-
pluginLogger.warn(`Rejected device registration: ${validationError}`);
|
|
157023
|
-
return;
|
|
157024
|
-
}
|
|
157025
|
-
if (devices.has(device.id)) {
|
|
157026
|
-
pluginLogger.warn(
|
|
157027
|
-
`Device "${device.id}" already registered, updating`
|
|
157028
|
-
);
|
|
157029
|
-
}
|
|
157030
|
-
devices.set(device.id, device);
|
|
157031
|
-
await this.onDeviceRegistered?.(plugin.name, device);
|
|
157032
|
-
pluginLogger.debug(`Registered device: ${device.name} (${device.id})`);
|
|
157033
|
-
},
|
|
157135
|
+
registerDevice: registerDeviceAt(0),
|
|
157034
157136
|
unregisterDevice: async (deviceId) => {
|
|
157035
157137
|
if (!devices.has(deviceId)) {
|
|
157036
157138
|
pluginLogger.warn(`Device "${deviceId}" not found`);
|
|
157037
157139
|
return;
|
|
157038
157140
|
}
|
|
157039
157141
|
devices.delete(deviceId);
|
|
157040
|
-
await this.onDeviceUnregistered?.(plugin.name, deviceId
|
|
157142
|
+
await this.onDeviceUnregistered?.(plugin.name, deviceId, {
|
|
157143
|
+
keepIdentity: this.instances.get(plugin.name)?.suspending === true
|
|
157144
|
+
});
|
|
157041
157145
|
pluginLogger.debug(`Unregistered device: ${deviceId}`);
|
|
157042
157146
|
},
|
|
157043
157147
|
updateDeviceState: (deviceId, clusterId3, attributes9) => {
|
|
@@ -157076,7 +157180,14 @@ var PluginManager = class {
|
|
|
157076
157180
|
context,
|
|
157077
157181
|
metadata,
|
|
157078
157182
|
devices,
|
|
157079
|
-
started: false
|
|
157183
|
+
started: false,
|
|
157184
|
+
queue: Promise.resolve(),
|
|
157185
|
+
epoch: 0,
|
|
157186
|
+
contextAt: (epoch) => ({
|
|
157187
|
+
...context,
|
|
157188
|
+
registerDevice: registerDeviceAt(epoch)
|
|
157189
|
+
}),
|
|
157190
|
+
suspending: false
|
|
157080
157191
|
});
|
|
157081
157192
|
logger202.info(
|
|
157082
157193
|
`Registered plugin: ${plugin.name} v${plugin.version} (${metadata.source})`
|
|
@@ -157087,28 +157198,42 @@ var PluginManager = class {
|
|
|
157087
157198
|
*/
|
|
157088
157199
|
async startAll() {
|
|
157089
157200
|
for (const [name, instance] of this.instances) {
|
|
157090
|
-
|
|
157091
|
-
|
|
157092
|
-
|
|
157093
|
-
|
|
157094
|
-
|
|
157095
|
-
|
|
157096
|
-
|
|
157097
|
-
|
|
157098
|
-
|
|
157099
|
-
|
|
157100
|
-
|
|
157101
|
-
|
|
157102
|
-
|
|
157201
|
+
await this.inTransition(instance, () => this.startPlugin(name, instance));
|
|
157202
|
+
}
|
|
157203
|
+
}
|
|
157204
|
+
// One transition at a time per plugin: a disable during a slow start waits
|
|
157205
|
+
// for the start to settle instead of interleaving with it.
|
|
157206
|
+
inTransition(instance, fn) {
|
|
157207
|
+
const run = instance.queue.then(fn, fn);
|
|
157208
|
+
instance.queue = run.then(
|
|
157209
|
+
() => void 0,
|
|
157210
|
+
() => void 0
|
|
157211
|
+
);
|
|
157212
|
+
return run;
|
|
157213
|
+
}
|
|
157214
|
+
async startPlugin(name, instance) {
|
|
157215
|
+
if (!instance.metadata.enabled) return;
|
|
157216
|
+
if (this.runner.isDisabled(name)) {
|
|
157217
|
+
logger202.warn(
|
|
157218
|
+
`Plugin "${name}" is disabled (circuit breaker), skipping start`
|
|
157103
157219
|
);
|
|
157104
|
-
|
|
157105
|
-
|
|
157106
|
-
|
|
157107
|
-
|
|
157108
|
-
|
|
157109
|
-
|
|
157110
|
-
|
|
157111
|
-
|
|
157220
|
+
instance.metadata.enabled = false;
|
|
157221
|
+
return;
|
|
157222
|
+
}
|
|
157223
|
+
logger202.info(`Starting plugin: ${name}`);
|
|
157224
|
+
const epoch = ++instance.epoch;
|
|
157225
|
+
await this.runner.run(
|
|
157226
|
+
name,
|
|
157227
|
+
"onStart",
|
|
157228
|
+
() => instance.plugin.onStart(instance.contextAt(epoch))
|
|
157229
|
+
);
|
|
157230
|
+
if (this.runner.isDisabled(name)) {
|
|
157231
|
+
instance.metadata.enabled = false;
|
|
157232
|
+
} else if (this.runner.getState(name).failures === 0) {
|
|
157233
|
+
instance.started = true;
|
|
157234
|
+
}
|
|
157235
|
+
if (instance.plugin.getCurrentConfig) {
|
|
157236
|
+
instance.metadata.config = instance.plugin.getCurrentConfig();
|
|
157112
157237
|
}
|
|
157113
157238
|
}
|
|
157114
157239
|
/**
|
|
@@ -157130,23 +157255,32 @@ var PluginManager = class {
|
|
|
157130
157255
|
}
|
|
157131
157256
|
}
|
|
157132
157257
|
/**
|
|
157133
|
-
* Shut down all plugins
|
|
157258
|
+
* Shut down all plugins. Runs outside the circuit breaker: cleanup must
|
|
157259
|
+
* happen even for a plugin the breaker took down.
|
|
157134
157260
|
*/
|
|
157135
157261
|
async shutdownAll(reason) {
|
|
157136
157262
|
for (const [name, instance] of this.instances) {
|
|
157137
|
-
|
|
157138
|
-
|
|
157139
|
-
|
|
157140
|
-
|
|
157141
|
-
(
|
|
157142
|
-
|
|
157143
|
-
|
|
157144
|
-
|
|
157145
|
-
|
|
157146
|
-
|
|
157147
|
-
|
|
157148
|
-
|
|
157149
|
-
|
|
157263
|
+
await this.inTransition(instance, async () => {
|
|
157264
|
+
instance.epoch++;
|
|
157265
|
+
instance.suspending = true;
|
|
157266
|
+
try {
|
|
157267
|
+
if (instance.started && instance.plugin.onShutdown) {
|
|
157268
|
+
await this.runner.runCleanup(
|
|
157269
|
+
name,
|
|
157270
|
+
"onShutdown",
|
|
157271
|
+
() => instance.plugin.onShutdown(reason)
|
|
157272
|
+
);
|
|
157273
|
+
}
|
|
157274
|
+
} finally {
|
|
157275
|
+
instance.suspending = false;
|
|
157276
|
+
}
|
|
157277
|
+
const storage2 = instance.context.storage;
|
|
157278
|
+
if (storage2 instanceof FilePluginStorage) {
|
|
157279
|
+
storage2.flush();
|
|
157280
|
+
}
|
|
157281
|
+
instance.started = false;
|
|
157282
|
+
logger202.info(`Plugin "${name}" shut down`);
|
|
157283
|
+
});
|
|
157150
157284
|
}
|
|
157151
157285
|
this.instances.clear();
|
|
157152
157286
|
}
|
|
@@ -157179,23 +157313,122 @@ var PluginManager = class {
|
|
|
157179
157313
|
instance.metadata.enabled = true;
|
|
157180
157314
|
}
|
|
157181
157315
|
}
|
|
157182
|
-
|
|
157316
|
+
/**
|
|
157317
|
+
* Disable a plugin: stop it, unmount its devices, and persist the choice so
|
|
157318
|
+
* a bridge restart does not bring it back (#439). The shutdown runs outside
|
|
157319
|
+
* the circuit breaker, a broken plugin still has to release its resources.
|
|
157320
|
+
* Returns the resulting metadata, or undefined for an unknown name.
|
|
157321
|
+
*/
|
|
157322
|
+
async disablePlugin(pluginName) {
|
|
157183
157323
|
const instance = this.instances.get(pluginName);
|
|
157184
|
-
if (instance)
|
|
157324
|
+
if (!instance) return void 0;
|
|
157325
|
+
return this.inTransition(instance, async () => {
|
|
157185
157326
|
instance.metadata.enabled = false;
|
|
157186
|
-
|
|
157187
|
-
|
|
157188
|
-
|
|
157189
|
-
|
|
157190
|
-
|
|
157327
|
+
instance.epoch++;
|
|
157328
|
+
this.persistEnabled(pluginName, false);
|
|
157329
|
+
instance.suspending = true;
|
|
157330
|
+
try {
|
|
157331
|
+
if (instance.started && instance.plugin.onShutdown) {
|
|
157332
|
+
await this.runner.runCleanup(
|
|
157333
|
+
pluginName,
|
|
157334
|
+
"onShutdown",
|
|
157335
|
+
() => instance.plugin.onShutdown("Plugin disabled")
|
|
157336
|
+
);
|
|
157337
|
+
}
|
|
157338
|
+
instance.started = false;
|
|
157339
|
+
for (const deviceId of [...instance.devices.keys()]) {
|
|
157340
|
+
instance.devices.delete(deviceId);
|
|
157341
|
+
await this.onDeviceUnregistered?.(pluginName, deviceId, {
|
|
157342
|
+
keepIdentity: true
|
|
157343
|
+
});
|
|
157344
|
+
}
|
|
157345
|
+
} finally {
|
|
157346
|
+
instance.suspending = false;
|
|
157191
157347
|
}
|
|
157192
|
-
|
|
157348
|
+
for (const [domain, owner] of this.domainMappingOwners) {
|
|
157349
|
+
if (owner === pluginName) {
|
|
157350
|
+
this.domainMappings.delete(domain);
|
|
157351
|
+
this.domainMappingOwners.delete(domain);
|
|
157352
|
+
}
|
|
157353
|
+
}
|
|
157354
|
+
return instance.metadata;
|
|
157355
|
+
});
|
|
157193
157356
|
}
|
|
157194
|
-
|
|
157195
|
-
|
|
157357
|
+
/**
|
|
157358
|
+
* Enable a plugin, persist the choice, and start it right away so its
|
|
157359
|
+
* devices come back without a bridge restart. Returns the resulting
|
|
157360
|
+
* metadata, or undefined for an unknown name.
|
|
157361
|
+
*/
|
|
157362
|
+
async enablePlugin(pluginName) {
|
|
157196
157363
|
const instance = this.instances.get(pluginName);
|
|
157197
|
-
if (instance)
|
|
157364
|
+
if (!instance) return void 0;
|
|
157365
|
+
return this.inTransition(instance, async () => {
|
|
157366
|
+
this.runner.resetCircuitBreaker(pluginName);
|
|
157198
157367
|
instance.metadata.enabled = true;
|
|
157368
|
+
this.persistEnabled(pluginName, true);
|
|
157369
|
+
if (instance.started) return instance.metadata;
|
|
157370
|
+
const pending = instance.pendingConfig;
|
|
157371
|
+
if (pending) {
|
|
157372
|
+
try {
|
|
157373
|
+
await instance.context.storage.set(PLUGIN_CONFIG_KEY, pending);
|
|
157374
|
+
await instance.context.storage.flush?.();
|
|
157375
|
+
instance.metadata.config = pending;
|
|
157376
|
+
instance.pendingConfig = void 0;
|
|
157377
|
+
} catch (e) {
|
|
157378
|
+
logger202.warn(
|
|
157379
|
+
`Failed to persist the parked config for "${pluginName}", it stays parked:`,
|
|
157380
|
+
e
|
|
157381
|
+
);
|
|
157382
|
+
}
|
|
157383
|
+
}
|
|
157384
|
+
logger202.info(`Starting plugin: ${pluginName}`);
|
|
157385
|
+
const epoch = ++instance.epoch;
|
|
157386
|
+
await this.runner.run(
|
|
157387
|
+
pluginName,
|
|
157388
|
+
"onStart",
|
|
157389
|
+
() => instance.plugin.onStart(instance.contextAt(epoch))
|
|
157390
|
+
);
|
|
157391
|
+
if (this.runner.isDisabled(pluginName)) {
|
|
157392
|
+
instance.metadata.enabled = false;
|
|
157393
|
+
return instance.metadata;
|
|
157394
|
+
}
|
|
157395
|
+
if (this.runner.getState(pluginName).failures === 0) {
|
|
157396
|
+
instance.started = true;
|
|
157397
|
+
}
|
|
157398
|
+
if (instance.plugin.getCurrentConfig) {
|
|
157399
|
+
instance.metadata.config = instance.plugin.getCurrentConfig();
|
|
157400
|
+
}
|
|
157401
|
+
if (instance.started && instance.plugin.onConfigure) {
|
|
157402
|
+
await this.runner.run(
|
|
157403
|
+
pluginName,
|
|
157404
|
+
"onConfigure",
|
|
157405
|
+
() => instance.plugin.onConfigure()
|
|
157406
|
+
);
|
|
157407
|
+
}
|
|
157408
|
+
return instance.metadata;
|
|
157409
|
+
});
|
|
157410
|
+
}
|
|
157411
|
+
readEnabledState() {
|
|
157412
|
+
try {
|
|
157413
|
+
if (fs10.existsSync(this.stateFile)) {
|
|
157414
|
+
return JSON.parse(fs10.readFileSync(this.stateFile, "utf-8"));
|
|
157415
|
+
}
|
|
157416
|
+
} catch (e) {
|
|
157417
|
+
logger202.warn(`Failed to read the plugin state file:`, e);
|
|
157418
|
+
}
|
|
157419
|
+
return {};
|
|
157420
|
+
}
|
|
157421
|
+
persistEnabled(pluginName, enabled) {
|
|
157422
|
+
try {
|
|
157423
|
+
const state = this.readEnabledState();
|
|
157424
|
+
state[pluginName] = enabled;
|
|
157425
|
+
fs10.mkdirSync(path12.dirname(this.stateFile), { recursive: true });
|
|
157426
|
+
fs10.writeFileSync(this.stateFile, JSON.stringify(state, null, 2));
|
|
157427
|
+
} catch (e) {
|
|
157428
|
+
logger202.warn(
|
|
157429
|
+
`Failed to persist enabled=${enabled} for "${pluginName}":`,
|
|
157430
|
+
e
|
|
157431
|
+
);
|
|
157199
157432
|
}
|
|
157200
157433
|
}
|
|
157201
157434
|
getConfigSchema(pluginName) {
|
|
@@ -157209,27 +157442,36 @@ var PluginManager = class {
|
|
|
157209
157442
|
async updateConfig(pluginName, config8) {
|
|
157210
157443
|
const instance = this.instances.get(pluginName);
|
|
157211
157444
|
if (!instance) return false;
|
|
157212
|
-
|
|
157213
|
-
|
|
157214
|
-
|
|
157215
|
-
|
|
157216
|
-
|
|
157217
|
-
|
|
157218
|
-
|
|
157219
|
-
|
|
157445
|
+
return this.inTransition(instance, async () => {
|
|
157446
|
+
config8 = { ...config8 };
|
|
157447
|
+
const schema6 = instance.plugin.getConfigSchema?.();
|
|
157448
|
+
if (schema6) {
|
|
157449
|
+
for (const [key, prop] of Object.entries(schema6.properties)) {
|
|
157450
|
+
if (prop.secret && config8[key] === SECRET_UNCHANGED) {
|
|
157451
|
+
const stored = instance.metadata.config[key];
|
|
157452
|
+
if (stored == null) delete config8[key];
|
|
157453
|
+
else config8[key] = stored;
|
|
157454
|
+
}
|
|
157455
|
+
}
|
|
157456
|
+
}
|
|
157457
|
+
instance.metadata.config = config8;
|
|
157458
|
+
this.registry?.updateConfig(pluginName, config8);
|
|
157459
|
+
if (instance.plugin.onConfigChanged) {
|
|
157460
|
+
if (!instance.metadata.enabled) {
|
|
157461
|
+
instance.pendingConfig = config8;
|
|
157462
|
+
logger202.info(
|
|
157463
|
+
`Plugin "${pluginName}" is disabled, the config applies on enable`
|
|
157464
|
+
);
|
|
157465
|
+
} else {
|
|
157466
|
+
await this.runner.run(
|
|
157467
|
+
pluginName,
|
|
157468
|
+
"onConfigChanged",
|
|
157469
|
+
() => instance.plugin.onConfigChanged(config8)
|
|
157470
|
+
);
|
|
157220
157471
|
}
|
|
157221
157472
|
}
|
|
157222
|
-
|
|
157223
|
-
|
|
157224
|
-
this.registry?.updateConfig(pluginName, config8);
|
|
157225
|
-
if (instance.plugin.onConfigChanged) {
|
|
157226
|
-
await this.runner.run(
|
|
157227
|
-
pluginName,
|
|
157228
|
-
"onConfigChanged",
|
|
157229
|
-
() => instance.plugin.onConfigChanged(config8)
|
|
157230
|
-
);
|
|
157231
|
-
}
|
|
157232
|
-
return true;
|
|
157473
|
+
return true;
|
|
157474
|
+
});
|
|
157233
157475
|
}
|
|
157234
157476
|
};
|
|
157235
157477
|
|
|
@@ -157980,11 +158222,11 @@ var Bridge = class {
|
|
|
157980
158222
|
get pluginInfo() {
|
|
157981
158223
|
return this.endpointManager.getPluginInfo();
|
|
157982
158224
|
}
|
|
157983
|
-
enablePlugin(pluginName) {
|
|
157984
|
-
this.endpointManager.enablePlugin(pluginName);
|
|
158225
|
+
async enablePlugin(pluginName) {
|
|
158226
|
+
return await this.endpointManager.enablePlugin(pluginName);
|
|
157985
158227
|
}
|
|
157986
|
-
disablePlugin(pluginName) {
|
|
157987
|
-
this.endpointManager.disablePlugin(pluginName);
|
|
158228
|
+
async disablePlugin(pluginName) {
|
|
158229
|
+
return await this.endpointManager.disablePlugin(pluginName);
|
|
157988
158230
|
}
|
|
157989
158231
|
resetPlugin(pluginName) {
|
|
157990
158232
|
this.endpointManager.resetPlugin(pluginName);
|
|
@@ -171139,6 +171381,31 @@ function createCleanAreaServiceAreaServer(cleanAreaRooms) {
|
|
|
171139
171381
|
currentArea: null
|
|
171140
171382
|
});
|
|
171141
171383
|
}
|
|
171384
|
+
function getVacuumServiceAreas(attributes9, mapping) {
|
|
171385
|
+
const cleanAreaRooms = mapping?.cleanAreaRooms;
|
|
171386
|
+
if (cleanAreaRooms && cleanAreaRooms.length > 0) {
|
|
171387
|
+
return cleanAreaRooms.map((room) => ({
|
|
171388
|
+
areaId: room.areaId,
|
|
171389
|
+
name: room.name
|
|
171390
|
+
}));
|
|
171391
|
+
}
|
|
171392
|
+
const customAreas = mapping?.customServiceAreas;
|
|
171393
|
+
if (customAreas && customAreas.length > 0) {
|
|
171394
|
+
return customAreas.map((area, index) => ({
|
|
171395
|
+
areaId: index + 1,
|
|
171396
|
+
name: area.name
|
|
171397
|
+
}));
|
|
171398
|
+
}
|
|
171399
|
+
const roomEntities = mapping?.roomEntities;
|
|
171400
|
+
const rooms = roomEntities && roomEntities.length > 0 ? buttonEntitiesToRooms(roomEntities, attributes9) : parseVacuumRooms(attributes9);
|
|
171401
|
+
if (rooms.length > 0) {
|
|
171402
|
+
return roomsToAreas(rooms).map((area) => ({
|
|
171403
|
+
areaId: area.areaId,
|
|
171404
|
+
name: area.areaInfo.locationInfo?.locationName ?? ""
|
|
171405
|
+
}));
|
|
171406
|
+
}
|
|
171407
|
+
return [];
|
|
171408
|
+
}
|
|
171142
171409
|
|
|
171143
171410
|
// src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-run-mode-server.ts
|
|
171144
171411
|
var logger236 = Logger.get("VacuumRvcRunModeServer");
|
|
@@ -171313,6 +171580,135 @@ var cleaningStates = [
|
|
|
171313
171580
|
function vacuumIsCleaning(state) {
|
|
171314
171581
|
return state != null && cleaningStates.includes(state);
|
|
171315
171582
|
}
|
|
171583
|
+
function dispatchRoomClean(attributes9, mapping, entityId, selectedAreas, seam) {
|
|
171584
|
+
const customAreas = mapping?.customServiceAreas;
|
|
171585
|
+
if (customAreas && customAreas.length > 0) {
|
|
171586
|
+
const ephemeral = {
|
|
171587
|
+
completedAreas: /* @__PURE__ */ new Set(),
|
|
171588
|
+
lastCurrentArea: null,
|
|
171589
|
+
activeAreas: [],
|
|
171590
|
+
loggedShortCircuits: /* @__PURE__ */ new Set(),
|
|
171591
|
+
observedCleaning: false,
|
|
171592
|
+
pendingDispatches: [],
|
|
171593
|
+
cleanedAreaBaseline: null
|
|
171594
|
+
};
|
|
171595
|
+
const action = handleCustomServiceAreas(
|
|
171596
|
+
selectedAreas,
|
|
171597
|
+
customAreas,
|
|
171598
|
+
ephemeral
|
|
171599
|
+
);
|
|
171600
|
+
return { action, pending: ephemeral.pendingDispatches };
|
|
171601
|
+
}
|
|
171602
|
+
const cleanAreaRooms = mapping?.cleanAreaRooms;
|
|
171603
|
+
if (cleanAreaRooms && cleanAreaRooms.length > 0) {
|
|
171604
|
+
const haAreaIds = resolveCleanAreaIds(selectedAreas, cleanAreaRooms);
|
|
171605
|
+
if (haAreaIds.length > 0) {
|
|
171606
|
+
logger236.info(`CLEAN_AREA: cleaning HA areas: ${haAreaIds.join(", ")}`);
|
|
171607
|
+
return {
|
|
171608
|
+
action: {
|
|
171609
|
+
action: "vacuum.clean_area",
|
|
171610
|
+
data: { cleaning_area_id: haAreaIds }
|
|
171611
|
+
},
|
|
171612
|
+
pending: []
|
|
171613
|
+
};
|
|
171614
|
+
}
|
|
171615
|
+
}
|
|
171616
|
+
const roomEntities = mapping?.roomEntities;
|
|
171617
|
+
if (roomEntities && roomEntities.length > 0) {
|
|
171618
|
+
const matched = [];
|
|
171619
|
+
for (const areaId of selectedAreas) {
|
|
171620
|
+
const buttonId = roomEntities.find((id) => toAreaId(id) === areaId);
|
|
171621
|
+
if (buttonId) {
|
|
171622
|
+
matched.push({ areaId, entityId: buttonId });
|
|
171623
|
+
}
|
|
171624
|
+
}
|
|
171625
|
+
if (matched.length > 0) {
|
|
171626
|
+
logger236.info(
|
|
171627
|
+
`Roborock: ${matched.length} room button(s) queued: ${matched.map((m) => m.entityId).join(", ")}`
|
|
171628
|
+
);
|
|
171629
|
+
return {
|
|
171630
|
+
action: { action: "button.press", target: matched[0].entityId },
|
|
171631
|
+
pending: matched.slice(1).map(({ areaId, entityId: buttonId }) => ({
|
|
171632
|
+
areaId,
|
|
171633
|
+
action: { action: "button.press", target: buttonId }
|
|
171634
|
+
}))
|
|
171635
|
+
};
|
|
171636
|
+
}
|
|
171637
|
+
}
|
|
171638
|
+
if (entityId.startsWith("vacuum.valetudo_")) {
|
|
171639
|
+
return {
|
|
171640
|
+
action: buildValetudoSegmentAction(
|
|
171641
|
+
entityId,
|
|
171642
|
+
selectedAreas,
|
|
171643
|
+
mapping?.valetudoIdentifier
|
|
171644
|
+
),
|
|
171645
|
+
pending: []
|
|
171646
|
+
};
|
|
171647
|
+
}
|
|
171648
|
+
const rooms = parseVacuumRooms(attributes9);
|
|
171649
|
+
const roomIds = [];
|
|
171650
|
+
let targetMapName;
|
|
171651
|
+
for (const areaId of selectedAreas) {
|
|
171652
|
+
const room = rooms.find((r) => toAreaId(r.id) === areaId);
|
|
171653
|
+
if (room) {
|
|
171654
|
+
roomIds.push(room.originalId ?? room.id);
|
|
171655
|
+
if (room.mapName && !targetMapName) {
|
|
171656
|
+
targetMapName = room.mapName;
|
|
171657
|
+
}
|
|
171658
|
+
}
|
|
171659
|
+
}
|
|
171660
|
+
if (roomIds.length > 0) {
|
|
171661
|
+
logger236.info(`Starting cleaning with selected areas: ${roomIds.join(", ")}`);
|
|
171662
|
+
if (isDreameVacuum(attributes9)) {
|
|
171663
|
+
if (targetMapName) {
|
|
171664
|
+
const vacName = entityId.replace("vacuum.", "");
|
|
171665
|
+
const selectedMapEntity = `select.${vacName}_selected_map`;
|
|
171666
|
+
logger236.info(
|
|
171667
|
+
`Dreame multi-floor: switching to map "${targetMapName}" via ${selectedMapEntity}`
|
|
171668
|
+
);
|
|
171669
|
+
seam.callAction({
|
|
171670
|
+
action: "select.select_option",
|
|
171671
|
+
target: selectedMapEntity,
|
|
171672
|
+
data: { option: targetMapName }
|
|
171673
|
+
});
|
|
171674
|
+
}
|
|
171675
|
+
return {
|
|
171676
|
+
action: {
|
|
171677
|
+
action: "dreame_vacuum.vacuum_clean_segment",
|
|
171678
|
+
data: { segments: roomIds.length === 1 ? roomIds[0] : roomIds }
|
|
171679
|
+
},
|
|
171680
|
+
pending: []
|
|
171681
|
+
};
|
|
171682
|
+
}
|
|
171683
|
+
if (isRoborockVacuum(attributes9) || isXiaomiMiotVacuum(attributes9)) {
|
|
171684
|
+
return {
|
|
171685
|
+
action: {
|
|
171686
|
+
action: "vacuum.send_command",
|
|
171687
|
+
data: { command: "app_segment_clean", params: roomIds }
|
|
171688
|
+
},
|
|
171689
|
+
pending: []
|
|
171690
|
+
};
|
|
171691
|
+
}
|
|
171692
|
+
if (isEcovacsVacuum(attributes9)) {
|
|
171693
|
+
const roomIdStr = roomIds.join(",");
|
|
171694
|
+
logger236.info(`Ecovacs vacuum: Using spot_area for rooms: ${roomIdStr}`);
|
|
171695
|
+
return {
|
|
171696
|
+
action: {
|
|
171697
|
+
action: "vacuum.send_command",
|
|
171698
|
+
data: {
|
|
171699
|
+
command: "spot_area",
|
|
171700
|
+
params: { mapID: 0, cleanings: 1, rooms: roomIdStr }
|
|
171701
|
+
}
|
|
171702
|
+
},
|
|
171703
|
+
pending: []
|
|
171704
|
+
};
|
|
171705
|
+
}
|
|
171706
|
+
logger236.warn(
|
|
171707
|
+
`Room cleaning via send_command not supported for this vacuum type. Rooms: ${roomIds.join(", ")}. Falling back to vacuum.start`
|
|
171708
|
+
);
|
|
171709
|
+
}
|
|
171710
|
+
return { action: { action: "vacuum.start" }, pending: [] };
|
|
171711
|
+
}
|
|
171316
171712
|
var vacuumRvcRunModeConfig = {
|
|
171317
171713
|
getCurrentMode: (entity) => {
|
|
171318
171714
|
const isCleaning = vacuumIsCleaning(entity.state);
|
|
@@ -171339,123 +171735,19 @@ var vacuumRvcRunModeConfig = {
|
|
|
171339
171735
|
const selectedAreas = [...serviceArea.state.selectedAreas];
|
|
171340
171736
|
if (selectedAreas.length > 0) {
|
|
171341
171737
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
171342
|
-
const
|
|
171343
|
-
const attributes9 = entity.state.attributes;
|
|
171738
|
+
const effective = homeAssistant.endpoint.vacuumEffective;
|
|
171739
|
+
const attributes9 = (effective?.state ?? homeAssistant.entity.state).attributes;
|
|
171740
|
+
const mapping = effective ? effective.mapping : homeAssistant.state.mapping;
|
|
171344
171741
|
const session = getSession(homeAssistant.endpoint);
|
|
171345
|
-
const
|
|
171346
|
-
|
|
171347
|
-
|
|
171348
|
-
|
|
171349
|
-
|
|
171350
|
-
|
|
171351
|
-
|
|
171352
|
-
|
|
171353
|
-
|
|
171354
|
-
`CLEAN_AREA: cleaning HA areas: ${haAreaIds.join(", ")}`
|
|
171355
|
-
);
|
|
171356
|
-
return {
|
|
171357
|
-
action: "vacuum.clean_area",
|
|
171358
|
-
data: { cleaning_area_id: haAreaIds }
|
|
171359
|
-
};
|
|
171360
|
-
}
|
|
171361
|
-
}
|
|
171362
|
-
const roomEntities = homeAssistant.state.mapping?.roomEntities;
|
|
171363
|
-
if (roomEntities && roomEntities.length > 0) {
|
|
171364
|
-
const matched = [];
|
|
171365
|
-
for (const areaId of selectedAreas) {
|
|
171366
|
-
const entityId = roomEntities.find((id) => toAreaId(id) === areaId);
|
|
171367
|
-
if (entityId) {
|
|
171368
|
-
matched.push({ areaId, entityId });
|
|
171369
|
-
}
|
|
171370
|
-
}
|
|
171371
|
-
if (matched.length > 0) {
|
|
171372
|
-
logger236.info(
|
|
171373
|
-
`Roborock: ${matched.length} room button(s) queued: ${matched.map((m) => m.entityId).join(", ")}`
|
|
171374
|
-
);
|
|
171375
|
-
session.pendingDispatches = matched.slice(1).map(({ areaId, entityId }) => ({
|
|
171376
|
-
areaId,
|
|
171377
|
-
action: { action: "button.press", target: entityId }
|
|
171378
|
-
}));
|
|
171379
|
-
return {
|
|
171380
|
-
action: "button.press",
|
|
171381
|
-
target: matched[0].entityId
|
|
171382
|
-
};
|
|
171383
|
-
}
|
|
171384
|
-
}
|
|
171385
|
-
const vacuumEntityId = homeAssistant.entityId;
|
|
171386
|
-
if (vacuumEntityId.startsWith("vacuum.valetudo_")) {
|
|
171387
|
-
return buildValetudoSegmentAction(
|
|
171388
|
-
vacuumEntityId,
|
|
171389
|
-
selectedAreas,
|
|
171390
|
-
homeAssistant.state.mapping?.valetudoIdentifier
|
|
171391
|
-
);
|
|
171392
|
-
}
|
|
171393
|
-
const rooms = parseVacuumRooms(attributes9);
|
|
171394
|
-
const roomIds = [];
|
|
171395
|
-
let targetMapName;
|
|
171396
|
-
for (const areaId of selectedAreas) {
|
|
171397
|
-
const room = rooms.find((r) => toAreaId(r.id) === areaId);
|
|
171398
|
-
if (room) {
|
|
171399
|
-
roomIds.push(room.originalId ?? room.id);
|
|
171400
|
-
if (room.mapName && !targetMapName) {
|
|
171401
|
-
targetMapName = room.mapName;
|
|
171402
|
-
}
|
|
171403
|
-
}
|
|
171404
|
-
}
|
|
171405
|
-
if (roomIds.length > 0) {
|
|
171406
|
-
logger236.info(
|
|
171407
|
-
`Starting cleaning with selected areas: ${roomIds.join(", ")}`
|
|
171408
|
-
);
|
|
171409
|
-
if (isDreameVacuum(attributes9)) {
|
|
171410
|
-
if (targetMapName) {
|
|
171411
|
-
const vacName = vacuumEntityId.replace("vacuum.", "");
|
|
171412
|
-
const selectedMapEntity = `select.${vacName}_selected_map`;
|
|
171413
|
-
logger236.info(
|
|
171414
|
-
`Dreame multi-floor: switching to map "${targetMapName}" via ${selectedMapEntity}`
|
|
171415
|
-
);
|
|
171416
|
-
homeAssistant.callAction({
|
|
171417
|
-
action: "select.select_option",
|
|
171418
|
-
target: selectedMapEntity,
|
|
171419
|
-
data: { option: targetMapName }
|
|
171420
|
-
});
|
|
171421
|
-
}
|
|
171422
|
-
return {
|
|
171423
|
-
action: "dreame_vacuum.vacuum_clean_segment",
|
|
171424
|
-
data: {
|
|
171425
|
-
segments: roomIds.length === 1 ? roomIds[0] : roomIds
|
|
171426
|
-
}
|
|
171427
|
-
};
|
|
171428
|
-
}
|
|
171429
|
-
if (isRoborockVacuum(attributes9) || isXiaomiMiotVacuum(attributes9)) {
|
|
171430
|
-
return {
|
|
171431
|
-
action: "vacuum.send_command",
|
|
171432
|
-
data: {
|
|
171433
|
-
command: "app_segment_clean",
|
|
171434
|
-
params: roomIds
|
|
171435
|
-
}
|
|
171436
|
-
};
|
|
171437
|
-
}
|
|
171438
|
-
if (isEcovacsVacuum(attributes9)) {
|
|
171439
|
-
const roomIdStr = roomIds.join(",");
|
|
171440
|
-
logger236.info(
|
|
171441
|
-
`Ecovacs vacuum: Using spot_area for rooms: ${roomIdStr}`
|
|
171442
|
-
);
|
|
171443
|
-
return {
|
|
171444
|
-
action: "vacuum.send_command",
|
|
171445
|
-
data: {
|
|
171446
|
-
command: "spot_area",
|
|
171447
|
-
params: {
|
|
171448
|
-
mapID: 0,
|
|
171449
|
-
cleanings: 1,
|
|
171450
|
-
rooms: roomIdStr
|
|
171451
|
-
}
|
|
171452
|
-
}
|
|
171453
|
-
};
|
|
171454
|
-
}
|
|
171455
|
-
logger236.warn(
|
|
171456
|
-
`Room cleaning via send_command not supported for this vacuum type. Rooms: ${roomIds.join(", ")}. Falling back to vacuum.start`
|
|
171457
|
-
);
|
|
171458
|
-
}
|
|
171742
|
+
const result = dispatchRoomClean(
|
|
171743
|
+
attributes9,
|
|
171744
|
+
mapping,
|
|
171745
|
+
homeAssistant.entityId,
|
|
171746
|
+
selectedAreas,
|
|
171747
|
+
{ callAction: (a) => homeAssistant.callAction(a) }
|
|
171748
|
+
);
|
|
171749
|
+
session.pendingDispatches = result.pending;
|
|
171750
|
+
return result.action;
|
|
171459
171751
|
}
|
|
171460
171752
|
} catch {
|
|
171461
171753
|
}
|
|
@@ -173922,6 +174214,12 @@ function asStandaloneEndpointType(type) {
|
|
|
173922
174214
|
// src/matter/endpoints/legacy/legacy-endpoint.ts
|
|
173923
174215
|
var logger245 = Logger.get("LegacyEndpoint");
|
|
173924
174216
|
var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
174217
|
+
constructor(type, entityId, customName, mappedEntityIds, throttleMs, endpointId, vacuumEffective) {
|
|
174218
|
+
super(type, entityId, customName, mappedEntityIds, endpointId);
|
|
174219
|
+
this.vacuumEffective = vacuumEffective;
|
|
174220
|
+
this.flushUpdate = throttleMs && throttleMs > 50 ? throttleLatest(this.flushPendingUpdate.bind(this), throttleMs) : debounce6(this.flushPendingUpdate.bind(this), 50);
|
|
174221
|
+
}
|
|
174222
|
+
vacuumEffective;
|
|
173925
174223
|
static async create(registry3, entityId, mapping, pluginDomainMappings, standalone = false, endpointId, identityAnchor) {
|
|
173926
174224
|
const deviceRegistry = registry3.deviceOf(entityId);
|
|
173927
174225
|
let state = registry3.initialState(entityId);
|
|
@@ -174295,19 +174593,17 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
174295
174593
|
}
|
|
174296
174594
|
const customName = effectiveMapping?.customName;
|
|
174297
174595
|
const mappedIds = getMappedEntityIds(effectiveMapping);
|
|
174596
|
+
const vacuumEffective = entityId.startsWith("vacuum.") ? { mapping: effectiveMapping, state } : void 0;
|
|
174298
174597
|
return new _LegacyEndpoint(
|
|
174299
174598
|
type,
|
|
174300
174599
|
entityId,
|
|
174301
174600
|
customName,
|
|
174302
174601
|
mappedIds,
|
|
174303
174602
|
effectiveMapping?.updateThrottleMs,
|
|
174304
|
-
endpointId
|
|
174603
|
+
endpointId,
|
|
174604
|
+
vacuumEffective
|
|
174305
174605
|
);
|
|
174306
174606
|
}
|
|
174307
|
-
constructor(type, entityId, customName, mappedEntityIds, throttleMs, endpointId) {
|
|
174308
|
-
super(type, entityId, customName, mappedEntityIds, endpointId);
|
|
174309
|
-
this.flushUpdate = throttleMs && throttleMs > 50 ? throttleLatest(this.flushPendingUpdate.bind(this), throttleMs) : debounce6(this.flushPendingUpdate.bind(this), 50);
|
|
174310
|
-
}
|
|
174311
174607
|
lastState;
|
|
174312
174608
|
pendingMappedChange = false;
|
|
174313
174609
|
flushUpdate;
|
|
@@ -174374,6 +174670,96 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
174374
174670
|
}
|
|
174375
174671
|
};
|
|
174376
174672
|
|
|
174673
|
+
// src/matter/endpoints/legacy/vacuum/vacuum-area-switch.ts
|
|
174674
|
+
init_esm();
|
|
174675
|
+
init_home_assistant_entity_behavior();
|
|
174676
|
+
var VacuumAreaSwitchEndpoint = class _VacuumAreaSwitchEndpoint extends EntityEndpoint {
|
|
174677
|
+
constructor(type, entityId, endpointId, areaId, vacuumEndpointId, parentEffective) {
|
|
174678
|
+
super(type, entityId, void 0, [], endpointId);
|
|
174679
|
+
this.areaId = areaId;
|
|
174680
|
+
this.vacuumEndpointId = vacuumEndpointId;
|
|
174681
|
+
this.parentEffective = parentEffective;
|
|
174682
|
+
}
|
|
174683
|
+
areaId;
|
|
174684
|
+
vacuumEndpointId;
|
|
174685
|
+
parentEffective;
|
|
174686
|
+
static create(params) {
|
|
174687
|
+
const { vacuumEndpointId, entity, mapping, area, parentEffective } = params;
|
|
174688
|
+
const endpointId = `${vacuumEndpointId}_roomsw_${area.areaId}`;
|
|
174689
|
+
const areaId = area.areaId;
|
|
174690
|
+
const switchMapping = mapping?.customSerialNumber ? { ...mapping, customSerialNumber: void 0 } : mapping;
|
|
174691
|
+
const switchEntity = entity.deviceRegistry?.serial_number ? {
|
|
174692
|
+
...entity,
|
|
174693
|
+
deviceRegistry: {
|
|
174694
|
+
...entity.deviceRegistry,
|
|
174695
|
+
serial_number: void 0
|
|
174696
|
+
}
|
|
174697
|
+
} : entity;
|
|
174698
|
+
const type = OnOffPlugInUnitDevice.with(
|
|
174699
|
+
BasicInformationServer2,
|
|
174700
|
+
IdentifyServer2,
|
|
174701
|
+
HomeAssistantEntityBehavior,
|
|
174702
|
+
OnOffServer2({
|
|
174703
|
+
isOn: () => false,
|
|
174704
|
+
// off is a no-op (momentary auto-reset only, script pattern).
|
|
174705
|
+
turnOff: null,
|
|
174706
|
+
turnOn: (_value, agent) => {
|
|
174707
|
+
const ha = agent.get(HomeAssistantEntityBehavior);
|
|
174708
|
+
const { action } = dispatchRoomClean(
|
|
174709
|
+
parentEffective.state.attributes,
|
|
174710
|
+
parentEffective.mapping,
|
|
174711
|
+
ha.entityId,
|
|
174712
|
+
[areaId],
|
|
174713
|
+
{ callAction: (a) => ha.callAction(a) }
|
|
174714
|
+
);
|
|
174715
|
+
return action;
|
|
174716
|
+
}
|
|
174717
|
+
})
|
|
174718
|
+
).set({
|
|
174719
|
+
homeAssistantEntity: {
|
|
174720
|
+
entity: switchEntity,
|
|
174721
|
+
mapping: switchMapping,
|
|
174722
|
+
customName: area.name,
|
|
174723
|
+
// Distinct anchor so uniqueId/serial don't collide with the vacuum's,
|
|
174724
|
+
// and stay frozen across HA renames.
|
|
174725
|
+
identityAnchor: endpointId
|
|
174726
|
+
}
|
|
174727
|
+
});
|
|
174728
|
+
return new _VacuumAreaSwitchEndpoint(
|
|
174729
|
+
type,
|
|
174730
|
+
entity.entity_id,
|
|
174731
|
+
endpointId,
|
|
174732
|
+
areaId,
|
|
174733
|
+
vacuumEndpointId,
|
|
174734
|
+
parentEffective
|
|
174735
|
+
);
|
|
174736
|
+
}
|
|
174737
|
+
async updateStates(states) {
|
|
174738
|
+
const state = states[this.entityId];
|
|
174739
|
+
if (!state) return;
|
|
174740
|
+
try {
|
|
174741
|
+
await this.construction.ready;
|
|
174742
|
+
} catch {
|
|
174743
|
+
return;
|
|
174744
|
+
}
|
|
174745
|
+
try {
|
|
174746
|
+
const current = this.stateOf(HomeAssistantEntityBehavior).entity;
|
|
174747
|
+
await this.setStateOf(HomeAssistantEntityBehavior, {
|
|
174748
|
+
entity: { ...current, state }
|
|
174749
|
+
});
|
|
174750
|
+
} catch (error) {
|
|
174751
|
+
if (error instanceof TransactionDestroyedError || error instanceof DestroyedDependencyError) {
|
|
174752
|
+
return;
|
|
174753
|
+
}
|
|
174754
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
174755
|
+
if (message.includes("Endpoint storage inaccessible")) {
|
|
174756
|
+
return;
|
|
174757
|
+
}
|
|
174758
|
+
throw error;
|
|
174759
|
+
}
|
|
174760
|
+
}
|
|
174761
|
+
};
|
|
174762
|
+
|
|
174377
174763
|
// src/plugins/builtin/camera/camera-plugin.ts
|
|
174378
174764
|
init_esm();
|
|
174379
174765
|
|
|
@@ -174482,10 +174868,11 @@ function unregisterRequestor(sessionId) {
|
|
|
174482
174868
|
pendingDeliveries.delete(sessionId);
|
|
174483
174869
|
}
|
|
174484
174870
|
}
|
|
174485
|
-
function
|
|
174486
|
-
|
|
174487
|
-
for (const
|
|
174488
|
-
|
|
174871
|
+
function unregisterRequestorsByOwner(owner) {
|
|
174872
|
+
if (owner == null) return;
|
|
174873
|
+
for (const [id, registration] of [...registry2]) {
|
|
174874
|
+
if (registration.owner === owner) unregisterRequestor(id);
|
|
174875
|
+
}
|
|
174489
174876
|
}
|
|
174490
174877
|
function deliverAnswerDeferred(sessionId, sdp, onGiveUp) {
|
|
174491
174878
|
const prior = pendingDeliveries.get(sessionId);
|
|
@@ -174653,7 +175040,9 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
|
|
|
174653
175040
|
registerRequestor(id, {
|
|
174654
175041
|
session,
|
|
174655
175042
|
requestorEndpoint,
|
|
174656
|
-
env: this.env
|
|
175043
|
+
env: this.env,
|
|
175044
|
+
// The bridge instance scopes this session to its camera plugin.
|
|
175045
|
+
owner: this.state.bridge
|
|
174657
175046
|
});
|
|
174658
175047
|
}
|
|
174659
175048
|
let answerSdp;
|
|
@@ -175358,10 +175747,11 @@ var CameraPlugin = class {
|
|
|
175358
175747
|
});
|
|
175359
175748
|
}
|
|
175360
175749
|
this.deviceIds = [];
|
|
175361
|
-
|
|
175362
|
-
});
|
|
175750
|
+
const bridge = this.bridge;
|
|
175363
175751
|
this.bridge = void 0;
|
|
175364
|
-
|
|
175752
|
+
await bridge?.close().catch(() => {
|
|
175753
|
+
});
|
|
175754
|
+
if (bridge) unregisterRequestorsByOwner(bridge);
|
|
175365
175755
|
}
|
|
175366
175756
|
};
|
|
175367
175757
|
|
|
@@ -175719,6 +176109,9 @@ var SecurityPlugin = class {
|
|
|
175719
176109
|
// coalesced per entity so a hung call cannot pile up work behind it.
|
|
175720
176110
|
tasks = [];
|
|
175721
176111
|
draining = false;
|
|
176112
|
+
// Bumped on every teardown and bring-up; queued tasks from an older
|
|
176113
|
+
// generation never dispatch.
|
|
176114
|
+
effectGeneration = 0;
|
|
175722
176115
|
connection;
|
|
175723
176116
|
unsubscribeEvents;
|
|
175724
176117
|
retryTimer;
|
|
@@ -175731,6 +176124,21 @@ var SecurityPlugin = class {
|
|
|
175731
176124
|
const stored = await context.storage.get(CONFIG_KEY2);
|
|
175732
176125
|
this.config = { ...this.config, ...stored ?? {} };
|
|
175733
176126
|
this.applyLists();
|
|
176127
|
+
if (!this.isConfigured()) {
|
|
176128
|
+
this.log.info(
|
|
176129
|
+
"no trigger entities configured, the security devices stay unregistered"
|
|
176130
|
+
);
|
|
176131
|
+
return;
|
|
176132
|
+
}
|
|
176133
|
+
await this.bringUp();
|
|
176134
|
+
}
|
|
176135
|
+
isConfigured() {
|
|
176136
|
+
return this.watched.size > 0;
|
|
176137
|
+
}
|
|
176138
|
+
async bringUp() {
|
|
176139
|
+
const context = this.context;
|
|
176140
|
+
if (!context) return;
|
|
176141
|
+
this.effectGeneration++;
|
|
175734
176142
|
this.machine = new SecurityStateMachine(
|
|
175735
176143
|
this.machineConfig(),
|
|
175736
176144
|
this.effects()
|
|
@@ -175753,21 +176161,38 @@ var SecurityPlugin = class {
|
|
|
175753
176161
|
this.pushDeviceStates();
|
|
175754
176162
|
this.startConnection();
|
|
175755
176163
|
}
|
|
176164
|
+
async tearDown() {
|
|
176165
|
+
this.machine?.shutdown();
|
|
176166
|
+
this.machine = void 0;
|
|
176167
|
+
this.tasks = [];
|
|
176168
|
+
this.effectGeneration++;
|
|
176169
|
+
await this.stopConnection();
|
|
176170
|
+
for (const id of [...Object.values(MODE_DEVICE_IDS), ALARM_DEVICE_ID]) {
|
|
176171
|
+
await this.context?.unregisterDevice(id).catch(() => {
|
|
176172
|
+
});
|
|
176173
|
+
}
|
|
176174
|
+
}
|
|
175756
176175
|
async onConfigChanged(config8) {
|
|
175757
176176
|
this.config = config8;
|
|
175758
176177
|
await this.context?.storage.set(CONFIG_KEY2, this.config);
|
|
175759
176178
|
this.applyLists();
|
|
175760
|
-
|
|
176179
|
+
if (!this.isConfigured()) {
|
|
176180
|
+
if (this.machine) {
|
|
176181
|
+
this.log.info("trigger lists emptied, removing the security devices");
|
|
176182
|
+
await this.tearDown();
|
|
176183
|
+
}
|
|
176184
|
+
return;
|
|
176185
|
+
}
|
|
176186
|
+
if (!this.machine) {
|
|
176187
|
+
await this.bringUp();
|
|
176188
|
+
return;
|
|
176189
|
+
}
|
|
176190
|
+
this.machine.setConfig(this.machineConfig());
|
|
175761
176191
|
await this.stopConnection();
|
|
175762
176192
|
this.startConnection();
|
|
175763
176193
|
}
|
|
175764
176194
|
async onShutdown() {
|
|
175765
|
-
this.
|
|
175766
|
-
await this.stopConnection();
|
|
175767
|
-
for (const id of [...Object.values(MODE_DEVICE_IDS), ALARM_DEVICE_ID]) {
|
|
175768
|
-
await this.context?.unregisterDevice(id).catch(() => {
|
|
175769
|
-
});
|
|
175770
|
-
}
|
|
176195
|
+
await this.tearDown();
|
|
175771
176196
|
}
|
|
175772
176197
|
getCurrentConfig() {
|
|
175773
176198
|
return { ...this.config };
|
|
@@ -176005,6 +176430,7 @@ var SecurityPlugin = class {
|
|
|
176005
176430
|
});
|
|
176006
176431
|
}
|
|
176007
176432
|
pushTask(task) {
|
|
176433
|
+
task.gen = this.effectGeneration;
|
|
176008
176434
|
this.tasks.push(task);
|
|
176009
176435
|
queueMicrotask(() => void this.drain());
|
|
176010
176436
|
}
|
|
@@ -176052,6 +176478,7 @@ var SecurityPlugin = class {
|
|
|
176052
176478
|
for (; ; ) {
|
|
176053
176479
|
const task = this.tasks.shift();
|
|
176054
176480
|
if (!task) return;
|
|
176481
|
+
if (task.gen !== this.effectGeneration) continue;
|
|
176055
176482
|
try {
|
|
176056
176483
|
await this.runTask(task);
|
|
176057
176484
|
} catch (e) {
|
|
@@ -176677,7 +177104,7 @@ var BridgeEndpointManager = class extends Service {
|
|
|
176677
177104
|
);
|
|
176678
177105
|
}
|
|
176679
177106
|
};
|
|
176680
|
-
this.pluginManager.onDeviceUnregistered = async (pluginName, deviceId) => {
|
|
177107
|
+
this.pluginManager.onDeviceUnregistered = async (pluginName, deviceId, options) => {
|
|
176681
177108
|
const listeners = this.pluginListeners.get(deviceId);
|
|
176682
177109
|
if (listeners) {
|
|
176683
177110
|
for (const { observable, listener } of listeners) {
|
|
@@ -176691,7 +177118,11 @@ var BridgeEndpointManager = class extends Service {
|
|
|
176691
177118
|
const endpoint = this.pluginEndpoints.get(deviceId);
|
|
176692
177119
|
if (endpoint) {
|
|
176693
177120
|
try {
|
|
176694
|
-
|
|
177121
|
+
if (options?.keepIdentity) {
|
|
177122
|
+
await endpoint.close();
|
|
177123
|
+
} else {
|
|
177124
|
+
await endpoint.delete();
|
|
177125
|
+
}
|
|
176695
177126
|
} catch (e) {
|
|
176696
177127
|
this.log.warn(
|
|
176697
177128
|
`Plugin "${pluginName}": failed to remove device "${deviceId}":`,
|
|
@@ -176827,11 +177258,11 @@ var BridgeEndpointManager = class extends Service {
|
|
|
176827
177258
|
circuitBreakers
|
|
176828
177259
|
};
|
|
176829
177260
|
}
|
|
176830
|
-
enablePlugin(pluginName) {
|
|
176831
|
-
this.pluginManager?.enablePlugin(pluginName);
|
|
177261
|
+
async enablePlugin(pluginName) {
|
|
177262
|
+
return await this.pluginManager?.enablePlugin(pluginName);
|
|
176832
177263
|
}
|
|
176833
|
-
disablePlugin(pluginName) {
|
|
176834
|
-
this.pluginManager?.disablePlugin(pluginName);
|
|
177264
|
+
async disablePlugin(pluginName) {
|
|
177265
|
+
return await this.pluginManager?.disablePlugin(pluginName);
|
|
176835
177266
|
}
|
|
176836
177267
|
resetPlugin(pluginName) {
|
|
176837
177268
|
this.pluginManager?.resetPlugin(pluginName);
|
|
@@ -176849,8 +177280,8 @@ var BridgeEndpointManager = class extends Service {
|
|
|
176849
177280
|
async isolateEntity(entityName) {
|
|
176850
177281
|
const endpoints = this.root.parts.map((p) => p);
|
|
176851
177282
|
const endpoint = endpoints.find(
|
|
176852
|
-
(e) => e.id === entityName || e.entityId === entityName
|
|
176853
|
-
);
|
|
177283
|
+
(e) => !(e instanceof VacuumAreaSwitchEndpoint) && (e.id === entityName || e.entityId === entityName)
|
|
177284
|
+
) ?? endpoints.find((e) => e.id === entityName);
|
|
176854
177285
|
if (endpoint) {
|
|
176855
177286
|
this.log.warn(
|
|
176856
177287
|
`Isolating entity ${endpoint.entityId} due to runtime error`
|
|
@@ -176862,6 +177293,17 @@ var BridgeEndpointManager = class extends Service {
|
|
|
176862
177293
|
}
|
|
176863
177294
|
this.pendingRemovals.delete(endpoint.entityId);
|
|
176864
177295
|
this.mappingFingerprints.delete(endpoint.entityId);
|
|
177296
|
+
if (!(endpoint instanceof VacuumAreaSwitchEndpoint)) {
|
|
177297
|
+
for (const sw of endpoints) {
|
|
177298
|
+
if (!(sw instanceof VacuumAreaSwitchEndpoint)) continue;
|
|
177299
|
+
if (sw.vacuumEndpointId !== endpoint.id) continue;
|
|
177300
|
+
try {
|
|
177301
|
+
await sw.delete();
|
|
177302
|
+
} catch (e) {
|
|
177303
|
+
this.log.warn(`Failed to remove area switch ${sw.id}:`, e);
|
|
177304
|
+
}
|
|
177305
|
+
}
|
|
177306
|
+
}
|
|
176865
177307
|
}
|
|
176866
177308
|
}
|
|
176867
177309
|
// refreshDevices only runs on registry-fingerprint changes, which may not
|
|
@@ -176947,7 +177389,7 @@ var BridgeEndpointManager = class extends Service {
|
|
|
176947
177389
|
async refreshDevices() {
|
|
176948
177390
|
this.registry.refresh();
|
|
176949
177391
|
this._failedEntities = [];
|
|
176950
|
-
const endpoints = this.root.parts.map((p) => p);
|
|
177392
|
+
const endpoints = this.root.parts.map((p) => p).filter((p) => !(p instanceof VacuumAreaSwitchEndpoint));
|
|
176951
177393
|
this.entityIds = this.registry.entityIds;
|
|
176952
177394
|
if (this.registry.isAutoComposedDevicesEnabled()) {
|
|
176953
177395
|
for (const eid of this.entityIds) {
|
|
@@ -177010,6 +177452,19 @@ var BridgeEndpointManager = class extends Service {
|
|
|
177010
177452
|
this.bridgeId,
|
|
177011
177453
|
buildPresentEntityIds(fullEntities)
|
|
177012
177454
|
);
|
|
177455
|
+
for (const part of [...this.root.parts]) {
|
|
177456
|
+
if (!(part instanceof VacuumAreaSwitchEndpoint)) continue;
|
|
177457
|
+
const claimant = endpointIdToEntity.get(part.id);
|
|
177458
|
+
if (claimant == null) continue;
|
|
177459
|
+
this.log.info(
|
|
177460
|
+
`Area switch ${part.id} collides with entity ${claimant}, removing the switch`
|
|
177461
|
+
);
|
|
177462
|
+
try {
|
|
177463
|
+
await part.delete();
|
|
177464
|
+
} catch (e) {
|
|
177465
|
+
this.log.warn(`Failed to remove colliding area switch ${part.id}:`, e);
|
|
177466
|
+
}
|
|
177467
|
+
}
|
|
177013
177468
|
const existingEndpoints = [];
|
|
177014
177469
|
const now = Date.now();
|
|
177015
177470
|
for (const endpoint of endpoints) {
|
|
@@ -177169,10 +177624,117 @@ var BridgeEndpointManager = class extends Service {
|
|
|
177169
177624
|
}
|
|
177170
177625
|
}
|
|
177171
177626
|
}
|
|
177627
|
+
await this.reconcileAreaSwitches();
|
|
177172
177628
|
if (this.unsubscribe) {
|
|
177173
177629
|
this.startObserving();
|
|
177174
177630
|
}
|
|
177175
177631
|
}
|
|
177632
|
+
// Opt-in per-area room switches (#355). One momentary OnOffPlugInUnit sibling
|
|
177633
|
+
// per configured service area, mounted alongside its vacuum with a stable
|
|
177634
|
+
// derived id. Areas and mapping come from the parent's vacuumEffective, the
|
|
177635
|
+
// exact config its ServiceArea cluster was built from, never from raw storage
|
|
177636
|
+
// (raw can be a different id space: injected Valetudo/Roborock rooms,
|
|
177637
|
+
// auto-resolved CLEAN_AREA). The vacuum's own endpoint is never touched here.
|
|
177638
|
+
// Switches are kept while their parent endpoint survives with the flag on
|
|
177639
|
+
// (the parent's removal grace is mirrored for free), rebuilt via close() when
|
|
177640
|
+
// the parent was recreated for a mapping change so numbers survive, and
|
|
177641
|
+
// deleted when the flag goes off, the area is gone, or the parent is gone.
|
|
177642
|
+
async reconcileAreaSwitches() {
|
|
177643
|
+
const parts = this.root.parts.map((p) => p);
|
|
177644
|
+
const switches = parts.filter(
|
|
177645
|
+
(p) => p instanceof VacuumAreaSwitchEndpoint
|
|
177646
|
+
);
|
|
177647
|
+
const vacuumById = /* @__PURE__ */ new Map();
|
|
177648
|
+
for (const part of parts) {
|
|
177649
|
+
if (part instanceof VacuumAreaSwitchEndpoint) continue;
|
|
177650
|
+
vacuumById.set(part.id, part);
|
|
177651
|
+
}
|
|
177652
|
+
const isolated = new Set(
|
|
177653
|
+
EntityIsolationService.getIsolatedEntities(this.bridgeId).map(
|
|
177654
|
+
(f) => f.entityId
|
|
177655
|
+
)
|
|
177656
|
+
);
|
|
177657
|
+
const parentIsolated = (parent) => isolated.has(parent.id) || parent.entityId != null && isolated.has(parent.entityId);
|
|
177658
|
+
const kept = /* @__PURE__ */ new Set();
|
|
177659
|
+
for (const sw of switches) {
|
|
177660
|
+
const parent = vacuumById.get(sw.vacuumEndpointId);
|
|
177661
|
+
const mapping = parent ? this.getEntityMapping(parent.entityId) : void 0;
|
|
177662
|
+
const effective = parent instanceof LegacyEndpoint ? parent.vacuumEffective : void 0;
|
|
177663
|
+
let keep = false;
|
|
177664
|
+
let rebuild = false;
|
|
177665
|
+
if (parent && !parentIsolated(parent) && mapping?.vacuumRoomSwitches && effective) {
|
|
177666
|
+
const areas = getVacuumServiceAreas(
|
|
177667
|
+
effective.state.attributes,
|
|
177668
|
+
effective.mapping
|
|
177669
|
+
);
|
|
177670
|
+
if (areas.some((a) => a.areaId === sw.areaId)) {
|
|
177671
|
+
if (sw.parentEffective === effective) {
|
|
177672
|
+
keep = true;
|
|
177673
|
+
} else {
|
|
177674
|
+
rebuild = true;
|
|
177675
|
+
}
|
|
177676
|
+
}
|
|
177677
|
+
}
|
|
177678
|
+
if (keep) {
|
|
177679
|
+
kept.add(sw.id);
|
|
177680
|
+
continue;
|
|
177681
|
+
}
|
|
177682
|
+
try {
|
|
177683
|
+
if (rebuild) {
|
|
177684
|
+
await sw.close();
|
|
177685
|
+
} else {
|
|
177686
|
+
await sw.delete();
|
|
177687
|
+
}
|
|
177688
|
+
} catch (e) {
|
|
177689
|
+
this.log.warn(`Failed to remove area switch ${sw.id}:`, e);
|
|
177690
|
+
}
|
|
177691
|
+
}
|
|
177692
|
+
for (const part of vacuumById.values()) {
|
|
177693
|
+
const entityId = part.entityId;
|
|
177694
|
+
if (!entityId?.startsWith("vacuum.")) continue;
|
|
177695
|
+
if (parentIsolated(part)) continue;
|
|
177696
|
+
const mapping = this.getEntityMapping(entityId);
|
|
177697
|
+
if (!mapping?.vacuumRoomSwitches) continue;
|
|
177698
|
+
const effective = part instanceof LegacyEndpoint ? part.vacuumEffective : void 0;
|
|
177699
|
+
if (!effective) continue;
|
|
177700
|
+
const areas = getVacuumServiceAreas(
|
|
177701
|
+
effective.state.attributes,
|
|
177702
|
+
effective.mapping
|
|
177703
|
+
);
|
|
177704
|
+
const entity = {
|
|
177705
|
+
entity_id: entityId,
|
|
177706
|
+
state: effective.state,
|
|
177707
|
+
registry: this.registry.entity(entityId),
|
|
177708
|
+
deviceRegistry: this.registry.deviceOf(entityId)
|
|
177709
|
+
};
|
|
177710
|
+
for (const area of areas) {
|
|
177711
|
+
const switchId = `${part.id}_roomsw_${area.areaId}`;
|
|
177712
|
+
if (kept.has(switchId)) continue;
|
|
177713
|
+
const holder = vacuumById.get(switchId);
|
|
177714
|
+
if (holder) {
|
|
177715
|
+
this.log.warn(
|
|
177716
|
+
`Skipping area switch ${switchId} for ${entityId}: id taken by entity ${holder.entityId}`
|
|
177717
|
+
);
|
|
177718
|
+
continue;
|
|
177719
|
+
}
|
|
177720
|
+
try {
|
|
177721
|
+
const endpoint = VacuumAreaSwitchEndpoint.create({
|
|
177722
|
+
vacuumEndpointId: part.id,
|
|
177723
|
+
entity,
|
|
177724
|
+
mapping: effective.mapping,
|
|
177725
|
+
area,
|
|
177726
|
+
parentEffective: effective
|
|
177727
|
+
});
|
|
177728
|
+
await this.root.add(endpoint);
|
|
177729
|
+
} catch (e) {
|
|
177730
|
+
this.log.warn(
|
|
177731
|
+
`Failed to add area switch ${switchId} for ${entityId}:`,
|
|
177732
|
+
e
|
|
177733
|
+
);
|
|
177734
|
+
}
|
|
177735
|
+
}
|
|
177736
|
+
}
|
|
177737
|
+
}
|
|
177176
177738
|
updateInFlight;
|
|
177177
177739
|
pendingStates;
|
|
177178
177740
|
pendingChanged;
|
|
@@ -180453,9 +181015,9 @@ function startCommand(webDist) {
|
|
|
180453
181015
|
}
|
|
180454
181016
|
|
|
180455
181017
|
// src/cli.ts
|
|
180456
|
-
var
|
|
181018
|
+
var dirname8 = import.meta.dirname ?? url.fileURLToPath(new URL(".", import.meta.url));
|
|
180457
181019
|
async function cli(argv) {
|
|
180458
|
-
const webDist = process.env.NODE_ENV === "development" ? void 0 : path13.join(
|
|
181020
|
+
const webDist = process.env.NODE_ENV === "development" ? void 0 : path13.join(dirname8, "../frontend");
|
|
180459
181021
|
const cli2 = yargs(hideBin(argv));
|
|
180460
181022
|
cli2.scriptName("home-assistant-matter-hub").version().strict().recommendCommands().detectLocale(false).help().command(startCommand(webDist)).demandCommand().wrap(Math.min(140, cli2.terminalWidth())).parse();
|
|
180461
181023
|
}
|