continue working on websocket implementation

This commit is contained in:
Nitwel 2023-11-03 02:07:53 +01:00
parent 40adc7b8ef
commit 3d411188a6
7 changed files with 142 additions and 55 deletions

View File

@ -5,7 +5,7 @@ extends StaticBody3D
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready(): func _ready():
var stateInfo = await HomeAdapters.adapter.get_state(entity_id) var stateInfo = await HomeAdapters.adapter_ws.get_state(entity_id)
if stateInfo["state"] == "on": if stateInfo["state"] == "on":
sprite.set_frame(0) sprite.set_frame(0)
else: else:
@ -13,7 +13,7 @@ func _ready():
func _on_toggle(): func _on_toggle():
HomeAdapters.adapter.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on") HomeAdapters.adapter_ws.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on")
if sprite.get_frame() == 0: if sprite.get_frame() == 0:
sprite.set_frame(1) sprite.set_frame(1)
else: else:

View File

@ -5,7 +5,7 @@ extends StaticBody3D
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready(): func _ready():
var stateInfo = await HomeAdapters.adapter.get_state(entity_id) var stateInfo = await HomeAdapters.adapter_ws.get_state(entity_id)
if stateInfo["state"] == "on": if stateInfo["state"] == "on":
sprite.set_frame(0) sprite.set_frame(0)
else: else:
@ -13,7 +13,7 @@ func _ready():
func _on_toggle(): func _on_toggle():
HomeAdapters.adapter.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on") HomeAdapters.adapter_ws.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on")
if sprite.get_frame() == 0: if sprite.get_frame() == 0:
sprite.set_frame(1) sprite.set_frame(1)
else: else:

View File

@ -6,21 +6,6 @@ var adapter = Adapter.new(Adapter.ADAPTER_TYPES.HASS)
var adapter_ws = Adapter.new(Adapter.ADAPTER_TYPES.HASS_WS) var adapter_ws = Adapter.new(Adapter.ADAPTER_TYPES.HASS_WS)
func _ready(): func _ready():
add_child(adapter)
add_child(adapter_ws) add_child(adapter_ws)
var timer = Timer.new()
timer.set_wait_time(1)
timer.set_one_shot(true)
print("timer started")
timer.timeout.connect(func():
print("timer done")
var result = await adapter_ws.get_state("light.living_room")
print(result)
)
add_child(timer)
timer.start()

View File

@ -0,0 +1,40 @@
extends Node
class_name CallbackMap
var callbacks := {}
func add(key: Variant, callback: Callable) -> void:
_validate_key(key)
if callbacks.has(key):
callbacks[key].append(callback)
else:
callbacks[key] = [callback]
func add_once(key: Variant, callback: Callable) -> void:
_validate_key(key)
var fn: Callable
fn = func(args: Array):
remove(key, fn)
callback.callv(args)
add(key, fn)
func remove(key: Variant, callback: Callable) -> void:
_validate_key(key)
if callbacks.has(key):
callbacks[key].erase(callback)
func call_key(key: Variant, args: Array) -> void:
_validate_key(key)
if callbacks.has(key):
for callback in callbacks[key]:
callback.callv(args)
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")

View File

