matterbridge-roborock-vacuum-plugin 1.1.1-rc10 → 1.1.1-rc11

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.
@@ -7,6 +7,7 @@
7
7
  | Roborock Qrevo Edge 5V1 | `QREVO_EDGE_5V1` | `roborock.vacuum.a187` | |
8
8
  | Roborock S8 Pro Ultra | `S8_PRO_ULTRA` | `roborock.vacuum.a70` | |
9
9
  | Roborock S7 MaxV | `S7_MAXV` | `roborock.vacuum.a27` | |
10
+ | Roborock Qrevo Plus | `QREVO_PLUS` | `roborock.vacuum.a123` | |
10
11
 
11
12
  These devices have been fully tested and are confirmed to work as expected.
12
13
 
@@ -9,7 +9,8 @@ export function configurateBehavior(model, duid, roborockService, cleanModeSetti
9
9
  return deviceHandler;
10
10
  }
11
11
  switch (model) {
12
- case DeviceModel.QREVO_EDGE_5V1: {
12
+ case DeviceModel.QREVO_EDGE_5V1:
13
+ case DeviceModel.QREVO_PLUS: {
13
14
  const deviceHandler = new BehaviorDeviceGeneric(logger);
14
15
  setCommandHandlerSmart(duid, deviceHandler, logger, roborockService, cleanModeSettings);
15
16
  return deviceHandler;
@@ -7,6 +7,7 @@ export function getSupportedCleanModes(model, enableExperimentalFeature) {
7
7
  }
8
8
  switch (model) {
9
9
  case DeviceModel.QREVO_EDGE_5V1:
10
+ case DeviceModel.QREVO_PLUS:
10
11
  return getSupportedCleanModesSmart(enableExperimentalFeature);
11
12
  case DeviceModel.S7_MAXV:
12
13
  case DeviceModel.S8_PRO_ULTRA:
package/dist/platform.js CHANGED
@@ -25,6 +25,11 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
25
25
  enableExperimentalFeature;
26
26
  persist;
27
27
  rrHomeId;
28
+ regionUrls = {
29
+ US: 'https://usiot.roborock.com',
30
+ EU: 'https://euiot.roborock.com',
31
+ CN: 'https://iot.roborock.com',
32
+ };
28
33
  constructor(matterbridge, log, config) {
29
34
  super(matterbridge, log, config);
30
35
  if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.3.6')) {
@@ -69,7 +74,10 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
69
74
  else {
70
75
  this.log.debug('Using cached deviceId:', deviceId);
71
76
  }
72
- this.roborockService = new RoborockService(() => new RoborockAuthenticateApi(this.log, axiosInstance, deviceId), (logger, ud) => new RoborockIoTApi(ud, logger), this.config.refreshInterval ?? 60, this.clientManager, this.log);
77
+ const region = this.config.region?.toUpperCase() ?? 'US';
78
+ const baseUrl = this.regionUrls[region] ?? this.regionUrls['US'];
79
+ this.log.notice(`Using region: ${region} (${baseUrl})`);
80
+ this.roborockService = new RoborockService((logger, url) => new RoborockAuthenticateApi(this.log, axiosInstance, deviceId, url), (logger, ud) => new RoborockIoTApi(ud, logger), this.config.refreshInterval ?? 60, this.clientManager, this.log, baseUrl);
73
81
  const username = this.config.username;
74
82
  this.log.debug(`config: ${debugStringify(this.config)}`);
75
83
  const authenticationPayload = this.config.authentication;
@@ -8,13 +8,15 @@ export class RoborockAuthenticateApi {
8
8
  deviceId;
9
9
  username;
10
10
  authToken;
11
+ baseUrl;
11
12
  cachedBaseUrl;
12
13
  cachedCountry;
13
14
  cachedCountryCode;
14
- constructor(logger, axiosFactory = axios, deviceId) {
15
+ constructor(logger, axiosFactory = axios, deviceId, baseUrl = 'https://usiot.roborock.com') {
15
16
  this.deviceId = deviceId ?? crypto.randomUUID();
16
17
  this.axiosFactory = axiosFactory;
17
18
  this.logger = logger;
19
+ this.baseUrl = baseUrl;
18
20
  }
19
21
  async loginWithUserData(username, userData) {
20
22
  this.loginWithAuthToken(username, userData.token);
@@ -56,12 +58,28 @@ export class RoborockAuthenticateApi {
56
58
  const api = await this.getAPIFor(email);
57
59
  const xMercyKs = this.generateRandomString(16);
58
60
  const xMercyK = await this.signKeyV3(api, xMercyKs);
61
+ let country = this.cachedCountry;
62
+ let countryCode = this.cachedCountryCode;
63
+ if (!country || !countryCode) {
64
+ if (this.baseUrl.includes('euiot')) {
65
+ country = country || 'Germany';
66
+ countryCode = countryCode || 'DE';
67
+ }
68
+ else if (this.baseUrl.includes('usiot')) {
69
+ country = country || 'United States';
70
+ countryCode = countryCode || 'US';
71
+ }
72
+ else if (this.baseUrl.includes('iot.roborock.com')) {
73
+ country = country || 'China';
74
+ countryCode = countryCode || 'CN';
75
+ }
76
+ }
59
77
  const response = await api.post('api/v4/auth/email/login/code', null, {
60
78
  params: {
61
79
  email: email,
62
80
  code: code,
63
- country: this.cachedCountry ?? '',
64
- countryCode: this.cachedCountryCode ?? '',
81
+ country: country ?? '',
82
+ countryCode: countryCode ?? '',
65
83
  majorVersion: '14',
66
84
  minorVersion: '0',
67
85
  },
@@ -117,7 +135,7 @@ export class RoborockAuthenticateApi {
117
135
  this.username = username;
118
136
  return apiResponse.data.url;
119
137
  }
120
- async apiForUser(username, baseUrl = 'https://usiot.roborock.com') {
138
+ async apiForUser(username, baseUrl = this.baseUrl) {
121
139
  const instance = this.axiosFactory.create({
122
140
  baseURL: baseUrl,
123
141
  headers: {
@@ -25,4 +25,5 @@ export var DeviceModel;
25
25
  DeviceModel["QREVO_MAXV"] = "roborock.vacuum.a87";
26
26
  DeviceModel["QREVO_EDGE_5V1"] = "roborock.vacuum.a187";
27
27
  DeviceModel["QREVO_EDGE_5AE"] = "roborock.vacuum.xxxx";
28
+ DeviceModel["QREVO_PLUS"] = "roborock.vacuum.a123";
28
29
  })(DeviceModel || (DeviceModel = {}));
@@ -24,9 +24,9 @@ export default class RoborockService {
24
24
  selectedAreas = new Map();
25
25
  supportedAreaIndexMaps = new Map();
26
26
  vacuumNeedAPIV3 = ['roborock.vacuum.ss07'];
27
- constructor(authenticateApiSupplier = (logger) => new RoborockAuthenticateApi(logger), iotApiSupplier = (logger, ud) => new RoborockIoTApi(ud, logger), refreshInterval, clientManager, logger) {
27
+ constructor(authenticateApiSupplier = (logger, baseUrl) => new RoborockAuthenticateApi(logger, undefined, undefined, baseUrl), iotApiSupplier = (logger, ud) => new RoborockIoTApi(ud, logger), refreshInterval, clientManager, logger, baseUrl = 'https://usiot.roborock.com') {
28
28
  this.logger = logger;
29
- this.loginApi = authenticateApiSupplier(logger);
29
+ this.loginApi = authenticateApiSupplier(logger, baseUrl);
30
30
  this.iotApiFactory = iotApiSupplier;
31
31
  this.refreshInterval = refreshInterval;
32
32
  this.clientManager = clientManager;
@@ -7,6 +7,7 @@ export function getCurrentCleanModeFunc(model, forceRunAtDefault) {
7
7
  }
8
8
  switch (model) {
9
9
  case DeviceModel.QREVO_EDGE_5V1:
10
+ case DeviceModel.QREVO_PLUS:
10
11
  return getCurrentCleanModeSmart;
11
12
  case DeviceModel.S7_MAXV:
12
13
  case DeviceModel.S8_PRO_ULTRA:
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
3
  "type": "DynamicPlatform",
4
- "version": "1.1.1-rc10",
4
+ "version": "1.1.1-rc11",
5
5
  "whiteList": [],
6
6
  "blackList": [],
7
7
  "useInterval": true,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "title": "Matterbridge Roborock Vacuum Plugin",
3
- "description": "matterbridge-roborock-vacuum-plugin v. 1.1.1-rc09 by https://github.com/RinDevJunior",
3
+ "description": "matterbridge-roborock-vacuum-plugin v. 1.1.1-rc11 by https://github.com/RinDevJunior",
4
4
  "type": "object",
5
5
  "required": ["username"],
6
6
  "properties": {
@@ -29,6 +29,12 @@
29
29
  "description": "Roborock account email address",
30
30
  "type": "string"
31
31
  },
32
+ "region": {
33
+ "description": "Roborock account region",
34
+ "type": "string",
35
+ "enum": ["US", "EU", "CN"],
36
+ "default": "US"
37
+ },
32
38
  "authentication": {
33
39
  "description": "Authentication method to use",
34
40
  "type": "object",
@@ -70,7 +76,6 @@
70
76
  }
71
77
  ]
72
78
  },
73
-
74
79
  "refreshInterval": {
75
80
  "description": "Refresh interval in seconds (default: 60)",
76
81
  "type": "number",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
- "version": "1.1.1-rc10",
3
+ "version": "1.1.1-rc11",
4
4
  "description": "Matterbridge Roborock Vacuum Plugin",
5
5
  "author": "https://github.com/RinDevJunior",
6
6
  "license": "MIT",
@@ -23,7 +23,8 @@ export function configurateBehavior(
23
23
  }
24
24
 
25
25
  switch (model) {
26
- case DeviceModel.QREVO_EDGE_5V1: {
26
+ case DeviceModel.QREVO_EDGE_5V1:
27
+ case DeviceModel.QREVO_PLUS: {
27
28
  const deviceHandler = new BehaviorDeviceGeneric<EndpointCommandsSmart>(logger);
28
29
  setCommandHandlerSmart(duid, deviceHandler, logger, roborockService, cleanModeSettings);
29
30
  return deviceHandler;
@@ -11,6 +11,7 @@ export function getSupportedCleanModes(model: string, enableExperimentalFeature:
11
11
 
12
12
  switch (model) {
13
13
  case DeviceModel.QREVO_EDGE_5V1:
14
+ case DeviceModel.QREVO_PLUS:
14
15
  return getSupportedCleanModesSmart(enableExperimentalFeature);
15
16
 
16
17
  case DeviceModel.S7_MAXV:
package/src/platform.ts CHANGED
@@ -30,6 +30,12 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
30
30
  persist: NodePersist.LocalStorage;
31
31
  rrHomeId: number | undefined;
32
32
 
33
+ private regionUrls: Record<string, string> = {
34
+ US: 'https://usiot.roborock.com',
35
+ EU: 'https://euiot.roborock.com',
36
+ CN: 'https://iot.roborock.com',
37
+ };
38
+
33
39
  constructor(matterbridge: PlatformMatterbridge, log: AnsiLogger, config: PlatformConfig) {
34
40
  super(matterbridge, log, config);
35
41
 
@@ -91,12 +97,18 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
91
97
  this.log.debug('Using cached deviceId:', deviceId);
92
98
  }
93
99
 
100
+ const region = (this.config.region as string)?.toUpperCase() ?? 'US';
101
+ // use regionmap to get baseUrl
102
+ const baseUrl = this.regionUrls[region] ?? this.regionUrls['US'];
103
+ this.log.notice(`Using region: ${region} (${baseUrl})`);
104
+
94
105
  this.roborockService = new RoborockService(
95
- () => new RoborockAuthenticateApi(this.log, axiosInstance, deviceId),
106
+ (logger, url) => new RoborockAuthenticateApi(this.log, axiosInstance, deviceId, url),
96
107
  (logger, ud) => new RoborockIoTApi(ud, logger),
97
108
  (this.config.refreshInterval as number) ?? 60,
98
109
  this.clientManager,
99
110
  this.log,
111
+ baseUrl,
100
112
  );
101
113
 
102
114
  const username = this.config.username as string;
@@ -14,15 +14,17 @@ export class RoborockAuthenticateApi {
14
14
  private deviceId: string;
15
15
  private username?: string;
16
16
  private authToken?: string;
17
+ private readonly baseUrl: string;
17
18
  // Cached values from base URL lookup for v4 login
18
19
  private cachedBaseUrl?: string;
19
20
  private cachedCountry?: string;
20
21
  private cachedCountryCode?: string;
21
22
 
22
- constructor(logger: AnsiLogger, axiosFactory: AxiosStatic = axios, deviceId?: string) {
23
+ constructor(logger: AnsiLogger, axiosFactory: AxiosStatic = axios, deviceId?: string, baseUrl = 'https://usiot.roborock.com') {
23
24
  this.deviceId = deviceId ?? crypto.randomUUID();
24
25
  this.axiosFactory = axiosFactory;
25
26
  this.logger = logger;
27
+ this.baseUrl = baseUrl;
26
28
  }
27
29
 
28
30
  public async loginWithUserData(username: string, userData: UserData): Promise<UserData> {
@@ -98,12 +100,28 @@ export class RoborockAuthenticateApi {
98
100
  // Get signed key from API
99
101
  const xMercyK = await this.signKeyV3(api, xMercyKs);
100
102
 
103
+ // Fallback for missing country info to avoid 1002 parameter error
104
+ let country = this.cachedCountry;
105
+ let countryCode = this.cachedCountryCode;
106
+ if (!country || !countryCode) {
107
+ if (this.baseUrl.includes('euiot')) {
108
+ country = country || 'Germany';
109
+ countryCode = countryCode || 'DE';
110
+ } else if (this.baseUrl.includes('usiot')) {
111
+ country = country || 'United States';
112
+ countryCode = countryCode || 'US';
113
+ } else if (this.baseUrl.includes('iot.roborock.com')) {
114
+ country = country || 'China';
115
+ countryCode = countryCode || 'CN';
116
+ }
117
+ }
118
+
101
119
  const response = await api.post('api/v4/auth/email/login/code', null, {
102
120
  params: {
103
121
  email: email,
104
122
  code: code,
105
- country: this.cachedCountry ?? '',
106
- countryCode: this.cachedCountryCode ?? '',
123
+ country: country ?? '',
124
+ countryCode: countryCode ?? '',
107
125
  majorVersion: '14',
108
126
  minorVersion: '0',
109
127
  },
@@ -177,7 +195,7 @@ export class RoborockAuthenticateApi {
177
195
  return apiResponse.data.url;
178
196
  }
179
197
 
180
- private async apiForUser(username: string, baseUrl = 'https://usiot.roborock.com'): Promise<AxiosInstance> {
198
+ private async apiForUser(username: string, baseUrl = this.baseUrl): Promise<AxiosInstance> {
181
199
  const instance = this.axiosFactory.create({
182
200
  baseURL: baseUrl,
183
201
  headers: {
@@ -24,4 +24,5 @@ export enum DeviceModel {
24
24
  QREVO_MAXV = 'roborock.vacuum.a87',
25
25
  QREVO_EDGE_5V1 = 'roborock.vacuum.a187',
26
26
  QREVO_EDGE_5AE = 'roborock.vacuum.xxxx',
27
+ QREVO_PLUS = 'roborock.vacuum.a123',
27
28
  }
@@ -64,14 +64,16 @@ export default class RoborockService {
64
64
  private readonly vacuumNeedAPIV3 = ['roborock.vacuum.ss07'];
65
65
 
66
66
  constructor(
67
- authenticateApiSupplier: Factory<void, RoborockAuthenticateApi> = (logger) => new RoborockAuthenticateApi(logger),
67
+ authenticateApiSupplier: (logger: AnsiLogger, baseUrl: string) => RoborockAuthenticateApi = (logger, baseUrl) =>
68
+ new RoborockAuthenticateApi(logger, undefined, undefined, baseUrl),
68
69
  iotApiSupplier: Factory<UserData, RoborockIoTApi> = (logger, ud) => new RoborockIoTApi(ud, logger),
69
70
  refreshInterval: number,
70
71
  clientManager: ClientManager,
71
72
  logger: AnsiLogger,
73
+ baseUrl = 'https://usiot.roborock.com',
72
74
  ) {
73
75
  this.logger = logger;
74
- this.loginApi = authenticateApiSupplier(logger);
76
+ this.loginApi = authenticateApiSupplier(logger, baseUrl);
75
77
  this.iotApiFactory = iotApiSupplier;
76
78
  this.refreshInterval = refreshInterval;
77
79
  this.clientManager = clientManager;
@@ -12,6 +12,7 @@ export function getCurrentCleanModeFunc(model: string, forceRunAtDefault: boolea
12
12
 
13
13
  switch (model) {
14
14
  case DeviceModel.QREVO_EDGE_5V1:
15
+ case DeviceModel.QREVO_PLUS:
15
16
  return getCurrentCleanModeSmart;
16
17
 
17
18
  case DeviceModel.S7_MAXV: