此文的方法是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。
獲取設備必要的信息
如果你的設備需要通過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
保存以後,就可以讓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