From ffdb1b607f87745ceb45c108228886ea4bf98497 Mon Sep 17 00:00:00 2001 From: Nitwel Date: Sun, 17 Mar 2024 17:05:45 +0100 Subject: [PATCH] add onboarding flow --- app/content/main.tscn | 6 +- app/content/system/assist/assist.gd | 26 +++++--- app/content/system/camera/camera.gd | 6 +- app/content/ui/console.gd | 4 +- app/content/ui/onboarding/onboarding.gd | 43 +++++++++++++ app/content/ui/onboarding/onboarding.tscn | 68 ++++++++++++++++++++ app/lib/globals/home_api.gd | 28 ++++++-- app/lib/globals/main_store.gd | 6 +- app/lib/home_apis/hass_ws/handlers/assist.gd | 12 +--- app/lib/home_apis/hass_ws/hass.gd | 6 +- app/lib/home_apis/voice_handler.gd | 11 ++++ app/lib/stores/settings.gd | 6 +- app/lib/stores/store.gd | 5 +- 13 files changed, 193 insertions(+), 34 deletions(-) create mode 100644 app/content/ui/onboarding/onboarding.gd create mode 100644 app/content/ui/onboarding/onboarding.tscn create mode 100644 app/lib/home_apis/voice_handler.gd diff --git a/app/content/main.tscn b/app/content/main.tscn index 421b116..8ca3ce1 100644 --- a/app/content/main.tscn +++ b/app/content/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=16 format=3 uid="uid://eecv28y6jxk4"] +[gd_scene load_steps=17 format=3 uid="uid://eecv28y6jxk4"] [ext_resource type="PackedScene" uid="uid://clc5dre31iskm" path="res://addons/godot-xr-tools/xr/start_xr.tscn" id="1_i4c04"] [ext_resource type="Script" path="res://content/main.gd" id="1_uvrd4"] @@ -11,6 +11,7 @@ [ext_resource type="PackedScene" uid="uid://c3kdssrmv84kv" path="res://content/ui/menu/menu.tscn" id="8_du83w"] [ext_resource type="PackedScene" uid="uid://lrehk38exd5n" path="res://content/system/keyboard/keyboard.tscn" id="9_e5n3p"] [ext_resource type="PackedScene" uid="uid://cbemihbxkd4ll" path="res://content/system/house/house.tscn" id="9_np6mw"] +[ext_resource type="PackedScene" uid="uid://bhyddd1f0ry1x" path="res://content/ui/onboarding/onboarding.tscn" id="12_uq2nj"] [sub_resource type="Sky" id="Sky_vhymk"] sky_material = ExtResource("5_wgwf8") @@ -83,4 +84,7 @@ transform = Transform3D(0.499999, -0.000139169, -6.50204e-05, 5.24307e-05, 0.353 [node name="House" parent="." instance=ExtResource("9_np6mw")] +[node name="Onboarding" parent="." instance=ExtResource("12_uq2nj")] +transform = Transform3D(0.999999, -1.39632e-11, 0, 9.48097e-12, 0.999999, 0, 0, 0, 0.999999, -0.529594, 0.820154, -0.600147) + [editable path="XROrigin3D/XRControllerLeft"] diff --git a/app/content/system/assist/assist.gd b/app/content/system/assist/assist.gd index ce3143a..f2aa43f 100644 --- a/app/content/system/assist/assist.gd +++ b/app/content/system/assist/assist.gd @@ -1,5 +1,6 @@ extends Node3D +const VoiceAssistant = preload ("res://lib/home_apis/voice_handler.gd") const sample_hold = preload ("res://lib/utils/sample_hold.gd") const Chat = preload ("./chat.gd") @@ -19,18 +20,27 @@ var effect: AudioEffectCapture @onready var camera = $"/root/Main/XROrigin3D/XRCamera3D" var running := false +var voice_assistant: VoiceAssistant func _ready(): var index = AudioServer.get_bus_index("Record") effect = AudioServer.get_bus_effect(index, 0) + if !HomeApi.has_connected(): + await HomeApi.on_connect + + voice_assistant = HomeApi.get_voice_assistant() + + if voice_assistant == null: + return + finish() audio_timer.timeout.connect(func(): - HomeApi.api.assist_handler.send_data(PackedByteArray()) + voice_assistant.send_data(PackedByteArray()) ) - HomeApi.api.assist_handler.on_wake_word.connect(func(text): + voice_assistant.on_wake_word.connect(func(_text): loader.visible=true chat_user.visible=false chat_assistant.visible=false @@ -40,24 +50,24 @@ func _ready(): running=true ) - HomeApi.api.assist_handler.on_stt_message.connect(func(text): + voice_assistant.on_stt_message.connect(func(text): loader.visible=false chat_user.visible=true chat_user.text=text ) - HomeApi.api.assist_handler.on_tts_message.connect(func(text): + voice_assistant.on_tts_message.connect(func(text): chat_assistant.visible=true chat_assistant.text=text ) - HomeApi.api.assist_handler.on_tts_sound.connect(func(audio): + voice_assistant.on_tts_sound.connect(func(audio): audio_player_3d.stream=audio audio_player_3d.play() visual_timer.start() running=false ) - HomeApi.api.assist_handler.on_error.connect(func(): + voice_assistant.on_error.connect(func(): running=false finish() ) @@ -101,9 +111,9 @@ func _process(_delta): if max_amplitude > input_threshold: if audio_timer.is_stopped(): - HomeApi.api.assist_handler.start_wakeword() + voice_assistant.start_wakeword() audio_timer.start() if audio_timer.is_stopped() == false: - HomeApi.api.assist_handler.send_data(data) + voice_assistant.send_data(data) diff --git a/app/content/system/camera/camera.gd b/app/content/system/camera/camera.gd index a6b7e01..486d514 100644 --- a/app/content/system/camera/camera.gd +++ b/app/content/system/camera/camera.gd @@ -3,7 +3,7 @@ extends XRCamera3D var last_room = null func _physics_process(_delta): - if HomeApi.api.has_integration(): + if HomeApi.has_integration(): update_room() func update_room(): @@ -11,8 +11,8 @@ func update_room(): if room != last_room: if room: - HomeApi.api.update_room(room.name) + HomeApi.update_room(room.name) last_room = room else: - HomeApi.api.update_room("outside") + HomeApi.update_room("outside") last_room = null \ No newline at end of file diff --git a/app/content/ui/console.gd b/app/content/ui/console.gd index ab59980..9f9c6c7 100644 --- a/app/content/ui/console.gd +++ b/app/content/ui/console.gd @@ -5,8 +5,8 @@ extends StaticBody3D var max_message = 30 var messages: Array = ["aaa", "bbb"] -func log(text: String) -> void: - messages.push_front(text) +func log(text: Variant) -> void: + messages.push_front(str(text)) if messages.size() > max_message: messages.pop_back() diff --git a/app/content/ui/onboarding/onboarding.gd b/app/content/ui/onboarding/onboarding.gd new file mode 100644 index 0000000..b30bf4d --- /dev/null +++ b/app/content/ui/onboarding/onboarding.gd @@ -0,0 +1,43 @@ +extends Node3D + +@onready var getting_started_button = $GettingStartedButton +@onready var close_button = $CloseButton +@onready var camera = $"/root/Main/XROrigin3D/XRCamera3D" +var next_new_position = global_position + +func _ready(): + if Store.settings.is_loaded() == false: + await Store.settings.on_loaded + + if (Store.settings.url != ""&&Store.settings.url != null)||Store.settings.onboarding_complete: + close() + return + + getting_started_button.on_button_down.connect(func(): + OS.shell_open("https://docs.immersive-home.org/") + ) + + close_button.on_button_down.connect(func(): + close() + ) + + EventSystem.on_slow_tick.connect(_slow_tick) + +func close(): + Store.settings.onboarding_complete = true + Store.settings.save_local() + queue_free() + +func _slow_tick(delta): + var new_position = camera.global_position + camera.global_transform.basis.z * - 0.5 + + if next_new_position.distance_to(new_position) > 0.2: + next_new_position = new_position + var new_direction = Basis.looking_at((camera.global_position - new_position) * - 1) + + var tween = create_tween() + tween.set_parallel(true) + tween.set_trans(Tween.TransitionType.TRANS_QUAD) + + tween.tween_property(self, "global_position", new_position, 0.6) + tween.tween_property(self, "global_transform:basis", new_direction, 0.6) diff --git a/app/content/ui/onboarding/onboarding.tscn b/app/content/ui/onboarding/onboarding.tscn new file mode 100644 index 0000000..5dcbd29 --- /dev/null +++ b/app/content/ui/onboarding/onboarding.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=6 format=3 uid="uid://bhyddd1f0ry1x"] + +[ext_resource type="Script" path="res://content/ui/onboarding/onboarding.gd" id="1_k4yvw"] +[ext_resource type="Material" uid="uid://bujy3egn1oqac" path="res://assets/materials/pri-500.material" id="2_aleti"] +[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="3_hlpow"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_nfwtf"] +size = Vector3(0.5, 0.3, 0.01) + +[sub_resource type="BoxMesh" id="BoxMesh_yknqs"] +size = Vector3(0.5, 0.3, 0.01) + +[node name="Onboarding" type="StaticBody3D"] +script = ExtResource("1_k4yvw") + +[node name="Label3D2" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.23, -0.1, 0.006) +pixel_size = 0.001 +text = "Getting Started" +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 +vertical_alignment = 0 +autowrap_mode = 3 +width = 470.0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("BoxShape3D_nfwtf") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +material_override = ExtResource("2_aleti") +mesh = SubResource("BoxMesh_yknqs") + +[node name="GettingStartedLabel" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.23, 0.14, 0.006) +pixel_size = 0.001 +text = "Hey! + +This app is still early in development and still has a lot of rough edges. Things might break or are sometimes difficult to use, this will improve the more the app ages. + +If this is the first time you try the app, please first read through the \"Getting Started\" Guide below." +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 +vertical_alignment = 0 +autowrap_mode = 3 +width = 470.0 + +[node name="GettingStartedButton" parent="." instance=ExtResource("3_hlpow")] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, -0.05, -0.11, 0) +label = "open_in_new" +icon = true + +[node name="CloseButton" parent="." instance=ExtResource("3_hlpow")] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0.21, -0.11, 0) +label = "done" +icon = true + +[node name="CloseLabel" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, -0.1, 0.006) +pixel_size = 0.001 +text = "Understood" +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 +vertical_alignment = 0 +autowrap_mode = 3 +width = 470.0 diff --git a/app/lib/globals/home_api.gd b/app/lib/globals/home_api.gd index 97b2b0a..1b37a7a 100644 --- a/app/lib/globals/home_api.gd +++ b/app/lib/globals/home_api.gd @@ -3,6 +3,7 @@ extends Node const Hass = preload ("res://lib/home_apis/hass/hass.gd") const HassWebSocket = preload ("res://lib/home_apis/hass_ws/hass.gd") +const VoiceAssistant = preload ("res://lib/home_apis/voice_handler.gd") const apis = { "hass": Hass, @@ -96,7 +97,26 @@ func watch_state(entity: String, callback: Callable): assert(has_connected(), "Not connected") return api.watch_state(entity, callback) -func _notification(what): - if what == NOTIFICATION_WM_CLOSE_REQUEST||what == NOTIFICATION_WM_GO_BACK_REQUEST: - # Store.house.save_local() - pass +## Returns true if the adapter has an integration in the home automation system +## allowing to send the room position of the headset. +func has_integration() -> bool: + if has_connected() == false||api.has_method("has_integration") == false: + return false + + return api.has_integration() + +## Updates the room position of the headset in the home automation system +func update_room(room: String) -> void: + if has_connected() == false||api.has_method("update_room") == false: + return + + api.update_room(room) + +## Returns the VoiceHandler if the adapter has a voice assistant +func get_voice_assistant() -> VoiceAssistant: + assert(has_connected(), "Not connected") + + if api.has_method("get_voice_assistant") == false: + return null + + return api.get_voice_assistant() \ No newline at end of file diff --git a/app/lib/globals/main_store.gd b/app/lib/globals/main_store.gd index eebfc97..daedd9e 100644 --- a/app/lib/globals/main_store.gd +++ b/app/lib/globals/main_store.gd @@ -5,6 +5,6 @@ const SettingsStore = preload ("res://lib/stores/settings.gd") const HouseStore = preload ("res://lib/stores/house.gd") const DevicesStore = preload ("res://lib/stores/devices.gd") -var settings = SettingsStore.new() -var house = HouseStore.new() -var devices = DevicesStore.new() +var settings := SettingsStore.new() +var house := HouseStore.new() +var devices := DevicesStore.new() diff --git a/app/lib/home_apis/hass_ws/handlers/assist.gd b/app/lib/home_apis/hass_ws/handlers/assist.gd index dde4eba..0efbfcb 100644 --- a/app/lib/home_apis/hass_ws/handlers/assist.gd +++ b/app/lib/home_apis/hass_ws/handlers/assist.gd @@ -1,10 +1,7 @@ -const HASS_API = preload ("../hass.gd") +extends VoiceHandler -signal on_wake_word(wake_word: String) -signal on_stt_message(message: String) -signal on_tts_message(message: String) -signal on_tts_sound(sound: AudioStreamMP3) -signal on_error() +const HASS_API = preload ("../hass.gd") +const VoiceHandler = preload ("res://lib/home_apis/voice_handler.gd") var api: HASS_API var pipe_running := false @@ -36,9 +33,6 @@ var tts_sound = null: func _init(hass: HASS_API): self.api = hass -func on_connect(): - pass - func start_wakeword(): if pipe_running: return diff --git a/app/lib/home_apis/hass_ws/hass.gd b/app/lib/home_apis/hass_ws/hass.gd index 91907e9..675010f 100644 --- a/app/lib/home_apis/hass_ws/hass.gd +++ b/app/lib/home_apis/hass_ws/hass.gd @@ -121,7 +121,6 @@ func start_subscriptions(): func handle_connect(): integration_handler.on_connect() - assist_handler.on_connect() connected = true on_connect.emit() @@ -282,4 +281,7 @@ func update_room(room: String): }) if response.status == Promise.Status.RESOLVED: - pass \ No newline at end of file + pass + +func get_voice_assistant(): + return assist_handler diff --git a/app/lib/home_apis/voice_handler.gd b/app/lib/home_apis/voice_handler.gd new file mode 100644 index 0000000..ec999ac --- /dev/null +++ b/app/lib/home_apis/voice_handler.gd @@ -0,0 +1,11 @@ +signal on_wake_word(wake_word: String) +signal on_stt_message(message: String) +signal on_tts_message(message: String) +signal on_tts_sound(sound: AudioStreamMP3) +signal on_error() + +func start_wakeword() -> bool: + return false + +func send_data(data: PackedByteArray) -> void: + pass \ No newline at end of file diff --git a/app/lib/stores/settings.gd b/app/lib/stores/settings.gd index 3c39ec0..84d50fd 100644 --- a/app/lib/stores/settings.gd +++ b/app/lib/stores/settings.gd @@ -11,6 +11,9 @@ var token: String = "" ## If the voice assistant should be enabled var voice_assistant: bool = false +## If the onboarding process has been completed +var onboarding_complete: bool = false + func _init(): _save_path = "user://settings.json" @@ -18,4 +21,5 @@ func clear(): type = "HASS_WS" url = "" token = "" - voice_assistant = false \ No newline at end of file + voice_assistant = false + onboarding_complete = false \ No newline at end of file diff --git a/app/lib/stores/store.gd b/app/lib/stores/store.gd index 0392f15..fb058b3 100644 --- a/app/lib/stores/store.gd +++ b/app/lib/stores/store.gd @@ -77,8 +77,11 @@ func load_local(path=_save_path): var save_file = FileAccess.open(path, FileAccess.READ) + # In case that there is no store file yet if save_file == null: - return false + _loaded = true + on_loaded.emit() + return true var json_text = save_file.get_as_text() var save_data = VariantSerializer.parse_value(JSON.parse_string(json_text))