commit
fb378954d2
14
README.md
14
README.md
|
@ -31,6 +31,20 @@ Communication with the Smart Home Environment is done using the `HomeAdapters` g
|
||||||
A device is a collection of different entities and entities can represent many different things in a smart home.
|
A device is a collection of different entities and entities can represent many different things in a smart home.
|
||||||
For example, the entity of name `lights.smart_lamp_1` would control the kitchen lamps while `state.smart_lamp_1_temp` would show the current temperature of the lamp.
|
For example, the entity of name `lights.smart_lamp_1` would control the kitchen lamps while `state.smart_lamp_1_temp` would show the current temperature of the lamp.
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── addons (All installed Godot Addons are saved here)
|
||||||
|
├── assets (Files like logos or assets that are shared across scenes)
|
||||||
|
├── content/ (Main files of the project)
|
||||||
|
│ ├── entities (Entities that can be placed into the room)
|
||||||
|
│ └── ui (User Interface Scenes and related files)
|
||||||
|
└── lib/ (Code that is global or shared across scenes)
|
||||||
|
├── globals (Globally running scripts)
|
||||||
|
└── home_adapters (Code allowing control smart home entities)
|
||||||
|
```
|
||||||
|
|
||||||
### Home Adapters
|
### Home Adapters
|
||||||
|
|
||||||
The `HomeAdapters` global allows to communicate with different backends and offers a set of fundamental functions allowing communication with the Smart Home.
|
The `HomeAdapters` global allows to communicate with different backends and offers a set of fundamental functions allowing communication with the Smart Home.
|
||||||
|
|
|
@ -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_ws.get_state(entity_id)
|
var stateInfo = await HomeAdapters.adapter.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_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:
|
if sprite.get_frame() == 0:
|
||||||
sprite.set_frame(1)
|
sprite.set_frame(1)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -5,10 +5,10 @@ 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_ws.get_state(entity_id)
|
var stateInfo = await HomeAdapters.adapter.get_state(entity_id)
|
||||||
label.text = stateInfo["state"]
|
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"]
|
label.text = new_state["state"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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_ws.get_state(entity_id)
|
var stateInfo = await HomeAdapters.adapter.get_state(entity_id)
|
||||||
if stateInfo == null:
|
if stateInfo == null:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ func _ready():
|
||||||
else:
|
else:
|
||||||
sprite.set_frame(1)
|
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":
|
if new_state["state"] == "on":
|
||||||
sprite.set_frame(0)
|
sprite.set_frame(0)
|
||||||
else:
|
else:
|
||||||
|
@ -23,7 +23,7 @@ func _ready():
|
||||||
|
|
||||||
|
|
||||||
func _on_toggle():
|
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:
|
if sprite.get_frame() == 0:
|
||||||
sprite.set_frame(1)
|
sprite.set_frame(1)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -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="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/raycast.gd" id="1_tsqxc"]
|
||||||
[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="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://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="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"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cscl5k7lhopj5" path="res://content/entities/switch/switch.tscn" id="8_uxmrb"]
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_m58yb"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_m58yb"]
|
||||||
ao_enabled = true
|
ao_enabled = true
|
||||||
|
@ -43,9 +41,6 @@ pose = &"aim"
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="XROrigin3D/XRControllerLeft"]
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="XROrigin3D/XRControllerLeft"]
|
||||||
mesh = SubResource("BoxMesh_ir3co")
|
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")]
|
[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)
|
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")]
|
[node name="XRSimulator" parent="." instance=ExtResource("5_3qc8g")]
|
||||||
xr_origin = NodePath("../XROrigin3D")
|
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"
|
|
||||||
|
|
|
@ -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
|
|
|
@ -12,7 +12,7 @@ var devices
|
||||||
var selected_device = null
|
var selected_device = null
|
||||||
# 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():
|
||||||
devices = await HomeAdapters.adapter.load_devices()
|
devices = await HomeAdapters.adapter.get_devices()
|
||||||
render_devices()
|
render_devices()
|
||||||
|
|
||||||
func render_devices():
|
func render_devices():
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
extends Node
|
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 = Adapter.new(Adapter.ADAPTER_TYPES.HASS_WS)
|
||||||
var adapter_ws = Adapter.new(Adapter.ADAPTER_TYPES.HASS_WS)
|
# var adapter_http = Adapter.new(Adapter.ADAPTER_TYPES.HASS)
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
add_child(adapter)
|
add_child(adapter)
|
||||||
add_child(adapter_ws)
|
# add_child(adapter_http)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const hass = preload("res://lib/home_adapters/hass/hass.gd")
|
const Hass = preload("res://lib/home_adapters/hass/hass.gd")
|
||||||
const hass_ws = preload("res://lib/home_adapters/hass_ws/hass.gd")
|
const HassWebSocket = preload("res://lib/home_adapters/hass_ws/hass.gd")
|
||||||
|
|
||||||
enum ADAPTER_TYPES {
|
enum ADAPTER_TYPES {
|
||||||
HASS,
|
HASS,
|
||||||
|
@ -9,14 +9,16 @@ enum ADAPTER_TYPES {
|
||||||
}
|
}
|
||||||
|
|
||||||
const adapters = {
|
const adapters = {
|
||||||
ADAPTER_TYPES.HASS: hass,
|
ADAPTER_TYPES.HASS: Hass,
|
||||||
ADAPTER_TYPES.HASS_WS: hass_ws
|
ADAPTER_TYPES.HASS_WS: HassWebSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
const methods = [
|
const methods = [
|
||||||
"load_devices",
|
"get_devices",
|
||||||
|
"get_device",
|
||||||
"get_state",
|
"get_state",
|
||||||
"set_state"
|
"set_state",
|
||||||
|
"watch_state"
|
||||||
]
|
]
|
||||||
|
|
||||||
var adapter: Node
|
var adapter: Node
|
||||||
|
@ -27,15 +29,23 @@ func _init(type: ADAPTER_TYPES):
|
||||||
|
|
||||||
for method in methods:
|
for method in methods:
|
||||||
assert(adapter.has_method(method), "Adapter does not implement method: " + method)
|
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):
|
func get_state(entity: String):
|
||||||
return await adapter.get_state(entity)
|
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 = {}):
|
func set_state(entity: String, state: String, attributes: Dictionary = {}):
|
||||||
return await adapter.set_state(entity, state, attributes)
|
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):
|
func watch_state(entity: String, callback: Callable):
|
||||||
return adapter.watch_state(entity, callback)
|
return adapter.watch_state(entity, callback)
|
||||||
|
|
|
@ -13,7 +13,7 @@ func _init(url := self.url, token := self.token):
|
||||||
headers = PackedStringArray(["Authorization: Bearer %s" % token, "Content-Type: application/json"])
|
headers = PackedStringArray(["Authorization: Bearer %s" % token, "Content-Type: application/json"])
|
||||||
devices_template = devices_template.replace("\n", " ").replace("\t", "").replace("\r", " ").replace("\"", "\\\"")
|
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])
|
Request.request("%s/api/template" % [url], headers, HTTPClient.METHOD_POST, "{\"template\": \"%s\"}" % [devices_template])
|
||||||
var response = await Request.request_completed
|
var response = await Request.request_completed
|
||||||
var data_string = response[3].get_string_from_utf8().replace("'", "\"")
|
var data_string = response[3].get_string_from_utf8().replace("'", "\"")
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
extends Node
|
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()
|
var socket := WebSocketPeer.new()
|
||||||
# in seconds
|
# in seconds
|
||||||
var request_timeout := 10.0
|
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 LOG_MESSAGES := true
|
||||||
|
|
||||||
var authenticated := false
|
var authenticated := false
|
||||||
var loading := true
|
var loading := true
|
||||||
|
@ -24,7 +24,7 @@ func _init(url := self.url, token := self.token):
|
||||||
self.url = url
|
self.url = url
|
||||||
self.token = token
|
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()
|
connect_ws()
|
||||||
|
|
||||||
func connect_ws():
|
func connect_ws():
|
||||||
|
@ -56,7 +56,7 @@ func _process(delta):
|
||||||
handle_disconnect()
|
handle_disconnect()
|
||||||
|
|
||||||
func handle_packet(packet: Dictionary):
|
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":
|
if packet.type == "auth_required":
|
||||||
send_packet({
|
send_packet({
|
||||||
|
@ -139,21 +139,34 @@ func send_subscribe_packet(packet: Dictionary, callback: Callable):
|
||||||
id += 1
|
id += 1
|
||||||
|
|
||||||
|
|
||||||
func send_request_packet(packet: Dictionary):
|
func send_request_packet(packet: Dictionary, ignore_initial := false):
|
||||||
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):
|
||||||
packet_callbacks.add_once(packet.id, resolve)
|
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()
|
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_callbacks.remove(packet.id, resolve)
|
if ignore_initial:
|
||||||
|
packet_callbacks.remove(packet.id, fn)
|
||||||
|
else:
|
||||||
|
packet_callbacks.remove(packet.id, resolve)
|
||||||
)
|
)
|
||||||
add_child(timeout)
|
add_child(timeout)
|
||||||
timeout.start()
|
timeout.start()
|
||||||
|
@ -163,7 +176,7 @@ func send_request_packet(packet: Dictionary):
|
||||||
|
|
||||||
|
|
||||||
func send_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))
|
socket.send_text(encode_packet(packet))
|
||||||
|
|
||||||
func decode_packet(packet: PackedByteArray):
|
func decode_packet(packet: PackedByteArray):
|
||||||
|
@ -172,11 +185,21 @@ func decode_packet(packet: PackedByteArray):
|
||||||
func encode_packet(packet: Dictionary):
|
func encode_packet(packet: Dictionary):
|
||||||
return JSON.stringify(packet)
|
return JSON.stringify(packet)
|
||||||
|
|
||||||
func load_devices():
|
func get_devices():
|
||||||
if loading:
|
if loading:
|
||||||
await on_connect
|
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):
|
func get_state(entity: String):
|
||||||
if loading:
|
if loading:
|
||||||
|
@ -193,6 +216,9 @@ func watch_state(entity: String, callback: Callable):
|
||||||
|
|
||||||
entitiy_callbacks.add(entity, callback)
|
entitiy_callbacks.add(entity, callback)
|
||||||
|
|
||||||
|
return func():
|
||||||
|
entitiy_callbacks.remove(entity, callback)
|
||||||
|
|
||||||
|
|
||||||
func set_state(entity: String, state: String, attributes: Dictionary = {}):
|
func set_state(entity: String, state: String, attributes: Dictionary = {}):
|
||||||
assert(!loading, "Still loading")
|
assert(!loading, "Still loading")
|
||||||
|
@ -211,6 +237,9 @@ func set_state(entity: String, state: String, attributes: Dictionary = {}):
|
||||||
elif state == 'off':
|
elif state == 'off':
|
||||||
service = 'turn_off'
|
service = 'turn_off'
|
||||||
|
|
||||||
|
if service == null:
|
||||||
|
return null
|
||||||
|
|
||||||
return await send_request_packet({
|
return await send_request_packet({
|
||||||
"type": "call_service",
|
"type": "call_service",
|
||||||
"domain": domain,
|
"domain": domain,
|
||||||
|
|
|
@ -7,6 +7,4 @@
|
||||||
{%- set ns.devices = ns.devices + [ {device: {"name": device_attr(device, "name"), "entities": entities }} ] %}
|
{%- set ns.devices = ns.devices + [ {device: {"name": device_attr(device, "name"), "entities": entities }} ] %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{
|
{{ ns.devices }}
|
||||||
"data": {{ ns.devices }}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user