@riddix/hamh 2.0.50 → 2.0.52
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/README.md +19 -4
- package/dist/backend/cli.js +1987 -627
- package/dist/frontend/assets/{index-3J_LQhv7.js → index-CujG_y56.js} +142 -142
- package/dist/frontend/index.html +1 -1
- package/package.json +5 -4
package/dist/backend/cli.js
CHANGED
|
@@ -1767,8 +1767,8 @@ var init_Cancelable = __esm({
|
|
|
1767
1767
|
};
|
|
1768
1768
|
return result;
|
|
1769
1769
|
}
|
|
1770
|
-
static set logger(
|
|
1771
|
-
this.#logger =
|
|
1770
|
+
static set logger(logger251) {
|
|
1771
|
+
this.#logger = logger251;
|
|
1772
1772
|
}
|
|
1773
1773
|
static get logger() {
|
|
1774
1774
|
return this.#logger;
|
|
@@ -3475,13 +3475,13 @@ var init_Logger = __esm({
|
|
|
3475
3475
|
*
|
|
3476
3476
|
* @deprecated use {@link destinations}
|
|
3477
3477
|
*/
|
|
3478
|
-
static addLogger(identifier,
|
|
3478
|
+
static addLogger(identifier, logger251, options) {
|
|
3479
3479
|
if (identifier in this.destinations) {
|
|
3480
3480
|
throw new ImplementationError(`Logger "${identifier}" already exists`);
|
|
3481
3481
|
}
|
|
3482
3482
|
const dest = LogDestination({ name: identifier });
|
|
3483
3483
|
const legacy = adaptDestinationToLegacy(dest);
|
|
3484
|
-
legacy.log =
|
|
3484
|
+
legacy.log = logger251;
|
|
3485
3485
|
if (options?.defaultLogLevel !== void 0) {
|
|
3486
3486
|
legacy.defaultLogLevel = options.defaultLogLevel;
|
|
3487
3487
|
}
|
|
@@ -5687,8 +5687,8 @@ var init_Construction = __esm({
|
|
|
5687
5687
|
}
|
|
5688
5688
|
}
|
|
5689
5689
|
#unhandledError(...args) {
|
|
5690
|
-
const
|
|
5691
|
-
|
|
5690
|
+
const logger251 = Logger.get(this.#subject.constructor.name);
|
|
5691
|
+
logger251.error(...args);
|
|
5692
5692
|
}
|
|
5693
5693
|
#createErrorHandler(name) {
|
|
5694
5694
|
return (e) => {
|
|
@@ -11712,18 +11712,18 @@ var init_FileHandleTracker = __esm({
|
|
|
11712
11712
|
logger12 = Logger.get("FileHandleTracker");
|
|
11713
11713
|
((FileHandleTracker2) => {
|
|
11714
11714
|
const openHandles = /* @__PURE__ */ new Set();
|
|
11715
|
-
const
|
|
11715
|
+
const registry3 = new FinalizationRegistry((info) => {
|
|
11716
11716
|
logger12.warn(`File handle GC'd without close: "${info.purpose}" for ${info.path}`);
|
|
11717
11717
|
});
|
|
11718
11718
|
function register(handle) {
|
|
11719
11719
|
const info = { purpose: handle.purpose, path: handle.path };
|
|
11720
|
-
|
|
11720
|
+
registry3.register(handle, info, handle);
|
|
11721
11721
|
const ref = new WeakRef(handle);
|
|
11722
11722
|
openHandles.add(ref);
|
|
11723
11723
|
}
|
|
11724
11724
|
FileHandleTracker2.register = register;
|
|
11725
11725
|
function unregister(handle) {
|
|
11726
|
-
|
|
11726
|
+
registry3.unregister(handle);
|
|
11727
11727
|
for (const ref of openHandles) {
|
|
11728
11728
|
if (ref.deref() === handle) {
|
|
11729
11729
|
openHandles.delete(ref);
|
|
@@ -64070,8 +64070,8 @@ var init_NodeSession = __esm({
|
|
|
64070
64070
|
return session?.type === SessionType.Unicast;
|
|
64071
64071
|
}
|
|
64072
64072
|
NodeSession2.is = is;
|
|
64073
|
-
function logNew(
|
|
64074
|
-
|
|
64073
|
+
function logNew(logger251, operation, session, messenger, fabric, peerNodeId) {
|
|
64074
|
+
logger251.info(
|
|
64075
64075
|
session.via,
|
|
64076
64076
|
`${operation} session with`,
|
|
64077
64077
|
Diagnostic.strong(PeerAddress({ fabricIndex: fabric.fabricIndex, nodeId: peerNodeId }).toString()),
|
|
@@ -117714,11 +117714,11 @@ var init_Api = __esm({
|
|
|
117714
117714
|
}
|
|
117715
117715
|
Api2.resourceFor = resourceFor;
|
|
117716
117716
|
function log(level, facility, id, ...message) {
|
|
117717
|
-
let
|
|
117718
|
-
if (!
|
|
117719
|
-
loggers.set(facility,
|
|
117717
|
+
let logger251 = loggers.get(facility);
|
|
117718
|
+
if (!logger251) {
|
|
117719
|
+
loggers.set(facility, logger251 = Logger.get(facility));
|
|
117720
117720
|
}
|
|
117721
|
-
|
|
117721
|
+
logger251[level](Diagnostic.via(id || "(anon)"), message);
|
|
117722
117722
|
}
|
|
117723
117723
|
Api2.log = log;
|
|
117724
117724
|
function logRequest(facility, id, method, target) {
|
|
@@ -131221,6 +131221,13 @@ var init_controller_compat = __esm({
|
|
|
131221
131221
|
aqara: "yes",
|
|
131222
131222
|
note: "Aqara lists battery storage; others show battery inside a device."
|
|
131223
131223
|
},
|
|
131224
|
+
1292: {
|
|
131225
|
+
apple: "no",
|
|
131226
|
+
google: "no",
|
|
131227
|
+
alexa: "no",
|
|
131228
|
+
aqara: "yes",
|
|
131229
|
+
note: "HA and Aqara render EnergyEvse; SmartThings announced support, unconfirmed. Bridged EVSE can break Alexa device recognition, keep it off Alexa bridges."
|
|
131230
|
+
},
|
|
131224
131231
|
39: {
|
|
131225
131232
|
apple: "no",
|
|
131226
131233
|
google: "no",
|
|
@@ -132058,6 +132065,12 @@ var init_bridge_config_schema = __esm({
|
|
|
132058
132065
|
description: "Anchor each device to its Home Assistant entity registry id instead of the entity id, so renaming an entity no longer re-adds it in your controller (Alexa, Google Home, Apple Home) and keeps groups and automations. Records are seeded from the start, so enabling this later is safe and never re-adds existing devices. Default off.",
|
|
132059
132066
|
type: "boolean",
|
|
132060
132067
|
default: false
|
|
132068
|
+
},
|
|
132069
|
+
wedgeWatchdog: {
|
|
132070
|
+
title: "Wedge Watchdog (Apple 'Updating' workaround)",
|
|
132071
|
+
description: "Rotate the one session that looks wedged, subscriptions still alive but no inbound request from the controller for about 45 minutes, earlier than the blind session rotation. Targets Apple Home tiles stuck on 'Updating' where the controller keeps acking but stops consuming data. A false positive only triggers a transparent reconnect, the same as normal rotation. Default off.",
|
|
132072
|
+
type: "boolean",
|
|
132073
|
+
default: false
|
|
132061
132074
|
}
|
|
132062
132075
|
}
|
|
132063
132076
|
};
|
|
@@ -132618,10 +132631,10 @@ var init_home_assistant_actions = __esm({
|
|
|
132618
132631
|
circuitBreakerResetMs: 3e4
|
|
132619
132632
|
};
|
|
132620
132633
|
HomeAssistantActions = class extends Service {
|
|
132621
|
-
constructor(
|
|
132634
|
+
constructor(logger251, client, config11) {
|
|
132622
132635
|
super("HomeAssistantActions");
|
|
132623
132636
|
this.client = client;
|
|
132624
|
-
this.log =
|
|
132637
|
+
this.log = logger251.get(this);
|
|
132625
132638
|
this.config = { ...defaultConfig, ...config11 };
|
|
132626
132639
|
this.circuitBreaker = new CircuitBreaker(
|
|
132627
132640
|
this.config.circuitBreakerThreshold,
|
|
@@ -132967,10 +132980,10 @@ var DiagnosticService = class {
|
|
|
132967
132980
|
};
|
|
132968
132981
|
|
|
132969
132982
|
// src/api/access-log.ts
|
|
132970
|
-
function accessLogger(
|
|
132983
|
+
function accessLogger(logger251) {
|
|
132971
132984
|
return (req, res, next) => {
|
|
132972
132985
|
res.on("finish", () => {
|
|
132973
|
-
|
|
132986
|
+
logger251.debug(
|
|
132974
132987
|
`${req.method} ${decodeURI(req.originalUrl)} ${res.statusCode} ${res.statusMessage} from ${req.socket.remoteAddress}`
|
|
132975
132988
|
);
|
|
132976
132989
|
});
|
|
@@ -133155,6 +133168,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133155
133168
|
currentEntity: config11.currentEntity,
|
|
133156
133169
|
batteryPowerEntity: config11.batteryPowerEntity,
|
|
133157
133170
|
batteryEnergyEntity: config11.batteryEnergyEntity,
|
|
133171
|
+
chargingSwitchEntity: config11.chargingSwitchEntity,
|
|
133172
|
+
currentLimitEntity: config11.currentLimitEntity,
|
|
133158
133173
|
suctionLevelEntity: config11.suctionLevelEntity,
|
|
133159
133174
|
mopIntensityEntity: config11.mopIntensityEntity,
|
|
133160
133175
|
valetudoIdentifier: config11.valetudoIdentifier,
|
|
@@ -133316,6 +133331,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133316
133331
|
currentEntity: config11.currentEntity,
|
|
133317
133332
|
batteryPowerEntity: config11.batteryPowerEntity,
|
|
133318
133333
|
batteryEnergyEntity: config11.batteryEnergyEntity,
|
|
133334
|
+
chargingSwitchEntity: config11.chargingSwitchEntity,
|
|
133335
|
+
currentLimitEntity: config11.currentLimitEntity,
|
|
133319
133336
|
suctionLevelEntity: config11.suctionLevelEntity,
|
|
133320
133337
|
mopIntensityEntity: config11.mopIntensityEntity,
|
|
133321
133338
|
temperatureEntity: config11.temperatureEntity,
|
|
@@ -134168,6 +134185,8 @@ function entityMappingApi(mappingStorage, identityStorage) {
|
|
|
134168
134185
|
currentEntity: body.currentEntity,
|
|
134169
134186
|
batteryPowerEntity: body.batteryPowerEntity,
|
|
134170
134187
|
batteryEnergyEntity: body.batteryEnergyEntity,
|
|
134188
|
+
chargingSwitchEntity: body.chargingSwitchEntity,
|
|
134189
|
+
currentLimitEntity: body.currentLimitEntity,
|
|
134171
134190
|
suctionLevelEntity: body.suctionLevelEntity,
|
|
134172
134191
|
mopIntensityEntity: body.mopIntensityEntity,
|
|
134173
134192
|
customServiceAreas: body.customServiceAreas,
|
|
@@ -134669,6 +134688,8 @@ function configToProfileEntry(config11) {
|
|
|
134669
134688
|
currentEntity: config11.currentEntity,
|
|
134670
134689
|
batteryPowerEntity: config11.batteryPowerEntity,
|
|
134671
134690
|
batteryEnergyEntity: config11.batteryEnergyEntity,
|
|
134691
|
+
chargingSwitchEntity: config11.chargingSwitchEntity,
|
|
134692
|
+
currentLimitEntity: config11.currentLimitEntity,
|
|
134672
134693
|
suctionLevelEntity: config11.suctionLevelEntity,
|
|
134673
134694
|
mopIntensityEntity: config11.mopIntensityEntity,
|
|
134674
134695
|
customServiceAreas: config11.customServiceAreas,
|
|
@@ -134808,6 +134829,8 @@ function mappingProfileApi(mappingStorage) {
|
|
|
134808
134829
|
currentEntity: entry.currentEntity,
|
|
134809
134830
|
batteryPowerEntity: entry.batteryPowerEntity,
|
|
134810
134831
|
batteryEnergyEntity: entry.batteryEnergyEntity,
|
|
134832
|
+
chargingSwitchEntity: entry.chargingSwitchEntity,
|
|
134833
|
+
currentLimitEntity: entry.currentLimitEntity,
|
|
134811
134834
|
suctionLevelEntity: entry.suctionLevelEntity,
|
|
134812
134835
|
mopIntensityEntity: entry.mopIntensityEntity,
|
|
134813
134836
|
customServiceAreas: entry.customServiceAreas,
|
|
@@ -135034,6 +135057,300 @@ function resolveLabelValue(value, labels) {
|
|
|
135034
135057
|
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
135035
135058
|
}
|
|
135036
135059
|
|
|
135060
|
+
// src/matter/endpoints/entity-endpoint.ts
|
|
135061
|
+
init_esm7();
|
|
135062
|
+
var EntityEndpoint = class extends Endpoint {
|
|
135063
|
+
constructor(type, entityId, customName, mappedEntityIds, endpointId) {
|
|
135064
|
+
super(type, { id: endpointId ?? createEndpointId(entityId, customName) });
|
|
135065
|
+
this.entityId = entityId;
|
|
135066
|
+
this.mappedEntityIds = mappedEntityIds ?? [];
|
|
135067
|
+
}
|
|
135068
|
+
entityId;
|
|
135069
|
+
mappedEntityIds;
|
|
135070
|
+
lastMappedStates = {};
|
|
135071
|
+
hasMappedEntityChanged(states) {
|
|
135072
|
+
let changed = false;
|
|
135073
|
+
for (const mappedId of this.mappedEntityIds) {
|
|
135074
|
+
const mappedState = states[mappedId];
|
|
135075
|
+
if (!mappedState) continue;
|
|
135076
|
+
const fp = mappedState.state;
|
|
135077
|
+
if (fp !== this.lastMappedStates[mappedId]) {
|
|
135078
|
+
this.lastMappedStates[mappedId] = fp;
|
|
135079
|
+
changed = true;
|
|
135080
|
+
}
|
|
135081
|
+
}
|
|
135082
|
+
return changed;
|
|
135083
|
+
}
|
|
135084
|
+
};
|
|
135085
|
+
function createEndpointId(entityId, customName) {
|
|
135086
|
+
const baseName = customName || entityId;
|
|
135087
|
+
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
135088
|
+
}
|
|
135089
|
+
function getMappedEntityIds(mapping) {
|
|
135090
|
+
if (!mapping) return [];
|
|
135091
|
+
const ids = [];
|
|
135092
|
+
if (mapping.batteryEntity && !mapping.disableBatteryMapping) {
|
|
135093
|
+
ids.push(mapping.batteryEntity);
|
|
135094
|
+
}
|
|
135095
|
+
if (mapping.faultEntity) ids.push(mapping.faultEntity);
|
|
135096
|
+
if (mapping.chargingStateEntity) ids.push(mapping.chargingStateEntity);
|
|
135097
|
+
if (mapping.temperatureEntity) ids.push(mapping.temperatureEntity);
|
|
135098
|
+
if (mapping.humidityEntity) ids.push(mapping.humidityEntity);
|
|
135099
|
+
if (mapping.pressureEntity) ids.push(mapping.pressureEntity);
|
|
135100
|
+
if (mapping.cleaningModeEntity) ids.push(mapping.cleaningModeEntity);
|
|
135101
|
+
if (mapping.suctionLevelEntity) ids.push(mapping.suctionLevelEntity);
|
|
135102
|
+
if (mapping.mopIntensityEntity) ids.push(mapping.mopIntensityEntity);
|
|
135103
|
+
if (mapping.filterLifeEntity) ids.push(mapping.filterLifeEntity);
|
|
135104
|
+
if (mapping.powerEntity) ids.push(mapping.powerEntity);
|
|
135105
|
+
if (mapping.energyEntity) ids.push(mapping.energyEntity);
|
|
135106
|
+
if (mapping.voltageEntity) ids.push(mapping.voltageEntity);
|
|
135107
|
+
if (mapping.currentEntity) ids.push(mapping.currentEntity);
|
|
135108
|
+
if (mapping.batteryPowerEntity) ids.push(mapping.batteryPowerEntity);
|
|
135109
|
+
if (mapping.batteryEnergyEntity) ids.push(mapping.batteryEnergyEntity);
|
|
135110
|
+
if (mapping.chargingSwitchEntity) ids.push(mapping.chargingSwitchEntity);
|
|
135111
|
+
if (mapping.currentLimitEntity) ids.push(mapping.currentLimitEntity);
|
|
135112
|
+
if (mapping.currentRoomEntity) ids.push(mapping.currentRoomEntity);
|
|
135113
|
+
if (mapping.cleanedAreaEntity) ids.push(mapping.cleanedAreaEntity);
|
|
135114
|
+
if (mapping.composedEntities) {
|
|
135115
|
+
for (const sub of mapping.composedEntities) {
|
|
135116
|
+
if (sub.entityId) ids.push(sub.entityId);
|
|
135117
|
+
}
|
|
135118
|
+
}
|
|
135119
|
+
return ids;
|
|
135120
|
+
}
|
|
135121
|
+
|
|
135122
|
+
// src/services/bridges/identity-resolver.ts
|
|
135123
|
+
var SEP = "\0";
|
|
135124
|
+
function identityKey(entity) {
|
|
135125
|
+
const uniqueId = entity.registry?.unique_id;
|
|
135126
|
+
const platform = entity.registry?.platform;
|
|
135127
|
+
if (!uniqueId || !platform) {
|
|
135128
|
+
return null;
|
|
135129
|
+
}
|
|
135130
|
+
const domain = entity.entity_id.split(".")[0];
|
|
135131
|
+
return platform + SEP + domain + SEP + uniqueId;
|
|
135132
|
+
}
|
|
135133
|
+
var IdentityResolver = class {
|
|
135134
|
+
constructor(identityStorage, mappingStorage) {
|
|
135135
|
+
this.identityStorage = identityStorage;
|
|
135136
|
+
this.mappingStorage = mappingStorage;
|
|
135137
|
+
}
|
|
135138
|
+
identityStorage;
|
|
135139
|
+
mappingStorage;
|
|
135140
|
+
async resolveIdentity(bridgeId, entity, mapping, opts) {
|
|
135141
|
+
const entityId = entity.entity_id;
|
|
135142
|
+
const key = identityKey(entity);
|
|
135143
|
+
if (key == null) {
|
|
135144
|
+
return {
|
|
135145
|
+
endpointId: createEndpointId(entityId, mapping?.customName),
|
|
135146
|
+
anchorEntityId: entityId,
|
|
135147
|
+
protected: false
|
|
135148
|
+
};
|
|
135149
|
+
}
|
|
135150
|
+
const existing = this.identityStorage.getIdentity(bridgeId, key);
|
|
135151
|
+
if (existing) {
|
|
135152
|
+
const renamedFrom = existing.lastEntityId !== entityId ? existing.lastEntityId ?? existing.anchorEntityId : void 0;
|
|
135153
|
+
if (opts.stableIdentity) {
|
|
135154
|
+
if (renamedFrom) {
|
|
135155
|
+
this.identityStorage.setIdentity(bridgeId, key, {
|
|
135156
|
+
...existing,
|
|
135157
|
+
lastEntityId: entityId
|
|
135158
|
+
});
|
|
135159
|
+
await this.rekeyMapping(bridgeId, renamedFrom, entityId);
|
|
135160
|
+
}
|
|
135161
|
+
return {
|
|
135162
|
+
endpointId: existing.endpointId,
|
|
135163
|
+
anchorEntityId: existing.anchorEntityId,
|
|
135164
|
+
protected: true,
|
|
135165
|
+
renamedFrom
|
|
135166
|
+
};
|
|
135167
|
+
}
|
|
135168
|
+
const liveEndpointId = createEndpointId(entityId, mapping?.customName);
|
|
135169
|
+
if (existing.endpointId !== liveEndpointId || existing.anchorEntityId !== entityId || existing.lastEntityId !== entityId) {
|
|
135170
|
+
this.identityStorage.setIdentity(bridgeId, key, {
|
|
135171
|
+
...existing,
|
|
135172
|
+
endpointId: liveEndpointId,
|
|
135173
|
+
anchorEntityId: entityId,
|
|
135174
|
+
lastEntityId: entityId
|
|
135175
|
+
});
|
|
135176
|
+
}
|
|
135177
|
+
return {
|
|
135178
|
+
endpointId: liveEndpointId,
|
|
135179
|
+
anchorEntityId: entityId,
|
|
135180
|
+
protected: false,
|
|
135181
|
+
renamedFrom
|
|
135182
|
+
};
|
|
135183
|
+
}
|
|
135184
|
+
const desired = createEndpointId(entityId, mapping?.customName);
|
|
135185
|
+
let endpointId = desired;
|
|
135186
|
+
if (opts.stableIdentity) {
|
|
135187
|
+
let n = 2;
|
|
135188
|
+
while (opts.isEndpointIdTaken(endpointId, key)) {
|
|
135189
|
+
endpointId = `${desired}_${n++}`;
|
|
135190
|
+
}
|
|
135191
|
+
}
|
|
135192
|
+
const record = {
|
|
135193
|
+
endpointId,
|
|
135194
|
+
anchorEntityId: entityId,
|
|
135195
|
+
lastEntityId: entityId,
|
|
135196
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
135197
|
+
};
|
|
135198
|
+
this.identityStorage.setIdentity(bridgeId, key, record);
|
|
135199
|
+
if (opts.stableIdentity) {
|
|
135200
|
+
return {
|
|
135201
|
+
endpointId: record.endpointId,
|
|
135202
|
+
anchorEntityId: record.anchorEntityId,
|
|
135203
|
+
protected: true
|
|
135204
|
+
};
|
|
135205
|
+
}
|
|
135206
|
+
return {
|
|
135207
|
+
endpointId: desired,
|
|
135208
|
+
anchorEntityId: entityId,
|
|
135209
|
+
protected: false
|
|
135210
|
+
};
|
|
135211
|
+
}
|
|
135212
|
+
// Carry a mapping from the old entity_id to the new one on rename. anchorEntityId
|
|
135213
|
+
// never changes, only the mapping key. Skips when no old mapping exists or a new
|
|
135214
|
+
// one already does, so a manual mapping under the new id is never clobbered.
|
|
135215
|
+
async rekeyMapping(bridgeId, oldEntityId, newEntityId) {
|
|
135216
|
+
if (oldEntityId === newEntityId) {
|
|
135217
|
+
return;
|
|
135218
|
+
}
|
|
135219
|
+
const old = this.mappingStorage.getMapping(bridgeId, oldEntityId);
|
|
135220
|
+
if (!old) {
|
|
135221
|
+
return;
|
|
135222
|
+
}
|
|
135223
|
+
if (this.mappingStorage.getMapping(bridgeId, newEntityId)) {
|
|
135224
|
+
return;
|
|
135225
|
+
}
|
|
135226
|
+
await this.mappingStorage.setMapping({
|
|
135227
|
+
...old,
|
|
135228
|
+
bridgeId,
|
|
135229
|
+
entityId: newEntityId
|
|
135230
|
+
});
|
|
135231
|
+
await this.mappingStorage.deleteMapping(bridgeId, oldEntityId);
|
|
135232
|
+
}
|
|
135233
|
+
};
|
|
135234
|
+
|
|
135235
|
+
// src/services/storage/orphan-cleanup.ts
|
|
135236
|
+
var ORPHAN_TOMBSTONE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
135237
|
+
function buildPresentIdentityKeys(entities) {
|
|
135238
|
+
const keys3 = /* @__PURE__ */ new Set();
|
|
135239
|
+
for (const entity of Object.values(entities ?? {})) {
|
|
135240
|
+
if (!entity?.entity_id) continue;
|
|
135241
|
+
const key = identityKey({ entity_id: entity.entity_id, registry: entity });
|
|
135242
|
+
if (key != null) keys3.add(key);
|
|
135243
|
+
}
|
|
135244
|
+
return keys3;
|
|
135245
|
+
}
|
|
135246
|
+
function buildPresentEntityIds(entities) {
|
|
135247
|
+
const ids = /* @__PURE__ */ new Set();
|
|
135248
|
+
for (const entity of Object.values(entities ?? {})) {
|
|
135249
|
+
if (entity?.entity_id) ids.add(entity.entity_id);
|
|
135250
|
+
}
|
|
135251
|
+
return ids;
|
|
135252
|
+
}
|
|
135253
|
+
function stampIdentityPresence(store, bridgeId, presentKeys, now = Date.now()) {
|
|
135254
|
+
const nowIso = new Date(now).toISOString();
|
|
135255
|
+
for (const key of [...store.getBridgeIdentities(bridgeId).keys()]) {
|
|
135256
|
+
if (presentKeys.has(key)) {
|
|
135257
|
+
store.clearIdentityMissing(bridgeId, key);
|
|
135258
|
+
} else {
|
|
135259
|
+
store.markIdentityMissing(bridgeId, key, nowIso);
|
|
135260
|
+
}
|
|
135261
|
+
}
|
|
135262
|
+
}
|
|
135263
|
+
function stampMappingPresence(store, bridgeId, presentEntityIds, now = Date.now()) {
|
|
135264
|
+
const nowIso = new Date(now).toISOString();
|
|
135265
|
+
const entityIds = store.getMappingsForBridge(bridgeId).map((m) => m.entityId);
|
|
135266
|
+
for (const entityId of entityIds) {
|
|
135267
|
+
if (presentEntityIds.has(entityId)) {
|
|
135268
|
+
store.clearMappingMissing(bridgeId, entityId);
|
|
135269
|
+
} else {
|
|
135270
|
+
store.markMappingMissing(bridgeId, entityId, nowIso);
|
|
135271
|
+
}
|
|
135272
|
+
}
|
|
135273
|
+
}
|
|
135274
|
+
function computeOrphanCandidates(input) {
|
|
135275
|
+
const now = input.now ?? Date.now();
|
|
135276
|
+
const candidates = [];
|
|
135277
|
+
for (const [key, record] of input.identities) {
|
|
135278
|
+
if (!record.missingSince) continue;
|
|
135279
|
+
const missingFor = now - Date.parse(record.missingSince);
|
|
135280
|
+
if (!(missingFor >= ORPHAN_TOMBSTONE_MS)) continue;
|
|
135281
|
+
if (input.presentKeys.has(key)) continue;
|
|
135282
|
+
const lastEntityId = record.lastEntityId ?? record.anchorEntityId;
|
|
135283
|
+
candidates.push({
|
|
135284
|
+
identityKey: key,
|
|
135285
|
+
lastEntityId,
|
|
135286
|
+
missingSince: record.missingSince,
|
|
135287
|
+
// Only a true orphan mapping, not one a live entity holds under a reused
|
|
135288
|
+
// entity_id (a different identity key). Guards the sweep from clobbering
|
|
135289
|
+
// that live entity's custom mapping.
|
|
135290
|
+
hasMapping: input.hasMapping(lastEntityId) && !input.presentEntityIds.has(lastEntityId),
|
|
135291
|
+
kind: "identity"
|
|
135292
|
+
});
|
|
135293
|
+
}
|
|
135294
|
+
if (input.mappings) {
|
|
135295
|
+
const linkedByIdentity = /* @__PURE__ */ new Set();
|
|
135296
|
+
for (const candidate of candidates) {
|
|
135297
|
+
if (candidate.hasMapping) linkedByIdentity.add(candidate.lastEntityId);
|
|
135298
|
+
}
|
|
135299
|
+
for (const mapping of input.mappings) {
|
|
135300
|
+
if (!mapping.missingSince) continue;
|
|
135301
|
+
const missingFor = now - Date.parse(mapping.missingSince);
|
|
135302
|
+
if (!(missingFor >= ORPHAN_TOMBSTONE_MS)) continue;
|
|
135303
|
+
if (input.presentEntityIds.has(mapping.entityId)) continue;
|
|
135304
|
+
if (linkedByIdentity.has(mapping.entityId)) continue;
|
|
135305
|
+
candidates.push({
|
|
135306
|
+
identityKey: mapping.entityId,
|
|
135307
|
+
lastEntityId: mapping.entityId,
|
|
135308
|
+
missingSince: mapping.missingSince,
|
|
135309
|
+
hasMapping: true,
|
|
135310
|
+
kind: "mapping"
|
|
135311
|
+
});
|
|
135312
|
+
}
|
|
135313
|
+
}
|
|
135314
|
+
return candidates;
|
|
135315
|
+
}
|
|
135316
|
+
async function executeOrphanCleanup(input) {
|
|
135317
|
+
const candidates = computeOrphanCandidates({
|
|
135318
|
+
bridgeId: input.bridgeId,
|
|
135319
|
+
identities: input.identities,
|
|
135320
|
+
presentKeys: input.presentKeys,
|
|
135321
|
+
presentEntityIds: input.presentEntityIds,
|
|
135322
|
+
hasMapping: (entityId) => input.getMapping(input.bridgeId, entityId) != null,
|
|
135323
|
+
mappings: input.mappings,
|
|
135324
|
+
now: input.now
|
|
135325
|
+
});
|
|
135326
|
+
const byKey = new Map(candidates.map((c) => [c.identityKey, c]));
|
|
135327
|
+
const results = [];
|
|
135328
|
+
for (const key of input.requestedKeys) {
|
|
135329
|
+
const candidate = byKey.get(key);
|
|
135330
|
+
if (!candidate) {
|
|
135331
|
+
results.push({
|
|
135332
|
+
identityKey: key,
|
|
135333
|
+
deleted: false,
|
|
135334
|
+
reason: "not a current orphan candidate"
|
|
135335
|
+
});
|
|
135336
|
+
continue;
|
|
135337
|
+
}
|
|
135338
|
+
if (candidate.kind === "mapping") {
|
|
135339
|
+
if (!input.presentEntityIds.has(candidate.lastEntityId)) {
|
|
135340
|
+
await input.deleteMapping(input.bridgeId, candidate.lastEntityId);
|
|
135341
|
+
}
|
|
135342
|
+
results.push({ identityKey: key, deleted: true });
|
|
135343
|
+
continue;
|
|
135344
|
+
}
|
|
135345
|
+
if (candidate.hasMapping && !input.presentEntityIds.has(candidate.lastEntityId)) {
|
|
135346
|
+
await input.deleteMapping(input.bridgeId, candidate.lastEntityId);
|
|
135347
|
+
}
|
|
135348
|
+
await input.deleteIdentity(input.bridgeId, key);
|
|
135349
|
+
results.push({ identityKey: key, deleted: true });
|
|
135350
|
+
}
|
|
135351
|
+
return results;
|
|
135352
|
+
}
|
|
135353
|
+
|
|
135037
135354
|
// src/utils/json/endpoint-to-json.ts
|
|
135038
135355
|
function safeClone(value, depth = 0) {
|
|
135039
135356
|
if (depth > 20 || value === null || value === void 0) return value;
|
|
@@ -135079,7 +135396,7 @@ function endpointToJson(endpoint, parentId) {
|
|
|
135079
135396
|
|
|
135080
135397
|
// src/api/matter-api.ts
|
|
135081
135398
|
var ajv = new Ajv();
|
|
135082
|
-
function matterApi(bridgeService, haRegistry, identityStorage) {
|
|
135399
|
+
function matterApi(bridgeService, haRegistry, identityStorage, mappingStorage) {
|
|
135083
135400
|
const router = express11.Router();
|
|
135084
135401
|
router.get("/", (_, res) => {
|
|
135085
135402
|
res.status(200).json({});
|
|
@@ -135182,6 +135499,64 @@ function matterApi(bridgeService, haRegistry, identityStorage) {
|
|
|
135182
135499
|
});
|
|
135183
135500
|
}
|
|
135184
135501
|
});
|
|
135502
|
+
router.get("/bridges/:bridgeId/orphans", async (req, res) => {
|
|
135503
|
+
const bridgeId = req.params.bridgeId;
|
|
135504
|
+
const bridge = bridgeService.bridges.find((b) => b.id === bridgeId);
|
|
135505
|
+
if (!bridge) {
|
|
135506
|
+
res.status(404).send("Not Found");
|
|
135507
|
+
return;
|
|
135508
|
+
}
|
|
135509
|
+
if (!haRegistry || !identityStorage) {
|
|
135510
|
+
res.status(503).json({ error: "Orphan cleanup is not available" });
|
|
135511
|
+
return;
|
|
135512
|
+
}
|
|
135513
|
+
const presentKeys = buildPresentIdentityKeys(haRegistry.entities);
|
|
135514
|
+
const presentEntityIds = buildPresentEntityIds(haRegistry.entities);
|
|
135515
|
+
const candidates = computeOrphanCandidates({
|
|
135516
|
+
bridgeId,
|
|
135517
|
+
identities: identityStorage.getBridgeIdentities(bridgeId),
|
|
135518
|
+
presentKeys,
|
|
135519
|
+
presentEntityIds,
|
|
135520
|
+
hasMapping: (entityId) => mappingStorage?.getMapping(bridgeId, entityId) != null,
|
|
135521
|
+
mappings: mappingStorage?.getMappingsForBridge(bridgeId)
|
|
135522
|
+
});
|
|
135523
|
+
res.status(200).json({ candidates });
|
|
135524
|
+
});
|
|
135525
|
+
router.post(
|
|
135526
|
+
"/bridges/:bridgeId/actions/cleanup-orphans",
|
|
135527
|
+
async (req, res) => {
|
|
135528
|
+
const bridgeId = req.params.bridgeId;
|
|
135529
|
+
const bridge = bridgeService.bridges.find((b) => b.id === bridgeId);
|
|
135530
|
+
if (!bridge) {
|
|
135531
|
+
res.status(404).send("Not Found");
|
|
135532
|
+
return;
|
|
135533
|
+
}
|
|
135534
|
+
if (!haRegistry || !identityStorage || !mappingStorage) {
|
|
135535
|
+
res.status(503).json({ error: "Orphan cleanup is not available" });
|
|
135536
|
+
return;
|
|
135537
|
+
}
|
|
135538
|
+
const body = req.body;
|
|
135539
|
+
const requestedKeys = Array.isArray(body?.identityKeys) ? body.identityKeys.filter((k) => typeof k === "string") : [];
|
|
135540
|
+
try {
|
|
135541
|
+
const results = await executeOrphanCleanup({
|
|
135542
|
+
bridgeId,
|
|
135543
|
+
requestedKeys,
|
|
135544
|
+
identities: identityStorage.getBridgeIdentities(bridgeId),
|
|
135545
|
+
presentKeys: buildPresentIdentityKeys(haRegistry.entities),
|
|
135546
|
+
presentEntityIds: buildPresentEntityIds(haRegistry.entities),
|
|
135547
|
+
getMapping: (b, e) => mappingStorage.getMapping(b, e),
|
|
135548
|
+
deleteMapping: (b, e) => mappingStorage.deleteMapping(b, e),
|
|
135549
|
+
deleteIdentity: (b, k) => identityStorage.deleteIdentity(b, k),
|
|
135550
|
+
mappings: mappingStorage.getMappingsForBridge(bridgeId)
|
|
135551
|
+
});
|
|
135552
|
+
res.status(200).json({ results });
|
|
135553
|
+
} catch (e) {
|
|
135554
|
+
res.status(500).json({
|
|
135555
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
135556
|
+
});
|
|
135557
|
+
}
|
|
135558
|
+
}
|
|
135559
|
+
);
|
|
135185
135560
|
router.get("/bridges/:bridgeId/devices", async (req, res) => {
|
|
135186
135561
|
const bridgeId = req.params.bridgeId;
|
|
135187
135562
|
const bridge = bridgeService.bridges.find((b) => b.id === bridgeId);
|
|
@@ -136242,11 +136617,12 @@ var BLOCKED_PREFIXES = [
|
|
|
136242
136617
|
function pluginApi(bridgeService, storageLocation) {
|
|
136243
136618
|
const router = express14.Router();
|
|
136244
136619
|
const installer = new PluginInstaller(storageLocation);
|
|
136245
|
-
const
|
|
136620
|
+
const registry3 = new PluginRegistry(storageLocation);
|
|
136246
136621
|
router.get("/", (_req, res) => {
|
|
136247
136622
|
const result = [];
|
|
136248
136623
|
for (const bridge of bridgeService.bridges) {
|
|
136249
136624
|
const info = bridge.pluginInfo;
|
|
136625
|
+
if (!info) continue;
|
|
136250
136626
|
const plugins = info.metadata.map((meta) => ({
|
|
136251
136627
|
name: meta.name,
|
|
136252
136628
|
version: meta.version,
|
|
@@ -136269,41 +136645,41 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136269
136645
|
}
|
|
136270
136646
|
res.json(result);
|
|
136271
136647
|
});
|
|
136272
|
-
|
|
136273
|
-
const bridge = bridgeService.get(
|
|
136648
|
+
function pluginBridge(bridgeId, res) {
|
|
136649
|
+
const bridge = bridgeService.get(bridgeId);
|
|
136274
136650
|
if (!bridge) {
|
|
136275
136651
|
res.status(404).json({ error: "Bridge not found" });
|
|
136276
|
-
return;
|
|
136652
|
+
return void 0;
|
|
136653
|
+
}
|
|
136654
|
+
if (typeof bridge.enablePlugin !== "function") {
|
|
136655
|
+
res.status(400).json({ error: "Bridge does not support plugins" });
|
|
136656
|
+
return void 0;
|
|
136277
136657
|
}
|
|
136658
|
+
return bridge;
|
|
136659
|
+
}
|
|
136660
|
+
router.post("/:bridgeId/:pluginName/enable", (req, res) => {
|
|
136661
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136662
|
+
if (!bridge) return;
|
|
136278
136663
|
const { pluginName } = req.params;
|
|
136279
136664
|
bridge.enablePlugin(pluginName);
|
|
136280
136665
|
res.json({ success: true, pluginName, enabled: true });
|
|
136281
136666
|
});
|
|
136282
136667
|
router.post("/:bridgeId/:pluginName/disable", (req, res) => {
|
|
136283
|
-
const bridge =
|
|
136284
|
-
if (!bridge)
|
|
136285
|
-
res.status(404).json({ error: "Bridge not found" });
|
|
136286
|
-
return;
|
|
136287
|
-
}
|
|
136668
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136669
|
+
if (!bridge) return;
|
|
136288
136670
|
const { pluginName } = req.params;
|
|
136289
136671
|
bridge.disablePlugin(pluginName);
|
|
136290
136672
|
res.json({ success: true, pluginName, enabled: false });
|
|
136291
136673
|
});
|
|
136292
136674
|
router.get("/:bridgeId/:pluginName/config-schema", (req, res) => {
|
|
136293
|
-
const bridge =
|
|
136294
|
-
if (!bridge)
|
|
136295
|
-
res.status(404).json({ error: "Bridge not found" });
|
|
136296
|
-
return;
|
|
136297
|
-
}
|
|
136675
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136676
|
+
if (!bridge) return;
|
|
136298
136677
|
const schema6 = bridge.getPluginConfigSchema(req.params.pluginName);
|
|
136299
136678
|
res.json({ pluginName: req.params.pluginName, schema: schema6 ?? null });
|
|
136300
136679
|
});
|
|
136301
136680
|
router.post("/:bridgeId/:pluginName/config", async (req, res) => {
|
|
136302
|
-
const bridge =
|
|
136303
|
-
if (!bridge)
|
|
136304
|
-
res.status(404).json({ error: "Bridge not found" });
|
|
136305
|
-
return;
|
|
136306
|
-
}
|
|
136681
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136682
|
+
if (!bridge) return;
|
|
136307
136683
|
const { config: config11 } = req.body;
|
|
136308
136684
|
if (!config11 || typeof config11 !== "object") {
|
|
136309
136685
|
res.status(400).json({ error: "config object is required" });
|
|
@@ -136317,17 +136693,14 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136317
136693
|
res.json({ success: true, pluginName: req.params.pluginName });
|
|
136318
136694
|
});
|
|
136319
136695
|
router.post("/:bridgeId/:pluginName/reset", (req, res) => {
|
|
136320
|
-
const bridge =
|
|
136321
|
-
if (!bridge)
|
|
136322
|
-
res.status(404).json({ error: "Bridge not found" });
|
|
136323
|
-
return;
|
|
136324
|
-
}
|
|
136696
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136697
|
+
if (!bridge) return;
|
|
136325
136698
|
const { pluginName } = req.params;
|
|
136326
136699
|
bridge.resetPlugin(pluginName);
|
|
136327
136700
|
res.json({ success: true, pluginName, reset: true });
|
|
136328
136701
|
});
|
|
136329
136702
|
router.get("/installed", (_req, res) => {
|
|
136330
|
-
const registered =
|
|
136703
|
+
const registered = registry3.getAll();
|
|
136331
136704
|
const npmInstalled = installer.listInstalled();
|
|
136332
136705
|
const result = registered.map((entry) => {
|
|
136333
136706
|
const npm = npmInstalled.find((p) => p.name === entry.packageName);
|
|
@@ -136357,7 +136730,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136357
136730
|
});
|
|
136358
136731
|
return;
|
|
136359
136732
|
}
|
|
136360
|
-
|
|
136733
|
+
registry3.add(packageName, config11 ?? {});
|
|
136361
136734
|
res.json({
|
|
136362
136735
|
success: true,
|
|
136363
136736
|
packageName,
|
|
@@ -136385,7 +136758,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136385
136758
|
});
|
|
136386
136759
|
return;
|
|
136387
136760
|
}
|
|
136388
|
-
|
|
136761
|
+
registry3.remove(packageName);
|
|
136389
136762
|
res.json({
|
|
136390
136763
|
success: true,
|
|
136391
136764
|
packageName,
|
|
@@ -136425,7 +136798,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136425
136798
|
});
|
|
136426
136799
|
return;
|
|
136427
136800
|
}
|
|
136428
|
-
|
|
136801
|
+
registry3.add(result.packageName, {});
|
|
136429
136802
|
res.json({
|
|
136430
136803
|
success: true,
|
|
136431
136804
|
packageName: result.packageName,
|
|
@@ -136458,7 +136831,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136458
136831
|
});
|
|
136459
136832
|
return;
|
|
136460
136833
|
}
|
|
136461
|
-
|
|
136834
|
+
registry3.add(result.packageName, {});
|
|
136462
136835
|
res.json({
|
|
136463
136836
|
success: true,
|
|
136464
136837
|
packageName: result.packageName,
|
|
@@ -137155,7 +137528,7 @@ var WebSocketApi = class {
|
|
|
137155
137528
|
|
|
137156
137529
|
// src/api/web-api.ts
|
|
137157
137530
|
var WebApi = class extends Service {
|
|
137158
|
-
constructor(
|
|
137531
|
+
constructor(logger251, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, identityStorage, lockCredentialStorage, settingsStorage, backupService, props) {
|
|
137159
137532
|
super("WebApi");
|
|
137160
137533
|
this.bridgeService = bridgeService;
|
|
137161
137534
|
this.haClient = haClient;
|
|
@@ -137167,8 +137540,8 @@ var WebApi = class extends Service {
|
|
|
137167
137540
|
this.settingsStorage = settingsStorage;
|
|
137168
137541
|
this.backupService = backupService;
|
|
137169
137542
|
this.props = props;
|
|
137170
|
-
this.logger =
|
|
137171
|
-
this.log =
|
|
137543
|
+
this.logger = logger251;
|
|
137544
|
+
this.log = logger251.get(this);
|
|
137172
137545
|
this.accessLogger = accessLogger(this.log.createChild("Access Log"));
|
|
137173
137546
|
this.startTime = Date.now();
|
|
137174
137547
|
this.wsApi = new WebSocketApi(
|
|
@@ -137201,7 +137574,12 @@ var WebApi = class extends Service {
|
|
|
137201
137574
|
const api = express17.Router();
|
|
137202
137575
|
api.use(express17.json()).use(nocache()).use(
|
|
137203
137576
|
"/matter",
|
|
137204
|
-
matterApi(
|
|
137577
|
+
matterApi(
|
|
137578
|
+
this.bridgeService,
|
|
137579
|
+
this.haRegistry,
|
|
137580
|
+
this.identityStorage,
|
|
137581
|
+
this.mappingStorage
|
|
137582
|
+
)
|
|
137205
137583
|
).use(
|
|
137206
137584
|
"/health",
|
|
137207
137585
|
healthApi(
|
|
@@ -138354,10 +138732,10 @@ function isTransientConnectError(reason) {
|
|
|
138354
138732
|
return msg.includes("socket hang up") || msg.includes("tls") || msg.includes("TLS");
|
|
138355
138733
|
}
|
|
138356
138734
|
var HomeAssistantClient = class extends Service {
|
|
138357
|
-
constructor(
|
|
138735
|
+
constructor(logger251, options) {
|
|
138358
138736
|
super("HomeAssistantClient");
|
|
138359
138737
|
this.options = options;
|
|
138360
|
-
this.log =
|
|
138738
|
+
this.log = logger251.get(this);
|
|
138361
138739
|
}
|
|
138362
138740
|
options;
|
|
138363
138741
|
static Options = /* @__PURE__ */ Symbol.for("HomeAssistantClientProps");
|
|
@@ -139022,11 +139400,37 @@ var EntityIdentityStorage = class extends Service {
|
|
|
139022
139400
|
this.identities.delete(bridgeId);
|
|
139023
139401
|
await this.flush();
|
|
139024
139402
|
}
|
|
139403
|
+
// Drop a single identity record. Used by the manual orphan cleanup after the
|
|
139404
|
+
// 7-day tombstone; flush immediately since it is a user-initiated deletion.
|
|
139405
|
+
async deleteIdentity(bridgeId, key) {
|
|
139406
|
+
const bridgeMap = this.identities.get(bridgeId);
|
|
139407
|
+
if (bridgeMap?.delete(key)) {
|
|
139408
|
+
await this.flush();
|
|
139409
|
+
}
|
|
139410
|
+
}
|
|
139411
|
+
// Stamp the tombstone the first time a record's entity is absent from HA. Keeps
|
|
139412
|
+
// the first-seen time on later passes and no-ops once stamped, so a steady
|
|
139413
|
+
// absence does not churn the debounced persist.
|
|
139414
|
+
markIdentityMissing(bridgeId, key, nowIso) {
|
|
139415
|
+
const record = this.identities.get(bridgeId)?.get(key);
|
|
139416
|
+
if (!record || record.missingSince != null) return;
|
|
139417
|
+
record.missingSince = nowIso;
|
|
139418
|
+
this.schedulePersist();
|
|
139419
|
+
}
|
|
139420
|
+
// Clear the tombstone when the entity is present again. No-ops when there is
|
|
139421
|
+
// nothing to clear, so an all-present bridge writes nothing on refresh.
|
|
139422
|
+
clearIdentityMissing(bridgeId, key) {
|
|
139423
|
+
const record = this.identities.get(bridgeId)?.get(key);
|
|
139424
|
+
if (!record || record.missingSince == null) return;
|
|
139425
|
+
delete record.missingSince;
|
|
139426
|
+
this.schedulePersist();
|
|
139427
|
+
}
|
|
139025
139428
|
};
|
|
139026
139429
|
|
|
139027
139430
|
// src/services/storage/entity-mapping-storage.ts
|
|
139028
139431
|
init_service();
|
|
139029
139432
|
var CURRENT_VERSION2 = 1;
|
|
139433
|
+
var PERSIST_DEBOUNCE_MS2 = 500;
|
|
139030
139434
|
var EntityMappingStorage = class extends Service {
|
|
139031
139435
|
constructor(appStorage) {
|
|
139032
139436
|
super("EntityMappingStorage");
|
|
@@ -139035,10 +139439,14 @@ var EntityMappingStorage = class extends Service {
|
|
|
139035
139439
|
appStorage;
|
|
139036
139440
|
storage;
|
|
139037
139441
|
mappings = /* @__PURE__ */ new Map();
|
|
139442
|
+
persistTimer = null;
|
|
139038
139443
|
async initialize() {
|
|
139039
139444
|
this.storage = this.appStorage.createContext("entity-mappings");
|
|
139040
139445
|
await this.load();
|
|
139041
139446
|
}
|
|
139447
|
+
async dispose() {
|
|
139448
|
+
await this.flush();
|
|
139449
|
+
}
|
|
139042
139450
|
async load() {
|
|
139043
139451
|
const stored = await this.storage.get("data", {
|
|
139044
139452
|
version: CURRENT_VERSION2,
|
|
@@ -139073,6 +139481,10 @@ var EntityMappingStorage = class extends Service {
|
|
|
139073
139481
|
}
|
|
139074
139482
|
}
|
|
139075
139483
|
async persist() {
|
|
139484
|
+
if (this.persistTimer) {
|
|
139485
|
+
clearTimeout(this.persistTimer);
|
|
139486
|
+
this.persistTimer = null;
|
|
139487
|
+
}
|
|
139076
139488
|
const data = {
|
|
139077
139489
|
version: CURRENT_VERSION2,
|
|
139078
139490
|
mappings: {}
|
|
@@ -139082,6 +139494,17 @@ var EntityMappingStorage = class extends Service {
|
|
|
139082
139494
|
}
|
|
139083
139495
|
await this.storage.set("data", data);
|
|
139084
139496
|
}
|
|
139497
|
+
schedulePersist() {
|
|
139498
|
+
if (this.persistTimer) return;
|
|
139499
|
+
this.persistTimer = setTimeout(() => {
|
|
139500
|
+
this.persistTimer = null;
|
|
139501
|
+
void this.persist();
|
|
139502
|
+
}, PERSIST_DEBOUNCE_MS2);
|
|
139503
|
+
}
|
|
139504
|
+
// Flush any pending debounced write, used on dispose and by tests.
|
|
139505
|
+
async flush() {
|
|
139506
|
+
await this.persist();
|
|
139507
|
+
}
|
|
139085
139508
|
getMappingsForBridge(bridgeId) {
|
|
139086
139509
|
const bridgeMap = this.mappings.get(bridgeId);
|
|
139087
139510
|
return bridgeMap ? Array.from(bridgeMap.values()) : [];
|
|
@@ -139128,6 +139551,8 @@ var EntityMappingStorage = class extends Service {
|
|
|
139128
139551
|
currentEntity: request.currentEntity?.trim() || void 0,
|
|
139129
139552
|
batteryPowerEntity: request.batteryPowerEntity?.trim() || void 0,
|
|
139130
139553
|
batteryEnergyEntity: request.batteryEnergyEntity?.trim() || void 0,
|
|
139554
|
+
chargingSwitchEntity: request.chargingSwitchEntity?.trim() || void 0,
|
|
139555
|
+
currentLimitEntity: request.currentLimitEntity?.trim() || void 0,
|
|
139131
139556
|
pressureEntity: request.pressureEntity?.trim() || void 0,
|
|
139132
139557
|
suctionLevelEntity: request.suctionLevelEntity?.trim() || void 0,
|
|
139133
139558
|
mopIntensityEntity: request.mopIntensityEntity?.trim() || void 0,
|
|
@@ -139156,7 +139581,7 @@ var EntityMappingStorage = class extends Service {
|
|
|
139156
139581
|
composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0,
|
|
139157
139582
|
disableMomentaryFlip: request.disableMomentaryFlip || void 0
|
|
139158
139583
|
};
|
|
139159
|
-
if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.disableBatteryMapping && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.lockUsercodeService && config11.lockUsercodeSlot === void 0 && config11.lockPinMinLength === void 0 && config11.lockPinMaxLength === void 0 && !config11.powerEntity && !config11.energyEntity && !config11.voltageEntity && !config11.currentEntity && !config11.batteryPowerEntity && !config11.batteryEnergyEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0) && !config11.disableMomentaryFlip) {
|
|
139584
|
+
if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.disableBatteryMapping && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.lockUsercodeService && config11.lockUsercodeSlot === void 0 && config11.lockPinMinLength === void 0 && config11.lockPinMaxLength === void 0 && !config11.powerEntity && !config11.energyEntity && !config11.voltageEntity && !config11.currentEntity && !config11.batteryPowerEntity && !config11.batteryEnergyEntity && !config11.chargingSwitchEntity && !config11.currentLimitEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0) && !config11.disableMomentaryFlip) {
|
|
139160
139585
|
bridgeMap.delete(request.entityId);
|
|
139161
139586
|
} else {
|
|
139162
139587
|
bridgeMap.set(request.entityId, config11);
|
|
@@ -139175,6 +139600,23 @@ var EntityMappingStorage = class extends Service {
|
|
|
139175
139600
|
this.mappings.delete(bridgeId);
|
|
139176
139601
|
await this.persist();
|
|
139177
139602
|
}
|
|
139603
|
+
// Stamp the tombstone the first time a mapping's entity is absent from HA.
|
|
139604
|
+
// Keeps the first-seen time on later passes and no-ops once stamped, so a
|
|
139605
|
+
// steady absence does not churn the debounced persist.
|
|
139606
|
+
markMappingMissing(bridgeId, entityId, nowIso) {
|
|
139607
|
+
const record = this.mappings.get(bridgeId)?.get(entityId);
|
|
139608
|
+
if (!record || record.missingSince != null) return;
|
|
139609
|
+
record.missingSince = nowIso;
|
|
139610
|
+
this.schedulePersist();
|
|
139611
|
+
}
|
|
139612
|
+
// Clear the tombstone when the entity is present again. No-ops when there is
|
|
139613
|
+
// nothing to clear, so an all-present bridge writes nothing on refresh.
|
|
139614
|
+
clearMappingMissing(bridgeId, entityId) {
|
|
139615
|
+
const record = this.mappings.get(bridgeId)?.get(entityId);
|
|
139616
|
+
if (!record || record.missingSince == null) return;
|
|
139617
|
+
delete record.missingSince;
|
|
139618
|
+
this.schedulePersist();
|
|
139619
|
+
}
|
|
139178
139620
|
};
|
|
139179
139621
|
function sanitizeVendorId(value) {
|
|
139180
139622
|
if (value === void 0 || value === null || value === "") {
|
|
@@ -149100,6 +149542,7 @@ var EnergyEvseDeviceDefinition = MutableEndpoint({
|
|
|
149100
149542
|
)
|
|
149101
149543
|
});
|
|
149102
149544
|
Object.freeze(EnergyEvseDeviceDefinition);
|
|
149545
|
+
var EnergyEvseDevice = EnergyEvseDeviceDefinition;
|
|
149103
149546
|
|
|
149104
149547
|
// ../../node_modules/.pnpm/@matter+node@0.17.7_patch_hash=34f8224b2964673272e9f22613c0cb1f8ec12758dd46b4fe303d42373451ad4c/node_modules/@matter/node/dist/esm/devices/extended-color-light.js
|
|
149105
149548
|
init_IdentifyServer();
|
|
@@ -156079,8 +156522,8 @@ var PluginManager = class {
|
|
|
156079
156522
|
this.storageDir = storageDir;
|
|
156080
156523
|
this.homeAssistant = homeAssistant;
|
|
156081
156524
|
}
|
|
156082
|
-
setRegistry(
|
|
156083
|
-
this.registry =
|
|
156525
|
+
setRegistry(registry3) {
|
|
156526
|
+
this.registry = registry3;
|
|
156084
156527
|
}
|
|
156085
156528
|
/**
|
|
156086
156529
|
* Load and register a built-in plugin instance.
|
|
@@ -156805,16 +157248,96 @@ function seedExistingSessionStarts(startedAt, sessions, now = Date.now()) {
|
|
|
156805
157248
|
}
|
|
156806
157249
|
}
|
|
156807
157250
|
|
|
157251
|
+
// src/services/bridges/subscription-summary.ts
|
|
157252
|
+
function readPaths(value) {
|
|
157253
|
+
return Array.isArray(value) ? value : null;
|
|
157254
|
+
}
|
|
157255
|
+
function unknownSummary(id) {
|
|
157256
|
+
return {
|
|
157257
|
+
id,
|
|
157258
|
+
attributePaths: 0,
|
|
157259
|
+
eventPaths: 0,
|
|
157260
|
+
scope: "unknown",
|
|
157261
|
+
endpointIds: []
|
|
157262
|
+
};
|
|
157263
|
+
}
|
|
157264
|
+
function summarizeSubscription(sub) {
|
|
157265
|
+
let id = null;
|
|
157266
|
+
let request;
|
|
157267
|
+
try {
|
|
157268
|
+
const s = sub;
|
|
157269
|
+
if (typeof s.subscriptionId === "number") {
|
|
157270
|
+
id = s.subscriptionId;
|
|
157271
|
+
}
|
|
157272
|
+
request = s.request;
|
|
157273
|
+
} catch {
|
|
157274
|
+
return unknownSummary(id);
|
|
157275
|
+
}
|
|
157276
|
+
if (request == null || typeof request !== "object") {
|
|
157277
|
+
return unknownSummary(id);
|
|
157278
|
+
}
|
|
157279
|
+
const req = request;
|
|
157280
|
+
const attrPaths = readPaths(req.attributeRequests);
|
|
157281
|
+
const eventPaths = readPaths(req.eventRequests);
|
|
157282
|
+
if (attrPaths === null && eventPaths === null) {
|
|
157283
|
+
return unknownSummary(id);
|
|
157284
|
+
}
|
|
157285
|
+
const all = [...attrPaths ?? [], ...eventPaths ?? []];
|
|
157286
|
+
let wildcard = false;
|
|
157287
|
+
const endpointIds = /* @__PURE__ */ new Set();
|
|
157288
|
+
for (const path14 of all) {
|
|
157289
|
+
const ep = path14?.endpointId;
|
|
157290
|
+
if (ep == null) {
|
|
157291
|
+
wildcard = true;
|
|
157292
|
+
} else if (typeof ep === "number" || typeof ep === "bigint") {
|
|
157293
|
+
endpointIds.add(Number(ep));
|
|
157294
|
+
}
|
|
157295
|
+
}
|
|
157296
|
+
return {
|
|
157297
|
+
id,
|
|
157298
|
+
attributePaths: attrPaths?.length ?? 0,
|
|
157299
|
+
eventPaths: eventPaths?.length ?? 0,
|
|
157300
|
+
scope: wildcard ? "wildcard" : "endpoint-specific",
|
|
157301
|
+
endpointIds: [...endpointIds].sort((a, b) => a - b)
|
|
157302
|
+
};
|
|
157303
|
+
}
|
|
157304
|
+
function summarizeSubscriptions(subscriptions) {
|
|
157305
|
+
const out = [];
|
|
157306
|
+
try {
|
|
157307
|
+
for (const sub of subscriptions) {
|
|
157308
|
+
out.push(summarizeSubscription(sub));
|
|
157309
|
+
}
|
|
157310
|
+
} catch {
|
|
157311
|
+
}
|
|
157312
|
+
return out;
|
|
157313
|
+
}
|
|
157314
|
+
|
|
157315
|
+
// src/services/bridges/wedge-watchdog.ts
|
|
157316
|
+
var WEDGE_WARMUP_MS = 15 * 60 * 1e3;
|
|
157317
|
+
var WEDGE_IM_SILENCE_MS = 45 * 60 * 1e3;
|
|
157318
|
+
var WEDGE_MIN_ROTATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
157319
|
+
function decideWedgeRotation(input) {
|
|
157320
|
+
const {
|
|
157321
|
+
subscriptionCount,
|
|
157322
|
+
sessionAgeMs,
|
|
157323
|
+
lastImRequestMsAgo,
|
|
157324
|
+
lastRotatedMsAgo
|
|
157325
|
+
} = input;
|
|
157326
|
+
const imSilenceMs = lastImRequestMsAgo == null ? sessionAgeMs : lastImRequestMsAgo;
|
|
157327
|
+
return subscriptionCount > 0 && sessionAgeMs > WEDGE_WARMUP_MS && imSilenceMs > WEDGE_IM_SILENCE_MS && (lastRotatedMsAgo == null || lastRotatedMsAgo > WEDGE_MIN_ROTATE_INTERVAL_MS);
|
|
157328
|
+
}
|
|
157329
|
+
|
|
156808
157330
|
// src/services/bridges/bridge.ts
|
|
157331
|
+
var imWrapMarker = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
|
|
156809
157332
|
var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
|
|
156810
157333
|
var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
|
|
156811
157334
|
var MDNS_ADDRESS_CHECK_INTERVAL_MS = 6e4;
|
|
156812
157335
|
var Bridge = class {
|
|
156813
|
-
constructor(env,
|
|
157336
|
+
constructor(env, logger251, dataProvider, endpointManager, serverOptions) {
|
|
156814
157337
|
this.dataProvider = dataProvider;
|
|
156815
157338
|
this.endpointManager = endpointManager;
|
|
156816
157339
|
this.serverOptions = serverOptions;
|
|
156817
|
-
this.log =
|
|
157340
|
+
this.log = logger251.get(`Bridge / ${dataProvider.id}`);
|
|
156818
157341
|
this.server = new BridgeServerNode(
|
|
156819
157342
|
env,
|
|
156820
157343
|
this.dataProvider,
|
|
@@ -156853,6 +157376,12 @@ var Bridge = class {
|
|
|
156853
157376
|
sessionStartedAt = /* @__PURE__ */ new Map();
|
|
156854
157377
|
rotationTimer = null;
|
|
156855
157378
|
maxSessionAgeMs = 0;
|
|
157379
|
+
// Wedge watchdog: last inbound Interaction Model request time per session and
|
|
157380
|
+
// last time the watchdog rotated it, both keyed by the long-lived session
|
|
157381
|
+
// object so they clear when the session goes away.
|
|
157382
|
+
lastImRequestAt = /* @__PURE__ */ new WeakMap();
|
|
157383
|
+
wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
|
|
157384
|
+
wedgeWatchdogTimer = null;
|
|
156856
157385
|
// Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
|
|
156857
157386
|
// change forces a fresh operational announcement (#415).
|
|
156858
157387
|
mdnsAddressTimer = null;
|
|
@@ -156899,6 +157428,7 @@ var Bridge = class {
|
|
|
156899
157428
|
const sessionList = sessions.map((s) => {
|
|
156900
157429
|
const subCount = s.subscriptions.size;
|
|
156901
157430
|
totalSubscriptions += subCount;
|
|
157431
|
+
const subscriptions = summarizeSubscriptions(s.subscriptions);
|
|
156902
157432
|
const fi = typeof s.fabric?.fabricIndex === "number" ? s.fabric.fabricIndex : null;
|
|
156903
157433
|
if (fi !== null) {
|
|
156904
157434
|
const existing = fabricMap.get(fi) ?? {
|
|
@@ -156912,14 +157442,18 @@ var Bridge = class {
|
|
|
156912
157442
|
const nowMs = Date.now();
|
|
156913
157443
|
const lastActiveMsAgo = typeof s.activeTimestamp === "number" && s.activeTimestamp > 0 ? nowMs - s.activeTimestamp : null;
|
|
156914
157444
|
const lastAnyActivityMsAgo = typeof s.timestamp === "number" ? nowMs - s.timestamp : null;
|
|
157445
|
+
const lastImAt = this.lastImRequestAt.get(s);
|
|
157446
|
+
const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
|
|
156915
157447
|
const startedAt = this.sessionStartedAt.get(s.id);
|
|
156916
157448
|
return {
|
|
156917
157449
|
id: s.id,
|
|
156918
157450
|
peerNodeId: String(s.peerNodeId),
|
|
156919
157451
|
fabricIndex: fi,
|
|
156920
157452
|
subscriptionCount: subCount,
|
|
157453
|
+
subscriptions,
|
|
156921
157454
|
lastActiveMsAgo,
|
|
156922
157455
|
lastAnyActivityMsAgo,
|
|
157456
|
+
lastImRequestMsAgo,
|
|
156923
157457
|
isPeerActive: Boolean(s.isPeerActive),
|
|
156924
157458
|
ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
|
|
156925
157459
|
};
|
|
@@ -157086,6 +157620,7 @@ var Bridge = class {
|
|
|
157086
157620
|
this.wireSessionDiagnostics();
|
|
157087
157621
|
this.wireFabricWarnings();
|
|
157088
157622
|
this.startSessionRotation();
|
|
157623
|
+
this.startWedgeWatchdog();
|
|
157089
157624
|
this.startMdnsAddressWatch();
|
|
157090
157625
|
logMemoryUsage(this.log, "bridge running");
|
|
157091
157626
|
diagnosticEventBus.emit("bridge_started", `Bridge started`, {
|
|
@@ -157285,6 +157820,29 @@ ${e?.toString()}`);
|
|
|
157285
157820
|
};
|
|
157286
157821
|
sessionManager.sessions.added.on(this.sessionAddedHandler);
|
|
157287
157822
|
sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
|
|
157823
|
+
this.wireImRequestTracking();
|
|
157824
|
+
} catch {
|
|
157825
|
+
}
|
|
157826
|
+
}
|
|
157827
|
+
// Stamp the time of every inbound Interaction Model request per session by
|
|
157828
|
+
// wrapping InteractionServer.onNewExchange. The wedge watchdog reads these to
|
|
157829
|
+
// tell a live-but-consuming controller from one that only keeps acking.
|
|
157830
|
+
wireImRequestTracking() {
|
|
157831
|
+
try {
|
|
157832
|
+
const is = this.server.env.get(InteractionServer);
|
|
157833
|
+
const marked = is;
|
|
157834
|
+
if (marked[imWrapMarker]) {
|
|
157835
|
+
return;
|
|
157836
|
+
}
|
|
157837
|
+
const original = is.onNewExchange.bind(is);
|
|
157838
|
+
is.onNewExchange = (exchange, message) => {
|
|
157839
|
+
const session = exchange.session;
|
|
157840
|
+
if (session) {
|
|
157841
|
+
this.lastImRequestAt.set(session, Date.now());
|
|
157842
|
+
}
|
|
157843
|
+
return original(exchange, message);
|
|
157844
|
+
};
|
|
157845
|
+
marked[imWrapMarker] = true;
|
|
157288
157846
|
} catch {
|
|
157289
157847
|
}
|
|
157290
157848
|
}
|
|
@@ -157447,6 +158005,7 @@ ${e?.toString()}`);
|
|
|
157447
158005
|
}
|
|
157448
158006
|
this.staleSessionTimers.clear();
|
|
157449
158007
|
this.stopSessionRotation();
|
|
158008
|
+
this.stopWedgeWatchdog();
|
|
157450
158009
|
this.stopMdnsAddressWatch();
|
|
157451
158010
|
this.sessionStartedAt.clear();
|
|
157452
158011
|
}
|
|
@@ -157512,6 +158071,72 @@ ${e?.toString()}`);
|
|
|
157512
158071
|
this.rotationTimer = null;
|
|
157513
158072
|
}
|
|
157514
158073
|
}
|
|
158074
|
+
// Opt-in wedge watchdog: reuse the rotation check cadence (5min) to look for
|
|
158075
|
+
// the one session wedged on "Updating" and rotate just that one.
|
|
158076
|
+
startWedgeWatchdog() {
|
|
158077
|
+
this.stopWedgeWatchdog();
|
|
158078
|
+
if (!this.dataProvider.featureFlags?.wedgeWatchdog) {
|
|
158079
|
+
return;
|
|
158080
|
+
}
|
|
158081
|
+
this.wedgeWatchdogTimer = setInterval(
|
|
158082
|
+
() => this.runWedgeWatchdogCheck(),
|
|
158083
|
+
ROTATION_CHECK_INTERVAL_MS
|
|
158084
|
+
);
|
|
158085
|
+
this.log.info(
|
|
158086
|
+
`Wedge watchdog: checking every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
158087
|
+
);
|
|
158088
|
+
}
|
|
158089
|
+
stopWedgeWatchdog() {
|
|
158090
|
+
if (this.wedgeWatchdogTimer) {
|
|
158091
|
+
clearInterval(this.wedgeWatchdogTimer);
|
|
158092
|
+
this.wedgeWatchdogTimer = null;
|
|
158093
|
+
}
|
|
158094
|
+
}
|
|
158095
|
+
// Rotate exactly the sessions the pure rule flags as wedged. Closing is the
|
|
158096
|
+
// same graceful-then-force path age rotation uses, so a false positive just
|
|
158097
|
+
// re-CASEs the controller.
|
|
158098
|
+
runWedgeWatchdogCheck() {
|
|
158099
|
+
try {
|
|
158100
|
+
const sessionManager = this.server.env.get(SessionManager);
|
|
158101
|
+
const now = Date.now();
|
|
158102
|
+
const closes = [];
|
|
158103
|
+
for (const s of [...sessionManager.sessions]) {
|
|
158104
|
+
if (s.isClosing) continue;
|
|
158105
|
+
const startedAt = this.sessionStartedAt.get(s.id);
|
|
158106
|
+
const sessionAgeMs = startedAt != null ? now - startedAt : 0;
|
|
158107
|
+
const lastImAt = this.lastImRequestAt.get(s);
|
|
158108
|
+
const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
|
|
158109
|
+
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
158110
|
+
const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
|
|
158111
|
+
if (!decideWedgeRotation({
|
|
158112
|
+
subscriptionCount: s.subscriptions.size,
|
|
158113
|
+
sessionAgeMs,
|
|
158114
|
+
lastImRequestMsAgo,
|
|
158115
|
+
lastRotatedMsAgo
|
|
158116
|
+
})) {
|
|
158117
|
+
continue;
|
|
158118
|
+
}
|
|
158119
|
+
const silenceMin = Math.round(
|
|
158120
|
+
(lastImRequestMsAgo ?? sessionAgeMs) / 6e4
|
|
158121
|
+
);
|
|
158122
|
+
this.log.info(
|
|
158123
|
+
`Wedge watchdog: rotating session ${s.id}, no inbound interaction for ${silenceMin}min`
|
|
158124
|
+
);
|
|
158125
|
+
this.wedgeLastRotatedAt.set(s, now);
|
|
158126
|
+
closes.push(
|
|
158127
|
+
s.initiateClose().catch(() => {
|
|
158128
|
+
return s.initiateForceClose({
|
|
158129
|
+
cause: new Error("wedge watchdog, forcing")
|
|
158130
|
+
});
|
|
158131
|
+
})
|
|
158132
|
+
);
|
|
158133
|
+
}
|
|
158134
|
+
if (closes.length > 0) {
|
|
158135
|
+
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
158136
|
+
}
|
|
158137
|
+
} catch {
|
|
158138
|
+
}
|
|
158139
|
+
}
|
|
157515
158140
|
// Poll the interface addresses mDNS advertises and re-announce when they
|
|
157516
158141
|
// change. matter.js caches the operational records at first announcement, so
|
|
157517
158142
|
// a dynamic ISP IPv6 prefix change keeps advertising the dead global address
|
|
@@ -157643,6 +158268,7 @@ ${e?.toString()}`);
|
|
|
157643
158268
|
if (this.status.code === BridgeStatus.Running) {
|
|
157644
158269
|
this.startAutoForceSyncIfEnabled();
|
|
157645
158270
|
this.startSessionRotation();
|
|
158271
|
+
this.startWedgeWatchdog();
|
|
157646
158272
|
}
|
|
157647
158273
|
} catch (e) {
|
|
157648
158274
|
const reason = "Failed to update bridge due to error:";
|
|
@@ -157765,66 +158391,6 @@ var AggregatorEndpoint2 = class extends Endpoint {
|
|
|
157765
158391
|
}
|
|
157766
158392
|
};
|
|
157767
158393
|
|
|
157768
|
-
// src/matter/endpoints/entity-endpoint.ts
|
|
157769
|
-
init_esm7();
|
|
157770
|
-
var EntityEndpoint = class extends Endpoint {
|
|
157771
|
-
constructor(type, entityId, customName, mappedEntityIds, endpointId) {
|
|
157772
|
-
super(type, { id: endpointId ?? createEndpointId(entityId, customName) });
|
|
157773
|
-
this.entityId = entityId;
|
|
157774
|
-
this.mappedEntityIds = mappedEntityIds ?? [];
|
|
157775
|
-
}
|
|
157776
|
-
entityId;
|
|
157777
|
-
mappedEntityIds;
|
|
157778
|
-
lastMappedStates = {};
|
|
157779
|
-
hasMappedEntityChanged(states) {
|
|
157780
|
-
let changed = false;
|
|
157781
|
-
for (const mappedId of this.mappedEntityIds) {
|
|
157782
|
-
const mappedState = states[mappedId];
|
|
157783
|
-
if (!mappedState) continue;
|
|
157784
|
-
const fp = mappedState.state;
|
|
157785
|
-
if (fp !== this.lastMappedStates[mappedId]) {
|
|
157786
|
-
this.lastMappedStates[mappedId] = fp;
|
|
157787
|
-
changed = true;
|
|
157788
|
-
}
|
|
157789
|
-
}
|
|
157790
|
-
return changed;
|
|
157791
|
-
}
|
|
157792
|
-
};
|
|
157793
|
-
function createEndpointId(entityId, customName) {
|
|
157794
|
-
const baseName = customName || entityId;
|
|
157795
|
-
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
157796
|
-
}
|
|
157797
|
-
function getMappedEntityIds(mapping) {
|
|
157798
|
-
if (!mapping) return [];
|
|
157799
|
-
const ids = [];
|
|
157800
|
-
if (mapping.batteryEntity && !mapping.disableBatteryMapping) {
|
|
157801
|
-
ids.push(mapping.batteryEntity);
|
|
157802
|
-
}
|
|
157803
|
-
if (mapping.faultEntity) ids.push(mapping.faultEntity);
|
|
157804
|
-
if (mapping.chargingStateEntity) ids.push(mapping.chargingStateEntity);
|
|
157805
|
-
if (mapping.temperatureEntity) ids.push(mapping.temperatureEntity);
|
|
157806
|
-
if (mapping.humidityEntity) ids.push(mapping.humidityEntity);
|
|
157807
|
-
if (mapping.pressureEntity) ids.push(mapping.pressureEntity);
|
|
157808
|
-
if (mapping.cleaningModeEntity) ids.push(mapping.cleaningModeEntity);
|
|
157809
|
-
if (mapping.suctionLevelEntity) ids.push(mapping.suctionLevelEntity);
|
|
157810
|
-
if (mapping.mopIntensityEntity) ids.push(mapping.mopIntensityEntity);
|
|
157811
|
-
if (mapping.filterLifeEntity) ids.push(mapping.filterLifeEntity);
|
|
157812
|
-
if (mapping.powerEntity) ids.push(mapping.powerEntity);
|
|
157813
|
-
if (mapping.energyEntity) ids.push(mapping.energyEntity);
|
|
157814
|
-
if (mapping.voltageEntity) ids.push(mapping.voltageEntity);
|
|
157815
|
-
if (mapping.currentEntity) ids.push(mapping.currentEntity);
|
|
157816
|
-
if (mapping.batteryPowerEntity) ids.push(mapping.batteryPowerEntity);
|
|
157817
|
-
if (mapping.batteryEnergyEntity) ids.push(mapping.batteryEnergyEntity);
|
|
157818
|
-
if (mapping.currentRoomEntity) ids.push(mapping.currentRoomEntity);
|
|
157819
|
-
if (mapping.cleanedAreaEntity) ids.push(mapping.cleanedAreaEntity);
|
|
157820
|
-
if (mapping.composedEntities) {
|
|
157821
|
-
for (const sub of mapping.composedEntities) {
|
|
157822
|
-
if (sub.entityId) ids.push(sub.entityId);
|
|
157823
|
-
}
|
|
157824
|
-
}
|
|
157825
|
-
return ids;
|
|
157826
|
-
}
|
|
157827
|
-
|
|
157828
158394
|
// src/matter/endpoints/legacy/legacy-endpoint.ts
|
|
157829
158395
|
init_dist();
|
|
157830
158396
|
init_esm();
|
|
@@ -157903,9 +158469,9 @@ function resolveBatteryPercent(state) {
|
|
|
157903
158469
|
return BATTERY_ENUM_PERCENT[state.toLowerCase()] ?? null;
|
|
157904
158470
|
}
|
|
157905
158471
|
var EntityStateProvider = class extends Service {
|
|
157906
|
-
constructor(
|
|
158472
|
+
constructor(registry3) {
|
|
157907
158473
|
super("EntityStateProvider");
|
|
157908
|
-
this.registry =
|
|
158474
|
+
this.registry = registry3;
|
|
157909
158475
|
}
|
|
157910
158476
|
registry;
|
|
157911
158477
|
/**
|
|
@@ -159823,11 +160389,11 @@ function createEndpointId2(entityId, customName) {
|
|
|
159823
160389
|
const baseName = customName || entityId;
|
|
159824
160390
|
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
159825
160391
|
}
|
|
159826
|
-
function buildEntityPayload(
|
|
159827
|
-
const state =
|
|
160392
|
+
function buildEntityPayload(registry3, entityId) {
|
|
160393
|
+
const state = registry3.initialState(entityId);
|
|
159828
160394
|
if (!state) return void 0;
|
|
159829
|
-
const entity =
|
|
159830
|
-
const deviceRegistry =
|
|
160395
|
+
const entity = registry3.entity(entityId);
|
|
160396
|
+
const deviceRegistry = registry3.deviceOf(entityId);
|
|
159831
160397
|
return {
|
|
159832
160398
|
entity_id: entityId,
|
|
159833
160399
|
state,
|
|
@@ -159842,8 +160408,8 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
|
|
|
159842
160408
|
lastStates = /* @__PURE__ */ new Map();
|
|
159843
160409
|
debouncedUpdates = /* @__PURE__ */ new Map();
|
|
159844
160410
|
static async create(config11) {
|
|
159845
|
-
const { registry:
|
|
159846
|
-
const primaryPayload = buildEntityPayload(
|
|
160411
|
+
const { registry: registry3, primaryEntityId } = config11;
|
|
160412
|
+
const primaryPayload = buildEntityPayload(registry3, primaryEntityId);
|
|
159847
160413
|
if (!primaryPayload) return void 0;
|
|
159848
160414
|
const airPurifierAttributes = primaryPayload.state.attributes;
|
|
159849
160415
|
const supportedFeatures = airPurifierAttributes.supported_features ?? 0;
|
|
@@ -159931,7 +160497,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
|
|
|
159931
160497
|
let tempSub;
|
|
159932
160498
|
if (config11.temperatureEntityId) {
|
|
159933
160499
|
const tempPayload = buildEntityPayload(
|
|
159934
|
-
|
|
160500
|
+
registry3,
|
|
159935
160501
|
config11.temperatureEntityId
|
|
159936
160502
|
);
|
|
159937
160503
|
if (tempPayload) {
|
|
@@ -159946,7 +160512,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
|
|
|
159946
160512
|
}
|
|
159947
160513
|
let humSub;
|
|
159948
160514
|
if (config11.humidityEntityId) {
|
|
159949
|
-
const humPayload = buildEntityPayload(
|
|
160515
|
+
const humPayload = buildEntityPayload(registry3, config11.humidityEntityId);
|
|
159950
160516
|
if (humPayload) {
|
|
159951
160517
|
humSub = new Endpoint(
|
|
159952
160518
|
HumiditySubType.set({
|
|
@@ -161400,14 +161966,14 @@ function createEndpointId3(entityId, customName) {
|
|
|
161400
161966
|
const baseName = customName || entityId;
|
|
161401
161967
|
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
161402
161968
|
}
|
|
161403
|
-
function buildEntityPayload2(
|
|
161404
|
-
const state =
|
|
161969
|
+
function buildEntityPayload2(registry3, entityId) {
|
|
161970
|
+
const state = registry3.initialState(entityId);
|
|
161405
161971
|
if (!state) return void 0;
|
|
161406
161972
|
return {
|
|
161407
161973
|
entity_id: entityId,
|
|
161408
161974
|
state,
|
|
161409
|
-
registry:
|
|
161410
|
-
deviceRegistry:
|
|
161975
|
+
registry: registry3.entity(entityId),
|
|
161976
|
+
deviceRegistry: registry3.deviceOf(entityId)
|
|
161411
161977
|
};
|
|
161412
161978
|
}
|
|
161413
161979
|
var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpoint {
|
|
@@ -161417,8 +161983,8 @@ var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpo
|
|
|
161417
161983
|
lastStates = /* @__PURE__ */ new Map();
|
|
161418
161984
|
debouncedUpdates = /* @__PURE__ */ new Map();
|
|
161419
161985
|
static async create(config11) {
|
|
161420
|
-
const { registry:
|
|
161421
|
-
const payload = buildEntityPayload2(
|
|
161986
|
+
const { registry: registry3, primaryEntityId } = config11;
|
|
161987
|
+
const payload = buildEntityPayload2(registry3, primaryEntityId);
|
|
161422
161988
|
if (!payload) return void 0;
|
|
161423
161989
|
const endpointId = config11.endpointId ?? createEndpointId3(primaryEntityId, config11.customName);
|
|
161424
161990
|
const mapping = config11.mapping ?? {
|
|
@@ -161676,11 +162242,11 @@ function createEndpointId4(entityId, customName) {
|
|
|
161676
162242
|
const baseName = customName || entityId;
|
|
161677
162243
|
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
161678
162244
|
}
|
|
161679
|
-
function buildEntityPayload3(
|
|
161680
|
-
const state =
|
|
162245
|
+
function buildEntityPayload3(registry3, entityId) {
|
|
162246
|
+
const state = registry3.initialStateIncludingUnfiltered(entityId);
|
|
161681
162247
|
if (!state) return void 0;
|
|
161682
|
-
const entity =
|
|
161683
|
-
const deviceRegistry =
|
|
162248
|
+
const entity = registry3.entityIncludingUnfiltered(entityId);
|
|
162249
|
+
const deviceRegistry = registry3.deviceOfIncludingUnfiltered(entityId);
|
|
161684
162250
|
return {
|
|
161685
162251
|
entity_id: entityId,
|
|
161686
162252
|
state,
|
|
@@ -161695,8 +162261,8 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
|
|
|
161695
162261
|
lastStates = /* @__PURE__ */ new Map();
|
|
161696
162262
|
debouncedUpdates = /* @__PURE__ */ new Map();
|
|
161697
162263
|
static async create(config11) {
|
|
161698
|
-
const { registry:
|
|
161699
|
-
const primaryPayload = buildEntityPayload3(
|
|
162264
|
+
const { registry: registry3, primaryEntityId } = config11;
|
|
162265
|
+
const primaryPayload = buildEntityPayload3(registry3, primaryEntityId);
|
|
161700
162266
|
if (!primaryPayload) return void 0;
|
|
161701
162267
|
let parentType = BridgedNodeEndpoint.with(
|
|
161702
162268
|
BasicInformationServer2,
|
|
@@ -161740,7 +162306,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
|
|
|
161740
162306
|
parts.push(tempSub);
|
|
161741
162307
|
let humSub;
|
|
161742
162308
|
if (config11.humidityEntityId) {
|
|
161743
|
-
const humPayload = buildEntityPayload3(
|
|
162309
|
+
const humPayload = buildEntityPayload3(registry3, config11.humidityEntityId);
|
|
161744
162310
|
if (humPayload) {
|
|
161745
162311
|
humSub = new Endpoint(
|
|
161746
162312
|
HumiditySubType2.set({
|
|
@@ -161754,7 +162320,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
|
|
|
161754
162320
|
let pressSub;
|
|
161755
162321
|
if (config11.pressureEntityId) {
|
|
161756
162322
|
const pressPayload = buildEntityPayload3(
|
|
161757
|
-
|
|
162323
|
+
registry3,
|
|
161758
162324
|
config11.pressureEntityId
|
|
161759
162325
|
);
|
|
161760
162326
|
if (pressPayload) {
|
|
@@ -162708,6 +163274,15 @@ function clearPendingTilt(st) {
|
|
|
162708
163274
|
st.pendingTilt = null;
|
|
162709
163275
|
}
|
|
162710
163276
|
var coverOptimistic = /* @__PURE__ */ new WeakMap();
|
|
163277
|
+
function haRestEnd(action) {
|
|
163278
|
+
if (action.action.includes("open_cover")) {
|
|
163279
|
+
return "open";
|
|
163280
|
+
}
|
|
163281
|
+
if (action.action.includes("close_cover")) {
|
|
163282
|
+
return "close";
|
|
163283
|
+
}
|
|
163284
|
+
return null;
|
|
163285
|
+
}
|
|
162711
163286
|
function getCoverOptimistic(endpoint) {
|
|
162712
163287
|
let st = coverOptimistic.get(endpoint);
|
|
162713
163288
|
if (!st) {
|
|
@@ -162822,7 +163397,10 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
162822
163397
|
if (!entry) {
|
|
162823
163398
|
return MovementStatus.Stopped;
|
|
162824
163399
|
}
|
|
162825
|
-
const
|
|
163400
|
+
const getExpected = config11.getExpectedRestPosition;
|
|
163401
|
+
const expectedPercent = entry.expectedRestEnd != null && getExpected != null ? getExpected(entry.expectedRestEnd, this.agent) : null;
|
|
163402
|
+
const goal = expectedPercent != null ? expectedPercent * 100 : existingTarget;
|
|
163403
|
+
const reachedTarget = positionAware && current100ths != null && goal != null && Math.abs(current100ths - goal) < 100;
|
|
162826
163404
|
const expired = Date.now() - entry.startedAt >= optimisticMovementTimeoutMs;
|
|
162827
163405
|
if (reachedTarget || expired) {
|
|
162828
163406
|
clearOptimisticAxis(optimistic, axis);
|
|
@@ -162927,7 +163505,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
162927
163505
|
}
|
|
162928
163506
|
// Record the optimistic direction and emit it now so a controller gets the
|
|
162929
163507
|
// moving->stopped edge even when HA never reports a transitional state (#429).
|
|
162930
|
-
startOptimisticMovement(axis, status3) {
|
|
163508
|
+
startOptimisticMovement(axis, status3, expectedRestEnd) {
|
|
162931
163509
|
const optimistic = getCoverOptimistic(this.endpoint);
|
|
162932
163510
|
clearOptimisticAxis(optimistic, axis);
|
|
162933
163511
|
const endpoint = this.endpoint;
|
|
@@ -162972,7 +163550,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
162972
163550
|
})();
|
|
162973
163551
|
}, optimisticMovementTimeoutMs);
|
|
162974
163552
|
timer.unref?.();
|
|
162975
|
-
optimistic[axis] = { status: status3, startedAt, timer };
|
|
163553
|
+
optimistic[axis] = { status: status3, startedAt, timer, expectedRestEnd };
|
|
162976
163554
|
this.writeOperationalStatus({ [axis]: status3 });
|
|
162977
163555
|
}
|
|
162978
163556
|
async handleMovement(type, _, direction, targetPercent100ths) {
|
|
@@ -163031,17 +163609,25 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163031
163609
|
}
|
|
163032
163610
|
handleLiftOpen() {
|
|
163033
163611
|
clearPendingLift(getCoverDebounce(this.endpoint));
|
|
163034
|
-
this.startOptimisticMovement("lift", MovementStatus.Opening);
|
|
163035
163612
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163036
163613
|
const action = this.state.config.openCoverLift(void 0, this.agent);
|
|
163614
|
+
this.startOptimisticMovement(
|
|
163615
|
+
"lift",
|
|
163616
|
+
MovementStatus.Opening,
|
|
163617
|
+
haRestEnd(action)
|
|
163618
|
+
);
|
|
163037
163619
|
logger218.info(`handleLiftOpen: calling action=${action.action}`);
|
|
163038
163620
|
homeAssistant.callAction(action);
|
|
163039
163621
|
}
|
|
163040
163622
|
handleLiftClose() {
|
|
163041
163623
|
clearPendingLift(getCoverDebounce(this.endpoint));
|
|
163042
|
-
this.startOptimisticMovement("lift", MovementStatus.Closing);
|
|
163043
163624
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163044
163625
|
const action = this.state.config.closeCoverLift(void 0, this.agent);
|
|
163626
|
+
this.startOptimisticMovement(
|
|
163627
|
+
"lift",
|
|
163628
|
+
MovementStatus.Closing,
|
|
163629
|
+
haRestEnd(action)
|
|
163630
|
+
);
|
|
163045
163631
|
logger218.info(`handleLiftClose: calling action=${action.action}`);
|
|
163046
163632
|
homeAssistant.callAction(action);
|
|
163047
163633
|
}
|
|
@@ -163052,14 +163638,15 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163052
163638
|
return;
|
|
163053
163639
|
}
|
|
163054
163640
|
this.state.targetPositionLiftPercent100ths = targetPercent100ths;
|
|
163055
|
-
this.startOptimisticMovement(
|
|
163056
|
-
"lift",
|
|
163057
|
-
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing
|
|
163058
|
-
);
|
|
163059
163641
|
const targetPosition = targetPercent100ths / 100;
|
|
163060
163642
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163061
163643
|
const action = config11.setLiftPosition(targetPosition, this.agent);
|
|
163062
163644
|
const entityId = homeAssistant.entityId;
|
|
163645
|
+
this.startOptimisticMovement(
|
|
163646
|
+
"lift",
|
|
163647
|
+
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing,
|
|
163648
|
+
haRestEnd(action)
|
|
163649
|
+
);
|
|
163063
163650
|
const actions = this.env.get(HomeAssistantActions);
|
|
163064
163651
|
const st = getCoverDebounce(this.endpoint);
|
|
163065
163652
|
st.pendingLift = { action, entityId, actions };
|
|
@@ -163092,21 +163679,27 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163092
163679
|
const st = getCoverDebounce(this.endpoint);
|
|
163093
163680
|
clearPendingTilt(st);
|
|
163094
163681
|
if (st.pendingLift?.action.action.includes("tilt")) clearPendingLift(st);
|
|
163095
|
-
this.
|
|
163096
|
-
|
|
163097
|
-
|
|
163098
|
-
|
|
163682
|
+
const action = this.state.config.openCoverTilt(void 0, this.agent);
|
|
163683
|
+
this.startOptimisticMovement(
|
|
163684
|
+
"tilt",
|
|
163685
|
+
MovementStatus.Opening,
|
|
163686
|
+
haRestEnd(action)
|
|
163099
163687
|
);
|
|
163688
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163689
|
+
homeAssistant.callAction(action);
|
|
163100
163690
|
}
|
|
163101
163691
|
handleTiltClose() {
|
|
163102
163692
|
const st = getCoverDebounce(this.endpoint);
|
|
163103
163693
|
clearPendingTilt(st);
|
|
163104
163694
|
if (st.pendingLift?.action.action.includes("tilt")) clearPendingLift(st);
|
|
163105
|
-
this.
|
|
163106
|
-
|
|
163107
|
-
|
|
163108
|
-
|
|
163695
|
+
const action = this.state.config.closeCoverTilt(void 0, this.agent);
|
|
163696
|
+
this.startOptimisticMovement(
|
|
163697
|
+
"tilt",
|
|
163698
|
+
MovementStatus.Closing,
|
|
163699
|
+
haRestEnd(action)
|
|
163109
163700
|
);
|
|
163701
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163702
|
+
homeAssistant.callAction(action);
|
|
163110
163703
|
}
|
|
163111
163704
|
handleGoToTiltPosition(targetPercent100ths) {
|
|
163112
163705
|
const config11 = this.state.config;
|
|
@@ -163115,14 +163708,15 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163115
163708
|
return;
|
|
163116
163709
|
}
|
|
163117
163710
|
this.state.targetPositionTiltPercent100ths = targetPercent100ths;
|
|
163118
|
-
this.startOptimisticMovement(
|
|
163119
|
-
"tilt",
|
|
163120
|
-
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing
|
|
163121
|
-
);
|
|
163122
163711
|
const targetPosition = targetPercent100ths / 100;
|
|
163123
163712
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163124
163713
|
const action = config11.setTiltPosition(targetPosition, this.agent);
|
|
163125
163714
|
const entityId = homeAssistant.entityId;
|
|
163715
|
+
this.startOptimisticMovement(
|
|
163716
|
+
"tilt",
|
|
163717
|
+
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing,
|
|
163718
|
+
haRestEnd(action)
|
|
163719
|
+
);
|
|
163126
163720
|
const actions = this.env.get(HomeAssistantActions);
|
|
163127
163721
|
const st = getCoverDebounce(this.endpoint);
|
|
163128
163722
|
st.pendingTilt = { action, entityId, actions };
|
|
@@ -163264,6 +163858,7 @@ var adjustPositionForWriting2 = (position, agent) => {
|
|
|
163264
163858
|
const matterSem = usesMatterSemantics(agent);
|
|
163265
163859
|
return adjustPositionForWriting(position, featureFlags, matterSem);
|
|
163266
163860
|
};
|
|
163861
|
+
var expectedRestPosition = (end, agent) => adjustPositionForReading2(end === "open" ? 100 : 0, agent);
|
|
163267
163862
|
var shouldSwapOpenClose = (agent) => {
|
|
163268
163863
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
163269
163864
|
const entitySwap = homeAssistant.state.mapping?.coverSwapOpenClose;
|
|
@@ -163351,11 +163946,11 @@ var config6 = {
|
|
|
163351
163946
|
}
|
|
163352
163947
|
return position == null ? null : adjustPositionForReading2(position, agent);
|
|
163353
163948
|
},
|
|
163949
|
+
getExpectedRestPosition: expectedRestPosition,
|
|
163354
163950
|
getCoverType: (entity) => deviceClassMapping(entity)?.type,
|
|
163355
163951
|
getEndProductType: (entity) => deviceClassMapping(entity)?.endProductType,
|
|
163356
163952
|
getMovementStatus: (entity, agent) => {
|
|
163357
|
-
const
|
|
163358
|
-
const swapped = featureFlags?.coverSwapOpenClose === true;
|
|
163953
|
+
const swapped = shouldSwapOpenClose(agent);
|
|
163359
163954
|
const coverState = entity.state;
|
|
163360
163955
|
if (coverState === CoverDeviceState.opening) {
|
|
163361
163956
|
return swapped ? WindowCovering3.MovementStatus.Closing : WindowCovering3.MovementStatus.Opening;
|
|
@@ -167397,6 +167992,284 @@ var ElectricalMeterType = ElectricalMeterDevice.with(
|
|
|
167397
167992
|
EnergyServer
|
|
167398
167993
|
);
|
|
167399
167994
|
|
|
167995
|
+
// src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
|
|
167996
|
+
init_esm();
|
|
167997
|
+
init_home_assistant_actions();
|
|
167998
|
+
init_home_assistant_entity_behavior();
|
|
167999
|
+
|
|
168000
|
+
// src/matter/endpoints/legacy/sensor/devices/evse-status.ts
|
|
168001
|
+
function mapEvseStatus(rawState, chargingSwitchOn) {
|
|
168002
|
+
const raw = rawState.toLowerCase().trim();
|
|
168003
|
+
const switchSupply = chargingSwitchOn ? EnergyEvse3.SupplyState.ChargingEnabled : EnergyEvse3.SupplyState.Disabled;
|
|
168004
|
+
if (raw === "" || raw === "unknown" || raw === "unavailable") {
|
|
168005
|
+
return {
|
|
168006
|
+
state: null,
|
|
168007
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168008
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168009
|
+
};
|
|
168010
|
+
}
|
|
168011
|
+
if (raw.includes("error") || raw.includes("fault")) {
|
|
168012
|
+
return {
|
|
168013
|
+
state: EnergyEvse3.State.Fault,
|
|
168014
|
+
supplyState: EnergyEvse3.SupplyState.DisabledError,
|
|
168015
|
+
faultState: EnergyEvse3.FaultState.Other
|
|
168016
|
+
};
|
|
168017
|
+
}
|
|
168018
|
+
if (raw.includes("not connected") || raw.includes("not_connected") || raw.includes("disconnected") || raw.includes("no vehicle")) {
|
|
168019
|
+
return {
|
|
168020
|
+
state: EnergyEvse3.State.NotPluggedIn,
|
|
168021
|
+
supplyState: switchSupply,
|
|
168022
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168023
|
+
};
|
|
168024
|
+
}
|
|
168025
|
+
if (raw.includes("not charging") || raw.includes("not_charging") || raw.includes("not-charging")) {
|
|
168026
|
+
return {
|
|
168027
|
+
state: EnergyEvse3.State.PluggedInNoDemand,
|
|
168028
|
+
supplyState: switchSupply,
|
|
168029
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168030
|
+
};
|
|
168031
|
+
}
|
|
168032
|
+
if (raw.includes("charg") && !raw.includes("discharg")) {
|
|
168033
|
+
return {
|
|
168034
|
+
state: EnergyEvse3.State.PluggedInCharging,
|
|
168035
|
+
supplyState: EnergyEvse3.SupplyState.ChargingEnabled,
|
|
168036
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168037
|
+
};
|
|
168038
|
+
}
|
|
168039
|
+
if (raw.includes("connected") || raw.includes("ready") || raw.includes("awaiting") || raw.includes("completed") || raw.includes("sleep") || raw.includes("paused")) {
|
|
168040
|
+
return {
|
|
168041
|
+
state: EnergyEvse3.State.PluggedInNoDemand,
|
|
168042
|
+
supplyState: switchSupply,
|
|
168043
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168044
|
+
};
|
|
168045
|
+
}
|
|
168046
|
+
return {
|
|
168047
|
+
state: null,
|
|
168048
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168049
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168050
|
+
};
|
|
168051
|
+
}
|
|
168052
|
+
|
|
168053
|
+
// src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
|
|
168054
|
+
var logger229 = Logger.get("EnergyEvse");
|
|
168055
|
+
var MIN_CHARGE_CURRENT_MA = 6e3;
|
|
168056
|
+
var MAX_CHARGE_CURRENT_MA = 32e3;
|
|
168057
|
+
var CIRCUIT_CAPACITY_MA = 32e3;
|
|
168058
|
+
var MAX_TIMER_MS = 2147483647;
|
|
168059
|
+
function clampMa(ma) {
|
|
168060
|
+
return Math.min(CIRCUIT_CAPACITY_MA, Math.max(MIN_CHARGE_CURRENT_MA, ma));
|
|
168061
|
+
}
|
|
168062
|
+
var evseChargingTimers = /* @__PURE__ */ new WeakMap();
|
|
168063
|
+
function clearEvseChargingTimer(endpoint) {
|
|
168064
|
+
const timer = evseChargingTimers.get(endpoint);
|
|
168065
|
+
if (timer) {
|
|
168066
|
+
clearTimeout(timer);
|
|
168067
|
+
evseChargingTimers.delete(endpoint);
|
|
168068
|
+
}
|
|
168069
|
+
}
|
|
168070
|
+
var EvseStatusServer = class _EvseStatusServer extends EnergyEvseServer {
|
|
168071
|
+
async initialize() {
|
|
168072
|
+
await super.initialize();
|
|
168073
|
+
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
168074
|
+
this.update();
|
|
168075
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
168076
|
+
}
|
|
168077
|
+
async [Symbol.asyncDispose]() {
|
|
168078
|
+
clearEvseChargingTimer(this.endpoint);
|
|
168079
|
+
await super[Symbol.asyncDispose]();
|
|
168080
|
+
}
|
|
168081
|
+
chargingSwitchIsOn() {
|
|
168082
|
+
const mapping = this.agent.get(HomeAssistantEntityBehavior).state.mapping;
|
|
168083
|
+
const switchEntity = mapping?.chargingSwitchEntity;
|
|
168084
|
+
if (!switchEntity) return false;
|
|
168085
|
+
const stateProvider = this.agent.env.get(EntityStateProvider);
|
|
168086
|
+
return stateProvider.getState(switchEntity)?.state === "on";
|
|
168087
|
+
}
|
|
168088
|
+
update() {
|
|
168089
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
168090
|
+
const raw = homeAssistant.entity.state?.state ?? "";
|
|
168091
|
+
const status3 = mapEvseStatus(raw, this.chargingSwitchIsOn());
|
|
168092
|
+
const patch = {
|
|
168093
|
+
state: status3.state,
|
|
168094
|
+
supplyState: status3.supplyState,
|
|
168095
|
+
faultState: status3.faultState
|
|
168096
|
+
};
|
|
168097
|
+
const mapping = homeAssistant.state.mapping;
|
|
168098
|
+
if (mapping?.currentLimitEntity) {
|
|
168099
|
+
const stateProvider = this.agent.env.get(EntityStateProvider);
|
|
168100
|
+
const amps = stateProvider.getNumericState(mapping.currentLimitEntity);
|
|
168101
|
+
if (amps != null) {
|
|
168102
|
+
patch.maximumChargeCurrent = clampMa(Math.round(amps * 1e3));
|
|
168103
|
+
}
|
|
168104
|
+
}
|
|
168105
|
+
applyPatchState(this.state, patch);
|
|
168106
|
+
}
|
|
168107
|
+
enableCharging(request) {
|
|
168108
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
168109
|
+
const mapping = homeAssistant.state.mapping;
|
|
168110
|
+
const until = request.chargingEnabledUntil;
|
|
168111
|
+
if (until != null && until * 1e3 <= Date.now()) {
|
|
168112
|
+
this.disable();
|
|
168113
|
+
return;
|
|
168114
|
+
}
|
|
168115
|
+
clearEvseChargingTimer(this.endpoint);
|
|
168116
|
+
let dispatched = false;
|
|
168117
|
+
if (mapping?.chargingSwitchEntity) {
|
|
168118
|
+
homeAssistant.callAction({
|
|
168119
|
+
action: "switch.turn_on",
|
|
168120
|
+
target: mapping.chargingSwitchEntity
|
|
168121
|
+
});
|
|
168122
|
+
dispatched = true;
|
|
168123
|
+
}
|
|
168124
|
+
const maxCurrent = request.maximumChargeCurrent;
|
|
168125
|
+
if (mapping?.currentLimitEntity && maxCurrent != null) {
|
|
168126
|
+
const amps = Math.max(
|
|
168127
|
+
MIN_CHARGE_CURRENT_MA / 1e3,
|
|
168128
|
+
Math.floor(clampMa(Number(maxCurrent)) / 1e3)
|
|
168129
|
+
);
|
|
168130
|
+
homeAssistant.callAction({
|
|
168131
|
+
action: "number.set_value",
|
|
168132
|
+
target: mapping.currentLimitEntity,
|
|
168133
|
+
data: { value: amps }
|
|
168134
|
+
});
|
|
168135
|
+
dispatched = true;
|
|
168136
|
+
}
|
|
168137
|
+
if (!dispatched) {
|
|
168138
|
+
logger229.debug(
|
|
168139
|
+
`enableCharging ignored for ${homeAssistant.entityId}: no charging switch or current limit mapped`
|
|
168140
|
+
);
|
|
168141
|
+
return;
|
|
168142
|
+
}
|
|
168143
|
+
const patch = {
|
|
168144
|
+
supplyState: EnergyEvse3.SupplyState.ChargingEnabled,
|
|
168145
|
+
chargingEnabledUntil: until ?? null
|
|
168146
|
+
};
|
|
168147
|
+
if (request.minimumChargeCurrent != null) {
|
|
168148
|
+
patch.minimumChargeCurrent = clampMa(
|
|
168149
|
+
Number(request.minimumChargeCurrent)
|
|
168150
|
+
);
|
|
168151
|
+
}
|
|
168152
|
+
if (maxCurrent != null) {
|
|
168153
|
+
patch.maximumChargeCurrent = clampMa(Number(maxCurrent));
|
|
168154
|
+
}
|
|
168155
|
+
applyPatchState(this.state, patch);
|
|
168156
|
+
if (until != null) {
|
|
168157
|
+
const delay = until * 1e3 - Date.now();
|
|
168158
|
+
if (delay > 0 && delay <= MAX_TIMER_MS) {
|
|
168159
|
+
const endpoint = this.endpoint;
|
|
168160
|
+
const actions = this.env.get(HomeAssistantActions);
|
|
168161
|
+
const entityId = homeAssistant.entityId;
|
|
168162
|
+
const switchEntity = mapping?.chargingSwitchEntity;
|
|
168163
|
+
const timer = setTimeout(() => {
|
|
168164
|
+
void (async () => {
|
|
168165
|
+
try {
|
|
168166
|
+
if (evseChargingTimers.get(endpoint) !== timer) return;
|
|
168167
|
+
if (switchEntity) {
|
|
168168
|
+
actions.call(
|
|
168169
|
+
{ action: "switch.turn_off", target: switchEntity },
|
|
168170
|
+
entityId
|
|
168171
|
+
);
|
|
168172
|
+
}
|
|
168173
|
+
await endpoint.setStateOf(_EvseStatusServer, {
|
|
168174
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168175
|
+
chargingEnabledUntil: null
|
|
168176
|
+
});
|
|
168177
|
+
} catch (error) {
|
|
168178
|
+
logger229.debug(
|
|
168179
|
+
`EVSE charge-window expiry write failed (endpoint may be closing): ${error}`
|
|
168180
|
+
);
|
|
168181
|
+
} finally {
|
|
168182
|
+
if (evseChargingTimers.get(endpoint) === timer) {
|
|
168183
|
+
evseChargingTimers.delete(endpoint);
|
|
168184
|
+
}
|
|
168185
|
+
}
|
|
168186
|
+
})();
|
|
168187
|
+
}, delay);
|
|
168188
|
+
timer.unref?.();
|
|
168189
|
+
evseChargingTimers.set(endpoint, timer);
|
|
168190
|
+
}
|
|
168191
|
+
}
|
|
168192
|
+
}
|
|
168193
|
+
disable() {
|
|
168194
|
+
clearEvseChargingTimer(this.endpoint);
|
|
168195
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
168196
|
+
const mapping = homeAssistant.state.mapping;
|
|
168197
|
+
if (mapping?.chargingSwitchEntity) {
|
|
168198
|
+
homeAssistant.callAction({
|
|
168199
|
+
action: "switch.turn_off",
|
|
168200
|
+
target: mapping.chargingSwitchEntity
|
|
168201
|
+
});
|
|
168202
|
+
}
|
|
168203
|
+
if (mapping?.chargingSwitchEntity || mapping?.currentLimitEntity) {
|
|
168204
|
+
applyPatchState(this.state, {
|
|
168205
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168206
|
+
chargingEnabledUntil: null
|
|
168207
|
+
});
|
|
168208
|
+
} else {
|
|
168209
|
+
logger229.debug(
|
|
168210
|
+
`disable ignored for ${homeAssistant.entityId}: no charging switch mapped`
|
|
168211
|
+
);
|
|
168212
|
+
}
|
|
168213
|
+
}
|
|
168214
|
+
};
|
|
168215
|
+
var EvseStatusSeeded = EvseStatusServer.set({
|
|
168216
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168217
|
+
faultState: EnergyEvse3.FaultState.NoError,
|
|
168218
|
+
circuitCapacity: CIRCUIT_CAPACITY_MA,
|
|
168219
|
+
minimumChargeCurrent: MIN_CHARGE_CURRENT_MA,
|
|
168220
|
+
maximumChargeCurrent: MAX_CHARGE_CURRENT_MA
|
|
168221
|
+
});
|
|
168222
|
+
var EvseModeSeeded = EnergyEvseModeServer.set({
|
|
168223
|
+
supportedModes: [
|
|
168224
|
+
{
|
|
168225
|
+
label: "Manual",
|
|
168226
|
+
mode: 1,
|
|
168227
|
+
modeTags: [{ value: EnergyEvseMode3.ModeTag.Manual }]
|
|
168228
|
+
}
|
|
168229
|
+
],
|
|
168230
|
+
currentMode: 1
|
|
168231
|
+
});
|
|
168232
|
+
var WiredPowerSourceBase = PowerSourceServer.with("Wired");
|
|
168233
|
+
var EvsePowerSourceServer = class extends WiredPowerSourceBase {
|
|
168234
|
+
async initialize() {
|
|
168235
|
+
await super.initialize();
|
|
168236
|
+
const endpointNumber = this.endpoint.number;
|
|
168237
|
+
if (endpointNumber != null) {
|
|
168238
|
+
applyPatchState(this.state, { endpointList: [endpointNumber] });
|
|
168239
|
+
}
|
|
168240
|
+
}
|
|
168241
|
+
};
|
|
168242
|
+
var EvsePowerSource = EvsePowerSourceServer.set({
|
|
168243
|
+
status: PowerSource3.PowerSourceStatus.Active,
|
|
168244
|
+
order: 0,
|
|
168245
|
+
description: "Mains",
|
|
168246
|
+
wiredCurrentType: PowerSource3.WiredCurrentType.Ac
|
|
168247
|
+
});
|
|
168248
|
+
var EnergyEvseBase = EnergyEvseDevice.with(
|
|
168249
|
+
BasicInformationServer2,
|
|
168250
|
+
IdentifyServer2,
|
|
168251
|
+
HomeAssistantEntityBehavior,
|
|
168252
|
+
EvseStatusSeeded,
|
|
168253
|
+
EvseModeSeeded,
|
|
168254
|
+
EvsePowerSource,
|
|
168255
|
+
// Always mount the measurement clusters; both seed safe defaults when no
|
|
168256
|
+
// power/energy entity is mapped, so the endpoint honestly carries the
|
|
168257
|
+
// ElectricalSensor device type.
|
|
168258
|
+
PowerServer,
|
|
168259
|
+
EnergyServer,
|
|
168260
|
+
DescriptorServer
|
|
168261
|
+
).set({
|
|
168262
|
+
descriptor: {
|
|
168263
|
+
deviceTypeList: [
|
|
168264
|
+
{ deviceType: DeviceTypeId(1292), revision: 2 },
|
|
168265
|
+
{ deviceType: DeviceTypeId(1296), revision: 1 }
|
|
168266
|
+
]
|
|
168267
|
+
}
|
|
168268
|
+
});
|
|
168269
|
+
function energyEvseType() {
|
|
168270
|
+
return EnergyEvseBase;
|
|
168271
|
+
}
|
|
168272
|
+
|
|
167400
168273
|
// src/matter/behaviors/flow-measurement-server.ts
|
|
167401
168274
|
init_home_assistant_entity_behavior();
|
|
167402
168275
|
var FlowMeasurementServerBase = class extends FlowMeasurementServer {
|
|
@@ -168136,7 +169009,7 @@ var TvocConcentrationMeasurementServer = class extends TvocConcentrationMeasurem
|
|
|
168136
169009
|
};
|
|
168137
169010
|
|
|
168138
169011
|
// src/matter/endpoints/legacy/sensor/devices/tvoc-sensor.ts
|
|
168139
|
-
var
|
|
169012
|
+
var logger230 = Logger.get("TvocSensor");
|
|
168140
169013
|
function airQualityFromUgm3(value) {
|
|
168141
169014
|
if (value <= 300) return AirQuality3.AirQualityEnum.Good;
|
|
168142
169015
|
if (value <= 1e3) return AirQuality3.AirQualityEnum.Fair;
|
|
@@ -168177,17 +169050,17 @@ var TvocAirQualityServer = class extends TvocAirQualityServerBase {
|
|
|
168177
169050
|
const attributes9 = entity.state.attributes;
|
|
168178
169051
|
const deviceClass = attributes9.device_class;
|
|
168179
169052
|
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
168180
|
-
|
|
169053
|
+
logger230.debug(
|
|
168181
169054
|
`[${entity.entity_id}] TVOC update: state="${state}", device_class="${deviceClass}"`
|
|
168182
169055
|
);
|
|
168183
169056
|
if (state != null && !Number.isNaN(+state)) {
|
|
168184
169057
|
const value = +state;
|
|
168185
169058
|
airQuality = deviceClass === SensorDeviceClass.volatile_organic_compounds ? airQualityFromUgm3(value) : airQualityFromPpb(value);
|
|
168186
|
-
|
|
169059
|
+
logger230.debug(
|
|
168187
169060
|
`[${entity.entity_id}] TVOC value=${value} (${deviceClass}) -> airQuality=${AirQuality3.AirQualityEnum[airQuality]}`
|
|
168188
169061
|
);
|
|
168189
169062
|
} else {
|
|
168190
|
-
|
|
169063
|
+
logger230.warn(
|
|
168191
169064
|
`[${entity.entity_id}] TVOC state not a valid number: "${state}"`
|
|
168192
169065
|
);
|
|
168193
169066
|
}
|
|
@@ -168401,7 +169274,7 @@ init_home_assistant_entity_behavior();
|
|
|
168401
169274
|
// src/matter/behaviors/pm25-concentration-measurement-server.ts
|
|
168402
169275
|
init_esm();
|
|
168403
169276
|
init_home_assistant_entity_behavior();
|
|
168404
|
-
var
|
|
169277
|
+
var logger231 = Logger.get("Pm25ConcentrationMeasurementServer");
|
|
168405
169278
|
var Pm25ConcentrationMeasurementServerBase = Pm25ConcentrationMeasurementServer.with(
|
|
168406
169279
|
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
168407
169280
|
);
|
|
@@ -168425,11 +169298,11 @@ var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasure
|
|
|
168425
169298
|
if (this.state.measurementMedium === void 0) {
|
|
168426
169299
|
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
168427
169300
|
}
|
|
168428
|
-
|
|
169301
|
+
logger231.debug(
|
|
168429
169302
|
"Pm25ConcentrationMeasurementServer: before super.initialize()"
|
|
168430
169303
|
);
|
|
168431
169304
|
await super.initialize();
|
|
168432
|
-
|
|
169305
|
+
logger231.debug(
|
|
168433
169306
|
"Pm25ConcentrationMeasurementServer: after super.initialize()"
|
|
168434
169307
|
);
|
|
168435
169308
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
@@ -168452,7 +169325,7 @@ var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasure
|
|
|
168452
169325
|
};
|
|
168453
169326
|
|
|
168454
169327
|
// src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
|
|
168455
|
-
var
|
|
169328
|
+
var logger232 = Logger.get("Pm25AirQualityServer");
|
|
168456
169329
|
var Pm25AirQualityServerBase = AirQualityServer.with(
|
|
168457
169330
|
AirQuality3.Feature.Fair,
|
|
168458
169331
|
AirQuality3.Feature.Moderate,
|
|
@@ -168464,9 +169337,9 @@ var Pm25AirQualityServer = class extends Pm25AirQualityServerBase {
|
|
|
168464
169337
|
if (this.state.airQuality === void 0) {
|
|
168465
169338
|
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
168466
169339
|
}
|
|
168467
|
-
|
|
169340
|
+
logger232.debug("Pm25AirQualityServer: before super.initialize()");
|
|
168468
169341
|
await super.initialize();
|
|
168469
|
-
|
|
169342
|
+
logger232.debug("Pm25AirQualityServer: after super.initialize()");
|
|
168470
169343
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
168471
169344
|
this.update(homeAssistant.entity);
|
|
168472
169345
|
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
@@ -168891,7 +169764,7 @@ init_home_assistant_entity_behavior();
|
|
|
168891
169764
|
init_dist();
|
|
168892
169765
|
init_esm();
|
|
168893
169766
|
init_home_assistant_entity_behavior();
|
|
168894
|
-
var
|
|
169767
|
+
var logger233 = Logger.get("VacuumIdentifyServer");
|
|
168895
169768
|
var IDENTIFY_BUTTON_SUFFIXES = ["_identify", "_locate", "_find_me"];
|
|
168896
169769
|
var VacuumIdentifyServer = class extends IdentifyServer2 {
|
|
168897
169770
|
triggerEffect(effect) {
|
|
@@ -168908,19 +169781,19 @@ var VacuumIdentifyServer = class extends IdentifyServer2 {
|
|
|
168908
169781
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
168909
169782
|
const features3 = homeAssistant.entity.state.attributes.supported_features ?? 0;
|
|
168910
169783
|
if (testBit(features3, VacuumDeviceFeature.LOCATE)) {
|
|
168911
|
-
|
|
169784
|
+
logger233.info(`${source} \u2192 vacuum.locate for ${homeAssistant.entityId}`);
|
|
168912
169785
|
homeAssistant.callAction({ action: "vacuum.locate" });
|
|
168913
169786
|
return;
|
|
168914
169787
|
}
|
|
168915
169788
|
const sibling = this.#findIdentifyButton(homeAssistant);
|
|
168916
169789
|
if (sibling) {
|
|
168917
|
-
|
|
169790
|
+
logger233.info(
|
|
168918
169791
|
`${source} \u2192 button.press ${sibling} for ${homeAssistant.entityId}`
|
|
168919
169792
|
);
|
|
168920
169793
|
homeAssistant.callAction({ action: "button.press", target: sibling });
|
|
168921
169794
|
return;
|
|
168922
169795
|
}
|
|
168923
|
-
|
|
169796
|
+
logger233.warn(
|
|
168924
169797
|
`${source} for ${homeAssistant.entityId}, LOCATE not in supported_features (${features3}), trying vacuum.locate anyway`
|
|
168925
169798
|
);
|
|
168926
169799
|
homeAssistant.callAction({ action: "vacuum.locate" });
|
|
@@ -168928,8 +169801,8 @@ var VacuumIdentifyServer = class extends IdentifyServer2 {
|
|
|
168928
169801
|
#findIdentifyButton(homeAssistant) {
|
|
168929
169802
|
const deviceId = homeAssistant.entity.registry?.device_id;
|
|
168930
169803
|
if (!deviceId) return void 0;
|
|
168931
|
-
const
|
|
168932
|
-
for (const entity of Object.values(
|
|
169804
|
+
const registry3 = this.env.get(HomeAssistantRegistry);
|
|
169805
|
+
for (const entity of Object.values(registry3.entities)) {
|
|
168933
169806
|
if (entity.device_id !== deviceId) continue;
|
|
168934
169807
|
if (!entity.entity_id.startsWith("button.")) continue;
|
|
168935
169808
|
const uniqueId = entity.unique_id ?? "";
|
|
@@ -169119,14 +169992,14 @@ init_esm();
|
|
|
169119
169992
|
|
|
169120
169993
|
// src/matter/behaviors/service-area-server.ts
|
|
169121
169994
|
init_esm();
|
|
169122
|
-
var
|
|
169995
|
+
var logger234 = Logger.get("ServiceAreaServer");
|
|
169123
169996
|
var ServiceAreaWithProgress = ServiceAreaBehavior.with(
|
|
169124
169997
|
ServiceArea3.Feature.ProgressReporting
|
|
169125
169998
|
);
|
|
169126
169999
|
var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
|
|
169127
170000
|
selectAreas(request) {
|
|
169128
170001
|
const { newAreas } = request;
|
|
169129
|
-
|
|
170002
|
+
logger234.info(
|
|
169130
170003
|
`ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
|
|
169131
170004
|
);
|
|
169132
170005
|
const uniqueAreas = [...new Set(newAreas)];
|
|
@@ -169135,7 +170008,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
|
|
|
169135
170008
|
(id) => !supportedAreaIds.includes(id)
|
|
169136
170009
|
);
|
|
169137
170010
|
if (invalidAreas.length > 0) {
|
|
169138
|
-
|
|
170011
|
+
logger234.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
|
|
169139
170012
|
return {
|
|
169140
170013
|
status: ServiceArea3.SelectAreasStatus.UnsupportedArea,
|
|
169141
170014
|
statusText: `Invalid area IDs: ${invalidAreas.join(", ")}`
|
|
@@ -169147,7 +170020,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
|
|
|
169147
170020
|
status: ServiceArea3.OperationalStatus.Pending
|
|
169148
170021
|
}));
|
|
169149
170022
|
this.state.currentArea = null;
|
|
169150
|
-
|
|
170023
|
+
logger234.info(
|
|
169151
170024
|
`ServiceArea: Stored ${uniqueAreas.length} areas for cleaning: ${uniqueAreas.join(", ")}`
|
|
169152
170025
|
);
|
|
169153
170026
|
return {
|
|
@@ -169168,7 +170041,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
|
|
|
169168
170041
|
ServiceAreaServerBase2.State = State;
|
|
169169
170042
|
})(ServiceAreaServerBase || (ServiceAreaServerBase = {}));
|
|
169170
170043
|
function ServiceAreaServer2(initialState) {
|
|
169171
|
-
|
|
170044
|
+
logger234.info(
|
|
169172
170045
|
`Creating ServiceAreaServer with ${initialState.supportedAreas.length} areas`
|
|
169173
170046
|
);
|
|
169174
170047
|
return ServiceAreaServerBase.set({
|
|
@@ -169185,7 +170058,7 @@ var ServiceAreaWithMapsAndProgress = ServiceAreaBehavior.with(
|
|
|
169185
170058
|
var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress {
|
|
169186
170059
|
selectAreas(request) {
|
|
169187
170060
|
const { newAreas } = request;
|
|
169188
|
-
|
|
170061
|
+
logger234.info(
|
|
169189
170062
|
`ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
|
|
169190
170063
|
);
|
|
169191
170064
|
const uniqueAreas = [...new Set(newAreas)];
|
|
@@ -169194,7 +170067,7 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
|
|
|
169194
170067
|
(id) => !supportedAreaIds.includes(id)
|
|
169195
170068
|
);
|
|
169196
170069
|
if (invalidAreas.length > 0) {
|
|
169197
|
-
|
|
170070
|
+
logger234.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
|
|
169198
170071
|
return {
|
|
169199
170072
|
status: ServiceArea3.SelectAreasStatus.UnsupportedArea,
|
|
169200
170073
|
statusText: `Invalid area IDs: ${invalidAreas.join(", ")}`
|
|
@@ -169206,7 +170079,7 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
|
|
|
169206
170079
|
status: ServiceArea3.OperationalStatus.Pending
|
|
169207
170080
|
}));
|
|
169208
170081
|
this.state.currentArea = null;
|
|
169209
|
-
|
|
170082
|
+
logger234.info(
|
|
169210
170083
|
`ServiceArea: Stored ${uniqueAreas.length} areas for cleaning: ${uniqueAreas.join(", ")}`
|
|
169211
170084
|
);
|
|
169212
170085
|
return {
|
|
@@ -169227,14 +170100,14 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
|
|
|
169227
170100
|
ServiceAreaServerWithMapsBase2.State = State;
|
|
169228
170101
|
})(ServiceAreaServerWithMapsBase || (ServiceAreaServerWithMapsBase = {}));
|
|
169229
170102
|
function ServiceAreaServerWithMaps(initialState) {
|
|
169230
|
-
|
|
170103
|
+
logger234.info(
|
|
169231
170104
|
`Creating ServiceAreaServer with Maps: ${initialState.supportedAreas.length} areas, ${initialState.supportedMaps.length} maps`
|
|
169232
170105
|
);
|
|
169233
170106
|
for (const map of initialState.supportedMaps) {
|
|
169234
170107
|
const areaCount = initialState.supportedAreas.filter(
|
|
169235
170108
|
(a) => a.mapId === map.mapId
|
|
169236
170109
|
).length;
|
|
169237
|
-
|
|
170110
|
+
logger234.info(` Map ${map.mapId}: "${map.name}" (${areaCount} areas)`);
|
|
169238
170111
|
}
|
|
169239
170112
|
return ServiceAreaServerWithMapsBase.set({
|
|
169240
170113
|
supportedAreas: initialState.supportedAreas,
|
|
@@ -169246,7 +170119,7 @@ function ServiceAreaServerWithMaps(initialState) {
|
|
|
169246
170119
|
}
|
|
169247
170120
|
|
|
169248
170121
|
// src/matter/endpoints/legacy/vacuum/behaviors/vacuum-service-area-server.ts
|
|
169249
|
-
var
|
|
170122
|
+
var logger235 = Logger.get("VacuumServiceAreaServer");
|
|
169250
170123
|
function toAreaId(roomId) {
|
|
169251
170124
|
if (typeof roomId === "number") {
|
|
169252
170125
|
return roomId;
|
|
@@ -169325,13 +170198,13 @@ function createVacuumServiceAreaServer(attributes9, roomEntities, includeUnnamed
|
|
|
169325
170198
|
let rooms;
|
|
169326
170199
|
if (roomEntities && roomEntities.length > 0) {
|
|
169327
170200
|
rooms = buttonEntitiesToRooms(roomEntities, attributes9);
|
|
169328
|
-
|
|
170201
|
+
logger235.info(
|
|
169329
170202
|
`Using ${rooms.length} button entities as rooms: ${rooms.map((r) => r.name).join(", ")}`
|
|
169330
170203
|
);
|
|
169331
170204
|
} else {
|
|
169332
170205
|
rooms = parseVacuumRooms(attributes9, includeUnnamedRooms);
|
|
169333
170206
|
if (rooms.length > 0) {
|
|
169334
|
-
|
|
170207
|
+
logger235.info(
|
|
169335
170208
|
`Using ${rooms.length} rooms from attributes: ${rooms.map((r) => r.name).join(", ")}`
|
|
169336
170209
|
);
|
|
169337
170210
|
}
|
|
@@ -169385,7 +170258,7 @@ function createCustomServiceAreaServer(customAreas) {
|
|
|
169385
170258
|
landmarkInfo: null
|
|
169386
170259
|
}
|
|
169387
170260
|
}));
|
|
169388
|
-
|
|
170261
|
+
logger235.info(
|
|
169389
170262
|
`Using ${customAreas.length} custom service areas: ${customAreas.map((a) => a.name).join(", ")}`
|
|
169390
170263
|
);
|
|
169391
170264
|
return ServiceAreaServer2({
|
|
@@ -169407,7 +170280,7 @@ function createCleanAreaServiceAreaServer(cleanAreaRooms) {
|
|
|
169407
170280
|
landmarkInfo: null
|
|
169408
170281
|
}
|
|
169409
170282
|
}));
|
|
169410
|
-
|
|
170283
|
+
logger235.info(
|
|
169411
170284
|
`Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA: ${cleanAreaRooms.map((r) => r.name).join(", ")}`
|
|
169412
170285
|
);
|
|
169413
170286
|
return ServiceAreaServer2({
|
|
@@ -169418,11 +170291,11 @@ function createCleanAreaServiceAreaServer(cleanAreaRooms) {
|
|
|
169418
170291
|
}
|
|
169419
170292
|
|
|
169420
170293
|
// src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-run-mode-server.ts
|
|
169421
|
-
var
|
|
170294
|
+
var logger236 = Logger.get("VacuumRvcRunModeServer");
|
|
169422
170295
|
function buildValetudoSegmentAction(vacuumEntityId, segmentIds, valetudoIdentifier) {
|
|
169423
170296
|
const identifier = valetudoIdentifier || vacuumEntityId.replace(/^vacuum\.valetudo_/, "");
|
|
169424
170297
|
const topic = `valetudo/${identifier}/MapSegmentationCapability/clean/set`;
|
|
169425
|
-
|
|
170298
|
+
logger236.info(
|
|
169426
170299
|
`Valetudo: mqtt.publish to ${topic}, segments: ${segmentIds.join(", ")}`
|
|
169427
170300
|
);
|
|
169428
170301
|
return {
|
|
@@ -169527,14 +170400,14 @@ function mergeBatchData(areas) {
|
|
|
169527
170400
|
function handleCustomServiceAreas(selectedAreas, customAreas, session) {
|
|
169528
170401
|
const matched = selectedAreas.map((areaId) => ({ areaId, area: customAreas[areaId - 1] })).filter((m) => !!m.area);
|
|
169529
170402
|
if (matched.length === 0) {
|
|
169530
|
-
|
|
170403
|
+
logger236.warn(
|
|
169531
170404
|
`Custom service areas: no match for selected IDs ${selectedAreas.join(", ")}`
|
|
169532
170405
|
);
|
|
169533
170406
|
return { action: "vacuum.start" };
|
|
169534
170407
|
}
|
|
169535
170408
|
const batchArea = matched.find(({ area }) => area.batchDispatch === true);
|
|
169536
170409
|
if (batchArea) {
|
|
169537
|
-
|
|
170410
|
+
logger236.info(
|
|
169538
170411
|
`Custom service areas (batch): single call for ${matched.length} room(s): ${matched.map(({ area }) => area.name).join(", ")}`
|
|
169539
170412
|
);
|
|
169540
170413
|
session.pendingDispatches = [];
|
|
@@ -169555,7 +170428,7 @@ function handleCustomServiceAreas(selectedAreas, customAreas, session) {
|
|
|
169555
170428
|
}
|
|
169556
170429
|
};
|
|
169557
170430
|
}
|
|
169558
|
-
|
|
170431
|
+
logger236.info(
|
|
169559
170432
|
`Custom service areas: ${matched.length} room(s) queued: ${matched.map(({ area }) => `${area.service} (${area.name})`).join(", ")}`
|
|
169560
170433
|
);
|
|
169561
170434
|
session.pendingDispatches = matched.slice(1).map(({ areaId, area }) => ({
|
|
@@ -169579,20 +170452,22 @@ function resolveCleanAreaIds(selectedAreas, cleanAreaRooms) {
|
|
|
169579
170452
|
}
|
|
169580
170453
|
return haAreaIds;
|
|
169581
170454
|
}
|
|
170455
|
+
var cleaningStates = [
|
|
170456
|
+
VacuumState.cleaning,
|
|
170457
|
+
VacuumState.segment_cleaning,
|
|
170458
|
+
VacuumState.zone_cleaning,
|
|
170459
|
+
VacuumState.spot_cleaning,
|
|
170460
|
+
VacuumState.mop_cleaning,
|
|
170461
|
+
VacuumState.paused
|
|
170462
|
+
];
|
|
170463
|
+
function vacuumIsCleaning(state) {
|
|
170464
|
+
return state != null && cleaningStates.includes(state);
|
|
170465
|
+
}
|
|
169582
170466
|
var vacuumRvcRunModeConfig = {
|
|
169583
170467
|
getCurrentMode: (entity) => {
|
|
169584
|
-
const
|
|
169585
|
-
|
|
169586
|
-
|
|
169587
|
-
VacuumState.segment_cleaning,
|
|
169588
|
-
VacuumState.zone_cleaning,
|
|
169589
|
-
VacuumState.spot_cleaning,
|
|
169590
|
-
VacuumState.mop_cleaning,
|
|
169591
|
-
VacuumState.paused
|
|
169592
|
-
];
|
|
169593
|
-
const isCleaning = cleaningStates.includes(state);
|
|
169594
|
-
logger235.debug(
|
|
169595
|
-
`Vacuum state: "${state}", isCleaning: ${isCleaning}, currentMode: ${isCleaning ? "Cleaning" : "Idle"}`
|
|
170468
|
+
const isCleaning = vacuumIsCleaning(entity.state);
|
|
170469
|
+
logger236.debug(
|
|
170470
|
+
`Vacuum state: "${entity.state}", isCleaning: ${isCleaning}, currentMode: ${isCleaning ? "Cleaning" : "Idle"}`
|
|
169596
170471
|
);
|
|
169597
170472
|
return isCleaning ? 1 /* Cleaning */ : 0 /* Idle */;
|
|
169598
170473
|
},
|
|
@@ -169625,7 +170500,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169625
170500
|
if (cleanAreaRooms && cleanAreaRooms.length > 0) {
|
|
169626
170501
|
const haAreaIds = resolveCleanAreaIds(selectedAreas, cleanAreaRooms);
|
|
169627
170502
|
if (haAreaIds.length > 0) {
|
|
169628
|
-
|
|
170503
|
+
logger236.info(
|
|
169629
170504
|
`CLEAN_AREA: cleaning HA areas: ${haAreaIds.join(", ")}`
|
|
169630
170505
|
);
|
|
169631
170506
|
return {
|
|
@@ -169644,7 +170519,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169644
170519
|
}
|
|
169645
170520
|
}
|
|
169646
170521
|
if (matched.length > 0) {
|
|
169647
|
-
|
|
170522
|
+
logger236.info(
|
|
169648
170523
|
`Roborock: ${matched.length} room button(s) queued: ${matched.map((m) => m.entityId).join(", ")}`
|
|
169649
170524
|
);
|
|
169650
170525
|
session.pendingDispatches = matched.slice(1).map(({ areaId, entityId }) => ({
|
|
@@ -169678,14 +170553,14 @@ var vacuumRvcRunModeConfig = {
|
|
|
169678
170553
|
}
|
|
169679
170554
|
}
|
|
169680
170555
|
if (roomIds.length > 0) {
|
|
169681
|
-
|
|
170556
|
+
logger236.info(
|
|
169682
170557
|
`Starting cleaning with selected areas: ${roomIds.join(", ")}`
|
|
169683
170558
|
);
|
|
169684
170559
|
if (isDreameVacuum(attributes9)) {
|
|
169685
170560
|
if (targetMapName) {
|
|
169686
170561
|
const vacName = vacuumEntityId.replace("vacuum.", "");
|
|
169687
170562
|
const selectedMapEntity = `select.${vacName}_selected_map`;
|
|
169688
|
-
|
|
170563
|
+
logger236.info(
|
|
169689
170564
|
`Dreame multi-floor: switching to map "${targetMapName}" via ${selectedMapEntity}`
|
|
169690
170565
|
);
|
|
169691
170566
|
homeAssistant.callAction({
|
|
@@ -169712,7 +170587,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169712
170587
|
}
|
|
169713
170588
|
if (isEcovacsVacuum(attributes9)) {
|
|
169714
170589
|
const roomIdStr = roomIds.join(",");
|
|
169715
|
-
|
|
170590
|
+
logger236.info(
|
|
169716
170591
|
`Ecovacs vacuum: Using spot_area for rooms: ${roomIdStr}`
|
|
169717
170592
|
);
|
|
169718
170593
|
return {
|
|
@@ -169727,14 +170602,14 @@ var vacuumRvcRunModeConfig = {
|
|
|
169727
170602
|
}
|
|
169728
170603
|
};
|
|
169729
170604
|
}
|
|
169730
|
-
|
|
170605
|
+
logger236.warn(
|
|
169731
170606
|
`Room cleaning via send_command not supported for this vacuum type. Rooms: ${roomIds.join(", ")}. Falling back to vacuum.start`
|
|
169732
170607
|
);
|
|
169733
170608
|
}
|
|
169734
170609
|
}
|
|
169735
170610
|
} catch {
|
|
169736
170611
|
}
|
|
169737
|
-
|
|
170612
|
+
logger236.info("Starting regular cleaning (no areas selected)");
|
|
169738
170613
|
return { action: "vacuum.start" };
|
|
169739
170614
|
},
|
|
169740
170615
|
returnToBase: () => ({ action: "vacuum.return_to_base" }),
|
|
@@ -169749,7 +170624,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169749
170624
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
169750
170625
|
const entity = homeAssistant.entity;
|
|
169751
170626
|
const attributes9 = entity.state.attributes;
|
|
169752
|
-
|
|
170627
|
+
logger236.info(`cleanRoom called: roomMode=${roomMode}`);
|
|
169753
170628
|
const cleanAreaRooms = homeAssistant.state.mapping?.cleanAreaRooms;
|
|
169754
170629
|
if (cleanAreaRooms && cleanAreaRooms.length > 0) {
|
|
169755
170630
|
const sorted = [...cleanAreaRooms].sort(
|
|
@@ -169758,7 +170633,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169758
170633
|
const areaIndex = roomMode - ROOM_MODE_BASE2 - 1;
|
|
169759
170634
|
if (areaIndex >= 0 && areaIndex < sorted.length) {
|
|
169760
170635
|
const area = sorted[areaIndex];
|
|
169761
|
-
|
|
170636
|
+
logger236.info(
|
|
169762
170637
|
`cleanRoom: CLEAN_AREA "${area.name}" \u2192 vacuum.clean_area(${area.haAreaId})`
|
|
169763
170638
|
);
|
|
169764
170639
|
return {
|
|
@@ -169775,7 +170650,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169775
170650
|
const areaIndex = roomMode - ROOM_MODE_BASE2 - 1;
|
|
169776
170651
|
if (areaIndex >= 0 && areaIndex < sorted.length) {
|
|
169777
170652
|
const area = sorted[areaIndex];
|
|
169778
|
-
|
|
170653
|
+
logger236.info(
|
|
169779
170654
|
`cleanRoom: custom service area "${area.name}" \u2192 ${area.service}`
|
|
169780
170655
|
);
|
|
169781
170656
|
return {
|
|
@@ -169796,7 +170671,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169796
170671
|
}
|
|
169797
170672
|
const rooms = parseVacuumRooms(attributes9);
|
|
169798
170673
|
const numericIdFromMode = getRoomIdFromMode(roomMode);
|
|
169799
|
-
|
|
170674
|
+
logger236.info(
|
|
169800
170675
|
`cleanRoom: numericIdFromMode=${numericIdFromMode}, available rooms: ${JSON.stringify(rooms.map((r) => ({ id: r.id, name: r.name, modeValue: getRoomModeValue(r) })))}`
|
|
169801
170676
|
);
|
|
169802
170677
|
const room = rooms.find((r) => getRoomModeValue(r) === roomMode);
|
|
@@ -169806,7 +170681,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169806
170681
|
if (room.mapName) {
|
|
169807
170682
|
const vacuumName = vacuumEntityId.replace("vacuum.", "");
|
|
169808
170683
|
const selectedMapEntity = `select.${vacuumName}_selected_map`;
|
|
169809
|
-
|
|
170684
|
+
logger236.info(
|
|
169810
170685
|
`Dreame multi-floor: switching to map "${room.mapName}" via ${selectedMapEntity}`
|
|
169811
170686
|
);
|
|
169812
170687
|
homeAssistant.callAction({
|
|
@@ -169815,7 +170690,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169815
170690
|
data: { option: room.mapName }
|
|
169816
170691
|
});
|
|
169817
170692
|
}
|
|
169818
|
-
|
|
170693
|
+
logger236.debug(
|
|
169819
170694
|
`Dreame vacuum detected, using dreame_vacuum.vacuum_clean_segment for room ${room.name} (commandId: ${commandId3}, id: ${room.id})`
|
|
169820
170695
|
);
|
|
169821
170696
|
return {
|
|
@@ -169826,7 +170701,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169826
170701
|
};
|
|
169827
170702
|
}
|
|
169828
170703
|
if (isRoborockVacuum(attributes9) || isXiaomiMiotVacuum(attributes9)) {
|
|
169829
|
-
|
|
170704
|
+
logger236.debug(
|
|
169830
170705
|
`Using vacuum.send_command with app_segment_clean for room ${room.name} (commandId: ${commandId3}, id: ${room.id})`
|
|
169831
170706
|
);
|
|
169832
170707
|
return {
|
|
@@ -169839,7 +170714,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169839
170714
|
}
|
|
169840
170715
|
if (isEcovacsVacuum(attributes9)) {
|
|
169841
170716
|
const roomIdStr = String(commandId3);
|
|
169842
|
-
|
|
170717
|
+
logger236.info(
|
|
169843
170718
|
`Ecovacs vacuum: Using spot_area for room ${room.name} (id: ${roomIdStr})`
|
|
169844
170719
|
);
|
|
169845
170720
|
return {
|
|
@@ -169854,7 +170729,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169854
170729
|
}
|
|
169855
170730
|
};
|
|
169856
170731
|
}
|
|
169857
|
-
|
|
170732
|
+
logger236.warn(
|
|
169858
170733
|
`Room cleaning via send_command not supported for this vacuum type. Room: ${room.name} (id=${commandId3}). Falling back to vacuum.start`
|
|
169859
170734
|
);
|
|
169860
170735
|
}
|
|
@@ -169871,20 +170746,20 @@ function createVacuumRvcRunModeServer(attributes9, includeUnnamedRooms = false,
|
|
|
169871
170746
|
customAreas,
|
|
169872
170747
|
disableRoomModes
|
|
169873
170748
|
);
|
|
169874
|
-
|
|
170749
|
+
logger236.info(
|
|
169875
170750
|
`Creating VacuumRvcRunModeServer with ${rooms.length} rooms, ${supportedModes2.length} total modes`
|
|
169876
170751
|
);
|
|
169877
170752
|
if (rooms.length > 0) {
|
|
169878
|
-
|
|
170753
|
+
logger236.info(`Rooms found: ${rooms.map((r) => r.name).join(", ")}`);
|
|
169879
170754
|
}
|
|
169880
170755
|
if (filteredCount > 0) {
|
|
169881
170756
|
const filtered = allRooms.filter((r) => !rooms.some((x) => x.id === r.id));
|
|
169882
|
-
|
|
170757
|
+
logger236.info(
|
|
169883
170758
|
`Filtered out ${filteredCount} unnamed room(s): ${filtered.map((r) => r.name).join(", ")}`
|
|
169884
170759
|
);
|
|
169885
170760
|
}
|
|
169886
170761
|
if (allRooms.length === 0) {
|
|
169887
|
-
|
|
170762
|
+
logger236.debug(
|
|
169888
170763
|
`No rooms found. Attributes: rooms=${JSON.stringify(attributes9.rooms)}, segments=${JSON.stringify(attributes9.segments)}, room_list=${attributes9.room_list}`
|
|
169889
170764
|
);
|
|
169890
170765
|
}
|
|
@@ -169918,7 +170793,7 @@ function createCleanAreaRvcRunModeServer(cleanAreaRooms) {
|
|
|
169918
170793
|
modeTags: [{ value: RvcRunMode3.ModeTag.Cleaning }]
|
|
169919
170794
|
});
|
|
169920
170795
|
}
|
|
169921
|
-
|
|
170796
|
+
logger236.info(
|
|
169922
170797
|
`Creating CLEAN_AREA RvcRunModeServer with ${cleanAreaRooms.length} HA areas, ${modes.length} total modes`
|
|
169923
170798
|
);
|
|
169924
170799
|
return RvcRunModeServer2(vacuumRvcRunModeConfig, {
|
|
@@ -169926,11 +170801,12 @@ function createCleanAreaRvcRunModeServer(cleanAreaRooms) {
|
|
|
169926
170801
|
currentMode: 0 /* Idle */
|
|
169927
170802
|
});
|
|
169928
170803
|
}
|
|
169929
|
-
var VacuumRvcRunModeServer = RvcRunModeServer2(vacuumRvcRunModeConfig);
|
|
169930
170804
|
|
|
169931
170805
|
// src/matter/endpoints/legacy/vacuum/behaviors/vacuum-on-off-server.ts
|
|
169932
170806
|
var VacuumOnOffServer = OnOffServer2({
|
|
169933
|
-
|
|
170807
|
+
// Derive from the entity directly. Reading the sibling rvcRunMode behavior
|
|
170808
|
+
// crashed with a class mismatch and would lag one tick behind anyway (#428).
|
|
170809
|
+
isOn: (entity) => vacuumIsCleaning(entity.state),
|
|
169934
170810
|
turnOn: () => ({ action: "vacuum.start" }),
|
|
169935
170811
|
turnOff: () => ({ action: "vacuum.return_to_base" })
|
|
169936
170812
|
}).with();
|
|
@@ -170007,7 +170883,7 @@ init_nodejs();
|
|
|
170007
170883
|
|
|
170008
170884
|
// src/matter/behaviors/rvc-clean-mode-server.ts
|
|
170009
170885
|
init_home_assistant_entity_behavior();
|
|
170010
|
-
var
|
|
170886
|
+
var logger237 = Logger.get("RvcCleanModeServerBase");
|
|
170011
170887
|
var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeServer {
|
|
170012
170888
|
// Pending mode from a recent changeToMode command.
|
|
170013
170889
|
// Prevents stale HA state (from a different entity like select.xxx)
|
|
@@ -170054,14 +170930,14 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
|
|
|
170054
170930
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
170055
170931
|
const { newMode } = request;
|
|
170056
170932
|
if (newMode !== this.state.currentMode && !this.state.supportedModes.some((m) => m.mode === newMode)) {
|
|
170057
|
-
|
|
170933
|
+
logger237.warn(`changeToMode(${newMode}) rejected: unsupported mode`);
|
|
170058
170934
|
return {
|
|
170059
170935
|
status: ModeBase3.ModeChangeStatus.UnsupportedMode,
|
|
170060
170936
|
statusText: `Unsupported mode: ${newMode}`
|
|
170061
170937
|
};
|
|
170062
170938
|
}
|
|
170063
170939
|
const modeLabel = this.state.supportedModes.find((m) => m.mode === newMode);
|
|
170064
|
-
|
|
170940
|
+
logger237.info(
|
|
170065
170941
|
`changeToMode(${newMode}) "${modeLabel?.label ?? "unknown"}" for ${homeAssistant.entityId}`
|
|
170066
170942
|
);
|
|
170067
170943
|
this.pendingMode = newMode;
|
|
@@ -170069,7 +170945,7 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
|
|
|
170069
170945
|
this.state.currentMode = newMode;
|
|
170070
170946
|
const action = this.state.config.setCleanMode(newMode, this.agent);
|
|
170071
170947
|
if (action) {
|
|
170072
|
-
|
|
170948
|
+
logger237.info(
|
|
170073
170949
|
`changeToMode: dispatching action ${action.action} \u2192 ${action.target ?? homeAssistant.entityId}`
|
|
170074
170950
|
);
|
|
170075
170951
|
homeAssistant.callAction(action);
|
|
@@ -170102,7 +170978,7 @@ function RvcCleanModeServer2(config11, initialState) {
|
|
|
170102
170978
|
}
|
|
170103
170979
|
|
|
170104
170980
|
// src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-clean-mode-server.ts
|
|
170105
|
-
var
|
|
170981
|
+
var logger238 = Logger.get("VacuumRvcCleanModeServer");
|
|
170106
170982
|
var MODE_VACUUM = 0;
|
|
170107
170983
|
var MODE_VACUUM_AND_MOP = 1;
|
|
170108
170984
|
var MODE_MOP = 2;
|
|
@@ -170372,7 +171248,7 @@ function findMatchingCleanOption(ct, availableOptions) {
|
|
|
170372
171248
|
const match = availableOptions.find((o) => classifyCleanOption(o) === type);
|
|
170373
171249
|
if (match) return match;
|
|
170374
171250
|
}
|
|
170375
|
-
|
|
171251
|
+
logger238.warn(
|
|
170376
171252
|
`No match for ${CLEAN_TYPE_LABELS[ct]} in [${availableOptions.join(", ")}]`
|
|
170377
171253
|
);
|
|
170378
171254
|
return availableOptions[0];
|
|
@@ -170381,7 +171257,7 @@ function buildCleaningModeAction(targetCleanType, agent) {
|
|
|
170381
171257
|
const selectEntityId = getCleaningModeSelectEntity(agent);
|
|
170382
171258
|
const { options } = readSelectEntity(selectEntityId, agent);
|
|
170383
171259
|
const optionToUse = findMatchingCleanOption(targetCleanType, options);
|
|
170384
|
-
|
|
171260
|
+
logger238.info(
|
|
170385
171261
|
`Switching cleaning mode to: ${optionToUse} via ${selectEntityId}`
|
|
170386
171262
|
);
|
|
170387
171263
|
return {
|
|
@@ -170470,7 +171346,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170470
171346
|
}
|
|
170471
171347
|
}
|
|
170472
171348
|
if (speedMode !== void 0) {
|
|
170473
|
-
|
|
171349
|
+
logger238.debug(
|
|
170474
171350
|
`Current mode: Vacuum + fan_speed="${speedState}" -> mode ${speedMode}`
|
|
170475
171351
|
);
|
|
170476
171352
|
return speedMode;
|
|
@@ -170491,7 +171367,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170491
171367
|
}
|
|
170492
171368
|
}
|
|
170493
171369
|
if (mopMode !== void 0) {
|
|
170494
|
-
|
|
171370
|
+
logger238.debug(
|
|
170495
171371
|
`Current mode: Mop + intensity="${state}" -> mode ${mopMode}`
|
|
170496
171372
|
);
|
|
170497
171373
|
return mopMode;
|
|
@@ -170509,14 +171385,14 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170509
171385
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
170510
171386
|
const vacuumEntityId = homeAssistant.entityId;
|
|
170511
171387
|
const mapping = homeAssistant.state.mapping;
|
|
170512
|
-
|
|
171388
|
+
logger238.info(
|
|
170513
171389
|
`setCleanMode(${mode}) for ${vacuumEntityId}, suctionEntity=${mapping?.suctionLevelEntity ?? "none"}, mopEntity=${mapping?.mopIntensityEntity ?? "none"}, fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])}, customTags=${JSON.stringify(customFanSpeedTags ?? {})}`
|
|
170514
171390
|
);
|
|
170515
171391
|
if (mopIntensityList && mopIntensityList.length > 0 && isMopIntensityMode(mode)) {
|
|
170516
171392
|
const mopIndex = mode - MOP_INTENSITY_MODE_BASE;
|
|
170517
171393
|
const mopName = mopIntensityList[mopIndex];
|
|
170518
171394
|
if (!mopName) {
|
|
170519
|
-
|
|
171395
|
+
logger238.warn(`Invalid mop intensity mode index: ${mopIndex}`);
|
|
170520
171396
|
return void 0;
|
|
170521
171397
|
}
|
|
170522
171398
|
if (hasCleanTypes) {
|
|
@@ -170529,18 +171405,18 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170529
171405
|
mapping.mopIntensityEntity,
|
|
170530
171406
|
agent
|
|
170531
171407
|
);
|
|
170532
|
-
|
|
171408
|
+
logger238.info(
|
|
170533
171409
|
`Mop intensity entity ${mapping.mopIntensityEntity}: current="${state}", options=${JSON.stringify(options ?? [])}`
|
|
170534
171410
|
);
|
|
170535
171411
|
let option = matchMopIntensityOption(mopName, options);
|
|
170536
171412
|
if (!option && options && mopIndex < options.length) {
|
|
170537
171413
|
option = options[mopIndex];
|
|
170538
|
-
|
|
171414
|
+
logger238.info(
|
|
170539
171415
|
`Positional match for mop "${mopName}" -> "${option}" (index ${mopIndex})`
|
|
170540
171416
|
);
|
|
170541
171417
|
}
|
|
170542
171418
|
if (option) {
|
|
170543
|
-
|
|
171419
|
+
logger238.info(
|
|
170544
171420
|
`Setting mop intensity to: ${option} via ${mapping.mopIntensityEntity}`
|
|
170545
171421
|
);
|
|
170546
171422
|
return {
|
|
@@ -170549,11 +171425,11 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170549
171425
|
target: mapping.mopIntensityEntity
|
|
170550
171426
|
};
|
|
170551
171427
|
}
|
|
170552
|
-
|
|
171428
|
+
logger238.warn(
|
|
170553
171429
|
`No match for mop intensity "${mopName}" in options: [${(options ?? []).join(", ")}]`
|
|
170554
171430
|
);
|
|
170555
171431
|
} else {
|
|
170556
|
-
|
|
171432
|
+
logger238.warn(
|
|
170557
171433
|
`Mop intensity mode ${mode} requested but no mopIntensityEntity configured`
|
|
170558
171434
|
);
|
|
170559
171435
|
}
|
|
@@ -170563,7 +171439,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170563
171439
|
const fanSpeedIndex = mode - FAN_SPEED_MODE_BASE;
|
|
170564
171440
|
const fanSpeedName = fanSpeedList[fanSpeedIndex];
|
|
170565
171441
|
if (!fanSpeedName) {
|
|
170566
|
-
|
|
171442
|
+
logger238.warn(`Invalid fan speed mode index: ${fanSpeedIndex}`);
|
|
170567
171443
|
return void 0;
|
|
170568
171444
|
}
|
|
170569
171445
|
if (mapping?.suctionLevelEntity) {
|
|
@@ -170576,7 +171452,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170576
171452
|
mapping.suctionLevelEntity,
|
|
170577
171453
|
agent
|
|
170578
171454
|
);
|
|
170579
|
-
|
|
171455
|
+
logger238.info(
|
|
170580
171456
|
`Suction entity ${mapping.suctionLevelEntity}: current="${state}", options=${JSON.stringify(options ?? [])}`
|
|
170581
171457
|
);
|
|
170582
171458
|
let option = matchFanSpeedOption(
|
|
@@ -170586,12 +171462,12 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170586
171462
|
);
|
|
170587
171463
|
if (!option && options && fanSpeedIndex < options.length) {
|
|
170588
171464
|
option = options[fanSpeedIndex];
|
|
170589
|
-
|
|
171465
|
+
logger238.info(
|
|
170590
171466
|
`Positional match for fan "${fanSpeedName}" -> "${option}" (index ${fanSpeedIndex})`
|
|
170591
171467
|
);
|
|
170592
171468
|
}
|
|
170593
171469
|
if (option) {
|
|
170594
|
-
|
|
171470
|
+
logger238.info(
|
|
170595
171471
|
`Setting suction to: ${option} via ${mapping.suctionLevelEntity}`
|
|
170596
171472
|
);
|
|
170597
171473
|
return {
|
|
@@ -170600,7 +171476,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170600
171476
|
target: mapping.suctionLevelEntity
|
|
170601
171477
|
};
|
|
170602
171478
|
}
|
|
170603
|
-
|
|
171479
|
+
logger238.warn(
|
|
170604
171480
|
`No match for fan speed "${fanSpeedName}" in suction options: [${(options ?? []).join(", ")}]`
|
|
170605
171481
|
);
|
|
170606
171482
|
return void 0;
|
|
@@ -170610,7 +171486,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170610
171486
|
buildCleaningModeAction(0 /* Sweeping */, agent)
|
|
170611
171487
|
);
|
|
170612
171488
|
}
|
|
170613
|
-
|
|
171489
|
+
logger238.info(
|
|
170614
171490
|
`Setting fan speed to: ${fanSpeedName} via vacuum.set_fan_speed`
|
|
170615
171491
|
);
|
|
170616
171492
|
return {
|
|
@@ -170620,7 +171496,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170620
171496
|
};
|
|
170621
171497
|
}
|
|
170622
171498
|
if (!hasCleanTypes) {
|
|
170623
|
-
|
|
171499
|
+
logger238.debug(
|
|
170624
171500
|
`Ignoring cleaning type change (mode=${mode}): no cleaning mode entity`
|
|
170625
171501
|
);
|
|
170626
171502
|
return void 0;
|
|
@@ -170632,7 +171508,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170632
171508
|
agent
|
|
170633
171509
|
);
|
|
170634
171510
|
const optionToUse = findMatchingCleanOption(cleanType, availableOptions);
|
|
170635
|
-
|
|
171511
|
+
logger238.info(
|
|
170636
171512
|
`Setting cleaning mode to: ${optionToUse} (mode=${mode}) via ${selectEntityId}`
|
|
170637
171513
|
);
|
|
170638
171514
|
return {
|
|
@@ -170650,10 +171526,10 @@ function createVacuumRvcCleanModeServer(_attributes, fanSpeedList, mopIntensityL
|
|
|
170650
171526
|
cleaningModeOptions,
|
|
170651
171527
|
customFanSpeedTags
|
|
170652
171528
|
);
|
|
170653
|
-
|
|
171529
|
+
logger238.info(
|
|
170654
171530
|
`Creating VacuumRvcCleanModeServer with ${supportedModes2.length} modes (fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])}, cleaningModeOptions=${JSON.stringify(cleaningModeOptions ?? [])}, customTags=${JSON.stringify(customFanSpeedTags ?? {})})`
|
|
170655
171531
|
);
|
|
170656
|
-
|
|
171532
|
+
logger238.info(
|
|
170657
171533
|
`Modes: ${supportedModes2.map((m) => `${m.mode}:${m.label}[${m.modeTags.map((t) => t.value).join(",")}]`).join(", ")}`
|
|
170658
171534
|
);
|
|
170659
171535
|
const initialState = {
|
|
@@ -170716,7 +171592,7 @@ function resolveMopIntensityList(mopIntensityEntity) {
|
|
|
170716
171592
|
init_dist();
|
|
170717
171593
|
init_esm();
|
|
170718
171594
|
init_home_assistant_entity_behavior();
|
|
170719
|
-
var
|
|
171595
|
+
var logger239 = Logger.get("VacuumRvcOperationalStateServer");
|
|
170720
171596
|
function batteryFromAttributes(attrs) {
|
|
170721
171597
|
const raw = attrs.battery_level ?? attrs.battery;
|
|
170722
171598
|
if (raw == null) return null;
|
|
@@ -170743,7 +171619,7 @@ function isDockedCharging(entity, batteryPercent) {
|
|
|
170743
171619
|
}
|
|
170744
171620
|
function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttributes(entity.attributes), chargingState = null) {
|
|
170745
171621
|
const state = entity.state;
|
|
170746
|
-
const
|
|
171622
|
+
const cleaningStates2 = [
|
|
170747
171623
|
VacuumState.cleaning,
|
|
170748
171624
|
VacuumState.segment_cleaning,
|
|
170749
171625
|
VacuumState.zone_cleaning,
|
|
@@ -170756,7 +171632,7 @@ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttribute
|
|
|
170756
171632
|
operationalState = charging ? RvcOperationalState4.OperationalState.Charging : RvcOperationalState4.OperationalState.Docked;
|
|
170757
171633
|
} else if (state === VacuumState.returning) {
|
|
170758
171634
|
operationalState = RvcOperationalState4.OperationalState.SeekingCharger;
|
|
170759
|
-
} else if (
|
|
171635
|
+
} else if (cleaningStates2.includes(state)) {
|
|
170760
171636
|
operationalState = RvcOperationalState4.OperationalState.Running;
|
|
170761
171637
|
} else if (state === VacuumState.paused) {
|
|
170762
171638
|
operationalState = RvcOperationalState4.OperationalState.Paused;
|
|
@@ -170767,16 +171643,16 @@ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttribute
|
|
|
170767
171643
|
operationalState = RvcOperationalState4.OperationalState.Error;
|
|
170768
171644
|
} else {
|
|
170769
171645
|
if (state.toLowerCase().includes("clean")) {
|
|
170770
|
-
|
|
171646
|
+
logger239.info(
|
|
170771
171647
|
`Unknown vacuum state "${state}" contains 'clean', treating as Running`
|
|
170772
171648
|
);
|
|
170773
171649
|
operationalState = RvcOperationalState4.OperationalState.Running;
|
|
170774
171650
|
} else {
|
|
170775
|
-
|
|
171651
|
+
logger239.info(`Unknown vacuum state "${state}", treating as Stopped`);
|
|
170776
171652
|
operationalState = RvcOperationalState4.OperationalState.Stopped;
|
|
170777
171653
|
}
|
|
170778
171654
|
}
|
|
170779
|
-
|
|
171655
|
+
logger239.debug(
|
|
170780
171656
|
`Vacuum operationalState: "${state}" -> ${RvcOperationalState4.OperationalState[operationalState]}`
|
|
170781
171657
|
);
|
|
170782
171658
|
return operationalState;
|
|
@@ -170805,7 +171681,7 @@ var VacuumRvcOperationalStateServer = RvcOperationalStateServer2({
|
|
|
170805
171681
|
});
|
|
170806
171682
|
|
|
170807
171683
|
// src/matter/endpoints/legacy/vacuum/index.ts
|
|
170808
|
-
var
|
|
171684
|
+
var logger240 = Logger.get("VacuumDevice");
|
|
170809
171685
|
var VacuumEndpointType = RoboticVacuumCleanerDevice.with(
|
|
170810
171686
|
BasicInformationServer2,
|
|
170811
171687
|
VacuumIdentifyServer,
|
|
@@ -170819,7 +171695,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
|
|
|
170819
171695
|
const entityId = homeAssistantEntity.entity.entity_id;
|
|
170820
171696
|
const attributes9 = homeAssistantEntity.entity.state.attributes;
|
|
170821
171697
|
const customAreas = homeAssistantEntity.mapping?.customServiceAreas;
|
|
170822
|
-
|
|
171698
|
+
logger240.info(
|
|
170823
171699
|
`Creating vacuum endpoint for ${entityId}, mapping: ${JSON.stringify(homeAssistantEntity.mapping ?? "none")}`
|
|
170824
171700
|
);
|
|
170825
171701
|
const cleanAreaRooms = homeAssistantEntity.mapping?.cleanAreaRooms;
|
|
@@ -170832,32 +171708,32 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
|
|
|
170832
171708
|
)
|
|
170833
171709
|
).set({ homeAssistantEntity });
|
|
170834
171710
|
if (includeOnOff) {
|
|
170835
|
-
|
|
171711
|
+
logger240.info(`${entityId}: Adding OnOff cluster (vacuumOnOff flag enabled)`);
|
|
170836
171712
|
device = device.with(VacuumOnOffServer);
|
|
170837
171713
|
}
|
|
170838
171714
|
device = device.with(VacuumPowerSourceServer);
|
|
170839
171715
|
const roomEntities = homeAssistantEntity.mapping?.roomEntities;
|
|
170840
171716
|
const rooms = parseVacuumRooms(attributes9);
|
|
170841
|
-
|
|
171717
|
+
logger240.info(
|
|
170842
171718
|
`${entityId}: customAreas=${customAreas?.length ?? 0}, roomEntities=${JSON.stringify(roomEntities ?? [])}, parsedRooms=${rooms.length}, cleanAreaRooms=${cleanAreaRooms?.length ?? 0}`
|
|
170843
171719
|
);
|
|
170844
171720
|
if (cleanAreaRooms && cleanAreaRooms.length > 0) {
|
|
170845
|
-
|
|
171721
|
+
logger240.info(
|
|
170846
171722
|
`${entityId}: Adding ServiceArea (${cleanAreaRooms.length} HA areas via CLEAN_AREA)`
|
|
170847
171723
|
);
|
|
170848
171724
|
device = device.with(createCleanAreaServiceAreaServer(cleanAreaRooms));
|
|
170849
171725
|
} else if (customAreas && customAreas.length > 0) {
|
|
170850
|
-
|
|
171726
|
+
logger240.info(
|
|
170851
171727
|
`${entityId}: Adding ServiceArea (${customAreas.length} custom areas)`
|
|
170852
171728
|
);
|
|
170853
171729
|
device = device.with(createCustomServiceAreaServer(customAreas));
|
|
170854
171730
|
} else if (rooms.length > 0 || roomEntities && roomEntities.length > 0) {
|
|
170855
|
-
|
|
171731
|
+
logger240.info(`${entityId}: Adding ServiceArea (${rooms.length} rooms)`);
|
|
170856
171732
|
device = device.with(
|
|
170857
171733
|
createVacuumServiceAreaServer(attributes9, roomEntities)
|
|
170858
171734
|
);
|
|
170859
171735
|
} else {
|
|
170860
|
-
|
|
171736
|
+
logger240.info(`${entityId}: Adding ServiceArea (default single-area)`);
|
|
170861
171737
|
device = device.with(createDefaultServiceAreaServer());
|
|
170862
171738
|
}
|
|
170863
171739
|
const fanSpeedList = resolveFanSpeedList(
|
|
@@ -170868,7 +171744,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
|
|
|
170868
171744
|
homeAssistantEntity.mapping?.mopIntensityEntity
|
|
170869
171745
|
);
|
|
170870
171746
|
if (cleaningModeOptions || fanSpeedList || mopIntensityList) {
|
|
170871
|
-
|
|
171747
|
+
logger240.info(
|
|
170872
171748
|
`${entityId}: Adding RvcCleanMode (multi-mode, cleaningModeOptions=${JSON.stringify(cleaningModeOptions ?? [])}, fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])})`
|
|
170873
171749
|
);
|
|
170874
171750
|
device = device.with(
|
|
@@ -170881,7 +171757,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
|
|
|
170881
171757
|
)
|
|
170882
171758
|
);
|
|
170883
171759
|
} else {
|
|
170884
|
-
|
|
171760
|
+
logger240.info(`${entityId}: Adding RvcCleanMode (default single-mode)`);
|
|
170885
171761
|
device = device.with(createDefaultRvcCleanModeServer());
|
|
170886
171762
|
}
|
|
170887
171763
|
return device;
|
|
@@ -171047,7 +171923,7 @@ var WaterHeaterThermostatServer = ThermostatServer2(
|
|
|
171047
171923
|
);
|
|
171048
171924
|
|
|
171049
171925
|
// src/matter/endpoints/legacy/water-heater/index.ts
|
|
171050
|
-
var
|
|
171926
|
+
var logger241 = Logger.get("WaterHeaterDevice");
|
|
171051
171927
|
var WaterHeaterDeviceType = ThermostatDevice.with(
|
|
171052
171928
|
BasicInformationServer2,
|
|
171053
171929
|
IdentifyServer2,
|
|
@@ -171063,7 +171939,7 @@ function toMatterTemp2(value) {
|
|
|
171063
171939
|
}
|
|
171064
171940
|
function WaterHeaterDevice(homeAssistantEntity) {
|
|
171065
171941
|
const attributes9 = homeAssistantEntity.entity.state.attributes;
|
|
171066
|
-
|
|
171942
|
+
logger241.debug(
|
|
171067
171943
|
`Creating device for ${homeAssistantEntity.entity.entity_id}, min_temp=${attributes9.min_temp}, max_temp=${attributes9.max_temp}`
|
|
171068
171944
|
);
|
|
171069
171945
|
const minLimit = toMatterTemp2(attributes9.min_temp) ?? 0;
|
|
@@ -171303,6 +172179,7 @@ var matterDeviceTypeFactories = {
|
|
|
171303
172179
|
// Legacy SolarPower alias, kept for existing mappings.
|
|
171304
172180
|
electrical_sensor: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
|
|
171305
172181
|
solar_power: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
|
|
172182
|
+
evse: (ha) => energyEvseType().set({ homeAssistantEntity: ha }),
|
|
171306
172183
|
contact_sensor: (ha) => ContactSensorType.set({ homeAssistantEntity: ha }),
|
|
171307
172184
|
motion_sensor: (ha) => MotionSensorType.set({ homeAssistantEntity: ha }),
|
|
171308
172185
|
occupancy_sensor: (ha) => OccupancySensorType.set({ homeAssistantEntity: ha }),
|
|
@@ -171318,7 +172195,7 @@ var matterDeviceTypeFactories = {
|
|
|
171318
172195
|
};
|
|
171319
172196
|
|
|
171320
172197
|
// src/matter/endpoints/composed/user-composed-endpoint.ts
|
|
171321
|
-
var
|
|
172198
|
+
var logger242 = Logger.get("UserComposedEndpoint");
|
|
171322
172199
|
function stripBasicInformation(type) {
|
|
171323
172200
|
const behaviors = { ...type.behaviors };
|
|
171324
172201
|
delete behaviors.bridgedDeviceBasicInformation;
|
|
@@ -171328,11 +172205,11 @@ function createEndpointId5(entityId, customName) {
|
|
|
171328
172205
|
const baseName = customName || entityId;
|
|
171329
172206
|
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
171330
172207
|
}
|
|
171331
|
-
function buildEntityPayload4(
|
|
171332
|
-
const state =
|
|
172208
|
+
function buildEntityPayload4(registry3, entityId) {
|
|
172209
|
+
const state = registry3.initialStateIncludingUnfiltered(entityId);
|
|
171333
172210
|
if (!state) return void 0;
|
|
171334
|
-
const entity =
|
|
171335
|
-
const deviceRegistry =
|
|
172211
|
+
const entity = registry3.entityIncludingUnfiltered(entityId);
|
|
172212
|
+
const deviceRegistry = registry3.deviceOfIncludingUnfiltered(entityId);
|
|
171336
172213
|
return {
|
|
171337
172214
|
entity_id: entityId,
|
|
171338
172215
|
state,
|
|
@@ -171348,8 +172225,8 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171348
172225
|
lastMappedStates = /* @__PURE__ */ new Map();
|
|
171349
172226
|
debouncedUpdates = /* @__PURE__ */ new Map();
|
|
171350
172227
|
static async create(config11) {
|
|
171351
|
-
const { registry:
|
|
171352
|
-
const primaryPayload = buildEntityPayload4(
|
|
172228
|
+
const { registry: registry3, primaryEntityId, composedEntities } = config11;
|
|
172229
|
+
const primaryPayload = buildEntityPayload4(registry3, primaryEntityId);
|
|
171353
172230
|
if (!primaryPayload) return void 0;
|
|
171354
172231
|
let parentType = BridgedNodeEndpoint.with(
|
|
171355
172232
|
BasicInformationServer2,
|
|
@@ -171391,10 +172268,10 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171391
172268
|
primarySubPayload,
|
|
171392
172269
|
primaryMapping,
|
|
171393
172270
|
config11.areaName,
|
|
171394
|
-
{ vacuumOnOff:
|
|
172271
|
+
{ vacuumOnOff: registry3.isVacuumOnOffEnabled() }
|
|
171395
172272
|
);
|
|
171396
172273
|
if (!primaryType) {
|
|
171397
|
-
|
|
172274
|
+
logger242.warn(
|
|
171398
172275
|
`Cannot create endpoint type for primary entity ${primaryEntityId}`
|
|
171399
172276
|
);
|
|
171400
172277
|
return void 0;
|
|
@@ -171407,9 +172284,9 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171407
172284
|
for (let i = 0; i < composedEntities.length; i++) {
|
|
171408
172285
|
const sub = composedEntities[i];
|
|
171409
172286
|
if (!sub.entityId) continue;
|
|
171410
|
-
const subPayload = buildEntityPayload4(
|
|
172287
|
+
const subPayload = buildEntityPayload4(registry3, sub.entityId);
|
|
171411
172288
|
if (!subPayload) {
|
|
171412
|
-
|
|
172289
|
+
logger242.warn(
|
|
171413
172290
|
`Cannot find state for composed sub-entity ${sub.entityId}, it does not exist in Home Assistant (removed or renamed?)`
|
|
171414
172291
|
);
|
|
171415
172292
|
continue;
|
|
@@ -171424,7 +172301,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171424
172301
|
config11.areaName
|
|
171425
172302
|
);
|
|
171426
172303
|
if (!subType) {
|
|
171427
|
-
|
|
172304
|
+
logger242.warn(
|
|
171428
172305
|
`Cannot create endpoint type for composed sub-entity ${sub.entityId}`
|
|
171429
172306
|
);
|
|
171430
172307
|
continue;
|
|
@@ -171437,7 +172314,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171437
172314
|
mappedIds.push(sub.entityId);
|
|
171438
172315
|
}
|
|
171439
172316
|
if (parts.length < 2) {
|
|
171440
|
-
|
|
172317
|
+
logger242.warn(
|
|
171441
172318
|
`User composed device ${primaryEntityId}: only ${parts.length} sub-endpoint(s), need at least 2 (primary + one sub-entity). Falling back to standalone.`
|
|
171442
172319
|
);
|
|
171443
172320
|
return void 0;
|
|
@@ -171461,7 +172338,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171461
172338
|
const labels = parts.map(
|
|
171462
172339
|
(_, i) => i === 0 ? primaryEntityId.split(".")[0] : composedEntities[i - 1]?.entityId?.split(".")[0] ?? "?"
|
|
171463
172340
|
).join("+");
|
|
171464
|
-
|
|
172341
|
+
logger242.info(
|
|
171465
172342
|
`Created user composed device ${primaryEntityId}: ${parts.length} sub-endpoint(s) [${labels}]`
|
|
171466
172343
|
);
|
|
171467
172344
|
return endpoint;
|
|
@@ -171567,45 +172444,45 @@ function asStandaloneEndpointType(type) {
|
|
|
171567
172444
|
}
|
|
171568
172445
|
|
|
171569
172446
|
// src/matter/endpoints/legacy/legacy-endpoint.ts
|
|
171570
|
-
var
|
|
172447
|
+
var logger243 = Logger.get("LegacyEndpoint");
|
|
171571
172448
|
var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
171572
|
-
static async create(
|
|
171573
|
-
const deviceRegistry =
|
|
171574
|
-
let state =
|
|
171575
|
-
const entity =
|
|
172449
|
+
static async create(registry3, entityId, mapping, pluginDomainMappings, standalone = false, endpointId, identityAnchor) {
|
|
172450
|
+
const deviceRegistry = registry3.deviceOf(entityId);
|
|
172451
|
+
let state = registry3.initialState(entityId);
|
|
172452
|
+
const entity = registry3.entity(entityId);
|
|
171576
172453
|
if (!state) {
|
|
171577
172454
|
return;
|
|
171578
172455
|
}
|
|
171579
|
-
if (
|
|
171580
|
-
|
|
172456
|
+
if (registry3.isAutoBatteryMappingEnabled() && registry3.isBatteryEntityUsed(entityId)) {
|
|
172457
|
+
logger243.debug(
|
|
171581
172458
|
`Skipping ${entityId} - already auto-assigned as battery to another device`
|
|
171582
172459
|
);
|
|
171583
172460
|
return;
|
|
171584
172461
|
}
|
|
171585
|
-
if (
|
|
171586
|
-
|
|
172462
|
+
if (registry3.isAutoHumidityMappingEnabled() && registry3.isHumidityEntityUsed(entityId)) {
|
|
172463
|
+
logger243.debug(
|
|
171587
172464
|
`Skipping ${entityId} - already auto-assigned as humidity to a temperature sensor`
|
|
171588
172465
|
);
|
|
171589
172466
|
return;
|
|
171590
172467
|
}
|
|
171591
|
-
if (
|
|
171592
|
-
|
|
172468
|
+
if (registry3.isAutoPressureMappingEnabled() && registry3.isPressureEntityUsed(entityId)) {
|
|
172469
|
+
logger243.debug(
|
|
171593
172470
|
`Skipping ${entityId} - already auto-assigned as pressure to a temperature sensor`
|
|
171594
172471
|
);
|
|
171595
172472
|
return;
|
|
171596
172473
|
}
|
|
171597
|
-
if (
|
|
171598
|
-
|
|
172474
|
+
if (registry3.isAutoComposedDevicesEnabled() && registry3.isComposedSubEntityUsed(entityId)) {
|
|
172475
|
+
logger243.debug(
|
|
171599
172476
|
`Skipping ${entityId} - already consumed by a composed device`
|
|
171600
172477
|
);
|
|
171601
172478
|
return;
|
|
171602
172479
|
}
|
|
171603
172480
|
let effectiveMapping = mapping;
|
|
171604
172481
|
if (entity.device_id) {
|
|
171605
|
-
if (
|
|
172482
|
+
if (registry3.isAutoHumidityMappingEnabled()) {
|
|
171606
172483
|
const attrs = state.attributes;
|
|
171607
172484
|
if (!mapping?.humidityEntity && entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature) {
|
|
171608
|
-
const humidityEntityId =
|
|
172485
|
+
const humidityEntityId = registry3.findHumidityEntityForDevice(
|
|
171609
172486
|
entity.device_id
|
|
171610
172487
|
);
|
|
171611
172488
|
if (humidityEntityId && humidityEntityId !== entityId) {
|
|
@@ -171614,17 +172491,17 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171614
172491
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171615
172492
|
humidityEntity: humidityEntityId
|
|
171616
172493
|
};
|
|
171617
|
-
|
|
171618
|
-
|
|
172494
|
+
registry3.markHumidityEntityUsed(humidityEntityId);
|
|
172495
|
+
logger243.debug(
|
|
171619
172496
|
`Auto-assigned humidity ${humidityEntityId} to ${entityId}`
|
|
171620
172497
|
);
|
|
171621
172498
|
}
|
|
171622
172499
|
}
|
|
171623
172500
|
}
|
|
171624
|
-
if (
|
|
172501
|
+
if (registry3.isAutoPressureMappingEnabled()) {
|
|
171625
172502
|
const attrs = state.attributes;
|
|
171626
172503
|
if (!mapping?.pressureEntity && entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature) {
|
|
171627
|
-
const pressureEntityId =
|
|
172504
|
+
const pressureEntityId = registry3.findPressureEntityForDevice(
|
|
171628
172505
|
entity.device_id
|
|
171629
172506
|
);
|
|
171630
172507
|
if (pressureEntityId && pressureEntityId !== entityId) {
|
|
@@ -171633,16 +172510,16 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171633
172510
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171634
172511
|
pressureEntity: pressureEntityId
|
|
171635
172512
|
};
|
|
171636
|
-
|
|
171637
|
-
|
|
172513
|
+
registry3.markPressureEntityUsed(pressureEntityId);
|
|
172514
|
+
logger243.debug(
|
|
171638
172515
|
`Auto-assigned pressure ${pressureEntityId} to ${entityId}`
|
|
171639
172516
|
);
|
|
171640
172517
|
}
|
|
171641
172518
|
}
|
|
171642
172519
|
}
|
|
171643
172520
|
const isVacuum = entityId.startsWith("vacuum.");
|
|
171644
|
-
if ((
|
|
171645
|
-
const batteryEntityId =
|
|
172521
|
+
if ((registry3.isAutoBatteryMappingEnabled() || isVacuum) && !mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
|
|
172522
|
+
const batteryEntityId = registry3.findBatteryEntityForDevice(
|
|
171646
172523
|
entity.device_id
|
|
171647
172524
|
);
|
|
171648
172525
|
if (batteryEntityId && batteryEntityId !== entityId) {
|
|
@@ -171651,16 +172528,16 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171651
172528
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171652
172529
|
batteryEntity: batteryEntityId
|
|
171653
172530
|
};
|
|
171654
|
-
|
|
171655
|
-
|
|
172531
|
+
registry3.markBatteryEntityUsed(batteryEntityId);
|
|
172532
|
+
logger243.debug(
|
|
171656
172533
|
`Auto-assigned battery ${batteryEntityId} to ${entityId}`
|
|
171657
172534
|
);
|
|
171658
172535
|
}
|
|
171659
172536
|
}
|
|
171660
172537
|
const alarmDeviceClass = state.attributes.device_class;
|
|
171661
172538
|
const isSmokeCoAlarm = mapping?.matterDeviceType === "smoke_co_alarm" || entityId.startsWith("binary_sensor.") && (alarmDeviceClass === "smoke" || alarmDeviceClass === "carbon_monoxide" || alarmDeviceClass === "gas");
|
|
171662
|
-
if (
|
|
171663
|
-
const faultEntityId =
|
|
172539
|
+
if (registry3.isAutoBatteryMappingEnabled() && !mapping?.faultEntity && isSmokeCoAlarm) {
|
|
172540
|
+
const faultEntityId = registry3.findProblemEntityForDevice(
|
|
171664
172541
|
entity.device_id
|
|
171665
172542
|
);
|
|
171666
172543
|
if (faultEntityId && faultEntityId !== entityId) {
|
|
@@ -171669,13 +172546,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171669
172546
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171670
172547
|
faultEntity: faultEntityId
|
|
171671
172548
|
};
|
|
171672
|
-
|
|
172549
|
+
logger243.debug(`Auto-assigned fault ${faultEntityId} to ${entityId}`);
|
|
171673
172550
|
}
|
|
171674
172551
|
}
|
|
171675
172552
|
if (!mapping?.powerEntity) {
|
|
171676
172553
|
const domain = entityId.split(".")[0];
|
|
171677
172554
|
if (domain === "switch") {
|
|
171678
|
-
const powerEntityId =
|
|
172555
|
+
const powerEntityId = registry3.findPowerEntityForDevice(
|
|
171679
172556
|
entity.device_id
|
|
171680
172557
|
);
|
|
171681
172558
|
if (powerEntityId && powerEntityId !== entityId) {
|
|
@@ -171684,15 +172561,15 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171684
172561
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171685
172562
|
powerEntity: powerEntityId
|
|
171686
172563
|
};
|
|
171687
|
-
|
|
171688
|
-
|
|
172564
|
+
registry3.markPowerEntityUsed(powerEntityId);
|
|
172565
|
+
logger243.debug(`Auto-assigned power ${powerEntityId} to ${entityId}`);
|
|
171689
172566
|
}
|
|
171690
172567
|
}
|
|
171691
172568
|
}
|
|
171692
172569
|
if (!mapping?.energyEntity) {
|
|
171693
172570
|
const domain = entityId.split(".")[0];
|
|
171694
172571
|
if (domain === "switch") {
|
|
171695
|
-
const energyEntityId =
|
|
172572
|
+
const energyEntityId = registry3.findEnergyEntityForDevice(
|
|
171696
172573
|
entity.device_id
|
|
171697
172574
|
);
|
|
171698
172575
|
if (energyEntityId && energyEntityId !== entityId) {
|
|
@@ -171701,15 +172578,15 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171701
172578
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171702
172579
|
energyEntity: energyEntityId
|
|
171703
172580
|
};
|
|
171704
|
-
|
|
171705
|
-
|
|
172581
|
+
registry3.markEnergyEntityUsed(energyEntityId);
|
|
172582
|
+
logger243.debug(
|
|
171706
172583
|
`Auto-assigned energy ${energyEntityId} to ${entityId}`
|
|
171707
172584
|
);
|
|
171708
172585
|
}
|
|
171709
172586
|
}
|
|
171710
172587
|
}
|
|
171711
172588
|
if (entityId.startsWith("vacuum.")) {
|
|
171712
|
-
const vacuumEntities =
|
|
172589
|
+
const vacuumEntities = registry3.findVacuumSelectEntities(
|
|
171713
172590
|
entity.device_id
|
|
171714
172591
|
);
|
|
171715
172592
|
if (!effectiveMapping?.cleaningModeEntity && vacuumEntities.cleaningModeEntity) {
|
|
@@ -171718,7 +172595,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171718
172595
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171719
172596
|
cleaningModeEntity: vacuumEntities.cleaningModeEntity
|
|
171720
172597
|
};
|
|
171721
|
-
|
|
172598
|
+
logger243.info(
|
|
171722
172599
|
`Auto-assigned cleaningMode ${vacuumEntities.cleaningModeEntity} to ${entityId}`
|
|
171723
172600
|
);
|
|
171724
172601
|
}
|
|
@@ -171728,7 +172605,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171728
172605
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171729
172606
|
suctionLevelEntity: vacuumEntities.suctionLevelEntity
|
|
171730
172607
|
};
|
|
171731
|
-
|
|
172608
|
+
logger243.info(
|
|
171732
172609
|
`Auto-assigned suctionLevel ${vacuumEntities.suctionLevelEntity} to ${entityId}`
|
|
171733
172610
|
);
|
|
171734
172611
|
}
|
|
@@ -171738,7 +172615,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171738
172615
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171739
172616
|
mopIntensityEntity: vacuumEntities.mopIntensityEntity
|
|
171740
172617
|
};
|
|
171741
|
-
|
|
172618
|
+
logger243.info(
|
|
171742
172619
|
`Auto-assigned mopIntensity ${vacuumEntities.mopIntensityEntity} to ${entityId}`
|
|
171743
172620
|
);
|
|
171744
172621
|
}
|
|
@@ -171748,12 +172625,12 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171748
172625
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171749
172626
|
currentRoomEntity: vacuumEntities.currentRoomEntity
|
|
171750
172627
|
};
|
|
171751
|
-
|
|
172628
|
+
logger243.info(
|
|
171752
172629
|
`Auto-assigned currentRoom ${vacuumEntities.currentRoomEntity} to ${entityId}`
|
|
171753
172630
|
);
|
|
171754
172631
|
}
|
|
171755
172632
|
const supportedFeatures = state.attributes.supported_features ?? 0;
|
|
171756
|
-
const cleanAreaRooms = await
|
|
172633
|
+
const cleanAreaRooms = await registry3.resolveCleanAreaRooms(
|
|
171757
172634
|
entityId,
|
|
171758
172635
|
supportedFeatures
|
|
171759
172636
|
);
|
|
@@ -171763,13 +172640,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171763
172640
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171764
172641
|
cleanAreaRooms
|
|
171765
172642
|
};
|
|
171766
|
-
|
|
172643
|
+
logger243.info(
|
|
171767
172644
|
`Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA for ${entityId}`
|
|
171768
172645
|
);
|
|
171769
172646
|
}
|
|
171770
172647
|
const vacAttrs = state.attributes;
|
|
171771
172648
|
if (cleanAreaRooms.length === 0 && !vacAttrs.rooms && !vacAttrs.segments && !vacAttrs.room_mapping) {
|
|
171772
|
-
const valetudoRooms =
|
|
172649
|
+
const valetudoRooms = registry3.findValetudoMapSegments(
|
|
171773
172650
|
entity.device_id
|
|
171774
172651
|
);
|
|
171775
172652
|
if (valetudoRooms.length > 0) {
|
|
@@ -171784,11 +172661,11 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171784
172661
|
rooms: roomsObj
|
|
171785
172662
|
}
|
|
171786
172663
|
};
|
|
171787
|
-
|
|
172664
|
+
logger243.debug(
|
|
171788
172665
|
`Auto-detected ${valetudoRooms.length} Valetudo segments for ${entityId}`
|
|
171789
172666
|
);
|
|
171790
172667
|
} else {
|
|
171791
|
-
const roborockRooms = await
|
|
172668
|
+
const roborockRooms = await registry3.resolveRoborockRooms(entityId);
|
|
171792
172669
|
if (roborockRooms.length > 0) {
|
|
171793
172670
|
const roomsObj = {};
|
|
171794
172671
|
for (const r of roborockRooms) {
|
|
@@ -171801,7 +172678,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171801
172678
|
rooms: roomsObj
|
|
171802
172679
|
}
|
|
171803
172680
|
};
|
|
171804
|
-
|
|
172681
|
+
logger243.debug(
|
|
171805
172682
|
`Auto-detected ${roborockRooms.length} Roborock rooms for ${entityId}`
|
|
171806
172683
|
);
|
|
171807
172684
|
}
|
|
@@ -171810,14 +172687,14 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171810
172687
|
}
|
|
171811
172688
|
}
|
|
171812
172689
|
if (standalone && ((effectiveMapping?.composedEntities?.length ?? 0) > 0 || effectiveMapping?.climateExposeFan === true)) {
|
|
171813
|
-
|
|
172690
|
+
logger243.warn(
|
|
171814
172691
|
`Composed mappings are not supported in server mode, exposing ${entityId} as a flat standalone endpoint`
|
|
171815
172692
|
);
|
|
171816
172693
|
}
|
|
171817
|
-
if (!standalone &&
|
|
171818
|
-
const composedAreaName =
|
|
172694
|
+
if (!standalone && registry3.isAutoComposedDevicesEnabled() && effectiveMapping?.composedEntities && effectiveMapping.composedEntities.length > 0) {
|
|
172695
|
+
const composedAreaName = registry3.getAreaName(entityId);
|
|
171819
172696
|
const composed = await UserComposedEndpoint.create({
|
|
171820
|
-
registry:
|
|
172697
|
+
registry: registry3,
|
|
171821
172698
|
primaryEntityId: entityId,
|
|
171822
172699
|
mapping: effectiveMapping,
|
|
171823
172700
|
composedEntities: effectiveMapping.composedEntities,
|
|
@@ -171829,16 +172706,16 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171829
172706
|
if (composed) {
|
|
171830
172707
|
return composed;
|
|
171831
172708
|
}
|
|
171832
|
-
|
|
172709
|
+
logger243.warn(
|
|
171833
172710
|
`User composed device creation failed for ${entityId}, falling back to standalone`
|
|
171834
172711
|
);
|
|
171835
172712
|
}
|
|
171836
|
-
if (!standalone &&
|
|
172713
|
+
if (!standalone && registry3.isAutoComposedDevicesEnabled()) {
|
|
171837
172714
|
const attrs = state.attributes;
|
|
171838
172715
|
if (entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature && (effectiveMapping?.humidityEntity || effectiveMapping?.pressureEntity)) {
|
|
171839
|
-
const composedAreaName =
|
|
172716
|
+
const composedAreaName = registry3.getAreaName(entityId);
|
|
171840
172717
|
const composed = await ComposedSensorEndpoint.create({
|
|
171841
|
-
registry:
|
|
172718
|
+
registry: registry3,
|
|
171842
172719
|
primaryEntityId: entityId,
|
|
171843
172720
|
humidityEntityId: effectiveMapping?.humidityEntity,
|
|
171844
172721
|
pressureEntityId: effectiveMapping?.pressureEntity,
|
|
@@ -171854,12 +172731,12 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171854
172731
|
}
|
|
171855
172732
|
const resolvedMatterType = mapping?.matterDeviceType ?? (entityId.startsWith("fan.") ? "fan" : void 0);
|
|
171856
172733
|
if (resolvedMatterType === "air_purifier") {
|
|
171857
|
-
const temperatureEntityId = effectiveMapping?.temperatureEntity || (entity.device_id ?
|
|
171858
|
-
const humidityEntityId = effectiveMapping?.humidityEntity || (entity.device_id ?
|
|
172734
|
+
const temperatureEntityId = effectiveMapping?.temperatureEntity || (entity.device_id ? registry3.findTemperatureEntityForDevice(entity.device_id) : void 0);
|
|
172735
|
+
const humidityEntityId = effectiveMapping?.humidityEntity || (entity.device_id ? registry3.findHumidityEntityForDevice(entity.device_id) : void 0);
|
|
171859
172736
|
if (temperatureEntityId || humidityEntityId) {
|
|
171860
|
-
const composedAreaName =
|
|
172737
|
+
const composedAreaName = registry3.getAreaName(entityId);
|
|
171861
172738
|
const composed = await ComposedAirPurifierEndpoint.create({
|
|
171862
|
-
registry:
|
|
172739
|
+
registry: registry3,
|
|
171863
172740
|
primaryEntityId: entityId,
|
|
171864
172741
|
temperatureEntityId,
|
|
171865
172742
|
humidityEntityId,
|
|
@@ -171881,9 +172758,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171881
172758
|
if (!standalone && entityId.startsWith("climate.") && effectiveMapping?.climateExposeFan === true) {
|
|
171882
172759
|
const climateFeatures = state.attributes.supported_features ?? 0;
|
|
171883
172760
|
if ((climateFeatures & ClimateDeviceFeature.FAN_MODE) !== 0) {
|
|
171884
|
-
const composedAreaName =
|
|
172761
|
+
const composedAreaName = registry3.getAreaName(entityId);
|
|
171885
172762
|
const composed = await ComposedClimateFanEndpoint.create({
|
|
171886
|
-
registry:
|
|
172763
|
+
registry: registry3,
|
|
171887
172764
|
primaryEntityId: entityId,
|
|
171888
172765
|
mapping: effectiveMapping,
|
|
171889
172766
|
customName: effectiveMapping?.customName,
|
|
@@ -171894,7 +172771,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171894
172771
|
if (composed) {
|
|
171895
172772
|
return composed;
|
|
171896
172773
|
}
|
|
171897
|
-
|
|
172774
|
+
logger243.warn(
|
|
171898
172775
|
`Companion fan creation failed for ${entityId}, falling back to standalone`
|
|
171899
172776
|
);
|
|
171900
172777
|
}
|
|
@@ -171908,7 +172785,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171908
172785
|
let cleaningModeOptions;
|
|
171909
172786
|
if (entityId.startsWith("vacuum.")) {
|
|
171910
172787
|
if (effectiveMapping?.cleaningModeEntity) {
|
|
171911
|
-
const cmState =
|
|
172788
|
+
const cmState = registry3.initialState(
|
|
171912
172789
|
effectiveMapping.cleaningModeEntity
|
|
171913
172790
|
);
|
|
171914
172791
|
cleaningModeOptions = cmState?.attributes?.options;
|
|
@@ -171922,13 +172799,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171922
172799
|
];
|
|
171923
172800
|
}
|
|
171924
172801
|
}
|
|
171925
|
-
const areaName =
|
|
172802
|
+
const areaName = registry3.getAreaName(entityId);
|
|
171926
172803
|
let type = createLegacyEndpointType(
|
|
171927
172804
|
payload,
|
|
171928
172805
|
effectiveMapping,
|
|
171929
172806
|
areaName,
|
|
171930
172807
|
{
|
|
171931
|
-
vacuumOnOff:
|
|
172808
|
+
vacuumOnOff: registry3.isVacuumOnOffEnabled(),
|
|
171932
172809
|
cleaningModeOptions,
|
|
171933
172810
|
pluginDomainMappings
|
|
171934
172811
|
},
|
|
@@ -171974,11 +172851,11 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171974
172851
|
}
|
|
171975
172852
|
if (mappedChanged) {
|
|
171976
172853
|
this.pendingMappedChange = true;
|
|
171977
|
-
|
|
172854
|
+
logger243.debug(
|
|
171978
172855
|
`Mapped entity change detected for ${this.entityId}, forcing update`
|
|
171979
172856
|
);
|
|
171980
172857
|
}
|
|
171981
|
-
|
|
172858
|
+
logger243.debug(
|
|
171982
172859
|
`State update received for ${this.entityId}: state=${state.state}`
|
|
171983
172860
|
);
|
|
171984
172861
|
this.lastState = state;
|
|
@@ -172107,18 +172984,186 @@ var CameraAvStreamServer = class extends Base4 {
|
|
|
172107
172984
|
})(CameraAvStreamServer || (CameraAvStreamServer = {}));
|
|
172108
172985
|
|
|
172109
172986
|
// src/plugins/builtin/camera/webrtc-provider-server.ts
|
|
172987
|
+
init_esm();
|
|
172110
172988
|
init_esm7();
|
|
172111
172989
|
init_esm3();
|
|
172990
|
+
|
|
172991
|
+
// src/plugins/builtin/camera/requestor-client.ts
|
|
172992
|
+
init_esm();
|
|
172993
|
+
init_esm4();
|
|
172994
|
+
var logger244 = Logger.get("CameraWebRtcRequestor");
|
|
172995
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
172996
|
+
var pendingDeliveries = /* @__PURE__ */ new Map();
|
|
172997
|
+
var invokeTransport = defaultInvoke;
|
|
172998
|
+
function registerRequestor(sessionId, registration) {
|
|
172999
|
+
registry2.set(sessionId, registration);
|
|
173000
|
+
}
|
|
173001
|
+
function unregisterRequestor(sessionId) {
|
|
173002
|
+
registry2.delete(sessionId);
|
|
173003
|
+
const timer = pendingDeliveries.get(sessionId);
|
|
173004
|
+
if (timer) {
|
|
173005
|
+
clearTimeout(timer);
|
|
173006
|
+
pendingDeliveries.delete(sessionId);
|
|
173007
|
+
}
|
|
173008
|
+
}
|
|
173009
|
+
function unregisterAllRequestors() {
|
|
173010
|
+
for (const id of [...registry2.keys()]) unregisterRequestor(id);
|
|
173011
|
+
for (const timer of pendingDeliveries.values()) clearTimeout(timer);
|
|
173012
|
+
pendingDeliveries.clear();
|
|
173013
|
+
}
|
|
173014
|
+
function deliverAnswerDeferred(sessionId, sdp, onGiveUp) {
|
|
173015
|
+
const prior = pendingDeliveries.get(sessionId);
|
|
173016
|
+
if (prior) clearTimeout(prior);
|
|
173017
|
+
const owner = registry2.get(sessionId);
|
|
173018
|
+
const timer = setTimeout(() => {
|
|
173019
|
+
void (async () => {
|
|
173020
|
+
try {
|
|
173021
|
+
for (const delayMs of [0, 250, 500]) {
|
|
173022
|
+
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
173023
|
+
if (registry2.get(sessionId) !== owner || pendingDeliveries.get(sessionId) !== timer) {
|
|
173024
|
+
return;
|
|
173025
|
+
}
|
|
173026
|
+
if (await sendAnswer(sessionId, sdp)) {
|
|
173027
|
+
logger244.info(`answer delivered for session ${sessionId}`);
|
|
173028
|
+
return;
|
|
173029
|
+
}
|
|
173030
|
+
}
|
|
173031
|
+
if (registry2.get(sessionId) !== owner || pendingDeliveries.get(sessionId) !== timer) {
|
|
173032
|
+
return;
|
|
173033
|
+
}
|
|
173034
|
+
logger244.info(
|
|
173035
|
+
`answer delivery failed for session ${sessionId}, giving up`
|
|
173036
|
+
);
|
|
173037
|
+
await onGiveUp();
|
|
173038
|
+
} catch (err) {
|
|
173039
|
+
logger244.info(
|
|
173040
|
+
`answer delivery for session ${sessionId} threw: ${errText(err)}`
|
|
173041
|
+
);
|
|
173042
|
+
} finally {
|
|
173043
|
+
if (pendingDeliveries.get(sessionId) === timer) {
|
|
173044
|
+
pendingDeliveries.delete(sessionId);
|
|
173045
|
+
}
|
|
173046
|
+
}
|
|
173047
|
+
})();
|
|
173048
|
+
}, 0);
|
|
173049
|
+
timer.unref?.();
|
|
173050
|
+
pendingDeliveries.set(sessionId, timer);
|
|
173051
|
+
}
|
|
173052
|
+
function hasRequestor(sessionId) {
|
|
173053
|
+
return registry2.has(sessionId);
|
|
173054
|
+
}
|
|
173055
|
+
function sendAnswer(sessionId, sdp) {
|
|
173056
|
+
return invokeRequestor(sessionId, "answer", {
|
|
173057
|
+
webRtcSessionId: sessionId,
|
|
173058
|
+
sdp
|
|
173059
|
+
});
|
|
173060
|
+
}
|
|
173061
|
+
async function invokeRequestor(sessionId, command, fields) {
|
|
173062
|
+
const registration = registry2.get(sessionId);
|
|
173063
|
+
if (!registration) {
|
|
173064
|
+
logger244.info(`requestor ${command}: no session ${sessionId} registered`);
|
|
173065
|
+
return false;
|
|
173066
|
+
}
|
|
173067
|
+
if (registration.session.isClosed) {
|
|
173068
|
+
logger244.info(`requestor ${command}: session ${sessionId} already closed`);
|
|
173069
|
+
return false;
|
|
173070
|
+
}
|
|
173071
|
+
const request = {
|
|
173072
|
+
endpoint: registration.requestorEndpoint,
|
|
173073
|
+
cluster: WebRtcTransportRequestor3.Cluster,
|
|
173074
|
+
command,
|
|
173075
|
+
fields
|
|
173076
|
+
};
|
|
173077
|
+
try {
|
|
173078
|
+
return await invokeTransport({ registration, request });
|
|
173079
|
+
} catch (err) {
|
|
173080
|
+
logger244.info(
|
|
173081
|
+
`requestor ${command} failed for session ${sessionId}: ${errText(err)}`
|
|
173082
|
+
);
|
|
173083
|
+
return false;
|
|
173084
|
+
}
|
|
173085
|
+
}
|
|
173086
|
+
async function defaultInvoke({
|
|
173087
|
+
registration,
|
|
173088
|
+
request
|
|
173089
|
+
}) {
|
|
173090
|
+
const { session, env } = registration;
|
|
173091
|
+
const exchangeManager = env.get(ExchangeManager);
|
|
173092
|
+
const exchangeProvider = new DedicatedChannelExchangeProvider(
|
|
173093
|
+
exchangeManager,
|
|
173094
|
+
session
|
|
173095
|
+
);
|
|
173096
|
+
const client = new ClientInteraction({ environment: env, exchangeProvider });
|
|
173097
|
+
try {
|
|
173098
|
+
const result = client.invoke(
|
|
173099
|
+
Invoke({
|
|
173100
|
+
commands: [
|
|
173101
|
+
{
|
|
173102
|
+
endpoint: request.endpoint,
|
|
173103
|
+
cluster: request.cluster,
|
|
173104
|
+
command: request.command,
|
|
173105
|
+
// biome-ignore lint/suspicious/noExplicitAny: fields typed per command by caller
|
|
173106
|
+
fields: request.fields
|
|
173107
|
+
}
|
|
173108
|
+
]
|
|
173109
|
+
})
|
|
173110
|
+
);
|
|
173111
|
+
for await (const chunk of result) {
|
|
173112
|
+
for (const entry of chunk) {
|
|
173113
|
+
if (entry.kind === "cmd-status") {
|
|
173114
|
+
if (entry.status !== Status2.Success) {
|
|
173115
|
+
logger244.info(
|
|
173116
|
+
`requestor ${request.command} status ${entry.status} for session ${String(request.fields.webRtcSessionId)}`
|
|
173117
|
+
);
|
|
173118
|
+
return false;
|
|
173119
|
+
}
|
|
173120
|
+
return true;
|
|
173121
|
+
}
|
|
173122
|
+
}
|
|
173123
|
+
}
|
|
173124
|
+
return true;
|
|
173125
|
+
} finally {
|
|
173126
|
+
await client.close().catch(() => {
|
|
173127
|
+
});
|
|
173128
|
+
}
|
|
173129
|
+
}
|
|
173130
|
+
function errText(err) {
|
|
173131
|
+
return err instanceof Error ? err.message : String(err);
|
|
173132
|
+
}
|
|
173133
|
+
|
|
173134
|
+
// src/plugins/builtin/camera/webrtc-provider-server.ts
|
|
173135
|
+
var logger245 = Logger.get("CameraWebRtc");
|
|
173136
|
+
var nextGlobalSessionId = 0;
|
|
173137
|
+
function mintSessionId() {
|
|
173138
|
+
for (let i = 0; i <= 65534; i++) {
|
|
173139
|
+
const id = nextGlobalSessionId;
|
|
173140
|
+
nextGlobalSessionId = nextGlobalSessionId >= 65534 ? 0 : nextGlobalSessionId + 1;
|
|
173141
|
+
if (!hasRequestor(id)) return id;
|
|
173142
|
+
}
|
|
173143
|
+
return nextGlobalSessionId;
|
|
173144
|
+
}
|
|
172112
173145
|
var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
|
|
172113
173146
|
solicitOffer(request) {
|
|
172114
|
-
const id =
|
|
173147
|
+
const id = mintSessionId();
|
|
172115
173148
|
this.trackSession(id, request.streamUsage, request.originatingEndpointId);
|
|
172116
|
-
|
|
172117
|
-
|
|
173149
|
+
logger245.info(
|
|
173150
|
+
`solicitOffer session=${id} (${this.state.entityId}), deferred offer`
|
|
173151
|
+
);
|
|
173152
|
+
void this.state.bridge.startSession(id, this.state.entityId, {
|
|
173153
|
+
iceServers: request.iceServers,
|
|
173154
|
+
iceTransportPolicy: request.iceTransportPolicy
|
|
173155
|
+
}).catch(
|
|
173156
|
+
(err) => logger245.info(
|
|
173157
|
+
`solicitOffer startSession failed for ${this.state.entityId}: ${errText2(err)}`
|
|
173158
|
+
)
|
|
173159
|
+
);
|
|
172118
173160
|
return { webRtcSessionId: id, deferredOffer: true };
|
|
172119
173161
|
}
|
|
172120
173162
|
async provideOffer(request) {
|
|
172121
|
-
const id = request.webRtcSessionId ??
|
|
173163
|
+
const id = request.webRtcSessionId ?? mintSessionId();
|
|
173164
|
+
logger245.info(
|
|
173165
|
+
`provideOffer entry: entityId=${this.state.entityId} session=${id} (sdp ${request.sdp.length} chars)`
|
|
173166
|
+
);
|
|
172122
173167
|
if (request.webRtcSessionId == null) {
|
|
172123
173168
|
this.trackSession(
|
|
172124
173169
|
id,
|
|
@@ -172126,37 +173171,96 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
|
|
|
172126
173171
|
request.originatingEndpointId ?? EndpointNumber(0)
|
|
172127
173172
|
);
|
|
172128
173173
|
}
|
|
172129
|
-
const
|
|
172130
|
-
|
|
172131
|
-
|
|
172132
|
-
|
|
173174
|
+
const requestorEndpoint = request.originatingEndpointId ?? EndpointNumber(0);
|
|
173175
|
+
const session = this.context.session;
|
|
173176
|
+
if (session) {
|
|
173177
|
+
registerRequestor(id, {
|
|
173178
|
+
session,
|
|
173179
|
+
requestorEndpoint,
|
|
173180
|
+
env: this.env
|
|
173181
|
+
});
|
|
173182
|
+
}
|
|
173183
|
+
let answerSdp;
|
|
173184
|
+
try {
|
|
173185
|
+
answerSdp = await this.state.bridge.acceptControllerOffer(
|
|
173186
|
+
id,
|
|
173187
|
+
this.state.entityId,
|
|
173188
|
+
request.sdp,
|
|
173189
|
+
{
|
|
173190
|
+
iceServers: request.iceServers,
|
|
173191
|
+
iceTransportPolicy: request.iceTransportPolicy
|
|
173192
|
+
}
|
|
173193
|
+
);
|
|
173194
|
+
} catch (err) {
|
|
173195
|
+
const message = errText2(err);
|
|
173196
|
+
logger245.info(
|
|
173197
|
+
`provideOffer failed for ${this.state.entityId} session=${id}: ${message}`
|
|
173198
|
+
);
|
|
173199
|
+
unregisterRequestor(id);
|
|
173200
|
+
this.state.currentSessions = this.state.currentSessions.filter(
|
|
173201
|
+
(s) => s.id !== id
|
|
173202
|
+
);
|
|
173203
|
+
throw new StatusResponseError(
|
|
173204
|
+
`WebRTC offer failed: ${message}`,
|
|
173205
|
+
StatusCode.Failure
|
|
173206
|
+
);
|
|
173207
|
+
}
|
|
173208
|
+
logger245.info(
|
|
173209
|
+
`provideOffer answer computed for ${this.state.entityId} session=${id} (${answerSdp.length} chars); delivering via requestor`
|
|
172133
173210
|
);
|
|
172134
|
-
|
|
173211
|
+
const bridge = this.state.bridge;
|
|
173212
|
+
const state = this.state;
|
|
173213
|
+
deliverAnswerDeferred(id, answerSdp, async () => {
|
|
173214
|
+
await bridge.endSession(id).catch(() => {
|
|
173215
|
+
});
|
|
173216
|
+
unregisterRequestor(id);
|
|
173217
|
+
try {
|
|
173218
|
+
state.currentSessions = state.currentSessions.filter(
|
|
173219
|
+
(s) => s.id !== id
|
|
173220
|
+
);
|
|
173221
|
+
} catch {
|
|
173222
|
+
}
|
|
173223
|
+
});
|
|
172135
173224
|
return { webRtcSessionId: id };
|
|
172136
173225
|
}
|
|
172137
173226
|
provideAnswer(request) {
|
|
173227
|
+
logger245.info(
|
|
173228
|
+
`provideAnswer session=${request.webRtcSessionId} (sdp ${request.sdp.length} chars, ${this.state.entityId})`
|
|
173229
|
+
);
|
|
172138
173230
|
return this.state.bridge.acceptControllerAnswer(
|
|
172139
173231
|
request.webRtcSessionId,
|
|
172140
173232
|
request.sdp
|
|
172141
173233
|
);
|
|
172142
173234
|
}
|
|
172143
173235
|
async provideIceCandidates(request) {
|
|
173236
|
+
logger245.info(
|
|
173237
|
+
`provideIceCandidates session=${request.webRtcSessionId}: ${request.iceCandidates.length} candidate(s) (${this.state.entityId})`
|
|
173238
|
+
);
|
|
172144
173239
|
for (const c of request.iceCandidates) {
|
|
172145
173240
|
await this.state.bridge.addControllerIceCandidate(
|
|
172146
173241
|
request.webRtcSessionId,
|
|
172147
173242
|
c.candidate,
|
|
172148
|
-
c.sdpMid
|
|
173243
|
+
c.sdpMid,
|
|
173244
|
+
c.sdpmLineIndex
|
|
172149
173245
|
);
|
|
172150
173246
|
}
|
|
172151
173247
|
}
|
|
172152
173248
|
async endSession(request) {
|
|
173249
|
+
logger245.info(
|
|
173250
|
+
`endSession session=${request.webRtcSessionId} (${this.state.entityId})`
|
|
173251
|
+
);
|
|
172153
173252
|
await this.state.bridge.endSession(request.webRtcSessionId);
|
|
173253
|
+
unregisterRequestor(request.webRtcSessionId);
|
|
172154
173254
|
this.state.currentSessions = this.state.currentSessions.filter(
|
|
172155
173255
|
(s) => s.id !== request.webRtcSessionId
|
|
172156
173256
|
);
|
|
172157
173257
|
}
|
|
172158
173258
|
trackSession(id, streamUsage, peerEndpointId) {
|
|
172159
173259
|
const session = this.context.session;
|
|
173260
|
+
const fabricIndex = session?.associatedFabric?.fabricIndex;
|
|
173261
|
+
if (fabricIndex == null) {
|
|
173262
|
+
return;
|
|
173263
|
+
}
|
|
172160
173264
|
this.state.currentSessions = [
|
|
172161
173265
|
...this.state.currentSessions,
|
|
172162
173266
|
{
|
|
@@ -172165,16 +173269,18 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
|
|
|
172165
173269
|
peerEndpointId,
|
|
172166
173270
|
streamUsage,
|
|
172167
173271
|
metadataEnabled: false,
|
|
172168
|
-
fabricIndex
|
|
173272
|
+
fabricIndex
|
|
172169
173273
|
}
|
|
172170
173274
|
];
|
|
172171
173275
|
}
|
|
172172
173276
|
};
|
|
173277
|
+
function errText2(err) {
|
|
173278
|
+
return err instanceof Error ? err.message : String(err);
|
|
173279
|
+
}
|
|
172173
173280
|
((CameraWebRtcProviderServer2) => {
|
|
172174
173281
|
class State extends WebRtcTransportProviderServer.State {
|
|
172175
173282
|
bridge;
|
|
172176
173283
|
entityId;
|
|
172177
|
-
nextSessionId = 1;
|
|
172178
173284
|
}
|
|
172179
173285
|
CameraWebRtcProviderServer2.State = State;
|
|
172180
173286
|
})(CameraWebRtcProviderServer || (CameraWebRtcProviderServer = {}));
|
|
@@ -172193,8 +173299,7 @@ function createCameraEndpointType(bridge, entityId, sensor = defaultSensorParams
|
|
|
172193
173299
|
webRtcTransportProvider: {
|
|
172194
173300
|
bridge,
|
|
172195
173301
|
entityId,
|
|
172196
|
-
currentSessions: []
|
|
172197
|
-
nextSessionId: 1
|
|
173302
|
+
currentSessions: []
|
|
172198
173303
|
},
|
|
172199
173304
|
cameraAvStreamManagement: {
|
|
172200
173305
|
bridge,
|
|
@@ -172256,128 +173361,232 @@ function createCameraEndpointType(bridge, entityId, sensor = defaultSensorParams
|
|
|
172256
173361
|
}
|
|
172257
173362
|
|
|
172258
173363
|
// src/plugins/builtin/camera/webrtc-bridge.ts
|
|
173364
|
+
init_esm();
|
|
172259
173365
|
import {
|
|
172260
173366
|
createConnection as createConnection3,
|
|
172261
173367
|
createLongLivedTokenAuth as createLongLivedTokenAuth2
|
|
172262
173368
|
} from "home-assistant-js-websocket";
|
|
172263
173369
|
import { RTCPeerConnection } from "werift";
|
|
173370
|
+
var logger246 = Logger.get("CameraWebRtc");
|
|
173371
|
+
var DEFAULT_HA_WEBRTC_TIMEOUT_MS = 15e3;
|
|
173372
|
+
var haWebRtcTimeoutMs = DEFAULT_HA_WEBRTC_TIMEOUT_MS;
|
|
172264
173373
|
var WebRtcBridge = class {
|
|
172265
|
-
constructor(config11) {
|
|
173374
|
+
constructor(config11, deps = {}) {
|
|
172266
173375
|
this.config = config11;
|
|
173376
|
+
this.deps = deps;
|
|
172267
173377
|
}
|
|
172268
173378
|
config;
|
|
173379
|
+
deps;
|
|
172269
173380
|
connection;
|
|
172270
173381
|
sessions = /* @__PURE__ */ new Map();
|
|
172271
173382
|
async ha() {
|
|
172272
173383
|
if (this.connection) return this.connection;
|
|
172273
|
-
|
|
172274
|
-
this.config
|
|
172275
|
-
|
|
172276
|
-
|
|
172277
|
-
|
|
173384
|
+
if (this.deps.connect) {
|
|
173385
|
+
this.connection = await this.deps.connect(this.config);
|
|
173386
|
+
} else {
|
|
173387
|
+
const auth = createLongLivedTokenAuth2(
|
|
173388
|
+
this.config.haUrl,
|
|
173389
|
+
this.config.haToken
|
|
173390
|
+
);
|
|
173391
|
+
this.connection = await createConnection3({ auth });
|
|
173392
|
+
}
|
|
172278
173393
|
return this.connection;
|
|
172279
173394
|
}
|
|
172280
173395
|
// We offer to the controller; it answers. Pulls HA media first, then forwards.
|
|
172281
|
-
async startSession(matterSessionId, entityId) {
|
|
173396
|
+
async startSession(matterSessionId, entityId, ice) {
|
|
172282
173397
|
const haPeer = new RTCPeerConnection();
|
|
172283
|
-
const controllerPeer = new RTCPeerConnection(
|
|
172284
|
-
|
|
172285
|
-
|
|
172286
|
-
|
|
172287
|
-
|
|
173398
|
+
const controllerPeer = new RTCPeerConnection(
|
|
173399
|
+
this.controllerConfig(entityId, ice)
|
|
173400
|
+
);
|
|
173401
|
+
this.wireStateLogging(haPeer, "haPeer", entityId);
|
|
173402
|
+
this.wireStateLogging(controllerPeer, "controllerPeer", entityId);
|
|
173403
|
+
const senders = /* @__PURE__ */ new Map();
|
|
173404
|
+
senders.set(
|
|
173405
|
+
"video",
|
|
173406
|
+
controllerPeer.addTransceiver("video", { direction: "sendonly" })
|
|
173407
|
+
);
|
|
173408
|
+
this.forwardHaTracks(haPeer, senders, entityId);
|
|
173409
|
+
await this.closeExistingPeers(matterSessionId);
|
|
173410
|
+
this.sessions.set(matterSessionId, { entityId, haPeer, controllerPeer });
|
|
173411
|
+
try {
|
|
173412
|
+
haPeer.addTransceiver("video", { direction: "recvonly" });
|
|
173413
|
+
haPeer.addTransceiver("audio", { direction: "recvonly" });
|
|
173414
|
+
const haOffer = await haPeer.createOffer();
|
|
173415
|
+
await haPeer.setLocalDescription(haOffer);
|
|
173416
|
+
const haOfferSdp = this.localSdp(haPeer);
|
|
173417
|
+
logger246.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
|
|
173418
|
+
const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
|
|
173419
|
+
entityId,
|
|
173420
|
+
haOfferSdp,
|
|
173421
|
+
haPeer
|
|
172288
173422
|
);
|
|
172289
|
-
|
|
172290
|
-
|
|
172291
|
-
|
|
172292
|
-
|
|
172293
|
-
|
|
172294
|
-
|
|
172295
|
-
|
|
172296
|
-
|
|
172297
|
-
|
|
172298
|
-
|
|
172299
|
-
|
|
172300
|
-
|
|
172301
|
-
|
|
172302
|
-
|
|
172303
|
-
|
|
172304
|
-
|
|
172305
|
-
|
|
172306
|
-
|
|
172307
|
-
|
|
172308
|
-
|
|
172309
|
-
|
|
172310
|
-
|
|
172311
|
-
|
|
172312
|
-
|
|
173423
|
+
const session = this.sessions.get(matterSessionId);
|
|
173424
|
+
if (session) {
|
|
173425
|
+
session.haSessionId = sessionId;
|
|
173426
|
+
session.haUnsubscribe = unsubscribe;
|
|
173427
|
+
}
|
|
173428
|
+
await haPeer.setRemoteDescription({ type: "answer", sdp: answer });
|
|
173429
|
+
const iceCandidates = [];
|
|
173430
|
+
controllerPeer.onIceCandidate.subscribe((c) => {
|
|
173431
|
+
if (c?.candidate)
|
|
173432
|
+
iceCandidates.push({
|
|
173433
|
+
candidate: c.candidate,
|
|
173434
|
+
sdpMid: c.sdpMid ?? null
|
|
173435
|
+
});
|
|
173436
|
+
});
|
|
173437
|
+
const controllerOffer = await controllerPeer.createOffer();
|
|
173438
|
+
await controllerPeer.setLocalDescription(controllerOffer);
|
|
173439
|
+
const sdp = this.localSdp(controllerPeer);
|
|
173440
|
+
logger246.info(
|
|
173441
|
+
`controller offer created for ${entityId} (${sdp.length} chars)`
|
|
173442
|
+
);
|
|
173443
|
+
return { sdp, iceCandidates };
|
|
173444
|
+
} catch (err) {
|
|
173445
|
+
logger246.info(`startSession failed for ${entityId}: ${errText3(err)}`);
|
|
173446
|
+
await this.endSession(matterSessionId);
|
|
173447
|
+
throw err;
|
|
173448
|
+
}
|
|
172313
173449
|
}
|
|
172314
173450
|
// ProvideOffer flow: controller sent its offer. Pull HA media, answer it.
|
|
172315
173451
|
// Returning the answer to the controller is the unverified Requestor path.
|
|
172316
|
-
async acceptControllerOffer(matterSessionId, entityId, controllerOfferSdp) {
|
|
173452
|
+
async acceptControllerOffer(matterSessionId, entityId, controllerOfferSdp, ice) {
|
|
172317
173453
|
const haPeer = new RTCPeerConnection();
|
|
172318
|
-
const controllerPeer = new RTCPeerConnection(
|
|
172319
|
-
|
|
172320
|
-
|
|
172321
|
-
|
|
172322
|
-
|
|
172323
|
-
|
|
172324
|
-
|
|
173454
|
+
const controllerPeer = new RTCPeerConnection(
|
|
173455
|
+
this.controllerConfig(entityId, ice)
|
|
173456
|
+
);
|
|
173457
|
+
this.wireStateLogging(haPeer, "haPeer", entityId);
|
|
173458
|
+
this.wireStateLogging(controllerPeer, "controllerPeer", entityId);
|
|
173459
|
+
const senders = /* @__PURE__ */ new Map();
|
|
173460
|
+
for (const kind of offerKinds(controllerOfferSdp)) {
|
|
173461
|
+
senders.set(
|
|
173462
|
+
kind,
|
|
173463
|
+
controllerPeer.addTransceiver(kind, { direction: "sendonly" })
|
|
173464
|
+
);
|
|
173465
|
+
}
|
|
173466
|
+
this.forwardHaTracks(haPeer, senders, entityId);
|
|
173467
|
+
await this.closeExistingPeers(matterSessionId);
|
|
172325
173468
|
this.sessions.set(matterSessionId, { entityId, haPeer, controllerPeer });
|
|
172326
|
-
|
|
172327
|
-
|
|
172328
|
-
|
|
172329
|
-
|
|
172330
|
-
|
|
172331
|
-
|
|
172332
|
-
|
|
172333
|
-
|
|
172334
|
-
|
|
172335
|
-
|
|
172336
|
-
|
|
172337
|
-
|
|
172338
|
-
|
|
173469
|
+
try {
|
|
173470
|
+
haPeer.addTransceiver("video", { direction: "recvonly" });
|
|
173471
|
+
haPeer.addTransceiver("audio", { direction: "recvonly" });
|
|
173472
|
+
const haOffer = await haPeer.createOffer();
|
|
173473
|
+
await haPeer.setLocalDescription(haOffer);
|
|
173474
|
+
const haOfferSdp = this.localSdp(haPeer);
|
|
173475
|
+
logger246.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
|
|
173476
|
+
const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
|
|
173477
|
+
entityId,
|
|
173478
|
+
haOfferSdp,
|
|
173479
|
+
haPeer
|
|
173480
|
+
);
|
|
173481
|
+
const session = this.sessions.get(matterSessionId);
|
|
173482
|
+
if (session) {
|
|
173483
|
+
session.haSessionId = sessionId;
|
|
173484
|
+
session.haUnsubscribe = unsubscribe;
|
|
173485
|
+
}
|
|
173486
|
+
await haPeer.setRemoteDescription({ type: "answer", sdp: answer });
|
|
173487
|
+
await controllerPeer.setRemoteDescription({
|
|
173488
|
+
type: "offer",
|
|
173489
|
+
sdp: controllerOfferSdp
|
|
173490
|
+
});
|
|
173491
|
+
const controllerAnswer = await controllerPeer.createAnswer();
|
|
173492
|
+
await controllerPeer.setLocalDescription(controllerAnswer);
|
|
173493
|
+
const answerSdp = this.localSdp(controllerPeer);
|
|
173494
|
+
logger246.info(
|
|
173495
|
+
`controller answer created for ${entityId} (${answerSdp.length} chars)`
|
|
173496
|
+
);
|
|
173497
|
+
return answerSdp;
|
|
173498
|
+
} catch (err) {
|
|
173499
|
+
logger246.info(
|
|
173500
|
+
`acceptControllerOffer failed for ${entityId}: ${errText3(err)}`
|
|
173501
|
+
);
|
|
173502
|
+
await this.endSession(matterSessionId);
|
|
173503
|
+
throw err;
|
|
173504
|
+
}
|
|
172339
173505
|
}
|
|
172340
173506
|
/** Apply the Matter controller's SDP answer to the controller peer. */
|
|
172341
173507
|
async acceptControllerAnswer(matterSessionId, sdp) {
|
|
172342
173508
|
const session = this.sessions.get(matterSessionId);
|
|
172343
173509
|
if (!session) return;
|
|
173510
|
+
logger246.debug(`controller answer applied for ${session.entityId}`);
|
|
172344
173511
|
await session.controllerPeer.setRemoteDescription({ type: "answer", sdp });
|
|
172345
173512
|
}
|
|
172346
173513
|
/** Add a remote ICE candidate from the Matter controller. */
|
|
172347
|
-
async addControllerIceCandidate(matterSessionId, candidate, sdpMid) {
|
|
173514
|
+
async addControllerIceCandidate(matterSessionId, candidate, sdpMid, sdpMLineIndex) {
|
|
172348
173515
|
const session = this.sessions.get(matterSessionId);
|
|
172349
173516
|
if (!session) return;
|
|
173517
|
+
logger246.debug(
|
|
173518
|
+
`controller ICE candidate for ${session.entityId}: ${candidate}`
|
|
173519
|
+
);
|
|
172350
173520
|
await session.controllerPeer.addIceCandidate({
|
|
172351
173521
|
candidate,
|
|
172352
|
-
sdpMid: sdpMid ?? void 0
|
|
173522
|
+
sdpMid: sdpMid ?? void 0,
|
|
173523
|
+
sdpMLineIndex: sdpMLineIndex ?? void 0
|
|
173524
|
+
});
|
|
173525
|
+
}
|
|
173526
|
+
// Close the peers of a session we are about to overwrite. Leaves no map
|
|
173527
|
+
// entry; the caller replaces it with fresh peers under the same id.
|
|
173528
|
+
async closeExistingPeers(matterSessionId) {
|
|
173529
|
+
const prior = this.sessions.get(matterSessionId);
|
|
173530
|
+
if (!prior) return;
|
|
173531
|
+
logger246.info(
|
|
173532
|
+
`replacing session ${matterSessionId} (${prior.entityId}), closing prior peers`
|
|
173533
|
+
);
|
|
173534
|
+
if (prior.haUnsubscribe) {
|
|
173535
|
+
await Promise.resolve(prior.haUnsubscribe()).catch(
|
|
173536
|
+
(err) => logger246.debug(`HA unsubscribe failed: ${errText3(err)}`)
|
|
173537
|
+
);
|
|
173538
|
+
}
|
|
173539
|
+
await prior.haPeer.close().catch(() => {
|
|
173540
|
+
});
|
|
173541
|
+
await prior.controllerPeer.close().catch(() => {
|
|
173542
|
+
});
|
|
173543
|
+
await this.stopHaStream(prior).catch(() => {
|
|
172353
173544
|
});
|
|
172354
173545
|
}
|
|
172355
173546
|
async endSession(matterSessionId) {
|
|
172356
173547
|
const session = this.sessions.get(matterSessionId);
|
|
172357
173548
|
if (!session) return;
|
|
173549
|
+
logger246.info(`ending session ${matterSessionId} (${session.entityId})`);
|
|
172358
173550
|
this.sessions.delete(matterSessionId);
|
|
172359
|
-
|
|
172360
|
-
|
|
172361
|
-
|
|
172362
|
-
|
|
172363
|
-
const conn = await this.ha();
|
|
172364
|
-
await conn.sendMessagePromise({
|
|
172365
|
-
type: "camera/webrtc/candidate",
|
|
172366
|
-
entity_id: session.entityId,
|
|
172367
|
-
session_id: session.haSessionId,
|
|
172368
|
-
candidate: { candidate: "" }
|
|
172369
|
-
});
|
|
172370
|
-
} catch {
|
|
172371
|
-
}
|
|
173551
|
+
if (session.haUnsubscribe) {
|
|
173552
|
+
await Promise.resolve(session.haUnsubscribe()).catch(
|
|
173553
|
+
(err) => logger246.debug(`HA unsubscribe failed: ${errText3(err)}`)
|
|
173554
|
+
);
|
|
172372
173555
|
}
|
|
173556
|
+
await session.haPeer.close().catch(() => {
|
|
173557
|
+
});
|
|
173558
|
+
await session.controllerPeer.close().catch(() => {
|
|
173559
|
+
});
|
|
173560
|
+
await this.stopHaStream(session).catch(() => {
|
|
173561
|
+
});
|
|
173562
|
+
}
|
|
173563
|
+
// Tell HA to stop the stream if we tracked an HA session id. Best effort.
|
|
173564
|
+
async stopHaStream(session) {
|
|
173565
|
+
if (!session.haSessionId) return;
|
|
173566
|
+
const conn = await this.ha();
|
|
173567
|
+
await conn.sendMessagePromise({
|
|
173568
|
+
type: "camera/webrtc/candidate",
|
|
173569
|
+
entity_id: session.entityId,
|
|
173570
|
+
session_id: session.haSessionId,
|
|
173571
|
+
candidate: { candidate: "" }
|
|
173572
|
+
});
|
|
172373
173573
|
}
|
|
172374
173574
|
// Grab a still JPEG via HA's camera proxy (for CaptureSnapshot).
|
|
172375
173575
|
async snapshot(entityId) {
|
|
172376
173576
|
const url2 = `${this.config.haUrl}/api/camera_proxy/${entityId}`;
|
|
172377
|
-
|
|
172378
|
-
|
|
172379
|
-
|
|
173577
|
+
let res;
|
|
173578
|
+
try {
|
|
173579
|
+
res = await fetch(url2, {
|
|
173580
|
+
headers: { Authorization: `Bearer ${this.config.haToken}` }
|
|
173581
|
+
});
|
|
173582
|
+
} catch (err) {
|
|
173583
|
+
logger246.info(`snapshot fetch failed for ${entityId}: ${errText3(err)}`);
|
|
173584
|
+
throw err;
|
|
173585
|
+
}
|
|
172380
173586
|
if (!res.ok) {
|
|
173587
|
+
logger246.info(
|
|
173588
|
+
`snapshot fetch failed for ${entityId}: camera_proxy ${res.status}`
|
|
173589
|
+
);
|
|
172381
173590
|
throw new Error(`HA camera_proxy ${entityId}: ${res.status}`);
|
|
172382
173591
|
}
|
|
172383
173592
|
return new Uint8Array(await res.arrayBuffer());
|
|
@@ -172389,29 +173598,157 @@ var WebRtcBridge = class {
|
|
|
172389
173598
|
this.connection?.close();
|
|
172390
173599
|
this.connection = void 0;
|
|
172391
173600
|
}
|
|
172392
|
-
//
|
|
172393
|
-
|
|
173601
|
+
// Forward every track HA sends into the matching pre-added controller sender.
|
|
173602
|
+
forwardHaTracks(haPeer, senders, entityId) {
|
|
173603
|
+
haPeer.onTrack.subscribe((track) => {
|
|
173604
|
+
logger246.info(`onTrack ${track.kind} from HA (${entityId})`);
|
|
173605
|
+
const transceiver = senders.get(track.kind);
|
|
173606
|
+
if (!transceiver) {
|
|
173607
|
+
logger246.info(
|
|
173608
|
+
`no controller transceiver for ${track.kind} (${entityId})`
|
|
173609
|
+
);
|
|
173610
|
+
return;
|
|
173611
|
+
}
|
|
173612
|
+
let firstLogged = false;
|
|
173613
|
+
track.onReceiveRtp.subscribe((rtp) => {
|
|
173614
|
+
if (!firstLogged) {
|
|
173615
|
+
firstLogged = true;
|
|
173616
|
+
logger246.info(`first RTP forwarded for ${track.kind} (${entityId})`);
|
|
173617
|
+
}
|
|
173618
|
+
transceiver.sender.sendRtp(rtp);
|
|
173619
|
+
});
|
|
173620
|
+
});
|
|
173621
|
+
}
|
|
173622
|
+
// Log connection/ice state transitions (these events fire on change only).
|
|
173623
|
+
wireStateLogging(peer, label, entityId) {
|
|
173624
|
+
peer.connectionStateChange.subscribe(
|
|
173625
|
+
(s) => logger246.info(`${label} connectionState=${s} (${entityId})`)
|
|
173626
|
+
);
|
|
173627
|
+
peer.iceConnectionStateChange.subscribe(
|
|
173628
|
+
(s) => logger246.info(`${label} iceConnectionState=${s} (${entityId})`)
|
|
173629
|
+
);
|
|
173630
|
+
}
|
|
173631
|
+
// Map the Matter ICE struct onto werift's config. Only the controller peer
|
|
173632
|
+
// uses controller-supplied servers; the HA peer keeps werift defaults.
|
|
173633
|
+
controllerConfig(entityId, ice) {
|
|
173634
|
+
const iceServers = (ice?.iceServers ?? []).flatMap(
|
|
173635
|
+
(s) => (s.urLs ?? []).map((url2) => ({
|
|
173636
|
+
urls: url2,
|
|
173637
|
+
username: s.username,
|
|
173638
|
+
credential: s.credential
|
|
173639
|
+
}))
|
|
173640
|
+
);
|
|
173641
|
+
if (iceServers.length === 0) {
|
|
173642
|
+
logger246.info(`controller peer using default ICE servers (${entityId})`);
|
|
173643
|
+
return void 0;
|
|
173644
|
+
}
|
|
173645
|
+
const policy = ice?.iceTransportPolicy === "relay" || ice?.iceTransportPolicy === "all" ? ice.iceTransportPolicy : void 0;
|
|
173646
|
+
logger246.info(
|
|
173647
|
+
`controller peer using ${iceServers.length} controller ICE server(s)${policy ? ` policy=${policy}` : ""} (${entityId})`
|
|
173648
|
+
);
|
|
173649
|
+
return policy ? { iceServers, iceTransportPolicy: policy } : { iceServers };
|
|
173650
|
+
}
|
|
173651
|
+
localSdp(peer) {
|
|
173652
|
+
const local = peer.localDescription;
|
|
173653
|
+
if (!local) throw new Error("no local description");
|
|
173654
|
+
return local.sdp;
|
|
173655
|
+
}
|
|
173656
|
+
// Feed an HA trickle-ICE candidate into the HA peer.
|
|
173657
|
+
feedHaCandidate(entityId, haPeer, candidate) {
|
|
173658
|
+
const value = candidate?.candidate;
|
|
173659
|
+
if (!value) return;
|
|
173660
|
+
logger246.debug(`HA ICE candidate for ${entityId}: ${value}`);
|
|
173661
|
+
void haPeer.addIceCandidate({
|
|
173662
|
+
candidate: value,
|
|
173663
|
+
sdpMid: candidate?.sdpMid ?? void 0,
|
|
173664
|
+
sdpMLineIndex: candidate?.sdpMLineIndex ?? void 0
|
|
173665
|
+
}).catch(
|
|
173666
|
+
(e) => logger246.debug(
|
|
173667
|
+
`HA addIceCandidate failed for ${entityId}: ${errText3(e)}`
|
|
173668
|
+
)
|
|
173669
|
+
);
|
|
173670
|
+
}
|
|
173671
|
+
// Offer to HA's WebRTC and resolve on the first answer. Rejects on an error
|
|
173672
|
+
// reply or after a timeout; keeps the subscription open past the answer so HA
|
|
173673
|
+
// trickle candidates keep flowing into the HA peer until the session ends.
|
|
173674
|
+
async requestHaWebRtc(entityId, offerSdp, haPeer) {
|
|
172394
173675
|
const conn = await this.ha();
|
|
172395
|
-
|
|
173676
|
+
let unsubscribe = () => {
|
|
173677
|
+
};
|
|
173678
|
+
let sessionId;
|
|
173679
|
+
const answer = await new Promise((resolve11, reject) => {
|
|
172396
173680
|
let settled = false;
|
|
173681
|
+
let rejected = false;
|
|
173682
|
+
let timer;
|
|
173683
|
+
const settle = (fn) => {
|
|
173684
|
+
if (settled) return;
|
|
173685
|
+
settled = true;
|
|
173686
|
+
clearTimeout(timer);
|
|
173687
|
+
fn();
|
|
173688
|
+
};
|
|
173689
|
+
const fail = (err) => settle(() => {
|
|
173690
|
+
rejected = true;
|
|
173691
|
+
unsubscribe();
|
|
173692
|
+
reject(err);
|
|
173693
|
+
});
|
|
173694
|
+
timer = setTimeout(() => {
|
|
173695
|
+
logger246.info(
|
|
173696
|
+
`HA WebRTC timed out after ${haWebRtcTimeoutMs}ms for ${entityId}`
|
|
173697
|
+
);
|
|
173698
|
+
fail(
|
|
173699
|
+
new Error(`HA WebRTC request timed out after ${haWebRtcTimeoutMs}ms`)
|
|
173700
|
+
);
|
|
173701
|
+
}, haWebRtcTimeoutMs);
|
|
172397
173702
|
conn.subscribeMessage(
|
|
172398
173703
|
(msg) => {
|
|
172399
|
-
|
|
172400
|
-
|
|
172401
|
-
|
|
172402
|
-
|
|
172403
|
-
|
|
172404
|
-
|
|
172405
|
-
|
|
173704
|
+
switch (msg.type) {
|
|
173705
|
+
case "session":
|
|
173706
|
+
if (msg.session_id) sessionId = msg.session_id;
|
|
173707
|
+
break;
|
|
173708
|
+
case "answer":
|
|
173709
|
+
if (msg.answer) {
|
|
173710
|
+
if (msg.session_id) sessionId = msg.session_id;
|
|
173711
|
+
const ans = msg.answer;
|
|
173712
|
+
settle(() => {
|
|
173713
|
+
logger246.info(
|
|
173714
|
+
`HA answer received for ${entityId} (${ans.length} chars)`
|
|
173715
|
+
);
|
|
173716
|
+
resolve11(ans);
|
|
173717
|
+
});
|
|
173718
|
+
}
|
|
173719
|
+
break;
|
|
173720
|
+
case "error": {
|
|
173721
|
+
const detail = msg.message ?? msg.code ?? "unknown error";
|
|
173722
|
+
logger246.info(`HA WebRTC error for ${entityId}: ${detail}`);
|
|
173723
|
+
fail(new Error(`HA WebRTC error: ${detail}`));
|
|
173724
|
+
break;
|
|
172406
173725
|
}
|
|
172407
|
-
|
|
173726
|
+
case "candidate":
|
|
173727
|
+
this.feedHaCandidate(entityId, haPeer, msg.candidate);
|
|
173728
|
+
break;
|
|
172408
173729
|
}
|
|
172409
173730
|
},
|
|
172410
173731
|
{ type: "camera/webrtc/offer", entity_id: entityId, offer: offerSdp }
|
|
172411
|
-
).
|
|
173732
|
+
).then((unsub) => {
|
|
173733
|
+
unsubscribe = unsub;
|
|
173734
|
+
if (rejected) unsub();
|
|
173735
|
+
}).catch((err) => {
|
|
173736
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
173737
|
+
});
|
|
172412
173738
|
});
|
|
173739
|
+
return { answer, sessionId, unsubscribe };
|
|
172413
173740
|
}
|
|
172414
173741
|
};
|
|
173742
|
+
function offerKinds(sdp) {
|
|
173743
|
+
const kinds = [];
|
|
173744
|
+
for (const m of sdp.matchAll(/^m=(video|audio)/gm)) {
|
|
173745
|
+
kinds.push(m[1]);
|
|
173746
|
+
}
|
|
173747
|
+
return kinds;
|
|
173748
|
+
}
|
|
173749
|
+
function errText3(err) {
|
|
173750
|
+
return err instanceof Error ? err.message : String(err);
|
|
173751
|
+
}
|
|
172415
173752
|
|
|
172416
173753
|
// src/plugins/builtin/camera/camera-plugin.ts
|
|
172417
173754
|
var CONFIG_KEY = "config";
|
|
@@ -172544,6 +173881,7 @@ var CameraPlugin = class {
|
|
|
172544
173881
|
await this.bridge?.close().catch(() => {
|
|
172545
173882
|
});
|
|
172546
173883
|
this.bridge = void 0;
|
|
173884
|
+
unregisterAllRequestors();
|
|
172547
173885
|
}
|
|
172548
173886
|
};
|
|
172549
173887
|
|
|
@@ -172557,7 +173895,7 @@ import {
|
|
|
172557
173895
|
getCollection
|
|
172558
173896
|
} from "home-assistant-js-websocket";
|
|
172559
173897
|
import { atLeastHaVersion } from "home-assistant-js-websocket/dist/util.js";
|
|
172560
|
-
var
|
|
173898
|
+
var logger247 = Logger.get("SubscribeEntities");
|
|
172561
173899
|
function processEvent(store, updates) {
|
|
172562
173900
|
const state = { ...store.state };
|
|
172563
173901
|
if (updates.a) {
|
|
@@ -172583,7 +173921,7 @@ function processEvent(store, updates) {
|
|
|
172583
173921
|
for (const entityId in updates.c) {
|
|
172584
173922
|
let entityState = state[entityId];
|
|
172585
173923
|
if (!entityState) {
|
|
172586
|
-
|
|
173924
|
+
logger247.warn("Received state update for unknown entity", entityId);
|
|
172587
173925
|
continue;
|
|
172588
173926
|
}
|
|
172589
173927
|
entityState = { ...entityState };
|
|
@@ -172672,7 +174010,7 @@ var subscribeEntities = (conn, onChange, entityIds) => {
|
|
|
172672
174010
|
// src/services/bridges/entity-isolation-service.ts
|
|
172673
174011
|
init_esm();
|
|
172674
174012
|
init_diagnostic_event_bus();
|
|
172675
|
-
var
|
|
174013
|
+
var logger248 = Logger.get("EntityIsolation");
|
|
172676
174014
|
var EntityIsolationServiceImpl = class {
|
|
172677
174015
|
isolatedEntities = /* @__PURE__ */ new Map();
|
|
172678
174016
|
isolationCallbacks = /* @__PURE__ */ new Map();
|
|
@@ -172737,13 +174075,13 @@ var EntityIsolationServiceImpl = class {
|
|
|
172737
174075
|
}
|
|
172738
174076
|
const parsed = this.parseEndpointPath(msg);
|
|
172739
174077
|
if (!parsed) {
|
|
172740
|
-
|
|
174078
|
+
logger248.warn("Could not parse entity from error:", msg);
|
|
172741
174079
|
return false;
|
|
172742
174080
|
}
|
|
172743
174081
|
const { bridgeId, entityName } = parsed;
|
|
172744
174082
|
const callback = this.isolationCallbacks.get(bridgeId);
|
|
172745
174083
|
if (!callback) {
|
|
172746
|
-
|
|
174084
|
+
logger248.warn(
|
|
172747
174085
|
`No isolation callback registered for bridge ${bridgeId}, entity: ${entityName}`
|
|
172748
174086
|
);
|
|
172749
174087
|
return false;
|
|
@@ -172758,7 +174096,7 @@ var EntityIsolationServiceImpl = class {
|
|
|
172758
174096
|
reason,
|
|
172759
174097
|
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
172760
174098
|
});
|
|
172761
|
-
|
|
174099
|
+
logger248.warn(
|
|
172762
174100
|
`Isolating entity "${entityName}" from bridge ${bridgeId} due to: ${reason}`
|
|
172763
174101
|
);
|
|
172764
174102
|
diagnosticEventBus.emit("entity_error", `Entity isolated: ${entityName}`, {
|
|
@@ -172770,7 +174108,7 @@ var EntityIsolationServiceImpl = class {
|
|
|
172770
174108
|
await callback(entityName);
|
|
172771
174109
|
return true;
|
|
172772
174110
|
} catch (e) {
|
|
172773
|
-
|
|
174111
|
+
logger248.error(`Failed to isolate entity ${entityName}:`, e);
|
|
172774
174112
|
return false;
|
|
172775
174113
|
}
|
|
172776
174114
|
}
|
|
@@ -172799,128 +174137,16 @@ var EntityIsolationServiceImpl = class {
|
|
|
172799
174137
|
};
|
|
172800
174138
|
var EntityIsolationService = new EntityIsolationServiceImpl();
|
|
172801
174139
|
|
|
172802
|
-
// src/services/bridges/identity-resolver.ts
|
|
172803
|
-
var SEP = "\0";
|
|
172804
|
-
function identityKey(entity) {
|
|
172805
|
-
const uniqueId = entity.registry?.unique_id;
|
|
172806
|
-
const platform = entity.registry?.platform;
|
|
172807
|
-
if (!uniqueId || !platform) {
|
|
172808
|
-
return null;
|
|
172809
|
-
}
|
|
172810
|
-
const domain = entity.entity_id.split(".")[0];
|
|
172811
|
-
return platform + SEP + domain + SEP + uniqueId;
|
|
172812
|
-
}
|
|
172813
|
-
var IdentityResolver = class {
|
|
172814
|
-
constructor(identityStorage, mappingStorage) {
|
|
172815
|
-
this.identityStorage = identityStorage;
|
|
172816
|
-
this.mappingStorage = mappingStorage;
|
|
172817
|
-
}
|
|
172818
|
-
identityStorage;
|
|
172819
|
-
mappingStorage;
|
|
172820
|
-
async resolveIdentity(bridgeId, entity, mapping, opts) {
|
|
172821
|
-
const entityId = entity.entity_id;
|
|
172822
|
-
const key = identityKey(entity);
|
|
172823
|
-
if (key == null) {
|
|
172824
|
-
return {
|
|
172825
|
-
endpointId: createEndpointId(entityId, mapping?.customName),
|
|
172826
|
-
anchorEntityId: entityId,
|
|
172827
|
-
protected: false
|
|
172828
|
-
};
|
|
172829
|
-
}
|
|
172830
|
-
const existing = this.identityStorage.getIdentity(bridgeId, key);
|
|
172831
|
-
if (existing) {
|
|
172832
|
-
const renamedFrom = existing.lastEntityId !== entityId ? existing.lastEntityId ?? existing.anchorEntityId : void 0;
|
|
172833
|
-
if (opts.stableIdentity) {
|
|
172834
|
-
if (renamedFrom) {
|
|
172835
|
-
this.identityStorage.setIdentity(bridgeId, key, {
|
|
172836
|
-
...existing,
|
|
172837
|
-
lastEntityId: entityId
|
|
172838
|
-
});
|
|
172839
|
-
await this.rekeyMapping(bridgeId, renamedFrom, entityId);
|
|
172840
|
-
}
|
|
172841
|
-
return {
|
|
172842
|
-
endpointId: existing.endpointId,
|
|
172843
|
-
anchorEntityId: existing.anchorEntityId,
|
|
172844
|
-
protected: true,
|
|
172845
|
-
renamedFrom
|
|
172846
|
-
};
|
|
172847
|
-
}
|
|
172848
|
-
const liveEndpointId = createEndpointId(entityId, mapping?.customName);
|
|
172849
|
-
if (existing.endpointId !== liveEndpointId || existing.anchorEntityId !== entityId || existing.lastEntityId !== entityId) {
|
|
172850
|
-
this.identityStorage.setIdentity(bridgeId, key, {
|
|
172851
|
-
...existing,
|
|
172852
|
-
endpointId: liveEndpointId,
|
|
172853
|
-
anchorEntityId: entityId,
|
|
172854
|
-
lastEntityId: entityId
|
|
172855
|
-
});
|
|
172856
|
-
}
|
|
172857
|
-
return {
|
|
172858
|
-
endpointId: liveEndpointId,
|
|
172859
|
-
anchorEntityId: entityId,
|
|
172860
|
-
protected: false,
|
|
172861
|
-
renamedFrom
|
|
172862
|
-
};
|
|
172863
|
-
}
|
|
172864
|
-
const desired = createEndpointId(entityId, mapping?.customName);
|
|
172865
|
-
let endpointId = desired;
|
|
172866
|
-
if (opts.stableIdentity) {
|
|
172867
|
-
let n = 2;
|
|
172868
|
-
while (opts.isEndpointIdTaken(endpointId, key)) {
|
|
172869
|
-
endpointId = `${desired}_${n++}`;
|
|
172870
|
-
}
|
|
172871
|
-
}
|
|
172872
|
-
const record = {
|
|
172873
|
-
endpointId,
|
|
172874
|
-
anchorEntityId: entityId,
|
|
172875
|
-
lastEntityId: entityId,
|
|
172876
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
172877
|
-
};
|
|
172878
|
-
this.identityStorage.setIdentity(bridgeId, key, record);
|
|
172879
|
-
if (opts.stableIdentity) {
|
|
172880
|
-
return {
|
|
172881
|
-
endpointId: record.endpointId,
|
|
172882
|
-
anchorEntityId: record.anchorEntityId,
|
|
172883
|
-
protected: true
|
|
172884
|
-
};
|
|
172885
|
-
}
|
|
172886
|
-
return {
|
|
172887
|
-
endpointId: desired,
|
|
172888
|
-
anchorEntityId: entityId,
|
|
172889
|
-
protected: false
|
|
172890
|
-
};
|
|
172891
|
-
}
|
|
172892
|
-
// Carry a mapping from the old entity_id to the new one on rename. anchorEntityId
|
|
172893
|
-
// never changes, only the mapping key. Skips when no old mapping exists or a new
|
|
172894
|
-
// one already does, so a manual mapping under the new id is never clobbered.
|
|
172895
|
-
async rekeyMapping(bridgeId, oldEntityId, newEntityId) {
|
|
172896
|
-
if (oldEntityId === newEntityId) {
|
|
172897
|
-
return;
|
|
172898
|
-
}
|
|
172899
|
-
const old = this.mappingStorage.getMapping(bridgeId, oldEntityId);
|
|
172900
|
-
if (!old) {
|
|
172901
|
-
return;
|
|
172902
|
-
}
|
|
172903
|
-
if (this.mappingStorage.getMapping(bridgeId, newEntityId)) {
|
|
172904
|
-
return;
|
|
172905
|
-
}
|
|
172906
|
-
await this.mappingStorage.setMapping({
|
|
172907
|
-
...old,
|
|
172908
|
-
bridgeId,
|
|
172909
|
-
entityId: newEntityId
|
|
172910
|
-
});
|
|
172911
|
-
await this.mappingStorage.deleteMapping(bridgeId, oldEntityId);
|
|
172912
|
-
}
|
|
172913
|
-
};
|
|
172914
|
-
|
|
172915
174140
|
// src/services/bridges/bridge-endpoint-manager.ts
|
|
172916
174141
|
var MAX_ENTITY_ID_LENGTH = 150;
|
|
172917
174142
|
var ENDPOINT_REMOVAL_GRACE_MS = 6e4;
|
|
172918
174143
|
var BridgeEndpointManager = class extends Service {
|
|
172919
|
-
constructor(client,
|
|
174144
|
+
constructor(client, registry3, mappingStorage, identityStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
|
|
172920
174145
|
super("BridgeEndpointManager");
|
|
172921
174146
|
this.client = client;
|
|
172922
|
-
this.registry =
|
|
174147
|
+
this.registry = registry3;
|
|
172923
174148
|
this.mappingStorage = mappingStorage;
|
|
174149
|
+
this.identityStorage = identityStorage;
|
|
172924
174150
|
this.bridgeId = bridgeId;
|
|
172925
174151
|
this.log = log;
|
|
172926
174152
|
this.pluginManager = pluginManager;
|
|
@@ -172942,6 +174168,7 @@ var BridgeEndpointManager = class extends Service {
|
|
|
172942
174168
|
client;
|
|
172943
174169
|
registry;
|
|
172944
174170
|
mappingStorage;
|
|
174171
|
+
identityStorage;
|
|
172945
174172
|
bridgeId;
|
|
172946
174173
|
log;
|
|
172947
174174
|
pluginManager;
|
|
@@ -173339,6 +174566,17 @@ var BridgeEndpointManager = class extends Service {
|
|
|
173339
174566
|
this.mappingFingerprints.set(entityId, fp);
|
|
173340
174567
|
}
|
|
173341
174568
|
}
|
|
174569
|
+
const fullEntities = this.registry.fullEntities;
|
|
174570
|
+
stampIdentityPresence(
|
|
174571
|
+
this.identityStorage,
|
|
174572
|
+
this.bridgeId,
|
|
174573
|
+
buildPresentIdentityKeys(fullEntities)
|
|
174574
|
+
);
|
|
174575
|
+
stampMappingPresence(
|
|
174576
|
+
this.mappingStorage,
|
|
174577
|
+
this.bridgeId,
|
|
174578
|
+
buildPresentEntityIds(fullEntities)
|
|
174579
|
+
);
|
|
173342
174580
|
const existingEndpoints = [];
|
|
173343
174581
|
const now = Date.now();
|
|
173344
174582
|
for (const endpoint of endpoints) {
|
|
@@ -173617,8 +174855,8 @@ init_send_ha_message();
|
|
|
173617
174855
|
import { callService as callService2 } from "home-assistant-js-websocket";
|
|
173618
174856
|
import { keys as keys2, pickBy, values as values3 } from "lodash-es";
|
|
173619
174857
|
var BridgeRegistry = class _BridgeRegistry {
|
|
173620
|
-
constructor(
|
|
173621
|
-
this.registry =
|
|
174858
|
+
constructor(registry3, dataProvider, client) {
|
|
174859
|
+
this.registry = registry3;
|
|
173622
174860
|
this.dataProvider = dataProvider;
|
|
173623
174861
|
this.client = client;
|
|
173624
174862
|
this.refresh();
|
|
@@ -173658,6 +174896,11 @@ var BridgeRegistry = class _BridgeRegistry {
|
|
|
173658
174896
|
initialState(entityId) {
|
|
173659
174897
|
return this._states[entityId];
|
|
173660
174898
|
}
|
|
174899
|
+
// The complete HA entity set (unfiltered). Used by orphan tombstone stamping
|
|
174900
|
+
// so a filter change or a scope narrowing never looks like a removal.
|
|
174901
|
+
get fullEntities() {
|
|
174902
|
+
return this.registry.entities;
|
|
174903
|
+
}
|
|
173661
174904
|
// composed sub-entities may sit outside the bridge filter (#408), so these
|
|
173662
174905
|
// fall back to the full HA registry. keep them separate from the strict
|
|
173663
174906
|
// accessors above, every other caller must stay filtered.
|
|
@@ -174375,6 +175618,7 @@ init_dist();
|
|
|
174375
175618
|
init_esm7();
|
|
174376
175619
|
import * as os10 from "node:os";
|
|
174377
175620
|
init_diagnostic_event_bus();
|
|
175621
|
+
var imWrapMarker2 = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
|
|
174378
175622
|
var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
|
|
174379
175623
|
var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2 = 2500;
|
|
174380
175624
|
var MDNS_ADDRESS_CHECK_INTERVAL_MS2 = 6e4;
|
|
@@ -174382,11 +175626,11 @@ function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOStrin
|
|
|
174382
175626
|
return { ...state, last_updated: now };
|
|
174383
175627
|
}
|
|
174384
175628
|
var ServerModeBridge = class {
|
|
174385
|
-
constructor(
|
|
175629
|
+
constructor(logger251, dataProvider, endpointManager, server) {
|
|
174386
175630
|
this.dataProvider = dataProvider;
|
|
174387
175631
|
this.endpointManager = endpointManager;
|
|
174388
175632
|
this.server = server;
|
|
174389
|
-
this.log =
|
|
175633
|
+
this.log = logger251.get(`ServerModeBridge / ${dataProvider.id}`);
|
|
174390
175634
|
}
|
|
174391
175635
|
dataProvider;
|
|
174392
175636
|
endpointManager;
|
|
@@ -174407,6 +175651,12 @@ var ServerModeBridge = class {
|
|
|
174407
175651
|
sessionStartedAt = /* @__PURE__ */ new Map();
|
|
174408
175652
|
rotationTimer = null;
|
|
174409
175653
|
maxSessionAgeMs = 0;
|
|
175654
|
+
// Wedge watchdog: last inbound Interaction Model request time per session and
|
|
175655
|
+
// last time the watchdog rotated it, both keyed by the long-lived session
|
|
175656
|
+
// object so they clear when the session goes away.
|
|
175657
|
+
lastImRequestAt = /* @__PURE__ */ new WeakMap();
|
|
175658
|
+
wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
|
|
175659
|
+
wedgeWatchdogTimer = null;
|
|
174410
175660
|
// Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
|
|
174411
175661
|
// change forces a fresh operational announcement (#415).
|
|
174412
175662
|
mdnsAddressTimer = null;
|
|
@@ -174470,6 +175720,7 @@ var ServerModeBridge = class {
|
|
|
174470
175720
|
const sessionList = sessions.map((s) => {
|
|
174471
175721
|
const subCount = s.subscriptions.size;
|
|
174472
175722
|
totalSubscriptions += subCount;
|
|
175723
|
+
const subscriptions = summarizeSubscriptions(s.subscriptions);
|
|
174473
175724
|
const fi = typeof s.fabric?.fabricIndex === "number" ? s.fabric.fabricIndex : null;
|
|
174474
175725
|
if (fi !== null) {
|
|
174475
175726
|
const existing = fabricMap.get(fi) ?? {
|
|
@@ -174483,14 +175734,18 @@ var ServerModeBridge = class {
|
|
|
174483
175734
|
const nowMs = Date.now();
|
|
174484
175735
|
const lastActiveMsAgo = typeof s.activeTimestamp === "number" && s.activeTimestamp > 0 ? nowMs - s.activeTimestamp : null;
|
|
174485
175736
|
const lastAnyActivityMsAgo = typeof s.timestamp === "number" ? nowMs - s.timestamp : null;
|
|
175737
|
+
const lastImAt = this.lastImRequestAt.get(s);
|
|
175738
|
+
const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
|
|
174486
175739
|
const startedAt = this.sessionStartedAt.get(s.id);
|
|
174487
175740
|
return {
|
|
174488
175741
|
id: s.id,
|
|
174489
175742
|
peerNodeId: String(s.peerNodeId),
|
|
174490
175743
|
fabricIndex: fi,
|
|
174491
175744
|
subscriptionCount: subCount,
|
|
175745
|
+
subscriptions,
|
|
174492
175746
|
lastActiveMsAgo,
|
|
174493
175747
|
lastAnyActivityMsAgo,
|
|
175748
|
+
lastImRequestMsAgo,
|
|
174494
175749
|
isPeerActive: Boolean(s.isPeerActive),
|
|
174495
175750
|
ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
|
|
174496
175751
|
};
|
|
@@ -174554,6 +175809,7 @@ var ServerModeBridge = class {
|
|
|
174554
175809
|
this.wireSessionDiagnostics();
|
|
174555
175810
|
this.wireFabricWarnings();
|
|
174556
175811
|
this.startSessionRotation();
|
|
175812
|
+
this.startWedgeWatchdog();
|
|
174557
175813
|
this.startMdnsAddressWatch();
|
|
174558
175814
|
this.scheduleWarmStart();
|
|
174559
175815
|
logMemoryUsage(this.log, "server mode bridge running");
|
|
@@ -174571,6 +175827,7 @@ ${e?.toString()}`);
|
|
|
174571
175827
|
}
|
|
174572
175828
|
async stop(code = BridgeStatus.Stopped, reason = "Manually stopped") {
|
|
174573
175829
|
this.stopSessionRotation();
|
|
175830
|
+
this.stopWedgeWatchdog();
|
|
174574
175831
|
this.stopMdnsAddressWatch();
|
|
174575
175832
|
this.unwireSessionDiagnostics();
|
|
174576
175833
|
this.unwireFabricWarnings();
|
|
@@ -174603,6 +175860,7 @@ ${e?.toString()}`);
|
|
|
174603
175860
|
if (this.status.code === BridgeStatus.Running) {
|
|
174604
175861
|
this.startAutoForceSyncIfEnabled();
|
|
174605
175862
|
this.startSessionRotation();
|
|
175863
|
+
this.startWedgeWatchdog();
|
|
174606
175864
|
}
|
|
174607
175865
|
} catch (e) {
|
|
174608
175866
|
const reason = "Failed to update server mode bridge due to error:";
|
|
@@ -174787,6 +176045,29 @@ ${e?.toString()}`);
|
|
|
174787
176045
|
sessionManager.sessions.added.on(this.sessionAddedHandler);
|
|
174788
176046
|
sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
|
|
174789
176047
|
seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
|
|
176048
|
+
this.wireImRequestTracking();
|
|
176049
|
+
} catch {
|
|
176050
|
+
}
|
|
176051
|
+
}
|
|
176052
|
+
// Stamp the time of every inbound Interaction Model request per session by
|
|
176053
|
+
// wrapping InteractionServer.onNewExchange. The wedge watchdog reads these to
|
|
176054
|
+
// tell a live-but-consuming controller from one that only keeps acking.
|
|
176055
|
+
wireImRequestTracking() {
|
|
176056
|
+
try {
|
|
176057
|
+
const is = this.server.env.get(InteractionServer);
|
|
176058
|
+
const marked = is;
|
|
176059
|
+
if (marked[imWrapMarker2]) {
|
|
176060
|
+
return;
|
|
176061
|
+
}
|
|
176062
|
+
const original = is.onNewExchange.bind(is);
|
|
176063
|
+
is.onNewExchange = (exchange, message) => {
|
|
176064
|
+
const session = exchange.session;
|
|
176065
|
+
if (session) {
|
|
176066
|
+
this.lastImRequestAt.set(session, Date.now());
|
|
176067
|
+
}
|
|
176068
|
+
return original(exchange, message);
|
|
176069
|
+
};
|
|
176070
|
+
marked[imWrapMarker2] = true;
|
|
174790
176071
|
} catch {
|
|
174791
176072
|
}
|
|
174792
176073
|
}
|
|
@@ -175004,6 +176285,72 @@ ${e?.toString()}`);
|
|
|
175004
176285
|
this.rotationTimer = null;
|
|
175005
176286
|
}
|
|
175006
176287
|
}
|
|
176288
|
+
// Opt-in wedge watchdog: reuse the rotation check cadence (5min) to look for
|
|
176289
|
+
// the one session wedged on "Updating" and rotate just that one.
|
|
176290
|
+
startWedgeWatchdog() {
|
|
176291
|
+
this.stopWedgeWatchdog();
|
|
176292
|
+
if (!this.dataProvider.featureFlags?.wedgeWatchdog) {
|
|
176293
|
+
return;
|
|
176294
|
+
}
|
|
176295
|
+
this.wedgeWatchdogTimer = setInterval(
|
|
176296
|
+
() => this.runWedgeWatchdogCheck(),
|
|
176297
|
+
ROTATION_CHECK_INTERVAL_MS
|
|
176298
|
+
);
|
|
176299
|
+
this.log.info(
|
|
176300
|
+
`Wedge watchdog: checking every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
176301
|
+
);
|
|
176302
|
+
}
|
|
176303
|
+
stopWedgeWatchdog() {
|
|
176304
|
+
if (this.wedgeWatchdogTimer) {
|
|
176305
|
+
clearInterval(this.wedgeWatchdogTimer);
|
|
176306
|
+
this.wedgeWatchdogTimer = null;
|
|
176307
|
+
}
|
|
176308
|
+
}
|
|
176309
|
+
// Rotate exactly the sessions the pure rule flags as wedged. Closing is the
|
|
176310
|
+
// same graceful-then-force path age rotation uses, so a false positive just
|
|
176311
|
+
// re-CASEs the controller.
|
|
176312
|
+
runWedgeWatchdogCheck() {
|
|
176313
|
+
try {
|
|
176314
|
+
const sessionManager = this.server.env.get(SessionManager);
|
|
176315
|
+
const now = Date.now();
|
|
176316
|
+
const closes = [];
|
|
176317
|
+
for (const s of [...sessionManager.sessions]) {
|
|
176318
|
+
if (s.isClosing) continue;
|
|
176319
|
+
const startedAt = this.sessionStartedAt.get(s.id);
|
|
176320
|
+
const sessionAgeMs = startedAt != null ? now - startedAt : 0;
|
|
176321
|
+
const lastImAt = this.lastImRequestAt.get(s);
|
|
176322
|
+
const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
|
|
176323
|
+
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
176324
|
+
const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
|
|
176325
|
+
if (!decideWedgeRotation({
|
|
176326
|
+
subscriptionCount: s.subscriptions.size,
|
|
176327
|
+
sessionAgeMs,
|
|
176328
|
+
lastImRequestMsAgo,
|
|
176329
|
+
lastRotatedMsAgo
|
|
176330
|
+
})) {
|
|
176331
|
+
continue;
|
|
176332
|
+
}
|
|
176333
|
+
const silenceMin = Math.round(
|
|
176334
|
+
(lastImRequestMsAgo ?? sessionAgeMs) / 6e4
|
|
176335
|
+
);
|
|
176336
|
+
this.log.info(
|
|
176337
|
+
`Wedge watchdog: rotating session ${s.id}, no inbound interaction for ${silenceMin}min`
|
|
176338
|
+
);
|
|
176339
|
+
this.wedgeLastRotatedAt.set(s, now);
|
|
176340
|
+
closes.push(
|
|
176341
|
+
s.initiateClose().catch(() => {
|
|
176342
|
+
return s.initiateForceClose({
|
|
176343
|
+
cause: new Error("wedge watchdog, forcing")
|
|
176344
|
+
});
|
|
176345
|
+
})
|
|
176346
|
+
);
|
|
176347
|
+
}
|
|
176348
|
+
if (closes.length > 0) {
|
|
176349
|
+
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
176350
|
+
}
|
|
176351
|
+
} catch {
|
|
176352
|
+
}
|
|
176353
|
+
}
|
|
175007
176354
|
// Poll the interface addresses mDNS advertises and re-announce when they
|
|
175008
176355
|
// change. matter.js caches the operational records at first announcement, so
|
|
175009
176356
|
// a dynamic ISP IPv6 prefix change keeps advertising the dead global address
|
|
@@ -175313,12 +176660,12 @@ function ServerModeVacuumDevice(homeAssistantEntity, includeOnOff = false, clean
|
|
|
175313
176660
|
}
|
|
175314
176661
|
|
|
175315
176662
|
// src/matter/endpoints/server-mode-vacuum-endpoint.ts
|
|
175316
|
-
var
|
|
176663
|
+
var logger249 = Logger.get("ServerModeVacuumEndpoint");
|
|
175317
176664
|
var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEndpoint {
|
|
175318
|
-
static async create(
|
|
175319
|
-
const deviceRegistry =
|
|
175320
|
-
let state =
|
|
175321
|
-
const entity =
|
|
176665
|
+
static async create(registry3, entityId, mapping, endpointId) {
|
|
176666
|
+
const deviceRegistry = registry3.deviceOf(entityId);
|
|
176667
|
+
let state = registry3.initialState(entityId);
|
|
176668
|
+
const entity = registry3.entity(entityId);
|
|
175322
176669
|
if (!state) {
|
|
175323
176670
|
return void 0;
|
|
175324
176671
|
}
|
|
@@ -175326,12 +176673,12 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175326
176673
|
if (mapping?.disableBatteryMapping && mapping.batteryEntity) {
|
|
175327
176674
|
effectiveMapping = { ...mapping, batteryEntity: void 0 };
|
|
175328
176675
|
}
|
|
175329
|
-
|
|
176676
|
+
logger249.info(
|
|
175330
176677
|
`${entityId}: device_id=${entity.device_id}, manualBattery=${mapping?.batteryEntity ?? "none"}`
|
|
175331
176678
|
);
|
|
175332
176679
|
if (entity.device_id) {
|
|
175333
176680
|
if (!mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
|
|
175334
|
-
const batteryEntityId =
|
|
176681
|
+
const batteryEntityId = registry3.findBatteryEntityForDevice(
|
|
175335
176682
|
entity.device_id
|
|
175336
176683
|
);
|
|
175337
176684
|
if (batteryEntityId && batteryEntityId !== entityId) {
|
|
@@ -175340,22 +176687,22 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175340
176687
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175341
176688
|
batteryEntity: batteryEntityId
|
|
175342
176689
|
};
|
|
175343
|
-
|
|
175344
|
-
|
|
176690
|
+
registry3.markBatteryEntityUsed(batteryEntityId);
|
|
176691
|
+
logger249.info(`${entityId}: Auto-assigned battery ${batteryEntityId}`);
|
|
175345
176692
|
} else {
|
|
175346
176693
|
const attrs = state.attributes;
|
|
175347
176694
|
if (attrs.battery_level != null || attrs.battery != null) {
|
|
175348
|
-
|
|
176695
|
+
logger249.info(
|
|
175349
176696
|
`${entityId}: No battery entity found, using battery attribute from vacuum state`
|
|
175350
176697
|
);
|
|
175351
176698
|
} else {
|
|
175352
|
-
|
|
176699
|
+
logger249.warn(
|
|
175353
176700
|
`${entityId}: No battery entity found for device ${entity.device_id}`
|
|
175354
176701
|
);
|
|
175355
176702
|
}
|
|
175356
176703
|
}
|
|
175357
176704
|
}
|
|
175358
|
-
const vacuumEntities =
|
|
176705
|
+
const vacuumEntities = registry3.findVacuumSelectEntities(
|
|
175359
176706
|
entity.device_id
|
|
175360
176707
|
);
|
|
175361
176708
|
if (!effectiveMapping?.cleaningModeEntity && vacuumEntities.cleaningModeEntity) {
|
|
@@ -175364,7 +176711,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175364
176711
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175365
176712
|
cleaningModeEntity: vacuumEntities.cleaningModeEntity
|
|
175366
176713
|
};
|
|
175367
|
-
|
|
176714
|
+
logger249.info(
|
|
175368
176715
|
`${entityId}: Auto-assigned cleaningMode ${vacuumEntities.cleaningModeEntity}`
|
|
175369
176716
|
);
|
|
175370
176717
|
}
|
|
@@ -175374,7 +176721,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175374
176721
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175375
176722
|
suctionLevelEntity: vacuumEntities.suctionLevelEntity
|
|
175376
176723
|
};
|
|
175377
|
-
|
|
176724
|
+
logger249.info(
|
|
175378
176725
|
`${entityId}: Auto-assigned suctionLevel ${vacuumEntities.suctionLevelEntity}`
|
|
175379
176726
|
);
|
|
175380
176727
|
}
|
|
@@ -175384,7 +176731,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175384
176731
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175385
176732
|
mopIntensityEntity: vacuumEntities.mopIntensityEntity
|
|
175386
176733
|
};
|
|
175387
|
-
|
|
176734
|
+
logger249.info(
|
|
175388
176735
|
`${entityId}: Auto-assigned mopIntensity ${vacuumEntities.mopIntensityEntity}`
|
|
175389
176736
|
);
|
|
175390
176737
|
}
|
|
@@ -175394,12 +176741,12 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175394
176741
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175395
176742
|
currentRoomEntity: vacuumEntities.currentRoomEntity
|
|
175396
176743
|
};
|
|
175397
|
-
|
|
176744
|
+
logger249.info(
|
|
175398
176745
|
`${entityId}: Auto-assigned currentRoom ${vacuumEntities.currentRoomEntity}`
|
|
175399
176746
|
);
|
|
175400
176747
|
}
|
|
175401
176748
|
const supportedFeatures = state.attributes.supported_features ?? 0;
|
|
175402
|
-
const cleanAreaRooms = await
|
|
176749
|
+
const cleanAreaRooms = await registry3.resolveCleanAreaRooms(
|
|
175403
176750
|
entityId,
|
|
175404
176751
|
supportedFeatures
|
|
175405
176752
|
);
|
|
@@ -175409,13 +176756,13 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175409
176756
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175410
176757
|
cleanAreaRooms
|
|
175411
176758
|
};
|
|
175412
|
-
|
|
176759
|
+
logger249.info(
|
|
175413
176760
|
`${entityId}: Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA`
|
|
175414
176761
|
);
|
|
175415
176762
|
}
|
|
175416
176763
|
const vacAttrs = state.attributes;
|
|
175417
176764
|
if (cleanAreaRooms.length === 0 && !vacAttrs.rooms && !vacAttrs.segments && !vacAttrs.room_mapping) {
|
|
175418
|
-
const valetudoRooms =
|
|
176765
|
+
const valetudoRooms = registry3.findValetudoMapSegments(
|
|
175419
176766
|
entity.device_id
|
|
175420
176767
|
);
|
|
175421
176768
|
if (valetudoRooms.length > 0) {
|
|
@@ -175430,11 +176777,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175430
176777
|
rooms: roomsObj
|
|
175431
176778
|
}
|
|
175432
176779
|
};
|
|
175433
|
-
|
|
176780
|
+
logger249.info(
|
|
175434
176781
|
`${entityId}: Auto-detected ${valetudoRooms.length} Valetudo segments`
|
|
175435
176782
|
);
|
|
175436
176783
|
} else {
|
|
175437
|
-
const roborockRooms = await
|
|
176784
|
+
const roborockRooms = await registry3.resolveRoborockRooms(entityId);
|
|
175438
176785
|
if (roborockRooms.length > 0) {
|
|
175439
176786
|
const roomsObj = {};
|
|
175440
176787
|
for (const r of roborockRooms) {
|
|
@@ -175447,14 +176794,14 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175447
176794
|
rooms: roomsObj
|
|
175448
176795
|
}
|
|
175449
176796
|
};
|
|
175450
|
-
|
|
176797
|
+
logger249.info(
|
|
175451
176798
|
`${entityId}: Auto-detected ${roborockRooms.length} Roborock rooms`
|
|
175452
176799
|
);
|
|
175453
176800
|
}
|
|
175454
176801
|
}
|
|
175455
176802
|
}
|
|
175456
176803
|
} else {
|
|
175457
|
-
|
|
176804
|
+
logger249.warn(`${entityId}: No device_id, cannot auto-assign battery`);
|
|
175458
176805
|
}
|
|
175459
176806
|
const payload = {
|
|
175460
176807
|
entity_id: entityId,
|
|
@@ -175465,7 +176812,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175465
176812
|
const vacAttrsForClean = state.attributes;
|
|
175466
176813
|
let cleaningModeOptions;
|
|
175467
176814
|
if (effectiveMapping?.cleaningModeEntity) {
|
|
175468
|
-
const cmState =
|
|
176815
|
+
const cmState = registry3.initialState(
|
|
175469
176816
|
effectiveMapping.cleaningModeEntity
|
|
175470
176817
|
);
|
|
175471
176818
|
cleaningModeOptions = cmState?.attributes?.options;
|
|
@@ -175485,7 +176832,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175485
176832
|
customName,
|
|
175486
176833
|
mapping: effectiveMapping
|
|
175487
176834
|
},
|
|
175488
|
-
|
|
176835
|
+
registry3.isServerModeVacuumOnOffEnabled(),
|
|
175489
176836
|
cleaningModeOptions
|
|
175490
176837
|
);
|
|
175491
176838
|
if (!endpointType) {
|
|
@@ -175527,11 +176874,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175527
176874
|
}
|
|
175528
176875
|
if (mappedChanged) {
|
|
175529
176876
|
this.pendingMappedChange = true;
|
|
175530
|
-
|
|
176877
|
+
logger249.debug(
|
|
175531
176878
|
`Mapped entity change detected for ${this.entityId}, forcing update`
|
|
175532
176879
|
);
|
|
175533
176880
|
}
|
|
175534
|
-
|
|
176881
|
+
logger249.debug(
|
|
175535
176882
|
`State update received for ${this.entityId}: state=${state.state}`
|
|
175536
176883
|
);
|
|
175537
176884
|
this.lastState = state;
|
|
@@ -175571,12 +176918,13 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175571
176918
|
// src/services/bridges/server-mode-endpoint-manager.ts
|
|
175572
176919
|
var MAX_SERVER_MODE_DEVICES = 10;
|
|
175573
176920
|
var ServerModeEndpointManager = class extends Service {
|
|
175574
|
-
constructor(serverNode, client,
|
|
176921
|
+
constructor(serverNode, client, registry3, mappingStorage, identityStorage, dataProvider, log) {
|
|
175575
176922
|
super("ServerModeEndpointManager");
|
|
175576
176923
|
this.serverNode = serverNode;
|
|
175577
176924
|
this.client = client;
|
|
175578
|
-
this.registry =
|
|
176925
|
+
this.registry = registry3;
|
|
175579
176926
|
this.mappingStorage = mappingStorage;
|
|
176927
|
+
this.identityStorage = identityStorage;
|
|
175580
176928
|
this.dataProvider = dataProvider;
|
|
175581
176929
|
this.log = log;
|
|
175582
176930
|
this.identityResolver = new IdentityResolver(
|
|
@@ -175588,6 +176936,7 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
175588
176936
|
client;
|
|
175589
176937
|
registry;
|
|
175590
176938
|
mappingStorage;
|
|
176939
|
+
identityStorage;
|
|
175591
176940
|
dataProvider;
|
|
175592
176941
|
log;
|
|
175593
176942
|
entityIds = [];
|
|
@@ -175687,6 +177036,17 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
175687
177036
|
this.registry.refresh();
|
|
175688
177037
|
this._failedEntities = [];
|
|
175689
177038
|
this.entityIds = this.registry.entityIds;
|
|
177039
|
+
const fullEntities = this.registry.fullEntities;
|
|
177040
|
+
stampIdentityPresence(
|
|
177041
|
+
this.identityStorage,
|
|
177042
|
+
this.dataProvider.id,
|
|
177043
|
+
buildPresentIdentityKeys(fullEntities)
|
|
177044
|
+
);
|
|
177045
|
+
stampMappingPresence(
|
|
177046
|
+
this.mappingStorage,
|
|
177047
|
+
this.dataProvider.id,
|
|
177048
|
+
buildPresentEntityIds(fullEntities)
|
|
177049
|
+
);
|
|
175690
177050
|
try {
|
|
175691
177051
|
if (this.entityIds.length === 0) {
|
|
175692
177052
|
this.log.warn("Server mode bridge has no entities configured");
|
|
@@ -176096,10 +177456,10 @@ var BridgeEnvironmentFactory = class extends BridgeFactory {
|
|
|
176096
177456
|
// src/core/ioc/app-environment.ts
|
|
176097
177457
|
var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
176098
177458
|
constructor(rootEnv, options) {
|
|
176099
|
-
const
|
|
177459
|
+
const logger251 = rootEnv.get(LoggerService);
|
|
176100
177460
|
super({
|
|
176101
177461
|
id: "App",
|
|
176102
|
-
log:
|
|
177462
|
+
log: logger251.get("AppContainer"),
|
|
176103
177463
|
parent: rootEnv
|
|
176104
177464
|
});
|
|
176105
177465
|
this.options = options;
|
|
@@ -176113,8 +177473,8 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176113
177473
|
}
|
|
176114
177474
|
construction;
|
|
176115
177475
|
async init() {
|
|
176116
|
-
const
|
|
176117
|
-
this.set(LoggerService,
|
|
177476
|
+
const logger251 = this.get(LoggerService);
|
|
177477
|
+
this.set(LoggerService, logger251);
|
|
176118
177478
|
this.set(AppStorage, new AppStorage(await this.load(StorageService)));
|
|
176119
177479
|
this.set(BridgeStorage, new BridgeStorage(await this.load(AppStorage)));
|
|
176120
177480
|
this.set(
|
|
@@ -176135,7 +177495,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176135
177495
|
);
|
|
176136
177496
|
this.set(
|
|
176137
177497
|
HomeAssistantClient,
|
|
176138
|
-
new HomeAssistantClient(
|
|
177498
|
+
new HomeAssistantClient(logger251, this.options.homeAssistant)
|
|
176139
177499
|
);
|
|
176140
177500
|
this.set(
|
|
176141
177501
|
HomeAssistantConfig,
|
|
@@ -176143,7 +177503,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176143
177503
|
);
|
|
176144
177504
|
this.set(
|
|
176145
177505
|
HomeAssistantActions,
|
|
176146
|
-
new HomeAssistantActions(
|
|
177506
|
+
new HomeAssistantActions(logger251, await this.load(HomeAssistantClient))
|
|
176147
177507
|
);
|
|
176148
177508
|
this.set(
|
|
176149
177509
|
HomeAssistantRegistry,
|
|
@@ -176179,7 +177539,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176179
177539
|
this.set(
|
|
176180
177540
|
WebApi,
|
|
176181
177541
|
new WebApi(
|
|
176182
|
-
|
|
177542
|
+
logger251,
|
|
176183
177543
|
await this.load(BridgeService),
|
|
176184
177544
|
await this.load(HomeAssistantClient),
|
|
176185
177545
|
await this.load(HomeAssistantRegistry),
|
|
@@ -176200,7 +177560,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176200
177560
|
|
|
176201
177561
|
// src/matter/patches/patch-level-control-tlv.ts
|
|
176202
177562
|
init_esm();
|
|
176203
|
-
var
|
|
177563
|
+
var logger250 = Logger.get("PatchLevelControlTlv");
|
|
176204
177564
|
var optionalFieldModels = /* @__PURE__ */ new WeakSet();
|
|
176205
177565
|
var fieldModelMandatoryGetterPatched = false;
|
|
176206
177566
|
function patchLevelControlTlv() {
|
|
@@ -176256,11 +177616,11 @@ function patchLevelControlTlv() {
|
|
|
176256
177616
|
patched++;
|
|
176257
177617
|
}
|
|
176258
177618
|
if (patched > 0) {
|
|
176259
|
-
|
|
177619
|
+
logger250.info(
|
|
176260
177620
|
`Patched ${patched} LevelControl TLV schema(s): transitionTime is now optional (Google Home compatibility)`
|
|
176261
177621
|
);
|
|
176262
177622
|
} else {
|
|
176263
|
-
|
|
177623
|
+
logger250.warn(
|
|
176264
177624
|
"Failed to patch LevelControl TLV schemas, field definitions not found. Google Home brightness adjustment may not work.",
|
|
176265
177625
|
{
|
|
176266
177626
|
moveToLevel: describeFields(
|