Create a Custom Alexa Handler Service
Additional requirements exist for using Amazon Alexa in your TomTom Digital Cockpit product.
The TomTom Digital Cockpit platform offers a stock implementation of a VPA Adaptation service for the Alexa Auto VPA, provided by the stock Alexa service. This service interacts with the Alexa engine by exchanging asynchronous JSON-based messages with the Alexa Auto Client Service (AACS) application. More information about the AACS is available in the Alexa Auto Client Service (AACS) documentation page.
These JSON messages are called "AASB" messages in the Alexa Auto documentation, because they are used by the Alexa Auto Services Bridge (AASB) Extension.
The AASB messages are grouped into "topics", each representing a domain that requires a platform implementation. For details about the AASB messages, see the AASB message interfaces and the AASB message definitions pages.
The stock Alexa service of TomTom Digital Cockpit already implements message handlers for many of the topics offered by Alexa Auto, namely:
- AddressBook
- Alerts
- AlexaClient
- APL (Alexa Presentation Language)
- AudioInput
- AudioOutput
- Authorization
- CarControl
- Messaging
- Navigation
- Notifications
- PhoneCallController
- PlaybackController
- PropertyManager
- SpeechRecognizer
- TemplateRuntime
The responsibility of a message handler is:
- To parse AASB messages received by AACS and handle them appropriately (for example, to instruct
the navigation engine to start navigation when a
StartNavigation
message is received). - To send "Reply" AASB messages to AACS, reporting whether a request from AACS was successfully executed or not.
- To send "Publish" AASB messages to AACS, for example, to send a request to Alexa or to report an event.
Some topics, though, are device-specific and cannot be implemented as part of the TomTom Digital Cockpit platform. The AlexaHandlerService is a discoverable IVI service interface that can be implemented by an OEM to fulfill one of these purposes:
- handle topics that are not supported by the TomTom Digital Cockpit platform.
- extend or replace some of the existing stock TomTom Digital Cockpit platform message handlers. Currently,
the only stock TomTom Digital Cockpit platform message handlers that can be extended or replaced with an
AlexaHandlerService
implementation are theCarControl
and theNavigation
handlers. For example, you might want to add support for switching the Cabin Light in the vehicle on and off: this is a setting that Alexa Auto supports, but for which the TomTom Digital Cockpit platform's CarControl message handler doesn't have built-in support. You could then add this functionality by implementing an AlexaHandlerService.
Example app
The TomTom Digital Cockpit SDK comes with an example app showing how to include Alexa in your product and add
custom Alexa handler services. See the
examples/alexa
directory.
By default, this application is excluded from the build, because it requires Alexa libraries that
are not publicly available. To build the Alexa example app, you should edit the top-level
gradle.properties
file:
optInToAlexaExamples=true
Alternatively, you can set this Gradle property from the command-line:
./gradlew assemble -PoptInToAlexaExamples=true
Additionally, you will need to register your own Alexa product with Amazon and obtain a set of Alexa IDs, as explained on the Register your Alexa product with Amazon page.
Once you have your own set of Alexa IDs, you can configure the Alexa example app:
- Edit the
examples/alexa/app/src/main/res/values/alexa_ids.xml
file and add your own Alexa IDs. - Set the
disableAlexaDeviceIdBuildTimeCheck
Gradle property totrue
to disable the build-time check for Alexa IDs.
Alternatively, you can also configure the Alexa example app by setting the alexaDeviceInfoClientId
and alexaDeviceInfoProductId
properties in the top-level local.properties
file or as Gradle
properties. In this case you won't need to set disableAlexaDeviceIdBuildTimeCheck
to true
.
The example app includes 2 examples of custom Alexa handler services:
CustomEqualizerControllerHandlerService
: sample implementation of an Alexa handler service for the EqualizerController topic. It shows how to handle user requests such as "Increase the bass level" or "Decrease the treble level".CustomCarControlHandlerService
: sample implementation of an Alexa handler service for the CarControl topic. It shows how to define custom CarControl endpoints and assets, as well as how to handle user requests such as "Turn on the light", "Increase the light brightness" or "Switch on my custom device". More details about customizing the Alexa Car Control feature are available in the Customize the Alexa Car Control Feature tutorial.
How to create a custom Alexa Handler service
The following sections describe how to create a custom Alexa Handler service implementation.
The Digital Cockpit Alexa example app shows two implementations of such a service, a
custom car control handler
and a
custom equalizer control handler.
The code snippets show a generic implementation of such a service, but they link to the source
code of the
CustomCarControlHandler
example implementation.
Create a service implementation module
To implement a custom Alexa Handler service, create a new module under examples/alexa
(for
example examples/alexa/customalexahandler
) and add a Gradle build script.
1import com.tomtom.ivi.platform.gradle.api.framework.config.ivi23plugins {4 `kotlinx-serialization`5}67ivi {8 // The AlexaHandlerService API is currently an experimental feature, and has to be explicitly9 // opted in.10 optInToExperimentalApis = true11}1213dependencies {14 implementation(libraries.iviPlatformAlexaApiCommonUtil)15 implementation(libraries.iviPlatformAlexaApiServiceAlexahandler)16}
Declare Alexa dependencies in the
/build-logic/libraries.versions.toml
file:
iviPlatformAlexaApiCommonUtil = { module = "com.tomtom.ivi.platform:platform_alexa_api_common_util", version.ref = "iviPlatform" }iviPlatformAlexaApiServiceAlexahandler = { module = "com.tomtom.ivi.platform:platform_alexa_api_service_alexahandler", version.ref = "iviPlatform" }
Implement the Alexa Handler service
The Alexa Handler service can be implemented by defining a class (for example,
CustomAlexaHandlerService
) that inherits from the abstract AlexaHandlerServiceBase
base class
and implements the methods defined in the AlexaHandlerService interface.
The service implementation needs to set a number of properties for configuring itself with the TomTom Digital Cockpit platform. Please refer to the AlexaHandlerService API reference documentation for detailed information on these properties.
The properties can be set by overriding the onCreate()
method. For example:
1import com.amazon.aacsconstants.Topic2import com.tomtom.ivi.platform.alexa.api.service.alexahandler.AlexaHandlerService34override fun onCreate() {5 super.onCreate()67 topic = Topic.CAR_CONTROL8 priority = AlexaHandlerService.DEFAULT_HANDLER_PRIORITY9 aacsConfiguration =10 """11 {12 "aacs.carControl" : {13 "endpoints": [14 ...15 ]16 }17 }18 """1920 serviceReady = true21}
Additionally, the service needs to override a number of methods. Please refer to the AlexaHandlerService API reference documentation for detailed information on these methods.
Parsing AASB messages
The Alexa Handler service is able to communicate with the Alexa Auto Client Service by exchanging JSON based Alexa Auto Services Bridge (AASB) messages. More information on the format of the AASB messages is available in the Alexa Auto AASB message definitions page.
Whenever the TomTom Digital Cockpit platform receives a JSON AASB message from the Alexa Auto Client Service which matches with the topic configured in the service implementation, the AlexaHandlerService.onMessageReceived method is called.
The service implementation needs to parse this message and handle it as needed. Typically, in order to parse an AASB message, you would:
-
Define a data class that represents the message payload to be parsed. For example, the data class representing the CarControl SetControllerValue message would look like this:
CustomAlexaHandlerService.kt
1@Serializable2data class SetControllerValueIncomingMessagePayload(3 val controllerType: String,4 val endpointId: String,5 val turnOn: Boolean6) -
Decode the AASB message into an instance of the data class using the
parseJsonMessage
function. For example:
CustomAlexaHandlerService.kt
1override suspend fun onMessageReceived(message: AacsMessage): Boolean =2 when (message.action) {3 Action.CarControl.SET_CONTROLLER_VALUE -> handleSetControllerValue(message.payload)4 else -> false5 }67private fun handleSetControllerValue(message: String): Boolean {8 val parsedMessage = parseJsonMessage<SetControllerValueIncomingMessagePayload>(message)9 return true10}
Sending AASB "Reply" messages
In most cases, you will need to send a reply message to AACS to inform Alexa about whether the request was successfully executed or not. Typically, in order to send an AASB reply message, you would:
-
Define a data class that represents the message payload to be sent to AACS. For example, the data class representing the CarControl SetControllerValueMessageReply message would look like this:
CustomAlexaHandlerService.kt
1@Serializable2data class SetControllerValueOutgoingMessagePayload(3 val success: Boolean4) -
Create an instance of this class and send it using the AacsSenderWrapper helper class. For example:
CustomAlexaHandlerService.kt
1private fun sendSetControllerValueReply(messageId: String, success: Boolean) {2 val aacsSender = AacsSenderWrapper(iviServiceHostContext)3 aacsSender.sendReplyMessage(4 messageId,5 Topic.CAR_CONTROL,6 Action.CarControl.SET_CONTROLLER_VALUE,7 SetControllerValueOutgoingMessagePayload(success)8 )9}
Sending AASB "Publish" messages
Some domains require the handler to proactively send a "Publish" message to AACS, for example, to send a request to Alexa or report an event. Typically, in order to send an AASB "Publish" message, you would:
-
Define a data class that represents the message payload to be sent to AACS. For example, the data class representing the DoNotDisturbChanged message would look like this:
1@Serializable2data class DoNotDisturbChangedOutgoingMessagePayload(3 val doNotDisturb: Boolean4) -
Create an instance of this class, encode it as a JSON string and send it using the AacsSenderWrapper helper class. For example:
1private fun sendDoNotDisturbChanged(success: Boolean) {2 val aacsSender = AacsSenderWrapper(iviServiceHostContext)3 aacsSender.sendMessage(4 Topic.DO_NOT_DISTURB,5 Action.DoNotDisturb.DO_NOT_DISTURB_CHANGED,6 DoNotDisturbChangedOutgoingMessagePayload(value)7 )8}
Create a service host
Your module will also need to define a service host where the service will be running, as well as provide a service host builder. This can be achieved by creating two classes.
-
A
CustomAlexaHandlerServiceHost
class:
CustomAlexaHandlerServiceHost.kt
1import com.tomtom.ivi.platform.framework.api.ipc.iviservice.IviDiscoverableServiceIdProvider2import com.tomtom.ivi.platform.framework.api.ipc.iviservice.IviServiceHostBase3import com.tomtom.ivi.platform.framework.api.ipc.iviservice.IviServiceHostContext45/**6 * A [CustomAlexaHandlerService] host server.7 */8internal class CustomAlexaHandlerServiceHost(9 iviServiceHostContext: IviServiceHostContext,10 iviDiscoverableServiceIdProvider: IviDiscoverableServiceIdProvider11) :12 IviServiceHostBase(iviServiceHostContext) {1314 override val iviServices = setOf(15 CustomAlexaHandlerService(iviServiceHostContext, iviDiscoverableServiceIdProvider)16 )17} -
A
CustomAlexaHandlerServiceHostBuilder
class:
CustomAlexaHandlerServiceHostBuilder.kt
1/**2 * A [CustomAlexaHandlerServiceHost] builder used to build a [CustomAlexaHandlerService]3 * host.4 */5class CustomAlexaHandlerServiceHostBuilder : IviServiceHostBuilder() {67 override fun build(iviServiceHostContext: IviServiceHostContext): IviServiceHostBase =8 CustomAlexaHandlerServiceHost(iviServiceHostContext) {9 getDiscoverableServiceId(it)10 }1112 companion object13}
Please ensure that the CustomAlexaHandlerServiceHostBuilder
class is added to the root of your
module hierarchy.
Configure the service host deployment
Define an IVI service host implementation in your gradle file. This can also be defined in a
top-level gradle file (for example, iviservicehosts.gradle.kts
) so it can be used in a
multi-project build, including the tests.
/examples/alexa/iviservicehosts.gradle.kts
1import com.tomtom.ivi.buildsrc.dependencies.ExampleModuleReference2import com.tomtom.ivi.platform.gradle.api.common.dependencies.IviPlatformModuleReference3import com.tomtom.ivi.platform.gradle.api.common.iviapplication.config.IviServiceHostConfig4import com.tomtom.ivi.platform.gradle.api.common.iviapplication.config.IviServiceInterfaceConfig56/**7 * Defines a configuration for the custom Alexa Handler service.8 *9 * The configuration specifies the service host implementation and the list of interfaces10 * implemented by this service host.11 */12val customAlexaHandlerServiceHost by extra {13 IviServiceHostConfig(14 serviceHostBuilderName = "CustomAlexaHandlerServiceHostBuilder",15 implementationModule = ExampleModuleReference("services_customalexahandler"),16 interfaces = listOf(17 IviServiceInterfaceConfig(18 serviceName = "AlexaHandlerService",19 serviceId = "com.tomtom.ivi.example.service.customalexahandler",20 serviceApiModule = IviPlatformModuleReference(21 "platform_alexa_api_service_alexahandler"22 )23 )24 )25 )26}
Register the service host build configuration in the main application's build script.
/examples/alexa/app/build.gradle.kts
1import com.tomtom.ivi.platform.gradle.api.common.iviapplication.config.IviServiceHostConfig2import com.tomtom.ivi.platform.gradle.api.framework.config.ivi34// Define the service host configs as defined in the top-level `iviservicehosts.gradle.kts` file.5apply(from = rootProject.file("examples/alexa/iviservicehosts.gradle.kts"))67// Use Gradle's extra extensions to obtain the `customAlexaHandlerServiceHost` config as defined in8// the top-level `iviservicehosts.gradle.kts` file.9val customAlexaHandlerServiceHost: IviServiceHostConfig by project.extra1011ivi {12 application {13 enabled = true14 services {15 // Add the custom Alexa Handler service host to the application.16 addHost(customAlexaHandlerServiceHost)17 }18 }19}2021// The rest of the build script, dependencies, etc.
References
- TomTom Digital Cockpit AlexaHandlerService
- TomTom Digital Cockpit Customize the Alexa Car Control Feature
- Alexa Auto Client Service (AACS)
- Alexa Auto AASB message interfaces
- Alexa Auto AASB message definitions