文章目录
- 前言
- 一、实现效果
- 二、程序设计
- 1. 界面背景图设计
- 2. 信号槽设计
- 3. 定时器设计
- 4. 动态曲/折线图的设计
- 5. 摄像头扫码
- 6. 注册设计
- 7. 登录设计
- 8. 巡检人员设计
- 三、综合分析
前言
本篇源于 “ 2022 湖南省大学生物联网应用创新设计竞赛技能赛参考样题 ”
——应用物联网的共享电动自行车
针对共享电动自行车应用场景,设计实现共享电动自行车、用户、管理等相关人、物互联的物联网系统。假设系统由电动自行车、后端服务器、前端应用终端、以及电动自行车专用巡检装置组成,各部分功能作用如下:
一、电动自行车:
1)每台电动自行车有一个唯一的 ID 号,以及由 GPS 采集的位置信息(比赛中,GPS 及地图信息可用人工输入固定位置编号代替);
2)电动自行车电池可换,每个电池上有唯一 RFID 信息,可被电动自行车读取;
3)电动自行车驱动部分有一个总开关,能够被远程控制其开关,以允许电动自行车被合法租用人驾驶;
4)电动自行车由直流电机驱动,能被控制不同速度转动(竞赛条件下可用任何小型直流电机代替),设有控制直流电机转速的操控开关、以及测量直流电机实际转速的装置;
5)电动自行车驾驶时设有左、右转向指示,当转向时对应转向指示灯开始闪烁(亮 0.7 秒、灭 0.3 秒,循环);
6)电动自行车设有一个夜间自动照明灯,当环境亮度低于所设定阈值时,照明灯自动打开;
7)电动自行车设有 1 个喇叭(可蜂鸣器代),行车需要时可提示路上行人、或当检测到故障时报警;
8)电动自行车的电池状态可被检测(比赛场景简化为检测以下数据:电压 V1、电压 V2、温度T1、温度 T2),设电池主要数据为:电池电压 V1、充放电电流 I = | V1 -V2| / R (R 为固定的电流测量采样电阻阻值)、电池工作温升 T=T2-T1;
9)电动自行车上能够显示其重要相关信息和数据;
10)电动自行车具备与远程服务器通信,将数据传输到后台服务器、或被查询、或被控制;
11)挑战性功能 1:当断电(如更换电池)时,一些设置的与电动车相关个性信息应该保留,复电后仍然有效。
12)挑战性功能 2:断网(如骑行到无网络场地)期间,电动自行车记录的信息在网络连通后,能够不丢失地将全部数据传送至后端服务器。
二、后端服务器:
1)构建网络后端服务器,收发、管理、控制多个电动自行车数据(竞赛场景管理不少于 2 台电动自行车);
2)按应用场景和题目要求,对后台数据计算、处理、运用;
3)按应用场景和题目要求,为应用前端(移动用户和计算机网络用户)提供服务。
三、前端应用:
网络终端(如计算机)或移动终端(如智能手机 App)针对应用场景和题目要求的相关应用功能,如信息显示、或远程控制、或预警、或统计等。
四、巡检装置:
电动自行车维护维修的专用移动装置。当巡检人员携带该巡检装置到达电动自行车附近时,可无线方式与电动自行车交互,方便维护维修人员快速了解电动自行车状况。搭建、设计、完成该物联网系统,实现共享电动自行车、用户、管理人员之间的互联互通,实现电动自行车数据采集、控制、传输、存储、查询和显示的全过程,用户能够更加方便地使用共享电动自行车,管理人员也能更加清晰透明地了解电动自行车实时和历史状态。
一、实现效果
1. 模式选择
2. 普通用户
3. 巡检人员
…
…
二、程序设计
本篇中信号与槽用之最多,且大多控件都是直接在ui界面设置的,这样方便在比赛中修改,所以我在这只对主要部分进行简述,如需详情请留言评论区。
1. 界面背景图设计
界面背景图一般放置构造函数中,且在ui->setupUi(this);之后
QPixmap pixmap = QPixmap(":/picture/pc10.png").scaled(this->size()); //设置背景图片
QPalette palette (this->palette());
palette .setBrush(QPalette::Background, QBrush(pixmap));
this-> setPalette( palette );
2. 信号槽设计
这里我介绍俩种常见的信号槽设计:
(1) 在ui界面中对要设置的信号控件右击
这样,系统就会自动给你创建出信号槽,你只用在该槽内设置操作数即可。
(2) 用connect函数中进行编写信号槽 如:
connect(ui->pushButton,&QPushButton::clicked,[=](){
this->hide();
Widget *w=new Widget;
w->show();
});
上述操作为,在该文件的ui界面 点击名为pushButton的控件(&QPushButton::clicked为文件QPushbutton自带的点击函数),如何触发信号执行={ };里面的操作。
3. 定时器设计
定时器的概念就不用我多说了把,毕竟都是学这行的,顾名思义,定时器就是用来定时的,直接上操作代码,简单粗暴且易懂。
timerDrawLine = new QTimer();
timerDrawLine->start(500);
connect(timerDrawLine,&QTimer::timeout,[=](){
DrawLine();
});
}
用计时器之前记得定义头文件#include 哦,简单来说 这几句代码的意思是创建一个名为timerDrawLine 的定时器,设置定时时间位500ms(注意单位是ms <1s=1000ms>),connect函数上面刚介绍完,&QTimer::timeout的意思是到设置时间了,就立即执行={ }里面些操作代码。
4. 动态曲/折线图的设计
在这里我引入随机数,通过不断产生的随机数再结合定时器动态画出曲/折线图,定时器中操作为:
timerDrawLine = new QTimer();
timerDrawLine->start(500);
connect(timerDrawLine,&QTimer::timeout,[=](){
DrawLine();
});
在pro文件中记得加上 QT += charts
不废话,曲/折线程序设计为:
#include <QtCharts/QChartView>
#include <QtCharts/QSplineSeries>
#include <QtCharts/QLineSeries>
#include <QValueAxis>
#include <QTimer>
QT_CHARTS_USE_NAMESPACE
#define MAX_X 10
#define MAX_Y 100
void lineseries::Chart_Init()
{
//初始化QChart的实例
chart = new QChart();
//初始化QSplineSeries的实例
lineSeries = new QSplineSeries();
//lineSeries = new QLineSeries();
//设置曲线的名称
lineSeries->setName("随机数测试曲线");
//把曲线添加到QChart的实例chart中
chart->addSeries(lineSeries);
//声明并初始化X轴、两个Y轴
QValueAxis *axisX = new QValueAxis();
QValueAxis *axisY = new QValueAxis();
//设置坐标轴显示的范围
axisX->setMin(0);
axisX->setMax(MAX_X);
axisY->setMin(0);
axisY->setMax(MAX_Y);
//设置坐标轴上的格点
axisX->setTickCount(11);
axisY->setTickCount(11);
//设置坐标轴显示的名称
QFont font("Microsoft YaHei",8,QFont::Normal);//微软雅黑。字体大小8
axisX->setTitleFont(font);
axisY->setTitleFont(font);
axisX->setTitleText("X-Test");
axisY->setTitleText("Y-Test");
//设置网格不显示
axisY->setGridLineVisible(false);
//下方:Qt::AlignBottom,左边:Qt::AlignLeft
//右边:Qt::AlignRight,上方:Qt::AlignTop
chart->addAxis(axisX, Qt::AlignBottom);
chart->addAxis(axisY, Qt::AlignLeft);
//把曲线关联到坐标轴
lineSeries->attachAxis(axisX);
lineSeries->attachAxis(axisY);
//把chart显示到窗口上
ui->graphicsView1->setChart(chart);
ui->graphicsView1->setRenderHint(QPainter::Antialiasing); // 设置渲染:抗锯齿,如果不设置那么曲线就显得不平滑
}
void lineseries::DrawLine()
{
static int count = 0;
if(count > MAX_X)
{
//当曲线上最早的点超出X轴的范围时,剔除最早的点,
lineSeries->removePoints(0,lineSeries->count() - MAX_X);
// 更新X轴的范围
chart->axisX()->setMin(count - MAX_X);
chart->axisX()->setMax(count);
//当折线上最早的点超出X轴的范围时,剔除最早的点,
lineSeries1->removePoints(0,lineSeries1->count() - MAX_X);
// 更新X轴的范围
chart1->axisX()->setMin(count - MAX_X);
chart1->axisX()->setMax(count);
}
//增加新的点到曲线末端
int ht=rand()%94+2;
lineSeries->append(count, ht);//随机生成2到95的随机数
lineSeries1->append(count, ht);
count ++;
}
void lineseries::Chart_Init1()
{
//初始化QChart的实例
chart1 = new QChart();
//初始化QSplineSeries的实例
lineSeries1 = new QLineSeries();
//设置曲线的名称
lineSeries1->setName("随机数测试折线");
//把折线添加到QChart的实例chart中
chart1->addSeries(lineSeries1);
//声明并初始化X轴、两个Y轴
QValueAxis *axisX = new QValueAxis();
QValueAxis *axisY = new QValueAxis();
//设置坐标轴显示的范围
axisX->setMin(0);
axisX->setMax(MAX_X);
axisY->setMin(0);
axisY->setMax(MAX_Y);
//设置坐标轴上的格点
axisX->setTickCount(11);
axisY->setTickCount(11);
//设置坐标轴显示的名称
QFont font("Microsoft YaHei",8,QFont::Normal);//微软雅黑。字体大小8
axisX->setTitleFont(font);
axisY->setTitleFont(font);
axisX->setTitleText("X-Test");
axisY->setTitleText("Y-Test");
//设置网格不显示
axisY->setGridLineVisible(false);
//下方:Qt::AlignBottom,左边:Qt::AlignLeft
//右边:Qt::AlignRight,上方:Qt::AlignTop
chart1->addAxis(axisX, Qt::AlignBottom);
chart1->addAxis(axisY, Qt::AlignLeft);
//把折线关联到坐标轴
lineSeries1->attachAxis(axisX);
lineSeries1->attachAxis(axisY);
//把chart显示到窗口上
ui->graphicsView2->setChart(chart1);
ui->graphicsView2->setRenderHint(QPainter::Antialiasing); // 设置渲染:抗锯齿,如果不设置那么折线就显得不平滑
}
5. 摄像头扫码
此功能用于开发的app在移动端模拟扫描自行车上的二维码,将捕获到的数据发送到服务器,服务器获取该自行车的相关信息,进行判断自行车是否满足解锁条件,再将数据返回到移动端。
开启摄像须在pro文件中添加
QT += multimedia multimediawidgets
其中Multimedia模块提供了丰富的接口,可以轻松地使用平台的多媒体功能。例如进行媒体播放、使用相机和收音机等;multimediawidgets可以实现点击播放按钮,可以播放视频;点击暂停按钮,可以停止播放视频;拉动进度条,可以定位视频播放位置。
首先,定义相关类名,这里为考虑读者通俗易懂,我起类名为“saoma”进行演示,在头文件里定义好相关对象 如下:
QCamera *camera;
QCameraViewfinder *viewfinder;
QCameraImageCapture *imageCapture;
QImage image;
然后在构造函数中操作摄像头,摄像头会自动获取设备资源,若无摄像头功能,会自动报错 具体操作如下:
//摄像头信息获取
QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
foreach (const QCameraInfo &cameraInfo, cameras) {
camera = new QCamera(cameraInfo);
qDebug()<<cameraInfo.deviceName();
}
//设置摄像头捕获模式
camera->setCaptureMode(QCamera::CaptureStillImage);
//图像回显
viewfinder = new QCameraViewfinder(this);
camera->setViewfinder(viewfinder);
this->setCentralWidget(viewfinder);
//QCameraImageCapture 是获取摄像头捕捉的图片 相关类
imageCapture = new QCameraImageCapture(camera);
//启动摄像头
this->camera->start();
运行如下:
6. 注册设计
在登录、注册操作中,较难设计的是判断这个用户的是否达到注册要求的标准,如用户名不得含有“@#*&”类似的特殊字符,俩次密码需要输入相同,手机号需要11位;登陆时检测这个用户是否已注册,账号密码是否存在,忘记密码时需要点击找回密码且在找回密码的界面里手机号不得输入错误等等。
(1) 创建用户类user,引入vector容器用于存储和判断
#include "user.h"
#include <QString>
#include <QDebug>
User::User()
{
}
QList<User *> User::userlist;
User::User(QString userName,QString userPwd,QString userNumber)
{
this->userName = userName;
this->userPwd = userPwd;
this->userNumber = userNumber;
this->userState =0;//0为不在线
}
User::User(QString userName, QString userPwd, QString userNumber, int userState)
{
this->userName = userName;
this->userPwd = userPwd;
this->userNumber = userNumber;
this->userState =userState;//1为在线
}
//用户名
QString User::GetUserName()
{
return userName;
}
void User::SetUserName(QString userName)
{
this->userName = userName;
}
//密码
QString User::GetUserPwd()
{
return userPwd;
}
void User::SetUserPwd(QString userPwd)
{
this->userPwd = userPwd;
}
//手机号
QString User::GetUserNumber()
{
return userNumber;
}
void User::SetUserNumber(QString userNumber)
{
this->userNumber = userNumber;
}
//用户状态
int User::GetUserState()
{
return userState;
}
void User::SetUserState(int userState)
{
this->userState = userState;
}
void User::print()
{
for (int i=0;i<userlist.size();i++) {
qDebug()<<userlist.at(i);
}
}
(2) 输入判断
if(ui->userEdit->text()=="")//名字判断
{
//弹窗(是否指定窗口,标题,内容)
QMessageBox::information(NULL,"错误","用户名为空");
}
else if(ui->pwdEdit->text()=="")//密码判断
{
QMessageBox::information(NULL,"错误","密码为空");
}
else if(ui->pwdEdit->text().length()<6)//密码长度判断
{
QMessageBox::information(NULL,"错误","密码长度有误");
}
else if(ui->nextpwdEdit->text()=="")
{
QMessageBox::information(NULL,"错误","重新输入密码为空");
}
else if(ui->nextpwdEdit->text().length()<6)//重新输入密码长度判断
{
QMessageBox::information(NULL,"错误","重新输入密码长度有误");
}
else if(ui->numberEdit->text()=="")
{
QMessageBox::information(NULL,"错误","密码为空");
}
else if(ui->numberEdit->text().length()<11)//判断电话号码长度
{
QMessageBox::information(NULL,"错误","电话号码长度有误");
}
else if(ui->nextpwdEdit->text()!=ui->pwdEdit->text())
{
QMessageBox::information(NULL,"错误","密码和重新输入密码不一致");
}
else
{
int i=0;//判断次数
for(i=0;i<User::userlist.size();i++)
{
if(ui->userEdit->text()==User::userlist.at(i)->GetUserName())//名字是否已被注册判断
{
QMessageBox::information(NULL,"错误","名字已被注册");
break;
}
else if(ui->numberEdit->text()==User::userlist.at(i)->GetUserNumber())//电话号码是否已被注册判断
{
QMessageBox::information(NULL,"错误","电话号码已被注册");
break;
}
}
if(i>=User::userlist.size())
{
User *newuser = new User(ui->userEdit->text(),ui->pwdEdit->text(),ui->numberEdit->text());
User::userlist.push_back(newuser);
QMessageBox::information(NULL,"成功","注册成功");
this->hide(); //关闭注册界面窗口
}
}
void Widget::regSlot(QString name,QString pwd,QString number)
{
User *newuser = new User(name,pwd,number);
User::userlist.push_back(newuser);
}
7. 登录设计
在登录界面需要输入已注册好的账号与密码,若忘记密码需要点击界面下方的”找回密码“,按照设计要求一步步操作,输入正确的手机号,获取验证码,输入新密码等。
在界面登录上方我设计了一个“输出已存储的用户”按钮,实则是给程序员设计的,这样方便查看已注册的用户信息;其次 界面还添加了“自动登录”和“记住密码”功能,因为我其实是根据“腾讯QQ”的样式进行设计的(haha)。
(1) 判断用户输入是否有误
void Widget::on_btn_denglu_clicked()
{
int flag=0;
for (int i=0;i<User::userlist.size();i++) {
if(ui->yonghuming->text()==User::userlist.at(i)->GetUserName()){
flag=1;
if(ui->mima->text()==User::userlist.at(i)->GetUserPwd()){
QMessageBox::information(NULL,"success","登录成功");
}
else {
QMessageBox::information(NULL,"error","密码错误");
}
}
}
if(flag==0) QMessageBox::information(NULL,"error","用户不存在");
}
(2) 二维码设计
点击右下方的二维码可弹出一个二维码对话框,支持扫码登录。
8. 巡检人员设计
巡检人员相当于管理员,权限自然大些,所以在这里我就没设计巡检人员的注册功能,直接在程序中给定了巡检人员的账号密码,正确输入即可成功登录,登录成功后可查看硬件“自行车”的相关信息,即自行车电压、电流、温湿度、气压、速度等等,详情可在上方界面中查看,这些每个控件我都有给其设置专有的信号槽,这样方便与服务器那边交接(按提要求可知,最终开发出来的app是要和服务器交互的,不可能自己跟自己玩是吧)。
在这里我用的是上方介绍的第一种信号槽方式设计:
void root_set::on_ton1_clicked()
{
qDebug()<<"点击了蓝牙选择框";
}
void root_set::on_ton2_clicked()
{
qDebug()<<"点击了扫描";
}
void root_set::on_ton3_clicked()
{
qDebug()<<"点击了断开";
}
void root_set::on_ton4_clicked()
{
qDebug()<<"点击了发送";
}
void root_set::on_ton5_clicked()
{
qDebug()<<"点击了获取";
}
void root_set::on_ton6_clicked()
{
qDebug()<<"点击了清空";
}
void root_set::on_ton7_clicked()
{
qDebug()<<"点击了设定";
}
void root_set::on_ton8_clicked()
{
qDebug()<<"点击了开控制";
}
void root_set::on_ton9_clicked()
{
qDebug()<<"点击了关控制";
}
void root_set::on_ton10_clicked()
{
qDebug()<<"点击了图表";
lineseries *line=new lineseries;
line->show();
}
void root_set::on_ton11_clicked()
{
qDebug()<<"点击了时间";
}
void root_set::on_ton12_clicked()
{
qDebug()<<"点击了转速";
}
void root_set::on_ton13_clicked()
{
qDebug()<<"点击了电压";
}
void root_set::on_ton14_clicked()
{
qDebug()<<"点击了电流";
}
void root_set::on_ton15_clicked()
{
qDebug()<<"点击了所有";
}
void root_set::on_ton16_clicked()
{
qDebug()<<"点击了查询";
}
void root_set::on_toolButton_clicked()
{
this->hide();
action * ac=new action;
ac->show();
}
三、综合分析
在此项目中,我几乎所有控件都在ui界面中设计的,(在平时操作中 我不是不推荐在ui界面操作的),这样确实省了不少事,但不利于实际的项目开发,项目文件如下:
需要Qt相关资料或项目源码的可以留言评论区或直接私信我 Respect!