Merge pull request #104 from Nitwel/integration

Add support for Home Assistant Integration
This commit is contained in:
Nitwel 2024-03-12 13:30:31 +01:00 committed by GitHub
commit 2a1207c513
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 244 additions and 121 deletions

View File

@ -84,7 +84,6 @@ func _ready():
right_tracker.set_pose(pose, child.transform, Vector3.ZERO, Vector3.ZERO, XRPose.XR_TRACKING_CONFIDENCE_HIGH) right_tracker.set_pose(pose, child.transform, Vector3.ZERO, Vector3.ZERO, XRPose.XR_TRACKING_CONFIDENCE_HIGH)
XRServer.add_tracker(right_tracker) XRServer.add_tracker(right_tracker)
func _process(_delta): func _process(_delta):
if enabled and disable_xr_in_editor and OS.has_feature("editor") and viewport.use_xr: if enabled and disable_xr_in_editor and OS.has_feature("editor") and viewport.use_xr:
viewport.use_xr = false viewport.use_xr = false
@ -219,7 +218,7 @@ func attract_controller(event: InputEventMouseButton, controller: XRController3D
func rotate_device(event: InputEventMouseMotion, device: Node3D): func rotate_device(event: InputEventMouseMotion, device: Node3D):
var motion = event.relative var motion = event.relative
device.rotate_y(motion.x * - device_x_sensitivity / 1000) device.rotate_y(motion.x * - device_x_sensitivity / 1000)
device.rotate(device.transform.basis.x, motion.y * -device_y_sensitivity/1000) device.rotate(device.transform.basis.x.normalized(), motion.y * - device_y_sensitivity / 1000)
func vector_key_mapping(key_positive_x: int, key_negative_x: int, key_positive_y: int, key_negative_y: int): func vector_key_mapping(key_positive_x: int, key_negative_x: int, key_positive_y: int, key_negative_y: int):
var x = 0 var x = 0

View File

@ -12,11 +12,9 @@ func _ready():
EventSystem.on_slow_tick.connect(_slow_tick) EventSystem.on_slow_tick.connect(_slow_tick)
func _slow_tick(_delta): func _slow_tick(_delta):
if player_camera.is_inside_tree() == false: if player_camera.is_inside_tree() == false||ray.is_inside_tree() == false:
printerr("Player camera is not inside the tree")
return return
ray.target_position = get_parent().to_local(player_camera.global_position) ray.target_position = get_parent().to_local(player_camera.global_position)
get_parent().visible = ray.is_colliding() == false get_parent().visible = ray.is_colliding() == false

View File

@ -1,9 +1,10 @@
[gd_scene load_steps=15 format=3 uid="uid://eecv28y6jxk4"] [gd_scene load_steps=16 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"]
[ext_resource type="PackedScene" uid="uid://b30w6tywfj4fp" path="res://content/system/controller_left/controller_left.tscn" id="2_2lraw"] [ext_resource type="PackedScene" uid="uid://b30w6tywfj4fp" path="res://content/system/controller_left/controller_left.tscn" id="2_2lraw"]
[ext_resource type="PackedScene" uid="uid://d3f8glx1xgm5w" path="res://content/system/raycast/raycast.tscn" id="3_67lii"] [ext_resource type="PackedScene" uid="uid://d3f8glx1xgm5w" path="res://content/system/raycast/raycast.tscn" id="3_67lii"]
[ext_resource type="PackedScene" uid="uid://b2kjh1fpjptdr" path="res://content/system/camera/camera.tscn" id="3_rj4ac"]
[ext_resource type="PackedScene" uid="uid://bsx12q23v8apy" path="res://content/system/hands/hands.tscn" id="4_v8xu6"] [ext_resource type="PackedScene" uid="uid://bsx12q23v8apy" path="res://content/system/hands/hands.tscn" id="4_v8xu6"]
[ext_resource type="PackedScene" uid="uid://ctltchlf2j2r4" path="res://addons/xr-simulator/XRSimulator.tscn" id="5_3qc8g"] [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="Material" uid="uid://bf5ina366dwm6" path="res://assets/materials/sky.material" id="5_wgwf8"]
@ -46,10 +47,7 @@ shadow_enabled = true
[node name="XROrigin3D" type="XROrigin3D" parent="."] [node name="XROrigin3D" type="XROrigin3D" parent="."]
[node name="XRCamera3D" type="XRCamera3D" parent="XROrigin3D"] [node name="XRCamera3D" parent="XROrigin3D" instance=ExtResource("3_rj4ac")]
transform = Transform3D(1, 1.8976e-10, 4.07454e-10, 6.76872e-11, 1, 2.08734e-08, -5.82077e-11, 1.09139e-11, 1, 0.0356618, 0.71033, 0.00564247)
cull_mask = 524287
current = true
[node name="XRControllerLeft" parent="XROrigin3D" instance=ExtResource("2_2lraw")] [node name="XRControllerLeft" parent="XROrigin3D" instance=ExtResource("2_2lraw")]
transform = Transform3D(0.999999, -1.39633e-11, 0, 9.48075e-12, 1, 0, 0, 0, 1, -0.355145, 0.550439, -0.477945) transform = Transform3D(0.999999, -1.39633e-11, 0, 9.48075e-12, 1, 0, 0, 0, 1, -0.355145, 0.550439, -0.477945)

View File

@ -0,0 +1,18 @@
extends XRCamera3D
var last_room = null
func _physics_process(_delta):
if HomeApi.api.has_integration():
update_room()
func update_room():
var room = House.body.find_room_at(global_position)
if room != last_room:
if room:
HomeApi.api.update_room(room.name)
last_room = room
else:
HomeApi.api.update_room("outside")
last_room = null

View File

@ -0,0 +1,6 @@
[gd_scene format=3 uid="uid://b2kjh1fpjptdr"]
[node name="XRCamera3D" type="XRCamera3D"]
transform = Transform3D(0.999999, 0.00106283, -0.000420545, -0.00106267, 0.999999, 0.000379457, 0.000420948, -0.000378989, 1, 0.0356617, 0.71033, 0.00564247)
cull_mask = 524287
current = true

View File

@ -90,7 +90,6 @@ func is_valid_room(room_name):
return room.wall_corners.get_child_count() >= 3 return room.wall_corners.get_child_count() >= 3
func delete_room(room_name): func delete_room(room_name):
var room = find_room(room_name) var room = find_room(room_name)
@ -125,6 +124,22 @@ func find_room_at(entity_position: Vector3):
return room return room
return null return null
func rename_room(old_room: String, new_name: String):
var room = find_room(old_room)
if room == null:
return
room.name = new_name
var store_room = Store.house.get_room(old_room)
if store_room != null:
store_room.name = new_name
save_all_entities()
Store.house.save_local()
func get_level(level: int): func get_level(level: int):
return levels.get_child(level) return levels.get_child(level)
@ -206,7 +221,6 @@ func update_mini_view():
camera_position.y *= 0.5 camera_position.y *= 0.5
camera_direction.y = 0.0 camera_direction.y = 0.0
var target_position = camera_position + camera_direction.normalized() * 0.2 var target_position = camera_position + camera_direction.normalized() * 0.2
levels.global_position = target_position - center * 0.1 levels.global_position = target_position - center * 0.1
else: else:

View File

@ -26,6 +26,9 @@ func has_point(point: Vector3) -> bool:
func get_aabb(): func get_aabb():
var room_store = Store.house.get_room(name) var room_store = Store.house.get_room(name)
if room_store == null:
return AABB()
var corners = room_store.corners var corners = room_store.corners
if corners.size() == 0: if corners.size() == 0:
@ -119,4 +122,3 @@ static func generate_ceiling_mesh(room_store: Dictionary):
var mesh = st.commit() var mesh = st.commit()
return mesh return mesh

View File

@ -99,6 +99,9 @@ func _ready():
func update_animation(): func update_animation():
var length = animation_player.get_animation("down").length var length = animation_player.get_animation("down").length
if animation_player.current_animation == "":
return
if active&&animation_player.current_animation_position != length: if active&&animation_player.current_animation_position != length:
animation_player.play("down") animation_player.play("down")
elif !active&&animation_player.current_animation_position != 0: elif !active&&animation_player.current_animation_position != 0:

View File

@ -33,6 +33,20 @@ var text_handler = preload("res://content/ui/components/input/text_handler.gd").
text_handler.set_text(value, focused) text_handler.set_text(value, focused)
label.text = text_handler.get_display_text() label.text = text_handler.get_display_text()
@export var disabled: bool = false:
set(value):
if !is_node_ready(): await ready
disabled = value
if disabled:
label.modulate = Color(0.7, 0.7, 0.7)
add_to_group("ui_focus_skip")
animation.stop()
caret.hide()
else:
label.modulate = Color(1, 1, 1)
remove_from_group("ui_focus_skip")
var keyboard_input: bool = false var keyboard_input: bool = false
var input_plane = Plane(Vector3.UP, Vector3.ZERO) var input_plane = Plane(Vector3.UP, Vector3.ZERO)
@ -44,7 +58,7 @@ func _ready():
return return
EventSystem.on_key_down.connect(func(event): EventSystem.on_key_down.connect(func(event):
if EventSystem.is_focused(self) == false: if EventSystem.is_focused(self) == false||disabled:
return return
text=EventKey.key_to_string(event.key, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length()) text=EventKey.key_to_string(event.key, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length())
@ -58,6 +72,9 @@ func _input(event):
keyboard_input = !keyboard_input keyboard_input = !keyboard_input
return return
if disabled:
return
if keyboard_input: if keyboard_input:
text = EventKey.key_to_string(event.keycode, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length()) text = EventKey.key_to_string(event.keycode, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length())
caret.position.x = text_handler.get_caret_position() caret.position.x = text_handler.get_caret_position()
@ -70,10 +87,16 @@ func _process(_delta):
_draw_debug_text_gaps() _draw_debug_text_gaps()
func _on_press_down(event): func _on_press_down(event):
if disabled:
return
var pos_x = label.to_local(event.ray.get_collision_point()).x var pos_x = label.to_local(event.ray.get_collision_point()).x
text_handler.update_caret_position(pos_x) text_handler.update_caret_position(pos_x)
func _on_press_move(event): func _on_press_move(event):
if disabled:
return
var ray_pos = event.ray.global_position var ray_pos = event.ray.global_position
var ray_dir = -event.ray.global_transform.basis.z var ray_dir = -event.ray.global_transform.basis.z
@ -92,6 +115,9 @@ func _on_press_move(event):
label.text = text_handler.get_display_text() label.text = text_handler.get_display_text()
func _on_focus_in(_event): func _on_focus_in(_event):
if disabled:
return
caret.position.x = text_handler.get_caret_position() caret.position.x = text_handler.get_caret_position()
label.text = text_handler.get_display_text() label.text = text_handler.get_display_text()
caret.show() caret.show()
@ -114,8 +140,10 @@ func update_caret_position(event):
caret.position.x = text_handler.get_caret_position() caret.position.x = text_handler.get_caret_position()
func _on_focus_out(_event): func _on_focus_out(_event):
if disabled:
return
animation.stop() animation.stop()
caret.hide() caret.hide()

View File

@ -33,8 +33,6 @@ func set_text(value: String, insert: bool=false):
overflow_index = _calculate_overflow_index() overflow_index = _calculate_overflow_index()
focus_caret() focus_caret()
print(overflow_index, " ", char_offset, " ", caret_position)
func get_display_text(): func get_display_text():
# In case all chars fit, return the whole text. # In case all chars fit, return the whole text.
if overflow_index == - 1: if overflow_index == - 1:

View File

@ -6,7 +6,6 @@ const RoomType = preload("res://content/system/house/room/room.gd")
const material_selected = preload ("../room_selected.tres") const material_selected = preload ("../room_selected.tres")
const material_unselected = preload ("../room_unselected.tres") const material_unselected = preload ("../room_unselected.tres")
@onready var room_button = $Button @onready var room_button = $Button
@onready var input = $Input @onready var input = $Input
@onready var rooms_map = $Rooms @onready var rooms_map = $Rooms
@ -17,28 +16,37 @@ var selected_room = null:
room_button.label = "add" room_button.label = "add"
input.text = "Room %s" % (rooms_map.get_child_count() + 1) input.text = "Room %s" % (rooms_map.get_child_count() + 1)
if selected_room != null:
var old_room = get_room(selected_room) var old_room = get_room(selected_room)
if old_room != null: if old_room != null:
old_room.get_node("MeshInstance3D").material_override = material_unselected old_room.get_node("MeshInstance3D").material_override = material_unselected
if value != null: if value != null:
room_button.label = "edit"
input.text = value input.text = value
edit_room = false edit_room = false
var new_room = get_room(value) var new_room = get_room(value)
if new_room != null: if new_room != null:
new_room.get_node("MeshInstance3D").material_override = material_selected new_room.get_node("MeshInstance3D").material_override = material_selected
selected_room = value selected_room = value
var edit_room = false: var edit_room = false:
set(value): set(value):
if value == edit_room:
return
edit_room = value edit_room = value
if value: if value:
room_button.label = "save" room_button.label = "save"
input.disabled = false
else: else:
room_button.label = "edit" room_button.label = "edit"
input.disabled = true
if selected_room != null&&selected_room != input.text:
House.body.rename_room(selected_room, input.text)
selected_room = input.text
func _ready(): func _ready():
if Store.house.is_loaded(): if Store.house.is_loaded():
@ -75,6 +83,9 @@ func _ready():
) )
func get_room(room_name): func get_room(room_name):
if room_name == null:
return null
if rooms_map.has_node("%s"% room_name): if rooms_map.has_node("%s"% room_name):
return rooms_map.get_node("%s"% room_name) return rooms_map.get_node("%s"% room_name)
return null return null

View File

@ -18,3 +18,4 @@ icon = true
[node name="Input" parent="." instance=ExtResource("2_hstw7")] [node name="Input" parent="." instance=ExtResource("2_hstw7")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.12, 0.005, 0.27) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.12, 0.005, 0.27)
text = "Room 1" text = "Room 1"
disabled = true

View File

@ -43,6 +43,7 @@ horizontal_alignment = 0
[node name="InputURL" parent="Content" instance=ExtResource("4_q3x6k")] [node name="InputURL" parent="Content" instance=ExtResource("4_q3x6k")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.18, 0, 0.03) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.18, 0, 0.03)
text = "ws://192.168.0.1:8123" text = "ws://192.168.0.1:8123"
disabled = null
[node name="LabelToken" type="Label3D" parent="Content"] [node name="LabelToken" type="Label3D" parent="Content"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.01, 0, 0.07) transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.01, 0, 0.07)
@ -55,6 +56,7 @@ horizontal_alignment = 0
[node name="InputToken" parent="Content" instance=ExtResource("4_q3x6k")] [node name="InputToken" parent="Content" instance=ExtResource("4_q3x6k")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.18, 0, 0.07) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.18, 0, 0.07)
text = "..." text = "..."
disabled = null
[node name="LabelConnect" type="Label3D" parent="Content"] [node name="LabelConnect" type="Label3D" parent="Content"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.14, 0, 0.12) transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.14, 0, 0.12)

