一,前期准备材料
- 一个亚马逊开发者账户。注册是免费的
- 连接设备,如灯,恒温器,相机或带有云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();@Overridepublic 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参数格式转化为mappublic 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类型按一定规则转换为Mappublic 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 {@Autowiredprivate AlexaResponse alexaResponse;@Autowiredprivate S_Alexa_Discovery s_Alexa_Discovery;@Autowiredprivate 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");//没有找到对应的类型,暂时全部为otherJSONArray 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));}@Overridepublic String toString() {return response.toString();}
}
后面的话可根据请求的不同类型进行不同的方法。
关于java如何请求响应请查看亚马逊alexa的列子
https://github.com/alexa/skill-sample-java-smarthome-switch/blob/master/instructions/setup-the-sample-resources.md
最后声明,本文章纯属原创,未经允许禁止转载。谢谢。。。