简易的停车收费系统

前言:

用了好几天的时间写完这个项目,主要是对QT不熟,走了很多弯路,导致百度的时间花去很多,真是烦🤢。好了,做完了就不再抱怨了。我自认为自己做的还行,哈哈,吹牛的👨。
客服端和服务器都是在Ubuntu里完成的,其中客服端在Ubuntu自带的QT5.4环境里完成,服务器端由Ubuntu中下载的codeblocks里完成的。注意编译环境!!!!!!
温馨提示:本文几乎没有代码,想要源码的可以点击链接下载,当然是要积分的 链接在这里(这什么审核速度啊,比我还拖,都1天了,才通过。。)毕竟这是努力成果。虽然在大佬看来没啥牛逼的地方,但大佬们可以瞧瞧渣渣是怎么思考问题的,哈哈😂。

接下来,我说一下怎么实现的这个项目,分为项目简介,项目理解,服务器端,客服端。

项目简介

设计一个完整的停车收费系统,能实时监控当前区域,对过往车辆实现收费,车辆消费记录要保存。

项目理解

个人觉得这样做:
服务器与客服端约定好通信方式,所以可以在客服端发送不同的指令,让服务器实现不同响应。比如:
约定客服端与服务器端的指令如下:命令开始码(0x08) 指令码 结束码(0x03)
打开监控: 0x08,0x01,0x03

关闭监控: 0x08,0x02,0x03

停止计费: 0x08,0x03,0x03

断开连接: 0x08,0x04,0x03
(以上都是写死的指令,自己看源码的时候注意就行了)
tips:
1.硬件摄像头:使用电脑自带的摄像头,由运行在此摄像头的电脑上的服务器端直接控制。通过服务器端的摄像头采集一帧帧图片,通过约定的IP和端口传给客服端,客服端再显示出来。

2.保存消费记录:这个我是用文件读写来实现的,放在服务器上进行。
看着简单吧,实际上也没太复杂,但是边百度边写代码就太慢了。

服务器端

由纯C写成,运行在一个性能良好的电脑上,初始化过后,开辟线程1。线程一用于进行视频的采集,并将数据存放在全局缓冲队列中,同时开辟线程2,线程二用于监听客服端(最多两个),收到客服端连接请求后,再次为客服端开辟线程3来通信(命令传输线程)。根据不同指令响应对应的操作,也就是按照约定的指令办事!
预览图:黑漆漆的,没啥好看的。。。
在这里插入图片描述

服务器主线程只用于接收输入(是拥有服务器的用户输入),进行车辆信息生成器操作,对输入的车辆信息进行保存。换句话,也就是对充钱了的车在文件里更新余额。

tips:
服务器开机运行后:

在主线程中的操作: 开辟全局内存,用于线程之间的通信,开辟线程1,对摄像头操作,接着同时开辟线程2进入TCP链接的监听状态,收到客服端的链接请求后,开辟线程3,用于与客服端的通信。最多同时和两个客服端建立连接–>最多两个通信线程!!!!(实际上没试过两个客户端,,,😂)

在子线程1中的操作: 子线程1用于响应主线程对摄像头的操作,子线程1不停地采集图片,并将图片缓存在全局图片缓存中。子线程1在没有收到主线程的停止命令之前,将一直采集图片,收到停止命令之后,停止采集,并释放摄像头的资源。(实际上,主线程不会关闭摄像头,不过功能是完整的)
在子线程2中的操作: 子线程2用于管理客服端的连接,对请求连接的客服端开辟新的线程,当客服端连接断开后,释放其占有的资源,当客户端个数到2时,不会响应后续的其他客服端的连接请求。IP为系统随机指定(要ifconfig查询才知道),默认的监听端口为8080。

子线程3: 用于响应客服端的指令:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
①收到客服端的 打开监控(== 0x08,0x01,0x03==) 指令,开辟子视频传输线程,开始进行视频流(1张/秒)的传输。注意,这里的视频传输线程采用了不同的端口来与客户端连接,默认8081,方便区分连接的种类
在子线程3中的操作: 开辟视频传输线程
在视频传输线程中的操作: 建立与客服端的TCP链接,向客服端返回全局图片缓存中的图片。

