From 427fb214ea4a998cc70d044fce3c971bd53c8f8f Mon Sep 17 00:00:00 2001 From: Donsidro Date: Tue, 14 Jul 2026 13:15:17 +0200 Subject: [PATCH 1/7] trying to implement iso paring --- .claude/roborock_robot_calls_report.md | 182 +++++++++++++++ PRIVACY.md | 8 +- README.md | 6 +- .../VacuumPairingTransport.android.kt | 2 + .../onboarding/VacuumPairingTransport.kt | 3 + .../localrock/ui/devices/DeviceListScreen.kt | 7 +- .../localrock/ui/settings/SettingsScreen.kt | 16 +- .../kodraliu/localrock/MainViewController.kt | 8 + .../onboarding/VacuumPairingTransport.ios.kt | 109 ++++++++- docs/index.html | 6 +- iosApp/iosApp.xcodeproj/project.pbxproj | 2 + iosApp/iosApp/ContentView.swift | 5 +- iosApp/iosApp/Info.plist | 2 + iosApp/iosApp/VacuumPairing.swift | 216 ++++++++++++++++++ iosApp/iosApp/iosApp.entitlements | 8 + 15 files changed, 547 insertions(+), 33 deletions(-) create mode 100644 .claude/roborock_robot_calls_report.md create mode 100644 iosApp/iosApp/VacuumPairing.swift create mode 100644 iosApp/iosApp/iosApp.entitlements diff --git a/.claude/roborock_robot_calls_report.md b/.claude/roborock_robot_calls_report.md new file mode 100644 index 0000000..a7e3fb0 --- /dev/null +++ b/.claude/roborock_robot_calls_report.md @@ -0,0 +1,182 @@ +# Robot command inventory — python-roborock-main + +Repository scanned: `roborock/` source tree (tests excluded for usage counts). + +## 1. V1 raw command catalog (253 commands) + +These are string RPC commands accepted by `device.v1_properties.command.send(command, params)`. Availability and parameter formats depend on robot model and firmware. + +### App / robot actions (50) + +`app_amethyst_self_check` · `app_charge` · `app_delete_wifi` · `app_get_amethyst_status` · `app_get_carpet_deep_clean_status` · `app_get_clean_estimate_info` + +`app_get_dryer_setting` · `app_get_init_status` · `app_get_locale` · `app_get_wifi_list` · `app_goto_target` · `app_keep_easter_egg` + +`app_pause` · `app_rc_end` · `app_rc_move` · `app_rc_start` · `app_rc_stop` · `app_resume_build_map` + +`app_resume_patrol` · `app_segment_clean` · `app_set_amethyst_status` · `app_set_carpet_deep_clean_status` · `app_set_cross_carpet_cleaning_status` · `app_set_door_sill_blocks` + +`app_set_dirty_replenish_clean_status` · `app_set_dryer_setting` · `app_set_dryer_status` · `app_set_dynamic_config` · `app_set_ignore_stuck_point` · `app_set_smart_cliff_forbidden` + +`app_set_smart_door_sill` · `app_spot` · `app_start` · `app_start_build_map` · `app_start_collect_dust` · `app_start_easter_egg` + +`app_start_patrol` · `app_start_pet_patrol` · `app_start_wash` · `app_stat` · `app_stop` · `app_stop_collect_dust` + +`app_stop_wash` · `app_update_unsave_map` · `app_wakeup_robot` · `app_zoned_clean` · `app_empty_rinse_tank_water` · `app_ignore_dirty_objects` + +`app_set_robot_setting` · `app_get_robot_setting` + +### Read / query (79) + +`get_auto_delivery_cleaning_fluid` · `get_camera_status` · `get_carpet_clean_mode` · `get_carpet_mode` · `get_child_lock_status` · `get_clean_follow_ground_material_status` + +`get_clean_motor_mode` · `get_clean_record` · `get_clean_record_map` · `get_clean_sequence` · `get_clean_summary` · `get_collision_avoid_status` + +`get_consumable` · `get_current_sound` · `get_custom_mode` · `get_customize_clean_mode` · `get_device_ice` · `get_device_sdp` + +`get_dnd_timer` · `get_dock_info` · `get_dust_collection_mode` · `get_dust_collection_switch_status` · `get_dynamic_data` · `get_dynamic_map_diff` + +`get_fan_motor_work_timeout` · `get_flow_led_status` · `get_fresh_map` · `get_fw_features` · `get_homesec_connect_status` · `get_identify_furniture_status` + +`get_identify_ground_material_status` · `get_led_status` · `get_log_upload_status` · `get_map` · `get_map_beautification_status` · `get_map_status` + +`get_map_v1` · `get_map_v2` · `get_map_calibration` · `get_mop_motor_status` · `get_mop_template_params_by_id` · `get_mop_template_params_summary` + +`get_multi_map` · `get_multi_maps_list` · `get_network_info` · `get_offline_map_status` · `get_persist_map` · `get_prop` + +`get_random_pkey` · `get_recover_map` · `get_recover_maps` · `get_room_mapping` · `get_scenes_valid_tids` · `get_segment_status` + +`get_serial_number` · `get_server_timer` · `get_smart_wash_params` · `get_sound_progress` · `get_sound_volume` · `get_status` + +`get_testid` · `get_timer` · `get_timer_detail` · `get_timer_summary` · `get_timezone` · `get_turn_server` + +`get_valley_electricity_timer` · `get_wash_debug_params` · `get_wash_towel_mode` · `get_wash_towel_params` · `get_water_box_custom_mode` · `get_stretch_tag_status` + +`get_right_brush_stretch_status` · `get_dirty_object_detect_status` · `get_wash_water_temperature` · `get_pet_supplies_deep_clean_status` · `get_ap_mic_led_status` · `get_handle_leak_water_status` + +`get_gap_deep_clean_status` + +### Configuration (57) + +`set_airdry_hours` · `set_app_timezone` · `set_auto_delivery_cleaning_fluid` · `set_camera_status` · `set_carpet_area` · `set_carpet_clean_mode` + +`set_carpet_mode` · `set_child_lock_status` · `set_clean_follow_ground_material_status` · `set_clean_motor_mode` · `set_clean_sequence` · `set_clean_repeat_times` + +`set_collision_avoid_status` · `set_custom_mode` · `set_customize_clean_mode` · `set_dnd_timer` · `set_dnd_timer_actions` · `set_dust_collection_mode` + +`set_dust_collection_switch_status` · `set_fan_motor_work_timeout` · `set_fds_endpoint` · `set_flow_led_status` · `set_homesec_password` · `set_identify_furniture_status` + +`set_identify_ground_material_status` · `set_ignore_carpet_zone` · `set_ignore_identify_area` · `set_lab_status` · `set_led_status` · `set_map_beautification_status` + +`set_mop_mode` · `set_mop_motor_status` · `set_mop_template_id` · `set_offline_map_status` · `set_scenes_segments` · `set_scenes_zones` + +`set_segment_ground_material` · `set_server_timer` · `set_smart_wash_params` · `set_switch_map_mode` · `set_timer` · `set_timezone` + +`set_valley_electricity_timer` · `set_voice_chat_volume` · `set_wash_debug_params` · `set_wash_towel_mode` · `set_wash_towel_params` · `set_water_box_custom_mode` + +`set_water_box_distance_off` · `set_stretch_tag_status` · `set_right_brush_stretch_status` · `set_dirty_object_detect_status` · `set_wash_water_temperature` · `set_pet_supplies_deep_clean_status` + +`set_ap_mic_led_status` · `set_handle_leak_water_status` · `set_gap_deep_clean_status` + +### Start actions (5) + +`start_camera_preview` · `start_clean` · `start_edit_map` · `start_voice_chat` · `start_wash_then_charge` + +### Stop actions (6) + +`stop_camera_preview` · `stop_fan_motor_work` · `stop_goto_target` · `stop_segment_clean` · `stop_voice_chat` · `stop_zoned_clean` + +### Delete (6) + +`del_clean_record` · `del_clean_record_map_v2` · `del_map` · `del_mop_template_params` · `del_server_timer` · `del_timer` + +### Other / map / service (50) + +`add_mop_template_params` · `change_sound_volume` · `check_homesec_password` · `close_dnd_timer` · `close_valley_electricity_timer` · `dnld_install_sound` + +`enable_homesec_voice` · `enable_log_upload` · `end_edit_map` · `find_me` · `load_multi_map` · `manual_bak_map` + +`manual_segment_map` · `merge_segment` · `mop_mode` · `mop_template_id` · `name_multi_map` · `name_segment` + +`play_audio` · `recover_map` · `recover_multi_map` · `reset_consumable` · `reset_homesec_password` · `reset_map` + +`resolve_error` · `resume_segment_clean` · `resume_zoned_clean` · `retry_request` · `reunion_scenes` · `save_furnitures` + +`save_map` · `send_ice_to_robot` · `send_sdp_to_robot` · `sort_mop_template_params` · `split_segment` · `switch_video_quality` + +`switch_water_mark` · `test_sound_volume` · `upd_server_timer` · `upd_timer` · `update_dock` · `update_mop_template_params` + +`upload_data_for_debug_mode` · `upload_photo` · `use_new_map` · `use_old_map` · `user_upload_log` · `matter.get_status` + +`matter.dnld_key` · `matter.reset` + +## 2. V1 commands explicitly wired into source traits (32) + +- `app_get_init_status` — `roborock/cli.py:1172`, `roborock/devices/traits/v1/device_features.py:34` +- `app_start_wash` — `roborock/devices/traits/v1/wash_towel_mode.py:45` +- `app_stop_wash` — `roborock/devices/traits/v1/wash_towel_mode.py:49` +- `change_sound_volume` — `roborock/devices/traits/v1/volume.py:24` +- `close_dnd_timer` — `roborock/devices/traits/v1/do_not_disturb.py:26`, `roborock/devices/traits/v1/do_not_disturb.py:40` +- `close_valley_electricity_timer` — `roborock/devices/traits/v1/valley_electricity_timer.py:27`, `roborock/devices/traits/v1/valley_electricity_timer.py:42` +- `get_child_lock_status` — `roborock/devices/traits/v1/child_lock.py:11` +- `get_clean_record` — `roborock/devices/traits/v1/clean_summary.py:90`, `roborock/devices/traits/v1/clean_summary.py:96` +- `get_clean_summary` — `roborock/devices/traits/v1/clean_summary.py:71` +- `get_consumable` — `roborock/devices/traits/v1/consumeable.py:52` +- `get_dnd_timer` — `roborock/devices/traits/v1/do_not_disturb.py:11` +- `get_dust_collection_mode` — `roborock/devices/traits/v1/dust_collection_mode.py:12` +- `get_flow_led_status` — `roborock/devices/traits/v1/flow_led_status.py:11` +- `get_led_status` — `roborock/devices/traits/v1/led_status.py:28` +- `get_map_v1` — `roborock/devices/traits/v1/home.py:42`, `roborock/devices/traits/v1/map_content.py:86` +- `get_multi_maps_list` — `roborock/devices/traits/v1/maps.py:53` +- `get_network_info` — `roborock/devices/rpc/v1_channel.py:390`, `roborock/devices/traits/v1/network_info.py:33` +- `get_room_mapping` — `roborock/devices/traits/v1/rooms.py:84` +- `get_smart_wash_params` — `roborock/devices/traits/v1/smart_wash_params.py:12` +- `get_sound_volume` — `roborock/devices/traits/v1/volume.py:19` +- `get_status` — `roborock/devices/traits/v1/status.py:52` +- `get_valley_electricity_timer` — `roborock/devices/traits/v1/valley_electricity_timer.py:11` +- `get_wash_towel_mode` — `roborock/devices/traits/v1/wash_towel_mode.py:16` +- `load_multi_map` — `roborock/devices/traits/v1/maps.py:82` +- `reset_consumable` — `roborock/devices/traits/v1/consumeable.py:62` +- `set_child_lock_status` — `roborock/devices/traits/v1/child_lock.py:22`, `roborock/devices/traits/v1/child_lock.py:28` +- `set_dnd_timer` — `roborock/devices/traits/v1/do_not_disturb.py:21`, `roborock/devices/traits/v1/do_not_disturb.py:32` +- `set_flow_led_status` — `roborock/devices/traits/v1/flow_led_status.py:22`, `roborock/devices/traits/v1/flow_led_status.py:28` +- `set_led_status` — `roborock/devices/traits/v1/led_status.py:39`, `roborock/devices/traits/v1/led_status.py:45` +- `set_valley_electricity_timer` — `roborock/devices/traits/v1/valley_electricity_timer.py:22`, `roborock/devices/traits/v1/valley_electricity_timer.py:33` +- `set_wash_towel_mode` — `roborock/devices/traits/v1/wash_towel_mode.py:41` + +## 3. B01/Q7 named method catalog + +60 known Q7 method strings are declared. The user-facing action wrappers include `service.set_room_clean`, `service.start_recharge`, and `service.find_device`; details are in `roborock/devices/traits/b01/q7/__init__.py`. + +`add_clean_failed.post` · `event.add_clean_failed.post` · `clean_finish.post` · `event.clean_finish.post` · `event.BuildMapFinish.post` · `event.map_change.post` + +`event.work_appoint_clean_failed.post` · `startClean.post` · `service.add_order` · `service.add_sweep_clean` · `service.arrange_room` · `service.del_map` + +`service.del_order` · `service.del_orders` · `service.delete_record_by_url` · `service.download_voice_type` · `service.erase_preference` · `service.find_device` + +`service.get_room_order` · `service.get_voice_download` · `service.hello_wikka` · `service.rename_map` · `service.rename_room` · `service.rename_rooms` + +`service.replace_map` · `service.reset_consumable` · `service.save_carpet` · `service.save_recommend_fb` · `service.save_sill` · `service.set_area_start` + +`service.set_areas_start` · `service.set_cur_map` · `service.set_direction` · `service.set_global_sort` · `service.set_map_hide` · `service.set_multi_room_material` + +`service.set_point_clean` · `service.set_preference` · `service.set_preference_type` · `service.set_quiet_time` · `service.set_room_clean` · `service.set_room_order` + +`service.set_virtual_wall` · `service.set_zone_clean` · `service.set_zone_points` · `service.split_room` · `service.start_explore` · `service.start_point_clean` + +`service.start_recharge` · `service.stop_recharge` · `service.upload_by_mapid` · `service.upload_record_by_url` · `prop.get` · `service.get_map_list` + +`service.upload_by_maptype` · `prop.set` · `service.get_preference` · `service.get_record_list` · `service.get_order` · `prop.post` + +## 4. B01/Q10 direct robot actions + +- Start whole-home cleaning: DP `201`, parameter `1` +- Start room/segment cleaning: DP `201`, parameter `{"cmd": 2, "clean_paramters": [room IDs]}` +- Spot clean: DP `201`, parameter `5` +- Pause: DP `204`, parameter `0` +- Resume: DP `205`, parameter `0` +- Stop: DP `206`, parameter `0` +- Return to dock/charge: DP `202`, parameter `5` +- Empty dustbin: DP `203`, parameter `2` +- Set cleaning mode: DP `137` (enum-mapped) +- Set fan level: DP `123` (enum-mapped) diff --git a/PRIVACY.md b/PRIVACY.md index b1a9e70..3ca9203 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -52,9 +52,9 @@ and with the message broker (MQTT) that **your server** designates. This is used The developer does **not** receive any of this traffic. The app contacts no developer-operated or analytics server. -The only other outbound connections are **links you tap yourself**: an optional -link to the open-source project page (GitHub) and an optional donation link. -These open in your browser and are only visited if you choose to tap them. +The only other outbound connections are **links you tap yourself**: optional links +to the open-source project page (GitHub) and this privacy policy. These open in +your browser and are only visited if you choose to tap them. ## 4. Camera / live view @@ -70,6 +70,8 @@ The app requests only the permissions it needs to function: - **Internet / network state** — to reach the server you configure. - **Wi-Fi state / nearby Wi-Fi devices** — to pair a new vacuum with your Wi-Fi network during setup. +- **Local network (iOS)** — to reach your server and to exchange the setup + handshake with the robot's own access point while pairing it. - **Location (Android 12 and below only)** — required by older versions of Android to scan for Wi-Fi networks during vacuum pairing. The app does not use or collect your location, and this permission is not requested on Android 13+. diff --git a/README.md b/README.md index 22f2b0d..347e650 100644 --- a/README.md +++ b/README.md @@ -221,11 +221,13 @@ tests/ # protocol/contract fixtures & tests | MQTT over TLS | Yes (KMQTT) | Yes (CocoaMQTT bridge) | | Camera live view (WebRTC) | Implemented, **untested** | Implemented (WebRTC.framework bridge), **untested** | | RSA (onboarding crypto) | Yes | Yes | -| Phone Wi‑Fi pairing (Add vacuum) | Yes | Not yet implemented | +| Phone Wi‑Fi pairing (Add vacuum) | Yes | Implemented (NEHotspotConfiguration + NWConnection), **untested** | **Camera live view note:** the WebRTC live feed is implemented from the protocol reference but has **not been tested or confirmed to work** on real hardware. Treat it as experimental. -**iOS note:** Kotlin/Native iOS targets must be built on macOS. The iOS MQTT and WebRTC integrations rely on Swift Package Manager dependencies ([CocoaMQTT](https://github.com/emqx/CocoaMQTT), [WebRTC](https://github.com/stasel/WebRTC)) that must be added to the `iosApp` Xcode target, and the Swift bridge files (`CocoaMqttTransport.swift`, `WebRtcPeer.swift`) added to the target. Pass `nil` for the WebRTC factory to ship without live view. iOS uses a custom bundle identifier that needs signing/provisioning selected in Xcode. +**iOS note:** Kotlin/Native iOS targets must be built on macOS. The iOS MQTT and WebRTC integrations rely on Swift Package Manager dependencies ([CocoaMQTT](https://github.com/emqx/CocoaMQTT), [WebRTC](https://github.com/stasel/WebRTC)) that must be added to the `iosApp` Xcode target, and the Swift bridge files (`CocoaMqttTransport.swift`, `WebRtcPeer.swift`, `VacuumPairing.swift`) added to the target. Pass `nil` for the WebRTC factory to ship without live view, or `nil` for the pairing factory to hide the "add vacuum" flow. iOS uses a custom bundle identifier that needs signing/provisioning selected in Xcode. + +**iOS pairing entitlement:** joining the robot's access point uses `NEHotspotConfiguration`, which needs the **Hotspot Configuration** capability. It is declared in `iosApp/iosApp.entitlements` and wired into the target, but it only signs once the capability is enabled for the App ID in the Apple Developer portal (Certificates, Identifiers & Profiles → your App ID → Hotspot Configuration). Without it the app fails to sign; with it, the UDP handshake also triggers the local-network prompt backed by `NSLocalNetworkUsageDescription`. --- diff --git a/composeApp/src/androidMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.android.kt b/composeApp/src/androidMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.android.kt index 88f53f0..45ec086 100644 --- a/composeApp/src/androidMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.android.kt +++ b/composeApp/src/androidMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.android.kt @@ -119,3 +119,5 @@ actual fun createVacuumPairingTransport(): VacuumPairingTransport { ?: throw VacuumPairingException("Android context not initialized; call initVacLocalAndroidContext first") return VacuumPairingTransport(ctx) } + +actual val vacuumPairingSupported: Boolean = true diff --git a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.kt b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.kt index 53befff..45702e3 100644 --- a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.kt +++ b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.kt @@ -16,5 +16,8 @@ class VacuumPairingException(message: String, cause: Throwable? = null) : Runtim expect fun createVacuumPairingTransport(): VacuumPairingTransport +/** False when the platform cannot pair over Wi-Fi, so the UI can hide the flow instead of failing. */ +expect val vacuumPairingSupported: Boolean + const val VACUUM_PAIRING_HOST: String = "192.168.8.1" const val VACUUM_PAIRING_PORT: Int = 55559 diff --git a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/devices/DeviceListScreen.kt b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/devices/DeviceListScreen.kt index 159ef6a..8c1786c 100644 --- a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/devices/DeviceListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/devices/DeviceListScreen.kt @@ -50,6 +50,7 @@ import androidx.compose.ui.unit.dp import com.kodraliu.localrock.resources.Res import com.kodraliu.localrock.resources.app_icon import com.kodraliu.localrock.shared.model.Device +import com.kodraliu.localrock.shared.onboarding.vacuumPairingSupported import com.kodraliu.localrock.ui.AppColors import org.jetbrains.compose.resources.painterResource @@ -91,8 +92,10 @@ fun DeviceListScreen( ) }, floatingActionButton = { - FloatingActionButton(onClick = onAddVacuum) { - Icon(Icons.Default.Add, contentDescription = "Add vacuum") + if (vacuumPairingSupported) { + FloatingActionButton(onClick = onAddVacuum) { + Icon(Icons.Default.Add, contentDescription = "Add vacuum") + } } }, ) { padding -> diff --git a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/settings/SettingsScreen.kt index cc8e54a..1f2840d 100644 --- a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/settings/SettingsScreen.kt @@ -43,8 +43,6 @@ import com.kodraliu.localrock.ui.LocalAppContainer const val APP_VERSION: String = "1.0.0" -private const val DONATE_URL = "https://buymeacoffee.com/sidon" - private const val PROJECT_URL = "https://github.com/DonSidro/LocalRock/" private const val PRIVACY_URL = "https://donsidro.github.io/LocalRock/" @@ -138,20 +136,20 @@ fun SettingsScreen(onDone: () -> Unit, allowCancel: Boolean) { HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp)) - SectionLabel("Support") + SectionLabel("Contribute") Text( - "LocalRock is a free, community project. If it's useful to you, a small donation " + - "helps keep it maintained.", + "LocalRock is a free, open-source community project. Issue reports, ideas, and " + + "pull requests are all welcome.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) Button( - onClick = { uriHandler.openUri(DONATE_URL) }, + onClick = { uriHandler.openUri(PROJECT_URL) }, modifier = Modifier.fillMaxWidth(), ) { Icon(Icons.Default.Favorite, contentDescription = null) Spacer(Modifier.width(8.dp)) - Text("Donate") + Text("Contribute on GitHub") } HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp)) @@ -167,10 +165,6 @@ fun SettingsScreen(onDone: () -> Unit, allowCancel: Boolean) { style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) - OutlinedButton( - onClick = { uriHandler.openUri(PROJECT_URL) }, - modifier = Modifier.fillMaxWidth(), - ) { Text("Project page") } OutlinedButton( onClick = { uriHandler.openUri(PRIVACY_URL) }, modifier = Modifier.fillMaxWidth(), diff --git a/composeApp/src/iosMain/kotlin/com/kodraliu/localrock/MainViewController.kt b/composeApp/src/iosMain/kotlin/com/kodraliu/localrock/MainViewController.kt index f6bae51..3a30c13 100644 --- a/composeApp/src/iosMain/kotlin/com/kodraliu/localrock/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/com/kodraliu/localrock/MainViewController.kt @@ -4,6 +4,8 @@ import androidx.compose.ui.window.ComposeUIViewController import com.russhwolf.settings.NSUserDefaultsSettings import com.kodraliu.localrock.shared.AppContainer import com.kodraliu.localrock.shared.mqtt.MqttNativeTransport +import com.kodraliu.localrock.shared.onboarding.IosPairing +import com.kodraliu.localrock.shared.onboarding.VacuumPairingNativeFactory import com.kodraliu.localrock.shared.webrtc.IosRtc import com.kodraliu.localrock.shared.webrtc.RtcNativePeerFactory import platform.Foundation.NSUserDefaults @@ -15,12 +17,18 @@ import platform.UIKit.UIViewController * - [rtcPeerFactory]: a WebRTC.framework-backed peer factory (see `WebRtcPeer.swift`). Pass `null` * to disable the camera live view; [com.kodraliu.localrock.shared.webrtc.liveViewSupported] * reflects whether it was provided. + * - [pairingFactory]: a NetworkExtension/Network.framework pairing session factory (see + * `VacuumPairing.swift`). Pass `null` to hide the "add vacuum" flow; + * [com.kodraliu.localrock.shared.onboarding.vacuumPairingSupported] reflects whether it was + * provided. Requires the Hotspot Configuration entitlement on the app target. */ fun MainViewController( mqttTransport: MqttNativeTransport, rtcPeerFactory: RtcNativePeerFactory?, + pairingFactory: VacuumPairingNativeFactory?, ): UIViewController { IosRtc.factory = rtcPeerFactory + IosPairing.factory = pairingFactory val defaults = NSUserDefaults(suiteName = "vaclocal") ?: NSUserDefaults.standardUserDefaults val container = AppContainer(NSUserDefaultsSettings(defaults), mqttTransport) return ComposeUIViewController { App(container) } diff --git a/composeApp/src/iosMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.ios.kt b/composeApp/src/iosMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.ios.kt index d4ab690..b2c8d58 100644 --- a/composeApp/src/iosMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/kodraliu/localrock/shared/onboarding/VacuumPairingTransport.ios.kt @@ -1,17 +1,102 @@ package com.kodraliu.localrock.shared.onboarding -actual class VacuumPairingTransport internal constructor() : AutoCloseable { - actual suspend fun joinVacuumWifi(ssidPrefix: String, timeoutMs: Long) { - throw NotImplementedError("VacuumPairingTransport is not yet implemented on iOS") - } - actual suspend fun sendUdp(host: String, port: Int, data: ByteArray) { - throw NotImplementedError("VacuumPairingTransport is not yet implemented on iOS") - } - actual suspend fun receiveUdp(timeoutMs: Long): ByteArray? { - throw NotImplementedError("VacuumPairingTransport is not yet implemented on iOS") +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +/** + * iOS Wi-Fi pairing lives on the Swift side: joining the vacuum's access point needs + * `NEHotspotConfiguration` (NetworkExtension) and the UDP handshake needs `NWConnection` + * (Network.framework), neither of which is reachable from Kotlin/Native. This mirrors the MQTT and + * WebRTC approach: the Swift app implements the callback-based, non-suspend [VacuumPairingNative] + * and injects a factory via `MainViewController`. + * + * See `iosApp/iosApp/VacuumPairing.swift`. + */ + +/** One pairing session, implemented in Swift. Non-suspend so Swift can implement it. */ +interface VacuumPairingNative { + fun joinWifi( + ssidPrefix: String, + timeoutMs: Long, + onJoined: () -> Unit, + onError: (String) -> Unit, + ) + + fun send( + host: String, + port: Int, + data: ByteArray, + onSent: () -> Unit, + onError: (String) -> Unit, + ) + + /** Calls back with null when nothing arrived within [timeoutMs]. */ + fun receive( + timeoutMs: Long, + onReceived: (ByteArray?) -> Unit, + onError: (String) -> Unit, + ) + + /** Tears down the socket and puts the phone back on its normal Wi-Fi network. */ + fun close() +} + +interface VacuumPairingNativeFactory { + fun create(): VacuumPairingNative +} + +/** Holds the Swift-injected factory; set once from `MainViewController(pairingFactory:)`. */ +object IosPairing { + var factory: VacuumPairingNativeFactory? = null +} + +actual val vacuumPairingSupported: Boolean + get() = IosPairing.factory != null + +actual class VacuumPairingTransport internal constructor( + private val native: VacuumPairingNative, +) : AutoCloseable { + + actual suspend fun joinVacuumWifi(ssidPrefix: String, timeoutMs: Long): Unit = + suspendCancellableCoroutine { cont -> + native.joinWifi( + ssidPrefix = ssidPrefix, + timeoutMs = timeoutMs, + onJoined = { cont.resume(Unit) }, + onError = { cont.resumeWithException(VacuumPairingException(it)) }, + ) + } + + actual suspend fun sendUdp(host: String, port: Int, data: ByteArray): Unit = + suspendCancellableCoroutine { cont -> + native.send( + host = host, + port = port, + data = data, + onSent = { cont.resume(Unit) }, + onError = { cont.resumeWithException(VacuumPairingException(it)) }, + ) + } + + actual suspend fun receiveUdp(timeoutMs: Long): ByteArray? = + suspendCancellableCoroutine { cont -> + native.receive( + timeoutMs = timeoutMs, + onReceived = { bytes -> cont.resume(bytes) }, + onError = { cont.resumeWithException(VacuumPairingException(it)) }, + ) + } + + actual override fun close() { + native.close() } - actual override fun close() { /* nothing to clean up on the stub */ } } -actual fun createVacuumPairingTransport(): VacuumPairingTransport = - throw NotImplementedError("Vacuum onboarding is not yet implemented on iOS") +actual fun createVacuumPairingTransport(): VacuumPairingTransport { + val factory = IosPairing.factory + ?: throw VacuumPairingException( + "Wi-Fi pairing is unavailable — no pairing factory was injected into MainViewController" + ) + return VacuumPairingTransport(factory.create()) +} diff --git a/docs/index.html b/docs/index.html index 0aeccf9..4b308cf 100644 --- a/docs/index.html +++ b/docs/index.html @@ -102,8 +102,8 @@

