@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,123 +0,0 @@
1
- /**
2
- * WebSocket Test Script - Mobile App Token
3
- *
4
- * Tests WebSocket with the mobile app token from mobile-tokens.json.
5
- * Run test-gigya-auth.js first to generate the token.
6
- */
7
-
8
- const WebSocket = require('ws');
9
- const fs = require('fs');
10
-
11
- // Load token from mobile-tokens.json
12
- let tokens;
13
- try {
14
- tokens = JSON.parse(fs.readFileSync('mobile-tokens.json', 'utf8'));
15
- } catch (e) {
16
- console.error('Error: Could not load mobile-tokens.json');
17
- console.error('Run test-gigya-auth.js first to generate the token.');
18
- process.exit(1);
19
- }
20
-
21
- const MOBILE_TOKEN = tokens.access_token;
22
- const WEBSOCKET_URL = 'wss://wsapi.onecta.daikineurope.com';
23
-
24
- console.log('=== Daikin WebSocket Test (Mobile App Token) ===\n');
25
-
26
- // Decode token to check expiry
27
- const payload = JSON.parse(Buffer.from(MOBILE_TOKEN.split('.')[1], 'base64').toString());
28
- console.log('Token scope:', payload.scope);
29
- console.log('Token expires at:', new Date(payload.exp * 1000).toISOString());
30
- console.log('Current time:', new Date().toISOString());
31
-
32
- const now = Math.floor(Date.now() / 1000);
33
- if (payload.exp < now) {
34
- console.log('\n⚠️ WARNING: Token is EXPIRED!\n');
35
- console.log('Run test-gigya-auth.js to get a new token.');
36
- process.exit(1);
37
- } else {
38
- console.log('Token valid for:', Math.round((payload.exp - now) / 60), 'minutes\n');
39
- }
40
-
41
- console.log('Connecting to:', WEBSOCKET_URL);
42
- console.log('---\n');
43
-
44
- const ws = new WebSocket(WEBSOCKET_URL, {
45
- headers: {
46
- 'Authorization': 'Bearer ' + MOBILE_TOKEN,
47
- },
48
- });
49
-
50
- let messageCount = 0;
51
- const deviceUpdates = new Map();
52
-
53
- ws.on('open', function() {
54
- console.log('✅ CONNECTED!\n');
55
- console.log('Listening for device updates... (press Ctrl+C to stop)\n');
56
- });
57
-
58
- ws.on('message', function(data) {
59
- try {
60
- const message = JSON.parse(data.toString());
61
-
62
- if (message.message === 'Internal server error') {
63
- return;
64
- }
65
-
66
- messageCount++;
67
-
68
- if (message.event === 'gateway:managementpoint:characteristic') {
69
- const deviceId = message.gatewayDeviceId;
70
- const characteristic = message.data.name;
71
- const value = message.data.value;
72
-
73
- if (!deviceUpdates.has(deviceId)) {
74
- deviceUpdates.set(deviceId, new Set());
75
- console.log('📱 New device discovered:', deviceId.substring(0, 8) + '...');
76
- }
77
- deviceUpdates.get(deviceId).add(characteristic);
78
-
79
- const timestamp = new Date().toLocaleTimeString();
80
- console.log('[' + timestamp + '] Device ' + characteristic + ':', JSON.stringify(value).substring(0, 100));
81
- } else if (message.event === 'group:characteristic') {
82
- const characteristic = message.data.name;
83
- const value = message.data.value;
84
-
85
- const timestamp = new Date().toLocaleTimeString();
86
- console.log('[' + timestamp + '] Group ' + characteristic + ':', JSON.stringify(value).substring(0, 100));
87
- }
88
-
89
- } catch (e) {
90
- // Ignore parse errors
91
- }
92
- });
93
-
94
- ws.on('error', function(err) {
95
- console.log('❌ ERROR:', err.message);
96
- });
97
-
98
- ws.on('close', function(code, reason) {
99
- console.log('\n--- Connection closed ---');
100
- console.log('Code:', code);
101
- console.log('Reason:', reason.toString() || 'none');
102
- console.log('\nTotal messages received:', messageCount);
103
- console.log('Unique devices:', deviceUpdates.size);
104
-
105
- if (deviceUpdates.size > 0) {
106
- console.log('\nDevice characteristics received:');
107
- deviceUpdates.forEach(function(chars, deviceId) {
108
- console.log(' ' + deviceId.substring(0, 8) + '...:', Array.from(chars).join(', '));
109
- });
110
- }
111
-
112
- process.exit(0);
113
- });
114
-
115
- process.on('SIGINT', function() {
116
- console.log('\n\nShutting down...');
117
- ws.close();
118
- });
119
-
120
- setTimeout(function() {
121
- console.log('\n\n⏱️ Test timeout (30s) - closing connection');
122
- ws.close();
123
- }, 30000);
@@ -1,116 +0,0 @@
1
- /**
2
- * WebSocket Test Script
3
- *
4
- * Tests the DaikinWebSocket implementation with a real token.
5
- */
6
-
7
- const WebSocket = require('ws');
8
-
9
- // Read token from file
10
- const fs = require('fs');
11
- const tokenData = JSON.parse(
12
- fs.readFileSync('./hbConfig/.daikin-controller-cloud-tokenset', 'utf8')
13
- );
14
-
15
- const WEBSOCKET_URL = 'wss://wsapi.onecta.daikineurope.com';
16
-
17
- console.log('=== Daikin WebSocket Test ===\n');
18
- console.log('Token expires at:', new Date(tokenData.expires_at * 1000).toISOString());
19
- console.log('Current time:', new Date().toISOString());
20
-
21
- const now = Math.floor(Date.now() / 1000);
22
- if (tokenData.expires_at < now) {
23
- console.log('\n⚠️ WARNING: Token is EXPIRED! You may need to refresh it.\n');
24
- }
25
-
26
- console.log('\nConnecting to:', WEBSOCKET_URL);
27
- console.log('---\n');
28
-
29
- const ws = new WebSocket(WEBSOCKET_URL, {
30
- headers: {
31
- 'Authorization': 'Bearer ' + tokenData.access_token,
32
- },
33
- });
34
-
35
- let messageCount = 0;
36
- const deviceUpdates = new Map();
37
-
38
- ws.on('open', function() {
39
- console.log('✅ CONNECTED!\n');
40
- console.log('Listening for device updates... (press Ctrl+C to stop)\n');
41
- });
42
-
43
- ws.on('message', function(data) {
44
- try {
45
- const message = JSON.parse(data.toString());
46
-
47
- // Skip internal server errors (from invalid message formats)
48
- if (message.message === 'Internal server error') {
49
- return;
50
- }
51
-
52
- messageCount++;
53
-
54
- if (message.event === 'gateway:managementpoint:characteristic') {
55
- const deviceId = message.gatewayDeviceId;
56
- const characteristic = message.data.name;
57
- const value = message.data.value;
58
-
59
- // Track unique devices
60
- if (!deviceUpdates.has(deviceId)) {
61
- deviceUpdates.set(deviceId, new Set());
62
- console.log('📱 New device discovered:', deviceId.substring(0, 8) + '...');
63
- }
64
- deviceUpdates.get(deviceId).add(characteristic);
65
-
66
- // Log the update
67
- const timestamp = new Date().toLocaleTimeString();
68
- console.log('[' + timestamp + '] Device: ' + deviceId.substring(0, 8) + '... | ' + characteristic + ':', JSON.stringify(value).substring(0, 80));
69
- } else if (message.event === 'group:characteristic') {
70
- const groupId = message.groupId;
71
- const characteristic = message.data.name;
72
- const value = message.data.value;
73
-
74
- const timestamp = new Date().toLocaleTimeString();
75
- console.log('[' + timestamp + '] Group: ' + groupId.substring(0, 8) + '... | ' + characteristic + ':', JSON.stringify(value).substring(0, 80));
76
- } else {
77
- console.log('Unknown event type:', message.event || 'N/A');
78
- }
79
-
80
- } catch (e) {
81
- // Ignore parse errors
82
- }
83
- });
84
-
85
- ws.on('error', function(err) {
86
- console.log('❌ ERROR:', err.message);
87
- });
88
-
89
- ws.on('close', function(code, reason) {
90
- console.log('\n--- Connection closed ---');
91
- console.log('Code:', code);
92
- console.log('Reason:', reason.toString() || 'none');
93
- console.log('\nTotal messages received:', messageCount);
94
- console.log('Unique devices:', deviceUpdates.size);
95
-
96
- if (deviceUpdates.size > 0) {
97
- console.log('\nDevice characteristics received:');
98
- deviceUpdates.forEach(function(chars, deviceId) {
99
- console.log(' ' + deviceId.substring(0, 8) + '...:', Array.from(chars).join(', '));
100
- });
101
- }
102
-
103
- process.exit(0);
104
- });
105
-
106
- // Handle Ctrl+C gracefully
107
- process.on('SIGINT', function() {
108
- console.log('\n\nShutting down...');
109
- ws.close();
110
- });
111
-
112
- // Keep running for 60 seconds max
113
- setTimeout(function() {
114
- console.log('\n\n⏱️ Test timeout (60s) - closing connection');
115
- ws.close();
116
- }, 60000);