超低成本DIY skill实现Amazon Echo Dot控制自己家的智能设备

此文的方法是DIY一个测试版Skill,会将设备直接暴露在公网上,安全性很差,而且账户认证什么的都是写死的,只适合自己玩,而且外网接口千万不要外泄。

原文链接:http://blog.csdn.net/luhanglei/article/details/56677567

最近发私信的人很多,决定直接创建一个开发爱好者的群:376862009

实现原理

Amazon Alexa →Amazon Cloud→Amazon Lambda→内网穿透服务器→局域网内的设备→终端(灯等设备)


新建一个Smart Home Skill

过程跟我前段时间写的文章差不多,但是这次创建的是Smart Home的Skill,不再是普通的Skill,也同时意味着必须要绑定账户(Auth Code Grant),而且EndPoint必须要使用Amazon Lambda了。

超低成本DIY skill实现Amazon Echo Dot控制自己家的智能设备_第1张图片

Endpoint

直接在AWS“弗吉尼亚北部区开通一个python语言(我习惯用JAVA,但是在这里没有python用起来简单,所以现学现卖写python)的空lambda,开通完了以后添加Alexa Smart Home的触发器,先放着。把arn:aws:lambda:开头的地址粘贴到Endpoint里。lambda每个月有100万次的免费额度,自己用足够了,也不必担心超时收费。算了一下,因为自己用不会并发,每个月可以免费用37天……

绑定账户

绑定的要求是要使用https进行oauth认证,而且只在初次启用Skill的时候会使用。因此喜欢自己折腾的童鞋可以自己找服务器写一个Oauth认证完成这一步。不想折腾或者不会写服务器的童鞋可以直接用我提供的代码包,部署到百度云BAE上(java8环境),在Authorization URL中填写你的域名+/login,比如:https://***.duapp.com/login,在Access Token URI中输入你的域名+/token,比如:https://***.duapp.com/token,提交之后应该就可以在Alexa的APP中的Your Skill里看到刚刚创建的Skill了,点enable,过一会即显示完成绑定。这时候把云服务器删掉就行了,节约money

Oauth认证包下载,可以直接用


获取设备必要的信息

