2023-11-03 00:13:55 +02:00
extends Node
2023-11-05 17:36:13 +02:00
var devices_template : = FileAccess . get_file_as_string ( " res://lib/home_adapters/hass_ws/templates/devices.j2 " )
2023-11-03 03:07:53 +02:00
var socket : = WebSocketPeer . new ( )
2023-11-03 00:13:55 +02:00
# in seconds
2023-11-03 03:07:53 +02:00
var request_timeout : = 10.0
2023-11-03 00:13:55 +02:00
2023-11-03 03:07:53 +02:00
var url : = " ws://192.168.33.33:8123/api/websocket "
var token : = " eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZjQ0ZGM2N2Y3YzY0MDc1OGZlMWI2ZjJlNmIxZjRkNSIsImlhdCI6MTY5ODAxMDcyOCwiZXhwIjoyMDEzMzcwNzI4fQ.K6ydLUC-4Q7BNIRCU1nWlI2s6sg9UCiOu-Lpedw2zJc "
2023-11-14 00:25:59 +02:00
# var url := "wss://8ybjhqcinfcdyvzu.myfritz.net:8123/api/websocket"
# var token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjNjU0ZDE3NDc2ZGM0NzU1OGY5NjIzMmM5ZjdjYzE2YSIsImlhdCI6MTY5OTgyMzcxOCwiZXhwIjoyMDE1MTgzNzE4fQ.XHlfnXUd16HaV9XjYrxzuNg23nFFeoEsIsaMVXwRkd8"
2023-11-05 22:32:50 +02:00
var LOG_MESSAGES : = false
2023-11-03 00:13:55 +02:00
2023-11-03 03:07:53 +02:00
var authenticated : = false
2023-11-05 14:59:33 +02:00
var loading : = true
2023-11-03 03:07:53 +02:00
var id : = 1
var entities : Dictionary = { }
2023-11-03 00:13:55 +02:00
2023-11-03 03:07:53 +02:00
var entitiy_callbacks : = CallbackMap . new ( )
var packet_callbacks : = CallbackMap . new ( )
signal on_connect ( )
signal on_disconnect ( )
2023-11-03 00:13:55 +02:00
func _init ( url : = self . url , token : = self . token ) :
self . url = url
self . token = token
2023-11-05 17:36:13 +02:00
devices_template = devices_template . replace ( " \n " , " " ) . replace ( " \t " , " " ) . replace ( " \r " , " " )
2023-11-03 00:13:55 +02:00
connect_ws ( )
func connect_ws ( ) :
print ( " Connecting to %s " % self . url )
socket . connect_to_url ( self . url )
2023-11-16 20:23:17 +02:00
set_process ( true )
2023-11-03 00:13:55 +02:00
2023-11-03 23:00:05 +02:00
# https://github.com/godotengine/godot/issues/84423
# Otherwise the WebSocketPeer will crash when receiving large packets
socket . set_inbound_buffer_size ( 65535 * 2 )
2023-11-03 00:13:55 +02:00
func _process ( delta ) :
socket . poll ( )
2023-11-03 03:07:53 +02:00
2023-11-03 00:13:55 +02:00
var state = socket . get_ready_state ( )
if state == WebSocketPeer . STATE_OPEN :
while socket . get_available_packet_count ( ) :
2023-11-03 23:00:05 +02:00
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 )
2023-11-03 00:13:55 +02:00
elif state == WebSocketPeer . STATE_CLOSING :
pass
elif state == WebSocketPeer . STATE_CLOSED :
var code = socket . get_close_code ( )
var reason = socket . get_close_reason ( )
print ( " WS connection closed with code: %s , reason: %s " % [ code , reason ] )
2023-11-03 03:07:53 +02:00
handle_disconnect ( )
2023-11-03 00:13:55 +02:00
2023-11-03 23:00:05 +02:00
func handle_packet ( packet : Dictionary ) :
2023-11-05 17:36:13 +02:00
if LOG_MESSAGES : print ( " Received packet: %s " % str ( packet ) . substr ( 0 , 1000 ) )
2023-11-03 00:13:55 +02:00
if packet . type == " auth_required " :
send_packet ( {
" type " : " auth " ,
" access_token " : self . token
} )
elif packet . type == " auth_ok " :
authenticated = true
2023-11-03 03:07:53 +02:00
start_subscriptions ( )
2023-11-03 00:13:55 +02:00
elif packet . type == " auth_invalid " :
2023-11-03 03:07:53 +02:00
handle_disconnect ( )
2023-11-03 00:13:55 +02:00
else :
2023-11-03 23:00:05 +02:00
packet_callbacks . call_key ( int ( packet . id ) , [ packet ] )
2023-11-03 03:07:53 +02:00
func start_subscriptions ( ) :
assert ( authenticated , " Not authenticated " )
2023-11-03 23:00:05 +02:00
# await send_request_packet({
# "type": "supported_features",
# "features": {
# "coalesce_messages": 1
# }
# })
# await send_request_packet({
# "type": "subscribe_events",
# "event_type": "state_changed"
# })
2023-11-03 03:07:53 +02:00
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 ( ) :
2023-11-03 23:00:05 +02:00
entities [ entity ] = {
" state " : packet . event . a [ entity ] [ " s " ] ,
" attributes " : packet . event . a [ entity ] [ " a " ]
}
2023-11-03 03:07:53 +02:00
entitiy_callbacks . call_key ( entity , [ entities [ entity ] ] )
2023-11-05 14:59:33 +02:00
loading = false
2023-11-03 03:07:53 +02:00
on_connect . emit ( )
if packet . event . has ( " c " ) :
for entity in packet . event . c . keys ( ) :
2023-11-03 23:00:05 +02:00
if ! entities . has ( entity ) :
continue
2023-11-03 03:07:53 +02:00
if packet . event . c [ entity ] . has ( " + " ) :
2023-11-03 23:00:05 +02:00
if packet . event . c [ entity ] [ " + " ] . has ( " s " ) :
entities [ entity ] [ " state " ] = packet . event . c [ entity ] [ " + " ] [ " s " ]
if packet . event . c [ entity ] [ " + " ] . has ( " a " ) :
2023-11-12 18:36:23 +02:00
entities [ entity ] [ " attributes " ] . merge ( packet . event . c [ entity ] [ " + " ] [ " a " ] , true )
2023-11-03 03:07:53 +02:00
entitiy_callbacks . call_key ( entity , [ entities [ entity ] ] )
)
func handle_disconnect ( ) :
authenticated = false
set_process ( false )
on_disconnect . emit ( )
2023-11-16 20:23:17 +02:00
# Reconnect
connect_ws ( )
2023-11-03 03:07:53 +02:00
func send_subscribe_packet ( packet : Dictionary , callback : Callable ) :
packet . id = id
id += 1
packet_callbacks . add ( packet . id , callback )
2023-11-03 23:00:05 +02:00
send_packet ( packet )
2023-11-03 03:07:53 +02:00
return func ( ) :
packet_callbacks . remove ( packet . id , callback )
send_packet ( {
id : id ,
" type " : packet . type . replace ( " subscribe " , " unsubscribe " ) ,
" subscription " : packet . id
} )
id += 1
2023-11-03 00:13:55 +02:00
2023-11-05 17:36:13 +02:00
func send_request_packet ( packet : Dictionary , ignore_initial : = false ) :
2023-11-03 00:13:55 +02:00
packet . id = id
id += 1
send_packet ( packet )
2023-11-05 17:36:13 +02:00
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 )
2023-11-03 00:13:55 +02:00
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 " ) )
2023-11-05 17:36:13 +02:00
if ignore_initial :
packet_callbacks . remove ( packet . id , fn )
else :
packet_callbacks . remove ( packet . id , resolve )
2023-11-03 00:13:55 +02:00
)
add_child ( timeout )
timeout . start ( )
)
return await promise . settled
func send_packet ( packet : Dictionary ) :
2023-11-05 17:36:13 +02:00
if LOG_MESSAGES : print ( " Sending packet: %s " % encode_packet ( packet ) )
2023-11-03 00:13:55 +02:00
socket . send_text ( encode_packet ( packet ) )
func decode_packet ( packet : PackedByteArray ) :
return JSON . parse_string ( packet . get_string_from_utf8 ( ) )
func encode_packet ( packet : Dictionary ) :
return JSON . stringify ( packet )
2023-11-05 17:36:13 +02:00
func get_devices ( ) :
2023-11-05 14:59:33 +02:00
if loading :
2023-11-03 23:00:05 +02:00
await on_connect
2023-11-05 17:36:13 +02:00
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
2023-11-03 00:13:55 +02:00
func get_state ( entity : String ) :
2023-11-05 14:59:33 +02:00
if loading :
2023-11-03 03:07:53 +02:00
await on_connect
2023-11-03 00:13:55 +02:00
2023-11-03 03:07:53 +02:00
if entities . has ( entity ) :
return entities [ entity ]
2023-11-03 23:00:05 +02:00
return null
2023-11-03 00:13:55 +02:00
func watch_state ( entity : String , callback : Callable ) :
2023-11-05 14:59:33 +02:00
if loading :
2023-11-03 03:07:53 +02:00
await on_connect
entitiy_callbacks . add ( entity , callback )
2023-11-03 00:13:55 +02:00
2023-11-05 17:36:13 +02:00
return func ( ) :
entitiy_callbacks . remove ( entity , callback )
2023-11-03 00:13:55 +02:00
func set_state ( entity : String , state : String , attributes : Dictionary = { } ) :
2023-11-05 14:59:33 +02:00
assert ( ! loading , " Still loading " )
2023-11-03 00:13:55 +02:00
2023-11-03 03:07:53 +02:00
var domain = entity . split ( " . " ) [ 0 ]
2023-11-03 23:00:05 +02:00
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 '
2023-11-03 03:07:53 +02:00
2023-11-05 17:36:13 +02:00
if service == null :
return null
2023-11-03 03:07:53 +02:00
return await send_request_packet ( {
" type " : " call_service " ,
" domain " : domain ,
" service " : service ,
" service_data " : attributes ,
" target " : {
" entity_id " : entity
}
} )
2023-11-03 00:13:55 +02:00