add onboarding flow

This commit is contained in:
Nitwel 2024-03-17 17:05:45 +01:00
parent bd5b630c04
commit ffdb1b607f
13 changed files with 193 additions and 34 deletions

View File

@ -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="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"] [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://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://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://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"] [sub_resource type="Sky" id="Sky_vhymk"]
sky_material = ExtResource("5_wgwf8") 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="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"] [editable path="XROrigin3D/XRControllerLeft"]

View File

@ -1,5 +1,6 @@
extends Node3D extends Node3D
const VoiceAssistant = preload ("res://lib/home_apis/voice_handler.gd")
const sample_hold = preload ("res://lib/utils/sample_hold.gd") const sample_hold = preload ("res://lib/utils/sample_hold.gd")
const Chat = preload ("./chat.gd") const Chat = preload ("./chat.gd")
@ -19,18 +20,27 @@ var effect: AudioEffectCapture
@onready var camera = $"/root/Main/XROrigin3D/XRCamera3D" @onready var camera = $"/root/Main/XROrigin3D/XRCamera3D"
var running := false var running := false
var voice_assistant: VoiceAssistant
func _ready(): func _ready():
var index = AudioServer.get_bus_index("Record") var index = AudioServer.get_bus_index("Record")
effect = AudioServer.get_bus_effect(index, 0) 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() finish()
audio_timer.timeout.connect(func(): 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 loader.visible=true
chat_user.visible=false chat_user.visible=false
chat_assistant.visible=false chat_assistant.visible=false
@ -40,24 +50,24 @@ func _ready():
running=true running=true
) )
HomeApi.api.assist_handler.on_stt_message.connect(func(text): voice_assistant.on_stt_message.connect(func(text):
loader.visible=false loader.visible=false
chat_user.visible=true chat_user.visible=true
chat_user.text=text 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.visible=true
chat_assistant.text=text 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.stream=audio
audio_player_3d.play() audio_player_3d.play()
visual_timer.start() visual_timer.start()
running=false running=false
) )
HomeApi.api.assist_handler.on_error.connect(func(): voice_assistant.on_error.connect(func():
running=false running=false
finish() finish()
) )
@ -101,9 +111,9 @@ func _process(_delta):
if max_amplitude > input_threshold: if max_amplitude > input_threshold:
if audio_timer.is_stopped(): if audio_timer.is_stopped():
HomeApi.api.assist_handler.start_wakeword() voice_assistant.start_wakeword()
audio_timer.start() audio_timer.start()
if audio_timer.is_stopped() == false: if audio_timer.is_stopped() == false:
HomeApi.api.assist_handler.send_data(data) voice_assistant.send_data(data)

View File

@ -3,7 +3,7 @@ extends XRCamera3D
var last_room = null var last_room = null
func _physics_process(_delta): func _physics_process(_delta):
if HomeApi.api.has_integration(): if HomeApi.has_integration():
update_room() update_room()
func update_room(): func update_room():
@ -11,8 +11,8 @@ func update_room():
if room != last_room: if room != last_room:
if room: if room:
HomeApi.api.update_room(room.name) HomeApi.update_room(room.name)
last_room = room last_room = room
else: else:
HomeApi.api.update_room("outside") HomeApi.update_room("outside")
last_room = null last_room = null

View File

@ -5,8 +5,8 @@ extends StaticBody3D
var max_message = 30 var max_message = 30
var messages: Array = ["aaa", "bbb"] var messages: Array = ["aaa", "bbb"]
func log(text: String) -> void: func log(text: Variant) -> void:
messages.push_front(text) messages.push_front(str(text))
if messages.size() > max_message: if messages.size() > max_message:
messages.pop_back() messages.pop_back()

View File

@ -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)

View File

@ -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

View File

@ -3,6 +3,7 @@ extends Node
const Hass = preload ("res://lib/home_apis/hass/hass.gd") const Hass = preload ("res://lib/home_apis/hass/hass.gd")
const HassWebSocket = preload ("res://lib/home_apis/hass_ws/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 = { const apis = {
"hass": Hass, "hass": Hass,
@ -96,7 +97,26 @@ func watch_state(entity: String, callback: Callable):
assert(has_connected(), "Not connected") assert(has_connected(), "Not connected")
return api.watch_state(entity, callback) return api.watch_state(entity, callback)
func _notification(what): ## Returns true if the adapter has an integration in the home automation system
if what == NOTIFICATION_WM_CLOSE_REQUEST||what == NOTIFICATION_WM_GO_BACK_REQUEST: ## allowing to send the room position of the headset.
# Store.house.save_local() func has_integration() -> bool:
pass 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()

View File

@ -5,6 +5,6 @@ const SettingsStore = preload ("res://lib/stores/settings.gd")
const HouseStore = preload ("res://lib/stores/house.gd") const HouseStore = preload ("res://lib/stores/house.gd")
const DevicesStore = preload ("res://lib/stores/devices.gd") const DevicesStore = preload ("res://lib/stores/devices.gd")
var settings = SettingsStore.new() var settings := SettingsStore.new()
var house = HouseStore.new() var house := HouseStore.new()
var devices = DevicesStore.new() var devices := DevicesStore.new()

View File

@ -1,10 +1,7 @@
const HASS_API = preload ("../hass.gd") extends VoiceHandler
signal on_wake_word(wake_word: String) const HASS_API = preload ("../hass.gd")
signal on_stt_message(message: String) const VoiceHandler = preload ("res://lib/home_apis/voice_handler.gd")
signal on_tts_message(message: String)
signal on_tts_sound(sound: AudioStreamMP3)
signal on_error()
var api: HASS_API var api: HASS_API
var pipe_running := false var pipe_running := false
@ -36,9 +33,6 @@ var tts_sound = null:
func _init(hass: HASS_API): func _init(hass: HASS_API):
self.api = hass self.api = hass
func on_connect():
pass
func start_wakeword(): func start_wakeword():
if pipe_running: if pipe_running:
return return

View File

@ -121,7 +121,6 @@ func start_subscriptions():
func handle_connect(): func handle_connect():
integration_handler.on_connect() integration_handler.on_connect()
assist_handler.on_connect()
connected = true connected = true
on_connect.emit() on_connect.emit()
@ -283,3 +282,6 @@ func update_room(room: String):
if response.status == Promise.Status.RESOLVED: if response.status == Promise.Status.RESOLVED:
pass pass
func get_voice_assistant():
return assist_handler

View File

@ -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

View File

@ -11,6 +11,9 @@ var token: String = ""
## If the voice assistant should be enabled ## If the voice assistant should be enabled
var voice_assistant: bool = false var voice_assistant: bool = false
## If the onboarding process has been completed
var onboarding_complete: bool = false
func _init(): func _init():
_save_path = "user://settings.json" _save_path = "user://settings.json"
@ -19,3 +22,4 @@ func clear():
url = "" url = ""
token = "" token = ""
voice_assistant = false voice_assistant = false
onboarding_complete = false

View File

@ -77,8 +77,11 @@ func load_local(path=_save_path):
var save_file = FileAccess.open(path, FileAccess.READ) var save_file = FileAccess.open(path, FileAccess.READ)
# In case that there is no store file yet
if save_file == null: if save_file == null:
return false _loaded = true
on_loaded.emit()
return true
var json_text = save_file.get_as_text() var json_text = save_file.get_as_text()
var save_data = VariantSerializer.parse_value(JSON.parse_string(json_text)) var save_data = VariantSerializer.parse_value(JSON.parse_string(json_text))