接 使用BASH来实现一个简单的摄像头运动检测监控程序 和 使用TCPDUMP和WIRESHARK来分析XBMC的网络协议,这是我春节安防手段第三部分,利用自动化控制的台灯来让踩点的小偷误以为家里有人。
安防其实就是个和骗子斗智斗勇的过程。
最早使用X10的目的是为了让自己的新房变得更酷一点,当时甚至还想过以后有机会买emotiv的脑波控制器,用那来控制家电。后来却因为装修电工师傅反对我的控制方案而让整个智能家居方案搁浅了,在那之前曾搭建过一个验证用的原型,却因为各种原因把代码搞丢了,所以这次春节去福建玩之前又重新研究了一下,把这个系统再一次搭建起来。这里记录一下,以免以后需要的时候找不到资料。
我使用的X10设备是国内瑞朗智能家居研发的X10设备,他们的X10设备基本分为三类,一个是阻波器,用来防止大功率电器产生的噪音或者别人家的X10控制信号影响自己家的X10网络。第二个是发射装置,用来将控制信号编码到220V交流电里。第三个是接受装置,一般是开关,用来接受交流电里载波的控制信号并控制对应的如灯泡之类的设备。这里讲的是发射装置。
我买的发射装置型号是 RL-TMJ,淘宝上已经没有卖的,卖家也全部下架,不明白他们为什么下架,个人感觉是这个技术虽然发展了二三十年,但是家庭用电环境较80年代变化太快,这个变得不太适应现在拥有众多大功率电器的家庭环境,以及X10传输速率低,抗干扰能力差,单工传输等问题,逐渐淘汰给使用无线或者专门布线的方案。按照我以前看的资料,这套设备最好是单独部署到照明线路中,以降低别的家用电器的干扰,不过家里因为照明电路已经在装修的时候搭建好,这次部署的时候不方便更改,所以我是在墙上插座里实验的,经过实验发现信号跨房间就没法传递了。
这个发射模块有4根TTL控制线以及2根接入交流电的线,L接220V电源火线和N接220V电源零线。TTL用的USART协议,分别是黑色接Vcc 5.0V,灰色GND,紫色RX,发射装置使用的USART配置如下:
<td>
2400
</td>
<td>
无
</td>
<td>
8
</td>
<td>
1
</td>
发射模块和接收模块首次使用要配对,配对方法是快速开关接收模块,好像是三次还是几次,然后这时接受模块进入学习模式,然后发送单播控制指令,接受模块收到后会将单播控制指令里的地址码设置为其地址。
通讯方面走的是USART,上层应用协议我对比过perl的CPAN里的实现,貌似不是标准X10协议,似乎是瑞朗他们自己定义的一套,因此没法直接用CPAN里现成的X10库,得自己做数据编码和解码,这里我把说明书上的说法和我以前看的标准X10协议整理一下,方便以后用的时候理解。
按照协议规定,设备可以按房间来划分来提供组播支持,使用4位根码表示房间代码,因此最大有16个房间支持,使用5位子码表示这个房间中的具体的某一个设备,但却不是一个房间中最多能有32个设备,却是16个设备。
指令与地址编码表
瑞郎的设备使用的控制指令有:
<td>
编码
</td>
<td>
00101
</td>
<p>
00011
</p>
<td>
</td>
<td>
01011
</td>
<td>
00111
</td>
<td>
00001
</td>
<td>
01001
</td>
设备的根码编码:
<td>
0110
</td>
<td>
1110
</td>
<td>
0010
</td>
<td>
1010
</td>
<td>
0001
</td>
<td>
1001
</td>
<td>
0101
</td>
<td>
1101
</td>
<td>
0111
</td>
<td>
1111
</td>
<td>
0011
</td>
<td>
1011
</td>
<td>
0000
</td>
<td>
1000
</td>
<td>
0100
</td>
<td>
1100
</td>
设备的子码编码:
<td>
01100
</td>
<td>
11100
</td>
<td>
00100
</td>
<td>
10100
</td>
<td>
00010
</td>
<td>
10010
</td>
<td>
01010
</td>
<td>
11010
</td>
<td>
01110
</td>
<td>
11110
</td>
<td>
00110
</td>
<td>
10110
</td>
<td>
00000
</td>
<td>
10000
</td>
<td>
01000
</td>
<td>
11000
</td>
协议定义
协议分为2种,分别是单播和组播,单播的格式为:
4位起始码(固定为1110) + 8位跟码 + 10位子码 + 4位起始码(固定为1110) + 8位跟码 + 10位子码 + 6位暂停码(固定000000)+ 4位起始码(固定1110) + 8位根码 + 10位功能码 + 4位起始码(固定1110) + 8位根码 + 10位功能码 + 2位结束码(固定为00),一共96位,12个字节。
组播格式为:
4位起始码(固定1110)+ 8位根码 + 10位功能码 + 4位起始码(固定1110)+ 8位根码 + 10位功能吗 + 4位结束码(固定0000),一共是48位,6个字节。
以上单播和组播格式中,功能码/子码/根码的长度是上面码表定义的一倍,是因为需要对码表中的代码做一次编码,将0用01表示,1用10表示。
例如:根码A的8位二进制数为01 10 10 01
例子:发送B6 On指令,则发送的二进制数据为:
1110(起始码) + 10101001(根码B) + 1001011001(子码6) + 1110(起始码) + 10101001(根码B) + 1001011001(子码6) + 000000(暂停码)
1110(起始码) + 10101001(根码B) + 0101100110(功能码)+ 1110(起始码) + 10101001(根码B) + 0101100110(功能码)+ 00
转换为16进制数为:
EA 99 67 AA 65 90 3A A5 66 EA 95 98
不管是单播还是组播,发送给发射装置都还要在上面的指令之上再一次进行一次编码:
FF FF 数据长度(1B) 数据内容 校验(1B)
长度只是前面编码的内容,比如前面12字节这里对应的就是0C
校验=数据内容的和取反=~SUM(Payload)
验证方法:数据内容的和+校验码=111111,那么数据正确,否则就有问题。
发射装置应答给上位机的数据格式: FF FF 应答码(1B)+ 校验码。
<td>
接收正确
</td>
<td>
执行完毕
</td>
<td>
接收错误
</td>
上位机需要做超时重复机制,如果超时30ms没有收到应答,重新发送数据。发送端在收到正确应答后不能发数据,直到收到执行完毕命令,才可以再次发送数据。而接受方(X10发射设备)在[正确应答]或者[错误应答]后,必须保证[执行完毕]命令发送成功。
发送举例:
发送B6 On 指令:
发送:
FF FF 0C EA 99 67 AA 65 90 3A A5 66 EA 95 98 1A
应答:
FF FF 00 FF 接收正确
FF FF 01 FE 执行完毕
FF FF 80 7F 接收错误
附上根据此协议编写的perl脚本:
#!/usr/bin/env perl
use strict;
use warnings;
use Device::SerialPort qw(:PARAM :STAT 0.07);
use Time::HiRes qw(usleep);
use Getopt::Std;
my %codes = (
A => '0110',
B => '1110',
C => '0010',
D => '1010',
E => '0001',
F => '1001',
G => '0101',
H => '1101',
I => '0111',
J => '1111',
K => '0011',
L => '1011',
M => '0000',
N => '1000',
O => '0100',
P => '1100'
);
my %subcodes = (
1 => '01100',
2 => '11100',
3 => '00100',
4 => '10100',
5 => '00010',
6 => '10010',
7 => '01010',
8 => '11010',
9 => '01110',
10 => '11110',
11 => '00110',
12 => '10110',
13 => '00000',
14 => '10000',
15 => '01000',
16 => '11000'
);
my %funcs = (
ON => '00101',
ON_ALL => '00011',
BRIGHT => '01011',
OFF => '00111',
OFF_ALL => '00001',
DIM => '01001'
);
sub encode
{
my $code = shift;
my $t = sub {
my $s = shift;
return $s eq '0' ? '01' : '10';
};
$code =~ s/([01])/&$t($1)/ge;
return $code;
}
sub checksum
{
my $str = shift;
my $sum = 0;
for(my $i = 0; $i < length($str); $i++)
{
my $char = substr($str, $i, 1);
$sum += ord($char);
}
return (~$sum) & 0xff;
}
sub gen
{
my ($addr, $func) = @_;
die "Invalid device device addr $addr\n" unless $addr =~ /([A-P])(\d{1,2})/;
my $code = encode($codes{$1} or die "Invalid device code $addr\n");
my $subcode = encode($subcodes{$2} or die "Invalid device subcode $addr\n");
$func = encode($funcs{$func} or die "Invalid function $func\n");
my $bin_payload = "1110${code}${subcode}" x2 . "000000" . "1110${code}${func}"x2 . "00";
my $payload = pack("B*", $bin_payload);
my $checksum = checksum($payload);
my $ret = sprintf("FFFF%02X%s%02x", length($payload), unpack('H*', $payload), $checksum);
return pack("H*", $ret);
}
die "Usage: ./lights device func\n" unless(scalar(@ARGV) == 2);
my ($dev, $func) = @ARGV;
my $cmd = gen($dev, $func);
my $port = new Device::SerialPort("/dev/ttyUSB0", 0, "x10.lock") or die "Cannot open X10 transmitter $!\n";
$port->devicetype('none');
$port->baudrate(2400);
$port->parity('none');
$port->databits(8);
$port->stopbits(1);
$port->buffers(4, 4);
$port->write($cmd);
usleep 100 * 1000;
my ($count, $resp) = $port->read(4);
$port->close;
写好以上代码后,我将其放到crontab里自动执行:
30 22 * * * x10 A1 OFF
Last modified on 2014-02-17