Approaches to room aware voice commands for Alexa with Node-Red

Introduction
Hey all, this is my first post, so go easy on me :wink: I just got started with Hass.io a couple weeks ago and I’ve been addicted ever since. I’ve been working to move the entirety of my home automation into Node-Red and implement some those features we’ve been waiting for the longest, including our desire for room-aware commands. Last night, I succeeded in developing a new, very straightforward approach for making room aware commands with Node-Red. Since I didn’t see this approach documented anywhere, I thought I’d share it here so that others may benefit from it.

TL;DR;
This approach gives you the ability to dynamically issue voice commands to a device based on your own room-aware logic, which you may also combine with other predicates at your discretion. The approach is meant avoid the need to create a custom Alexa Skill or write any custom code, though you may do so if you wish to go the extra mile and improve the implementation.

Prerequisites:

  1. Node-Red installed via the Hass.io add-on (technically, you can do this with vanilla node-red and not just the Hass.io add-on, but I haven’t tested it in a standalone instance yet)
  2. Have 586837r’s node-red-contrib-alexa-remote2 package for Node-Red installed https://flows.nodered.org/node/node-red-contrib-alexa-remote2
  3. (Optional) Have datech’s node-red-contrib-amazon-echo package for Node-Red installed https://flows.nodered.org/node/node-red-contrib-amazon-echo

As noted above, the third package is optional and is required if you want to incorporate device emulation into your flows, as I have. For example, if you are using my solution to issue commands like “turn on the fan” to the fan in the room you’re in then you may want to emulate a device called “Fan”. Note that because I used this package for nodes in my flow, you may have difficulty importing my flow without it. Also, it’s in my manual install instructions, but you can skip the parts that use it if you want to.

My flows:

