资讯专栏INFORMATION COLUMN

写一个树莓派管理系统

linkin / 1300人阅读

摘要:二维码扫描成功解析获得的字符串信息,然后将其写入配置文件,然后重启树莓派系统。这样重启后的树莓派系统就可以连接到了。

源码及模型下载

1. 前言

我有一个树莓派和 oled IIC接口 128x64的屏幕,另外我买了树莓派的排线的摄像头。我总想在oled屏幕上显示些什么,一般也就显示一下系统信息,显示一些动画,感觉没什么意思(也不怎么实用),所以我花时间做了一个可以控制树莓派的系统。也花时间用sketchup画了一个简单的外壳模型。

排线屏幕接口我采用插拔的杜邦线接头,这样方便换屏幕,接线地方简单用电烙铁焊接一下然后用热缩管包裹起来。

2. 外壳模型

我有9层外壳,去掉最上面的,用新画的这个就行,四个立柱是固定oled屏幕的,圆洞是风扇的,方框是摄像头的,至于螺丝孔位我没有留,因为我总觉得3d打印的孔位不结实,不如后期我自己用小电转转几个洞,然后固定上螺丝。

弄好了就可以切片了,然后用我廉价的3D打印机打印出模型。 模型看起来很简单,但是画的时候也花了很长时间,每个地方尺寸我都是用游标卡尺测量的,但是打印机精度比较低,后期我用打磨工具(小电转)打磨了一下,还可以。 洞的大小总是打印小,可能是我没有增加水平扩展的缘故吧。

最终效果:

看起来不咋好看,但是还算稳定。

3.手机设置

手机使用软件"蓝牙串口"软件控制:

4.系统的设计

我想实现一个用手机控制树莓派上的一些服务的系统,并且可以实现自定义命令,系统中的每一个选项由json 配置文件决定,这样可以易于拓展功能。开始时我通过网络控制树莓派,树莓派上建立socket服务,手机上发送http请求,但是这种操作很傻,因为你不能保证树莓派到一个环境中肯定是连接到网络上的,如果网络断了就不能控制树莓派了。后来我改成了通过蓝牙串口 rfcomm 协议控制。

oled屏幕驱动我使用的是Adafruit-SSD1306,这个驱动渲染使用的图像对象是一个PIL库的Image对象 , 如果需要拓展系统上功能,可以修改 json文件,并添加子任务来实现,而不需要在做Image上的处理。

command文件夹中存放子任务模块:扫描wifi二维码,显示系统信息,显示动画。

createJson.py

import jsondata = [    {"text":"设置","function":"test","child":            [                {"text":"wifi设置","function":"JscanQRcode"},                {"text":"service设置","function":"test"},                {"text":"其它设置","function":"test"},            ]    },    {"text":"显示动画(Bad Apple)","function":"JtestMovie"},    {"text":"显示系统信息","function":"JdisplayInfo"},    {"text":"重启系统","function":"Jrestart"},    {"text":"设置5","function":"test"},    {"text":"设置6","function":"test"},    {"text":"设置7","function":"test"},    {"text":"设置8","function":"test"},    {"text":"设置9","function":"test"},    {"text":"设置10","function":"test"},    {"text":"设置11","function":"test"},    {"text":"其它设置","function":"test"},]with open("config.json","w") as f:    f.write(json.dumps(data))

4.1 监听通信线程

开始时,监听蓝牙发送指令与收到指令处理分别独立于两个线程,只能分别采用轮询的方式,后来我发现这样对于cpu占用过高,为了考虑资源占用问题和指令的实时性问题,我将指令处理控制放到蓝牙监听循环中,这样在没有收到指令的时候,就会阻塞住,而收到指令的时候会立刻对指令进行处理。减少资源占用,并且提高了指令的实时性。

server_sock=bluetooth.BluetoothSocket(bluetooth.RFCOMM)server_sock.bind(("",1))server_sock.listen(1)while True:    client_sock,address = server_sock.accept()    while True:        try:            control = client_sock.recv(1024).decode()            判断control, 我定义了5种,分别为 上,下,确认,返回,息屏,5个指令            ....        except Exception as e:            print(e)            client_sock.close()            break

这里使用两个while 嵌套是因为,rfcomm协议需要一直连接才能通信,每次传输完指令后不会断开连接,这样读取每次的指令都会再第二层循环中, 而如果没有连接的时候就会阻塞在server_sock.accept() ,在连接过程中,断开连接会抛出异常,抛出异常就会执行到 break 从而跳出第二层循环,执行到server_sock.accept()阻塞住,重新等待新的连接到达。

这里我设计了一个息屏指令,因为我发现之前一直显示系统信息导致有一些烧屏效果,屏幕在全亮的时候有一些字暗痕,所以最好不要让屏幕一直亮着。

4.2 主线程与子任务线程

子任务与主线程不能在同一线程中,否则必然会阻塞到子任务中,从而不能获得下一条指令(也就是不知道什么时候退出),所以子任务的执行需要独立于主线程之外,用另一个线程去执行。独立出主线程外就可以接受到什么时候返回了。

