M5311模块 NBIOT LWM2M 方式连接OneNET,采集推送温湿度

想要梳理一下OneNET模组数据采集推送,并最终在业务平台展示的整个过程,就淘宝上买了一块“墨子号科技”的M5311模板+STM32G070开发底板。随机配送NB卡都给插好了,到手安装天线并直接USB上电即可(安装驱动CH340_341.EXE)。

串口工具调试AT命令

模组默认刷的是01_串口透传调试(出厂默认),用串口工具sscom5.13.1.exe,选好对应的COM串口,勾选“加回车换行”,发送“AT”命令,如果响应OK,那至少模组的命令交互是通的,接以下步骤测试
1、上电检查流程

AT //判断模组是否上电开机成功
AT+CIMI //读取 IMSI, 判断 SIM 卡初始化是否成功
AT+CSQ //信号质量检查
AT+CEREG? //判断 PS 域附着状态,标识位返回 1 或 5 表示附着正常
AT+CGATT? //检查模组 PS 附着状态
//如果需要使用网络信号变更提示主动上报,可以使用如下 AT 指令:
AT+CSCON=1 //打开信号提示自动上报, 此步骤可省略
//如果需要注册状态信息主动上报,可以使用如下 AT 指令:
AT+CEREG=1 //打开注册信息自动上报, 此步骤可省略

注意:
调试的时候可以关闭休眠,不然有时AT命令会没回应,AT+SM=LOCK
需要确认入网状态为已注册才能进行后续数据收发操作,目前测试开机注册时间范围为 20s 左右, 在极端网络情况下,可能最大延迟到 120s;
每条 AT 命令之间应该留有 300ms 以上的时间间隔;
如果设备终端处于运动状态,建议开启注册状态主动上报功能,并周期性监控CSQ 信号质量。
2、模组侧设备创建及资源订阅,登录流程

AT+MIPLCONF=72,1003000………3033393639050501,1,1//设置模组侧设备注册码
AT+MIPLADDOBJ=0,3200,0 //订阅 Object 3200 资源设置
AT+MIPLNOTIFY=0,3200,0,5505,6," E309C82FE6",1 //订阅 Resource 5500 资源设置
AT+MIPLOPEN=0,30 //设备登录到 OneNET 平台

3、OneNET 数据收发流程

AT+MIPLNOTIFY=0,3200,0,5505,6,“E309C82FE6”,1 //数据上传
AT+MIPLREAD=0,60204,3200,0,5505,“E309C82FE6”,1 //Read 操作回复流程
AT+MIPLWRITE=0,62069,1 //Write 操作回复流程
AT+MIPLEXECUTE=0,46081,1 //Execute 操作回复流程

4、模组侧设备注销流程

AT+MIPLCLOSE=0 //登录注销流程
AT+MIPLDELOBJ=0,3200,0 //模组侧订阅资源列表释放
AT+MIPLDEL=0 //模组侧通信实例删除

在OneNET平台上面创建产品,添加设备

这个网络上找一下,或者打开网址摸索一下就好,协议选LWM2M,网络选NB,直连,添加设备时,填入IMEI,IMSI即可。

刷入M5311_ONENET_LWM2M 传温湿度的hex文件到设备

编译代码

用到工具为:Keil uVision5,安装后需要导入Keil.STM32G0xx_DFP.1.3.0.pack包,还要注意在Options里的C/C++里设置为C99 Mode(如果你的代码是需要C99的话),修改一些你需要改的代码,比如我改了几个地方,传输的数据格式(按你喜欢的来就行),定期发送刷新在网的命令AT+MIPLUPDATE=0,300,1 (不然好像几分钟后会断),发送数据等待恢复的设置一个循环1000次,如果还没回来就忽略。

   int c = 0;
   while(strx==NULL & c<1000)
	{
		c++;
		strx=strstr((const char*)RxBuffer,(const char*)"+MIPLEVENT");
	}

烧录hex文件

