@riddix/hamh 2.1.0-alpha.584 → 2.1.0-alpha.586
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/backend/cli.js
CHANGED
|
@@ -164899,6 +164899,74 @@ init_esm5();
|
|
|
164899
164899
|
init_nodejs();
|
|
164900
164900
|
init_esm3();
|
|
164901
164901
|
|
|
164902
|
+
// src/utils/apply-patch-state.ts
|
|
164903
|
+
init_esm();
|
|
164904
|
+
var logger157 = Logger.get("ApplyPatchState");
|
|
164905
|
+
function applyPatchState(state, patch, options) {
|
|
164906
|
+
return applyPatch(state, patch, options?.force);
|
|
164907
|
+
}
|
|
164908
|
+
function applyPatch(state, patch, force = false) {
|
|
164909
|
+
const actualPatch = {};
|
|
164910
|
+
for (const key in patch) {
|
|
164911
|
+
if (Object.hasOwn(patch, key)) {
|
|
164912
|
+
const patchValue = patch[key];
|
|
164913
|
+
if (patchValue !== void 0) {
|
|
164914
|
+
const stateValue = state[key];
|
|
164915
|
+
if (force || !deepEqual(stateValue, patchValue)) {
|
|
164916
|
+
actualPatch[key] = patchValue;
|
|
164917
|
+
}
|
|
164918
|
+
}
|
|
164919
|
+
}
|
|
164920
|
+
}
|
|
164921
|
+
const failedKeys = [];
|
|
164922
|
+
for (const key in actualPatch) {
|
|
164923
|
+
if (!Object.hasOwn(actualPatch, key)) continue;
|
|
164924
|
+
try {
|
|
164925
|
+
state[key] = actualPatch[key];
|
|
164926
|
+
} catch (e) {
|
|
164927
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
164928
|
+
if (errorMessage.includes(
|
|
164929
|
+
"Endpoint storage inaccessible because endpoint is not a node and is not owned by another endpoint"
|
|
164930
|
+
)) {
|
|
164931
|
+
logger157.debug(
|
|
164932
|
+
`Suppressed endpoint storage error, patch not applied: ${JSON.stringify(actualPatch)}`
|
|
164933
|
+
);
|
|
164934
|
+
return actualPatch;
|
|
164935
|
+
}
|
|
164936
|
+
if (errorMessage.includes("synchronous-transaction-conflict")) {
|
|
164937
|
+
logger157.warn(
|
|
164938
|
+
`Transaction conflict, state update DROPPED: ${JSON.stringify(actualPatch)}`
|
|
164939
|
+
);
|
|
164940
|
+
return actualPatch;
|
|
164941
|
+
}
|
|
164942
|
+
failedKeys.push(key);
|
|
164943
|
+
logger157.warn(`Failed to set property '${key}': ${errorMessage}`);
|
|
164944
|
+
}
|
|
164945
|
+
}
|
|
164946
|
+
if (failedKeys.length > 0) {
|
|
164947
|
+
logger157.warn(
|
|
164948
|
+
`${failedKeys.length} properties failed to update: [${failedKeys.join(", ")}]`
|
|
164949
|
+
);
|
|
164950
|
+
}
|
|
164951
|
+
return actualPatch;
|
|
164952
|
+
}
|
|
164953
|
+
function deepEqual(a, b) {
|
|
164954
|
+
if (a == null || b == null) {
|
|
164955
|
+
return a === b;
|
|
164956
|
+
}
|
|
164957
|
+
if (typeof a !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
|
|
164958
|
+
return false;
|
|
164959
|
+
}
|
|
164960
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
164961
|
+
return a.length === b.length && a.every((vA, idx) => deepEqual(vA, b[idx]));
|
|
164962
|
+
}
|
|
164963
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
164964
|
+
const keys3 = Object.keys({ ...a, ...b });
|
|
164965
|
+
return keys3.every((key) => deepEqual(a[key], b[key]));
|
|
164966
|
+
}
|
|
164967
|
+
return a === b;
|
|
164968
|
+
}
|
|
164969
|
+
|
|
164902
164970
|
// src/utils/trim-to-length.ts
|
|
164903
164971
|
function trimToLength(value, maxLength, suffix) {
|
|
164904
164972
|
const stringValue = value?.toString();
|
|
@@ -164962,6 +165030,23 @@ var ServerModeServerNode = class extends ServerNode {
|
|
|
164962
165030
|
clearDevice() {
|
|
164963
165031
|
this.deviceEndpoint = void 0;
|
|
164964
165032
|
}
|
|
165033
|
+
/**
|
|
165034
|
+
* Update root-level BasicInformation with entity-specific data.
|
|
165035
|
+
* In server mode, controllers (Apple Home, Alexa) read the root node's
|
|
165036
|
+
* BasicInformation — not the device endpoint's BridgedDeviceBasicInformation.
|
|
165037
|
+
* Without this, server-mode devices show bridge defaults (e.g. "riddix" / "MatterHub").
|
|
165038
|
+
*/
|
|
165039
|
+
updateDeviceIdentity(entityId, device, mapping, friendlyName) {
|
|
165040
|
+
applyPatchState(this.state.basicInformation, {
|
|
165041
|
+
vendorName: trimToLength(mapping?.customVendorName, 32, "...") ?? trimToLength(device?.manufacturer, 32, "..."),
|
|
165042
|
+
productName: trimToLength(mapping?.customProductName, 32, "...") ?? trimToLength(device?.model_id, 32, "...") ?? trimToLength(device?.model, 32, "..."),
|
|
165043
|
+
productLabel: trimToLength(device?.model, 64, "..."),
|
|
165044
|
+
nodeLabel: trimToLength(mapping?.customName, 32, "...") ?? trimToLength(friendlyName, 32, "...") ?? trimToLength(entityId, 32, "..."),
|
|
165045
|
+
serialNumber: trimToLength(mapping?.customSerialNumber, 32, "..."),
|
|
165046
|
+
hardwareVersionString: trimToLength(device?.hw_version, 64, "..."),
|
|
165047
|
+
softwareVersionString: trimToLength(device?.sw_version, 64, "...")
|
|
165048
|
+
});
|
|
165049
|
+
}
|
|
164965
165050
|
async factoryReset() {
|
|
164966
165051
|
await this.cancel();
|
|
164967
165052
|
await this.erase();
|
|
@@ -165053,7 +165138,7 @@ init_basic_information2();
|
|
|
165053
165138
|
init_descriptor2();
|
|
165054
165139
|
init_aggregator();
|
|
165055
165140
|
init_esm();
|
|
165056
|
-
var
|
|
165141
|
+
var logger158 = Logger.get("BridgedDeviceBasicInformationServer");
|
|
165057
165142
|
var BridgedDeviceBasicInformationServer = class extends BridgedDeviceBasicInformationBehavior {
|
|
165058
165143
|
async initialize() {
|
|
165059
165144
|
if (this.endpoint.lifecycle.isInstalled) {
|
|
@@ -165068,7 +165153,7 @@ var BridgedDeviceBasicInformationServer = class extends BridgedDeviceBasicInform
|
|
|
165068
165153
|
this.state.uniqueId = BasicInformationServer.createUniqueId();
|
|
165069
165154
|
}
|
|
165070
165155
|
if (serialNumber !== void 0 && uniqueId === this.state.serialNumber) {
|
|
165071
|
-
|
|
165156
|
+
logger158.warn("uniqueId and serialNumber shall not be the same.");
|
|
165072
165157
|
}
|
|
165073
165158
|
}
|
|
165074
165159
|
static schema = BasicInformationServer.enableUniqueIdPersistence(
|
|
@@ -165940,7 +166025,7 @@ var IdentifyServer2 = class extends IdentifyServer {
|
|
|
165940
166025
|
// src/matter/endpoints/validate-endpoint-type.ts
|
|
165941
166026
|
init_esm();
|
|
165942
166027
|
init_esm7();
|
|
165943
|
-
var
|
|
166028
|
+
var logger159 = Logger.get("EndpointValidation");
|
|
165944
166029
|
function toCamelCase(name) {
|
|
165945
166030
|
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
165946
166031
|
}
|
|
@@ -165970,12 +166055,12 @@ function validateEndpointType(endpointType, entityId) {
|
|
|
165970
166055
|
}
|
|
165971
166056
|
const prefix = entityId ? `[${entityId}] ` : "";
|
|
165972
166057
|
if (missingMandatory.length > 0) {
|
|
165973
|
-
|
|
166058
|
+
logger159.warn(
|
|
165974
166059
|
`${prefix}${deviceTypeModel.name} (0x${endpointType.deviceType.toString(16)}): missing mandatory clusters: ${missingMandatory.join(", ")}`
|
|
165975
166060
|
);
|
|
165976
166061
|
}
|
|
165977
166062
|
if (availableOptional.length > 0) {
|
|
165978
|
-
|
|
166063
|
+
logger159.debug(
|
|
165979
166064
|
`${prefix}${deviceTypeModel.name} (0x${endpointType.deviceType.toString(16)}): optional clusters not used: ${availableOptional.join(", ")}`
|
|
165980
166065
|
);
|
|
165981
166066
|
}
|
|
@@ -166078,74 +166163,6 @@ var BridgeDataProvider = class extends Service {
|
|
|
166078
166163
|
}
|
|
166079
166164
|
};
|
|
166080
166165
|
|
|
166081
|
-
// src/utils/apply-patch-state.ts
|
|
166082
|
-
init_esm();
|
|
166083
|
-
var logger159 = Logger.get("ApplyPatchState");
|
|
166084
|
-
function applyPatchState(state, patch, options) {
|
|
166085
|
-
return applyPatch(state, patch, options?.force);
|
|
166086
|
-
}
|
|
166087
|
-
function applyPatch(state, patch, force = false) {
|
|
166088
|
-
const actualPatch = {};
|
|
166089
|
-
for (const key in patch) {
|
|
166090
|
-
if (Object.hasOwn(patch, key)) {
|
|
166091
|
-
const patchValue = patch[key];
|
|
166092
|
-
if (patchValue !== void 0) {
|
|
166093
|
-
const stateValue = state[key];
|
|
166094
|
-
if (force || !deepEqual(stateValue, patchValue)) {
|
|
166095
|
-
actualPatch[key] = patchValue;
|
|
166096
|
-
}
|
|
166097
|
-
}
|
|
166098
|
-
}
|
|
166099
|
-
}
|
|
166100
|
-
const failedKeys = [];
|
|
166101
|
-
for (const key in actualPatch) {
|
|
166102
|
-
if (!Object.hasOwn(actualPatch, key)) continue;
|
|
166103
|
-
try {
|
|
166104
|
-
state[key] = actualPatch[key];
|
|
166105
|
-
} catch (e) {
|
|
166106
|
-
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
166107
|
-
if (errorMessage.includes(
|
|
166108
|
-
"Endpoint storage inaccessible because endpoint is not a node and is not owned by another endpoint"
|
|
166109
|
-
)) {
|
|
166110
|
-
logger159.debug(
|
|
166111
|
-
`Suppressed endpoint storage error, patch not applied: ${JSON.stringify(actualPatch)}`
|
|
166112
|
-
);
|
|
166113
|
-
return actualPatch;
|
|
166114
|
-
}
|
|
166115
|
-
if (errorMessage.includes("synchronous-transaction-conflict")) {
|
|
166116
|
-
logger159.warn(
|
|
166117
|
-
`Transaction conflict, state update DROPPED: ${JSON.stringify(actualPatch)}`
|
|
166118
|
-
);
|
|
166119
|
-
return actualPatch;
|
|
166120
|
-
}
|
|
166121
|
-
failedKeys.push(key);
|
|
166122
|
-
logger159.warn(`Failed to set property '${key}': ${errorMessage}`);
|
|
166123
|
-
}
|
|
166124
|
-
}
|
|
166125
|
-
if (failedKeys.length > 0) {
|
|
166126
|
-
logger159.warn(
|
|
166127
|
-
`${failedKeys.length} properties failed to update: [${failedKeys.join(", ")}]`
|
|
166128
|
-
);
|
|
166129
|
-
}
|
|
166130
|
-
return actualPatch;
|
|
166131
|
-
}
|
|
166132
|
-
function deepEqual(a, b) {
|
|
166133
|
-
if (a == null || b == null) {
|
|
166134
|
-
return a === b;
|
|
166135
|
-
}
|
|
166136
|
-
if (typeof a !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
|
|
166137
|
-
return false;
|
|
166138
|
-
}
|
|
166139
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
166140
|
-
return a.length === b.length && a.every((vA, idx) => deepEqual(vA, b[idx]));
|
|
166141
|
-
}
|
|
166142
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
166143
|
-
const keys3 = Object.keys({ ...a, ...b });
|
|
166144
|
-
return keys3.every((key) => deepEqual(a[key], b[key]));
|
|
166145
|
-
}
|
|
166146
|
-
return a === b;
|
|
166147
|
-
}
|
|
166148
|
-
|
|
166149
166166
|
// src/plugins/plugin-behavior.ts
|
|
166150
166167
|
init_esm7();
|
|
166151
166168
|
var PluginDeviceBehavior = class extends Behavior {
|
|
@@ -168708,6 +168725,7 @@ var FanControlServerBase = class extends FeaturedBase5 {
|
|
|
168708
168725
|
// Track last non-zero fan speed for restore on turn-on (#225)
|
|
168709
168726
|
lastNonZeroPercent = 0;
|
|
168710
168727
|
lastNonZeroSpeed = 0;
|
|
168728
|
+
lastIsAutoMode = false;
|
|
168711
168729
|
async initialize() {
|
|
168712
168730
|
if (this.features.multiSpeed) {
|
|
168713
168731
|
if (this.state.speedMax == null || this.state.speedMax < minSpeedMax) {
|
|
@@ -168819,6 +168837,7 @@ var FanControlServerBase = class extends FeaturedBase5 {
|
|
|
168819
168837
|
if (percentage > 0) {
|
|
168820
168838
|
this.lastNonZeroPercent = percentage;
|
|
168821
168839
|
this.lastNonZeroSpeed = speed;
|
|
168840
|
+
this.lastIsAutoMode = config10.isInAutoMode(entity.state, this.agent);
|
|
168822
168841
|
}
|
|
168823
168842
|
try {
|
|
168824
168843
|
applyPatchState(this.state, {
|
|
@@ -168885,6 +168904,9 @@ var FanControlServerBase = class extends FeaturedBase5 {
|
|
|
168885
168904
|
if (speed == null) {
|
|
168886
168905
|
return;
|
|
168887
168906
|
}
|
|
168907
|
+
if (speed === 0 && (this.state.percentSetting ?? 0) > 0) {
|
|
168908
|
+
return;
|
|
168909
|
+
}
|
|
168888
168910
|
this.agent.asLocalActor(() => {
|
|
168889
168911
|
const percentage = Math.floor(speed / this.state.speedMax * 100);
|
|
168890
168912
|
this.applyPercentageAction(percentage);
|
|
@@ -169033,6 +169055,18 @@ var FanControlServerBase = class extends FeaturedBase5 {
|
|
|
169033
169055
|
} catch {
|
|
169034
169056
|
}
|
|
169035
169057
|
});
|
|
169058
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
169059
|
+
if (homeAssistant.isAvailable) {
|
|
169060
|
+
if (this.lastIsAutoMode) {
|
|
169061
|
+
homeAssistant.callAction(
|
|
169062
|
+
this.state.config.setAutoMode(void 0, this.agent)
|
|
169063
|
+
);
|
|
169064
|
+
} else {
|
|
169065
|
+
homeAssistant.callAction(
|
|
169066
|
+
this.state.config.turnOn(this.lastNonZeroPercent, this.agent)
|
|
169067
|
+
);
|
|
169068
|
+
}
|
|
169069
|
+
}
|
|
169036
169070
|
}
|
|
169037
169071
|
}
|
|
169038
169072
|
// Cross-cluster sync: keep OnOff in sync with FanControl per Matter spec
|
|
@@ -181592,6 +181626,7 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
181592
181626
|
await this.serverNode.addDevice(endpoint2);
|
|
181593
181627
|
this.deviceEndpoint = endpoint2;
|
|
181594
181628
|
this.mappingFingerprint = currentFp;
|
|
181629
|
+
this.updateServerNodeIdentity(entityId, mapping);
|
|
181595
181630
|
this.log.info(
|
|
181596
181631
|
`Server mode: Added vacuum ${entityId} as standalone device`
|
|
181597
181632
|
);
|
|
@@ -181612,6 +181647,7 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
181612
181647
|
await this.serverNode.addDevice(endpoint);
|
|
181613
181648
|
this.deviceEndpoint = endpoint;
|
|
181614
181649
|
this.mappingFingerprint = currentFp;
|
|
181650
|
+
this.updateServerNodeIdentity(entityId, mapping);
|
|
181615
181651
|
this.log.info(`Server mode: Added device ${entityId}`);
|
|
181616
181652
|
} catch (e) {
|
|
181617
181653
|
const reason = e instanceof Error ? e.message : String(e);
|
|
@@ -181632,6 +181668,17 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
181632
181668
|
}
|
|
181633
181669
|
}
|
|
181634
181670
|
}
|
|
181671
|
+
updateServerNodeIdentity(entityId, mapping) {
|
|
181672
|
+
const device = this.registry.deviceOf(entityId);
|
|
181673
|
+
const state = this.registry.initialState(entityId);
|
|
181674
|
+
const friendlyName = state?.attributes?.friendly_name;
|
|
181675
|
+
this.serverNode.updateDeviceIdentity(
|
|
181676
|
+
entityId,
|
|
181677
|
+
device,
|
|
181678
|
+
mapping,
|
|
181679
|
+
friendlyName
|
|
181680
|
+
);
|
|
181681
|
+
}
|
|
181635
181682
|
/**
|
|
181636
181683
|
* Creates a Server Mode Vacuum endpoint without BridgedDeviceBasicInformation.
|
|
181637
181684
|
* This makes the vacuum appear as a standalone Matter device, which is required
|