此文的方法是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了。
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,速度快,但没有花生壳稳定,路由器插件设置参考如图
特别说明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
假如我的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("值")
)
如果想让设备去显示天气状况等信息,可以再建立一个Lambda函数,设置一个“CloudWatch事件-计划”的触发器,设定频率进行触发(最小1每分钟,最大可以到天),利用百度地图的天气接口等免费资源,再结合DynamoDB存储一下开关状态和是否推送状态,完成智能设备实时显示天气状况等信息。CloudWatch的费用我发工单咨询亚马逊客服中,暂时还没有回复……
原文链接:http://blog.csdn.net/luhanglei/article/details/56677567