@mp-consulting/homebridge-daikin-cloud 1.3.5 → 1.3.7

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.
Files changed (234) hide show
  1. package/LICENSE +39 -1
  2. package/README.md +5 -3
  3. package/dist/src/accessories/air-conditioning-accessory.d.ts +2 -2
  4. package/dist/src/accessories/air-conditioning-accessory.d.ts.map +1 -1
  5. package/dist/src/accessories/air-conditioning-accessory.js.map +1 -1
  6. package/dist/src/accessories/altherma-accessory.d.ts +2 -2
  7. package/dist/src/accessories/altherma-accessory.d.ts.map +1 -1
  8. package/dist/src/accessories/altherma-accessory.js.map +1 -1
  9. package/dist/src/accessories/base-accessory.d.ts +6 -6
  10. package/dist/src/accessories/base-accessory.d.ts.map +1 -1
  11. package/dist/src/accessories/base-accessory.js +15 -15
  12. package/dist/src/accessories/base-accessory.js.map +1 -1
  13. package/dist/src/api/daikin-api.d.ts +26 -26
  14. package/dist/src/api/daikin-api.d.ts.map +1 -1
  15. package/dist/src/api/daikin-api.js +68 -42
  16. package/dist/src/api/daikin-api.js.map +1 -1
  17. package/dist/src/api/daikin-cloud.repository.d.ts.map +1 -1
  18. package/dist/src/api/daikin-cloud.repository.js +22 -14
  19. package/dist/src/api/daikin-cloud.repository.js.map +1 -1
  20. package/dist/src/api/daikin-controller.d.ts +41 -47
  21. package/dist/src/api/daikin-controller.d.ts.map +1 -1
  22. package/dist/src/api/daikin-controller.js +40 -64
  23. package/dist/src/api/daikin-controller.js.map +1 -1
  24. package/dist/src/api/daikin-device.d.ts +36 -31
  25. package/dist/src/api/daikin-device.d.ts.map +1 -1
  26. package/dist/src/api/daikin-device.js +45 -31
  27. package/dist/src/api/daikin-device.js.map +1 -1
  28. package/dist/src/api/daikin-mobile-oauth.d.ts +20 -20
  29. package/dist/src/api/daikin-mobile-oauth.d.ts.map +1 -1
  30. package/dist/src/api/daikin-mobile-oauth.js +49 -44
  31. package/dist/src/api/daikin-mobile-oauth.js.map +1 -1
  32. package/dist/src/api/daikin-oauth.d.ts +32 -32
  33. package/dist/src/api/daikin-oauth.d.ts.map +1 -1
  34. package/dist/src/api/daikin-oauth.js +64 -56
  35. package/dist/src/api/daikin-oauth.js.map +1 -1
  36. package/dist/src/api/daikin-schemas.d.ts +476 -351
  37. package/dist/src/api/daikin-schemas.d.ts.map +1 -1
  38. package/dist/src/api/daikin-schemas.js +11 -42
  39. package/dist/src/api/daikin-schemas.js.map +1 -1
  40. package/dist/src/api/daikin-types.d.ts +5 -1
  41. package/dist/src/api/daikin-types.d.ts.map +1 -1
  42. package/dist/src/api/daikin-types.js.map +1 -1
  43. package/dist/src/api/daikin-websocket.d.ts +31 -32
  44. package/dist/src/api/daikin-websocket.d.ts.map +1 -1
  45. package/dist/src/api/daikin-websocket.js +55 -35
  46. package/dist/src/api/daikin-websocket.js.map +1 -1
  47. package/dist/src/api/index.d.ts +2 -1
  48. package/dist/src/api/index.d.ts.map +1 -1
  49. package/dist/src/api/index.js +3 -1
  50. package/dist/src/api/index.js.map +1 -1
  51. package/dist/src/api/token-storage.d.ts +21 -0
  52. package/dist/src/api/token-storage.d.ts.map +1 -0
  53. package/dist/src/api/token-storage.js +90 -0
  54. package/dist/src/api/token-storage.js.map +1 -0
  55. package/dist/src/config/config-manager.d.ts +33 -33
  56. package/dist/src/config/config-manager.d.ts.map +1 -1
  57. package/dist/src/config/config-manager.js +33 -33
  58. package/dist/src/config/config-manager.js.map +1 -1
  59. package/dist/src/constants/api.constants.d.ts +4 -0
  60. package/dist/src/constants/api.constants.d.ts.map +1 -1
  61. package/dist/src/constants/api.constants.js +5 -1
  62. package/dist/src/constants/api.constants.js.map +1 -1
  63. package/dist/src/constants/device.constants.d.ts +4 -0
  64. package/dist/src/constants/device.constants.d.ts.map +1 -1
  65. package/dist/src/constants/device.constants.js +5 -1
  66. package/dist/src/constants/device.constants.js.map +1 -1
  67. package/dist/src/device/accessory-factory.d.ts +10 -10
  68. package/dist/src/device/accessory-factory.d.ts.map +1 -1
  69. package/dist/src/device/accessory-factory.js +7 -7
  70. package/dist/src/device/accessory-factory.js.map +1 -1
  71. package/dist/src/device/capability-detector.d.ts +8 -8
  72. package/dist/src/device/capability-detector.d.ts.map +1 -1
  73. package/dist/src/device/capability-detector.js +6 -6
  74. package/dist/src/device/capability-detector.js.map +1 -1
  75. package/dist/src/device/capability-docs.d.ts +1 -9
  76. package/dist/src/device/capability-docs.d.ts.map +1 -1
  77. package/dist/src/device/capability-docs.js +19 -73
  78. package/dist/src/device/capability-docs.js.map +1 -1
  79. package/dist/src/device/profiles/device-profile.d.ts +1 -1
  80. package/dist/src/device/profiles/device-profile.d.ts.map +1 -1
  81. package/dist/src/device/profiles/device-profile.js +4 -4
  82. package/dist/src/device/profiles/device-profile.js.map +1 -1
  83. package/dist/src/features/base-feature.d.ts +2 -2
  84. package/dist/src/features/base-feature.d.ts.map +1 -1
  85. package/dist/src/features/base-feature.js +2 -3
  86. package/dist/src/features/base-feature.js.map +1 -1
  87. package/dist/src/features/feature-manager.d.ts +8 -16
  88. package/dist/src/features/feature-manager.d.ts.map +1 -1
  89. package/dist/src/features/feature-manager.js +5 -17
  90. package/dist/src/features/feature-manager.js.map +1 -1
  91. package/dist/src/features/modes/dry-operation-mode.feature.d.ts +1 -1
  92. package/dist/src/features/modes/dry-operation-mode.feature.d.ts.map +1 -1
  93. package/dist/src/features/modes/dry-operation-mode.feature.js.map +1 -1
  94. package/dist/src/features/modes/econo-mode.feature.d.ts +1 -1
  95. package/dist/src/features/modes/econo-mode.feature.d.ts.map +1 -1
  96. package/dist/src/features/modes/econo-mode.feature.js.map +1 -1
  97. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts +1 -1
  98. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts.map +1 -1
  99. package/dist/src/features/modes/fan-only-operation-mode.feature.js.map +1 -1
  100. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts +1 -1
  101. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts.map +1 -1
  102. package/dist/src/features/modes/indoor-silent-mode.feature.js.map +1 -1
  103. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts +1 -1
  104. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts.map +1 -1
  105. package/dist/src/features/modes/outdoor-silent-mode.feature.js.map +1 -1
  106. package/dist/src/features/modes/powerful-mode.feature.d.ts +1 -1
  107. package/dist/src/features/modes/powerful-mode.feature.d.ts.map +1 -1
  108. package/dist/src/features/modes/powerful-mode.feature.js.map +1 -1
  109. package/dist/src/features/modes/streamer-mode.feature.d.ts +1 -1
  110. package/dist/src/features/modes/streamer-mode.feature.d.ts.map +1 -1
  111. package/dist/src/features/modes/streamer-mode.feature.js.map +1 -1
  112. package/dist/src/index.d.ts +1 -1
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js.map +1 -1
  115. package/dist/src/platform.d.ts +11 -8
  116. package/dist/src/platform.d.ts.map +1 -1
  117. package/dist/src/platform.js +64 -15
  118. package/dist/src/platform.js.map +1 -1
  119. package/dist/src/services/climate-control.service.d.ts +8 -2
  120. package/dist/src/services/climate-control.service.d.ts.map +1 -1
  121. package/dist/src/services/climate-control.service.js +59 -58
  122. package/dist/src/services/climate-control.service.js.map +1 -1
  123. package/dist/src/services/hot-water-tank.service.d.ts +6 -2
  124. package/dist/src/services/hot-water-tank.service.d.ts.map +1 -1
  125. package/dist/src/services/hot-water-tank.service.js +33 -31
  126. package/dist/src/services/hot-water-tank.service.js.map +1 -1
  127. package/dist/src/types/daikin-enums.js +12 -12
  128. package/dist/src/types/daikin-enums.js.map +1 -1
  129. package/dist/src/types/device-capabilities.d.ts +1 -1
  130. package/dist/src/types/device-capabilities.d.ts.map +1 -1
  131. package/dist/src/utils/log-context.d.ts +23 -23
  132. package/dist/src/utils/log-context.d.ts.map +1 -1
  133. package/dist/src/utils/log-context.js +28 -28
  134. package/dist/src/utils/log-context.js.map +1 -1
  135. package/dist/src/utils/strings.d.ts.map +1 -1
  136. package/dist/src/utils/strings.js.map +1 -1
  137. package/dist/src/utils/update-mapper.d.ts +16 -16
  138. package/dist/src/utils/update-mapper.d.ts.map +1 -1
  139. package/dist/src/utils/update-mapper.js +14 -14
  140. package/dist/src/utils/update-mapper.js.map +1 -1
  141. package/homebridge-ui/public/index.html +2 -2
  142. package/homebridge-ui/public/script.js +957 -898
  143. package/homebridge-ui/server.js +746 -678
  144. package/package.json +29 -27
  145. package/.claude/settings.json +0 -3
  146. package/.claude/settings.local.json +0 -29
  147. package/CHANGELOG.md +0 -103
  148. package/CLAUDE.md +0 -269
  149. package/config.md +0 -2
  150. package/dist/src/api/daikin-device-tracker.d.ts +0 -97
  151. package/dist/src/api/daikin-device-tracker.d.ts.map +0 -1
  152. package/dist/src/api/daikin-device-tracker.js +0 -136
  153. package/dist/src/api/daikin-device-tracker.js.map +0 -1
  154. package/dist/src/api/http-interceptor.d.ts +0 -99
  155. package/dist/src/api/http-interceptor.d.ts.map +0 -1
  156. package/dist/src/api/http-interceptor.js +0 -177
  157. package/dist/src/api/http-interceptor.js.map +0 -1
  158. package/dist/src/di/service-container.d.ts +0 -92
  159. package/dist/src/di/service-container.d.ts.map +0 -1
  160. package/dist/src/di/service-container.js +0 -156
  161. package/dist/src/di/service-container.js.map +0 -1
  162. package/dist/src/features/feature-registry.d.ts +0 -100
  163. package/dist/src/features/feature-registry.d.ts.map +0 -1
  164. package/dist/src/features/feature-registry.js +0 -142
  165. package/dist/src/features/feature-registry.js.map +0 -1
  166. package/dist/src/services/service-factory.d.ts +0 -46
  167. package/dist/src/services/service-factory.d.ts.map +0 -1
  168. package/dist/src/services/service-factory.js +0 -72
  169. package/dist/src/services/service-factory.js.map +0 -1
  170. package/dist/src/utils/error-handler.d.ts +0 -101
  171. package/dist/src/utils/error-handler.d.ts.map +0 -1
  172. package/dist/src/utils/error-handler.js +0 -251
  173. package/dist/src/utils/error-handler.js.map +0 -1
  174. package/dist/src/utils/retry.d.ts +0 -42
  175. package/dist/src/utils/retry.d.ts.map +0 -1
  176. package/dist/src/utils/retry.js +0 -70
  177. package/dist/src/utils/retry.js.map +0 -1
  178. package/docs/ARCHITECTURE.md +0 -645
  179. package/docs/IMPLEMENTATION_GUIDE.md +0 -899
  180. package/docs/IMPROVEMENTS_SUMMARY.md +0 -415
  181. package/docs/NEXT_STEPS.md +0 -368
  182. package/docs/Screenshot 2024-07-04 at 18.41.28.png +0 -0
  183. package/docs/TROUBLESHOOTING.md +0 -475
  184. package/docs/api-response-for-BRP069A8x.json +0 -520
  185. package/docs/api-response-for-BRP069C4x-2.json +0 -881
  186. package/docs/api-response-for-BRP069C4x.json +0 -916
  187. package/docs/api-response-for-altherma.json +0 -759
  188. package/docs/api-response-for-altherma2.json +0 -2735
  189. package/docs/api-response-with-multiple-devices-incl-heatpump.json +0 -2544
  190. package/docs/cr-insance-altherma-id-0.json +0 -834
  191. package/docs/mock-air-to-air-dx23.json +0 -759
  192. package/docs/mock-air-to-air-dx4.json +0 -1134
  193. package/docs/mock-airpurifier-with-humidifier.json +0 -732
  194. package/docs/mock-airpurifier.json +0 -450
  195. package/docs/mock-altherma-air-to-water-lan.json +0 -845
  196. package/docs/mock-altherma-air-to-water-wlan.json +0 -845
  197. package/docs/mock-d2cnd-gas-boiler.json +0 -649
  198. package/docs/setpointmode-vs-controlmode-vs-setpoints-vs-sensorydata.txt +0 -6
  199. package/images/fan-speed.jpeg +0 -0
  200. package/images/homekit-controls.jpeg +0 -0
  201. package/images/homekit-settings.jpeg +0 -0
  202. package/images/swing-mode.png +0 -0
  203. package/jest.config.ts +0 -13
  204. package/test/fixtures/altherma-crSense-2.ts +0 -834
  205. package/test/fixtures/altherma-fraction.ts +0 -718
  206. package/test/fixtures/altherma-heat-pump-2.ts +0 -479
  207. package/test/fixtures/altherma-heat-pump.ts +0 -757
  208. package/test/fixtures/altherma-miladcerkic-off.ts +0 -524
  209. package/test/fixtures/altherma-miladcerkic.ts +0 -524
  210. package/test/fixtures/altherma-v1ckoeln.ts +0 -644
  211. package/test/fixtures/altherma-with-embedded-id-zero.ts +0 -834
  212. package/test/fixtures/dx23-airco-2.ts +0 -343
  213. package/test/fixtures/dx23-airco.ts +0 -518
  214. package/test/fixtures/dx4-airco.ts +0 -914
  215. package/test/fixtures/unknown-jan.ts +0 -488
  216. package/test/fixtures/unknown-kitchen-guests.ts +0 -488
  217. package/test/helpers/test-isolation.ts +0 -228
  218. package/test/integration/air-conditioning.test.ts +0 -410
  219. package/test/integration/altherma.test.ts +0 -289
  220. package/test/integration/platform.test.ts +0 -118
  221. package/test/mocks/index.ts +0 -27
  222. package/test/test-gigya-auth.js +0 -443
  223. package/test/test-mobile-oauth.js +0 -175
  224. package/test/test-websocket-mobile.js +0 -123
  225. package/test/test-websocket.js +0 -116
  226. package/test/unit/api/__snapshots__/daikinCloud.test.ts.snap +0 -1320
  227. package/test/unit/api/daikin-api.test.ts +0 -384
  228. package/test/unit/api/daikin-oauth.test.ts +0 -214
  229. package/test/unit/api/daikinCloud.test.ts +0 -12
  230. package/test/unit/config/config-manager.test.ts +0 -271
  231. package/test/unit/device/daikin-device.test.ts +0 -79
  232. package/test/unit/services/hot-water-tank.service.test.ts +0 -123
  233. package/test/unit/utils/error-handler.test.ts +0 -274
  234. package/test/unit/utils/log-context.test.ts +0 -271
