NOTE: First post and not a javascript programmer.
I wanted to create a flexible setup with Rhasspy and Node-Red in Homeassistant that wouldn’t require me updating Rhasspy AND my flow for each new item I added to my configuration. Currently, this is only set up for:
- Lights
- Fans
- Covers
- Switches
I only have lights currently, so I was unable to test this out on the other devices, but it should work. Essentially the sentences are set to allow me to simply add a new object within my object slot. This is needed to Rhasspy to interpret the object as a sentence. Once I do this, I don’t need to create a new Node-Red flow based on the new object, or even edit my existing one. Since the flow will gather all of the entities within Homeassistant, it can check to see if it exists based on the variables passed by Rhasspy.
Rhasspy Sentences
[TurnState](turn on | turn off){state} [the] [($room){room}] ($object){object}
[ChangeLightState]
(set | change) the [($room){room}] (light | lights){object} to (($colors){color_name} | (0…100){brightness_pct} percent)
[ChangeCoverState]
(open | close | set){state} the [($room){room}] ($covers){object} [to (0…100){position}]
[ChangeFanState]
(set | change) the [($room){room}] (fan){object} to (low|medium|high){speed}
Rhasspy Slots
colors
BlueRed
....
covers
blindsgarage
window
shutters
gate
shutter
blind
curtains
awning
curtain
shade
door
shades
damper
room
bedroomliving room
loft
game room
master bedroom
kitchen
bathroom
guest bedroom
dining room
hallway
object
desk lampfan
shade
light
table 1
lights
shades
Node Red Flow
[{"id":"70d90eed.9fc7e8","type":"tab","label":"Rhasspy Intent","disabled":false,"info":""},{"id":"df68e344.5897","type":"api-call-service","z":"70d90eed.9fc7e8","name":"Execute","server":"9ca4f6d5.10c6d8","version":1,"debugenabled":false,"service_domain":"","service":"","entityId":"","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":1140,"y":360,"wires":[["6001337d.9617cc"]]},{"id":"bee9de11.1d509","type":"function","z":"70d90eed.9fc7e8","name":"Initial Setup","func":"if (msg.slots.room) {\n msg.slots.room = msg.slots.room.replace(\" \", \"_\");\n}\n\nif (msg.slots.object == \"lights\") {\n msg.slots.object = \"light\";\n}\n\nmsg.slots.object = msg.slots.object.replace(\" \", \"_\");\n\nif (msg.slots.state) {\n msg.slots.state = msg.slots.state.replace(' ', '_');\n} else {\n msg.slots.state = \"\";\n}\n\nmsg.payload = {\n \"data\": {\n \"entity_id\": \"\",\n },\n \"domain\": \"\",\n \"service\": \"\"\n}\n\nObject.entries(msg.slots).forEach(([key, value]) => {\n if (key != 'room' && key != 'object' && key != 'state') {\n msg.payload.data[key] = value;\n }\n});\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":330,"y":360,"wires":[["558c9722.f9d858"]]},{"id":"6001337d.9617cc","type":"debug","z":"70d90eed.9fc7e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1310,"y":360,"wires":[]},{"id":"558c9722.f9d858","type":"ha-get-entities","z":"70d90eed.9fc7e8","server":"9ca4f6d5.10c6d8","name":"Get Entities","rules":[],"output_type":"array","output_empty_results":true,"output_location_type":"msg","output_location":"entities","output_results_count":1,"x":530,"y":360,"wires":[["97fa5f32.9ca14"]]},{"id":"97fa5f32.9ca14","type":"function","z":"70d90eed.9fc7e8","name":"Find Object","func":"if (msg.entities) {\n for (var val in msg.entities) {\n currentEntity = msg.entities[val][\"entity_id\"];\n \n var cur = currentEntity.split('.');\n domain = cur[0];\n obj = cur[1];\n \n if (msg.slots.room) {\n if (obj == msg.slots.room + '_' + msg.slots.object) {\n //e.g. fan.living_room_fan\n //e.g. cover.kitchen_blinds\n msg.payload.data.entity_id = msg.slots.object + '.' + msg.slots.room + '_' + msg.slots.object;\n msg.payload.domain = domain;\n msg.payload.service = msg.slots.state;\n break;\n } else if (domain == msg.slots.object && obj == msg.slots.room) {\n //e.g. light.living_room\n msg.payload.data.entity_id = msg.slots.object + '.' + msg.slots.room;\n msg.payload.domain = domain;\n msg.payload.service = msg.slots.state;\n break;\n }\n } else if (obj == msg.slots.object) {\n //e.g. light.desk_lamp\n //e.g. cover.blinds\n //e.g. fan.ceiling_fan\n msg.payload.data.entity_id = domain + '.' + obj;\n msg.payload.domain = domain;\n msg.payload.service = msg.slots.state;\n break;\n }\n }\n \n //Some variations for lights, fans, and covers\n if (msg.intent.name == \"ChangeLightState\") {\n msg.payload.service = \"turn_on\";\n } else if (msg.intent.name == \"ChangeCoverState\") {\n if (msg.slots.state == \"open\" || msg.slots.state == \"close\") {\n msg.payload.service = msg.slots.state + \"_cover\";\n } else {\n msg.payload.service = \"set_cover_position\";\n }\n } else if (msg.intent.name == \"ChangeFanState\") {\n msg.payload.service = \"set_speed\";\n }\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":730,"y":360,"wires":[["5b1a1dc0.bf6e34"]]},{"id":"5b1a1dc0.bf6e34","type":"switch","z":"70d90eed.9fc7e8","name":"Check Entity ID","property":"payload.data.entity_id","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":960,"y":360,"wires":[["df68e344.5897"]]},{"id":"13ddb6e0.cfcd39","type":"websocket in","z":"70d90eed.9fc7e8","name":"Rhasspy","server":"","client":"be111083.116b5","x":100,"y":340,"wires":[["bee9de11.1d509"]]},{"id":"9ca4f6d5.10c6d8","type":"server","name":"Home Assistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true},{"id":"be111083.116b5","type":"websocket-client","path":"ws://myRhasspyIP:myRhasspyPort/api/events/intent","tls":"","wholemsg":"true"}]
Initial Setup
Just set initial variables, and replace spaces with underscores, etc.if (msg.slots.room) {
msg.slots.room = msg.slots.room.replace(" ", "_");
}
if (msg.slots.object == "lights") {
msg.slots.object = "light";
}
msg.slots.object = msg.slots.object.replace(" ", "_");
if (msg.slots.state) {
msg.slots.state = msg.slots.state.replace(' ', '_');
} else {
msg.slots.state = "";
}
msg.payload = {
"data": {
"entity_id": "",
},
"domain": "",
"service": ""
}
//This is a trick to set variables like brightness_pct, color, and speed automatically
//set as slots in Rhasspy
Object.entries(msg.slots).forEach(([key, value]) => {
if (key != 'room' && key != 'object' && key != 'state') {
msg.payload.data[key] = value;
}
});
return msg;
Get Entities
Just pull all entities and put in to msg.entitiesFind Object
if (msg.entities) {
for (var val in msg.entities) {
currentEntity = msg.entities[val]["entity_id"];
var cur = currentEntity.split('.');
domain = cur[0];
obj = cur[1];
if (msg.slots.room) {
if (obj == msg.slots.room + '_' + msg.slots.object) {
//e.g. fan.living_room_fan
//e.g. cover.kitchen_blinds
msg.payload.data.entity_id = msg.slots.object + '.' + msg.slots.room + '_' + msg.slots.object;
msg.payload.domain = domain;
msg.payload.service = msg.slots.state;
break;
} else if (domain == msg.slots.object && obj == msg.slots.room) {
//e.g. light.living_room
msg.payload.data.entity_id = msg.slots.object + '.' + msg.slots.room;
msg.payload.domain = domain;
msg.payload.service = msg.slots.state;
break;
}
} else if (obj == msg.slots.object) {
//e.g. light.desk_lamp
//e.g. cover.blinds
//e.g. fan.ceiling_fan
msg.payload.data.entity_id = domain + '.' + obj;
msg.payload.domain = domain;
msg.payload.service = msg.slots.state;
break;
}
}
//Some variations for lights, fans, and covers
//This is only needed since services are different for covers and fans
if (msg.intent.name == "ChangeLightState") {
//We always need to set turn_on for lights when adjusting colors and brightness
msg.payload.service = "turn_on";
} else if (msg.intent.name == "ChangeCoverState") {
if (msg.slots.state == "open" || msg.slots.state == "close") {
msg.payload.service = msg.slots.state + "_cover";
} else {
msg.payload.service = "set_cover_position";
}
} else if (msg.intent.name == "ChangeFanState") {
msg.payload.service = "set_speed";
}
}
return msg;