UNPKG

iobroker.roborock

Version:
1,172 lines (1,140 loc) 84.5 kB
import { beforeEach, describe, expect, it, vi } from "vitest"; import type { FeatureDependencies } from "../baseDeviceFeatures"; import { A179Features } from "./a179_features"; import { VACUUM_CONSTANTS } from "./vacuumConstants"; describe("A179Features", () => { let adapterMock: any; let depsMock: FeatureDependencies; let requestsHandlerMock: { sendRequest: ReturnType<typeof vi.fn> }; beforeEach(() => { requestsHandlerMock = { sendRequest: vi.fn().mockResolvedValue({}) }; adapterMock = { log: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), silly: vi.fn() }, setStateChanged: vi.fn().mockResolvedValue(undefined), setState: vi.fn(), delObjectAsync: vi.fn().mockResolvedValue(undefined), ensureState: vi.fn().mockResolvedValue(undefined), ensureFolder: vi.fn().mockResolvedValue(undefined), getStateAsync: vi.fn().mockImplementation(async (id: string) => id === "clientID" ? { val: "client-123" } : undefined), getObjectAsync: vi.fn().mockResolvedValue(undefined), extendObject: vi.fn().mockResolvedValue(undefined), setObject: vi.fn().mockResolvedValue(undefined), setObjectNotExistsAsync: vi.fn().mockResolvedValue(undefined), requestsHandler: requestsHandlerMock, getDeviceProtocolVersion: vi.fn().mockResolvedValue("1.0"), translations: {}, http_api: { getFwFeaturesResult: vi.fn(), storeFwFeaturesResult: vi.fn(), getRobotModel: vi.fn().mockReturnValue("roborock.vacuum.a179"), getDevices: vi.fn().mockReturnValue([]) }, rLog: vi.fn(), errorMessage: vi.fn().mockImplementation((error: unknown) => error instanceof Error ? error.message : String(error)), translationManager: { get: vi.fn().mockImplementation((key, def) => def || key), }, config: { cameraPin: "9876" }, }; depsMock = { adapter: adapterMock, http_api: adapterMock.http_api, ensureState: vi.fn().mockResolvedValue(undefined), ensureFolder: vi.fn().mockResolvedValue(undefined), log: adapterMock.log, config: { staticFeatures: [] } } as unknown as FeatureDependencies; }); it("registers Z70 actions under commands and moves get/set RPCs into grouped folders", async () => { const feature = new A179Features(depsMock, "duid1"); await feature.setupProtocolFeatures(); expect(feature.commands).toMatchObject({ set_water_box_distance_off: expect.objectContaining({ type: "number", min: 1, max: 30 }), app_empty_inbuilt_water_tank: expect.objectContaining({ type: "boolean", role: "button" }), app_arm_out_compartment: expect.objectContaining({ type: "boolean", role: "button" }), app_arm_direction_move: expect.objectContaining({ type: "number", states: { 1: "Up", 2: "Down", 3: "Go", 4: "Back" } }), app_arm_move: expect.objectContaining({ type: "json", role: "json" }), app_stop_grasp: expect.objectContaining({ type: "boolean", role: "button" }), app_rc_roller_mop_cover: expect.objectContaining({ type: "number", states: { 0: "Stretch Out", 1: "Retract" } }), app_rc_roller_mop: expect.objectContaining({ type: "number", states: { 0: "In", 1: "Out" } }), app_rc_lds_lifting: expect.objectContaining({ type: "number", states: { 0: "Lower", 1: "Lift Up" } }), app_wakeup_robot: expect.objectContaining({ type: "boolean", role: "button" }), app_segment_clean_subdivision: expect.objectContaining({ type: "json", role: "json" }), app_zoned_clean_subdivision: expect.objectContaining({ type: "json", role: "json" }), app_ignore_dirty_objects: expect.objectContaining({ type: "json", role: "json" }), app_start_build_map: expect.objectContaining({ type: "boolean", role: "button" }), app_resume_build_map: expect.objectContaining({ type: "boolean", role: "button" }), app_start_collect_dust: expect.objectContaining({ type: "boolean", role: "button" }), app_stop_collect_dust: expect.objectContaining({ type: "boolean", role: "button" }), app_switch_dock_cool_fan: expect.objectContaining({ type: "boolean", role: "switch.enable" }), app_delete_wifi: expect.objectContaining({ type: "number", role: "value" }), app_pick_up_enter_exit: expect.objectContaining({ type: "number", states: { 0: "Exit / Off", 1: "Manual Mode", 2: "Auto Mode" } }), app_pick_up: expect.objectContaining({ type: "number", states: { 0: "Auto Grab", 1: "Manual Grab" } }), app_put_down: expect.objectContaining({ type: "number", states: { 0: "Put Down" } }), app_change_camera: expect.objectContaining({ type: "number", states: { 0: "Disable Mechanical Camera", 1: "Enable Mechanical Camera" } }), start_camera_preview: expect.objectContaining({ type: "json", role: "json" }), stop_camera_preview: expect.objectContaining({ type: "json", role: "json" }), app_exhibition_enter_exit: expect.objectContaining({ type: "number", states: { 0: "Exit", 1: "Enter" } }), app_exhibition_action: expect.objectContaining({ type: "json", role: "json" }), app_start_tidy_up: expect.objectContaining({ type: "json" }), app_delete_tidy_up_record: expect.objectContaining({ type: "number" }), start_wash_then_charge: expect.objectContaining({ type: "boolean", role: "button" }), app_program_enter_exit: expect.objectContaining({ type: "number", states: { 0: "Exit", 1: "Enter" } }), app_copy_program: expect.objectContaining({ type: "json", role: "json" }), app_delete_program: expect.objectContaining({ type: "number", role: "value" }), app_modify_program: expect.objectContaining({ type: "json", role: "json" }), app_save_program: expect.objectContaining({ type: "json", role: "json" }), app_start_program: expect.objectContaining({ type: "json", role: "json" }), app_start_patrol: expect.objectContaining({ type: "json", role: "json" }), app_resume_patrol: expect.objectContaining({ type: "boolean", role: "button" }), app_start_pet_patrol: expect.objectContaining({ type: "boolean", role: "button" }), app_empty_rinse_tank_water: expect.objectContaining({ type: "boolean", role: "button" }), app_start_easter_egg: expect.objectContaining({ type: "boolean", role: "button" }), start_new_easter_egg: expect.objectContaining({ type: "number", states: { 0: "Dance" } }), start_voice_chat: expect.objectContaining({ type: "json", role: "json" }), stop_voice_chat: expect.objectContaining({ type: "boolean", role: "button" }), app_start_replenish_clean_area: expect.objectContaining({ type: "json", role: "json" }), app_skip_current_cleaning_area: expect.objectContaining({ type: "json", role: "json" }), set_voice_chat_volume: expect.objectContaining({ type: "number", role: "value" }), }); expect(feature.commands).not.toHaveProperty("get_camera_status"); expect(feature.commands).not.toHaveProperty("set_camera_status"); expect(feature.commands).not.toHaveProperty("app_get_arm_joints_data"); expect(feature.commands).not.toHaveProperty("app_get_program"); expect(feature.commands).not.toHaveProperty("app_set_robot_setting"); expect(feature.commands).not.toHaveProperty("get_dock_info"); expect(feature.commands).not.toHaveProperty("set_child_lock_status"); expect(feature.extraCommandGroups.queries).toMatchObject({ get_camera_status: expect.objectContaining({ type: "boolean", role: "button" }), app_get_program: expect.objectContaining({ type: "number", role: "value" }), app_get_program_runtime: expect.objectContaining({ type: "boolean", role: "button" }), app_tidy_up_record_summary: expect.objectContaining({ type: "boolean", role: "button" }), get_dock_info: expect.objectContaining({ type: "boolean", role: "button" }), get_homesec_connect_status: expect.objectContaining({ type: "boolean", role: "button" }), check_homesec_password: expect.objectContaining({ type: "string", role: "text" }), }); expect(feature.extraCommandGroups.settings).toMatchObject({ set_camera_status: expect.objectContaining({ type: "number", role: "value" }), app_set_robot_setting: expect.objectContaining({ type: "json", role: "json" }), set_child_lock_status: expect.objectContaining({ type: "boolean", role: "switch.enable" }), set_map_beautification_status: expect.objectContaining({ type: "boolean", role: "switch.enable" }), switch_video_quality: expect.objectContaining({ type: "string", states: { SD: "SD", HD: "HD", FHD: "FHD", AUTO: "AUTO" } }), enable_homesec_voice: expect.objectContaining({ type: "boolean", role: "switch.enable" }), }); }); it("creates grouped writable folders and removes legacy get/set states from commands", async () => { const feature = new A179Features(depsMock, "duid1"); adapterMock.getObjectAsync.mockImplementation(async (id: string) => { if (id === "Devices.duid1.commands.get_camera_status" || id === "Devices.duid1.commands.set_camera_status") { return { _id: id, type: "state", common: {}, native: {} }; } return undefined; }); await feature.setupProtocolFeatures(); await feature.createCommandObjects(); expect(depsMock.ensureFolder).toHaveBeenCalledWith("Devices.duid1.commands"); expect(depsMock.ensureFolder).toHaveBeenCalledWith("Devices.duid1.queries"); expect(depsMock.ensureFolder).toHaveBeenCalledWith("Devices.duid1.settings"); expect(adapterMock.delObjectAsync).toHaveBeenCalledWith("Devices.duid1.commands.get_camera_status"); expect(adapterMock.delObjectAsync).toHaveBeenCalledWith("Devices.duid1.commands.set_camera_status"); }); it("maps Z70 command parameters to the app payload shape", async () => { const feature = new A179Features(depsMock, "duid1"); await expect(feature.getCommandParams("app_empty_inbuilt_water_tank", true)).resolves.toEqual({ method: "app_empty_inbuilt_water_tank", params: {} }); await expect(feature.getCommandParams("app_keep_easter_egg", true)).resolves.toEqual({ method: "app_keep_easter_egg", params: {} }); await expect(feature.getCommandParams("app_open_gripper", true)).resolves.toEqual({ method: "app_open_gripper", params: {} }); await expect(feature.getCommandParams("app_arm_direction_move", "2")).resolves.toEqual({ method: "app_arm_direction_move", params: { direction: 2 } }); await expect(feature.getCommandParams("app_arm_move", "[1,2,3]")).resolves.toEqual({ method: "app_arm_move", params: { m: [1, 2, 3] } }); await expect(feature.getCommandParams("app_arm_move_preset", "[0,1,0]")).resolves.toEqual({ method: "app_arm_move", params: { m: [0, 1, 0] } }); await expect(feature.getCommandParams("app_arm_recover", true)).resolves.toEqual({ method: "app_arm_recover", params: {} }); await expect(feature.getCommandParams("app_arm_recover_payload", '{"confirm":true}')).resolves.toEqual({ method: "app_arm_recover", params: { confirm: true } }); await expect(feature.getCommandParams("app_get_arm_joints_data", true)).resolves.toEqual({ method: "app_get_arm_joints_data", params: {} }); await expect(feature.getCommandParams("app_stop_grasp", true)).resolves.toEqual({ method: "app_stop_grasp", params: [] }); await expect(feature.getCommandParams("app_rc_lds_lifting", 1)).resolves.toEqual({ method: "app_rc_lds_lifting", params: { lift_up: 1 } }); await expect(feature.getCommandParams("app_wakeup_robot", true)).resolves.toEqual({ method: "app_wakeup_robot", params: [] }); await expect(feature.getCommandParams("app_rc_roller_mop_cover", "2")).resolves.toEqual({ method: "app_rc_roller_mop_cover", params: { cmd: 2 } }); await expect(feature.getCommandParams("app_rc_roller_mop", 1)).resolves.toEqual({ method: "app_rc_roller_mop", params: { cmd: 1 } }); await expect(feature.getCommandParams("app_pick_up_enter_exit", "1")).resolves.toEqual({ method: "app_pick_up_enter_exit", params: { flag: 1 } }); await expect(feature.getCommandParams("app_pick_up", 1)).resolves.toEqual({ method: "app_pick_up", params: { flag: 1 } }); await expect(feature.getCommandParams("app_put_down", "0")).resolves.toEqual({ method: "app_put_down", params: { flag: 0 } }); await expect(feature.getCommandParams("app_change_camera", 1)).resolves.toEqual({ method: "app_change_camera", params: { flag: 1 } }); await expect(feature.getCommandParams("get_camera_status", true)).resolves.toEqual({ method: "get_camera_status", params: [] }); await expect(feature.getCommandParams("set_camera_status", 58805)).resolves.toEqual({ method: "set_camera_status", params: [58805] }); await expect(feature.getCommandParams("start_camera_preview", '{"password":"1234","client_id":"demo","quality":"HD"}')).resolves.toEqual({ method: "start_camera_preview", params: { password: "1234", client_id: "demo", quality: "HD" } }); await expect(feature.getCommandParams("stop_camera_preview", '{"client_id":"demo"}')).resolves.toEqual({ method: "stop_camera_preview", params: { client_id: "demo" } }); await expect(feature.getCommandParams("switch_video_quality", "fhd")).resolves.toEqual({ method: "switch_video_quality", params: { quality: "FHD" } }); await expect(feature.getCommandParams("app_exhibition_enter_exit", 1)).resolves.toEqual({ method: "app_exhibition_enter_exit", params: { flag: 1 } }); await expect(feature.getCommandParams("app_exhibition_action", '{"type":8,"value":1}')).resolves.toEqual({ method: "app_exhibition_action", params: { type: 8, value: 1 } }); await expect(feature.getCommandParams("app_set_tidy_up_zones", '{"zones":[{"id":1}]}')).resolves.toEqual({ method: "app_set_tidy_up_zones", params: { zones: [{ id: 1 }] } }); await expect(feature.getCommandParams("app_set_door_sill_blocks", '[{"id":1}]')).resolves.toEqual({ method: "app_set_door_sill_blocks", params: [{ id: 1 }] }); await expect(feature.getCommandParams("app_set_beautify_blocks", '{"erase":[1]}')).resolves.toEqual({ method: "app_set_beautify_blocks", params: { erase: [1] } }); await expect(feature.getCommandParams("app_set_smart_door_sill", '{"enabled":true}')).resolves.toEqual({ method: "app_set_smart_door_sill", params: { enabled: true } }); await expect(feature.getCommandParams("app_get_segment_clean_subdivision", '{"segment_id":12}')).resolves.toEqual({ method: "app_get_segment_clean_subdivision", params: { segment_id: 12 } }); await expect(feature.getCommandParams("app_segment_clean_subdivision", '{"segment_id":12,"mode":3}')).resolves.toEqual({ method: "app_segment_clean_subdivision", params: { segment_id: 12, mode: 3 } }); await expect(feature.getCommandParams("app_get_zoned_clean_subdivision", '{"zones":[[1,2,3,4]]}')).resolves.toEqual({ method: "app_get_zoned_clean_subdivision", params: { zones: [[1, 2, 3, 4]] } }); await expect(feature.getCommandParams("app_zoned_clean_subdivision", '{"zones":[[1,2,3,4]],"mode":2}')).resolves.toEqual({ method: "app_zoned_clean_subdivision", params: { zones: [[1, 2, 3, 4]], mode: 2 } }); await expect(feature.getCommandParams("app_set_dirty_replenish_clean_status", '{"status":1}')).resolves.toEqual({ method: "app_set_dirty_replenish_clean_status", params: { status: 1 } }); await expect(feature.getCommandParams("app_set_dynamic_config", '{"url":"https://example.invalid"}')).resolves.toEqual({ method: "app_set_dynamic_config", params: { url: "https://example.invalid" } }); await expect(feature.getCommandParams("app_set_ignore_stuck_point", '{"points":[[1,2]]}')).resolves.toEqual({ method: "app_set_ignore_stuck_point", params: { points: [[1, 2]] } }); await expect(feature.getCommandParams("app_set_low_space_zones", '{"zones":[{"id":1}]}')).resolves.toEqual({ method: "app_set_low_space_zones", params: { zones: [{ id: 1 }] } }); await expect(feature.getCommandParams("app_set_smart_cliff_forbidden", '{"status":1}')).resolves.toEqual({ method: "app_set_smart_cliff_forbidden", params: { status: 1 } }); await expect(feature.getCommandParams("app_ignore_dirty_objects", '{"object_ids":[1,2]}')).resolves.toEqual({ method: "app_ignore_dirty_objects", params: { object_ids: [1, 2] } }); await expect(feature.getCommandParams("app_save_beautification_pic", true)).resolves.toEqual({ method: "app_save_beautification_pic", params: {} }); await expect(feature.getCommandParams("app_start_build_map", true)).resolves.toEqual({ method: "app_start_build_map", params: [] }); await expect(feature.getCommandParams("app_resume_build_map", true)).resolves.toEqual({ method: "app_resume_build_map", params: [] }); await expect(feature.getCommandParams("app_start_collect_dust", true)).resolves.toEqual({ method: "app_start_collect_dust", params: [] }); await expect(feature.getCommandParams("app_stop_collect_dust", true)).resolves.toEqual({ method: "app_stop_collect_dust", params: [] }); await expect(feature.getCommandParams("app_switch_dock_cool_fan", true)).resolves.toEqual({ method: "app_switch_dock_cool_fan", params: { status: 1 } }); await expect(feature.getCommandParams("app_delete_wifi", 7)).resolves.toEqual({ method: "app_delete_wifi", params: { id: 7 } }); await expect(feature.getCommandParams("set_water_box_distance_off", 15)).resolves.toEqual({ distance_off: 160 }); await expect(feature.getCommandParams("app_start_tidy_up", '[{"object_id":"sock"}]')).resolves.toEqual({ method: "app_start_tidy_up", params: { objects: [{ object_id: "sock" }] } }); await expect(feature.getCommandParams("app_delete_tidy_up_record", "1713371337")).resolves.toEqual({ method: "app_delete_tidy_up_record", params: { start_time: 1713371337 } }); await expect(feature.getCommandParams("get_wash_debug_params", true)).resolves.toEqual({ method: "get_wash_debug_params", params: [] }); await expect(feature.getCommandParams("get_clean_roller_debug_info", true)).resolves.toEqual({ method: "get_wash_debug_params", params: [] }); await expect(feature.getCommandParams("set_wash_debug_params", '{"rollerspeed":3,"washinterval":10}')).resolves.toEqual({ method: "set_wash_debug_params", params: { rollerspeed: 3, washinterval: 10 } }); await expect(feature.getCommandParams("set_roller_speed", 5)).resolves.toEqual({ method: "set_wash_debug_params", params: { rollerspeed: 5 } }); await expect(feature.getCommandParams("set_drying_time", 60)).resolves.toEqual({ method: "set_wash_debug_params", params: { dryingtime: 60 } }); await expect(feature.getCommandParams("set_mopping_speed", 2)).resolves.toEqual({ method: "set_wash_debug_params", params: { moppingspeed: 2 } }); await expect(feature.getCommandParams("set_wash_interval", 8)).resolves.toEqual({ method: "set_wash_debug_params", params: { washinterval: 8 } }); await expect(feature.getCommandParams("get_smart_wash_params", true)).resolves.toEqual({ method: "get_smart_wash_params", params: {} }); await expect(feature.getCommandParams("set_smart_wash_params", '{"smart_wash":2,"wash_interval":1500}')).resolves.toEqual({ method: "set_smart_wash_params", params: { smart_wash: 2, wash_interval: 1500 } }); await expect(feature.getCommandParams("set_back_wash_mode", 0)).resolves.toEqual({ method: "set_smart_wash_params", params: { smart_wash: 1, wash_interval: 1200 } }); adapterMock.getStateAsync.mockImplementation(async (id: string) => id.endsWith(".dockingStationStatus.smartWash.washIntervalMinutes") ? { val: 25 } : undefined); requestsHandlerMock.sendRequest.mockImplementation(async (_duid: string, method: string) => method === "get_fw_features" ? [109] : {}); await expect(feature.getCommandParams("set_back_wash_mode", 2)).resolves.toEqual({ method: "set_smart_wash_params", params: { smart_wash: 2, wash_interval: 1500 } }); adapterMock.getStateAsync.mockResolvedValue(undefined); requestsHandlerMock.sendRequest.mockResolvedValue({}); await expect(feature.getCommandParams("set_back_wash_interval", 25)).resolves.toEqual({ method: "set_smart_wash_params", params: { smart_wash: 0, wash_interval: 1500 } }); await expect(feature.getCommandParams("get_wash_towel_params", true)).resolves.toEqual({ method: "get_wash_towel_params", params: [] }); await expect(feature.getCommandParams("get_wash_towel_mode", true)).resolves.toEqual({ method: "get_wash_towel_mode", params: {} }); await expect(feature.getCommandParams("get_wash_water_temperature", true)).resolves.toEqual({ method: "get_wash_water_temperature", params: {} }); await expect(feature.getCommandParams("set_wash_towel_params", '{"interval":12,"status":1}')).resolves.toEqual({ method: "set_wash_towel_params", params: { interval: 12, status: 1 } }); await expect(feature.getCommandParams("set_wash_towel_interval", 12)).resolves.toEqual({ method: "set_wash_towel_params", params: { interval: 12 } }); await expect(feature.getCommandParams("set_wash_towel_mode", 10)).resolves.toEqual({ method: "set_wash_towel_mode", params: { wash_mode: 10 } }); await expect(feature.getCommandParams("set_wash_towel_status", 1)).resolves.toEqual({ method: "set_wash_towel_params", params: { status: 1 } }); await expect(feature.getCommandParams("set_wash_water_temperature", 2)).resolves.toEqual({ method: "set_wash_water_temperature", params: { values: 2 } }); await expect(feature.getCommandParams("start_wash_then_charge", true)).resolves.toEqual({ method: "start_wash_then_charge", params: [] }); await expect(feature.getCommandParams("app_get_program", 2)).resolves.toEqual({ method: "app_get_program", params: { program_id: 2 } }); await expect(feature.getCommandParams("app_get_robot_setting", "custom_mode")).resolves.toEqual({ method: "app_get_robot_setting", params: { name: "custom_mode" } }); await expect(feature.getCommandParams("app_get_robot_setting", '["custom_mode","voice_service"]')).resolves.toEqual({ method: "app_get_robot_setting", params: [{ name: "custom_mode" }, { name: "voice_service" }] }); await expect(feature.getCommandParams("app_set_robot_setting", '{"name":"custom_mode","status":1}')).resolves.toEqual({ method: "app_set_robot_setting", params: { name: "custom_mode", status: 1 } }); await expect(feature.getCommandParams("app_get_program_runtime", true)).resolves.toEqual({ method: "app_get_program_runtime", params: {} }); await expect(feature.getCommandParams("app_get_program_soundlist", true)).resolves.toEqual({ method: "app_get_program_soundlist", params: {} }); await expect(feature.getCommandParams("app_get_programs_summary", true)).resolves.toEqual({ method: "app_get_programs_summary", params: {} }); await expect(feature.getCommandParams("app_program_enter_exit", 1)).resolves.toEqual({ method: "app_program_enter_exit", params: { flag: 1 } }); await expect(feature.getCommandParams("app_copy_program", '{"name":"Demo","program_id":3}')).resolves.toEqual({ method: "app_copy_program", params: { name: "Demo", program_id: 3 } }); await expect(feature.getCommandParams("app_delete_program", 3)).resolves.toEqual({ method: "app_delete_program", params: { program_id: 3 } }); await expect(feature.getCommandParams("app_modify_program", '{"name":"Demo 2","program_id":3}')).resolves.toEqual({ method: "app_modify_program", params: { name: "Demo 2", program_id: 3 } }); await expect(feature.getCommandParams("app_save_program", '{"program_id":3,"steps":[{"cmd_id":"sound"}]}')).resolves.toEqual({ method: "app_save_program", params: { program_id: 3, steps: [{ cmd_id: "sound" }] } }); requestsHandlerMock.sendRequest.mockImplementation(async (_duid: string, method: string) => { if (method === "app_get_program") { return { result: { cmd_ids: [11, 12, 13] } }; } return {}; }); await expect(feature.getCommandParams("app_start_program", '{"program_id":3}')).resolves.toEqual({ method: "app_start_program", params: { cmd_ids: [11, 12, 13] } }); await expect(feature.getCommandParams("app_start_program", 3)).resolves.toEqual({ method: "app_start_program", params: { cmd_ids: [11, 12, 13] } }); await expect(feature.getCommandParams("app_start_patrol", '{"map_index":0,"points":[[100,200],[300,400]]}')).resolves.toEqual({ method: "app_start_patrol", params: { map_index: 0, points: [[100, 200], [300, 400]] } }); await expect(feature.getCommandParams("app_resume_patrol", true)).resolves.toEqual({ method: "app_resume_patrol", params: {} }); await expect(feature.getCommandParams("app_start_pet_patrol", true)).resolves.toEqual({ method: "app_start_pet_patrol", params: {} }); await expect(feature.getCommandParams("app_empty_rinse_tank_water", true)).resolves.toEqual({ method: "app_empty_rinse_tank_water", params: {} }); await expect(feature.getCommandParams("app_start_easter_egg", true)).resolves.toEqual({ method: "app_start_easter_egg", params: {} }); await expect(feature.getCommandParams("start_new_easter_egg", 0)).resolves.toEqual({ method: "start_new_easter_egg", params: { type: 0 } }); await expect(feature.getCommandParams("start_voice_chat", '{"play":true,"record":false}')).resolves.toEqual({ method: "start_voice_chat", params: { play: true, record: false } }); await expect(feature.getCommandParams("stop_voice_chat", true)).resolves.toEqual({ method: "stop_voice_chat", params: {} }); await expect(feature.getCommandParams("set_voice_chat_volume", 66)).resolves.toEqual({ method: "set_voice_chat_volume", params: { volume: 66 } }); await expect(feature.getCommandParams("app_get_clean_estimate_info", true)).resolves.toEqual({ method: "app_get_clean_estimate_info", params: {} }); await expect(feature.getCommandParams("app_get_carpet_deep_clean_status", true)).resolves.toEqual({ method: "app_get_carpet_deep_clean_status", params: {} }); await expect(feature.getCommandParams("app_set_carpet_deep_clean_status", '{"status":1}')).resolves.toEqual({ method: "app_set_carpet_deep_clean_status", params: { status: 1 } }); await expect(feature.getCommandParams("app_set_cross_carpet_cleaning_status", true)).resolves.toEqual({ method: "app_set_cross_carpet_cleaning_status", params: { status: 1 } }); await expect(feature.getCommandParams("app_set_priority_carpet_cleaning_status", false)).resolves.toEqual({ method: "app_set_priority_carpet_cleaning_status", params: { status: 0 } }); await expect(feature.getCommandParams("app_get_dryer_setting", true)).resolves.toEqual({ method: "app_get_dryer_setting", params: [] }); await expect(feature.getCommandParams("app_set_dryer_setting", '{"on":{"dry_time":1800},"status":1}')).resolves.toEqual({ method: "app_set_dryer_setting", params: { on: { dry_time: 1800 }, status: 1 } }); await expect(feature.getCommandParams("app_set_dryer_status", true)).resolves.toEqual({ method: "app_set_dryer_status", params: { status: 1 } }); await expect(feature.getCommandParams("app_get_wifi_list", true)).resolves.toEqual({ method: "app_get_wifi_list", params: {} }); await expect(feature.getCommandParams("get_dock_info", true)).resolves.toEqual({ method: "get_dock_info", params: {} }); await expect(feature.getCommandParams("get_child_lock_status", true)).resolves.toEqual({ method: "get_child_lock_status", params: [] }); await expect(feature.getCommandParams("set_child_lock_status", true)).resolves.toEqual({ method: "set_child_lock_status", params: { lock_status: 1 } }); await expect(feature.getCommandParams("get_collision_avoid_status", true)).resolves.toEqual({ method: "get_collision_avoid_status", params: [] }); await expect(feature.getCommandParams("set_collision_avoid_status", false)).resolves.toEqual({ method: "set_collision_avoid_status", params: { status: 0 } }); await expect(feature.getCommandParams("get_flow_led_status", true)).resolves.toEqual({ method: "get_flow_led_status", params: [] }); await expect(feature.getCommandParams("set_flow_led_status", 1)).resolves.toEqual({ method: "set_flow_led_status", params: { status: 1 } }); await expect(feature.getCommandParams("get_map_beautification_status", true)).resolves.toEqual({ method: "get_map_beautification_status", params: [] }); await expect(feature.getCommandParams("set_map_beautification_status", true)).resolves.toEqual({ method: "set_map_beautification_status", params: { status: 1 } }); await expect(feature.getCommandParams("set_ignore_carpet_zone", '{"zones":[[1,2,3,4]]}')).resolves.toEqual({ method: "set_ignore_carpet_zone", params: { zones: [[1, 2, 3, 4]] } }); await expect(feature.getCommandParams("set_ignore_identify_area", '{"areas":[{"id":3}]}')).resolves.toEqual({ method: "set_ignore_identify_area", params: { areas: [{ id: 3 }] } }); await expect(feature.getCommandParams("test_get_enable_wakeup", true)).resolves.toEqual({ method: "test_get_enable_wakeup", params: [] }); await expect(feature.getCommandParams("test_set_enable_wakeup", false)).resolves.toEqual({ method: "test_set_enable_wakeup", params: { enable_wakeup: 0 } }); await expect(feature.getCommandParams("test_get_voice_keep_seconds", true)).resolves.toEqual({ method: "test_get_voice_keep_seconds", params: [] }); await expect(feature.getCommandParams("test_set_voice_keep_seconds", 18)).resolves.toEqual({ method: "test_set_voice_keep_seconds", params: { keep_seconds: 18 } }); await expect(feature.getCommandParams("get_voice_history_summary", true)).resolves.toEqual({ method: "get_voice_history_summary", params: [] }); await expect(feature.getCommandParams("get_voice_history", 42)).resolves.toEqual({ method: "get_voice_history", params: { max_id: 42 } }); await expect(feature.getCommandParams("test_get_voice_list", true)).resolves.toEqual({ method: "test_get_voice_list", params: [] }); await expect(feature.getCommandParams("app_start_replenish_clean_area", '{"zones":[1,2]}')).resolves.toEqual({ method: "app_start_replenish_clean_area", params: { zones: [1, 2] } }); await expect(feature.getCommandParams("app_skip_current_cleaning_area", '{"skip":"current_zone"}')).resolves.toEqual({ method: "app_skip_current_cleaning_area", params: { skip: "current_zone" } }); await expect(feature.getCommandParams("app_update_unsave_map", true)).resolves.toEqual({ method: "app_update_unsave_map", params: { update: true } }); await expect(feature.getCommandParams("get_auto_delivery_cleaning_fluid", true)).resolves.toEqual({ method: "get_auto_delivery_cleaning_fluid", params: {} }); await expect(feature.getCommandParams("set_auto_delivery_cleaning_fluid", true)).resolves.toEqual({ method: "set_auto_delivery_cleaning_fluid", params: { status: 1 } }); await expect(feature.getCommandParams("get_ap_mic_led_status", true)).resolves.toEqual({ method: "get_ap_mic_led_status", params: {} }); await expect(feature.getCommandParams("set_ap_mic_led_status", false)).resolves.toEqual({ method: "set_ap_mic_led_status", params: { status: 0 } }); await expect(feature.getCommandParams("get_voice_service_switch", true)).resolves.toEqual({ method: "get_voice_service_switch", params: [] }); await expect(feature.getCommandParams("set_voice_service_switch", true)).resolves.toEqual({ method: "set_voice_service_switch", params: { data: 1 } }); await expect(feature.getCommandParams("get_handle_leak_water_status", true)).resolves.toEqual({ method: "get_handle_leak_water_status", params: {} }); await expect(feature.getCommandParams("set_handle_leak_water_status", true)).resolves.toEqual({ method: "set_handle_leak_water_status", params: { status: 1 } }); await expect(feature.getCommandParams("check_homesec_password", "123456")).resolves.toEqual({ method: "check_homesec_password", params: { password: "123456" } }); await expect(feature.getCommandParams("set_homesec_password", '{"enable_password":1,"old_password":"1111","new_password":"2222"}')).resolves.toEqual({ method: "set_homesec_password", params: { enable_password: 1, old_password: "1111", new_password: "2222" } }); await expect(feature.getCommandParams("reset_homesec_password", true)).resolves.toEqual({ method: "reset_homesec_password", params: [] }); await expect(feature.getCommandParams("enable_homesec_voice", true)).resolves.toEqual({ method: "enable_homesec_voice", params: { enable: true } }); await expect(feature.getCommandParams("get_homesec_connect_status", true)).resolves.toEqual({ method: "get_homesec_connect_status", params: [] }); }); it("builds app-like preview defaults from cameraPin and clientID", async () => { const feature = new A179Features(depsMock, "duid1"); await expect(feature.getCommandParams("start_camera_preview", true)).resolves.toEqual({ method: "start_camera_preview", params: { quality: "SD", password: "9876", client_id: "client-123" } }); await expect(feature.getCommandParams("start_camera_preview", '{"quality":"FHD"}')).resolves.toEqual({ method: "start_camera_preview", params: { quality: "FHD", password: "9876", client_id: "client-123" } }); await expect(feature.getCommandParams("stop_camera_preview", true)).resolves.toEqual({ method: "stop_camera_preview", params: { client_id: "client-123" } }); }); it("stores app_get_program command results as compact program detail state", async () => { const feature = new A179Features(depsMock, "duid1"); await feature.onCommandResult("app_get_program", "app_get_program", { result: { program_id: 5, name: "Manual Program", description: "From command result", program_type: 1, cmd_ids: [{ id: 1, cmd_id: "sound" }, { id: 2, cmd_id: "arm_out" }], blocks: { blocks: [{ id: "p1" }] } } }, { program_id: 5 }); const programDetailCall = adapterMock.setStateChanged.mock.calls.find( ([id]: [string]) => id === "Devices.duid1.program.selectedJSON" ); expect(programDetailCall).toBeDefined(); expect(JSON.parse(String(programDetailCall?.[1]?.val))).toEqual({ program_id: 5, name: "Manual Program", description: "From command result", program_type: 1, cmd_ids: [{ id: 1, cmd_id: "sound" }, { id: 2, cmd_id: "arm_out" }], blocks: { blocks: [{ id: "p1" }] }, programTypeLabel: "My Program", isOfficialProgram: false, isMyProgram: true, commandCount: 2, blockCount: 1, hasBlocks: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.selectedCommandCount", { val: 2, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.selectedHasBlocks", { val: true, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.selectedTypeLabel", { val: "My Program", ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.selectedName", { val: "Manual Program", ack: true }); }); it("stores shared manual getter results and unwraps transport wrappers", async () => { requestsHandlerMock.sendRequest.mockImplementation(async (_duid: string, method: string, params?: unknown) => { if (method === "app_get_program") { const programId = Number((params as { program_id?: unknown } | undefined)?.program_id); if (programId === 1) { return { result: { program_id: 1, name: "Official A", description: "Alpha", program_type: 2, cmd_ids: [{ id: 1, cmd_id: "arm_out" }], blocks: { blocks: [{ id: "oa" }] } } }; } if (programId === 9) { return { result: { program_id: 9, name: "My Routine", description: "Custom", program_type: 1, cmd_ids: [{ id: 4, cmd_id: "sound" }] } }; } } return {}; }); const feature = new A179Features(depsMock, "duid1"); await feature.onCommandResult("app_tidy_up_record_summary", "app_tidy_up_record_summary", { data: { result: { tidyup_count: 4, tidyup_time: 1200 } }, version: "L01" }); await feature.onCommandResult("app_get_tidy_up_zones", "app_get_tidy_up_zones", { data: { result: [{ id: 1, name: "Desk" }] }, version: "L01" }); await feature.onCommandResult("app_get_programs_summary", "app_get_programs_summary", { data: { result: [ { program_id: 9, program_type: 1, name: "My Routine" }, { program_id: 1, program_type: 2, name: "Official A" } ] }, version: "L01" }); await feature.onCommandResult("app_get_program_runtime", "app_get_program_runtime", { data: { result: { program_state: 2, result: [{ cmd_id: "arm_move", value: [1, 0, 2, 1], result: -2 }] } }, version: "L01" }); await feature.onCommandResult("app_get_program_soundlist", "app_get_program_soundlist", { data: { result: [{ id: 6, cmd_id: "sound", value: "code_mode_process_9" }] }, version: "L01" }); await feature.onCommandResult("get_homesec_connect_status", "get_homesec_connect_status", { data: { result: { client_id: "client-123" } }, version: "L01" }, []); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.tidyUp.summaryJSON", { val: '{"tidyup_count":4,"tidyup_time":1200}', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.tidyUp.count", { val: 4, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.tidyUp.timeSeconds", { val: 1200, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.tidyUp.zonesJSON", { val: '[{"id":1,"name":"Desk"}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.tidyUp.zoneCount", { val: 1, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.summaryJSON", { val: '[{"program_id":9,"program_type":1,"name":"My Routine"},{"program_id":1,"program_type":2,"name":"Official A"}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.officialProgramsJSON", { val: '[{"program_id":1,"program_type":2,"name":"Official A","programTypeLabel":"Official Recommendation"}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.myProgramsJSON", { val: '[{"program_id":9,"program_type":1,"name":"My Routine","programTypeLabel":"My Program"}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.detailsCount", { val: 2, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.runtime.programState", { val: 2, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.runtime.lastResult", { val: -2, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.program.soundListJSON", { val: '[{"id":6,"cmd_id":"sound","value":"code_mode_process_9"}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.homeSecurity.connectJSON", { val: '{"client_id":"client-123"}', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.deviceStatus.homeSecClientId", { val: "client-123", ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.deviceStatus.isHomeSecControlledByCurrentClient", { val: true, ack: true }); }); it("stores compact manual getter payloads for newly exposed Z70 app wrappers", async () => { const feature = new A179Features(depsMock, "duid1"); await feature.onCommandResult("app_get_robot_setting", "app_get_robot_setting", { data: { result: [{ name: "custom_mode", status: 1 }] }, version: "L01" }, { name: "custom_mode" }); await feature.onCommandResult("app_get_clean_estimate_info", "app_get_clean_estimate_info", { data: { result: { clean_time: 900, clean_area: 12000000 } }, version: "L01" }, {}); await feature.onCommandResult("app_get_dryer_setting", "app_get_dryer_setting", { data: { result: { status: 1, on: { dry_time: 1800 } } }, version: "L01" }, []); await feature.onCommandResult("app_get_wifi_list", "app_get_wifi_list", { data: { result: [{ ssid: "PixelPalace", rssi: -48 }] }, version: "L01" }, {}); await feature.onCommandResult("app_get_segment_clean_subdivision", "app_get_segment_clean_subdivision", { data: { result: [{ segment_id: 2, mode: 3 }] }, version: "L01" }, { segment_id: 2 }); await feature.onCommandResult("app_get_zoned_clean_subdivision", "app_get_zoned_clean_subdivision", { data: { result: [{ zone_id: 1, mode: 2 }] }, version: "L01" }, { zones: [[1, 2, 3, 4]] }); await feature.onCommandResult("get_dock_info", "get_dock_info", { data: { result: { cool_fan: 1, dock_type: 16 } }, version: "L01" }, {}); await feature.onCommandResult("get_child_lock_status", "get_child_lock_status", { data: { result: { lock_status: 1 } }, version: "L01" }, []); await feature.onCommandResult("get_collision_avoid_status", "get_collision_avoid_status", { data: { result: { status: 1 } }, version: "L01" }, []); await feature.onCommandResult("get_flow_led_status", "get_flow_led_status", { data: { result: { status: 1 } }, version: "L01" }, []); await feature.onCommandResult("test_get_enable_wakeup", "test_get_enable_wakeup", { data: { result: { enable_wakeup: 1 } }, version: "L01" }, []); await feature.onCommandResult("test_get_voice_keep_seconds", "test_get_voice_keep_seconds", { data: { result: { keep_seconds: 18 } }, version: "L01" }, []); await feature.onCommandResult("get_map_beautification_status", "get_map_beautification_status", { data: { result: { status: 1 } }, version: "L01" }, []); await feature.onCommandResult("get_voice_history_summary", "get_voice_history_summary", { data: { result: { total: 5, latest_id: 77 } }, version: "L01" }, []); await feature.onCommandResult("get_voice_history", "get_voice_history", { data: { result: { list: [{ id: 77, text: "Hello" }] } }, version: "L01" }, { max_id: 77 }); await feature.onCommandResult("test_get_voice_list", "test_get_voice_list", { data: { result: [{ id: 9, name: "Welcome" }] }, version: "L01" }, []); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.deviceStatus.robotSettingQueryJSON", { val: '[{"name":"custom_mode","status":1}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.deviceStatus.robotSettingQueryCount", { val: 1, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.cleaningInfo.estimateJSON", { val: '{"clean_time":900,"clean_area":12000000}', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.cleaningInfo.estimateTimeMinutes", { val: 15, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.cleaningInfo.estimateArea", { val: 12, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.dryerJSON", { val: '{"status":1,"on":{"dry_time":1800}}', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.dryerStatus", { val: 1, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.dryerEnabled", { val: true, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.dryerTimeSeconds", { val: 1800, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.networkInfo.wifiListJSON", { val: '[{"ssid":"PixelPalace","rssi":-48}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.networkInfo.wifiListCount", { val: 1, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.cleaningInfo.segmentSubdivisionJSON", { val: '[{"segment_id":2,"mode":3}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.cleaningInfo.segmentSubdivisionCount", { val: 1, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.cleaningInfo.zoneSubdivisionJSON", { val: '[{"zone_id":1,"mode":2}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.cleaningInfo.zoneSubdivisionCount", { val: 1, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.dockInfoJSON", { val: '{"cool_fan":1,"dock_type":16}', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.deviceStatus.childLockStatus", { val: 1, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.deviceStatus.childLockEnabled", { val: true, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.deviceStatus.collisionAvoidStatus", { val: 1, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.deviceStatus.flowLedEnabled", { val: true, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.homeSecurity.voiceWakeupEnabled", { val: true, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.homeSecurity.voiceKeepSeconds", { val: 18, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.cleaningInfo.mapBeautificationEnabled", { val: true, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.homeSecurity.voiceHistorySummaryJSON", { val: '{"total":5,"latest_id":77}', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.homeSecurity.voiceHistoryJSON", { val: '{"list":[{"id":77,"text":"Hello"}]}', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.homeSecurity.voiceHistoryCount", { val: 1, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.homeSecurity.voiceListJSON", { val: '[{"id":9,"name":"Welcome"}]', ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.homeSecurity.voiceListCount", { val: 1, ack: true }); }); it("stores manual smart wash, wash towel, and binary getter results through shared handlers", async () => { requestsHandlerMock.sendRequest.mockImplementation(async (_duid: string, method: string) => { if (method === "get_fw_features") { return { data: [41, 107, 109], version: "L01" }; } return {}; }); const feature = new A179Features(depsMock, "duid1"); await feature.onCommandResult("get_smart_wash_params", "get_smart_wash_params", { data: { smart_wash: 2, wash_interval: 1500 }, version: "L01" }, {}); await feature.onCommandResult("get_wash_towel_params", "get_wash_towel_params", { data: { interval: 12, status: 1, mode: 8 }, version: "L01" }, []); await feature.onCommandResult("get_wash_towel_mode", "get_wash_towel_mode", { data: { wash_mode: 8 }, version: "L01" }, {}); await feature.onCommandResult("get_wash_water_temperature", "get_wash_water_temperature", { data: { wash_water_temperature: 2 }, version: "L01" }, {}); await feature.onCommandResult("get_auto_delivery_cleaning_fluid", "get_auto_delivery_cleaning_fluid", { data: { result: { status: 1 } }, version: "L01" }, {}); await feature.onCommandResult("get_ap_mic_led_status", "get_ap_mic_led_status", { data: { result: { status: 1 } }, version: "L01" }, {}); await feature.onCommandResult("get_voice_service_switch", "get_voice_service_switch", { data: { result: { data: 1 } }, version: "L01" }, []); await feature.onCommandResult("get_handle_leak_water_status", "get_handle_leak_water_status", { data: { result: { status: 1 } }, version: "L01" }, {}); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.smartWash.smartWash", { val: 2, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.smartWash.washIntervalMinutes", { val: 25, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.smartWash.backWashMode", { val: 2, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.washTowel.hotWashTowelSupported", { val: true, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.washTowel.soakAndWashSupported", { val: true, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.washTowel.interval", { val: 12, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.washTowel.paramMode", { val: 8, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.washTowel.mode", { val: 8, ack: true }); expect(adapterMock.setStateChanged).toHaveBeenCalledWith("Devices.duid1.dockingStationStatus.washT