JavaScript – A Bedtime Story

It all started with a nightlight that looked like a snowman and ended up taking 89 lines of JavaScript to make my kids go to sleep.

Like most young children, mine have always slept with a small nightlight that plugs-in directly to the wall outlet. It’s a warm, comforting, not-too-bright glow.

But this past Christmas, my wife’s parents gave them a large, ten inch tall, battery powered, super-bright nightlight that looked like a snowman. My son and daughter were enthralled, and it quickly became irreplaceable and a required part of their bedtime routine.

The problem, like most parents can tell you, is that – I assume for safety and/or liability reasons – most large nightlights are battery powered. There’s no cord plugging into an outlet. And, boy oh boy, was this snowman battery powered. It took three C batteries to light him up. And as parents will also tell you, it’s not good enough to only turn it on at bedtime and then sneak back in the middle of the night to turn the light off to save power. The slim chance that a kid who is now used to sleeping with a bright nightlight will wake up before morning, find themselves in a dark room, and freak out is way worse than having to replace batteries.

But this particular snowman? Two C batteries. Every. Three. Days.

So that lasted until February before we had enough and began looking for a rare, plug-in variety. I quickly learned that non-battery powered nightlights on Amazon fall into three categories:

  1. Sketchy under $10 ones that might as well say “fire hazard” in the product listing.
  2. Lights in the $25 – $40 range that might be OK, but look suspiciously like cheap, dollar store plastic.
  3. Boutique, child-themed, bedroom illumination appliances in the $70 – $200 range.

After narrowing down three options in the second group, I let my son make the final decision and he chose a $29 model that promised to project a multi-colored array of rotating constellations on the ceiling. And I should have known better. All of the parent and consumer product review websites that I normally turn to for buying advice all said the same thing: if you want a bright nightlight that plugs in and won’t burn your house down, you might as well pony up for something quality made.

In writing this blog post, I looked up my order so I could show some details about the light. But if it’s any indication of how well it turned out, I’ll just show this screenshot from my order history. That’s all I could get because both the product listing and company are gone from Amazon.

But this isn’t a post about a fly-by-night Amazon seller. This post is about a Saturday afternoon’s JavaScript diversion. Which I’m getting to soon, I swear.

But, quickly: the globe around the light didn’t fit or attach, the inner lid sitting on top of the bulb kept falling off, and it didn’t project a star pattern. But! It did change colors and my kids loved it.

Until it broke. And I fixed it.

And then it broke again. For good.

So for the past three weeks my kids have been back to their old, dim, drugstore-bought nightlight. And if they weren’t falling asleep because of that it would actually be OK. But it’s worse than that. Now, every night when I say goodnight, they whimper and tell me how much they missed their old snowman light. And how much they want a “fun” light to sleep with. Sleepless nights I can deal with. But when the last image of your kids every single night is them genuinely sad – and not even in a whiny, complaining, spoiled type of way – just matter-of-factly sad that a toy they loved is now gone, you as a parent start looking on Amazon all over again.

And I don’t know how I missed it during my first search, but this time my wife sent me a link to Echo Glow.

Our family isn’t really in the Google ecosystem, so we’ve never had a Google Assistant smart speaker. And my wife outright loathes Siri. So, when I got to beta test the first Echo, we found a helpful, useful middle ground that we’ve never switched away from. And as a decade-long fan of Sonos, even HomePod couldn’t sway us away from Alexa.

So, for $30 I can get a first-party, plug-in, made-for-kids, Amazon nightlight that looks nice, changes colors, and does all the things you’d expect of a smart bulb? Sold.

Buuuut, obviously, I’ll need an Echo of some sort to control it. We’ve got a few around the house, but there have never been any electronics in the kids’ bedroom. So after running it by my wife, I added a new Echo Dot to our order. Our kids love asking Alexa questions on our existing Echoes. And my son loves exploring new music with his Spotify kids account. (Seriously, Spotify, thank-you so much for making a dedicated kids app.) My plan was they’d love the new nightlight, could have fun controlling it with the Echo Dot, which could also serve double-duty as a replacement for their bedroom sound machine.

Wait? Did I not tell you about their sound machine already? Because that’s where the JavaScript comes in.

My son had colic. As a baby, his crying was so ceaselessly unending that we still refer to those first twelve months as “the dark times”. We eventually found a combination of Baby Merlin’s Magic Sleepsuit (seriously) and white noise that would finally, finally help him fall asleep by 3am. Around the house in his rocker during nap time, he cuddled up to an iPod touch running Swish. (Thanks, Daniel!) And in his room we had a white noise machine made for babies.

And we still have it.

Try as we might, we could never transition him (and now his younger sister) away from it. So, now, six years later, they both fall asleep to the sound of a light forest rain that still gives me anxiety almost as bad as the Slack knock knock brush sound.

So when I swapped out their ancient white noise machine for the new Echo, I naively thought it would be easy to add an Alexa skill to play some rain sounds at night.

Narrator: It wasn’t.

As the parent of a formerly fussy baby, I have exacting requirements. Which means the white noise app:

  1. Must play a gentle rain noise. No thunder. No babbling brook or chirping birds. No rain forest frogs. Just rain.
  2. The sound has to be continuous. If it loops, it can’t have a break while the audio starts over. If that’s not possible, then each loop needs to be long enough that my kids aren’t going to notice.
  3. I don’t want to play twenty questions with Alexa just to get it playing.

Turns out, that’s not how any of the 283 available white noise Alexa skills work.

Once I narrowed down the options to those that didn’t come from obviously shady developers, I started giving them a try. The best was White Noise by TMSOFT, who also happens to make the iOS white noise app I’ve used for years to help me sleep when I’m away from home.

But each Alexa skill had the same problem: the sound would end after an hour or two before they asked you to subscribe for unlimited play time. At first I just assumed this was part of the new wave of everything-is-a-subscription software. Which, as a developer, I totally get – and I’m not opposed at all to paying for an app that helps my kids sleep. But still, a subscription? Not just a one time purchase?

So, I started investigating and finally realized something that makes total sense when you think about it. These Alexa skills are all cloud based. As far as I’m aware, they’re not actually stored on your Echo. And, so, the audio isn’t on the device either. It’s streamed. Every time. And an app that streams eight hours of nighttime audio every night for every customer is quickly going to burn through some bandwidth. And bandwidth isn’t free. And thus the justification for the subscription.

Again, not opposed to paying, but let’s see if I can find another solution that works with what I already have. Could I just…play?…my own rain noise?

I have some old, multi-hour long, rain sound mp3s in my iTunes library dating back to when I used a click-wheel iPod plugged into a bedroom speaker to fall asleep in college. And because they’re in my iTunes library, that means they’re available in iTunes Match. And Alexa can play Apple Music, so…

Nope. Apple Music and only Apple Music – as in the streaming service. Not your real music library in Apple’s cloud.

But, at one point years ago I paid for Amazon’s cloud music storage service and uploaded all of my music to their service for playback. Unfortunately, not only has that service been discontinued, but Alexa won’t play music from the libraries of those of use grandfathered in.

Next up. Spotify. Two problems.

  1. Yes, their catalog does contain lots of rain sounds and other relaxing and white noise “albums”. But, like an album, they’re all just songs. As in mostly 5 – 10 minutes long each – if even that long. I tested a few on repeat just to see how they sounded when looped, but you could clearly hear the gap as playback restarted.
  2. Assuming, I did find a long enough track, Spotify only allows your account to play music on one device at a time. Admittedly, Amazon has done the best at supporting multiple users and families of any of the tech ecosystems, but I still haven’t figured out how to connect multiple Spotify accounts to a device. So every time my kids’ rain starts, that would stop the music I’d otherwise be listening to and vice versa.

Ok, let’s get clever. I pay for YouTube Premium, and YouTube has some absolutely insane users who uploaded hours of lengthy video content. So it took all of thirty seconds to find a suitable eight hour (!!!) YouTube “video” of rain noise.

Sadly, there’s no official YouTube skill for Alexa. I assume because something something tech giants can’t play nice together. And even if the few 3rd party YouTube Alexa apps I found weren’t totally sketchy, I wasn’t ready to hand over access – even if the OAuth permissions were limited.

Finally, I turned to my best friend, Plex. Last year I gave up iTunes Match as well as all the other TV / movie services and just started keeping our content hosted locally. That includes our family music library. And, sure enough, Plex has a nice, officially supported skill for Alexa.

I downloaded the eight hour rain video from YouTube I mentioned above – all 13.87 GB of it. And converted it into a 461 MB mp3. Dropped it into my Plex library, and, boom! Playback success.

Sadly, the Plex skill violates requirement #3. It’s way too verbose to start playing when dealing with sleepy children. Back to the drawing board.

If I may skip ahead to the end quickly, now that I’m writing this and looking back at all the trouble I went through to find a working solution I was happy with, I probably should have saved myself a lot of time and just paid for one of the subscription white noise Alexa apps. But that’s not what a nerd does when they’re faced with an annoying challenge and the opportunity to learn something new.

Instead, I decided it was time to write my own Alexa skill. And so now, 1,985 words deep into this blog post, I get to the point and the part about JavaScript.

All of the existing Alexa skills I tried do too much. Not surprisingly in a competitive market, they try and stand out by competing on features. But as a parent, I just need something that works. And kids are creatures of habit. They want the exact same routine every night. They don’t need a library of 200 white noise options with durations, times, cross fades, or anything else. That’s exactly why, as the parent of a new born six years ago, Swish was on my wife’s home screen. Open the app. Done. No fuss. Nothing to press.

So the goal for my Alexa app was to say an invocation phrase. And. That’s it. No dialog. No choices. Nothing else.

I’m happy to say that after cobbling together a few StackOverflow answers and the Alexa Hello World template, my solution does that. After I finish reading my kids their nightly book, I’ll say

Alexa, start bedtime routine

and she’ll reply

Night, night. Sleep tight. Night, night. I love you.

The new nightlight will start flickering like a campfire at 30% brightness, and that eight hour mp3 of rain noise, hosted on my own web server, will gently stream into my kids’ bedroom

My first pass at making it work just started the audio playing. But I say the above goodnight phrase to my daughter every night when I tuck her in. Actually, she first said it to me about a year ago. And I’ve repeated back it ever since. It only seemed natural to have their new, all knowing, disembodied companion say it as well.

To all the engineers who work on making ecosystems for developers available, open, and hackable, so that I can put together something like this in a couple hours even when I know better and should have just kept their old sound machine plugged in, thank you. And sleep tight.

Technical Notes

The Alexa skill web debugging environment is, how should I put it? Awesome. When you create your project, Amazon spins up a Node lambda environment for you, gives you a mostly comprehensible GUI to set basic parameters, and then a really nice code editor with error checking to write your JavaScript.

Click a few buttons, and everything is built and deployed for testing in under 30 seconds. If the computer you’re working on is near an Echo device that is signed in to the same Amazon developer account, your new dev skill is just available for you to use. No setup needed. If not, you can use their web debugger, which lets you type to Alexa or you can use your browser’s microphone functionality to speak to her like a real device. And if your skill is designed to run on an Echo with a screen, Amazon even displays a preview of the visual output as well.

That’s just the debugger for interacting with Alexa. Before you can do that you first have to speak an invocation phrase to launch your skill. How do you know if what you say will properly trigger Alexa? They have a debugger for that, too. Type an invocation phrase, and Amazon will parse it and tell you which, if any, of your intents match.

And speaking of invocation phrases and intents, a funny thing happened on the way to an AI powered bedtime routine.

I initially tried to launch my skill (and stop playback in the morning) with phrases like “goodnight”, “go to sleep”, “time for bed”, “wake up”, etc.

Much like how Siri (unhelpfully) works with third party apps, Alexa assumed common phrases like those were meant for her and her only. (At least in my testing. Maybe I was doing something wrong?) When I’d try to stop the rain noise by saying “wake up”, Alexa would respond with a cutesy phrase like

Hello. I’m right here.

Similar things would happen with every other phrase I could think of around how you might verbally say goodnight or good morning. Alexa always intercepted the invocation phrase before it got to my app. (Just like if you have ever asked Siri to play music or set a reminder with a non-system app.)

So, sitting at my desk, frustrated because I just want to test the damn thing and at this point I could care less what I say as long as I can find out if my code even works or not, I pick the first random phrase I can think of that can’t possibly be interpreted in any other way.

So, earlier when I said that I told Alexa to run my custom skill by saying

Alexa, start bedtime routine

I lied.

Now that I realize the phrases I was first trying were getting intercepted, I could probably make that one work. But in the heat of the moment when I needed one that would just work, I came up with something else. So, now, when it’s time to say goodnight to my kids, I tell her

Alexa, open a good bottle of Scotch

Anyway, this was my first time ever dipping my toes into the development side of Amazon’s voice ecosystem. And I’m incredibly impressed. Folks can argue about which tech giant has the smartest or most useful voice assistant, but after the deep dive I’ve done on a new product using Siri on iOS the past few months, the difference between Amazon and Apple’s documentation, development and debugging environments are night and day.

This afternoon was a nice break from my normal tech stack and a lot of fun. I’m excited to try a real idea one day, or see how Amazon’s voice model influences the Siri app I’ve been working on.

For the curious, here’s my cobbled together Alexa skill index.js. Apologies to all the Amazon engineers whose API I’ve made a mess of by not reading the documentation carefully enough.

const Alexa = require('ask-sdk-core');

const soundURL = "https://domain.com/rain.mp3";

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle(handlerInput) {
        return handlerInput.responseBuilder
            .speak("Night night sleep tight night night I love you.")
            .addDirective({
                type: 'AudioPlayer.Play',
                playBehavior: 'REPLACE_ALL',
                audioItem: {
                    stream: {
                        token: "0",
                        url: soundURL,
                        offsetInMilliseconds: 0
                    }
                }
            })
            .getResponse();
    }
};

const GoodScotchIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'GoodScotchIntent';
    },
    handle(handlerInput) {
        return handlerInput.responseBuilder
            .getResponse();
    }
};

const CheapWhiskeyIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'CheapWhiskeyIntent';
    },
    handle(handlerInput) {
        return handlerInput.responseBuilder
            .speak("Good morning.")
            .addAudioPlayerStopDirective()
            .getResponse();
    }
};

const ExitHandler = {
    canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;

    return request.type === 'IntentRequest' &&
        (request.intent.name === 'AMAZON.StopIntent' ||
        request.intent.name === 'AMAZON.CancelIntent');
    },
    handle(handlerInput) {
        return handlerInput.responseBuilder
        .getResponse();
    }
};

const ErrorHandler = {
    canHandle() {
        return true;
    },
    handle(handlerInput, error) {
        console.log(~~~~ Error handled: ${error.stack}); const speakOutput = Sorry, please try again.; return handlerInput.responseBuilder .speak(speakOutput) .getResponse(); } }; exports.handler = Alexa.SkillBuilders.custom() .addRequestHandlers( LaunchRequestHandler, GoodScotchIntentHandler, CheapWhiskeyIntentHandler, ExitHandler, ) .addErrorHandlers( ErrorHandler, ) .lambda();