1 概述
由于工作的需要,我们希望做一个工具,连接电话线,然后将来电的号码转换为名字,图片显示出来。
2 设计
2.1 需求
系统设计的目标是输入是电话线,然后由树莓派输出展示电话号码对应的名字,分组,和备注的信息。但是,有两个重要的额外需求:
- 存放电话本的原始数据不能放在树莓派,因为树莓派的sd卡并不可靠,丢数据就麻烦大了。另外在内网的树莓派并不方便随时随地对电话本进行更新操作。
- 展示端需要支持在树莓派或者在其他的移动设备上。树莓派的屏幕太小了,如果有必要的话,系统能支持在一些废弃的平板电脑上展示来电显示信息,这样看着比较方便。
2.2 系统设计
系统设计分为数据管理端和信息展示端两部分。
- 数据管理端,数据放在阿里云的公网服务器上,用户就能随时随地对电话本进行更新操作。当阿里云的电话本数据发生变化时,能够以websocket的形式及时推送到内网的树莓派去更新。另外,树莓派的来电号码,时间也需要及时记录到阿里云上,以备以后的数据分析。
- 信息展示端,树莓派本地将会启动两个任务,服务任务和展示任务,服务任务会监听来电号码,然后通过查询本地电话本获取信息,然后推送信息到展示任务。以及和阿里云的信息更新任务。展示任务,以web的方式展示,树莓派默认以浏览器在自己的屏幕上展示来电号码的信息,其他移动设备也可以通过打开树莓派的web服务来展示一样的信息。
3 硬件
3.1 来电显示盒
整个系统最关键的是读取电话线的模拟信号,将来电号码解码出来。目前来电号码标准只有两种,DTMF和FSK。有很多成熟的IC芯片可以用,搜索出来后一大堆,但这种芯片需要自己焊接电话线口,并且接入到树莓派的GPIO口上解析,对于我数字电路盲来说简直不可能。
后来,我在淘宝上发现这个,来电显示盒,直接从RS232串口中读取信息就能得到电话号码了,相当简单暴力。
当然,这个过程也不是一帆风顺的,我中间还买过一个56k的modem,还有usb的来电显示盒,驱动问题蛋疼到要死,全部都是默认支持windows,想想还要自己移植到arm版的linux上,这是要多麻烦。而且,,如果厂商的驱动没有提供源代码,根本不可能移植。
3.1 串口转usb
树莓派上是没有rs232接口的,来电显示盒是不能直接插上去的,幸好有rs232转usb接口的设备,买回来直接插上去就能用了,驱动基本不用考虑,从树莓派到mac都是免驱的,特别方便。
3.2 树莓派+显示器
最后是选购一个树莓派了,很明显就是最新版,树莓派3b。但屏幕也是个问题,我最后选了这个。hdmi接口免驱动,而且清晰,外壳还是3d打印出来的,刚刚好,和屏幕是一体化的,特别方便。
4 安装
4.1 系统
在屏幕的官网上下载系统,记得要选HDMI_Normal_LCD的Rspberry(480x320)下载,不要选错版本了。480x320这个分辨率是最好的,其他分辨率看起来都很怪。
我试了好几个版本,最新的stretch版本是最好的,因为它自带就是chromium 52浏览器,版本高达52,基本上什么html5特性支持,特别爽。
4.2 串口
options := serial.OpenOptions{
PortName:'/dev/ttyUSB0',
BaudRate:1200,
DataBits: 8,
StopBits: 1,
InterCharacterTimeout: 100,
MinimumReadSize: 1,
}
port, err := serial.Open(options)
if err != nil {
panic(err)
}
defer port.Close()
this.log.Debug("serial connect success!")
result := []byte{}
for {
buf := make([]byte, 128)
n, err := port.Read(buf)
if err != nil {
if err != io.EOF {
panic(err)
} else {
break
}
} else {
buf = buf[0:n]
endIndex := -1
for i := 0; i != n; i++ {
if buf[i] == '\n' {
endIndex = i
break
}
}
if endIndex != -1 {
result = append(result, buf[0:endIndex]...)
var data string
if len(result) != 0 && result[len(result)-1] == 13 {
data = string(result[0 : len(result)-1])
} else {
data = string(result)
}
if len(data) != 0 {
this.msgChan <- data
}
if endIndex+1 >= len(buf) {
result = []byte{}
} else {
result = buf[endIndex+1 : len(buf)]
}
} else {
result = append(result, buf...)
}
}
}
我使用的是golang的”github.com/jacobsa/go-serial/serial”库,直接像网络操作一样,读取串口的数据流就可以了,相当简单,注意一下读到换行符就截断就可以了。
4.3 开发
开发就比较简单了,代码在这里,分为三个模块,caller是树莓派的服务任务,manager是阿里云上的管理系统,push是树莓派和阿里云的通用websocket服务器。
值得一提的是,push服务是websocket服务器,它跟socket.io不同的是,它是使用多路复用订阅的,二进制分包传输,耗费的资源更少,效率更高,也算是又重新发明了一个小轮子吧。
GOOS=linux GOARCH=arm GOARM=7 go build
不得不说,golang的交叉编译实在太爽,一条命令就能在mac上直接编译出arm版上的可执行程序。不再需要在树莓派上安装各种golang环境,和等待漫长的编译时间(树莓派的性能相比pc实在太差)。但是,要注意避免引入需要cgo的外部库,这样会导致交叉编译很麻烦,需要配置clang的交叉编译参数。
4.4 自启动
@chromium-browser --noerrdialogs --disable-popup-blocking --no-first-run --disable-desktop-notifications -kiosk --incognito http://localhost:9090
在以上的目录中的autostart文件里,加入上面这一句就可以了,开机就会自动启动chromium-browser,全屏隐身方式启动,并打开指定的网址。
#!/bin/sh
export DISPLAY=:0.0
cd /home/pi/Project/push && make
cd /home/pi/Project/caller && make
另外,在autostart的文件夹上,添加一个包含以上内容的run.sh,然后在autostart的chromium-browser前一行加入@bash /home/pi/.config/lxsession/LXDE-pi/run.sh。为什么要这样做,而不是在/etc/rc.local添加服务自启动?因为rc.local里面的服务自启动默认是以root启动的,而且rc.local是在x11启动前执行的。但是caller中的电源管理工作是需要在x11启动后才能执行的,所以服务的自启动就放在autostart,而不是rc.local了。
4.5 电源管理
export DISPLAY=:0.0
xset dpms 60 120 180
以上命令是指定x11在空闲时间的60秒以后进入standby,120秒以后进入suspend,180秒以后进入off。一旦进入三个模式中的其中一种,屏幕就会完全关掉以最优化保护屏幕和减少能耗。并且,越往后的模式关闭得就越彻底,恢复时也越慢。
xset dpms force on
以上这个命令是强制打开dpms,激活dpms进入on模式,这样就会点亮屏幕,并重新计算空闲时间。
两条指令的搭配使用,我们就能实现pi的屏幕在空闲时关闭,在来电时自动打开,当有一段时间没有来电时屏幕也会自动关闭,就像手机一样。这样既能达到通知的效果,也能保护到屏幕和减少能耗。
5 总结
这是一个有趣的小玩具。
- 本文作者: fishedee
- 版权声明: 本博客所有文章均采用 CC BY-NC-SA 3.0 CN 许可协议,转载必须注明出处!