②收到客服端的 关闭监控(== 0x08,0x02,0x03==) 指令,停止图片的传输。
在子线程3中的操作: 结束视频传输线程,然后向客服端返回 视频传输结束的信号
视频传输线程的操作: 等待被子线程3终结。

③收到客服端的 停止计费(== 0x08,0x03,0x03==) 指令,由客服端提交的车辆信息(就在指令的后面,也就是偏移3个字节之后就是车辆信息),在服务器端进行扣费,并返回余额信息。
在子线程3中的操作: 查找文件,找到车辆信息,得到车辆余额,进行扣费,然后保存文件,再将扣费之后的车辆信息返回给客服端。
当然如果在文件中没找到当前的车辆信息,则返回给客服端没找到的原因。比如说:保存车辆信息的文件打不开,余额不足,账号不存在。

④收到客服端的 断开连接(== 0x08,0x04,0x03==) 指令,断开与当前客服端的连接。
在子线程2中的操作::结束子线程3:,释放子线程3所占用的资源,也就是内存和端口,以及视频传输线程。
在子线程3中的操作: 等待被子线程2终结。
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

客服端

先放一下预览图:
预览图
请忽略中间的按钮。。。。。

在QT客服端:基于QT的图形化程序,运行在一般的PC上,受百度API的免费额度的限制,最多同时运行两个相同的客服端来查询车牌只是同时查询的额度,但是每天每个个人开发者只有200次的总查询额度

///
点击不同的功能按钮:
刚打开的时候:只有连接服务器按钮可点击!输入约定的IP和端口之后,点击连接,就会连接服务器,端口不输入的话,默认8080(说的好像废话,,)

在下方就是断开连接按钮,点击的话。。。。

在 UI主线程 中的操作: 创建子线程1,时刻响应按钮的回调,响应子线程1的回调,同时也负责刷新UI界面,切记不可以在主线程中直接进行网络访问!!!因为容易造成ANR,对子线程1的信号的响应:利用预先定义的槽函数接收到子线程1的回调之后,将信号中的图片内容取出来,并将其显示在指定区域,即刷新UI界面。 以下将不对UI主线程说明。

在子线程1中的操作: 向指定服务器发送TCP连接请求,同时建立连接。于是此线程实际上就是指令传输线程,对接收到的指令进行判别,执行不同的操作。一句话,它一方面接收UI主线程对服务器的指令,一方面也接收服务器返回的指令响应,另一方面也负责对开辟的子线程的回调处理。是不折不扣的通信线程!

②**打开监控:**功能概述:向服务器发送 打开监控 指令,同时开辟线程,分配内存并接收图片流(在子线程中)。接收到的图片将连续显示在 视频显示 区域。

==在子线程1中的操作:==收到UI的按钮消息之后,向服务器发送打开监控的指令,等待服务器的视频传输线程就绪,然后启动子线程2,连接约定好的端口(默认8081),,进行图片流的接收。

==在子线程2中的操作:==向服务器发送TCP连接请求,同时建立连接。分配适当的内存开始接收图片,每接收一张完整的图片就使用自定义的回调函数向子线程1的槽里发送信号。

关闭监控:功能概述:。。。
在子线程1中的操作: 线程1通过按钮的信号识别到是 关闭监控 后,向服务器发送关闭监控指令,等待等待服务器的视频传输线程终结信号,然后终结
子线程2

在子线程2中的操作: 子线程2收到被终结信号后,释放所占用的资源(包括端口号),清理自身内存。

④**车辆截图:**功能概述:获取当前图片帧,保存为新图片,打开文件选择对话框,得到图片的路径。启动线程3,在线程3中进行Base64编码,并调用百度API上传图片,等待百度服务器的返回报文,然后解析报文,提取车牌信息并放入发射信号到子线程1的槽里。子线程1同步在UI线程里的车辆信息一览表。

在线程中1的操作: 线程1通过按钮的信号识别到是 车辆截图 后,获取当前图片帧,保存为新图片,然后开辟子线程3,发射带图片路径的信号到线程3,随后刷新UI界面,准备接收子线程的回调信号和用户的UI线程操作。接收到子线程3的回调信号后,提取信号中车牌信息,然后同步在UI上。

在子线程3中的操作:子线程3创建之后,得到图片路径,以路经创建一个图片对象,接着对图片进行Base64编码,并调用百度API上传图片,等待百度服务器的返回报文,然后解析报文,提取车牌信息并写入在要发射到线程1的信号里传递信息。由线程1刷新当前保存的车辆信息并通知UI线程显示。

开始计费: 功能概述:获取当前系统时间T0,获取当前收费价格P。
在UI主线程中的操作 : UI主线程通过按钮的信号识别到是 开始计费 后,得到停车价格栏的价格并以信号的方式发送给线程1。

==在线程中1的操作:==读取并记录当前系统时间T0,同时得到停车价格。

停止计费: 功能概述:获取当前系统时间T1,由收费价格和总时长计算此次停车费用,并上传到服务器。接收当前车辆的信息,并在车辆信息一览表里刷新。
==在线程中1的操作:==线程1通过信号识别到是 停止计费 后,读取并记录当前系统时间T1,将前后时间相减,得到停车费用为 P*(T1-T0) 。然后将此次扣费信息上传至服务器。待接收到服务器的返回消息后,提取车辆信息(当然包括服务器的出错消息),然后将信息刷新在UI线程里。

退出客户端: 功能概述:向服务器发送断开连接指令,然后释放线程1,清理自身的内存并退出。
在UI主线程中的操作: 主线程通过按钮的信号识别到是 断开连接 后,向子线程1发送断开连接的信号。然后等待子线程1回调。接收到子线程1的回调后,释放线程1。然后exit(0)退出程序。

在子线程1中的操作: 收到主线程的终结信号后,向服务器发送断开连接指令,然后向主线程发送结束信号,清理自身的内存和占用的资源(包括端口号)。
///

总结

看一下实际运行:
实际效果

零.客服端和服务器端还有不少问题的,只是时间紧迫,不能再弄了,不过按照步骤来操作的话,大体上能运行,传输监控的图片很卡顿,也能正常识别车牌,服务器也能记录消费信息,但是有时候服务器端不知什么原因会卡掉。或者客服端也会直接卡掉!!!!!如果有大佬解决了,可以麻烦发在评论里

一.服务器端的开发纯粹用C,几乎难度不大,就是偶尔指针的乱用。

二.QT客户端的开发难度对没用过QT的新手有点大,主要问题如下:

1.线程的使用问题:QT中的线程和 C的线程 和 java的线程都有很大的区别,让我体会到线程的退出问题的困难,QT线程创建容易,但安全释放难度大,不仅仅是简单的 quit() 和 wait() 就好了的。为此,我总结了一条规律:谁创建线程,谁就管理线程。在子线程里不对自身的退出问题考虑,而交由父线程管理,这样可方便的利用信号与槽的机制来执行任务。

2.在新开辟的子线程里使用QNetworkAccessManager这个类的对象来进行百度API的调用时,由于要刷新 token值 ,发送指定报文后,总是收不到finish()信号,导致不能回调槽函数,所以调用一直失败,百度了很多文章,下面为这些文章的结论:QNetworkAccessManager为异步方式访问,而且需要依赖 创建的新的槽 才能完成信号的正常发送,否则信号队列一边堵塞,而槽这里又收不到信号,相当于直接失效, 所以在 post() 数据之后,会直接运行后面的代码,这将导致这个子线程可能已经运行完之后的代码任务了,然而回调还是没出现,这将导致一些逻辑顺序的错误:以调用百度API为例:首先每次调用API进行图片识别之前,应该先更新 token值,然后上传编码后图片。但实际情况总是先把图片上传了,然后token值才更新,一直出现110(无效的token)的错误。
说一下解决方法:

