- int main(void) {
- char s_Str[256];
- float temperature, humidity;
- u32 last_time0 = 0;
- u32 last_time1 = 0;
- short aacx, aacy, aacz; //加速度原始数据
- short gyrox, gyroy, gyroz; //陀螺仪原始数据
- short temp; //温度
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- Delay_Init();
- USART_Printf_Init(115200);
- lcd_init();
- lcd_set_color(WHITE, GREEN);
- lcd_set_color(BLACK, WHITE);
- uart6_init(115200); //ESP8266
- TIM2_Init(144, 1000);
- AHT10_Init(); //初始化AHT10
- LED_GPIO_Init();
- USART4_CFG();
- DMA_INIT();
- USARTx_CFG(); /* USART INIT */
- USART_DMACmd(UART7, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE);
- GPIO_CFG();
- GPIO_WriteBit(GPIOA, GPIO_Pin_7, RESET); //进入 AT
- GPIO_WriteBit(GPIOC, GPIO_Pin_13, SET); //enable CH9141
- Delay_Ms(1000);
- GPIO_WriteBit(GPIOA, GPIO_Pin_7, SET); // 退出AT。可用手机或电脑连接CH9141,测试数据收发
- while (SD_Init()) {
- printf("SD Card Error!\r\n");
- }
- show_sdcard_info();
- test_SD();
- BMP_ShowPicture("test.bmp");
- AliIoT_Parameter_Init();
- init_ESP8266();
- u8 led_cnt = 0;
- while (1) {
- if (get_tDiff(last_time0) > 5000) {
- last_time0 = timer_cnt;
- temperature = AHT10_Read_Temperature();
- humidity = AHT10_Read_Humidity();
- temp = MPU_Get_Temperature(); //获得温度
- MPU_Get_Accelerometer( & aacx, & aacy, & aacz); //获得加速度原始数据
- MPU_Get_Gyroscope( & gyrox, & gyroy, & gyroz); //获得陀螺仪原始数据
- lcd_set_color(BLACK, GREEN);
- lcd_show_string(10, 222, 16, "Temp:%2d'C", (int)(temperature));
- lcd_show_string(120, 222, 16, "Humi:%2d %%", (int)(humidity));
- if (Connnect_flag == 1) {
- sprintf(s_Str, "{"id":"No.1","temp":%0.1f,"Humidity":%0.1f,"Longitude":%0.6f,"Latitude":%0.6f}",
- temperature, humidity, now_lon, now_lat);
- MQTT_PublishData("/property/post", s_Str, 0);
- }
- sprintf(s_Str, "{"t":%d,"h":%d},"Longitude":%0.6f,"Latitude":%0.6f\r\n", (int)(temperature), (int)(humidity), now_lon, now_lat);
- while (uartWriteBLEstr(s_Str) == RESET);
- }
- if (get_tDiff(last_time1) > 500) {
- last_time1 = timer_cnt;
- if (led_cnt > 0) {
- if (led_cnt % 2 == 0) {
- GPIO_ResetBits(GPIOE, GPIO_Pin_11);
- GPIO_ResetBits(GPIOE, GPIO_Pin_12);
- } else {
- GPIO_SetBits(GPIOE, GPIO_Pin_11);
- GPIO_SetBits(GPIOE, GPIO_Pin_12);
- }
- led_cnt--;
- }
- }
- if (USART6_RX_OK == 1) {
- USART6_RX_OK = 0;
- USART6_RX_STA = 0;
- if (strstr(usart6_rxbuf + 4, ""state":1"))
- led_cnt = 10;
- memset(usart6_rxbuf, 0, USART6_MAX);
- }
- if (GPS_RX_OK) {
- GPS_RX_OK = 0;
- GPS_RX_STA = 0;
- GPS_Analysis( & gpsx, (u8 * ) GPS_RX_BUF); //分析字符串
- now_lon=(float)gpsx.longitude/100000;
- now_lat=(float)gpsx.latitude/100000;
- memset(GPS_RX_BUF, 0, sizeof(GPS_RX_BUF));
- printf("Lon:%f ", (float) gpsx.longitude / 100000);
- printf("Lat:%f \r\n ", (float) gpsx.latitude / 100000);
- }
- }
- }
4.3 网络通信
这里使用的ESP8266实现的 MQTT通信协议,使用的是AT指令。
- MQTT(Message Queuing Telemetry Transport) 消息队列遥测传输协议,是一个基于客户端-服务器的消息发布/订阅传输协议。
- 主要的概念有5个:
- Broker 代理:MQTT 服务器
- Publish 发布者:客户端
- Subscribe 订阅者:客户端,可订阅多个 topic
- Topic 主题:消息的类型,订阅主题之后就可以收到该 topic 的消息内容即 payload
- Payload 消息内容:具体的内容
服务区段部署EMQX 开源版。
全球下载量超千万的开源物联网 MQTT 服务器,高效可靠连接海量物联网设备,高性能实时处理消息与事件流数据,可运行在公有云、私有云和混合云上。EMQ X (Erlang/Enterprise/Elastic MQTT Broker) 是基于 Erlang/OTP 平台开发的开源物联网 MQTT 消息服务器。Erlang/OTP 是出色的软实时(Soft-Realtime)、低延时(Low-Latency)、分布式(Distributed) 的语言平台。MQTT 是轻量的(Lightweight)、发布订阅模式(PubSub) 的物联网消息协议。
- 访问 emqx.io (opens new window)或 Github (opens new window)下载要安装的 EMQX 的 tar.gz 包。
- 解压程序包
- tar -zxf emqx-full-package-name.tar.gz
-
- Copied!
-
- 启动 EMQX Broker
- cd ./emqx
- ./bin/emqx start
- ./bin/emqx_ctl status
ESP8266要实现的功能为,具体功能见代码。
- extern void ESP8266_ATSendBuf(uint8_t* buf,uint16_t len); //向ESP8266发送指定长度数据
- extern void ESP8266_ATSendString(char* str); //向ESP8266模块发送字符串
- extern void ESP8266_ExitUnvarnishedTrans(void); //ESP8266退出透传模式
- extern uint8_t ESP8266_ConnectAP(char* ssid,char* pswd); //ESP8266连接热点
- extern uint8_t ESP8266_ConnectServer(char* mode,char* ip,uint16_t port); //使用指定协议(TCP/UDP)连接到服务器
- extern void Reset_ESP8266_RxBuffer(void);
- extern uint8_t Sent2Client(char *context);
- extern uint8_t ESP8266_Status(void);
- uint8_t DisconnectServer(void);
4.4 GPS数据解析
这里使用定时器判断一帧数据是否接收完成。
配置串口3波特率为9600,并启动中断接收数据。
- void USART3_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
- void USART3_IRQHandler(void)
- {
- u8 uartR=0;
- uartR= USART_ReceiveData(USART3);
- flag_rx1=1;
- rx_cnt1=0;
- if(GPS_RX_STA<GPS_MAX_RECV_LEN) //还可以接收数据
- {
- GPS_RX_BUF[GPS_RX_STA++]=uartR; //记录接收到的值
- } else
- {
- GPS_RX_OK=1; //强制标记接收完成
- }
- USART_ClearFlag(USART3, USART_IT_RXNE); //清除标志位;
- }
- }
在定时器内部进行计数,判断数据接收完成。
- void TIM2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
- void TIM2_IRQHandler(void)
- {
- if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查TIM2中断是否发生。
- {
- TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除TIM2的中断挂起位。
- timer_cnt++;
- if(flag_rx1>0)//说明在收数据
- {
- rx_cnt1++;
- if(rx_cnt1>20)
- {
- flag_rx1=0;
- rx_cnt1=0;
- GPS_RX_OK=1; //强制标记接收完成
- }
- }
- }
- }
GPS解析数据,使用的是正点原子的代码。
- //分析GPRMC信息
- //gpsx:nmea信息结构体
- //buf:接收到的GPS数据缓冲区首地址
- void NMEA_GPRMC_Analysis(nmea_msg *gpsx,u8 *buf)
- {
- u8 *p1,dx;
- u8 posx;
- u32 temp;
- float rs;
- p1=(u8*)strstr((const char *)buf,"GNRMC");//"$GPRMC",经常有&和GPRMC分开的情况,故只判断GPRMC.
- posx=NMEA_Comma_Pos(p1,1); //得到UTC时间
- if(posx!=0XFF)
- {
- temp=NMEA_Str2num(p1+posx,&dx)/NMEA_Pow(10,dx); //得到UTC时间,去掉ms
- gpsx->utc.hour=temp/10000;
- gpsx->utc.min=(temp/100)%100;
- gpsx->utc.sec=temp%100;
- }
- posx=NMEA_Comma_Pos(p1,3); //得到纬度
- if(posx!=0XFF)
- {
- temp=NMEA_Str2num(p1+posx,&dx);
- gpsx->latitude=temp/NMEA_Pow(10,dx+2); //得到°
- rs=temp%NMEA_Pow(10,dx+2); //得到'
- gpsx->latitude=gpsx->latitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//转换为°
- }
- posx=NMEA_Comma_Pos(p1,4); //南纬还是北纬
- if(posx!=0XFF)gpsx->nshemi=*(p1+posx);
- posx=NMEA_Comma_Pos(p1,5); //得到经度
- if(posx!=0XFF)
- {
- temp=NMEA_Str2num(p1+posx,&dx);
- gpsx->longitude=temp/NMEA_Pow(10,dx+2); //得到°
- rs=temp%NMEA_Pow(10,dx+2); //得到'
- gpsx->longitude=gpsx->longitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//转换为°
- }
- posx=NMEA_Comma_Pos(p1,6); //东经还是西经
- if(posx!=0XFF)gpsx->ewhemi=*(p1+posx);
- posx=NMEA_Comma_Pos(p1,9); //得到UTC日期
- if(posx!=0XFF)
- {
- temp=NMEA_Str2num(p1+posx,&dx); //得到UTC日期
- gpsx->utc.date=temp/10000;
- gpsx->utc.month=(temp/100)%100;
- gpsx->utc.year=2000+temp%100;
- }
- }
4.5 传感器数据
这里设置的平均5s读取一次数据
- if(get_tDiff(last_time0)>5000){
- last_time0=timer_cnt;
- temperature = AHT10_Read_Temperature();
- humidity = AHT10_Read_Humidity();
- temp=MPU_Get_Temperature(); //获得温度
- MPU_Get_Accelerometer(&aacx,&aacy,&aacz); //获得加速度原始数据
- MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz); //获得陀螺仪原始数据
- lcd_set_color(BLACK,GREEN);
- lcd_show_string(10, 222, 16, "Temp:%2d'C", (int)(temperature));
- lcd_show_string(120, 222, 16,"Humi:%2d %%", (int)(humidity));
- }
4.6 加载地图
使用SD卡内部的图片加载地图并实现。参考正点原子的样式。
- void BMP_ShowPicture(uint8_t *dir)
- {
- FRESULT res;
- FIL fsrc;
- UINT br;
- uint8_t rgb;
- BMP_HeaderTypeDef bmpHeader;
- uint16_t a, x, y, color=0, width, xCount;
- float xRatio, yRatio;
- printf("pictest.bmp\r\n");
- /* 将屏幕刷黑色 */
- lcd_clear(BLACK);
- /* 打开要读取的文件 */
- res = f_open(&fsrc, (const TCHAR*)"test.bmp", FA_READ);
- if(res == FR_OK) //打开成功
- {
- /* 读取BMP文件的文件信息 */
- res = f_read(&fsrc, buffer, sizeof(buffer), &br);
- printf("f_read res :%d\r\n",res);
- /* 将数组里面的数据放入到结构数组中,并排序好 */
- BMP_ReadHeader(buffer, &bmpHeader);
- /* 判断图片的大小超过TFT的大小的话,进行缩小 */
- if(((LCD_W) < bmpHeader.infoHeader.biWidth) ||
- ((LCD_H-20) < bmpHeader.infoHeader.biHeight))
- {
- /* 求出缩小比例 */
- xRatio = (float)(LCD_W) / (float)bmpHeader.infoHeader.biWidth;
- yRatio = (float)(LCD_H-20) / (float)bmpHeader.infoHeader.biHeight;
-
- /* 如果只有一边超出TFT屏的大小的话,不超出部分不进行缩小 */
- if(xRatio > 1)
- {
- xRatio = 1;
- }
- if(yRatio > 1)
- {
- yRatio = 1;
- }
- }
- else //如果图片大小小于TFT屏
- {
- xRatio = 1;
- yRatio = 1;
- }
- /* BMP图片的宽像素数据必须是4的倍数,如果不是那么将其补齐 */
- /* 所以这里是判断BMP图片的横坐标跳转数据。 */
- if(bmpHeader.infoHeader.biWidth % 4)
- {
- width = bmpHeader.infoHeader.biWidth * 3 / 4;
- width += 1;
- width *= 4;
- }
- else
- {
- width = bmpHeader.infoHeader.biWidth * 3;
- }
- /* 初始化应用的值 */
- x = 0;
- y = 0;
- rgb = 0;
- xCount = 0;
- a = bmpHeader.fileHeader.bfOffBits; //去掉文件信息才开始是像素数据
- while(1)
- {
- /* SD卡读取一次数据的长度 */
- while(a < 1024)
- {
- /* 将读取到的24位色转换为16位色 */
- switch (rgb)
- {
- case 0:
- color = buffer[a] >> 3; //B
- break ;
- case 1:
- color += ((uint16_t)buffer[a] << 3) & 0X07E0;//G
- break;
- case 2 :
- color += ((uint16_t)buffer[a] << 8) & 0XF800;//R
- break ;
- default:
- break;
- }
- a++;
- rgb++;
- /* 如果读取完一个像素点,就写到TFT屏上面 */
- if(rgb == 3)
- {
- /* 设置要写入的点 */
- lcd_address_set((u16)((float)x * xRatio + 0.5), LCD_H-20-(u16)((float)y * yRatio + 0.5),
- (u16)((float)x * xRatio + 0.5), LCD_H-20-(u16)((float)y * yRatio + 0.5));
- //LCD_WriteData_Color(color);
- lcd_write_data(color >> 8);
- lcd_write_data(color&0xff);
- rgb =0;
- x++; //X坐标+1
- }
- /* 计数一共读取了多少像素值 */
- xCount++;
- if(xCount >= width) //如果等于一行的像素了,那么换行显示
- {
- xCount = 0;
- x = 0;
- y++;
- }
- }
- /* 继续读取图片数据 */
- res = f_read(&fsrc, buffer, sizeof(buffer), &br);
- a = 0;
- /* 判断手否读取完结,若完结跳出循环 */
- if (res || br < sizeof(buffer))
- {
- break; // error or eof
- }
- }
- }
-
- f_close(&fsrc); //不论是打开,还是新建文件,一定记得关闭
- }
4.7 CH9141通信模块使用的官网提供的例程,使用串口7和DMA通信的方式。
- /*******************************************************************************
- * Function Name : uartWriteBLE
- * Description : send data to BLE via UART7 向蓝牙模组发送数据
- * Input : char * data data to send 要发送的数据的首地址
- * uint16_t num number of data 数据长度
- * Return : RESET UART7 busy,failed to send 发送失败
- * SET send success 发送成功
- *******************************************************************************/
- FlagStatus uartWriteBLE(char * data , uint16_t num)
- {
- //如上次发送未完成,返回
- if(DMA_GetCurrDataCounter(DMA2_Channel8) != 0){
- return RESET;
- }
- DMA_ClearFlag(DMA2_FLAG_TC8);
- DMA_Cmd(DMA2_Channel8, DISABLE ); // 关 DMA 后操作
- DMA2_Channel8->MADDR = (uint32_t)data; // 发送缓冲区为 data
- DMA_SetCurrDataCounter(DMA2_Channel8,num); // 设置缓冲区长度
- DMA_Cmd(DMA2_Channel8, ENABLE); // 开 DMA
- return SET;
- }
- /*******************************************************************************
- * Function Name : uartWriteBLEstr
- * Description : send string to BLE via UART7 向蓝牙模组发送字符串
- * Input : char * str string to send
- * Return : RESET UART7 busy,failed to send 发送失败
- * SET send success 发送成功
- *******************************************************************************/
- FlagStatus uartWriteBLEstr(char * str)
- {
- uint16_t num = 0;
- while(str[num])num++; // 计算字符串长度
- return uartWriteBLE(str,num);
- }
4.9 服务前段设计。
加载百度地图引入paho-mqtt。参考文档地址: https://www.eclipse.org/paho/files/jsdoc/Paho.MQTT.Client.html
JavaScript应用程序使用Paho.MQTT与服务器通信。客户端对象。
大多数应用程序只创建一个Client对象,然后调用其connect()方法,但是如果需要,应用程序可以创建多个Client对象。在这种情况下,每个客户端对象的主机、端口和clientId属性的组合必须不同。
发送、订阅和取消订阅方法被实现为异步JavaScript方法(即使底层协议交换本质上可能是同步的)。这意味着它们通过调用应用程序(通过应用程序提供的有关方法的成功或失败回调函数)来发出完成的信号。这种回调在每个方法调用中最多调用一次,并且不会持续到调用脚本的生命周期之外。
相比之下,Paho.MQTT上定义了一些回调函数,尤其是onMessageArrived。客户端对象。这些方法可能会被多次调用,并且与客户端进行的特定方法调用没有直接关系。
- <script src="https://cdn.bootcss.com/paho-mqtt/1.0.2/mqttws31.min.js" type="text/javascript"></script>
- <script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.min.js"></script>
- <script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=5E5EE28a7615536d1ffe2ce2a3667859"></script>
- <!--加载鼠标绘制工具-->
- <script type="text/javascript" src="http://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.js"></script>
- <link rel="stylesheet" />
- <!--加载检索信息窗口-->
- <script type="text/javascript" src="http://api.map.baidu.com/library/SearchInfoWindow/1.4/src/SearchInfoWindow_min.js"></script>
- <link rel="stylesheet" />
- <script type="text/javascript" src="http://api.map.baidu.com/library/GeoUtils/1.2/src/GeoUtils_min.js"></script>
在百度地图中加载设备标记。
- var point = new BMap.Point(lon, lat);
- var marker = new BMap.Marker(point);
- marker.addEventListener("click",
- function(e) {
- geoc.getLocation(e.point,
- function(rs) {
- var status;
- var content = '<div>'+ '<p>' + '<b>温度:</b>' + temp + '</p>' + '<p>' + '<b>湿度:</b>' +humi + '</p>' + '</div>';//创建信息窗口
- var opts = {
- width: 80,// 信息窗口宽度
- height: 150,// 信息窗口高度
- title: '<h4 style="color: #00a65a"><strong>场面设备'+id+'监控</strong></h4>',// 信息窗口标题
- }
- var infoWindow = new BMap.InfoWindow(content, opts);
- // 创建信息窗口对象
- map.openInfoWindow(infoWindow, e.point);
- // 打开信息窗口
- }
- );
- }
- );
- planeMarkers[1] = marker;
- map.addOverlay(marker);
如何判断该地面设备处于禁区之内的判断代码
- for(var i = 0; i < overlays.length; i++){
- if(BMapLib.GeoUtils.isPointInPolygon(point, overlays[i])) {
- inrange++;
- }else{
- }
- }
发送告警信息
- client.send("/sys/property/set", payload='{ "state":1}', qos=0);
显示效果
5. 最终的显示效果
演示视频
https://www.bilibili.com/video/BV1y44y1Z7uY/?share_source=copy_web