本帖最后由 gaochy1126 于 2020-12-15 12:36 编辑
#申请原创# @21小跑堂
之前玩航模的时候,飞机飞丢了。设备信号传输不好,飞丢位置找不到了,造成了一定的经济损失,所以想做一个位置跟踪和展示的设备。
这是做的一个简单的框架图。因为设备需要锂电池供电,所以功耗需要降低。
同时数据还需要同步到Web网页数据上,所以WebSocket 是最好的选择。
实现的模式是终端设备通过网络将位置数据传输到服务上,然后保存相关数据,并将数据推送到Web网页上显示。
先来个视频看看效果怎么样。这个是开着车跑了一圈,检测效果怎么样。基本上能够实时传输地址,说明通信的速度和显示的效果都可以的。
https://player.youku.com/embed/XNTAwODMxNTI1Mg==
看到很多的人都在玩ESP32。而且是arduino的开发环境,开发比较简单,而且性能和价格都比较便宜一些。第三方的库文件比较多,不需要自己另外的编写库函数。所以选择了ESP32作为处理器了。ESP32超低功耗, 专为移动设备、可穿戴电子产品和物联网应用而设计,具有业内高水平的低功耗性能,包括精细分辨时钟门控、省电模式和动态电压调整等。ESP32 只需极少的外围器件,即可实现强大的处理性能、可靠的安全性能,和 Wi-Fi & 蓝牙功能。
看到网上有SIM868,sim868是一款体积很小的2G模块,包含GPS GSM GPRS功能。定位精度2.5米左右。这样就节省了使用GPS模块的花费,而且SIM868还可以有网络定位的功能,如果前期GPS信号不好的时候,可以使用网络定位作为信号的补充。
服务器的购买环节就不在这里赘述了。现在新用户的优惠比较多一些。几百块钱可以用一年多,算是自己研究着玩吧。
通信协议选择了MQTT。 (MQTT) 是轻量级基于代理的发布/订阅的消息传输协议,设计思想是开放、简单、轻量、易于实现。小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量。而且在paho-mqtt库中,有一种重要的函数–回调函数。这个在网页开发比较简单了。
在服务器上搭建一个MQTT的服务器,有两种选择一个EMQ X和 mosquitto。MQ X Broker 是基于高并发的 Erlang/OTP 语言平台开发,支持百万级连接和分布式集群架构,发布订阅模式的开源 MQTT 消息服务器。Eclipse Mosquitto是一个开源消息代理,实现了MQTT协议版本3.1和3.1.1。Mosquitto轻量,适用于低功耗单板计算机到完整服务器的所有设备。
经过综合评估,于是在服务器上搭建了一个EMQ 的服务器,因为保存数据到SQL数据库,需要企业级别的才行(价格比较高)。所以选择了另外的方式进行保存:在MQTT服务器上搭建了Web_Hook的方式进行保存,节省了一笔开销。EMQ的安装建议大家百度一下,网上有很多的教程,建议开启账号和密码的验证功能,这样防止数据的泄漏。
页面展示使用百度地图。百度地图的开发比较成熟了,而且API函数非常的多,可以直接调用,性能也不错。
先从显示网页的开发介绍起来。
百度地图的显示建议搜索百度API代码非常的多。推荐地址http://lbsyun.baidu.com/index.php?title=jspopularGL 。查看demo的地址http://lbsyun.baidu.com/jsdemo.htm#a1_2
需要注意的是:GPS的经纬度转换到百度地图,需要多次转换才行。
目前国内主要有以下三种坐标系:
WGS84:为一种大地坐标系,也是目前广泛使用的GPS全球卫星定位系统使用的坐标系。
GCJ02:又称火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。
BD09:为百度坐标系,在GCJ02坐标系基础上再次加密。其中bd09ll表示百度经纬度坐标,bd09mc表示百度墨卡托米制坐标。
附上一个JavaScript坐标系转换的代码。 var GPS = {
PI: 3.14159265358979324,
x_pi: 3.14159265358979324 * 3000.0 / 180.0,
delta: function (lat, lon)
{
var a = 6378245.0; // a: 卫星椭球坐标投影到平面地图坐标系的投影因子。
var ee = 0.00669342162296594323; // ee: 椭球的偏心率。
var dLat = this.transformLat(lon - 105.0, lat - 35.0);
var dLon = this.transformLon(lon - 105.0, lat - 35.0);
var radLat = lat / 180.0 * this.PI;
var magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
var sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * this.PI);
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * this.PI);
return { 'lat': dLat, 'lon': dLon };
},
//WGS-84 to GCJ-02
gcj_encrypt: function (wgsLat, wgsLon)
{
if (this.outOfChina(wgsLat, wgsLon))
return { 'lat': wgsLat, 'lon': wgsLon };
var d = this.delta(wgsLat, wgsLon);
return { 'lat': wgsLat + d.lat, 'lon': wgsLon + d.lon };
},
//GCJ-02 to WGS-84
gcj_decrypt: function (gcjLat, gcjLon)
{
if (this.outOfChina(gcjLat, gcjLon))
return { 'lat': gcjLat, 'lon': gcjLon };
var d = this.delta(gcjLat, gcjLon);
return { 'lat': gcjLat - d.lat, 'lon': gcjLon - d.lon };
},
//GCJ-02 to WGS-84 exactly
gcj_decrypt_exact: function (gcjLat, gcjLon)
{
var initDelta = 0.01;
var threshold = 0.000000001;
var dLat = initDelta, dLon = initDelta;
var mLat = gcjLat - dLat, mLon = gcjLon - dLon;
var pLat = gcjLat + dLat, pLon = gcjLon + dLon;
var wgsLat, wgsLon, i = 0;
while (1)
{
wgsLat = (mLat + pLat) / 2;
wgsLon = (mLon + pLon) / 2;
var tmp = this.gcj_encrypt(wgsLat, wgsLon)
dLat = tmp.lat - gcjLat;
dLon = tmp.lon - gcjLon;
if ((Math.abs(dLat) < threshold) && (Math.abs(dLon) < threshold))
break;
if (dLat > 0) pLat = wgsLat; else mLat = wgsLat;
if (dLon > 0) pLon = wgsLon; else mLon = wgsLon;
if (++i > 10000) break;
}
//console.log(i);
return { 'lat': wgsLat, 'lon': wgsLon };
},
//GCJ-02 to BD-09
bd_encrypt: function (gcjLat, gcjLon)
{
var x = gcjLon, y = gcjLat;
var z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * this.x_pi);
var theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * this.x_pi);
bdLon = z * Math.cos(theta) + 0.0065;
bdLat = z * Math.sin(theta) + 0.006;
return { 'lat': bdLat, 'lon': bdLon };
},
//BD-09 to GCJ-02
bd_decrypt: function (bdLat, bdLon)
{
var x = bdLon - 0.0065, y = bdLat - 0.006;
var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * this.x_pi);
var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * this.x_pi);
var gcjLon = z * Math.cos(theta);
var gcjLat = z * Math.sin(theta);
return { 'lat': gcjLat, 'lon': gcjLon };
},
//WGS-84 to Web mercator
//mercatorLat -> y mercatorLon -> x
mercator_encrypt: function (wgsLat, wgsLon)
{
var x = wgsLon * 20037508.34 / 180.;
var y = Math.log(Math.tan((90. + wgsLat) * this.PI / 360.)) / (this.PI / 180.);
y = y * 20037508.34 / 180.;
return { 'lat': y, 'lon': x };
},
// Web mercator to WGS-84
// mercatorLat -> y mercatorLon -> x
mercator_decrypt: function (mercatorLat, mercatorLon)
{
var x = mercatorLon / 20037508.34 * 180.;
var y = mercatorLat / 20037508.34 * 180.;
y = 180 / this.PI * (2 * Math.atan(Math.exp(y * this.PI / 180.)) - this.PI / 2);
return { 'lat': y, 'lon': x };
},
// two point's distance
distance: function (latA, lonA, latB, lonB)
{
var earthR = 6371000.;
var x = Math.cos(latA * this.PI / 180.) * Math.cos(latB * this.PI / 180.) * Math.cos((lonA - lonB) * this.PI / 180);
var y = Math.sin(latA * this.PI / 180.) * Math.sin(latB * this.PI / 180.);
var s = x + y;
if (s > 1) s = 1;
if (s < -1) s = -1;
var alpha = Math.acos(s);
var distance = alpha * earthR;
return distance;
},
outOfChina: function (lat, lon)
{
if (lon < 72.004 || lon > 137.8347)
return true;
if (lat < 0.8293 || lat > 55.8271)
return true;
return false;
},
transformLat: function (x, y)
{
var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * this.PI) + 40.0 * Math.sin(y / 3.0 * this.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * this.PI) + 320 * Math.sin(y * this.PI / 30.0)) * 2.0 / 3.0;
return ret;
},
transformLon: function (x, y)
{
var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * this.PI) + 40.0 * Math.sin(x / 3.0 * this.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * this.PI) + 300.0 * Math.sin(x / 30.0 * this.PI)) * 2.0 / 3.0;
return ret;
}
};
加载百度地图的关键代码 <script type="text/javascript" src="//api.map.baidu.com/api?v=2.0&ak=您的密钥"></script>其他的代码都是配置代码了。
然后再JavaScript编写MQTT的协议就行了。
下面开始做MQTT初始化;XXXX为服务器地址,需要配置服务器为Wss,即需要证书。
client = new Paho.MQTT.Client("XXXXX", Number(8084), GenNonDuplicateID());
连接MQTT,并订阅 Pos的位置。
//初始化客户端选项 conn_opts
client.connect({
onSuccess: onConnect
}
);
//连接服务器并注册连接成功处理事件
function onConnect() {
console.log("onConnected");
client.subscribe("pos", {
qos: 0
}
);
}
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
这里是添加标志的代码
var myIcon = new BMap.Icon("__INDEX__/img/car.gif", new BMap.Size(20, 40));
var point = new BMap.Point(lon, lat);
var marker = new BMap.Marker(point, {
icon: myIcon
});
未完待续。。。。。
|