homebridge-roborock-matter 2.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.
Files changed (59) hide show
  1. package/.gitattributes +13 -0
  2. package/CHANGELOG.md +469 -0
  3. package/LICENSE +23 -0
  4. package/README.md +45 -0
  5. package/__tests__/testdata/b01_q7_get_prop_response.json +1 -0
  6. package/__tests__/testdata/b01_q7_map_fixture.json +16 -0
  7. package/config.schema.json +118 -0
  8. package/dist/crypto.js +65 -0
  9. package/dist/crypto.js.map +1 -0
  10. package/dist/index.js +10 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/live_message.js +25 -0
  13. package/dist/live_message.js.map +1 -0
  14. package/dist/logger.js +37 -0
  15. package/dist/logger.js.map +1 -0
  16. package/dist/matter_vacuum_accessory.js +1877 -0
  17. package/dist/matter_vacuum_accessory.js.map +1 -0
  18. package/dist/platform.js +441 -0
  19. package/dist/platform.js.map +1 -0
  20. package/dist/settings.js +8 -0
  21. package/dist/settings.js.map +1 -0
  22. package/dist/types.js +3 -0
  23. package/dist/types.js.map +1 -0
  24. package/dist/ui/index.js +741 -0
  25. package/dist/ui/index.js.map +1 -0
  26. package/docs/matter-rvc-updating-homebridge-report.md +120 -0
  27. package/homebridge-roborock-vacuum.code-workspace +7 -0
  28. package/homebridge-ui/public/index.html +257 -0
  29. package/homebridge-ui/public/index.js +1276 -0
  30. package/homebridge-ui/public/styles.css +611 -0
  31. package/homebridge-ui/server.js +3 -0
  32. package/package.json +91 -0
  33. package/roborockLib/i18n/de/translations.json +188 -0
  34. package/roborockLib/i18n/en/translations.json +208 -0
  35. package/roborockLib/i18n/es/translations.json +188 -0
  36. package/roborockLib/i18n/fr/translations.json +188 -0
  37. package/roborockLib/i18n/it/translations.json +188 -0
  38. package/roborockLib/i18n/nl/translations.json +188 -0
  39. package/roborockLib/i18n/pl/translations.json +188 -0
  40. package/roborockLib/i18n/pt/translations.json +188 -0
  41. package/roborockLib/i18n/ru/translations.json +188 -0
  42. package/roborockLib/i18n/uk/translations.json +188 -0
  43. package/roborockLib/i18n/zh-cn/translations.json +188 -0
  44. package/roborockLib/lib/RRMapParser.js +459 -0
  45. package/roborockLib/lib/b01Q7Adapter.js +468 -0
  46. package/roborockLib/lib/deviceFeatures.js +1349 -0
  47. package/roborockLib/lib/localConnector.js +629 -0
  48. package/roborockLib/lib/map/map.html +137 -0
  49. package/roborockLib/lib/map/zones.js +851 -0
  50. package/roborockLib/lib/message.js +402 -0
  51. package/roborockLib/lib/messageQueueHandler.js +411 -0
  52. package/roborockLib/lib/roborockAuth.js +178 -0
  53. package/roborockLib/lib/roborockCrypto.js +55 -0
  54. package/roborockLib/lib/roborockPackageHelper.js +142 -0
  55. package/roborockLib/lib/roborock_mqtt_connector.js +567 -0
  56. package/roborockLib/lib/sniffing/mitmproxy_roborock.py +300 -0
  57. package/roborockLib/lib/vacuum.js +992 -0
  58. package/roborockLib/roborockAPI.js +3635 -0
  59. package/tsconfig.roborockLib.json +17 -0
@@ -0,0 +1,611 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&display=swap");
2
+
3
+ :root {
4
+ color-scheme: light;
5
+ --bg: #f4f6f9;
6
+ --panel: #ffffff;
7
+ --panel-soft: #f6f8fa;
8
+ --input: #ffffff;
9
+ --text: #17242f;
10
+ --heading: #0c1a24;
11
+ --muted: #5a6b7b;
12
+ --primary: #0e7a6a;
13
+ --primary-strong: #0b6357;
14
+ --primary-text: #ffffff;
15
+ --secondary: #eef2f5;
16
+ --border: #d9e0e7;
17
+ --shadow: 0 6px 18px rgba(15, 32, 44, 0.07);
18
+ --warning: #9a5b00;
19
+ --danger: #b42323;
20
+ --link: #0e7a6a;
21
+ }
22
+
23
+ * {
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ body {
28
+ margin: 0;
29
+ font-family: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
30
+ background: linear-gradient(
31
+ 180deg,
32
+ rgba(14, 122, 106, 0.06),
33
+ transparent 260px
34
+ ),
35
+ var(--bg);
36
+ color: var(--text);
37
+ }
38
+
39
+ .container {
40
+ max-width: 860px;
41
+ margin: 0 auto;
42
+ padding: 28px 24px 24px;
43
+ display: grid;
44
+ gap: 18px;
45
+ }
46
+
47
+ /* Explicit heading colors: Homebridge UI injects its own theme stylesheet
48
+ into custom-UI iframes, so headings must not rely on inheritance or they
49
+ can end up dark-on-dark / light-on-light. */
50
+ header h1 {
51
+ font-size: 2.15rem;
52
+ line-height: 1.1;
53
+ margin: 0 0 8px;
54
+ color: var(--heading);
55
+ }
56
+
57
+ .subtitle {
58
+ margin: 0;
59
+ color: var(--muted);
60
+ }
61
+
62
+ .panel {
63
+ background: var(--panel);
64
+ border: 1px solid var(--border);
65
+ border-radius: 10px;
66
+ padding: 22px;
67
+ box-shadow: var(--shadow);
68
+ }
69
+
70
+ .panel h2 {
71
+ margin: 0 0 6px;
72
+ font-size: 1.18rem;
73
+ line-height: 1.25;
74
+ color: var(--heading);
75
+ }
76
+
77
+ .panel h3 {
78
+ color: var(--heading);
79
+ }
80
+
81
+ .section-header {
82
+ display: flex;
83
+ justify-content: space-between;
84
+ align-items: flex-start;
85
+ gap: 16px;
86
+ }
87
+
88
+ .help {
89
+ color: var(--muted);
90
+ margin: 0;
91
+ line-height: 1.5;
92
+ }
93
+
94
+ .help a {
95
+ color: var(--link);
96
+ font-weight: 600;
97
+ }
98
+
99
+ .muted {
100
+ color: var(--muted);
101
+ }
102
+
103
+ .settings-grid {
104
+ display: grid;
105
+ grid-template-columns: repeat(2, minmax(0, 1fr));
106
+ gap: 16px;
107
+ margin-top: 18px;
108
+ }
109
+
110
+ .field {
111
+ display: grid;
112
+ gap: 7px;
113
+ color: var(--text);
114
+ font-size: 0.95rem;
115
+ font-weight: 600;
116
+ }
117
+
118
+ .field small,
119
+ .checkbox small {
120
+ color: var(--muted);
121
+ display: block;
122
+ font-size: 0.82rem;
123
+ font-weight: 400;
124
+ line-height: 1.35;
125
+ }
126
+
127
+ select,
128
+ input {
129
+ width: 100%;
130
+ padding: 12px 14px;
131
+ border-radius: 8px;
132
+ border: 1px solid var(--border);
133
+ background: var(--input);
134
+ color: var(--text);
135
+ font-size: 1rem;
136
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
137
+ }
138
+
139
+ select:focus,
140
+ input:focus {
141
+ outline: none;
142
+ border-color: var(--primary);
143
+ box-shadow: 0 0 0 3px rgba(14, 122, 106, 0.16);
144
+ }
145
+
146
+ input::placeholder {
147
+ color: #8a97a5;
148
+ }
149
+
150
+ .checkbox {
151
+ display: flex;
152
+ align-items: flex-start;
153
+ gap: 10px;
154
+ margin-top: 18px;
155
+ color: var(--text);
156
+ font-weight: 600;
157
+ }
158
+
159
+ .checkbox input {
160
+ width: auto;
161
+ margin: 4px 0 0;
162
+ accent-color: var(--primary);
163
+ }
164
+
165
+ .matter-setup-note {
166
+ margin: 8px 0 0 28px;
167
+ color: var(--warning);
168
+ max-width: 680px;
169
+ }
170
+
171
+ .advanced-settings {
172
+ margin-top: 20px;
173
+ padding: 14px 16px 16px;
174
+ border: 1px solid var(--border);
175
+ border-radius: 8px;
176
+ background: var(--panel-soft);
177
+ }
178
+
179
+ .advanced-settings summary {
180
+ cursor: pointer;
181
+ color: var(--heading);
182
+ font-weight: 600;
183
+ }
184
+
185
+ .advanced-settings .help {
186
+ margin-top: 8px;
187
+ }
188
+
189
+ .advanced-settings .checkbox {
190
+ margin-top: 16px;
191
+ }
192
+
193
+ .hidden {
194
+ display: none;
195
+ }
196
+
197
+ .readonly input,
198
+ .readonly select {
199
+ background: var(--secondary);
200
+ color: var(--muted);
201
+ }
202
+
203
+ .actions {
204
+ display: flex;
205
+ gap: 12px;
206
+ flex-wrap: wrap;
207
+ margin-top: 20px;
208
+ }
209
+
210
+ .diagnostics-actions {
211
+ display: flex;
212
+ gap: 10px;
213
+ justify-content: flex-end;
214
+ flex-wrap: wrap;
215
+ }
216
+
217
+ button {
218
+ border: none;
219
+ border-radius: 8px;
220
+ padding: 12px 20px;
221
+ font-size: 0.95rem;
222
+ font-weight: 600;
223
+ cursor: pointer;
224
+ transition:
225
+ transform 0.15s ease,
226
+ box-shadow 0.15s ease,
227
+ background-color 0.15s ease;
228
+ }
229
+
230
+ button:hover {
231
+ transform: translateY(-1px);
232
+ box-shadow: 0 10px 20px rgba(14, 122, 106, 0.14);
233
+ }
234
+
235
+ button:active {
236
+ transform: translateY(0);
237
+ }
238
+
239
+ button:focus-visible,
240
+ a:focus-visible,
241
+ input:focus-visible,
242
+ select:focus-visible {
243
+ outline: 2px solid var(--primary);
244
+ outline-offset: 2px;
245
+ }
246
+
247
+ .primary {
248
+ background: var(--primary);
249
+ color: var(--primary-text);
250
+ }
251
+
252
+ .primary:hover {
253
+ background: var(--primary-strong);
254
+ }
255
+
256
+ .secondary {
257
+ background: var(--panel);
258
+ color: var(--text);
259
+ border: 1px solid var(--border);
260
+ }
261
+
262
+ .secondary:hover {
263
+ background: var(--secondary);
264
+ }
265
+
266
+ .toast-container {
267
+ position: fixed;
268
+ right: 24px;
269
+ bottom: 24px;
270
+ display: flex;
271
+ flex-direction: column;
272
+ gap: 12px;
273
+ z-index: 9999;
274
+ }
275
+
276
+ .toast {
277
+ background: var(--panel);
278
+ border: 1px solid var(--border);
279
+ border-left-width: 4px;
280
+ border-radius: 8px;
281
+ padding: 12px 16px;
282
+ box-shadow: var(--shadow);
283
+ min-width: 220px;
284
+ color: var(--text);
285
+ animation: slide-in 0.2s ease-out;
286
+ }
287
+
288
+ .toast.success {
289
+ border-left-color: #16a34a;
290
+ }
291
+
292
+ .toast.error {
293
+ border-left-color: #dc2626;
294
+ }
295
+
296
+ .toast.warning {
297
+ border-left-color: #d97706;
298
+ }
299
+
300
+ .diagnostics-summary {
301
+ margin: 18px 0 14px;
302
+ }
303
+
304
+ .local-test-results {
305
+ display: grid;
306
+ gap: 12px;
307
+ margin: 0 0 14px;
308
+ }
309
+
310
+ .local-test-results.hidden {
311
+ display: none;
312
+ }
313
+
314
+ .local-test-heading {
315
+ display: flex;
316
+ justify-content: space-between;
317
+ align-items: baseline;
318
+ gap: 12px;
319
+ padding-top: 4px;
320
+ }
321
+
322
+ .local-test-heading h3 {
323
+ margin: 0;
324
+ font-size: 1rem;
325
+ color: var(--heading);
326
+ }
327
+
328
+ .diagnostics-list {
329
+ display: grid;
330
+ gap: 14px;
331
+ }
332
+
333
+ .pairing-list {
334
+ display: grid;
335
+ gap: 14px;
336
+ }
337
+
338
+ .device-list {
339
+ display: grid;
340
+ gap: 8px;
341
+ margin-top: 14px;
342
+ }
343
+
344
+ .device-row {
345
+ display: flex;
346
+ align-items: flex-start;
347
+ gap: 12px;
348
+ padding: 12px 14px;
349
+ border: 1px solid var(--border);
350
+ border-radius: 8px;
351
+ background: var(--panel-soft);
352
+ cursor: pointer;
353
+ transition: border-color 0.15s ease, background-color 0.15s ease;
354
+ }
355
+
356
+ .device-row:hover {
357
+ border-color: var(--primary);
358
+ background: #f1f7f5;
359
+ }
360
+
361
+ .device-row input {
362
+ width: auto;
363
+ margin-top: 3px;
364
+ accent-color: var(--primary);
365
+ }
366
+
367
+ .device-row .device-text {
368
+ display: grid;
369
+ gap: 2px;
370
+ text-align: left;
371
+ }
372
+
373
+ .device-row .device-title {
374
+ font-weight: 600;
375
+ color: var(--heading);
376
+ }
377
+
378
+ .device-row small {
379
+ color: var(--muted);
380
+ line-height: 1.35;
381
+ }
382
+
383
+ .device-row .pill {
384
+ margin-left: auto;
385
+ align-self: center;
386
+ }
387
+
388
+ .diagnostic-device,
389
+ .local-test-card,
390
+ .pairing-card {
391
+ border: 1px solid var(--border);
392
+ border-radius: 8px;
393
+ padding: 16px;
394
+ background: var(--panel-soft);
395
+ }
396
+
397
+ .local-test-card.good {
398
+ border-color: rgba(22, 163, 74, 0.45);
399
+ }
400
+
401
+ .local-test-card.warn {
402
+ border-color: rgba(217, 119, 6, 0.45);
403
+ }
404
+
405
+ .device-header {
406
+ display: flex;
407
+ justify-content: space-between;
408
+ align-items: center;
409
+ gap: 12px;
410
+ margin-bottom: 12px;
411
+ }
412
+
413
+ .device-header h3 {
414
+ margin: 0;
415
+ font-size: 1.05rem;
416
+ color: var(--heading);
417
+ }
418
+
419
+ .connection-hint {
420
+ color: var(--muted);
421
+ font-size: 0.9rem;
422
+ line-height: 1.45;
423
+ margin: 0 0 14px;
424
+ }
425
+
426
+ .pairing-content {
427
+ display: grid;
428
+ grid-template-columns: 210px minmax(0, 1fr);
429
+ gap: 16px;
430
+ align-items: start;
431
+ }
432
+
433
+ .qr-wrap {
434
+ display: grid;
435
+ gap: 10px;
436
+ justify-items: center;
437
+ }
438
+
439
+ .qr-image,
440
+ .qr-placeholder {
441
+ width: 180px;
442
+ height: 180px;
443
+ border-radius: 8px;
444
+ border: 1px solid var(--border);
445
+ background: #ffffff;
446
+ }
447
+
448
+ .qr-placeholder {
449
+ color: var(--muted);
450
+ display: grid;
451
+ place-items: center;
452
+ padding: 12px;
453
+ text-align: center;
454
+ }
455
+
456
+ .pairing-details {
457
+ margin: 0;
458
+ display: grid;
459
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
460
+ gap: 10px 16px;
461
+ }
462
+
463
+ .pairing-details dt {
464
+ color: var(--muted);
465
+ font-size: 0.82rem;
466
+ margin-bottom: 4px;
467
+ }
468
+
469
+ .pairing-details dd {
470
+ margin: 0;
471
+ word-break: break-word;
472
+ color: var(--text);
473
+ }
474
+
475
+ .setup-code {
476
+ color: var(--heading);
477
+ font-family: "Consolas", "Menlo", monospace;
478
+ font-size: 1.15rem;
479
+ font-weight: 700;
480
+ letter-spacing: 0;
481
+ }
482
+
483
+ .pairing-actions {
484
+ display: flex;
485
+ gap: 10px;
486
+ flex-wrap: wrap;
487
+ margin-top: 14px;
488
+ }
489
+
490
+ .compact {
491
+ padding: 9px 12px;
492
+ font-size: 0.84rem;
493
+ }
494
+
495
+ .pill {
496
+ display: inline-flex;
497
+ align-items: center;
498
+ border-radius: 999px;
499
+ padding: 6px 10px;
500
+ font-size: 0.8rem;
501
+ font-weight: 600;
502
+ border: 1px solid var(--border);
503
+ white-space: nowrap;
504
+ color: var(--muted);
505
+ background: var(--panel);
506
+ }
507
+
508
+ .pill.good {
509
+ background: #e6f6ec;
510
+ border-color: #b5e3c5;
511
+ color: #14683a;
512
+ }
513
+
514
+ .pill.warn {
515
+ background: #fdf3df;
516
+ border-color: #f0d9a8;
517
+ color: #8a5a00;
518
+ }
519
+
520
+ .status-pill {
521
+ border: 1px solid var(--border);
522
+ border-radius: 999px;
523
+ color: var(--muted);
524
+ font-size: 0.82rem;
525
+ font-weight: 600;
526
+ padding: 7px 11px;
527
+ white-space: nowrap;
528
+ background: var(--panel);
529
+ }
530
+
531
+ .status-pill.good {
532
+ background: #e4f5ef;
533
+ border-color: #a9dfcd;
534
+ color: #0c6b52;
535
+ }
536
+
537
+ .status-pill.warn {
538
+ background: #fdf3df;
539
+ border-color: #edd39c;
540
+ color: #8a5a00;
541
+ }
542
+
543
+ .diagnostic-device dl,
544
+ .local-test-card dl {
545
+ margin: 0;
546
+ display: grid;
547
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
548
+ gap: 10px 16px;
549
+ }
550
+
551
+ .diagnostic-device dt,
552
+ .local-test-card dt {
553
+ color: var(--muted);
554
+ font-size: 0.82rem;
555
+ margin-bottom: 4px;
556
+ }
557
+
558
+ .diagnostic-device dd,
559
+ .local-test-card dd {
560
+ margin: 0;
561
+ word-break: break-word;
562
+ color: var(--text);
563
+ }
564
+
565
+ @keyframes slide-in {
566
+ from {
567
+ transform: translateY(10px);
568
+ opacity: 0;
569
+ }
570
+ to {
571
+ transform: translateY(0);
572
+ opacity: 1;
573
+ }
574
+ }
575
+
576
+ @media (max-width: 640px) {
577
+ header h1 {
578
+ font-size: 1.85rem;
579
+ }
580
+
581
+ .container {
582
+ padding: 22px 16px;
583
+ }
584
+
585
+ .settings-grid {
586
+ grid-template-columns: 1fr;
587
+ }
588
+
589
+ .section-header,
590
+ .device-header,
591
+ .local-test-heading {
592
+ flex-direction: column;
593
+ align-items: stretch;
594
+ }
595
+
596
+ .pairing-content {
597
+ grid-template-columns: 1fr;
598
+ }
599
+
600
+ .diagnostics-actions {
601
+ justify-content: stretch;
602
+ }
603
+
604
+ .diagnostics-actions button {
605
+ flex: 1 1 100%;
606
+ }
607
+
608
+ .device-row .pill {
609
+ margin-left: 0;
610
+ }
611
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ require("../dist/ui/index.js");
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "homebridge-roborock-matter",
3
+ "version": "2.1.0",
4
+ "description": "Matter-only Homebridge plugin publishing Roborock robot vacuums (including 2025 B01/Q7-series) as native Matter accessories for Apple Home. Fork of homebridge-roborock-vacuum2.",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "Mathias Hornbek"
8
+ },
9
+ "keywords": [
10
+ "apple-home",
11
+ "homebridge-plugin",
12
+ "homekit",
13
+ "matter",
14
+ "q7",
15
+ "roborock",
16
+ "roborock app",
17
+ "roborock vacuum",
18
+ "vacuum",
19
+ "vacuum cleaner"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/mathiashornbek/homebridge-roborock-matter.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/mathiashornbek/homebridge-roborock-matter/issues"
27
+ },
28
+ "homepage": "https://github.com/mathiashornbek/homebridge-roborock-matter#readme",
29
+ "main": "dist/index.js",
30
+ "engines": {
31
+ "homebridge": "^1.6.0 || ^2.0.0-beta.0",
32
+ "node": "^18.20.4 || ^20.15.1 || ^22 || ^24"
33
+ },
34
+ "scripts": {
35
+ "clean": "rimraf ./dist",
36
+ "build": "rimraf ./dist && tsc",
37
+ "prepare": "npm run build",
38
+ "prepublishOnly": "npm run build",
39
+ "postpublish": "npm run clean",
40
+ "lint": "prettier --check .",
41
+ "lint:fix": "prettier --write .",
42
+ "test": "jest",
43
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.roborockLib.json"
44
+ },
45
+ "dependencies": {
46
+ "@homebridge/plugin-ui-utils": "^2.1.2",
47
+ "abstract-things": "0.9.0",
48
+ "axios": "^1.13.4",
49
+ "binary-parser": "^2.3.0",
50
+ "chalk": "4.1.2",
51
+ "crc-32": "^1.2.2",
52
+ "debug": "4.3.5",
53
+ "deep-equal": "2.2.3",
54
+ "express": "^4.22.1",
55
+ "jszip": "^3.10.1",
56
+ "mqtt": "^5.15.0",
57
+ "node-forge": "^1.3.3",
58
+ "qrcode": "^1.5.4",
59
+ "rxjs": "^7.8.2",
60
+ "semver": "7.6.2",
61
+ "tinkerhub-discovery": "0.3.1",
62
+ "yargs": "15.4.1"
63
+ },
64
+ "devDependencies": {
65
+ "@babel/core": "7.24.8",
66
+ "@babel/preset-env": "7.24.8",
67
+ "@babel/preset-typescript": "7.24.7",
68
+ "@types/jest": "30.0.0",
69
+ "@types/node": "20.14.10",
70
+ "@types/qrcode": "^1.5.6",
71
+ "@types/semver": "7.5.8",
72
+ "babel-jest": "30.2.0",
73
+ "homebridge": "1.8.3",
74
+ "jest": "30.2.0",
75
+ "prettier": "3.3.2",
76
+ "rimraf": "6.0.1",
77
+ "typescript": "5.5.3"
78
+ },
79
+ "contributors": [
80
+ {
81
+ "name": "Nico Hartung",
82
+ "url": "https://github.com/homebridge-roborock"
83
+ },
84
+ {
85
+ "name": "Joshua Appleman",
86
+ "email": "joshua@appleman.us",
87
+ "url": "https://github.com/applemanj"
88
+ }
89
+ ],
90
+ "forkedFrom": "homebridge-roborock-vacuum2 by Joshua Appleman (https://github.com/applemanj/homebridge-roborock-vacuum2)"
91
+ }