小米智能家庭接入亚马逊Echo

效果:效果视频

本文讲述到的器材:Echo Dot(当然也可以接入其他设备),极路由1S(已经开启极客模式,理论上只要能进入ssh的路由器都可以),小米网关,温湿度传感器

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

原文地址:http://blog.csdn.net/luhanglei/article/details/60140972

1.首先,把小米网关的“开发者模式”打开(小米网关页面→更多(三个点)→关于→狂点插件版本号),到这里网关就可以被发现且局域网控制了。

2.然后下载绿米官方的通信文档:点击这里下载通信文档

发现设备,把以下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("224.0.0.50");
		final MulticastSocket clientSocket = new MulticastSocket();
		clientSocket.joinGroup(ia);
		new Thread() {
			@Override
			public void run() {
				try {
					listen(clientSocket);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}.start();
		StringBuffer sb = new StringBuffer();
		sb.append("{\"cmd\":\"whois\"}");
		DatagramPacket sendPacket = new DatagramPacket(sb.toString().getBytes(), sb.toString().length(), ia, 4321);// 1982
		clientSocket.send(sendPacket);
	}

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

}


应答信息里有网关的IP和端口号

发送单播信息获取设备列表,同样是java代码放进去跑,需要把其中的ip和端口号换掉,也就是把“IPIPIP”和“PORT”替换掉

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

public class Gateway {

	public static void main(String[] args) throws IOException, InterruptedException {
		// TODO 自动生成的方法存根
		InetAddress ia = InetAddress.getByName("IPIPIP");
		// final MulticastSocket clientSocket = new MulticastSocket();
		final DatagramSocket clientSocket = new DatagramSocket();
		// 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("{\"cmd\":\"get_id_list\"}");
		DatagramPacket sendPacket = new DatagramPacket(sb.toString().getBytes(), sb.toString().length(), ia, PORT);
		clientSocket.send(sendPacket);
	}

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

}


代码执行以后,会收到如图回复

小米智能家庭接入亚马逊Echo_第1张图片

data中有网关下所有设备的信息,通过把"{\"cmd\":\"get_id_list\"}"命令修改为"{\"cmd\":\"read\",\"sid\":\"设备的ID\"}"可以看到设备的信息,从而可以判断出设备是什么。因为此文主要通过温湿度传感器进行示例,我将继续以温湿度传感器为例子进行描述。

通过文档可以看到开发者模式的通信方式是在局域网内进行UDP通信。而亚马逊的echo dot必须要通过云服务去控制设备,并不支持在局域网内进行操作。因此我们要做的就是让网关可以在公网被访问到。把tcp进行内网穿透的软件很多,比如ngrok、frp、花生壳(内网版)等,但udp的内网穿透却不像tcp那么成熟,即便支持,也不是很稳定。我尝试过用frp进行udp内网穿透,刚运行的时候反应迅速,但过了一夜之后就失联了。由于对路由器的系统不是很了解,一直以为我的路由器在不刷机的情况下不能自由的运行程序,因为缩水版linux安装运行环境也很麻烦,没怎么尝试过。直到看到frp直接下载就能运行以后,才知道原来可以通过go语言编译成mips32le版本在极路由之类的路由器上直接运行。

于是赶紧学了一点go语言,实现了一个在路由器某个端口监听TCP,将消息通过UDP转发给网关的程序。最后工作流程如下:

Amazon Lambda →TCP→路由器内网穿透软件→我写的tcp2udp→网关,打通了一条相对稳定的通路。

我把编译后的程序传在了CSDN下载中:tcp2udp(仅限mips32le芯片的路由器,其他的如果需要可以在评论里留下平台我再编译)

首先,我在内网穿透服务新建一个到我路由器12143端口的通道。如果使用ngrok,配置如下,其他类似(最后的55555为服务提供者提供的端口号,如果自己搭的服务器,相信你知道什么意思):

tcp,127.0.0.1,12143,55555

配置完成之后,相当于服务器的55555端口和路由器的12143端口打通了。下面要做的,就是用tcp2udp把路由器的12143端口监听器来,如果有消息来,发给网关。

把文件放到存储卡里,比如我放到了存储卡里的tcp2udp文件夹下,那我就需要在路由器的shell里CD到这个文件夹,然后执行它。

小米智能家庭接入亚马逊Echo_第2张图片

打开程序所在文件夹(文件夹名字是tcp2udp,里面还有tcp2udp的程序,只是个名字,任意起)

cd /tmp/storage/mmcblk0/tcp2udp

执行(-p是端口号,-t是网关的ip和端口号,最后的&符号是添加进队列在后台运行,不至于关掉ssh后就被干掉)

./tcp2udp -p 12143 -t 192.168.199.162:9898 &
出现像上图中的样子,就是执行成功了。这时候把第二段java代码中的ip和端口号换成内网穿透服务器的地址和端口号,则也能工作。

最后一步,接入echo dot,创建skill的步骤在前两篇关于echo开发的博文中说过,这次类似。

Lambda的创建在下文中有,但触发器要用custom skill,因为smart home对于获取温湿度的接口并不支持:

点击打开链接

创建custom skill的过程下文中有介绍,自己用,则不需要使用账户,可以选不用账户

点击打开链接

我在lamda中的代码如下,已经实现了获取温度和湿度

import socket
import json
def lambda_handler(event, context):
    # TODO implement
    slots = event['request']['intent']['slots']
    isTemperature = 0
    isHumidity = 0
    if slots['firstval']['value']=="temperature" :
        isTemperature = 1
    elif slots['firstval']['value']=="humidity":
         isHumidity = 1
    if slots['secondval'].has_key("value"):
        if slots['secondval']['value']=="temperature" :
            isTemperature = 1
        elif slots['secondval']['value']=="humidity":
            isHumidity = 1
    address = ('内网穿透服务器地址', 内网穿透服务器端口)  
    s = socket.socket() 
    s.connect(address)  
    s.send('{"cmd":"read","sid":"温湿度传感器ID"}')
    data= s.recv(1024) 
    d = json.loads(json.loads(data)['data'])
    print d
    temperature = float(d['temperature'])/100
    humidity = float(d['humidity'])/100
    res = {}
    res['version'] = "1.0"
    speechText = ""
    if (isTemperature==1) and (isHumidity==1):
        speechText = "The temperature is "+str(temperature)+" degree, and the humidity is "+str(humidity)+" percent."
    elif isTemperature==1:
         speechText = "The temperature is "+str(temperature)+" degree."
    else:
         speechText = "The humidity is "+str(humidity)+" percent."
    outputSpeech = {}
    outputSpeech['type'] = "PlainText"
    outputSpeech['text'] = speechText
    card = {}
    card['type'] = "Simple"
    card['title'] = "Smart Home"
    card['content'] = speechText
    resp = {}
    resp['outputSpeech'] = outputSpeech
    resp['card'] = card
    res['response'] = resp
    return res


原文地址:http://blog.csdn.net/luhanglei/article/details/60140972


你可能感兴趣的:(小米智能家庭接入亚马逊Echo)