-
请问下,在物联网开发中,MQTT协议为什么如此受欢迎?
-
目前物联网这么火,物联网(IoT)的基本概念是什么?
-
一、部署华为云物联网平台华为云官网: cid:link_8打开官网,搜索物联网,就能快速找到 设备接入IoTDA。1.1 物联网平台介绍华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。1.2 开通物联网服务地址: cid:link_7点击立即创建。正在创建标准版实例,需要等待片刻。创建完成之后,点击实例名称。 可以看到标准版实例的设备接入端口和地址。在上面也能看到 免费单元的限制。开通之后,点击总览,也能查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。总结:端口号: MQTT (1883)| MQTTS (8883) 接入地址:ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com根据域名地址得到IP地址信息:打开Windows电脑的命令行控制台终端,使用ping 命令。ping一下即可。Microsoft Windows [版本 10.0.19045.4170](c) Microsoft Corporation。保留所有权利。C:\Users\11266>ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com正在 Ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:来自 117.78.5.125 的回复: 字节=32 时间=35ms TTL=93来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=93来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=93来自 117.78.5.125 的回复: 字节=32 时间=39ms TTL=93117.78.5.125 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),往返行程的估计时间(以毫秒为单位): 最短 = 35ms,最长 = 39ms,平均 = 36msC:\Users\11266>MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。2.3 创建产品(1)创建产品(2)填写产品信息根据自己产品名字填写,下面的设备类型选择自定义类型。(3)产品创建成功创建完成之后点击查看详情。(4)添加自定义模型产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。模型简单来说: 就是存放设备上传到云平台的数据。当前设备需要与云平台交互的属性如下: 接下来就按照下面的属性创建 华为云平台的模型。上传到华为云物联网平台的参数:DHT11_T 环境温度DHT11_H 环境湿度BH1750 环境光照强度MQ135 空气质量先点击自定义模型。再创建一个服务ID。接着点击新增属性。(A)DHT11_T 环境温度(B)DHT11_H 环境湿度(C)BH1750 环境光照强度(D)MQ135 空气质量(5)创建完成1.4 添加设备产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。(1)注册设备(2)根据自己的设备填写(3)保存设备信息创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。(4)设备创建完成(5)设备详情1.5 MQTT协议主题订阅与发布(1)MQTT协议介绍当前的设备是采用MQTT协议与华为云平台进行通信。MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。华为云的MQTT协议接入帮助文档在这里: cid:link_5业务流程:(2)华为云平台MQTT协议使用限制描述限制支持的MQTT协议版本3.1.1与标准MQTT协议的区别支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msgMQTTS支持的安全等级采用TCP通道基础 + TLS协议(最高TLSv1.3版本)单帐号每秒最大MQTT连接请求数无限制单个设备每分钟支持的最大MQTT连接数1单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关3KB/sMQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝1MBMQTT连接心跳时间建议值心跳时间限定为30至1200秒,推荐设置为120秒产品是否支持自定义Topic支持消息发布与订阅设备只能对自己的Topic进行消息发布与订阅每个订阅请求的最大订阅数无限制(3)主题订阅格式帮助文档地址:cid:link_5对于设备而言,一般会订阅平台下发消息给设备 这个主题。设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。以当前设备为例,最终订阅主题的格式如下:$oc/devices/{device_id}/sys/messages/down 最终的格式:$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down(4)主题发布格式对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。这个操作称为:属性上报。帮助文档地址:cid:link_2根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:发布的主题格式:$oc/devices/{device_id}/sys/properties/report 最终的格式:$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report发布主题时,需要上传数据,这个数据格式是JSON格式。上传的JSON数据格式如下:{ "services": [ { "service_id": <填服务ID>, "properties": { "<填属性名称1>": <填属性值>, "<填属性名称2>": <填属性值>, .......... } } ]}根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。根据这个格式,组合一次上传的属性数据:{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}1.6 MQTT三元组MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。接下来介绍,华为云平台的MQTT三元组参数如何得到。(1)MQTT服务器地址要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。帮助文档地址:cid:link_1MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)华为云的MQTT服务器地址:117.78.5.125华为云的MQTT端口号:1883如何得到IP地址?如何域名转IP? 打开Windows的命令行输入以下命令。ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com(2)生成MQTT三元组华为云提供了一个在线工具,用来生成MQTT鉴权三元组: cid:link_6打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。下面是打开的页面:填入设备的信息: (上面两行就是设备创建完成之后保存得到的)直接得到三元组信息。得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911Username 663cb18871d845632a0912e7_dev1Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac2371.7 模拟设备登录测试经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。(1)填入登录信息打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。(2)打开网页查看完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。点击详情页面,可以看到上传的数据:到此,云平台的部署已经完成,设备已经可以正常上传数据了。(3)MQTT登录测试参数总结MQTT服务器: 117.78.5.125MQTT端口号: 183//物联网服务器的设备信息#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"#define MQTT_UserName "663cb18871d845632a0912e7_dev1"#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"//订阅与发布的主题#define SET_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down" //订阅#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report" //发布发布的数据:{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}1.8 创建IAM账户创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。地址: cid:link_3【1】获取项目凭证 点击左上角用户名,选择下拉菜单里的我的凭证项目凭证:28add376c01e4a61ac8b621c714bf459【2】创建IAM用户鼠标放在左上角头像上,在下拉菜单里选择统一身份认证。点击左上角创建用户。创建成功:【3】创建完成用户信息如下:主用户名 l19504562721IAM用户 ds_abc密码 DS123456781.9 获取影子数据帮助文档:cid:link_4设备影子介绍:设备影子是一个用于存储和检索设备当前状态信息的JSON文档。每个设备有且只有一个设备影子,由设备ID唯一标识设备影子仅保存最近一次设备的上报数据和预期数据无论该设备是否在线,都可以通过该影子获取和设置设备的属性简单来说:设备影子就是保存,设备最新上传的一次数据。我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。如果对接口不熟悉,可以先进行在线调试:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。调试完成看右下角的响应体,就是返回的影子数据。设备影子接口返回的数据如下:{ "device_id": "663cb18871d845632a0912e7_dev1", "shadow": [ { "service_id": "stm32", "desired": { "properties": null, "event_time": null }, "reported": { "properties": { "DHT11_T": 18, "DHT11_H": 90, "BH1750": 38, "MQ135": 70 }, "event_time": "20240509T113448Z" }, "version": 3 } ]}调试成功之后,可以得到访问影子数据的真实链接,接下来的代码开发中,就采用Qt写代码访问此链接,获取影子数据,完成上位机开发。链接如下:https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c
-
获取设备影子时出现以下错误,我的IAM用户权限都开放了的{ "error_code": "IOTDA.000021", "error_msg": "Operation not allowed. User not found by IAM token or the authorized user has not subscribed to IoTDA."}
-
iota设备,走的coap协议
-
就是我的串口可以一直显示数据的,BC35上传信息到Iot平台几分钟后,我的应用模拟器就打印不出消息日志了,隔一段时间又恢复。一直循环上述过程
-
求助大佬!最近想用RT-Thread星火一号开发板做东西,发现它有连接阿里云,腾讯云的软件包,没有华为云的,这两个是不能连吗?(可以连的话,要怎么连呢?)
-
能否在华为云上搭建数据库,实现将嵌入式设备接入华为云后,将嵌入式设备上采集到的数据存储到华为云的数据库中,再由其他的应用端区访问数据库中的数据
-
通过产品文档下载的Postman 环境以及api配置可以正常执行,但是操作的是【IoTDA 基础版实例】(创建的产品,设备都在这个实例的资源空间内)如果在【标准版】中,建立一个实例,并在其默认资源空间里,手动创建产品和设备。再使用应用侧API查询产品列表,根据API的说明,在【header】中设置【instance-Id】,取这里的值在【Query参数中】设置【app-id】,取这里的值会返回如下错误{ "error_code": "IOTDA.001006", "error_msg": "Operation not allowed. Application not found by authorized user or the authorized user has no application."}请指点一下,到底哪里设置的有问题,或者是不是在自建的实例中还要进行一些授权操作?
-
按照AT+HMCON=bsmode,lifetime,serverip,serverport,deviceid,passwd,codec的格式连接,一直出现问题,经测试,反复改变设备ID和密钥,报错不改变,设备已经连接上IP。
-
真实设备都在线,设备信息实时上传刷新,同样配置我的不能联动,其他同学可以
-
感谢评论区各位开发者精彩的内容分享让我们充分感受到了IoT技术开发的魅力与力量如果不是奖品数量有限,真想给每一位开发者都颁发奖品~~经过IoT技术专家评审 恭喜:林欣、小修 两位开发者成为本次优质内容创作者本次活动奖品小熊派星闪开发板将按照问卷提供的地址在公示期结束后(2024.5.10)进行邮寄也欢迎大家持续关注华为云开发者社区论坛活动哦!———————————————————————————————————————活动已结束,火热评奖中感谢各位开发者的积极参与获奖名单将于5月6日前公布哦!———————————————————————————————————————一块掌心大小的开发板可以做什么?给物联网开发爱好者,他们能将普通门锁改造成为智能指纹门锁,让家里的花花草草自动浇水给专业工程师,他们能开发出脑卒中患者步态评估等辅助医疗诊断设备,让问诊更加严谨高效若是给资深极客发明家,他们能爆改出威力无穷的机械臂,变身为野生钢铁侠体验云和物联网交融的奇妙开发之旅,你曾有过哪些嵌入式开发经历?在本帖分享可赢取小熊派最新产品——星闪开发板,快来讲讲吧!【互动方式】即日起——4月28日23:59,在本帖分享300字以上自己的嵌入式开发故事(如果附上相关视频、文章,中奖概率翻倍哦!),评选2名优质内容创作者,送小熊派星闪开发板一个~嵌入式开发故事主题满足下方任一内容即可:使用华为云产品进行物联网开发参加华为云赛事活动进行物联网开发通过小熊派开发板做过的有趣的项目开发<<更多精彩>>参与华为云开发者联盟账号今日头条、微博转发小熊派云上奇遇记内容,还有机会获得华为云云宝盲盒哦!想了解更多华为云IoT物联网开发者故事,请查看《小熊派的云上奇遇记》【互动礼品】小熊派星闪开发板小熊派星闪(NearLink)开发板,使用了中国原生的新一代无线短距通信技术。与传统短距传输技术方案相比,星闪在功耗、速度、覆盖范围和连接性能全面领先,可以在智能终端、智能家居、智能汽车、智能制造等各类细分场景下实现更极致的用户体验。【注意事项】1、所有参与活动的内容,如发现为抄袭内容或水文,则取消获奖资格。2、为保证您顺利领取活动奖品,请您在活动公示奖项后2个工作日内私信提前填写奖品收货信息,如您没有填写,视为自动放弃奖励。3、活动奖项公示时间截止2024年5月10日,如未反馈邮寄信息视为弃奖。本次活动奖品将于奖项公示后30个工作日内统一发出,请您耐心等待。4、活动期间同类子活动每个ID(同一姓名/电话/收货地址)只能获奖一次,若重复则中奖资格顺延至下一位合格开发者,仅一次顺延。5、如活动奖品出现没有库存的情况,华为云工作人员将会替换等价值的奖品,获奖者不同意此规则视为放弃奖品。6、如您在体验中发现任何体验不友好、产品Bug、文档页面错漏等情况,欢迎通过云声平台反馈给我们,还有机会领取云声专属礼品!7、其他事宜请参考【华为云社区常规活动规则】。
-
提示我设备未激活,然后调试命令失败,该怎么处理
-
IoT物联网,做智能水表项目,用户要能跟电表一样,看到每小时的用水情况想咨询下,1. 智能水表的数据一般是每隔多久上发一次比较好?2. 服务器需要买多少带宽?3. 统计是精确到分钟的统计,还是小时?
-
一、前言1.1 项目介绍【1】项目功能介绍随着物联网技术与移动通信技术的快速发展,远程遥控设备在日常生活及工业应用中的普及度日益提高。无论是家用扫地机器人实现自主导航清扫,还是目前抖音平台上展示的实景互动小车等创新应用,都体现了远程控制和实时视频监控技术对现代智能设备的重要性。本项目设计并实现一款基于STM32微控制器的远程遥控安卓小车系统。该系统充分利用了淘汰下来的安卓旧手机作为车载信息处理单元,不仅实现了资源的有效再利用,还结合4G网络技术以及先进的流媒体服务和物联网技术,搭建起一套集远程操控、实时视频音频传输于一体的高效解决方案。项目的小车搭载了STM32主控板以精确控制四个电机的动作,通过L298N驱动芯片确保了底座移动的稳定性和灵活性。同时,小车的动力源采用两节18650锂电池提供充足的电力支持。车载的旧安卓手机通过USB线连接到STM32主控板上,接收并执行来自远端手机APP的指令。这款由Qt开发的Android APP能够利用4G网络实现实时在线,并通过摄像头采集音视频数据,通过RTMP协议将这些数据推送到华为云ECS服务器上的NGINX流媒体服务器,从而实现高清流畅的远程视频监控。为了实现双向交互和低延迟控制,整个系统还借助MQTT协议连接至华为云IOT服务器。另一台安装了同样由Qt开发的Android手机APP的终端设备,可以通过该APP拉取小车端的实时音视频流进行播放,并通过方向键菜单实现对小车的精准远程操控。这种设计不仅极大地拓展了传统遥控小车的功能性与实用性,还为其他类似应用场景提供了可借鉴的技术框架。当前设计的这种基于4G网络设计的远程遥控巡检小车的技术应用场景主要是: 安全防护、环境监测、设备巡检、物料搬运、应急救援这些地方。(1)远程监控:可应用于安全防护、环境监测、农业监控等领域,例如森林防火、农田灌溉管理、危险区域侦查等,通过实时视频和音频传输,工作人员可以在远程位置对现场情况进行实时了解和操控。(2)工业巡检:在工厂、仓库或大型设施内部署此类小车,用于设备巡检、物料搬运或生产流程监控,尤其适合那些人员不易到达或者存在安全隐患的地方。(3)应急救援:在地震、火灾等灾害现场,远程遥控小车可用于进入倒塌建筑内部搜寻生命迹象,或是携带传感器测量有害气体浓度等,为救援决策提供及时信息下面是当前小车整体技术框架:小车的模型:本次设计里放在小车终端上的Android手机采用的是小米4C,一款普通的Android手机:2015年上市的小米4C。 从本身价值来讲,现在在某鱼上差不多是200块,本身就是一个完整的系统。性价比非常高。比去单独买Linux开发板进行模型开发实验来说,成本低低很多。 主流的Linux、Android开发板价格都比较贵的。开发过程中,测试遥控效果:【2】设计实现的功能(1)STM32主控板功能:控制4个电机:STM32通过L298N驱动芯片驱动4个电机,实现小车的前进、后退、左转、右转等动作。数据通信:STM32通过USB接口与安卓手机通信,接收手机APP发送的控制指令,并将小车的状态信息(如电量、速度、位置等)发送回手机。电源管理:管理2节18650锂电池的供电,确保电压稳定并监控电池电量。(2)安卓手机APP功能控制指令下发:手机APP通过USB接口向STM32发送控制指令,控制小车的动作。视频和音频流获取:APP从手机摄像头和麦克风获取视频和音频流,并进行编码处理。流媒体推流:通过RTMP协议将编码后的视频和音频流推送到华为云ECS服务器+NGINX搭建的RTMP流媒体服务器。MQTT连接:APP通过MQTT协议与华为云IOT服务器建立连接,实现双向通信。(3)华为云服务器功能:RTMP流媒体服务:接收并转发安卓手机APP推送的视频和音频流。MQTT服务:作为MQTT消息代理,实现远程手机与STM32主控板之间的通信。(4)远程Android手机APP功能:实时视频和音频播放:从华为云ECS服务器拉取视频和音频流,并实时显示在APP界面上。MQTT连接:与华为云IOT服务器建立连接,接收STM32主控板发送的小车状态信息。远程控制:提供方向键控制菜单,允许用户远程控制小车前进、后退、转弯等动作。【3】项目硬件模块组成(1)电源模块:电池组:采用两节18650锂电池作为供电源,它们具有高能量密度、体积小的特点,能够为整个系统提供稳定的直流电能。(2)主控模块:STM32微控制器:这是整个小车的核心控制单元,负责处理所有的逻辑运算和数据通信任务。通过编程实现对电机驱动、USB通信、网络连接等功能的控制。(3)电机驱动模块:L298N驱动芯片:用于驱动底座上的四个电机,L298N是一个高性能的H桥电机驱动器,可以接收来自STM32的信号,转换为足够驱动电机工作的电流和电压,并且支持电机正反转及速度调节。(4)移动平台模块:四个直流电机:直接安装在小车底座上,通过L298N驱动进行精确的速度和方向控制,以实现小车前进、后退、左右转弯等运动功能。(5)通信模块:USB接口:STM32主控板通过USB线与安卓手机物理连接,实现数据传输,接收来自手机APP的控制指令。4G模组:集成在安卓手机内部,插入SIM卡后可实现高速无线网络连接,使小车能够在远程环境下通过互联网与其他设备通信。(6)多媒体采集模块:安卓手机摄像头:用于捕捉实时视频和音频信息,是小车端环境感知的关键组件。(7)云服务交互模块:华为云ECS服务器+NGINX RTMP流媒体服务器:小车端将采集到的音视频流推送到华为云服务器上,通过RTMP协议实现实时音视频的低延迟传输和分发。华为云IOT服务器:小车和远端控制手机均通过MQTT协议与之建立连接,实现远程数据交换和控制命令的下发。【3】功能总结(1)电机驱动与控制:通过STM32微控制器和L298N驱动芯片,实现对小车上四个电机的精确控制,包括前进、后退、左转、右转等动作,从而控制小车的移动方向和速度。(2)无线通信与数据传输:STM32与安卓手机之间通过USB接口建立通信,实现控制指令的下发和小车状态信息的上传。同时,安卓手机通过4G网络连接到华为云服务器,实现了远程控制命令的远程传输和视频音频流的推送。(3)流媒体推流与播放:安卓手机APP能够捕获手机摄像头和麦克风的视频和音频流,通过RTMP协议推送到华为云服务器。另一台安卓手机APP则从服务器拉取这些流,实现实时播放,从而允许用户远程观看小车的实时画面和音频。(4)华为云服务器支持:华为云ECS服务器和NGINX搭建的RTMP流媒体服务器负责接收、转发视频和音频流,确保流媒体的稳定性和实时性。同时,华为云IOT服务器通过MQTT协议提供消息代理服务,实现远程手机与STM32之间的双向通信。(5)用户界面与交互设计:安卓手机APP提供了直观的用户界面,包括控制按钮、状态显示、视频播放器等,使用户能够方便地对小车进行控制、观看视频、监听音频,以及监控小车的状态信息。(6)远程控制:通过结合STM32的电机控制、华为云服务器的数据处理和传输,以及安卓手机的用户界面和交互设计,实现了从远程手机到小车的远程控制功能。用户可以在远离小车的地点,通过手机APP发出控制指令,实时观察小车的动作和周围环境。1.2 设计思路1.3 系统功能总结自主供电与移动控制采用2节18650锂电池为小车提供电力供应;STM32微控制器结合L298N驱动芯片,精准控制4个电机动作,实现前进、后退、转弯等移动功能手机APP通信与指令传输STM32通过USB线与安卓手机连接,接收并解析来自手机APP的控制指令,实现人机交互和远程指令执行实时视频音频流传输安卓手机利用4G网络上网,搭载Qt开发的Android APP采集摄像头视频和音频数据,并通过RTMP协议将音视频流推送到华为云ECS服务器+NGINX搭建的流媒体服务器物联网(IoT)连接与远程监控小车端及远端控制手机均通过MQTT协议连接华为云IOT服务器,实现车辆状态信息实时上传及远程音视频流拉取显示;远端手机APP提供方向键菜单以远程操控小车数据交互与低延迟控制利用MQTT协议确保在4G网络环境下高效、低延迟的数据交互,实现对小车的实时远程控制,提升整体系统的响应速度和操作体验二、搭建视频监控流媒体服务器2.1 购买云服务器如果之前没有使用过华为云的ECS服务器,可以先申请试用1个月,了解服务器的基本使用然后再购买,不得不说提供这个试用服务还是非常不错。产品试用领取地址: cid:link_4每天9:30开抢,每天限量100份,这个页面不仅有云服务器可以领取试用,还有云数据库、沙盒等其他产品。我这里领取了 2核4G S6云服务器,这个服务器是配套华为自研25GE智能高速网卡,适用于网站和Web应用等中轻载企业应用。选择配置的时候发现S6规格的已经没有了,来晚了。S6规格没有了,就选择了S3,2核,4GB的配置结算。页面向下翻,下面选择系统预装的系统,我这里选择ubuntu 20.04,安装NGINX,来搭建流媒体服务器。页面继续下翻,设置云服务器名称,设置系统的root密码。接着就可以继续去支付了。购买后等待一段时间,系统资源就即可分配完成。2.2 登录云服务器云服务器的管理控制台从这里进入: cid:link_6在官网主页,点击产品,找到计算选项,就可以看到弹性云服务器ECS,点击进去就可以看到管理控制台的选项。在弹性云服务器的选项页面可以看到刚才购买的云服务器,如果点击进去提示该区域没有可用的服务器,说明区域选择的不对,在下面截图红色框框的位置可以看到可用的区域切换按钮,切换之后就行了。点击服务器右边的更多,可以对服务器重装系统、切换操作系统、关机、开机、重启、重置密码等操作。接下来先点击登录,了解一下登录的流程,看看系统进去的效果。云服务器支持SSH协议远程登录,可以在浏览器上直接使用CloudShell方式或者VNC方式登录,如果本身你自己也是使用Linux系统,可以在Linux系统里通过ssh命令直接登录,如果是在windows下可以使用SecureCRT登录。其他登录方式。最方便的登录方式,直接在控制台使用VNC在浏览器里登录,点击立即登录。根据提示输入用户名,密码后,按下回车键即可登录。用户名直接输入root,密码就是刚才配置云服务器时,填入的root密码。注意: Linux下输入密码默认都是隐藏的,也就是在键盘上输入密码界面上是不会有反应的,自己按照正确的密码输入即可。用户名、密码输入正确后,登录成功。可以点击全屏模式,更好的操作。2.3 使用CloudShell登录云服务器在页面上直接点击CloudShell登录按钮。CloudShell方式比VNC方式方便、流畅多了,也支持命令的复制粘贴。输入密码点击连接。登录成功进入终端。2.4 使用SecureCRT登录云服务器上面演示了两种登录方式,都是直接在浏览器里登录,这种两种方式比较来看,VNC方式效率最低,CloudShell相对来说要方便很多。一般我自己正常开发时,都是使用第三方工具来登录的,如果本身自己开发环境的电脑就是Linux,MAC等,可以直接使用ssh命令登录,这种方式操作流畅方便。如果在windows下,可以使用第三方软件登录。我现在使用的系统是win10,在windows系统下有很多远程终端软件支持SSH登录到Linux云服务器,我当前采用的使用SecureCRT 6.5来作为登录终端,登录云服务器。注意: SecureCRT 6.5 登录高版本Linux系统会出现Key exchange failed问题,导致登录失败,比如: 登录ubuntu 20.04 系统。 出现这种问题需要对系统ssh配置文件进行添加配置。添加配置的流程:命令行输入:vim /etc/ssh/sshd_config在文件最后添加:KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1保存退出。 重启ssh服务service ssh restart如果不想这么麻烦的去修改配置文件,那么最简单的办法就是: 切换操作系统,换一个低版本的,比如:ubuntu18.04 即可。在云服务器的控制台,找到自己的服务器,然后选择切换操作系统。根据界面上的引导流程,切换即可。更换新的系统需要1~4分钟时间,稍微等待一下即可。如果要使用远程SSH协议方式登录云服务器,需要具备以下几个前提条件。[1]弹性云服务器状态为“运行中”。[2]弹性云服务器已经绑定弹性公网IP,绑定方式请参见绑定弹性公网IP。[3]所在安全组入方向已开放22端口,配置方式请参见配置安全组规则。[4]使用的登录工具(如PuTTY,SecureCRT`)与待登录的弹性云服务器之间网络连通。例如,默认的22端口没有被防火墙屏蔽。但是这些配置不用担心,在购买服务器后,根据引导一套走完,上面的这些配置都已经默认配置好了,不用自己再去单独配置。系统切换成功后,打开SecureCRT 6.5软件,进行登录。SecureCRT 6.5整体而言还是挺好用的。如果自己没有`SecureCRT,自己下载一个即可。当然,不一定非要使用SecureCRT,其他还有很多SSH远程登录工具,喜欢哪个使用哪个。下面介绍`SecureCRT登录的流程。选择新建会话。选择SSH2协议。主机名就填服务器的公网IP地址,端口号默认是22,用户名填root。自己云服务器的公网IP地址,在控制台可以看到。软件点击下一步之后,可以填充描述信息,备注这个链接是干什么的。选择刚才新建的协议端口点击连接。云服务器连接上之后,软件界面会弹出对话框填充用户名、密码。登录成功的效果如下。软件可以配置一些选项,让界面符合Linux终端配色,可以修改字体大小、字体编码等等。配置后的效果。2.5 安装NFS服务器为了方便向服务器上拷贝文件,可以采用NFS服务器、或者FTP服务器 这些方式。 我本地有一台ubuntu 18.04 系统笔记本,我这里采用NFS方式拷贝文件上去。(1)安装NFS服务器root@ecs-348470:~# sudo apt-get install nfs-kernel-server(2)创建一个work目录方便当做共享目录使用root@ecs-348470:~# mkdir work(3)编写NFS配置文件NFS 服务的配置文件为/etc/exports,如果系统没有默认值,这个文件就不一定会存在,可以使用 vim 手动建立,然后在文件里面写入配置内容。/home/work *(rw,no_root_squash,sync,no_subtree_check,insecure) (4)NFS服务器相关指令/etc/init.d/nfs-kernel-server start #启动 NFS 服务ufw disable #关闭防火墙/etc/init.d/nfs-kernel-server restart #重启NFS服务exportfs -arv #共享路径生效(5)本地客户机挂载服务器的目录wbyq@wbyq:~$ sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/(6)设置华为云服务器的安全策略需要把华为云服务器的端口号开放出来,不然其他客户端是无法访问服务器的。我这里比较粗暴直接,直接将所有端口号,所有IP地址都开放出来了。(7)本地客户机挂载服务器测试 挂载指令:sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/2.6 安装NGINX(配置RTMP服务)(1)下载编译时需要依赖的一些工具root@ecs-348470:~# sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev(2)下载NGINX编译需要的软件包root@ecs-348470:~# mkdir nginx root@ecs-348470:~# cd nginx/root@ecs-348470:~# wget http://nginx.org/download/nginx-1.10.3.tar.gzroot@ecs-348470:~# wget http://zlib.net/zlib-1.2.11.tar.gzroot@ecs-348470:~# wget https://ftp.pcre.org/pub/pcre/pcre-8.40.tar.gzroot@ecs-348470:~# wget https://www.openssl.org/source/openssl-1.0.2k.tar.gzroot@ecs-348470:~# wget https://github.com/arut/nginx-rtmp-module/archive/master.zip(3)下载后的文件全部解压root@ecs-348470:~# tar xvf openssl-1.0.2k.tar.gzroot@ecs-348470:~# tar xvf nginx-rtmp-module-master.tar.gzroot@ecs-348470:~# tar xvf nginx-1.8.1.tar.gzroot@ecs-348470:~# tar xvf pcre-8.40.tar.gzroot@ecs-348470:~# tar xvf zlib-1.2.11.tar.gz(4)配置NGINX源码,生成Makefile文件root@ecs-348470:~# cd nginx-1.8.1/root@ecs-348470:~# ./configure --prefix=/usr/local/nginx --with-debug --with-pcre=../pcre-8.40 --with-zlib=../zlib-1.2.11 --with-openssl=../openssl-1.0.2k --add-module=../nginx-rtmp-module-master执行完上一步之后,打开objs/Makefile文件,找到-Werror选项并删除。(5)编译并安装NGINX root@ecs-348470:~/nginx/nginx-1.8.1# make && make install安装之后NGINX的配置文件存放路径:/usr/local/nginx/nginx:主程序(6)查看NGINX的版本号root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -vnginx version: nginx/1.8.1(5)在配置文件里加入RTMP服务器的配置root@ecs-348470:~# vim /usr/local/nginx/conf/nginx.conf 打开文件后,在文件最后加入以下配置:rtmp { server { listen 8888; application live { live on; record all; record_unique on; record_path "./video"; #视频缓存的路径 record_suffix -%Y-%m-%d-%H_%M_%S.flv; } } }这样配置之后,服务器收到RTMP流会在NGINX的当前目录下,创建一个video目录用来缓存视频。客户端向服务器推流之后,服务器就会缓存视频到设置的目录。(5)检查配置文件是否正确root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -tnginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is oknginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful(6)NGINX常用的3个命令sudo service nginx startsudo service nginx stopsudo service nginx restart(7)启动NGINX服务器sudo service nginx start2.7 摄像头推流音视频到服务器为了模拟摄像头实时监控推流,我这使用QT+FFMPEG编写了一个小软件,在windows下运行,推流本地笔记本电脑的数据到服务器。软件运行之后,将本地音频、视频编码之后通过RTMP协议推流到NGINX服务器。软件运行效果:推流工具运行过程中效果。2.8 编写本地RTMP流播放器在上面通过推流客户端模拟摄像头,已经将本地的摄像头数据实时推流到服务器了,那么还差一个播放器,为了方便能够在任何有网的地方实时查看摄像头的音频和图像,还需要编写一个RTMP流媒体播放器。我这里的播放器内核是采用libvlc开发的,使用QT作为GUI框架,开发了一个流媒体播放器,可以实时拉取服务器上的流数据,并且还支持回放。因为服务器上的NGINX配置了自动保存的参数,可以将推上去的流按时间段保存下来。输入服务器地址之后就可以拉取流进行播放。点击获取回放列表,可以查看服务器上保存的历史视频回放播放。三、华为云IOT服务器部署过程在华为云IOT平台上,需要进行设备接入、数据模型定义、规则引擎配置和应用开发等四个核心模块的开发。其中,设备接入模块包括设备注册、获取设备证书、建立连接等步骤,以保障设备与云平台之间的安全通信;数据模型定义模块需要根据实际需求定义相应的数据模型,包括上传数据格式、设备属性和服务等。规则引擎配置模块需要完成实时消息推送、远程控制和告警等功能。应用开发模块则是将完整的智能井盖系统进行打包,为用户提供统一的操作接口。华为云官网: cid:link_7打开官网,搜索物联网,就能快速找到 设备接入IoTDA。3.1 物联网平台介绍华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。3.2 开通物联网服务地址: cid:link_5开通标准版免费单元。开通之后,点击总览,查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。总结:端口号: MQTT (1883)| MQTTS (8883) 接入地址: a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com根据域名地址得到IP地址信息:Microsoft Windows [版本 10.0.19044.2846](c) Microsoft Corporation。保留所有权利。C:\Users\11266>ping a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com正在 Ping a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com [121.36.42.100] 具有 32 字节的数据:来自 121.36.42.100 的回复: 字节=32 时间=37ms TTL=31来自 121.36.42.100 的回复: 字节=32 时间=37ms TTL=31来自 121.36.42.100 的回复: 字节=32 时间=36ms TTL=31来自 121.36.42.100 的回复: 字节=32 时间=37ms TTL=31121.36.42.100 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),往返行程的估计时间(以毫秒为单位): 最短 = 36ms,最长 = 37ms,平均 = 36msC:\Users\11266>MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。3.3 创建产品(1)创建产品点击右上角创建产品。(2)填写产品信息根据自己产品名字填写,设备类型选择自定义类型。(3)添加自定义模型产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。模型简单来说: 就是存放设备上传到云平台的数据。比如:环境温度、环境湿度、环境烟雾浓度、火焰检测状态图等等,这些我们都可以单独创建一个模型保存。3.4 添加设备产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。(1)注册设备点击右上角注册设备。(2)根据自己的设备填写在弹出的对话框里填写自己设备的信息。根据自己设备详细情况填写。(3)保存设备信息创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。比如我当前设备的信息如下:{ "device_id": "64000697352830580e48df07_dev1", "secret": "12345678"}3.5 MQTT协议主题订阅与发布(1)MQTT协议介绍当前的设备是采用MQTT协议与华为云平台进行通信。MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。华为云的MQTT协议接入帮助文档在这里: cid:link_2业务流程:(2)华为云平台MQTT协议使用限制描述限制支持的MQTT协议版本3.1.1与标准MQTT协议的区别支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msgMQTTS支持的安全等级采用TCP通道基础 + TLS协议(最高TLSv1.3版本)单帐号每秒最大MQTT连接请求数无限制单个设备每分钟支持的最大MQTT连接数1单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关3KB/sMQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝1MBMQTT连接心跳时间建议值心跳时间限定为30至1200秒,推荐设置为120秒产品是否支持自定义Topic支持消息发布与订阅设备只能对自己的Topic进行消息发布与订阅每个订阅请求的最大订阅数无限制(3)主题订阅格式帮助文档地址:cid:link_2对于设备而言,一般会订阅平台下发消息给设备 这个主题。设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。比如:我创建的设备信息如下以当前设备为例,最终订阅主题的格式如下:$oc/devices/{device_id}/sys/messages/down 最终的格式:$oc/devices/64000697352830580e48df07_dev1/sys/messages/down(4)主题发布格式对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。这个操作称为:属性上报。帮助文档地址:cid:link_1根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:发布的主题格式:$oc/devices/{device_id}/sys/properties/report 最终的格式:$oc/devices/64000697352830580e48df07_dev1/sys/properties/report发布主题时,需要上传数据,这个数据格式是JSON格式。上传的JSON数据格式如下:{ "services": [ { "service_id": <填服务ID>, "properties": { "<填属性名称1>": <填属性值>, "<填属性名称2>": <填属性值>, .......... } } ]}根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。//Up, Down, Left, Right, Stop 根据这个格式,组合一次上传的属性数据:{"services": [{"service_id": "stm32","properties":{"Up":1,"Down":1,"Left":1,"Right":1,"Stop":1}}]}3.6 MQTT三元组MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。接下来介绍,华为云平台的MQTT三元组参数如何得到。(1)MQTT服务器地址要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。帮助文档地址:cid:link_0MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)华为云的MQTT服务器地址:117.78.5.125华为云的MQTT端口号:1883(2)生成MQTT三元组华为云提供了一个在线工具,用来生成MQTT鉴权三元组: cid:link_3打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。下面是打开的页面:填入设备的信息: (上面两行就是设备创建完成之后保存得到的)直接得到三元组信息。得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。DeviceId 64000697352830580e48df07_dev1DeviceSecret 12345678ClientId 64000697352830580e48df07_dev1_0_0_2023030206Username 64000697352830580e48df07_dev1Password a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf4493.7 模拟设备登录测试经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。(1)填入登录信息打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。(2)打开网页查看完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。到此,云平台的部署已经完成,设备已经可以正常上传数据了。(3)MQTT录测试参数总结IP地址:117.78.5.125端口号:1883DeviceId 64000697352830580e48df07_dev1DeviceSecret 12345678ClientId 64000697352830580e48df07_dev1_0_0_2023030206Username 64000697352830580e48df07_dev1Password a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449订阅主题:$oc/devices/64000697352830580e48df07_dev1/sys/messages/down发布主题:$oc/devices/64000697352830580e48df07_dev1/sys/properties/report发布的消息:{"services": [{"service_id": "stm32","properties":{"Up":1,"Down":1,"Left":1,"Right":1,"Stop":1}}]}四、Android手机APP开发4.1 开发环境介绍在当前项目中,用于远程遥控安卓小车的Android手机APP是基于Qt框架开发的。Qt是一个功能强大且高度灵活的跨平台应用程序开发框架,特别适合构建具有丰富图形用户界面(GUI)的应用程序,同时也支持开发非GUI程序。在开发这款远程遥控APP时,Qt的优势在于其跨平台性,使得同一份代码可以轻松部署在不同操作系统平台上,包括Android。Android开发必备的工具链包括:Java JDK 、Android SDK 、Android NDK。NDK下载地址:https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zipSDK下载: https://www.androiddevtools.cn/JDK下载地址:https://www.oracle.com/java/technologies/javase-jdk8-downloads.html4.2 ffmpeg介绍说起ffmpeg,只要是搞音视频相关的开发应该都是听过的。FFmpeg提供了非常先进的音频/视频编解码库,并且支持跨平台。现在互联网上ffmpeg相关的文章、教程也非常的多,ffmpeg本身主要是用来对视频、音频进行解码、编码,对音视频进行处理。其中主要是解码和编码。 解码的应用主要是视频播放器制作、音乐播放器制作,解码视频文件得到视频画面再渲染显示出来就是播放器的基本模型了。 编码主要是用于视频录制保存,就是将摄像头的画面或者屏幕的画面编码后写入文件保存为视频,比如:行车记录仪录制视频,监控摄像头录制视频等等。 当然也可以编码推流到服务器,现在的直播平台、智能家居里的视频监控、智能安防摄像头都是这样的应用。在本项目里,通过ffmpeg技术将手机采集的视频图像编码后,推流到搭建好的流媒体服务器,实现远程监控。4.3 Linux下编译安装ffmpeg(1)安装依赖项:sudo apt-get updatesudo apt-get install build-essential nasm yasm cmake libx264-dev libx265-dev libvpx-dev libfdk-aac-dev libmp3lame-dev libopus-dev libssl-dev(2)下载FFmpeg源码:wget https://ffmpeg.org/releases/ffmpeg-4.2.2.tar.gztar -zxvf ffmpeg-x.y.z.tar.gzcd ffmpeg-x.y.z注意替换 4.2.2 为实际的版本号。(3)配置编译选项:./configure --enable-gpl --enable-libx264 --enable-libx265 --enable-libvpx --enable-libfdk-aac --enable-libmp3lame --enable-libopus --enable-nonfree如果需要其他编码器或功能,可以根据需要添加或修改配置选项。(4)编译和安装:make -j$(nproc)sudo make install-j$(nproc) 表示使用多个CPU核心进行并行编译,可以根据实际情况调整。(5)完成后,可以通过运行以下命令来验证FFmpeg是否正确安装:ffmpeg -version这样就完成了在Linux下编译FFmpeg源码的过程。4.4 Qt摄像头采集下面代码实现,通过子线程采集摄像头画面,并通过信号槽机制将图像传递给主线程显示。// mainwindow.h#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QCamera>#include <QCameraViewfinder>#include <QCameraImageCapture>#include <QThread>class CameraThread;namespace Ui {class MainWindow;}class MainWindow : public QMainWindow{ Q_OBJECTpublic: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow();private slots: void on_pushButton_start_clicked(); void on_pushButton_stop_clicked(); void onNewImageAvailable(const QImage &image);private: Ui::MainWindow *ui; QCamera *camera; QCameraViewfinder *viewfinder; QCameraImageCapture *imageCapture; CameraThread *cameraThread;};#endif // MAINWINDOW_H// mainwindow.cpp#include "mainwindow.h"#include "ui_mainwindow.h"#include <QThread>class CameraThread : public QThread{ Q_OBJECTpublic: explicit CameraThread(QObject *parent = nullptr);signals: void newImageAvailable(const QImage &image);protected: void run() override;private: QCamera *camera; QCameraImageCapture *imageCapture;};CameraThread::CameraThread(QObject *parent) : QThread(parent){ camera = new QCamera(this); imageCapture = new QCameraImageCapture(camera, this);}void CameraThread::run(){ camera->setCaptureMode(QCamera::CaptureStillImage); camera->start(); connect(imageCapture, &QCameraImageCapture::imageCaptured, this, [&](int id, const QImage &preview) { emit newImageAvailable(preview); }); exec();}MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow){ ui->setupUi(this); camera = new QCamera(this); viewfinder = new QCameraViewfinder(this); imageCapture = new QCameraImageCapture(camera, this); cameraThread = new CameraThread(this); cameraThread->start(); connect(cameraThread, &CameraThread::newImageAvailable, this, &MainWindow::onNewImageAvailable);}MainWindow::~MainWindow(){ delete ui;}void MainWindow::on_pushButton_start_clicked(){ camera->setViewfinder(viewfinder); camera->start(); ui->verticalLayout->addWidget(viewfinder);}void MainWindow::on_pushButton_stop_clicked(){ camera->stop(); ui->verticalLayout->removeWidget(viewfinder); viewfinder->deleteLater();}void MainWindow::onNewImageAvailable(const QImage &image){ // 在这里处理接收到的图像,例如将其显示在 QLabel 上 ui->label_image->setPixmap(QPixmap::fromImage(image));}// main.cpp#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); MainWindow w; w.show(); return a.exec();}(1)CameraThread 类继承自 QThread,在子线程中负责采集摄像头画面。在 run() 方法中,首先创建了一个 QCamera 对象和一个 QCameraImageCapture 对象,然后设置摄像头的捕获模式为静态图像,启动摄像头。通过连接 imageCapture 的 imageCaptured 信号到 lambda 函数,当捕获到新图像时,将图像通过自定义信号 newImageAvailable 发送出去。(2)MainWindow 类是程序的主窗口,其中包含了摄像头的视图控件、开始按钮、停止按钮以及用于显示图像的标签。在构造函数中,创建了摄像头对象、视图控件对象、图像捕获对象,并创建了一个 CameraThread 对象作为子线程来处理摄像头画面的采集。(3)当用户点击开始按钮时,调用 on_pushButton_start_clicked() 槽函数,将摄像头视图控件添加到界面上并启动摄像头,开始显示摄像头画面。(4)当用户点击停止按钮时,调用 on_pushButton_stop_clicked() 槽函数,停止摄像头捕获并移除视图控件。(5)当子线程采集到新的图像时,通过 onNewImageAvailable() 槽函数接收到图像,并在标签上显示该图像。(6)main.cpp 文件是程序的入口,创建了 QApplication 对象和 MainWindow 对象,并执行主事件循环。4.6 ffmpeg视频编码推流代码使用Qt(C++)结合FFmpeg库来采集摄像头画面,进行编码,并通过子线程将视频推送到RTMP流媒体服务器。// mainwindow.h#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QThread>extern "C" {#include <libavformat/avformat.h>#include <libavdevice/avdevice.h>#include <libavcodec/avcodec.h>#include <libswscale/swscale.h>}class VideoCaptureThread;namespace Ui {class MainWindow;}class MainWindow : public QMainWindow{ Q_OBJECTpublic: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow();private slots: void on_pushButton_start_clicked(); void on_pushButton_stop_clicked(); void onNewFrameAvailable(const QImage &frame);private: Ui::MainWindow *ui; VideoCaptureThread *videoCaptureThread;};#endif // MAINWINDOW_H// mainwindow.cpp#include "mainwindow.h"#include "ui_mainwindow.h"class VideoCaptureThread : public QThread{ Q_OBJECTpublic: explicit VideoCaptureThread(QObject *parent = nullptr); ~VideoCaptureThread();protected: void run() override;signals: void newFrameAvailable(const QImage &frame);private: AVFormatContext *formatContext; AVCodecContext *codecContext; AVStream *videoStream; SwsContext *swsContext; bool stop;};VideoCaptureThread::VideoCaptureThread(QObject *parent) : QThread(parent), stop(false){ avformat_network_init(); formatContext = avformat_alloc_context(); AVInputFormat *inputFormat = av_find_input_format("dshow"); avformat_open_input(&formatContext, "video=YourCameraDevice", inputFormat, NULL); // 省略了初始化视频编码器的部分,需要根据实际情况添加 swsContext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->width, codecContext->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);}VideoCaptureThread::~VideoCaptureThread(){ stop = true; wait(); sws_freeContext(swsContext); avcodec_free_context(&codecContext); avformat_close_input(&formatContext); avformat_free_context(formatContext);}void VideoCaptureThread::run(){ while (!stop) { AVPacket packet; av_init_packet(&packet); if (av_read_frame(formatContext, &packet) >= 0) { // 省略了视频编码的部分,需要根据实际情况添加 QImage frameImage(codecContext->width, codecContext->height, QImage::Format_RGB32); sws_scale(swsContext, codecContext->coded_frame->data, codecContext->coded_frame->linesize, 0, codecContext->height, reinterpret_cast<uint8_t **>(frameImage.bits()), frameImage.bytesPerLine()); emit newFrameAvailable(frameImage); } av_packet_unref(&packet); }}MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow){ ui->setupUi(this); videoCaptureThread = new VideoCaptureThread(this); connect(videoCaptureThread, &VideoCaptureThread::newFrameAvailable, this, &MainWindow::onNewFrameAvailable);}MainWindow::~MainWindow(){ delete ui;}void MainWindow::on_pushButton_start_clicked(){ videoCaptureThread->start();}void MainWindow::on_pushButton_stop_clicked(){ videoCaptureThread->quit();}void MainWindow::onNewFrameAvailable(const QImage &frame){ // 将QImage转换为AVFrame AVFrame *avFrame = av_frame_alloc(); avFrame->width = frame.width(); avFrame->height = frame.height(); avFrame->format = AV_PIX_FMT_RGB32; av_frame_get_buffer(avFrame, 0); for (int y = 0; y < frame.height(); ++y) { memcpy(avFrame->data[0] + y * avFrame->linesize[0], frame.scanLine(y), frame.width() * 4); } // 编码视频帧 AVPacket packet; av_init_packet(&packet); int ret = avcodec_send_frame(codecContext, avFrame); if (ret < 0) { qDebug() << "Failed to send frame to encoder"; return; } while (ret >= 0) { ret = avcodec_receive_packet(codecContext, &packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { qDebug() << "Error during encoding"; return; } // 推送到RTMP服务器 RTMP *rtmp = RTMP_Alloc(); RTMP_Init(rtmp); if (!RTMP_SetupURL(rtmp, "rtmp://your_rtmp_server_url")) { qDebug() << "Failed to set RTMP URL"; RTMP_Close(rtmp); RTMP_Free(rtmp); return; } RTMP_EnableWrite(rtmp); if (!RTMP_Connect(rtmp, NULL)) { qDebug() << "Failed to connect to RTMP server"; RTMP_Close(rtmp); RTMP_Free(rtmp); return; } if (!RTMP_ConnectStream(rtmp, 0)) { qDebug() << "Failed to connect to RTMP stream"; RTMP_Close(rtmp); RTMP_Free(rtmp); return; } packet.stream_index = videoStream->index; ret = RTMP_SendPacket(rtmp, reinterpret_cast<char*>(packet.data), packet.size, TRUE); if (ret < 0) { qDebug() << "Failed to send packet to RTMP server"; RTMP_Close(rtmp); RTMP_Free(rtmp); return; } av_packet_unref(&packet); RTMP_Close(rtmp); RTMP_Free(rtmp); } av_frame_free(&avFrame);}代码中创建了一个 VideoCaptureThread 类作为子线程,负责采集摄像头画面并进行视频编码。在 run() 方法中,利用FFmpeg库读取摄像头画面,进行视频编码,并通过自定义信号 newFrameAvailable 发送每一帧图像。主界面中的 MainWindow 类负责开始和停止视频采集线程,并处理接收到的视频帧,可以在 onNewFrameAvailable() 槽函数中将视频帧编码为RTMP流并推送到服务器。将QImage转换为AVFrame,使用avcodec_send_frame和avcodec_receive_packet函数对视频帧进行编码。创建一个RTMP连接,并将编码后的视频包发送到RTMP服务器。五、STM32端代码设计STM32端的代码主要是控制小车的移动,代码比较少。5.1 STM32小车底座驱动代码#include "stm32f10x.h"#define MOTOR1_PIN1 GPIO_Pin_0#define MOTOR1_PIN2 GPIO_Pin_1#define MOTOR2_PIN1 GPIO_Pin_2#define MOTOR2_PIN2 GPIO_Pin_3#define MOTOR3_PIN1 GPIO_Pin_4#define MOTOR3_PIN2 GPIO_Pin_5#define MOTOR4_PIN1 GPIO_Pin_6#define MOTOR4_PIN2 GPIO_Pin_7void delay_ms(uint32_t ms) { uint32_t i, j; for (i = 0; i < ms; i++) for (j = 0; j < 7200; j++);}void motor_init() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = MOTOR1_PIN1 | MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN1 | MOTOR3_PIN2 | MOTOR4_PIN1 | MOTOR4_PIN2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);}void forward() { GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2); GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);}void backward() { GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1); GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);}void left() { GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2); GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);}void right() { GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1); GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);}int main(void) { motor_init(); while (1) { forward(); delay_ms(1000); backward(); delay_ms(1000); left(); delay_ms(1000); right(); delay_ms(1000); }}5.2 小车控制代码#include "stm32f10x.h"#include <stdio.h>#include <string.h>#define MOTOR1_PIN1 GPIO_Pin_0#define MOTOR1_PIN2 GPIO_Pin_1#define MOTOR2_PIN1 GPIO_Pin_2#define MOTOR2_PIN2 GPIO_Pin_3#define MOTOR3_PIN1 GPIO_Pin_4#define MOTOR3_PIN2 GPIO_Pin_5#define MOTOR4_PIN1 GPIO_Pin_6#define MOTOR4_PIN2 GPIO_Pin_7void delay_ms(uint32_t ms) { uint32_t i, j; for (i = 0; i < ms; i++) for (j = 0; j < 7200; j++);}void motor_init() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = MOTOR1_PIN1 | MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN1 | MOTOR3_PIN2 | MOTOR4_PIN1 | MOTOR4_PIN2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);}void forward() { GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2); GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);}void backward() { GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1); GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);}void left() { GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2); GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);}void right() { GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1); GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);}void usart_init() { USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure);}void usart_send(USART_TypeDef* USARTx, uint8_t data) { while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET); USART_SendData(USARTx, data);}uint8_t usart_receive(USART_TypeDef* USARTx) { while (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET); return USART_ReceiveData(USARTx);}void usart_puts(USART_TypeDef* USARTx, char* str) { while (*str) { usart_send(USARTx, *str++); }}void control(char* cmd) { if (strcmp(cmd, "forward") == 0) { forward(); usart_puts(USART1, "OK\n"); } else if (strcmp(cmd, "backward") == 0) { backward(); usart_puts(USART1, "OK\n"); } else if (strcmp(cmd, "left") == 0) { left(); usart_puts(USART1, "OK\n"); } else if (strcmp(cmd, "right") == 0) { right(); usart_puts(USART1, "OK\n"); } else { usart_puts(USART1, "Invalid command\n"); }}int main(void) { motor_init(); usart_init(); while (1) { char cmd[10]; memset(cmd, 0, sizeof(cmd)); int i = 0; while (1) { char c = usart_receive(USART1); if (c == '\r' || c == '\n') { break; } cmd[i++] = c; } control(cmd); }}六、关于Android手机USB通信的问题在Qt中开发Android手机APP并利用USB线进行串口通信,需要启用权限。(1)添加权限:在AndroidManifest.xml文件中添加USB权限,并在Qt项目中的Android配置文件中声明需要的权限。例如,在AndroidManifest.xml中添加以下代码:<uses-permission android:name="android.permission.USB_PERMISSION" />(2)检测USB连接:通过Qt的Android JNI接口(Java Native Interface)来检测USB设备的插拔状态,并获取USB设备的信息。(3)打开和关闭USB串口:使用Qt的QSerialPort类来打开和关闭USB串口,并进行数据的读写操作。可以通过检测到的USB设备路径来打开对应的串口。(4)处理串口数据:接收到的串口数据可以通过信号槽机制或者其他方式传递给界面进行显示或进一步处理。下面是测试的代码:#include <QSerialPort>#include <QSerialPortInfo>void detectUsbDevices() { QList<QSerialPortInfo> usbDevices = QSerialPortInfo::availablePorts(); foreach (const QSerialPortInfo &info, usbDevices) { qDebug() << "USB Device Name: " << info.portName(); qDebug() << "Description: " << info.description(); }}void openUsbSerialPort(const QString &portName) { QSerialPort serialPort; serialPort.setPortName(portName); serialPort.setBaudRate(QSerialPort::Baud9600); if (serialPort.open(QIODevice::ReadWrite)) { qDebug() << "USB Serial Port opened successfully!"; // Read or write data here serialPort.close(); } else { qDebug() << "Failed to open USB Serial Port!"; }}int main(int argc, char *argv[]) { QApplication app(argc, argv); // Detect USB devices detectUsbDevices(); // Open USB serial port openUsbSerialPort("/dev/ttyUSB0"); // Replace with the actual port name return app.exec();}七、总结本文详细介绍了一款创新且环保的基于4G网络设计的远程遥控安卓小车系统的开发与实现过程。该项目巧妙地将被淘汰的安卓旧手机升级转化为车载信息处理单元,赋予其新的生命力,同时融入先进的4G网络技术、流媒体服务以及物联网技术,成功打造出一个集远程操控与实时音视频传输功能于一身的高效率解决方案。该智能小车以其独特的设计思路和强大的功能特性,展现出广泛的应用潜力。无论是作为教育科研领域的实践平台,还是在远程监控、工业巡检、应急救援、无人驾驶技术验证,甚至智能家居与物流等方面,均能发挥重要作用,显著提升了工作效率,降低了人力成本,并有效保障了作业的安全性。该项目积极响应可持续发展号召,通过资源循环利用,成功展示了科技如何助力环保,彰显了技术创新的社会价值。展望未来,随着5G网络技术的广泛应用,这款基于4G网络的远程遥控安卓小车将进一步优化性能,拓展应用场景,为社会各领域带来更加智能化、便捷化的技术服务。
上滑加载中
推荐直播
-
DTT年度收官盛典:华为开发者空间大咖汇,共探云端开发创新
2025/01/08 周三 16:30-18:00
Yawei 华为云开发工具和效率首席专家 Edwin 华为开发者空间产品总监
数字化转型进程持续加速,驱动着技术革新发展,华为开发者空间如何巧妙整合鸿蒙、昇腾、鲲鹏等核心资源,打破平台间的壁垒,实现跨平台协同?在科技迅猛发展的今天,开发者们如何迅速把握机遇,实现高效、创新的技术突破?DTT 年度收官盛典,将与大家共同探索华为开发者空间的创新奥秘。
回顾中 -
GaussDB应用实战:手把手带你写SQL
2025/01/09 周四 16:00-18:00
Steven 华为云学堂技术讲师
本期直播将围绕数据库中常用的数据类型、数据库对象、系统函数及操作符等内容展开介绍,帮助初学者掌握SQL入门级的基础语法。同时在线手把手教你写好SQL。
回顾中 -
算子工具性能优化新特性演示——MatMulLeakyRelu性能调优实操
2025/01/10 周五 15:30-17:30
MindStudio布道师
算子工具性能优化新特性演示——MatMulLeakyRelu性能调优实操
即将直播
热门标签