Alexa 智能音箱开发智能家居
一,前期准备材料
- 一个亚马逊开发者账户。注册是免费的
- 连接设备,如灯,恒温器,相机或带有云API的锁,用于控制它
- 支持Alexa的设备,例如Amazon Echo
- 一个AWS账户。您在AWS Lambda函数中托管您的技能代码(这个账户可以注册,可以免费使用12个月,但需要绑定信用卡,且必须交1美元的预授权的费用,国内信用卡可以绑定,亲测)
- 了解JSON
- Java,Node.js,C#或Python作为Lambda函数的知识可以用任何这些语言编写
- 了解OAuth 2.0
二,技能创建
https://developer.amazon.com/zh/alexa 登录上述网址,并登录您的账号,在下图选择你的技能创建
进去之后,点击create skill,技能名称输入您的技能名,语言默认(也可以选择你自己喜欢的语言,然而并不支持中文,所以。。。),模型选择 智能家居,然后点击创建技能。
三,技能设置
- Payload version 选择v3
- Smart Home service endpoint
2.1 AWS Lambda ARN 设置 Your Skill ID 点击复制到文本,方便后面使用
2.2 Default endpoint* 这个是你在创建aws lambda函数时对应的表达式,在后面会给出具体的位置。
2.3 下面三个复选框 只是为了让你给不同的地区选择不同的区域,为了使用不同的语言版本用户时能够得到更好的体验
此处只使用默认的端点,不勾选其它的。
3.完成以上设置,请点击保存按钮。
四,lambda 函数的创建
1.登录您的lambda控制台,如果找不到lambda函数的具体位置,请点击aws-->计算-->选择lambda,或者直接搜索。
2.进入lambda控制台,点击创建功能,选择从新开始,输入名称,因为笔者是用java开发的,所以运行的哪一块就选择 java8
3.选择角色,若没有点击创建,具体流程就不再介绍了,https://developer.amazon.com/docs/smarthome/steps-to-build-a- smart-home-skill.html#create-an-iam-role-for-lambda
4.进行lambda配置(在配置前请确保右上角你的地区选择,因为有的地区的触发器没有smart home 选项),
在左侧的触发器,里面选择 alexa smart home,然后点击它,并进行配置,复制你的技能id,并添加。
5.点击右上角的保存
五,配置你的账户关联
在配置账户关联之前请确保2.2 步骤的默认端点已填写你刚才创建的lambda函数,
六,关于如何通过lambda向本地进行测试
如果我们在一个项目里面写业务逻辑,然后再进行上传jar包到lambda函数的话,有很多的确点,一方面就意味着你要重复的上传代码,如果你的网络允许你这样做的话,当我没说。另一方面,如果代码上传完你需要调试的话会很麻烦。
所以,我们可以这么做
// -*- coding: utf-8 -*-
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Licensed under the Amazon Software License (the "License"). You may not use this file except in
// compliance with the License. A copy of the License is located at
// http://aws.amazon.com/asl/
// or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific
// language governing permissions and limitations under the License.
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Scanner;
import org.json.JSONObject;
import com.amazonaws.services.lambda.runtime.Context;
public class AlexaHandler {
public static void handler(InputStream inputStream, OutputStream outputStream, Context context) {
String request;
try {
System.out.println("----开始请求");
request = getRequest(inputStream);
//System.out.println("Request:");
System.out.println("请求参数:"+request);
String reqURL = "https://www.******/rest/alexa/S_Alexa_Gateway/postGateway";
// url参数转换
String url = UrlEncoderUntil.GetRealUrl(reqURL + "?request=" + request);
// 响应内容
String responseData= GetSample(url);
System.out.println("----结束请求");
System.out.println(responseData);
outputStream.write(responseData.getBytes(Charset.forName("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 说明: 发送请求到后台(请求转发) 方法名: GetSample
*
* @param url
* @return
*/
private static String GetSample(String url) {
StringBuilder sb = new StringBuilder();
try {
URL iurl = new URL(url);
HttpURLConnection c = (HttpURLConnection) iurl.openConnection();
c.connect();
int status = c.getResponseCode();
switch (status) {
case 200:
case 201:
case 202:
BufferedReader br = new BufferedReader(new InputStreamReader(c.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
@SuppressWarnings("unused")
private static JSONObject GetResponse(String json) {
InputStream inputStream = new ByteArrayInputStream(json.getBytes(Charset.forName("UTF-8")));
OutputStream outputStream = new OutputStream() {
private StringBuilder sb = new StringBuilder();
@Override
public void write(int b) throws IOException {
this.sb.append((char) b);
}
public String toString() {
return this.sb.toString();
}
};
String responseString = outputStream.toString();
return new JSONObject(responseString);
}
/**
* 说明:判断请求的是否有值,若没有值返回空 方法名: getRequest
*
* @param is
* @return
*/
static String getRequest(java.io.InputStream is) {
Scanner s = new Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
}
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
public class UrlEncoderUntil {
public static void main(String[] args)throws Exception {
String str="http://nufm.dfcfw.com/EM_Finance2014NumericApplication/JS.aspx?type=CT&cmd=C._A&sty=FCOIATA&sortType=C&sortRule=-1&page=1&pageSize=70&js=var%20quote_123%3d{rank:[(x)],pages:(pc)}&token=7bc05d0d4c3c22ef9fca8c2a912d779c&jsName=quote_123&_g=0.5927966693718834";
String result=GetRealUrl(str);
System.out.println(result);
}
//对url中的参数进行url编码
public static String GetRealUrl(String str) {
try {
int index = str.indexOf("?");
if (index < 0) return str;
String query = str.substring(0, index);
String params = str.substring(index + 1);
Map map = GetArgs(params);
//Map map=TransStringToMap(params);
String encodeParams = TransMapToString(map);
return query + "?" + encodeParams;
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return "";
}
//将url参数格式转化为map
public static Map GetArgs(String params) throws Exception{
Map map=new HashMap();
String[] pairs=params.split("&");
for(int i=0;i<pairs.length;i++){
int pos=pairs[i].indexOf("=");
if(pos==-1) continue;
String argname=pairs[i].substring(0,pos);
String value=pairs[i].substring(pos+1);
value= URLEncoder.encode(value,"utf-8");
map.put(argname,value);
}
return map;
}
//将map转化为指定的String类型
public static String TransMapToString(Map map){
java.util.Map.Entry entry;
StringBuffer sb = new StringBuffer();
for(Iterator iterator = map.entrySet().iterator(); iterator.hasNext();)
{
entry = (java.util.Map.Entry)iterator.next();
sb.append(entry.getKey().toString()).append( "=" ).append(null==entry.getValue()?"":
entry.getValue().toString()).append (iterator.hasNext() ? "&" : "");
}
return sb.toString();
}
//将String类型按一定规则转换为Map
public static Map TransStringToMap(String mapString){
Map map = new HashMap();
java.util.StringTokenizer items;
for(StringTokenizer entrys = new StringTokenizer(mapString, "&"); entrys.hasMoreTokens();
map.put(items.nextToken(), items.hasMoreTokens() ? ((Object) (items.nextToken())) : null))
items = new StringTokenizer(entrys.nextToken(), "=");
return map;
}
}
通过以上的代码,你就可以轻松的把请求转发到你的本地去,那样就方便多了。
后台接收lambda请求的方法。。。
package com.cn.whirlpool.services.alexaIot;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import com.cn.whirlpool.services.DbService;
import com.cn.whirlpool.services.RedisService;
import Utils.UicJsonUtils;
import antlr.Token;
/**
* 说明: S_DingDong_Gateway
* @author author
* @date 2018年9月10日
*/
@SuppressWarnings("unused")
@Transactional
@Component
@Provider
@Path("/alexa/S_Alexa_Gateway")
public class S_Alexa_Gateway {
@Autowired
private AlexaResponse alexaResponse;
@Autowired
private S_Alexa_Discovery s_Alexa_Discovery;
@Autowired
private S_Alexa_Controller s_Alexa_Controller;
private static Logger log = LoggerFactory.getLogger(S_Alexa_Gateway.class);
DbService redis=RedisService.getInstance();
@SuppressWarnings("unused")
@GET
@Path("/postGateway/")
public JSONObject postGateway(@Context HttpServletRequest request, @Context HttpServletResponse response) throws Exception {
JSONObject jsonObject =new JSONObject();
//打印请求的参数,这样方便查看,后面删掉
showParams(request);
// 用来接收返回信息的对象信息
AlexaResponse alexaResponse = null;
//处理接收的参数
String requestParam = request.getParameter("request");
// 接收请求信息
JSONObject jsonRequest = new JSONObject(requestParam);
JSONObject directive = (JSONObject) jsonRequest.get("directive");
JSONObject header = (JSONObject) directive.get("header");
JSONObject payload = (JSONObject) directive.get("payload");
String namespace = header.optString("namespace", "INVALID");
String correlationToken = header.optString("correlationToken", "INVALID");
//客户的授权码
String code = null;
//客户的访问令牌
String token = null;
if (payload.has("grant")) {
JSONObject grant = (JSONObject) payload.get("grant");
code = grant.optString("code", "INVALID");
}
if (payload.has("grantee")) {
JSONObject grantee = (JSONObject) payload.get("grantee");
token = grantee.optString("token", "INVALID");
}
switch(namespace) {
case "Alexa":
//状态报告
log.info("Found Alexa Namespace");
alexaResponse = s_Alexa_Discovery.StateReport(jsonRequest);
break;
case "Alexa.Authorization":
//向alexa发送网关事件,主要是事件验证使用
log.info("Found Alexa.Authorization Namespace");
alexaResponse = new AlexaResponse("Alexa.Authorization","AcceptGrant.Response");
JSONObject payloads = new JSONObject("{}");
alexaResponse.SetPayload(payloads.toString());
//alexaResponse = new AlexaResponse("Alexa.Authorization","AcceptGrant", "INVALID", "INVALID", correlationToken);
break;
case "Alexa.Discovery":
//发现设备的命令
log.info("Found Alexa.Discovery Namespace");
alexaResponse = s_Alexa_Discovery.Discovery(jsonRequest);
log.info("发现设备的响应参数:"+alexaResponse.toString());
break;
case "Alexa.PowerController":
//开关设备的指令
System.out.println("Found Alexa.PowerController Namespace");
alexaResponse = s_Alexa_Controller.Controller(jsonRequest,namespace);
log.info("控制设备开关的响应参数:"+alexaResponse.toString());
break;
case "Alexa.ThermostatController":
//温度控制指令
System.out.println("Found Alexa.ThermostatController Namespace");
alexaResponse = s_Alexa_Controller.Controller(jsonRequest,namespace);
log.info("控制设备温度的响应参数:"+alexaResponse.toString());
break;
//以下功能未实现
case "Alexa.ModeController":
//模式设置的指令(暂时不支持,该功能只有预览版才有)
System.out.println("Found Alexa.ModeController Namespace");
alexaResponse = s_Alexa_Controller.Controller(jsonRequest,namespace);
log.info("控制设备模式的响应参数:"+alexaResponse.toString());
break;
case "Alexa.RangeController":
//温度范围设置的指令(暂时不支持,该功能只有预览版才有)
System.out.println("Found Alexa.RangeController Namespace");
alexaResponse = s_Alexa_Controller.Controller(jsonRequest,namespace);
log.info("控制设备温度范围的响应参数:"+alexaResponse.toString());
break;
default:
System.out.println("INVALID Namespace");
alexaResponse = new AlexaResponse();
break;
}
jsonObject = new JSONObject(alexaResponse.toString());
log.info(jsonObject.toString());
return jsonObject;
}
@SuppressWarnings({ "unused", "rawtypes", "unchecked" })
private void showParams(HttpServletRequest request) {
Map map = new HashMap();
Enumeration paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String paramName = (String) paramNames.nextElement();
String[] paramValues = request.getParameterValues(paramName);
if (paramValues.length == 1) {
String paramValue = paramValues[0];
if (paramValue.length() != 0) {
map.put(paramName, paramValue);
}
}
}
Set<Map.Entry<String, String>> set = map.entrySet();
System.out.println("------------------------------");
for (Map.Entry entry : set) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("------------------------------");
}
}
响应的方法
package com.cn.whirlpool.services.alexaIot;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.TimeZone;
import java.util.UUID;
import javax.ws.rs.ext.Provider;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Component
@Provider
public class AlexaResponse {
private JSONObject response = new JSONObject("{}");
private JSONObject event = new JSONObject("{}");
private JSONObject header = new JSONObject("{}");
private JSONObject endpoint = new JSONObject("{}");
private JSONObject payload = new JSONObject("{}");
private String CheckValue(String value, String defaultValue) {
if (value.isEmpty())
return defaultValue;
return value;
}
public AlexaResponse() throws JSONException {
this("Alexa", "Response", "INVALID", "INVALID", null);
}
public AlexaResponse(String namespace, String name) throws JSONException { this(namespace, name, "INVALID", "INVALID", null); }
public AlexaResponse(String namespace, String name,String errType,String errMessage) throws JSONException {
header.put("namespace", CheckValue(namespace, "Alexa"));
header.put("name", CheckValue(name,"Response"));
header.put("messageId", UUID.randomUUID().toString());
header.put("payloadVersion", "3");
event.put("header", header);
event.put("endpoint", endpoint);
if (StringUtils.isNotBlank(errType)) {
payload.put("type", CheckValue(errType, "INVALID"));
payload.put("message", CheckValue(errMessage, "INVALID"));
}
event.put("payload", payload);
response.put("event", event);
}
public AlexaResponse(String namespace, String name, String endpointId, String token, String correlationToken) throws JSONException {
header.put("namespace", CheckValue(namespace, "Alexa"));
header.put("name", CheckValue(name,"Response"));
header.put("messageId", UUID.randomUUID().toString());
header.put("payloadVersion", "3");
if (correlationToken != null) {
header.put("correlationToken", CheckValue(correlationToken, "INVALID"));
}
JSONObject scope = new JSONObject("{}");
scope.put("type", "BearerToken");
scope.put("token", CheckValue(token, "INVALID"));
endpoint.put("scope", scope);
endpoint.put("endpointId", CheckValue(endpointId, "INVALID"));
event.put("header", header);
event.put("endpoint", endpoint);
event.put("payload", payload);
response.put("event", event);
}
public AlexaResponse(String namespace, String name, String endpointId, String token, String correlationToken,String errType,String errMessage) throws JSONException {
header.put("namespace", CheckValue(namespace, "Alexa"));
header.put("name", CheckValue(name,"Response"));
header.put("messageId", UUID.randomUUID().toString());
header.put("payloadVersion", "3");
if (StringUtils.isBlank(correlationToken)) {
header.put("correlationToken", CheckValue(correlationToken, "INVALID"));
}
JSONObject scope = new JSONObject("{}");
scope.put("type", "BearerToken");
scope.put("token", CheckValue(token, "INVALID"));
endpoint.put("scope", scope);
endpoint.put("endpointId", CheckValue(endpointId, "INVALID"));
event.put("header", header);
event.put("endpoint", endpoint);
if (StringUtils.isNotBlank(errType)) {
payload.put("type", CheckValue(errType, "INVALID"));
payload.put("message", CheckValue(errMessage, "INVALID"));
}
event.put("payload", payload);
response.put("event", event);
}
public AlexaResponse(String namespace, String name, String endpointId, String token, String correlationToken,String errType,String errMessage,String validRange) throws JSONException {
header.put("namespace", CheckValue(namespace, "Alexa"));
header.put("name", CheckValue(name,"Response"));
header.put("messageId", UUID.randomUUID().toString());
header.put("payloadVersion", "3");
if (StringUtils.isBlank(correlationToken)) {
header.put("correlationToken", CheckValue(correlationToken, "INVALID"));
}
JSONObject scope = new JSONObject("{}");
scope.put("type", "BearerToken");
scope.put("token", CheckValue(token, "INVALID"));
endpoint.put("scope", scope);
endpoint.put("endpointId", CheckValue(endpointId, "INVALID"));
event.put("header", header);
event.put("endpoint", endpoint);
if (StringUtils.isNotBlank(errType)&&StringUtils.isNotBlank(validRange)) {
payload.put("type", CheckValue(errType, "INVALID"));
payload.put("message", CheckValue(errMessage, "INVALID"));
payload.put("validRange", new JSONObject(validRange));
}
event.put("payload", payload);
response.put("event", event);
}
public void AddCookie(String key, String value) throws JSONException {
JSONObject endpointObject = response.getJSONObject("event").getJSONObject("endpoint");
JSONObject cookie;
if (endpointObject.has("cookie")) {
cookie = endpointObject.getJSONObject("cookie");
cookie.put(key, value);
} else {
cookie = new JSONObject();
cookie.put(key, value);
endpointObject.put("cookie", cookie);
}
}
public void AddPayloadEndpoint(String friendlyName, String endpointId, String capabilities) throws JSONException {
JSONObject payload = response.getJSONObject("event").getJSONObject("payload");
if (payload.has("endpoints"))
{
JSONArray endpoints = payload.getJSONArray("endpoints");
endpoints.put(new JSONObject(CreatePayloadEndpoint(friendlyName, endpointId, capabilities, null)));
}
else
{
JSONArray endpoints = new JSONArray();
endpoints.put(new JSONObject(CreatePayloadEndpoint(friendlyName, endpointId, capabilities, null)));
payload.put("endpoints", endpoints);
}
}
public void AddContextProperty(String namespace, String name, String value, int uncertaintyInMilliseconds) throws JSONException
{
JSONObject context;
JSONArray properties;
try {
context = response.getJSONObject("context");
properties = context.getJSONArray("properties");
} catch (JSONException jse) {
context = new JSONObject();
properties = new JSONArray();
context.put("properties", properties);
}
properties.put(new JSONObject(CreateContextProperty(namespace, name, value, uncertaintyInMilliseconds)));
response.put("context", context);
}
public String CreateContextProperty(String namespace, String name, String value, int uncertaintyInMilliseconds) throws JSONException {
JSONObject property = new JSONObject();
try {
property.put("namespace", namespace);
property.put("name", name);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sss'Z'");
TimeZone tz = TimeZone.getTimeZone("UTC");
sdf.setTimeZone(tz);
String timeOfSample = sdf.format(new Date().getTime());
property.put("timeOfSample", timeOfSample);
property.put("uncertaintyInMilliseconds", uncertaintyInMilliseconds);
/* property.put("value", value);*/
property.put("value", new JSONObject(value));
} catch (Exception je) {
property.put("value", value);
}
return property.toString();
}
public String CreatePayloadEndpoint(String friendlyName, String endpointId, String capabilities, String cookie) throws JSONException{
JSONObject endpoint = new JSONObject();
endpoint.put("capabilities", new JSONArray(capabilities));
//设备的描述(暂时写死)
endpoint.put("description", "Whirlpool smart home");
//没有找到对应的类型,暂时全部为other
JSONArray displayCategories = new JSONArray("[\"OTHER\"]");
endpoint.put("displayCategories", displayCategories);
//设备制造商的名称
endpoint.put("manufacturerName", "Whirlpool Corporation");
if (endpointId == null)
endpointId = "endpoint_" + 100000 + new Random().nextInt(900000);
endpoint.put("endpointId", endpointId);
if (friendlyName == null)
friendlyName = "Sample Endpoint";
endpoint.put("friendlyName", friendlyName);
if (cookie != null)
endpoint.put("cookie", new JSONObject(cookie));
return endpoint.toString();
}
public String CreatePayloadEndpointCapability(String type, String interfaceValue, String version, String properties, String configuration) throws JSONException {
JSONObject capability = new JSONObject();
capability.put("type", type);
capability.put("interface", interfaceValue);
capability.put("version", version);
if (properties != null)
capability.put("properties", new JSONObject(properties));
if (configuration!=null) {
capability.put("configuration", new JSONObject(configuration));
}
return capability.toString();
}
public void SetPayload(String payload) throws JSONException {
response.getJSONObject("event").put("payload", new JSONObject(payload));
}
@Override
public String toString() {
return response.toString();
}
}
后面的话可根据请求的不同类型进行不同的方法。
关于java如何请求响应请查看亚马逊alexa的列子
最后声明,本文章纯属原创,未经允许禁止转载。谢谢。。。