finally fix websockets and add sensor entity

This commit is contained in:
Nitwel 2023-11-03 22:00:05 +01:00
parent 3d411188a6
commit f3f0c93547
5 changed files with 101 additions and 22 deletions

View File

@ -0,0 +1,15 @@
[gd_scene load_steps=3 format=3 uid="uid://xsiy71rsqulj"]
[ext_resource type="Script" path="res://src/entities/sensor.gd" id="1_57ac8"]
[sub_resource type="SphereShape3D" id="SphereShape3D_r20gc"]
radius = 0.1
[node name="Sensor" type="StaticBody3D"]
script = ExtResource("1_57ac8")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("SphereShape3D_r20gc")
[node name="Label" type="Label3D" parent="."]
text = "some text"

16
src/entities/sensor.gd Normal file
View File

@ -0,0 +1,16 @@
extends StaticBody3D
@export var entity_id = "sensor.sun_next_dawn"
@onready var label: Label3D = $Label
# Called when the node enters the scene tree for the first time.
func _ready():
var stateInfo = await HomeAdapters.adapter_ws.get_state(entity_id)
label.text = stateInfo["state"]
await HomeAdapters.adapter_ws.watch_state(entity_id, func(new_state):
label.text = new_state["state"]
)
func _on_toggle():
pass

View File

@ -11,6 +11,13 @@ func _ready():
else: else:
sprite.set_frame(1) sprite.set_frame(1)
await HomeAdapters.adapter_ws.watch_state(entity_id, func(new_state):
if new_state["state"] == "on":
sprite.set_frame(0)
else:
sprite.set_frame(1)
)
func _on_toggle(): func _on_toggle():
HomeAdapters.adapter_ws.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")

View File

