matterbridge-lg-thinq-plugin 0.1.0
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/.claude/CLAUDE.md +43 -0
- package/.claude/agents/compiler.md +91 -0
- package/.claude/agents/direct-executor.md +98 -0
- package/.claude/agents/documenter.md +78 -0
- package/.claude/agents/finalizer.md +233 -0
- package/.claude/agents/implementer.md +112 -0
- package/.claude/agents/investigator.md +100 -0
- package/.claude/agents/release-manager.md +203 -0
- package/.claude/agents/reviewer.md +131 -0
- package/.claude/agents/technical-architect.md +403 -0
- package/.claude/agents/test-writer.md +159 -0
- package/.claude/agents/wiki-manager.md +147 -0
- package/.claude/instructions/agent-prompts.md +90 -0
- package/.claude/instructions/shared-rules.md +11 -0
- package/.claude/instructions/team-orchestrator-policy.md +165 -0
- package/.claude/memory.md +186 -0
- package/.claude/output-styles/engineer-manager.md +38 -0
- package/.claude/settings.json +40 -0
- package/.claude/settings.local.json +30 -0
- package/.claude/skills/load-policy/SKILL.md +13 -0
- package/.claude/skills/ref-idea/SKILL.md +44 -0
- package/.claude/skills/status-of/SKILL.md +38 -0
- package/.claude/skills/workspace-console/SKILL.md +49 -0
- package/.claude/statusline-command.sh +130 -0
- package/.claude/statusline.md +53 -0
- package/.claude/templates/approval-packet.template.html +171 -0
- package/.claude/templates/reference-workspaces.md +16 -0
- package/.claude/tools/cli-runner-server.cjs +220 -0
- package/.claude/tools/discord-send-server.cjs +188 -0
- package/.claude/tools/github-release-server.cjs +181 -0
- package/.claude/tools/glob-grep-server.cjs +267 -0
- package/.claude/tools/read-log-server.cjs +175 -0
- package/.github/codeql/codeql-config.yml +14 -0
- package/.github/workflows/build.yml +54 -0
- package/.github/workflows/codeql.yml +59 -0
- package/.github/workflows/coverage.yml +70 -0
- package/.github/workflows/publish.yml +103 -0
- package/.mcp.json +34 -0
- package/CHANGELOG.md +195 -0
- package/LICENSE +21 -0
- package/dist/cli/commands/devices.js +52 -0
- package/dist/cli/commands/energy.js +122 -0
- package/dist/cli/commands/login.js +54 -0
- package/dist/cli/help.js +50 -0
- package/dist/cli/main.js +56 -0
- package/dist/cli/session.js +13 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utils.js +29 -0
- package/dist/cli.js +2 -0
- package/dist/constants/index.js +1 -0
- package/dist/core/domain/entities/ThinqDevice.js +43 -0
- package/dist/core/domain/value-objects/AirConditionerCapabilities.js +22 -0
- package/dist/core/domain/value-objects/ThinqFilterState.js +35 -0
- package/dist/core/domain/value-objects/ThinqSnapshot.js +125 -0
- package/dist/errors/AuthenticationError.js +26 -0
- package/dist/errors/BaseError.js +23 -0
- package/dist/errors/index.js +2 -0
- package/dist/model/LgThinkqPluginPlatformConfig.js +23 -0
- package/dist/model/LgThinqPluginPlatformConfig.js +23 -0
- package/dist/module.js +212 -0
- package/dist/platform/deviceRegistry.js +26 -0
- package/dist/platform/platformConfigManager.js +89 -0
- package/dist/platform/platformState.js +9 -0
- package/dist/platform/thinq/thinqAirConditionerCommandHandlers.js +190 -0
- package/dist/platform/thinq/thinqAirConditionerEndpointFactory.js +53 -0
- package/dist/platform/thinq/thinqAirConditionerFilterResetCommandHandler.js +54 -0
- package/dist/platform/thinq/thinqAirConditionerFilterStateSync.js +19 -0
- package/dist/platform/thinq/thinqAirConditionerStateSync.js +205 -0
- package/dist/platform/thinq/thinqDeviceConfigReconciler.js +26 -0
- package/dist/platform/thinq/thinqDeviceConfigurator.js +124 -0
- package/dist/platform/thinq/thinqDeviceDiscovery.js +33 -0
- package/dist/platform/thinq/thinqWasherCommandHandlers.js +54 -0
- package/dist/platform/thinq/thinqWasherEndpointFactory.js +6 -0
- package/dist/platform/thinq/thinqWasherStateSync.js +31 -0
- package/dist/platform/thinq/thinqWasherStopCommandResolver.js +38 -0
- package/dist/services/authentication/AccountAuthStrategy.js +135 -0
- package/dist/services/authentication/AuthContext.js +1 -0
- package/dist/services/authentication/AuthenticationCoordinator.js +29 -0
- package/dist/services/authentication/IAuthStrategy.js +1 -0
- package/dist/services/authentication/TokenAuthStrategy.js +27 -0
- package/dist/services/authentication/UserDataRepository.js +32 -0
- package/dist/services/thinq/gateway.js +61 -0
- package/dist/services/thinq/mqtt/mqttCertificate.js +63 -0
- package/dist/services/thinq/mqtt/mqttConnectionEvents.js +37 -0
- package/dist/services/thinq/mqtt/mqttKeyRepository.js +33 -0
- package/dist/services/thinq/mqtt/mqttRetry.js +25 -0
- package/dist/services/thinq/mqtt/thinqMqttListener.js +97 -0
- package/dist/services/thinq/serviceContainer.js +73 -0
- package/dist/services/thinq/session.js +48 -0
- package/dist/services/thinq/thinqApiClient.js +258 -0
- package/dist/services/thinq/thinqDeviceService.js +75 -0
- package/dist/services/thinq/thinqFilterMonitoringService.js +40 -0
- package/dist/settings.js +9 -0
- package/dist/tests/helpers/testUtils.js +87 -0
- package/dist/utils/mask.js +39 -0
- package/docs/README_CLI.md +166 -0
- package/eslint.config.js +194 -0
- package/matterbridge-lg-thinq-plugin.config.json +27 -0
- package/matterbridge-lg-thinq-plugin.schema.json +520 -0
- package/package.json +107 -0
- package/prettier.config.js +49 -0
- package/scripts/parse-junit-failures.mjs +45 -0
- package/scripts/parse-tsc-failures.mjs +48 -0
- package/scripts/run-build-local-summary.mjs +29 -0
- package/scripts/run-format-summary.mjs +36 -0
- package/scripts/run-lint-fix-summary.mjs +38 -0
- package/scripts/run-precommit-summary.mjs +94 -0
- package/src/cli/commands/devices.ts +67 -0
- package/src/cli/commands/energy.ts +160 -0
- package/src/cli/commands/login.ts +74 -0
- package/src/cli/help.ts +53 -0
- package/src/cli/main.ts +66 -0
- package/src/cli/session.ts +15 -0
- package/src/cli/types.ts +10 -0
- package/src/cli/utils.ts +30 -0
- package/src/cli.ts +3 -0
- package/src/constants/index.ts +8 -0
- package/src/core/domain/entities/ThinqDevice.test.ts +224 -0
- package/src/core/domain/entities/ThinqDevice.ts +74 -0
- package/src/core/domain/value-objects/AirConditionerCapabilities.ts +50 -0
- package/src/core/domain/value-objects/ThinqFilterState.ts +50 -0
- package/src/core/domain/value-objects/ThinqSnapshot.ts +162 -0
- package/src/errors/AuthenticationError.ts +37 -0
- package/src/errors/BaseError.ts +38 -0
- package/src/errors/index.ts +24 -0
- package/src/model/LgThinqPluginPlatformConfig.ts +94 -0
- package/src/module.ts +298 -0
- package/src/platform/deviceRegistry.ts +58 -0
- package/src/platform/platformConfigManager.ts +133 -0
- package/src/platform/platformState.ts +16 -0
- package/src/platform/thinq/thinqAirConditionerCommandHandlers.ts +291 -0
- package/src/platform/thinq/thinqAirConditionerEndpointFactory.ts +145 -0
- package/src/platform/thinq/thinqAirConditionerFilterResetCommandHandler.ts +96 -0
- package/src/platform/thinq/thinqAirConditionerFilterStateSync.ts +42 -0
- package/src/platform/thinq/thinqAirConditionerStateSync.ts +343 -0
- package/src/platform/thinq/thinqDeviceConfigReconciler.ts +42 -0
- package/src/platform/thinq/thinqDeviceConfigurator.test.ts +90 -0
- package/src/platform/thinq/thinqDeviceConfigurator.ts +191 -0
- package/src/platform/thinq/thinqDeviceDiscovery.test.ts +173 -0
- package/src/platform/thinq/thinqDeviceDiscovery.ts +51 -0
- package/src/platform/thinq/thinqWasherCommandHandlers.ts +120 -0
- package/src/platform/thinq/thinqWasherEndpointFactory.ts +37 -0
- package/src/platform/thinq/thinqWasherStateSync.ts +60 -0
- package/src/platform/thinq/thinqWasherStopCommandResolver.ts +62 -0
- package/src/services/authentication/AccountAuthStrategy.test.ts +358 -0
- package/src/services/authentication/AccountAuthStrategy.ts +209 -0
- package/src/services/authentication/AuthContext.ts +19 -0
- package/src/services/authentication/AuthenticationCoordinator.test.ts +217 -0
- package/src/services/authentication/AuthenticationCoordinator.ts +59 -0
- package/src/services/authentication/IAuthStrategy.ts +10 -0
- package/src/services/authentication/TokenAuthStrategy.test.ts +167 -0
- package/src/services/authentication/TokenAuthStrategy.ts +40 -0
- package/src/services/authentication/UserDataRepository.test.ts +149 -0
- package/src/services/authentication/UserDataRepository.ts +46 -0
- package/src/services/thinq/gateway.ts +86 -0
- package/src/services/thinq/mqtt/mqttCertificate.test.ts +344 -0
- package/src/services/thinq/mqtt/mqttCertificate.ts +102 -0
- package/src/services/thinq/mqtt/mqttConnectionEvents.test.ts +276 -0
- package/src/services/thinq/mqtt/mqttConnectionEvents.ts +76 -0
- package/src/services/thinq/mqtt/mqttKeyRepository.test.ts +164 -0
- package/src/services/thinq/mqtt/mqttKeyRepository.ts +43 -0
- package/src/services/thinq/mqtt/mqttRetry.test.ts +239 -0
- package/src/services/thinq/mqtt/mqttRetry.ts +43 -0
- package/src/services/thinq/mqtt/thinqMqttListener.test.ts +349 -0
- package/src/services/thinq/mqtt/thinqMqttListener.ts +120 -0
- package/src/services/thinq/serviceContainer.test.ts +203 -0
- package/src/services/thinq/serviceContainer.ts +105 -0
- package/src/services/thinq/session.test.ts +287 -0
- package/src/services/thinq/session.ts +69 -0
- package/src/services/thinq/thinqApiClient.test.ts +702 -0
- package/src/services/thinq/thinqApiClient.ts +418 -0
- package/src/services/thinq/thinqDeviceService.test.ts +415 -0
- package/src/services/thinq/thinqDeviceService.ts +101 -0
- package/src/services/thinq/thinqFilterMonitoringService.test.ts +264 -0
- package/src/services/thinq/thinqFilterMonitoringService.ts +53 -0
- package/src/settings.ts +18 -0
- package/src/tests/cli/commands/devices.test.ts +293 -0
- package/src/tests/cli/commands/login.test.ts +240 -0
- package/src/tests/cli/help.test.ts +46 -0
- package/src/tests/cli/main.test.ts +214 -0
- package/src/tests/cli/session.test.ts +198 -0
- package/src/tests/cli/utils.test.ts +194 -0
- package/src/tests/core/domain/value-objects/AirConditionerCapabilities.test.ts +685 -0
- package/src/tests/core/domain/value-objects/ThinqFilterState.test.ts +210 -0
- package/src/tests/core/domain/value-objects/ThinqSnapshot.test.ts +1035 -0
- package/src/tests/helpers/testUtils.ts +118 -0
- package/src/tests/model/LgThinqPluginPlatformConfig.test.ts +81 -0
- package/src/tests/platform/deviceRegistry.test.ts +177 -0
- package/src/tests/platform/platformConfigManager.test.ts +790 -0
- package/src/tests/platform/platformState.test.ts +41 -0
- package/src/tests/platform/thinq/thinqAirConditionerCommandHandlers.test.ts +879 -0
- package/src/tests/platform/thinq/thinqAirConditionerEndpointFactory.test.ts +993 -0
- package/src/tests/platform/thinq/thinqAirConditionerFilterResetCommandHandler.test.ts +508 -0
- package/src/tests/platform/thinq/thinqAirConditionerFilterStateSync.test.ts +212 -0
- package/src/tests/platform/thinq/thinqAirConditionerStateSync.test.ts +1586 -0
- package/src/tests/platform/thinq/thinqDeviceConfigReconciler.test.ts +269 -0
- package/src/tests/platform/thinq/thinqDeviceConfigurator.test.ts +492 -0
- package/src/tests/platform/thinq/thinqWasherCommandHandlers.test.ts +594 -0
- package/src/tests/platform/thinq/thinqWasherEndpointFactory.test.ts +84 -0
- package/src/tests/platform/thinq/thinqWasherStateSync.test.ts +373 -0
- package/src/tests/platform/thinq/thinqWasherStopCommandResolver.test.ts +392 -0
- package/src/tests/settings.test.ts +33 -0
- package/src/utils/mask.ts +50 -0
- package/tsconfig.base.json +25 -0
- package/tsconfig.json +9 -0
- package/tsconfig.production.json +13 -0
- package/tsconfig.vitest.json +5 -0
- package/vite.config.ts +43 -0
- package/wiki/memory-archive.md +12 -0
- package/wiki/reference-workspaces.md +23 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const DEFAULT_AIR_CONDITIONER_CAPABILITIES = {
|
|
2
|
+
supportsHeat: true,
|
|
3
|
+
supportsFanSpeedControl: true,
|
|
4
|
+
supportsSwingMode: false,
|
|
5
|
+
supportsHumiditySensor: false,
|
|
6
|
+
supportsAirQualitySensor: false,
|
|
7
|
+
supportsEnergyMonitoring: false,
|
|
8
|
+
supportsFilterMonitoring: false,
|
|
9
|
+
};
|
|
10
|
+
export function resolveAirConditionerCapabilities(devices, deviceId) {
|
|
11
|
+
const entry = devices?.find((device) => device.deviceId === deviceId);
|
|
12
|
+
const capabilities = entry?.capabilities;
|
|
13
|
+
return {
|
|
14
|
+
supportsHeat: capabilities?.supportsHeat ?? DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsHeat,
|
|
15
|
+
supportsFanSpeedControl: capabilities?.supportsFanSpeedControl ?? DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsFanSpeedControl,
|
|
16
|
+
supportsSwingMode: capabilities?.supportsSwingMode ?? DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsSwingMode,
|
|
17
|
+
supportsHumiditySensor: capabilities?.supportsHumiditySensor ?? DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsHumiditySensor,
|
|
18
|
+
supportsAirQualitySensor: capabilities?.supportsAirQualitySensor ?? DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsAirQualitySensor,
|
|
19
|
+
supportsEnergyMonitoring: capabilities?.supportsEnergyMonitoring ?? DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsEnergyMonitoring,
|
|
20
|
+
supportsFilterMonitoring: capabilities?.supportsFilterMonitoring ?? DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsFilterMonitoring,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const KEY_USE_TIME = 'airState.filterMngStates.useTime';
|
|
2
|
+
const KEY_MAX_TIME = 'airState.filterMngStates.maxTime';
|
|
3
|
+
const KEY_CHANGE_DATE = 'airState.filterMngStates.changeDate';
|
|
4
|
+
export class ThinqFilterState {
|
|
5
|
+
useTimeHours;
|
|
6
|
+
maxTimeHours;
|
|
7
|
+
changeDateRaw;
|
|
8
|
+
constructor(useTimeHours, maxTimeHours, changeDateRaw) {
|
|
9
|
+
this.useTimeHours = useTimeHours;
|
|
10
|
+
this.maxTimeHours = maxTimeHours;
|
|
11
|
+
this.changeDateRaw = changeDateRaw;
|
|
12
|
+
}
|
|
13
|
+
static fromRaw(data) {
|
|
14
|
+
const useTimeHours = readNumber(data, KEY_USE_TIME);
|
|
15
|
+
const maxTimeHours = readNumber(data, KEY_MAX_TIME);
|
|
16
|
+
if (useTimeHours === undefined || maxTimeHours === undefined || maxTimeHours <= 0) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return new ThinqFilterState(useTimeHours, maxTimeHours, readNumber(data, KEY_CHANGE_DATE));
|
|
20
|
+
}
|
|
21
|
+
get remainingPercent() {
|
|
22
|
+
const percent = ((this.maxTimeHours - this.useTimeHours) / this.maxTimeHours) * 100;
|
|
23
|
+
return Math.max(0, Math.min(100, Math.round(percent)));
|
|
24
|
+
}
|
|
25
|
+
get changeDate() {
|
|
26
|
+
return this.changeDateRaw;
|
|
27
|
+
}
|
|
28
|
+
get ratedMaxTimeHours() {
|
|
29
|
+
return this.maxTimeHours;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function readNumber(data, key) {
|
|
33
|
+
const value = data?.[key];
|
|
34
|
+
return typeof value === 'number' ? value : undefined;
|
|
35
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const WASHER_NOT_RUNNING_STATES = [
|
|
2
|
+
'COOLDOWN',
|
|
3
|
+
'POWEROFF',
|
|
4
|
+
'POWERFAIL',
|
|
5
|
+
'INITIAL',
|
|
6
|
+
'PAUSE',
|
|
7
|
+
'AUDIBLE_DIAGNOSIS',
|
|
8
|
+
'FIRMWARE',
|
|
9
|
+
'COURSE_DOWNLOAD',
|
|
10
|
+
'ERROR',
|
|
11
|
+
'END',
|
|
12
|
+
];
|
|
13
|
+
export class ThinqSnapshot {
|
|
14
|
+
data;
|
|
15
|
+
constructor(data) {
|
|
16
|
+
this.data = data;
|
|
17
|
+
}
|
|
18
|
+
get raw() {
|
|
19
|
+
return this.data;
|
|
20
|
+
}
|
|
21
|
+
has(key) {
|
|
22
|
+
const value = this.data[key];
|
|
23
|
+
return value !== undefined && value !== null;
|
|
24
|
+
}
|
|
25
|
+
get isPowerOn() {
|
|
26
|
+
return Number(this.data['airState.operation']) === 1;
|
|
27
|
+
}
|
|
28
|
+
get currentTemperatureCelsius() {
|
|
29
|
+
return this.readNumber('airState.tempState.current');
|
|
30
|
+
}
|
|
31
|
+
get targetTemperatureCelsius() {
|
|
32
|
+
return this.readNumber('airState.tempState.target');
|
|
33
|
+
}
|
|
34
|
+
get operationMode() {
|
|
35
|
+
return this.readNumber('airState.opMode');
|
|
36
|
+
}
|
|
37
|
+
get windStrength() {
|
|
38
|
+
return this.readNumber('airState.windStrength');
|
|
39
|
+
}
|
|
40
|
+
get online() {
|
|
41
|
+
return typeof this.data.online === 'boolean' ? this.data.online : undefined;
|
|
42
|
+
}
|
|
43
|
+
get isJetModeOn() {
|
|
44
|
+
return Number(this.data['airState.wMode.jet']) === 1;
|
|
45
|
+
}
|
|
46
|
+
get isQuietModeOn() {
|
|
47
|
+
return Number(this.data['airState.miscFuncState.silentAWHP']) === 1;
|
|
48
|
+
}
|
|
49
|
+
get isEnergySaveModeOn() {
|
|
50
|
+
return Number(this.data['airState.powerSave.basic']) === 1;
|
|
51
|
+
}
|
|
52
|
+
get isAirCleanModeOn() {
|
|
53
|
+
return Number(this.data['airState.wMode.airClean']) === 1;
|
|
54
|
+
}
|
|
55
|
+
get isLedOn() {
|
|
56
|
+
return Number(this.data['airState.lightingState.displayControl']) === 1;
|
|
57
|
+
}
|
|
58
|
+
get isVerticalSwingOn() {
|
|
59
|
+
return Number(this.data['airState.wDir.vStep']) === 100;
|
|
60
|
+
}
|
|
61
|
+
get isHorizontalSwingOn() {
|
|
62
|
+
return Number(this.data['airState.wDir.hStep']) === 100;
|
|
63
|
+
}
|
|
64
|
+
get humidityPercent() {
|
|
65
|
+
const value = this.readNumber('airState.humidity.current');
|
|
66
|
+
if (value === undefined) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return value > 100 ? value / 10 : value;
|
|
70
|
+
}
|
|
71
|
+
get airQualityOverall() {
|
|
72
|
+
return this.readNumber('airState.quality.overall');
|
|
73
|
+
}
|
|
74
|
+
get pm25() {
|
|
75
|
+
return this.readNumber('airState.quality.PM2');
|
|
76
|
+
}
|
|
77
|
+
get pm10() {
|
|
78
|
+
return this.readNumber('airState.quality.PM10');
|
|
79
|
+
}
|
|
80
|
+
get powerConsumptionWatts() {
|
|
81
|
+
const value = this.readNumber('airState.energy.onCurrent');
|
|
82
|
+
return value === undefined || Number.isNaN(value) ? undefined : value;
|
|
83
|
+
}
|
|
84
|
+
get washerDryer() {
|
|
85
|
+
const value = this.data['washerDryer'];
|
|
86
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
87
|
+
}
|
|
88
|
+
get hasWasherDryer() {
|
|
89
|
+
return this.washerDryer !== undefined;
|
|
90
|
+
}
|
|
91
|
+
get washerRawState() {
|
|
92
|
+
const value = this.washerDryer?.['state'];
|
|
93
|
+
return typeof value === 'string' ? value : undefined;
|
|
94
|
+
}
|
|
95
|
+
get isWasherPowerOn() {
|
|
96
|
+
return this.washerRawState !== undefined && !['POWEROFF', 'POWERFAIL'].includes(this.washerRawState);
|
|
97
|
+
}
|
|
98
|
+
get isWasherRunning() {
|
|
99
|
+
return (this.isWasherPowerOn &&
|
|
100
|
+
this.washerRawState !== undefined &&
|
|
101
|
+
!WASHER_NOT_RUNNING_STATES.includes(this.washerRawState));
|
|
102
|
+
}
|
|
103
|
+
get isWasherError() {
|
|
104
|
+
return this.washerRawState === 'ERROR';
|
|
105
|
+
}
|
|
106
|
+
get washerRemainingDurationSeconds() {
|
|
107
|
+
const hours = this.readWasherNumber('remainTimeHour');
|
|
108
|
+
const minutes = this.readWasherNumber('remainTimeMinute');
|
|
109
|
+
if (hours === undefined && minutes === undefined) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
if (!this.isWasherRunning) {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
return (hours ?? 0) * 3600 + (minutes ?? 0) * 60;
|
|
116
|
+
}
|
|
117
|
+
readNumber(key) {
|
|
118
|
+
const value = this.data[key];
|
|
119
|
+
return typeof value === 'number' ? value : undefined;
|
|
120
|
+
}
|
|
121
|
+
readWasherNumber(key) {
|
|
122
|
+
const value = this.washerDryer?.[key];
|
|
123
|
+
return typeof value === 'number' ? value : undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BaseError } from './BaseError.js';
|
|
2
|
+
export class AuthenticationError extends BaseError {
|
|
3
|
+
constructor(message, metadata) {
|
|
4
|
+
super(message, 'THINQ_AUTH_ERROR', 401, metadata);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class InvalidCredentialsError extends AuthenticationError {
|
|
8
|
+
constructor(username) {
|
|
9
|
+
super('Invalid LG ThinQ username or password. Please check your credentials.', {
|
|
10
|
+
reason: 'INVALID_CREDENTIALS',
|
|
11
|
+
username,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class TokenExpiredError extends AuthenticationError {
|
|
16
|
+
constructor() {
|
|
17
|
+
super('LG ThinQ authentication token has expired. Please log in again.', {
|
|
18
|
+
reason: 'TOKEN_EXPIRED',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class ManualProcessNeededError extends BaseError {
|
|
23
|
+
constructor(message = 'LG ThinQ requires a manual process (e.g. accepting new terms in the native LG app) before continuing.', metadata) {
|
|
24
|
+
super(message, 'MANUAL_PROCESS_NEEDED', 428, metadata);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class BaseError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
statusCode;
|
|
4
|
+
metadata;
|
|
5
|
+
constructor(message, code, statusCode, metadata) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.statusCode = statusCode;
|
|
9
|
+
this.metadata = metadata;
|
|
10
|
+
this.name = this.constructor.name;
|
|
11
|
+
Error.captureStackTrace(this, this.constructor);
|
|
12
|
+
}
|
|
13
|
+
toJSON() {
|
|
14
|
+
return {
|
|
15
|
+
name: this.name,
|
|
16
|
+
message: this.message,
|
|
17
|
+
code: this.code,
|
|
18
|
+
statusCode: this.statusCode,
|
|
19
|
+
metadata: this.metadata,
|
|
20
|
+
stack: this.stack,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function createDefaultAdvancedFeature() {
|
|
2
|
+
return {
|
|
3
|
+
settings: {
|
|
4
|
+
debug: false,
|
|
5
|
+
clearStorageOnStartup: false,
|
|
6
|
+
forceAuthentication: false,
|
|
7
|
+
unregisterOnShutdown: false,
|
|
8
|
+
overrideMatterConfiguration: false,
|
|
9
|
+
matterOverrideSettings: {
|
|
10
|
+
matterVendorName: 'Matterbridge',
|
|
11
|
+
matterVendorId: 0xfff1,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function createDefaultThinqConfig() {
|
|
17
|
+
return {
|
|
18
|
+
loginType: 'account',
|
|
19
|
+
country: 'US',
|
|
20
|
+
language: 'en-US',
|
|
21
|
+
devices: [],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function createDefaultAdvancedFeature() {
|
|
2
|
+
return {
|
|
3
|
+
settings: {
|
|
4
|
+
debug: false,
|
|
5
|
+
clearStorageOnStartup: false,
|
|
6
|
+
forceAuthentication: false,
|
|
7
|
+
unregisterOnShutdown: false,
|
|
8
|
+
overrideMatterConfiguration: false,
|
|
9
|
+
matterOverrideSettings: {
|
|
10
|
+
matterVendorName: 'Matterbridge',
|
|
11
|
+
matterVendorId: 0xfff1,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function createDefaultThinqConfig() {
|
|
17
|
+
return {
|
|
18
|
+
loginType: 'account',
|
|
19
|
+
country: 'US',
|
|
20
|
+
language: 'en-US',
|
|
21
|
+
devices: [],
|
|
22
|
+
};
|
|
23
|
+
}
|
package/dist/module.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import Path from 'node:path';
|
|
2
|
+
import { MatterbridgeDynamicPlatform } from 'matterbridge';
|
|
3
|
+
import NodePersist from 'node-persist';
|
|
4
|
+
import { UNREGISTER_DEVICES_DELAY_MS } from './constants/index.js';
|
|
5
|
+
import { isAirConditionerDevice, isWasherDevice } from './core/domain/entities/ThinqDevice.js';
|
|
6
|
+
import { ManualProcessNeededError } from './errors/index.js';
|
|
7
|
+
import { DeviceRegistry } from './platform/deviceRegistry.js';
|
|
8
|
+
import { PlatformConfigManager } from './platform/platformConfigManager.js';
|
|
9
|
+
import { PlatformState } from './platform/platformState.js';
|
|
10
|
+
import { applyThinqFilterStateToAirConditioner } from './platform/thinq/thinqAirConditionerFilterStateSync.js';
|
|
11
|
+
import { applyThinqSnapshotToAirConditioner } from './platform/thinq/thinqAirConditionerStateSync.js';
|
|
12
|
+
import { reconcileThinqDeviceConfigEntries } from './platform/thinq/thinqDeviceConfigReconciler.js';
|
|
13
|
+
import { applyThinqSnapshotToWasher } from './platform/thinq/thinqWasherStateSync.js';
|
|
14
|
+
import { ThinqServiceContainer } from './services/thinq/serviceContainer.js';
|
|
15
|
+
import { ThinqSession } from './services/thinq/session.js';
|
|
16
|
+
import { PLUGIN_NAME } from './settings.js';
|
|
17
|
+
export default function initializePlugin(matterbridge, log, config) {
|
|
18
|
+
return new LgThinqMatterbridgePlatform(matterbridge, log, config);
|
|
19
|
+
}
|
|
20
|
+
export class LgThinqMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
21
|
+
config;
|
|
22
|
+
persist;
|
|
23
|
+
registry;
|
|
24
|
+
configManager;
|
|
25
|
+
state;
|
|
26
|
+
thinqServices;
|
|
27
|
+
thinqPollingIntervalMs;
|
|
28
|
+
thinqDeviceKindById = new Map();
|
|
29
|
+
filterMonitoringDeviceIds = [];
|
|
30
|
+
constructor(matterbridge, logger, config) {
|
|
31
|
+
super(matterbridge, logger, config);
|
|
32
|
+
this.config = config;
|
|
33
|
+
logger.logLevel = this.config.advancedFeature.settings.debug ? "debug" : "info";
|
|
34
|
+
this.log.info('Initializing platform:', this.config.name);
|
|
35
|
+
const persistDir = Path.join(this.matterbridge.matterbridgePluginDirectory, PLUGIN_NAME, 'persist');
|
|
36
|
+
this.persist = NodePersist.create({ dir: persistDir });
|
|
37
|
+
this.configManager = PlatformConfigManager.create(config, this.log);
|
|
38
|
+
this.registry = new DeviceRegistry();
|
|
39
|
+
this.state = new PlatformState();
|
|
40
|
+
this.thinqServices = new ThinqServiceContainer(this.log, this.persist, this.configManager, Path.join(this.matterbridge.matterbridgePluginDirectory, PLUGIN_NAME, 'mqtt-certs'));
|
|
41
|
+
}
|
|
42
|
+
async onStart(reason) {
|
|
43
|
+
this.log.debug(`onStart: entry (reason=${reason ?? 'none'})`);
|
|
44
|
+
this.log.notice('onStart called with reason:', reason ?? 'none');
|
|
45
|
+
await this.ready;
|
|
46
|
+
await this.clearSelect();
|
|
47
|
+
await this.persist.init();
|
|
48
|
+
if (this.configManager.isClearStorageOnStartupEnabled) {
|
|
49
|
+
this.log.debug('onStart: exit early — clear-storage-on-startup enabled, skipping device startup');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!this.configManager.validateConfig()) {
|
|
53
|
+
this.log.error('Platform configuration is invalid.');
|
|
54
|
+
this.state.setStartupCompleted(false);
|
|
55
|
+
this.log.debug('onStart: exit — invalid platform configuration');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
await this.startThinqDevices();
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
this.log.error(`ThinQ startup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
this.state.setStartupCompleted(false);
|
|
64
|
+
this.log.debug('onStart: exit — ThinQ startup threw');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.log.notice('onStart finished');
|
|
68
|
+
this.state.setStartupCompleted(true);
|
|
69
|
+
this.log.debug('onStart: exit — startup completed successfully');
|
|
70
|
+
}
|
|
71
|
+
async onConfigure() {
|
|
72
|
+
await super.onConfigure();
|
|
73
|
+
this.log.debug('onConfigure: entry');
|
|
74
|
+
this.log.notice('onConfigure called');
|
|
75
|
+
if (this.configManager.isClearStorageOnStartupEnabled) {
|
|
76
|
+
this.log.warn('Clearing persistence storage as per configuration.');
|
|
77
|
+
await this.persist
|
|
78
|
+
.clear()
|
|
79
|
+
.then(() => this.unregisterAllDevices(UNREGISTER_DEVICES_DELAY_MS))
|
|
80
|
+
.then(() => {
|
|
81
|
+
this.log.notice('Please restart the platform now.');
|
|
82
|
+
this.wssSendRestartRequired();
|
|
83
|
+
})
|
|
84
|
+
.catch((error) => {
|
|
85
|
+
this.log.error(`Error clearing persistence storage: ${error}`);
|
|
86
|
+
});
|
|
87
|
+
this.log.debug('onConfigure: exit early — clear-storage-on-startup enabled');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!this.state.isStartupCompleted) {
|
|
91
|
+
this.log.debug('onConfigure: exit early — startup did not complete, skipping polling setup');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const applyDeviceUpdate = (deviceId, snapshot) => {
|
|
95
|
+
const endpoint = this.registry.getDevice(deviceId);
|
|
96
|
+
if (!endpoint) {
|
|
97
|
+
this.log.debug(`ThinQ device update received for unregistered device ${deviceId}, skipping.`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (this.thinqDeviceKindById.get(deviceId) === 'WASHER') {
|
|
101
|
+
void applyThinqSnapshotToWasher(endpoint, snapshot, this.log).catch((error) => {
|
|
102
|
+
this.log.error(`Failed to apply ThinQ state update for ${deviceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
void applyThinqSnapshotToAirConditioner(endpoint, snapshot, this.configManager.getDeviceCapabilities(deviceId), this.log).catch((error) => {
|
|
107
|
+
this.log.error(`Failed to apply ThinQ state update for ${deviceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
this.thinqPollingIntervalMs = this.configManager.thinqRefreshIntervalSeconds * 1000;
|
|
111
|
+
this.thinqServices.getDeviceService().startPolling(this.thinqPollingIntervalMs, applyDeviceUpdate);
|
|
112
|
+
this.thinqServices.getDeviceService().startKeepAlive();
|
|
113
|
+
void this.thinqServices
|
|
114
|
+
.getMqttListener()
|
|
115
|
+
.start(applyDeviceUpdate)
|
|
116
|
+
.catch((error) => {
|
|
117
|
+
this.log.error(`ThinQ MQTT listener failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
+
});
|
|
119
|
+
if (this.filterMonitoringDeviceIds.length > 0) {
|
|
120
|
+
const applyFilterUpdate = (deviceId, filterState) => {
|
|
121
|
+
const endpoint = this.registry.getDevice(deviceId);
|
|
122
|
+
if (!endpoint) {
|
|
123
|
+
this.log.debug(`ThinQ filter update received for unregistered device ${deviceId}, skipping.`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
void applyThinqFilterStateToAirConditioner(endpoint, filterState, this.log).catch((error) => {
|
|
127
|
+
this.log.error(`Failed to apply ThinQ filter state update for ${deviceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const filterMonitoringIntervalMs = this.configManager.thinqFilterMonitoringIntervalSeconds * 1000;
|
|
131
|
+
this.thinqServices
|
|
132
|
+
.getFilterMonitoringService()
|
|
133
|
+
.startPolling(filterMonitoringIntervalMs, this.filterMonitoringDeviceIds, applyFilterUpdate);
|
|
134
|
+
}
|
|
135
|
+
this.log.debug(`onConfigure: exit — polling started at ${this.thinqPollingIntervalMs}ms interval`);
|
|
136
|
+
}
|
|
137
|
+
async onShutdown(reason) {
|
|
138
|
+
await super.onShutdown(reason);
|
|
139
|
+
this.log.debug(`onShutdown: entry (reason=${reason ?? 'none'})`);
|
|
140
|
+
this.log.notice('onShutdown called with reason:', reason ?? 'none');
|
|
141
|
+
this.thinqServices.getDeviceService().stopPolling();
|
|
142
|
+
this.thinqServices.getDeviceService().stopKeepAlive();
|
|
143
|
+
this.thinqServices.getMqttListener().stop();
|
|
144
|
+
this.thinqServices.getFilterMonitoringService().stopPolling();
|
|
145
|
+
if (this.configManager.unregisterOnShutdown) {
|
|
146
|
+
await this.unregisterAllDevices(UNREGISTER_DEVICES_DELAY_MS);
|
|
147
|
+
}
|
|
148
|
+
this.state.setStartupCompleted(false);
|
|
149
|
+
this.log.debug('onShutdown: exit');
|
|
150
|
+
}
|
|
151
|
+
async startThinqDevices() {
|
|
152
|
+
const userDataRepository = this.thinqServices.getUserDataRepository();
|
|
153
|
+
let userData = this.configManager.isForceAuthenticationEnabled
|
|
154
|
+
? undefined
|
|
155
|
+
: await userDataRepository.loadUserData();
|
|
156
|
+
if (!userData) {
|
|
157
|
+
userData = await this.thinqServices
|
|
158
|
+
.getAuthenticationCoordinator()
|
|
159
|
+
.authenticate(this.configManager.thinqLoginType, {
|
|
160
|
+
username: this.configManager.thinqUsername,
|
|
161
|
+
password: this.configManager.thinqPassword,
|
|
162
|
+
refreshToken: this.configManager.thinqRefreshToken,
|
|
163
|
+
country: this.configManager.country,
|
|
164
|
+
language: this.configManager.language,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (!userData) {
|
|
168
|
+
throw new ManualProcessNeededError('LG ThinQ authentication did not return user data. This account likely uses a third-party SSO login ' +
|
|
169
|
+
'(Google/Apple/Facebook/Amazon), which is not yet supported headlessly — please log in with a native ' +
|
|
170
|
+
'LG account (email/password) or refresh token instead.', { reason: 'THIRD_PARTY_SSO_NOT_SUPPORTED' });
|
|
171
|
+
}
|
|
172
|
+
this.thinqServices.apiClient.setSession(ThinqSession.fromData(userData));
|
|
173
|
+
if (userData.userNumber) {
|
|
174
|
+
this.thinqServices.apiClient.setUserNumber(userData.userNumber);
|
|
175
|
+
}
|
|
176
|
+
await userDataRepository.saveUserData(userData);
|
|
177
|
+
const devices = await this.thinqServices.getDeviceDiscovery().discoverDevices();
|
|
178
|
+
const configChanged = reconcileThinqDeviceConfigEntries(this.config.thinq.devices, devices);
|
|
179
|
+
if (configChanged) {
|
|
180
|
+
try {
|
|
181
|
+
this.saveConfig(this.config);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
this.log.error(`Failed to persist auto-populated ThinQ device config: ${error instanceof Error ? error.message : String(error)}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const configurator = this.thinqServices.getDeviceConfigurator();
|
|
188
|
+
for (const device of devices) {
|
|
189
|
+
if (isAirConditionerDevice(device)) {
|
|
190
|
+
const airConditioner = await configurator.registerAirConditioner(device);
|
|
191
|
+
await this.registerDevice(airConditioner);
|
|
192
|
+
this.registry.register(device.id, airConditioner);
|
|
193
|
+
this.thinqDeviceKindById.set(device.id, 'AC');
|
|
194
|
+
if (this.configManager.getDeviceCapabilities(device.id).supportsFilterMonitoring) {
|
|
195
|
+
this.filterMonitoringDeviceIds.push(device.id);
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (isWasherDevice(device)) {
|
|
200
|
+
const washer = await configurator.registerWasher(device);
|
|
201
|
+
await this.registerDevice(washer);
|
|
202
|
+
this.registry.register(device.id, washer);
|
|
203
|
+
this.thinqDeviceKindById.set(device.id, 'WASHER');
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async onChangeLoggerLevel(logLevel) {
|
|
209
|
+
this.log.notice(`Change ${PLUGIN_NAME} log level: ${logLevel} (was ${this.log.logLevel})`);
|
|
210
|
+
this.log.logLevel = logLevel;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class DeviceRegistry {
|
|
2
|
+
devices = new Map();
|
|
3
|
+
register(deviceId, device) {
|
|
4
|
+
if (!deviceId)
|
|
5
|
+
return;
|
|
6
|
+
this.devices.set(deviceId, device);
|
|
7
|
+
}
|
|
8
|
+
unregister(deviceId) {
|
|
9
|
+
this.devices.delete(deviceId);
|
|
10
|
+
}
|
|
11
|
+
getDevice(deviceId) {
|
|
12
|
+
return this.devices.get(deviceId);
|
|
13
|
+
}
|
|
14
|
+
getAllDevices() {
|
|
15
|
+
return Array.from(this.devices.values());
|
|
16
|
+
}
|
|
17
|
+
get size() {
|
|
18
|
+
return this.devices.size;
|
|
19
|
+
}
|
|
20
|
+
hasDevices() {
|
|
21
|
+
return this.devices.size > 0;
|
|
22
|
+
}
|
|
23
|
+
clear() {
|
|
24
|
+
this.devices.clear();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { resolveAirConditionerCapabilities } from '../core/domain/value-objects/AirConditionerCapabilities.js';
|
|
2
|
+
import { createDefaultAdvancedFeature, createDefaultThinqConfig, } from '../model/LgThinqPluginPlatformConfig.js';
|
|
3
|
+
const DEFAULT_THINQ_REFRESH_INTERVAL_SECONDS = 60;
|
|
4
|
+
const DEFAULT_FILTER_MONITORING_INTERVAL_SECONDS = 3600;
|
|
5
|
+
export class PlatformConfigManager {
|
|
6
|
+
config;
|
|
7
|
+
log;
|
|
8
|
+
constructor(config, log) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.log = log;
|
|
11
|
+
this.config.thinq ??= createDefaultThinqConfig();
|
|
12
|
+
this.config.webos ??= { devices: [] };
|
|
13
|
+
this.config.advancedFeature ??= createDefaultAdvancedFeature();
|
|
14
|
+
}
|
|
15
|
+
static create(config, log) {
|
|
16
|
+
return new PlatformConfigManager(config, log);
|
|
17
|
+
}
|
|
18
|
+
get rawConfig() {
|
|
19
|
+
return this.config;
|
|
20
|
+
}
|
|
21
|
+
get debug() {
|
|
22
|
+
return this.config.advancedFeature.settings.debug;
|
|
23
|
+
}
|
|
24
|
+
get isClearStorageOnStartupEnabled() {
|
|
25
|
+
return this.config.advancedFeature.settings.clearStorageOnStartup;
|
|
26
|
+
}
|
|
27
|
+
get isForceAuthenticationEnabled() {
|
|
28
|
+
return this.config.advancedFeature.settings.forceAuthentication;
|
|
29
|
+
}
|
|
30
|
+
get unregisterOnShutdown() {
|
|
31
|
+
return this.config.advancedFeature.settings.unregisterOnShutdown;
|
|
32
|
+
}
|
|
33
|
+
get thinqLoginType() {
|
|
34
|
+
return this.config.thinq.loginType;
|
|
35
|
+
}
|
|
36
|
+
get thinqUsername() {
|
|
37
|
+
return this.config.thinq.username;
|
|
38
|
+
}
|
|
39
|
+
get thinqPassword() {
|
|
40
|
+
return this.config.thinq.password;
|
|
41
|
+
}
|
|
42
|
+
get thinqRefreshToken() {
|
|
43
|
+
return this.config.thinq.refreshToken;
|
|
44
|
+
}
|
|
45
|
+
get country() {
|
|
46
|
+
return this.config.thinq.country;
|
|
47
|
+
}
|
|
48
|
+
get language() {
|
|
49
|
+
return this.config.thinq.language;
|
|
50
|
+
}
|
|
51
|
+
get thinqRefreshIntervalSeconds() {
|
|
52
|
+
return this.config.thinq.refreshIntervalSeconds ?? DEFAULT_THINQ_REFRESH_INTERVAL_SECONDS;
|
|
53
|
+
}
|
|
54
|
+
get thinqFilterMonitoringIntervalSeconds() {
|
|
55
|
+
return this.config.thinq.filterMonitoringIntervalSeconds ?? DEFAULT_FILTER_MONITORING_INTERVAL_SECONDS;
|
|
56
|
+
}
|
|
57
|
+
getDeviceCapabilities(deviceId) {
|
|
58
|
+
return resolveAirConditionerCapabilities(this.config.thinq.devices, deviceId);
|
|
59
|
+
}
|
|
60
|
+
getWasherControlConfig(deviceId) {
|
|
61
|
+
return this.config.thinq.devices?.find((d) => d.deviceId === deviceId)?.washerControl ?? {};
|
|
62
|
+
}
|
|
63
|
+
getAcFilterControlConfig(deviceId) {
|
|
64
|
+
return this.config.thinq.devices?.find((d) => d.deviceId === deviceId)?.acFilterControl ?? {};
|
|
65
|
+
}
|
|
66
|
+
get overrideMatterConfiguration() {
|
|
67
|
+
return this.config.advancedFeature.settings.overrideMatterConfiguration;
|
|
68
|
+
}
|
|
69
|
+
get matterOverrideSettings() {
|
|
70
|
+
return this.config.advancedFeature.settings.matterOverrideSettings;
|
|
71
|
+
}
|
|
72
|
+
getProductNameForDevice(deviceId) {
|
|
73
|
+
if (!this.overrideMatterConfiguration)
|
|
74
|
+
return undefined;
|
|
75
|
+
return this.config.thinq.devices?.find((d) => d.deviceId === deviceId)?.productName;
|
|
76
|
+
}
|
|
77
|
+
getProductIdForDevice(deviceId) {
|
|
78
|
+
if (!this.overrideMatterConfiguration)
|
|
79
|
+
return undefined;
|
|
80
|
+
return this.config.thinq.devices?.find((d) => d.deviceId === deviceId)?.productId;
|
|
81
|
+
}
|
|
82
|
+
validateConfig() {
|
|
83
|
+
this.log.debug('Validating platform config');
|
|
84
|
+
if (this.thinqLoginType === 'token') {
|
|
85
|
+
return Boolean(this.thinqRefreshToken);
|
|
86
|
+
}
|
|
87
|
+
return Boolean(this.thinqUsername && this.thinqPassword);
|
|
88
|
+
}
|
|
89
|
+
}
|