赞
踩
首先本文是自己的智能车系统项目的第三篇文章,换句话说,本文是基于前两篇文章的一个拓展,前两篇文章连接:一:智能车上位机系统,pyqt下的socket通信,python实现服务器+客户端,文本+视频不定长字节传输,超详细,小白都能看懂_pyqt socket上位机显示波形-CSDN博客
二:PyQt5使用matplotlib画图,并嵌入qt控件中,涉及使用消息队列与共享内存来进行进程间通信或线程间通信。-CSDN博客
具体的拓展是增加了数据库来查询对应时间的速度大小,使用数据库(用户登录)限制系统的使用,以及excel表在python中和数据库交互和主题切换的相关内容。
pip install pymysql
安装完成后导入
import pymysql
- try:
- db = pymysql.connect(host="你的host地址",user=“用户名”, password=“用户密码”, database=“使用的数据库名称”)
- print('数据库连接成功!')
- #创建游标
- cur = db.cursor()
- except pymysql.Error as e:
- print('数据库连接失败'+str(e))
cur.execute('''CREATE TABLE speed_trend(sec INT,line FLOAT, ang FLOAT)''')
其中使用游标中的execute函数。speed_trend为表名,后面的sec为字段名,INT为整型。
插入数据
- sqlQuery = 'INSERT INTO speed_trend (sec,line,ang) VALUE (%s,%s,%s)'
- value = (1,0.45,0.56)
- cur.execute(sqlQuery,value)
- db.commit()
其中 VALUE (%s,%s,%s) 必须用%s做占位符。db.commit()十分重要,目的是提交至数据库,没有加它表中是不会更新的。
更新数据
- sqlQuery = "UPDATE speed_trend SET line= %s WHERE line=%s"
- value = ('0.58', '0.45')
- cur.execute(sqlQuery,value)
- db.commit()
上述将line等于0.45的替换为0.58
查找表
- sqlarry = 'SELECT * FROM speed_trend WHERE sec=6}'
- cur.execute(sqlarry)
- res=cur.fetchall()
res为查找后返回的数据,是一个二维数组,res[0][0]为sec的值,res[0][1]为line的值。查找表是不需要提交数据库,即无需db.commit()
7. 删除表
- sqlQuery = "delete from speed_trend where sec=%s"
- value = ('4')
- cur.execute(sqlQuery,value)
- db.commit()
删除sec=4的那一行。
- pip install pandas
- pip install openpyxl
将数据写入excel表
- #保存为excel表
- def ifsaveexcel(self,arg):
- if arg :
- #接收主进程数据
- sec,linear,angular = queuelist.get()
- writedata = {
- '时间s' : sec,
- '线速度m/s' : linear,
- '角速度m/s' : angular
- }
- fwrite = pd.DataFrame(writedata)
- fwrite.to_excel('laspeed.xlsx',index=False)
- self.show_data.emit(time.time(),"excel图表生成成功!文件名为laspeed.xlsx")
1. sec,linear,angular = queuelist.get() 这是接收子进程发来的数据,这个在后面会讲到。
2. fwrite = pd.DataFrame(writedata) writedata是一个字典,并将其传入pandas中的datafream函数的参数中,fwrite是一个datafream对象。
3. fwrite.to_excel('laspeed.xlsx',index=False) 接着使用to_excel函数将其保存至“laspeed.xlsx”,index=fasle,是指不使用索引。
将excel表导入数据库的代码编写
- #将excel表导入数据库
- def importexcel(self):
- if self.mysqlflag:
- if os.path.exists('laspeed.xlsx'):
- #导入前判断表中数据是否为空
- sqlsel = 'SELECT COUNT(*) FROM speed_trend'
- count = self.cursor.execute(sqlsel)
- if count > 0:
- sqldel = 'DELETE FROM speed_trend'
- self.cursor.execute(sqldel)
- self.db.commit()
- df = pd.read_excel('laspeed.xlsx')
- #执行插入操作
- sqlQuery = 'INSERT INTO speed_trend (sec,line,ang) VALUE (%s,%s,%s)'
- for k in range(1,len(df)):
- sec = df.iloc[k,0]
- linear_speed = df.iloc[k,1]
- angular_speed = df.iloc[k,2]
- value = (sec,linear_speed,angular_speed)
- self.cursor.execute(sqlQuery,value)
- self.db.commit()
- self.show_mysqldata.emit("导入成功!")
- else:
- self.show_mysqldata.emit("excel文件不存在")
- else:
- self.show_mysqldata.emit("数据库未连接")
该importexecl函数是通过信号和曹机制绑定在了按钮上。
1. os.path.exists('laspeed.xlsx')与if count > 0::这里使用了os库判断是否存在excel文件,如果存在的情况下我们会首先判断数据库表中是否为空,如果非空即count>0,我们需要将表中内容删除('DELETE FROM speed_trend'),保证每次的数据都是最新的。
2. df = pd.read_excel('laspeed.xlsx'): 这里是使用pandas库的函数read_xlsx.来读取excel文件。这里的
df
s是DataFrame对象,DataFrame是Pandas库提供的一种数据结构,类似于表格或二维数组。3. df.iloc[k,0]: iloc是Pandas库中DataFrame对象的一个属性,它允许你通过整数索引来访问DataFrame中的数据,[k,0]即访问第k行第0列的值。
接着将 秒,线速度,角速度分别存储到对应变量,最后执行sql语句,提交数据库即可完成导入将excel表数据库的操作。
登录或注册代码介绍
- #登录或者注册
- def user_login(self):
- #登录状态
- if self.loginflag:
- #查询数据库
- nameval = self.logapp.username.text()
- sqlarry = f'SELECT * FROM car_user WHERE username=\'{nameval}\''
- self.cursor.execute(sqlarry)
- res = self.cursor.fetchall()
- if not res:
- msg = QMessageBox(QMessageBox.Warning,"警告","登录失败,用户名错误或用户不存在")
- msg.exec()
- else:
- if res[0][1] == self.logapp.password.text():
- print("登录成功!")
- #登陆成功,关闭数据库连接
- self.db.close()
- self.cursor.close()
- self.form.close()
- # 并启动主窗口
- testr = client()
- testr.myapp.statusBar.showMessage(f"当前用户:{nameval}")
- testr.form.show()
- else:
- msg = QMessageBox(QMessageBox.Warning,"提示","登录失败,密码错误")
- msg.exec()
- #注册状态
- elif self.signflag:
- #将数据插入表格
- sql = 'INSERT INTO car_user (username,password) VALUE (%s,%s)'
- value = (self.logapp.username.text(),self.logapp.password.text())
- self.cursor.execute(sql,value)
- self.db.commit()
- msg = QMessageBox(QMessageBox.Information,"提示","注册成功")
- msg.exec_()
- self.logapp.namelab.setText("用户名:")
- self.logapp.paslab.setText("密码:")
- self.logapp.login.setText("登录")
- self.loginflag = True
- self.signflag = False
这里我觉得没有太多需要介绍的,之前都有涉及到。
1. sqlarry = f'SELECT * FROM car_user WHERE username=\'{nameval}\'' :注意这里使用的语法是 f‘{变量}’ ,这个语法在python中可以在字符串的引号中;访问变量,还有\'{nameval}\' 中的 \' 代表引号也被包含在字符串中,不加的话该sql语句会报错。
2.self.db.close() self.cursor.close() self.form.close() 分别是在登陆成功后关闭数据库连接,关闭游标,关闭窗口。
3. testr = client()
testr.myapp.statusBar.showMessage(f"当前用户:{nameval}")
testr.form.show() 登录窗口界面关闭时,代表登陆成功。实例化我的客户端主窗口,并在状态栏中显示当前用户。
主函数
- if __name__ == "__main__":
- app = QApplication(sys.argv)
- login = signup()
- login.form.show()
- sys.exit(app.exec_())
- app = QApplication(sys.argv) 创建一个
QApplication
对象,并将其赋值给变量app
。这样,后续的窗口和控件才能被正确地创建和管理。- login = signup()
login.form.show() 这里的指的是我登录界面的窗口显示- sys.exit(app.exec_()) 这个就比较重要的了,因为在我写程序的时候,关闭一个窗口再显示另一个窗口的时候,会出现一个段错误,使用gdb python工具调试时具体报错如下
Thread 1 "python3" received signal SIGSEGV, Segmentation fault. 0x00007fffdd4dc1a2 in QHash<unsigned int, QXcbWindowEventListener*>::findNode(unsigned int const&, unsigned int) const () from /home/qtz-robot/.local/lib/python3.8/site-packages/PyQt5/Qt/plugins/platforms/../../lib/libQt5XcbQpa.so.5这段报错好像看不出来是什么错误,但是我在gdb的信息输出中看到我在窗口关闭的时候有些进程没有退出。最后查阅了相关csdn资料后发现是由于窗口关闭时(close)Python 进程可能仍然在后台运行,因为没有明确的退出指令。(我之前的不是使用sys.exit(app.exec_())而是app.exec_())所以说问题就很明显了,
app.exec_()
是一个阻塞调用,它启动了 Qt 事件循环,该循环会一直运行直到事件循环被明确地结束(例如通过关闭窗口)但此时可能进程并未退出,而sys.exit(app.exec_())意味着你请求系统在app.exec_()
完成后退出 Python 解释器的进程。如果app.exec_()
是正常结束的(例如用户关闭了所有窗口),sys.exit()
将会被调用,并引发一个SystemExit
异常来结束进程。
在登陆成功后
- # 并启动主窗口
- testr = client()
- testr.myapp.statusBar.showMessage(f"当前用户:{nameval}")
- testr.form.show()
show()方法会显示出窗口。
最后使用gdb python 调试后输出 ,即所有进程正常退出。
- --Type <RET> for more, q to quit, c to continue without paging--
- [Inferior 1 (process 45110) exited normally]
子进程发送函数:
- def drawchart(self,arg):
- if matflag[0].value == 1:
- print("bye bye")
- #向主进程发送元组,每个为列表
- arg.put((lx,ly,ay))
'运行
主进程接收的函数:
- #保存为excel表
- def ifsaveexcel(self,arg):
- if arg :
- #接收主进程数据
- sec,linear,angular = queuelist.get()
- writedata = {
- '时间s' : sec,
- '线速度m/s' : linear,
- '角速度m/s' : angular
- }
- fwrite = pd.DataFrame(writedata)
- fwrite.to_excel('laspeed.xlsx',index=False)
- self.show_data.emit(time.time(),"excel图表生成成功!文件名为laspeed.xlsx")
drawchart函数是通过multiprocessing函数启动的一个子进程(该函数不完整,只把主要内容显示),并附带了一个参数arg类型是Queue(),这个参数是queuelist = Queue() 全局变量,想了解的可以看我的前言里面提到的第二篇博客。
- arg.put((lx,ly,ay)) 在我停止接收图表数据的时候,也就是该进程退出的时候,我通过arg也就是Queue()对象将三个列表封装成元组的形式,将其发送之主进程。
- sec,linear,angular = queuelist.get() 对于主进程接收的函数,使用该全局变量queuelist的get方法来进行接收三个列表。
这个就比较容易了,就是一个setstylesheet()方法配合Qcombox控件使用,不再说了。直接看代码。具体效果看第二个动图的结尾。
- #更换主题
- def changebackground(self,value):
- if value == 0:
- self.form.setStyleSheet("background-color: rgb(255,255,255);color: rgb(0, 0, 0);")
- elif value == 1:
- self.form.setStyleSheet("background-color: rgb(46, 52, 54);color: rgb(255, 255, 255);")
- self.myapp.line.setStyleSheet("background-color: rgb(255, 255, 255);")
- self.myapp.line_2.setStyleSheet("background-color: rgb(255, 255, 255);")
- self.myapp.line_3.setStyleSheet("background-color: rgb(255, 255, 255);")
- self.myapp.line_4.setStyleSheet("background-color: rgb(255, 255, 255);")
- self.myapp.line_5.setStyleSheet("background-color: rgb(255, 255, 255);")
- elif value == 2:
- self.form.setStyleSheet("background-color: rgb(52, 101, 164);color: rgb(241, 232, 25);")
- elif value == 3:
- self.form.setStyleSheet("background-color: rgb(117, 80, 123);color: rgb(255, 255, 255);")
'运行
信号和曹的连接:
self.myapp.comboBox.currentIndexChanged.connect(lambda state: self.changebackground(state))
到此,经过三篇博客的编写,我的项目也总算完成了,本项目涉及python,opencv,pyqt,mysql数据库,ros,tcp(socket)网络通信,多线程,多进程及通信,matplotlib画图,excel表等,算是比较综合的项目了。为期差不多两周多一点的时间(比赛都没做来搞这哈哈),觉得自己确实学到了好多东西,同时也是第一次将自己的内容分享到CSDN上。这也是我在python和pyqt上的一个较好的开端了,我会继续努力的,加油啊,老弟!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。