void baiduRunnable::renewal_token()
{
    QEventLoop loop;//自定义事件循环
 
    manager_token = new QNetworkAccessManager(this);


    QString url_token="https://aip.baidubce.com/oauth/2.0/token";

    QString req_url = url_token+"?grant_type=client_credentials&client_id="+AK+"&client_secret="+SK;

    qDebug()<<"url_token:"<<req_url<<endl;

    QUrl url(req_url);

    QNetworkRequest request(url);

    QByteArray data="";
    reply=manager_token->post(request,data);
    connect(reply,SIGNAL(readyRead()),this,SLOT(replyToken()));
    connect(manager_token, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));//绑定退出信号

    loop.exec();//进入事件循环

    delete reply;
    delete manager_token;

}

以上有注释的三条代码就解决了这个问题,创建了一个消息循环,保证一定在收到finish 信号之后才退出消息循环,执行之后的代码。

3.QT的UI编辑器比较难用或者说我还没用掌握精髓,相比AS来说感觉更难操作(或许换句话对AS使用的久),比如到现在我也不知道怎么设置占空比,怎么设置网格布局的行列数,怎么设置窗口的自由拉伸等等…

三.
时间过得好快,导致这几天都没复习,有点担心啊,都6月快下旬了,大家应该一轮复习完成了吧,我也要追上你们,啊啊啊啊啊啊。

加油,奥利给,干就完了!!!!!! 0803,爱启航,宇哥助我!

在这里插入图片描述

  • 8
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是一个简单的停车收费 Python 代码示例: ```python class ParkingLot: def __init__(self, spots): self.total_spots = spots self.available_spots = spots self.parking_slots = {} def park(self, reg_number): if self.available_spots == 0: print("Sorry, parking lot is full.") return False else: slot = self.total_spots - self.available_spots + 1 self.available_spots -= 1 print(f"Allocated slot number: {slot}") self.parking_slots[slot] = reg_number return True def leave(self, slot): if slot not in self.parking_slots: print("Invalid slot number.") return False else: self.available_spots += 1 del self.parking_slots[slot] print(f"Slot number {slot} is free.") return True def status(self): print("Slot No.\tRegistration No.") for slot, reg_num in self.parking_slots.items(): print(f"{slot}\t\t{reg_num}") def get_reg_numbers_for_color(self, color): reg_numbers = [] for slot, reg_num in self.parking_slots.items(): if self.parking_slots[slot] == color: reg_numbers.append(reg_num) if reg_numbers: print(", ".join(reg_numbers)) else: print("Not found.") def get_slot_numbers_for_color(self, color): slot_numbers = [] for slot, reg_num in self.parking_slots.items(): if self.parking_slots[slot] == color: slot_numbers.append(str(slot)) if slot_numbers: print(", ".join(slot_numbers)) else: print("Not found.") def get_slot_number_for_reg_number(self, reg_number): for slot, num in self.parking_slots.items(): if self.parking_slots[slot] == reg_number: print(f"{slot}") return print("Not found.") def main(): capacity = input("Enter parking lot capacity: ") parking_lot = ParkingLot(int(capacity)) while True: command = input("Enter command: ") inputs = command.split() if inputs[0] == "park": parking_lot.park(inputs[1]) elif inputs[0] == "leave": parking_lot.leave(int(inputs[1])) elif inputs[0] == "status": parking_lot.status() elif inputs[0] == "registration_numbers_for_cars_with_colour": parking_lot.get_reg_numbers_for_color(inputs[1]) elif inputs[0] == "slot_numbers_for_cars_with_colour": parking_lot.get_slot_numbers_for_color(inputs[1]) elif inputs[0] == "slot_number_for_registration_number": parking_lot.get_slot_number_for_reg_number(inputs[1]) elif inputs[0] == "exit": break else: print("Invalid input") if __name__ == "__main__": main() ``` 该停车场有以下功能: - park <reg_number>:停车并分配空闲车位 - leave <slot>:从指定车位上离开车辆 - status:查看停车场状态,包括所有已占用车位的车牌号码和车位号码 - registration_numbers_for_cars_with_colour <color>:查找与指定颜色匹配的所有车牌号码 - slot_numbers_for_cars_with_colour <color>:查找与指定颜色匹配的所有车位号码 - slot_number_for_registration_number <reg_number>:查找与指定车牌号码匹配的车位号码 当输入 “exit” 时程序停止运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖工人_0803号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值