因为原来是空白知识,网络上找了一些,很多用的是st-link 然后可以直接在Keil里点击download烧录,后面才了解到买的部件里不包含st-link连接器,不过可以直接以ch340串口烧入,下载STM32CubeProgrammer,选择UART模式,设备断电,底板按钮拨到“ON”,上电,选择端口连接,烧录即可。烧完断开连接,按钮拨回“1”,上电即可。

OneNET 数据推送

设备正常上报数据的话,可以从OneNET网站上看到:
在这里插入图片描述
这时,你就可以在 数据推送-http推送里面设置接收数据的url接口,接口的代码可以在OneNET上下载
接口写好并上线后,OneNET的http推送那边才会验证通过。

业务处理

处理之前,可以把OneNET要推送过来的数据建模,建表格式如下:

CREATE TABLE `onenet_data` (
  `id` int(100) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `type` int(20) DEFAULT NULL COMMENT '标识消息类型',
  `dev_id` int(11) DEFAULT NULL COMMENT '设备ID',
  `ds_id` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '数据流名称',
  `at` bigint(20) DEFAULT NULL COMMENT '平台时间戳',
  `value` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '数据',
  `status` int(11) DEFAULT NULL COMMENT '设备上下线标识',
  `login_type` int(11) DEFAULT NULL COMMENT '设备登录协议类型',
  `cmd_type` int(11) DEFAULT NULL COMMENT '命令响应的类型',
  `cmd_id` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '命令ID',
  `msg_signature` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '消息摘要',
  `nonce` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '随机串',
  `enc_msg` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '加密消息体',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='OneNET数据';

建立Bean类:

public class OnenetData 
{
    private static final long serialVersionUID = 1L;

    /** id */
    private Long id;
    /** 标识消息类型 */
    @Excel(name = "标识消息类型")
    private Long type;
    /** 设备ID 注意加入com.alibaba.fastjson.annotation.JSONField,转换下划线格式和驼峰格式 */
    @Excel(name = "设备ID")
    @JSONField(name = "dev_id")
    private Long devId;
    ...其他字段省略
}

业务处理,为了实时更新前端,采用WebSocketServer,具体随便找一篇参考,比如:使用spring boot +WebSocket实现(后台主动)消息推送

   @PostMapping("/receive")
    @ResponseBody
    public String receive(@RequestBody String body) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {

        logger.info("data receive:  body String --- " +body);
        /************************************************
         *  解析数据推送请求,非加密模式。
         *  如果是明文模式使用以下代码
         **************************************************/
        /*************明文模式  start****************/
        Util.BodyObj obj = Util.resolveBody(body, false);
        logger.info("data receive:  body Object --- " +obj);
        if (obj != null){
            boolean dataRight = Util.checkSignature(obj, token);
            if (dataRight){
                JSONObject jb = JSONObject.parseObject(body);
                logger.info(jb.toJSONString());
                Object msglist = jb.get("msg");
                if(msglist instanceof JSONArray) {
                    for (Object o : (JSONArray)msglist) {
                        dealTask(o);
                    }
                }else{
                    dealTask(msglist);
                }
            }else {
                logger.info("data receive: signature error");
            }

        }else {
            logger.info("data receive: body empty error");
        }
    return "ok";
  }
      private void dealTask(Object o){
        JSONObject jo = (JSONObject) o;
        OnenetData onenetData = JSONObject.parseObject(jo.toJSONString(), OnenetData.class);
        // 想要持久化保存数据,添加下面这行就可以
        //onenetDataService.insertOnenetData(onenetData);
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //转换
            String time = sdf.format(new Date(onenetData.getAt()));
            String tem = onenetData.getValue().substring(3);
            String hmi = onenetData.getValue().substring(0,3);
            WebSocketServer.sendInfo(time + "," + tem + "," + hmi);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

前端实现,下载一个echarts.min.js,采用里面的仪表盘组件(或者你喜欢其他的)

<!DOCTYPE html>
<html lang="zh-CN" style="height: 100%">
<head>
	<meta charset="utf-8">
	<title>温湿度</title>
</head>

<script src="./echarts.min.js"></script>

<body style="height: 100%; margin: 0">

	<div id="container" style="height: 90%"></div>
	<div id="time" style="height: 10%;text-align:center;"></div>

	<script type="text/javascript">
		var dom = document.getElementById('container');
		var time = document.getElementById('time');
		var myChart = echarts.init(dom, null, {
			renderer: 'canvas',
			useDirtyRect: false
		});
		var app = {};
		
		var option;

		option = {
			series: [
			{
				type: 'gauge',
				center: ['50%', '60%'],
				startAngle: 200,
				endAngle: -20,
				min: 0,
				max: 100,
				splitNumber: 10,
				itemStyle: {
					color: '#ff0033'
				},
				progress: {
					show: true,
					width: 30
				},
				pointer: {
					show: false
				},
				axisLine: {
					lineStyle: {
						width: 30
					}
				},
				axisTick: {
					distance: -45,
					splitNumber: 5,
					lineStyle: {
						width: 2,
						color: '#999'
					}
				},
				splitLine: {
					distance: -52,
					length: 14,
					lineStyle: {
						width: 3,
						color: '#999'
					}
				},
				axisLabel: {
					distance: -20,
					color: '#999',
					fontSize: 20
				},
				anchor: {
					show: false
				},
				title: {
					show: false
				},
				detail: {
					valueAnimation: true,
					width: '60%',
					lineHeight: 40,
					borderRadius: 8,
					offsetCenter: [0, '-15%'],
					fontSize: 60,
					fontWeight: 'bolder',
					formatter: '温度:{value} °C',
					color: 'inherit'
				},
				data: [
				{
					value: 20.0
				}
				]
			},
			{
				type: 'gauge',
				center: ['50%', '60%'],
				startAngle: 200,
				endAngle: -20,
				min: 0,
				max: 100,
				itemStyle: {
					color: '#458B00'
				},
				progress: {
					show: true,
					width: 15
				},
				pointer: {
					show: false
				},
				axisLine: {
					show: false
				},
				axisTick: {
					show: false
				},
				splitLine: {
					show: false
				},
				axisLabel: {
					show: false
				},
				detail: {
					show: false
				},
				detail: {
					valueAnimation: true,
					width: '60%',
					lineHeight: 20,
					borderRadius: 8,
					offsetCenter: [0, '20%'],
					fontSize: 40,
					fontWeight: 'bolder',
					formatter: '湿度:{value} %rh',
					color: 'inherit'
				},
				data: [
				{
					value: 15
				}
				]
			}
			]
		};



		if (option && typeof option === 'object') {
			myChart.setOption(option);
		}

		var socket;
		function openSocket() {
			if(typeof(WebSocket) == "undefined") {
				console.log("您的浏览器不支持WebSocket");
			}else{
				console.log("您的浏览器支持WebSocket");
            //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
            //等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
            var socketUrl="http://xxx.xxx.xxx:xxx/websocket/"+Date.now();
            socketUrl=socketUrl.replace("https","ws").replace("http","ws");
            console.log(socketUrl);
            if(socket!=null){
            	socket.close();
            	socket=null;
            }
            socket = new WebSocket(socketUrl);
            //打开事件
            socket.onopen = function() {
            	console.log("websocket已打开");

            };
            //获得消息事件
            socket.onmessage = function(msg) {
            	console.log(msg.data);
            	let arr = msg.data.split(',');
            	time.innerHTML='<span style="font-size:20px;font-weight:bold">' +arr[0]+((arr[0]==='连接成功')?',正在加载数据... ...':'') + '</span>';
            	myChart.setOption({
            		series: [
            		{
            			data: [
            			{   // 温度
            				value: parseFloat(arr[1])/10
            			}
            			]
            		},
            		{
            			data: [
            			{  // 湿度
            				value: parseFloat(arr[2])/10
            			}
            			]
            		}
            		]
            	});
                //发现消息进入    开始处理前端触发逻辑
            };
            //关闭事件
            socket.onclose = function() {
            	console.log("websocket已关闭");
            };
            //发生了错误事件
            socket.onerror = function() {
            	console.log("websocket发生了错误");
            }
        }
    }

    openSocket();
    window.addEventListener('resize', myChart.resize);
</script>
</body>
</html>

最终效果
在这里插入图片描述

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值