stm32mp157开发板FS-MP1A是华清远见自主研发的一款高品质、高性价比的Linux+单片机二合一的嵌入式教学级开发板。开发板搭载ST的STM32MP157高性能微处理器,集成2个Cortex-A7核和1个Cortex-M4 核,A7核上可以跑Linux操作系统,M4核上可以跑FreeRTOS、RT-Thread等实时操作系统。开发板搭配仿真器、显示屏、摄像头、资源扩展板等丰富的扩展模块,可拓展物联网、人工智能等相关技术学习,还可以拓展丰富的项目实战,非常贴合企业当下开发需求,是一款嵌入式Linux入门进阶必备开发板!
可学习技术:嵌入式Linux应用/系统/驱动开发、ARM裸机开发、Qt界面编程、STM32单片机、FreeRTOS、人工智能机器视觉等。其中ARM Cortex-A7裸机开发课程是华清远见独有特色课程,可关注:https://www.bilibili.com/video/BV1Xe4y1i7vm/,持续更新中。
可实战项目:14个Linux+Qt综合项目案例,8个MP1A物联网拓展项目
项目配套文档及源码,可在下方评论区留言索取~~
1、Linux+Qt综合项目案例:华清远见stm32mp157开发板优势特色部分,包括音乐播放器、智慧家庭、智能工业电表、智能出行助手、智能猫眼、环境监测、智能安防、智能语音识别等10余个项目案例,涉及家居、医疗、农业多种应用方向,在案例中使用了多种物联网和嵌入式技术,包括OT开发、linux应用开发、linux驱动开发、物联网云端接入、MQTT协议、json字符串等知识点。
基于Linux+Qt的智慧家庭项目
项目简介:
智慧家庭又可称为智慧家庭服务平台,是智慧城市的最小单元,是以家庭为载体,以家庭成员之间的亲情为纽带。智慧家庭综合利用物联网、云计算、移动互联网和大数据等新一代信息技术,结合自动控制技术,将家庭设备智能控制、家庭环境感知、家人健康感知、家居安全感知以及信息交流、消费服务等家居生活有效地结合起来,创造出健康、安全、舒适、低碳、便捷、个性化和充满关爱的家庭生活方式。
开发平台:
华清远见stm32mp157开发板豪华套餐(开发板+仿真器+五寸屏+摄像头+资源扩展板+tf卡+读卡器)
项目功能展示:
Wifi 模块
点击刷新按钮,可以实时更新附近的 wifi,选择要连接的 wifi,会弹出输入密码的页面,输入密码,点击连接即可连接成功。
选择要查看天气的城市,点击获取按钮
环境监测模块
设备控制模块
选择按钮即可完成设备的控制
开启和关闭按钮控制智能监测的开启和关闭,提交按钮上的输入框输入的是智能监测触发的阈值。
连接百度云模块
输入百度云连接三元组,点击获取时间戳,点击计算、连接,即可实现向云端发送环境进而转发到微信小程序,并且通过微信小程序控制设备。
在微信小程序显示温湿度。并且控制开发板 led 灯
遮挡光电开关,会自动弹出门禁系统。
Qt开发环境搭建
主机开发环境说明
1) 本文档主要介绍 linux 环境下的 Qt 程序开发;
2) 主机 Qt 版本为 5.14.1;
主机 Qt 环境搭建及使用
Qt Creator 安装
将 qt-creator-opensource-linux-x86_64-4.10.1.run(Qt 实验源码\工具软件) 复制到 ubuntu 主机中,可以采用共享文件夹的方式也可以使用 tfp方式将文 件存入家目录下的 Downloads 目录。我们需要在终端中赋予安装程序可执行的权限
我们可以使用图形化的文件管理器来查看
双击“qt-creator-opensource-linux-x86_64-4.10.1.run”图标运行安装程序。
出现如下界面:
等待程序验证完成后点击“Next”
这里我们需要登录或者注册一个账号,如果我们之前已经注册过直接登录就可以。如果没有注册过则需要新注册有一个账号后登录。这里笔者已经注册过账号,所以直接登录。
登录成功后出现如下界面,点击 Next
这里选择安装路径
可以直接默认,Next
这路选择安装的组件,直接默认即可
这里我们需要同意用户协议
这个界面告诉我们安装完成后需要占用的空间。点击”Install”按钮后开始安装。
安装完成后出现如下界面
点击“Finish”按钮后将弹出 Qt Creator 主界面
点击“Cancel”按钮后即可正常使用
Qt5.14.1 安装
复制到 qt-opensource-linux-x64-5.14.1.run(Qt 实验源码\工具软件)到 ubuntu 主机中,可以采用共享文件夹的方式也可以使用 tfp 方式将文件存入家目录下的 Downloads 目录。进入所在文件夹,先给执行权限输入命令
chmod +x ./qt-opensource-linux-x64-5.14.1.run
安装在命令行输入
./qt-opensource-linux-x64-5.14.1.run
会有可视化引导安装,一直 next 就行了
在选择安装组件的时候要是不知道选择那些就全选了 大概有 4 个 G 左右
下载 gcc 和 g++
sudo apt-get install gcc g++
下载 cmake
sudo apt-get install cmake
下载链接库
sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev
Qt Creator 配置
1)配置 GCC
运行 QtCreator 后,依次点击"Tool"->"Options",出现选项对话框,在左侧点击"Kits",右 边选择"Compilers"标签。 检查有没有下图标注的 C++和 C ,一般按上面步骤执行后都会有
点击右侧"Add"按钮,弹出下拉列表后,选择"GCC"的"C"
填写信息如下,"Name"为"Auto-GCC","Compiler path"点击旁边的"Browse.."按钮选择编译器的路径,例子中的路径是 “/usr/bin/gcc”
2)配置 G++
点击右侧"Add"按钮,弹出下拉列表后,选择"GCC"的"C++",下面的文本框填写"Name" 为"Auto-G++","Compiler path"点击旁边的"Browse.."按钮选择编译器的路径,例子中的路径是" /usr/bin/g++"。
填写完成后,点击"Apply"。
3)配置 qmake
选择"Qt Versions"标签,如果有下面红框中的文本,可以跳过下面步骤
如果没有,在右侧点击"Add..."
会弹出 qmake 路径选择对话框,这里以"/home/linux/Qt5.14.1/5.14.1/gcc_64/bin/qmake"为例子。 选择”qmake”文件后,击"Open"按钮
"Version name"改为" Qt %{Qt:Version} GCC"。然后点击"Apply"按钮。
4)配置 Kits
点击左侧"Kits",右侧选择"Kits"标签。检查有没有下图红框选中的文本,如果有可以跳过下面步骤
然后没有,点击 Add:
在弹出的对话框中"Name"为"Desktop","Device Type"选择"Desktop"选项, "Sysroot"选择目标设备的系统目录,"Compiler"选择之前配置的名称
"Auto-GCC"和"Auto-G++","Qt version"选择之前配 置的名称"Qt 5.14.1
GCC",其它默认即可,最后点击"Apply"和"OK"按钮
Qt Creator 新建工程
注意:工程路径最好不要包含中文、特殊字符、空格等
我们可以新建一个“qt”文件夹,该文件夹用作我们以后存放源代码。
打开 Qt Creator,在欢迎页面点击 “New”按钮,来新建一个工程。
在出现的新建项目窗口中,我们选则“Application”->“Qt Widgets
Application”,然后点击右下方“Choose…”按钮,来创建一个桌面 Qt 应用。
我们在这里设置项目介绍和源码位置,我们这里创建一个名为
“HelloWorld”的示例项目,设置完成之后点击 next
直接点击 next
随后进行细节设置,主要设置要创建的源码文件的基本类信息,包括类名等。这里我们可以根据自己的项目特点进行设置。需要说明的一点就是基类的选择,这里基类有 QMainWindow、QWidget、QDialog 三种,它们的不同之处如下:
⚫ QMainWindow 类提供一个带有菜单条,工具条和一个状态条的主应用程序窗口。主窗口通常提供一个大的中央窗口部件,以及周围菜单,工具条,和一个状态栏。QMainWindow 窗口经常被继承,使得封装中央部件,菜单,工具条,状态栏等都变得很容易,当用户点击它的时候,相应的槽就会被调用;
⚫ QWidget 类是所有用户界面对象的基类,窗口部件是用户界面的一个基本单元,它从窗口系统接收鼠标,键盘和其他消息,并在屏幕上绘制自己。一个窗口部件可以被他的父窗口或者是其他窗口挡住一部分;
⚫ QDialog 类是对话框窗口的基类,对话框窗口主要用于短期任务和用户进行短期通讯的顶级窗口,QDialog 可以是模态对话框或者是非模态对话框。QDialog 支持扩展并带有返回值,他们可以带有默认值;我们在这里选择 QDialog 类即可,点击 next 完成类信息设置
直接点击 next 按钮即可。
然后进行工具选择,该页面可以选择我们创建的工程可以使用的工具,选择想要使用的编译器模块,例如下图 。点击 next
最后我们设置汇总信息,如果不需要版本控制等功能,直接点击完成finish 即可
随后我们就进入到了主界面,这时候 Qt 已经帮我们做好了一些准备工作,包括创建了一些文件,写好了一些前置代码等等。
我们可以点击左边 protect 栏,来查看我们的编译选项
我们可以在左下角选择编译 Debug 版或者 Release 版,即调试版或发行版。
左下角绿色剪头是编译并运行,锤子是仅编译,我们可以直接点击绿色小箭头将我们导入的工程编译并运行起来。
点击运行按钮后,我们可以看到 HelloWorld 窗口运行起来了。
导入工程
我们可以将已存在的 Qt 程序项目直接打开,这里以上一章节的HelloWorld 程序为例。首先我们确定源码存在的位置,如 HelloWorld 程序源码在 /home/linux/qt/helloworld 路径下。
点击欢迎页面的“Open” 按钮可以打开已有的工程。
找到我们刚才解压好的源码,选择“helloworld.pro”文件并点击打开
接下来我们就可以进入到代码编辑界面了。
左上角是项目栏,点击项目名称左边的小箭头可以展开项目目录。
我们可以点击左边项目栏,来查看我们的编译选项。需注意的是构建设置中的路径应与工程路径处于同级目录下。
我们可以在左下角选择编译 Debug 版或者 Release 版,即调试版或发行版。
左下角绿色剪头是编译并运行,锤子是仅编译,我们可以直接点击绿色小箭头将我们导入的工程编译并运行起来。
点击运行按钮后,我们可以看到 HelloWorld 窗口运行起来了。
文件说明
通过上面两个章节,我们学习到了 Qt 程序的新建与导入的方法,也知道了Qt 会帮我们做一些基础工作,比如帮我们建立了一些文件,那么这些文件都是干什么用的呢?我们以HelloWorld 程序来说明一下。
以“.pro”为后缀名的文件,为 Qt 的项目管理文件,存储项目设置的文件;
“Qt += core gui”表示项目中加入 core gui 模块。core gui 是 Qt 用于GUI 设计的类库模块,如果创建的是控制台(console)应用程序,就不需要添加 core gui。
Qt 类库以模块的形式组织各种功能的类,根据项目涉及的功能需求,在项目中添加适当的类库模块支持。例如,如果项目中使用到了涉及数据库操作的类就需要用到 sql(数据库)模块,在 pro 文件中需要在后面加上 sql:
1 Qt += core gui sql
“greaterThan(QT_MAJOR_VERSION, 4): QT += widgets”,这是个条件执行语句,表示当 Qt 主版本大于 4 时,才加入 widgets 模块。
“TARGET = HelloWorld”表示生成的目标可执行文件的名称,即编译后生成的可执行文件是 HelloWorld.exe。
“TEMPLATE = app”表示项目使用的模板是 app,是一般的应用程序。
后面的 SOURCES、HEADERS、FORMS 记录了项目中包含的源程序文件、头文件和窗体文件(.ui 文件)的名称。这些文件列表是 Qt Creator 自动添加到项目管理文件里面的,用户不需要手动修改。当添加一个文件到项目,或从项目里删除一个文件时,项目管理文件里的条目会自动修改。
文件夹“Header”中,存放的是所设计的窗体类的头文件;
文件夹“Sources”中,存放着源码文件。main.cpp 是实现 main()函数的程序文件,HelloWorld.cpp 是 widget.h 里定义类的实现文件。C++中,任何窗体或界面组件都是用类封装的,一个类一般有一个头文件(.h 文件)和一个源程序文件(.cpp 文件);
文件夹“Forms”中,存放着界面设计文件,“.ui”文件是一个 XML 格式存储的窗体上的元件及其布局的文件,双击项目文件目录树中的文件 ui,会打开一个集成在 Qt Creator 中的 Qt Designer 对窗体进行可视化设计;
UI 设计器有以下一些功能区域:
组件面板:窗口左侧是界面设计组件面板,分为多个组,如 Layouts、Buttons、Display Widgets 等,界面设计的常见组件都可以在组件面板里找到。
中间主要区域是待设计的窗体。如果要将某个组件放置到窗体上时,从组件面板上拖放一个组件到窗体上即可。
Signals 和 Slots 编辑器与 Action 编辑器是位于待设计窗体下方的两个编辑器。Signals 和 Slots 编辑器用于可视化地进行信号与槽的关联,Action 编辑器用于可视化设计 Action。
布局和界面设计工具栏:窗口上方的一个工具栏,工具栏上的按钮主要实现布局和界面设计。
对象浏览器(Object Inspector):窗口右上方是 Object Inspector,用树状视图显示窗体上各组件之间的布局包含关系,视图有两列,显示每个组件的对象名称(ObjectName)和类名称。
属性编辑器(Property Editor):窗口右下方是属性编辑器,是界面设计时最常用到的编辑器。属性编辑器显示某个选中的组件或窗体的各种属性及其取值,可以在属性编辑器里修改这些属性的值。属性编辑器的内容分为两列,左侧为属性的名称,右侧为属性的值。属性又分为多个组,实际上表示了类的继承关系,位于下方的类属性组继承自位于上方的类属性组;如果我们需要新建资源文件、源码文件等,可以在项目文件夹出点击鼠标右键,选择 Add New;如果我们有新的文件需要添加,可以在项目文件夹出点击鼠标右键,选择 Add Existing Files。
帮助文档
Qt 的帮助文档是伴随我们学习 Qt 开发的好伙伴。在 Qt 开发过程中,我们会面临图形接口使用的问题,它不像 C 语言那样就那么几个函数接口,图形接口的接口数量可以用海量来形容,常用的我们可能能记住,其它的就没有必要去记了,用到什么就去帮助文档查看用法是比较方便的。我们可以按 F1 按键,或通过上方导航栏的“help->contects”来进入帮助文档。
上方的前进后退按钮方便我们查看文档,如返回到上一步,返回到下一步。
我们可以通过帮助文档来查看以下几个部分:
类使用的相关介绍;
查看相关类的使用介绍,我们可以先进入到帮助文档,然后在左上角选择“Search”。笔者这里以 QWidget 类为例,输入我们想要查找的类的名字,然后双击查找结果来查看说明
也可以先将鼠标移动到想要查询的类的位置,如图所示,将鼠标移动至“QWidget”处,然后按“F1”键,即可跳转到相应的帮助文档
我们可以通过再按一次“F1”键来全窗口查看帮助文档,按“Esc”键可以退出。
部分常用的成员元素包括以下几项:
⚫ 公有成员函数:操作部件属性的相关函数;
⚫ 公有槽函数:Qt 类中已经定义好的槽函数,直接可与信号相连接;
⚫ 信号:软中断,如按下按钮触发 pressed() 信号等;
⚫ 保护成员函数:通常事件所对应的虚函数放在此处;
⚫ 事件:常用事件,如操作鼠标触发的鼠标事件;
滚动鼠标滚轮,向下即可看到“Qwdget Class”类的相关说明了。
1) 查看所用的部件的相应成员函数。
我们可以查找到该类所用部件的相应成员函数的使用方法、功能、参数、
返回值等等,我们以“按钮”控件,即“QPushButton Class”类为例,我们通
过索引搜索的方式,来找到这个类
我们可以通过点击“Public Functions” 来查看“QPushButton”这个类中的成员函数。
这里以“QPushButton(const QString &text, QWidget *parent =Q_NULLPTR)”为例,我们点击函数名字可以进入到函数详情中。我们可以看到相应的描述为:以“text”为显示内容,以“parent”为父对象,构造一个push 按钮。“text”“parent”为函数参数,由于是构造函数,所以此函数没有返回值。
还有一些函数是继承自其它类的,例如“Public Functions”中有 21 个继承自“QAbstractButton”类的函数,我们点击“QAbstractButton”即可查看。
点击“QAbstractButton”即可查看。
同样我们可以点击相应的函数进入查看详情。如查看“void setText(const QString &text)”。
2) 查看所用的部件的信号。
我们这里还是以“PushButton”为例,我们点击“Public Slots”。
可以看到“PushButton”本身有一个“void showMenu()”的信号,并且有很多继承自其他类的信号。
一般来说我们用的“PushButton”的信号,最多的是用到其继承自基类“
QAbstractButton”中的几个信号,分别是点击(按下后抬起)、按压(单按下)、释放(单抬起)等。
我们可以点击相应信号查看详情
3) 查看所用的部件的事件(所对应的虚函数如何编写)。部件常用事件主要在 “QWidget”中声明,选择“Events”即可查看相关说明。
每个事件都对应着事件函数。
点击事件函数可查看详情
微信小程序开发环境搭建
微信小程序开发工具简介
微信小程序是小程序中的一种,英文名 Wechat Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用。全面开放申请后,主体类型为企业、政府、媒体、其他组织或个人的开发者,均可申请注册小程序。微信小程序、微信订阅号、微信服务号、微信企业号是并行的体系。以下是小程序所涉及的技术概括:
⚫ JSON
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它是基于 ECMAScript(w3c 制定的 js 规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人的阅读和编写,同时也易于机器解析和生成,并有效提升网络传输效率。
⚫ XML
XML(Extensible Markup Language),中文名为可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。
⚫ CSS
层叠样式表(Cascading Style Sheets)是一种用来表现 HTML(标准通用标记语言的一个应用)或 XML(标准通用标记语言的一个子集)等文件样式的计算机语言。CSS 不仅可以静态的修饰网页,还可以配合各种脚本语言动态的对网页各元素进行格式化。
⚫ JavaScript
JavaScript,是一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。它的解释器被称为 JavaScript 引擎,是浏览器的一部分,广泛用于客户端的脚本语言。
申请微信小程序
登录微信公众平台,注册账号,选择小程序。https://mp.weixin.qq.com/
按照步骤依次注册,输入邮箱,密码,验证码等,同意协议进行注册。
然后登录自己的邮箱,查阅邮件,点击链接进行激活。进入步骤 3,信息登记,按照网页要求,依次输入信息,身份信息,管理员微信信息,即可激活成功。
返回微信公众平台,输入刚刚注册的账户密码,会需要用管理员微信扫码登录,登录后,下载普通小程序开发者工具。
点击开发,选择开发设置,获取小程序 ID,以备后续开发需求。
微信小程序开发工具下载完成后,进行默认安装即可
创建新项目工程
打开微信小程序开发者工具,点击创建新工程,填写自己的 APPID,选择默认模板,语言选择 JavaScript,点击新建。
新建工程完成为如下界面:
基本环境配置
打开主界面的右上角的详情按钮,找到本地设置,将“增强编译”和“不校验合法域名”这两个选项进行勾选,因为在小程序的开发阶段,尽量把这两勾选上。
编译、调试
打开 app.json 文件,可以更改微信小程序的标题,改为“工程 demo”。然后按下 Ctrl + S 快捷键进行保存,即可完成编译,调试输出。查看现象。
项目总体设计介绍
总体框架
智慧家庭系统的设计基于物联网的思想,物联网是新一代信息技术的重要组成部分,其英文名称是“The Internet of things”。其基本思想是以互联网为媒介,实现远程监督、控制。它在各个领域有着非常广泛的应用。
总体框架如下:
该项目分为 WIFI 连接模块、智能门禁模块、数据采集模块、智能检测模块、设备控制模块、天气预报模块、与百度云交互模块:下面具体介绍几个模块的功能。
WIFI 连接模块
该模块实现的原理是使用 wpa_supplicant 工具对无线网络进行管理和控制的功能。wpa_supplicant 是一个开源项目,已经被移植到 Linux,Windows 以及很多嵌入式系统上。它是 WPA 的应用层认证客户端,负责完成认证相关的登录、加密等工作。
wpa_supplicant 工具包含 wpa_supplicant 和 wpa_cli 这 2 个程序,其中wpa_supplicant 程序作为服务端在后台运行,服务 wpa_cli 客户端的请求,从而实现 WiFi 的配置连接。下面是通过 shell 命令去进行 WIFI 的配置及连接。
1.打开 wlan0 接口:
root@fsmp1c:~# ifconfig wlan0 up
2.启动 wpa_supplicant 进程并在后台运行
root@fsmp1c:~# wpa_supplicant -D nl80211 -i wlan0 -c /etc/wpa_supplicant.conf-B
3.扫描周边 WiFi 热点:
wpa_cli -i wlan0 scan
4.查看扫描结果:
root@fsmp1c:~# wpa_cli -i wlan0 scan_results
5.添加一个网络连接
root@fsmp1c:~# wpa_cli -i wlan0 add_network
6.配置 WiFi 热点的名称 ssid:
root@fsmp1c:~# wpa_cli -i wlan0 set_network 1 ssid '"FARSIGHT"'
7.配置 WiFi 热点的密码 psk:
root@fsmp1c:~# wpa_cli -i wlan0 set_network 1 psk '"fs123456"'
8.列举所有保存的连接
root@fsmp1c:~# wpa_cli -i wlan0 list_network
9.连接第 1 个保存的连接
root@fsmp1c:~# wpa_cli -i wlan0 select_network 1
10.启动 wpa_supplicant 应用
root@fsmp1c:~#wpa_supplicant -B -c wifi.conf -i wlan0
11.使用 udhcpc 命令动态获取 IP
root@fsmp1c:~#udhcpc -i wlan0
智能门禁模块
该模块是根据关电开关智能识别有没有人来,当有人触发光电开关,会自动弹出登陆界面,输入用户名和密码,程序会自动匹配数据库,如果用户名密码错误超过三次,会自动报警;如果输入正确,则开门。
数据采集模块
另开一个线程,实时去读取温湿度驱动设备文件的数据,进行计算得出温湿度的数值同样的方式得到光照的数值,通过信号传参的方式传给主线程,将数据设置到 ui 界面上。
智能检测模块
开启智能检测后,程序会根据你设定的阈值进行检测,假如温度超过你设定的温度阈值,会自动开启风扇;或者当光照低于你设定的阈值,会开灯提高照明的亮度。
设备控制模块
通过 ui 界面的按键去开关灯或者风扇,以及蜂鸣器。
天气预报模块
连接 WIFI 之后,通过 get 方法从网上获取信息,得到 Json 类型的数据,对这个数据进行解析,将解析到的数据设置到 ui 界面上面。
百度云交互模块
在 ui 界面输入在百度云创建设备时的 IoTCoreld、DeviceKey、DeviceSecret(三元组)通过组合生成 addr 和用户名,使用 MD5 加密算法计算得到密码,用于连接,连接成功后开启定时器,自动向指定好的 topic 发布采集到的温度湿度和光照,并且订阅云端控制设备的 topic,用户向云端发布json 数据,开发板接收到云端转发的 json 数据会做出响应。如{“led1”,1},开发板收到数据后 led1 会亮
源码分析
WIFI 连接模块
在 Qt 程序里使用 system 函数来执行 5.2 的命令来实现 WIFI 的配置
建立刷新按钮信号槽连接,实现点击刷新按钮界面显示附近 wifi。实现原理是启动 wpa_supplicant 进程并在后台运行,扫描周边 WiFi 热点;
使用wpa_cli -i wlan0 scan_results 命令查看扫描结果,并将扫描结果重定向到wifilist 文件中,对 wifilist 文件进行读操作,将读到的数据直接显示到 ui界面上面。核心代码如下
……
system("wpa_supplicant -D nl80211 -i wlan0 -c /etc/wpa_supplicant.conf -
B");
system("wpa_cli -i wlan0 scan");
system("wpa_cli -i wlan0 scan_results > ./wifilist");
usleep(50000);
QString fileName = "./wifilist";
QFile file(fileName);
int f = 0;
j = 0;
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::warning(this,"Warnning","can't
open",QMessageBox::Yes);
}
QTextStream in(&file);
QString str;
while (!((str = in.readLine()).isEmpty ()))
……
选中刷新后显示的 wifi 名后,点击连接会进入二级界面。二级界面输入密码正确后点击连接,等待几 s 后即可完成连接。这里输入框使用自定义类MylineEdit,继承自 QlineEdit,增加了鼠标点击事件,当触摸屏点击输入框的时候,会弹出软键盘,用来输入密码
这里连接 wifi 的核心代码如下:
……
sprintf(set_ssid,"wpa_cli -i wlan0 set_network %d ssid
'\"%s\"'",i,wifiName.toLatin1().data());
sprintf(set_password, "wpa_cli -i wlan0 set_network %d psk '\"%s\"' >
TorF.ini",i,password_edit->text().toLatin1().data());
sprintf(select_wlan, "wpa_cli -i wlan0 select_network %d ",i);
system(set_ssid);
system(set_password);
system("wpa_cli -i wlan0 list_network");
system(select_wlan);
system("wpa_supplicant -B -c wifi.conf -i wlan0");
qDebug()<< get_TorF().data()->toUpper();
if(get_TorF().data()->toUpper()=="F")
{
QMessageBox::warning(this,tr("Connect information"), tr("密码
错误"));
return ;
}
system("udhcpc -i wlan0 -B");
char echo_1[64];
char echo_2[64];
sprintf(echo_1,"echo \"nameserver 114.114.114.114\" >
/etc/resolv.conf");
system(echo_1);
sprintf(echo_2,"echo \"nameserver 8.8.8.8\" > /etc/resolv.conf");
system(echo_2);
close();
QMessageBox::information(this,tr("Connect information"), tr("连接成
功"));
}
……
智能门禁模块
PanGu 开发板有多个 GPIO 组,查看 GPIO 组信息,可以使用 gpiodetect 命令。
# gpiodetect
gpiochip0 [GPIOA] (16 lines)
gpiochip1 [GPIOB] (16 lines)
gpiochip10 [GPIOK] (16 lines)
gpiochip11 [GPIOZ] (16 lines)
gpiochip2 [GPIOC] (16 lines)
gpiochip3 [GPIOD] (16 lines)
gpiochip4 [GPIOE] (16 lines)
gpiochip5 [GPIOF] (16 lines)
gpiochip6 [GPIOG] (16 lines)
gpiochip7 [GPIOH] (16 lines)
gpiochip8 [GPIOI] (16 lines)
gpiochip9 [GPIOJ] (16 lines)
通过查扩展板的原理图可以看到 :光电开关的 GPIO 管脚是 PE15
读取 PE15 的状态
# gpioget gpiochip4 15
当有东西遮挡光电开关时,执行以上命令得到的是 0;没有东西遮挡。得
到的是 1.
所以我们开一个线程让它一直去读取光 PE15 的状态,当读到为 0 时,说明
有人,发送信号给主线程,使主线程开启登陆界面。
void ReadPE15Thread::run()
{
system("touch pe15.txt");
system("gpioget gpiochip4 15 > pe15.txt");
int fd;
char buf[32];
while (1) {
fd = open("./pe15.txt",O_RDONLY);
system("gpioget gpiochip4 15 > pe15.txt");
read(fd,buf,sizeof(buf));
if(strcmp(buf,"1\n")==0)
{
qDebug()<<tr("login !!");
emit pesig();
}
sleep(1);
close(fd);
}
}
建立信号槽连接
void MainWindow::loginSlot()
{
disconnect(&pe15thread,SIGNAL(pesig()),this,SLOT(loginSlot()));
login->show();
connect(login,SIGNAL(loginsuccess()),this,SLOT(loginsuccessSlot()));
connect(login,SIGNAL(loginfailed()),this,SLOT(loginfailedSlot()));
connect(login,SIGNAL(loginclose()),this,SLOT(logincloseSlot()));
}
登陆界面的编写
编译 UI 界面。
创建 usr.db 数据库文件
# sqlit3 usr.db
创建表
sqlite> CREATE TABLE usr(
usrname TEXT PRIMARY KEY ,
password NOT NULL
);
向数据库中添加用户名和密码
sqlite> INSERT INTO usr values(“usr”,“123”);
login.cpp
打开数据库 验证密码
bool Login::openDb()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("usr.db");
if(!db.open())
{
QMessageBox::warning(0, tr("Warning"), db.lastError().text());
return false;
}
QSqlQuery query(db);
if(!query.exec("select usrname,password from usr"))
{
db.close();
return false;
}
while(query.next())
{
QString UserName = query.value(0).toString();
QString Password = query.value(1).toString();
// qDebug()<< UserName;
// qDebug()<< Password;
if(UserName == usr_edit->text() &&Password ==password_edit->text())
return true;
}
db.close();
return false;
}
登陆成功发送成功信号,登陆失败发送失败信号
void MainWindow::loginSlot()
{
disconnect(&pe15thread,SIGNAL(pesig()),this,SLOT(loginSlot()));
login->show();
connect(login,SIGNAL(loginsuccess()),this,SLOT(loginsuccessSlot()));
connect(login,SIGNAL(loginfailed()),this,SLOT(loginfailedSlot()));
connect(login,SIGNAL(loginclose()),this,SLOT(logincloseSlot()));
}
void MainWindow::loginsuccessSlot()
{
QMessageBox::information(this, tr("information"),"密码正确,门锁已打开");
// 重置错误次数
Numberoferrors =3;
login->close();
beepunring();
connect(&pe15thread,SIGNAL(pesig()),this,SLOT(loginSlot()));
}
void MainWindow::loginfailedSlot()
{
QString info;
Numberoferrors--;
switch(Numberoferrors)
{
case 2:
info ="密码错误,还有 3 次机会";
break;
case 1:
info ="密码错误,还有 2 次机会";
break;
case 0:
info ="密码错误,还有 1 次机会";
break;
default:
info ="即将报警";
break;
}
QMessageBox::warning(this, tr("warning"),info);
if(Numberoferrors <0)
{
qDebug() << Numberoferrors;
beepring();
}
}
验证密码错误三次后蜂鸣器报警
数据采集和智能检测模块
系统启动后可以查看目录/sys/bus/iio/devices/
root@fsmp1c:~# ls /sys/bus/iio/devices/ iio:device0
如果系统中有多个 iio 设备,这里可能会有很多个 iio 目录,确定哪个
目录是我们的设备对应目录,可以通过查看
/sys/bus/iio/devices/iio\:device0/name 信息确认:
root@fsmp1c:~# cat /sys/bus/iio/devices/iio\:device0/name
0-0040
由显示信息每个驱动对应设备可能有所不同,当前显示内容为设备的物理
地址,与设备树中地址一致,可以确认 iio:device0 是当前设备对应目录查看
当目录下内容:
root@fsmp1c:~# ls -l /sys/bus/iio/devices/iio\:device0/
total 0
-r--r--r-- 1 root root 4096 Feb 7 15:51 dev
-rw-r--r-- 1 root root 4096 Feb 7 15:51 in_humidityrelative_offset
-rw-r--r-- 1 root root 4096 Feb 7 15:51 in_humidityrelative_raw
-rw-r--r-- 1 root root 4096 Feb 7 15:51 in_humidityrelative_scale
-rw-r--r-- 1 root root 4096 Feb 7 15:51 in_temp_offset
-rw-r--r-- 1 root root 4096 Feb 7 15:51 in_temp_raw
-rw-r--r-- 1 root root 4096 Feb 7 15:51 in_temp_scale
-r--r--r-- 1 root root 4096 Feb 7 15:51 name
drwxr-xr-x 2 root root 0 Feb 7 15:51 power
lrwxrwxrwx 1 root root 0 Feb 7 15:50 subsystem -> ../../../../../../../bus/iio
-rw-r--r-- 1 root root 4096 Feb 7 15:50 uevent
文件说明:
文件 in_ temp_scale 为温度标尺,计算公式如下,公式来自与驱动对应代码:
𝑆𝑐𝑎𝑙𝑒𝑡𝑒𝑚𝑝 =
175.72 × 1000 × 4
65535 = 10.725097656
in_ temp_offset 为数据偏移,计算公式如下,公式来自于驱动对应代码
𝑜𝑓𝑓𝑠𝑒𝑡𝑡𝑒𝑚𝑝 =
−46.85 × 65536
4 × 175.72 = −4368
in_ temp_raw 为原始数据,计算公式如下,公式来自于驱动对应代码:
𝑅𝑎𝑤𝑡𝑒𝑚𝑝 =
𝐶𝑜𝑑𝑒𝑡𝑒𝑚𝑝
4
阅读 SI7006 芯片手册可以看到温度的计算公式为:
𝑇𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑒(℃) =
175.72 × 𝐶𝑜𝑑𝑒𝑡𝑒𝑚𝑝
65535 − 46.85
上述公式与驱动返回值看不出直接对应关系,所以我们按照驱动提供的 scale、
offset 及 Raw 的计算公式对公式进行处理,得到最终公式计算过程如下:
𝑇𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑒(℃) × 65536 = 175.72 × 𝐶𝑜𝑑𝑒𝑡𝑒𝑚𝑝 − 46.85 × 65536
𝑇𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑒(℃) × 65536 = 175.72 × (𝐶𝑜𝑑𝑒𝑡𝑒𝑚𝑝+
−46.85 × 65536
175.72 )
𝑇𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑒(℃) =
175.72
65536 × (𝐶𝑜𝑑𝑒𝑡𝑒𝑚𝑝+
−46.85 × 65536
175.72 )
𝑇𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑒(℃) =
175.72 × 1000
65536 × (𝐶𝑜𝑑𝑒𝑡𝑒𝑚𝑝+
−46.85 × 65536
175.72 ) ÷ 1000
𝑇𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑒(℃) =
175.72 × 1000 × 4
65536 × (
𝐶𝑜𝑑𝑒𝑡𝑒𝑚𝑝
4
+
−46.85 × 65536
175.72 × 4
) ÷ 1000
最终确定温度的计算公式为:
𝑇𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑒(℃) = (𝑅𝑎𝑤𝑡𝑒𝑚𝑝 + 𝑂𝑓𝑓𝑠𝑒𝑡𝑡𝑒𝑚𝑝) × 𝑆𝑐𝑎𝑙𝑒𝑡𝑒𝑚𝑝 ÷ 1000
同理湿度的计算公式为:
𝐻𝑢𝑚𝑖𝑑𝑖𝑡𝑦(%𝑅𝐻) = (𝑅𝑎𝑤ℎ𝑢𝑚 + 𝑂𝑓𝑓𝑠𝑒𝑡ℎ𝑢𝑚) × 𝑆𝑐𝑎𝑙𝑒ℎ𝑢𝑚 ÷ 1000
主要代码如下:
开一个线程去读取设备文件的信息并计算,得到温度湿度光照,通过信号传参的方式
传给主线程。
子线程:
……
void CollentdataThread::run()
{
int temp_raw = 0;
int temp_offset = 0;
float temp_scale = 0;
int hum_raw = 0;
int hum_offset = 0;
float hum_scale = 0;
float tem_float =0;
float hum_float =0;
float ill_float =0;
QString hum;
QString tem;
QString ill;
const char *device1 ="iio:device0";//温湿度
const char *device2 ="iio:device1";//光照
while (1)
{
/*read temp data*/
read_sysfs_int(device1, "in_temp_raw", &temp_raw);
read_sysfs_int(device1, "in_temp_offset", &temp_offset);
read_sysfs_float(device1, "in_temp_scale", &temp_scale);
tem_float =(temp_raw + temp_offset) * temp_scale / 1000;
tem =QString::number(tem_float,'f', 2);
read_sysfs_int(device1, "in_humidityrelative_raw", &hum_raw);
read_sysfs_int(device1, "in_humidityrelative_offset", &hum_offset);
read_sysfs_float(device1, "in_humidityrelative_scale", &hum_scale);
hum_float = (hum_raw + hum_offset) * hum_scale / 1000;
hum =QString::number(hum_float,'f', 2);
read_sysfs_float(device2, "in_illuminance_input", &ill_float);
ill =QString::number(ill_float,'f', 2);
emit send(tem,hum,ill);
sleep(2);
}
}
int CollentdataThread::read_sysfs_float(const char *device, const char *filename, float
*val)
{
int ret = 0;
FILE *sysfsfp;
char temp[128];
memset(temp, '0', 128);
ret = sprintf(temp, "/sys/bus/iio/devices/%s/%s", device, filename);
if (ret < 0)
goto error;
sysfsfp = fopen(temp, "r");
if (!sysfsfp)
{
ret = -errno;
goto error;
}
errno = 0;
if (fscanf(sysfsfp, "%f\n", val) != 1)
{
ret = errno ? -errno : -ENODATA;
if (fclose(sysfsfp))
perror("read_sysfs_float(): Failed to close dir");
goto error;
}
if (fclose(sysfsfp))
ret = -errno;
error:
return ret;
}
int CollentdataThread::read_sysfs_int(const char *device, const char *filename, int *val)
{
int ret = 0;
FILE *sysfsfp;
char temp[128];
memset(temp, '0', 128);
ret = sprintf(temp, "/sys/bus/iio/devices/%s/%s", device, filename);
if (ret < 0)
goto error;
sysfsfp = fopen(temp, "r");
if (!sysfsfp)
{
ret = -errno;
goto error;
}
errno = 0;
if (fscanf(sysfsfp, "%d\n", val) != 1)
{
ret = errno ? -errno : -ENODATA;
if (fclose(sysfsfp))
perror("read_sysfs_float(): Failed to close dir");
goto error;
}
if (fclose(sysfsfp))
ret = -errno;
error:
return ret;
}
主线程:
信号槽连接
connect(&thread_collentdata,SIGNAL(send(QString,QString,QString)),this,SLOT(set_
humAdte
mAdill(QString,QString,QString)));
槽函数:
void MainWindow::set_humAdtemAdill(QString tem,QString hum,QString ill)
{
// 将线程采集的数据赋值给成员变量
this->tem =tem;
this->hum =hum;
this->ill =ill;
/************** 异常处理 ********/
if(abnormalSwitch == true)
{
if(this->tem.toFloat() >tem_max.toFloat())
{
system("echo 255 > /sys/class/hwmon/hwmon1/pwm1");
}
else
system("echo 0 > /sys/class/hwmon/hwmon1/pwm1");
if(this->ill.toFloat() < ill_lv1.toFloat()&&this->ill.toFloat() > ill_lv2.toFloat())
{
system("echo 1 > /sys/class/leds/led1/brightness");
}
else if(this->ill.toFloat() < ill_lv2.toFloat()&&this->ill.toFloat() > ill_lv3.toFloat())
{
system("echo 1 > /sys/class/leds/led1/brightness");
system("echo 1 > /sys/class/leds/led2/brightness");
}
else if(this->ill.toFloat() < ill_lv3.toFloat())
{
system("echo 1 > /sys/class/leds/led1/brightness");
system("echo 1 > /sys/class/leds/led2/brightness");
system("echo 1 > /sys/class/leds/led3/brightness");
}
else
{
system("echo 0 > /sys/class/leds/led1/brightness");
system("echo 0 > /sys/class/leds/led2/brightness");
system("echo 0 > /sys/class/leds/led3/brightness");
}
}
else
{
/*************************************************************************/
tem.append("℃");
hum.append("%");
ill.append("Candela");
ui->illTextBrowser->setText(ill);
ui->humTextBrowser_2->setText(hum);
ui->temTextBrowser_2->setText(tem);
}
}
//开启/关闭智能检测
void MainWindow::abn_pushbutton_ONSlot()
{
abnormalSwitch = true;
QMessageBox::information(this, tr("information"),"开启智能检测成功");
}
void MainWindow::abn_pushbutton_OFFSlot()
{
abnormalSwitch =false;
QMessageBox::information(this, tr("information"),"关闭智能检测成功");
}
其中 abnormalSwitch 这个变量是全局变量,通过两个按钮来改变这个变量的值,如果关
闭按钮点击这个值变成 false
If 语句就不会执行。直接将信息显示到 ui 界面
设备控制模块
这个模块很简单,直接上命令。
Led1 亮/灭
Led2 亮/灭
Led3 亮/灭
root@fsmp1c:~# echo 1 > /sys/class/leds/user1/brightness
root@fsmp1c:~# echo 0 > /sys/class/leds/ user1/brightness
root@fsmp1c:~# echo 1> /sys/class/leds/ user2/brightness
root@fsmp1c:~# echo 0 > /sys/class/leds/ user2/brightness
root@fsmp1c:~# echo 1 > /sys/class/leds/ user3/brightness
root@fsmp1c:~# echo 0 > /sys/class/leds/ user3/brightness
蜂鸣器响
void MainWindow::beepring()
{
int fd;
struct input_event event;
struct timeval time;
fd = open("/dev/input/by-path/platform-beeper-event", O_RDWR);
event.type = EV_SND;
event.code = SND_TONE;
event.value = 1000;
time.tv_sec = 1;
time.tv_usec = 0;
event.time = time;
write(fd, &event, sizeof(struct input_event));
}
让蜂鸣器关的话把 event.value 的值置成 0,将结构体对象再写到
/dev/input/by-path/platform-beeper-event 文件里
天气预报模块
使用 QNetworkAccessManager 类定义一个请求句柄;使用 QNetworkRequest 类
定义一个操作请求。
QNetworkAccessManager *manager;
QNetworkRequest quest;
QNetworkAccessManager 是用来协调网络操作的,即用来操作 QNetworkRequest
请求的 。QNetworkRequest 是 Network Access API 的一部分,是在网络上保
存着发送一个请求的必要信息.它包含一个 URL 和一些辅助信息,可以被用来去
修改请求。打个比方: 你想要带一些水给朋友,这个水就好比
QNetWorkRequest 你的请求,而装水的容器就是 QNetworkAccessManager.。有
了容器你才可以带走水,同理你想要发送请求就需要
QNetworkAccessManager。
这里,在 ui 界面的 comboBox 选择要查看的城市,填充到 QnetWorkRequest
里,使用 get 方法发送请求。
/*********************** 天气模块 *********************/
//点击查询请求天气数据
void MainWindow::weather_cilcked_Slot()
{
QString local_city = ui->comboBox->currentText().trimmed(); //获得需要查询
天气的城市名称
sendQuest(local_city);
}
//get 方法获取信息
void MainWindow::sendQuest(QString cityStr)
{
char quest_array[256] = "http://wthrcdn.etouch.cn/weather_mini?city=";
QNetworkRequest quest;
sprintf(quest_array, "%s%s", quest_array, cityStr.toUtf8().data());
quest.setUrl(QUrl(quest_array));
quest.setHeader(QNetworkRequest::UserAgentHeader, "RT-Thread ART");
manager->get(quest); /*发送 get 网络请求*/
}
建立信号槽连接
connect(manager, SIGNAL(finished(QNetworkReply*)), this,
SLOT(replyFinished(QNetworkReply*)));
当发送请求后,会收到网络的回复信号,只需要使用 QJsonDocument 解析 Json
类型的数据就好了。具体代码如下。
//天气数据处理槽函数
void MainWindow::replyFinished(QNetworkReply *reply)
{
QString all = reply->readAll();
QJsonParseError err;
//解析 json 对象
QJsonDocument json_recv = QJsonDocument::fromJson(all.toUtf8(), &err);
qDebug() << "recv weather data! error:"<< err.error;
if (!json_recv.isNull())
{
QJsonObject object = json_recv.object();
if (object.contains("data"))
{
QJsonValue value = object.value("data"); // 获取指定 key 对应的 value
if (value.isObject())
{
QJsonObject object_data = value.toObject();
if (object_data.contains("forecast"))
{
QJsonValue value = object_data.value("forecast");
if (value.isArray())
{
QJsonObject today_weather = value.toArray().at(0).toObject();
QString weather_type = today_weather.value("type").toString();
QString tuijian = object.value("data").toObject().value("ganmao").toString();
QString low = today_weather.value("low").toString();
QString high = today_weather.value("high").toString();
QString wendu = low.mid(low.length() - 4, 4) + "~" + high.mid(high.length()
- 4, 4);
QString strength = today_weather.value("fengli").toString();
strength.remove(0, 8);
strength.remove(strength.length() - 2, 2);
QString fengli = today_weather.value("fengxiang").toString() + strength;
ui->label_weather_2->setText(weather_type); //显示天气类型
ui->label_temperature_2->setText(wendu);
ui->label_wind_2->setText(fengli);
ui->label_recommend_2->setText(tuijian);
}
}
}
}
}
else
ui->label_recommend_2->setText( "json_recv is NULL or is not a object !");
reply->deleteLater(); //销毁请求对象
}
百度云交互模块
通过 ui 界面输入建立云端设备时生成的 ioTcoreid、DeviceKety、DeviceSecret。使用代码进行组合生成 brokerAddr、以及用户名密码,再使用
MD5 加密算法对密码进行加密得到连接需要的信息。点击计算按钮就将计算的结果复制给成员变量,便于连接时使用。
void MainWindow::pushButton_calculateSlot()
{
username.clear();
password.clear();
password_md5.clear();
brokerAddr.clear();
ioTCoreld = lineEdit_coreid->text();
deviceKey = lineEdit_devkey->text();
deviceSecret = lineEdit_devsecret->text();
brokerPort = "1883";
if(ui->comboBox_city->currentText() =="广州")
brokerAddr =
brokerAddr.append(ioTCoreld).append(".iot.").append("gz").append(".baidubce.com"
);
else
brokerAddr =
brokerAddr.append(ioTCoreld).append(".iot.").append("bj").append(".baidubce.com");
clientId = "zhjt123";
username =
username.append("thingidp").append("@").append(ioTCoreld).append("|").append(de
viceKey)\
.append("|").append(currentTimestamp).append("|").append("MD5");
password =
password.append(deviceKey).append("&").append(currentTimestamp).append("&").a
ppend("MD5")\
.append(deviceSecret);
QByteArray password_md5result;
QCryptographicHash md(QCryptographicHash::Md5);
md.addData(password.toUtf8());
password_md5result = md.result();
password_md5.append(password_md5result.toHex());
}
在 pro 文件里加入
QT += mqtt
然后在 mainwindow.h 加入头文件
#include <QtMqtt/qmqttclient.h>
使用 QmqttClient 定义 m_client 对象用于连接。
QMqttClient *m_client;//mqtt client 指针
将上一步计算好的信息设置到 m_client 对象中,使用
m_client->connectToHost();
进行连接。
void MainWindow::pushButton_connectmqttSlot()
{
//未连接服务器则连接
if (m_client->state() == QMqttClient::Disconnected) {
ui->pushButton_connectmqtt->setText(tr("断开"));
m_client->setHostname(brokerAddr);
m_client->setPort(brokerPort.toInt());
m_client->setUsername(username);
m_client->setPassword(password_md5);
m_client->connectToHost();
// 定时器初始化
InitTimer();
connect(m_client,SIGNAL(connected()),this,SLOT(mqttconnectSlot()));
connect(m_client, SIGNAL(messageReceived(const QByteArray, const
QMqttTopicName)),
this, SLOT(messageReceived(const QByteArray, const
QMqttTopicName)));
} else {//断开连接
ui->pushButton_connectmqtt->setText(tr("连接"));
m_client->disconnectFromHost();
}
}
发布信息
使用定时器设置定时时间,这里设置的是 5s,每隔 5s 会向云端指定的
topic 发布温湿度的数据。
void MainWindow::InitTimer()
{
m_timer = new QTimer;
//设置定时器是否为单次触发。默认为 false 多次触发
m_timer->setSingleShot(false);
//启动或重启定时器, 并设置定时器时间:毫秒
m_timer->start(5000);
//定时器触发信号槽
connect(m_timer,SIGNAL(timeout()),this,SLOT(TimertimeOut()));
}
void MainWindow::TimertimeOut()
{
mqttTopic ="$iot/zhjt/user/fortest";
mqttMessage.clear();
mqttMessage.append("{\"temperature").append("\"").append(":").append(tem)\
.append(",").append("\"humidity").append("\"").append(":").append(hu
m)\
.append(",").append("\"illumination").append("\"").append(":").append
(ill)\
.append("}");
//执行定时器触发时需要处理的业务
// 发布
if(m_client->publish(mqttTopic,mqttMessage.toUtf8()) == -1)
{
QMessageBox::critical(this,"Error","连接断开或输入的 topic 有误,无法
发布",QMessageBox::Yes);
m_timer->stop(); //停止定时器
}
}
订阅信息
订阅 Topic 为 $iot/zhjt/user/control
当有客户端发送 Topic 为 $iot/zhjt/user/control 的数据时,会接收到
数据并进行处理。
void MainWindow::mqttconnectSlot()
{
QString subScribeTopic ="$iot/zhjt/user/control";
m_client->subscribe(subScribeTopic);
}
void MainWindow::messageReceived(const QByteArray &message, const
QMqttTopicName &topic)
{
qDebug()<<"messageReceived:"<<topic.name()<<QString(message);
QJsonObject json_object = QJsonDocument::fromJson(message).object();
if(json_object.value("led1").toInt() ==1)
system("echo 1 > /sys/class/leds/led1/brightness");
else
system("echo 0 > /sys/class/leds/led1/brightness");
if(json_object.value("led2").toInt() ==1)
system("echo 1 > /sys/class/leds/led2/brightness");
else
system("echo 0 > /sys/class/leds/led2/brightness");
if(json_object.value("led3").toInt() ==1)
system("echo 1 > /sys/class/leds/led3/brightness");
else
system("echo 0 > /sys/class/leds/led3/brightness");
if(json_object.value("fan").toInt() ==0)
system("echo 0 > /sys/class/hwmon/hwmon1/pwm1");
else
{
char fan_on[64];
sprintf(fan_on,"echo %d >
/sys/class/hwmon/hwmon1/pwm1",json_object.value("fan").toInt());
system(fan_on);
}
if(json_object.value("beep").toInt() ==1)
beepring();
else
beepunring();
}
微信小程序设计
创建新项目
打开微信开发者工具,新建项目,填写自己的 AppID,新建。
注意:创建工程的路径一定不能有中文。
准备图片
下载几个图标,可以去阿里巴巴矢量图标库进行下载。下载好的图片放在
pages 文件夹下得 image 文件夹中。(没有可以自己新建一个文件夹)
https://www.iconfont.cn/home/index?spm=a313x.7781069.1998910419.2
修改微信小程序代码
修改 app.json 文件
下载支持 MQTT 协议和 sha1 加密的 js 库
下载 mqtt.js https://github.com/mqttjs/MQTT.js
下载 hex_hmac_sha1.js https://github.com/xihu-fm/aliyun-iotclient-sdk/tree/master/lib
将这两个文件存放到 utils 目录下
编写 index.wxml,这个文件是用来编写页面的布局。
编写 index.wxss,这个文件是用来配置页面的属性。
编写 index.js,修改设备信息三元组。这个文件用来主要逻辑的编写
修改 socket 合法域名
所有的程序编写完成之后,进入调试窗口,就会看到下面这种情况:
说明没有在微信小程序的开发管理中添加这个域名所导致的。
打开小程序开发网页:
https://mp.weixin.qq.com/wxamp/devprofile/get_profile?token=58461
2979&lang=zh_CN
打开开发管理->开发设置->服务器域名,修改 socket 合法域名,添加这个域名即可。
实验源码
源码路径【4_智慧家庭\实验源码\4_zhjt】
【4_智慧家庭\实验源码\WeChat_zhjt_pro】
注意事项
1.在开发板运行时,需要导入中文字库,否则会因为识别不了中文。
将【4_智慧家庭\工具软件\wqy-zenhei-0.9.47-nightlybuild.tar.gz 或 wqyzenhei-0.8.38-1.tar.gz】复制到 ubuntu 下。并使用 scp 命令将文件拷贝到开发板
的 usr/share/fonts 目录下,使用 tar 命令解压后即可。
linux@ubuntu:~$ scp wqy-zenhei-0.8.38-1.tar.gz
root@192.168.10.128:/usr/share/fonts/
2.如果使用 mipi 五寸屏运行此项目,需要进行屏幕旋转以适应屏幕,具体
步骤如下:
在/etc/profile.d/qt-eglfs.sh 添加环境变量如下:
下面变量的 event0 设备需要填实际的触摸屏设备
这里即填 event0
export QT_QPA_EGLFS_ROTATION=90
export QT_QPA_EGLFS_NO_LIBINPUT=1
export
QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=/dev/input/event0:rotate=90
时间显示的时候 ARM 系统的时间要和当前时间进行同步需要使用 ntp 服务。
ntpd
ntpd 是一个时间服务。采用柔性时间调整策略,让时间的变化和调整尽量减少对业务的影响。
ntpd 不盲目相信远端时钟,服务器时间和远端时钟超过恐慌阈值(默认 1000 秒),ntpd 甚至会停止时间同步。
ntpd 自己会思考。它相信本地时间可能不对,但是不会忽快忽慢甚至停滞。ntpd 通过多次收发包选择权威稳定的时间源,算出双方间的网络延迟,然后才会采信新的远端时钟进行时间同步。
ntpd 在和时间服务器的同步过程中,会把 BIOS 计时器的振荡频率偏差——或者说 Local Clock 的自然漂移 (drift) ——记录下来。这样即使网络有问题,本机仍然能维持一个相当精确的走时。
在 ubuntu 主机下载安装 ntp 服务
linux@ubuntu:~$ sudo apt-get install ntp
linux@ubuntu:~$ vi /etc/ntp.conf
将里面的文本复制下来,修改 ntpd 配置文件
root@fsmp1c:~# vi /etc/ntp.conf
将刚才复制的粘贴到这个文件下,重启 ntpd 服务
root@fsmp1c:~# systemctl restart ntpd.service
为开发板增加时区,在开发板创建文件夹
root@fsmp1c:~# mkdir /usr/share/zoneinfo
root@fsmp1c:~# mkdir /usr/share/zoneinfo/Asia
进入 ubuntu 时区目录
linux@ubuntu:~$ cd /usr/share/zoneinfo/Asia/
拷贝当前目录下的 shanghai 文件到开发板的/usr/share/zoneinfo/Asia/
linux@ubuntu:~$ scp Shanghai root@192.168.10.105:/usr/share/zoneinfo/Asia/
root@fsmp1c:~#ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime