How to build Alexa skills with Kotlin

August 10, 2017
Written by

bin-colour

My local council operates on a fortnightly basis where they collect household waste one week, and recycling and garden waste the following. All I have to do is make sure I roll the correct wheelie bin once a week to a designated place so they can come and collect.

I want to emphasise the word “correct” here since for household it’s the black bin, and for recycling and garden waste the orange and green bins, and I’ve lost count of how many times I came back home to find I’ve rolled the wrong bin colour for that week.

To solve that I decided to create an Alexa skill that I can just ask which the correct colour for that week is. My local council offers a pdf file that tells me the bin colour of each week, so every odd week is orange and green, and every even week is black. Knowing this is great because I don’t need to make any extra external requests to figure this stuff out.

Our tools

You can also download the entire code for this project here.

Creating an AWS Lambda function with Kotlin

Alexa skills can be powered by your server or by serverless functions you can create using Amazon’s infrastructure. My colleague Ricky wrote about how to build an IVR using AWS Lambda functions and Twilio some time ago. We will also use AWS Lambda functions here.

Open up IntelliJ and create a new Gradle project for Kotlin(Java) called “AlexaBinColourSkill” making sure you enable “Auto-import”. Save it to a place you remember. I usually store my Kotlin projects under ~/Projects/Kotlin.

Once the project finishes loading open up build.gradle and add the dependencies we’re going to need later.
group 'uk.co.placona'
version '1.0-SNAPSHOT'

buildscript {
   ext.kotlin_version = '1.1.3-2'
   ext.spring_boot_version = '1.4.3.RELEASE'

   repositories {
       mavenCentral()
       jcenter()
   }
   dependencies {
       classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
       classpath "com.github.jengelman.gradle.plugins:shadow:1.2.3"
       classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version"
   }
}

apply plugin: 'kotlin'
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'org.springframework.boot'

repositories {
   mavenCentral()
}

dependencies {
   compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
   compile 'com.amazon.alexa:alexa-skills-kit:1.1.2'
   compile 'com.amazonaws:aws-lambda-java-core:1.0.0'
   compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.8.2"

   compile 'log4j:log4j:1.2.17'
   compile 'org.apache.commons:commons-lang3:3.3.2'
   compile 'org.apache.directory.studio:org.apache.commons.io:2.4'
   compile 'org.eclipse.jetty:jetty-server:9.0.6.v20130930'
   compile 'org.eclipse.jetty:jetty-servlet:9.0.6.v20130930'
   compile 'org.slf4j:slf4j-api:1.7.10'
}

compileKotlin {
   kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
   kotlinOptions.jvmTarget = "1.8"
}

IntelliJ should automatically download and sync those for you, but you can also force it if it isn’t happening.

When it’s done, create a new package called speechlets under src/main/kotlin. Inside this package create a new Kotlin class called BinColourSpeechletRequestHandler and add the following code to it.

package speechlets
import java.io.*
import com.fasterxml.jackson.module.kotlin.*

data class HandlerInput(val who: String)
data class HandlerOutput(val message: String)

class BinColourSpeechletRequestHandler {
   val mapper = jacksonObjectMapper()

   fun handler(input: InputStream, output: OutputStream): Unit {
       val inputObj = mapper.readValue<HandlerInput>(input)
       mapper.writeValue(output, HandlerOutput("Hello ${inputObj.who}"))
   }
}

We’re just trying to make sure our setup works, so we’ll push this function to AWS and request it before we start modifying it for our needs.

Open a new terminal window on the same directory where you created your project and run the following command to turn the application into a jar file:

./gradlew shadowjar

If you now go back to IntelliJ, you will see that there’s a new jar file under build/libs. Right click on it and choose copy path.

Back on your terminal application enter the following command to create a new function:

aws lambda create-function —region eu-west-1 —function-name AlexaBinColourSkill 
—zip-file "fileb:///Users/mplacona/Projects/Kotlin/AlexaSkillBinColour/build/libs/AlexaSkillBinColour-1.0-SNAPSHOT-all.jar" 
—role arn:aws:iam::XXXXXXXXXX:role/lambda_basic_execution 
—handler speechlets.BinColourSpeechletRequestHandler::handler —runtime java8 
—timeout 15 —memory-size 128