View File

@ -3,6 +3,7 @@ extends Node
class_name CallbackMap class_name CallbackMap
var callbacks := {} var callbacks := {}
var single_callbacks: Array = []
func add(key: Variant, callback: Callable) -> void: func add(key: Variant, callback: Callable) -> void:
_validate_key(key) _validate_key(key)
@ -15,13 +16,9 @@ func add(key: Variant, callback: Callable) -> void:
func add_once(key: Variant, callback: Callable) -> void: func add_once(key: Variant, callback: Callable) -> void:
_validate_key(key) _validate_key(key)
var fn: Callable single_callbacks.append(callback)
fn = func(args: Array): add(key, callback)
remove(key, fn)
callback.callv(args)
add(key, fn)
func remove(key: Variant, callback: Callable) -> void: func remove(key: Variant, callback: Callable) -> void:
_validate_key(key) _validate_key(key)
@ -29,6 +26,9 @@ func remove(key: Variant, callback: Callable) -> void:
if callbacks.has(key): if callbacks.has(key):
callbacks[key].erase(callback) callbacks[key].erase(callback)
if single_callbacks.has(callback):
single_callbacks.erase(callback)
func call_key(key: Variant, args: Array) -> void: func call_key(key: Variant, args: Array) -> void:
_validate_key(key) _validate_key(key)
@ -36,5 +36,8 @@ func call_key(key: Variant, args: Array) -> void:
for callback in callbacks[key]: for callback in callbacks[key]:
callback.callv(args) callback.callv(args)
if single_callbacks.has(callback):
remove(key, callback)
func _validate_key(key: Variant): func _validate_key(key: Variant):
assert(typeof(key) == TYPE_STRING||typeof(key) == TYPE_INT||typeof(key) == TYPE_FLOAT, "key must be a string or number") assert(typeof(key) == TYPE_STRING||typeof(key) == TYPE_INT||typeof(key) == TYPE_FLOAT, "key must be a string or number")

