@matterbridge/core 3.10.13-dev-20261004-0c9c4ee → 3.10.13-dev-20261007-320f739
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/cli.js +5 -0
- package/dist/deviceManager.d.ts +2 -1
- package/dist/deviceManager.js +29 -0
- package/dist/frontend.d.ts +2 -5
- package/dist/frontend.js +48 -307
- package/dist/matterNode.d.ts +3 -8
- package/dist/matterNode.js +25 -115
- package/dist/matterNodeHelpers.d.ts +15 -0
- package/dist/matterNodeHelpers.js +393 -0
- package/dist/matterbridge.d.ts +1 -6
- package/dist/matterbridge.js +48 -116
- package/dist/pluginManager.js +2 -1
- package/package.json +5 -5
- package/dist/backend.d.ts +0 -37
- package/dist/backend.js +0 -308
- package/dist/backendExpress.d.ts +0 -20
- package/dist/backendExpress.js +0 -453
- package/dist/backendWsServer.d.ts +0 -38
- package/dist/backendWsServer.js +0 -326
package/dist/matterNode.js
CHANGED
|
@@ -25,6 +25,7 @@ import { NodeStorageManager } from 'node-persist-manager';
|
|
|
25
25
|
import { toBaseDevice } from './deviceManager.js';
|
|
26
26
|
import { addVirtualDevice } from './helpers.js';
|
|
27
27
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
28
|
+
import { deleteAdvertisingNode, getServerNodeData, setAdvertisingNode } from './matterNodeHelpers.js';
|
|
28
29
|
import { PluginManager } from './pluginManager.js';
|
|
29
30
|
logModuleLoaded('MatterNode');
|
|
30
31
|
export class MatterNode extends EventEmitter {
|
|
@@ -55,7 +56,6 @@ export class MatterNode extends EventEmitter {
|
|
|
55
56
|
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
56
57
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
57
58
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
58
|
-
advertisingNodes = new Map();
|
|
59
59
|
pluginManager;
|
|
60
60
|
dependantMatterNodes = new Map();
|
|
61
61
|
server;
|
|
@@ -479,13 +479,13 @@ export class MatterNode extends EventEmitter {
|
|
|
479
479
|
});
|
|
480
480
|
serverNode.lifecycle.commissioned.on(() => {
|
|
481
481
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
482
|
-
|
|
483
|
-
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...
|
|
482
|
+
deleteAdvertisingNode(storeId);
|
|
483
|
+
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...getServerNodeData(serverNode) } } });
|
|
484
484
|
});
|
|
485
485
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
486
486
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
487
|
-
|
|
488
|
-
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...
|
|
487
|
+
deleteAdvertisingNode(storeId);
|
|
488
|
+
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...getServerNodeData(serverNode) } } });
|
|
489
489
|
this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
|
|
490
490
|
});
|
|
491
491
|
serverNode.lifecycle.online.on(() => {
|
|
@@ -499,23 +499,23 @@ export class MatterNode extends EventEmitter {
|
|
|
499
499
|
}
|
|
500
500
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
501
501
|
this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission ...`);
|
|
502
|
-
|
|
502
|
+
setAdvertisingNode(storeId, Date.now());
|
|
503
503
|
const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
|
|
504
504
|
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
505
505
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
506
506
|
}
|
|
507
507
|
else {
|
|
508
508
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
509
|
-
|
|
509
|
+
deleteAdvertisingNode(storeId);
|
|
510
510
|
}
|
|
511
|
-
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...
|
|
511
|
+
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...getServerNodeData(serverNode) } } });
|
|
512
512
|
this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is online`, timeout: 5, severity: 'success' } });
|
|
513
513
|
this.emit('online', storeId);
|
|
514
514
|
});
|
|
515
515
|
serverNode.lifecycle.offline.on(() => {
|
|
516
516
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
517
|
-
|
|
518
|
-
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...
|
|
517
|
+
deleteAdvertisingNode(storeId);
|
|
518
|
+
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...getServerNodeData(serverNode) } } });
|
|
519
519
|
this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
|
|
520
520
|
this.emit('offline', storeId);
|
|
521
521
|
});
|
|
@@ -523,7 +523,7 @@ export class MatterNode extends EventEmitter {
|
|
|
523
523
|
let action = '';
|
|
524
524
|
switch (fabricAction) {
|
|
525
525
|
case 'added':
|
|
526
|
-
|
|
526
|
+
deleteAdvertisingNode(storeId);
|
|
527
527
|
action = 'added';
|
|
528
528
|
break;
|
|
529
529
|
case 'deleted':
|
|
@@ -534,40 +534,24 @@ export class MatterNode extends EventEmitter {
|
|
|
534
534
|
break;
|
|
535
535
|
}
|
|
536
536
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
537
|
-
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...
|
|
537
|
+
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...getServerNodeData(serverNode) } } });
|
|
538
538
|
});
|
|
539
539
|
serverNode.events.sessions.opened.on((session) => {
|
|
540
540
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
541
|
-
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...
|
|
541
|
+
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...getServerNodeData(serverNode) } } });
|
|
542
542
|
});
|
|
543
543
|
serverNode.events.sessions.closed.on((session) => {
|
|
544
544
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
545
|
-
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...
|
|
545
|
+
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...getServerNodeData(serverNode) } } });
|
|
546
546
|
});
|
|
547
547
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
548
548
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
549
|
-
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...
|
|
549
|
+
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...getServerNodeData(serverNode) } } });
|
|
550
550
|
});
|
|
551
551
|
this.storeId = storeId;
|
|
552
552
|
this.log.info(`Created server node for ${this.storeId}`);
|
|
553
553
|
return serverNode;
|
|
554
554
|
}
|
|
555
|
-
getServerNodeData(serverNode) {
|
|
556
|
-
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
557
|
-
return {
|
|
558
|
-
id: serverNode.id,
|
|
559
|
-
online: serverNode.lifecycle.isOnline,
|
|
560
|
-
commissioned: serverNode.state.commissioning.commissioned,
|
|
561
|
-
advertising: advertiseTime > Date.now() - 15 * 60 * 1000,
|
|
562
|
-
advertiseTime,
|
|
563
|
-
windowStatus: serverNode.state.administratorCommissioning.windowStatus,
|
|
564
|
-
qrPairingCode: serverNode.state.commissioning.pairingCodes.qrPairingCode,
|
|
565
|
-
manualPairingCode: serverNode.state.commissioning.pairingCodes.manualPairingCode,
|
|
566
|
-
fabricInformations: this.sanitizeFabricInformations(Object.values(serverNode.state.commissioning.fabrics)),
|
|
567
|
-
sessionInformations: this.sanitizeSessionInformation(Object.values(serverNode.state.sessions.sessions)),
|
|
568
|
-
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
555
|
async startServerNode(timeout = 30000) {
|
|
572
556
|
if (!this.serverNode) {
|
|
573
557
|
throw new Error('Matter server node not created yet. Call create() first.');
|
|
@@ -593,6 +577,7 @@ export class MatterNode extends EventEmitter {
|
|
|
593
577
|
catch (error) {
|
|
594
578
|
this.log.error(`Failed to close ${this.serverNode.id} server node: ${getErrorMessage(error)}`);
|
|
595
579
|
}
|
|
580
|
+
deleteAdvertisingNode(this.serverNode.id);
|
|
596
581
|
}
|
|
597
582
|
async createAggregatorNode() {
|
|
598
583
|
if (!this.matterStorageContext) {
|
|
@@ -773,7 +758,15 @@ export class MatterNode extends EventEmitter {
|
|
|
773
758
|
const plugin = this.pluginManager.get(pluginName);
|
|
774
759
|
if (!plugin)
|
|
775
760
|
throw new Error(`Error removing bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): plugin not found`);
|
|
776
|
-
if (device.
|
|
761
|
+
if (device.mode === 'server') {
|
|
762
|
+
const matterNode = this.dependantMatterNodes.get(device.id);
|
|
763
|
+
if (matterNode) {
|
|
764
|
+
this.log.debug(`Closing MatterNode for device ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
765
|
+
await matterNode.stop();
|
|
766
|
+
await matterNode.destroy(false);
|
|
767
|
+
this.dependantMatterNodes.delete(device.id);
|
|
768
|
+
this.log.debug(`Closed MatterNode for device ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})`);
|
|
769
|
+
}
|
|
777
770
|
}
|
|
778
771
|
else if (this.matterbridge.bridgeMode === 'bridge') {
|
|
779
772
|
if (!this.aggregatorNode)
|
|
@@ -963,45 +956,6 @@ export class MatterNode extends EventEmitter {
|
|
|
963
956
|
}
|
|
964
957
|
}
|
|
965
958
|
}
|
|
966
|
-
sanitizeFabricInformations(fabricInfo) {
|
|
967
|
-
return fabricInfo.map((info) => {
|
|
968
|
-
return {
|
|
969
|
-
fabricIndex: info.fabricIndex,
|
|
970
|
-
fabricId: info.fabricId.toString(),
|
|
971
|
-
nodeId: info.nodeId.toString(),
|
|
972
|
-
rootNodeId: info.rootNodeId.toString(),
|
|
973
|
-
rootVendorId: info.rootVendorId,
|
|
974
|
-
rootVendorName: this.getVendorIdName(info.rootVendorId),
|
|
975
|
-
label: info.label,
|
|
976
|
-
};
|
|
977
|
-
});
|
|
978
|
-
}
|
|
979
|
-
sanitizeSessionInformation(sessions) {
|
|
980
|
-
return sessions
|
|
981
|
-
.filter((session) => session.isPeerActive)
|
|
982
|
-
.map((session) => {
|
|
983
|
-
return {
|
|
984
|
-
name: session.name,
|
|
985
|
-
nodeId: session.nodeId.toString(),
|
|
986
|
-
peerNodeId: session.peerNodeId.toString(),
|
|
987
|
-
fabric: session.fabric
|
|
988
|
-
? {
|
|
989
|
-
fabricIndex: session.fabric.fabricIndex,
|
|
990
|
-
fabricId: session.fabric.fabricId.toString(),
|
|
991
|
-
nodeId: session.fabric.nodeId.toString(),
|
|
992
|
-
rootNodeId: session.fabric.rootNodeId.toString(),
|
|
993
|
-
rootVendorId: session.fabric.rootVendorId,
|
|
994
|
-
rootVendorName: this.getVendorIdName(session.fabric.rootVendorId),
|
|
995
|
-
label: session.fabric.label,
|
|
996
|
-
}
|
|
997
|
-
: undefined,
|
|
998
|
-
isPeerActive: session.isPeerActive,
|
|
999
|
-
lastInteractionTimestamp: session.lastInteractionTimestamp?.toString(),
|
|
1000
|
-
lastActiveTimestamp: session.lastActiveTimestamp?.toString(),
|
|
1001
|
-
numberOfActiveSubscriptions: session.numberOfActiveSubscriptions,
|
|
1002
|
-
};
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
959
|
async setServerReachability(reachable) {
|
|
1006
960
|
await this.serverNode?.setStateOf(BasicInformationServer, { reachable });
|
|
1007
961
|
this.serverNode?.act((agent) => this.serverNode?.eventsOf(BasicInformationServer).reachableChanged?.emit({ reachableNewValue: reachable }, agent.context));
|
|
@@ -1013,50 +967,6 @@ export class MatterNode extends EventEmitter {
|
|
|
1013
967
|
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
1014
968
|
}
|
|
1015
969
|
}
|
|
1016
|
-
getVendorIdName = (vendorId) => {
|
|
1017
|
-
if (!vendorId)
|
|
1018
|
-
return '';
|
|
1019
|
-
let vendorName = '(Unknown vendorId)';
|
|
1020
|
-
switch (vendorId) {
|
|
1021
|
-
case 4937:
|
|
1022
|
-
vendorName = '(AppleHome)';
|
|
1023
|
-
break;
|
|
1024
|
-
case 4996:
|
|
1025
|
-
vendorName = '(AppleKeyChain)';
|
|
1026
|
-
break;
|
|
1027
|
-
case 4362:
|
|
1028
|
-
vendorName = '(SmartThings)';
|
|
1029
|
-
break;
|
|
1030
|
-
case 4939:
|
|
1031
|
-
vendorName = '(HomeAssistant)';
|
|
1032
|
-
break;
|
|
1033
|
-
case 24582:
|
|
1034
|
-
vendorName = '(GoogleHome)';
|
|
1035
|
-
break;
|
|
1036
|
-
case 4631:
|
|
1037
|
-
vendorName = '(Alexa)';
|
|
1038
|
-
break;
|
|
1039
|
-
case 4701:
|
|
1040
|
-
vendorName = '(Tuya)';
|
|
1041
|
-
break;
|
|
1042
|
-
case 4718:
|
|
1043
|
-
vendorName = '(Xiaomi)';
|
|
1044
|
-
break;
|
|
1045
|
-
case 4742:
|
|
1046
|
-
vendorName = '(eWeLink)';
|
|
1047
|
-
break;
|
|
1048
|
-
case 5264:
|
|
1049
|
-
vendorName = '(Shelly)';
|
|
1050
|
-
break;
|
|
1051
|
-
case 0x1488:
|
|
1052
|
-
vendorName = '(ShortcutLabsFlic)';
|
|
1053
|
-
break;
|
|
1054
|
-
case 65521:
|
|
1055
|
-
vendorName = '(MatterTest)';
|
|
1056
|
-
break;
|
|
1057
|
-
}
|
|
1058
|
-
return vendorName;
|
|
1059
|
-
};
|
|
1060
970
|
async yieldToNode(timeout = 100) {
|
|
1061
971
|
await Promise.resolve();
|
|
1062
972
|
await new Promise((resolve) => {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ServerNode, type SessionsBehavior } from '@matter/node';
|
|
2
|
+
import type { ExposedFabricInformation } from '@matter/protocol';
|
|
3
|
+
import type { ApiMatter, SanitizedExposedFabricInformation, SanitizedSession } from '@matterbridge/types';
|
|
4
|
+
import type { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
5
|
+
export declare function getReachability(device: MatterbridgeEndpoint): boolean;
|
|
6
|
+
export declare function getPowerSource(endpoint: MatterbridgeEndpoint): 'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined;
|
|
7
|
+
export declare function getBatteryLevel(endpoint: MatterbridgeEndpoint): number | undefined;
|
|
8
|
+
export declare function getClusterTextFromDevice(device: MatterbridgeEndpoint): string;
|
|
9
|
+
export declare function setAdvertisingNode(storeId: string, advertiseTime: number): void;
|
|
10
|
+
export declare function deleteAdvertisingNode(storeId: string): void;
|
|
11
|
+
export declare function clearAdvertisingNodes(): void;
|
|
12
|
+
export declare function getServerNodeData(serverNode: ServerNode): ApiMatter;
|
|
13
|
+
export declare function sanitizeFabricInformations(fabricInfo: ExposedFabricInformation[]): SanitizedExposedFabricInformation[];
|
|
14
|
+
export declare function sanitizeSessionInformation(sessions: SessionsBehavior.Session[]): SanitizedSession[];
|
|
15
|
+
export declare function getVendorIdName(vendorId: number | undefined): string;
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { Lifecycle } from '@matter/general';
|
|
2
|
+
import { ServerNode } from '@matter/node';
|
|
3
|
+
import { getClusterNameById } from '@matter/types/cluster';
|
|
4
|
+
import { AirQuality } from '@matter/types/clusters/air-quality';
|
|
5
|
+
import { BridgedDeviceBasicInformation } from '@matter/types/clusters/bridged-device-basic-information';
|
|
6
|
+
import { ClosureControl } from '@matter/types/clusters/closure-control';
|
|
7
|
+
import { DeviceEnergyManagement } from '@matter/types/clusters/device-energy-management';
|
|
8
|
+
import { FanControl } from '@matter/types/clusters/fan-control';
|
|
9
|
+
import { FixedLabel } from '@matter/types/clusters/fixed-label';
|
|
10
|
+
import { OperationalState } from '@matter/types/clusters/operational-state';
|
|
11
|
+
import { PowerSource } from '@matter/types/clusters/power-source';
|
|
12
|
+
import { RvcOperationalState } from '@matter/types/clusters/rvc-operational-state';
|
|
13
|
+
import { SmokeCoAlarm } from '@matter/types/clusters/smoke-co-alarm';
|
|
14
|
+
import { SoilMeasurement } from '@matter/types/clusters/soil-measurement';
|
|
15
|
+
import { UserLabel } from '@matter/types/clusters/user-label';
|
|
16
|
+
import { ValveConfigurationAndControl } from '@matter/types/clusters/valve-configuration-and-control';
|
|
17
|
+
import { ThreeLevelAuto } from '@matter/types/globals';
|
|
18
|
+
import { getEnumDescription } from '@matterbridge/utils/enum';
|
|
19
|
+
import { isValidArray, isValidNumber, isValidObject } from '@matterbridge/utils/validate';
|
|
20
|
+
import { stringify } from 'node-ansi-logger';
|
|
21
|
+
import { featuresFor, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
22
|
+
const advertisingNodes = new Map();
|
|
23
|
+
export function getReachability(device) {
|
|
24
|
+
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
25
|
+
return false;
|
|
26
|
+
if (device.hasClusterServer(BridgedDeviceBasicInformation.id))
|
|
27
|
+
return device.getAttribute(BridgedDeviceBasicInformation, 'reachable');
|
|
28
|
+
if (device.mode === 'server' && device.serverNode?.state.basicInformation.reachable !== undefined)
|
|
29
|
+
return device.serverNode.state.basicInformation.reachable;
|
|
30
|
+
const owner = device.ownerOfType(ServerNode.RootEndpoint);
|
|
31
|
+
if (owner?.state.basicInformation.reachable !== undefined)
|
|
32
|
+
return owner.state.basicInformation.reachable;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
export function getPowerSource(endpoint) {
|
|
36
|
+
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
37
|
+
return undefined;
|
|
38
|
+
const powerSource = (device) => {
|
|
39
|
+
const featureMap = featuresFor(device, PowerSource);
|
|
40
|
+
if (featureMap.wired) {
|
|
41
|
+
const wiredCurrentType = device.getAttribute(PowerSource.id, 'wiredCurrentType');
|
|
42
|
+
return ['ac', 'dc'][wiredCurrentType];
|
|
43
|
+
}
|
|
44
|
+
if (featureMap.battery) {
|
|
45
|
+
const batChargeLevel = device.getAttribute(PowerSource.id, 'batChargeLevel');
|
|
46
|
+
return ['ok', 'warning', 'critical'][batChargeLevel];
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
};
|
|
50
|
+
if (endpoint.hasClusterServer(PowerSource.id))
|
|
51
|
+
return powerSource(endpoint);
|
|
52
|
+
for (const child of endpoint.getChildEndpoints()) {
|
|
53
|
+
if (child.hasClusterServer(PowerSource.id))
|
|
54
|
+
return powerSource(child);
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
export function getBatteryLevel(endpoint) {
|
|
59
|
+
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
60
|
+
return undefined;
|
|
61
|
+
const batteryLevel = (device) => {
|
|
62
|
+
const featureMap = featuresFor(device, PowerSource);
|
|
63
|
+
if (featureMap.battery) {
|
|
64
|
+
const batChargeLevel = device.getAttribute(PowerSource, 'batPercentRemaining');
|
|
65
|
+
return isValidNumber(batChargeLevel, 0, 200) ? batChargeLevel / 2 : undefined;
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
};
|
|
69
|
+
if (endpoint.hasClusterServer(PowerSource.id))
|
|
70
|
+
return batteryLevel(endpoint);
|
|
71
|
+
for (const child of endpoint.getChildEndpoints()) {
|
|
72
|
+
if (child.hasClusterServer(PowerSource.id))
|
|
73
|
+
return batteryLevel(child);
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
export function getClusterTextFromDevice(device) {
|
|
78
|
+
if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
|
|
79
|
+
return '';
|
|
80
|
+
let attributes = '';
|
|
81
|
+
const getUserLabel = (device) => {
|
|
82
|
+
const labelList = getAttribute(device, UserLabel, 'labelList');
|
|
83
|
+
if (labelList) {
|
|
84
|
+
const composed = labelList.find((entry) => entry.label === 'composed');
|
|
85
|
+
if (composed)
|
|
86
|
+
return 'Composed: ' + composed.value;
|
|
87
|
+
}
|
|
88
|
+
return '';
|
|
89
|
+
};
|
|
90
|
+
const getFixedLabel = (device) => {
|
|
91
|
+
const labelList = getAttribute(device, FixedLabel, 'labelList');
|
|
92
|
+
if (labelList) {
|
|
93
|
+
const composed = labelList.find((entry) => entry.label === 'composed');
|
|
94
|
+
if (composed)
|
|
95
|
+
return 'Composed: ' + composed.value;
|
|
96
|
+
}
|
|
97
|
+
return '';
|
|
98
|
+
};
|
|
99
|
+
const getMeasurementText = (value, scale, unit) => {
|
|
100
|
+
if (value === null)
|
|
101
|
+
return 'unknown';
|
|
102
|
+
if (isValidNumber(value) || typeof value === 'bigint')
|
|
103
|
+
return `${Number(value) / scale}${unit}`;
|
|
104
|
+
return '';
|
|
105
|
+
};
|
|
106
|
+
const getEnergyText = (value) => {
|
|
107
|
+
if (value === null)
|
|
108
|
+
return 'unknown';
|
|
109
|
+
if (isValidObject(value) && 'energy' in value && (isValidNumber(value.energy) || typeof value.energy === 'bigint'))
|
|
110
|
+
return `${Number(value.energy) / 1_000_000}kWh`;
|
|
111
|
+
return '';
|
|
112
|
+
};
|
|
113
|
+
const appendMeasurement = (label, value) => {
|
|
114
|
+
if (value)
|
|
115
|
+
attributes += `${label}: ${value} `;
|
|
116
|
+
};
|
|
117
|
+
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
118
|
+
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
119
|
+
return;
|
|
120
|
+
if (clusterName === 'descriptor' && attributeName === 'clientList' && isValidArray(attributeValue, 1))
|
|
121
|
+
attributes += `Client cluster(s): [${attributeValue.map((id) => getClusterNameById(id)).join(', ')}] `;
|
|
122
|
+
if (clusterName === 'binding' && attributeName === 'binding' && isValidArray(attributeValue)) {
|
|
123
|
+
if (attributeValue.length === 0)
|
|
124
|
+
attributes += `Bound cluster(s): none `;
|
|
125
|
+
else {
|
|
126
|
+
const targets = attributeValue;
|
|
127
|
+
const formatted = targets.map((target) => {
|
|
128
|
+
const parts = [];
|
|
129
|
+
if (target.node !== undefined)
|
|
130
|
+
parts.push(`node: ${target.node}`);
|
|
131
|
+
if (target.group !== undefined)
|
|
132
|
+
parts.push(`group: ${target.group}`);
|
|
133
|
+
if (target.endpoint !== undefined)
|
|
134
|
+
parts.push(`endpoint: ${target.endpoint}`);
|
|
135
|
+
if (target.cluster !== undefined)
|
|
136
|
+
parts.push(`cluster: ${getClusterNameById(target.cluster)}`);
|
|
137
|
+
if (target.fabricIndex !== undefined)
|
|
138
|
+
parts.push(`fabricIndex: ${target.fabricIndex}`);
|
|
139
|
+
return `[${parts.join(', ')}]`;
|
|
140
|
+
});
|
|
141
|
+
attributes += `Bound cluster(s): ${formatted.join(' ')} `;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
145
|
+
attributes += `OnOff: ${attributeValue} `;
|
|
146
|
+
if (clusterName === 'switch' && attributeName === 'currentPosition')
|
|
147
|
+
attributes += `Position: ${attributeValue} `;
|
|
148
|
+
if (clusterName === 'windowCovering' && attributeName === 'currentPositionLiftPercent100ths' && isValidNumber(attributeValue, 0, 10000))
|
|
149
|
+
attributes += `Cover position: ${attributeValue / 100}% `;
|
|
150
|
+
if (clusterName === 'doorLock' && attributeName === 'lockState')
|
|
151
|
+
attributes += `State: ${attributeValue === 1 ? 'Locked' : 'Not locked'} `;
|
|
152
|
+
if (clusterName === 'closureControl' && attributeName === 'overallCurrentState' && attributeValue === null)
|
|
153
|
+
attributes += `Position: unknown Latch: unknown Speed: unknown SecureState: unknown `;
|
|
154
|
+
if (clusterName === 'closureControl' && attributeName === 'overallCurrentState' && isValidObject(attributeValue)) {
|
|
155
|
+
const overallCurrentState = attributeValue;
|
|
156
|
+
attributes += `Position: ${getEnumDescription(ClosureControl.CurrentPosition, overallCurrentState.position, { fallback: 'unknown' })} `;
|
|
157
|
+
if (overallCurrentState.latch !== undefined)
|
|
158
|
+
attributes += `Latch: ${overallCurrentState.latch} `;
|
|
159
|
+
if (overallCurrentState.speed !== undefined)
|
|
160
|
+
attributes += `Speed: ${getEnumDescription(ThreeLevelAuto, overallCurrentState.speed)} `;
|
|
161
|
+
attributes += `SecureState: ${overallCurrentState.secureState ?? 'unknown'} `;
|
|
162
|
+
}
|
|
163
|
+
if (clusterName === 'thermostat' && attributeName === 'localTemperature' && isValidNumber(attributeValue))
|
|
164
|
+
attributes += `Temperature: ${attributeValue / 100}°C `;
|
|
165
|
+
if (clusterName === 'thermostat' && attributeName === 'occupiedHeatingSetpoint' && isValidNumber(attributeValue))
|
|
166
|
+
attributes += `Heat to: ${attributeValue / 100}°C `;
|
|
167
|
+
if (clusterName === 'thermostat' && attributeName === 'occupiedCoolingSetpoint' && isValidNumber(attributeValue))
|
|
168
|
+
attributes += `Cool to: ${attributeValue / 100}°C `;
|
|
169
|
+
const modeClusters = new Set(['modeSelect', 'rvcRunMode', 'rvcCleanMode', 'laundryWasherMode', 'ovenMode', 'microwaveOvenMode', 'deviceEnergyManagementMode']);
|
|
170
|
+
if (modeClusters.has(clusterName) && attributeName === 'currentMode') {
|
|
171
|
+
const supportedModes = getAttribute(device, clusterName, 'supportedModes');
|
|
172
|
+
const supportedMode = supportedModes?.find((mode) => mode.mode === attributeValue);
|
|
173
|
+
if (supportedMode)
|
|
174
|
+
attributes += `Mode: ${supportedMode.label} `;
|
|
175
|
+
}
|
|
176
|
+
if (clusterName === 'operationalState' && attributeName === 'operationalState')
|
|
177
|
+
attributes += `OpState: ${getEnumDescription(OperationalState.OperationalStateEnum, attributeValue)} `;
|
|
178
|
+
if (clusterName === 'rvcOperationalState' && attributeName === 'operationalState')
|
|
179
|
+
attributes += `OpState: ${getEnumDescription(RvcOperationalState.OperationalState, attributeValue)} `;
|
|
180
|
+
if (clusterName === 'pumpConfigurationAndControl' && attributeName === 'operationMode')
|
|
181
|
+
attributes += `Mode: ${attributeValue} `;
|
|
182
|
+
if (clusterName === 'valveConfigurationAndControl' && attributeName === 'currentState')
|
|
183
|
+
attributes += `State: ${getEnumDescription(ValveConfigurationAndControl.ValveState, attributeValue, { fallback: 'unknown' })} `;
|
|
184
|
+
if (clusterName === 'levelControl' && attributeName === 'currentLevel')
|
|
185
|
+
attributes += `Level: ${attributeValue} `;
|
|
186
|
+
if (clusterName === 'applicationBasic' && attributeName === 'applicationName')
|
|
187
|
+
attributes += `App: ${attributeValue} `;
|
|
188
|
+
if (clusterName === 'colorControl' && attributeName === 'colorMode' && isValidNumber(attributeValue, 0, 2))
|
|
189
|
+
attributes += `Mode: ${['HS', 'XY', 'CT'][attributeValue]} `;
|
|
190
|
+
if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 0 && attributeName === 'currentHue' && isValidNumber(attributeValue))
|
|
191
|
+
attributes += `Hue: ${Math.round(attributeValue)} `;
|
|
192
|
+
if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 0 && attributeName === 'currentSaturation' && isValidNumber(attributeValue))
|
|
193
|
+
attributes += `Saturation: ${Math.round(attributeValue)} `;
|
|
194
|
+
if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 1 && attributeName === 'currentX' && isValidNumber(attributeValue))
|
|
195
|
+
attributes += `X: ${Math.round(attributeValue / 655.36) / 100} `;
|
|
196
|
+
if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 1 && attributeName === 'currentY' && isValidNumber(attributeValue))
|
|
197
|
+
attributes += `Y: ${Math.round(attributeValue / 655.36) / 100} `;
|
|
198
|
+
if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 2 && attributeName === 'colorTemperatureMireds' && isValidNumber(attributeValue))
|
|
199
|
+
attributes += `ColorTemp: ${Math.round(attributeValue)} `;
|
|
200
|
+
if (clusterName === 'booleanState' && attributeName === 'stateValue')
|
|
201
|
+
attributes += `Contact: ${attributeValue} `;
|
|
202
|
+
if (clusterName === 'booleanStateConfiguration' && attributeName === 'alarmsActive' && isValidObject(attributeValue))
|
|
203
|
+
attributes += `Active alarms: ${stringify(attributeValue)} `;
|
|
204
|
+
if (clusterName === 'smokeCoAlarm' && attributeName === 'smokeState')
|
|
205
|
+
attributes += `Smoke: ${getEnumDescription(SmokeCoAlarm.AlarmState, attributeValue)} `;
|
|
206
|
+
if (clusterName === 'smokeCoAlarm' && attributeName === 'coState')
|
|
207
|
+
attributes += `Co: ${getEnumDescription(SmokeCoAlarm.AlarmState, attributeValue)} `;
|
|
208
|
+
if (clusterName === 'fanControl' && attributeName === 'fanMode')
|
|
209
|
+
attributes += `Mode: ${getEnumDescription(FanControl.FanMode, attributeValue)} `;
|
|
210
|
+
if (clusterName === 'fanControl' && attributeName === 'percentCurrent')
|
|
211
|
+
attributes += `Percent: ${attributeValue}% `;
|
|
212
|
+
if (clusterName === 'fanControl' && attributeName === 'speedCurrent')
|
|
213
|
+
attributes += `Speed: ${attributeValue} `;
|
|
214
|
+
if (clusterName === 'fanControl' && attributeName === 'rockSetting' && isValidObject(attributeValue))
|
|
215
|
+
attributes += `Rock: ${stringify(attributeValue)} `;
|
|
216
|
+
if (clusterName === 'fanControl' && attributeName === 'windSetting' && isValidObject(attributeValue))
|
|
217
|
+
attributes += `Wind: ${stringify(attributeValue)} `;
|
|
218
|
+
if (clusterName === 'fanControl' && attributeName === 'airflowDirection')
|
|
219
|
+
attributes += `Direction: ${getEnumDescription(FanControl.AirflowDirection, attributeValue)} `;
|
|
220
|
+
if (clusterName === 'hepaFilterMonitoring' && attributeName === 'condition')
|
|
221
|
+
attributes += `Hepa filter: ${attributeValue}% `;
|
|
222
|
+
if (clusterName === 'activatedCarbonFilterMonitoring' && attributeName === 'condition')
|
|
223
|
+
attributes += `Carbon filter: ${attributeValue}% `;
|
|
224
|
+
if (clusterName === 'waterTankLevelMonitoring' && attributeName === 'condition')
|
|
225
|
+
attributes += `Water tank: ${attributeValue}% `;
|
|
226
|
+
if (clusterName === 'temperatureAlarm' && attributeName === 'state' && isValidObject(attributeValue))
|
|
227
|
+
attributes += `Temp alarm: ${stringify(attributeValue)} `;
|
|
228
|
+
if (clusterName === 'occupancySensing' && attributeName === 'occupancy' && isValidObject(attributeValue, 1))
|
|
229
|
+
attributes += `Occupancy: ${attributeValue.occupied} `;
|
|
230
|
+
if (clusterName === 'illuminanceMeasurement' && attributeName === 'measuredValue') {
|
|
231
|
+
if (attributeValue === null)
|
|
232
|
+
attributes += `Illuminance: unknown `;
|
|
233
|
+
else if (isValidNumber(attributeValue))
|
|
234
|
+
attributes += `Illuminance: ${Math.round(Math.max(Math.pow(10, attributeValue / 10000), 0))} lx `;
|
|
235
|
+
}
|
|
236
|
+
if (clusterName === 'airQuality' && attributeName === 'airQuality')
|
|
237
|
+
attributes += `Air quality: ${getEnumDescription(AirQuality.AirQualityEnum, attributeValue)} `;
|
|
238
|
+
if (clusterName === 'totalVolatileOrganicCompoundsConcentrationMeasurement' && attributeName === 'measuredValue')
|
|
239
|
+
appendMeasurement('Voc', getMeasurementText(attributeValue, 1, ''));
|
|
240
|
+
if (clusterName === 'pm1ConcentrationMeasurement' && attributeName === 'measuredValue')
|
|
241
|
+
appendMeasurement('Pm1', getMeasurementText(attributeValue, 1, ''));
|
|
242
|
+
if (clusterName === 'pm25ConcentrationMeasurement' && attributeName === 'measuredValue')
|
|
243
|
+
appendMeasurement('Pm2.5', getMeasurementText(attributeValue, 1, ''));
|
|
244
|
+
if (clusterName === 'pm10ConcentrationMeasurement' && attributeName === 'measuredValue')
|
|
245
|
+
appendMeasurement('Pm10', getMeasurementText(attributeValue, 1, ''));
|
|
246
|
+
if (clusterName === 'formaldehydeConcentrationMeasurement' && attributeName === 'measuredValue')
|
|
247
|
+
appendMeasurement('CH₂O', getMeasurementText(attributeValue, 1, ''));
|
|
248
|
+
if (clusterName === 'temperatureMeasurement' && attributeName === 'measuredValue')
|
|
249
|
+
appendMeasurement('Temperature', getMeasurementText(attributeValue, 100, ' °C'));
|
|
250
|
+
if (clusterName === 'relativeHumidityMeasurement' && attributeName === 'measuredValue')
|
|
251
|
+
appendMeasurement('Humidity', getMeasurementText(attributeValue, 100, '%'));
|
|
252
|
+
if (clusterName === 'pressureMeasurement' && attributeName === 'measuredValue')
|
|
253
|
+
appendMeasurement('Pressure', getMeasurementText(attributeValue, 1, ' hPa'));
|
|
254
|
+
if (clusterName === 'flowMeasurement' && attributeName === 'measuredValue')
|
|
255
|
+
appendMeasurement('Flow', getMeasurementText(attributeValue, 10, ' m³/h'));
|
|
256
|
+
if (clusterId === SoilMeasurement.id && attributeName === 'soilMoistureMeasuredValue')
|
|
257
|
+
appendMeasurement('Soil moisture', getMeasurementText(attributeValue, 1, '%'));
|
|
258
|
+
if (clusterName === 'electricalPowerMeasurement' && attributeName === 'voltage')
|
|
259
|
+
appendMeasurement('Voltage', getMeasurementText(attributeValue, 1_000, 'V'));
|
|
260
|
+
if (clusterName === 'electricalPowerMeasurement' && attributeName === 'activeCurrent')
|
|
261
|
+
appendMeasurement('Current', getMeasurementText(attributeValue, 1_000, 'A'));
|
|
262
|
+
if (clusterName === 'electricalPowerMeasurement' && attributeName === 'activePower')
|
|
263
|
+
appendMeasurement('Power', getMeasurementText(attributeValue, 1_000_000, 'kW'));
|
|
264
|
+
if (clusterName === 'electricalPowerMeasurement' && attributeName === 'frequency')
|
|
265
|
+
appendMeasurement('Frequency', getMeasurementText(attributeValue, 1_000, 'Hz'));
|
|
266
|
+
if (clusterName === 'electricalEnergyMeasurement' && attributeName === 'cumulativeEnergyImported')
|
|
267
|
+
appendMeasurement('Imported', getEnergyText(attributeValue));
|
|
268
|
+
if (clusterName === 'electricalEnergyMeasurement' && attributeName === 'cumulativeEnergyExported')
|
|
269
|
+
appendMeasurement('Exported', getEnergyText(attributeValue));
|
|
270
|
+
if (clusterName === 'deviceEnergyManagement' && attributeName === 'esaCanGenerate')
|
|
271
|
+
attributes += `ESA can generate: ${attributeValue} `;
|
|
272
|
+
if (clusterName === 'deviceEnergyManagement' && attributeName === 'esaState')
|
|
273
|
+
attributes += `ESA state: ${DeviceEnergyManagement.EsaState[attributeValue]} `;
|
|
274
|
+
if (clusterName === 'fixedLabel' && attributeName === 'labelList')
|
|
275
|
+
attributes += `${getFixedLabel(device)} `;
|
|
276
|
+
if (clusterName === 'userLabel' && attributeName === 'labelList')
|
|
277
|
+
attributes += `${getUserLabel(device)} `;
|
|
278
|
+
});
|
|
279
|
+
return attributes.trimStart().trimEnd();
|
|
280
|
+
}
|
|
281
|
+
export function setAdvertisingNode(storeId, advertiseTime) {
|
|
282
|
+
advertisingNodes.set(storeId, advertiseTime);
|
|
283
|
+
}
|
|
284
|
+
export function deleteAdvertisingNode(storeId) {
|
|
285
|
+
advertisingNodes.delete(storeId);
|
|
286
|
+
}
|
|
287
|
+
export function clearAdvertisingNodes() {
|
|
288
|
+
advertisingNodes.clear();
|
|
289
|
+
}
|
|
290
|
+
export function getServerNodeData(serverNode) {
|
|
291
|
+
const advertiseTime = advertisingNodes.get(serverNode.id) ?? 0;
|
|
292
|
+
return {
|
|
293
|
+
id: serverNode.id,
|
|
294
|
+
online: serverNode.lifecycle.isOnline,
|
|
295
|
+
commissioned: serverNode.state.commissioning.commissioned,
|
|
296
|
+
advertising: advertiseTime > 0,
|
|
297
|
+
advertiseTime,
|
|
298
|
+
windowStatus: serverNode.state.administratorCommissioning.windowStatus,
|
|
299
|
+
qrPairingCode: serverNode.state.commissioning.pairingCodes.qrPairingCode,
|
|
300
|
+
manualPairingCode: serverNode.state.commissioning.pairingCodes.manualPairingCode,
|
|
301
|
+
fabricInformations: sanitizeFabricInformations(Object.values(serverNode.state.commissioning.fabrics)),
|
|
302
|
+
sessionInformations: sanitizeSessionInformation(Object.values(serverNode.state.sessions.sessions)),
|
|
303
|
+
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
export function sanitizeFabricInformations(fabricInfo) {
|
|
307
|
+
return fabricInfo.map((info) => {
|
|
308
|
+
return {
|
|
309
|
+
fabricIndex: info.fabricIndex,
|
|
310
|
+
fabricId: info.fabricId.toString(),
|
|
311
|
+
nodeId: info.nodeId.toString(),
|
|
312
|
+
rootNodeId: info.rootNodeId.toString(),
|
|
313
|
+
rootVendorId: info.rootVendorId,
|
|
314
|
+
rootVendorName: getVendorIdName(info.rootVendorId),
|
|
315
|
+
label: info.label,
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function toIsoTimestamp(timestamp) {
|
|
320
|
+
if (!isValidNumber(timestamp, 1) || Number.isNaN(new Date(timestamp).getTime()))
|
|
321
|
+
return '';
|
|
322
|
+
return new Date(timestamp).toISOString();
|
|
323
|
+
}
|
|
324
|
+
export function sanitizeSessionInformation(sessions) {
|
|
325
|
+
return sessions
|
|
326
|
+
.filter((session) => session.isPeerActive)
|
|
327
|
+
.map((session) => {
|
|
328
|
+
return {
|
|
329
|
+
name: session.name,
|
|
330
|
+
nodeId: session.nodeId.toString(),
|
|
331
|
+
peerNodeId: session.peerNodeId.toString(),
|
|
332
|
+
fabric: session.fabric
|
|
333
|
+
? {
|
|
334
|
+
fabricIndex: session.fabric.fabricIndex,
|
|
335
|
+
fabricId: session.fabric.fabricId.toString(),
|
|
336
|
+
nodeId: session.fabric.nodeId.toString(),
|
|
337
|
+
rootNodeId: session.fabric.rootNodeId.toString(),
|
|
338
|
+
rootVendorId: session.fabric.rootVendorId,
|
|
339
|
+
rootVendorName: getVendorIdName(session.fabric.rootVendorId),
|
|
340
|
+
label: session.fabric.label,
|
|
341
|
+
}
|
|
342
|
+
: undefined,
|
|
343
|
+
isPeerActive: session.isPeerActive,
|
|
344
|
+
lastInteractionTimestamp: toIsoTimestamp(session.lastInteractionTimestamp),
|
|
345
|
+
lastActiveTimestamp: toIsoTimestamp(session.lastActiveTimestamp),
|
|
346
|
+
numberOfActiveSubscriptions: session.numberOfActiveSubscriptions,
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
export function getVendorIdName(vendorId) {
|
|
351
|
+
if (!vendorId)
|
|
352
|
+
return '';
|
|
353
|
+
let vendorName = '(Unknown vendorId)';
|
|
354
|
+
switch (vendorId) {
|
|
355
|
+
case 4937:
|
|
356
|
+
vendorName = '(AppleHome)';
|
|
357
|
+
break;
|
|
358
|
+
case 4996:
|
|
359
|
+
vendorName = '(AppleKeyChain)';
|
|
360
|
+
break;
|
|
361
|
+
case 4362:
|
|
362
|
+
vendorName = '(SmartThings)';
|
|
363
|
+
break;
|
|
364
|
+
case 4939:
|
|
365
|
+
vendorName = '(HomeAssistant)';
|
|
366
|
+
break;
|
|
367
|
+
case 24582:
|
|
368
|
+
vendorName = '(GoogleHome)';
|
|
369
|
+
break;
|
|
370
|
+
case 4631:
|
|
371
|
+
vendorName = '(Alexa)';
|
|
372
|
+
break;
|
|
373
|
+
case 4701:
|
|
374
|
+
vendorName = '(Tuya)';
|
|
375
|
+
break;
|
|
376
|
+
case 4718:
|
|
377
|
+
vendorName = '(Xiaomi)';
|
|
378
|
+
break;
|
|
379
|
+
case 4742:
|
|
380
|
+
vendorName = '(eWeLink)';
|
|
381
|
+
break;
|
|
382
|
+
case 5264:
|
|
383
|
+
vendorName = '(Shelly)';
|
|
384
|
+
break;
|
|
385
|
+
case 0x1488:
|
|
386
|
+
vendorName = '(ShortcutLabsFlic)';
|
|
387
|
+
break;
|
|
388
|
+
case 65521:
|
|
389
|
+
vendorName = '(MatterTest)';
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
return vendorName;
|
|
393
|
+
}
|