本帖最后由 gandonggandong 于 2019-12-24 08:21 编辑
GPS定位发展很快,随着物联网的推广,衍生出很多GPS相关的应用万变不离其宗,主要应用的技术是GPS信号的采集、解析、将GPS信号通过4G或GPRS等传至服务器,客户端与服务器通信,获取设备位置信息,实现定位轨迹跟踪等。大概都包含这三大部分,设备、服务器后台、客户端。
设备如下
C#编写服务器后台
C#客户端
下面逐个介绍
设备采用STM32串口接收GPS模块输出的位置信息,解析出经纬度,通过AT指令控制GPRS模块与服务器通信。硬件电路如下
单片机解析GPS模块的位置信息,一般解析GPRMC这一条就可以,解析的方法很多,可以用找字头,数逗号的方式例如下面的信息。
$GPRMC,092927.000,A,2235.9058,N,11400.0518,E,0.000,74.11,151216,,D*49
$GPVTG,74.11,T,,M,0.000,N,0.000,K,D*0B
$GPGGA,092927.000,2235.9058,N,11400.0518,E,2,9,1.03,53.1,M,-2.4,M,0.0,0*6B
$GPGSA,A,3,29,18,12,25,10,193,32,14,31,,,,1.34,1.03,0.85*31
$GPGSV,3,1,12,10,77,192,17,25,59,077,42,32,51,359,39,193,49,157,36*48
$GPGSV,3,2,12,31,47,274,25,50,46,122,37,18,45,158,37,14,36,326,18*70
$GPGSV,3,3,12,12,24,045,45,26,17,200,18,29,07,128,38,21,02,174,*79
- char *gpsdata;
- int i,count;
- if(USART_GetiTStatus(USART2,USART_IT_IDLE) == SET)
- {
- USART2->SR;
- USART2->DR;
- USART_ClearITPendingBit(USART2,USART_IT_IDLE);
- DMA_Cmd(DMA1_Channel6,DISABLE); //??DMA
- U2_Rx_Counter = 1024 - DMA_GetCurrDataCounter(DMA1_Channel6); //??????????
- gpsdata = strstr(U2_RX_data,"$GNRMC");
- if(gpsdata)
- {
- for(i=0;i
- {
- if(gpsdata[i]==',')
- {
- count++;
- if(count==2)
- {
-
- if(gpsdata[i+1]=='A')
- {
- gps_flag=1;
-
- }
- else
- {
- gps_n=0;
- gps_e=0;
- break;
- }
- }
- if(gps_flag==1)
- {
- if((count==3)&&(gpsdata[i+1]!=','))
- {
- gps_n=((gpsdata[i+1]-0x30)*100000+(gpsdata[i+2]-0x30)*10000+((gpsdata[i+3]-0x30)*100000+(gpsdata[i+4]-0x30)*10000+(gpsdata[i+6]-0x30)*1000+(gpsdata[i+7]-0x30)*100+(gpsdata[i+8]-0x30)*10+(gpsdata[i+9]-0x30))/60);
- }
- if((count==5)&&(gpsdata[i+1]!=','))
- {
- gps_e=((gpsdata[i+1]-0x30)*1000000+(gpsdata[i+2]-0x30)*100000+(gpsdata[i+3]-0x30)*10000+((gpsdata[i+4]-0x30)*100000+(gpsdata[i+5]-0x30)*10000+(gpsdata[i+7]-0x30)*1000+(gpsdata[i+8]-0x30)*100+(gpsdata[i+9]-0x30)*10+(gpsdata[i+10]-0x30))/60);
- }
- if(count>=13)
- {
- count=0;
- gps_flag=0;
- break;
- }
- }
- }
- }
- }
- memset(U2_RX_data,0, 1024);
- DMA1_Channel6->CNDTR = 1024; //??????????
- DMA_Cmd(DMA1_Channel6,ENABLE); //??DMA
- }
复制代码
单片机与服务器通过AT指令控制GPRS模块与服务器通信,AT指令是比较难的,开发过的会有感受,AT指令返回的状态比较多,并不是返回一种或两种结果。不但要按照正常流程一步一步发送AT指令,还要有错误返回处理。AT指令挺复杂的,特别适合用状态机处理这些流程。
- switch (M26_info.state)
- {
- case GPRS_state_Poweroff :
- if(AT_Delay_Timer>5)
- {
- GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_SET);
- GPIO_SetBits(GPIOA,GPIO_Pin_1);
- GPIO_ResetBits(GPIOA, GPIO_Pin_7);
- GPIO_ResetBits(GPIOA, GPIO_Pin_4); // Sleep
- }
- if(AT_Delay_Timer>25)
- {
- GPIO_ResetBits(GPIOA,GPIO_Pin_1);
- GPIO_SetBits(GPIOA, GPIO_Pin_7);
- }
- if(AT_Delay_Timer>45)
- {
- GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_RESET);
- M26_info.state=GPRS_state_AT;
- AT_Timeslimite=0;
- AT_Delay_Timer=0;
- AT_overtime=0;
- ZC_DATA_flog=1;
- }
- break;
- case GPRS_state_AT :
- M26_ATTR(5,30,50,"AT+IPR=115200&Wrn","OK","","","","",GPRS_state_CREG,GPRS_state_AT);
- break;
- case GPRS_state_CREG :
- M26_ATTR(5,30,100,"AT+CREG?rn","+CREG: 0,1","+CREG: 0,5","+CREG: 0,2","+CREG: 0,3","+CREG: 0,4",GPRS_state_CSQ,GPRS_state_CREG);
- break;
- case GPRS_state_CSQ :
- M26_ATTR(2,30,20,"AT+CSQrn","+CSQ:","","","","",GPRS_state_QIDNSIP,GPRS_state_CSQ);
- case GPRS_state_QIDNSIP:
- M26_ATTR(2,30,5,"AT+QIFGCNT=0rn","OK","","","","",GPRS_state_QIDEACT,GPRS_state_Poweroff);
- break;
- case GPRS_state_QIDEACT:
- M26_ATTR(2,2,50,"AT+QICSGP=1,"CMMTM"rn","OK","","ERROR","","",GPRS_state_QIREGAPP,GPRS_state_Poweroff);
- break;
- case GPRS_state_QIREGAPP:
- M26_ATTR(2,2,50,"AT+QIREGAPPrn","OK","","ERROR","","",GPRS_state_QIACT,GPRS_state_Poweroff);
- break;
- case GPRS_state_QIACT:
- M26_ATTR(10,2,300,"AT+QIACTrn","OK","","ERROR","","",GPRS_state_QIOPEN,GPRS_state_Poweroff);
- break;
- case GPRS_state_QIOPEN:
- M26_ATTR(20,10,50,"AT+QIOPEN="TCP","122.51.33.246","8888"rn","CONNECT OK","ALREADY CONNECT","ERROR","CONNECT FAIL","",GPRS_state_QISEND,GPRS_state_QIACT);
- break;
- case GPRS_state_QISTAT:
- M26_ATTR(10,10,500,"AT+QISTATrn","STATE: CONNECT OK","","STATE: IP INITIAL","","",GPRS_state_QISEND,GPRS_state_QIOPEN);
- break;
- case GPRS_state_QISEND:
- ////GPRS_DATA_flog=1;
- M26_ATTR(50,20,100,"AT+QISEND=27rn",">",">","ERROR","ERROR","ERROR",GPRS_state_QISENDDATA,GPRS_state_QISENDDATA);
- break;
- case GPRS_state_QISENDDATA:
- M26_senddata(10,10,100,"12345678","SEND OK","","ERROR","ERROR","ERROR",GPRS_state_QISEND,GPRS_state_Poweroff);
- break;
-
- default: ;
- }
复制代码
服务器后台
用C#编写服务器后台程序,他负责接收所有设备发来数据,把与客户端有关的设备数据转发给客户端。这样局域网里的客户端可以通过网关获取服务器的数据。服务器程序用到多线程技术,可以同时处理多个设备发来的数据。用到了Dictionary数据类型,实现设备ID与socket对应,关键代码如下
- private void ReceiveClient(object obj)
- {
- Socket _ClientSocket = (Socket)obj;
- while (true)
- {
- try
- {
- byte[] result = new byte[1024];
- int receiveLength = _ClientSocket.Receive(result);
- string clientMessage = Encoding.UTF8.GetString(result, 0, receiveLength);
- string Destination_Address;
- string Data_Rcve;
- if (receiveLength == 0)
- {
- ClientSocketDictionary.Remove(_ClientSocket);
- _ClientSocket.Shutdown(SocketShutdown.Both);
- _ClientSocket.Close();
- SetText2box();
- break;
- }
- else
- {
- if ((result[0] == '
- 客户端
- C#编写客户端程序,调用百度地图API,实现地图打标,绘制轨迹。客户端工作流程是这样的,首先向服务器发送注册自身ID,然后向服务器获取相关设备ID的数据,解析数据,调用百度地图API实现打标定位等。关键代码如下
- [code]private void button1_Click(object sender, EventArgs e)
- {
- try
- {
- clientScoket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- clientScoket.Connect(new IPEndPoint(IPAddress.Parse(textBox1.Text.ToString()), 8888));
- t = new Thread(ReceiveMessage);//开启线程执行循环接收消息
- t.IsBackground=true;
- t.Start();
- button1.Text = "已连接服务器";
- button1.Enabled = false;
- }
- catch (Exception ex)
- {
- SetText3box(ex);
- button1.Text = "无法连接服务器";
- button1.Enabled = true;
- }
-
- }
-
- public delegate void CallSetTextbox(string ms);
- void ReceiveMessage()//接收消息
- {
-
- string Destination_Address;
- string Data_Rcve;
- send_flog = 1;
- while (true)
- {
- if (clientScoket.Connected == true)
- {
-
-
- if (send_flog == 1)
- {
- send_flog = 0;
- message = "$1" + textBox4.Text;
- SendMessage(message);
- }
- int length = 0;
- try
- {
- length = clientScoket.Receive(data);
- }
- catch (Exception e)
- {
- SetText3box(e);
-
- break;
- }
- if (length != 0)
- {
- message = Encoding.UTF8.GetString(data, 0, length);
- if ((data[0] == '
- ) && (result[1] == '2'))//发送数据 目的地址
- {
- Destination_Address = clientMessage.Substring(2, 10);
- Data_Rcve = clientMessage.Substring(12, clientMessage.Length-12);
- temp = DateTime.Now.ToString()+ " Dest is : " + Destination_Address + " Data is : " + Data_Rcve;
- SetTextbox();
- SendMessage(clientMessage);
- }
- if ((result[0] == '
- 客户端
- C#编写客户端程序,调用百度地图API,实现地图打标,绘制轨迹。客户端工作流程是这样的,首先向服务器发送注册自身ID,然后向服务器获取相关设备ID的数据,解析数据,调用百度地图API实现打标定位等。关键代码如下
- [ DISCUZ_CODE_52 ]
- ) && (result[1] == '1'))//注册ID 源地址
- {
- if (!ClientSocketDictionary.ContainsKey(_ClientSocket))
- {
- ClientSocketDictionary.Add(_ClientSocket, clientMessage.Substring(2, 10));
- SetText2box();
- }
- else
- {
- ClientSocketDictionary.Remove(_ClientSocket);
- ClientSocketDictionary.Add(_ClientSocket, clientMessage.Substring(2, 10));
- SetText2box();
- }
- }
- }
-
- }
- catch (Exception e)
- {
- ClientSocketDictionary.Remove(_ClientSocket);
- _ClientSocket.Shutdown(SocketShutdown.Both);
- _ClientSocket.Close();
- SetText2box();
- SetText3box(e);
- break;
- }
- }
- }
- public void SendMessage(string msg)
- {
- if (msg == string.Empty || this.ClientSocketDictionary.Count <= 0) return;
- string Destination_Address = msg.Substring(2, 10);
- string Data_Rcve = msg.Substring(12, msg.Length-12);
- msg = "$3" + Destination_Address + Data_Rcve;
-
- try
- {
- foreach (KeyValuePair kvp in ClientSocketDictionary)
- {
- if(kvp.Value.Substring(0,10)== Destination_Address)
- {
- kvp.Key.Send(Encoding.UTF8.GetBytes(msg));
- }
- }
- }
- catch (Exception e)
- {
- SetText3box(e);
- }
- }
复制代码
客户端
C#编写客户端程序,调用百度地图API,实现地图打标,绘制轨迹。客户端工作流程是这样的,首先向服务器发送注册自身ID,然后向服务器获取相关设备ID的数据,解析数据,调用百度地图API实现打标定位等。关键代码如下
- private void button1_Click(object sender, EventArgs e)
- {
- try
- {
- clientScoket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- clientScoket.Connect(new IPEndPoint(IPAddress.Parse(textBox1.Text.ToString()), 8888));
- t = new Thread(ReceiveMessage);//开启线程执行循环接收消息
- t.IsBackground=true;
- t.Start();
- button1.Text = "已连接服务器";
- button1.Enabled = false;
- }
- catch (Exception ex)
- {
- SetText3box(ex);
- button1.Text = "无法连接服务器";
- button1.Enabled = true;
- }
-
- }
-
- public delegate void CallSetTextbox(string ms);
- void ReceiveMessage()//接收消息
- {
-
- string Destination_Address;
- string Data_Rcve;
- send_flog = 1;
- while (true)
- {
- if (clientScoket.Connected == true)
- {
-
-
- if (send_flog == 1)
- {
- send_flog = 0;
- message = "$1" + textBox4.Text;
- SendMessage(message);
- }
- int length = 0;
- try
- {
- length = clientScoket.Receive(data);
- }
- catch (Exception e)
- {
- SetText3box(e);
-
- break;
- }
- if (length != 0)
- {
- message = Encoding.UTF8.GetString(data, 0, length);
- if ((data[0] == '
- ) && (data[1] == '3'))//配置ID
- {
- Destination_Address = message.Substring(2, 10);
- Data_Rcve = message.Substring(12, message.Length - 12);
- Longitude = Data_Rcve.Substring(0, 8);
- Latitude = Data_Rcve.Substring(8, 7);
- if ((Longitude != "000.0000") && (Latitude != "00.0000"))
- {
- SetTextbox(DateTime.Now.ToString() + " Dest is : " + Destination_Address + " Data is : " + Data_Rcve);
- }
- SetText2box(DateTime.Now.ToString() + " Dest is : " + Destination_Address + " Data is : " + Data_Rcve);
- }
- }
-
-
- }
- else
- {
- break;
- }
- }
复制代码
|