超低成本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了。

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

保存以後,就可以讓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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章