树莓派做来电显示

2018-03-04 fishedee 后端

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 开发

Screen Shot 2018-03-04 at 5.10.10 P

开发就比较简单了,代码在这里,分为三个模块,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 自启动

Screen Shot 2018-03-04 at 5.22.51 P
@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 总结

这是一个有趣的小玩具。

相关文章