There are some things you’re going to have to replace here. The first one is the region, which according to this page can be:

Lambda functions for Alexa skills can be hosted in either the US East (N. Virginia) or EU (Ireland) region. These are the only regions the Alexa Skills Kit supports.

So if you’re in Europe, you don’t need to change anything, but if you’re elsewhere, your best bet is to use us-east-1.

Next, you will paste the file path you copied earlier to replace the one you see above. Notice it uses three slashes in the beginning.

Create a new role to attach to your function by heading to AWS’s IAM Roles. Click “Create new Role” -> select “AWS Lambda” -> Choose “AWSLambdaBasicExecutionRole” and on the next screen name this policy lambda_basic_execution

Lastly, you’ll have to add your AWS account ID for the function role in —role. You can get that by going to the AWS account’s page.


When you run that you should see a similar result in your terminal.

If you want to test that your function works you can invoke it directly from the same terminal window by running:
aws lambda invoke —region eu-west-1 —function-name AlexaBinColourSkill —payload '{"who": "Kotlin"}' output.txt; cat output.txt

You will then see the output of your function with a message saying “Hello Kotlin”. You’ve just deployed your first Kotlin function to AWS Lambda.

giphy.gif

But let’s have a look at how we can modify this function so we can use it to interact with our Alexa Skill.

Updating our function

Alongside the BinColourSpeechletRequestHandler class create a new Kotlin class called BinColourSpeechlet and make it implement the Speechlet interface.

package speechlet
import com.amazon.speech.speechlet.Speechlet
class BinColourSpeechlet : Speechlet {
}

Your IDE will ask you to implement the methods in Speechlet. Let it do its thing, and you will end up with overrides for onSessionStarted, onSessionEnded, onIntent and onLaunch. You can read more about what each of these methods is for here.

At the bottom of that class create two methods: one for returning the week number of the year, and one to return the message depending on the week of the year.

private fun binColourResponse(): SpeechletResponse {

   val weekNumber = weekOfYear()
   var binColourOutput = ""

   // black
   if (weekNumber % 2 == 1) {
       binColourOutput = "This week's bin colour is orange and green"
   } else if (weekNumber % 2 == 0) {
       binColourOutput = "This week's bin colour is black"
   }

   val speechText = "Welcome to the bin colour bot. $binColourOutput"
   val card = SimpleCard()
   card.title = "BinColour"
   card.content = speechText
   val speech = PlainTextOutputSpeech()
   speech.text = speechText

   return SpeechletResponse.newTellResponse(speech, card)
}

private fun weekOfYear(): Int {
   val date = LocalDate.now()
   val weekFields = WeekFields.of(Locale.getDefault())
   return date.get(weekFields.weekOfWeekBasedYear())
}

Notice that what we’re doing here is just alternating the response according to the week number based on the information I got from the council’s website.

Now that our logic is written, we just need to make sure we call our SpeechletResponse when the skill is launched or when we ask the question directly. Example requests would be:

Alexa, open bin colour
Alexa, what’s this week’s bin colour?

That should drop us in the binColourResponse method. Let’s change the onLaunch and onIntent methods.

@Throws(SpeechletException::class)
override fun onIntent(request: IntentRequest, session: Session): SpeechletResponse {
    val intent = request.intent
    val intentName = intent?.name

    if ("BinColourIntent" == intentName) {
        return binColourResponse()
    } else {
        throw SpeechletException("Invalid Intent")
    }
}

@Throws(SpeechletException::class)
override fun onLaunch(request: LaunchRequest, session: Session): SpeechletResponse {
    return binColourResponse()
}

Now is also a good time to remove any of the TODO("not implemented") code from this class as these will error otherwise.

We’re done here, but we need to change our handler class so it knows it can use the class we just created to get this information. Go back to the BinColourSpeechletRequestHandler class and change it to the following:

class BinColourSpeechletRequestHandler : SpeechletRequestStreamHandler(BinColourSpeechlet(), supportedApplicationIds) {
    companion object {
        private val supportedApplicationIds = HashSet<String>()

        init {
            supportedApplicationIds.add("amzn1.ask.skill.YOUR-SKILL-ID")
        }
    }
}

We will replace YOUR-SKILL-ID later on after we’ve created our skill. For now, we can just update this function configuration by running the following in terminal:

./gradlew shadowjar

aws lambda update-function-configuration —region eu-west-1 —function-name AlexaBinColourSkill —handler speechlets.BinColourSpeechletRequestHandler

aws lambda add-permission —region eu-west-1 —function-name AlexaBinColourSkill —statement-id `date  %s` —action lambda:invokeFunction —principal alexa-appkit.amazon.com

This will update the function’s handler and then make our function available to be used by an Alexa Skill. Next, we will get our code deployed to AWS Lambda again by updating our function code.

We’re ready to start hooking this function up with our Alexa skill, so let’s look at how to do that.

Creating a new Alexa Skill

Head to the skills page in the Alexa portal and click on the “Add new Skill” button. Change the language accordingly and give your skill a name. I’ve named mine “Central Bedfordshire Collection”. You can use the same value for the “Invocation Name”.

Hit “Save” and you will notice you now have an “Application Id” on the screen. Make a note of that as we will update our function with it in a bit. Click “Next” and you will be shown a screen that asks you to create an interaction model. Under “Intent Schema” you will need to add an intent that maps to the one we created in our function.
{
  "intents": [
    {
      "intent": "BinColourIntent"
    },
    {
      "intent": "AMAZON.HelpIntent"
    }
  ]
}

Under “Sample Utterances”, we will define how we want to interact with our Skill. You can change all this later but for now we will have the following:

BinColourIntent what's this week's bin colour
BinColourIntent bin colour

Click next and your interaction model will start building. On the next screen, you will then be able to tell this skill which AWS Lambda function to use as well as its geographical location, which should match up with the region you created your function.

If you still have your terminal window open you can just copy the FunctionArn value that was outputted to the screen.

If you’ve already closed it you can get it from the function’s settings page.

Click “Next” and you will be taken to a test page where you can start testing your skill programmatically.

If you try to invoke your skill, you will notice there is a problem.

And that is because we wrote our function securely so no one else other than our Skill can use it. Back on IntelliJ open up the BinColourSpeechletRequestHandler class again and replace the value on supportedApplicationIds with the “Application Id” you copied earlier from the “Skill Information” page.

Compress and deploy your function again.

./gradlew shadowjar
aws lambda update-function-code —region eu-west-1 —function-name AlexaBinColourSkill —zip-file "fileb:///Users/mplacona/Projects/Kotlin/AlexaBinColourSkill/build/libs/AlexaBinColourSkill-1.0-SNAPSHOT-all.jar"

Go back into your browser and you will now be able to test the function.


Click “Next” and you will be prompted to enter some publishing information. This and the next section are mandatory if you want to be able to test this on a real device. Once you have completed those sections, submitting for certification is not necessary, and you can even open this app for beta testing by your friends.

Testing your Alexa skill on a real device

Now that your skill is ready, all you need to do is open the Alexa app on your phone, select “Skills” and then “Your Skills”. In there you will see your skill which you can then enable.

Test it out by saying:

“Alexa, open Central Bedfordshire bin collection”

No more worries

There are better things to remember in life than what is this week’s bin colour! With only a few lines of code and some configuring, we’ve built an Alexa skill that removes this preoccupation from our lives forever.

But this is only the tip of the iceberg as we could add many other things to make this skill even smarter. How about getting it to tell you the exact day when the collection is coming? Does your council have an API you could use to get some other useful information?

I would love to hear about how you make your device even smarter. Hit me up on twitter @marcos_placona or send me an email on marcos@twilio.com to tell me more about it.