@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.
@@ -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 logger157 = Logger.get("BridgedDeviceBasicInformationServer");
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
- logger157.warn("uniqueId and serialNumber shall not be the same.");
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 logger158 = Logger.get("EndpointValidation");
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
- logger158.warn(
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
- logger158.debug(
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