Linux 将串口映射为TCP服务+无线热点(树莓派)
起因
最近在做一个Android控制APP,本来想使用蓝牙BLE的串口透传模块,但是因为项目中使用的串口数据包比较长,通信比较频繁,买了个透传模块一次多大(MTU)只有20个字节,也不支持修改,而且写入过程需要有Thread.sleep(23),就很蛋疼了,数据量一大通信延迟特别严重,而且控制的设备对实时性要求较高,这个方案显然不能满足要求,放弃。
手里刚好有一块树莓派3B+,放着吃灰,想着是不是可以用树莓派建WIFI热点,把串口用网络转发出来。树莓派自带的串口,网上说有些小问题,再有项目中需要控制多个串口,树莓派最多自带2个,不走弯路直接上usb转串口,插到rspi的USB上,试了几个常用模块,都可以识别,美滋滋。插上之后在/dev/ttyUSB*
上,可以用echo 123 > /dev/ttyUSB0
去尝试发送。
第一步将串口映射为TCP服务,双向转发。
首先当然是想自己写代码转发,网上搜了下,嗯~~,好像都是简单demo,要写成产品级别的估计要下点功夫。看有没有现成的工具,学过几天信安,转发当然是nc(netcat),奈何好像脚本挺负载的,最后在国外某论坛上找到了一个一条命令搞定的好玩意,socat,完美解决问题
socat TCP-LISTEN:4161,fork,reuseaddr FILE:/dev/ttyUSB0,b57600,raw,echo=0”
#4161是TCP服务的端口号
#/dev/ttyUSB0就是对应的USB转的串口
#最后加echo=0,不加的话树莓派会给串口回写,也就是外部设备向串口写什么,串口会原样向串口返回同样的数据
测试使用sscom串口调试工具使用网络“TCPClient”连接测试,通信顺畅,测试一小时连接正常,大问题解决
第二步将TCP服务设为自启动,不需要也不可能每次都要人工启动。
确定串口设备的名称
首先要解决的问题是usb转串口每次插入时设备名不确定的问题,由于是多个串口,不能确定是哪一个,这就要求设置usb串口名称固定
在/etc/udev/rules.d/99-input.rules
(也有可能不一样),中添加一行:
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523",ATTRS{serial}=="123456", SYMLINK+="userilaMoto"
这些信息可以使用命令 udevadmin info -a -n /dev/ttyUSB0 中查找idVendor,idProduct,serial,可以添加更多只要能区分出不同设备即可
最后SYMLINK就是设置完成后在/dev中要出现的名称,当然不能与现有的重名。
可是有些串口设备没有serial号,呵呵,如果买了几个相同的usb转串口根本没法区分。
最后在/dev/serial目录下有两个目录 by-id 和 by-path,在by-path下不管是什么模块插上只要物理USB端口一样,名字就不会变,这就方便多了,哈哈。
串口名称形如/dev/serial/by-path/platform-3f980000.usb-0:1.2:0-port0
设置编写服务脚本
在/etc/init.d/下建立一个脚本文件取名netserial,名字可以自己起
在/etc/init.d/下有很多服务脚本,可以参考编写,注意下面脚本中echo和log*mes使用service或systemctl命令都不会显示,不写无妨,只是在自己测试脚本的时候比较方便而已
/etc/init.d/netserial
#!/bin/sh
#
### BEGIN INIT INFO
# Provides: netserial
# Required-Start:
# Required-Stop:
# Should-Start:
# Should-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: serial port via network
# Description:
### END INIT INFO
PATH=/sbin:/bin:/usr/sbin:/usr/bin
. /lib/lsb/init-functions
start(){
pcount=$(ps -aux | grep TCP-LISTEN:5961 | grep socat | wc -l)
if [ $pcount -eq 0 ]
then
nohup socat -lu -d -d -d TCP-LISTEN:5961,fork,reuseaddr FILE:'/dev/serial/by-path/platform-3f980000.usb-usb-0\:1.3\:1.0-port0',b57600,raw,echo=0 > /var/logs/socat5961.log &
log_success_msg "\033[32msocat TCP:5961 <----> RightTop USB Serial Port@57600 Started \033[0m"
else
log_warning_msg "\033[35msocat TCP:5961 <----> RightTop USB Serial Port@57600 has Started last time \033[0m"
fi
pcount=$(ps -aux | grep TCP-LISTEN:5962 | grep socat | wc -l)
if [ $pcount -eq 0 ]
then
nohup socat -lu -d -d -d TCP-LISTEN:5962,fork,reuseaddr FILE:'/dev/serial/by-path/platform-3f980000.usb-usb-0\:1.2\:1.0-port0',b38400,raw,echo=0 > /var/logs/socat5962.log &
log_success_msg "\033[32msocat TCP:5962 <----> RightBottom USB Serial Port@38400 Started \033[0m"
else
log_warning_msg "\033[35msocat TCP:5962 <----> RightBottom USB Serial Port@38400 has Started last time \033[0m"
fi
pcount=$(ps -aux | grep TCP-LISTEN:5963| grep socat | wc -l)
if [ $pcount -eq 0 ]
then
nohup socat -lu -d -d -d TCP-LISTEN:5963,fork,reuseaddr FILE:'/dev/serial/by-path/platform-3f980000.usb-usb-0\:1.1.3\:1.0-port0',b9600,raw,echo=0 > /var/logs/socat5963.log &
log_success_msg "\033[32msocat TCP:5963 <----> LeftBottom USB Serial Port@9600 Started \033[0m"
else
log_warning_msg "\033[35msocat TCP:5963 <----> LeftBottom USB Serial Port@9600 has Started last time \033[0m"
fi
}
stop(){
pcount=$(ps -aux | grep TCP-LISTEN:5961 | grep socat | wc -l)
if [ $pcount -gt 0 ]
then
ps -aux | grep TCP-LISTEN:5961 | grep socat | awk '{print $2}' | xargs kill
log_success_msg "\033[31msocat TCP:5961 <----> RightTop USB Serial Port Stop\033[0m"
fi
pcount=$(ps -aux | grep TCP-LISTEN:5962| grep socat | wc -l)
if [ $pcount -gt 0 ]
then
ps -aux | grep TCP-LISTEN:5962| grep socat | awk '{print $2}' | xargs kill
log_success_msg "\033[31msocat TCP:5962 <----> RightBottom USB Serial Port Stop\033[0m"
fi
pcount=$(ps -aux | grep TCP-LISTEN:5963 | grep socat | wc -l)
if [ $pcount -gt 0 ]
then
ps -aux | grep TCP-LISTEN:5963 | grep socat | awk '{print $2}' | xargs kill
log_success_msg "\033[31msocat TCP:5963 <----> LeftBottom USB Serial Port Stop\033[0m"
fi
}
status(){
pcount=$(ps -aux | grep TCP-LISTEN:5961 | grep socat | wc -l)
if [ $pcount -gt 0 ]
then
log_progress_msg "\033[32msocat TCP:5961 <----> RightTop USB Serial Port @57600 active\033[0m \n"
else
echo "\033[31msocat TCP:5961 <----> RightTop USB Serial Port @57600 inactive\033[0m \n"
#cat /var/logs/socat5961.log | grep -v transferred | tail -n 20
fi
log_progress_msg "see log in /var/logs/socat5961.log \n"
pcount=$(ps -aux | grep TCP-LISTEN:5962| grep socat | wc -l)
if [ $pcount -gt 0 ]
then
echo "\033[32msocat TCP:5962 <----> RightBottom USB Serial Port @38400 active\033[0m"
else
echo "\033[31msocat TCP:5962 <----> RightBottom USB Serial Port @38400 inactive\033[0m"
fi
log_progress_msg "see log in /var/logs/socat5962.log \n"
pcount=$(ps -aux | grep TCP-LISTEN:5963 | grep socat | wc -l)
if [ $pcount -gt 0 ]
then
echo "\033[32msocat TCP:5963 <----> LeftBottom USB Serial Port @9600 active\033[0m"
else
echo "\033[31msocat TCP:5963 <----> LeftBottom USB Serial Port @9600 inactive\033[0m"
fi
log_progress_msg "see log in /var/logs/socat5963.log \n"
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
status)
status
;;
*)
log_failure_msg "Usage: /etc/init.d/netserial {start|stop|restart|status}"
exit 1
;;
esac
设置开机启动
cd /etc/init.d
#脚本权限
chmod 775 netserial
#安装启动脚本,96是启动序号,因为需要使用网络,和usb硬件,设置到靠后,多个串口可以分别安装
update-rc.d netserial defaults 96
#卸载启动脚本,先写到这里,以免后期需要卸载
# update-rc.d -f netserial remove
# 同样可以使用 service或者systemctl start/stop/restart/status 控制服务
无线热点
由于不可能每次都要手动在设备上连接无线网络,需要无线网络自动连接,有两种方案可选:
-
手机/平板创建热点,树莓派自动连接,缺点,移动设备热点不会自动开启,长时间不连接会关闭,如果热点名字改了密码变了,就再连接不上了,而且支持特定设备。
-
树莓派创建热点,移动设备自动连接,缺点实现有点难度,但使用方便。
3.使用自动化工具完成 [更新]
主要有两种一个是create_ap,另一个是 raspap-webgui
推荐使用raspap-webgui一个命令自动安装,还带web管理端,非常方便。
使用方法参见其github,我就不多嘴了。https://github.com/billz/raspap-webgui
wget -q https://git.io/voEUQ -O /tmp/raspap && bash /tmp/raspap
更新:以下方式太麻烦(不推荐)
下面采用法案2搭建热点,就当给自己做笔记,一面以后再填坑。
以下内容大部分参考 Simpreative文章 https://www.jianshu.com/p/0ecd8b734204
使用hostapd dnsmasq 搭建
sudo apt-get install dnsmasq hostapd
# 修改 /etc/dhcpcd.conf
interface wlan0
static ip_address=192.168.0.1/24
# 创建 sudo nano /etc/hostapd/hostapd.conf
interface=wlan0
driver=nl80211
ssid=RaspberryPi
hw_mode=g
channel=7
wmm_enabled=0
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=12345678
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
# 修改/etc/default/hostapd
DAEMON_CONF="/etc/hostapd/hostapd.conf"
#配置DNSMASQ
sudo mv /etc/dnsmasq.conf /etc/bak_dnsmasq.conf
#重新创建 /etc/dnsmasq.conf,并写入
interface=wlan0
dhcp-range=192.168.0.2,192.168.0.20,255.255.255.0,24h
# 还有net转发,当然这里不涉及数据转发问题
到此为止好像结束了,但是hostapd 起不来,查看状态,提示
Loaded: masked (/dev/null; bad)
需要使用命令"unmask"
sudo systemctl unmask hostapd.service
sudo systemctl enable hostapd.service
但是开机之后还是没有无线热点,但是 service hostapd status
提示启动了呀,并且重启服务就可以正常连接,好坑呀。
最后查看状态有一个日志提示
random: Cannot readfrom /dev/random: Try again
random: Only 0/20bytes of strong random data available from /dev/random
random: Not enoughentropy pool available for secure operations
表示随机数不够,查了一下是因为/dev/random 生成速度太慢,改成/dev/urandom 就好了
网上有说是将random 备份然后软链接urandom->random,但是每次重启后就没有了...
没办法只有源码改造了
#下载源码
wget http://w1.fi/releases/hostapd-2.9.tar.gz
tar -xzvf hostapd-2.6.tar.gz
#配置文件
cd hostapd-2.6/hostapd
cp defconfig .config
#修改源码
hostapd-2.6/src/crypto/random.c :(不止1处)
由 fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
改为 fd = open("/dev/urandom", O_RDONLY | O_NONBLOCK);
#安装依赖,使用工具搜索缺失的依赖
sudo apt install apt-file
apt-file search xx/xx/xx.h
#编译,如果出现错误安装对应的依赖
cd hostapd-2.9/hostapd
make
#将编译好的hostapd覆盖原来的文件
sudo cp hostapd /usr/sbin/
sudo cp hostapd_cli /usr/sbin/
嗯....,提示信息是没了,但是还是不能开机启动,手启可以,没办法了,最后在netserial脚本上加了一句
start(){
...
#暂停10秒
sleep 10
#重启hostapd服务
service hostapd restart
}
不算完美,功能实现了,不想折腾了...