@riddix/hamh 2.0.50 → 2.0.51
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 +15 -4
- package/dist/backend/cli.js +1966 -604
- package/dist/frontend/assets/{index-3J_LQhv7.js → index-Cs14KhUG.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,7 +136617,7 @@ 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) {
|
|
@@ -136327,7 +136702,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136327
136702
|
res.json({ success: true, pluginName, reset: true });
|
|
136328
136703
|
});
|
|
136329
136704
|
router.get("/installed", (_req, res) => {
|
|
136330
|
-
const registered =
|
|
136705
|
+
const registered = registry3.getAll();
|
|
136331
136706
|
const npmInstalled = installer.listInstalled();
|
|
136332
136707
|
const result = registered.map((entry) => {
|
|
136333
136708
|
const npm = npmInstalled.find((p) => p.name === entry.packageName);
|
|
@@ -136357,7 +136732,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136357
136732
|
});
|
|
136358
136733
|
return;
|
|
136359
136734
|
}
|
|
136360
|
-
|
|
136735
|
+
registry3.add(packageName, config11 ?? {});
|
|
136361
136736
|
res.json({
|
|
136362
136737
|
success: true,
|
|
136363
136738
|
packageName,
|
|
@@ -136385,7 +136760,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136385
136760
|
});
|
|
136386
136761
|
return;
|
|
136387
136762
|
}
|
|
136388
|
-
|
|
136763
|
+
registry3.remove(packageName);
|
|
136389
136764
|
res.json({
|
|
136390
136765
|
success: true,
|
|
136391
136766
|
packageName,
|
|
@@ -136425,7 +136800,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136425
136800
|
});
|
|
136426
136801
|
return;
|
|
136427
136802
|
}
|
|
136428
|
-
|
|
136803
|
+
registry3.add(result.packageName, {});
|
|
136429
136804
|
res.json({
|
|
136430
136805
|
success: true,
|
|
136431
136806
|
packageName: result.packageName,
|
|
@@ -136458,7 +136833,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136458
136833
|
});
|
|
136459
136834
|
return;
|
|
136460
136835
|
}
|
|
136461
|
-
|
|
136836
|
+
registry3.add(result.packageName, {});
|
|
136462
136837
|
res.json({
|
|
136463
136838
|
success: true,
|
|
136464
136839
|
packageName: result.packageName,
|
|
@@ -137155,7 +137530,7 @@ var WebSocketApi = class {
|
|
|
137155
137530
|
|
|
137156
137531
|
// src/api/web-api.ts
|
|
137157
137532
|
var WebApi = class extends Service {
|
|
137158
|
-
constructor(
|
|
137533
|
+
constructor(logger251, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, identityStorage, lockCredentialStorage, settingsStorage, backupService, props) {
|
|
137159
137534
|
super("WebApi");
|
|
137160
137535
|
this.bridgeService = bridgeService;
|
|
137161
137536
|
this.haClient = haClient;
|
|
@@ -137167,8 +137542,8 @@ var WebApi = class extends Service {
|
|
|
137167
137542
|
this.settingsStorage = settingsStorage;
|
|
137168
137543
|
this.backupService = backupService;
|
|
137169
137544
|
this.props = props;
|
|
137170
|
-
this.logger =
|
|
137171
|
-
this.log =
|
|
137545
|
+
this.logger = logger251;
|
|
137546
|
+
this.log = logger251.get(this);
|
|
137172
137547
|
this.accessLogger = accessLogger(this.log.createChild("Access Log"));
|
|
137173
137548
|
this.startTime = Date.now();
|
|
137174
137549
|
this.wsApi = new WebSocketApi(
|
|
@@ -137201,7 +137576,12 @@ var WebApi = class extends Service {
|
|
|
137201
137576
|
const api = express17.Router();
|
|
137202
137577
|
api.use(express17.json()).use(nocache()).use(
|
|
137203
137578
|
"/matter",
|
|
137204
|
-
matterApi(
|
|
137579
|
+
matterApi(
|
|
137580
|
+
this.bridgeService,
|
|
137581
|
+
this.haRegistry,
|
|
137582
|
+
this.identityStorage,
|
|
137583
|
+
this.mappingStorage
|
|
137584
|
+
)
|
|
137205
137585
|
).use(
|
|
137206
137586
|
"/health",
|
|
137207
137587
|
healthApi(
|
|
@@ -138354,10 +138734,10 @@ function isTransientConnectError(reason) {
|
|
|
138354
138734
|
return msg.includes("socket hang up") || msg.includes("tls") || msg.includes("TLS");
|
|
138355
138735
|
}
|
|
138356
138736
|
var HomeAssistantClient = class extends Service {
|
|
138357
|
-
constructor(
|
|
138737
|
+
constructor(logger251, options) {
|
|
138358
138738
|
super("HomeAssistantClient");
|
|
138359
138739
|
this.options = options;
|
|
138360
|
-
this.log =
|
|
138740
|
+
this.log = logger251.get(this);
|
|
138361
138741
|
}
|
|
138362
138742
|
options;
|
|
138363
138743
|
static Options = /* @__PURE__ */ Symbol.for("HomeAssistantClientProps");
|
|
@@ -139022,11 +139402,37 @@ var EntityIdentityStorage = class extends Service {
|
|
|
139022
139402
|
this.identities.delete(bridgeId);
|
|
139023
139403
|
await this.flush();
|
|
139024
139404
|
}
|
|
139405
|
+
// Drop a single identity record. Used by the manual orphan cleanup after the
|
|
139406
|
+
// 7-day tombstone; flush immediately since it is a user-initiated deletion.
|
|
139407
|
+
async deleteIdentity(bridgeId, key) {
|
|
139408
|
+
const bridgeMap = this.identities.get(bridgeId);
|
|
139409
|
+
if (bridgeMap?.delete(key)) {
|
|
139410
|
+
await this.flush();
|
|
139411
|
+
}
|
|
139412
|
+
}
|
|
139413
|
+
// Stamp the tombstone the first time a record's entity is absent from HA. Keeps
|
|
139414
|
+
// the first-seen time on later passes and no-ops once stamped, so a steady
|
|
139415
|
+
// absence does not churn the debounced persist.
|
|
139416
|
+
markIdentityMissing(bridgeId, key, nowIso) {
|
|
139417
|
+
const record = this.identities.get(bridgeId)?.get(key);
|
|
139418
|
+
if (!record || record.missingSince != null) return;
|
|
139419
|
+
record.missingSince = nowIso;
|
|
139420
|
+
this.schedulePersist();
|
|
139421
|
+
}
|
|
139422
|
+
// Clear the tombstone when the entity is present again. No-ops when there is
|
|
139423
|
+
// nothing to clear, so an all-present bridge writes nothing on refresh.
|
|
139424
|
+
clearIdentityMissing(bridgeId, key) {
|
|
139425
|
+
const record = this.identities.get(bridgeId)?.get(key);
|
|
139426
|
+
if (!record || record.missingSince == null) return;
|
|
139427
|
+
delete record.missingSince;
|
|
139428
|
+
this.schedulePersist();
|
|
139429
|
+
}
|
|
139025
139430
|
};
|
|
139026
139431
|
|
|
139027
139432
|
// src/services/storage/entity-mapping-storage.ts
|
|
139028
139433
|
init_service();
|
|
139029
139434
|
var CURRENT_VERSION2 = 1;
|
|
139435
|
+
var PERSIST_DEBOUNCE_MS2 = 500;
|
|
139030
139436
|
var EntityMappingStorage = class extends Service {
|
|
139031
139437
|
constructor(appStorage) {
|
|
139032
139438
|
super("EntityMappingStorage");
|
|
@@ -139035,10 +139441,14 @@ var EntityMappingStorage = class extends Service {
|
|
|
139035
139441
|
appStorage;
|
|
139036
139442
|
storage;
|
|
139037
139443
|
mappings = /* @__PURE__ */ new Map();
|
|
139444
|
+
persistTimer = null;
|
|
139038
139445
|
async initialize() {
|
|
139039
139446
|
this.storage = this.appStorage.createContext("entity-mappings");
|
|
139040
139447
|
await this.load();
|
|
139041
139448
|
}
|
|
139449
|
+
async dispose() {
|
|
139450
|
+
await this.flush();
|
|
139451
|
+
}
|
|
139042
139452
|
async load() {
|
|
139043
139453
|
const stored = await this.storage.get("data", {
|
|
139044
139454
|
version: CURRENT_VERSION2,
|
|
@@ -139073,6 +139483,10 @@ var EntityMappingStorage = class extends Service {
|
|
|
139073
139483
|
}
|
|
139074
139484
|
}
|
|
139075
139485
|
async persist() {
|
|
139486
|
+
if (this.persistTimer) {
|
|
139487
|
+
clearTimeout(this.persistTimer);
|
|
139488
|
+
this.persistTimer = null;
|
|
139489
|
+
}
|
|
139076
139490
|
const data = {
|
|
139077
139491
|
version: CURRENT_VERSION2,
|
|
139078
139492
|
mappings: {}
|
|
@@ -139082,6 +139496,17 @@ var EntityMappingStorage = class extends Service {
|
|
|
139082
139496
|
}
|
|
139083
139497
|
await this.storage.set("data", data);
|
|
139084
139498
|
}
|
|
139499
|
+
schedulePersist() {
|
|
139500
|
+
if (this.persistTimer) return;
|
|
139501
|
+
this.persistTimer = setTimeout(() => {
|
|
139502
|
+
this.persistTimer = null;
|
|
139503
|
+
void this.persist();
|
|
139504
|
+
}, PERSIST_DEBOUNCE_MS2);
|
|
139505
|
+
}
|
|
139506
|
+
// Flush any pending debounced write, used on dispose and by tests.
|
|
139507
|
+
async flush() {
|
|
139508
|
+
await this.persist();
|
|
139509
|
+
}
|
|
139085
139510
|
getMappingsForBridge(bridgeId) {
|
|
139086
139511
|
const bridgeMap = this.mappings.get(bridgeId);
|
|
139087
139512
|
return bridgeMap ? Array.from(bridgeMap.values()) : [];
|
|
@@ -139128,6 +139553,8 @@ var EntityMappingStorage = class extends Service {
|
|
|
139128
139553
|
currentEntity: request.currentEntity?.trim() || void 0,
|
|
139129
139554
|
batteryPowerEntity: request.batteryPowerEntity?.trim() || void 0,
|
|
139130
139555
|
batteryEnergyEntity: request.batteryEnergyEntity?.trim() || void 0,
|
|
139556
|
+
chargingSwitchEntity: request.chargingSwitchEntity?.trim() || void 0,
|
|
139557
|
+
currentLimitEntity: request.currentLimitEntity?.trim() || void 0,
|
|
139131
139558
|
pressureEntity: request.pressureEntity?.trim() || void 0,
|
|
139132
139559
|
suctionLevelEntity: request.suctionLevelEntity?.trim() || void 0,
|
|
139133
139560
|
mopIntensityEntity: request.mopIntensityEntity?.trim() || void 0,
|
|
@@ -139156,7 +139583,7 @@ var EntityMappingStorage = class extends Service {
|
|
|
139156
139583
|
composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0,
|
|
139157
139584
|
disableMomentaryFlip: request.disableMomentaryFlip || void 0
|
|
139158
139585
|
};
|
|
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) {
|
|
139586
|
+
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
139587
|
bridgeMap.delete(request.entityId);
|
|
139161
139588
|
} else {
|
|
139162
139589
|
bridgeMap.set(request.entityId, config11);
|
|
@@ -139175,6 +139602,23 @@ var EntityMappingStorage = class extends Service {
|
|
|
139175
139602
|
this.mappings.delete(bridgeId);
|
|
139176
139603
|
await this.persist();
|
|
139177
139604
|
}
|
|
139605
|
+
// Stamp the tombstone the first time a mapping's entity is absent from HA.
|
|
139606
|
+
// Keeps the first-seen time on later passes and no-ops once stamped, so a
|
|
139607
|
+
// steady absence does not churn the debounced persist.
|
|
139608
|
+
markMappingMissing(bridgeId, entityId, nowIso) {
|
|
139609
|
+
const record = this.mappings.get(bridgeId)?.get(entityId);
|
|
139610
|
+
if (!record || record.missingSince != null) return;
|
|
139611
|
+
record.missingSince = nowIso;
|
|
139612
|
+
this.schedulePersist();
|
|
139613
|
+
}
|
|
139614
|
+
// Clear the tombstone when the entity is present again. No-ops when there is
|
|
139615
|
+
// nothing to clear, so an all-present bridge writes nothing on refresh.
|
|
139616
|
+
clearMappingMissing(bridgeId, entityId) {
|
|
139617
|
+
const record = this.mappings.get(bridgeId)?.get(entityId);
|
|
139618
|
+
if (!record || record.missingSince == null) return;
|
|
139619
|
+
delete record.missingSince;
|
|
139620
|
+
this.schedulePersist();
|
|
139621
|
+
}
|
|
139178
139622
|
};
|
|
139179
139623
|
function sanitizeVendorId(value) {
|
|
139180
139624
|
if (value === void 0 || value === null || value === "") {
|
|
@@ -149100,6 +149544,7 @@ var EnergyEvseDeviceDefinition = MutableEndpoint({
|
|
|
149100
149544
|
)
|
|
149101
149545
|
});
|
|
149102
149546
|
Object.freeze(EnergyEvseDeviceDefinition);
|
|
149547
|
+
var EnergyEvseDevice = EnergyEvseDeviceDefinition;
|
|
149103
149548
|
|
|
149104
149549
|
// ../../node_modules/.pnpm/@matter+node@0.17.7_patch_hash=34f8224b2964673272e9f22613c0cb1f8ec12758dd46b4fe303d42373451ad4c/node_modules/@matter/node/dist/esm/devices/extended-color-light.js
|
|
149105
149550
|
init_IdentifyServer();
|
|
@@ -156079,8 +156524,8 @@ var PluginManager = class {
|
|
|
156079
156524
|
this.storageDir = storageDir;
|
|
156080
156525
|
this.homeAssistant = homeAssistant;
|
|
156081
156526
|
}
|
|
156082
|
-
setRegistry(
|
|
156083
|
-
this.registry =
|
|
156527
|
+
setRegistry(registry3) {
|
|
156528
|
+
this.registry = registry3;
|
|
156084
156529
|
}
|
|
156085
156530
|
/**
|
|
156086
156531
|
* Load and register a built-in plugin instance.
|
|
@@ -156805,16 +157250,96 @@ function seedExistingSessionStarts(startedAt, sessions, now = Date.now()) {
|
|
|
156805
157250
|
}
|
|
156806
157251
|
}
|
|
156807
157252
|
|
|
157253
|
+
// src/services/bridges/subscription-summary.ts
|
|
157254
|
+
function readPaths(value) {
|
|
157255
|
+
return Array.isArray(value) ? value : null;
|
|
157256
|
+
}
|
|
157257
|
+
function unknownSummary(id) {
|
|
157258
|
+
return {
|
|
157259
|
+
id,
|
|
157260
|
+
attributePaths: 0,
|
|
157261
|
+
eventPaths: 0,
|
|
157262
|
+
scope: "unknown",
|
|
157263
|
+
endpointIds: []
|
|
157264
|
+
};
|
|
157265
|
+
}
|
|
157266
|
+
function summarizeSubscription(sub) {
|
|
157267
|
+
let id = null;
|
|
157268
|
+
let request;
|
|
157269
|
+
try {
|
|
157270
|
+
const s = sub;
|
|
157271
|
+
if (typeof s.subscriptionId === "number") {
|
|
157272
|
+
id = s.subscriptionId;
|
|
157273
|
+
}
|
|
157274
|
+
request = s.request;
|
|
157275
|
+
} catch {
|
|
157276
|
+
return unknownSummary(id);
|
|
157277
|
+
}
|
|
157278
|
+
if (request == null || typeof request !== "object") {
|
|
157279
|
+
return unknownSummary(id);
|
|
157280
|
+
}
|
|
157281
|
+
const req = request;
|
|
157282
|
+
const attrPaths = readPaths(req.attributeRequests);
|
|
157283
|
+
const eventPaths = readPaths(req.eventRequests);
|
|
157284
|
+
if (attrPaths === null && eventPaths === null) {
|
|
157285
|
+
return unknownSummary(id);
|
|
157286
|
+
}
|
|
157287
|
+
const all = [...attrPaths ?? [], ...eventPaths ?? []];
|
|
157288
|
+
let wildcard = false;
|
|
157289
|
+
const endpointIds = /* @__PURE__ */ new Set();
|
|
157290
|
+
for (const path14 of all) {
|
|
157291
|
+
const ep = path14?.endpointId;
|
|
157292
|
+
if (ep == null) {
|
|
157293
|
+
wildcard = true;
|
|
157294
|
+
} else if (typeof ep === "number" || typeof ep === "bigint") {
|
|
157295
|
+
endpointIds.add(Number(ep));
|
|
157296
|
+
}
|
|
157297
|
+
}
|
|
157298
|
+
return {
|
|
157299
|
+
id,
|
|
157300
|
+
attributePaths: attrPaths?.length ?? 0,
|
|
157301
|
+
eventPaths: eventPaths?.length ?? 0,
|
|
157302
|
+
scope: wildcard ? "wildcard" : "endpoint-specific",
|
|
157303
|
+
endpointIds: [...endpointIds].sort((a, b) => a - b)
|
|
157304
|
+
};
|
|
157305
|
+
}
|
|
157306
|
+
function summarizeSubscriptions(subscriptions) {
|
|
157307
|
+
const out = [];
|
|
157308
|
+
try {
|
|
157309
|
+
for (const sub of subscriptions) {
|
|
157310
|
+
out.push(summarizeSubscription(sub));
|
|
157311
|
+
}
|
|
157312
|
+
} catch {
|
|
157313
|
+
}
|
|
157314
|
+
return out;
|
|
157315
|
+
}
|
|
157316
|
+
|
|
157317
|
+
// src/services/bridges/wedge-watchdog.ts
|
|
157318
|
+
var WEDGE_WARMUP_MS = 15 * 60 * 1e3;
|
|
157319
|
+
var WEDGE_IM_SILENCE_MS = 45 * 60 * 1e3;
|
|
157320
|
+
var WEDGE_MIN_ROTATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
157321
|
+
function decideWedgeRotation(input) {
|
|
157322
|
+
const {
|
|
157323
|
+
subscriptionCount,
|
|
157324
|
+
sessionAgeMs,
|
|
157325
|
+
lastImRequestMsAgo,
|
|
157326
|
+
lastRotatedMsAgo
|
|
157327
|
+
} = input;
|
|
157328
|
+
const imSilenceMs = lastImRequestMsAgo == null ? sessionAgeMs : lastImRequestMsAgo;
|
|
157329
|
+
return subscriptionCount > 0 && sessionAgeMs > WEDGE_WARMUP_MS && imSilenceMs > WEDGE_IM_SILENCE_MS && (lastRotatedMsAgo == null || lastRotatedMsAgo > WEDGE_MIN_ROTATE_INTERVAL_MS);
|
|
157330
|
+
}
|
|
157331
|
+
|
|
156808
157332
|
// src/services/bridges/bridge.ts
|
|
157333
|
+
var imWrapMarker = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
|
|
156809
157334
|
var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
|
|
156810
157335
|
var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
|
|
156811
157336
|
var MDNS_ADDRESS_CHECK_INTERVAL_MS = 6e4;
|
|
156812
157337
|
var Bridge = class {
|
|
156813
|
-
constructor(env,
|
|
157338
|
+
constructor(env, logger251, dataProvider, endpointManager, serverOptions) {
|
|
156814
157339
|
this.dataProvider = dataProvider;
|
|
156815
157340
|
this.endpointManager = endpointManager;
|
|
156816
157341
|
this.serverOptions = serverOptions;
|
|
156817
|
-
this.log =
|
|
157342
|
+
this.log = logger251.get(`Bridge / ${dataProvider.id}`);
|
|
156818
157343
|
this.server = new BridgeServerNode(
|
|
156819
157344
|
env,
|
|
156820
157345
|
this.dataProvider,
|
|
@@ -156853,6 +157378,12 @@ var Bridge = class {
|
|
|
156853
157378
|
sessionStartedAt = /* @__PURE__ */ new Map();
|
|
156854
157379
|
rotationTimer = null;
|
|
156855
157380
|
maxSessionAgeMs = 0;
|
|
157381
|
+
// Wedge watchdog: last inbound Interaction Model request time per session and
|
|
157382
|
+
// last time the watchdog rotated it, both keyed by the long-lived session
|
|
157383
|
+
// object so they clear when the session goes away.
|
|
157384
|
+
lastImRequestAt = /* @__PURE__ */ new WeakMap();
|
|
157385
|
+
wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
|
|
157386
|
+
wedgeWatchdogTimer = null;
|
|
156856
157387
|
// Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
|
|
156857
157388
|
// change forces a fresh operational announcement (#415).
|
|
156858
157389
|
mdnsAddressTimer = null;
|
|
@@ -156899,6 +157430,7 @@ var Bridge = class {
|
|
|
156899
157430
|
const sessionList = sessions.map((s) => {
|
|
156900
157431
|
const subCount = s.subscriptions.size;
|
|
156901
157432
|
totalSubscriptions += subCount;
|
|
157433
|
+
const subscriptions = summarizeSubscriptions(s.subscriptions);
|
|
156902
157434
|
const fi = typeof s.fabric?.fabricIndex === "number" ? s.fabric.fabricIndex : null;
|
|
156903
157435
|
if (fi !== null) {
|
|
156904
157436
|
const existing = fabricMap.get(fi) ?? {
|
|
@@ -156912,14 +157444,18 @@ var Bridge = class {
|
|
|
156912
157444
|
const nowMs = Date.now();
|
|
156913
157445
|
const lastActiveMsAgo = typeof s.activeTimestamp === "number" && s.activeTimestamp > 0 ? nowMs - s.activeTimestamp : null;
|
|
156914
157446
|
const lastAnyActivityMsAgo = typeof s.timestamp === "number" ? nowMs - s.timestamp : null;
|
|
157447
|
+
const lastImAt = this.lastImRequestAt.get(s);
|
|
157448
|
+
const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
|
|
156915
157449
|
const startedAt = this.sessionStartedAt.get(s.id);
|
|
156916
157450
|
return {
|
|
156917
157451
|
id: s.id,
|
|
156918
157452
|
peerNodeId: String(s.peerNodeId),
|
|
156919
157453
|
fabricIndex: fi,
|
|
156920
157454
|
subscriptionCount: subCount,
|
|
157455
|
+
subscriptions,
|
|
156921
157456
|
lastActiveMsAgo,
|
|
156922
157457
|
lastAnyActivityMsAgo,
|
|
157458
|
+
lastImRequestMsAgo,
|
|
156923
157459
|
isPeerActive: Boolean(s.isPeerActive),
|
|
156924
157460
|
ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
|
|
156925
157461
|
};
|
|
@@ -157086,6 +157622,7 @@ var Bridge = class {
|
|
|
157086
157622
|
this.wireSessionDiagnostics();
|
|
157087
157623
|
this.wireFabricWarnings();
|
|
157088
157624
|
this.startSessionRotation();
|
|
157625
|
+
this.startWedgeWatchdog();
|
|
157089
157626
|
this.startMdnsAddressWatch();
|
|
157090
157627
|
logMemoryUsage(this.log, "bridge running");
|
|
157091
157628
|
diagnosticEventBus.emit("bridge_started", `Bridge started`, {
|
|
@@ -157285,6 +157822,29 @@ ${e?.toString()}`);
|
|
|
157285
157822
|
};
|
|
157286
157823
|
sessionManager.sessions.added.on(this.sessionAddedHandler);
|
|
157287
157824
|
sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
|
|
157825
|
+
this.wireImRequestTracking();
|
|
157826
|
+
} catch {
|
|
157827
|
+
}
|
|
157828
|
+
}
|
|
157829
|
+
// Stamp the time of every inbound Interaction Model request per session by
|
|
157830
|
+
// wrapping InteractionServer.onNewExchange. The wedge watchdog reads these to
|
|
157831
|
+
// tell a live-but-consuming controller from one that only keeps acking.
|
|
157832
|
+
wireImRequestTracking() {
|
|
157833
|
+
try {
|
|
157834
|
+
const is = this.server.env.get(InteractionServer);
|
|
157835
|
+
const marked = is;
|
|
157836
|
+
if (marked[imWrapMarker]) {
|
|
157837
|
+
return;
|
|
157838
|
+
}
|
|
157839
|
+
const original = is.onNewExchange.bind(is);
|
|
157840
|
+
is.onNewExchange = (exchange, message) => {
|
|
157841
|
+
const session = exchange.session;
|
|
157842
|
+
if (session) {
|
|
157843
|
+
this.lastImRequestAt.set(session, Date.now());
|
|
157844
|
+
}
|
|
157845
|
+
return original(exchange, message);
|
|
157846
|
+
};
|
|
157847
|
+
marked[imWrapMarker] = true;
|
|
157288
157848
|
} catch {
|
|
157289
157849
|
}
|
|
157290
157850
|
}
|
|
@@ -157447,6 +158007,7 @@ ${e?.toString()}`);
|
|
|
157447
158007
|
}
|
|
157448
158008
|
this.staleSessionTimers.clear();
|
|
157449
158009
|
this.stopSessionRotation();
|
|
158010
|
+
this.stopWedgeWatchdog();
|
|
157450
158011
|
this.stopMdnsAddressWatch();
|
|
157451
158012
|
this.sessionStartedAt.clear();
|
|
157452
158013
|
}
|
|
@@ -157512,6 +158073,72 @@ ${e?.toString()}`);
|
|
|
157512
158073
|
this.rotationTimer = null;
|
|
157513
158074
|
}
|
|
157514
158075
|
}
|
|
158076
|
+
// Opt-in wedge watchdog: reuse the rotation check cadence (5min) to look for
|
|
158077
|
+
// the one session wedged on "Updating" and rotate just that one.
|
|
158078
|
+
startWedgeWatchdog() {
|
|
158079
|
+
this.stopWedgeWatchdog();
|
|
158080
|
+
if (!this.dataProvider.featureFlags?.wedgeWatchdog) {
|
|
158081
|
+
return;
|
|
158082
|
+
}
|
|
158083
|
+
this.wedgeWatchdogTimer = setInterval(
|
|
158084
|
+
() => this.runWedgeWatchdogCheck(),
|
|
158085
|
+
ROTATION_CHECK_INTERVAL_MS
|
|
158086
|
+
);
|
|
158087
|
+
this.log.info(
|
|
158088
|
+
`Wedge watchdog: checking every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
158089
|
+
);
|
|
158090
|
+
}
|
|
158091
|
+
stopWedgeWatchdog() {
|
|
158092
|
+
if (this.wedgeWatchdogTimer) {
|
|
158093
|
+
clearInterval(this.wedgeWatchdogTimer);
|
|
158094
|
+
this.wedgeWatchdogTimer = null;
|
|
158095
|
+
}
|
|
158096
|
+
}
|
|
158097
|
+
// Rotate exactly the sessions the pure rule flags as wedged. Closing is the
|
|
158098
|
+
// same graceful-then-force path age rotation uses, so a false positive just
|
|
158099
|
+
// re-CASEs the controller.
|
|
158100
|
+
runWedgeWatchdogCheck() {
|
|
158101
|
+
try {
|
|
158102
|
+
const sessionManager = this.server.env.get(SessionManager);
|
|
158103
|
+
const now = Date.now();
|
|
158104
|
+
const closes = [];
|
|
158105
|
+
for (const s of [...sessionManager.sessions]) {
|
|
158106
|
+
if (s.isClosing) continue;
|
|
158107
|
+
const startedAt = this.sessionStartedAt.get(s.id);
|
|
158108
|
+
const sessionAgeMs = startedAt != null ? now - startedAt : 0;
|
|
158109
|
+
const lastImAt = this.lastImRequestAt.get(s);
|
|
158110
|
+
const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
|
|
158111
|
+
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
158112
|
+
const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
|
|
158113
|
+
if (!decideWedgeRotation({
|
|
158114
|
+
subscriptionCount: s.subscriptions.size,
|
|
158115
|
+
sessionAgeMs,
|
|
158116
|
+
lastImRequestMsAgo,
|
|
158117
|
+
lastRotatedMsAgo
|
|
158118
|
+
})) {
|
|
158119
|
+
continue;
|
|
158120
|
+
}
|
|
158121
|
+
const silenceMin = Math.round(
|
|
158122
|
+
(lastImRequestMsAgo ?? sessionAgeMs) / 6e4
|
|
158123
|
+
);
|
|
158124
|
+
this.log.info(
|
|
158125
|
+
`Wedge watchdog: rotating session ${s.id}, no inbound interaction for ${silenceMin}min`
|
|
158126
|
+
);
|
|
158127
|
+
this.wedgeLastRotatedAt.set(s, now);
|
|
158128
|
+
closes.push(
|
|
158129
|
+
s.initiateClose().catch(() => {
|
|
158130
|
+
return s.initiateForceClose({
|
|
158131
|
+
cause: new Error("wedge watchdog, forcing")
|
|
158132
|
+
});
|
|
158133
|
+
})
|
|
158134
|
+
);
|
|
158135
|
+
}
|
|
158136
|
+
if (closes.length > 0) {
|
|
158137
|
+
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
158138
|
+
}
|
|
158139
|
+
} catch {
|
|
158140
|
+
}
|
|
158141
|
+
}
|
|
157515
158142
|
// Poll the interface addresses mDNS advertises and re-announce when they
|
|
157516
158143
|
// change. matter.js caches the operational records at first announcement, so
|
|
157517
158144
|
// a dynamic ISP IPv6 prefix change keeps advertising the dead global address
|
|
@@ -157643,6 +158270,7 @@ ${e?.toString()}`);
|
|
|
157643
158270
|
if (this.status.code === BridgeStatus.Running) {
|
|
157644
158271
|
this.startAutoForceSyncIfEnabled();
|
|
157645
158272
|
this.startSessionRotation();
|
|
158273
|
+
this.startWedgeWatchdog();
|
|
157646
158274
|
}
|
|
157647
158275
|
} catch (e) {
|
|
157648
158276
|
const reason = "Failed to update bridge due to error:";
|
|
@@ -157765,66 +158393,6 @@ var AggregatorEndpoint2 = class extends Endpoint {
|
|
|
157765
158393
|
}
|
|
157766
158394
|
};
|
|
157767
158395
|
|
|
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
158396
|
// src/matter/endpoints/legacy/legacy-endpoint.ts
|
|
157829
158397
|
init_dist();
|
|
157830
158398
|
init_esm();
|
|
@@ -157903,9 +158471,9 @@ function resolveBatteryPercent(state) {
|
|
|
157903
158471
|
return BATTERY_ENUM_PERCENT[state.toLowerCase()] ?? null;
|
|
157904
158472
|
}
|
|
157905
158473
|
var EntityStateProvider = class extends Service {
|
|
157906
|
-
constructor(
|
|
158474
|
+
constructor(registry3) {
|
|
157907
158475
|
super("EntityStateProvider");
|
|
157908
|
-
this.registry =
|
|
158476
|
+
this.registry = registry3;
|
|
157909
158477
|
}
|
|
157910
158478
|
registry;
|
|
157911
158479
|
/**
|
|
@@ -159823,11 +160391,11 @@ function createEndpointId2(entityId, customName) {
|
|
|
159823
160391
|
const baseName = customName || entityId;
|
|
159824
160392
|
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
159825
160393
|
}
|
|
159826
|
-
function buildEntityPayload(
|
|
159827
|
-
const state =
|
|
160394
|
+
function buildEntityPayload(registry3, entityId) {
|
|
160395
|
+
const state = registry3.initialState(entityId);
|
|
159828
160396
|
if (!state) return void 0;
|
|
159829
|
-
const entity =
|
|
159830
|
-
const deviceRegistry =
|
|
160397
|
+
const entity = registry3.entity(entityId);
|
|
160398
|
+
const deviceRegistry = registry3.deviceOf(entityId);
|
|
159831
160399
|
return {
|
|
159832
160400
|
entity_id: entityId,
|
|
159833
160401
|
state,
|
|
@@ -159842,8 +160410,8 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
|
|
|
159842
160410
|
lastStates = /* @__PURE__ */ new Map();
|
|
159843
160411
|
debouncedUpdates = /* @__PURE__ */ new Map();
|
|
159844
160412
|
static async create(config11) {
|
|
159845
|
-
const { registry:
|
|
159846
|
-
const primaryPayload = buildEntityPayload(
|
|
160413
|
+
const { registry: registry3, primaryEntityId } = config11;
|
|
160414
|
+
const primaryPayload = buildEntityPayload(registry3, primaryEntityId);
|
|
159847
160415
|
if (!primaryPayload) return void 0;
|
|
159848
160416
|
const airPurifierAttributes = primaryPayload.state.attributes;
|
|
159849
160417
|
const supportedFeatures = airPurifierAttributes.supported_features ?? 0;
|
|
@@ -159931,7 +160499,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
|
|
|
159931
160499
|
let tempSub;
|
|
159932
160500
|
if (config11.temperatureEntityId) {
|
|
159933
160501
|
const tempPayload = buildEntityPayload(
|
|
159934
|
-
|
|
160502
|
+
registry3,
|
|
159935
160503
|
config11.temperatureEntityId
|
|
159936
160504
|
);
|
|
159937
160505
|
if (tempPayload) {
|
|
@@ -159946,7 +160514,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
|
|
|
159946
160514
|
}
|
|
159947
160515
|
let humSub;
|
|
159948
160516
|
if (config11.humidityEntityId) {
|
|
159949
|
-
const humPayload = buildEntityPayload(
|
|
160517
|
+
const humPayload = buildEntityPayload(registry3, config11.humidityEntityId);
|
|
159950
160518
|
if (humPayload) {
|
|
159951
160519
|
humSub = new Endpoint(
|
|
159952
160520
|
HumiditySubType.set({
|
|
@@ -161400,14 +161968,14 @@ function createEndpointId3(entityId, customName) {
|
|
|
161400
161968
|
const baseName = customName || entityId;
|
|
161401
161969
|
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
161402
161970
|
}
|
|
161403
|
-
function buildEntityPayload2(
|
|
161404
|
-
const state =
|
|
161971
|
+
function buildEntityPayload2(registry3, entityId) {
|
|
161972
|
+
const state = registry3.initialState(entityId);
|
|
161405
161973
|
if (!state) return void 0;
|
|
161406
161974
|
return {
|
|
161407
161975
|
entity_id: entityId,
|
|
161408
161976
|
state,
|
|
161409
|
-
registry:
|
|
161410
|
-
deviceRegistry:
|
|
161977
|
+
registry: registry3.entity(entityId),
|
|
161978
|
+
deviceRegistry: registry3.deviceOf(entityId)
|
|
161411
161979
|
};
|
|
161412
161980
|
}
|
|
161413
161981
|
var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpoint {
|
|
@@ -161417,8 +161985,8 @@ var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpo
|
|
|
161417
161985
|
lastStates = /* @__PURE__ */ new Map();
|
|
161418
161986
|
debouncedUpdates = /* @__PURE__ */ new Map();
|
|
161419
161987
|
static async create(config11) {
|
|
161420
|
-
const { registry:
|
|
161421
|
-
const payload = buildEntityPayload2(
|
|
161988
|
+
const { registry: registry3, primaryEntityId } = config11;
|
|
161989
|
+
const payload = buildEntityPayload2(registry3, primaryEntityId);
|
|
161422
161990
|
if (!payload) return void 0;
|
|
161423
161991
|
const endpointId = config11.endpointId ?? createEndpointId3(primaryEntityId, config11.customName);
|
|
161424
161992
|
const mapping = config11.mapping ?? {
|
|
@@ -161676,11 +162244,11 @@ function createEndpointId4(entityId, customName) {
|
|
|
161676
162244
|
const baseName = customName || entityId;
|
|
161677
162245
|
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
161678
162246
|
}
|
|
161679
|
-
function buildEntityPayload3(
|
|
161680
|
-
const state =
|
|
162247
|
+
function buildEntityPayload3(registry3, entityId) {
|
|
162248
|
+
const state = registry3.initialStateIncludingUnfiltered(entityId);
|
|
161681
162249
|
if (!state) return void 0;
|
|
161682
|
-
const entity =
|
|
161683
|
-
const deviceRegistry =
|
|
162250
|
+
const entity = registry3.entityIncludingUnfiltered(entityId);
|
|
162251
|
+
const deviceRegistry = registry3.deviceOfIncludingUnfiltered(entityId);
|
|
161684
162252
|
return {
|
|
161685
162253
|
entity_id: entityId,
|
|
161686
162254
|
state,
|
|
@@ -161695,8 +162263,8 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
|
|
|
161695
162263
|
lastStates = /* @__PURE__ */ new Map();
|
|
161696
162264
|
debouncedUpdates = /* @__PURE__ */ new Map();
|
|
161697
162265
|
static async create(config11) {
|
|
161698
|
-
const { registry:
|
|
161699
|
-
const primaryPayload = buildEntityPayload3(
|
|
162266
|
+
const { registry: registry3, primaryEntityId } = config11;
|
|
162267
|
+
const primaryPayload = buildEntityPayload3(registry3, primaryEntityId);
|
|
161700
162268
|
if (!primaryPayload) return void 0;
|
|
161701
162269
|
let parentType = BridgedNodeEndpoint.with(
|
|
161702
162270
|
BasicInformationServer2,
|
|
@@ -161740,7 +162308,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
|
|
|
161740
162308
|
parts.push(tempSub);
|
|
161741
162309
|
let humSub;
|
|
161742
162310
|
if (config11.humidityEntityId) {
|
|
161743
|
-
const humPayload = buildEntityPayload3(
|
|
162311
|
+
const humPayload = buildEntityPayload3(registry3, config11.humidityEntityId);
|
|
161744
162312
|
if (humPayload) {
|
|
161745
162313
|
humSub = new Endpoint(
|
|
161746
162314
|
HumiditySubType2.set({
|
|
@@ -161754,7 +162322,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
|
|
|
161754
162322
|
let pressSub;
|
|
161755
162323
|
if (config11.pressureEntityId) {
|
|
161756
162324
|
const pressPayload = buildEntityPayload3(
|
|
161757
|
-
|
|
162325
|
+
registry3,
|
|
161758
162326
|
config11.pressureEntityId
|
|
161759
162327
|
);
|
|
161760
162328
|
if (pressPayload) {
|
|
@@ -162708,6 +163276,15 @@ function clearPendingTilt(st) {
|
|
|
162708
163276
|
st.pendingTilt = null;
|
|
162709
163277
|
}
|
|
162710
163278
|
var coverOptimistic = /* @__PURE__ */ new WeakMap();
|
|
163279
|
+
function haRestEnd(action) {
|
|
163280
|
+
if (action.action.includes("open_cover")) {
|
|
163281
|
+
return "open";
|
|
163282
|
+
}
|
|
163283
|
+
if (action.action.includes("close_cover")) {
|
|
163284
|
+
return "close";
|
|
163285
|
+
}
|
|
163286
|
+
return null;
|
|
163287
|
+
}
|
|
162711
163288
|
function getCoverOptimistic(endpoint) {
|
|
162712
163289
|
let st = coverOptimistic.get(endpoint);
|
|
162713
163290
|
if (!st) {
|
|
@@ -162822,7 +163399,10 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
162822
163399
|
if (!entry) {
|
|
162823
163400
|
return MovementStatus.Stopped;
|
|
162824
163401
|
}
|
|
162825
|
-
const
|
|
163402
|
+
const getExpected = config11.getExpectedRestPosition;
|
|
163403
|
+
const expectedPercent = entry.expectedRestEnd != null && getExpected != null ? getExpected(entry.expectedRestEnd, this.agent) : null;
|
|
163404
|
+
const goal = expectedPercent != null ? expectedPercent * 100 : existingTarget;
|
|
163405
|
+
const reachedTarget = positionAware && current100ths != null && goal != null && Math.abs(current100ths - goal) < 100;
|
|
162826
163406
|
const expired = Date.now() - entry.startedAt >= optimisticMovementTimeoutMs;
|
|
162827
163407
|
if (reachedTarget || expired) {
|
|
162828
163408
|
clearOptimisticAxis(optimistic, axis);
|
|
@@ -162927,7 +163507,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
162927
163507
|
}
|
|
162928
163508
|
// Record the optimistic direction and emit it now so a controller gets the
|
|
162929
163509
|
// moving->stopped edge even when HA never reports a transitional state (#429).
|
|
162930
|
-
startOptimisticMovement(axis, status3) {
|
|
163510
|
+
startOptimisticMovement(axis, status3, expectedRestEnd) {
|
|
162931
163511
|
const optimistic = getCoverOptimistic(this.endpoint);
|
|
162932
163512
|
clearOptimisticAxis(optimistic, axis);
|
|
162933
163513
|
const endpoint = this.endpoint;
|
|
@@ -162972,7 +163552,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
162972
163552
|
})();
|
|
162973
163553
|
}, optimisticMovementTimeoutMs);
|
|
162974
163554
|
timer.unref?.();
|
|
162975
|
-
optimistic[axis] = { status: status3, startedAt, timer };
|
|
163555
|
+
optimistic[axis] = { status: status3, startedAt, timer, expectedRestEnd };
|
|
162976
163556
|
this.writeOperationalStatus({ [axis]: status3 });
|
|
162977
163557
|
}
|
|
162978
163558
|
async handleMovement(type, _, direction, targetPercent100ths) {
|
|
@@ -163031,17 +163611,25 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163031
163611
|
}
|
|
163032
163612
|
handleLiftOpen() {
|
|
163033
163613
|
clearPendingLift(getCoverDebounce(this.endpoint));
|
|
163034
|
-
this.startOptimisticMovement("lift", MovementStatus.Opening);
|
|
163035
163614
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163036
163615
|
const action = this.state.config.openCoverLift(void 0, this.agent);
|
|
163616
|
+
this.startOptimisticMovement(
|
|
163617
|
+
"lift",
|
|
163618
|
+
MovementStatus.Opening,
|
|
163619
|
+
haRestEnd(action)
|
|
163620
|
+
);
|
|
163037
163621
|
logger218.info(`handleLiftOpen: calling action=${action.action}`);
|
|
163038
163622
|
homeAssistant.callAction(action);
|
|
163039
163623
|
}
|
|
163040
163624
|
handleLiftClose() {
|
|
163041
163625
|
clearPendingLift(getCoverDebounce(this.endpoint));
|
|
163042
|
-
this.startOptimisticMovement("lift", MovementStatus.Closing);
|
|
163043
163626
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163044
163627
|
const action = this.state.config.closeCoverLift(void 0, this.agent);
|
|
163628
|
+
this.startOptimisticMovement(
|
|
163629
|
+
"lift",
|
|
163630
|
+
MovementStatus.Closing,
|
|
163631
|
+
haRestEnd(action)
|
|
163632
|
+
);
|
|
163045
163633
|
logger218.info(`handleLiftClose: calling action=${action.action}`);
|
|
163046
163634
|
homeAssistant.callAction(action);
|
|
163047
163635
|
}
|
|
@@ -163052,14 +163640,15 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163052
163640
|
return;
|
|
163053
163641
|
}
|
|
163054
163642
|
this.state.targetPositionLiftPercent100ths = targetPercent100ths;
|
|
163055
|
-
this.startOptimisticMovement(
|
|
163056
|
-
"lift",
|
|
163057
|
-
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing
|
|
163058
|
-
);
|
|
163059
163643
|
const targetPosition = targetPercent100ths / 100;
|
|
163060
163644
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163061
163645
|
const action = config11.setLiftPosition(targetPosition, this.agent);
|
|
163062
163646
|
const entityId = homeAssistant.entityId;
|
|
163647
|
+
this.startOptimisticMovement(
|
|
163648
|
+
"lift",
|
|
163649
|
+
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing,
|
|
163650
|
+
haRestEnd(action)
|
|
163651
|
+
);
|
|
163063
163652
|
const actions = this.env.get(HomeAssistantActions);
|
|
163064
163653
|
const st = getCoverDebounce(this.endpoint);
|
|
163065
163654
|
st.pendingLift = { action, entityId, actions };
|
|
@@ -163092,21 +163681,27 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163092
163681
|
const st = getCoverDebounce(this.endpoint);
|
|
163093
163682
|
clearPendingTilt(st);
|
|
163094
163683
|
if (st.pendingLift?.action.action.includes("tilt")) clearPendingLift(st);
|
|
163095
|
-
this.
|
|
163096
|
-
|
|
163097
|
-
|
|
163098
|
-
|
|
163684
|
+
const action = this.state.config.openCoverTilt(void 0, this.agent);
|
|
163685
|
+
this.startOptimisticMovement(
|
|
163686
|
+
"tilt",
|
|
163687
|
+
MovementStatus.Opening,
|
|
163688
|
+
haRestEnd(action)
|
|
163099
163689
|
);
|
|
163690
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163691
|
+
homeAssistant.callAction(action);
|
|
163100
163692
|
}
|
|
163101
163693
|
handleTiltClose() {
|
|
163102
163694
|
const st = getCoverDebounce(this.endpoint);
|
|
163103
163695
|
clearPendingTilt(st);
|
|
163104
163696
|
if (st.pendingLift?.action.action.includes("tilt")) clearPendingLift(st);
|
|
163105
|
-
this.
|
|
163106
|
-
|
|
163107
|
-
|
|
163108
|
-
|
|
163697
|
+
const action = this.state.config.closeCoverTilt(void 0, this.agent);
|
|
163698
|
+
this.startOptimisticMovement(
|
|
163699
|
+
"tilt",
|
|
163700
|
+
MovementStatus.Closing,
|
|
163701
|
+
haRestEnd(action)
|
|
163109
163702
|
);
|
|
163703
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163704
|
+
homeAssistant.callAction(action);
|
|
163110
163705
|
}
|
|
163111
163706
|
handleGoToTiltPosition(targetPercent100ths) {
|
|
163112
163707
|
const config11 = this.state.config;
|
|
@@ -163115,14 +163710,15 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163115
163710
|
return;
|
|
163116
163711
|
}
|
|
163117
163712
|
this.state.targetPositionTiltPercent100ths = targetPercent100ths;
|
|
163118
|
-
this.startOptimisticMovement(
|
|
163119
|
-
"tilt",
|
|
163120
|
-
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing
|
|
163121
|
-
);
|
|
163122
163713
|
const targetPosition = targetPercent100ths / 100;
|
|
163123
163714
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163124
163715
|
const action = config11.setTiltPosition(targetPosition, this.agent);
|
|
163125
163716
|
const entityId = homeAssistant.entityId;
|
|
163717
|
+
this.startOptimisticMovement(
|
|
163718
|
+
"tilt",
|
|
163719
|
+
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing,
|
|
163720
|
+
haRestEnd(action)
|
|
163721
|
+
);
|
|
163126
163722
|
const actions = this.env.get(HomeAssistantActions);
|
|
163127
163723
|
const st = getCoverDebounce(this.endpoint);
|
|
163128
163724
|
st.pendingTilt = { action, entityId, actions };
|
|
@@ -163264,6 +163860,7 @@ var adjustPositionForWriting2 = (position, agent) => {
|
|
|
163264
163860
|
const matterSem = usesMatterSemantics(agent);
|
|
163265
163861
|
return adjustPositionForWriting(position, featureFlags, matterSem);
|
|
163266
163862
|
};
|
|
163863
|
+
var expectedRestPosition = (end, agent) => adjustPositionForReading2(end === "open" ? 100 : 0, agent);
|
|
163267
163864
|
var shouldSwapOpenClose = (agent) => {
|
|
163268
163865
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
163269
163866
|
const entitySwap = homeAssistant.state.mapping?.coverSwapOpenClose;
|
|
@@ -163351,11 +163948,11 @@ var config6 = {
|
|
|
163351
163948
|
}
|
|
163352
163949
|
return position == null ? null : adjustPositionForReading2(position, agent);
|
|
163353
163950
|
},
|
|
163951
|
+
getExpectedRestPosition: expectedRestPosition,
|
|
163354
163952
|
getCoverType: (entity) => deviceClassMapping(entity)?.type,
|
|
163355
163953
|
getEndProductType: (entity) => deviceClassMapping(entity)?.endProductType,
|
|
163356
163954
|
getMovementStatus: (entity, agent) => {
|
|
163357
|
-
const
|
|
163358
|
-
const swapped = featureFlags?.coverSwapOpenClose === true;
|
|
163955
|
+
const swapped = shouldSwapOpenClose(agent);
|
|
163359
163956
|
const coverState = entity.state;
|
|
163360
163957
|
if (coverState === CoverDeviceState.opening) {
|
|
163361
163958
|
return swapped ? WindowCovering3.MovementStatus.Closing : WindowCovering3.MovementStatus.Opening;
|
|
@@ -167397,6 +167994,284 @@ var ElectricalMeterType = ElectricalMeterDevice.with(
|
|
|
167397
167994
|
EnergyServer
|
|
167398
167995
|
);
|
|
167399
167996
|
|
|
167997
|
+
// src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
|
|
167998
|
+
init_esm();
|
|
167999
|
+
init_home_assistant_actions();
|
|
168000
|
+
init_home_assistant_entity_behavior();
|
|
168001
|
+
|
|
168002
|
+
// src/matter/endpoints/legacy/sensor/devices/evse-status.ts
|
|
168003
|
+
function mapEvseStatus(rawState, chargingSwitchOn) {
|
|
168004
|
+
const raw = rawState.toLowerCase().trim();
|
|
168005
|
+
const switchSupply = chargingSwitchOn ? EnergyEvse3.SupplyState.ChargingEnabled : EnergyEvse3.SupplyState.Disabled;
|
|
168006
|
+
if (raw === "" || raw === "unknown" || raw === "unavailable") {
|
|
168007
|
+
return {
|
|
168008
|
+
state: null,
|
|
168009
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168010
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168011
|
+
};
|
|
168012
|
+
}
|
|
168013
|
+
if (raw.includes("error") || raw.includes("fault")) {
|
|
168014
|
+
return {
|
|
168015
|
+
state: EnergyEvse3.State.Fault,
|
|
168016
|
+
supplyState: EnergyEvse3.SupplyState.DisabledError,
|
|
168017
|
+
faultState: EnergyEvse3.FaultState.Other
|
|
168018
|
+
};
|
|
168019
|
+
}
|
|
168020
|
+
if (raw.includes("not connected") || raw.includes("not_connected") || raw.includes("disconnected") || raw.includes("no vehicle")) {
|
|
168021
|
+
return {
|
|
168022
|
+
state: EnergyEvse3.State.NotPluggedIn,
|
|
168023
|
+
supplyState: switchSupply,
|
|
168024
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168025
|
+
};
|
|
168026
|
+
}
|
|
168027
|
+
if (raw.includes("not charging") || raw.includes("not_charging") || raw.includes("not-charging")) {
|
|
168028
|
+
return {
|
|
168029
|
+
state: EnergyEvse3.State.PluggedInNoDemand,
|
|
168030
|
+
supplyState: switchSupply,
|
|
168031
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168032
|
+
};
|
|
168033
|
+
}
|
|
168034
|
+
if (raw.includes("charg") && !raw.includes("discharg")) {
|
|
168035
|
+
return {
|
|
168036
|
+
state: EnergyEvse3.State.PluggedInCharging,
|
|
168037
|
+
supplyState: EnergyEvse3.SupplyState.ChargingEnabled,
|
|
168038
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168039
|
+
};
|
|
168040
|
+
}
|
|
168041
|
+
if (raw.includes("connected") || raw.includes("ready") || raw.includes("awaiting") || raw.includes("completed") || raw.includes("sleep") || raw.includes("paused")) {
|
|
168042
|
+
return {
|
|
168043
|
+
state: EnergyEvse3.State.PluggedInNoDemand,
|
|
168044
|
+
supplyState: switchSupply,
|
|
168045
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168046
|
+
};
|
|
168047
|
+
}
|
|
168048
|
+
return {
|
|
168049
|
+
state: null,
|
|
168050
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168051
|
+
faultState: EnergyEvse3.FaultState.NoError
|
|
168052
|
+
};
|
|
168053
|
+
}
|
|
168054
|
+
|
|
168055
|
+
// src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
|
|
168056
|
+
var logger229 = Logger.get("EnergyEvse");
|
|
168057
|
+
var MIN_CHARGE_CURRENT_MA = 6e3;
|
|
168058
|
+
var MAX_CHARGE_CURRENT_MA = 32e3;
|
|
168059
|
+
var CIRCUIT_CAPACITY_MA = 32e3;
|
|
168060
|
+
var MAX_TIMER_MS = 2147483647;
|
|
168061
|
+
function clampMa(ma) {
|
|
168062
|
+
return Math.min(CIRCUIT_CAPACITY_MA, Math.max(MIN_CHARGE_CURRENT_MA, ma));
|
|
168063
|
+
}
|
|
168064
|
+
var evseChargingTimers = /* @__PURE__ */ new WeakMap();
|
|
168065
|
+
function clearEvseChargingTimer(endpoint) {
|
|
168066
|
+
const timer = evseChargingTimers.get(endpoint);
|
|
168067
|
+
if (timer) {
|
|
168068
|
+
clearTimeout(timer);
|
|
168069
|
+
evseChargingTimers.delete(endpoint);
|
|
168070
|
+
}
|
|
168071
|
+
}
|
|
168072
|
+
var EvseStatusServer = class _EvseStatusServer extends EnergyEvseServer {
|
|
168073
|
+
async initialize() {
|
|
168074
|
+
await super.initialize();
|
|
168075
|
+
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
168076
|
+
this.update();
|
|
168077
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
168078
|
+
}
|
|
168079
|
+
async [Symbol.asyncDispose]() {
|
|
168080
|
+
clearEvseChargingTimer(this.endpoint);
|
|
168081
|
+
await super[Symbol.asyncDispose]();
|
|
168082
|
+
}
|
|
168083
|
+
chargingSwitchIsOn() {
|
|
168084
|
+
const mapping = this.agent.get(HomeAssistantEntityBehavior).state.mapping;
|
|
168085
|
+
const switchEntity = mapping?.chargingSwitchEntity;
|
|
168086
|
+
if (!switchEntity) return false;
|
|
168087
|
+
const stateProvider = this.agent.env.get(EntityStateProvider);
|
|
168088
|
+
return stateProvider.getState(switchEntity)?.state === "on";
|
|
168089
|
+
}
|
|
168090
|
+
update() {
|
|
168091
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
168092
|
+
const raw = homeAssistant.entity.state?.state ?? "";
|
|
168093
|
+
const status3 = mapEvseStatus(raw, this.chargingSwitchIsOn());
|
|
168094
|
+
const patch = {
|
|
168095
|
+
state: status3.state,
|
|
168096
|
+
supplyState: status3.supplyState,
|
|
168097
|
+
faultState: status3.faultState
|
|
168098
|
+
};
|
|
168099
|
+
const mapping = homeAssistant.state.mapping;
|
|
168100
|
+
if (mapping?.currentLimitEntity) {
|
|
168101
|
+
const stateProvider = this.agent.env.get(EntityStateProvider);
|
|
168102
|
+
const amps = stateProvider.getNumericState(mapping.currentLimitEntity);
|
|
168103
|
+
if (amps != null) {
|
|
168104
|
+
patch.maximumChargeCurrent = clampMa(Math.round(amps * 1e3));
|
|
168105
|
+
}
|
|
168106
|
+
}
|
|
168107
|
+
applyPatchState(this.state, patch);
|
|
168108
|
+
}
|
|
168109
|
+
enableCharging(request) {
|
|
168110
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
168111
|
+
const mapping = homeAssistant.state.mapping;
|
|
168112
|
+
const until = request.chargingEnabledUntil;
|
|
168113
|
+
if (until != null && until * 1e3 <= Date.now()) {
|
|
168114
|
+
this.disable();
|
|
168115
|
+
return;
|
|
168116
|
+
}
|
|
168117
|
+
clearEvseChargingTimer(this.endpoint);
|
|
168118
|
+
let dispatched = false;
|
|
168119
|
+
if (mapping?.chargingSwitchEntity) {
|
|
168120
|
+
homeAssistant.callAction({
|
|
168121
|
+
action: "switch.turn_on",
|
|
168122
|
+
target: mapping.chargingSwitchEntity
|
|
168123
|
+
});
|
|
168124
|
+
dispatched = true;
|
|
168125
|
+
}
|
|
168126
|
+
const maxCurrent = request.maximumChargeCurrent;
|
|
168127
|
+
if (mapping?.currentLimitEntity && maxCurrent != null) {
|
|
168128
|
+
const amps = Math.max(
|
|
168129
|
+
MIN_CHARGE_CURRENT_MA / 1e3,
|
|
168130
|
+
Math.floor(clampMa(Number(maxCurrent)) / 1e3)
|
|
168131
|
+
);
|
|
168132
|
+
homeAssistant.callAction({
|
|
168133
|
+
action: "number.set_value",
|
|
168134
|
+
target: mapping.currentLimitEntity,
|
|
168135
|
+
data: { value: amps }
|
|
168136
|
+
});
|
|
168137
|
+
dispatched = true;
|
|
168138
|
+
}
|
|
168139
|
+
if (!dispatched) {
|
|
168140
|
+
logger229.debug(
|
|
168141
|
+
`enableCharging ignored for ${homeAssistant.entityId}: no charging switch or current limit mapped`
|
|
168142
|
+
);
|
|
168143
|
+
return;
|
|
168144
|
+
}
|
|
168145
|
+
const patch = {
|
|
168146
|
+
supplyState: EnergyEvse3.SupplyState.ChargingEnabled,
|
|
168147
|
+
chargingEnabledUntil: until ?? null
|
|
168148
|
+
};
|
|
168149
|
+
if (request.minimumChargeCurrent != null) {
|
|
168150
|
+
patch.minimumChargeCurrent = clampMa(
|
|
168151
|
+
Number(request.minimumChargeCurrent)
|
|
168152
|
+
);
|
|
168153
|
+
}
|
|
168154
|
+
if (maxCurrent != null) {
|
|
168155
|
+
patch.maximumChargeCurrent = clampMa(Number(maxCurrent));
|
|
168156
|
+
}
|
|
168157
|
+
applyPatchState(this.state, patch);
|
|
168158
|
+
if (until != null) {
|
|
168159
|
+
const delay = until * 1e3 - Date.now();
|
|
168160
|
+
if (delay > 0 && delay <= MAX_TIMER_MS) {
|
|
168161
|
+
const endpoint = this.endpoint;
|
|
168162
|
+
const actions = this.env.get(HomeAssistantActions);
|
|
168163
|
+
const entityId = homeAssistant.entityId;
|
|
168164
|
+
const switchEntity = mapping?.chargingSwitchEntity;
|
|
168165
|
+
const timer = setTimeout(() => {
|
|
168166
|
+
void (async () => {
|
|
168167
|
+
try {
|
|
168168
|
+
if (evseChargingTimers.get(endpoint) !== timer) return;
|
|
168169
|
+
if (switchEntity) {
|
|
168170
|
+
actions.call(
|
|
168171
|
+
{ action: "switch.turn_off", target: switchEntity },
|
|
168172
|
+
entityId
|
|
168173
|
+
);
|
|
168174
|
+
}
|
|
168175
|
+
await endpoint.setStateOf(_EvseStatusServer, {
|
|
168176
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168177
|
+
chargingEnabledUntil: null
|
|
168178
|
+
});
|
|
168179
|
+
} catch (error) {
|
|
168180
|
+
logger229.debug(
|
|
168181
|
+
`EVSE charge-window expiry write failed (endpoint may be closing): ${error}`
|
|
168182
|
+
);
|
|
168183
|
+
} finally {
|
|
168184
|
+
if (evseChargingTimers.get(endpoint) === timer) {
|
|
168185
|
+
evseChargingTimers.delete(endpoint);
|
|
168186
|
+
}
|
|
168187
|
+
}
|
|
168188
|
+
})();
|
|
168189
|
+
}, delay);
|
|
168190
|
+
timer.unref?.();
|
|
168191
|
+
evseChargingTimers.set(endpoint, timer);
|
|
168192
|
+
}
|
|
168193
|
+
}
|
|
168194
|
+
}
|
|
168195
|
+
disable() {
|
|
168196
|
+
clearEvseChargingTimer(this.endpoint);
|
|
168197
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
168198
|
+
const mapping = homeAssistant.state.mapping;
|
|
168199
|
+
if (mapping?.chargingSwitchEntity) {
|
|
168200
|
+
homeAssistant.callAction({
|
|
168201
|
+
action: "switch.turn_off",
|
|
168202
|
+
target: mapping.chargingSwitchEntity
|
|
168203
|
+
});
|
|
168204
|
+
}
|
|
168205
|
+
if (mapping?.chargingSwitchEntity || mapping?.currentLimitEntity) {
|
|
168206
|
+
applyPatchState(this.state, {
|
|
168207
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168208
|
+
chargingEnabledUntil: null
|
|
168209
|
+
});
|
|
168210
|
+
} else {
|
|
168211
|
+
logger229.debug(
|
|
168212
|
+
`disable ignored for ${homeAssistant.entityId}: no charging switch mapped`
|
|
168213
|
+
);
|
|
168214
|
+
}
|
|
168215
|
+
}
|
|
168216
|
+
};
|
|
168217
|
+
var EvseStatusSeeded = EvseStatusServer.set({
|
|
168218
|
+
supplyState: EnergyEvse3.SupplyState.Disabled,
|
|
168219
|
+
faultState: EnergyEvse3.FaultState.NoError,
|
|
168220
|
+
circuitCapacity: CIRCUIT_CAPACITY_MA,
|
|
168221
|
+
minimumChargeCurrent: MIN_CHARGE_CURRENT_MA,
|
|
168222
|
+
maximumChargeCurrent: MAX_CHARGE_CURRENT_MA
|
|
168223
|
+
});
|
|
168224
|
+
var EvseModeSeeded = EnergyEvseModeServer.set({
|
|
168225
|
+
supportedModes: [
|
|
168226
|
+
{
|
|
168227
|
+
label: "Manual",
|
|
168228
|
+
mode: 1,
|
|
168229
|
+
modeTags: [{ value: EnergyEvseMode3.ModeTag.Manual }]
|
|
168230
|
+
}
|
|
168231
|
+
],
|
|
168232
|
+
currentMode: 1
|
|
168233
|
+
});
|
|
168234
|
+
var WiredPowerSourceBase = PowerSourceServer.with("Wired");
|
|
168235
|
+
var EvsePowerSourceServer = class extends WiredPowerSourceBase {
|
|
168236
|
+
async initialize() {
|
|
168237
|
+
await super.initialize();
|
|
168238
|
+
const endpointNumber = this.endpoint.number;
|
|
168239
|
+
if (endpointNumber != null) {
|
|
168240
|
+
applyPatchState(this.state, { endpointList: [endpointNumber] });
|
|
168241
|
+
}
|
|
168242
|
+
}
|
|
168243
|
+
};
|
|
168244
|
+
var EvsePowerSource = EvsePowerSourceServer.set({
|
|
168245
|
+
status: PowerSource3.PowerSourceStatus.Active,
|
|
168246
|
+
order: 0,
|
|
168247
|
+
description: "Mains",
|
|
168248
|
+
wiredCurrentType: PowerSource3.WiredCurrentType.Ac
|
|
168249
|
+
});
|
|
168250
|
+
var EnergyEvseBase = EnergyEvseDevice.with(
|
|
168251
|
+
BasicInformationServer2,
|
|
168252
|
+
IdentifyServer2,
|
|
168253
|
+
HomeAssistantEntityBehavior,
|
|
168254
|
+
EvseStatusSeeded,
|
|
168255
|
+
EvseModeSeeded,
|
|
168256
|
+
EvsePowerSource,
|
|
168257
|
+
// Always mount the measurement clusters; both seed safe defaults when no
|
|
168258
|
+
// power/energy entity is mapped, so the endpoint honestly carries the
|
|
168259
|
+
// ElectricalSensor device type.
|
|
168260
|
+
PowerServer,
|
|
168261
|
+
EnergyServer,
|
|
168262
|
+
DescriptorServer
|
|
168263
|
+
).set({
|
|
168264
|
+
descriptor: {
|
|
168265
|
+
deviceTypeList: [
|
|
168266
|
+
{ deviceType: DeviceTypeId(1292), revision: 2 },
|
|
168267
|
+
{ deviceType: DeviceTypeId(1296), revision: 1 }
|
|
168268
|
+
]
|
|
168269
|
+
}
|
|
168270
|
+
});
|
|
168271
|
+
function energyEvseType() {
|
|
168272
|
+
return EnergyEvseBase;
|
|
168273
|
+
}
|
|
168274
|
+
|
|
167400
168275
|
// src/matter/behaviors/flow-measurement-server.ts
|
|
167401
168276
|
init_home_assistant_entity_behavior();
|
|
167402
168277
|
var FlowMeasurementServerBase = class extends FlowMeasurementServer {
|
|
@@ -168136,7 +169011,7 @@ var TvocConcentrationMeasurementServer = class extends TvocConcentrationMeasurem
|
|
|
168136
169011
|
};
|
|
168137
169012
|
|
|
168138
169013
|
// src/matter/endpoints/legacy/sensor/devices/tvoc-sensor.ts
|
|
168139
|
-
var
|
|
169014
|
+
var logger230 = Logger.get("TvocSensor");
|
|
168140
169015
|
function airQualityFromUgm3(value) {
|
|
168141
169016
|
if (value <= 300) return AirQuality3.AirQualityEnum.Good;
|
|
168142
169017
|
if (value <= 1e3) return AirQuality3.AirQualityEnum.Fair;
|
|
@@ -168177,17 +169052,17 @@ var TvocAirQualityServer = class extends TvocAirQualityServerBase {
|
|
|
168177
169052
|
const attributes9 = entity.state.attributes;
|
|
168178
169053
|
const deviceClass = attributes9.device_class;
|
|
168179
169054
|
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
168180
|
-
|
|
169055
|
+
logger230.debug(
|
|
168181
169056
|
`[${entity.entity_id}] TVOC update: state="${state}", device_class="${deviceClass}"`
|
|
168182
169057
|
);
|
|
168183
169058
|
if (state != null && !Number.isNaN(+state)) {
|
|
168184
169059
|
const value = +state;
|
|
168185
169060
|
airQuality = deviceClass === SensorDeviceClass.volatile_organic_compounds ? airQualityFromUgm3(value) : airQualityFromPpb(value);
|
|
168186
|
-
|
|
169061
|
+
logger230.debug(
|
|
168187
169062
|
`[${entity.entity_id}] TVOC value=${value} (${deviceClass}) -> airQuality=${AirQuality3.AirQualityEnum[airQuality]}`
|
|
168188
169063
|
);
|
|
168189
169064
|
} else {
|
|
168190
|
-
|
|
169065
|
+
logger230.warn(
|
|
168191
169066
|
`[${entity.entity_id}] TVOC state not a valid number: "${state}"`
|
|
168192
169067
|
);
|
|
168193
169068
|
}
|
|
@@ -168401,7 +169276,7 @@ init_home_assistant_entity_behavior();
|
|
|
168401
169276
|
// src/matter/behaviors/pm25-concentration-measurement-server.ts
|
|
168402
169277
|
init_esm();
|
|
168403
169278
|
init_home_assistant_entity_behavior();
|
|
168404
|
-
var
|
|
169279
|
+
var logger231 = Logger.get("Pm25ConcentrationMeasurementServer");
|
|
168405
169280
|
var Pm25ConcentrationMeasurementServerBase = Pm25ConcentrationMeasurementServer.with(
|
|
168406
169281
|
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
168407
169282
|
);
|
|
@@ -168425,11 +169300,11 @@ var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasure
|
|
|
168425
169300
|
if (this.state.measurementMedium === void 0) {
|
|
168426
169301
|
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
168427
169302
|
}
|
|
168428
|
-
|
|
169303
|
+
logger231.debug(
|
|
168429
169304
|
"Pm25ConcentrationMeasurementServer: before super.initialize()"
|
|
168430
169305
|
);
|
|
168431
169306
|
await super.initialize();
|
|
168432
|
-
|
|
169307
|
+
logger231.debug(
|
|
168433
169308
|
"Pm25ConcentrationMeasurementServer: after super.initialize()"
|
|
168434
169309
|
);
|
|
168435
169310
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
@@ -168452,7 +169327,7 @@ var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasure
|
|
|
168452
169327
|
};
|
|
168453
169328
|
|
|
168454
169329
|
// src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
|
|
168455
|
-
var
|
|
169330
|
+
var logger232 = Logger.get("Pm25AirQualityServer");
|
|
168456
169331
|
var Pm25AirQualityServerBase = AirQualityServer.with(
|
|
168457
169332
|
AirQuality3.Feature.Fair,
|
|
168458
169333
|
AirQuality3.Feature.Moderate,
|
|
@@ -168464,9 +169339,9 @@ var Pm25AirQualityServer = class extends Pm25AirQualityServerBase {
|
|
|
168464
169339
|
if (this.state.airQuality === void 0) {
|
|
168465
169340
|
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
168466
169341
|
}
|
|
168467
|
-
|
|
169342
|
+
logger232.debug("Pm25AirQualityServer: before super.initialize()");
|
|
168468
169343
|
await super.initialize();
|
|
168469
|
-
|
|
169344
|
+
logger232.debug("Pm25AirQualityServer: after super.initialize()");
|
|
168470
169345
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
168471
169346
|
this.update(homeAssistant.entity);
|
|
168472
169347
|
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
@@ -168891,7 +169766,7 @@ init_home_assistant_entity_behavior();
|
|
|
168891
169766
|
init_dist();
|
|
168892
169767
|
init_esm();
|
|
168893
169768
|
init_home_assistant_entity_behavior();
|
|
168894
|
-
var
|
|
169769
|
+
var logger233 = Logger.get("VacuumIdentifyServer");
|
|
168895
169770
|
var IDENTIFY_BUTTON_SUFFIXES = ["_identify", "_locate", "_find_me"];
|
|
168896
169771
|
var VacuumIdentifyServer = class extends IdentifyServer2 {
|
|
168897
169772
|
triggerEffect(effect) {
|
|
@@ -168908,19 +169783,19 @@ var VacuumIdentifyServer = class extends IdentifyServer2 {
|
|
|
168908
169783
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
168909
169784
|
const features3 = homeAssistant.entity.state.attributes.supported_features ?? 0;
|
|
168910
169785
|
if (testBit(features3, VacuumDeviceFeature.LOCATE)) {
|
|
168911
|
-
|
|
169786
|
+
logger233.info(`${source} \u2192 vacuum.locate for ${homeAssistant.entityId}`);
|
|
168912
169787
|
homeAssistant.callAction({ action: "vacuum.locate" });
|
|
168913
169788
|
return;
|
|
168914
169789
|
}
|
|
168915
169790
|
const sibling = this.#findIdentifyButton(homeAssistant);
|
|
168916
169791
|
if (sibling) {
|
|
168917
|
-
|
|
169792
|
+
logger233.info(
|
|
168918
169793
|
`${source} \u2192 button.press ${sibling} for ${homeAssistant.entityId}`
|
|
168919
169794
|
);
|
|
168920
169795
|
homeAssistant.callAction({ action: "button.press", target: sibling });
|
|
168921
169796
|
return;
|
|
168922
169797
|
}
|
|
168923
|
-
|
|
169798
|
+
logger233.warn(
|
|
168924
169799
|
`${source} for ${homeAssistant.entityId}, LOCATE not in supported_features (${features3}), trying vacuum.locate anyway`
|
|
168925
169800
|
);
|
|
168926
169801
|
homeAssistant.callAction({ action: "vacuum.locate" });
|
|
@@ -168928,8 +169803,8 @@ var VacuumIdentifyServer = class extends IdentifyServer2 {
|
|
|
168928
169803
|
#findIdentifyButton(homeAssistant) {
|
|
168929
169804
|
const deviceId = homeAssistant.entity.registry?.device_id;
|
|
168930
169805
|
if (!deviceId) return void 0;
|
|
168931
|
-
const
|
|
168932
|
-
for (const entity of Object.values(
|
|
169806
|
+
const registry3 = this.env.get(HomeAssistantRegistry);
|
|
169807
|
+
for (const entity of Object.values(registry3.entities)) {
|
|
168933
169808
|
if (entity.device_id !== deviceId) continue;
|
|
168934
169809
|
if (!entity.entity_id.startsWith("button.")) continue;
|
|
168935
169810
|
const uniqueId = entity.unique_id ?? "";
|
|
@@ -169119,14 +169994,14 @@ init_esm();
|
|
|
169119
169994
|
|
|
169120
169995
|
// src/matter/behaviors/service-area-server.ts
|
|
169121
169996
|
init_esm();
|
|
169122
|
-
var
|
|
169997
|
+
var logger234 = Logger.get("ServiceAreaServer");
|
|
169123
169998
|
var ServiceAreaWithProgress = ServiceAreaBehavior.with(
|
|
169124
169999
|
ServiceArea3.Feature.ProgressReporting
|
|
169125
170000
|
);
|
|
169126
170001
|
var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
|
|
169127
170002
|
selectAreas(request) {
|
|
169128
170003
|
const { newAreas } = request;
|
|
169129
|
-
|
|
170004
|
+
logger234.info(
|
|
169130
170005
|
`ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
|
|
169131
170006
|
);
|
|
169132
170007
|
const uniqueAreas = [...new Set(newAreas)];
|
|
@@ -169135,7 +170010,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
|
|
|
169135
170010
|
(id) => !supportedAreaIds.includes(id)
|
|
169136
170011
|
);
|
|
169137
170012
|
if (invalidAreas.length > 0) {
|
|
169138
|
-
|
|
170013
|
+
logger234.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
|
|
169139
170014
|
return {
|
|
169140
170015
|
status: ServiceArea3.SelectAreasStatus.UnsupportedArea,
|
|
169141
170016
|
statusText: `Invalid area IDs: ${invalidAreas.join(", ")}`
|
|
@@ -169147,7 +170022,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
|
|
|
169147
170022
|
status: ServiceArea3.OperationalStatus.Pending
|
|
169148
170023
|
}));
|
|
169149
170024
|
this.state.currentArea = null;
|
|
169150
|
-
|
|
170025
|
+
logger234.info(
|
|
169151
170026
|
`ServiceArea: Stored ${uniqueAreas.length} areas for cleaning: ${uniqueAreas.join(", ")}`
|
|
169152
170027
|
);
|
|
169153
170028
|
return {
|
|
@@ -169168,7 +170043,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
|
|
|
169168
170043
|
ServiceAreaServerBase2.State = State;
|
|
169169
170044
|
})(ServiceAreaServerBase || (ServiceAreaServerBase = {}));
|
|
169170
170045
|
function ServiceAreaServer2(initialState) {
|
|
169171
|
-
|
|
170046
|
+
logger234.info(
|
|
169172
170047
|
`Creating ServiceAreaServer with ${initialState.supportedAreas.length} areas`
|
|
169173
170048
|
);
|
|
169174
170049
|
return ServiceAreaServerBase.set({
|
|
@@ -169185,7 +170060,7 @@ var ServiceAreaWithMapsAndProgress = ServiceAreaBehavior.with(
|
|
|
169185
170060
|
var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress {
|
|
169186
170061
|
selectAreas(request) {
|
|
169187
170062
|
const { newAreas } = request;
|
|
169188
|
-
|
|
170063
|
+
logger234.info(
|
|
169189
170064
|
`ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
|
|
169190
170065
|
);
|
|
169191
170066
|
const uniqueAreas = [...new Set(newAreas)];
|
|
@@ -169194,7 +170069,7 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
|
|
|
169194
170069
|
(id) => !supportedAreaIds.includes(id)
|
|
169195
170070
|
);
|
|
169196
170071
|
if (invalidAreas.length > 0) {
|
|
169197
|
-
|
|
170072
|
+
logger234.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
|
|
169198
170073
|
return {
|
|
169199
170074
|
status: ServiceArea3.SelectAreasStatus.UnsupportedArea,
|
|
169200
170075
|
statusText: `Invalid area IDs: ${invalidAreas.join(", ")}`
|
|
@@ -169206,7 +170081,7 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
|
|
|
169206
170081
|
status: ServiceArea3.OperationalStatus.Pending
|
|
169207
170082
|
}));
|
|
169208
170083
|
this.state.currentArea = null;
|
|
169209
|
-
|
|
170084
|
+
logger234.info(
|
|
169210
170085
|
`ServiceArea: Stored ${uniqueAreas.length} areas for cleaning: ${uniqueAreas.join(", ")}`
|
|
169211
170086
|
);
|
|
169212
170087
|
return {
|
|
@@ -169227,14 +170102,14 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
|
|
|
169227
170102
|
ServiceAreaServerWithMapsBase2.State = State;
|
|
169228
170103
|
})(ServiceAreaServerWithMapsBase || (ServiceAreaServerWithMapsBase = {}));
|
|
169229
170104
|
function ServiceAreaServerWithMaps(initialState) {
|
|
169230
|
-
|
|
170105
|
+
logger234.info(
|
|
169231
170106
|
`Creating ServiceAreaServer with Maps: ${initialState.supportedAreas.length} areas, ${initialState.supportedMaps.length} maps`
|
|
169232
170107
|
);
|
|
169233
170108
|
for (const map of initialState.supportedMaps) {
|
|
169234
170109
|
const areaCount = initialState.supportedAreas.filter(
|
|
169235
170110
|
(a) => a.mapId === map.mapId
|
|
169236
170111
|
).length;
|
|
169237
|
-
|
|
170112
|
+
logger234.info(` Map ${map.mapId}: "${map.name}" (${areaCount} areas)`);
|
|
169238
170113
|
}
|
|
169239
170114
|
return ServiceAreaServerWithMapsBase.set({
|
|
169240
170115
|
supportedAreas: initialState.supportedAreas,
|
|
@@ -169246,7 +170121,7 @@ function ServiceAreaServerWithMaps(initialState) {
|
|
|
169246
170121
|
}
|
|
169247
170122
|
|
|
169248
170123
|
// src/matter/endpoints/legacy/vacuum/behaviors/vacuum-service-area-server.ts
|
|
169249
|
-
var
|
|
170124
|
+
var logger235 = Logger.get("VacuumServiceAreaServer");
|
|
169250
170125
|
function toAreaId(roomId) {
|
|
169251
170126
|
if (typeof roomId === "number") {
|
|
169252
170127
|
return roomId;
|
|
@@ -169325,13 +170200,13 @@ function createVacuumServiceAreaServer(attributes9, roomEntities, includeUnnamed
|
|
|
169325
170200
|
let rooms;
|
|
169326
170201
|
if (roomEntities && roomEntities.length > 0) {
|
|
169327
170202
|
rooms = buttonEntitiesToRooms(roomEntities, attributes9);
|
|
169328
|
-
|
|
170203
|
+
logger235.info(
|
|
169329
170204
|
`Using ${rooms.length} button entities as rooms: ${rooms.map((r) => r.name).join(", ")}`
|
|
169330
170205
|
);
|
|
169331
170206
|
} else {
|
|
169332
170207
|
rooms = parseVacuumRooms(attributes9, includeUnnamedRooms);
|
|
169333
170208
|
if (rooms.length > 0) {
|
|
169334
|
-
|
|
170209
|
+
logger235.info(
|
|
169335
170210
|
`Using ${rooms.length} rooms from attributes: ${rooms.map((r) => r.name).join(", ")}`
|
|
169336
170211
|
);
|
|
169337
170212
|
}
|
|
@@ -169385,7 +170260,7 @@ function createCustomServiceAreaServer(customAreas) {
|
|
|
169385
170260
|
landmarkInfo: null
|
|
169386
170261
|
}
|
|
169387
170262
|
}));
|
|
169388
|
-
|
|
170263
|
+
logger235.info(
|
|
169389
170264
|
`Using ${customAreas.length} custom service areas: ${customAreas.map((a) => a.name).join(", ")}`
|
|
169390
170265
|
);
|
|
169391
170266
|
return ServiceAreaServer2({
|
|
@@ -169407,7 +170282,7 @@ function createCleanAreaServiceAreaServer(cleanAreaRooms) {
|
|
|
169407
170282
|
landmarkInfo: null
|
|
169408
170283
|
}
|
|
169409
170284
|
}));
|
|
169410
|
-
|
|
170285
|
+
logger235.info(
|
|
169411
170286
|
`Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA: ${cleanAreaRooms.map((r) => r.name).join(", ")}`
|
|
169412
170287
|
);
|
|
169413
170288
|
return ServiceAreaServer2({
|
|
@@ -169418,11 +170293,11 @@ function createCleanAreaServiceAreaServer(cleanAreaRooms) {
|
|
|
169418
170293
|
}
|
|
169419
170294
|
|
|
169420
170295
|
// src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-run-mode-server.ts
|
|
169421
|
-
var
|
|
170296
|
+
var logger236 = Logger.get("VacuumRvcRunModeServer");
|
|
169422
170297
|
function buildValetudoSegmentAction(vacuumEntityId, segmentIds, valetudoIdentifier) {
|
|
169423
170298
|
const identifier = valetudoIdentifier || vacuumEntityId.replace(/^vacuum\.valetudo_/, "");
|
|
169424
170299
|
const topic = `valetudo/${identifier}/MapSegmentationCapability/clean/set`;
|
|
169425
|
-
|
|
170300
|
+
logger236.info(
|
|
169426
170301
|
`Valetudo: mqtt.publish to ${topic}, segments: ${segmentIds.join(", ")}`
|
|
169427
170302
|
);
|
|
169428
170303
|
return {
|
|
@@ -169527,14 +170402,14 @@ function mergeBatchData(areas) {
|
|
|
169527
170402
|
function handleCustomServiceAreas(selectedAreas, customAreas, session) {
|
|
169528
170403
|
const matched = selectedAreas.map((areaId) => ({ areaId, area: customAreas[areaId - 1] })).filter((m) => !!m.area);
|
|
169529
170404
|
if (matched.length === 0) {
|
|
169530
|
-
|
|
170405
|
+
logger236.warn(
|
|
169531
170406
|
`Custom service areas: no match for selected IDs ${selectedAreas.join(", ")}`
|
|
169532
170407
|
);
|
|
169533
170408
|
return { action: "vacuum.start" };
|
|
169534
170409
|
}
|
|
169535
170410
|
const batchArea = matched.find(({ area }) => area.batchDispatch === true);
|
|
169536
170411
|
if (batchArea) {
|
|
169537
|
-
|
|
170412
|
+
logger236.info(
|
|
169538
170413
|
`Custom service areas (batch): single call for ${matched.length} room(s): ${matched.map(({ area }) => area.name).join(", ")}`
|
|
169539
170414
|
);
|
|
169540
170415
|
session.pendingDispatches = [];
|
|
@@ -169555,7 +170430,7 @@ function handleCustomServiceAreas(selectedAreas, customAreas, session) {
|
|
|
169555
170430
|
}
|
|
169556
170431
|
};
|
|
169557
170432
|
}
|
|
169558
|
-
|
|
170433
|
+
logger236.info(
|
|
169559
170434
|
`Custom service areas: ${matched.length} room(s) queued: ${matched.map(({ area }) => `${area.service} (${area.name})`).join(", ")}`
|
|
169560
170435
|
);
|
|
169561
170436
|
session.pendingDispatches = matched.slice(1).map(({ areaId, area }) => ({
|
|
@@ -169579,20 +170454,22 @@ function resolveCleanAreaIds(selectedAreas, cleanAreaRooms) {
|
|
|
169579
170454
|
}
|
|
169580
170455
|
return haAreaIds;
|
|
169581
170456
|
}
|
|
170457
|
+
var cleaningStates = [
|
|
170458
|
+
VacuumState.cleaning,
|
|
170459
|
+
VacuumState.segment_cleaning,
|
|
170460
|
+
VacuumState.zone_cleaning,
|
|
170461
|
+
VacuumState.spot_cleaning,
|
|
170462
|
+
VacuumState.mop_cleaning,
|
|
170463
|
+
VacuumState.paused
|
|
170464
|
+
];
|
|
170465
|
+
function vacuumIsCleaning(state) {
|
|
170466
|
+
return state != null && cleaningStates.includes(state);
|
|
170467
|
+
}
|
|
169582
170468
|
var vacuumRvcRunModeConfig = {
|
|
169583
170469
|
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"}`
|
|
170470
|
+
const isCleaning = vacuumIsCleaning(entity.state);
|
|
170471
|
+
logger236.debug(
|
|
170472
|
+
`Vacuum state: "${entity.state}", isCleaning: ${isCleaning}, currentMode: ${isCleaning ? "Cleaning" : "Idle"}`
|
|
169596
170473
|
);
|
|
169597
170474
|
return isCleaning ? 1 /* Cleaning */ : 0 /* Idle */;
|
|
169598
170475
|
},
|
|
@@ -169625,7 +170502,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169625
170502
|
if (cleanAreaRooms && cleanAreaRooms.length > 0) {
|
|
169626
170503
|
const haAreaIds = resolveCleanAreaIds(selectedAreas, cleanAreaRooms);
|
|
169627
170504
|
if (haAreaIds.length > 0) {
|
|
169628
|
-
|
|
170505
|
+
logger236.info(
|
|
169629
170506
|
`CLEAN_AREA: cleaning HA areas: ${haAreaIds.join(", ")}`
|
|
169630
170507
|
);
|
|
169631
170508
|
return {
|
|
@@ -169644,7 +170521,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169644
170521
|
}
|
|
169645
170522
|
}
|
|
169646
170523
|
if (matched.length > 0) {
|
|
169647
|
-
|
|
170524
|
+
logger236.info(
|
|
169648
170525
|
`Roborock: ${matched.length} room button(s) queued: ${matched.map((m) => m.entityId).join(", ")}`
|
|
169649
170526
|
);
|
|
169650
170527
|
session.pendingDispatches = matched.slice(1).map(({ areaId, entityId }) => ({
|
|
@@ -169678,14 +170555,14 @@ var vacuumRvcRunModeConfig = {
|
|
|
169678
170555
|
}
|
|
169679
170556
|
}
|
|
169680
170557
|
if (roomIds.length > 0) {
|
|
169681
|
-
|
|
170558
|
+
logger236.info(
|
|
169682
170559
|
`Starting cleaning with selected areas: ${roomIds.join(", ")}`
|
|
169683
170560
|
);
|
|
169684
170561
|
if (isDreameVacuum(attributes9)) {
|
|
169685
170562
|
if (targetMapName) {
|
|
169686
170563
|
const vacName = vacuumEntityId.replace("vacuum.", "");
|
|
169687
170564
|
const selectedMapEntity = `select.${vacName}_selected_map`;
|
|
169688
|
-
|
|
170565
|
+
logger236.info(
|
|
169689
170566
|
`Dreame multi-floor: switching to map "${targetMapName}" via ${selectedMapEntity}`
|
|
169690
170567
|
);
|
|
169691
170568
|
homeAssistant.callAction({
|
|
@@ -169712,7 +170589,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169712
170589
|
}
|
|
169713
170590
|
if (isEcovacsVacuum(attributes9)) {
|
|
169714
170591
|
const roomIdStr = roomIds.join(",");
|
|
169715
|
-
|
|
170592
|
+
logger236.info(
|
|
169716
170593
|
`Ecovacs vacuum: Using spot_area for rooms: ${roomIdStr}`
|
|
169717
170594
|
);
|
|
169718
170595
|
return {
|
|
@@ -169727,14 +170604,14 @@ var vacuumRvcRunModeConfig = {
|
|
|
169727
170604
|
}
|
|
169728
170605
|
};
|
|
169729
170606
|
}
|
|
169730
|
-
|
|
170607
|
+
logger236.warn(
|
|
169731
170608
|
`Room cleaning via send_command not supported for this vacuum type. Rooms: ${roomIds.join(", ")}. Falling back to vacuum.start`
|
|
169732
170609
|
);
|
|
169733
170610
|
}
|
|
169734
170611
|
}
|
|
169735
170612
|
} catch {
|
|
169736
170613
|
}
|
|
169737
|
-
|
|
170614
|
+
logger236.info("Starting regular cleaning (no areas selected)");
|
|
169738
170615
|
return { action: "vacuum.start" };
|
|
169739
170616
|
},
|
|
169740
170617
|
returnToBase: () => ({ action: "vacuum.return_to_base" }),
|
|
@@ -169749,7 +170626,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169749
170626
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
169750
170627
|
const entity = homeAssistant.entity;
|
|
169751
170628
|
const attributes9 = entity.state.attributes;
|
|
169752
|
-
|
|
170629
|
+
logger236.info(`cleanRoom called: roomMode=${roomMode}`);
|
|
169753
170630
|
const cleanAreaRooms = homeAssistant.state.mapping?.cleanAreaRooms;
|
|
169754
170631
|
if (cleanAreaRooms && cleanAreaRooms.length > 0) {
|
|
169755
170632
|
const sorted = [...cleanAreaRooms].sort(
|
|
@@ -169758,7 +170635,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169758
170635
|
const areaIndex = roomMode - ROOM_MODE_BASE2 - 1;
|
|
169759
170636
|
if (areaIndex >= 0 && areaIndex < sorted.length) {
|
|
169760
170637
|
const area = sorted[areaIndex];
|
|
169761
|
-
|
|
170638
|
+
logger236.info(
|
|
169762
170639
|
`cleanRoom: CLEAN_AREA "${area.name}" \u2192 vacuum.clean_area(${area.haAreaId})`
|
|
169763
170640
|
);
|
|
169764
170641
|
return {
|
|
@@ -169775,7 +170652,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169775
170652
|
const areaIndex = roomMode - ROOM_MODE_BASE2 - 1;
|
|
169776
170653
|
if (areaIndex >= 0 && areaIndex < sorted.length) {
|
|
169777
170654
|
const area = sorted[areaIndex];
|
|
169778
|
-
|
|
170655
|
+
logger236.info(
|
|
169779
170656
|
`cleanRoom: custom service area "${area.name}" \u2192 ${area.service}`
|
|
169780
170657
|
);
|
|
169781
170658
|
return {
|
|
@@ -169796,7 +170673,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169796
170673
|
}
|
|
169797
170674
|
const rooms = parseVacuumRooms(attributes9);
|
|
169798
170675
|
const numericIdFromMode = getRoomIdFromMode(roomMode);
|
|
169799
|
-
|
|
170676
|
+
logger236.info(
|
|
169800
170677
|
`cleanRoom: numericIdFromMode=${numericIdFromMode}, available rooms: ${JSON.stringify(rooms.map((r) => ({ id: r.id, name: r.name, modeValue: getRoomModeValue(r) })))}`
|
|
169801
170678
|
);
|
|
169802
170679
|
const room = rooms.find((r) => getRoomModeValue(r) === roomMode);
|
|
@@ -169806,7 +170683,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169806
170683
|
if (room.mapName) {
|
|
169807
170684
|
const vacuumName = vacuumEntityId.replace("vacuum.", "");
|
|
169808
170685
|
const selectedMapEntity = `select.${vacuumName}_selected_map`;
|
|
169809
|
-
|
|
170686
|
+
logger236.info(
|
|
169810
170687
|
`Dreame multi-floor: switching to map "${room.mapName}" via ${selectedMapEntity}`
|
|
169811
170688
|
);
|
|
169812
170689
|
homeAssistant.callAction({
|
|
@@ -169815,7 +170692,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169815
170692
|
data: { option: room.mapName }
|
|
169816
170693
|
});
|
|
169817
170694
|
}
|
|
169818
|
-
|
|
170695
|
+
logger236.debug(
|
|
169819
170696
|
`Dreame vacuum detected, using dreame_vacuum.vacuum_clean_segment for room ${room.name} (commandId: ${commandId3}, id: ${room.id})`
|
|
169820
170697
|
);
|
|
169821
170698
|
return {
|
|
@@ -169826,7 +170703,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169826
170703
|
};
|
|
169827
170704
|
}
|
|
169828
170705
|
if (isRoborockVacuum(attributes9) || isXiaomiMiotVacuum(attributes9)) {
|
|
169829
|
-
|
|
170706
|
+
logger236.debug(
|
|
169830
170707
|
`Using vacuum.send_command with app_segment_clean for room ${room.name} (commandId: ${commandId3}, id: ${room.id})`
|
|
169831
170708
|
);
|
|
169832
170709
|
return {
|
|
@@ -169839,7 +170716,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169839
170716
|
}
|
|
169840
170717
|
if (isEcovacsVacuum(attributes9)) {
|
|
169841
170718
|
const roomIdStr = String(commandId3);
|
|
169842
|
-
|
|
170719
|
+
logger236.info(
|
|
169843
170720
|
`Ecovacs vacuum: Using spot_area for room ${room.name} (id: ${roomIdStr})`
|
|
169844
170721
|
);
|
|
169845
170722
|
return {
|
|
@@ -169854,7 +170731,7 @@ var vacuumRvcRunModeConfig = {
|
|
|
169854
170731
|
}
|
|
169855
170732
|
};
|
|
169856
170733
|
}
|
|
169857
|
-
|
|
170734
|
+
logger236.warn(
|
|
169858
170735
|
`Room cleaning via send_command not supported for this vacuum type. Room: ${room.name} (id=${commandId3}). Falling back to vacuum.start`
|
|
169859
170736
|
);
|
|
169860
170737
|
}
|
|
@@ -169871,20 +170748,20 @@ function createVacuumRvcRunModeServer(attributes9, includeUnnamedRooms = false,
|
|
|
169871
170748
|
customAreas,
|
|
169872
170749
|
disableRoomModes
|
|
169873
170750
|
);
|
|
169874
|
-
|
|
170751
|
+
logger236.info(
|
|
169875
170752
|
`Creating VacuumRvcRunModeServer with ${rooms.length} rooms, ${supportedModes2.length} total modes`
|
|
169876
170753
|
);
|
|
169877
170754
|
if (rooms.length > 0) {
|
|
169878
|
-
|
|
170755
|
+
logger236.info(`Rooms found: ${rooms.map((r) => r.name).join(", ")}`);
|
|
169879
170756
|
}
|
|
169880
170757
|
if (filteredCount > 0) {
|
|
169881
170758
|
const filtered = allRooms.filter((r) => !rooms.some((x) => x.id === r.id));
|
|
169882
|
-
|
|
170759
|
+
logger236.info(
|
|
169883
170760
|
`Filtered out ${filteredCount} unnamed room(s): ${filtered.map((r) => r.name).join(", ")}`
|
|
169884
170761
|
);
|
|
169885
170762
|
}
|
|
169886
170763
|
if (allRooms.length === 0) {
|
|
169887
|
-
|
|
170764
|
+
logger236.debug(
|
|
169888
170765
|
`No rooms found. Attributes: rooms=${JSON.stringify(attributes9.rooms)}, segments=${JSON.stringify(attributes9.segments)}, room_list=${attributes9.room_list}`
|
|
169889
170766
|
);
|
|
169890
170767
|
}
|
|
@@ -169918,7 +170795,7 @@ function createCleanAreaRvcRunModeServer(cleanAreaRooms) {
|
|
|
169918
170795
|
modeTags: [{ value: RvcRunMode3.ModeTag.Cleaning }]
|
|
169919
170796
|
});
|
|
169920
170797
|
}
|
|
169921
|
-
|
|
170798
|
+
logger236.info(
|
|
169922
170799
|
`Creating CLEAN_AREA RvcRunModeServer with ${cleanAreaRooms.length} HA areas, ${modes.length} total modes`
|
|
169923
170800
|
);
|
|
169924
170801
|
return RvcRunModeServer2(vacuumRvcRunModeConfig, {
|
|
@@ -169926,11 +170803,12 @@ function createCleanAreaRvcRunModeServer(cleanAreaRooms) {
|
|
|
169926
170803
|
currentMode: 0 /* Idle */
|
|
169927
170804
|
});
|
|
169928
170805
|
}
|
|
169929
|
-
var VacuumRvcRunModeServer = RvcRunModeServer2(vacuumRvcRunModeConfig);
|
|
169930
170806
|
|
|
169931
170807
|
// src/matter/endpoints/legacy/vacuum/behaviors/vacuum-on-off-server.ts
|
|
169932
170808
|
var VacuumOnOffServer = OnOffServer2({
|
|
169933
|
-
|
|
170809
|
+
// Derive from the entity directly. Reading the sibling rvcRunMode behavior
|
|
170810
|
+
// crashed with a class mismatch and would lag one tick behind anyway (#428).
|
|
170811
|
+
isOn: (entity) => vacuumIsCleaning(entity.state),
|
|
169934
170812
|
turnOn: () => ({ action: "vacuum.start" }),
|
|
169935
170813
|
turnOff: () => ({ action: "vacuum.return_to_base" })
|
|
169936
170814
|
}).with();
|
|
@@ -170007,7 +170885,7 @@ init_nodejs();
|
|
|
170007
170885
|
|
|
170008
170886
|
// src/matter/behaviors/rvc-clean-mode-server.ts
|
|
170009
170887
|
init_home_assistant_entity_behavior();
|
|
170010
|
-
var
|
|
170888
|
+
var logger237 = Logger.get("RvcCleanModeServerBase");
|
|
170011
170889
|
var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeServer {
|
|
170012
170890
|
// Pending mode from a recent changeToMode command.
|
|
170013
170891
|
// Prevents stale HA state (from a different entity like select.xxx)
|
|
@@ -170054,14 +170932,14 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
|
|
|
170054
170932
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
170055
170933
|
const { newMode } = request;
|
|
170056
170934
|
if (newMode !== this.state.currentMode && !this.state.supportedModes.some((m) => m.mode === newMode)) {
|
|
170057
|
-
|
|
170935
|
+
logger237.warn(`changeToMode(${newMode}) rejected: unsupported mode`);
|
|
170058
170936
|
return {
|
|
170059
170937
|
status: ModeBase3.ModeChangeStatus.UnsupportedMode,
|
|
170060
170938
|
statusText: `Unsupported mode: ${newMode}`
|
|
170061
170939
|
};
|
|
170062
170940
|
}
|
|
170063
170941
|
const modeLabel = this.state.supportedModes.find((m) => m.mode === newMode);
|
|
170064
|
-
|
|
170942
|
+
logger237.info(
|
|
170065
170943
|
`changeToMode(${newMode}) "${modeLabel?.label ?? "unknown"}" for ${homeAssistant.entityId}`
|
|
170066
170944
|
);
|
|
170067
170945
|
this.pendingMode = newMode;
|
|
@@ -170069,7 +170947,7 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
|
|
|
170069
170947
|
this.state.currentMode = newMode;
|
|
170070
170948
|
const action = this.state.config.setCleanMode(newMode, this.agent);
|
|
170071
170949
|
if (action) {
|
|
170072
|
-
|
|
170950
|
+
logger237.info(
|
|
170073
170951
|
`changeToMode: dispatching action ${action.action} \u2192 ${action.target ?? homeAssistant.entityId}`
|
|
170074
170952
|
);
|
|
170075
170953
|
homeAssistant.callAction(action);
|
|
@@ -170102,7 +170980,7 @@ function RvcCleanModeServer2(config11, initialState) {
|
|
|
170102
170980
|
}
|
|
170103
170981
|
|
|
170104
170982
|
// src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-clean-mode-server.ts
|
|
170105
|
-
var
|
|
170983
|
+
var logger238 = Logger.get("VacuumRvcCleanModeServer");
|
|
170106
170984
|
var MODE_VACUUM = 0;
|
|
170107
170985
|
var MODE_VACUUM_AND_MOP = 1;
|
|
170108
170986
|
var MODE_MOP = 2;
|
|
@@ -170372,7 +171250,7 @@ function findMatchingCleanOption(ct, availableOptions) {
|
|
|
170372
171250
|
const match = availableOptions.find((o) => classifyCleanOption(o) === type);
|
|
170373
171251
|
if (match) return match;
|
|
170374
171252
|
}
|
|
170375
|
-
|
|
171253
|
+
logger238.warn(
|
|
170376
171254
|
`No match for ${CLEAN_TYPE_LABELS[ct]} in [${availableOptions.join(", ")}]`
|
|
170377
171255
|
);
|
|
170378
171256
|
return availableOptions[0];
|
|
@@ -170381,7 +171259,7 @@ function buildCleaningModeAction(targetCleanType, agent) {
|
|
|
170381
171259
|
const selectEntityId = getCleaningModeSelectEntity(agent);
|
|
170382
171260
|
const { options } = readSelectEntity(selectEntityId, agent);
|
|
170383
171261
|
const optionToUse = findMatchingCleanOption(targetCleanType, options);
|
|
170384
|
-
|
|
171262
|
+
logger238.info(
|
|
170385
171263
|
`Switching cleaning mode to: ${optionToUse} via ${selectEntityId}`
|
|
170386
171264
|
);
|
|
170387
171265
|
return {
|
|
@@ -170470,7 +171348,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170470
171348
|
}
|
|
170471
171349
|
}
|
|
170472
171350
|
if (speedMode !== void 0) {
|
|
170473
|
-
|
|
171351
|
+
logger238.debug(
|
|
170474
171352
|
`Current mode: Vacuum + fan_speed="${speedState}" -> mode ${speedMode}`
|
|
170475
171353
|
);
|
|
170476
171354
|
return speedMode;
|
|
@@ -170491,7 +171369,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170491
171369
|
}
|
|
170492
171370
|
}
|
|
170493
171371
|
if (mopMode !== void 0) {
|
|
170494
|
-
|
|
171372
|
+
logger238.debug(
|
|
170495
171373
|
`Current mode: Mop + intensity="${state}" -> mode ${mopMode}`
|
|
170496
171374
|
);
|
|
170497
171375
|
return mopMode;
|
|
@@ -170509,14 +171387,14 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170509
171387
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
170510
171388
|
const vacuumEntityId = homeAssistant.entityId;
|
|
170511
171389
|
const mapping = homeAssistant.state.mapping;
|
|
170512
|
-
|
|
171390
|
+
logger238.info(
|
|
170513
171391
|
`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
171392
|
);
|
|
170515
171393
|
if (mopIntensityList && mopIntensityList.length > 0 && isMopIntensityMode(mode)) {
|
|
170516
171394
|
const mopIndex = mode - MOP_INTENSITY_MODE_BASE;
|
|
170517
171395
|
const mopName = mopIntensityList[mopIndex];
|
|
170518
171396
|
if (!mopName) {
|
|
170519
|
-
|
|
171397
|
+
logger238.warn(`Invalid mop intensity mode index: ${mopIndex}`);
|
|
170520
171398
|
return void 0;
|
|
170521
171399
|
}
|
|
170522
171400
|
if (hasCleanTypes) {
|
|
@@ -170529,18 +171407,18 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170529
171407
|
mapping.mopIntensityEntity,
|
|
170530
171408
|
agent
|
|
170531
171409
|
);
|
|
170532
|
-
|
|
171410
|
+
logger238.info(
|
|
170533
171411
|
`Mop intensity entity ${mapping.mopIntensityEntity}: current="${state}", options=${JSON.stringify(options ?? [])}`
|
|
170534
171412
|
);
|
|
170535
171413
|
let option = matchMopIntensityOption(mopName, options);
|
|
170536
171414
|
if (!option && options && mopIndex < options.length) {
|
|
170537
171415
|
option = options[mopIndex];
|
|
170538
|
-
|
|
171416
|
+
logger238.info(
|
|
170539
171417
|
`Positional match for mop "${mopName}" -> "${option}" (index ${mopIndex})`
|
|
170540
171418
|
);
|
|
170541
171419
|
}
|
|
170542
171420
|
if (option) {
|
|
170543
|
-
|
|
171421
|
+
logger238.info(
|
|
170544
171422
|
`Setting mop intensity to: ${option} via ${mapping.mopIntensityEntity}`
|
|
170545
171423
|
);
|
|
170546
171424
|
return {
|
|
@@ -170549,11 +171427,11 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170549
171427
|
target: mapping.mopIntensityEntity
|
|
170550
171428
|
};
|
|
170551
171429
|
}
|
|
170552
|
-
|
|
171430
|
+
logger238.warn(
|
|
170553
171431
|
`No match for mop intensity "${mopName}" in options: [${(options ?? []).join(", ")}]`
|
|
170554
171432
|
);
|
|
170555
171433
|
} else {
|
|
170556
|
-
|
|
171434
|
+
logger238.warn(
|
|
170557
171435
|
`Mop intensity mode ${mode} requested but no mopIntensityEntity configured`
|
|
170558
171436
|
);
|
|
170559
171437
|
}
|
|
@@ -170563,7 +171441,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170563
171441
|
const fanSpeedIndex = mode - FAN_SPEED_MODE_BASE;
|
|
170564
171442
|
const fanSpeedName = fanSpeedList[fanSpeedIndex];
|
|
170565
171443
|
if (!fanSpeedName) {
|
|
170566
|
-
|
|
171444
|
+
logger238.warn(`Invalid fan speed mode index: ${fanSpeedIndex}`);
|
|
170567
171445
|
return void 0;
|
|
170568
171446
|
}
|
|
170569
171447
|
if (mapping?.suctionLevelEntity) {
|
|
@@ -170576,7 +171454,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170576
171454
|
mapping.suctionLevelEntity,
|
|
170577
171455
|
agent
|
|
170578
171456
|
);
|
|
170579
|
-
|
|
171457
|
+
logger238.info(
|
|
170580
171458
|
`Suction entity ${mapping.suctionLevelEntity}: current="${state}", options=${JSON.stringify(options ?? [])}`
|
|
170581
171459
|
);
|
|
170582
171460
|
let option = matchFanSpeedOption(
|
|
@@ -170586,12 +171464,12 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170586
171464
|
);
|
|
170587
171465
|
if (!option && options && fanSpeedIndex < options.length) {
|
|
170588
171466
|
option = options[fanSpeedIndex];
|
|
170589
|
-
|
|
171467
|
+
logger238.info(
|
|
170590
171468
|
`Positional match for fan "${fanSpeedName}" -> "${option}" (index ${fanSpeedIndex})`
|
|
170591
171469
|
);
|
|
170592
171470
|
}
|
|
170593
171471
|
if (option) {
|
|
170594
|
-
|
|
171472
|
+
logger238.info(
|
|
170595
171473
|
`Setting suction to: ${option} via ${mapping.suctionLevelEntity}`
|
|
170596
171474
|
);
|
|
170597
171475
|
return {
|
|
@@ -170600,7 +171478,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170600
171478
|
target: mapping.suctionLevelEntity
|
|
170601
171479
|
};
|
|
170602
171480
|
}
|
|
170603
|
-
|
|
171481
|
+
logger238.warn(
|
|
170604
171482
|
`No match for fan speed "${fanSpeedName}" in suction options: [${(options ?? []).join(", ")}]`
|
|
170605
171483
|
);
|
|
170606
171484
|
return void 0;
|
|
@@ -170610,7 +171488,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170610
171488
|
buildCleaningModeAction(0 /* Sweeping */, agent)
|
|
170611
171489
|
);
|
|
170612
171490
|
}
|
|
170613
|
-
|
|
171491
|
+
logger238.info(
|
|
170614
171492
|
`Setting fan speed to: ${fanSpeedName} via vacuum.set_fan_speed`
|
|
170615
171493
|
);
|
|
170616
171494
|
return {
|
|
@@ -170620,7 +171498,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170620
171498
|
};
|
|
170621
171499
|
}
|
|
170622
171500
|
if (!hasCleanTypes) {
|
|
170623
|
-
|
|
171501
|
+
logger238.debug(
|
|
170624
171502
|
`Ignoring cleaning type change (mode=${mode}): no cleaning mode entity`
|
|
170625
171503
|
);
|
|
170626
171504
|
return void 0;
|
|
@@ -170632,7 +171510,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
170632
171510
|
agent
|
|
170633
171511
|
);
|
|
170634
171512
|
const optionToUse = findMatchingCleanOption(cleanType, availableOptions);
|
|
170635
|
-
|
|
171513
|
+
logger238.info(
|
|
170636
171514
|
`Setting cleaning mode to: ${optionToUse} (mode=${mode}) via ${selectEntityId}`
|
|
170637
171515
|
);
|
|
170638
171516
|
return {
|
|
@@ -170650,10 +171528,10 @@ function createVacuumRvcCleanModeServer(_attributes, fanSpeedList, mopIntensityL
|
|
|
170650
171528
|
cleaningModeOptions,
|
|
170651
171529
|
customFanSpeedTags
|
|
170652
171530
|
);
|
|
170653
|
-
|
|
171531
|
+
logger238.info(
|
|
170654
171532
|
`Creating VacuumRvcCleanModeServer with ${supportedModes2.length} modes (fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])}, cleaningModeOptions=${JSON.stringify(cleaningModeOptions ?? [])}, customTags=${JSON.stringify(customFanSpeedTags ?? {})})`
|
|
170655
171533
|
);
|
|
170656
|
-
|
|
171534
|
+
logger238.info(
|
|
170657
171535
|
`Modes: ${supportedModes2.map((m) => `${m.mode}:${m.label}[${m.modeTags.map((t) => t.value).join(",")}]`).join(", ")}`
|
|
170658
171536
|
);
|
|
170659
171537
|
const initialState = {
|
|
@@ -170716,7 +171594,7 @@ function resolveMopIntensityList(mopIntensityEntity) {
|
|
|
170716
171594
|
init_dist();
|
|
170717
171595
|
init_esm();
|
|
170718
171596
|
init_home_assistant_entity_behavior();
|
|
170719
|
-
var
|
|
171597
|
+
var logger239 = Logger.get("VacuumRvcOperationalStateServer");
|
|
170720
171598
|
function batteryFromAttributes(attrs) {
|
|
170721
171599
|
const raw = attrs.battery_level ?? attrs.battery;
|
|
170722
171600
|
if (raw == null) return null;
|
|
@@ -170743,7 +171621,7 @@ function isDockedCharging(entity, batteryPercent) {
|
|
|
170743
171621
|
}
|
|
170744
171622
|
function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttributes(entity.attributes), chargingState = null) {
|
|
170745
171623
|
const state = entity.state;
|
|
170746
|
-
const
|
|
171624
|
+
const cleaningStates2 = [
|
|
170747
171625
|
VacuumState.cleaning,
|
|
170748
171626
|
VacuumState.segment_cleaning,
|
|
170749
171627
|
VacuumState.zone_cleaning,
|
|
@@ -170756,7 +171634,7 @@ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttribute
|
|
|
170756
171634
|
operationalState = charging ? RvcOperationalState4.OperationalState.Charging : RvcOperationalState4.OperationalState.Docked;
|
|
170757
171635
|
} else if (state === VacuumState.returning) {
|
|
170758
171636
|
operationalState = RvcOperationalState4.OperationalState.SeekingCharger;
|
|
170759
|
-
} else if (
|
|
171637
|
+
} else if (cleaningStates2.includes(state)) {
|
|
170760
171638
|
operationalState = RvcOperationalState4.OperationalState.Running;
|
|
170761
171639
|
} else if (state === VacuumState.paused) {
|
|
170762
171640
|
operationalState = RvcOperationalState4.OperationalState.Paused;
|
|
@@ -170767,16 +171645,16 @@ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttribute
|
|
|
170767
171645
|
operationalState = RvcOperationalState4.OperationalState.Error;
|
|
170768
171646
|
} else {
|
|
170769
171647
|
if (state.toLowerCase().includes("clean")) {
|
|
170770
|
-
|
|
171648
|
+
logger239.info(
|
|
170771
171649
|
`Unknown vacuum state "${state}" contains 'clean', treating as Running`
|
|
170772
171650
|
);
|
|
170773
171651
|
operationalState = RvcOperationalState4.OperationalState.Running;
|
|
170774
171652
|
} else {
|
|
170775
|
-
|
|
171653
|
+
logger239.info(`Unknown vacuum state "${state}", treating as Stopped`);
|
|
170776
171654
|
operationalState = RvcOperationalState4.OperationalState.Stopped;
|
|
170777
171655
|
}
|
|
170778
171656
|
}
|
|
170779
|
-
|
|
171657
|
+
logger239.debug(
|
|
170780
171658
|
`Vacuum operationalState: "${state}" -> ${RvcOperationalState4.OperationalState[operationalState]}`
|
|
170781
171659
|
);
|
|
170782
171660
|
return operationalState;
|
|
@@ -170805,7 +171683,7 @@ var VacuumRvcOperationalStateServer = RvcOperationalStateServer2({
|
|
|
170805
171683
|
});
|
|
170806
171684
|
|
|
170807
171685
|
// src/matter/endpoints/legacy/vacuum/index.ts
|
|
170808
|
-
var
|
|
171686
|
+
var logger240 = Logger.get("VacuumDevice");
|
|
170809
171687
|
var VacuumEndpointType = RoboticVacuumCleanerDevice.with(
|
|
170810
171688
|
BasicInformationServer2,
|
|
170811
171689
|
VacuumIdentifyServer,
|
|
@@ -170819,7 +171697,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
|
|
|
170819
171697
|
const entityId = homeAssistantEntity.entity.entity_id;
|
|
170820
171698
|
const attributes9 = homeAssistantEntity.entity.state.attributes;
|
|
170821
171699
|
const customAreas = homeAssistantEntity.mapping?.customServiceAreas;
|
|
170822
|
-
|
|
171700
|
+
logger240.info(
|
|
170823
171701
|
`Creating vacuum endpoint for ${entityId}, mapping: ${JSON.stringify(homeAssistantEntity.mapping ?? "none")}`
|
|
170824
171702
|
);
|
|
170825
171703
|
const cleanAreaRooms = homeAssistantEntity.mapping?.cleanAreaRooms;
|
|
@@ -170832,32 +171710,32 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
|
|
|
170832
171710
|
)
|
|
170833
171711
|
).set({ homeAssistantEntity });
|
|
170834
171712
|
if (includeOnOff) {
|
|
170835
|
-
|
|
171713
|
+
logger240.info(`${entityId}: Adding OnOff cluster (vacuumOnOff flag enabled)`);
|
|
170836
171714
|
device = device.with(VacuumOnOffServer);
|
|
170837
171715
|
}
|
|
170838
171716
|
device = device.with(VacuumPowerSourceServer);
|
|
170839
171717
|
const roomEntities = homeAssistantEntity.mapping?.roomEntities;
|
|
170840
171718
|
const rooms = parseVacuumRooms(attributes9);
|
|
170841
|
-
|
|
171719
|
+
logger240.info(
|
|
170842
171720
|
`${entityId}: customAreas=${customAreas?.length ?? 0}, roomEntities=${JSON.stringify(roomEntities ?? [])}, parsedRooms=${rooms.length}, cleanAreaRooms=${cleanAreaRooms?.length ?? 0}`
|
|
170843
171721
|
);
|
|
170844
171722
|
if (cleanAreaRooms && cleanAreaRooms.length > 0) {
|
|
170845
|
-
|
|
171723
|
+
logger240.info(
|
|
170846
171724
|
`${entityId}: Adding ServiceArea (${cleanAreaRooms.length} HA areas via CLEAN_AREA)`
|
|
170847
171725
|
);
|
|
170848
171726
|
device = device.with(createCleanAreaServiceAreaServer(cleanAreaRooms));
|
|
170849
171727
|
} else if (customAreas && customAreas.length > 0) {
|
|
170850
|
-
|
|
171728
|
+
logger240.info(
|
|
170851
171729
|
`${entityId}: Adding ServiceArea (${customAreas.length} custom areas)`
|
|
170852
171730
|
);
|
|
170853
171731
|
device = device.with(createCustomServiceAreaServer(customAreas));
|
|
170854
171732
|
} else if (rooms.length > 0 || roomEntities && roomEntities.length > 0) {
|
|
170855
|
-
|
|
171733
|
+
logger240.info(`${entityId}: Adding ServiceArea (${rooms.length} rooms)`);
|
|
170856
171734
|
device = device.with(
|
|
170857
171735
|
createVacuumServiceAreaServer(attributes9, roomEntities)
|
|
170858
171736
|
);
|
|
170859
171737
|
} else {
|
|
170860
|
-
|
|
171738
|
+
logger240.info(`${entityId}: Adding ServiceArea (default single-area)`);
|
|
170861
171739
|
device = device.with(createDefaultServiceAreaServer());
|
|
170862
171740
|
}
|
|
170863
171741
|
const fanSpeedList = resolveFanSpeedList(
|
|
@@ -170868,7 +171746,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
|
|
|
170868
171746
|
homeAssistantEntity.mapping?.mopIntensityEntity
|
|
170869
171747
|
);
|
|
170870
171748
|
if (cleaningModeOptions || fanSpeedList || mopIntensityList) {
|
|
170871
|
-
|
|
171749
|
+
logger240.info(
|
|
170872
171750
|
`${entityId}: Adding RvcCleanMode (multi-mode, cleaningModeOptions=${JSON.stringify(cleaningModeOptions ?? [])}, fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])})`
|
|
170873
171751
|
);
|
|
170874
171752
|
device = device.with(
|
|
@@ -170881,7 +171759,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
|
|
|
170881
171759
|
)
|
|
170882
171760
|
);
|
|
170883
171761
|
} else {
|
|
170884
|
-
|
|
171762
|
+
logger240.info(`${entityId}: Adding RvcCleanMode (default single-mode)`);
|
|
170885
171763
|
device = device.with(createDefaultRvcCleanModeServer());
|
|
170886
171764
|
}
|
|
170887
171765
|
return device;
|
|
@@ -171047,7 +171925,7 @@ var WaterHeaterThermostatServer = ThermostatServer2(
|
|
|
171047
171925
|
);
|
|
171048
171926
|
|
|
171049
171927
|
// src/matter/endpoints/legacy/water-heater/index.ts
|
|
171050
|
-
var
|
|
171928
|
+
var logger241 = Logger.get("WaterHeaterDevice");
|
|
171051
171929
|
var WaterHeaterDeviceType = ThermostatDevice.with(
|
|
171052
171930
|
BasicInformationServer2,
|
|
171053
171931
|
IdentifyServer2,
|
|
@@ -171063,7 +171941,7 @@ function toMatterTemp2(value) {
|
|
|
171063
171941
|
}
|
|
171064
171942
|
function WaterHeaterDevice(homeAssistantEntity) {
|
|
171065
171943
|
const attributes9 = homeAssistantEntity.entity.state.attributes;
|
|
171066
|
-
|
|
171944
|
+
logger241.debug(
|
|
171067
171945
|
`Creating device for ${homeAssistantEntity.entity.entity_id}, min_temp=${attributes9.min_temp}, max_temp=${attributes9.max_temp}`
|
|
171068
171946
|
);
|
|
171069
171947
|
const minLimit = toMatterTemp2(attributes9.min_temp) ?? 0;
|
|
@@ -171303,6 +172181,7 @@ var matterDeviceTypeFactories = {
|
|
|
171303
172181
|
// Legacy SolarPower alias, kept for existing mappings.
|
|
171304
172182
|
electrical_sensor: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
|
|
171305
172183
|
solar_power: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
|
|
172184
|
+
evse: (ha) => energyEvseType().set({ homeAssistantEntity: ha }),
|
|
171306
172185
|
contact_sensor: (ha) => ContactSensorType.set({ homeAssistantEntity: ha }),
|
|
171307
172186
|
motion_sensor: (ha) => MotionSensorType.set({ homeAssistantEntity: ha }),
|
|
171308
172187
|
occupancy_sensor: (ha) => OccupancySensorType.set({ homeAssistantEntity: ha }),
|
|
@@ -171318,7 +172197,7 @@ var matterDeviceTypeFactories = {
|
|
|
171318
172197
|
};
|
|
171319
172198
|
|
|
171320
172199
|
// src/matter/endpoints/composed/user-composed-endpoint.ts
|
|
171321
|
-
var
|
|
172200
|
+
var logger242 = Logger.get("UserComposedEndpoint");
|
|
171322
172201
|
function stripBasicInformation(type) {
|
|
171323
172202
|
const behaviors = { ...type.behaviors };
|
|
171324
172203
|
delete behaviors.bridgedDeviceBasicInformation;
|
|
@@ -171328,11 +172207,11 @@ function createEndpointId5(entityId, customName) {
|
|
|
171328
172207
|
const baseName = customName || entityId;
|
|
171329
172208
|
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
171330
172209
|
}
|
|
171331
|
-
function buildEntityPayload4(
|
|
171332
|
-
const state =
|
|
172210
|
+
function buildEntityPayload4(registry3, entityId) {
|
|
172211
|
+
const state = registry3.initialStateIncludingUnfiltered(entityId);
|
|
171333
172212
|
if (!state) return void 0;
|
|
171334
|
-
const entity =
|
|
171335
|
-
const deviceRegistry =
|
|
172213
|
+
const entity = registry3.entityIncludingUnfiltered(entityId);
|
|
172214
|
+
const deviceRegistry = registry3.deviceOfIncludingUnfiltered(entityId);
|
|
171336
172215
|
return {
|
|
171337
172216
|
entity_id: entityId,
|
|
171338
172217
|
state,
|
|
@@ -171348,8 +172227,8 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171348
172227
|
lastMappedStates = /* @__PURE__ */ new Map();
|
|
171349
172228
|
debouncedUpdates = /* @__PURE__ */ new Map();
|
|
171350
172229
|
static async create(config11) {
|
|
171351
|
-
const { registry:
|
|
171352
|
-
const primaryPayload = buildEntityPayload4(
|
|
172230
|
+
const { registry: registry3, primaryEntityId, composedEntities } = config11;
|
|
172231
|
+
const primaryPayload = buildEntityPayload4(registry3, primaryEntityId);
|
|
171353
172232
|
if (!primaryPayload) return void 0;
|
|
171354
172233
|
let parentType = BridgedNodeEndpoint.with(
|
|
171355
172234
|
BasicInformationServer2,
|
|
@@ -171391,10 +172270,10 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171391
172270
|
primarySubPayload,
|
|
171392
172271
|
primaryMapping,
|
|
171393
172272
|
config11.areaName,
|
|
171394
|
-
{ vacuumOnOff:
|
|
172273
|
+
{ vacuumOnOff: registry3.isVacuumOnOffEnabled() }
|
|
171395
172274
|
);
|
|
171396
172275
|
if (!primaryType) {
|
|
171397
|
-
|
|
172276
|
+
logger242.warn(
|
|
171398
172277
|
`Cannot create endpoint type for primary entity ${primaryEntityId}`
|
|
171399
172278
|
);
|
|
171400
172279
|
return void 0;
|
|
@@ -171407,9 +172286,9 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171407
172286
|
for (let i = 0; i < composedEntities.length; i++) {
|
|
171408
172287
|
const sub = composedEntities[i];
|
|
171409
172288
|
if (!sub.entityId) continue;
|
|
171410
|
-
const subPayload = buildEntityPayload4(
|
|
172289
|
+
const subPayload = buildEntityPayload4(registry3, sub.entityId);
|
|
171411
172290
|
if (!subPayload) {
|
|
171412
|
-
|
|
172291
|
+
logger242.warn(
|
|
171413
172292
|
`Cannot find state for composed sub-entity ${sub.entityId}, it does not exist in Home Assistant (removed or renamed?)`
|
|
171414
172293
|
);
|
|
171415
172294
|
continue;
|
|
@@ -171424,7 +172303,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171424
172303
|
config11.areaName
|
|
171425
172304
|
);
|
|
171426
172305
|
if (!subType) {
|
|
171427
|
-
|
|
172306
|
+
logger242.warn(
|
|
171428
172307
|
`Cannot create endpoint type for composed sub-entity ${sub.entityId}`
|
|
171429
172308
|
);
|
|
171430
172309
|
continue;
|
|
@@ -171437,7 +172316,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171437
172316
|
mappedIds.push(sub.entityId);
|
|
171438
172317
|
}
|
|
171439
172318
|
if (parts.length < 2) {
|
|
171440
|
-
|
|
172319
|
+
logger242.warn(
|
|
171441
172320
|
`User composed device ${primaryEntityId}: only ${parts.length} sub-endpoint(s), need at least 2 (primary + one sub-entity). Falling back to standalone.`
|
|
171442
172321
|
);
|
|
171443
172322
|
return void 0;
|
|
@@ -171461,7 +172340,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
|
|
|
171461
172340
|
const labels = parts.map(
|
|
171462
172341
|
(_, i) => i === 0 ? primaryEntityId.split(".")[0] : composedEntities[i - 1]?.entityId?.split(".")[0] ?? "?"
|
|
171463
172342
|
).join("+");
|
|
171464
|
-
|
|
172343
|
+
logger242.info(
|
|
171465
172344
|
`Created user composed device ${primaryEntityId}: ${parts.length} sub-endpoint(s) [${labels}]`
|
|
171466
172345
|
);
|
|
171467
172346
|
return endpoint;
|
|
@@ -171567,45 +172446,45 @@ function asStandaloneEndpointType(type) {
|
|
|
171567
172446
|
}
|
|
171568
172447
|
|
|
171569
172448
|
// src/matter/endpoints/legacy/legacy-endpoint.ts
|
|
171570
|
-
var
|
|
172449
|
+
var logger243 = Logger.get("LegacyEndpoint");
|
|
171571
172450
|
var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
171572
|
-
static async create(
|
|
171573
|
-
const deviceRegistry =
|
|
171574
|
-
let state =
|
|
171575
|
-
const entity =
|
|
172451
|
+
static async create(registry3, entityId, mapping, pluginDomainMappings, standalone = false, endpointId, identityAnchor) {
|
|
172452
|
+
const deviceRegistry = registry3.deviceOf(entityId);
|
|
172453
|
+
let state = registry3.initialState(entityId);
|
|
172454
|
+
const entity = registry3.entity(entityId);
|
|
171576
172455
|
if (!state) {
|
|
171577
172456
|
return;
|
|
171578
172457
|
}
|
|
171579
|
-
if (
|
|
171580
|
-
|
|
172458
|
+
if (registry3.isAutoBatteryMappingEnabled() && registry3.isBatteryEntityUsed(entityId)) {
|
|
172459
|
+
logger243.debug(
|
|
171581
172460
|
`Skipping ${entityId} - already auto-assigned as battery to another device`
|
|
171582
172461
|
);
|
|
171583
172462
|
return;
|
|
171584
172463
|
}
|
|
171585
|
-
if (
|
|
171586
|
-
|
|
172464
|
+
if (registry3.isAutoHumidityMappingEnabled() && registry3.isHumidityEntityUsed(entityId)) {
|
|
172465
|
+
logger243.debug(
|
|
171587
172466
|
`Skipping ${entityId} - already auto-assigned as humidity to a temperature sensor`
|
|
171588
172467
|
);
|
|
171589
172468
|
return;
|
|
171590
172469
|
}
|
|
171591
|
-
if (
|
|
171592
|
-
|
|
172470
|
+
if (registry3.isAutoPressureMappingEnabled() && registry3.isPressureEntityUsed(entityId)) {
|
|
172471
|
+
logger243.debug(
|
|
171593
172472
|
`Skipping ${entityId} - already auto-assigned as pressure to a temperature sensor`
|
|
171594
172473
|
);
|
|
171595
172474
|
return;
|
|
171596
172475
|
}
|
|
171597
|
-
if (
|
|
171598
|
-
|
|
172476
|
+
if (registry3.isAutoComposedDevicesEnabled() && registry3.isComposedSubEntityUsed(entityId)) {
|
|
172477
|
+
logger243.debug(
|
|
171599
172478
|
`Skipping ${entityId} - already consumed by a composed device`
|
|
171600
172479
|
);
|
|
171601
172480
|
return;
|
|
171602
172481
|
}
|
|
171603
172482
|
let effectiveMapping = mapping;
|
|
171604
172483
|
if (entity.device_id) {
|
|
171605
|
-
if (
|
|
172484
|
+
if (registry3.isAutoHumidityMappingEnabled()) {
|
|
171606
172485
|
const attrs = state.attributes;
|
|
171607
172486
|
if (!mapping?.humidityEntity && entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature) {
|
|
171608
|
-
const humidityEntityId =
|
|
172487
|
+
const humidityEntityId = registry3.findHumidityEntityForDevice(
|
|
171609
172488
|
entity.device_id
|
|
171610
172489
|
);
|
|
171611
172490
|
if (humidityEntityId && humidityEntityId !== entityId) {
|
|
@@ -171614,17 +172493,17 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171614
172493
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171615
172494
|
humidityEntity: humidityEntityId
|
|
171616
172495
|
};
|
|
171617
|
-
|
|
171618
|
-
|
|
172496
|
+
registry3.markHumidityEntityUsed(humidityEntityId);
|
|
172497
|
+
logger243.debug(
|
|
171619
172498
|
`Auto-assigned humidity ${humidityEntityId} to ${entityId}`
|
|
171620
172499
|
);
|
|
171621
172500
|
}
|
|
171622
172501
|
}
|
|
171623
172502
|
}
|
|
171624
|
-
if (
|
|
172503
|
+
if (registry3.isAutoPressureMappingEnabled()) {
|
|
171625
172504
|
const attrs = state.attributes;
|
|
171626
172505
|
if (!mapping?.pressureEntity && entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature) {
|
|
171627
|
-
const pressureEntityId =
|
|
172506
|
+
const pressureEntityId = registry3.findPressureEntityForDevice(
|
|
171628
172507
|
entity.device_id
|
|
171629
172508
|
);
|
|
171630
172509
|
if (pressureEntityId && pressureEntityId !== entityId) {
|
|
@@ -171633,16 +172512,16 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171633
172512
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171634
172513
|
pressureEntity: pressureEntityId
|
|
171635
172514
|
};
|
|
171636
|
-
|
|
171637
|
-
|
|
172515
|
+
registry3.markPressureEntityUsed(pressureEntityId);
|
|
172516
|
+
logger243.debug(
|
|
171638
172517
|
`Auto-assigned pressure ${pressureEntityId} to ${entityId}`
|
|
171639
172518
|
);
|
|
171640
172519
|
}
|
|
171641
172520
|
}
|
|
171642
172521
|
}
|
|
171643
172522
|
const isVacuum = entityId.startsWith("vacuum.");
|
|
171644
|
-
if ((
|
|
171645
|
-
const batteryEntityId =
|
|
172523
|
+
if ((registry3.isAutoBatteryMappingEnabled() || isVacuum) && !mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
|
|
172524
|
+
const batteryEntityId = registry3.findBatteryEntityForDevice(
|
|
171646
172525
|
entity.device_id
|
|
171647
172526
|
);
|
|
171648
172527
|
if (batteryEntityId && batteryEntityId !== entityId) {
|
|
@@ -171651,16 +172530,16 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171651
172530
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171652
172531
|
batteryEntity: batteryEntityId
|
|
171653
172532
|
};
|
|
171654
|
-
|
|
171655
|
-
|
|
172533
|
+
registry3.markBatteryEntityUsed(batteryEntityId);
|
|
172534
|
+
logger243.debug(
|
|
171656
172535
|
`Auto-assigned battery ${batteryEntityId} to ${entityId}`
|
|
171657
172536
|
);
|
|
171658
172537
|
}
|
|
171659
172538
|
}
|
|
171660
172539
|
const alarmDeviceClass = state.attributes.device_class;
|
|
171661
172540
|
const isSmokeCoAlarm = mapping?.matterDeviceType === "smoke_co_alarm" || entityId.startsWith("binary_sensor.") && (alarmDeviceClass === "smoke" || alarmDeviceClass === "carbon_monoxide" || alarmDeviceClass === "gas");
|
|
171662
|
-
if (
|
|
171663
|
-
const faultEntityId =
|
|
172541
|
+
if (registry3.isAutoBatteryMappingEnabled() && !mapping?.faultEntity && isSmokeCoAlarm) {
|
|
172542
|
+
const faultEntityId = registry3.findProblemEntityForDevice(
|
|
171664
172543
|
entity.device_id
|
|
171665
172544
|
);
|
|
171666
172545
|
if (faultEntityId && faultEntityId !== entityId) {
|
|
@@ -171669,13 +172548,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171669
172548
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171670
172549
|
faultEntity: faultEntityId
|
|
171671
172550
|
};
|
|
171672
|
-
|
|
172551
|
+
logger243.debug(`Auto-assigned fault ${faultEntityId} to ${entityId}`);
|
|
171673
172552
|
}
|
|
171674
172553
|
}
|
|
171675
172554
|
if (!mapping?.powerEntity) {
|
|
171676
172555
|
const domain = entityId.split(".")[0];
|
|
171677
172556
|
if (domain === "switch") {
|
|
171678
|
-
const powerEntityId =
|
|
172557
|
+
const powerEntityId = registry3.findPowerEntityForDevice(
|
|
171679
172558
|
entity.device_id
|
|
171680
172559
|
);
|
|
171681
172560
|
if (powerEntityId && powerEntityId !== entityId) {
|
|
@@ -171684,15 +172563,15 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171684
172563
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171685
172564
|
powerEntity: powerEntityId
|
|
171686
172565
|
};
|
|
171687
|
-
|
|
171688
|
-
|
|
172566
|
+
registry3.markPowerEntityUsed(powerEntityId);
|
|
172567
|
+
logger243.debug(`Auto-assigned power ${powerEntityId} to ${entityId}`);
|
|
171689
172568
|
}
|
|
171690
172569
|
}
|
|
171691
172570
|
}
|
|
171692
172571
|
if (!mapping?.energyEntity) {
|
|
171693
172572
|
const domain = entityId.split(".")[0];
|
|
171694
172573
|
if (domain === "switch") {
|
|
171695
|
-
const energyEntityId =
|
|
172574
|
+
const energyEntityId = registry3.findEnergyEntityForDevice(
|
|
171696
172575
|
entity.device_id
|
|
171697
172576
|
);
|
|
171698
172577
|
if (energyEntityId && energyEntityId !== entityId) {
|
|
@@ -171701,15 +172580,15 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171701
172580
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171702
172581
|
energyEntity: energyEntityId
|
|
171703
172582
|
};
|
|
171704
|
-
|
|
171705
|
-
|
|
172583
|
+
registry3.markEnergyEntityUsed(energyEntityId);
|
|
172584
|
+
logger243.debug(
|
|
171706
172585
|
`Auto-assigned energy ${energyEntityId} to ${entityId}`
|
|
171707
172586
|
);
|
|
171708
172587
|
}
|
|
171709
172588
|
}
|
|
171710
172589
|
}
|
|
171711
172590
|
if (entityId.startsWith("vacuum.")) {
|
|
171712
|
-
const vacuumEntities =
|
|
172591
|
+
const vacuumEntities = registry3.findVacuumSelectEntities(
|
|
171713
172592
|
entity.device_id
|
|
171714
172593
|
);
|
|
171715
172594
|
if (!effectiveMapping?.cleaningModeEntity && vacuumEntities.cleaningModeEntity) {
|
|
@@ -171718,7 +172597,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171718
172597
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171719
172598
|
cleaningModeEntity: vacuumEntities.cleaningModeEntity
|
|
171720
172599
|
};
|
|
171721
|
-
|
|
172600
|
+
logger243.info(
|
|
171722
172601
|
`Auto-assigned cleaningMode ${vacuumEntities.cleaningModeEntity} to ${entityId}`
|
|
171723
172602
|
);
|
|
171724
172603
|
}
|
|
@@ -171728,7 +172607,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171728
172607
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171729
172608
|
suctionLevelEntity: vacuumEntities.suctionLevelEntity
|
|
171730
172609
|
};
|
|
171731
|
-
|
|
172610
|
+
logger243.info(
|
|
171732
172611
|
`Auto-assigned suctionLevel ${vacuumEntities.suctionLevelEntity} to ${entityId}`
|
|
171733
172612
|
);
|
|
171734
172613
|
}
|
|
@@ -171738,7 +172617,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171738
172617
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171739
172618
|
mopIntensityEntity: vacuumEntities.mopIntensityEntity
|
|
171740
172619
|
};
|
|
171741
|
-
|
|
172620
|
+
logger243.info(
|
|
171742
172621
|
`Auto-assigned mopIntensity ${vacuumEntities.mopIntensityEntity} to ${entityId}`
|
|
171743
172622
|
);
|
|
171744
172623
|
}
|
|
@@ -171748,12 +172627,12 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171748
172627
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171749
172628
|
currentRoomEntity: vacuumEntities.currentRoomEntity
|
|
171750
172629
|
};
|
|
171751
|
-
|
|
172630
|
+
logger243.info(
|
|
171752
172631
|
`Auto-assigned currentRoom ${vacuumEntities.currentRoomEntity} to ${entityId}`
|
|
171753
172632
|
);
|
|
171754
172633
|
}
|
|
171755
172634
|
const supportedFeatures = state.attributes.supported_features ?? 0;
|
|
171756
|
-
const cleanAreaRooms = await
|
|
172635
|
+
const cleanAreaRooms = await registry3.resolveCleanAreaRooms(
|
|
171757
172636
|
entityId,
|
|
171758
172637
|
supportedFeatures
|
|
171759
172638
|
);
|
|
@@ -171763,13 +172642,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171763
172642
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
171764
172643
|
cleanAreaRooms
|
|
171765
172644
|
};
|
|
171766
|
-
|
|
172645
|
+
logger243.info(
|
|
171767
172646
|
`Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA for ${entityId}`
|
|
171768
172647
|
);
|
|
171769
172648
|
}
|
|
171770
172649
|
const vacAttrs = state.attributes;
|
|
171771
172650
|
if (cleanAreaRooms.length === 0 && !vacAttrs.rooms && !vacAttrs.segments && !vacAttrs.room_mapping) {
|
|
171772
|
-
const valetudoRooms =
|
|
172651
|
+
const valetudoRooms = registry3.findValetudoMapSegments(
|
|
171773
172652
|
entity.device_id
|
|
171774
172653
|
);
|
|
171775
172654
|
if (valetudoRooms.length > 0) {
|
|
@@ -171784,11 +172663,11 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171784
172663
|
rooms: roomsObj
|
|
171785
172664
|
}
|
|
171786
172665
|
};
|
|
171787
|
-
|
|
172666
|
+
logger243.debug(
|
|
171788
172667
|
`Auto-detected ${valetudoRooms.length} Valetudo segments for ${entityId}`
|
|
171789
172668
|
);
|
|
171790
172669
|
} else {
|
|
171791
|
-
const roborockRooms = await
|
|
172670
|
+
const roborockRooms = await registry3.resolveRoborockRooms(entityId);
|
|
171792
172671
|
if (roborockRooms.length > 0) {
|
|
171793
172672
|
const roomsObj = {};
|
|
171794
172673
|
for (const r of roborockRooms) {
|
|
@@ -171801,7 +172680,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171801
172680
|
rooms: roomsObj
|
|
171802
172681
|
}
|
|
171803
172682
|
};
|
|
171804
|
-
|
|
172683
|
+
logger243.debug(
|
|
171805
172684
|
`Auto-detected ${roborockRooms.length} Roborock rooms for ${entityId}`
|
|
171806
172685
|
);
|
|
171807
172686
|
}
|
|
@@ -171810,14 +172689,14 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171810
172689
|
}
|
|
171811
172690
|
}
|
|
171812
172691
|
if (standalone && ((effectiveMapping?.composedEntities?.length ?? 0) > 0 || effectiveMapping?.climateExposeFan === true)) {
|
|
171813
|
-
|
|
172692
|
+
logger243.warn(
|
|
171814
172693
|
`Composed mappings are not supported in server mode, exposing ${entityId} as a flat standalone endpoint`
|
|
171815
172694
|
);
|
|
171816
172695
|
}
|
|
171817
|
-
if (!standalone &&
|
|
171818
|
-
const composedAreaName =
|
|
172696
|
+
if (!standalone && registry3.isAutoComposedDevicesEnabled() && effectiveMapping?.composedEntities && effectiveMapping.composedEntities.length > 0) {
|
|
172697
|
+
const composedAreaName = registry3.getAreaName(entityId);
|
|
171819
172698
|
const composed = await UserComposedEndpoint.create({
|
|
171820
|
-
registry:
|
|
172699
|
+
registry: registry3,
|
|
171821
172700
|
primaryEntityId: entityId,
|
|
171822
172701
|
mapping: effectiveMapping,
|
|
171823
172702
|
composedEntities: effectiveMapping.composedEntities,
|
|
@@ -171829,16 +172708,16 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171829
172708
|
if (composed) {
|
|
171830
172709
|
return composed;
|
|
171831
172710
|
}
|
|
171832
|
-
|
|
172711
|
+
logger243.warn(
|
|
171833
172712
|
`User composed device creation failed for ${entityId}, falling back to standalone`
|
|
171834
172713
|
);
|
|
171835
172714
|
}
|
|
171836
|
-
if (!standalone &&
|
|
172715
|
+
if (!standalone && registry3.isAutoComposedDevicesEnabled()) {
|
|
171837
172716
|
const attrs = state.attributes;
|
|
171838
172717
|
if (entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature && (effectiveMapping?.humidityEntity || effectiveMapping?.pressureEntity)) {
|
|
171839
|
-
const composedAreaName =
|
|
172718
|
+
const composedAreaName = registry3.getAreaName(entityId);
|
|
171840
172719
|
const composed = await ComposedSensorEndpoint.create({
|
|
171841
|
-
registry:
|
|
172720
|
+
registry: registry3,
|
|
171842
172721
|
primaryEntityId: entityId,
|
|
171843
172722
|
humidityEntityId: effectiveMapping?.humidityEntity,
|
|
171844
172723
|
pressureEntityId: effectiveMapping?.pressureEntity,
|
|
@@ -171854,12 +172733,12 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171854
172733
|
}
|
|
171855
172734
|
const resolvedMatterType = mapping?.matterDeviceType ?? (entityId.startsWith("fan.") ? "fan" : void 0);
|
|
171856
172735
|
if (resolvedMatterType === "air_purifier") {
|
|
171857
|
-
const temperatureEntityId = effectiveMapping?.temperatureEntity || (entity.device_id ?
|
|
171858
|
-
const humidityEntityId = effectiveMapping?.humidityEntity || (entity.device_id ?
|
|
172736
|
+
const temperatureEntityId = effectiveMapping?.temperatureEntity || (entity.device_id ? registry3.findTemperatureEntityForDevice(entity.device_id) : void 0);
|
|
172737
|
+
const humidityEntityId = effectiveMapping?.humidityEntity || (entity.device_id ? registry3.findHumidityEntityForDevice(entity.device_id) : void 0);
|
|
171859
172738
|
if (temperatureEntityId || humidityEntityId) {
|
|
171860
|
-
const composedAreaName =
|
|
172739
|
+
const composedAreaName = registry3.getAreaName(entityId);
|
|
171861
172740
|
const composed = await ComposedAirPurifierEndpoint.create({
|
|
171862
|
-
registry:
|
|
172741
|
+
registry: registry3,
|
|
171863
172742
|
primaryEntityId: entityId,
|
|
171864
172743
|
temperatureEntityId,
|
|
171865
172744
|
humidityEntityId,
|
|
@@ -171881,9 +172760,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171881
172760
|
if (!standalone && entityId.startsWith("climate.") && effectiveMapping?.climateExposeFan === true) {
|
|
171882
172761
|
const climateFeatures = state.attributes.supported_features ?? 0;
|
|
171883
172762
|
if ((climateFeatures & ClimateDeviceFeature.FAN_MODE) !== 0) {
|
|
171884
|
-
const composedAreaName =
|
|
172763
|
+
const composedAreaName = registry3.getAreaName(entityId);
|
|
171885
172764
|
const composed = await ComposedClimateFanEndpoint.create({
|
|
171886
|
-
registry:
|
|
172765
|
+
registry: registry3,
|
|
171887
172766
|
primaryEntityId: entityId,
|
|
171888
172767
|
mapping: effectiveMapping,
|
|
171889
172768
|
customName: effectiveMapping?.customName,
|
|
@@ -171894,7 +172773,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171894
172773
|
if (composed) {
|
|
171895
172774
|
return composed;
|
|
171896
172775
|
}
|
|
171897
|
-
|
|
172776
|
+
logger243.warn(
|
|
171898
172777
|
`Companion fan creation failed for ${entityId}, falling back to standalone`
|
|
171899
172778
|
);
|
|
171900
172779
|
}
|
|
@@ -171908,7 +172787,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171908
172787
|
let cleaningModeOptions;
|
|
171909
172788
|
if (entityId.startsWith("vacuum.")) {
|
|
171910
172789
|
if (effectiveMapping?.cleaningModeEntity) {
|
|
171911
|
-
const cmState =
|
|
172790
|
+
const cmState = registry3.initialState(
|
|
171912
172791
|
effectiveMapping.cleaningModeEntity
|
|
171913
172792
|
);
|
|
171914
172793
|
cleaningModeOptions = cmState?.attributes?.options;
|
|
@@ -171922,13 +172801,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171922
172801
|
];
|
|
171923
172802
|
}
|
|
171924
172803
|
}
|
|
171925
|
-
const areaName =
|
|
172804
|
+
const areaName = registry3.getAreaName(entityId);
|
|
171926
172805
|
let type = createLegacyEndpointType(
|
|
171927
172806
|
payload,
|
|
171928
172807
|
effectiveMapping,
|
|
171929
172808
|
areaName,
|
|
171930
172809
|
{
|
|
171931
|
-
vacuumOnOff:
|
|
172810
|
+
vacuumOnOff: registry3.isVacuumOnOffEnabled(),
|
|
171932
172811
|
cleaningModeOptions,
|
|
171933
172812
|
pluginDomainMappings
|
|
171934
172813
|
},
|
|
@@ -171974,11 +172853,11 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
171974
172853
|
}
|
|
171975
172854
|
if (mappedChanged) {
|
|
171976
172855
|
this.pendingMappedChange = true;
|
|
171977
|
-
|
|
172856
|
+
logger243.debug(
|
|
171978
172857
|
`Mapped entity change detected for ${this.entityId}, forcing update`
|
|
171979
172858
|
);
|
|
171980
172859
|
}
|
|
171981
|
-
|
|
172860
|
+
logger243.debug(
|
|
171982
172861
|
`State update received for ${this.entityId}: state=${state.state}`
|
|
171983
172862
|
);
|
|
171984
172863
|
this.lastState = state;
|
|
@@ -172107,18 +172986,186 @@ var CameraAvStreamServer = class extends Base4 {
|
|
|
172107
172986
|
})(CameraAvStreamServer || (CameraAvStreamServer = {}));
|
|
172108
172987
|
|
|
172109
172988
|
// src/plugins/builtin/camera/webrtc-provider-server.ts
|
|
172989
|
+
init_esm();
|
|
172110
172990
|
init_esm7();
|
|
172111
172991
|
init_esm3();
|
|
172992
|
+
|
|
172993
|
+
// src/plugins/builtin/camera/requestor-client.ts
|
|
172994
|
+
init_esm();
|
|
172995
|
+
init_esm4();
|
|
172996
|
+
var logger244 = Logger.get("CameraWebRtcRequestor");
|
|
172997
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
172998
|
+
var pendingDeliveries = /* @__PURE__ */ new Map();
|
|
172999
|
+
var invokeTransport = defaultInvoke;
|
|
173000
|
+
function registerRequestor(sessionId, registration) {
|
|
173001
|
+
registry2.set(sessionId, registration);
|
|
173002
|
+
}
|
|
173003
|
+
function unregisterRequestor(sessionId) {
|
|
173004
|
+
registry2.delete(sessionId);
|
|
173005
|
+
const timer = pendingDeliveries.get(sessionId);
|
|
173006
|
+
if (timer) {
|
|
173007
|
+
clearTimeout(timer);
|
|
173008
|
+
pendingDeliveries.delete(sessionId);
|
|
173009
|
+
}
|
|
173010
|
+
}
|
|
173011
|
+
function unregisterAllRequestors() {
|
|
173012
|
+
for (const id of [...registry2.keys()]) unregisterRequestor(id);
|
|
173013
|
+
for (const timer of pendingDeliveries.values()) clearTimeout(timer);
|
|
173014
|
+
pendingDeliveries.clear();
|
|
173015
|
+
}
|
|
173016
|
+
function deliverAnswerDeferred(sessionId, sdp, onGiveUp) {
|
|
173017
|
+
const prior = pendingDeliveries.get(sessionId);
|
|
173018
|
+
if (prior) clearTimeout(prior);
|
|
173019
|
+
const owner = registry2.get(sessionId);
|
|
173020
|
+
const timer = setTimeout(() => {
|
|
173021
|
+
void (async () => {
|
|
173022
|
+
try {
|
|
173023
|
+
for (const delayMs of [0, 250, 500]) {
|
|
173024
|
+
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
173025
|
+
if (registry2.get(sessionId) !== owner || pendingDeliveries.get(sessionId) !== timer) {
|
|
173026
|
+
return;
|
|
173027
|
+
}
|
|
173028
|
+
if (await sendAnswer(sessionId, sdp)) {
|
|
173029
|
+
logger244.info(`answer delivered for session ${sessionId}`);
|
|
173030
|
+
return;
|
|
173031
|
+
}
|
|
173032
|
+
}
|
|
173033
|
+
if (registry2.get(sessionId) !== owner || pendingDeliveries.get(sessionId) !== timer) {
|
|
173034
|
+
return;
|
|
173035
|
+
}
|
|
173036
|
+
logger244.info(
|
|
173037
|
+
`answer delivery failed for session ${sessionId}, giving up`
|
|
173038
|
+
);
|
|
173039
|
+
await onGiveUp();
|
|
173040
|
+
} catch (err) {
|
|
173041
|
+
logger244.info(
|
|
173042
|
+
`answer delivery for session ${sessionId} threw: ${errText(err)}`
|
|
173043
|
+
);
|
|
173044
|
+
} finally {
|
|
173045
|
+
if (pendingDeliveries.get(sessionId) === timer) {
|
|
173046
|
+
pendingDeliveries.delete(sessionId);
|
|
173047
|
+
}
|
|
173048
|
+
}
|
|
173049
|
+
})();
|
|
173050
|
+
}, 0);
|
|
173051
|
+
timer.unref?.();
|
|
173052
|
+
pendingDeliveries.set(sessionId, timer);
|
|
173053
|
+
}
|
|
173054
|
+
function hasRequestor(sessionId) {
|
|
173055
|
+
return registry2.has(sessionId);
|
|
173056
|
+
}
|
|
173057
|
+
function sendAnswer(sessionId, sdp) {
|
|
173058
|
+
return invokeRequestor(sessionId, "answer", {
|
|
173059
|
+
webRtcSessionId: sessionId,
|
|
173060
|
+
sdp
|
|
173061
|
+
});
|
|
173062
|
+
}
|
|
173063
|
+
async function invokeRequestor(sessionId, command, fields) {
|
|
173064
|
+
const registration = registry2.get(sessionId);
|
|
173065
|
+
if (!registration) {
|
|
173066
|
+
logger244.info(`requestor ${command}: no session ${sessionId} registered`);
|
|
173067
|
+
return false;
|
|
173068
|
+
}
|
|
173069
|
+
if (registration.session.isClosed) {
|
|
173070
|
+
logger244.info(`requestor ${command}: session ${sessionId} already closed`);
|
|
173071
|
+
return false;
|
|
173072
|
+
}
|
|
173073
|
+
const request = {
|
|
173074
|
+
endpoint: registration.requestorEndpoint,
|
|
173075
|
+
cluster: WebRtcTransportRequestor3.Cluster,
|
|
173076
|
+
command,
|
|
173077
|
+
fields
|
|
173078
|
+
};
|
|
173079
|
+
try {
|
|
173080
|
+
return await invokeTransport({ registration, request });
|
|
173081
|
+
} catch (err) {
|
|
173082
|
+
logger244.info(
|
|
173083
|
+
`requestor ${command} failed for session ${sessionId}: ${errText(err)}`
|
|
173084
|
+
);
|
|
173085
|
+
return false;
|
|
173086
|
+
}
|
|
173087
|
+
}
|
|
173088
|
+
async function defaultInvoke({
|
|
173089
|
+
registration,
|
|
173090
|
+
request
|
|
173091
|
+
}) {
|
|
173092
|
+
const { session, env } = registration;
|
|
173093
|
+
const exchangeManager = env.get(ExchangeManager);
|
|
173094
|
+
const exchangeProvider = new DedicatedChannelExchangeProvider(
|
|
173095
|
+
exchangeManager,
|
|
173096
|
+
session
|
|
173097
|
+
);
|
|
173098
|
+
const client = new ClientInteraction({ environment: env, exchangeProvider });
|
|
173099
|
+
try {
|
|
173100
|
+
const result = client.invoke(
|
|
173101
|
+
Invoke({
|
|
173102
|
+
commands: [
|
|
173103
|
+
{
|
|
173104
|
+
endpoint: request.endpoint,
|
|
173105
|
+
cluster: request.cluster,
|
|
173106
|
+
command: request.command,
|
|
173107
|
+
// biome-ignore lint/suspicious/noExplicitAny: fields typed per command by caller
|
|
173108
|
+
fields: request.fields
|
|
173109
|
+
}
|
|
173110
|
+
]
|
|
173111
|
+
})
|
|
173112
|
+
);
|
|
173113
|
+
for await (const chunk of result) {
|
|
173114
|
+
for (const entry of chunk) {
|
|
173115
|
+
if (entry.kind === "cmd-status") {
|
|
173116
|
+
if (entry.status !== Status2.Success) {
|
|
173117
|
+
logger244.info(
|
|
173118
|
+
`requestor ${request.command} status ${entry.status} for session ${String(request.fields.webRtcSessionId)}`
|
|
173119
|
+
);
|
|
173120
|
+
return false;
|
|
173121
|
+
}
|
|
173122
|
+
return true;
|
|
173123
|
+
}
|
|
173124
|
+
}
|
|
173125
|
+
}
|
|
173126
|
+
return true;
|
|
173127
|
+
} finally {
|
|
173128
|
+
await client.close().catch(() => {
|
|
173129
|
+
});
|
|
173130
|
+
}
|
|
173131
|
+
}
|
|
173132
|
+
function errText(err) {
|
|
173133
|
+
return err instanceof Error ? err.message : String(err);
|
|
173134
|
+
}
|
|
173135
|
+
|
|
173136
|
+
// src/plugins/builtin/camera/webrtc-provider-server.ts
|
|
173137
|
+
var logger245 = Logger.get("CameraWebRtc");
|
|
173138
|
+
var nextGlobalSessionId = 0;
|
|
173139
|
+
function mintSessionId() {
|
|
173140
|
+
for (let i = 0; i <= 65534; i++) {
|
|
173141
|
+
const id = nextGlobalSessionId;
|
|
173142
|
+
nextGlobalSessionId = nextGlobalSessionId >= 65534 ? 0 : nextGlobalSessionId + 1;
|
|
173143
|
+
if (!hasRequestor(id)) return id;
|
|
173144
|
+
}
|
|
173145
|
+
return nextGlobalSessionId;
|
|
173146
|
+
}
|
|
172112
173147
|
var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
|
|
172113
173148
|
solicitOffer(request) {
|
|
172114
|
-
const id =
|
|
173149
|
+
const id = mintSessionId();
|
|
172115
173150
|
this.trackSession(id, request.streamUsage, request.originatingEndpointId);
|
|
172116
|
-
|
|
172117
|
-
|
|
173151
|
+
logger245.info(
|
|
173152
|
+
`solicitOffer session=${id} (${this.state.entityId}), deferred offer`
|
|
173153
|
+
);
|
|
173154
|
+
void this.state.bridge.startSession(id, this.state.entityId, {
|
|
173155
|
+
iceServers: request.iceServers,
|
|
173156
|
+
iceTransportPolicy: request.iceTransportPolicy
|
|
173157
|
+
}).catch(
|
|
173158
|
+
(err) => logger245.info(
|
|
173159
|
+
`solicitOffer startSession failed for ${this.state.entityId}: ${errText2(err)}`
|
|
173160
|
+
)
|
|
173161
|
+
);
|
|
172118
173162
|
return { webRtcSessionId: id, deferredOffer: true };
|
|
172119
173163
|
}
|
|
172120
173164
|
async provideOffer(request) {
|
|
172121
|
-
const id = request.webRtcSessionId ??
|
|
173165
|
+
const id = request.webRtcSessionId ?? mintSessionId();
|
|
173166
|
+
logger245.info(
|
|
173167
|
+
`provideOffer entry: entityId=${this.state.entityId} session=${id} (sdp ${request.sdp.length} chars)`
|
|
173168
|
+
);
|
|
172122
173169
|
if (request.webRtcSessionId == null) {
|
|
172123
173170
|
this.trackSession(
|
|
172124
173171
|
id,
|
|
@@ -172126,37 +173173,96 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
|
|
|
172126
173173
|
request.originatingEndpointId ?? EndpointNumber(0)
|
|
172127
173174
|
);
|
|
172128
173175
|
}
|
|
172129
|
-
const
|
|
172130
|
-
|
|
172131
|
-
|
|
172132
|
-
|
|
173176
|
+
const requestorEndpoint = request.originatingEndpointId ?? EndpointNumber(0);
|
|
173177
|
+
const session = this.context.session;
|
|
173178
|
+
if (session) {
|
|
173179
|
+
registerRequestor(id, {
|
|
173180
|
+
session,
|
|
173181
|
+
requestorEndpoint,
|
|
173182
|
+
env: this.env
|
|
173183
|
+
});
|
|
173184
|
+
}
|
|
173185
|
+
let answerSdp;
|
|
173186
|
+
try {
|
|
173187
|
+
answerSdp = await this.state.bridge.acceptControllerOffer(
|
|
173188
|
+
id,
|
|
173189
|
+
this.state.entityId,
|
|
173190
|
+
request.sdp,
|
|
173191
|
+
{
|
|
173192
|
+
iceServers: request.iceServers,
|
|
173193
|
+
iceTransportPolicy: request.iceTransportPolicy
|
|
173194
|
+
}
|
|
173195
|
+
);
|
|
173196
|
+
} catch (err) {
|
|
173197
|
+
const message = errText2(err);
|
|
173198
|
+
logger245.info(
|
|
173199
|
+
`provideOffer failed for ${this.state.entityId} session=${id}: ${message}`
|
|
173200
|
+
);
|
|
173201
|
+
unregisterRequestor(id);
|
|
173202
|
+
this.state.currentSessions = this.state.currentSessions.filter(
|
|
173203
|
+
(s) => s.id !== id
|
|
173204
|
+
);
|
|
173205
|
+
throw new StatusResponseError(
|
|
173206
|
+
`WebRTC offer failed: ${message}`,
|
|
173207
|
+
StatusCode.Failure
|
|
173208
|
+
);
|
|
173209
|
+
}
|
|
173210
|
+
logger245.info(
|
|
173211
|
+
`provideOffer answer computed for ${this.state.entityId} session=${id} (${answerSdp.length} chars); delivering via requestor`
|
|
172133
173212
|
);
|
|
172134
|
-
|
|
173213
|
+
const bridge = this.state.bridge;
|
|
173214
|
+
const state = this.state;
|
|
173215
|
+
deliverAnswerDeferred(id, answerSdp, async () => {
|
|
173216
|
+
await bridge.endSession(id).catch(() => {
|
|
173217
|
+
});
|
|
173218
|
+
unregisterRequestor(id);
|
|
173219
|
+
try {
|
|
173220
|
+
state.currentSessions = state.currentSessions.filter(
|
|
173221
|
+
(s) => s.id !== id
|
|
173222
|
+
);
|
|
173223
|
+
} catch {
|
|
173224
|
+
}
|
|
173225
|
+
});
|
|
172135
173226
|
return { webRtcSessionId: id };
|
|
172136
173227
|
}
|
|
172137
173228
|
provideAnswer(request) {
|
|
173229
|
+
logger245.info(
|
|
173230
|
+
`provideAnswer session=${request.webRtcSessionId} (sdp ${request.sdp.length} chars, ${this.state.entityId})`
|
|
173231
|
+
);
|
|
172138
173232
|
return this.state.bridge.acceptControllerAnswer(
|
|
172139
173233
|
request.webRtcSessionId,
|
|
172140
173234
|
request.sdp
|
|
172141
173235
|
);
|
|
172142
173236
|
}
|
|
172143
173237
|
async provideIceCandidates(request) {
|
|
173238
|
+
logger245.info(
|
|
173239
|
+
`provideIceCandidates session=${request.webRtcSessionId}: ${request.iceCandidates.length} candidate(s) (${this.state.entityId})`
|
|
173240
|
+
);
|
|
172144
173241
|
for (const c of request.iceCandidates) {
|
|
172145
173242
|
await this.state.bridge.addControllerIceCandidate(
|
|
172146
173243
|
request.webRtcSessionId,
|
|
172147
173244
|
c.candidate,
|
|
172148
|
-
c.sdpMid
|
|
173245
|
+
c.sdpMid,
|
|
173246
|
+
c.sdpmLineIndex
|
|
172149
173247
|
);
|
|
172150
173248
|
}
|
|
172151
173249
|
}
|
|
172152
173250
|
async endSession(request) {
|
|
173251
|
+
logger245.info(
|
|
173252
|
+
`endSession session=${request.webRtcSessionId} (${this.state.entityId})`
|
|
173253
|
+
);
|
|
172153
173254
|
await this.state.bridge.endSession(request.webRtcSessionId);
|
|
173255
|
+
unregisterRequestor(request.webRtcSessionId);
|
|
172154
173256
|
this.state.currentSessions = this.state.currentSessions.filter(
|
|
172155
173257
|
(s) => s.id !== request.webRtcSessionId
|
|
172156
173258
|
);
|
|
172157
173259
|
}
|
|
172158
173260
|
trackSession(id, streamUsage, peerEndpointId) {
|
|
172159
173261
|
const session = this.context.session;
|
|
173262
|
+
const fabricIndex = session?.associatedFabric?.fabricIndex;
|
|
173263
|
+
if (fabricIndex == null) {
|
|
173264
|
+
return;
|
|
173265
|
+
}
|
|
172160
173266
|
this.state.currentSessions = [
|
|
172161
173267
|
...this.state.currentSessions,
|
|
172162
173268
|
{
|
|
@@ -172165,16 +173271,18 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
|
|
|
172165
173271
|
peerEndpointId,
|
|
172166
173272
|
streamUsage,
|
|
172167
173273
|
metadataEnabled: false,
|
|
172168
|
-
fabricIndex
|
|
173274
|
+
fabricIndex
|
|
172169
173275
|
}
|
|
172170
173276
|
];
|
|
172171
173277
|
}
|
|
172172
173278
|
};
|
|
173279
|
+
function errText2(err) {
|
|
173280
|
+
return err instanceof Error ? err.message : String(err);
|
|
173281
|
+
}
|
|
172173
173282
|
((CameraWebRtcProviderServer2) => {
|
|
172174
173283
|
class State extends WebRtcTransportProviderServer.State {
|
|
172175
173284
|
bridge;
|
|
172176
173285
|
entityId;
|
|
172177
|
-
nextSessionId = 1;
|
|
172178
173286
|
}
|
|
172179
173287
|
CameraWebRtcProviderServer2.State = State;
|
|
172180
173288
|
})(CameraWebRtcProviderServer || (CameraWebRtcProviderServer = {}));
|
|
@@ -172193,8 +173301,7 @@ function createCameraEndpointType(bridge, entityId, sensor = defaultSensorParams
|
|
|
172193
173301
|
webRtcTransportProvider: {
|
|
172194
173302
|
bridge,
|
|
172195
173303
|
entityId,
|
|
172196
|
-
currentSessions: []
|
|
172197
|
-
nextSessionId: 1
|
|
173304
|
+
currentSessions: []
|
|
172198
173305
|
},
|
|
172199
173306
|
cameraAvStreamManagement: {
|
|
172200
173307
|
bridge,
|
|
@@ -172256,128 +173363,232 @@ function createCameraEndpointType(bridge, entityId, sensor = defaultSensorParams
|
|
|
172256
173363
|
}
|
|
172257
173364
|
|
|
172258
173365
|
// src/plugins/builtin/camera/webrtc-bridge.ts
|
|
173366
|
+
init_esm();
|
|
172259
173367
|
import {
|
|
172260
173368
|
createConnection as createConnection3,
|
|
172261
173369
|
createLongLivedTokenAuth as createLongLivedTokenAuth2
|
|
172262
173370
|
} from "home-assistant-js-websocket";
|
|
172263
173371
|
import { RTCPeerConnection } from "werift";
|
|
173372
|
+
var logger246 = Logger.get("CameraWebRtc");
|
|
173373
|
+
var DEFAULT_HA_WEBRTC_TIMEOUT_MS = 15e3;
|
|
173374
|
+
var haWebRtcTimeoutMs = DEFAULT_HA_WEBRTC_TIMEOUT_MS;
|
|
172264
173375
|
var WebRtcBridge = class {
|
|
172265
|
-
constructor(config11) {
|
|
173376
|
+
constructor(config11, deps = {}) {
|
|
172266
173377
|
this.config = config11;
|
|
173378
|
+
this.deps = deps;
|
|
172267
173379
|
}
|
|
172268
173380
|
config;
|
|
173381
|
+
deps;
|
|
172269
173382
|
connection;
|
|
172270
173383
|
sessions = /* @__PURE__ */ new Map();
|
|
172271
173384
|
async ha() {
|
|
172272
173385
|
if (this.connection) return this.connection;
|
|
172273
|
-
|
|
172274
|
-
this.config
|
|
172275
|
-
|
|
172276
|
-
|
|
172277
|
-
|
|
173386
|
+
if (this.deps.connect) {
|
|
173387
|
+
this.connection = await this.deps.connect(this.config);
|
|
173388
|
+
} else {
|
|
173389
|
+
const auth = createLongLivedTokenAuth2(
|
|
173390
|
+
this.config.haUrl,
|
|
173391
|
+
this.config.haToken
|
|
173392
|
+
);
|
|
173393
|
+
this.connection = await createConnection3({ auth });
|
|
173394
|
+
}
|
|
172278
173395
|
return this.connection;
|
|
172279
173396
|
}
|
|
172280
173397
|
// We offer to the controller; it answers. Pulls HA media first, then forwards.
|
|
172281
|
-
async startSession(matterSessionId, entityId) {
|
|
173398
|
+
async startSession(matterSessionId, entityId, ice) {
|
|
172282
173399
|
const haPeer = new RTCPeerConnection();
|
|
172283
|
-
const controllerPeer = new RTCPeerConnection(
|
|
172284
|
-
|
|
172285
|
-
|
|
172286
|
-
|
|
172287
|
-
|
|
173400
|
+
const controllerPeer = new RTCPeerConnection(
|
|
173401
|
+
this.controllerConfig(entityId, ice)
|
|
173402
|
+
);
|
|
173403
|
+
this.wireStateLogging(haPeer, "haPeer", entityId);
|
|
173404
|
+
this.wireStateLogging(controllerPeer, "controllerPeer", entityId);
|
|
173405
|
+
const senders = /* @__PURE__ */ new Map();
|
|
173406
|
+
senders.set(
|
|
173407
|
+
"video",
|
|
173408
|
+
controllerPeer.addTransceiver("video", { direction: "sendonly" })
|
|
173409
|
+
);
|
|
173410
|
+
this.forwardHaTracks(haPeer, senders, entityId);
|
|
173411
|
+
await this.closeExistingPeers(matterSessionId);
|
|
173412
|
+
this.sessions.set(matterSessionId, { entityId, haPeer, controllerPeer });
|
|
173413
|
+
try {
|
|
173414
|
+
haPeer.addTransceiver("video", { direction: "recvonly" });
|
|
173415
|
+
haPeer.addTransceiver("audio", { direction: "recvonly" });
|
|
173416
|
+
const haOffer = await haPeer.createOffer();
|
|
173417
|
+
await haPeer.setLocalDescription(haOffer);
|
|
173418
|
+
const haOfferSdp = this.localSdp(haPeer);
|
|
173419
|
+
logger246.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
|
|
173420
|
+
const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
|
|
173421
|
+
entityId,
|
|
173422
|
+
haOfferSdp,
|
|
173423
|
+
haPeer
|
|
172288
173424
|
);
|
|
172289
|
-
|
|
172290
|
-
|
|
172291
|
-
|
|
172292
|
-
|
|
172293
|
-
|
|
172294
|
-
|
|
172295
|
-
|
|
172296
|
-
|
|
172297
|
-
|
|
172298
|
-
|
|
172299
|
-
|
|
172300
|
-
|
|
172301
|
-
|
|
172302
|
-
|
|
172303
|
-
|
|
172304
|
-
|
|
172305
|
-
|
|
172306
|
-
|
|
172307
|
-
|
|
172308
|
-
|
|
172309
|
-
|
|
172310
|
-
|
|
172311
|
-
|
|
172312
|
-
|
|
173425
|
+
const session = this.sessions.get(matterSessionId);
|
|
173426
|
+
if (session) {
|
|
173427
|
+
session.haSessionId = sessionId;
|
|
173428
|
+
session.haUnsubscribe = unsubscribe;
|
|
173429
|
+
}
|
|
173430
|
+
await haPeer.setRemoteDescription({ type: "answer", sdp: answer });
|
|
173431
|
+
const iceCandidates = [];
|
|
173432
|
+
controllerPeer.onIceCandidate.subscribe((c) => {
|
|
173433
|
+
if (c?.candidate)
|
|
173434
|
+
iceCandidates.push({
|
|
173435
|
+
candidate: c.candidate,
|
|
173436
|
+
sdpMid: c.sdpMid ?? null
|
|
173437
|
+
});
|
|
173438
|
+
});
|
|
173439
|
+
const controllerOffer = await controllerPeer.createOffer();
|
|
173440
|
+
await controllerPeer.setLocalDescription(controllerOffer);
|
|
173441
|
+
const sdp = this.localSdp(controllerPeer);
|
|
173442
|
+
logger246.info(
|
|
173443
|
+
`controller offer created for ${entityId} (${sdp.length} chars)`
|
|
173444
|
+
);
|
|
173445
|
+
return { sdp, iceCandidates };
|
|
173446
|
+
} catch (err) {
|
|
173447
|
+
logger246.info(`startSession failed for ${entityId}: ${errText3(err)}`);
|
|
173448
|
+
await this.endSession(matterSessionId);
|
|
173449
|
+
throw err;
|
|
173450
|
+
}
|
|
172313
173451
|
}
|
|
172314
173452
|
// ProvideOffer flow: controller sent its offer. Pull HA media, answer it.
|
|
172315
173453
|
// Returning the answer to the controller is the unverified Requestor path.
|
|
172316
|
-
async acceptControllerOffer(matterSessionId, entityId, controllerOfferSdp) {
|
|
173454
|
+
async acceptControllerOffer(matterSessionId, entityId, controllerOfferSdp, ice) {
|
|
172317
173455
|
const haPeer = new RTCPeerConnection();
|
|
172318
|
-
const controllerPeer = new RTCPeerConnection(
|
|
172319
|
-
|
|
172320
|
-
|
|
172321
|
-
|
|
172322
|
-
|
|
172323
|
-
|
|
172324
|
-
|
|
173456
|
+
const controllerPeer = new RTCPeerConnection(
|
|
173457
|
+
this.controllerConfig(entityId, ice)
|
|
173458
|
+
);
|
|
173459
|
+
this.wireStateLogging(haPeer, "haPeer", entityId);
|
|
173460
|
+
this.wireStateLogging(controllerPeer, "controllerPeer", entityId);
|
|
173461
|
+
const senders = /* @__PURE__ */ new Map();
|
|
173462
|
+
for (const kind of offerKinds(controllerOfferSdp)) {
|
|
173463
|
+
senders.set(
|
|
173464
|
+
kind,
|
|
173465
|
+
controllerPeer.addTransceiver(kind, { direction: "sendonly" })
|
|
173466
|
+
);
|
|
173467
|
+
}
|
|
173468
|
+
this.forwardHaTracks(haPeer, senders, entityId);
|
|
173469
|
+
await this.closeExistingPeers(matterSessionId);
|
|
172325
173470
|
this.sessions.set(matterSessionId, { entityId, haPeer, controllerPeer });
|
|
172326
|
-
|
|
172327
|
-
|
|
172328
|
-
|
|
172329
|
-
|
|
172330
|
-
|
|
172331
|
-
|
|
172332
|
-
|
|
172333
|
-
|
|
172334
|
-
|
|
172335
|
-
|
|
172336
|
-
|
|
172337
|
-
|
|
172338
|
-
|
|
173471
|
+
try {
|
|
173472
|
+
haPeer.addTransceiver("video", { direction: "recvonly" });
|
|
173473
|
+
haPeer.addTransceiver("audio", { direction: "recvonly" });
|
|
173474
|
+
const haOffer = await haPeer.createOffer();
|
|
173475
|
+
await haPeer.setLocalDescription(haOffer);
|
|
173476
|
+
const haOfferSdp = this.localSdp(haPeer);
|
|
173477
|
+
logger246.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
|
|
173478
|
+
const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
|
|
173479
|
+
entityId,
|
|
173480
|
+
haOfferSdp,
|
|
173481
|
+
haPeer
|
|
173482
|
+
);
|
|
173483
|
+
const session = this.sessions.get(matterSessionId);
|
|
173484
|
+
if (session) {
|
|
173485
|
+
session.haSessionId = sessionId;
|
|
173486
|
+
session.haUnsubscribe = unsubscribe;
|
|
173487
|
+
}
|
|
173488
|
+
await haPeer.setRemoteDescription({ type: "answer", sdp: answer });
|
|
173489
|
+
await controllerPeer.setRemoteDescription({
|
|
173490
|
+
type: "offer",
|
|
173491
|
+
sdp: controllerOfferSdp
|
|
173492
|
+
});
|
|
173493
|
+
const controllerAnswer = await controllerPeer.createAnswer();
|
|
173494
|
+
await controllerPeer.setLocalDescription(controllerAnswer);
|
|
173495
|
+
const answerSdp = this.localSdp(controllerPeer);
|
|
173496
|
+
logger246.info(
|
|
173497
|
+
`controller answer created for ${entityId} (${answerSdp.length} chars)`
|
|
173498
|
+
);
|
|
173499
|
+
return answerSdp;
|
|
173500
|
+
} catch (err) {
|
|
173501
|
+
logger246.info(
|
|
173502
|
+
`acceptControllerOffer failed for ${entityId}: ${errText3(err)}`
|
|
173503
|
+
);
|
|
173504
|
+
await this.endSession(matterSessionId);
|
|
173505
|
+
throw err;
|
|
173506
|
+
}
|
|
172339
173507
|
}
|
|
172340
173508
|
/** Apply the Matter controller's SDP answer to the controller peer. */
|
|
172341
173509
|
async acceptControllerAnswer(matterSessionId, sdp) {
|
|
172342
173510
|
const session = this.sessions.get(matterSessionId);
|
|
172343
173511
|
if (!session) return;
|
|
173512
|
+
logger246.debug(`controller answer applied for ${session.entityId}`);
|
|
172344
173513
|
await session.controllerPeer.setRemoteDescription({ type: "answer", sdp });
|
|
172345
173514
|
}
|
|
172346
173515
|
/** Add a remote ICE candidate from the Matter controller. */
|
|
172347
|
-
async addControllerIceCandidate(matterSessionId, candidate, sdpMid) {
|
|
173516
|
+
async addControllerIceCandidate(matterSessionId, candidate, sdpMid, sdpMLineIndex) {
|
|
172348
173517
|
const session = this.sessions.get(matterSessionId);
|
|
172349
173518
|
if (!session) return;
|
|
173519
|
+
logger246.debug(
|
|
173520
|
+
`controller ICE candidate for ${session.entityId}: ${candidate}`
|
|
173521
|
+
);
|
|
172350
173522
|
await session.controllerPeer.addIceCandidate({
|
|
172351
173523
|
candidate,
|
|
172352
|
-
sdpMid: sdpMid ?? void 0
|
|
173524
|
+
sdpMid: sdpMid ?? void 0,
|
|
173525
|
+
sdpMLineIndex: sdpMLineIndex ?? void 0
|
|
173526
|
+
});
|
|
173527
|
+
}
|
|
173528
|
+
// Close the peers of a session we are about to overwrite. Leaves no map
|
|
173529
|
+
// entry; the caller replaces it with fresh peers under the same id.
|
|
173530
|
+
async closeExistingPeers(matterSessionId) {
|
|
173531
|
+
const prior = this.sessions.get(matterSessionId);
|
|
173532
|
+
if (!prior) return;
|
|
173533
|
+
logger246.info(
|
|
173534
|
+
`replacing session ${matterSessionId} (${prior.entityId}), closing prior peers`
|
|
173535
|
+
);
|
|
173536
|
+
if (prior.haUnsubscribe) {
|
|
173537
|
+
await Promise.resolve(prior.haUnsubscribe()).catch(
|
|
173538
|
+
(err) => logger246.debug(`HA unsubscribe failed: ${errText3(err)}`)
|
|
173539
|
+
);
|
|
173540
|
+
}
|
|
173541
|
+
await prior.haPeer.close().catch(() => {
|
|
173542
|
+
});
|
|
173543
|
+
await prior.controllerPeer.close().catch(() => {
|
|
173544
|
+
});
|
|
173545
|
+
await this.stopHaStream(prior).catch(() => {
|
|
172353
173546
|
});
|
|
172354
173547
|
}
|
|
172355
173548
|
async endSession(matterSessionId) {
|
|
172356
173549
|
const session = this.sessions.get(matterSessionId);
|
|
172357
173550
|
if (!session) return;
|
|
173551
|
+
logger246.info(`ending session ${matterSessionId} (${session.entityId})`);
|
|
172358
173552
|
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
|
-
}
|
|
173553
|
+
if (session.haUnsubscribe) {
|
|
173554
|
+
await Promise.resolve(session.haUnsubscribe()).catch(
|
|
173555
|
+
(err) => logger246.debug(`HA unsubscribe failed: ${errText3(err)}`)
|
|
173556
|
+
);
|
|
172372
173557
|
}
|
|
173558
|
+
await session.haPeer.close().catch(() => {
|
|
173559
|
+
});
|
|
173560
|
+
await session.controllerPeer.close().catch(() => {
|
|
173561
|
+
});
|
|
173562
|
+
await this.stopHaStream(session).catch(() => {
|
|
173563
|
+
});
|
|
173564
|
+
}
|
|
173565
|
+
// Tell HA to stop the stream if we tracked an HA session id. Best effort.
|
|
173566
|
+
async stopHaStream(session) {
|
|
173567
|
+
if (!session.haSessionId) return;
|
|
173568
|
+
const conn = await this.ha();
|
|
173569
|
+
await conn.sendMessagePromise({
|
|
173570
|
+
type: "camera/webrtc/candidate",
|
|
173571
|
+
entity_id: session.entityId,
|
|
173572
|
+
session_id: session.haSessionId,
|
|
173573
|
+
candidate: { candidate: "" }
|
|
173574
|
+
});
|
|
172373
173575
|
}
|
|
172374
173576
|
// Grab a still JPEG via HA's camera proxy (for CaptureSnapshot).
|
|
172375
173577
|
async snapshot(entityId) {
|
|
172376
173578
|
const url2 = `${this.config.haUrl}/api/camera_proxy/${entityId}`;
|
|
172377
|
-
|
|
172378
|
-
|
|
172379
|
-
|
|
173579
|
+
let res;
|
|
173580
|
+
try {
|
|
173581
|
+
res = await fetch(url2, {
|
|
173582
|
+
headers: { Authorization: `Bearer ${this.config.haToken}` }
|
|
173583
|
+
});
|
|
173584
|
+
} catch (err) {
|
|
173585
|
+
logger246.info(`snapshot fetch failed for ${entityId}: ${errText3(err)}`);
|
|
173586
|
+
throw err;
|
|
173587
|
+
}
|
|
172380
173588
|
if (!res.ok) {
|
|
173589
|
+
logger246.info(
|
|
173590
|
+
`snapshot fetch failed for ${entityId}: camera_proxy ${res.status}`
|
|
173591
|
+
);
|
|
172381
173592
|
throw new Error(`HA camera_proxy ${entityId}: ${res.status}`);
|
|
172382
173593
|
}
|
|
172383
173594
|
return new Uint8Array(await res.arrayBuffer());
|
|
@@ -172389,29 +173600,157 @@ var WebRtcBridge = class {
|
|
|
172389
173600
|
this.connection?.close();
|
|
172390
173601
|
this.connection = void 0;
|
|
172391
173602
|
}
|
|
172392
|
-
//
|
|
172393
|
-
|
|
173603
|
+
// Forward every track HA sends into the matching pre-added controller sender.
|
|
173604
|
+
forwardHaTracks(haPeer, senders, entityId) {
|
|
173605
|
+
haPeer.onTrack.subscribe((track) => {
|
|
173606
|
+
logger246.info(`onTrack ${track.kind} from HA (${entityId})`);
|
|
173607
|
+
const transceiver = senders.get(track.kind);
|
|
173608
|
+
if (!transceiver) {
|
|
173609
|
+
logger246.info(
|
|
173610
|
+
`no controller transceiver for ${track.kind} (${entityId})`
|
|
173611
|
+
);
|
|
173612
|
+
return;
|
|
173613
|
+
}
|
|
173614
|
+
let firstLogged = false;
|
|
173615
|
+
track.onReceiveRtp.subscribe((rtp) => {
|
|
173616
|
+
if (!firstLogged) {
|
|
173617
|
+
firstLogged = true;
|
|
173618
|
+
logger246.info(`first RTP forwarded for ${track.kind} (${entityId})`);
|
|
173619
|
+
}
|
|
173620
|
+
transceiver.sender.sendRtp(rtp);
|
|
173621
|
+
});
|
|
173622
|
+
});
|
|
173623
|
+
}
|
|
173624
|
+
// Log connection/ice state transitions (these events fire on change only).
|
|
173625
|
+
wireStateLogging(peer, label, entityId) {
|
|
173626
|
+
peer.connectionStateChange.subscribe(
|
|
173627
|
+
(s) => logger246.info(`${label} connectionState=${s} (${entityId})`)
|
|
173628
|
+
);
|
|
173629
|
+
peer.iceConnectionStateChange.subscribe(
|
|
173630
|
+
(s) => logger246.info(`${label} iceConnectionState=${s} (${entityId})`)
|
|
173631
|
+
);
|
|
173632
|
+
}
|
|
173633
|
+
// Map the Matter ICE struct onto werift's config. Only the controller peer
|
|
173634
|
+
// uses controller-supplied servers; the HA peer keeps werift defaults.
|
|
173635
|
+
controllerConfig(entityId, ice) {
|
|
173636
|
+
const iceServers = (ice?.iceServers ?? []).flatMap(
|
|
173637
|
+
(s) => (s.urLs ?? []).map((url2) => ({
|
|
173638
|
+
urls: url2,
|
|
173639
|
+
username: s.username,
|
|
173640
|
+
credential: s.credential
|
|
173641
|
+
}))
|
|
173642
|
+
);
|
|
173643
|
+
if (iceServers.length === 0) {
|
|
173644
|
+
logger246.info(`controller peer using default ICE servers (${entityId})`);
|
|
173645
|
+
return void 0;
|
|
173646
|
+
}
|
|
173647
|
+
const policy = ice?.iceTransportPolicy === "relay" || ice?.iceTransportPolicy === "all" ? ice.iceTransportPolicy : void 0;
|
|
173648
|
+
logger246.info(
|
|
173649
|
+
`controller peer using ${iceServers.length} controller ICE server(s)${policy ? ` policy=${policy}` : ""} (${entityId})`
|
|
173650
|
+
);
|
|
173651
|
+
return policy ? { iceServers, iceTransportPolicy: policy } : { iceServers };
|
|
173652
|
+
}
|
|
173653
|
+
localSdp(peer) {
|
|
173654
|
+
const local = peer.localDescription;
|
|
173655
|
+
if (!local) throw new Error("no local description");
|
|
173656
|
+
return local.sdp;
|
|
173657
|
+
}
|
|
173658
|
+
// Feed an HA trickle-ICE candidate into the HA peer.
|
|
173659
|
+
feedHaCandidate(entityId, haPeer, candidate) {
|
|
173660
|
+
const value = candidate?.candidate;
|
|
173661
|
+
if (!value) return;
|
|
173662
|
+
logger246.debug(`HA ICE candidate for ${entityId}: ${value}`);
|
|
173663
|
+
void haPeer.addIceCandidate({
|
|
173664
|
+
candidate: value,
|
|
173665
|
+
sdpMid: candidate?.sdpMid ?? void 0,
|
|
173666
|
+
sdpMLineIndex: candidate?.sdpMLineIndex ?? void 0
|
|
173667
|
+
}).catch(
|
|
173668
|
+
(e) => logger246.debug(
|
|
173669
|
+
`HA addIceCandidate failed for ${entityId}: ${errText3(e)}`
|
|
173670
|
+
)
|
|
173671
|
+
);
|
|
173672
|
+
}
|
|
173673
|
+
// Offer to HA's WebRTC and resolve on the first answer. Rejects on an error
|
|
173674
|
+
// reply or after a timeout; keeps the subscription open past the answer so HA
|
|
173675
|
+
// trickle candidates keep flowing into the HA peer until the session ends.
|
|
173676
|
+
async requestHaWebRtc(entityId, offerSdp, haPeer) {
|
|
172394
173677
|
const conn = await this.ha();
|
|
172395
|
-
|
|
173678
|
+
let unsubscribe = () => {
|
|
173679
|
+
};
|
|
173680
|
+
let sessionId;
|
|
173681
|
+
const answer = await new Promise((resolve11, reject) => {
|
|
172396
173682
|
let settled = false;
|
|
173683
|
+
let rejected = false;
|
|
173684
|
+
let timer;
|
|
173685
|
+
const settle = (fn) => {
|
|
173686
|
+
if (settled) return;
|
|
173687
|
+
settled = true;
|
|
173688
|
+
clearTimeout(timer);
|
|
173689
|
+
fn();
|
|
173690
|
+
};
|
|
173691
|
+
const fail = (err) => settle(() => {
|
|
173692
|
+
rejected = true;
|
|
173693
|
+
unsubscribe();
|
|
173694
|
+
reject(err);
|
|
173695
|
+
});
|
|
173696
|
+
timer = setTimeout(() => {
|
|
173697
|
+
logger246.info(
|
|
173698
|
+
`HA WebRTC timed out after ${haWebRtcTimeoutMs}ms for ${entityId}`
|
|
173699
|
+
);
|
|
173700
|
+
fail(
|
|
173701
|
+
new Error(`HA WebRTC request timed out after ${haWebRtcTimeoutMs}ms`)
|
|
173702
|
+
);
|
|
173703
|
+
}, haWebRtcTimeoutMs);
|
|
172397
173704
|
conn.subscribeMessage(
|
|
172398
173705
|
(msg) => {
|
|
172399
|
-
|
|
172400
|
-
|
|
172401
|
-
|
|
172402
|
-
|
|
172403
|
-
|
|
172404
|
-
|
|
172405
|
-
|
|
173706
|
+
switch (msg.type) {
|
|
173707
|
+
case "session":
|
|
173708
|
+
if (msg.session_id) sessionId = msg.session_id;
|
|
173709
|
+
break;
|
|
173710
|
+
case "answer":
|
|
173711
|
+
if (msg.answer) {
|
|
173712
|
+
if (msg.session_id) sessionId = msg.session_id;
|
|
173713
|
+
const ans = msg.answer;
|
|
173714
|
+
settle(() => {
|
|
173715
|
+
logger246.info(
|
|
173716
|
+
`HA answer received for ${entityId} (${ans.length} chars)`
|
|
173717
|
+
);
|
|
173718
|
+
resolve11(ans);
|
|
173719
|
+
});
|
|
173720
|
+
}
|
|
173721
|
+
break;
|
|
173722
|
+
case "error": {
|
|
173723
|
+
const detail = msg.message ?? msg.code ?? "unknown error";
|
|
173724
|
+
logger246.info(`HA WebRTC error for ${entityId}: ${detail}`);
|
|
173725
|
+
fail(new Error(`HA WebRTC error: ${detail}`));
|
|
173726
|
+
break;
|
|
172406
173727
|
}
|
|
172407
|
-
|
|
173728
|
+
case "candidate":
|
|
173729
|
+
this.feedHaCandidate(entityId, haPeer, msg.candidate);
|
|
173730
|
+
break;
|
|
172408
173731
|
}
|
|
172409
173732
|
},
|
|
172410
173733
|
{ type: "camera/webrtc/offer", entity_id: entityId, offer: offerSdp }
|
|
172411
|
-
).
|
|
173734
|
+
).then((unsub) => {
|
|
173735
|
+
unsubscribe = unsub;
|
|
173736
|
+
if (rejected) unsub();
|
|
173737
|
+
}).catch((err) => {
|
|
173738
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
173739
|
+
});
|
|
172412
173740
|
});
|
|
173741
|
+
return { answer, sessionId, unsubscribe };
|
|
172413
173742
|
}
|
|
172414
173743
|
};
|
|
173744
|
+
function offerKinds(sdp) {
|
|
173745
|
+
const kinds = [];
|
|
173746
|
+
for (const m of sdp.matchAll(/^m=(video|audio)/gm)) {
|
|
173747
|
+
kinds.push(m[1]);
|
|
173748
|
+
}
|
|
173749
|
+
return kinds;
|
|
173750
|
+
}
|
|
173751
|
+
function errText3(err) {
|
|
173752
|
+
return err instanceof Error ? err.message : String(err);
|
|
173753
|
+
}
|
|
172415
173754
|
|
|
172416
173755
|
// src/plugins/builtin/camera/camera-plugin.ts
|
|
172417
173756
|
var CONFIG_KEY = "config";
|
|
@@ -172544,6 +173883,7 @@ var CameraPlugin = class {
|
|
|
172544
173883
|
await this.bridge?.close().catch(() => {
|
|
172545
173884
|
});
|
|
172546
173885
|
this.bridge = void 0;
|
|
173886
|
+
unregisterAllRequestors();
|
|
172547
173887
|
}
|
|
172548
173888
|
};
|
|
172549
173889
|
|
|
@@ -172557,7 +173897,7 @@ import {
|
|
|
172557
173897
|
getCollection
|
|
172558
173898
|
} from "home-assistant-js-websocket";
|
|
172559
173899
|
import { atLeastHaVersion } from "home-assistant-js-websocket/dist/util.js";
|
|
172560
|
-
var
|
|
173900
|
+
var logger247 = Logger.get("SubscribeEntities");
|
|
172561
173901
|
function processEvent(store, updates) {
|
|
172562
173902
|
const state = { ...store.state };
|
|
172563
173903
|
if (updates.a) {
|
|
@@ -172583,7 +173923,7 @@ function processEvent(store, updates) {
|
|
|
172583
173923
|
for (const entityId in updates.c) {
|
|
172584
173924
|
let entityState = state[entityId];
|
|
172585
173925
|
if (!entityState) {
|
|
172586
|
-
|
|
173926
|
+
logger247.warn("Received state update for unknown entity", entityId);
|
|
172587
173927
|
continue;
|
|
172588
173928
|
}
|
|
172589
173929
|
entityState = { ...entityState };
|
|
@@ -172672,7 +174012,7 @@ var subscribeEntities = (conn, onChange, entityIds) => {
|
|
|
172672
174012
|
// src/services/bridges/entity-isolation-service.ts
|
|
172673
174013
|
init_esm();
|
|
172674
174014
|
init_diagnostic_event_bus();
|
|
172675
|
-
var
|
|
174015
|
+
var logger248 = Logger.get("EntityIsolation");
|
|
172676
174016
|
var EntityIsolationServiceImpl = class {
|
|
172677
174017
|
isolatedEntities = /* @__PURE__ */ new Map();
|
|
172678
174018
|
isolationCallbacks = /* @__PURE__ */ new Map();
|
|
@@ -172737,13 +174077,13 @@ var EntityIsolationServiceImpl = class {
|
|
|
172737
174077
|
}
|
|
172738
174078
|
const parsed = this.parseEndpointPath(msg);
|
|
172739
174079
|
if (!parsed) {
|
|
172740
|
-
|
|
174080
|
+
logger248.warn("Could not parse entity from error:", msg);
|
|
172741
174081
|
return false;
|
|
172742
174082
|
}
|
|
172743
174083
|
const { bridgeId, entityName } = parsed;
|
|
172744
174084
|
const callback = this.isolationCallbacks.get(bridgeId);
|
|
172745
174085
|
if (!callback) {
|
|
172746
|
-
|
|
174086
|
+
logger248.warn(
|
|
172747
174087
|
`No isolation callback registered for bridge ${bridgeId}, entity: ${entityName}`
|
|
172748
174088
|
);
|
|
172749
174089
|
return false;
|
|
@@ -172758,7 +174098,7 @@ var EntityIsolationServiceImpl = class {
|
|
|
172758
174098
|
reason,
|
|
172759
174099
|
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
172760
174100
|
});
|
|
172761
|
-
|
|
174101
|
+
logger248.warn(
|
|
172762
174102
|
`Isolating entity "${entityName}" from bridge ${bridgeId} due to: ${reason}`
|
|
172763
174103
|
);
|
|
172764
174104
|
diagnosticEventBus.emit("entity_error", `Entity isolated: ${entityName}`, {
|
|
@@ -172770,7 +174110,7 @@ var EntityIsolationServiceImpl = class {
|
|
|
172770
174110
|
await callback(entityName);
|
|
172771
174111
|
return true;
|
|
172772
174112
|
} catch (e) {
|
|
172773
|
-
|
|
174113
|
+
logger248.error(`Failed to isolate entity ${entityName}:`, e);
|
|
172774
174114
|
return false;
|
|
172775
174115
|
}
|
|
172776
174116
|
}
|
|
@@ -172799,128 +174139,16 @@ var EntityIsolationServiceImpl = class {
|
|
|
172799
174139
|
};
|
|
172800
174140
|
var EntityIsolationService = new EntityIsolationServiceImpl();
|
|
172801
174141
|
|
|
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
174142
|
// src/services/bridges/bridge-endpoint-manager.ts
|
|
172916
174143
|
var MAX_ENTITY_ID_LENGTH = 150;
|
|
172917
174144
|
var ENDPOINT_REMOVAL_GRACE_MS = 6e4;
|
|
172918
174145
|
var BridgeEndpointManager = class extends Service {
|
|
172919
|
-
constructor(client,
|
|
174146
|
+
constructor(client, registry3, mappingStorage, identityStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
|
|
172920
174147
|
super("BridgeEndpointManager");
|
|
172921
174148
|
this.client = client;
|
|
172922
|
-
this.registry =
|
|
174149
|
+
this.registry = registry3;
|
|
172923
174150
|
this.mappingStorage = mappingStorage;
|
|
174151
|
+
this.identityStorage = identityStorage;
|
|
172924
174152
|
this.bridgeId = bridgeId;
|
|
172925
174153
|
this.log = log;
|
|
172926
174154
|
this.pluginManager = pluginManager;
|
|
@@ -172942,6 +174170,7 @@ var BridgeEndpointManager = class extends Service {
|
|
|
172942
174170
|
client;
|
|
172943
174171
|
registry;
|
|
172944
174172
|
mappingStorage;
|
|
174173
|
+
identityStorage;
|
|
172945
174174
|
bridgeId;
|
|
172946
174175
|
log;
|
|
172947
174176
|
pluginManager;
|
|
@@ -173339,6 +174568,17 @@ var BridgeEndpointManager = class extends Service {
|
|
|
173339
174568
|
this.mappingFingerprints.set(entityId, fp);
|
|
173340
174569
|
}
|
|
173341
174570
|
}
|
|
174571
|
+
const fullEntities = this.registry.fullEntities;
|
|
174572
|
+
stampIdentityPresence(
|
|
174573
|
+
this.identityStorage,
|
|
174574
|
+
this.bridgeId,
|
|
174575
|
+
buildPresentIdentityKeys(fullEntities)
|
|
174576
|
+
);
|
|
174577
|
+
stampMappingPresence(
|
|
174578
|
+
this.mappingStorage,
|
|
174579
|
+
this.bridgeId,
|
|
174580
|
+
buildPresentEntityIds(fullEntities)
|
|
174581
|
+
);
|
|
173342
174582
|
const existingEndpoints = [];
|
|
173343
174583
|
const now = Date.now();
|
|
173344
174584
|
for (const endpoint of endpoints) {
|
|
@@ -173617,8 +174857,8 @@ init_send_ha_message();
|
|
|
173617
174857
|
import { callService as callService2 } from "home-assistant-js-websocket";
|
|
173618
174858
|
import { keys as keys2, pickBy, values as values3 } from "lodash-es";
|
|
173619
174859
|
var BridgeRegistry = class _BridgeRegistry {
|
|
173620
|
-
constructor(
|
|
173621
|
-
this.registry =
|
|
174860
|
+
constructor(registry3, dataProvider, client) {
|
|
174861
|
+
this.registry = registry3;
|
|
173622
174862
|
this.dataProvider = dataProvider;
|
|
173623
174863
|
this.client = client;
|
|
173624
174864
|
this.refresh();
|
|
@@ -173658,6 +174898,11 @@ var BridgeRegistry = class _BridgeRegistry {
|
|
|
173658
174898
|
initialState(entityId) {
|
|
173659
174899
|
return this._states[entityId];
|
|
173660
174900
|
}
|
|
174901
|
+
// The complete HA entity set (unfiltered). Used by orphan tombstone stamping
|
|
174902
|
+
// so a filter change or a scope narrowing never looks like a removal.
|
|
174903
|
+
get fullEntities() {
|
|
174904
|
+
return this.registry.entities;
|
|
174905
|
+
}
|
|
173661
174906
|
// composed sub-entities may sit outside the bridge filter (#408), so these
|
|
173662
174907
|
// fall back to the full HA registry. keep them separate from the strict
|
|
173663
174908
|
// accessors above, every other caller must stay filtered.
|
|
@@ -174375,6 +175620,7 @@ init_dist();
|
|
|
174375
175620
|
init_esm7();
|
|
174376
175621
|
import * as os10 from "node:os";
|
|
174377
175622
|
init_diagnostic_event_bus();
|
|
175623
|
+
var imWrapMarker2 = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
|
|
174378
175624
|
var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
|
|
174379
175625
|
var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2 = 2500;
|
|
174380
175626
|
var MDNS_ADDRESS_CHECK_INTERVAL_MS2 = 6e4;
|
|
@@ -174382,11 +175628,11 @@ function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOStrin
|
|
|
174382
175628
|
return { ...state, last_updated: now };
|
|
174383
175629
|
}
|
|
174384
175630
|
var ServerModeBridge = class {
|
|
174385
|
-
constructor(
|
|
175631
|
+
constructor(logger251, dataProvider, endpointManager, server) {
|
|
174386
175632
|
this.dataProvider = dataProvider;
|
|
174387
175633
|
this.endpointManager = endpointManager;
|
|
174388
175634
|
this.server = server;
|
|
174389
|
-
this.log =
|
|
175635
|
+
this.log = logger251.get(`ServerModeBridge / ${dataProvider.id}`);
|
|
174390
175636
|
}
|
|
174391
175637
|
dataProvider;
|
|
174392
175638
|
endpointManager;
|
|
@@ -174407,6 +175653,12 @@ var ServerModeBridge = class {
|
|
|
174407
175653
|
sessionStartedAt = /* @__PURE__ */ new Map();
|
|
174408
175654
|
rotationTimer = null;
|
|
174409
175655
|
maxSessionAgeMs = 0;
|
|
175656
|
+
// Wedge watchdog: last inbound Interaction Model request time per session and
|
|
175657
|
+
// last time the watchdog rotated it, both keyed by the long-lived session
|
|
175658
|
+
// object so they clear when the session goes away.
|
|
175659
|
+
lastImRequestAt = /* @__PURE__ */ new WeakMap();
|
|
175660
|
+
wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
|
|
175661
|
+
wedgeWatchdogTimer = null;
|
|
174410
175662
|
// Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
|
|
174411
175663
|
// change forces a fresh operational announcement (#415).
|
|
174412
175664
|
mdnsAddressTimer = null;
|
|
@@ -174470,6 +175722,7 @@ var ServerModeBridge = class {
|
|
|
174470
175722
|
const sessionList = sessions.map((s) => {
|
|
174471
175723
|
const subCount = s.subscriptions.size;
|
|
174472
175724
|
totalSubscriptions += subCount;
|
|
175725
|
+
const subscriptions = summarizeSubscriptions(s.subscriptions);
|
|
174473
175726
|
const fi = typeof s.fabric?.fabricIndex === "number" ? s.fabric.fabricIndex : null;
|
|
174474
175727
|
if (fi !== null) {
|
|
174475
175728
|
const existing = fabricMap.get(fi) ?? {
|
|
@@ -174483,14 +175736,18 @@ var ServerModeBridge = class {
|
|
|
174483
175736
|
const nowMs = Date.now();
|
|
174484
175737
|
const lastActiveMsAgo = typeof s.activeTimestamp === "number" && s.activeTimestamp > 0 ? nowMs - s.activeTimestamp : null;
|
|
174485
175738
|
const lastAnyActivityMsAgo = typeof s.timestamp === "number" ? nowMs - s.timestamp : null;
|
|
175739
|
+
const lastImAt = this.lastImRequestAt.get(s);
|
|
175740
|
+
const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
|
|
174486
175741
|
const startedAt = this.sessionStartedAt.get(s.id);
|
|
174487
175742
|
return {
|
|
174488
175743
|
id: s.id,
|
|
174489
175744
|
peerNodeId: String(s.peerNodeId),
|
|
174490
175745
|
fabricIndex: fi,
|
|
174491
175746
|
subscriptionCount: subCount,
|
|
175747
|
+
subscriptions,
|
|
174492
175748
|
lastActiveMsAgo,
|
|
174493
175749
|
lastAnyActivityMsAgo,
|
|
175750
|
+
lastImRequestMsAgo,
|
|
174494
175751
|
isPeerActive: Boolean(s.isPeerActive),
|
|
174495
175752
|
ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
|
|
174496
175753
|
};
|
|
@@ -174554,6 +175811,7 @@ var ServerModeBridge = class {
|
|
|
174554
175811
|
this.wireSessionDiagnostics();
|
|
174555
175812
|
this.wireFabricWarnings();
|
|
174556
175813
|
this.startSessionRotation();
|
|
175814
|
+
this.startWedgeWatchdog();
|
|
174557
175815
|
this.startMdnsAddressWatch();
|
|
174558
175816
|
this.scheduleWarmStart();
|
|
174559
175817
|
logMemoryUsage(this.log, "server mode bridge running");
|
|
@@ -174571,6 +175829,7 @@ ${e?.toString()}`);
|
|
|
174571
175829
|
}
|
|
174572
175830
|
async stop(code = BridgeStatus.Stopped, reason = "Manually stopped") {
|
|
174573
175831
|
this.stopSessionRotation();
|
|
175832
|
+
this.stopWedgeWatchdog();
|
|
174574
175833
|
this.stopMdnsAddressWatch();
|
|
174575
175834
|
this.unwireSessionDiagnostics();
|
|
174576
175835
|
this.unwireFabricWarnings();
|
|
@@ -174603,6 +175862,7 @@ ${e?.toString()}`);
|
|
|
174603
175862
|
if (this.status.code === BridgeStatus.Running) {
|
|
174604
175863
|
this.startAutoForceSyncIfEnabled();
|
|
174605
175864
|
this.startSessionRotation();
|
|
175865
|
+
this.startWedgeWatchdog();
|
|
174606
175866
|
}
|
|
174607
175867
|
} catch (e) {
|
|
174608
175868
|
const reason = "Failed to update server mode bridge due to error:";
|
|
@@ -174787,6 +176047,29 @@ ${e?.toString()}`);
|
|
|
174787
176047
|
sessionManager.sessions.added.on(this.sessionAddedHandler);
|
|
174788
176048
|
sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
|
|
174789
176049
|
seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
|
|
176050
|
+
this.wireImRequestTracking();
|
|
176051
|
+
} catch {
|
|
176052
|
+
}
|
|
176053
|
+
}
|
|
176054
|
+
// Stamp the time of every inbound Interaction Model request per session by
|
|
176055
|
+
// wrapping InteractionServer.onNewExchange. The wedge watchdog reads these to
|
|
176056
|
+
// tell a live-but-consuming controller from one that only keeps acking.
|
|
176057
|
+
wireImRequestTracking() {
|
|
176058
|
+
try {
|
|
176059
|
+
const is = this.server.env.get(InteractionServer);
|
|
176060
|
+
const marked = is;
|
|
176061
|
+
if (marked[imWrapMarker2]) {
|
|
176062
|
+
return;
|
|
176063
|
+
}
|
|
176064
|
+
const original = is.onNewExchange.bind(is);
|
|
176065
|
+
is.onNewExchange = (exchange, message) => {
|
|
176066
|
+
const session = exchange.session;
|
|
176067
|
+
if (session) {
|
|
176068
|
+
this.lastImRequestAt.set(session, Date.now());
|
|
176069
|
+
}
|
|
176070
|
+
return original(exchange, message);
|
|
176071
|
+
};
|
|
176072
|
+
marked[imWrapMarker2] = true;
|
|
174790
176073
|
} catch {
|
|
174791
176074
|
}
|
|
174792
176075
|
}
|
|
@@ -175004,6 +176287,72 @@ ${e?.toString()}`);
|
|
|
175004
176287
|
this.rotationTimer = null;
|
|
175005
176288
|
}
|
|
175006
176289
|
}
|
|
176290
|
+
// Opt-in wedge watchdog: reuse the rotation check cadence (5min) to look for
|
|
176291
|
+
// the one session wedged on "Updating" and rotate just that one.
|
|
176292
|
+
startWedgeWatchdog() {
|
|
176293
|
+
this.stopWedgeWatchdog();
|
|
176294
|
+
if (!this.dataProvider.featureFlags?.wedgeWatchdog) {
|
|
176295
|
+
return;
|
|
176296
|
+
}
|
|
176297
|
+
this.wedgeWatchdogTimer = setInterval(
|
|
176298
|
+
() => this.runWedgeWatchdogCheck(),
|
|
176299
|
+
ROTATION_CHECK_INTERVAL_MS
|
|
176300
|
+
);
|
|
176301
|
+
this.log.info(
|
|
176302
|
+
`Wedge watchdog: checking every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
176303
|
+
);
|
|
176304
|
+
}
|
|
176305
|
+
stopWedgeWatchdog() {
|
|
176306
|
+
if (this.wedgeWatchdogTimer) {
|
|
176307
|
+
clearInterval(this.wedgeWatchdogTimer);
|
|
176308
|
+
this.wedgeWatchdogTimer = null;
|
|
176309
|
+
}
|
|
176310
|
+
}
|
|
176311
|
+
// Rotate exactly the sessions the pure rule flags as wedged. Closing is the
|
|
176312
|
+
// same graceful-then-force path age rotation uses, so a false positive just
|
|
176313
|
+
// re-CASEs the controller.
|
|
176314
|
+
runWedgeWatchdogCheck() {
|
|
176315
|
+
try {
|
|
176316
|
+
const sessionManager = this.server.env.get(SessionManager);
|
|
176317
|
+
const now = Date.now();
|
|
176318
|
+
const closes = [];
|
|
176319
|
+
for (const s of [...sessionManager.sessions]) {
|
|
176320
|
+
if (s.isClosing) continue;
|
|
176321
|
+
const startedAt = this.sessionStartedAt.get(s.id);
|
|
176322
|
+
const sessionAgeMs = startedAt != null ? now - startedAt : 0;
|
|
176323
|
+
const lastImAt = this.lastImRequestAt.get(s);
|
|
176324
|
+
const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
|
|
176325
|
+
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
176326
|
+
const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
|
|
176327
|
+
if (!decideWedgeRotation({
|
|
176328
|
+
subscriptionCount: s.subscriptions.size,
|
|
176329
|
+
sessionAgeMs,
|
|
176330
|
+
lastImRequestMsAgo,
|
|
176331
|
+
lastRotatedMsAgo
|
|
176332
|
+
})) {
|
|
176333
|
+
continue;
|
|
176334
|
+
}
|
|
176335
|
+
const silenceMin = Math.round(
|
|
176336
|
+
(lastImRequestMsAgo ?? sessionAgeMs) / 6e4
|
|
176337
|
+
);
|
|
176338
|
+
this.log.info(
|
|
176339
|
+
`Wedge watchdog: rotating session ${s.id}, no inbound interaction for ${silenceMin}min`
|
|
176340
|
+
);
|
|
176341
|
+
this.wedgeLastRotatedAt.set(s, now);
|
|
176342
|
+
closes.push(
|
|
176343
|
+
s.initiateClose().catch(() => {
|
|
176344
|
+
return s.initiateForceClose({
|
|
176345
|
+
cause: new Error("wedge watchdog, forcing")
|
|
176346
|
+
});
|
|
176347
|
+
})
|
|
176348
|
+
);
|
|
176349
|
+
}
|
|
176350
|
+
if (closes.length > 0) {
|
|
176351
|
+
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
176352
|
+
}
|
|
176353
|
+
} catch {
|
|
176354
|
+
}
|
|
176355
|
+
}
|
|
175007
176356
|
// Poll the interface addresses mDNS advertises and re-announce when they
|
|
175008
176357
|
// change. matter.js caches the operational records at first announcement, so
|
|
175009
176358
|
// a dynamic ISP IPv6 prefix change keeps advertising the dead global address
|
|
@@ -175313,12 +176662,12 @@ function ServerModeVacuumDevice(homeAssistantEntity, includeOnOff = false, clean
|
|
|
175313
176662
|
}
|
|
175314
176663
|
|
|
175315
176664
|
// src/matter/endpoints/server-mode-vacuum-endpoint.ts
|
|
175316
|
-
var
|
|
176665
|
+
var logger249 = Logger.get("ServerModeVacuumEndpoint");
|
|
175317
176666
|
var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEndpoint {
|
|
175318
|
-
static async create(
|
|
175319
|
-
const deviceRegistry =
|
|
175320
|
-
let state =
|
|
175321
|
-
const entity =
|
|
176667
|
+
static async create(registry3, entityId, mapping, endpointId) {
|
|
176668
|
+
const deviceRegistry = registry3.deviceOf(entityId);
|
|
176669
|
+
let state = registry3.initialState(entityId);
|
|
176670
|
+
const entity = registry3.entity(entityId);
|
|
175322
176671
|
if (!state) {
|
|
175323
176672
|
return void 0;
|
|
175324
176673
|
}
|
|
@@ -175326,12 +176675,12 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175326
176675
|
if (mapping?.disableBatteryMapping && mapping.batteryEntity) {
|
|
175327
176676
|
effectiveMapping = { ...mapping, batteryEntity: void 0 };
|
|
175328
176677
|
}
|
|
175329
|
-
|
|
176678
|
+
logger249.info(
|
|
175330
176679
|
`${entityId}: device_id=${entity.device_id}, manualBattery=${mapping?.batteryEntity ?? "none"}`
|
|
175331
176680
|
);
|
|
175332
176681
|
if (entity.device_id) {
|
|
175333
176682
|
if (!mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
|
|
175334
|
-
const batteryEntityId =
|
|
176683
|
+
const batteryEntityId = registry3.findBatteryEntityForDevice(
|
|
175335
176684
|
entity.device_id
|
|
175336
176685
|
);
|
|
175337
176686
|
if (batteryEntityId && batteryEntityId !== entityId) {
|
|
@@ -175340,22 +176689,22 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175340
176689
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175341
176690
|
batteryEntity: batteryEntityId
|
|
175342
176691
|
};
|
|
175343
|
-
|
|
175344
|
-
|
|
176692
|
+
registry3.markBatteryEntityUsed(batteryEntityId);
|
|
176693
|
+
logger249.info(`${entityId}: Auto-assigned battery ${batteryEntityId}`);
|
|
175345
176694
|
} else {
|
|
175346
176695
|
const attrs = state.attributes;
|
|
175347
176696
|
if (attrs.battery_level != null || attrs.battery != null) {
|
|
175348
|
-
|
|
176697
|
+
logger249.info(
|
|
175349
176698
|
`${entityId}: No battery entity found, using battery attribute from vacuum state`
|
|
175350
176699
|
);
|
|
175351
176700
|
} else {
|
|
175352
|
-
|
|
176701
|
+
logger249.warn(
|
|
175353
176702
|
`${entityId}: No battery entity found for device ${entity.device_id}`
|
|
175354
176703
|
);
|
|
175355
176704
|
}
|
|
175356
176705
|
}
|
|
175357
176706
|
}
|
|
175358
|
-
const vacuumEntities =
|
|
176707
|
+
const vacuumEntities = registry3.findVacuumSelectEntities(
|
|
175359
176708
|
entity.device_id
|
|
175360
176709
|
);
|
|
175361
176710
|
if (!effectiveMapping?.cleaningModeEntity && vacuumEntities.cleaningModeEntity) {
|
|
@@ -175364,7 +176713,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175364
176713
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175365
176714
|
cleaningModeEntity: vacuumEntities.cleaningModeEntity
|
|
175366
176715
|
};
|
|
175367
|
-
|
|
176716
|
+
logger249.info(
|
|
175368
176717
|
`${entityId}: Auto-assigned cleaningMode ${vacuumEntities.cleaningModeEntity}`
|
|
175369
176718
|
);
|
|
175370
176719
|
}
|
|
@@ -175374,7 +176723,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175374
176723
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175375
176724
|
suctionLevelEntity: vacuumEntities.suctionLevelEntity
|
|
175376
176725
|
};
|
|
175377
|
-
|
|
176726
|
+
logger249.info(
|
|
175378
176727
|
`${entityId}: Auto-assigned suctionLevel ${vacuumEntities.suctionLevelEntity}`
|
|
175379
176728
|
);
|
|
175380
176729
|
}
|
|
@@ -175384,7 +176733,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175384
176733
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175385
176734
|
mopIntensityEntity: vacuumEntities.mopIntensityEntity
|
|
175386
176735
|
};
|
|
175387
|
-
|
|
176736
|
+
logger249.info(
|
|
175388
176737
|
`${entityId}: Auto-assigned mopIntensity ${vacuumEntities.mopIntensityEntity}`
|
|
175389
176738
|
);
|
|
175390
176739
|
}
|
|
@@ -175394,12 +176743,12 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175394
176743
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175395
176744
|
currentRoomEntity: vacuumEntities.currentRoomEntity
|
|
175396
176745
|
};
|
|
175397
|
-
|
|
176746
|
+
logger249.info(
|
|
175398
176747
|
`${entityId}: Auto-assigned currentRoom ${vacuumEntities.currentRoomEntity}`
|
|
175399
176748
|
);
|
|
175400
176749
|
}
|
|
175401
176750
|
const supportedFeatures = state.attributes.supported_features ?? 0;
|
|
175402
|
-
const cleanAreaRooms = await
|
|
176751
|
+
const cleanAreaRooms = await registry3.resolveCleanAreaRooms(
|
|
175403
176752
|
entityId,
|
|
175404
176753
|
supportedFeatures
|
|
175405
176754
|
);
|
|
@@ -175409,13 +176758,13 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175409
176758
|
entityId: effectiveMapping?.entityId ?? entityId,
|
|
175410
176759
|
cleanAreaRooms
|
|
175411
176760
|
};
|
|
175412
|
-
|
|
176761
|
+
logger249.info(
|
|
175413
176762
|
`${entityId}: Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA`
|
|
175414
176763
|
);
|
|
175415
176764
|
}
|
|
175416
176765
|
const vacAttrs = state.attributes;
|
|
175417
176766
|
if (cleanAreaRooms.length === 0 && !vacAttrs.rooms && !vacAttrs.segments && !vacAttrs.room_mapping) {
|
|
175418
|
-
const valetudoRooms =
|
|
176767
|
+
const valetudoRooms = registry3.findValetudoMapSegments(
|
|
175419
176768
|
entity.device_id
|
|
175420
176769
|
);
|
|
175421
176770
|
if (valetudoRooms.length > 0) {
|
|
@@ -175430,11 +176779,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175430
176779
|
rooms: roomsObj
|
|
175431
176780
|
}
|
|
175432
176781
|
};
|
|
175433
|
-
|
|
176782
|
+
logger249.info(
|
|
175434
176783
|
`${entityId}: Auto-detected ${valetudoRooms.length} Valetudo segments`
|
|
175435
176784
|
);
|
|
175436
176785
|
} else {
|
|
175437
|
-
const roborockRooms = await
|
|
176786
|
+
const roborockRooms = await registry3.resolveRoborockRooms(entityId);
|
|
175438
176787
|
if (roborockRooms.length > 0) {
|
|
175439
176788
|
const roomsObj = {};
|
|
175440
176789
|
for (const r of roborockRooms) {
|
|
@@ -175447,14 +176796,14 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175447
176796
|
rooms: roomsObj
|
|
175448
176797
|
}
|
|
175449
176798
|
};
|
|
175450
|
-
|
|
176799
|
+
logger249.info(
|
|
175451
176800
|
`${entityId}: Auto-detected ${roborockRooms.length} Roborock rooms`
|
|
175452
176801
|
);
|
|
175453
176802
|
}
|
|
175454
176803
|
}
|
|
175455
176804
|
}
|
|
175456
176805
|
} else {
|
|
175457
|
-
|
|
176806
|
+
logger249.warn(`${entityId}: No device_id, cannot auto-assign battery`);
|
|
175458
176807
|
}
|
|
175459
176808
|
const payload = {
|
|
175460
176809
|
entity_id: entityId,
|
|
@@ -175465,7 +176814,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175465
176814
|
const vacAttrsForClean = state.attributes;
|
|
175466
176815
|
let cleaningModeOptions;
|
|
175467
176816
|
if (effectiveMapping?.cleaningModeEntity) {
|
|
175468
|
-
const cmState =
|
|
176817
|
+
const cmState = registry3.initialState(
|
|
175469
176818
|
effectiveMapping.cleaningModeEntity
|
|
175470
176819
|
);
|
|
175471
176820
|
cleaningModeOptions = cmState?.attributes?.options;
|
|
@@ -175485,7 +176834,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175485
176834
|
customName,
|
|
175486
176835
|
mapping: effectiveMapping
|
|
175487
176836
|
},
|
|
175488
|
-
|
|
176837
|
+
registry3.isServerModeVacuumOnOffEnabled(),
|
|
175489
176838
|
cleaningModeOptions
|
|
175490
176839
|
);
|
|
175491
176840
|
if (!endpointType) {
|
|
@@ -175527,11 +176876,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175527
176876
|
}
|
|
175528
176877
|
if (mappedChanged) {
|
|
175529
176878
|
this.pendingMappedChange = true;
|
|
175530
|
-
|
|
176879
|
+
logger249.debug(
|
|
175531
176880
|
`Mapped entity change detected for ${this.entityId}, forcing update`
|
|
175532
176881
|
);
|
|
175533
176882
|
}
|
|
175534
|
-
|
|
176883
|
+
logger249.debug(
|
|
175535
176884
|
`State update received for ${this.entityId}: state=${state.state}`
|
|
175536
176885
|
);
|
|
175537
176886
|
this.lastState = state;
|
|
@@ -175571,12 +176920,13 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
175571
176920
|
// src/services/bridges/server-mode-endpoint-manager.ts
|
|
175572
176921
|
var MAX_SERVER_MODE_DEVICES = 10;
|
|
175573
176922
|
var ServerModeEndpointManager = class extends Service {
|
|
175574
|
-
constructor(serverNode, client,
|
|
176923
|
+
constructor(serverNode, client, registry3, mappingStorage, identityStorage, dataProvider, log) {
|
|
175575
176924
|
super("ServerModeEndpointManager");
|
|
175576
176925
|
this.serverNode = serverNode;
|
|
175577
176926
|
this.client = client;
|
|
175578
|
-
this.registry =
|
|
176927
|
+
this.registry = registry3;
|
|
175579
176928
|
this.mappingStorage = mappingStorage;
|
|
176929
|
+
this.identityStorage = identityStorage;
|
|
175580
176930
|
this.dataProvider = dataProvider;
|
|
175581
176931
|
this.log = log;
|
|
175582
176932
|
this.identityResolver = new IdentityResolver(
|
|
@@ -175588,6 +176938,7 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
175588
176938
|
client;
|
|
175589
176939
|
registry;
|
|
175590
176940
|
mappingStorage;
|
|
176941
|
+
identityStorage;
|
|
175591
176942
|
dataProvider;
|
|
175592
176943
|
log;
|
|
175593
176944
|
entityIds = [];
|
|
@@ -175687,6 +177038,17 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
175687
177038
|
this.registry.refresh();
|
|
175688
177039
|
this._failedEntities = [];
|
|
175689
177040
|
this.entityIds = this.registry.entityIds;
|
|
177041
|
+
const fullEntities = this.registry.fullEntities;
|
|
177042
|
+
stampIdentityPresence(
|
|
177043
|
+
this.identityStorage,
|
|
177044
|
+
this.dataProvider.id,
|
|
177045
|
+
buildPresentIdentityKeys(fullEntities)
|
|
177046
|
+
);
|
|
177047
|
+
stampMappingPresence(
|
|
177048
|
+
this.mappingStorage,
|
|
177049
|
+
this.dataProvider.id,
|
|
177050
|
+
buildPresentEntityIds(fullEntities)
|
|
177051
|
+
);
|
|
175690
177052
|
try {
|
|
175691
177053
|
if (this.entityIds.length === 0) {
|
|
175692
177054
|
this.log.warn("Server mode bridge has no entities configured");
|
|
@@ -176096,10 +177458,10 @@ var BridgeEnvironmentFactory = class extends BridgeFactory {
|
|
|
176096
177458
|
// src/core/ioc/app-environment.ts
|
|
176097
177459
|
var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
176098
177460
|
constructor(rootEnv, options) {
|
|
176099
|
-
const
|
|
177461
|
+
const logger251 = rootEnv.get(LoggerService);
|
|
176100
177462
|
super({
|
|
176101
177463
|
id: "App",
|
|
176102
|
-
log:
|
|
177464
|
+
log: logger251.get("AppContainer"),
|
|
176103
177465
|
parent: rootEnv
|
|
176104
177466
|
});
|
|
176105
177467
|
this.options = options;
|
|
@@ -176113,8 +177475,8 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176113
177475
|
}
|
|
176114
177476
|
construction;
|
|
176115
177477
|
async init() {
|
|
176116
|
-
const
|
|
176117
|
-
this.set(LoggerService,
|
|
177478
|
+
const logger251 = this.get(LoggerService);
|
|
177479
|
+
this.set(LoggerService, logger251);
|
|
176118
177480
|
this.set(AppStorage, new AppStorage(await this.load(StorageService)));
|
|
176119
177481
|
this.set(BridgeStorage, new BridgeStorage(await this.load(AppStorage)));
|
|
176120
177482
|
this.set(
|
|
@@ -176135,7 +177497,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176135
177497
|
);
|
|
176136
177498
|
this.set(
|
|
176137
177499
|
HomeAssistantClient,
|
|
176138
|
-
new HomeAssistantClient(
|
|
177500
|
+
new HomeAssistantClient(logger251, this.options.homeAssistant)
|
|
176139
177501
|
);
|
|
176140
177502
|
this.set(
|
|
176141
177503
|
HomeAssistantConfig,
|
|
@@ -176143,7 +177505,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176143
177505
|
);
|
|
176144
177506
|
this.set(
|
|
176145
177507
|
HomeAssistantActions,
|
|
176146
|
-
new HomeAssistantActions(
|
|
177508
|
+
new HomeAssistantActions(logger251, await this.load(HomeAssistantClient))
|
|
176147
177509
|
);
|
|
176148
177510
|
this.set(
|
|
176149
177511
|
HomeAssistantRegistry,
|
|
@@ -176179,7 +177541,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176179
177541
|
this.set(
|
|
176180
177542
|
WebApi,
|
|
176181
177543
|
new WebApi(
|
|
176182
|
-
|
|
177544
|
+
logger251,
|
|
176183
177545
|
await this.load(BridgeService),
|
|
176184
177546
|
await this.load(HomeAssistantClient),
|
|
176185
177547
|
await this.load(HomeAssistantRegistry),
|
|
@@ -176200,7 +177562,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
|
|
|
176200
177562
|
|
|
176201
177563
|
// src/matter/patches/patch-level-control-tlv.ts
|
|
176202
177564
|
init_esm();
|
|
176203
|
-
var
|
|
177565
|
+
var logger250 = Logger.get("PatchLevelControlTlv");
|
|
176204
177566
|
var optionalFieldModels = /* @__PURE__ */ new WeakSet();
|
|
176205
177567
|
var fieldModelMandatoryGetterPatched = false;
|
|
176206
177568
|
function patchLevelControlTlv() {
|
|
@@ -176256,11 +177618,11 @@ function patchLevelControlTlv() {
|
|
|
176256
177618
|
patched++;
|
|
176257
177619
|
}
|
|
176258
177620
|
if (patched > 0) {
|
|
176259
|
-
|
|
177621
|
+
logger250.info(
|
|
176260
177622
|
`Patched ${patched} LevelControl TLV schema(s): transitionTime is now optional (Google Home compatibility)`
|
|
176261
177623
|
);
|
|
176262
177624
|
} else {
|
|
176263
|
-
|
|
177625
|
+
logger250.warn(
|
|
176264
177626
|
"Failed to patch LevelControl TLV schemas, field definitions not found. Google Home brightness adjustment may not work.",
|
|
176265
177627
|
{
|
|
176266
177628
|
moveToLevel: describeFields(
|