另外就是主线程接受到返回指令后如何通知子线程退出,这里我采用通过文件的方式来传递的消息,当子任务开始运行前,在项目目录中写入一个文件is_running 其中值为1,表示子任务正在运行,然后在子任务循环中判断内容是否为0,如果为0立刻退出,而如果主线程收到返回指令后立刻修改 is_running 中值为0,这样就可以通知子任务结束了。

这里也会产生一个问题,当主线程向is_running中写入值为0执行完毕后,子线程未必立刻退出,而这个时候子线程与主线程可能在同时显示图像(在子任务中有时也可能调用oled屏幕显示一些内容,例如显示动画,显示系统信息),此时会照成冲突,导致oled屏幕不稳定,会出现亮屏,闪屏的现象。所以要保证同一时间,只能有一个线程向oled屏幕中显示图像。加锁是一种解决办法,不过我采用的是一种判定当前线程数量来判断子线程是否真的结束len(threading.enumerate()) > 1 ,如果线程数量大于1,说明子线程正在运行。

while len(threading.enumerate()) > 1:    pass

采用这种方式进行阻塞,从而防止多个线程对oled屏幕显示图像,导致oled屏幕不稳定。

5.扫描WIFI二维码功能

小米手机可以分享wifi (以二维码的方式),二维码包含 wifi 信息 ssid, password,以及使用的加密协议。通过这些信息就可以树莓派连接wifi了。 opencv库中有识别二维码的类cv2.QRCodeDetector() ,但是这个功能需要opencv4.0及以后的版本,树莓派执行使用 pip3 安装opencv4.0 及以后肯定会失败的,因为 在 build_wheel 的时候占用大量内存,还要各种原因,无奈,我只能下载opencv源码手动进行的编译。 编译成功后就可以调用这个类了。

这里我想用 oled 屏幕显示摄像头的内容,这种默认肯定是显示不了的,因为oled屏幕只能显示黑白两种状态,也就是图像经过二值化处理后的信息。大致能看清手机轮廓信息,这些也就够了。

二维码扫描成功解析获得的字符串信息,然后将其写入/etc/wpa_supplicant/wpa_supplicant.conf 配置文件,然后重启树莓派系统。这样重启后的树莓派系统就可以连接到wifi了。(这个系统软件肯定是需要root权限运行的,否则就修改不了配置文件)

6.动画

动画的实现是多个图片切换形成的。

6.1启动动画

只有一张图片,不够炫酷,用opencv掩模的方法将其做成动画。

createStartPic.py

import osimport cv2import numpy as npwidth = 128height = 64img = cv2.imread("result.png")mask = np.zeros((height,width,3),np.uint8)backImg = np.zeros((height,width,3),np.uint8)for x in range(width):    for y in range(height):        if y % 2 and x % 2:            backImg[y,x,:] = 255for i in range(100):    temp_mask = mask.copy()    cv2.circle(temp_mask,(width//2-1,height//2),i,(255,255,255),-1)    temp_mask = cv2.cvtColor(temp_mask,cv2.COLOR_BGR2GRAY)    temp_mask = cv2.threshold(temp_mask,150,255,cv2.THRESH_BINARY)[1]    inv_mask = 255 - temp_mask    result = cv2.bitwise_and(img,img,mask=temp_mask)    backImgTemp = cv2.bitwise_and(backImg,backImg,mask=inv_mask)    # cv2.imwrite(f"startPic2/{i}.jpg",backImgTemp+result)    cv2.imshow("title",backImgTemp+result)    cv2.waitKey(10)

然后将每一帧保持一个图片,使用的时候直接调PIL库读取每一帧图像。当然也可以直接用 opencv临时绘制,但是我觉得浪费性能,所以还是将其每一帧保存成图片了。

6.2过渡动画

过渡动画参考开机动画的代码,也就是将上一个状态的图片当做背景,新的图片为最终要展示的图片:

代码可以精确的控制每一帧的延时,过渡动画的快慢都可以进行精细的调节。

但是我感觉浪费些性能,所以过渡动画功能目前没有加到代码中。

7 调试方面

我电脑是win7,在调试写这个控制系统的时候调试起来非常麻烦,每次修改完需要上传到树莓派,才能看到显示的效果。后来我想到,驱动库调用就是PIL图像对象。那么我可以将其转换为 opencv的图像对象(numpy数组),然后进行显示,这也是我文章中上面几个截图中的显示。而要做到不进行任何修改就可以上传,这里调用的Adafruit_SSD1306驱动库,所以我在项目目录中直接建立一个名为 Adafruit_SSD1306 的模块,里面调用opencv将图像用一个子线程显示出来:

import cv2import numpy as npfrom PIL import Imagefrom threading import Threadclass SSD1306_128_64:    def __init__(self,rst):        self.img = Image.new("1", (128,64))        Thread(target=self.display_thread).start()    def begin(self):        pass    def clear(self):        self.img = Image.new("1", (128,64))            def display_thread(self):        while True:            img = np.asarray(self.img,dtype="uint8")            img[img==1] = 255            cv2.imshow("title",img)            cv2.waitKey(10)    def display(self):        pass    def image(self,image):        self.img = image

上传的时候,不上传 Adafruit_SSD1306.py 文件即可。 当然这里只能作为调试oled屏幕显示方面,不能作为具体任务执行时调试,因为windows没有对应的命令,并且子任务线程判定也会进行干扰。

8.将其配置成服务

新建文件: /etc/systemd/system/rfcomm.service

Description=RFCOMM serviceAfter=bluetooth.serviceRequires=bluetooth.service [Service]ExecStart=/usr/bin/python3 /root/raspi/main.py [Install]WantedBy=multi-user.target

增加到开机自启中:

systemctl enable rfcomm

关闭开机自启:

systemctl disable rfcomm

管理服务:

service rfcomm stop #停止service rfcomm start #启动service rfcomm restart #重启

9.使用

依赖:

Adafruit_SSD1306opencv 4.1.0 (手动编译)

可参考我的几篇文章:

树莓派编译opencv4

树莓派蓝牙rfcomm协议通信

上传树莓派不需上传项目目录下 Adafruit_SSD1306.py

10.其它

为了写这篇文章我用到了一个gif转换生成的工具,我发现生成的gif带有水印,需要充值才可以去水印,简单研究了一下,我猜测水印是png叠加的,所以用正则表达式匹配png文件头和尾,得到了水印图片,然后将其修改成完全透明,然后将修改完的完全透明图像替换到exe中,改完png图像比原始图像要小,所以我进行了补0操作,然后二进制替换就实现了去水印的效果。毕竟gif工具只是临时使用,就只是为了展示一下效果代码效果(我买过这个公司的软件终身会员(不过听说也是github开源项目改的,很不良心))。建议以后软件水印在软件代码逻辑中进行生成,这样尽量防止被破解。

另外,破解和反破解相互促进发展。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/123594.html

相关文章

  • Linux学习

    摘要:学习树莓派的的初识学习树莓派的的初识初识初识学习书籍正点原子嵌入式驱动开发指南章节第三十章学习内容书中介绍的获取可以有三个途径第一个是的官方代码。网上的烧写树莓派教程很多,但是为了学习我选择了官方的代码。Linux学习 - 树莓派4b的U-Boot的初识初识U-Boot学习书籍:《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.1》章节:第三十章学习内容:书中介绍uboot的获...

    不知名网友 评论0 收藏0
  • 树莓系列】一.准备工作与开机

    摘要:很早前就听说树莓派了,但一直没有亲手把弄过。树莓派的一些改变个人建议是购买最新版本,因为它板载了和蓝牙模块,大大减少了我们的前期准备工作。 很早前就听说树莓派了,但一直没有亲手把弄过。直到有了给儿子打造智能玩具的想法,才决定入手了一台。之前没有过任何单片机和硬件的经验,只能从网上收集资料。这个过程充满挫折也十分有趣,于是决定记录下来,希望能对同样刚入门的同学有帮助。 树莓派版本 树莓派...

    bang590 评论0 收藏0
  • 树莓2上安装Ubuntu mate系统并成功部署基于.net core的CMS系统Zkeacms

    摘要:亲戚送了一个树莓派以下内容也适用树莓派,决定拿来学习折腾一下,由于想学,决定首先安装系统。配置使用以下内容替换原有内容重启添加服务运行在安装服务之前,可以先手动运行一下看是否可正常访问,定位到目录,然后运行然后使用服务器树莓派访问。 亲戚送了一个树莓派2(以下内容也适用树莓派3),决定拿来学习折腾一下,由于想学Ubuntu,决定首先安装Ubuntu mate系统。 准备一张16G以上...

    Soarkey 评论0 收藏0
  • 树莓软件安装指引

    摘要:通过安装是的简称,如果你之前没有接触过,可以借助玩树莓派开始使用。通电启动现在将卡插入树莓派,接上电源。如果你使用,你所要做的就是选择一个操作系统,树莓派就会执行安装。 前言 想玩一下树莓派,做点有趣的东西,但是看了一圈的百度,涉及的文章都比较高端,看来还是得从树莓派官网看起。就起了翻译一下这个入门文章的念头。了解的同时也可以帮助其他和我一样的朋友。喜欢看原文的同学点这里。 这一系列一...

    scq000 评论0 收藏0
  • 树莓软件安装指引

    摘要:通过安装是的简称,如果你之前没有接触过,可以借助玩树莓派开始使用。通电启动现在将卡插入树莓派,接上电源。如果你使用,你所要做的就是选择一个操作系统,树莓派就会执行安装。 前言 想玩一下树莓派,做点有趣的东西,但是看了一圈的百度,涉及的文章都比较高端,看来还是得从树莓派官网看起。就起了翻译一下这个入门文章的念头。了解的同时也可以帮助其他和我一样的朋友。喜欢看原文的同学点这里。 这一系列一...

    pekonchan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<