From 7927d00e5bca65019955b8f9cb75a105c058ed90 Mon Sep 17 00:00:00 2001 From: Nitwel Date: Sun, 5 Nov 2023 16:36:13 +0100 Subject: [PATCH] Finish websocket adapter --- content/entities/light/light.gd | 4 +- content/entities/sensor/sensor.gd | 4 +- content/entities/switch/switch.gd | 6 +-- content/main.tscn | 11 +--- content/model.gd | 23 --------- content/ui/menu/menu.gd | 2 +- lib/globals/home_adapters.gd | 8 +-- lib/home_adapters/adapter.gd | 28 ++++++---- lib/home_adapters/hass/hass.gd | 2 +- lib/home_adapters/hass_ws/hass.gd | 51 +++++++++++++++---- .../hass_ws/templates/devices.j2 | 4 +- 11 files changed, 74 insertions(+), 69 deletions(-) delete mode 100644 content/model.gd diff --git a/content/entities/light/light.gd b/content/entities/light/light.gd index aced8f0..5f77c50 100644 --- a/content/entities/light/light.gd +++ b/content/entities/light/light.gd @@ -5,7 +5,7 @@ extends StaticBody3D # Called when the node enters the scene tree for the first time. func _ready(): - var stateInfo = await HomeAdapters.adapter_ws.get_state(entity_id) + var stateInfo = await HomeAdapters.adapter.get_state(entity_id) if stateInfo["state"] == "on": sprite.set_frame(0) else: @@ -13,7 +13,7 @@ func _ready(): func _on_toggle(): - HomeAdapters.adapter_ws.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on") + HomeAdapters.adapter.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on") if sprite.get_frame() == 0: sprite.set_frame(1) else: diff --git a/content/entities/sensor/sensor.gd b/content/entities/sensor/sensor.gd index 5aa4d22..f3da193 100644 --- a/content/entities/sensor/sensor.gd +++ b/content/entities/sensor/sensor.gd @@ -5,10 +5,10 @@ extends StaticBody3D # Called when the node enters the scene tree for the first time. func _ready(): - var stateInfo = await HomeAdapters.adapter_ws.get_state(entity_id) + var stateInfo = await HomeAdapters.adapter.get_state(entity_id) label.text = stateInfo["state"] - await HomeAdapters.adapter_ws.watch_state(entity_id, func(new_state): + await HomeAdapters.adapter.watch_state(entity_id, func(new_state): label.text = new_state["state"] ) diff --git a/content/entities/switch/switch.gd b/content/entities/switch/switch.gd index 9f9e3f9..7c41724 100644 --- a/content/entities/switch/switch.gd +++ b/content/entities/switch/switch.gd @@ -5,7 +5,7 @@ extends StaticBody3D # Called when the node enters the scene tree for the first time. func _ready(): - var stateInfo = await HomeAdapters.adapter_ws.get_state(entity_id) + var stateInfo = await HomeAdapters.adapter.get_state(entity_id) if stateInfo == null: return @@ -14,7 +14,7 @@ func _ready(): else: sprite.set_frame(1) - await HomeAdapters.adapter_ws.watch_state(entity_id, func(new_state): + await HomeAdapters.adapter.watch_state(entity_id, func(new_state): if new_state["state"] == "on": sprite.set_frame(0) else: @@ -23,7 +23,7 @@ func _ready(): func _on_toggle(): - HomeAdapters.adapter_ws.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on") + HomeAdapters.adapter.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on") if sprite.get_frame() == 0: sprite.set_frame(1) else: diff --git a/content/main.tscn b/content/main.tscn index 2979f54..5a04593 100644 --- a/content/main.tscn +++ b/content/main.tscn @@ -1,13 +1,11 @@ -[gd_scene load_steps=13 format=3 uid="uid://eecv28y6jxk4"] +[gd_scene load_steps=11 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/raycast.gd" id="1_tsqxc"] [ext_resource type="Script" path="res://content/main.gd" id="1_uvrd4"] -[ext_resource type="Script" path="res://content/model.gd" id="2_7f1x4"] [ext_resource type="PackedScene" uid="uid://c3kdssrmv84kv" path="res://content/ui/menu/menu.tscn" id="3_1tbp3"] [ext_resource type="PackedScene" uid="uid://ctltchlf2j2r4" path="res://addons/xr-simulator/XRSimulator.tscn" id="5_3qc8g"] [ext_resource type="Material" uid="uid://bf5ina366dwm6" path="res://assets/materials/sky.material" id="5_wgwf8"] -[ext_resource type="PackedScene" uid="uid://cscl5k7lhopj5" path="res://content/entities/switch/switch.tscn" id="8_uxmrb"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_m58yb"] ao_enabled = true @@ -43,9 +41,6 @@ pose = &"aim" [node name="MeshInstance3D" type="MeshInstance3D" parent="XROrigin3D/XRControllerLeft"] mesh = SubResource("BoxMesh_ir3co") -[node name="Model" type="Node3D" parent="XROrigin3D/XRControllerLeft"] -script = ExtResource("2_7f1x4") - [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.183517, 0, -0.0534939) @@ -77,7 +72,3 @@ shadow_enabled = true [node name="XRSimulator" parent="." instance=ExtResource("5_3qc8g")] xr_origin = NodePath("../XROrigin3D") - -[node name="Switch" parent="." instance=ExtResource("8_uxmrb")] -transform = Transform3D(0.999999, -1.39635e-11, 0, 9.48031e-12, 1, 0, 0, 0, 1, 0.564168, 0.725642, -1.56163) -entity_id = "switch.plug_printer_2_fale" diff --git a/content/model.gd b/content/model.gd deleted file mode 100644 index 4eeb541..0000000 --- a/content/model.gd +++ /dev/null @@ -1,23 +0,0 @@ -extends Node3D - -@onready var _controller := XRHelpers.get_xr_controller(self) -@export var light: StaticBody3D - -# Called when the node enters the scene tree for the first time. -func _ready(): - _controller.button_pressed.connect(self._on_button_pressed) - -# Called every frame. 'delta' is the elapsed time since the previous frame. -func _process(delta): - pass - -func _on_button_pressed(button): - print("right: ", button) - if button != "trigger_click": - return - - if light == null: - return - - # set light position to controller position - light.transform.origin = _controller.transform.origin diff --git a/content/ui/menu/menu.gd b/content/ui/menu/menu.gd index aef98b7..0c16034 100644 --- a/content/ui/menu/menu.gd +++ b/content/ui/menu/menu.gd @@ -12,7 +12,7 @@ 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() + devices = await HomeAdapters.adapter.get_devices() render_devices() func render_devices(): diff --git a/lib/globals/home_adapters.gd b/lib/globals/home_adapters.gd index 100a140..275327c 100644 --- a/lib/globals/home_adapters.gd +++ b/lib/globals/home_adapters.gd @@ -1,11 +1,11 @@ extends Node -var Adapter = preload("res://lib/home_adapters/adapter.gd") +const Adapter = preload("res://lib/home_adapters/adapter.gd") -var adapter = Adapter.new(Adapter.ADAPTER_TYPES.HASS) -var adapter_ws = Adapter.new(Adapter.ADAPTER_TYPES.HASS_WS) +var adapter = Adapter.new(Adapter.ADAPTER_TYPES.HASS_WS) +# var adapter_http = Adapter.new(Adapter.ADAPTER_TYPES.HASS) func _ready(): add_child(adapter) - add_child(adapter_ws) + # add_child(adapter_http) diff --git a/lib/home_adapters/adapter.gd b/lib/home_adapters/adapter.gd index acbe8df..430d702 100644 --- a/lib/home_adapters/adapter.gd +++ b/lib/home_adapters/adapter.gd @@ -1,7 +1,7 @@ extends Node -const hass = preload("res://lib/home_adapters/hass/hass.gd") -const hass_ws = preload("res://lib/home_adapters/hass_ws/hass.gd") +const Hass = preload("res://lib/home_adapters/hass/hass.gd") +const HassWebSocket = preload("res://lib/home_adapters/hass_ws/hass.gd") enum ADAPTER_TYPES { HASS, @@ -9,14 +9,16 @@ enum ADAPTER_TYPES { } const adapters = { - ADAPTER_TYPES.HASS: hass, - ADAPTER_TYPES.HASS_WS: hass_ws + ADAPTER_TYPES.HASS: Hass, + ADAPTER_TYPES.HASS_WS: HassWebSocket } const methods = [ - "load_devices", + "get_devices", + "get_device", "get_state", - "set_state" + "set_state", + "watch_state" ] var adapter: Node @@ -27,15 +29,23 @@ func _init(type: ADAPTER_TYPES): for method in methods: assert(adapter.has_method(method), "Adapter does not implement method: " + method) - -func load_devices(): - return await adapter.load_devices() +## Get a list of all devices +func get_devices(): + return await adapter.get_devices() + +## Get a single device by id +func get_device(id: String): + return await adapter.get_device(id) + +## Returns the current state of an entity func get_state(entity: String): return await adapter.get_state(entity) +## Updates the state of the entity and returns the resulting state func set_state(entity: String, state: String, attributes: Dictionary = {}): return await adapter.set_state(entity, state, attributes) +## Watches the state and each time it changes, calls the callback with the changed state, returns a function to stop watching the state func watch_state(entity: String, callback: Callable): return adapter.watch_state(entity, callback) diff --git a/lib/home_adapters/hass/hass.gd b/lib/home_adapters/hass/hass.gd index ca96a8a..3924522 100644 --- a/lib/home_adapters/hass/hass.gd +++ b/lib/home_adapters/hass/hass.gd @@ -13,7 +13,7 @@ func _init(url := self.url, token := self.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(): +func get_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("'", "\"") diff --git a/lib/home_adapters/hass_ws/hass.gd b/lib/home_adapters/hass_ws/hass.gd index d176ccc..60e80cf 100644 --- a/lib/home_adapters/hass_ws/hass.gd +++ b/lib/home_adapters/hass_ws/hass.gd @@ -1,13 +1,13 @@ extends Node -var devices_template := FileAccess.get_file_as_string("res://lib/home_adapters/hass/templates/devices.j2") +var devices_template := FileAccess.get_file_as_string("res://lib/home_adapters/hass_ws/templates/devices.j2") var socket := WebSocketPeer.new() # in seconds var request_timeout := 10.0 var url := "ws://192.168.33.33:8123/api/websocket" var token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZjQ0ZGM2N2Y3YzY0MDc1OGZlMWI2ZjJlNmIxZjRkNSIsImlhdCI6MTY5ODAxMDcyOCwiZXhwIjoyMDEzMzcwNzI4fQ.K6ydLUC-4Q7BNIRCU1nWlI2s6sg9UCiOu-Lpedw2zJc" -var LOG_MESSAGES := false +var LOG_MESSAGES := true var authenticated := false var loading := true @@ -24,7 +24,7 @@ func _init(url := self.url, token := self.token): self.url = url self.token = token - devices_template = devices_template.replace("\n", " ").replace("\t", "").replace("\r", " ").replace("\"", "\\\"") + devices_template = devices_template.replace("\n", " ").replace("\t", "").replace("\r", " ") connect_ws() func connect_ws(): @@ -56,7 +56,7 @@ func _process(delta): handle_disconnect() func handle_packet(packet: Dictionary): - if LOG_MESSAGES: print("Received packet: %s" % packet) + if LOG_MESSAGES: print("Received packet: %s" % str(packet).substr(0, 1000)) if packet.type == "auth_required": send_packet({ @@ -139,21 +139,34 @@ func send_subscribe_packet(packet: Dictionary, callback: Callable): id += 1 -func send_request_packet(packet: Dictionary): +func send_request_packet(packet: Dictionary, ignore_initial := false): packet.id = id id += 1 send_packet(packet) - var promise = Promise.new(func(resolve: Callable, reject: Callable): - packet_callbacks.add_once(packet.id, resolve) + var promise = Promise.new(func(resolve: Callable, reject: Callable): + var fn: Callable + + if ignore_initial: + fn = func(packet: Dictionary): + if packet.type == "event": + resolve.call(packet) + packet_callbacks.remove(packet.id, fn) + + packet_callbacks.add(packet.id, fn) + else: + packet_callbacks.add_once(packet.id, resolve) var timeout = Timer.new() timeout.set_wait_time(request_timeout) timeout.set_one_shot(true) timeout.timeout.connect(func(): reject.call(Promise.Rejection.new("Request timed out")) - packet_callbacks.remove(packet.id, resolve) + if ignore_initial: + packet_callbacks.remove(packet.id, fn) + else: + packet_callbacks.remove(packet.id, resolve) ) add_child(timeout) timeout.start() @@ -163,7 +176,7 @@ func send_request_packet(packet: Dictionary): func send_packet(packet: Dictionary): - if LOG_MESSAGES || true: print("Sending packet: %s" % encode_packet(packet)) + if LOG_MESSAGES: print("Sending packet: %s" % encode_packet(packet)) socket.send_text(encode_packet(packet)) func decode_packet(packet: PackedByteArray): @@ -172,11 +185,21 @@ func decode_packet(packet: PackedByteArray): func encode_packet(packet: Dictionary): return JSON.stringify(packet) -func load_devices(): +func get_devices(): if loading: await on_connect - return entities + var result = await send_request_packet({ + "type": "render_template", + "template": devices_template, + "timeout": 3, + "report_errors": true + }, true) + + return result.payload.event.result + +func get_device(id: String): + pass func get_state(entity: String): if loading: @@ -193,6 +216,9 @@ func watch_state(entity: String, callback: Callable): entitiy_callbacks.add(entity, callback) + return func(): + entitiy_callbacks.remove(entity, callback) + func set_state(entity: String, state: String, attributes: Dictionary = {}): assert(!loading, "Still loading") @@ -211,6 +237,9 @@ func set_state(entity: String, state: String, attributes: Dictionary = {}): elif state == 'off': service = 'turn_off' + if service == null: + return null + return await send_request_packet({ "type": "call_service", "domain": domain, diff --git a/lib/home_adapters/hass_ws/templates/devices.j2 b/lib/home_adapters/hass_ws/templates/devices.j2 index d1750ba..40b05f7 100644 --- a/lib/home_adapters/hass_ws/templates/devices.j2 +++ b/lib/home_adapters/hass_ws/templates/devices.j2 @@ -7,6 +7,4 @@ {%- set ns.devices = ns.devices + [ {device: {"name": device_attr(device, "name"), "entities": entities }} ] %} {%- endif %} {%- endfor %} -{ - "data": {{ ns.devices }} -} \ No newline at end of file +{{ ns.devices }} \ No newline at end of file