@@ -1,228 +0,0 @@
1
- /**
2
- * Test Isolation Helpers
3
- *
4
- * Provides utilities for better test isolation:
5
- * - Test context management
6
- * - Automatic cleanup
7
- * - Mock factories
8
- * - Fake timers
9
- */
10
-
11
- import {Logger, PlatformAccessory, PlatformConfig} from 'homebridge';
12
- import {DaikinCloudAccessoryContext} from '../../src/platform';
13
-
14
- /**
15
- * Test context for managing test lifecycle
16
- */
17
- export class TestContext {
18
- private readonly cleanupCallbacks: Array<() => void | Promise<void>> = [];
19
- private timersMocked = false;
20
-
21
- /**
22
- * Register a cleanup callback
23
- */
24
- onCleanup(callback: () => void | Promise<void>): void {
25
- this.cleanupCallbacks.push(callback);
26
- }
27
-
28
- /**
29
- * Use fake timers for this test
30
- */
31
- useFakeTimers(): void {
32
- if (!this.timersMocked) {
33
- jest.useFakeTimers();
34
- this.timersMocked = true;
35
- this.onCleanup(() => {
36
- jest.runOnlyPendingTimers();
37
- jest.useRealTimers();
38
- this.timersMocked = false;
39
- });
40
- }
41
- }
42
-
43
- /**
44
- * Advance timers by time
45
- */
46
- async advanceTimersByTime(ms: number): Promise<void> {
47
- if (!this.timersMocked) {
48
- throw new Error('Timers must be mocked first. Call useFakeTimers()');
49
- }
50
- jest.advanceTimersByTime(ms);
51
- await Promise.resolve(); // Flush microtasks
52
- }
53
-
54
- /**
55
- * Run all timers
56
- */
57
- async runAllTimers(): Promise<void> {
58
- if (!this.timersMocked) {
59
- throw new Error('Timers must be mocked first. Call useFakeTimers()');
60
- }
61
- jest.runAllTimers();
62
- await Promise.resolve(); // Flush microtasks
63
- }
64
-
65
- /**
66
- * Clean up all resources
67
- */
68
- async cleanup(): Promise<void> {
69
- for (const callback of this.cleanupCallbacks.reverse()) {
70
- await callback();
71
- }
72
- this.cleanupCallbacks.length = 0;
73
- }
74
- }
75
-
76
- /**
77
- * Create a test context for a test
78
- */
79
- export function createTestContext(): TestContext {
80
- return new TestContext();
81
- }
82
-
83
- /**
84
- * Create a mock logger
85
- */
86
- export function createMockLogger(): Logger {
87
- const logger = {
88
- debug: jest.fn(),
89
- info: jest.fn(),
90
- warn: jest.fn(),
91
- error: jest.fn(),
92
- log: jest.fn(),
93
- success: jest.fn(),
94
- } as unknown as Logger;
95
-
96
- return logger;
97
- }
98
-
99
- /**
100
- * Create a mock platform config
101
- */
102
- export function createMockConfig(overrides: Partial<PlatformConfig> = {}): PlatformConfig {
103
- return {
104
- name: 'Test Platform',
105
- platform: 'DaikinCloud',
106
- clientId: 'test-client-id',
107
- clientSecret: 'test-client-secret',
108
- callbackServerExternalAddress: 'test.example.com',
109
- callbackServerPort: 8582,
110
- oidcCallbackServerBindAddr: '0.0.0.0',
111
- updateIntervalInMinutes: 15,
112
- forceUpdateDelay: 60000,
113
- showExtraFeatures: false,
114
- enableWebSocket: true,
115
- ...overrides,
116
- } as PlatformConfig;
117
- }
118
-
119
- /**
120
- * Create a mock accessory
121
- */
122
- export function createMockAccessory(
123
- displayName: string,
124
- context?: Partial<DaikinCloudAccessoryContext>,
125
- ): PlatformAccessory<DaikinCloudAccessoryContext> {
126
- const mockContext: DaikinCloudAccessoryContext = {
127
- device: {} as any,
128
- useHeaterCooler: false,
129
- ...context,
130
- };
131
-
132
- const accessory = {
133
- UUID: `test-uuid-${displayName}`,
134
- displayName,
135
- context: mockContext,
136
- services: [],
137
- getService: jest.fn(),
138
- addService: jest.fn(),
139
- removeService: jest.fn(),
140
- getServiceById: jest.fn(),
141
- } as unknown as PlatformAccessory<DaikinCloudAccessoryContext>;
142
-
143
- return accessory;
144
- }
145
-
146
- /**
147
- * Spy on console methods and restore after cleanup
148
- */
149
- export function spyOnConsole(context: TestContext): {
150
- log: jest.SpyInstance;
151
- warn: jest.SpyInstance;
152
- error: jest.SpyInstance;
153
- } {
154
- const spies = {
155
- log: jest.spyOn(console, 'log').mockImplementation(),
156
- warn: jest.spyOn(console, 'warn').mockImplementation(),
157
- error: jest.spyOn(console, 'error').mockImplementation(),
158
- };
159
-
160
- context.onCleanup(() => {
161
- spies.log.mockRestore();
162
- spies.warn.mockRestore();
163
- spies.error.mockRestore();
164
- });
165
-
166
- return spies;
167
- }
168
-
169
- /**
170
- * Create isolated test suite with automatic cleanup
171
- */
172
- export function describeIsolated(name: string, fn: (getContext: () => TestContext) => void): void {
173
- describe(name, () => {
174
- let context: TestContext;
175
-
176
- beforeEach(() => {
177
- context = createTestContext();
178
- });
179
-
180
- afterEach(async () => {
181
- await context.cleanup();
182
- });
183
-
184
- fn(() => context);
185
- });
186
- }
187
-
188
- /**
189
- * Wait for a condition to be true
190
- */
191
- export async function waitFor(
192
- condition: () => boolean | Promise<boolean>,
193
- options: { timeout?: number; interval?: number } = {},
194
- ): Promise<void> {
195
- const timeout = options.timeout ?? 5000;
196
- const interval = options.interval ?? 50;
197
- const startTime = Date.now();
198
-
199
- while (!(await condition())) {
200
- if (Date.now() - startTime > timeout) {
201
- throw new Error('waitFor timeout exceeded');
202
- }
203
- await new Promise(resolve => setTimeout(resolve, interval));
204
- }
205
- }
206
-
207
- /**
208
- * Create a deferred promise for testing async flows
209
- */
210
- export function createDeferred<T>(): {
211
- promise: Promise<T>;
212
- resolve: (value: T) => void;
213
- reject: (error: Error) => void;
214
- } {
215
- let resolve: (value: T) => void;
216
- let reject: (error: Error) => void;
217
-
218
- const promise = new Promise<T>((res, rej) => {
219
- resolve = res;
220
- reject = rej;
221
- });
222
-
223
- return {
224
- promise,
225
- resolve: resolve!,
226
- reject: reject!,
227
- };
228
- }
@@ -1,410 +0,0 @@
1
- import {PlatformAccessory} from 'homebridge/lib/platformAccessory';
2
- import {DaikinCloudAccessoryContext, DaikinCloudPlatform} from '../../src/platform';
3
- import {MockPlatformConfig} from '../mocks';
4
- import {AirConditioningAccessory} from '../../src/accessories';
5
- import {DaikinCloudDevice, DaikinCloudController, DaikinApi} from '../../src/api';
6
- import {unknownJan} from '../fixtures/unknown-jan';
7
- import {unknownKitchenGuests} from '../fixtures/unknown-kitchen-guests';
8
- import {dx23Airco} from '../fixtures/dx23-airco';
9
- import {dx4Airco} from '../fixtures/dx4-airco';
10
- import {dx23Airco2} from '../fixtures/dx23-airco-2';
11
-
12
- import {HomebridgeAPI} from 'homebridge/lib/api.js';
13
- import {Logger} from 'homebridge/lib/logger.js';
14
- import {
15
- PowerfulModeFeature,
16
- EconoModeFeature,
17
- StreamerModeFeature,
18
- OutdoorSilentModeFeature,
19
- IndoorSilentModeFeature,
20
- DryOperationModeFeature,
21
- FanOnlyOperationModeFeature,
22
- } from '../../src/features';
23
-
24
- // Use fake timers to prevent tests from hanging due to setInterval/setTimeout in platform
25
- beforeEach(() => {
26
- jest.useFakeTimers();
27
- });
28
-
29
- afterEach(() => {
30
- jest.clearAllTimers();
31
- jest.useRealTimers();
32
- });
33
-
34
- type DeviceState = {
35
- activeState: boolean;
36
- currentTemperature: number;
37
- targetHeaterCoolerState: string;
38
- coolingThresholdTemperature: number;
39
- heatingThresholdTemperature: number;
40
- rotationSpeed: number;
41
- swingMode: number;
42
- powerfulMode: number;
43
- econoMode: number;
44
- streamerMode: number;
45
- outdoorSilentMode: number;
46
- indoorSilentMode: number;
47
- dryOperationMode: number;
48
- fanOnlyOperationMode: number;
49
- };
50
-
51
- test.each<Array<string | string | any | DeviceState>>([
52
- [
53
- 'dx4',
54
- 'climateControl',
55
- dx4Airco,
56
- {
57
- activeState: true,
58
- currentTemperature: 25,
59
- targetHeaterCoolerState: 1,
60
- coolingThresholdTemperature: 25,
61
- heatingThresholdTemperature: 22,
62
- rotationSpeed: 2,
63
- swingMode: 0,
64
- powerfulMode: false,
65
- econoMode: false,
66
- streamerMode: false,
67
- outdoorSilentMode: false,
68
- indoorSilentMode: false,
69
- dryOperationMode: false,
70
- fanOnlyOperationMode: false,
71
- },
72
- ],
73
- [
74
- 'dx23',
75
- 'climateControl',
76
- dx23Airco,
77
- {
78
- activeState: false,
79
- currentTemperature: 27,
80
- targetHeaterCoolerState: 2,
81
- coolingThresholdTemperature: 17,
82
- heatingThresholdTemperature: 17,
83
- rotationSpeed: 3,
84
- swingMode: 1,
85
- powerfulMode: undefined,
86
- econoMode: undefined,
87
- streamerMode: undefined,
88
- outdoorSilentMode: undefined,
89
- indoorSilentMode: undefined,
90
- dryOperationMode: false,
91
- fanOnlyOperationMode: false,
92
- },
93
- ],
94
- [
95
- 'dx23-2',
96
- 'climateControl',
97
- dx23Airco2,
98
- {
99
- activeState: true,
100
- currentTemperature: 19,
101
- targetHeaterCoolerState: 1,
102
- coolingThresholdTemperature: 25,
103
- heatingThresholdTemperature: 13,
104
- rotationSpeed: 4,
105
- swingMode: 0,
106
- powerfulMode: false,
107
- econoMode: undefined,
108
- streamerMode: undefined,
109
- outdoorSilentMode: undefined,
110
- indoorSilentMode: false,
111
- dryOperationMode: false,
112
- fanOnlyOperationMode: false,
113
- },
114
- ],
115
- [
116
- 'unknown',
117
- 'climateControl',
118
- unknownKitchenGuests,
119
- {
120
- activeState: false,
121
- currentTemperature: 30.1,
122
- targetHeaterCoolerState: 2,
123
- coolingThresholdTemperature: 23.5,
124
- heatingThresholdTemperature: undefined,
125
- rotationSpeed: 1,
126
- swingMode: 1,
127
- powerfulMode: undefined,
128
- econoMode: undefined,
129
- streamerMode: undefined,
130
- outdoorSilentMode: undefined,
131
- indoorSilentMode: undefined,
132
- dryOperationMode: false,
133
- fanOnlyOperationMode: false,
134
- },
135
- ],
136
- [
137
- 'unknown2',
138
- 'climateControl',
139
- unknownJan,
140
- {
141
- activeState: false,
142
- currentTemperature: 27,
143
- targetHeaterCoolerState: 2,
144
- coolingThresholdTemperature: 26.1,
145
- heatingThresholdTemperature: undefined,
146
- rotationSpeed: 1,
147
- swingMode: 1,
148
- powerfulMode: undefined,
149
- econoMode: undefined,
150
- streamerMode: undefined,
151
- outdoorSilentMode: undefined,
152
- indoorSilentMode: undefined,
153
- dryOperationMode: false,
154
- fanOnlyOperationMode: false,
155
- },
156
- ],
157
- ])('Create DaikinCloudAirConditioningAccessory with %s device', async (name: string, climateControlEmbeddedId: string, deviceJson, state: DeviceState) => {
158
- const mockApi = {
159
- updateDevice: jest.fn().mockResolvedValue(undefined),
160
- } as unknown as DaikinApi;
161
- const device = new DaikinCloudDevice(deviceJson as any, mockApi);
162
-
163
- jest.spyOn(DaikinCloudController.prototype, 'getCloudDevices').mockImplementation(async () => {
164
- return [device];
165
- });
166
-
167
- const config = new MockPlatformConfig(true);
168
- const api = new HomebridgeAPI();
169
-
170
- const uuid = api.hap.uuid.generate(device.getId());
171
- const accessory = new api.platformAccessory("NAME_FOR_TEST", uuid);
172
- accessory.context['device'] = device;
173
-
174
- expect(() => {
175
- new AirConditioningAccessory(new DaikinCloudPlatform(new Logger(), config, api), accessory as unknown as PlatformAccessory<DaikinCloudAccessoryContext>);
176
- }).not.toThrow();
177
-
178
- const homebridgeAccessory = new AirConditioningAccessory(new DaikinCloudPlatform(new Logger(), config, api), accessory as unknown as PlatformAccessory<DaikinCloudAccessoryContext>);
179
-
180
- if (typeof state.activeState !== 'undefined') {
181
- expect(await homebridgeAccessory.service.handleActiveStateGet()).toBe(state.activeState);
182
- expect(async () => {
183
- await homebridgeAccessory.service.handleActiveStateSet(1);
184
- }).not.toThrow();
185
- expect(async () => {
186
- await homebridgeAccessory.service.handleActiveStateSet(0);
187
- }).not.toThrow();
188
- }
189
-
190
- expect(await homebridgeAccessory.service.handleCurrentTemperatureGet()).toBe(state.currentTemperature);
191
-
192
- if (typeof state.coolingThresholdTemperature !== 'undefined') {
193
- expect(await homebridgeAccessory.service.handleCoolingThresholdTemperatureGet()).toBe(state.coolingThresholdTemperature);
194
- expect(async () => {
195
- await homebridgeAccessory.service.handleCoolingThresholdTemperatureSet(21);
196
- }).not.toThrow();
197
- }
198
-
199
- if (typeof state.heatingThresholdTemperature !== 'undefined') {
200
- expect(await homebridgeAccessory.service.handleHeatingThresholdTemperatureGet()).toBe(state.heatingThresholdTemperature);
201
- expect(async () => {
202
- await homebridgeAccessory.service.handleHeatingThresholdTemperatureSet(25);
203
- }).not.toThrow();
204
- }
205
-
206
- if (typeof state.rotationSpeed !== 'undefined') {
207
- expect(await homebridgeAccessory.service.handleRotationSpeedGet()).toBe(state.rotationSpeed);
208
- expect(async () => {
209
- await homebridgeAccessory.service.handleRotationSpeedSet(50);
210
- }).not.toThrow();
211
- }
212
-
213
- if (typeof state.targetHeaterCoolerState !== 'undefined') {
214
- expect(await homebridgeAccessory.service.handleTargetHeaterCoolerStateGet()).toBe(state.targetHeaterCoolerState);
215
- expect(async () => {
216
- await homebridgeAccessory.service.handleTargetHeaterCoolerStateSet(1);
217
- }).not.toThrow();
218
- }
219
-
220
- if (typeof state.swingMode !== 'undefined') {
221
- expect(await homebridgeAccessory.service.handleSwingModeGet()).toBe(state.swingMode);
222
- expect(async () => {
223
- await homebridgeAccessory.service.handleSwingModeSet(1);
224
- }).not.toThrow();
225
- }
226
-
227
- if (typeof state.powerfulMode !== 'undefined') {
228
- const feature = homebridgeAccessory.service.featureManager.getFeature(PowerfulModeFeature);
229
- expect(feature).toBeDefined();
230
- expect(await feature!.handleGet()).toBe(state.powerfulMode);
231
- }
232
-
233
- if (typeof state.econoMode !== 'undefined') {
234
- const feature = homebridgeAccessory.service.featureManager.getFeature(EconoModeFeature);
235
- expect(feature).toBeDefined();
236
- expect(await feature!.handleGet()).toBe(state.econoMode);
237
- }
238
-
239
- if (typeof state.streamerMode !== 'undefined') {
240
- const feature = homebridgeAccessory.service.featureManager.getFeature(StreamerModeFeature);
241
- expect(feature).toBeDefined();
242
- expect(await feature!.handleGet()).toBe(state.streamerMode);
243
- }
244
-
245
- if (typeof state.outdoorSilentMode !== 'undefined') {
246
- const feature = homebridgeAccessory.service.featureManager.getFeature(OutdoorSilentModeFeature);
247
- expect(feature).toBeDefined();
248
- expect(await feature!.handleGet()).toBe(state.outdoorSilentMode);
249
- }
250
-
251
- if (typeof state.indoorSilentMode !== 'undefined') {
252
- const feature = homebridgeAccessory.service.featureManager.getFeature(IndoorSilentModeFeature);
253
- expect(feature).toBeDefined();
254
- expect(await feature!.handleGet()).toBe(state.indoorSilentMode);
255
- }
256
-
257
- if (typeof state.dryOperationMode !== 'undefined') {
258
- const feature = homebridgeAccessory.service.featureManager.getFeature(DryOperationModeFeature);
259
- expect(feature).toBeDefined();
260
- expect(await feature!.handleGet()).toBe(state.dryOperationMode);
261
- }
262
-
263
- if (typeof state.fanOnlyOperationMode !== 'undefined') {
264
- const feature = homebridgeAccessory.service.featureManager.getFeature(FanOnlyOperationModeFeature);
265
- expect(feature).toBeDefined();
266
- expect(await feature!.handleGet()).toBe(state.fanOnlyOperationMode);
267
- }
268
- });
269
-
270
- test.each<Array<string | string | any>>([
271
- ['dx4', 'climateControl', dx4Airco],
272
- ['dx23', 'climateControl', dx23Airco],
273
- ])('Create DaikinCloudAirConditioningAccessory with %s device, showExtraFeatures disabled', async (name, climateControlEmbeddedId, deviceJson) => {
274
- const mockApi = { updateDevice: jest.fn().mockResolvedValue(undefined) } as unknown as DaikinApi;
275
- const device = new DaikinCloudDevice(deviceJson as any, mockApi);
276
-
277
- jest.spyOn(DaikinCloudController.prototype, 'getCloudDevices').mockImplementation(async () => {
278
- return [device];
279
- });
280
-
281
-
282
- const config = new MockPlatformConfig(false);
283
- const api = new HomebridgeAPI();
284
-
285
- const uuid = api.hap.uuid.generate(device.getId());
286
- const accessory = new api.platformAccessory("NAME_FOR_TEST", uuid);
287
-
288
- accessory.addService(api.hap.Service.Switch, 'Powerful mode', 'Powerful_Mode');
289
- accessory.addService(api.hap.Service.Switch, 'Econo mode', 'Econo_Mode');
290
- accessory.addService(api.hap.Service.Switch, 'Streamer mode', 'Streamer_Mode');
291
- accessory.addService(api.hap.Service.Switch, 'Outdoor silent mode', 'Outdoor_Silent_Mode');
292
- accessory.addService(api.hap.Service.Switch, 'Indoor silent mode', 'Indoor_Silent_Mode');
293
- accessory.context['device'] = device;
294
-
295
- const removeServiceSpy = jest.spyOn(accessory, 'removeService').mockImplementation();
296
-
297
- const homebridgeAccessory = new AirConditioningAccessory(new DaikinCloudPlatform(new Logger(), config, api), accessory as unknown as PlatformAccessory<DaikinCloudAccessoryContext>);
298
-
299
-
300
- expect(removeServiceSpy).toHaveBeenNthCalledWith(1, expect.objectContaining({ displayName: 'Powerful mode', subtype: 'Powerful_Mode' }));
301
- expect(removeServiceSpy).toHaveBeenNthCalledWith(2, expect.objectContaining({ displayName: 'Econo mode', subtype: 'Econo_Mode' }));
302
- expect(removeServiceSpy).toHaveBeenNthCalledWith(3, expect.objectContaining({ displayName: 'Streamer mode', subtype: 'Streamer_Mode' }));
303
- expect(removeServiceSpy).toHaveBeenNthCalledWith(4, expect.objectContaining({ displayName: 'Outdoor silent mode', subtype: 'Outdoor_Silent_Mode' }));
304
- expect(removeServiceSpy).toHaveBeenNthCalledWith(5, expect.objectContaining({ displayName: 'Indoor silent mode', subtype: 'Indoor_Silent_Mode' }));
305
-
306
- });
307
-
308
- test('DaikinCloudAirConditioningAccessory Getters', async () => {
309
- const mockApi = { updateDevice: jest.fn().mockResolvedValue(undefined) } as unknown as DaikinApi;
310
- const device = new DaikinCloudDevice(dx4Airco as any, mockApi);
311
-
312
- jest.spyOn(DaikinCloudController.prototype, 'getCloudDevices').mockImplementation(async () => {
313
- return [device];
314
- });
315
-
316
- const config = new MockPlatformConfig(false);
317
- const api = new HomebridgeAPI();
318
-
319
- const uuid = api.hap.uuid.generate(device.getId());
320
- const accessory = new api.platformAccessory(device.getData('climateControl', 'name', undefined).value as string, uuid);
321
- accessory.context['device'] = device;
322
-
323
- const homebridgeAccessory = new AirConditioningAccessory(new DaikinCloudPlatform(new Logger(), config, api), accessory as unknown as PlatformAccessory<DaikinCloudAccessoryContext>);
324
-
325
- expect(await homebridgeAccessory.service.handleActiveStateGet()).toEqual(true);
326
- expect(await homebridgeAccessory.service.handleCurrentTemperatureGet()).toEqual(25);
327
- expect(await homebridgeAccessory.service.handleCoolingThresholdTemperatureGet()).toEqual(25);
328
- expect(await homebridgeAccessory.service.handleRotationSpeedGet()).toEqual(2);
329
- expect(await homebridgeAccessory.service.handleHeatingThresholdTemperatureGet()).toEqual(22);
330
- expect(await homebridgeAccessory.service.handleTargetHeaterCoolerStateGet()).toEqual(1);
331
- expect(await homebridgeAccessory.service.handleSwingModeGet()).toEqual(0);
332
-
333
- // Feature-based getters via FeatureManager
334
- const powerfulFeature = homebridgeAccessory.service.featureManager.getFeature(PowerfulModeFeature);
335
- expect(await powerfulFeature!.handleGet()).toEqual(false);
336
- const econoFeature = homebridgeAccessory.service.featureManager.getFeature(EconoModeFeature);
337
- expect(await econoFeature!.handleGet()).toEqual(false);
338
- const streamerFeature = homebridgeAccessory.service.featureManager.getFeature(StreamerModeFeature);
339
- expect(await streamerFeature!.handleGet()).toEqual(false);
340
- const outdoorSilentFeature = homebridgeAccessory.service.featureManager.getFeature(OutdoorSilentModeFeature);
341
- expect(await outdoorSilentFeature!.handleGet()).toEqual(false);
342
- const indoorSilentFeature = homebridgeAccessory.service.featureManager.getFeature(IndoorSilentModeFeature);
343
- expect(await indoorSilentFeature!.handleGet()).toEqual(false);
344
- });
345
-
346
- test('DaikinCloudAirConditioningAccessory Setters', async () => {
347
- const mockApi = { updateDevice: jest.fn().mockResolvedValue(undefined) } as unknown as DaikinApi;
348
- const device = new DaikinCloudDevice(dx4Airco as any, mockApi);
349
-
350
- jest.spyOn(DaikinCloudController.prototype, 'getCloudDevices').mockImplementation(async () => {
351
- return [device];
352
- });
353
-
354
- const setDataSpy = jest.spyOn(DaikinCloudDevice.prototype, 'setData').mockImplementation();
355
-
356
- const config = new MockPlatformConfig(false);
357
- const api = new HomebridgeAPI();
358
-
359
- const uuid = api.hap.uuid.generate(device.getId());
360
- const accessory = new api.platformAccessory(device.getData('climateControl', 'name', undefined).value as string, uuid);
361
- // device.updateData = () => jest.fn();
362
- accessory.context['device'] = device;
363
-
364
- const homebridgeAccessory = new AirConditioningAccessory(new DaikinCloudPlatform(new Logger(), config, api), accessory as unknown as PlatformAccessory<DaikinCloudAccessoryContext>);
365
-
366
- await homebridgeAccessory.service.handleActiveStateSet(1);
367
- expect(setDataSpy).toHaveBeenNthCalledWith(1, 'climateControl', 'onOffMode', 'on', undefined);
368
-
369
- await homebridgeAccessory.service.handleActiveStateSet(0);
370
- expect(setDataSpy).toHaveBeenNthCalledWith(2, 'climateControl', 'onOffMode', 'off', undefined);
371
-
372
- await homebridgeAccessory.service.handleCoolingThresholdTemperatureSet(21);
373
- expect(setDataSpy).toHaveBeenNthCalledWith(3, 'climateControl', 'temperatureControl', '/operationModes/cooling/setpoints/roomTemperature', 21);
374
-
375
- await homebridgeAccessory.service.handleRotationSpeedSet(50);
376
- expect(setDataSpy).toHaveBeenNthCalledWith(4, 'climateControl', 'fanControl', '/operationModes/heating/fanSpeed/currentMode', 'fixed');
377
- expect(setDataSpy).toHaveBeenNthCalledWith(5, 'climateControl', 'fanControl', '/operationModes/heating/fanSpeed/modes/fixed', 50);
378
-
379
- await homebridgeAccessory.service.handleHeatingThresholdTemperatureSet(25);
380
- expect(setDataSpy).toHaveBeenNthCalledWith(6, 'climateControl', 'temperatureControl', '/operationModes/heating/setpoints/roomTemperature', 25);
381
-
382
- await homebridgeAccessory.service.handleTargetHeaterCoolerStateSet(1);
383
- expect(setDataSpy).toHaveBeenNthCalledWith(7, 'climateControl', 'operationMode', 'heating', undefined);
384
- expect(setDataSpy).toHaveBeenNthCalledWith(8, 'climateControl', 'onOffMode', 'on', undefined);
385
-
386
- await homebridgeAccessory.service.handleSwingModeSet(1);
387
- expect(setDataSpy).toHaveBeenNthCalledWith(9, 'climateControl', 'fanControl', '/operationModes/heating/fanDirection/horizontal/currentMode', 'swing');
388
- expect(setDataSpy).toHaveBeenNthCalledWith(10, 'climateControl', 'fanControl', '/operationModes/heating/fanDirection/vertical/currentMode', 'swing');
389
-
390
- // Feature-based setters via FeatureManager
391
- const powerfulFeature = homebridgeAccessory.service.featureManager.getFeature(PowerfulModeFeature);
392
- await powerfulFeature!.handleSet(true);
393
- expect(setDataSpy).toHaveBeenNthCalledWith(11, 'climateControl', 'powerfulMode', 'on', undefined);
394
-
395
- const econoFeature = homebridgeAccessory.service.featureManager.getFeature(EconoModeFeature);
396
- await econoFeature!.handleSet(true);
397
- expect(setDataSpy).toHaveBeenNthCalledWith(12, 'climateControl', 'econoMode', 'on', undefined);
398
-
399
- const streamerFeature = homebridgeAccessory.service.featureManager.getFeature(StreamerModeFeature);
400
- await streamerFeature!.handleSet(true);
401
- expect(setDataSpy).toHaveBeenNthCalledWith(13, 'climateControl', 'streamerMode', 'on', undefined);
402
-
403
- const outdoorSilentFeature = homebridgeAccessory.service.featureManager.getFeature(OutdoorSilentModeFeature);
404
- await outdoorSilentFeature!.handleSet(true);
405
- expect(setDataSpy).toHaveBeenNthCalledWith(14, 'climateControl', 'outdoorSilentMode', 'on', undefined);
406
-
407
- const indoorSilentFeature = homebridgeAccessory.service.featureManager.getFeature(IndoorSilentModeFeature);
408
- await indoorSilentFeature!.handleSet(true);
409
- expect(setDataSpy).toHaveBeenNthCalledWith(15, 'climateControl', 'fanControl', '/operationModes/heating/fanSpeed/currentMode', 'quiet');
410
- });