matterbridge-roborock-vacuum-plugin 1.1.0-rc18 → 1.1.1-rc01
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.
- package/dist/helper.js +2 -7
- package/dist/model/RoomMap.js +1 -1
- package/dist/model/roomIndexMap.js +8 -4
- package/dist/platform.js +4 -3
- package/dist/platformRunner.js +10 -268
- package/dist/runtimes/handleCloudMessage.js +110 -0
- package/dist/runtimes/handleHomeDataMessage.js +57 -0
- package/dist/runtimes/handleLocalMessage.js +169 -0
- package/dist/tests/testData/mockData.js +359 -0
- package/matterbridge-roborock-vacuum-plugin.config.json +2 -2
- package/matterbridge-roborock-vacuum-plugin.schema.json +10 -37
- package/package.json +2 -2
- package/src/behaviors/roborock.vacuum/default/runtimes.ts +1 -1
- package/src/behaviors/roborock.vacuum/smart/runtimes.ts +1 -1
- package/src/helper.ts +2 -12
- package/src/initialData/getSupportedAreas.ts +1 -9
- package/src/model/RoomMap.ts +4 -30
- package/src/model/roomIndexMap.ts +10 -6
- package/src/platform.ts +6 -3
- package/src/platformRunner.ts +12 -350
- package/src/roborockCommunication/Zmodel/device.ts +13 -1
- package/src/roborockCommunication/Zmodel/messageResult.ts +28 -27
- package/src/roborockCommunication/Zmodel/userData.ts +2 -1
- package/src/runtimes/handleCloudMessage.ts +134 -0
- package/src/runtimes/handleHomeDataMessage.ts +67 -0
- package/src/runtimes/handleLocalMessage.ts +209 -0
- package/src/share/runtimeHelper.ts +1 -1
- package/src/tests/helper.test.ts +59 -10
- package/src/tests/roborockService.setSelectedAreas.test.ts +61 -0
- package/src/tests/runtimes/handleCloudMessage.test.ts +200 -0
- package/src/tests/runtimes/handleHomeDataMessage.test.ts +53 -0
- package/src/tests/runtimes/handleLocalMessage.test.ts +222 -0
- package/src/tests/testData/mockData.ts +370 -0
|
@@ -12,10 +12,10 @@ export interface CloudMessageResult {
|
|
|
12
12
|
in_fresh_state: number;
|
|
13
13
|
lab_status: number;
|
|
14
14
|
water_box_status: number;
|
|
15
|
-
back_type
|
|
16
|
-
wash_phase
|
|
17
|
-
wash_ready
|
|
18
|
-
wash_status
|
|
15
|
+
back_type?: number;
|
|
16
|
+
wash_phase?: number;
|
|
17
|
+
wash_ready?: number;
|
|
18
|
+
wash_status?: number;
|
|
19
19
|
fan_power: number;
|
|
20
20
|
dnd_enabled: number;
|
|
21
21
|
map_status: number;
|
|
@@ -25,43 +25,44 @@ export interface CloudMessageResult {
|
|
|
25
25
|
distance_off: number;
|
|
26
26
|
water_box_carriage_status: number;
|
|
27
27
|
mop_forbidden_enable: number;
|
|
28
|
-
camera_status
|
|
29
|
-
is_exploring
|
|
28
|
+
camera_status?: number;
|
|
29
|
+
is_exploring?: number;
|
|
30
30
|
adbumper_status: number[];
|
|
31
|
-
water_shortage_status
|
|
31
|
+
water_shortage_status?: number;
|
|
32
32
|
dock_type: number;
|
|
33
33
|
dust_collection_status: number;
|
|
34
34
|
auto_dust_collection: number;
|
|
35
|
-
avoid_count
|
|
36
|
-
mop_mode
|
|
35
|
+
avoid_count?: number;
|
|
36
|
+
mop_mode?: number;
|
|
37
37
|
debug_mode: number;
|
|
38
|
-
in_warmup
|
|
39
|
-
collision_avoid_status
|
|
38
|
+
in_warmup?: number;
|
|
39
|
+
collision_avoid_status?: number;
|
|
40
40
|
switch_map_mode: number;
|
|
41
41
|
dock_error_status: number;
|
|
42
42
|
charge_status: number;
|
|
43
|
-
unsave_map_reason
|
|
44
|
-
unsave_map_flag
|
|
45
|
-
dry_status
|
|
46
|
-
rdt
|
|
47
|
-
clean_percent
|
|
48
|
-
extra_time
|
|
49
|
-
rss
|
|
50
|
-
dss
|
|
51
|
-
common_status
|
|
52
|
-
last_clean_t
|
|
53
|
-
replenish_mode
|
|
54
|
-
repeat
|
|
55
|
-
kct
|
|
56
|
-
subdivision_sets
|
|
57
|
-
cleaning_info
|
|
43
|
+
unsave_map_reason?: number;
|
|
44
|
+
unsave_map_flag?: number;
|
|
45
|
+
dry_status?: number;
|
|
46
|
+
rdt?: number;
|
|
47
|
+
clean_percent?: number;
|
|
48
|
+
extra_time?: number;
|
|
49
|
+
rss?: number;
|
|
50
|
+
dss?: number;
|
|
51
|
+
common_status?: number;
|
|
52
|
+
last_clean_t?: number;
|
|
53
|
+
replenish_mode?: number;
|
|
54
|
+
repeat?: number;
|
|
55
|
+
kct?: number;
|
|
56
|
+
subdivision_sets?: number;
|
|
57
|
+
cleaning_info?: {
|
|
58
58
|
target_segment_id: number;
|
|
59
59
|
segment_id: number;
|
|
60
60
|
fan_power: number;
|
|
61
61
|
water_box_status: number;
|
|
62
62
|
mop_mode: number;
|
|
63
63
|
};
|
|
64
|
-
exit_dock
|
|
64
|
+
exit_dock?: number;
|
|
65
|
+
seq_type?: number;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
export enum CarpetCleanMode {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { getRunningMode } from '../initialData/getSupportedRunModes.js';
|
|
2
|
+
import { CloudMessageModel } from '../model/CloudMessageModel.js';
|
|
3
|
+
import { hasDockingStationError } from '../model/DockingStationStatus.js';
|
|
4
|
+
import { RoborockMatterbridgePlatform } from '../platform.js';
|
|
5
|
+
import { AdditionalPropCode, DeviceStatusNotify, Protocol } from '../roborockCommunication/index.js';
|
|
6
|
+
import { state_to_matter_operational_status, state_to_matter_state } from '../share/function.js';
|
|
7
|
+
import { RvcCleanMode, RvcOperationalState, RvcRunMode, ServiceArea } from 'matterbridge/matter/clusters';
|
|
8
|
+
import { triggerDssError } from './handleLocalMessage.js';
|
|
9
|
+
import { DpsPayload } from '../roborockCommunication/broadcast/model/dps.js';
|
|
10
|
+
import { getRoomMapFromDevice, isStatusUpdate } from '../helper.js';
|
|
11
|
+
import { debugStringify } from 'matterbridge/logger';
|
|
12
|
+
import { CloudMessageResult } from '../roborockCommunication/Zmodel/messageResult.js';
|
|
13
|
+
import { NotifyMessageTypes } from '../notifyMessageTypes.js';
|
|
14
|
+
import { getCurrentCleanModeFunc } from '../share/runtimeHelper.js';
|
|
15
|
+
import { getSupportedAreas } from '../initialData/getSupportedAreas.js';
|
|
16
|
+
import { PlatformRunner } from '../platformRunner.js';
|
|
17
|
+
import { RoborockVacuumCleaner } from '../rvc.js';
|
|
18
|
+
|
|
19
|
+
export async function handleCloudMessage(data: CloudMessageModel, platform: RoborockMatterbridgePlatform, runner: PlatformRunner, duid: string): Promise<void> {
|
|
20
|
+
const messageTypes = Object.keys(data.dps).map(Number);
|
|
21
|
+
const robot = platform.robots.get(duid);
|
|
22
|
+
if (robot === undefined) {
|
|
23
|
+
platform.log.error(`Error3: Robot with DUID ${duid} not found`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Known: 122, 121, 102,
|
|
28
|
+
// Unknown: 128, 139
|
|
29
|
+
messageTypes.forEach(async (messageType) => {
|
|
30
|
+
switch (messageType) {
|
|
31
|
+
case Protocol.status_update: {
|
|
32
|
+
const status = Number(data.dps[messageType]);
|
|
33
|
+
const matterState = state_to_matter_state(status);
|
|
34
|
+
|
|
35
|
+
if (matterState) {
|
|
36
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const operationalStateId = state_to_matter_operational_status(status);
|
|
40
|
+
if (operationalStateId) {
|
|
41
|
+
const dssHasError = hasDockingStationError(robot.dockStationStatus);
|
|
42
|
+
if (!(dssHasError && triggerDssError(robot, platform))) {
|
|
43
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case Protocol.rpc_response: {
|
|
49
|
+
const response = data.dps[messageType] as DpsPayload;
|
|
50
|
+
// ignore network info
|
|
51
|
+
if (!isStatusUpdate(response.result)) {
|
|
52
|
+
platform.log.debug('Ignore message:', debugStringify(data));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let roboStatus: CloudMessageResult | undefined;
|
|
57
|
+
if (Array.isArray(response.result) && response.result.length > 0) {
|
|
58
|
+
roboStatus = response.result[0] as CloudMessageResult;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (roboStatus) {
|
|
62
|
+
const message = { ...roboStatus } as DeviceStatusNotify;
|
|
63
|
+
platform.log.debug('rpc_response:', debugStringify(message));
|
|
64
|
+
await runner.updateFromMQTTMessage(NotifyMessageTypes.LocalMessage, message, duid, true);
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case Protocol.suction_power:
|
|
69
|
+
case Protocol.water_box_mode: {
|
|
70
|
+
await platform.roborockService?.getCleanModeData(duid).then((cleanModeData) => {
|
|
71
|
+
if (cleanModeData) {
|
|
72
|
+
const currentCleanMode = getCurrentCleanModeFunc(
|
|
73
|
+
robot.device.data.model,
|
|
74
|
+
platform.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false,
|
|
75
|
+
)({
|
|
76
|
+
suctionPower: cleanModeData.suctionPower,
|
|
77
|
+
waterFlow: cleanModeData.waterFlow,
|
|
78
|
+
distance_off: cleanModeData.distance_off,
|
|
79
|
+
mopRoute: cleanModeData.mopRoute,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
platform.log.debug(`Clean mode data: ${debugStringify(cleanModeData)}`);
|
|
83
|
+
platform.log.debug(`Current clean mode: ${currentCleanMode}`);
|
|
84
|
+
if (currentCleanMode) {
|
|
85
|
+
robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
break; // Do nothing, handled in local message
|
|
90
|
+
}
|
|
91
|
+
case Protocol.additional_props: {
|
|
92
|
+
platform.log.notice(`Received additional properties for robot ${duid}: ${debugStringify(data)}`);
|
|
93
|
+
const propCode = data.dps[Protocol.additional_props] as number;
|
|
94
|
+
platform.log.debug(`DPS for additional properties: ${propCode}, AdditionalPropCode: ${AdditionalPropCode[propCode]}`);
|
|
95
|
+
if (propCode === AdditionalPropCode.map_change) {
|
|
96
|
+
await handleMapChange(robot, platform, duid);
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case Protocol.back_type: {
|
|
101
|
+
// TODO: check if this is needed
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
default: {
|
|
105
|
+
platform.log.notice(`Unknown message type ${messageType}, protocol: ${Protocol[messageType]}, message: ${debugStringify(data)}`);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function handleMapChange(robot: RoborockVacuumCleaner, platform: RoborockMatterbridgePlatform, duid: string): Promise<void> {
|
|
113
|
+
const enableMultipleMap = (platform.enableExperimentalFeature?.enableExperimentalFeature && platform.enableExperimentalFeature?.advancedFeature?.enableMultipleMap) ?? false;
|
|
114
|
+
if (!enableMultipleMap) return;
|
|
115
|
+
|
|
116
|
+
await getRoomMapFromDevice(robot.device, platform).then((roomMap) => {
|
|
117
|
+
const { supportedAreas, supportedMaps, roomIndexMap } = getSupportedAreas(robot.device.rooms, roomMap, enableMultipleMap, platform.log);
|
|
118
|
+
|
|
119
|
+
platform.log.debug(`handleMapChange - supportedAreas: ${debugStringify(supportedAreas)}`);
|
|
120
|
+
platform.log.debug(`handleMapChange - supportedMaps: ${debugStringify(supportedMaps)}`);
|
|
121
|
+
platform.log.debug(`handleMapChange - roomIndexMap: `, roomIndexMap);
|
|
122
|
+
|
|
123
|
+
platform.roborockService?.setSupportedAreas(duid, supportedAreas);
|
|
124
|
+
platform.roborockService?.setSelectedAreas(duid, []);
|
|
125
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'supportedAreas', supportedAreas, platform.log);
|
|
126
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', [], platform.log);
|
|
127
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', null, platform.log);
|
|
128
|
+
|
|
129
|
+
if (enableMultipleMap) {
|
|
130
|
+
platform.roborockService?.setSupportedAreaIndexMap(duid, roomIndexMap);
|
|
131
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'supportedMaps', supportedMaps, platform.log);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { PowerSource, RvcOperationalState, RvcRunMode } from 'matterbridge/matter/clusters';
|
|
2
|
+
import { getVacuumProperty } from '../helper.js';
|
|
3
|
+
import { RoborockMatterbridgePlatform } from '../platform.js';
|
|
4
|
+
import { Device, Home } from '../roborockCommunication/index.js';
|
|
5
|
+
import { debugStringify } from 'matterbridge/logger';
|
|
6
|
+
import { getBatteryState, getBatteryStatus } from '../initialData/index.js';
|
|
7
|
+
import { state_to_matter_operational_status, state_to_matter_state } from '../share/function.js';
|
|
8
|
+
import { OperationStatusCode } from '../roborockCommunication/Zenum/operationStatusCode.js';
|
|
9
|
+
import { getRunningMode } from '../initialData/getSupportedRunModes.js';
|
|
10
|
+
import { hasDockingStationError } from '../model/DockingStationStatus.js';
|
|
11
|
+
import { triggerDssError } from './handleLocalMessage.js';
|
|
12
|
+
|
|
13
|
+
export async function updateFromHomeData(homeData: Home, platform: RoborockMatterbridgePlatform): Promise<void> {
|
|
14
|
+
if (platform.robots.size === 0) return;
|
|
15
|
+
const devices = homeData.devices.filter((d: Device) => platform.robots.has(d.duid));
|
|
16
|
+
|
|
17
|
+
for (const device of devices) {
|
|
18
|
+
const robot = platform.robots.get(device.duid);
|
|
19
|
+
if (robot === undefined) {
|
|
20
|
+
platform.log.error(`Error5: Robot with DUID ${device.duid} not found`);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const deviceData = robot.device.data;
|
|
25
|
+
if (!device || deviceData === undefined) {
|
|
26
|
+
platform.log.error('Device not found in home data');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
device.schema = homeData.products.find((prd) => prd.id === device.productId || prd.model === device.data.model)?.schema ?? [];
|
|
31
|
+
platform.log.debug('updateFromHomeData-homeData:', debugStringify(homeData));
|
|
32
|
+
platform.log.debug('updateFromHomeData-device:', debugStringify(device));
|
|
33
|
+
platform.log.debug('updateFromHomeData-schema:' + debugStringify(device.schema));
|
|
34
|
+
platform.log.debug('updateFromHomeData-battery:' + debugStringify(device.deviceStatus));
|
|
35
|
+
|
|
36
|
+
const batteryLevel = getVacuumProperty(device, 'battery');
|
|
37
|
+
if (batteryLevel) {
|
|
38
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel ? batteryLevel * 2 : 200, platform.log);
|
|
39
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const state = getVacuumProperty(device, 'state');
|
|
43
|
+
const matterState = state_to_matter_state(state);
|
|
44
|
+
if (!state || !matterState) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
platform.log.debug(`updateFromHomeData-RvcRunMode code: ${state} name: ${OperationStatusCode[state]}, matterState: ${RvcRunMode.ModeTag[matterState]}`);
|
|
48
|
+
|
|
49
|
+
if (matterState) {
|
|
50
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
|
|
51
|
+
}
|
|
52
|
+
if (batteryLevel) {
|
|
53
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(state, batteryLevel), platform.log);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const operationalStateId = state_to_matter_operational_status(state);
|
|
57
|
+
|
|
58
|
+
if (operationalStateId) {
|
|
59
|
+
const dssHasError = hasDockingStationError(robot.dockStationStatus);
|
|
60
|
+
platform.log.debug(`dssHasError: ${dssHasError}, dockStationStatus: ${debugStringify(robot.dockStationStatus ?? {})}`);
|
|
61
|
+
if (!(dssHasError && triggerDssError(robot, platform))) {
|
|
62
|
+
platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
|
|
63
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { PowerSource, RvcCleanMode, RvcOperationalState, RvcRunMode, ServiceArea } from 'matterbridge/matter/clusters';
|
|
2
|
+
import { getRunningMode } from '../initialData/getSupportedRunModes.js';
|
|
3
|
+
import { CloudMessageResult } from '../roborockCommunication/Zmodel/messageResult.js';
|
|
4
|
+
import { state_to_matter_state } from '../share/function.js';
|
|
5
|
+
import { RoborockMatterbridgePlatform } from '../platform.js';
|
|
6
|
+
import { OperationStatusCode } from '../roborockCommunication/Zenum/operationStatusCode.js';
|
|
7
|
+
import { getRoomMap } from '../helper.js';
|
|
8
|
+
import { debugStringify } from 'matterbridge/logger';
|
|
9
|
+
import { getBatteryState, getBatteryStatus } from '../initialData/index.js';
|
|
10
|
+
import { getCurrentCleanModeFunc } from '../share/runtimeHelper.js';
|
|
11
|
+
import { RoborockVacuumCleaner } from '../rvc.js';
|
|
12
|
+
import { hasDockingStationError, parseDockingStationStatus } from '../model/DockingStationStatus.js';
|
|
13
|
+
|
|
14
|
+
export async function handleLocalMessage(data: CloudMessageResult, platform: RoborockMatterbridgePlatform, duid = ''): Promise<void> {
|
|
15
|
+
const robot = platform.robots.get(duid);
|
|
16
|
+
|
|
17
|
+
if (!robot) {
|
|
18
|
+
platform.log.error(`Robot with DUID ${duid} not found.`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const currentMappedAreas = platform.roborockService?.getSupportedAreas(duid);
|
|
23
|
+
const roomIndexMap = platform.roborockService?.getSupportedAreasIndexMap(duid);
|
|
24
|
+
const roomMap = await getRoomMap(duid, platform);
|
|
25
|
+
platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
|
|
26
|
+
platform.log.debug(`Current mapped areas: ${currentMappedAreas ? debugStringify(currentMappedAreas) : 'undefined'}`);
|
|
27
|
+
platform.log.debug(
|
|
28
|
+
`RoomIndexMap:
|
|
29
|
+
`,
|
|
30
|
+
roomIndexMap,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const deviceData = robot.device.data;
|
|
34
|
+
const state = state_to_matter_state(data.state);
|
|
35
|
+
if (state) {
|
|
36
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(state), platform.log);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (data.state === OperationStatusCode.Idle) {
|
|
40
|
+
const selectedAreas = platform.roborockService?.getSelectedAreas(duid) ?? [];
|
|
41
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', selectedAreas, platform.log);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (state === RvcRunMode.ModeTag.Cleaning && !data.cleaning_info) {
|
|
45
|
+
platform.log.debug('No cleaning_info, setting currentArea to null');
|
|
46
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', null, platform.log);
|
|
47
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', [], platform.log);
|
|
48
|
+
} else {
|
|
49
|
+
const isMultipleMapEnable = platform.enableExperimentalFeature?.enableExperimentalFeature && platform.enableExperimentalFeature?.advancedFeature?.enableMultipleMap;
|
|
50
|
+
if (isMultipleMapEnable) {
|
|
51
|
+
await mapRoomsToAreasFeatureOn(platform, duid, data);
|
|
52
|
+
} else {
|
|
53
|
+
await mapRoomsToAreasFeatureOff(platform, duid, data);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (data.battery) {
|
|
58
|
+
const batteryLevel = data.battery as number;
|
|
59
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
|
|
60
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(data.state, data.battery), platform.log);
|
|
61
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const currentCleanModeSetting = {
|
|
65
|
+
suctionPower: data.cleaning_info?.fan_power ?? data.fan_power,
|
|
66
|
+
waterFlow: data.cleaning_info?.water_box_status ?? data.water_box_mode,
|
|
67
|
+
distance_off: data.distance_off,
|
|
68
|
+
mopRoute: data.cleaning_info?.mop_mode ?? data.mop_mode,
|
|
69
|
+
segment_id: data.cleaning_info?.segment_id,
|
|
70
|
+
target_segment_id: data.cleaning_info?.target_segment_id,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
platform.log.debug(`data: ${debugStringify(data)}`);
|
|
74
|
+
platform.log.notice(`currentCleanModeSetting: ${debugStringify(currentCleanModeSetting)}`);
|
|
75
|
+
|
|
76
|
+
if (currentCleanModeSetting.mopRoute && currentCleanModeSetting.suctionPower && currentCleanModeSetting.waterFlow) {
|
|
77
|
+
const currentCleanMode = getCurrentCleanModeFunc(deviceData.model, platform.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false)(currentCleanModeSetting);
|
|
78
|
+
platform.log.debug(`Current clean mode: ${currentCleanMode}`);
|
|
79
|
+
|
|
80
|
+
if (currentCleanMode) {
|
|
81
|
+
robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
processAdditionalProps(robot, data, duid, platform);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function triggerDssError(robot: RoborockVacuumCleaner, platform: RoborockMatterbridgePlatform): boolean {
|
|
89
|
+
const currentOperationState = robot.getAttribute(RvcOperationalState.Cluster.id, 'operationalState') as RvcOperationalState.OperationalState;
|
|
90
|
+
if (currentOperationState === RvcOperationalState.OperationalState.Error) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (currentOperationState === RvcOperationalState.OperationalState.Docked) {
|
|
95
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function processAdditionalProps(robot: RoborockVacuumCleaner, message: CloudMessageResult, duid: string, platform: RoborockMatterbridgePlatform): Promise<void> {
|
|
103
|
+
// dss -> DockingStationStatus
|
|
104
|
+
const dssStatus = getDssStatus(message, duid, platform);
|
|
105
|
+
if (dssStatus) {
|
|
106
|
+
triggerDssError(robot, platform);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getDssStatus(message: CloudMessageResult, duid: string, platform: RoborockMatterbridgePlatform): RvcOperationalState.OperationalState | undefined {
|
|
111
|
+
const robot = platform.robots.get(duid);
|
|
112
|
+
if (robot === undefined) {
|
|
113
|
+
platform.log.error(`Error4: Robot with DUID ${duid} not found`);
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
platform.enableExperimentalFeature &&
|
|
119
|
+
platform.enableExperimentalFeature.enableExperimentalFeature &&
|
|
120
|
+
platform.enableExperimentalFeature.advancedFeature.includeDockStationStatus &&
|
|
121
|
+
message.dss !== undefined
|
|
122
|
+
) {
|
|
123
|
+
const dss = parseDockingStationStatus(message.dss);
|
|
124
|
+
if (dss && robot) {
|
|
125
|
+
robot.dockStationStatus = dss;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (dss && hasDockingStationError(dss)) {
|
|
129
|
+
return RvcOperationalState.OperationalState.Error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function mapRoomsToAreasFeatureOff(platform: RoborockMatterbridgePlatform, duid: string, data: CloudMessageResult): Promise<void> {
|
|
136
|
+
const currentMappedAreas = platform.roborockService?.getSupportedAreas(duid);
|
|
137
|
+
const robot = platform.robots.get(duid);
|
|
138
|
+
if (!robot) {
|
|
139
|
+
platform.log.error(`Robot with DUID ${duid} not found.`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!data.cleaning_info) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const source_segment_id = data.cleaning_info.segment_id ?? -1; // 4
|
|
148
|
+
const source_target_segment_id = data.cleaning_info.target_segment_id ?? -1; // -1
|
|
149
|
+
const segment_id = source_segment_id !== -1 ? source_segment_id : source_target_segment_id; // 4
|
|
150
|
+
const mappedArea = currentMappedAreas?.find((x) => x.areaId == segment_id);
|
|
151
|
+
platform.log.debug(
|
|
152
|
+
`mappedArea:
|
|
153
|
+
source_segment_id: ${source_segment_id},
|
|
154
|
+
source_target_segment_id: ${source_target_segment_id},
|
|
155
|
+
segment_id: ${segment_id},
|
|
156
|
+
result: ${mappedArea ? debugStringify(mappedArea) : 'undefined'}
|
|
157
|
+
`,
|
|
158
|
+
);
|
|
159
|
+
if (segment_id !== -1 && mappedArea) {
|
|
160
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', segment_id, platform.log);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (segment_id == -1) {
|
|
164
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', null, platform.log);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function mapRoomsToAreasFeatureOn(platform: RoborockMatterbridgePlatform, duid: string, data: CloudMessageResult): Promise<void> {
|
|
169
|
+
const robot = platform.robots.get(duid);
|
|
170
|
+
if (!robot) {
|
|
171
|
+
platform.log.error(`Robot with DUID ${duid} not found.`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!data.cleaning_info) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const currentMappedAreas = platform.roborockService?.getSupportedAreas(duid);
|
|
180
|
+
const roomIndexMap = platform.roborockService?.getSupportedAreasIndexMap(duid);
|
|
181
|
+
const source_segment_id = data.cleaning_info.segment_id ?? -1; // 4
|
|
182
|
+
const source_target_segment_id = data.cleaning_info.target_segment_id ?? -1; // -1
|
|
183
|
+
const segment_id = source_segment_id !== -1 ? source_segment_id : source_target_segment_id; // 4
|
|
184
|
+
const areaId = roomIndexMap?.getAreaId(segment_id, 0);
|
|
185
|
+
|
|
186
|
+
if (!areaId) {
|
|
187
|
+
platform.log.debug(`
|
|
188
|
+
source_segment_id: ${source_segment_id},
|
|
189
|
+
source_target_segment_id: ${source_target_segment_id},
|
|
190
|
+
segment_id: ${segment_id},
|
|
191
|
+
areaId: ${areaId}`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const mappedArea = currentMappedAreas?.find((x) => x.areaId == areaId);
|
|
196
|
+
platform.log.debug(
|
|
197
|
+
`mappedArea:
|
|
198
|
+
source_segment_id: ${source_segment_id},
|
|
199
|
+
source_target_segment_id: ${source_target_segment_id},
|
|
200
|
+
segment_id: ${segment_id},
|
|
201
|
+
areaId: ${areaId},
|
|
202
|
+
result: ${mappedArea ? debugStringify(mappedArea) : 'undefined'}
|
|
203
|
+
`,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if (areaId !== -1 && mappedArea) {
|
|
207
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', areaId, platform.log); // need to be 107
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -2,7 +2,7 @@ import { getCurrentCleanModeDefault } from '../behaviors/roborock.vacuum/default
|
|
|
2
2
|
import { getCurrentCleanModeSmart } from '../behaviors/roborock.vacuum/smart/runtimes.js';
|
|
3
3
|
import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
|
|
4
4
|
|
|
5
|
-
export type CleanModeFunc = (setting: { suctionPower: number; waterFlow: number; distance_off: number; mopRoute: number }) => number | undefined;
|
|
5
|
+
export type CleanModeFunc = (setting: { suctionPower: number; waterFlow: number; distance_off: number; mopRoute: number | undefined }) => number | undefined;
|
|
6
6
|
|
|
7
7
|
export function getCurrentCleanModeFunc(model: string, forceRunAtDefault: boolean): CleanModeFunc {
|
|
8
8
|
if (forceRunAtDefault) {
|
package/src/tests/helper.test.ts
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { getRoomMapFromDevice } from '../helper';
|
|
2
2
|
import { RoomMap } from '../model/RoomMap';
|
|
3
3
|
|
|
4
|
-
// Mocks
|
|
5
|
-
// const mockLog = {
|
|
6
|
-
// notice: (message: string, ...arg: unknown[]) => console.log('NOTICE:', message, ...arg),
|
|
7
|
-
// error: (message: string, ...arg: unknown[]) => console.error('ERROR:', message, ...arg),
|
|
8
|
-
// debug: (message: string, ...arg: unknown[]) => console.debug('DEBUG:', message, ...arg),
|
|
9
|
-
// info: (message: string, ...arg: unknown[]) => console.info('INFO:', message, ...arg),
|
|
10
|
-
// warn: (message: string, ...arg: unknown[]) => console.warn('WARN:', message, ...arg),
|
|
11
|
-
// verbose: (message: string, ...arg: unknown[]) => console.log('VERBOSE:', message, ...arg),
|
|
12
|
-
// };
|
|
13
|
-
|
|
14
4
|
const mockLog = {
|
|
15
5
|
notice: jest.fn(),
|
|
16
6
|
error: jest.fn(),
|
|
@@ -110,4 +100,63 @@ describe('getRoomMapFromDevice', () => {
|
|
|
110
100
|
expect(mockRoborockService.getRoomMappings).toHaveBeenCalledWith('123');
|
|
111
101
|
expect(result.rooms.length).toBeGreaterThan(0);
|
|
112
102
|
});
|
|
103
|
+
|
|
104
|
+
it('returns RoomMap from getMapInformation if available', async () => {
|
|
105
|
+
const device = {
|
|
106
|
+
duid: '123',
|
|
107
|
+
rooms: [
|
|
108
|
+
{ id: 11100845, name: 'Kitchen' },
|
|
109
|
+
{ id: 11100849, name: 'Study' },
|
|
110
|
+
{ id: 11100842, name: 'Living room' },
|
|
111
|
+
{ id: 11100847, name: 'Bedroom' },
|
|
112
|
+
{ id: 12469150, name: 'Dining room' },
|
|
113
|
+
{ id: 12461114, name: 'Guest bedroom' },
|
|
114
|
+
{ id: 12461109, name: 'Master bedroom' },
|
|
115
|
+
{ id: 12461111, name: 'Balcony' },
|
|
116
|
+
{ id: 11100842, name: 'Living room' },
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
mockRoborockService.getRoomMappings.mockResolvedValue(undefined);
|
|
120
|
+
mockRoborockService.getMapInformation.mockResolvedValue({
|
|
121
|
+
maps: [
|
|
122
|
+
{
|
|
123
|
+
id: 0,
|
|
124
|
+
name: 'First Map',
|
|
125
|
+
rooms: [
|
|
126
|
+
{ id: 1, globalId: 11100845, iot_name_id: '11100845', tag: 14, displayName: 'Kitchen', mapId: 0 },
|
|
127
|
+
{ id: 2, globalId: 11100849, iot_name_id: '11100849', tag: 9, displayName: 'Study', mapId: 0 },
|
|
128
|
+
{ id: 3, globalId: 11100842, iot_name_id: '11100842', tag: 6, displayName: 'Living room', mapId: 0 },
|
|
129
|
+
{ id: 4, globalId: 11100847, iot_name_id: '11100847', tag: 1, displayName: 'Bedroom', mapId: 0 },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 1,
|
|
134
|
+
name: 'Second Map',
|
|
135
|
+
rooms: [
|
|
136
|
+
{ id: 1, globalId: 12469150, iot_name_id: '12469150', tag: 13, displayName: 'Dining room', mapId: 1 },
|
|
137
|
+
{ id: 2, globalId: 12461114, iot_name_id: '12461114', tag: 3, displayName: 'Guest bedroom', mapId: 1 },
|
|
138
|
+
{ id: 3, globalId: 12461109, iot_name_id: '12461109', tag: 2, displayName: 'Master bedroom', mapId: 1 },
|
|
139
|
+
{ id: 4, globalId: 12461111, iot_name_id: '12461111', tag: 7, displayName: 'Balcony', mapId: 1 },
|
|
140
|
+
{ id: 5, globalId: 11100842, iot_name_id: '11100842', tag: 6, displayName: 'Living room', mapId: 1 },
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
allRooms: [
|
|
145
|
+
{ id: 1, globalId: 11100845, iot_name_id: '11100845', tag: 14, displayName: 'Kitchen', mapId: 0 },
|
|
146
|
+
{ id: 2, globalId: 11100849, iot_name_id: '11100849', tag: 9, displayName: 'Study', mapId: 0 },
|
|
147
|
+
{ id: 3, globalId: 11100842, iot_name_id: '11100842', tag: 6, displayName: 'Living room', mapId: 0 },
|
|
148
|
+
{ id: 4, globalId: 11100847, iot_name_id: '11100847', tag: 1, displayName: 'Bedroom', mapId: 0 },
|
|
149
|
+
{ id: 1, globalId: 12469150, iot_name_id: '12469150', tag: 13, displayName: 'Dining room', mapId: 1 },
|
|
150
|
+
{ id: 2, globalId: 12461114, iot_name_id: '12461114', tag: 3, displayName: 'Guest bedroom', mapId: 1 },
|
|
151
|
+
{ id: 3, globalId: 12461109, iot_name_id: '12461109', tag: 2, displayName: 'Master bedroom', mapId: 1 },
|
|
152
|
+
{ id: 4, globalId: 12461111, iot_name_id: '12461111', tag: 7, displayName: 'Balcony', mapId: 1 },
|
|
153
|
+
{ id: 5, globalId: 11100842, iot_name_id: '11100842', tag: 6, displayName: 'Living room', mapId: 1 },
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const result = await getRoomMapFromDevice(device as any, mockPlatform as any);
|
|
158
|
+
expect(result).toBeInstanceOf(RoomMap);
|
|
159
|
+
expect(mockRoborockService.getMapInformation).toHaveBeenCalledWith('123');
|
|
160
|
+
expect(result.rooms.length).toBeGreaterThan(0);
|
|
161
|
+
});
|
|
113
162
|
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AnsiLogger } from 'matterbridge/logger';
|
|
2
|
+
import RoborockService from '../roborockService';
|
|
3
|
+
import { MessageProcessor } from '../roborockCommunication/broadcast/messageProcessor';
|
|
4
|
+
import { RoomIndexMap } from '../model/roomIndexMap';
|
|
5
|
+
|
|
6
|
+
describe('RoborockService - startClean', () => {
|
|
7
|
+
let roborockService: RoborockService;
|
|
8
|
+
let mockLogger: AnsiLogger;
|
|
9
|
+
let mockMessageProcessor: jest.Mocked<MessageProcessor>;
|
|
10
|
+
let mockLoginApi: any;
|
|
11
|
+
let mockIotApi: any;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mockLogger = {
|
|
15
|
+
debug: jest.fn(),
|
|
16
|
+
notice: jest.fn(),
|
|
17
|
+
error: jest.fn(),
|
|
18
|
+
warn: jest.fn(),
|
|
19
|
+
} as any;
|
|
20
|
+
|
|
21
|
+
mockMessageProcessor = {
|
|
22
|
+
startClean: jest.fn(),
|
|
23
|
+
startRoomClean: jest.fn(),
|
|
24
|
+
} as any;
|
|
25
|
+
|
|
26
|
+
mockLoginApi = {
|
|
27
|
+
loginWithPassword: jest.fn(),
|
|
28
|
+
loginWithUserData: jest.fn(),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
roborockService = new RoborockService(() => mockLoginApi, jest.fn(), 10, {} as any, mockLogger);
|
|
32
|
+
roborockService['auth'] = jest.fn((ud) => ud);
|
|
33
|
+
roborockService['messageProcessorMap'] = new Map<string, MessageProcessor>([['test-duid', mockMessageProcessor]]);
|
|
34
|
+
|
|
35
|
+
mockIotApi = { getCustom: jest.fn() };
|
|
36
|
+
roborockService['iotApi'] = mockIotApi;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('setSelectedAreas should set selected areas', () => {
|
|
40
|
+
roborockService.setSupportedAreaIndexMap(
|
|
41
|
+
'duid',
|
|
42
|
+
new RoomIndexMap(
|
|
43
|
+
new Map([
|
|
44
|
+
[100, { roomId: 1, mapId: 0 }],
|
|
45
|
+
[101, { roomId: 2, mapId: 0 }],
|
|
46
|
+
[102, { roomId: 3, mapId: 0 }],
|
|
47
|
+
[103, { roomId: 4, mapId: 0 }],
|
|
48
|
+
[104, { roomId: 1, mapId: 1 }],
|
|
49
|
+
[105, { roomId: 2, mapId: 1 }],
|
|
50
|
+
[106, { roomId: 3, mapId: 1 }],
|
|
51
|
+
[107, { roomId: 4, mapId: 1 }],
|
|
52
|
+
[108, { roomId: 5, mapId: 1 }],
|
|
53
|
+
]),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
roborockService.setSelectedAreas('duid', [106, 108]);
|
|
57
|
+
|
|
58
|
+
expect(roborockService['selectedAreas'].get('duid')).toEqual([3, 5]);
|
|
59
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - setSelectedAreas - roomIds', [3, 5]);
|
|
60
|
+
});
|
|
61
|
+
});
|