@homebridge-plugins/homebridge-ecovacs 7.2.4-beta.1 → 7.2.4-beta.2

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 (2) hide show
  1. package/lib/platform.js +111 -65
  2. package/package.json +1 -1
package/lib/platform.js CHANGED
@@ -14,6 +14,7 @@ const plugin = require('../package.json')
14
14
 
15
15
  const devicesInHB = new Map()
16
16
  const matterDevicesInHB = new Map()
17
+ const deviceControls = new Map()
17
18
 
18
19
  export default class {
19
20
  constructor(log, config, api) {
@@ -441,9 +442,9 @@ export default class {
441
442
  })
442
443
 
443
444
  // Disconnect from each Ecovacs/Yeedi device
444
- devicesInHB.forEach((accessory) => {
445
- if (accessory.control?.is_ready) {
446
- accessory.control.disconnect()
445
+ deviceControls.forEach((control) => {
446
+ if (control?.is_ready) {
447
+ control.disconnect()
447
448
  }
448
449
  })
449
450
  } catch (err) {
@@ -451,6 +452,11 @@ export default class {
451
452
  }
452
453
  }
453
454
 
455
+ getControl(accessory) {
456
+ // Get the device control from the shared deviceControls map
457
+ return deviceControls.get(accessory.context.ecoDeviceId)
458
+ }
459
+
454
460
  createMatterRVCAccessory(device, accessory) {
455
461
  // Create a Matter RVC (Robotic Vacuum Cleaner) accessory config
456
462
  const serialNumber = device.did
@@ -497,7 +503,12 @@ export default class {
497
503
 
498
504
  context: {
499
505
  hapAccessoryUUID: accessory.UUID,
500
- deviceId: device.did,
506
+ ecoDeviceId: device.did,
507
+ ecoCompany: device.company || 'Ecovacs',
508
+ ecoModel: device.deviceModel || 'Deebot',
509
+ ecoClass: device.class,
510
+ ecoResource: device.resource,
511
+ ecoImage: device.deviceImageURL,
501
512
  },
502
513
 
503
514
  clusters: {
@@ -876,99 +887,99 @@ export default class {
876
887
  accessory.removeService(accessory.getService('TrueDetect'))
877
888
  }
878
889
 
879
- // Save the device control information to the accessory
880
- accessory.control = loadedDevice
890
+ // Save the device control information to the shared deviceControls map
891
+ deviceControls.set(device.did, loadedDevice)
881
892
 
882
893
  // Some models can use a V2 for supported commands
883
- accessory.context.commandSuffix = accessory.control.is950type_V2()
894
+ accessory.context.commandSuffix = loadedDevice.is950type_V2()
884
895
  ? '_V2'
885
896
  : ''
886
897
 
887
898
  // Set up a listener for the device 'ready' event
888
- accessory.control.on('ready', (event) => {
899
+ loadedDevice.on('ready', (event) => {
889
900
  if (event) {
890
901
  this.externalReadyUpdate(accessory)
891
902
  }
892
903
  })
893
904
 
894
905
  // Set up a listener for the device 'CleanReport' event
895
- accessory.control.on('CleanReport', (newVal) => {
906
+ loadedDevice.on('CleanReport', (newVal) => {
896
907
  this.externalCleanUpdate(accessory, newVal)
897
908
  })
898
909
 
899
910
  // Set up a listener for the device 'CurrentCustomAreaValues' event
900
- accessory.control.on('CurrentCustomAreaValues', (newVal) => {
911
+ loadedDevice.on('CurrentCustomAreaValues', (newVal) => {
901
912
  accessory.logDebug(`CurrentCustomAreaValues: ${JSON.stringify(newVal)}`)
902
913
  })
903
914
 
904
915
  // Set up a listener for the device 'CleanSpeed' event
905
- accessory.control.on('CleanSpeed', (newVal) => {
916
+ loadedDevice.on('CleanSpeed', (newVal) => {
906
917
  this.externalSpeedUpdate(accessory, newVal)
907
918
  })
908
919
 
909
920
  // Set up a listener for the device 'BatteryInfo' event
910
- accessory.control.on('BatteryInfo', async (newVal) => {
921
+ loadedDevice.on('BatteryInfo', async (newVal) => {
911
922
  await this.externalBatteryUpdate(accessory, newVal)
912
923
  })
913
924
 
914
925
  // Set up a listener for the device 'AirDryingState' event
915
926
  // Only if the service exists
916
927
  if (accessory.getService('Air Drying')) {
917
- accessory.control.on('AirDryingState', (newVal) => {
928
+ loadedDevice.on('AirDryingState', (newVal) => {
918
929
  this.externalAirDryingUpdate(accessory, newVal)
919
930
  })
920
931
  }
921
932
 
922
933
  // Set up a listener for the device 'ChargeState' event
923
- accessory.control.on('ChargeState', (newVal) => {
934
+ loadedDevice.on('ChargeState', (newVal) => {
924
935
  this.externalChargeUpdate(accessory, newVal)
925
936
  })
926
937
 
927
938
  // Set up a listener for the device 'NetInfoIP' event
928
- accessory.control.on('NetInfoIP', (newVal) => {
939
+ loadedDevice.on('NetInfoIP', (newVal) => {
929
940
  this.externalIPUpdate(accessory, newVal)
930
941
  })
931
942
 
932
943
  // Set up a listener for the device 'NetInfoMAC' event
933
- accessory.control.on('NetInfoMAC', (newVal) => {
944
+ loadedDevice.on('NetInfoMAC', (newVal) => {
934
945
  this.externalMacUpdate(accessory, newVal)
935
946
  })
936
947
 
937
948
  if (accessory.context.rawConfig.supportTrueDetect) {
938
949
  // Set up a listener for the device 'TrueDetect' event
939
- accessory.control.on('TrueDetect', (newVal) => {
950
+ loadedDevice.on('TrueDetect', (newVal) => {
940
951
  this.externalTrueDetectUpdate(accessory, newVal)
941
952
  })
942
953
  }
943
954
 
944
955
  // Set up a listener for the device 'message' event
945
- accessory.control.on('message', async (msg) => {
956
+ loadedDevice.on('message', async (msg) => {
946
957
  await this.externalMessageUpdate(accessory, msg)
947
958
  })
948
959
 
949
960
  // Set up a listener for the device 'Error' event
950
- accessory.control.on('Error', async (err) => {
961
+ loadedDevice.on('Error', async (err) => {
951
962
  if (err) {
952
963
  await this.externalErrorUpdate(accessory, err)
953
964
  }
954
965
  })
955
966
 
956
967
  // Set up listeners for map data if accessory debug logging is on
957
- accessory.control.on('Maps', (maps) => {
968
+ loadedDevice.on('Maps', (maps) => {
958
969
  if (maps) {
959
970
  accessory.logDebug(`Maps: ${JSON.stringify(maps)}`)
960
971
  Object.keys(maps.maps).forEach((key) => {
961
- accessory.control.run('GetSpotAreas', maps.maps[key].mapID)
962
- accessory.control.run('GetVirtualBoundaries', maps.maps[key].mapID)
972
+ loadedDevice.run('GetSpotAreas', maps.maps[key].mapID)
973
+ loadedDevice.run('GetVirtualBoundaries', maps.maps[key].mapID)
963
974
  })
964
975
  }
965
976
  })
966
977
 
967
- accessory.control.on('MapSpotAreas', (spotAreas) => {
978
+ loadedDevice.on('MapSpotAreas', (spotAreas) => {
968
979
  if (spotAreas) {
969
980
  accessory.logDebug(`MapSpotAreas: ${JSON.stringify(spotAreas)}`)
970
981
  Object.keys(spotAreas.mapSpotAreas).forEach((key) => {
971
- accessory.control.run(
982
+ loadedDevice.run(
972
983
  'GetSpotAreaInfo',
973
984
  spotAreas.mapID,
974
985
  spotAreas.mapSpotAreas[key].mapSpotAreaID,
@@ -977,13 +988,13 @@ export default class {
977
988
  }
978
989
  })
979
990
 
980
- accessory.control.on('MapSpotAreaInfo', (area) => {
991
+ loadedDevice.on('MapSpotAreaInfo', (area) => {
981
992
  if (area) {
982
993
  accessory.logDebug(`MapSpotAreaInfo: ${JSON.stringify(area)}`)
983
994
  }
984
995
  })
985
996
 
986
- accessory.control.on('MapVirtualBoundaries', (vbs) => {
997
+ loadedDevice.on('MapVirtualBoundaries', (vbs) => {
987
998
  if (vbs) {
988
999
  accessory.logDebug(`MapVirtualBoundaries: ${JSON.stringify(vbs)}`)
989
1000
  const vbsCombined = [...vbs.mapVirtualWalls, ...vbs.mapNoMopZones]
@@ -992,7 +1003,7 @@ export default class {
992
1003
  virtualBoundaryArray[vbsCombined[key].mapVirtualBoundaryID] = vbsCombined[key]
993
1004
  })
994
1005
  Object.keys(virtualBoundaryArray).forEach((key) => {
995
- accessory.control.run(
1006
+ loadedDevice.run(
996
1007
  'GetVirtualBoundaryInfo',
997
1008
  vbs.mapID,
998
1009
  virtualBoundaryArray[key].mapVirtualBoundaryID,
@@ -1002,21 +1013,21 @@ export default class {
1002
1013
  }
1003
1014
  })
1004
1015
 
1005
- accessory.control.on('MapVirtualBoundaryInfo', (vb) => {
1016
+ loadedDevice.on('MapVirtualBoundaryInfo', (vb) => {
1006
1017
  if (vb) {
1007
1018
  accessory.logDebug(`MapVirtualBoundaryInfo: ${JSON.stringify(vb)}`)
1008
1019
  }
1009
1020
  })
1010
1021
 
1011
1022
  // Connect to the device
1012
- accessory.control.connect()
1023
+ loadedDevice.connect()
1013
1024
 
1014
1025
  // Refresh the current state of all the accessories
1015
1026
  this.refreshAccessory(accessory)
1016
1027
  const { pollInterval } = accessory.context.rawConfig[device] || platformConsts.defaultValues
1017
1028
  if (pollInterval > 0) {
1018
1029
  this.refreshIntervals[device.did] = setInterval(() => {
1019
- devicesInHB.get(this.api.hap.uuid.generate(device.did)).control?.refresh()
1030
+ deviceControls.get(device.did)?.refresh()
1020
1031
  }, pollInterval * 1000)
1021
1032
  }
1022
1033
 
@@ -1099,7 +1110,8 @@ export default class {
1099
1110
  refreshAccessory(accessory) {
1100
1111
  try {
1101
1112
  // Check the device has initialised already
1102
- if (!accessory.control) {
1113
+ const control = this.getControl(accessory)
1114
+ if (!control) {
1103
1115
  return
1104
1116
  }
1105
1117
 
@@ -1108,29 +1120,29 @@ export default class {
1108
1120
 
1109
1121
  // Run the commands to get the state of the device
1110
1122
  accessory.logDebug(`${platformLang.sendCmd} [GetBatteryState]`)
1111
- accessory.control.run('GetBatteryState')
1123
+ control.run('GetBatteryState')
1112
1124
 
1113
1125
  accessory.logDebug(`${platformLang.sendCmd} [GetChargeState]`)
1114
- accessory.control.run('GetChargeState')
1126
+ control.run('GetChargeState')
1115
1127
 
1116
1128
  if (accessory.getService('Air Drying')) {
1117
1129
  accessory.logDebug(`${platformLang.sendCmd} [GetAirDrying]`)
1118
- accessory.control.run('GetAirDrying')
1130
+ control.run('GetAirDrying')
1119
1131
  }
1120
1132
 
1121
1133
  accessory.logDebug(`${platformLang.sendCmd} [GetCleanState${accessory.context.commandSuffix}]`)
1122
- accessory.control.run(`GetCleanState${accessory.context.commandSuffix}`)
1134
+ control.run(`GetCleanState${accessory.context.commandSuffix}`)
1123
1135
 
1124
1136
  accessory.logDebug(`${platformLang.sendCmd} [GetCleanSpeed]`)
1125
- accessory.control.run('GetCleanSpeed')
1137
+ control.run('GetCleanSpeed')
1126
1138
 
1127
1139
  accessory.logDebug(`${platformLang.sendCmd} [GetNetInfo]`)
1128
- accessory.control.run('GetNetInfo')
1140
+ control.run('GetNetInfo')
1129
1141
 
1130
1142
  // TrueDetect if the accessory supports it
1131
1143
  if (accessory.context.rawConfig.supportTrueDetect) {
1132
1144
  accessory.logDebug(`${platformLang.sendCmd} [GetTrueDetect]`)
1133
- accessory.control.run('GetTrueDetect')
1145
+ control.run('GetTrueDetect')
1134
1146
  }
1135
1147
 
1136
1148
  setTimeout(() => {
@@ -1206,6 +1218,11 @@ export default class {
1206
1218
  this.api.unregisterPlatformAccessories(plugin.name, plugin.alias, [accessory])
1207
1219
  devicesInHB.delete(accessory.UUID)
1208
1220
 
1221
+ // Remove device control if it exists
1222
+ if (accessory.context.ecoDeviceId) {
1223
+ deviceControls.delete(accessory.context.ecoDeviceId)
1224
+ }
1225
+
1209
1226
  // Remove corresponding Matter accessory if it exists
1210
1227
  if (this.matterEnabled && accessory.context.ecoDeviceId) {
1211
1228
  try {
@@ -1232,10 +1249,11 @@ export default class {
1232
1249
  async internalCleanUpdate(accessory, value) {
1233
1250
  try {
1234
1251
  // Don't continue if we can't send commands to the device
1235
- if (!accessory.control) {
1252
+ const control = this.getControl(accessory)
1253
+ if (!control) {
1236
1254
  throw new Error(platformLang.errNotInit)
1237
1255
  }
1238
- if (!accessory.control.is_ready) {
1256
+ if (!control.is_ready) {
1239
1257
  throw new Error(platformLang.errNotReady)
1240
1258
  }
1241
1259
 
@@ -1253,7 +1271,16 @@ export default class {
1253
1271
 
1254
1272
  // Send the command
1255
1273
  accessory.logDebug(`${platformLang.sendCmd} [${order}]`)
1256
- accessory.control.run(order)
1274
+ control.run(order)
1275
+
1276
+ // Update Matter states immediately to keep in sync
1277
+ if (value) {
1278
+ this.updateMatterRunMode(accessory, 1) // Cleaning
1279
+ this.updateMatterOperationalState(accessory, 1) // Running
1280
+ } else {
1281
+ this.updateMatterRunMode(accessory, 0) // Idle
1282
+ this.updateMatterOperationalState(accessory, 0) // Stopped
1283
+ }
1257
1284
  } catch (err) {
1258
1285
  // Catch any errors during the process
1259
1286
  accessory.logWarn(`${platformLang.cleanFail} ${parseError(err, [platformLang.errNotInit, platformLang.errNotReady])}`)
@@ -1271,10 +1298,11 @@ export default class {
1271
1298
  async internalSpeedUpdate(accessory, value) {
1272
1299
  try {
1273
1300
  // Don't continue if we can't send commands to the device
1274
- if (!accessory.control) {
1301
+ const control = this.getControl(accessory)
1302
+ if (!control) {
1275
1303
  throw new Error(platformLang.errNotInit)
1276
1304
  }
1277
- if (!accessory.control.is_ready) {
1305
+ if (!control.is_ready) {
1278
1306
  throw new Error(platformLang.errNotReady)
1279
1307
  }
1280
1308
 
@@ -1286,7 +1314,7 @@ export default class {
1286
1314
 
1287
1315
  // Send the command
1288
1316
  accessory.logDebug(`${platformLang.sendCmd} [SetCleanSpeed: ${command}]`)
1289
- accessory.control.run('SetCleanSpeed', command)
1317
+ control.run('SetCleanSpeed', command)
1290
1318
  } catch (err) {
1291
1319
  // Catch any errors during the process
1292
1320
  accessory.logWarn(`${platformLang.speedFail} ${parseError(err, [platformLang.errNotInit, platformLang.errNotReady])}`)
@@ -1304,10 +1332,11 @@ export default class {
1304
1332
  async internalPredefinedAreaUpdate(accessory, value) {
1305
1333
  try {
1306
1334
  // Don't continue if we can't send commands to the device
1307
- if (!accessory.control) {
1335
+ const control = this.getControl(accessory)
1336
+ if (!control) {
1308
1337
  throw new Error(platformLang.errNotInit)
1309
1338
  }
1310
- if (!accessory.control.is_ready) {
1339
+ if (!control.is_ready) {
1311
1340
  throw new Error(platformLang.errNotReady)
1312
1341
  }
1313
1342
 
@@ -1359,9 +1388,9 @@ export default class {
1359
1388
  accessory.logDebug(`${platformLang.sendCmd} [SpotArea${accessory.context.commandSuffix}: ${command}]`)
1360
1389
 
1361
1390
  if (accessory.context.commandSuffix === '_V2') {
1362
- accessory.control.run('SpotArea_V2', command)
1391
+ control.run('SpotArea_V2', command)
1363
1392
  } else {
1364
- accessory.control.run('SpotArea', 'start', command)
1393
+ control.run('SpotArea', 'start', command)
1365
1394
  }
1366
1395
  break
1367
1396
 
@@ -1369,9 +1398,9 @@ export default class {
1369
1398
  accessory.logDebug(`${platformLang.sendCmd} [CustomArea${accessory.context.commandSuffix}: ${command}]`)
1370
1399
 
1371
1400
  if (accessory.context.commandSuffix === '_V2') {
1372
- accessory.control.run('CustomArea_V2', command)
1401
+ control.run('CustomArea_V2', command)
1373
1402
  } else {
1374
- accessory.control.run('CustomArea', 'start', command)
1403
+ control.run('CustomArea', 'start', command)
1375
1404
  }
1376
1405
  break
1377
1406
 
@@ -1402,10 +1431,11 @@ export default class {
1402
1431
  async internalChargeUpdate(accessory, value) {
1403
1432
  try {
1404
1433
  // Don't continue if we can't send commands to the device
1405
- if (!accessory.control) {
1434
+ const control = this.getControl(accessory)
1435
+ if (!control) {
1406
1436
  throw new Error(platformLang.errNotInit)
1407
1437
  }
1408
- if (!accessory.control.is_ready) {
1438
+ if (!control.is_ready) {
1409
1439
  throw new Error(platformLang.errNotReady)
1410
1440
  }
1411
1441
 
@@ -1426,7 +1456,16 @@ export default class {
1426
1456
 
1427
1457
  // Send the command
1428
1458
  accessory.logDebug(`${platformLang.sendCmd} [${order}]`)
1429
- accessory.control.run(order)
1459
+ control.run(order)
1460
+
1461
+ // Update Matter states immediately to keep in sync
1462
+ if (value) {
1463
+ this.updateMatterRunMode(accessory, 0) // Idle
1464
+ this.updateMatterOperationalState(accessory, 64) // Seeking Charger
1465
+ } else {
1466
+ this.updateMatterRunMode(accessory, 0) // Idle
1467
+ this.updateMatterOperationalState(accessory, 0) // Stopped
1468
+ }
1430
1469
  } catch (err) {
1431
1470
  // Catch any errors during the process
1432
1471
  accessory.logWarn(`${platformLang.chargeFail} ${parseError(err, [platformLang.errNotInit, platformLang.errNotReady])}`)
@@ -1444,10 +1483,11 @@ export default class {
1444
1483
  async internalAirDryingUpdate(accessory, value) {
1445
1484
  try {
1446
1485
  // Don't continue if we can't send commands to the device
1447
- if (!accessory.control) {
1486
+ const control = this.getControl(accessory)
1487
+ if (!control) {
1448
1488
  throw new Error(platformLang.errNotInit)
1449
1489
  }
1450
- if (!accessory.control.is_ready) {
1490
+ if (!control.is_ready) {
1451
1491
  throw new Error(platformLang.errNotReady)
1452
1492
  }
1453
1493
 
@@ -1459,7 +1499,7 @@ export default class {
1459
1499
 
1460
1500
  // Send the command
1461
1501
  accessory.logDebug(`${platformLang.sendCmd} [${order}]`)
1462
- accessory.control.run(order)
1502
+ control.run(order)
1463
1503
  } catch (err) {
1464
1504
  // Catch any errors during the process
1465
1505
  accessory.logWarn(`${platformLang.airDryingFail} ${parseError(err, [platformLang.errNotInit, platformLang.errNotReady])}`)
@@ -1477,10 +1517,11 @@ export default class {
1477
1517
  async internalTrueDetectUpdate(accessory, value) {
1478
1518
  try {
1479
1519
  // Don't continue if we can't send commands to the device
1480
- if (!accessory.control) {
1520
+ const control = this.getControl(accessory)
1521
+ if (!control) {
1481
1522
  throw new Error(platformLang.errNotInit)
1482
1523
  }
1483
- if (!accessory.control.is_ready) {
1524
+ if (!control.is_ready) {
1484
1525
  throw new Error(platformLang.errNotReady)
1485
1526
  }
1486
1527
 
@@ -1492,7 +1533,7 @@ export default class {
1492
1533
 
1493
1534
  // Send the command
1494
1535
  accessory.logDebug(`${platformLang.sendCmd} [${command}]`)
1495
- accessory.control.run(command)
1536
+ control.run(command)
1496
1537
  } catch (err) {
1497
1538
  // Catch any errors during the process
1498
1539
  accessory.logWarn(`${platformLang.cleanFail} ${parseError(err, [platformLang.errNotInit, platformLang.errNotReady])}`)
@@ -1510,23 +1551,28 @@ export default class {
1510
1551
  externalReadyUpdate(accessory) {
1511
1552
  try {
1512
1553
  // Called on the 'ready' event sent by the device so request update for states
1554
+ const control = this.getControl(accessory)
1555
+ if (!control) {
1556
+ return
1557
+ }
1558
+
1513
1559
  accessory.logDebug(`${platformLang.sendCmd} [GetBatteryState]`)
1514
- accessory.control.run('GetBatteryState')
1560
+ control.run('GetBatteryState')
1515
1561
 
1516
1562
  accessory.log(`${platformLang.sendCmd} [GetChargeState]`)
1517
- accessory.control.run('GetChargeState')
1563
+ control.run('GetChargeState')
1518
1564
 
1519
1565
  accessory.logDebug(`${platformLang.sendCmd} [GetCleanState${accessory.context.commandSuffix}]`)
1520
- accessory.control.run(`GetCleanState${accessory.context.commandSuffix}`)
1566
+ control.run(`GetCleanState${accessory.context.commandSuffix}`)
1521
1567
 
1522
1568
  accessory.logDebug(`${platformLang.sendCmd} [GetCleanSpeed]`)
1523
- accessory.control.run('GetCleanSpeed')
1569
+ control.run('GetCleanSpeed')
1524
1570
 
1525
1571
  accessory.logDebug(`${platformLang.sendCmd} [GetNetInfo]`)
1526
- accessory.control.run('GetNetInfo')
1572
+ control.run('GetNetInfo')
1527
1573
 
1528
1574
  accessory.logDebug(`${platformLang.sendCmd} [GetMaps]`)
1529
- accessory.control.run('GetMaps')
1575
+ control.run('GetMaps')
1530
1576
  } catch (err) {
1531
1577
  // Catch any errors during the process
1532
1578
  accessory.logWarn(`${platformLang.inRdyFail} ${parseError(err)}`)
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Homebridge Ecovacs",
4
4
  "alias": "Deebot",
5
5
  "type": "module",
6
- "version": "7.2.4-beta.1",
6
+ "version": "7.2.4-beta.2",
7
7
  "description": "Homebridge plugin to integrate Ecovacs Deebot devices into HomeKit.",
8
8
  "author": {
9
9
  "name": "bwp91",