在中移动标准板上利用TencentOS tiny实现GPS追踪功能
因为收到的TencentOS tiny内测开发板只有ESP8266,利用WiFi来实现GPS跟踪有点不太现实。而最近正好从中移动手里薅了一个标准开发板(如下图),上面自带GSM模组M6312,就想着把tos搞到这个开发板上来利用,M6312接入网络来实现地理位置上报。
移植的过程中除了搞定tos在MAC系统的STM32CubeIDE上的编译问题外,最大的一个麻烦就是当前开发库还不支持M6312,所以只能自己动手现撸一个。期间遇到的一个坑是在接收数据的过程中,除了你要获取完所有的数据外,额外的数据也必需清理干净,这个问题我搞了很久。现说明如下: M6312在收到数据后返回的格式如下:
<IPDATA: 4>\r\nDATA\r\nOK\r\n
其中4是数据长度,也就是说按上例,在跳过”\r\n”后收完4字节数据”DATA”后还余下”\r\nOK\r\n”这些数据,这些数据必需要清理掉,不能放任不管。
__STATIC__ void m6312_incoming_data_process(void)
{
// \r\nDATA\r\nOK\r\n
//
// "= '0' && data <= '9') {
data_len = data_len * 10 + (data - '0');
}
}
if (data_len > sizeof(incoming_data_buffer)) {
discard_suffix = 0;
data_len = sizeof(incoming_data_buffer);
}
// 获取数据
if (tos_at_uart_read(incoming_data_buffer, data_len) != data_len) {
return;
}
// 在把数据传进内核之前,必需先把垃圾数据清理干净
// discard suffix "\r\nOK\r\n"
while(discard_suffix && (tos_at_uart_read_timed(&data, 1, 1000) == 1)) { }
tos_at_channel_write(0, incoming_data_buffer, data_len);
}
M6312的使用方法略去不表,类似ESP8266等。这部分代码暂时还没有合进主仓库,需要的同学可能要稍等一下,不会太久。 接下来就是接收GPS数据。GPS模块用的是ATGM336H,因M6312占用的是USART2,所以GPS接到USART3。而这个GPS模块有个问题就是一直往外吐数据,没法禁止,数据量还大,如果每来个字符都中断处理一下,MCU就全忙这事了,因此对GPS数据采用DMA收取分析。 先定义一下用到的相关数据结构
#define USART_RX_BUFSZ 1024
typedef struct {
uint32_t len : 24;
uint32_t end : 8;
DMA_HandleTypeDef *dmarx;
uint8_t buf[USART_RX_BUFSZ];
} UsartDmaData_t;
#define DMA_HUART huart3
#define HDMA_USART_RX hdma_usart3_rx
初始化DMA
void Init_Dma_Recv() {
__HAL_UART_ENABLE_IT(&DMA_HUART, UART_IT_IDLE);
dma_data.dmarx = &HDMA_USART_RX;
HAL_UART_Receive_DMA(&DMA_HUART, dma_data.buf, USART_RX_BUFSZ);
}
收到DMA IDLE中断表示数据接收完成了
void DmaReceiveUsartData(UART_HandleTypeDef *huart) {
UsartDmaData_t *data = &dma_data;
uint32_t flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE);
if(flag == RESET) {
return;
}
//软件清空空闲中断标志位
volatile uint32_t tmp;
tmp = huart->Instance->SR;
tmp = huart->Instance->DR;
__HAL_UART_CLEAR_IDLEFLAG(huart);
HAL_UART_DMAStop(huart);
if(0 == data->dmarx) {
return ;
}
tmp = data->dmarx->Instance->CNDTR;
data->len = USART_RX_BUFSZ - tmp;
data->end = 1;
}
接下来就是解析GPS数据的时候了
int parse_gps(char *data, int len, char *lat, char *lng) {
*lat = *lng = 0;
// 找到$GNRMC开头
char *gpsline = strnstr(data, "$GNRMC", len);
if(gpsline == 0) {
return -1;
}
len = gpsline - data;
// 找到$GNRMC行尾
char *end = strnstr(gpsline, "\r\n", len);
if(end == 0) {
return -2;
}
// 断尾
*end = 0;
len = end - gpsline;
printf("%s\n", gpsline);
// 判断是否是有效数据
gpsline = strnstr(gpsline, ",A,", len);
if(gpsline == 0) {
return -3;
}
// skip ",A,"
gpsline += 3;
len = end - gpsline;
// 合理值在50左右
if(len <= 40) {
return -4;
}
int commaInx = 0;
for(int i=0; i
把收到的数据送到MQTT任务,发送到服务器。 在管理平台创建一个GPS产品,创建两个设备,一个名叫ChinaMobileStandardBoard对应该中移动开发板,一个叫Server,它的作用见后文。
再创建两条规则:
- 把xx/ChinaMobileStandardBoard/gps topic的lat,lng两个字段转发到xx/Server/gps的topic
- 把xx/Server/cmd 的topic里的cmd字段转发到xx/ChinaMobileStandardBoard/cmd的topic
接下来就是作为主控端,接收需要的数据的时候了,这里我没有用腾讯云的SDK,接下来就是作为主控端,接收需要的数据的时候了,这里我没有用腾讯云的SDK,而是用的go语言的eclipse/paho.mqtt.golang,这就是前面说的Server的设备。
代码我全都贴出来吧:
package main
import (
"encoding/json"
"fmt"
MQTT "github.com/eclipse/paho.mqtt.golang"
"html/template"
"log"
"math"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
var qos = 0
var chcmd chan string
var gps string
func Pub(client MQTT.Client) {
for {
pub_topic := "jiubugaoshuni/Server/cmd"
cmd := <-chcmd
payload := fmt.Sprintf("{\"cmd\":%s}", cmd)
fmt.Printf("publish payload: %s\n", payload)
token := client.Publish(pub_topic, byte(qos), false, payload)
token.Wait()
}
}
func Sub(client MQTT.Client, opts *MQTT.ClientOptions) {
sub_topic := "jiubugaoshuni/Server/gps"
chgps := make(chan [2]string)
for {
if token := client.Subscribe(sub_topic, byte(qos),
func(client MQTT.Client, msg MQTT.Message) {
fmt.Println(msg.Topic(), string(msg.Payload()))
chgps <- [2]string{msg.Topic(), string(msg.Payload())}
}); token.Wait() && token.Error() != nil {
fmt.Println(token.Error())
os.Exit(1)
}
incoming := <-chgps
fmt.Printf("RECEIVED TOPIC: %s MESSAGE: %s\n", incoming[0], incoming[1])
gps = incoming[1]
}
}
type GPS struct {
Lat string `json:"lat"`
Lng string `json:"lng"`
Latf string
Lngf string
}
func calc_gps_degree(s string) string {
v, _ := strconv.ParseFloat(s, 64)
v /= 100.0
df := math.Floor(v)
mf := v - df
mf *= 100
df = df + mf/60.0
degree := fmt.Sprintf("%12.8f", df)
return degree
}
func SyncGpsHandler(w http.ResponseWriter, r *http.Request) {
var g GPS
json.Unmarshal([]byte(gps), &g)
if len(g.Lat) > 0 {
g.Lat = g.Lat[:len(g.Lat)-1]
g.Latf = calc_gps_degree(g.Lat)
}
if len(g.Lng) > 0 {
g.Lng = g.Lng[:len(g.Lng)-1]
g.Lngf = calc_gps_degree(g.Lng)
}
fmt.Printf("> %s %s %s %s %s\n", gps, g.Lat, g.Lng, g.Latf, g.Lngf)
cmd := exec.Command("python", "./gps.py", g.Lngf, g.Latf)
buf, _ := cmd.Output()
fmt.Printf("# %s\n", buf)
//fmt.Fprintf(w, "{\"lat\":%s, \"lng\":%s}", g.Latf, g.Lngf)
fmt.Fprintf(w, string(buf))
}
func GpsHandler(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("./views/gps.html")
if err != nil {
fmt.Fprintf(w, "parse template error: %s", err.Error())
return
}
t.Execute(w, nil)
}
func CmdHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
values := make(map[string]string)
for k, v := range r.Form {
val := strings.Join(v, "")
values[k] = val
}
cmd := values["cmd"]
select {
case chcmd <- cmd:
default:
}
fmt.Fprintf(w, "OK")
}
func http_server() {
http.HandleFunc("/Gps", GpsHandler)
http.HandleFunc("/Cmd", CmdHandler)
err := http.ListenAndServe(":7777", nil)
for {
log.Print("http coroutine exited")
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
}
func https_server() {
http.HandleFunc("/SyncGps", SyncGpsHandler)
http.HandleFunc("/Gps", GpsHandler)
http.HandleFunc("/Cmd", CmdHandler)
http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, r.URL.Path[1:])
})
for {
err := http.ListenAndServeTLS(":7777", "./xxxx.xyz.crt", "./xxxx.xyz.key", nil)
log.Print("https coroutine exited")
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
}
func main() {
broker := "tcp://111.230.189.156:1883"
password := "meiyoumima;hmacsha1"
user := "jiubugaoshuniServer;21010406;12365;4294967295"
clientid := "jiubugaoshuniServer"
chcmd = make(chan string, 1)
opts := MQTT.NewClientOptions()
opts.AddBroker(broker)
opts.SetClientID(clientid)
opts.SetUsername(user)
opts.SetPassword(password)
opts.SetCleanSession(false)
client := MQTT.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
go Pub(client)
go Sub(client, opts)
go https_server()
for {
time.Sleep(1 * time.Second)
}
client.Disconnect(250)
}
GPS数据不能直接用,先要转国测局标准,如果是用的百度地图还要再转相应的百度标准,实在懒得写GO语言的转换代码了,加之之前有写好的python代码,就直接拿来调用了。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import math
import sys
from math import pi
a = 6378245.0;
ee = 0.00669342162296594323;
class Storage(dict) :
def __getattr__(self, key) :
try :
return self[key]
except KeyError, k:
raise AttributeError, k
def __setattr__(self, key, value) :
self[key] = value
def __delattr__(self, key) :
try :
del self[key]
except KeyError, k:
raise AttributeError, k
def __repr__(self) :
return ''
class GPS(Storage) :
def __init__(self, lng = 0.0, lat = 0.0) :
self.lng = lng
self.lat = lat
gps_data = []
def transformLng(x, y) :
ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x));
ret += (20.0 * math.sin(6.0 * x * pi) + 20.0 * math.sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * math.sin(x * pi) + 40.0 * math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
ret += (150.0 * math.sin(x / 12.0 * pi) + 300.0 * math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
return ret;
def transformLat(x, y) :
ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x));
ret += (20.0 * math.sin(6.0 * x * pi) + 20.0 * math.sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * math.sin(y * pi) + 40.0 * math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
ret += (160.0 * math.sin(y / 12.0 * pi) + 320 * math.sin(y * pi / 30.0)) * 2.0 / 3.0;
return ret;
def wgs82_to_gcj02(lng, lat) :
dLat = transformLat(lng - 105.0, lat - 35.0)
dLng = transformLng(lng - 105.0, lat - 35.0)
radLat = lat / 180.0 * pi
magic = math.sin(radLat)
magic = 1 - ee * magic * magic
sqrtMagic = math.sqrt(magic)
dLng = (dLng * 180.0) / (a / sqrtMagic * math.cos(radLat) * pi)
dLat = (dLat * 180.0) / ((a * (1 -ee)) / (magic * sqrtMagic) * pi)
gcjLng = lng + dLng
gcjLat = lat + dLat
x = GPS(gcjLng, gcjLat)
#print(lng, lat)
#print(x.lng, x.lat)
return x
def gcj02_to_bd09(lng, lat) :
x = lng
y = lat
z = math.sqrt(x * x + y * y) + 0.00002 * math.sin(y * pi);
theta = math.atan2(y, x) + 0.000003 * math.cos(x * pi);
bdLng = z * math.cos(theta) + 0.0065;
bdLat = z * math.sin(theta) + 0.006;
x = GPS(bdLng, bdLat);
return x
def bd09_to_gcj02(lng, lat) :
x = lng - 0.0065
y = lat - 0.006;
z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * pi);
theta = math.atan2(y, x) - 0.000003 * math.cos(x * pi);
gcjLng = z * math.cos(theta);
gcjLat = z * math.sin(theta);
x = GPS(gcjLng, gcjLat)
return x
def out_of_china(lng, lat) :
if lng < 72.004 or lng > 137.8347 :
return True
if lat < 0.8293 or lat > 55.8271 :
return True
return False
def transform(lng, lat) :
if (out_of_china(lng, lat)) :
return GPS(lng, lat)
dLng = transformLng(lng - 105.0, lat - 35.0);
dLat = transformLat(lng - 105.0, lat - 35.0);
radLat = lat / 180.0 * pi;
magic = math.sin(radLat);
magic = 1 - ee * magic * magic;
sqrtMagic = math.sqrt(magic);
dLng = (dLng * 180.0) / (a / sqrtMagic * math.cos(radLat) * pi);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
mgLng = lng + dLng;
mgLat = lat + dLat;
return GPS(mgLng, mgLat);
def gcj02_to_wgs84(lng, lat) :
gps = transform(lng, lat);
wgsLng = lng * 2 - gps.lng;
wgsLat = lat * 2 - gps.lat;
x = GPS(wgsLng, wgsLat);
return x
if __name__ == '__main__':
lng = float(sys.argv[1])
lat = float(sys.argv[2])
g = wgs82_to_gcj02(lng, lat)
b = gcj02_to_bd09(g.lng, g.lat)
print("""{{"lng":"{0}","lat":"{1}"}}""".format(b.lng, b.lat))
sys.exit(0)
GPS跟踪效果,其实很容易做到根据历史数据画出路径线,但是我不想做这事了。