diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..381ee74 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = tab +indent_size = 4 +end_of_line = crlf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/assets/materials/interface.tres b/assets/materials/interface.tres new file mode 100644 index 0000000..3af2e71 --- /dev/null +++ b/assets/materials/interface.tres @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55d5f30db336a8f8f7633743c23e41077d5c1a1b900c475a6d3811746eba3d1b +size 141 diff --git a/icon.svg b/icon.svg deleted file mode 100644 index b370ceb..0000000 --- a/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/icon.svg.import b/icon.svg.import deleted file mode 100644 index 226566a..0000000 --- a/icon.svg.import +++ /dev/null @@ -1,37 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bnc4gf261swvs" -path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://icon.svg" -dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=false -editor/convert_colors_with_editor_theme=false diff --git a/main.tscn b/main.tscn index e79d8fc..f8bc998 100644 --- a/main.tscn +++ b/main.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=12 format=3 uid="uid://eecv28y6jxk4"] +[gd_scene load_steps=13 format=3 uid="uid://eecv28y6jxk4"] +[ext_resource type="Script" path="res://src/devices/light.gd" id="1_5mqma"] [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://src/raycast.gd" id="1_tsqxc"] [ext_resource type="Script" path="res://src/model.gd" id="2_7f1x4"] -[ext_resource type="Script" path="res://light.gd" id="4_bm5yj"] +[ext_resource type="PackedScene" uid="uid://c3kdssrmv84kv" path="res://scenes/menu.tscn" id="3_1tbp3"] [sub_resource type="SphereShape3D" id="SphereShape3D_2igmd"] radius = 0.1 @@ -42,7 +43,7 @@ ambient_light_sky_contribution = 0.72 [node name="Light" type="StaticBody3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.45064, 0, 0.20152) -script = ExtResource("4_bm5yj") +script = ExtResource("1_5mqma") [node name="CollisionShape3D" type="CollisionShape3D" parent="Light"] shape = SubResource("SphereShape3D_2igmd") @@ -67,6 +68,9 @@ mesh = SubResource("BoxMesh_ir3co") script = ExtResource("2_7f1x4") light = NodePath("../../../Light") +[node name="Menu" parent="XROrigin3D/XRControllerLeft" instance=ExtResource("3_1tbp3")] +transform = Transform3D(-4.37114e-08, 0, -1, -0.707107, 0.707107, 3.09086e-08, 0.707107, 0.707107, -3.09086e-08, 0.194945, 0, -0.0534939) + [node name="XRControllerRight" type="XRController3D" parent="XROrigin3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.488349, 0.559219, -0.2988) tracker = &"right_hand" diff --git a/project.godot b/project.godot index 482aeb0..f605205 100644 --- a/project.godot +++ b/project.godot @@ -13,11 +13,12 @@ config_version=5 config/name="ImmersiveHome" run/main_scene="res://main.tscn" config/features=PackedStringArray("4.1", "Mobile") -config/icon="res://icon.svg" [autoload] XRToolsUserSettings="*res://addons/godot-xr-tools/user_settings/user_settings.gd" +Request="*res://src/globals/request.gd" +HomeAdapters="*res://src/globals/home_adapters.gd" [editor_plugins] diff --git a/scenes/device.tscn b/scenes/device.tscn new file mode 100644 index 0000000..c3e4958 --- /dev/null +++ b/scenes/device.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=4 format=3 uid="uid://dbe8slnyhro2n"] + +[ext_resource type="Script" path="res://src/ui/device.gd" id="1_rbo86"] + +[sub_resource type="BoxMesh" id="BoxMesh_aa3i4"] +size = Vector3(0.05, 0.01, 0.05) + +[sub_resource type="BoxShape3D" id="BoxShape3D_28fjq"] +size = Vector3(0.05, 0.01, 0.05) + +[node name="Device" type="StaticBody3D"] +script = ExtResource("1_rbo86") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +mesh = SubResource("BoxMesh_aa3i4") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("BoxShape3D_28fjq") + +[node name="Label" type="Label3D" parent="."] +transform = Transform3D(-2.18557e-09, -0.05, -2.18557e-09, 0, -2.18557e-09, 0.05, -0.05, 2.18557e-09, 9.55343e-17, 0, 0.00918245, 0) +text = "Text" +autowrap_mode = 3 diff --git a/scenes/entity.tscn b/scenes/entity.tscn new file mode 100644 index 0000000..e22a66b --- /dev/null +++ b/scenes/entity.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=4 format=3 uid="uid://xo0o5nrfjl23"] + +[ext_resource type="Script" path="res://src/ui/entity.gd" id="1_825oj"] + +[sub_resource type="BoxMesh" id="BoxMesh_aa3i4"] +size = Vector3(0.05, 0.01, 0.05) + +[sub_resource type="BoxShape3D" id="BoxShape3D_28fjq"] +size = Vector3(0.05, 0.01, 0.05) + +[node name="Entity" type="StaticBody3D"] +script = ExtResource("1_825oj") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +mesh = SubResource("BoxMesh_aa3i4") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("BoxShape3D_28fjq") + +[node name="Label" type="Label3D" parent="."] +transform = Transform3D(-2.18557e-09, -0.05, -2.18557e-09, 0, -2.18557e-09, 0.05, -0.05, 2.18557e-09, 9.55343e-17, 0, 0.00918245, 0) +text = "Text" +autowrap_mode = 3 diff --git a/scenes/menu.tscn b/scenes/menu.tscn new file mode 100644 index 0000000..374f8fe --- /dev/null +++ b/scenes/menu.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=4 format=3 uid="uid://c3kdssrmv84kv"] + +[ext_resource type="Script" path="res://src/menu.gd" id="1_ng4u3"] +[ext_resource type="Material" uid="uid://bertj8bp8b5l1" path="res://assets/materials/interface.tres" id="2_nsukb"] + +[sub_resource type="PlaneMesh" id="PlaneMesh_6t3dn"] +material = ExtResource("2_nsukb") +size = Vector2(0.3, 0.3) + +[node name="Menu" type="Node3D"] +script = ExtResource("1_ng4u3") + +[node name="Background" type="MeshInstance3D" parent="."] +mesh = SubResource("PlaneMesh_6t3dn") + +[node name="Devices" type="Node3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.149223, 0, 0.150667) diff --git a/light.gd b/src/devices/light.gd similarity index 100% rename from light.gd rename to src/devices/light.gd diff --git a/src/globals/home_adapters.gd b/src/globals/home_adapters.gd new file mode 100644 index 0000000..3717b94 --- /dev/null +++ b/src/globals/home_adapters.gd @@ -0,0 +1,6 @@ +extends Node + +var Adapter = preload("res://src/home_adapters/adapter.gd") + +var adapter = Adapter.new(Adapter.ADAPTER_TYPES.HASS) + \ No newline at end of file diff --git a/src/globals/request.gd b/src/globals/request.gd new file mode 100644 index 0000000..6685be2 --- /dev/null +++ b/src/globals/request.gd @@ -0,0 +1 @@ +extends HTTPRequest \ No newline at end of file diff --git a/src/home_adapters/adapter.gd b/src/home_adapters/adapter.gd new file mode 100644 index 0000000..c18ae44 --- /dev/null +++ b/src/home_adapters/adapter.gd @@ -0,0 +1,26 @@ +extends Node + +const hass = preload("res://src/home_adapters/hass/hass.gd") + +enum ADAPTER_TYPES { + HASS +} + +const adapters = { + ADAPTER_TYPES.HASS: hass +} + +const methods = [ + "load_devices" +] + +var adapter: Node + +func _init(type: ADAPTER_TYPES): + adapter = adapters[type].new() + + for method in methods: + assert(adapter.has_method(method), "Adapter does not implement method: " + method) + +func load_devices(): + return await adapter.load_devices() diff --git a/src/home_adapters/hass/hass.gd b/src/home_adapters/hass/hass.gd new file mode 100644 index 0000000..9c0bc35 --- /dev/null +++ b/src/home_adapters/hass/hass.gd @@ -0,0 +1,23 @@ +extends Node + +var url: String = "http://192.168.33.33:8123" +var token: String = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZjQ0ZGM2N2Y3YzY0MDc1OGZlMWI2ZjJlNmIxZjRkNSIsImlhdCI6MTY5ODAxMDcyOCwiZXhwIjoyMDEzMzcwNzI4fQ.K6ydLUC-4Q7BNIRCU1nWlI2s6sg9UCiOu-Lpedw2zJc" +var headers: PackedStringArray = PackedStringArray([]) + +var devices_template = FileAccess.get_file_as_string("res://src/home_adapters/hass/templates/devices.j2") + +func _init(url := self.url, token := self.token): + self.url = url + self.token = token + + headers = PackedStringArray(["Authorization: Bearer %s" % token, "Content-Type: application/json"]) + devices_template = devices_template.replace("\n", " ").replace("\t", "").replace("\r", " ").replace("\"", "\\\"") + +func load_devices(): + Request.request("%s/api/template" % [url], headers, HTTPClient.METHOD_POST, "{\"template\": \"%s\"}" % [devices_template]) + var response = await Request.request_completed + var data_string = response[3].get_string_from_utf8().replace("'", "\"") + var json = JSON.parse_string(data_string).data + + print(json) + return json diff --git a/src/home_adapters/hass/templates/devices.j2 b/src/home_adapters/hass/templates/devices.j2 new file mode 100644 index 0000000..d1750ba --- /dev/null +++ b/src/home_adapters/hass/templates/devices.j2 @@ -0,0 +1,12 @@ +{% set devices = states | map(attribute='entity_id') | map('device_id') | unique | reject('eq',None) | list %} + +{%- set ns = namespace(devices = []) %} +{%- for device in devices %} + {%- set entities = device_entities(device) | list %} + {%- if entities %} + {%- set ns.devices = ns.devices + [ {device: {"name": device_attr(device, "name"), "entities": entities }} ] %} + {%- endif %} +{%- endfor %} +{ + "data": {{ ns.devices }} +} \ No newline at end of file diff --git a/src/menu.gd b/src/menu.gd new file mode 100644 index 0000000..da4d042 --- /dev/null +++ b/src/menu.gd @@ -0,0 +1,83 @@ +extends Node3D + +const Device = preload("res://scenes/device.tscn") +const Entity = preload("res://scenes/entity.tscn") + +@onready var devices_node = $Devices +var devices + +var selected_device = null +# Called when the node enters the scene tree for the first time. +func _ready(): + devices = await HomeAdapters.adapter.load_devices() + render_devices() + +func render_devices(): + var x = 0 + var y = 0 + + for device in devices: + var info = device.values()[0] + + var device_instance = Device.instantiate() + device_instance.set_position(Vector3(y * 0.08, 0, -x * 0.08)) + device_instance.click.connect(_on_device_click) + + devices_node.add_child(device_instance) + + device_instance.set_device_name(info["name"]) + + x += 1 + if x % 5 == 0: + x = 0 + y += 1 + +func render_entities(): + var x = 0 + var y = 0 + + var info + + for device in devices: + if device.values()[0].name == selected_device: + info = device.values()[0] + break + + if info == null: + return + + var entities = info["entities"] + + for entity in entities: + var entity_instance = Entity.instantiate() + entity_instance.set_position(Vector3(y * 0.08, 0, -x * 0.08)) + entity_instance.click.connect(_on_entity_click) + + devices_node.add_child(entity_instance) + + entity_instance.set_entity_name(entity) + + x += 1 + if x % 5 == 0: + x = 0 + y += 1 + +func _on_device_click(device_name): + selected_device = device_name + print(selected_device) + clear_menu() + render_entities() + +func _on_entity_click(entity_name): + print(entity_name) + selected_device = null + clear_menu() + render_devices() + +func clear_menu(): + for child in devices_node.get_children(): + devices_node.remove_child(child) + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass diff --git a/src/ui/device.gd b/src/ui/device.gd new file mode 100644 index 0000000..d112556 --- /dev/null +++ b/src/ui/device.gd @@ -0,0 +1,12 @@ +extends StaticBody3D + +@onready var label: Label3D = $Label + +signal click(name: String) + +func _on_toggle(): + click.emit(label.text) + +func set_device_name(text): + assert(label != null, "Device has to be added to the scene tree") + label.text = text \ No newline at end of file diff --git a/src/ui/entity.gd b/src/ui/entity.gd new file mode 100644 index 0000000..0d5a281 --- /dev/null +++ b/src/ui/entity.gd @@ -0,0 +1,12 @@ +extends StaticBody3D + +@onready var label: Label3D = $Label + +signal click(name: String) + +func _on_toggle(): + click.emit(label.text) + +func set_entity_name(text): + assert(label != null, "Entity has to be added to the scene tree") + label.text = text