[{"id":"35964044.fcf53","type":"tab","label":"Contextual Routines","disabled":false,"info":""},{"id":"35bdb0e0.53f29","type":"alexa-remote-event","z":"35964044.fcf53","name":"","account":"d99d5f0e.4d0f5","event":"ws-device-activity","x":110,"y":500,"wires":[["a26a3b25.554268"]]},{"id":"1a5b5511.75c5db","type":"amazon-echo-device","z":"35964044.fcf53","name":"Fan","topic":"","x":330,"y":200,"wires":[[]]},{"id":"545ebb02.da3e74","type":"switch","z":"35964044.fcf53","name":"Route: Contextual Routine","property":"payload.description.summary","propertyType":"msg","rules":[{"t":"regex","v":"turn on (the )?fan","vt":"str","case":false},{"t":"regex","v":"turn (the )?fan on","vt":"str","case":false},{"t":"regex","v":"turn off (the )?fan","vt":"str","case":false},{"t":"regex","v":"turn (the )?fan off","vt":"str","case":false},{"t":"regex","v":"(set|turn) (the )?fan ((on|to) )?low","vt":"str","case":false},{"t":"regex","v":"(set|turn) (the )?fan ((on|to) )?medium","vt":"str","case":false},{"t":"regex","v":"(set|turn) (the )?fan ((on|to) )?high","vt":"str","case":false}],"checkall":"true","repair":false,"outputs":7,"x":600,"y":500,"wires":[["a3767ece.d2e99"],["a3767ece.d2e99"],["b94005ac.8f4828"],["b94005ac.8f4828"],["9a8de045.916b6"],["f6f03dd2.05328"],["951d9ca0.ccb17"]],"outputLabels":["Turn on","Turn on","Turn off","Turn off","Set low","Set medium","Set high"]},{"id":"b94005ac.8f4828","type":"switch","z":"35964044.fcf53","name":"Route: Fan off","property":"payload.deviceSerialNumber","propertyType":"msg","rules":[{"t":"eq","v":"[MASTER BEDROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[GUEST ROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[KID'S ROOM ECHO'S SERIAL NUMBER]","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":920,"y":260,"wires":[["cc815395.81107"],["a71773aa.a7cfa"],["b2c077b2.fa0458"]],"outputLabels":["Master Bedroom","Guest Room","Office"]},{"id":"9a8de045.916b6","type":"switch","z":"35964044.fcf53","name":"Route: Fan low","property":"payload.deviceSerialNumber","propertyType":"msg","rules":[{"t":"eq","v":"[MASTER BEDROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[GUEST ROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[KID'S ROOM ECHO'S SERIAL NUMBER]","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":920,"y":420,"wires":[["1f3f79ac.0d4826"],["1fc26ad0.fdf265"],["bbc0c776.bfe818"]],"outputLabels":["Master Bedroom","Guest Room","Office"]},{"id":"f6f03dd2.05328","type":"switch","z":"35964044.fcf53","name":"Route: Fan medium","property":"payload.deviceSerialNumber","propertyType":"msg","rules":[{"t":"eq","v":"[MASTER BEDROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[GUEST ROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[KID'S ROOM ECHO'S SERIAL NUMBER]","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":930,"y":580,"wires":[["65787ee0.02993"],["1a8e26ed.8395f9"],["5c630a47.ce7804"]],"outputLabels":["Master Bedroom","Guest Room","Office"]},{"id":"951d9ca0.ccb17","type":"switch","z":"35964044.fcf53","name":"Route: Fan high","property":"payload.deviceSerialNumber","propertyType":"msg","rules":[{"t":"eq","v":"[MASTER BEDROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[GUEST ROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[KID'S ROOM ECHO'S SERIAL NUMBER]","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":920,"y":740,"wires":[["3ce2a8d3.776708"],["8a8c5698.828958"],["9a3c1659.0b29e8"]],"outputLabels":["Master Bedroom","Guest Room","Office"]},{"id":"a3767ece.d2e99","type":"switch","z":"35964044.fcf53","name":"Route: Fan on","property":"payload.deviceSerialNumber","propertyType":"msg","rules":[{"t":"eq","v":"[MASTER BEDROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[GUEST ROOM ECHO'S SERIAL NUMBER]","vt":"str"},{"t":"eq","v":"[KID'S ROOM ECHO'S SERIAL NUMBER]","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":920,"y":100,"wires":[["8ba6d633.30fa68"],["b7f0f5d0.1d4938"],["15374f8.3db2bb1"]],"outputLabels":["Master Bedroom","Guest Room","Office"]},{"id":"8ba6d633.30fa68","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"48aa8e73-2067-48f7-9625-3ec34ea69363","action":"turnOn"}]},"outputs":1,"x":1120,"y":60,"wires":[[]]},{"id":"b7f0f5d0.1d4938","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"b0b1ec9e-7723-4826-ba85-f569b465dd6d","action":"turnOn"}]},"outputs":1,"x":1120,"y":100,"wires":[[]]},{"id":"15374f8.3db2bb1","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"a8174fea-b7b1-4331-bf17-5da01a963729","action":"turnOn"}]},"outputs":1,"x":1120,"y":140,"wires":[[]]},{"id":"cc815395.81107","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn off","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"48aa8e73-2067-48f7-9625-3ec34ea69363","action":"turnOff"}]},"outputs":1,"x":1120,"y":220,"wires":[[]]},{"id":"a71773aa.a7cfa","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn off","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"b0b1ec9e-7723-4826-ba85-f569b465dd6d","action":"turnOff"}]},"outputs":1,"x":1120,"y":260,"wires":[[]]},{"id":"b2c077b2.fa0458","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn off","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"a8174fea-b7b1-4331-bf17-5da01a963729","action":"turnOff"}]},"outputs":1,"x":1120,"y":300,"wires":[[]]},{"id":"1f3f79ac.0d4826","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on low","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"48aa8e73-2067-48f7-9625-3ec34ea69363","action":"setPercentage","value":{"type":"num","value":"33"}}]},"outputs":1,"x":1130,"y":380,"wires":[[]]},{"id":"1fc26ad0.fdf265","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on low","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"b0b1ec9e-7723-4826-ba85-f569b465dd6d","action":"setPercentage","value":{"type":"num","value":"33"}}]},"outputs":1,"x":1130,"y":420,"wires":[[]]},{"id":"bbc0c776.bfe818","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on low","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"a8174fea-b7b1-4331-bf17-5da01a963729","action":"setPercentage","value":{"type":"num","value":"33"}}]},"outputs":1,"x":1130,"y":460,"wires":[[]]},{"id":"65787ee0.02993","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on medium","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"48aa8e73-2067-48f7-9625-3ec34ea69363","action":"setPercentage","value":{"type":"num","value":"66"}}]},"outputs":1,"x":1140,"y":540,"wires":[[]]},{"id":"1a8e26ed.8395f9","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on medium","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"b0b1ec9e-7723-4826-ba85-f569b465dd6d","action":"setPercentage","value":{"type":"num","value":"66"}}]},"outputs":1,"x":1140,"y":580,"wires":[[]]},{"id":"5c630a47.ce7804","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on medium","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"a8174fea-b7b1-4331-bf17-5da01a963729","action":"setPercentage","value":{"type":"num","value":"66"}}]},"outputs":1,"x":1140,"y":620,"wires":[[]]},{"id":"3ce2a8d3.776708","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on high","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"48aa8e73-2067-48f7-9625-3ec34ea69363","action":"setPercentage","value":{"type":"num","value":"100"}}]},"outputs":1,"x":1130,"y":700,"wires":[[]]},{"id":"8a8c5698.828958","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on high","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"b0b1ec9e-7723-4826-ba85-f569b465dd6d","action":"setPercentage","value":{"type":"num","value":"100"}}]},"outputs":1,"x":1130,"y":740,"wires":[[]]},{"id":"9a3c1659.0b29e8","type":"alexa-remote-smarthome","z":"35964044.fcf53","name":"Turn on high","account":"d99d5f0e.4d0f5","config":{"option":"action","value":[{"entity":"a8174fea-b7b1-4331-bf17-5da01a963729","action":"setPercentage","value":{"type":"num","value":"100"}}]},"outputs":1,"x":1130,"y":780,"wires":[[]]},{"id":"a26a3b25.554268","type":"change","z":"35964044.fcf53","name":"Strip Wake Word","rules":[{"t":"change","p":"payload.description.summary","pt":"msg","from":"^(alexa )?","fromt":"re","to":"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":350,"y":500,"wires":[["545ebb02.da3e74"]]},{"id":"62a42905.95f0b8","type":"amazon-echo-hub","z":"35964044.fcf53","port":"80","processinput":0,"x":130,"y":200,"wires":[["1a5b5511.75c5db"]]},{"id":"d99d5f0e.4d0f5","type":"alexa-remote-account","z":"","name":"Zachary's Account","authMethod":"proxy","proxyOwnIp":"192.168.1.227","proxyPort":"3456","cookieFile":"/config/node-red/add-ons/node-red-contrib-alexa-remote2/alexa.json","refreshInterval":"3","alexaServiceHost":"pitangui.amazon.com","amazonPage":"amazon.com","acceptLanguage":"en-US","userAgent":"","useWsMqtt":"on","autoInit":"on"}]

My Solution: Room-Aware Routines
After thoroughly researching, reviewing and experimenting with the known solutions in the market (see Comparison with Alternative Solutions, below), I was admittedly dissatisfied with the available solutions to this problem and decided to come up with my own that combines as many of the benefits from each of the two most prominent solutions and as few of the caveats as possible. I wanted to make something easily extensible that remains close to both Alexa and Node-Red (and the plugins I use), as reliable and predictable as possible (no race conditions), and as low-effort to set up and manage as possible. As an added benefit, my approach features the ability to use regex matching for routine commands, and I plan to further enhance it by incorporating TTS.

How it Works
The solution works by defining a virtual (emulated hue) device to represent the device that you want to be able to control using room-aware commands, and then attaching a listener to your Alexa account that listens to all activity and routes any known room-aware commands appropriately. Following along with the image below:

  1. When any command is issued to an echo device, the “on device activity” handler is run and kicks off the flow
  2. A “change” node is used to strip the wake word from the command, if present (i.e. if the user didn’t pause for Alexa to respond before uttering the command); the wake-word is stripped ahead of time rather than simply being matched in the regex-based router for reusability (specifically, for sharing with you all, as some of you may use a custom wake word) and also to shorten the required pattern string (none of the solutions are great for performance and they really don’t need our help becoming worse when it’s easily avoidable :wink:)
  3. A “switch” node is used to inspect the command (not the room yet - we want to stop processing ASAP whenever possible) string and match it against the known routine text. I use regex for this, as I’m willing to trade some performance for simplicity, readability and robustness (e.g. I make words like “the” optional, match synonyms like “set” and “turn”, etc.). Feel free to instead use strings (like normal routines) if you’re so inclined.
  4. If the command is recognized (matched) as a known room-aware routine then it’s sent to another “switch” node that determines the room-specific action that should be called for the command. Note that using the mentioned Node-Red packages also gives you the ability to have Alexa state custom responses when no device was matched (e.g. “there’s no fan in this room to turn on” if I said “turn on the fan” while in the living room). I won’t go into this in this post, but with a little tinkering you should be able to figure it out.

Setup Instructions
You can either import my flow and use it as a starting point, or you can set it up manually, following the steps, below:

  1. Make sure that you have all required prerequisites installed
  2. Add a new “Alexa Event” node to your flow. By default, it should be set to trigger “On Device Activity”. Be sure to set the account to a working account, if you have not done so already (side-note: if you want your routines to keep working you have to have the File Path set in the account so that the plugin can store your oauth tokens without you having to constantly log in)
  3. Add a “change” node and wire the output of your “Alexa Event” node to your “change” node’s input.
  4. Set your change node to strip out your wake-word from the beginning of the msg.payload.description.summary property
  5. Add a “switch” node and wire the output of your “change” node to your “switch” node’s input. This switch is your “room-aware routine router” (I call them contextual routines in my flow because I have a predefined naming convention that I didn’t want to conflict with or change)
  6. Using regex matching, string comparison or your preferred matching predicate, create at least one switch case that checks for your desired room-aware routine (e.g. “turn the fan on”). Remember to leave the wake-word out, since it’s been stripped, and to write in all lowercase. If using regex, since Alexa will always pass the command in lowercase, it is recommended to not ignore case (performance). Note: for the purpose of these instructions, it is assumed that your routine aligns with a typical command that would be used to control a device, e.g. “turn the fan on”. This is necessary for the optional steps below, but you can make the routine whatever you want and skip those steps if you so desire.
  7. Add another “switch” node to your flow, connecting the output corresponding to the new routine-aware case that you defined in the previous step to the input of the new switch. This new “switch” is going to be your room-router and will decide which room-specific action to perform.
  8. Using string comparison, add a case for each room that you want to be able to issue the command in, comparing the msg.payload.deviceSerialNumber to the serial number of the echo device in that room. You can find the serial number of your echo devices in the Alexa App, by going to the device and clicking “About”.
  9. (Somewhat Optional) Add your desired “Alexa Smarthome”, “Alexa Routine”, or other desired logic nodes for each room and wire them appropriately to run when the case for their corresponding room is true (wire their inputs to the switch’s output corresponding to the case associated with their room). Following our example, add a new “Alexa Smarthome” node to the flow and wire it’s input to the switch output corresponding to the case you created in the previous step. Set your new Alexa Smarthome node to use your account and set it to “Turn On” the fan device in the matching room. Note: if you have already created your emulated “Fan” device then do not wire this action to that device, but rather wire it to the actual device that you wish to control.
  10. (Optional) Assuming that you wish to be able to run a routine that sounds like a normal device command (e.g. “turn on the fan”) you will want to create an emulated device named “fan” so that Alexa doesn’t say “I couldn’t find a device named ‘fan’” every time you issue a room-aware fan command. If you haven’t already, add a “amazon echo hub” node to your flow (if you have one in another flow you shouldn’t need to add another one, unless you want isolated hub services - off topic, but I can’t think of a good reason to do this). Then add a “amazon echo device” node and give it the exact name that you wish to use for your device. When you’re done, have Alexa discover devices and she’ll find the virtual one you created. That’s it. You don’t need to actually do anything else with your device, since it’s just there to enable you to make commands without Alexa throwing a fit.

Eliminating the “[device] doesn’t do that” messages for commands hue devices don’t support
You can actually eliminate these messages pretty easily by creating routines in your Alexa App that don’t actually do anything. The routines won’t prevent your room-aware routines from being called. However, it may be more work than you’d like to create all of the routines in the Alexa App to match your room-aware routines. For example, I use a the following regex to match the command to set a fan to medium speed:

(set|turn) (the )?fan ((on|to) )?medium

There are two possible states for the verb, (set|turn), two possible states for the presence of the article, (the )?, and three possible states for the preposition, ((on|to) )?. Thus there are 2*2*3=12 valid variants of my room-aware routine for setting the fan to medium speed. That’s 12 different dummy routines that I’d have to make (insert Alexa Skill here :wink:). In fairness, Alexa often lets you skip articles/prepositions when speaking commands and I’ve experienced her doing the same for and matching routines for me before. However, it’s not reliable so you may want to add these anyway, or just do what I do (though not in my exported flow, above) and have Alexa say something funny, like “Just kidding, I set the fan to medium for you” after she tells you that “fan doesn’t support that” (if you go that route, I suggest using SSML interjections for the joke; my wife was in tears laughing when she heard it the first time :laughing:).

Overall Pros:

  • No external scripts, accounts, systems or tools beyond Node-Red, the aforementioned plugins and Alexa
  • Can combine your room aware logic with other predicates to create complex room-aware routines and device commands
  • No custom code beyond nodes, and no function nodes used
  • Creates routines that can be executed based on a regex match against the spoken command (e.g. ^(alexa )?turn on (the )?fan)
  • Extensible and maintainable through Node-Red

Overall Cons:

  • Version I posted provides a routine-like approach that requires you to match the utterance and execute the action without being able to leverage Alexa’s native utterance processing features (i.e. no AVS) and could be improved to leverage Alexa Voice Service (AVS) and TTS in the future
  • Although this post describes several techniques to eliminate or avoid some of these issues, it’s worth mentioning that some commands may result in “[device name] does not support that” messages from Alexa for certain actions when using device emulation (e.g. “set the fan to medium”, since emulated devices are hue devices) or a “no device found named [device name]” if using commands for a device that hasn’t yet been emulated

Comparison with and Review of Alternative Solutions
Most of the solutions that I’ve seen for room-aware voice commands involve one of two things:

  1. Clever TTS scripts that look at the last echo device used (often paired with hass.io service/entity configuration)
  2. Assigning a separate account to each echo device

Below, I describe each approach based on my experiments with and research into various approaches matching each.

Alternative 1: TTS scripts using lastecho/lastalexa
Okay, this one doesn’t necessarily use Node-Red, but you can make it use Node-Red and it’s one of the most popular solutions I found, so I’m including it :upside_down_face:. At a high-level, this approach involves using a CLI (Command-Line Interface) script to call the Alexa cloud service and retrieve an identifier for the last echo device (“lastalexa” is typically the name, but from what I’ve seen this is a misnomer and I believe it should be “lastecho” - feel free to correct me on this if you can explain the reasoning behind it) to which a command was issued. The idea is that the last used echo would be the one the user just spoke the room-aware command to. The retrieved ID can then be used in conditional logic to determine what room to perform the action in and the original command can be updated from “turn the fan on” to “turn the fan on in the guest room”, leveraging the room determined by the output of the CLI calls. Please note that I’ve seen several different approaches to this one, and some are more streamlined than the multi-step process described, but the overall approach and pattern remains the same and I believe this explanation is easier to follow for readers picking this up.

Perhaps the biggest benefit of the “last echo” approach is that its use of CLI-based Alexa service calls makes it superior for the processing of utterances/spoken text and mapping it to the desired commands than my own approach. This is because the CLI scripts used in the “last echo” approach have support for leveraging Alexa’s own TTS/text processing capabilities, enabling developers to pass Alexa the text form of the spoken command and let her decide what to do with it. In contrast, my approach is more akin to routines in that the spoken text must be matched in custom logic/Node-Red rules in order to determine which action should run. This is an area of opportunity for my approach, in that I could use my existing logic for determining the echo device to which the command was issued, modify the utterance text in Node to reflect the actual name of the device to command and then use TTS and AVS to send the resulting utterance to Alexa for processing.

As a software architect, I’m not at all a fan of the “last echo” approach. It’s hacky and ripe for conflicts and unexpected behavior when two devices are used at relatively the same time. My wife and I both work from home and often issue commands at similar times. Commands issued within 10 seconds of each other can easily cause unexpected results due to the delay between the spoken command and when it is actually processed. This delay is impacted by everything from network latency to the user’s proximity to other devices when the command was spoken and beyond. As an example, yesterday when I was experimenting with the “lastecho” approach, I issued a test room-aware command with a fan just a couple of seconds before my wife coincidentally issued a command in a different room with a fan. Her fan turned on instead of mine. She’s frequently cold and was…displeased…with the experience :grimacing:. It should suffice to say that this option wasn’t for me, but I’ll also add a few more objective rationales against the “last echo” approach from a technical perspective:

  1. All of the scripts that I’ve seen for this are reverse-engineered versions of Alexa web apps or other tools that can change at any time. In a way, my solution is no better, since the Node-Red plugins that I use are also both based on reverse-engineered and/or unofficial Alexa APIs. Choose your poison.
  2. Race-condition roulette is something that should be avoided whenever possible. This is when you have two things that could possibly happen out of order (like my wife and I both issuing commands in separate rooms and the possibility that the last echo might thus result in the wrong room being returned to me). With that said, home automation is still in its infancy and we, as hobbyists, tend to be willing to deal with a little slippage from time to time. If this is something you’re okay with then there are pretty simple and reasonably well-documented hass.io and platform-agnostic approaches to this on the web.

Alternative 2: Assigning a separate account per echo device
This is perhaps the most popular approach that I’ve seen out there for room-aware commands, likely because it’s perhaps the most straightforward approach, requires minimal effort and shares the benefit of Alexa being responsible for utterance/spoken language processing with the “last echo” approach. The multi-account approach leverages the “Amazon Household” feature of Alexa that allows users to associate multiple accounts (intended for family members) with the household and its Alexa-enabled devices. Following this approach, instead of assigning a separate account to each family member, a separate account is assigned to each echo device (or at least to each of the ones that you want to run room-aware commands on). The multi-account approach requires datech’s node-red-contrib-amazon-echo package for Node-Red, and I recommend also using 586837r’s node-red-contrib-alexa-remote2 package for Node-Red to perform the resulting actions on Alexa-connected devices. Additionally, for this approach, the echo devices that you plan to issue room-aware commands to must all be on separate accounts and have their own static IPs (internal to the network is fine; technically, you can combine some clever logic nodes from 586837r’s node-red-contrib-alexa-remote2 nodes in order to dynamically get assigned IP addresses for devices to match room-aware commands against, but I won’t be going into that today). Following this approach, room-specific devices are modeled in Node-Red as a virtual (emulated hue) device node. As such, to make room-specific fans that you control through room-aware commands like “turn on the fan”, you would create a “Fan” emulated hue device node to be shared by all of the room-specific fans (this will become clearer as you read on). When a command comes into one of the emulated hue device nodes, a switch (conditional) node downstream of the emulated device node is used to inspect the IP address of the device that issued the command (found in the msg.meta.insert.details.ip property) and determine what action to take based on the room the echo device with the inspected IP is associated with.

If you’re unfamiliar with this solution, then you may be wondering why you have to use separate accounts for each echo to make that work, since it sounds like a good solution on its own. After some research and experimentation, it is clear (though I wasn’t able to find official documentation to substantiate) that within the account and/or network Alexa chooses a “master” device from which to issue commands to connected devices, and so all commands for a given account will appear to have come from the same IP address. We work around this by associating each device with a different account, thus making each its own “master”. Following with our fan example from before, when the room-aware “turn on the fan” command comes into the emulated hue “Fan” node, the command object will be holding the IP of the echo device that the command was spoken to, so long as that device is on its own account (i.e. the device is guaranteed to be the “master”). For example, if I was standing in the guest room when I said “Alexa, turn on the fan”, then the command object will hold the IP address of the Guest Room Echo. The command object is then passed from the “Fan” node to a switch node, in order to decide what to do with it. In our example, the switch node will match the IP address against the known IP address (or dynamically retrieved using …remote2 nodes) of the Guest Room Echo, and then will then turn the fan on in that room. If you are using 586837r’s node-red-contrib-alexa-remote2 package for Node-Red then downstream of the switch’s output for commands with IPs matching the Guest Room Echo condition you would have an “Alexa Smarthome” node configured to turn on “Guest Room Fan”.

This isn’t a bad approach, and it benefits greatly from sticking very close to native Alexa functionality. However, there are several limitations that should be mentioned:

  1. It doesn’t scale well. You only get 2 adult and 4 child accounts per household, so assuming you have 1 account as the “main” account you only have 5 more that you can use for your devices. In my case, my wife likes to actually use a different account so she can easily switch to her own music, calendar, etc. and to ensure that we don’t see gifts purchased for us by one another when looking at order history. Even if that wasn’t the case, I still have more rooms that I would want this to work in than it can support, so it doesn’t really scale.
  2. As with my approach, it’s limited by the capabilities of the Node-Red plugins used to implement it, including emulated hue capabilities, and so some commands (e.g. “Set the fan to medium”) will work but will also result in Alexa saying “Fan cannot do that”. The workarounds I described for this issue with my approach will also work for this one.
  3. Like all of the known approaches I’ve found (including my own), this approach is dependent upon tools written against unofficial/reverse-engineered Alexa API’s that could change at any time

Regarding the scalability issue, one thought that occurred to me that I have not yet tried is to achieve the same result as this approach through the use of subnets or isolated networks. This seemed like a “mouse meets elephant gun” scenario, requiring additional hardware (routers and possibly switches) for something that really shouldn’t, interfering with multi-room music (less important for bedrooms, but still a valid point), and requiring additional effort to configure bandwidth limiting in order to avoid the additional networks eating up your available network bandwidth. In theory, I expect that this would work but It wasn’t something I was willing to pursue. If anyone else has experimented with this already and is willing to save me a few hours and can share their observations then I would be most appreciative.

15 Likes

UPDATE
While walking a friend through my configuration in person, I noticed that I actually forgot to set the “switch” nodes to all stop after the first match. While not as important on the “room routers” (the second switch nodes that are used to match the serial number of the echo device), this is very important for the “room-aware routine routers” (the first switch node that is used to match the raw utterance text against known room-aware commands) if you’re using regular expression comparison, like I am.

In order to improve performance, for each of your switch nodes double-click and at the bottom change “checking all rules” to “stopping after first match” by selecting the latter from the drop-down.

I wish to thank you for sharing your approach for adding room-awareness to Alexa. I’ve taken what you’ve presented and created a version that is tailored to my needs.

  • I don’t have fans with speed-levels, they can only be turned on/off.
  • I have five Echo devices (one Amazon Echo Dot and four ecobee Switch+) located in five different rooms. I want each one to have a default light with the ability to control its brightness.
  • Four of the Echo devices will also have a default fan (two separate bathroom fans and the furnace fan).

To meet these requirements, the resulting flow would be quite elaborate if I chose to create nodes for each action. To simplify the flow, I created function nodes that generate a single message packet that can support both fans and lights.

The following spoken phrases are supported:

  • Turn on/off the fan/light.
  • Turn on/off fan/light.
  • Turn the fan/light on/off.
  • Turn fan/light on/off.
  • Set the light to <level>.
  • Set light to <level>.

The brightness level of a light is limited to increments of five (zero, five, ten, fifteen, twenty, … one hundred). If someone asks to set the light to thirty three, the flow will end at the first function node .

Within the received phrase, Alexa reports the brightness level using words, not numbers. The first function node (“Parse”) converts the words to numbers (because the command to set a light’s level requires a numeric value). The conversion is performed using a map. It’s possible to enhance the map by adding words like “low”, “medium”, and “high”, or even “night time”, with corresponding numeric values. Otherwise, there’s no need for the end-user to change anything within the first function node.

The second function node (“Route”) determines which Echo heard the spoken phrase and then specifies the associated fan or light. This is the only function node that an end-user needs to customize. You must enter the serial number of each Echo (displayed in Alexa’s web app) and specify the exact name of each light and fan.

  • The initial and final nodes must be configured with your Amazon account as per the instructions for node-red-contrib-alexa-remote2.

  • Naturally, to have this work, node-red-contrib-amazon-echo also needs to be used to make “Fan” and “Light” devices known to Alexa. These nodes are not included in the following flow.

Copy the following flow and import it into Node-Red:

[
    {
        "id": "7c21f2b6.c338f4",
        "type": "tab",
        "label": "Room-aware Alexa",
        "disabled": false,
        "info": ""
    },
    {
        "id": "857cc38e.e8cbd8",
        "type": "function",
        "z": "7c21f2b6.c338f4",
        "name": "Parse",
        "func": "// Determine if verb is 'turn' or 'set'\nvar phrase = msg.payload.description.summary;\n\nvar parts = phrase.match(/(turn|set) t?h?e? ?(fan|light)/);\nif (parts) {\n    msg.action = parts[1];\n    msg.device = parts[2];\n}\nelse {\n    parts = phrase.match(/(turn|set) (on|off) t?h?e? ?\\b(fan|light)\\b/);\n    if (parts) {\n        msg.action = parts[1];\n        msg.device = parts[3];\n    }\n}\n\n// Process verb\nif (parts) {\n    if (msg.action == \"turn\") {\n        // Verb is 'turn'\n        // Convert verb to Alexa action: turnOn or turnOff\n        parts = phrase.match(/\\b(on|off)\\b/);\n        if (parts) {\n            msg.action = msg.action + parts[1].replace(\"o\", \"O\");\n            msg.value = 0; // dummy value\n            return msg;\n        }\n    }\n    else {\n        // Verb is 'set'\n        // Convert verb to Alexa action: setBrightness\n        // parts = phrase.match(/set t?h?e? ?light t?o? ?(\\w+ ?\\w+)/);\n        parts = phrase.match(/set t?h?e? ?light to (\\w+ ?\\w+)/);\n        if (parts) {\n            msg.action = \"setBrightness\";\n\n            // Convert light level from words to numbers\n            let words = new Map([\n              [\"zero\", 0],\n              [\"five\", 5],\n              [\"ten\", 10],\n              [\"fifteen\", 15],\n              [\"twenty\", 20],\n              [\"twenty five\", 25],\n              [\"thirty\", 30],\n              [\"thirty five\", 35],\n              [\"forty\", 40],\n              [\"forty five\", 45],\n              [\"fifty\", 50],\n              [\"fifty five\", 55],\n              [\"sixty\", 60],\n              [\"sixty five\", 65],\n              [\"seventy\", 70],\n              [\"seventy five\", 70],\n              [\"eighty\", 80],\n              [\"eighty five\", 85],\n              [\"ninety\", 90],\n              [\"ninety five\", 95],\n              [\"hundred\", 100],\n              [\"one hundred\", 100],\n            ]);\n\n            var tmp = words.get(parts[1]);\n            if (tmp) {\n                msg.value = tmp;\n                return msg;\n            }\n        }\n    }\n}\n// Unable to find matching verb or invalid action type or invalid brightness level\nreturn null;\n\n",
        "outputs": 1,
        "noerr": 0,
        "x": 350,
        "y": 160,
        "wires": [
            [
                "a6c16849.84555"
            ]
        ]
    },
    {
        "id": "e5447e3e.4e78e",
        "type": "alexa-remote-smarthome",
        "z": "7c21f2b6.c338f4",
        "name": "",
        "account": "501c7e4b.3357b",
        "config": {
            "option": "action",
            "value": []
        },
        "outputs": 1,
        "x": 870,
        "y": 160,
        "wires": [
            []
        ]
    },
    {
        "id": "a6c16849.84555",
        "type": "function",
        "z": "7c21f2b6.c338f4",
        "name": "Route",
        "func": "if (msg.payload.deviceSerialNumber == \"a1\") {\n    // Master Bathroom\n    if (msg.device == \"fan\") {\n        msg.entity = \"Master Bathroom Fan\";\n    }\n    else {\n        msg.entity = \"Master Bathroom Light\";\n    }\n    return msg;\n}\nelse if (msg.payload.deviceSerialNumber == \"b2\") {\n    // Guest Bathroom\n    if (msg.device == \"fan\") {\n        msg.entity = \"Guest Bathroom Fan\";\n    }\n    else {\n        msg.entity = \"Guest Bathroom Light\";\n    }\n    return msg;\n}\nelse if (msg.payload.deviceSerialNumber == \"c3\") {\n    // Kitchen\n    if (msg.device == \"fan\") {\n        msg.entity = \"Furnace Fan\";\n    }\n    else {\n        msg.entity = \"Kitchen Light\";\n    }\n    return msg;\n}\nelse if (msg.payload.deviceSerialNumber == \"d4\") {\n    // Living Room\n    if (msg.device == \"fan\") {\n        msg.entity = \"Furnace Fan\";\n    }\n    else {\n        msg.entity = \"Living Room Light\";\n    }\n    return msg;\n}\nelse if (msg.payload.deviceSerialNumber == \"e5\") {\n    // Garage\n    if (msg.device == \"light\") {\n        msg.entity = \"Garage Light\";\n        return msg;\n    }\n}\n// Unknown Echo or unsupported device\nreturn null;\n",
        "outputs": 1,
        "noerr": 0,
        "x": 490,
        "y": 160,
        "wires": [
            [
                "deda6b87.de2518"
            ]
        ]
    },
    {
        "id": "deda6b87.de2518",
        "type": "template",
        "z": "7c21f2b6.c338f4",
        "name": "Create payload",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "[{\"entity\":\"{{entity}}\", \"action\":\"{{action}}\", \"value\":{{value}}}]",
        "output": "json",
        "x": 660,
        "y": 160,
        "wires": [
            [
                "e5447e3e.4e78e"
            ]
        ]
    },
    {
        "id": "d245c878.64fbf8",
        "type": "alexa-remote-event",
        "z": "7c21f2b6.c338f4",
        "name": "",
        "account": "501c7e4b.3357b",
        "event": "ws-device-activity",
        "x": 170,
        "y": 160,
        "wires": [
            [
                "857cc38e.e8cbd8"
            ]
        ]
    },
    {
        "id": "501c7e4b.3357b",
        "type": "alexa-remote-account",
        "z": "",
        "name": "",
        "authMethod": "proxy",
        "proxyOwnIp": "HOSTNAMEorIPADDRESS",
        "proxyPort": "3456",
        "cookieFile": "/home/username/.node-red/alexa.proxy",
        "refreshInterval": "3",
        "alexaServiceHost": "pitangui.amazon.com",
        "amazonPage": "amazon.com",
        "acceptLanguage": "en-US",
        "userAgent": "",
        "useWsMqtt": "on",
        "autoInit": "on"
    }
]
4 Likes

Very nice right up, thank you.

This is amazing. I know what I’m coding this weekend :grin:

Tip: If you are using the node-red addon from the HA “Add-on Store”, then you can set the “File Path” to “/config/alexa_cookies.txt” and you’ll see the file in the familiar /config directory where configuration.yaml is stored.

Also no port forwarding into the container is needed when using the node-red add-on because that container is run with ‘host’ network mode.

I found this video tutorial really helpful for getting started with alexa-remote2 because the setup is a bit crazy the first time.

FYI, I’m using this to build a hands-free Alexa to Plex automation. My plan is documented here:

1 Like

Has anyone noticed that the message payload has changed using this method?

For me the msg.payload.description.summary is now blank, which means I can’t use this approach for room aware voice commands anymore. :frowning:

yup its doing it to me as well but it seems its just for on/off commands total bummer

I can confirm.
“What is the time”:
alexa time Screen Shot 2020-07-27 at 10.52.59 PM

“Turn on device_name”:
turn on x Screen Shot 2020-07-27 at 10.53.36 PM

I have zigbee buttons configured to trigger the same automations, so I can live without Alexa. Maybe amazon is no longer providing this data? I have not changed anything related to alexa or node red, so I suspect amazon has decided to withhold this data.

I have a dumb workaround: Instead of switching on “Turn on device_name”, i changed it to switch on “What is device_name”.
There may be a better phrase to switch on, but that’s what I came up with in 5 minutes.
“initiate” and “terminate” seem to work too. ok for me, but not other members of the house.

That’s a cunning workaround. What is lights does indeed show up in the payload summary, but it also starts Alexa on a rant about light pollution! :slight_smile:

I’m going to try a more complicated approach by monitoring payload.name, and using a switch to fire off an MQTT event for that room, which will enable an EventGhost folder with that room name for 10 seconds, and then disable it. The same device event ( lights, fan, aircon et cetera) will be retriggered after a few seconds with a off/on, but only the enabled folder for that room will do anything.

So that when it gets a lights payload, it only runs in that room. Such a hassle though. There’s probably some way to do this in node red using triggers with delays and cancels perhaps, but I’m not au fait with it enough to manage.

The issue seemed to have resolved itself, with the payload now being displayed.

As an aside to this, if you use https://red.cb-net.co.uk/ you can create a device which has both a range, (so you can choose low medium high, open/close), and power, ( turn on/off) without that particular error message above.

There are some cons, in that it needs a skill, and an extra node to be installed, but it’s easier than creating lots of different routines.

Well I decided to have a go at it through node red, rather than sending it to EventGhost. The following seems to work pretty well, no matter whether you say, Alexa, (wait for the beep), and then issue the command, or if you just say it in one continuous command.

It relies on the use of virtual switches in home Assistant, which can be added by adding this to the configuration.yaml

input_boolean:

  rumpus_alexa:
    name: Rumpus Alexa
    icon: mdi:motion-sensor
  
  bedroom_alexa:
    name: Bedroom Alexa
    icon: mdi:motion-sensor

  alexa_idle:
    name: Alexa Idle
    icon: mdi:motion-sensor

I just did 2 rooms in the example, but obviously you would need to make a virtual switch for each room you intend to use it for.

https://i.imgur.com/Ev36y3Q.png

[
    {
        "id": "2d30d6f8.0bb382",
        "type": "tab",
        "label": "Room Aware Alexa",
        "disabled": false,
        "info": ""
    },
    {
        "id": "b2e86d16.b61b08",
        "type": "alexa-remote-event",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "account": "34",
        "event": "ws-device-activity",
        "x": 110,
        "y": 340,
        "wires": [
            [
                "b67bca26.943068"
            ]
        ]
    },
    {
        "id": "b67bca26.943068",
        "type": "switch",
        "z": "2d30d6f8.0bb382",
        "name": "Alexa hears payload.name",
        "property": "payload.name",
        "propertyType": "msg",
        "rules": [
            {
                "t": "cont",
                "v": "Rumpus Room",
                "vt": "str"
            },
            {
                "t": "cont",
                "v": "Bedroom",
                "vt": "str"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 380,
        "y": 340,
        "wires": [
            [
                "9f8b010c.35537"
            ],
            [
                "841b89fa.48a9b8"
            ]
        ]
    },
    {
        "id": "d2c5388f.4c433",
        "type": "api-call-service",
        "z": "2d30d6f8.0bb382",
        "name": "Turn on input_boolean.rumpus_alexa",
        "version": 1,
        "debugenabled": false,
        "service_domain": "input_boolean",
        "service": "turn_on",
        "entityId": "input_boolean.rumpus_alexa",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 1230,
        "y": 300,
        "wires": [
            []
        ]
    },
    {
        "id": "27b8a0c2.738c5",
        "type": "api-call-service",
        "z": "2d30d6f8.0bb382",
        "name": "Turn on input_boolean.bedroom_alexa",
        "version": 1,
        "debugenabled": false,
        "service_domain": "input_boolean",
        "service": "turn_on",
        "entityId": "input_boolean.bedroom_alexa",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 1240,
        "y": 360,
        "wires": [
            []
        ]
    },
    {
        "id": "9e37d879.0d3",
        "type": "mqtt out",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/Rumpus/Device",
        "qos": "",
        "retain": "",
        "broker": "692dea2e.02145c",
        "x": 1360,
        "y": 563,
        "wires": []
    },
    {
        "id": "d337b25d.ae39e",
        "type": "amazon-echo-device",
        "z": "2d30d6f8.0bb382",
        "name": "Device",
        "topic": "",
        "x": 130,
        "y": 680,
        "wires": [
            [
                "998f437c.67e52"
            ]
        ]
    },
    {
        "id": "7495f658.7d8a08",
        "type": "switch",
        "z": "2d30d6f8.0bb382",
        "name": "on / off",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "on",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "off",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 1158,
        "y": 600,
        "wires": [
            [
                "9e37d879.0d3"
            ],
            [
                "39e987bc.dc16b"
            ]
        ]
    },
    {
        "id": "cf00428b.abeae",
        "type": "api-current-state",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "version": 1,
        "outputs": 2,
        "halt_if": "on",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "override_topic": false,
        "entity_id": "input_boolean.rumpus_alexa",
        "state_type": "str",
        "state_location": "",
        "override_payload": "none",
        "entity_location": "",
        "override_data": "none",
        "blockInputOverrides": true,
        "x": 884,
        "y": 600,
        "wires": [
            [
                "7495f658.7d8a08",
                "619744d5.5b09ec",
                "f87782ad.2dd568"
            ],
            []
        ]
    },
    {
        "id": "39e987bc.dc16b",
        "type": "mqtt out",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/Rumpus/Device",
        "qos": "",
        "retain": "",
        "broker": "692dea2e.02145c",
        "x": 1358,
        "y": 641,
        "wires": []
    },
    {
        "id": "ae53b3ff.983038",
        "type": "mqtt out",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/Bedroom/Device",
        "qos": "",
        "retain": "",
        "broker": "692dea2e.02145c",
        "x": 1381,
        "y": 738,
        "wires": []
    },
    {
        "id": "96c9345a.d1fda",
        "type": "switch",
        "z": "2d30d6f8.0bb382",
        "name": "on / off",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "on",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "off",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 1158,
        "y": 780,
        "wires": [
            [
                "ae53b3ff.983038"
            ],
            [
                "612704b4.74f2b4"
            ]
        ]
    },
    {
        "id": "e74e6680.719d4",
        "type": "api-current-state",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "version": 1,
        "outputs": 2,
        "halt_if": "on",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "override_topic": false,
        "entity_id": "input_boolean.bedroom_alexa",
        "state_type": "str",
        "state_location": "",
        "override_payload": "none",
        "entity_location": "",
        "override_data": "none",
        "blockInputOverrides": false,
        "x": 882,
        "y": 780,
        "wires": [
            [
                "96c9345a.d1fda",
                "a01adb7b.af38a",
                "5eae98dc.381268"
            ],
            []
        ]
    },
    {
        "id": "612704b4.74f2b4",
        "type": "mqtt out",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/Bedroom/Device",
        "qos": "",
        "retain": "",
        "broker": "692dea2e.02145c",
        "x": 1381,
        "y": 829,
        "wires": []
    },
    {
        "id": "ba8bea22.fa235",
        "type": "api-current-state",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "version": 1,
        "outputs": 2,
        "halt_if": "on",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "override_topic": false,
        "entity_id": "input_boolean.alexa_idle",
        "state_type": "str",
        "state_location": "",
        "override_payload": "none",
        "entity_location": "",
        "override_data": "none",
        "blockInputOverrides": false,
        "x": 520,
        "y": 680,
        "wires": [
            [],
            [
                "cf00428b.abeae",
                "e74e6680.719d4"
            ]
        ]
    },
    {
        "id": "1121c148.4586cf",
        "type": "api-call-service",
        "z": "2d30d6f8.0bb382",
        "name": "Turn Off alexa_idle",
        "version": 1,
        "debugenabled": false,
        "service_domain": "input_boolean",
        "service": "turn_off",
        "entityId": "input_boolean.alexa_idle",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 886,
        "y": 360,
        "wires": [
            [
                "27b8a0c2.738c5"
            ]
        ]
    },
    {
        "id": "288d3431.5f9b24",
        "type": "api-call-service",
        "z": "2d30d6f8.0bb382",
        "name": "Turn Off alexa_idle",
        "version": 1,
        "debugenabled": false,
        "service_domain": "input_boolean",
        "service": "turn_off",
        "entityId": "input_boolean.alexa_idle",
        "data": "{}",
        "dataType": "jsonata",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 890,
        "y": 300,
        "wires": [
            [
                "d2c5388f.4c433"
            ]
        ]
    },
    {
        "id": "63330662.bcd4c",
        "type": "mqtt out",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/InputBoolean/Reset",
        "qos": "",
        "retain": "",
        "broker": "692dea2e.02145c",
        "x": 1380,
        "y": 420,
        "wires": []
    },
    {
        "id": "619744d5.5b09ec",
        "type": "trigger",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "op1": "",
        "op2": "",
        "op1type": "nul",
        "op2type": "payl",
        "duration": "1",
        "extend": false,
        "units": "s",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 1180,
        "y": 420,
        "wires": [
            [
                "63330662.bcd4c"
            ]
        ]
    },
    {
        "id": "f70b05f.8f1bbf8",
        "type": "mqtt out",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/InputBoolean/Reset",
        "qos": "",
        "retain": "",
        "broker": "692dea2e.02145c",
        "x": 1380,
        "y": 960,
        "wires": []
    },
    {
        "id": "a01adb7b.af38a",
        "type": "trigger",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "op1": "",
        "op2": "",
        "op1type": "nul",
        "op2type": "payl",
        "duration": "1",
        "extend": false,
        "units": "s",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 1182,
        "y": 960,
        "wires": [
            [
                "f70b05f.8f1bbf8"
            ]
        ]
    },
    {
        "id": "9f8b010c.35537",
        "type": "change",
        "z": "2d30d6f8.0bb382",
        "name": "set msg.payload to empty",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 650,
        "y": 300,
        "wires": [
            [
                "288d3431.5f9b24"
            ]
        ]
    },
    {
        "id": "841b89fa.48a9b8",
        "type": "change",
        "z": "2d30d6f8.0bb382",
        "name": "set msg.payload to empty",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 650,
        "y": 360,
        "wires": [
            [
                "1121c148.4586cf"
            ]
        ]
    },
    {
        "id": "69b7d8b1.4fb038",
        "type": "api-call-service",
        "z": "2d30d6f8.0bb382",
        "name": "Turn on alexa_idle",
        "version": 1,
        "debugenabled": false,
        "service_domain": "input_boolean",
        "service": "turn_on",
        "entityId": "input_boolean.alexa_idle",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 910,
        "y": 40,
        "wires": [
            []
        ]
    },
    {
        "id": "db19af72.f03728",
        "type": "api-call-service",
        "z": "2d30d6f8.0bb382",
        "name": "Turn off input_boolean.bedroom_alexa",
        "version": 1,
        "debugenabled": false,
        "service_domain": "input_boolean",
        "service": "turn_off",
        "entityId": "input_boolean.bedroom_alexa",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 980,
        "y": 160,
        "wires": [
            []
        ]
    },
    {
        "id": "4885c283.e319ac",
        "type": "api-call-service",
        "z": "2d30d6f8.0bb382",
        "name": "Turn off input_boolean.rumpus_alexa",
        "version": 1,
        "debugenabled": false,
        "service_domain": "input_boolean",
        "service": "turn_off",
        "entityId": "input_boolean.rumpus_alexa",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 970,
        "y": 100,
        "wires": [
            []
        ]
    },
    {
        "id": "25d0673a.f13de",
        "type": "mqtt in",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/InputBoolean/Reset",
        "qos": "2",
        "datatype": "auto",
        "broker": "692dea2e.02145c",
        "x": 650,
        "y": 100,
        "wires": [
            [
                "69b7d8b1.4fb038",
                "4885c283.e319ac",
                "db19af72.f03728"
            ]
        ]
    },
    {
        "id": "9cd8d1c6.cbce88",
        "type": "inject",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "5",
        "topic": "",
        "payload": "",
        "payloadType": "str",
        "x": 90,
        "y": 100,
        "wires": [
            [
                "1a017e98.224c11"
            ]
        ]
    },
    {
        "id": "1a017e98.224c11",
        "type": "mqtt out",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/InputBoolean/Reset",
        "qos": "",
        "retain": "",
        "broker": "692dea2e.02145c",
        "x": 280,
        "y": 100,
        "wires": []
    },
    {
        "id": "dfeb3444.169d98",
        "type": "comment",
        "z": "2d30d6f8.0bb382",
        "name": "Reset Virtual Switches on Startup",
        "info": "",
        "x": 190,
        "y": 40,
        "wires": []
    },
    {
        "id": "f87782ad.2dd568",
        "type": "mqtt out",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/Alexa/Trigger/Cancel",
        "qos": "",
        "retain": "",
        "broker": "692dea2e.02145c",
        "x": 1380,
        "y": 480,
        "wires": []
    },
    {
        "id": "f865de51.0cf7",
        "type": "mqtt in",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/Alexa/Trigger/Cancel",
        "qos": "2",
        "datatype": "auto",
        "broker": "692dea2e.02145c",
        "x": 140,
        "y": 580,
        "wires": [
            [
                "365ce9ee.bbe946"
            ]
        ]
    },
    {
        "id": "998f437c.67e52",
        "type": "trigger",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "op1": "",
        "op2": "",
        "op1type": "pay",
        "op2type": "payl",
        "duration": "3",
        "extend": false,
        "units": "s",
        "reset": "cancel",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 264,
        "y": 679,
        "wires": [
            [
                "ba8bea22.fa235"
            ]
        ]
    },
    {
        "id": "365ce9ee.bbe946",
        "type": "trigger",
        "z": "2d30d6f8.0bb382",
        "name": "send cancel",
        "op1": "",
        "op2": "cancel",
        "op1type": "nul",
        "op2type": "str",
        "duration": "1",
        "extend": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 370,
        "y": 580,
        "wires": [
            [
                "998f437c.67e52"
            ]
        ]
    },
    {
        "id": "5eae98dc.381268",
        "type": "mqtt out",
        "z": "2d30d6f8.0bb382",
        "name": "",
        "topic": "Home/Alexa/Trigger/Cancel",
        "qos": "",
        "retain": "",
        "broker": "692dea2e.02145c",
        "x": 1387,
        "y": 893,
        "wires": []
    },
    {
        "id": "22d94418.12cbec",
        "type": "comment",
        "z": "2d30d6f8.0bb382",
        "name": "Reset Virtual Switches after Event",
        "info": "",
        "x": 640,
        "y": 40,
        "wires": []
    },
    {
        "id": "15abc1e7.e77a66",
        "type": "comment",
        "z": "2d30d6f8.0bb382",
        "name": "Remove Payload",
        "info": "",
        "x": 660,
        "y": 260,
        "wires": []
    },
    {
        "id": "1cf471e.1bf080e",
        "type": "comment",
        "z": "2d30d6f8.0bb382",
        "name": "Cancel trigger if already fired",
        "info": "",
        "x": 220,
        "y": 540,
        "wires": []
    },
    {
        "id": "692dea2e.02145c",
        "type": "mqtt-broker",
        "z": "",
        "name": "Home Assistant",
        "broker": "",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": false,
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "willTopic": "",
        "willQos": "0",
        "willPayload": ""
    }
]
1 Like

Big thanks for posting this up - @zkniebel

I have been looking to switch from google home to alexa devices and was surprised how dumb alexa’s ‘smart home’ skills are…
I wanted to have the command ‘Turn on/off the light’ only control the ‘main’ light in each room, and not every single light in the room - thanks to this thread, I can now do that :slight_smile:

I ended up using a slightly different approach and didn’t use emulated hue, but set a template light device (called ‘light’) and exposed it alongside all my other bulbs using HA alexa integration.

@zkniebel GREAT WRITE-UP! I did something similar but simpler using Node Red, node-red-contrib-alexa-remote2, and Hubitat about 6 weeks ago and just found your post today. I used “Contains” in my Switch Nodes because I don’t know how to use Regex. Your flow definitely helps to transition to using Regex and will make my flows much more user-friendly (AKA more wife friendly). Thank you.

@123 - I have everything setup using the flow you posted, and have made some alterations to the parse function to support different wording I use to control brightness.

I’m also attempting to added support for some basic colour control, but I’m having a problem with the ‘Create Payload’ node - it doesn’t seem to treat the colour as a string and prints '“Unexpected token b in JSON at position 64”
(“b” changes with the first character of whatever colour I’ve asked Alexa to set…)

Can you see where I’ve gone wrong in the modified code? - https://pastebin.com/80uq7JqU

Just realised that it also adds ‘per / percent’ into the 2nd capture group too if the brightness only contains one word too… back to the drawing board :stuck_out_tongue:

This device isn’t showing up for me in Alexa. Is that because I’m filtering devices that Alexa can see through Nabu Casa? If so, how do I get this to show up?

edit: So one issue is that my Echos were on a different wifi network than my HA box. I moved one of them over to my internal network. Should that be enough, or do all of them have to be moved over?

edit2: Figured it out. I needed to check ‘Device Discovery in the hub node’.

I just learned from the Node Red forum that you can use

Alexa, Simon says <any_words_here>

and she will repeat any_words_here back to you (she won’t say “I don’t know what to do with that”).
Well, turns out you can use this as an alternate/expansion to “Alexa, turn off my_device” and with no changes to the flow here in this thread.

The message (as shown in a debug node):
node red alexa simon says garage temp Screen Shot 2020-11-16 at 3.37.24 PM

Use a switch node to parse the simon-says “command”:
node red switch contains Screen Shot 2020-11-16 at 3.38.49 PM

Thanks for this post, did it last night

For this to work for me, I had to add a function node with

msg.payload="1";
return msg;

Before the call to the HA node that turns on my light.
Otherwise the call to the HA node returned an error (API I think)

I’m also trying to figure out what to do if I say “turn on light” and two dots hear it. I have dots in connected rooms so sometimes the one in the other room responds.

This is an amazing write up. I can’t wait to dig into it, and try it out. Quick question - did you consider using Almond or Ada to process the commands? (I can’t remember which does the processing). That way, you can combine the room awareness, but not reinvent the wheel on the language processing.

b