材料清单
- TinkerNode NB-IoT 物联网开发板 X1 链接
开发板简介
物联网与NB-IoT简介
物联网(The Internet of things,IoT)顾名思义,就是物与物相连的互联网。这有两层意思:第一,物联网的核心和基础仍然是互联网,是在互联网基础上的延伸和扩展的网络;第二,其用户端延伸和扩展到了任何物品与物品之间,进行信息交换和通信。
窄带物联网(Narrow Band Internet of Things,NB-IoT)是物联网领域一个新兴的技术,主要用于低移动性、小数据量、对时延不敏感的连接服务,其支持低功耗设备在网络中的数据传输,因此也是一种低功耗广域网(Low Power Wide Area Network,LPWAN)通信技术。相对于被逐渐淘汰的2G通信,NB-IoT具有三大优势:
大连接:海量链接的能力,在同一基站的情况下, NB-IoT 可以比现有无线技术提供 50~100 倍的接入数。一个扇区能够支持,10 万个连接,设备成本与功耗有效降低,网络架构得到优化。广覆盖:在同样的频段下, NB-IoT比现有的网络增益提升了 20 dB,相当于提升了 100 倍的覆盖面积。低功耗:NB-IoT借助 PSM(Power Saving Mode,节电模式)和 eDRX(Extended Discontinuous Reception,超长非连续接收)可实现更长待机。特色
TinkerNode NB-IoT物联网开发板具有:NB-IoT全网通、GPS/北斗双星精确定位、全方位电源管理、太阳能供电、WiFi+蓝牙4.0二合一EPS32主控、微型U盘等六大特色功能,全方位覆盖物联网项目中的各类需要。
Tinkernode接口说明
Tinkernode在本项目的作用
因为Tinkernode搭载了北斗和GPS模块,同时有具有窄带物联网的功能。因此对于无法携带手机进校园的中小学生来说,可以作为可穿戴设备携带(如腕带),走到哪里,通过卫星实时获得的位置信息就可以随时发送出去。
但是经过我的数次尝试,随Tinkernode购买附送的联通NB IoT卡信号不好,打过客服的电话,了解到在深圳自己常呆的地方信号都不太好,“很少有绿色,基本都是灰色的”。因此无法通过sim卡将信息发送出来。所以只能通过Tinkernode的wifi功能连接手机热点将信息发送。后面可以尝试购买其他网络运营商的NB IoT卡来看看网络效果。
因为正值暑假,手头材料有限,只能裸机展示。
步骤1 接线
接天线
GSM天线插在板子的NB孔上,GPS/BeiDou陶瓷有源天线连接在开发板背面标有GNSS的u.FL天线连接孔上。这两个孔都非常小,一定要对准才能插的上去。
说明:如果不需要用到NBIoT,可以不用接GSM天线。
接电池
Tinkernode有一个3.7V锂电池接口,可以接上3.7V的锂电池,带电状态下是给锂电池充电,此时板子上的CHG灯亮红色,脱机运行时可由接在电池接口上的锂电池供电。
注意事项:
GPS/BeiDou陶瓷有源天线的陶瓷面应该面向天空,尽可能在室外使用,否则无法获取有效的定位信息。
某天晚上在学校测试,当时在空旷的走廊上都没有任何卫星信号,索性抱着手提电脑到操场上绕了一圈,虽然NBIoT仍然没有信号,但是能看到的卫星一下子多了起来。如下图,能够看到20颗卫星,GPS9颗,北斗11颗,一共19颗卫星被用到。需要提醒的是北京在东8区,Tinkernode返回的时间需要加上8小时,实际时间是21点41分多。
步骤2 编写程序
使用合适的示例程序
本项目只需要实时的经纬度数据,在Tinkernode示例程序中的GNSS(全球导航卫星系统)中有多个示例程序,我们要弄清楚它们代表的是什么意思。
上图中的GGA、GSA、GSV、RMC、VTG、GLL等是常见的GPS数据协议格式,我们只有理解了这些数据格式,才能对数据进行解析并找到我们需要的数据。
比如GGA返回的数据为:
//$GNGGA,023100.000,2240.63547,N,11403.56956,E,1,22,0.7,230.0,M,0.0,M,,*7D
这些数据代表的含义如下,我们可以根据自己的需要来解析相应字段的数据。
//$GNGGA,023100.000,2240.63547,N,11403.56956,E,1,22,0.7,230.0,M,0.0,M,,*7D
字段0:$GPGGA,语句ID,表明该语句为Global Positioning System Fix Data(GGA)GPS定位信息
字段1:UTC 时间,hhmmss.sss,时分秒格式
字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段3:纬度N(北纬)或S(南纬)
字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段5:经度E(东经)或W(西经)
字段6:GPS状态,0=未定位,1=非差分定位,2=差分定位,3=无效PPS,6=正在估算
字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0)
字段8:HDOP水平精度因子(0.5 - 99.9)
字段9:海拔高度(-9999.9 - 99999.9)
字段10:M
字段11:地球椭球面相对大地水准面的高度
字段12:M
字段13:差分修正的数据龄期
字段14:差分参考站的ID
字段15:CRC
字段16:回车换行
GNSS示例程序
本项目中,我们只需要经纬度信息(及时间信息),几乎所有示例程序我们都可以使用,这里我们使用GNSS示例程序,它返回的有时间信息、位置信息和卫星信息。
/*!
* @file getGNSS.ino
* @brief Print all the GNSS info available in BC20.
*
* @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
* @licence The MIT License (MIT)
* @author [Wuxiao](xiao.wu@dfrobot.com)
* @version V1.0
* @date 2019-10-29
* @get from https://www.dfrobot.com
*/
#include "DFRobot_BC20.h"
DFRobot_BC20 myBC20;
void Display_Location_Information(){
//UTC time of the anchor point
Serial.print("Time:\t\t");
Serial.print(sCLK.Year);
Serial.print("/");
Serial.printf("%02d",sCLK.Month);
Serial.print("/");
Serial.printf("%02d ",sCLK.Day);
Serial.printf(" %02d",sCLK.Hour);
Serial.printf(":%02d",sCLK.Minute);
Serial.printf(":%02d\r\n",sCLK.Second);
Serial.print("Latitude:\t");
Serial.print(sGGNS.LatitudeVal,6);
Serial.print(" deg ");
Serial.println(sRMC.LatitudeDir);
Serial.print("Longitude:\t");
Serial.print(sGGNS.LongitudeVal,6);
Serial.print(" deg ");
Serial.println(sRMC.LongitudeDir);
Serial.print("Altitude:\t");
Serial.print(sGGNS.Altitude,1);
Serial.println(" m");
Serial.print("Speed:\t\t");
Serial.print(sGGNS.Speed);
Serial.println(" km/h");
Serial.print("Heading:\t");
Serial.print(sGGNS.Heading);
Serial.println(" deg");
Serial.print("Status:\t\t");
Serial.println(sGGNS.FixStatus);
Serial.print("PDOP:\t\t");
Serial.println(sGGNS.PDOP);
Serial.print("HDOP:\t\t");
Serial.println(sGGNS.HDOP);
Serial.print("VDOP:\t\t");
Serial.println(sGGNS.VDOP);
Serial.println();
}
void Display_Satellite_Information(){
Serial.print(sSAT.NUM);
Serial.println(" in view.");
Serial.print(sSAT.USE);
Serial.println(" in used.");
Serial.print("PRN\t");
Serial.print("Elev(deg)\t");
Serial.print("Azim(deg)\t");
Serial.print("SNR(dBHz)\t");
Serial.print("SYS\t");
Serial.println("Used");
for(uint8_t i = 0; i <sSAT.NUM; i++){
Serial.print(sSAT.data[i].PRN);
Serial.print("\t");
Serial.print(sSAT.data[i].Elev);
Serial.print("\t\t");
Serial.print(sSAT.data[i].Azim);
Serial.print("\t\t");
Serial.print(sSAT.data[i].SNR);
Serial.print("\t\t");
Serial.print(sSAT.data[i].SYS);
Serial.print("\t");
Serial.println(sSAT.data[i].Status);
}
}
void setup(){
Serial.begin(115200);
Serial.print("Starting the BC20.Please wait. . . ");
while(!myBC20.powerOn()){
delay(1000);
Serial.print(".");
}
Serial.println("BC20 started successfully !");
Serial.println("check OK");
if(myBC20.getQGNSSC() == OFF){
Serial.println("open QGNSSC");
myBC20.setQGNSSC(ON);
}
}
void loop(){
delay(5000);
myBC20.getQGNSSRD();
Display_Location_Information();
Display_Satellite_Information();
}
改写程序
因为我们使用wifi来进行MQTT连接并发布消息,所以需要添加wifi头文件。
虽然使用Tinkernode发送消息没有20个字符的限制(如下图是在EasyIoT上接收到的Tinkernode发送的消息),但是由于我们在行空板已经进行了数据格式的定义,所以在Tinkernode端我们也需要对经纬度进行编码。即经度、纬度加上100再乘以10的6次方,连同家庭成员ID一起发送。
进行编码后再发送的经纬度消息:
使用相同的经纬度数据,家庭成员ID在1~6中随机产生。
头文件
#include "DFRobot_BC20.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include "DFRobot_Iot.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
配置wifi
/*配置WIFI名和密码*/
const char * WIFI_SSID = ""; //填入WiFi SSID
const char * WIFI_PASSWORD = ""; //填入WiFi 密码
配置MQTT参数
/*配置设备的认证信息*/
String Iot_id = ""; //填入Iot_id
String Client_ID = ""; //任意数值,用于区分不同设备
String Iot_pwd = ""; //填入Iot_pwd
/*配置IoT云平台的IP地址和端口号*/
String EasyIot_SERVER = "182.254.130.180";
uint16_t PORT = 1883;
/*配置要推送(Publish)或订阅(Subscribe)的topic*/
const char * pubTopic = ""; //填入发布消息的Topic
定义变量
/*位置信息变量*/
float Latitude; /* 经度原始值*/
float Longitude; /* 纬度原始值*/
int Latitude_int; /* 经度转换为整数值*/
int Longitude_int; /* 纬度转换为整数值*/
int id; /*家庭成员ID号,1:爸爸, 2: 妈妈, 3:爷爷, 4:奶奶, 5: 大宝, 6: 小宝*/
String str; /*存放待发送的MQTT消息:ID号,转换为整数的经纬度值*/
String str1; /*存放转换为字符串的原始的经度*/
String str2; /*存放转换为字符串的原始的纬度*/
String joint; /*用于合并信息发送*/
连接WiFi
/*连接WiFi*/
void connectWiFi() {
WiFi.disconnect();
delay(100);
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected");
Serial.print("IP Adderss: ");
Serial.println(WiFi.localIP());
}
连接到IoT云服务器
/*连接到IoT云服务器*/
void ConnectCloud() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect(myEasyIoT._clientId, myEasyIoT._username, myEasyIoT._password)) {
Serial.println("Connect Server OK");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" Try again in 5 seconds");
delay(5000);
}
}
}
对经纬度进行编码
由于需要在空旷的地方才能获得卫星信号并传回经纬度信息,为了方便展示,这里我们使用固定数值来演示。只要程序能够测试通过,等实际使用时效果是完全一样的。
为了能够在20位以内的字符将信息完整的发送出去,我们仍然要对经纬度进行适当的编码。经度、纬度加上100后再乘以10的6次方转换为整数,前面拼接上ID发送到行空板,行空板再进行解码即可获得原始的经纬度数据。
/*显示位置信息*/
void Display_Location_Information(){
Serial.print("Latitude:\t");
Serial.print(sGGNS.LatitudeVal,6);
Serial.print(" deg ");
Serial.println(sRMC.LatitudeDir);
//Latitude = sGGNS.LatitudeVal;
Latitude = 22.70537;
Serial.print("Longitude:\t");
Serial.print(sGGNS.LongitudeVal,6);
Serial.print(" deg ");
Serial.println(sRMC.LongitudeDir);
//Longitude = sGGNS.LongitudeVal;
Longitude = 114.2185209;
str1 = String(Longitude,6);
str2 = String(Latitude,6);
Longitude_int = (Longitude+100) * pow(10,6); /* 将经度编码,加100后转换为整数*/
Latitude_int = (Latitude+100) * pow(10,6); /* 将纬度编码,加100后转换为整数*/
str = (String(rand()%6+1)) + String(Longitude_int) + String(Latitude_int); /* 将随机生成的id号和经度纬度拼接*/
}
setup函数
void setup(){
Serial.begin(115200);
/*连接WIFI*/
connectWiFi();
/*配置需要连接的IoT云服务器(Easy-IoT)*/
myEasyIoT.init(EasyIot_SERVER, Iot_id, Client_ID, Iot_pwd);
client.setServer(myEasyIoT._mqttServer, PORT);
/*连接到Easy-IoT*/
ConnectCloud();
/*启动BC20*/
Serial.print("Starting the BC20.Please wait. . . ");
while(!myBC20.powerOn()){
delay(1000);
Serial.print(".");
}
Serial.println("BC20 started successfully !");
Serial.println("check OK");
if(myBC20.getQGNSSC() == OFF){
Serial.println("open QGNSSC");
myBC20.setQGNSSC(ON);
}
}
loop函数
void loop(){
delay(5000);
myBC20.getQGNSSRD();
Display_Location_Information();
// Display_Satellite_Information();
/*IoT云服务器断线重连*/
if (!client.connected()) {
ConnectCloud();
}
client.loop();
/*每隔一段时间向Easy-IoT发送消息*/
Serial.println("Sending message to cloud...");
joint = String("Latitude:") + str1 + String(" Longitude:" )+ String(str2);
Serial.println(joint); //将经度和纬度合并一起打印
client.publish(pubTopic, str.c_str()); //将字符串str转换为publish支持的格式:const char*
Serial.println("Message is sent.");
delay(10000);
}
完整的程序
#include "DFRobot_BC20.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include "DFRobot_Iot.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*配置WIFI名和密码*/
const char * WIFI_SSID = ""; //填入WiFi SSID
const char * WIFI_PASSWORD = ""; //填入WiFi 密码
/*配置设备的认证信息*/
String Iot_id = "S1ZmZq37SX"; //填入Iot_id
String Client_ID = "12345"; //任意数值,用于区分不同设备
String Iot_pwd = "SkGQ-937Hm"; //填入Iot_pwd
/*配置IoT云平台的IP地址和端口号*/
String EasyIot_SERVER = "182.254.130.180";
uint16_t PORT = 1883;
/*配置要推送(Publish)或订阅(Subscribe)的topic*/
const char * pubTopic = ""; //填入Topic
/*位置信息变量*/
float Latitude; /* 经度原始值*/
float Longitude; /* 纬度原始值*/
int Latitude_int; /* 经度转换为整数值*/
int Longitude_int; /* 纬度转换为整数值*/
int id; /*家庭成员ID号,1:爸爸, 2: 妈妈, 3:爷爷, 4:奶奶, 5: 大宝, 6: 小宝*/
String str; /*存放待发送的MQTT消息:ID号,转换为整数的经纬度值*/
String str1; /*存放转换为字符串的原始的经度*/
String str2; /*存放转换为字符串的原始的纬度*/
String joint; /*用于合并信息发送*/
DFRobot_Iot myEasyIoT;
WiFiClient espClient;
PubSubClient client(espClient);
DFRobot_BC20 myBC20;
/*连接WiFi*/
void connectWiFi() {
WiFi.disconnect();
delay(100);
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected");
Serial.print("IP Adderss: ");
Serial.println(WiFi.localIP());
}
/*连接到IoT云服务器*/
void ConnectCloud() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect(myEasyIoT._clientId, myEasyIoT._username, myEasyIoT._password)) {
Serial.println("Connect Server OK");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" Try again in 5 seconds");
delay(5000);
}
}
}
/*显示位置信息*/
void Display_Location_Information(){
//UTC time of the anchor point
Serial.print("Latitude:\t");
Serial.print(sGGNS.LatitudeVal,6);
Serial.print(" deg ");
Serial.println(sRMC.LatitudeDir);
//Latitude = sGGNS.LatitudeVal;
Latitude = 22.70537;
Serial.print("Longitude:\t");
Serial.print(sGGNS.LongitudeVal,6);
Serial.print(" deg ");
Serial.println(sRMC.LongitudeDir);
//Longitude = sGGNS.LongitudeVal;
Longitude = 114.2185209;
str1 = String(Longitude,6);
str2 = String(Latitude,6);
Longitude_int = (Longitude+100) * pow(10,6); /* 将经度加100后转换为整数*/
Latitude_int = (Latitude+100) * pow(10,6); /* 将纬度加100后转换为整数*/
str = (String(rand()%6+1)) + String(Longitude_int) + String(Latitude_int); /* 将随机生成的id号和经度纬度拼接*/
}
void setup(){
Serial.begin(115200);
/*连接WIFI*/
connectWiFi();
/*配置需要连接的IoT云服务器(Easy-IoT)*/
myEasyIoT.init(EasyIot_SERVER, Iot_id, Client_ID, Iot_pwd);
client.setServer(myEasyIoT._mqttServer, PORT);
/*连接到Easy-IoT*/
ConnectCloud();
/*启动BC20*/
Serial.print("Starting the BC20.Please wait. . . ");
while(!myBC20.powerOn()){
delay(1000);
Serial.print(".");
}
Serial.println("BC20 started successfully !");
Serial.println("check OK");
if(myBC20.getQGNSSC() == OFF){
Serial.println("open QGNSSC");
myBC20.setQGNSSC(ON);
}
}
void loop(){
delay(5000);
myBC20.getQGNSSRD();
Display_Location_Information();
// Display_Satellite_Information();
/*IoT云服务器断线重连*/
if (!client.connected()) {
ConnectCloud();
}
client.loop();
/*每隔一段时间向Easy-IoT发送消息*/
Serial.println("Sending message to cloud...");
joint = String("Latitude:") + str1 + String(" Longitude:" )+ String(str2);
Serial.println(joint); //将经度和纬度合并一起打印
client.publish(pubTopic, str.c_str()); //将字符串str转换为publish支持的格式:const char*
Serial.println("Message is sent.");
delay(10000);
}
演示视频
DeadWalking2024.04.05
厉害,安全设备自己也能做了
三春牛-创客2023.01.28
赞
Fish012022.09.08
这个太牛了。