Google Home Guide
Learn to make a Goole action using Grails
Authors: Ryan Vanderwerf, Sergio del Amo
Grails Version: 3.3.0
1 Grails Training
Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.
2 Getting Started
In this guide you are going to learn how to create a Grails application to host a Google Action for your Google Home device to interact with. We will build, deploy, and set up everything necessary to facilitate this on Google App Engine Flexible and Google APIs.
We will be using the early access unofficial Java SDK to build actions. This is because the only official SDK is built with node.
2.1 What you will need
-
Some time on your hands
-
A decent text editor or IDE
-
A Google account
-
A Google Cloud account
-
curl command to help debugging
2.2 How to complete the guide
To get started do the following:
-
Download and unzip the source
or
-
Clone the Git repository:
git clone https://github.com/grails-guides/grails-google-home.git
The Grails guides repositories contain two folders:
-
initial
Initial project. Often a simple Grails app with some additional code to give you a head-start. -
complete
A completed example. It is the result of working through the steps presented by the guide and applying those changes to theinitial
folder.
To complete the guide, go to the initial
folder
-
cd
intograils-guides/grails-google-home/initial
and follow the instructions in the next sections.
You can go right to the completed example if you cd into grails-guides/grails-google-home/complete
|
The initial project is a Grails Application built with the web
profile where we removed the asset-pipeline
and hibernate
depenencies.
Although you can go right to the completed example, in order to deploy the app you would need to complete several configuration steps Google Cloud Settings.
You will need to edit the file src/main/resources/action.json
to point to your Google App Engine Flexible deployment.
3 Google Cloud Settings
This guide uses paid services; you may need to enable Billing in Google Cloud to complete some steps in this guide. |
3.1 Create a Google Cloud Project
Signup for Google Cloud Platform and create a new project:
Install Cloud SDK for your operating system.
After you have installed the SDK, run the init command in your terminal:
$ gcloud init
It will prompt you to select the Google account and the project which you want to use.
3.2 Install gactions CLI
We will use gactions CLI
to test our app.
Go to gactions CLI and download it:
Make the gactions binary executable (chmod +x on linux and OSX).
After you have installed gactions, run the init command in your terminal:
$ gactions init --force
Other commands you can run: preview
, update
, deploy
, test
, and list
.
3.3 Configure Google Actions
Import your Project to the Google Actions Console
Go to the new Google Actions Console and import the project your previoulsy created:
Configure your project ID in action.json
action.json
is a metadata file which we use to inform the Google Cloud Actions Console about the
actions supported by our application.
Here you can also control:
-
sample queries to help google understand which action to call
-
intent to call
{
"actions": [
{
"description": "Default Welcome Intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "color-finder-echo"
},
"intent": {
"name": "actions.intent.MAIN"
}
},
{
"description": "Deep link that finds brighter colors",
"name":"color.intent",
"fulfillment": {
"conversationName": "color.intent"
},
"intent": {
"name": "color.intent",
"parameters": [{
"name": "color",
"type": "SchemaOrg_Color"
}],
"trigger": {
"queryPatterns": [
"find a brighter color for $SchemaOrg_Color:color"
]
}
}
}
],
"conversations": {
"color-finder-echo": {
"name": "color-finder-echo",
"url": "https://grails-color-finder.appspot.com/assistantAction/index"
},
"color.intent": {
"name": "color.intent",
"url": "https://grails-color-finder.appspot.com/assistantAction/color"
}
}
}
As shown above, our app supports two actions. We explain those actions with more detail in the next sections of this guide.
We will need to modify the httpExecution items to match your deployed application endpoints.
"url": "https://PROJECT_ID.appspot.com/assistantAction/index"
Add Actions to your assistant app
The first time you select a project in the Google Cloud Actions Console, you will need to supply the action package JSON via the gactions
tool like so:
gactions update --action_package src/main/resources/action.json --project PROJECT_ID
Follow the instructions prompted by the above command. It involves an authorization process.
At the end you will see an output similar to:
Your app for the Assistant for project PROJECT_ID was successfully updated with your actions. Visit the Actions on Google console to finish registering your app and submit it for review at https://console.actions.google.com/project/PROJECT_ID/overview
Add App Information
After you actions package has been validated, you will be able to enter directory information such as images, privacy policy, and descriptions. Here you can also pick the voice of the speaker in your skill.
The previous screenshots display two buttons. Test and Submit. We will click the Test
in the previous screenshots once we have deployed our app to Google App Engine Flexible and we are ready to test our actions.
3.4 Google App Engine
We are going to deploy the Grails application developed in this guide to the Google App Engine Flexible Environment
App Engine allows developers to focus on doing what they do best: writing code. Based on Google Compute Engine, the App Engine flexible environment automatically scales your app up and down while balancing the load. Microservices, authorization, SQL and NoSQL databases, traffic splitting, logging, versioning, security scanning, and content delivery networks are all supported natively.
Run the command:
$ gcloud app create
to initialize an App Engine application within the current Google Cloud project.
4 Writing the Application
4.1 Handle Request from Google in Grails
We name our app color finder. We want to achieve the scenario pictured below:
First, we are going to add a dependency to Google Actions Java SDK
compile 'com.frogermcs.gactions:gactions:0.1.1'
The illustrated conversation is managed with the handlers. We instantiate the handlers with factories. Add the next classes to your app:
package demo
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import groovy.transform.CompileStatic
@CompileStatic
class MainRequestHandlerFactory extends RequestHandler.Factory {
@Override
RequestHandler create(RootRequest rootRequest) {
new MainRequestHandler(rootRequest)
}
}
package demo
import com.frogermcs.gactions.ResponseBuilder
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import com.frogermcs.gactions.api.response.RootResponse
import groovy.transform.CompileStatic
@CompileStatic
class MainRequestHandler extends RequestHandler {
protected MainRequestHandler(RootRequest rootRequest) {
super(rootRequest)
}
@Override
RootResponse getResponse() {
ResponseBuilder.askResponse('Hey, it works! Now tell something so I could repeat it.')
}
}
package demo
import com.frogermcs.gactions.ResponseBuilder
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import com.frogermcs.gactions.api.response.RootResponse
import groovy.transform.CompileStatic
@CompileStatic
class MainRequestHandler extends RequestHandler {
protected MainRequestHandler(RootRequest rootRequest) {
super(rootRequest)
}
@Override
RootResponse getResponse() {
ResponseBuilder.askResponse('Hey, it works! Now tell something so I could repeat it.')
}
}
package demo
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import groovy.transform.CompileStatic
@CompileStatic
class TextRequestHandlerFactory extends RequestHandler.Factory {
@Override
RequestHandler create(RootRequest rootRequest) {
new TextRequestHandler(rootRequest)
}
}
package demo
import com.frogermcs.gactions.ResponseBuilder
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import com.frogermcs.gactions.api.response.RootResponse
import groovy.transform.CompileStatic
@CompileStatic
class TextRequestHandler extends RequestHandler {
protected TextRequestHandler(RootRequest rootRequest) {
super(rootRequest)
}
@Override
RootResponse getResponse() {
ResponseBuilder.tellResponse("You just told: ${rootRequest.inputs.get(0).raw_inputs.get(0).query}")
}
}
We are going to handle the request that come from Google in a Grails Controller:
A controller fulfills the C in the Model View Controller (MVC) pattern and is responsible for handling web requests. In Grails a controller is a class with a name that ends in the convention "Controller" and lives in the grails-app/controllers directory.
The Grails Controller, in turn, will delegate to Actions SDK.
package demo
import com.frogermcs.gactions.AssistantActions
import com.frogermcs.gactions.api.StandardIntents
import com.frogermcs.gactions.api.request.RootRequest
import com.google.gson.Gson
import com.google.gson.stream.JsonReader
import javax.servlet.http.HttpServletRequest
import groovy.transform.CompileStatic
@CompileStatic
class AssistantActionController {
def index() {
AssistantActions assistantActions =
new AssistantActions.Builder(new GrailsResponseHandler(response))
.addRequestHandlerFactory(StandardIntents.MAIN, new MainRequestHandlerFactory()) (1)
.addRequestHandlerFactory(StandardIntents.TEXT, new TextRequestHandlerFactory())
.build()
RootRequest rootRequest = parseActionRequest(request)
assistantActions.handleRequest(rootRequest) (2)
null (3)
}
private RootRequest parseActionRequest(HttpServletRequest request) throws IOException {
JsonReader jsonReader = new JsonReader(request.reader)
new Gson().fromJson(jsonReader, RootRequest)
}
1 | We can chain handlers |
2 | Grails action delegates to Actions SDK to handle requests that come from Google. |
3 | We do not want to return a grails view. The handlers manage the response |
Add GrailsResponseHandler
to your project. As illustrated above, it allow us to use a controller to process the incoming requests.
package demo
import com.frogermcs.gactions.ResponseHandler
import com.frogermcs.gactions.api.response.RootResponse
import com.google.gson.Gson
import groovy.transform.CompileStatic
import javax.servlet.http.HttpServletResponse
import groovy.util.logging.Slf4j
/**
* This is a handler for the SDK to make it work with Grails properly
*/
@CompileStatic
@Slf4j
class GrailsResponseHandler implements ResponseHandler {
private final HttpServletResponse httpServletResponse
private final Gson gson
GrailsResponseHandler(HttpServletResponse httpServletResponse) {
this(httpServletResponse, new Gson())
}
GrailsResponseHandler(HttpServletResponse httpServletResponse, Gson gson) {
this.httpServletResponse = httpServletResponse
this.gson = gson
}
@Override
void onPrepareContentType(String contentType) {
httpServletResponse.setContentType(contentType)
}
@Override
void onPrepareResponseHeaders(Map<String, String> headers) {
for (String headerName : headers.keySet()) {
httpServletResponse.addHeader(headerName, headers.get(headerName))
}
}
@Override
void onResponse(RootResponse rootResponse) {
try {
gson.toJson(rootResponse, httpServletResponse.writer)
httpServletResponse.flushBuffer()
} catch (IOException e) {
log.error('Error writing response', e)
}
}
String getResponse(RootResponse rootResponse) {
try {
gson.toJson(rootResponse)
} catch (IOException e) {
log.error('Error getting response', e)
}
}
}
4.2 Color Handler
To honor our app’s name we want to handle the next scenario as well:
Similar to the previous example. We need a handler and a factory to instantiate the handler.
This handler demonstrates passing a verbal parameter into the intent. When the user asks for a brighter color for X it will try to find a brighter color with a name and say it to the user.
package demo
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import groovy.transform.CompileStatic
@CompileStatic
class ColorRequestHandlerFactory extends RequestHandler.Factory {
@Override
RequestHandler create(RootRequest rootRequest) {
new ColorRequestHandler(rootRequest)
}
}
package demo
import com.frogermcs.gactions.ResponseBuilder
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import com.frogermcs.gactions.api.response.RootResponse
import demo.util.ColorUtils
import java.awt.Color
import java.lang.reflect.Field
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
/**
* take a color name input and returns a brighter color name in return if possible
*/
@CompileStatic
@Slf4j
class ColorRequestHandler extends RequestHandler {
protected ColorRequestHandler(RootRequest rootRequest) {
super(rootRequest)
}
@Override
RootResponse getResponse() {
log.debug("Inputs=${rootRequest.inputs.toListString()}")
String color = rootRequest.inputs[0].arguments[0].raw_text.toLowerCase()
Color parsedColor = null
try {
Field field = Class.forName('java.awt.Color').getField(color)
parsedColor = (Color)field.get(null)
} catch (NoSuchFieldException ne) {
return colorNotFound(color)
}
if (parsedColor) {
ColorUtils colorUtils = new ColorUtils()
String brighter = colorUtils.findBrighterNameForColor(parsedColor).toLowerCase()
String answer = "Sorry I can't find a brighter color for ${color}."
if (brighter != color) {
answer = "The brighter color for ${color} is ${brighter} "
}
return ResponseBuilder.tellResponse(answer)
}
colorNotFound(color)
}
private RootResponse colorNotFound(String color) {
ResponseBuilder.tellResponse("Sorry I don't understand the color ${color}.")
}
}
We are going to handle the request that come from Google in another action of our controller:
package demo
import com.frogermcs.gactions.AssistantActions
import com.frogermcs.gactions.api.StandardIntents
import com.frogermcs.gactions.api.request.RootRequest
import com.google.gson.Gson
import com.google.gson.stream.JsonReader
import javax.servlet.http.HttpServletRequest
import groovy.transform.CompileStatic
@CompileStatic
class AssistantActionController {
def color() {
AssistantActions assistantActions =
new AssistantActions.Builder(new GrailsResponseHandler(response))
.addRequestHandlerFactory('color.intent', new ColorRequestHandlerFactory()) (1)
.build()
RootRequest rootRequest = parseActionRequest(request) (2)
assistantActions.handleRequest(rootRequest)
null (3)
}
private RootRequest parseActionRequest(HttpServletRequest request) throws IOException {
JsonReader jsonReader = new JsonReader(request.reader)
new Gson().fromJson(jsonReader, RootRequest)
}
1 | Instantiate the handler with the help of the factory |
2 | Grails action delegates to Actions SDK to handle requests that come from Google. |
3 | We do not want to return a grails view. The handlers manage the response |
Let’s use a simple utility class to make our action do something interesting. This will help us map a given java.awt.Color
to give us a english name for it.
package demo.util;
import java.awt.*;
import java.util.ArrayList;
/**
* Java Code to get a color name from rgb/hex value/awt color
*
* The part of looking up a color name from the rgb values is edited from
* https://gist.github.com/nightlark/6482130#file-gistfile1-java (that has some errors) by Ryan Mast (nightlark)
*
* @author Xiaoxiao Li
*
*/
public class ColorUtils {
/**
* Initialize the color list that we have.
*/
private ArrayList<ColorName> initColorList() {
ArrayList<ColorName> colorList = new ArrayList<ColorName>();
colorList.add(new ColorName("AliceBlue", 0xF0, 0xF8, 0xFF));
colorList.add(new ColorName("AntiqueWhite", 0xFA, 0xEB, 0xD7));
colorList.add(new ColorName("Aqua", 0x00, 0xFF, 0xFF));
colorList.add(new ColorName("Aquamarine", 0x7F, 0xFF, 0xD4));
colorList.add(new ColorName("Azure", 0xF0, 0xFF, 0xFF));
colorList.add(new ColorName("Beige", 0xF5, 0xF5, 0xDC));
colorList.add(new ColorName("Bisque", 0xFF, 0xE4, 0xC4));
colorList.add(new ColorName("Black", 0x00, 0x00, 0x00));
colorList.add(new ColorName("BlanchedAlmond", 0xFF, 0xEB, 0xCD));
colorList.add(new ColorName("Blue", 0x00, 0x00, 0xFF));
colorList.add(new ColorName("BlueViolet", 0x8A, 0x2B, 0xE2));
colorList.add(new ColorName("Brown", 0xA5, 0x2A, 0x2A));
colorList.add(new ColorName("BurlyWood", 0xDE, 0xB8, 0x87));
colorList.add(new ColorName("CadetBlue", 0x5F, 0x9E, 0xA0));
colorList.add(new ColorName("Chartreuse", 0x7F, 0xFF, 0x00));
colorList.add(new ColorName("Chocolate", 0xD2, 0x69, 0x1E));
colorList.add(new ColorName("Coral", 0xFF, 0x7F, 0x50));
colorList.add(new ColorName("CornflowerBlue", 0x64, 0x95, 0xED));
colorList.add(new ColorName("Cornsilk", 0xFF, 0xF8, 0xDC));
colorList.add(new ColorName("Crimson", 0xDC, 0x14, 0x3C));
colorList.add(new ColorName("Cyan", 0x00, 0xFF, 0xFF));
colorList.add(new ColorName("DarkBlue", 0x00, 0x00, 0x8B));
colorList.add(new ColorName("DarkCyan", 0x00, 0x8B, 0x8B));
colorList.add(new ColorName("DarkGoldenRod", 0xB8, 0x86, 0x0B));
colorList.add(new ColorName("DarkGray", 0xA9, 0xA9, 0xA9));
colorList.add(new ColorName("DarkGreen", 0x00, 0x64, 0x00));
colorList.add(new ColorName("DarkKhaki", 0xBD, 0xB7, 0x6B));
colorList.add(new ColorName("DarkMagenta", 0x8B, 0x00, 0x8B));
colorList.add(new ColorName("DarkOliveGreen", 0x55, 0x6B, 0x2F));
colorList.add(new ColorName("DarkOrange", 0xFF, 0x8C, 0x00));
colorList.add(new ColorName("DarkOrchid", 0x99, 0x32, 0xCC));
colorList.add(new ColorName("DarkRed", 0x8B, 0x00, 0x00));
colorList.add(new ColorName("DarkSalmon", 0xE9, 0x96, 0x7A));
colorList.add(new ColorName("DarkSeaGreen", 0x8F, 0xBC, 0x8F));
colorList.add(new ColorName("DarkSlateBlue", 0x48, 0x3D, 0x8B));
colorList.add(new ColorName("DarkSlateGray", 0x2F, 0x4F, 0x4F));
colorList.add(new ColorName("DarkTurquoise", 0x00, 0xCE, 0xD1));
colorList.add(new ColorName("DarkViolet", 0x94, 0x00, 0xD3));
colorList.add(new ColorName("DeepPink", 0xFF, 0x14, 0x93));
colorList.add(new ColorName("DeepSkyBlue", 0x00, 0xBF, 0xFF));
colorList.add(new ColorName("DimGray", 0x69, 0x69, 0x69));
colorList.add(new ColorName("DodgerBlue", 0x1E, 0x90, 0xFF));
colorList.add(new ColorName("FireBrick", 0xB2, 0x22, 0x22));
colorList.add(new ColorName("FloralWhite", 0xFF, 0xFA, 0xF0));
colorList.add(new ColorName("ForestGreen", 0x22, 0x8B, 0x22));
colorList.add(new ColorName("Fuchsia", 0xFF, 0x00, 0xFF));
colorList.add(new ColorName("Gainsboro", 0xDC, 0xDC, 0xDC));
colorList.add(new ColorName("GhostWhite", 0xF8, 0xF8, 0xFF));
colorList.add(new ColorName("Gold", 0xFF, 0xD7, 0x00));
colorList.add(new ColorName("GoldenRod", 0xDA, 0xA5, 0x20));
colorList.add(new ColorName("Gray", 0x80, 0x80, 0x80));
colorList.add(new ColorName("Green", 0x00, 0x80, 0x00));
colorList.add(new ColorName("GreenYellow", 0xAD, 0xFF, 0x2F));
colorList.add(new ColorName("HoneyDew", 0xF0, 0xFF, 0xF0));
colorList.add(new ColorName("HotPink", 0xFF, 0x69, 0xB4));
colorList.add(new ColorName("IndianRed", 0xCD, 0x5C, 0x5C));
colorList.add(new ColorName("Indigo", 0x4B, 0x00, 0x82));
colorList.add(new ColorName("Ivory", 0xFF, 0xFF, 0xF0));
colorList.add(new ColorName("Khaki", 0xF0, 0xE6, 0x8C));
colorList.add(new ColorName("Lavender", 0xE6, 0xE6, 0xFA));
colorList.add(new ColorName("LavenderBlush", 0xFF, 0xF0, 0xF5));
colorList.add(new ColorName("LawnGreen", 0x7C, 0xFC, 0x00));
colorList.add(new ColorName("LemonChiffon", 0xFF, 0xFA, 0xCD));
colorList.add(new ColorName("LightBlue", 0xAD, 0xD8, 0xE6));
colorList.add(new ColorName("LightCoral", 0xF0, 0x80, 0x80));
colorList.add(new ColorName("LightCyan", 0xE0, 0xFF, 0xFF));
colorList.add(new ColorName("LightGoldenRodYellow", 0xFA, 0xFA, 0xD2));
colorList.add(new ColorName("LightGray", 0xD3, 0xD3, 0xD3));
colorList.add(new ColorName("LightGreen", 0x90, 0xEE, 0x90));
colorList.add(new ColorName("LightPink", 0xFF, 0xB6, 0xC1));
colorList.add(new ColorName("LightSalmon", 0xFF, 0xA0, 0x7A));
colorList.add(new ColorName("LightSeaGreen", 0x20, 0xB2, 0xAA));
colorList.add(new ColorName("LightSkyBlue", 0x87, 0xCE, 0xFA));
colorList.add(new ColorName("LightSlateGray", 0x77, 0x88, 0x99));
colorList.add(new ColorName("LightSteelBlue", 0xB0, 0xC4, 0xDE));
colorList.add(new ColorName("LightYellow", 0xFF, 0xFF, 0xE0));
colorList.add(new ColorName("Lime", 0x00, 0xFF, 0x00));
colorList.add(new ColorName("LimeGreen", 0x32, 0xCD, 0x32));
colorList.add(new ColorName("Linen", 0xFA, 0xF0, 0xE6));
colorList.add(new ColorName("Magenta", 0xFF, 0x00, 0xFF));
colorList.add(new ColorName("Maroon", 0x80, 0x00, 0x00));
colorList.add(new ColorName("MediumAquaMarine", 0x66, 0xCD, 0xAA));
colorList.add(new ColorName("MediumBlue", 0x00, 0x00, 0xCD));
colorList.add(new ColorName("MediumOrchid", 0xBA, 0x55, 0xD3));
colorList.add(new ColorName("MediumPurple", 0x93, 0x70, 0xDB));
colorList.add(new ColorName("MediumSeaGreen", 0x3C, 0xB3, 0x71));
colorList.add(new ColorName("MediumSlateBlue", 0x7B, 0x68, 0xEE));
colorList.add(new ColorName("MediumSpringGreen", 0x00, 0xFA, 0x9A));
colorList.add(new ColorName("MediumTurquoise", 0x48, 0xD1, 0xCC));
colorList.add(new ColorName("MediumVioletRed", 0xC7, 0x15, 0x85));
colorList.add(new ColorName("MidnightBlue", 0x19, 0x19, 0x70));
colorList.add(new ColorName("MintCream", 0xF5, 0xFF, 0xFA));
colorList.add(new ColorName("MistyRose", 0xFF, 0xE4, 0xE1));
colorList.add(new ColorName("Moccasin", 0xFF, 0xE4, 0xB5));
colorList.add(new ColorName("NavajoWhite", 0xFF, 0xDE, 0xAD));
colorList.add(new ColorName("Navy", 0x00, 0x00, 0x80));
colorList.add(new ColorName("OldLace", 0xFD, 0xF5, 0xE6));
colorList.add(new ColorName("Olive", 0x80, 0x80, 0x00));
colorList.add(new ColorName("OliveDrab", 0x6B, 0x8E, 0x23));
colorList.add(new ColorName("Orange", 0xFF, 0xA5, 0x00));
colorList.add(new ColorName("OrangeRed", 0xFF, 0x45, 0x00));
colorList.add(new ColorName("Orchid", 0xDA, 0x70, 0xD6));
colorList.add(new ColorName("PaleGoldenRod", 0xEE, 0xE8, 0xAA));
colorList.add(new ColorName("PaleGreen", 0x98, 0xFB, 0x98));
colorList.add(new ColorName("PaleTurquoise", 0xAF, 0xEE, 0xEE));
colorList.add(new ColorName("PaleVioletRed", 0xDB, 0x70, 0x93));
colorList.add(new ColorName("PapayaWhip", 0xFF, 0xEF, 0xD5));
colorList.add(new ColorName("PeachPuff", 0xFF, 0xDA, 0xB9));
colorList.add(new ColorName("Peru", 0xCD, 0x85, 0x3F));
colorList.add(new ColorName("Pink", 0xFF, 0xC0, 0xCB));
colorList.add(new ColorName("Plum", 0xDD, 0xA0, 0xDD));
colorList.add(new ColorName("PowderBlue", 0xB0, 0xE0, 0xE6));
colorList.add(new ColorName("Purple", 0x80, 0x00, 0x80));
colorList.add(new ColorName("Red", 0xFF, 0x00, 0x00));
colorList.add(new ColorName("RosyBrown", 0xBC, 0x8F, 0x8F));
colorList.add(new ColorName("RoyalBlue", 0x41, 0x69, 0xE1));
colorList.add(new ColorName("SaddleBrown", 0x8B, 0x45, 0x13));
colorList.add(new ColorName("Salmon", 0xFA, 0x80, 0x72));
colorList.add(new ColorName("SandyBrown", 0xF4, 0xA4, 0x60));
colorList.add(new ColorName("SeaGreen", 0x2E, 0x8B, 0x57));
colorList.add(new ColorName("SeaShell", 0xFF, 0xF5, 0xEE));
colorList.add(new ColorName("Sienna", 0xA0, 0x52, 0x2D));
colorList.add(new ColorName("Silver", 0xC0, 0xC0, 0xC0));
colorList.add(new ColorName("SkyBlue", 0x87, 0xCE, 0xEB));
colorList.add(new ColorName("SlateBlue", 0x6A, 0x5A, 0xCD));
colorList.add(new ColorName("SlateGray", 0x70, 0x80, 0x90));
colorList.add(new ColorName("Snow", 0xFF, 0xFA, 0xFA));
colorList.add(new ColorName("SpringGreen", 0x00, 0xFF, 0x7F));
colorList.add(new ColorName("SteelBlue", 0x46, 0x82, 0xB4));
colorList.add(new ColorName("Tan", 0xD2, 0xB4, 0x8C));
colorList.add(new ColorName("Teal", 0x00, 0x80, 0x80));
colorList.add(new ColorName("Thistle", 0xD8, 0xBF, 0xD8));
colorList.add(new ColorName("Tomato", 0xFF, 0x63, 0x47));
colorList.add(new ColorName("Turquoise", 0x40, 0xE0, 0xD0));
colorList.add(new ColorName("Violet", 0xEE, 0x82, 0xEE));
colorList.add(new ColorName("Wheat", 0xF5, 0xDE, 0xB3));
colorList.add(new ColorName("White", 0xFF, 0xFF, 0xFF));
colorList.add(new ColorName("WhiteSmoke", 0xF5, 0xF5, 0xF5));
colorList.add(new ColorName("Yellow", 0xFF, 0xFF, 0x00));
colorList.add(new ColorName("YellowGreen", 0x9A, 0xCD, 0x32));
return colorList;
}
/**
* Get the closest color name from our list
*
* @param r
* @param g
* @param b
* @return
*/
public String getColorNameFromRgb(int r, int g, int b) {
ArrayList<ColorName> colorList = initColorList();
ColorName closestMatch = null;
int minMSE = Integer.MAX_VALUE;
int mse;
for (ColorName c : colorList) {
mse = c.computeMSE(r, g, b);
if (mse < minMSE) {
minMSE = mse;
closestMatch = c;
}
}
if (closestMatch != null) {
return closestMatch.getName();
} else {
return "No matched color name.";
}
}
/**
* Convert hexColor to rgb, then call getColorNameFromRgb(r, g, b)
*
* @param hexColor
* @return
*/
public String getColorNameFromHex(int hexColor) {
int r = (hexColor & 0xFF0000) >> 16;
int g = (hexColor & 0xFF00) >> 8;
int b = (hexColor & 0xFF);
return getColorNameFromRgb(r, g, b);
}
public int colorToHex(Color c) {
return Integer.decode("0x"
+ Integer.toHexString(c.getRGB()).substring(2));
}
public String getColorNameFromColor(Color color) {
return getColorNameFromRgb(color.getRed(), color.getGreen(),
color.getBlue());
}
public String findBrighterNameForColor(Color color) {
String name = getColorNameFromColor(color);
String nextColorName = name;
int colorCount = 0;
while (name.equals(nextColorName) && colorCount < 100) {
color = color.brighter();
nextColorName = getColorNameFromColor(color);
colorCount++;
}
return nextColorName;
}
public String findDarkerNameForColor(Color color) {
String name = getColorNameFromColor(color);
String nextColorName = name;
int colorCount = 0;
while (name.equals(nextColorName) && colorCount < 100) {
color = color.darker();
nextColorName = getColorNameFromColor(color);
colorCount++;
}
return nextColorName;
}
/**
* SubClass of ColorUtils. In order to lookup color name
*
* @author Xiaoxiao Li
*
*/
public class ColorName {
public int r, g, b;
public String name;
public ColorName(String name, int r, int g, int b) {
this.r = r;
this.g = g;
this.b = b;
this.name = name;
}
public int computeMSE(int pixR, int pixG, int pixB) {
return (int) (((pixR - r) * (pixR - r) + (pixG - g) * (pixG - g) + (pixB - b)
* (pixB - b)) / 3);
}
public int getR() {
return r;
}
public int getG() {
return g;
}
public int getB() {
return b;
}
public String getName() {
return name;
}
}
}
Previous class is a Java Class. Grails allows us to mix Groovy and Java code seamlessly.
4.3 Unofficial Google Actions Java SDK
The SDK is at https://github.com/frogermcs/Google-Actions-Java-SDK however all we need to do is edit our build.gradle to include the library.
compile 'com.frogermcs.gactions:gactions:0.1.1'
4.4 Google App Engine Gradle Plugin
To deploy to App Engine, we are going to use the Google App Engine Gradle Plugin.
We need to modify build.gradle
, add the plugin as a buildscript dependency and apply the plugin:
buildscript {
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.1.1'
}
}
...
apply plugin: 'com.google.cloud.tools.appengine'
4.5 Application Deployment Configuration
To deploy to Google App Engine, we need to add the file src/main/appengine/app.yaml
It describes the application’s deployment configuration:
runtime: java
env: flex
runtime_config:
jdk: openjdk8
server: jetty9
health_check:
enable_health_check: False
resources:
cpu: 1
memory_gb: 2.3
manual_scaling:
instances: 1
Here, app.yaml
specifies the runtime used by the app, and sets env: flex
,
specifying that the app uses the flexible environment.
The minimal app.yaml application configuration file shown above is sufficient for a simple Grails application.
Depending on the size, complexity, and features that your application uses, you may need to change and extend this basic
configuration file. For more information on what can be configured via app.yaml , please see the
Configuring Your App with app.yaml
guide.
|
For more information on how the Java runtime works, see Java 8 / Jetty 9.3 Runtime.
4.6 SpringBoot Jetty
As shown in the previous app engine configuration file, we are using Jetty.
Following SpringBoot’s documentation, we need to do the following changes to deploy to Jetty instead of Tomcat
Replace tomcat:
provided 'org.springframework.boot:spring-boot-starter-tomcat'
with jetty:
provided "org.springframework.boot:spring-boot-starter-jetty"
We need to exclude the tomcat-juli dependency as well. Add:
configurations {
compile.exclude module: "tomcat-juli"
}
4.7 Deploying the App
To deploy the app to Google App Engine run:
$ ./gradlew appengineDeploy
Initial deployment may take a while. When finished, you will be able to access your app.
5 Test
5.1 Test and interact with your Google Action
Once your app is deployed in Google App Engine Flexible, you can test it.
Go back to the Google Actions Console and click test.
Try to test out your actions with the following phrases:
Ask to Grails Color Finder to find a brighter color for magenta
You should see the response:
Sure, here is the test version of Grails Color Finder The brighter color for magenta is fuchsia"
Let’s try another:
talk to Grails Color Finder
You’ll see:
Sure, here is the test version of Grails Color Finder Hey, it works! Now tell something so I could repeat it.
Now respond with something:
Grails is awesome
You should see it echo back what you entered:
You just told: Grails is awesome
Now you can make a more rich skill, and when you are ready go to the Google API dashboard, fill in the directory listing and submit it for testing.