Alexa 智能音箱开发智能家居

一,前期准备材料

  1. 一个亚马逊开发者账户。注册是免费的
  2. 连接设备,如灯,恒温器,相机或带有云API的锁,用于控制它
  3. 支持Alexa的设备,例如Amazon Echo
  4. 一个AWS账户。您在AWS Lambda函数中托管您的技能代码(这个账户可以注册,可以免费使用12个月,但需要绑定信用卡,且必须交1美元的预授权的费用,国内信用卡可以绑定,亲测)
  5. 了解JSON
  6. Java,Node.js,C#或Python作为Lambda函数的知识可以用任何这些语言编写
  7. 了解OAuth 2.0

二,技能创建

https://developer.amazon.com/zh/alexa  登录上述网址,并登录您的账号,在下图选择你的技能创建

进去之后,点击create skill,技能名称输入您的技能名,语言默认(也可以选择你自己喜欢的语言,然而并不支持中文,所以。。。),模型选择 智能家居,然后点击创建技能。

三,技能设置

 

  1. Payload version 选择v3
  2. 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的列子

https://github.com/alexa/skill-sample-java-smarthome-switch/blob/master/instructions/setup-the-sample-resources.md

 

最后声明,本文章纯属原创,未经允许禁止转载。谢谢。。。