View File

@ -0,0 +1,28 @@
const HASS_API = preload ("../hass.gd")
signal on_authenticated()
var api: HASS_API
var url: String
var token: String
var authenticated := false
func _init(hass: HASS_API, url: String, token: String):
self.api = hass
self.url = url
self.token = token
func handle_message(message):
match message["type"]:
"auth_required":
api.send_packet({"type": "auth", "access_token": self.token})
"auth_ok":
authenticated = true
on_authenticated.emit()
"auth_invalid":
EventSystem.notify("Failed to authenticate with Home Assistant. Check your token and try again.", EventNotify.Type.DANGER)
api.handle_disconnect()
func on_disconnect():
authenticated = false

View File

@ -0,0 +1,19 @@
const HASS_API = preload ("../hass.gd")
var api: HASS_API
var integration_exists: bool = false
func _init(hass: HASS_API):
self.api = hass
func on_connect():
var response = await api.send_request_packet({
"type": "immersive_home/register",
"device_id": OS.get_unique_id(),
"name": OS.get_model_name(),
"version": OS.get_version(),
"platform": OS.get_name(),
})
if response.status == Promise.Status.RESOLVED:
integration_exists = true

View File

@ -1,5 +1,8 @@
extends Node extends Node
const AuthHandler = preload ("./handlers/auth.gd")
const IntegrationHandler = preload ("./handlers/integration.gd")
signal on_connect() signal on_connect()
signal on_disconnect() signal on_disconnect()
var connected := false var connected := false
@ -13,23 +16,30 @@ var request_timeout := 10.0
var url := "" var url := ""
var token := "" var token := ""
var LOG_MESSAGES := false var LOG_MESSAGES := false
var authenticated := false
var id := 1 var id := 1
var entities: Dictionary = {} var entities: Dictionary = {}
var entitiy_callbacks := CallbackMap.new() var entitiy_callbacks := CallbackMap.new()
var packet_callbacks := CallbackMap.new() var packet_callbacks := CallbackMap.new()
var auth_handler: AuthHandler
var integration_handler: IntegrationHandler
func _init(url:=self.url, token:=self.token): func _init(url:=self.url, token:=self.token):
self.url = url self.url = url
self.token = token self.token = token
auth_handler = AuthHandler.new(self, url, token)
integration_handler = IntegrationHandler.new(self)
devices_template = devices_template.replace("\n", " ").replace("\t", "").replace("\r", " ") devices_template = devices_template.replace("\n", " ").replace("\t", "").replace("\r", " ")
connect_ws() connect_ws()
auth_handler.on_authenticated.connect(func():
start_subscriptions()
)
func connect_ws(): func connect_ws():
if url == ""||token == "": if url == ""||token == "":
return return
@ -71,38 +81,12 @@ func _process(delta):
func handle_packet(packet: Dictionary): func handle_packet(packet: Dictionary):
if LOG_MESSAGES: print("Received packet: %s" % str(packet).substr(0, 1000)) if LOG_MESSAGES: print("Received packet: %s" % str(packet).substr(0, 1000))
if packet.type == "auth_required": auth_handler.handle_message(packet)
send_packet({
"type": "auth",
"access_token": self.token
})
elif packet.type == "auth_ok": if packet.has("id"):
authenticated = true
start_subscriptions()
elif packet.type == "auth_invalid":
EventSystem.notify("Failed to authenticate, invalid auth token", EventNotify.Type.DANGER)
print("Failed to authenticate, invalid auth token")
handle_disconnect()
else:
packet_callbacks.call_key(int(packet.id), [packet]) packet_callbacks.call_key(int(packet.id), [packet])
func start_subscriptions(): func start_subscriptions():
assert(authenticated, "Not authenticated")
# await send_request_packet({
# "type": "supported_features",
# "features": {
# "coalesce_messages": 1
# }
# })
# await send_request_packet({
# "type": "subscribe_events",
# "event_type": "state_changed"
# })
send_subscribe_packet({ send_subscribe_packet({
"type": "subscribe_entities" "type": "subscribe_entities"
}, func(packet: Dictionary): }, func(packet: Dictionary):
@ -116,8 +100,7 @@ func start_subscriptions():
"attributes": packet.event.a[entity]["a"] "attributes": packet.event.a[entity]["a"]
} }
entitiy_callbacks.call_key(entity, [entities[entity]]) entitiy_callbacks.call_key(entity, [entities[entity]])
connected = true handle_connect()
on_connect.emit()
if packet.event.has("c"): if packet.event.has("c"):
for entity in packet.event.c.keys(): for entity in packet.event.c.keys():
@ -132,8 +115,13 @@ func start_subscriptions():
entitiy_callbacks.call_key(entity, [entities[entity]]) entitiy_callbacks.call_key(entity, [entities[entity]])
) )
func handle_connect():
integration_handler.on_connect()
connected = true
on_connect.emit()
func handle_disconnect(): func handle_disconnect():
authenticated = false auth_handler.on_disconnect()
set_process(false) set_process(false)
on_disconnect.emit() on_disconnect.emit()
@ -153,13 +141,10 @@ func send_subscribe_packet(packet: Dictionary, callback: Callable):
}) })
id += 1 id += 1
func send_request_packet(packet: Dictionary, ignore_initial:=false): func send_request_packet(packet: Dictionary, ignore_initial:=false):
packet.id = id packet.id = id
id += 1 id += 1
send_packet(packet)
var promise = Promise.new(func(resolve: Callable, reject: Callable): var promise = Promise.new(func(resolve: Callable, reject: Callable):
var fn: Callable var fn: Callable
@ -187,8 +172,9 @@ func send_request_packet(packet: Dictionary, ignore_initial := false):
timeout.start() timeout.start()
) )
return await promise.settled send_packet(packet)
return await promise.settled
func send_packet(packet: Dictionary): func send_packet(packet: Dictionary):
if LOG_MESSAGES: print("Sending packet: %s" % encode_packet(packet)) if LOG_MESSAGES: print("Sending packet: %s" % encode_packet(packet))
@ -221,14 +207,12 @@ func get_state(entity: String):
return entities[entity] return entities[entity]
return null return null
func watch_state(entity: String, callback: Callable): func watch_state(entity: String, callback: Callable):
entitiy_callbacks.add(entity, callback) entitiy_callbacks.add(entity, callback)
return func(): return func():
entitiy_callbacks.remove(entity, callback) entitiy_callbacks.remove(entity, callback)
func set_state(entity: String, state: String, attributes: Dictionary={}): func set_state(entity: String, state: String, attributes: Dictionary={}):
var domain = entity.split(".")[0] var domain = entity.split(".")[0]
var service: String var service: String
@ -271,4 +255,15 @@ func set_state(entity: String, state: String, attributes: Dictionary = {}):
} }
}) })
func has_integration():
return integration_handler.integration_exists
func update_room(room: String):
var response = await send_request_packet({
"type": "immersive_home/update",
"device_id": OS.get_unique_id(),
"room": room
})
if response.status == Promise.Status.RESOLVED:
pass