如果你的设备需要通过SSDP来获取相关参数,比如接口号、ID等,请继续;如果没有,请跳过本条。我用java写了一个方法,放在Eclipse中,只需要将需要替换的多播地址、端口号等内容替换成具体文档要求的内容,跟智能设备在同一个局域网执行就可以看到设备的回复,其中包含需要的信息,在开发文档中需要用到的,请拷贝下来。

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class Discover {

	public static void main(String[] args) throws IOException, InterruptedException {
		// TODO 自动生成的方法存根
		InetAddress ia = InetAddress.getByName("SSDP多播地址");
		final MulticastSocket clientSocket = new MulticastSocket();
		clientSocket.joinGroup(ia);
		new Thread() {
			@Override
			public void run() {
				try {
					listen(clientSocket);
				} catch (IOException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			}
		}.start();
		StringBuffer sb = new StringBuffer();
		sb.append("M-SEARCH * HTTP/1.1\r\n");
		sb.append("HOST:SSDP多播地址:SSDP端口号\r\n");
		sb.append("MAN:\"ssdp:discover\"\r\n");	//一般都是ssdp:discover,具体看相关设备文档
		sb.append("ST:文档里这里的内容");
		DatagramPacket sendPacket = new DatagramPacket(sb.toString().getBytes(), sb.toString().length(), ia, SSDP端口号);
		clientSocket.send(sendPacket);
		Thread.sleep(5000);
		clientSocket.close();
	}

	static void listen(DatagramSocket clientSocket) throws IOException {
		byte[] receiveData = new byte[1024];
		DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
		for (int i = 0; i < 10; i++) {
			clientSocket.receive(receivePacket);
			String response = new String(receivePacket.getData());
			System.out.println(response);
		}
	}

}

让设备可以在公网访问到

因为好多智能设备的SDK只适合在内网使用,并不提供云接口,而且设备工作在内网,让工作在公网的Alexa访问不到。因此要采用内网穿透的方法让我们的Lambda可以访问到我们的设备,并发送命令。

我所尝试的方法有两种(当然指的成本最小的方法,有自己服务器的土豪请自己搭建frp或者ngrok):ngrok和花生壳(内网版,不是DDNS版)。这两种方法都需要路由器的支持(路由器不支持也可以买花生壳出的花生棒,插在路由器上也能让我们的设备上云),我使用的极路由1S有两者的插件,设置比较简单,注意ngrok在设置的时候服务器的端口号一般都是4443,跟要转发的端口号没有任何关系。

我喜欢用sunny提供的免费版ngrok,速度快,但没有花生壳稳定,路由器插件设置参考如图

超低成本DIY skill实现Amazon Echo Dot控制自己家的智能设备_第2张图片

特别说明rules,协议为TCP,IP 为路由器中看到的要操控设备的局域网IP地址,端口号一般文档里或者上一步SSDP的返回值里有写,最后一个为申请到的服务器端口号。

提交完成之后我们的设备就可以在公网访问了。


在Lambda中写代码实现Discover和Control事件,实现了开关和调百分比等操作,具体的可以自己再扩展(貌似Lambda连注释中都不准出现中文,自己删掉或者换掉中文再跑)API传送门

import uuid
import socket
import sys 

def lanbda_handler(event,context):
    eventNameSpace = event['header']['namespace']
    if eventNameSpace=="Alexa.ConnectedHome.Discovery":
        return onDiscovery(event)
    elif eventNameSpace=="Alexa.ConnectedHome.Control":
        return onControl(event)
def onDiscovery(event):#发现事件
    header={}
    header['messageId'] =  str(uuid.uuid1())
    header['name'] = "DiscoverAppliancesResponse"
    header['namespace'] = "Alexa.ConnectedHome.Discovery"
    header['payloadVersion'] = "2"
    payload={}
    discoveredAppliances=[]
    device = {}
    actions = ["turnOn","turnOff","setPercentage"]#声明可以进行打开、关闭、调百分比的操作
    device['actions'] = actions
    device['additionalApplianceDetails'] = {}
    device['applianceId'] = "编一个ID"
    device['friendlyDescription'] = "编一个名字,会显示在APP里"
    device['friendlyName'] = "称呼它什么,语音控制的关键词,比如light"
    device['isReachable'] = "true"
    device['manufacturerName'] = "生产商,自己写"
    device['modelName'] = "型号"
    device['version'] = "版本号"
    discoveredAppliances.append(device)
    payload['discoveredAppliances'] = discoveredAppliances
    result = {}
    result['header'] = header
    result['payload'] = payload
    return result
def onControl(event):
    eventName = event['header']['name']
    order = ""
    responseHeaderName = ""
    if eventName == "TurnOnRequest":
        order = '打开设备的命令'
        responseHeaderName = "TurnOnConfirmation"
    elif eventName == "TurnOffRequest":
        order = '关闭设备的命令'
        responseHeaderName = "TurnOffConfirmation"
    elif eventName == "SetPercentageRequest":
        order = '调节百分比的命令'
        responseHeaderName = "SetPercentageConfirmation"
    try:
        s = socket.socket()
        host = "内网穿透服务器的域名或者IP"
        port =  内网穿透服务器给你的端口号
        s.connect((host, port))
        s.send(order)
        s.recv(1024)
        header={}
        header['messageId'] =  str(uuid.uuid1())
        header['name'] = responseHeaderName
        header['namespace'] = "Alexa.ConnectedHome.Control"
        header['payloadVersion'] = "2"
        result = {}
        result['header'] = header
        result['payload'] = {}
        return result
    except:
        return targetOffLine()
    return {}
def targetOffLine():
    header={}
    header['messageId'] =  str(uuid.uuid1())
    header['name'] = "TargetOfflineError"
    header['namespace'] = "Alexa.ConnectedHome.Control"
    header['payloadVersion'] = "2"
    result = {}
    result['header'] = header
    result['payload'] = {}
    return result

保存以后,就可以让Alexa 进行“Discover devices”操作了。

假如我的friendly name是light,则可以执行的命令为:

“Alexa, turn on light”

“Alexa, turn off light”

“Alexa, set light to 50%”等

补充

如果想使用增大和减小百分比等接口,可以使用Amazon的DynamoDB数据库(看文档说,也有相当不错的免费额度),来记录设备的状态。

1.新建一个表,随便指定一个主键。

2.到IAM里,给lambda的账户增加DynamoDB的访问权限,即在策略中的statement中加入类似于下面一段策略

 {
            "Effect": "Allow",
            "Action": [
                "dynamodb:BatchGetItem",
                "dynamodb:BatchWriteItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-east-1:……完整的可以在表的概述里看到"
            ]
        }


3.使用python语言对表进行操作,需要导入boto3和查询需要的函数(文档传送门):

import boto3
from boto3.dynamodb.conditions import Key, Attr

获取表:

dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('表名')


插入操作(必须要包含主键,其他随意):

    response = table.put_item(
        Item={
            '主键': "主键内容",
            '参数':"参数内容",
            '某数字参数':decimal.Decimal(数字),           
        }
    )

根据主键查询:

 response = table.query(
        KeyConditionExpression=Key('主键').eq("值")
    )

查询结果是一个Dictionary,里面有 Count和Items等内容可以使用。

如果想让设备去显示天气状况等信息,可以再建立一个Lambda函数,设置一个“CloudWatch事件-计划”的触发器,设定频率进行触发(最小1每分钟,最大可以到天),利用百度地图的天气接口等免费资源,再结合DynamoDB存储一下开关状态和是否推送状态,完成智能设备实时显示天气状况等信息。CloudWatch的费用我发工单咨询亚马逊客服中,暂时还没有回复……


原文链接:http://blog.csdn.net/luhanglei/article/details/56677567

你可能感兴趣的:(超低成本DIY skill实现Amazon Echo Dot控制自己家的智能设备)