Alexa TTS: Giving the House a Voice
Local orchestration, isolated cloud state, and announcements through the speakers already in every room
Alexa TTS: Giving the House a Voice
Not in the HomeSeer store quite yet. It’s been running here every day, and the plan is to send it to the store as a beta.
Amazon’s HomeSeer integration works very well in one direction.
Alexa can control HomeSeer.
HomeSeer can expose devices.
Alexa can turn things on.
Turn things off.
Adjust levels.
Trigger routines.
And generally boss the house around quite effectively.
What HomeSeer cannot do through Amazon’s normal smart-home integration is turn around and say:
“Alexa, announce that the garage door has been open for twenty minutes.”
Amazon does have announcement and notification capabilities for certain approved Alexa integrations and programs.
What it does not provide is a general-purpose public API that lets an ordinary HomeSeer plugin send arbitrary spoken text to whichever Echo devices happen to be in your account.
Which is a shame.
Because one of the most useful things an automated house can do is simply:
Say something out loud.
And many houses already contain perfectly good network-connected speakers distributed through nearly every room.
They just happen to answer to Alexa.
So this plugin exists to bridge that missing direction.
The shape, and why it is that shape
The architecture looks roughly like this:
HomeSeer ──HTTP──► Local Service ──HTTPS──► Amazon ──► Echo devices
(plugin) (Node, same machine)
The most important part of that diagram is not really the arrows.
It is this:
The HomeSeer plugin does not own the Amazon session.
That was a deliberate architectural decision.
The mechanism used to communicate with Alexa devices is not a neat supported API login where I exchange a client ID and secret and receive a tidy token with a documented lifetime.
It behaves much more like maintaining a browser session with Amazon.
There are cookies.
Tokens.
Refresh state.
Browser-style authentication.
Occasional human involvement.
And, periodically, Amazon changes something and the libraries that reproduce that behavior need to catch up.
That sort of state does not belong buried inside the HomeSeer plugin if I can avoid putting it there.
Why not keep all of it in the plugin?
HomeSeer plugins restart.
They restart when HomeSeer restarts.
They can restart during upgrades.
They can restart during configuration changes.
They get stopped and started while troubleshooting completely unrelated things.
If the Amazon authentication session lived directly inside the plugin, every one of those lifecycle events would also become part of the authentication lifecycle.
Now the plugin would be responsible for:
Maintaining Amazon session state.
Persisting authentication material.
Refreshing it.
Handling browser-style login.
Dealing with changes in the underlying libraries.
Detecting when Amazon wants a human involved again.
And whatever Amazon changes next Thursday.
All inside the component whose actual job is supposed to be:
“Please say this sentence on those speakers.”
That seemed like an unnecessarily complicated place to put the complicated part.
So I did not.
Keep the awkward part in its own box
A small Node service runs locally alongside HomeSeer.
That service owns the Amazon-facing session.
It maintains whatever authentication state the current libraries require.
It performs refreshes.
It talks to Amazon.
It has its own logging.
And if the occasionally-needs-a-human part of the system actually needs a human, there is one clearly defined component where that problem lives.
The HomeSeer plugin stays much simpler.
It talks to the local service over HTTP.
Conceptually, the request is something like:
Speak this text on these devices.
The service handles everything beyond that boundary.
That separation also makes failures much easier to reason about.
If HomeSeer cannot reach the local service:
That is a local-service problem.
If the service is healthy but cannot authenticate to Amazon:
That is an Amazon-session problem.
If authentication works but a particular Echo does not respond:
That is a device or Amazon-side problem.
Those do not all collapse into the wonderfully unhelpful:
“Alexa TTS failed.”
I like systems where the failure boundaries resemble the component boundaries.
Stateless where possible, stateful where necessary
That is really the architectural rule underneath the whole design.
The plugin should not own complicated persistent state merely because it happens to be the thing the user sees in HomeSeer.
The Amazon session genuinely is stateful.
So the service designed to maintain that state owns it.
The HomeSeer side can remain relatively simple and disposable.
Restart it.
Upgrade it.
Reconfigure it.
The Amazon session does not necessarily have to participate in the excitement.
That makes the plugin easier to reason about and considerably less likely to turn an unrelated HomeSeer restart into an unexpected Amazon-login ceremony.
And Amazon does change
This separation is not solving an imaginary problem.
The unofficial libraries used to communicate with Alexa devices have had to change repeatedly over the years as Amazon changed authentication flows, cookies, redirects, endpoints, and other behavior.
There have even been cases where installations stopped working despite:
No local software update.
No credential change.
No network change.
Amazon changed something on its side.
The next library release adjusted.
Things worked again.
That is simply the reality of integrating with an interface that Amazon did not publish for this purpose.
Which is another reason I wanted the Amazon-facing machinery isolated.
If that layer needs surgery, I want to operate on the layer that actually changed.
Not on the HomeSeer plugin wrapped around it.
Built for a second backend it does not have yet
There is another abstraction hiding inside the plugin.
Announcements do not go directly to:
Alexa
They go to a speaker backend.
At the moment there is exactly one implementation behind that interface:
Alexa.
Which means, yes, today I have an abstraction with one thing plugged into it.
That can be either good foresight or architectural optimism.
Time will tell.
But the obvious candidate for backend number two is Google Cast.
Cast devices offer a potentially much more local transport path and, importantly, do not require maintaining the same kind of Amazon account session simply to address a speaker.
There can still be dependencies involved in producing or serving the audio used for text-to-speech, depending on how that backend is implemented.
So I do not want to claim:
“Cast means absolutely no external dependency whatsoever.”
What I can say is:
It offers a very different and potentially much more local architecture.
And I want the rest of the plugin ready for that possibility.
Nothing above the backend interface is permitted to assume:
“Announcement means Alexa.”
It means:
“Send this announcement through the selected speaker system.”
Today that happens to mean Alexa.
If Cast gets written later, the HomeSeer-facing logic should not care.
Whether that turns out to have been foresight or me leaving homework for Future Me can be decided then.
The version check that is allowed to say “I don’t know”
Because the local service depends on libraries that communicate with Amazon, their versions matter.
Amazon changes.
The libraries adapt.
And sometimes the fix for:
“Alexa suddenly stopped working yesterday”
is simply:
“There is a newer library version because Amazon changed something.”
So the plugin reports useful information about those dependencies.
Which versions are installed.
Whether a newer version appears to be available.
And whether the check itself actually succeeded.
That last part matters more than it sounds.
Because the answer is not binary.
There are three legitimate states:
Installed and current.
Installed but newer version available.
Could not determine.
That third state is deliberate.
Null does not mean green
Imagine the plugin tries to check the current library version.
The request times out.
Or the package service cannot be reached.
Or DNS fails.
Or the returned data cannot be parsed.
At that point the plugin knows:
I was unable to determine whether this is current.
What it does not know is:
Everything is up to date.
Those statements are not interchangeable.
So the health display is intentionally three-valued.
A failed check does not quietly turn into a cheerful green checkmark.
It says:
Unable to check.
That is less visually satisfying.
It is also true.
And I have already had one particularly expensive afternoon caused by software taking:
“I could not verify this”
and presenting it as:
“Verified good.”
Once is enough.
The comment is there for Future Me
There is even a comment beside that logic explaining why the unknown state must remain distinct.
Because this is exactly the kind of thing someone tidying code later may be tempted to simplify.
Three states:
true
false
null
become:
true
false
And perhaps null gets treated as true because:
“We do not know there is a problem.”
Cleaner code.
Simpler UI.
Wrong answer.
So the comment explains the reasoning.
Not merely:
Do not change this.
But:
Here is the failure this rule exists to prevent.
I increasingly like comments that explain the scar rather than just the rule.
Future Me is much less likely to “improve” something when Present Me has left evidence from the crime scene.
What it actually does
The plugin currently supports several useful Alexa output types.
It can speak arbitrary text.
Which means HomeSeer can generate announcements dynamically:
“The garage door has been open for twenty minutes.”
“Someone is at the front door.”
“The washing machine has finished.”
“There is a water leak under the kitchen sink.”
It can also play sounds from Amazon’s available sound library.
And it can trigger built-in Alexa phrases and behaviors supported by the underlying Alexa interface.
From HomeSeer’s perspective, those become ordinary automation actions.
The interesting part is not that an Echo can speak.
Alexa has been doing that for years.
The interesting part is that HomeSeer can decide when it should speak and what it should say.
Why this one gets used so much
This plugin is still beta.
It is also probably the plugin in this entire group that I personally use the most.
Because spoken feedback solves a problem screens do not.
You do not have to be looking at a dashboard.
You do not have to have your phone in your hand.
You do not have to notice a push notification.
The house can simply say:
Something happened.
That is useful for:
Warnings.
Reminders.
Doors left open.
Water leaks.
Appliances finishing.
People arriving.
Conditions becoming unusual.
Or anything else important enough that I would rather not depend on someone happening to glance at the right screen.
Once the house can speak through devices that are already sitting in its rooms, an interesting transition happens.
The automation stops merely recording state.
It gains a way to get your attention.
And that makes a surprisingly large difference.
What I like about this one
Technically, this plugin is a bridge around an unsupported direction in Amazon’s ecosystem.
Architecturally, though, the part I like is the separation.
HomeSeer does HomeSeer things.
The local service owns the ugly Amazon session.
The backend abstraction keeps Alexa from becoming permanently baked into the rest of the plugin.
The version check is allowed to admit uncertainty.
And when Amazon changes something, the component that depends on Amazon is the component expected to absorb the change.
That all seems fairly ordinary when written down.
Which is generally a sign that the boundaries are doing their job.
The result is something much more human:
The house can say something when it needs you.
For all the code underneath it, that is ultimately the feature I care about.