@ -7,7 +7,7 @@ var request_timeout := 10.0
var url := "ws://192.168.33.33:8123/api/websocket" var url := "ws://192.168.33.33:8123/api/websocket"
var token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZjQ0ZGM2N2Y3YzY0MDc1OGZlMWI2ZjJlNmIxZjRkNSIsImlhdCI6MTY5ODAxMDcyOCwiZXhwIjoyMDEzMzcwNzI4fQ.K6ydLUC-4Q7BNIRCU1nWlI2s6sg9UCiOu-Lpedw2zJc" var token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZjQ0ZGM2N2Y3YzY0MDc1OGZlMWI2ZjJlNmIxZjRkNSIsImlhdCI6MTY5ODAxMDcyOCwiZXhwIjoyMDEzMzcwNzI4fQ.K6ydLUC-4Q7BNIRCU1nWlI2s6sg9UCiOu-Lpedw2zJc"
var LOG_MESSAGES := false
var authenticated := false var authenticated := false
var id := 1 var id := 1
@ -30,14 +30,22 @@ func connect_ws():
print("Connecting to %s" % self.url) print("Connecting to %s" % self.url)
socket.connect_to_url(self.url) socket.connect_to_url(self.url)
# https://github.com/godotengine/godot/issues/84423
# Otherwise the WebSocketPeer will crash when receiving large packets
socket.set_inbound_buffer_size(65535 * 2)
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()) var packet = decode_packet(socket.get_packet())
if typeof(packet) == TYPE_DICTIONARY:
handle_packet(packet)
elif typeof(packet) == TYPE_ARRAY:
for p in packet:
handle_packet(p)
elif state == WebSocketPeer.STATE_CLOSING: elif state == WebSocketPeer.STATE_CLOSING:
pass pass
elif state == WebSocketPeer.STATE_CLOSED: elif state == WebSocketPeer.STATE_CLOSED:
@ -46,10 +54,8 @@ func _process(delta):
print("WS connection closed with code: %s, reason: %s" % [code, reason]) print("WS connection closed with code: %s, reason: %s" % [code, reason])
handle_disconnect() handle_disconnect()
func handle_packet(raw_packet: PackedByteArray): func handle_packet(packet: Dictionary):
var packet = decode_packet(raw_packet) if LOG_MESSAGES: print("Received packet: %s" % packet)
print("Received packet: %s" % packet)
if packet.type == "auth_required": if packet.type == "auth_required":
send_packet({ send_packet({
@ -64,17 +70,22 @@ func handle_packet(raw_packet: PackedByteArray):
elif packet.type == "auth_invalid": elif packet.type == "auth_invalid":
handle_disconnect() handle_disconnect()
else: else:
packet_callbacks.call_key(packet.id, [packet]) packet_callbacks.call_key(int(packet.id), [packet])
func start_subscriptions(): func start_subscriptions():
assert(authenticated, "Not authenticated") assert(authenticated, "Not authenticated")
await send_request_packet({ # await send_request_packet({
"type": "supported_features", # "type": "supported_features",
"features": { # "features": {
"coalesce_messages": 1 # "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"
@ -84,14 +95,23 @@ func start_subscriptions():
if packet.event.has("a"): if packet.event.has("a"):
for entity in packet.event.a.keys(): for entity in packet.event.a.keys():
entities[entity] = packet.event.a[entity] entities[entity] = {
"state": packet.event.a[entity]["s"],
"attributes": packet.event.a[entity]["a"]
}
entitiy_callbacks.call_key(entity, [entities[entity]]) entitiy_callbacks.call_key(entity, [entities[entity]])
on_connect.emit() 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():
if !entities.has(entity):
continue
if packet.event.c[entity].has("+"): if packet.event.c[entity].has("+"):
entities[entity].merge(packet.event.c[entity]["+"]) if packet.event.c[entity]["+"].has("s"):
entities[entity]["state"] = packet.event.c[entity]["+"]["s"]
if packet.event.c[entity]["+"].has("a"):
entities[entity]["attributes"].merge(packet.event.c[entity]["+"]["a"])
entitiy_callbacks.call_key(entity, [entities[entity]]) entitiy_callbacks.call_key(entity, [entities[entity]])
) )
@ -104,8 +124,8 @@ func send_subscribe_packet(packet: Dictionary, callback: Callable):
packet.id = id packet.id = id
id += 1 id += 1
send_packet(packet)
packet_callbacks.add(packet.id, callback) packet_callbacks.add(packet.id, callback)
send_packet(packet)
return func(): return func():
packet_callbacks.remove(packet.id, callback) packet_callbacks.remove(packet.id, callback)
@ -141,7 +161,7 @@ func send_request_packet(packet: Dictionary):
func send_packet(packet: Dictionary): func send_packet(packet: Dictionary):
print("Sending packet: %s" % encode_packet(packet)) if LOG_MESSAGES || true: print("Sending packet: %s" % encode_packet(packet))
socket.send_text(encode_packet(packet)) socket.send_text(encode_packet(packet))
func decode_packet(packet: PackedByteArray): func decode_packet(packet: PackedByteArray):
@ -151,7 +171,10 @@ func encode_packet(packet: Dictionary):
return JSON.stringify(packet) return JSON.stringify(packet)
func load_devices(): func load_devices():
pass if !authenticated:
await on_connect
return entities
func get_state(entity: String): func get_state(entity: String):
if !authenticated: if !authenticated:
@ -159,8 +182,7 @@ func get_state(entity: String):
if entities.has(entity): if entities.has(entity):
return entities[entity] return entities[entity]
else: return null
print(entities, entity)
func watch_state(entity: String, callback: Callable): func watch_state(entity: String, callback: Callable):
@ -174,7 +196,18 @@ func set_state(entity: String, state: String, attributes: Dictionary = {}):
assert(authenticated, "Not authenticated") assert(authenticated, "Not authenticated")
var domain = entity.split(".")[0] var domain = entity.split(".")[0]
var service = entity.split(".")[1] var service: String
if domain == 'switch':
if state == 'on':
service = 'turn_on'
elif state == 'off':
service = 'turn_off'
elif domain == 'light':
if state == 'on':
service = 'turn_on'
elif state == 'off':
service = 'turn_off'
return await send_request_packet({ return await send_request_packet({
"type": "call_service", "type": "call_service",

View File

@ -4,6 +4,7 @@ const Device = preload("res://scenes/device.tscn")
const Entity = preload("res://scenes/entity.tscn") const Entity = preload("res://scenes/entity.tscn")
const Switch = preload("res://scenes/entities/switch.tscn") const Switch = preload("res://scenes/entities/switch.tscn")
const Light = preload("res://scenes/entities/light.tscn") const Light = preload("res://scenes/entities/light.tscn")
const Sensor = preload("res://scenes/entities/sensor.tscn")
@onready var devices_node = $Devices @onready var devices_node = $Devices
var devices var devices
@ -94,6 +95,13 @@ func _on_entity_click(entity_name):
light.set_position(global_position) light.set_position(global_position)
get_node("/root").add_child(light) get_node("/root").add_child(light)
if type == "sensor":
var sensor = Sensor.instantiate()
sensor.entity_id = entity_name
sensor.set_position(global_position)
get_node("/root").add_child(sensor)
func clear_menu(): func clear_menu():
for child in devices_node.get_children(): for child in devices_node.get_children():
devices_node.remove_child(child) devices_node.remove_child(child)