前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【IoT迷你赛】在中移动标准板上利用tos实现GPS追踪器

【IoT迷你赛】在中移动标准板上利用tos实现GPS追踪器

原创
作者头像
kernel
修改2019-09-16 15:08:54
1K0
修改2019-09-16 15:08:54
举报
文章被收录于专栏:腾讯云IoT腾讯云IoT

因为收到的TencentOS tiny内测开发板只有ESP8266,利用WiFi来实现GPS跟踪有点不太现实。而最近正好从中移动手里薅了一个标准开发板(如下图),上面自带GSM模组M6312,就想着把tos搞到这个开发板上来利用,M6312接入网络来实现地理位置上报。

移植的过程中除了搞定tos在MAC系统的STM32CubeIDE上的编译问题外,最大的一个麻烦就是当前开发库还不支持M6312,所以只能自己动手现撸一个。期间遇到的一个坑是在接收数据的过程中,除了你要获取完所有的数据外,额外的数据也必需清理干净,这个问题我搞了很久。现说明如下:

M6312在收到数据后返回的格式如下:

代码语言:txt
复制
<IPDATA: 4>\r\nDATA\r\nOK\r\n

其中4是数据长度,也就是说按上例,在跳过"\r\n"后收完4字节数据"DATA"后还余下"\r\nOK\r\n"这些数据,这些数据必需要清理掉,不能放任不管。

代码语言:txt
复制
__STATIC__ void m6312_incoming_data_process(void)
{
    // <IPDATA: 4>\r\nDATA\r\nOK\r\n
    //
    // "<IPDATA: "    prefix
    // "4"            data length
    // "\r\nOK\r\n"   suffix

    uint8_t data=0;
    int data_len = 0;
    int discard_suffix = 1;

    // 获取数据实际长度,并路过"\r\n"
    while (data != '\n') {
        if (tos_at_uart_read(&data, 1) != 1) {
            return;
        }

        if(data >= '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收取分析。

先定义一下用到的相关数据结构

代码语言:txt
复制
#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

代码语言:txt
复制
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中断表示数据接收完成了

代码语言:txt
复制
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数据的时候了

代码语言:txt
复制
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<len; i++) {
		char c = gpsline[i];
		if(c == ',') {
			commaInx++;
			continue;
		}

		if(commaInx == 0 || commaInx == 1) {
			*(lat++) = c;
			*lat = 0;
		} else if(commaInx == 2 || commaInx == 3) {
			*(lng++) = c;
			*lng = 0;
		} else {
			return 0;
		}
	}

	return -5;
}

把收到的数据送到MQTT任务,发送到服务器。

在管理平台创建一个GPS产品,创建两个设备,一个名叫ChinaMobileStandardBoard对应该中移动开发板,一个叫Server,它的作用见后文。

再创建两条规则:

  1. 把xx/ChinaMobileStandardBoard/gps topic的lat,lng两个字段转发到xx/Server/gps的topic
  1. 把xx/Server/cmd 的topic里的cmd字段转发到xx/ChinaMobileStandardBoard/cmd的topic

接下来就是作为主控端,接收需要的数据的时候了,这里我没有用腾讯云的SDK,而是用的go语言的github.com/eclipse/paho.mqtt.golang,这就是前面说的Server的设备。

代码我全都贴出来吧:

代码语言:txt
复制
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代码,就直接拿来调用了。

代码语言:txt
复制
#!/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 '<Storage ' + dict.__repr__(self) + '>'

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跟踪效果,其实很容易做到根据历史数据画出路径线,但是我不想做这事了。

最后就是最简单的远程控制LED灯了,通过https://xxxx.xxx:7777/Cmd?cmd=x来控制,就不细说了,本来想上传一个视频的,但是还要实名认证,太麻烦,就算了。

附:

申请移动各种开发板的链接: https://open.iot.10086.cn/productservice/onenetdevboard/

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
命令行工具
腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档