@ -1,18 +1,23 @@
extends Node extends Node
var devices_template = FileAccess.get_file_as_string("res://src/home_adapters/hass/templates/devices.j2") var devices_template := FileAccess.get_file_as_string("res://src/home_adapters/hass/templates/devices.j2")
var socket = WebSocketPeer.new() var socket := WebSocketPeer.new()
# in seconds # in seconds
var request_timeout: float = 10 var request_timeout := 10.0
var url: String = "ws://192.168.33.33:8123/api/websocket" var url := "ws://192.168.33.33:8123/api/websocket"
var token: String = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZjQ0ZGM2N2Y3YzY0MDc1OGZlMWI2ZjJlNmIxZjRkNSIsImlhdCI6MTY5ODAxMDcyOCwiZXhwIjoyMDEzMzcwNzI4fQ.K6ydLUC-4Q7BNIRCU1nWlI2s6sg9UCiOu-Lpedw2zJc" var token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZjQ0ZGM2N2Y3YzY0MDc1OGZlMWI2ZjJlNmIxZjRkNSIsImlhdCI6MTY5ODAxMDcyOCwiZXhwIjoyMDEzMzcwNzI4fQ.K6ydLUC-4Q7BNIRCU1nWlI2s6sg9UCiOu-Lpedw2zJc"
var authenticated: bool = false var authenticated := false
var id = 1 var id := 1
var entities: Dictionary = {}
signal packet_received(packet: Dictionary) var entitiy_callbacks := CallbackMap.new()
var packet_callbacks := CallbackMap.new()
signal on_connect()
signal on_disconnect()
func _init(url := self.url, token := self.token): func _init(url := self.url, token := self.token):
self.url = url self.url = url
@ -27,8 +32,9 @@ func connect_ws():
func _process(delta): func _process(delta):
socket.poll() socket.poll()
var state = socket.get_ready_state() var state = socket.get_ready_state()
print(state, "POLLING")
if state == WebSocketPeer.STATE_OPEN: if state == WebSocketPeer.STATE_OPEN:
while socket.get_available_packet_count(): while socket.get_available_packet_count():
handle_packet(socket.get_packet()) handle_packet(socket.get_packet())
@ -38,8 +44,7 @@ func _process(delta):
var code = socket.get_close_code() var code = socket.get_close_code()
var reason = socket.get_close_reason() var reason = socket.get_close_reason()
print("WS connection closed with code: %s, reason: %s" % [code, reason]) print("WS connection closed with code: %s, reason: %s" % [code, reason])
authenticated = false handle_disconnect()
set_process(false)
func handle_packet(raw_packet: PackedByteArray): func handle_packet(raw_packet: PackedByteArray):
var packet = decode_packet(raw_packet) var packet = decode_packet(raw_packet)
@ -54,36 +59,79 @@ func handle_packet(raw_packet: PackedByteArray):
elif packet.type == "auth_ok": elif packet.type == "auth_ok":
authenticated = true authenticated = true
start_subscriptions()
elif packet.type == "auth_invalid": elif packet.type == "auth_invalid":
authenticated = false handle_disconnect()
print("Authentication failed")
set_process(false)
else: else:
packet_received.emit(packet) packet_callbacks.call_key(packet.id, [packet])
func start_subscriptions():
assert(authenticated, "Not authenticated")
await send_request_packet({
"type": "supported_features",
"features": {
"coalesce_messages": 1
}
})
send_subscribe_packet({
"type": "subscribe_entities"
}, func(packet: Dictionary):
if packet.type != "event":
return
if packet.event.has("a"):
for entity in packet.event.a.keys():
entities[entity] = packet.event.a[entity]
entitiy_callbacks.call_key(entity, [entities[entity]])
on_connect.emit()
if packet.event.has("c"):
for entity in packet.event.c.keys():
if packet.event.c[entity].has("+"):
entities[entity].merge(packet.event.c[entity]["+"])
entitiy_callbacks.call_key(entity, [entities[entity]])
)
func handle_disconnect():
authenticated = false
set_process(false)
on_disconnect.emit()
func send_subscribe_packet(packet: Dictionary, callback: Callable):
packet.id = id
id += 1
send_packet(packet)
packet_callbacks.add(packet.id, callback)
return func():
packet_callbacks.remove(packet.id, callback)
send_packet({
id: id,
"type": packet.type.replace("subscribe", "unsubscribe"),
"subscription": packet.id
})
id += 1
func send_request_packet(packet: Dictionary): func send_request_packet(packet: Dictionary):
packet.id = id packet.id = id
id += 1 id += 1
send_packet(packet) send_packet(packet)
var promise = Promise.new(func(resolve: Callable, reject: Callable): var promise = Promise.new(func(resolve: Callable, reject: Callable):
var handle_packet = func(recieved_packet: Dictionary): packet_callbacks.add_once(packet.id, resolve)
print("Received packet in handler: %s" % recieved_packet)
if packet.id == recieved_packet.id:
print("same id")
resolve.call(recieved_packet)
packet_received.disconnect(handle_packet)
packet_received.connect(handle_packet)
var timeout = Timer.new() var timeout = Timer.new()
timeout.set_wait_time(request_timeout) timeout.set_wait_time(request_timeout)
timeout.set_one_shot(true) timeout.set_one_shot(true)
timeout.timeout.connect(func(): timeout.timeout.connect(func():
reject.call(Promise.Rejection.new("Request timed out")) reject.call(Promise.Rejection.new("Request timed out"))
packet_received.disconnect(handle_packet) packet_callbacks.remove(packet.id, resolve)
) )
add_child(timeout) add_child(timeout)
timeout.start() timeout.start()
@ -106,22 +154,36 @@ func load_devices():
pass pass
func get_state(entity: String): func get_state(entity: String):
assert(authenticated, "Not authenticated") if !authenticated:
await on_connect
var result = await send_request_packet({ if entities.has(entity):
"type": "get_states" return entities[entity]
}) else:
print(entities, entity)
if result.status == Promise.Status.RESOLVED:
return result.payload
return null
func watch_state(entity: String, callback: Callable): func watch_state(entity: String, callback: Callable):
assert(authenticated, "Not authenticated") if !authenticated:
await on_connect
entitiy_callbacks.add(entity, callback)
func set_state(entity: String, state: String, attributes: Dictionary = {}): func set_state(entity: String, state: String, attributes: Dictionary = {}):
assert(authenticated, "Not authenticated") assert(authenticated, "Not authenticated")
var domain = entity.split(".")[0]
var service = entity.split(".")[1]
return await send_request_packet({
"type": "call_service",
"domain": domain,
"service": service,
"service_data": attributes,
"target": {
"entity_id": entity
}
})