3. Data transmitted, and to whom

The developer does not receive any of this traffic. The app contacts no developer-operated or analytics server.

-

The only other outbound connections are links you tap yourself: an optional - link to the open-source project page (GitHub) and an optional donation link. These open in your +

The only other outbound connections are links you tap yourself: optional links + to the open-source project page (GitHub) and this privacy policy. These open in your browser and are only visited if you choose to tap them.

4. Camera / live view

@@ -117,6 +117,8 @@

5. Permissions

  • Internet / network state — to reach the server you configure.
  • Wi-Fi state / nearby Wi-Fi devices — to pair a new vacuum with your Wi-Fi network during setup.
  • +
  • Local network (iOS) — to reach your server and to exchange the setup + handshake with the robot's own access point while pairing it.
  • Location (Android 12 and below only) — required by older versions of Android to scan for Wi-Fi networks during vacuum pairing. The app does not use or collect your location, and this permission is not requested on Android 13+.
  • diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 03811bd..4ef2166 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -190,6 +190,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; @@ -224,6 +225,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 3f5737d..1fa9b29 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -7,9 +7,12 @@ struct ComposeView: UIViewControllerRepresentable { // Inject the Swift-backed platform pieces into the shared Kotlin code: // - CocoaMQTT transport for MQTT // - WebRTC.framework peer factory for the camera live view (pass nil to disable it) + // - NetworkExtension/Network.framework session factory for Wi-Fi vacuum pairing + // (pass nil to hide the "add vacuum" flow) MainViewControllerKt.MainViewController( mqttTransport: CocoaMqttTransport(), - rtcPeerFactory: WebRtcNativeFactory() + rtcPeerFactory: WebRtcNativeFactory(), + pairingFactory: VacuumPairingFactory() ) } diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist index eafc13f..9524ced 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -4,6 +4,8 @@ CADisableMinimumFrameDurationOnPhone + NSLocalNetworkUsageDescription + LocalRock talks directly to your robot vacuum over your local network: to your own self-hosted server, and to the robot's own Wi-Fi access point while you set it up. Nothing is sent to the developer. NSCameraUsageDescription LocalRock displays the live video feed from your robot vacuum's built-in camera. It does not access your device's own camera. NSMicrophoneUsageDescription diff --git a/iosApp/iosApp/VacuumPairing.swift b/iosApp/iosApp/VacuumPairing.swift new file mode 100644 index 0000000..a5b35cd --- /dev/null +++ b/iosApp/iosApp/VacuumPairing.swift @@ -0,0 +1,216 @@ +import Foundation +import Network +import NetworkExtension +import ComposeApp + +// iOS Wi-Fi pairing for LocalRock, implementing the Kotlin `VacuumPairingNative` contract. +// +// Pairing works like this: the vacuum, when unprovisioned, exposes an open access point named +// `roborock-vacuum-`. The phone joins that AP, exchanges a UDP handshake with the robot +// at 192.168.8.1:55559 (hello -> RSA-wrapped session key -> Wi-Fi config -> ack), and then leaves. +// +// Two Apple APIs are needed, neither of which Kotlin/Native can reach: +// - NEHotspotConfiguration (NetworkExtension) to join the vacuum's AP. This requires the Hotspot +// Configuration entitlement on the app target (`iosApp.entitlements`). +// - NWConnection (Network.framework) for the UDP exchange. Talking to a private LAN address makes +// iOS show the local-network prompt (NSLocalNetworkUsageDescription in Info.plist). +// +// The shared Kotlin `VacuumOnboarder` drives the protocol and the step ordering; this class is just +// the platform plumbing: join, send, receive, clean up. + +final class VacuumPairingFactory: VacuumPairingNativeFactory { + func create() -> VacuumPairingNative { VacuumPairingSession() } +} + +final class VacuumPairingSession: VacuumPairingNative { + + private let queue = DispatchQueue(label: "com.kodraliu.localrock.pairing") + private var connection: NWConnection? + private var joinedSsid: String? + + // MARK: Joining the vacuum's access point + + func joinWifi( + ssidPrefix: String, + timeoutMs: Int64, + onJoined: @escaping () -> Void, + onError: @escaping (String) -> Void + ) { + // The join and the timeout race each other; whichever lands first wins. + let done = CallbackGuard() + + let config = NEHotspotConfiguration(ssidPrefix: ssidPrefix) + // Don't leave the vacuum's AP in the user's known-networks list after pairing. + config.joinOnce = true + + queue.asyncAfter(deadline: .now() + .milliseconds(Int(timeoutMs))) { + guard done.claim() else { return } + onError("Timed out waiting to join the vacuum's Wi-Fi network (\(ssidPrefix)…). " + + "Make sure the robot is in pairing mode and close to the phone.") + } + + NEHotspotConfigurationManager.shared.apply(config) { error in + if let error = error as NSError?, + error.code != NEHotspotConfigurationError.alreadyAssociated.rawValue { + guard done.claim() else { return } + onError(Self.describe(error, ssidPrefix: ssidPrefix)) + return + } + // `apply` reports success as soon as the association is requested, so confirm which + // network we actually landed on before sending anything. + NEHotspotNetwork.fetchCurrent { network in + guard done.claim() else { return } + guard let network, network.ssid.hasPrefix(ssidPrefix) else { + onError("The phone is not on the vacuum's access point " + + "(expected an SSID starting with \(ssidPrefix)).") + return + } + self.joinedSsid = network.ssid + onJoined() + } + } + } + + // MARK: UDP handshake + + func send( + host: String, + port: Int32, + data: KotlinByteArray, + onSent: @escaping () -> Void, + onError: @escaping (String) -> Void + ) { + let payload = Data(data.toUInt8()) + withReadyConnection(host: host, port: port, onError: onError) { connection in + connection.send(content: payload, completion: .contentProcessed { error in + if let error { + onError("UDP send failed: \(error.localizedDescription)") + } else { + onSent() + } + }) + } + } + + func receive( + timeoutMs: Int64, + onReceived: @escaping (KotlinByteArray?) -> Void, + onError: @escaping (String) -> Void + ) { + guard let connection else { + onError("Cannot receive before sending: no UDP socket is open.") + return + } + + // The robot answers to the source port of our datagram, so the same NWConnection is reused + // for the whole exchange rather than opened per message. + let done = CallbackGuard() + + queue.asyncAfter(deadline: .now() + .milliseconds(Int(timeoutMs))) { + guard done.claim() else { return } + onReceived(nil) // A timeout is a normal outcome here, not an error. + } + + connection.receiveMessage { data, _, _, error in + guard done.claim() else { return } + if let error { + onError("UDP receive failed: \(error.localizedDescription)") + return + } + guard let data, !data.isEmpty else { + onReceived(nil) + return + } + onReceived(KotlinByteArray.from([UInt8](data))) + } + } + + func close() { + connection?.cancel() + connection = nil + // Put the phone back on its normal network instead of stranding it on the robot's AP. + if let ssid = joinedSsid { + NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid) + joinedSsid = nil + } + } + + // MARK: Helpers + + /// Opens the UDP socket on first use and calls `body` once it is ready to carry traffic. + private func withReadyConnection( + host: String, + port: Int32, + onError: @escaping (String) -> Void, + body: @escaping (NWConnection) -> Void + ) { + if let connection, connection.state == .ready { + body(connection) + return + } + + guard let nwPort = NWEndpoint.Port(rawValue: UInt16(clamping: port)) else { + onError("Invalid pairing port: \(port)") + return + } + + let parameters = NWParameters.udp + // The vacuum's AP has no internet, so pin the socket to Wi-Fi and stop iOS from quietly + // routing these datagrams over cellular. + parameters.requiredInterfaceType = .wifi + parameters.prohibitExpensivePaths = false + + let connection = NWConnection( + host: NWEndpoint.Host(host), + port: nwPort, + using: parameters + ) + self.connection = connection + + let done = CallbackGuard() + connection.stateUpdateHandler = { state in + switch state { + case .ready: + guard done.claim() else { return } + body(connection) + case .failed(let error): + guard done.claim() else { return } + onError("UDP socket failed: \(error.localizedDescription)") + case .cancelled: + guard done.claim() else { return } + onError("UDP socket was cancelled.") + default: + break + } + } + connection.start(queue: queue) + } + + private static func describe(_ error: NSError, ssidPrefix: String) -> String { + switch NEHotspotConfigurationError(rawValue: error.code) { + case .userDenied: + return "Joining the vacuum's Wi-Fi network was declined." + case .invalidSSIDPrefix: + return "iOS rejected the vacuum SSID prefix \(ssidPrefix)." + case .pending, .systemConfiguration, .unknown, .joinOnceNotSupported: + return "iOS could not join the vacuum's Wi-Fi network: \(error.localizedDescription)" + default: + return "Could not join the vacuum's Wi-Fi network: \(error.localizedDescription)" + } + } +} + +/// Both the network callback and its timeout can fire; the first one through wins and the loser is +/// dropped, so the Kotlin continuation is never resumed twice. +private final class CallbackGuard { + private let lock = NSLock() + private var used = false + + func claim() -> Bool { + lock.lock() + defer { lock.unlock() } + if used { return false } + used = true + return true + } +} diff --git a/iosApp/iosApp/iosApp.entitlements b/iosApp/iosApp/iosApp.entitlements new file mode 100644 index 0000000..7026276 --- /dev/null +++ b/iosApp/iosApp/iosApp.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.networking.HotspotConfiguration + + + From 4b9ce3c751a8b3a8c7d827b36f673b010ea5e9fc Mon Sep 17 00:00:00 2001 From: Donsidro Date: Tue, 14 Jul 2026 13:40:22 +0200 Subject: [PATCH 2/7] trying to implement iso paring --- iosApp/iosApp/VacuumPairing.swift | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/iosApp/iosApp/VacuumPairing.swift b/iosApp/iosApp/VacuumPairing.swift index a5b35cd..da6184f 100644 --- a/iosApp/iosApp/VacuumPairing.swift +++ b/iosApp/iosApp/VacuumPairing.swift @@ -27,6 +27,7 @@ final class VacuumPairingSession: VacuumPairingNative { private let queue = DispatchQueue(label: "com.kodraliu.localrock.pairing") private var connection: NWConnection? private var joinedSsid: String? + private var joinedPrefix: String? // MARK: Joining the vacuum's access point @@ -43,6 +44,11 @@ final class VacuumPairingSession: VacuumPairingNative { // Don't leave the vacuum's AP in the user's known-networks list after pairing. config.joinOnce = true + // Remember the prefix even if the join later fails: iOS may already have associated, and + // `close()` has to be able to undo that or the phone is stranded on an AP with no route to + // the user's server. + joinedPrefix = ssidPrefix + queue.asyncAfter(deadline: .now() + .milliseconds(Int(timeoutMs))) { guard done.claim() else { return } onError("Timed out waiting to join the vacuum's Wi-Fi network (\(ssidPrefix)…). " + @@ -128,11 +134,28 @@ final class VacuumPairingSession: VacuumPairingNative { func close() { connection?.cancel() connection = nil - // Put the phone back on its normal network instead of stranding it on the robot's AP. - if let ssid = joinedSsid { - NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid) - joinedSsid = nil + + // Put the phone back on its normal network. A prefix-joined configuration is registered + // under the *prefix*, not under the SSID the phone actually landed on, so removing only the + // resolved SSID is a silent no-op that strands the phone on the robot's AP — with no DNS and + // no route to the user's server. Remove every configuration this session could have created, + // and let iOS's own list be the source of truth. + let manager = NEHotspotConfigurationManager.shared + let ssid = joinedSsid + let prefix = joinedPrefix + + if let prefix { manager.removeConfiguration(forSSID: prefix) } + if let ssid { manager.removeConfiguration(forSSID: ssid) } + manager.getConfiguredSSIDs { configured in + for entry in configured { + if entry == ssid || entry == prefix || (prefix.map { entry.hasPrefix($0) } ?? false) { + manager.removeConfiguration(forSSID: entry) + } + } } + + joinedSsid = nil + joinedPrefix = nil } // MARK: Helpers From 69ddb18a16ddd2fed06283b54ede93275c139f74 Mon Sep 17 00:00:00 2001 From: Donsidro Date: Tue, 14 Jul 2026 13:50:04 +0200 Subject: [PATCH 3/7] trying to implement iso paring --- iosApp/iosApp/VacuumPairing.swift | 45 +++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/iosApp/iosApp/VacuumPairing.swift b/iosApp/iosApp/VacuumPairing.swift index da6184f..5f3a931 100644 --- a/iosApp/iosApp/VacuumPairing.swift +++ b/iosApp/iosApp/VacuumPairing.swift @@ -62,21 +62,50 @@ final class VacuumPairingSession: VacuumPairingNative { onError(Self.describe(error, ssidPrefix: ssidPrefix)) return } - // `apply` reports success as soon as the association is requested, so confirm which - // network we actually landed on before sending anything. - NEHotspotNetwork.fetchCurrent { network in + // `apply` reports success as soon as iOS accepts the join, not once Wi-Fi has finished + // associating, so the current network needs to be sampled until it settles rather than + // read once. + self.confirmJoined(ssidPrefix: ssidPrefix, done: done, onJoined: onJoined) + } + } + + /// Polls the current network until it is the vacuum's AP. `fetchCurrent` is unreliable right + /// after a join — it can report the previous network, or nothing at all — so a single mismatched + /// sample is not treated as failure. If it never confirms, the handshake proceeds anyway and the + /// UDP hello timeout becomes the real verdict: a false "you're not on the vacuum's network" is + /// worse than a clean handshake timeout, because the former blocks a pairing that would work. + private func confirmJoined( + ssidPrefix: String, + done: CallbackGuard, + attempt: Int = 0, + onJoined: @escaping () -> Void + ) { + NEHotspotNetwork.fetchCurrent { network in + if let network, network.ssid.hasPrefix(ssidPrefix) { guard done.claim() else { return } - guard let network, network.ssid.hasPrefix(ssidPrefix) else { - onError("The phone is not on the vacuum's access point " + - "(expected an SSID starting with \(ssidPrefix)).") - return - } self.joinedSsid = network.ssid onJoined() + return + } + guard attempt < Self.joinConfirmAttempts else { + guard done.claim() else { return } + onJoined() // Unconfirmed, but let the handshake decide. + return + } + self.queue.asyncAfter(deadline: .now() + .milliseconds(Self.joinConfirmIntervalMs)) { + self.confirmJoined( + ssidPrefix: ssidPrefix, + done: done, + attempt: attempt + 1, + onJoined: onJoined + ) } } } + private static let joinConfirmAttempts = 20 // ~10s of association grace + private static let joinConfirmIntervalMs = 500 + // MARK: UDP handshake func send( From 58bed49fc6d857b9f1bb9c2836c7c39e76df5dcd Mon Sep 17 00:00:00 2001 From: Donsidro Date: Wed, 15 Jul 2026 17:21:19 +0200 Subject: [PATCH 4/7] fixing apple app store problems --- .claude/Screenshot 2026-07-14 at 13.22.14.png | Bin 0 -> 94255 bytes .../localrock/shared/demo/DemoData.kt | 8 ++++---- .../localrock/ui/intro/SplashScreen.kt | 2 +- .../localrock/ui/intro/WelcomeScreen.kt | 2 +- docs/screenshots/localrock-icon-1a.svg | 7 +++++++ iosApp/iosApp/VacuumPairing.swift | 18 ++++++++++++++++++ 6 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 .claude/Screenshot 2026-07-14 at 13.22.14.png create mode 100644 docs/screenshots/localrock-icon-1a.svg diff --git a/.claude/Screenshot 2026-07-14 at 13.22.14.png b/.claude/Screenshot 2026-07-14 at 13.22.14.png new file mode 100644 index 0000000000000000000000000000000000000000..491ae366e43afb8f3e0eebbdb9aad33de7938685 GIT binary patch literal 94255 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 N4))H+8;=E~{4d>|f~^1m literal 0 HcmV?d00001 diff --git a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/shared/demo/DemoData.kt b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/shared/demo/DemoData.kt index bc66c45..256d190 100644 --- a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/shared/demo/DemoData.kt +++ b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/shared/demo/DemoData.kt @@ -53,14 +53,14 @@ object DemoData { private val products: List = listOf( Product( id = "demo-prod-s8", - name = "S8 Pro Ultra", - model = "roborock.vacuum.a70", + name = "Robot Vacuum", + model = "Vacuum", category = "robot.vacuum.cleaner", ), Product( id = "demo-prod-q7", - name = "Q7 Max+", - model = "roborock.vacuum.a38", + name = "Robot Vacuum", + model = "Vacuum", category = "robot.vacuum.cleaner", ), ) diff --git a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/SplashScreen.kt b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/SplashScreen.kt index 944337a..7bd12b3 100644 --- a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/SplashScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/SplashScreen.kt @@ -36,7 +36,7 @@ fun SplashScreen(onTimeout: () -> Unit) { color = MaterialTheme.colorScheme.primary, ) Text( - "Local control for Roborock", + "Local control for your robot vacuum", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, diff --git a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/WelcomeScreen.kt b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/WelcomeScreen.kt index ddec6c1..8188029 100644 --- a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/WelcomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/WelcomeScreen.kt @@ -45,7 +45,7 @@ fun WelcomeScreen() { fontWeight = FontWeight.Bold, ) Text( - "Control your Roborock vacuum over your own network — please read this first.", + "Control your robot vacuum over your own network — please read this first.", style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/docs/screenshots/localrock-icon-1a.svg b/docs/screenshots/localrock-icon-1a.svg new file mode 100644 index 0000000..712e436 --- /dev/null +++ b/docs/screenshots/localrock-icon-1a.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/iosApp/iosApp/VacuumPairing.swift b/iosApp/iosApp/VacuumPairing.swift index 5f3a931..92fbe61 100644 --- a/iosApp/iosApp/VacuumPairing.swift +++ b/iosApp/iosApp/VacuumPairing.swift @@ -19,7 +19,25 @@ import ComposeApp // the platform plumbing: join, send, receive, clean up. final class VacuumPairingFactory: VacuumPairingNativeFactory { + + /// Drops any hotspot configuration left behind by an earlier session. A pairing attempt that + /// died without tearing down leaves the phone on the robot's AP, where the server is + /// unreachable — and since pairing talks to the server *before* it joins the robot, a stale + /// configuration would sink the next attempt during login, with a DNS error that says nothing + /// about Wi-Fi. Cheap to run at launch, so run it at launch. + init() { + let manager = NEHotspotConfigurationManager.shared + manager.getConfiguredSSIDs { configured in + for ssid in configured where ssid.hasPrefix(Self.vacuumSsidPrefix) { + manager.removeConfiguration(forSSID: ssid) + } + } + } + func create() -> VacuumPairingNative { VacuumPairingSession() } + + // Matches the default in the shared Kotlin `VacuumPairingTransport.joinVacuumWifi`. + private static let vacuumSsidPrefix = "roborock-vacuum-" } final class VacuumPairingSession: VacuumPairingNative { From a9d3f4364b5193437d9c80cc9e52c775ca7052d4 Mon Sep 17 00:00:00 2001 From: Donsidro Date: Wed, 15 Jul 2026 17:35:40 +0200 Subject: [PATCH 5/7] fixing apple app store problems --- .../kotlin/com/kodraliu/localrock/ui/intro/WelcomeScreen.kt | 2 +- .../com/kodraliu/localrock/ui/settings/SettingsScreen.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/WelcomeScreen.kt b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/WelcomeScreen.kt index 8188029..814318e 100644 --- a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/WelcomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/intro/WelcomeScreen.kt @@ -60,7 +60,7 @@ fun WelcomeScreen() { "2", "Sign in with your server credentials", "You'll sign in with the email and login code you configured during server setup — " + - "not a new Roborock account.", + "not a new cloud account.", ) Point( "3", diff --git a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/settings/SettingsScreen.kt index 1f2840d..ca25c13 100644 --- a/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/kodraliu/localrock/ui/settings/SettingsScreen.kt @@ -105,7 +105,7 @@ fun SettingsScreen(onDone: () -> Unit, allowCancel: Boolean) { value = url, onValueChange = { url = it }, label = { Text("Server URL") }, - placeholder = { Text("https://api-roborock.example.com") }, + placeholder = { Text("https://api-test.example.com") }, singleLine = true, modifier = Modifier.fillMaxWidth(), ) @@ -160,7 +160,7 @@ fun SettingsScreen(onDone: () -> Unit, allowCancel: Boolean) { style = MaterialTheme.typography.bodyMedium, ) Text( - "Control Roborock vacuums locally through your own server. Not affiliated with, " + + "Control vacuums locally through your own server. Not affiliated with, " + "endorsed by, or connected to Roborock. \"Roborock\" is a trademark of its owner.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -213,5 +213,5 @@ private const val ACKNOWLEDGEMENTS = "• multiplatform-settings (Apache-2.0) — Russell Wolf\n" + "• KotlinCrypto hash & macs (Apache-2.0)\n" + "• Okio (Apache-2.0) — Square\n\n" + - "And to the Roborock reverse-engineering community — especially the python-roborock " + + "And to the reverse-engineering community — especially the python-roborock " + "project — whose protocol work made local control possible." From 7d6ce67d6f84657bc7363dbcba4fac4010f65a02 Mon Sep 17 00:00:00 2001 From: Sidon Kodraliu Date: Thu, 16 Jul 2026 18:36:34 +0200 Subject: [PATCH 6/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22f2b0d..e20e4c9 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ LocalRock is a [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform. - + Download on the App Store From 0c3207813d96ac59e29be59915ff25e7147b73ad Mon Sep 17 00:00:00 2001 From: Sidon Kodraliu Date: Thu, 16 Jul 2026 18:44:14 +0200 Subject: [PATCH 7/7] Update Wi-Fi pairing status for vacuum integration --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e20e4c9..4c216eb 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ tests/ # protocol/contract fixtures & tests | MQTT over TLS | Yes (KMQTT) | Yes (CocoaMQTT bridge) | | Camera live view (WebRTC) | Implemented, **untested** | Implemented (WebRTC.framework bridge), **untested** | | RSA (onboarding crypto) | Yes | Yes | -| Phone Wi‑Fi pairing (Add vacuum) | Yes | Not yet implemented | +| Phone Wi‑Fi pairing (Add vacuum) | Yes | Yes | **Camera live view note:** the WebRTC live feed is implemented from the protocol reference but has **not been tested or confirmed to work** on real hardware. Treat it as experimental.