admin管理员组

文章数量:1603247

饮水机加入Onenet联网代码实操

引言

饮水机联网调试变量解析

在上一讲, 我们已经分析了 饮水机各个功能的调试方法, 并且确定了需要调试的变量, 这次我们就进行Onenet的代码实操, 加入联网工程代码, 主要是走个流程.

我们加入联网, 主要是把这些变量,上传到服务器, 让我们可视化的实时的观察单片机的内部变量状态。

把变量上传到服务器实时观察, 就取代了oled在屏幕上, 观察变量

同时我们要修改其中的变量, 所以我们就要在服务器下发修改变量的值, 然后单片机拆包解析后, 赋予本地变量。

服务器下发指令, 就代替了, 调试时候的赋予变量和按键赋值, 更具有灵活性

加入工程, 参考示例步骤:

手把手stm32与Onenet联网配置步骤

上面的示例步骤是物联网预警系统的, 我们只是变量不同罢了, 所以只需要更改相关的标识符就可以了.

所以我们比葫芦画瓢就可以了.在掌握了上面的联网工程后, 后面就会很简单, 我们直接忽略详细步骤, 只讲 饮水机和预警系统, 赋值的不同的变量的部分, 站在已经能够掌握整个流程的角度来, 如何快速复制黏贴.

本项目调试好的工程代码(后面是从头开始操作,这是成品):

跳转下载

https://wwyz.lanzoul/i3Wn728iriji

本地工程加入联网

无联网本地饮水机工程

点击跳转下载

https://wwyz.lanzoul/i6fZu28he71a

饮水机联网工程文件

点击跳转下载

回去(ctrl 加鼠标左键,快速跳转)

https://wwyz.lanzoul/ix8Ys28heg4h

需要上传的变量整理

加热系统

回去(ctrl 加鼠标左键,快速跳转)

需要上传

//水温
water_temp
//加热阈值
heat_temp_th
//保温阈值
KeepWarm_th
//加热模式
heat_mode

饮水系统

我们根据饮水机的童锁和红外的交互功能, 来判断, 是否可以控制底层开启水龙头

上图, 描述了他们之间的关系, 代码中也体现了出来, 所以我们上传的变量分别是

//童锁状态
child_lock_info.Status
//杯子是否放置
cup_place_info.Status
//水龙头出水状态    
out_water_info.Status   

Onenet服务器产品创建

1.官网

OneNET - 中国移动物联网开放平台 (10086)

2.登录注册

注意, 密码的特殊符号是 @ 其他标点不行

3.进入Onenet产品开发

如果找不到, 就直接把这个链接放在导航栏直接回车

产品开发 - OneNET物联网平台 (10086)

https://open.iot.10086/console/product/own

4.点击创建产品

5.产品分类->其他行业->其他行业->其他类别

6.产品名称:饮水机示例

所属城市:随便填

节点类型: 直连设备

接入协议: MQTT

数据协议: OneJson

联网方式: wifi

开发方案: 自定义方案

7.根据所需变量设置对应的物模型

8.变量对应的物模型

本地变量(ctrl 加鼠标左键,快速跳转)

一般情况下, 服务器物模型标识符可以设置成和本地一样, 这样方便, 但是如果我们使用结构体的话, 直接使用会造成变量太长, 所以我们可以换其他简短的意思一样的变量就行, 相当于取了一个代号, 到时候服务器下发控制变量也会按照这个标识符进行下发, 我们单片机解析的时候,记得按照Onenet设置的这个简短标识符进行拆包解析就行了.

<1>水温

物模型标识符 water_temp

对应本地变量 water_temp

数据类型: int32(整数型)(onenet整数类型都是这个)

取值范围: 0-100

步长: 1

单位:摄氏度

读写类型: 读写 (可读可写方便一会儿我们进行模拟上帝调试, 实际使用切勿可写, 需要真正从器件读取水温)

<2>加热阈值

物模型标识符 heat_temp_th

对应本地变量 heat_temp_th

数据类型: int32(整数型)(onenet整数类型都是这个)

取值范围: 0-100

步长: 1

单位:摄氏度

读写类型: 读写 (可读可写方便一会儿我们进行模拟上帝调试, 同时后续方便用户设置阈值)

<3>保温阈值

物模型标识符 Keepwarm_th

对应本地变量 Keepwarm_th

数据类型: int32(整数型)(onenet整数类型都是这个)

取值范围: 0-100

步长: 1

单位:摄氏度

读写类型: 读写 (可读可写方便一会儿我们进行模拟上帝调试, 同时后续方便用户设置阈值)

<4>加热模式

物模型标识符 heat_mode

对应本地变量 heat_mode

数据类型: int32(整数型)(onenet整数类型都是这个)

取值范围: 0-2

步长: 1

单位:个

读写类型: 读写 (可读可写方便一会儿我们进行模拟上帝调试, 同时后续方便用户设置模式)

<5> 童锁状态

物模型标识符 children_lock

对应本地变量 child_lock_info.Status

数据类型: bool(布尔)

布尔值 true - 1 (童锁锁住)

​ false - 0 (童锁解开)

读写类型: 读写 (可读可写方便一会儿我们进行模拟上帝调试, 同时后续方便用户设置童锁)

<6>杯子是否放置 (红外检测)

物模型标识符 cup_place

对应本地变量 cup_place_info.Status

数据类型: bool(布尔)

布尔值 true - 1 (放置了杯子)

​ false - 0 (未检测到杯子)

读写类型: 读写 (可读可写方便一会儿我们进行模拟上帝调试, 同时后续方便模拟杯子放上,优化本地逻辑)

<7> 水龙头出水状态

物模型标识符 out_water

对应本地变量 out_water_info.Status

数据类型: bool(布尔)

布尔值 true - 1 (正在放水)

​ false - 0 (水龙头关闭)

读写类型: 读写 (可读可写方便一会儿我们进行模拟上帝调试, 同时后续方便测试安全措施,优化本地逻辑)

9.创建产品

回去(ctrl 加鼠标左键,快速跳转)

(1)产品id:A9g7LOO1B3

(2)设备名称:d1

(3)设备秘钥:(设备管理->设备->详情->设备秘钥)

UGNZQmt2SFFpbFV0NkczMGNTMzh0QW9IUTVpbGp1Skg=

单片机本地联网代码加入

1.代码下载

联网代码下载(ctrl 加鼠标左键,快速跳转)

下载完联网功能代码, 解压

2.把Net文件夹复制加入Source_code源代码文件夹内

3.把此文件夹内的所有类别文件夹, 加入工程

我们首先打开工程

4.点击品字, 然后添加联网文件夹内的工程名字

cjson
mqtt
onenet
uart
wifi

5.逐个把对应文件夹的文件加入

点击对应的Group,再点击Add Files,然后选中本地对应的文件夹, 把文件类型选为all , 然后鼠标选中所有文件点击add

6.接着把这些文件夹路径加入

7.这样就可以看到工程左侧已经添加了文件

联网代码初始化

wifi:ESP32-01S初始化

1.main.c引入 wifi头文件

#include "esp8266.h"

定义

#define ESP8266_ONENET_INFO		"AT+CIPSTART=\"TCP\",\"mqtts.heclouds\",1883\r\n"

2.wifi串口初始化

	Delay_Init();//滴答定时器初始化
	uart1_init(115200);//串口1,调试用
	uart2_Init(115200);	//串口2,驱动ESP8266用
	ESP8266_Init();//wifi初始化

3.wifi配置

回去(ctrl 加鼠标左键,快速跳转)

4.发现文件里都有delay.c

user里面添加delay.c delay.h

delay.h

#ifndef _DELAY_H_
#define _DELAY_H_



#include "stm32f10x.h"



void Delay_Init(void);

void DelayUs(unsigned short us);

void DelayXms(unsigned short ms);

void DelayMs(unsigned short ms);
void delay(uint32_t t);

void delay_us(u32 us);
void delay_ms(u32 ms);
#endif


delay.c

/**
	************************************************************
	************************************************************
	************************************************************
	*	文件名: 	delay.c
	*
	*	作者: 		张继瑞
	*
	*	日期: 		2016-11-23
	*
	*	版本: 		V1.0
	*
	*	说明: 		利用systick做阻塞式延时
	*
	*	修改记录:	
	************************************************************
	************************************************************
	************************************************************
**/

//单片机头文件
#include "stm32f10x.h"

//delay头文件
#include "delay.h"


//延时系数
unsigned char UsCount = 0;
unsigned short MsCount = 0;


/*
************************************************************
*	函数名称:	Delay_Init
*
*	函数功能:	systick初始化
*
*	入口参数:	无
*
*	返回参数:	无
*
*	说明:		
************************************************************
*/
void Delay_Init(void)
{

	SysTick->CTRL &= ~(1 << 2);		//选择时钟为HCLK(72MHz)/8		103--9MHz
	
	UsCount = 9;					//微秒级延时系数
	
	MsCount = UsCount * 1000;		//毫秒级延时系数

}

/*
************************************************************
*	函数名称:	DelayUs
*
*	函数功能:	微秒级延时
*
*	入口参数:	us:延时的时长
*
*	返回参数:	无
*
*	说明:		此时钟(21MHz)最大延时798915us
************************************************************
*/
void DelayUs(unsigned short us)
{

	unsigned int ctrlResult = 0;
	
	us &= 0x00FFFFFF;											//取低24位
	
	SysTick->LOAD = us * UsCount;								//装载数据
	SysTick->VAL = 0;
	SysTick->CTRL = 1;											//使能倒计数器
	
	do
	{
		ctrlResult = SysTick->CTRL;
	}
	while((ctrlResult & 0x01) && !(ctrlResult & (1 << 16)));	//保证在运行、检查是否倒计数到0
	
	SysTick->CTRL = 0;											//关闭倒计数器
	SysTick->VAL = 0;

}

/*
************************************************************
*	函数名称:	DelayXms
*
*	函数功能:	毫秒级延时
*
*	入口参数:	ms:延时的时长
*
*	返回参数:	无
*
*	说明:		
************************************************************
*/
void DelayXms(unsigned short ms)
{

	unsigned int ctrlResult = 0;
	
	if(ms == 0)
		return;
	
	ms &= 0x00FFFFFF;											//取低24位
	
	SysTick->LOAD = ms * MsCount;								//装载数据
	SysTick->VAL = 0;
	SysTick->CTRL = 1;											//使能倒计数器
	
	do
	{
		ctrlResult = SysTick->CTRL;
	}
	while((ctrlResult & 0x01) && !(ctrlResult & (1 << 16)));	//保证在运行、检查是否倒计数到0
	
	SysTick->CTRL = 0;											//关闭倒计数器
	SysTick->VAL = 0;

}

/*
************************************************************
*	函数名称:	DelayMs
*
*	函数功能:	微秒级长延时
*
*	入口参数:	ms:延时的时长
*
*	返回参数:	无
*
*	说明:		多次调用DelayXms,做到长延时
************************************************************
*/
void DelayMs(unsigned short ms)
{

	unsigned char repeat = 0;
	unsigned short remain = 0;
	
	repeat = ms / 500;
	remain = ms % 500;
	
	while(repeat)
	{
		DelayXms(500);
		repeat--;
	}
	
	if(remain)
		DelayXms(remain);

}

void delay(uint32_t t)
{
	while(t--);
}	

void delay_us(u32 us)//微秒
{
	SysTick_Config(72);
	while(us-->0)
	{
		while(!((SysTick->CTRL)&(1<<16)));
	}
	SysTick ->CTRL&=~SysTick_CTRL_ENABLE_Msk;
}

void delay_ms(u32 ms)//毫秒
{
	SysTick_Config(72000);
  while(ms-->0)
	{
		while(!((SysTick->CTRL)&(1<<16)));
	}
	SysTick ->CTRL&=~SysTick_CTRL_ENABLE_Msk;
}


5.发现delay.c里面有中断, 加入中断的库函数

配置Onenet联网文件

wifi已经配置成功,下面配置链接onenet服务器

1.刚才导入的onenet配置文件

2.在main.c里面引入 onenet头文件

#include "onenet.h"

3.main.c主函数里面, 链接onetnet

	while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
		DelayXms(500);
	
	while(OneNet_DevLink())			//接入OneNET
		DelayXms(500);	
	
	OneNET_Subscribe();				//解析onenet下发指令	

4.要想链接成功, onenet里面的服务器配置也要对应

5.进入onenet.c, 定义的属性, 进行替换

服务器信息(ctrl 加鼠标左键,快速跳转)

6.回到主函数, 进行上传变量

定义发送间隔

unsigned short timeCount = 0;	//发送间隔变量

main.c主函数, 定时上传, 延时10ms, 100次就是1s

72Mhz = 7200,0000次,(1s)

10ms,就是 7200,00次, 换算十六进制为 0xAFC80

		//打包上传
		if(++timeCount >= 100)
		{
			
			OneNet_SendData();	
			timeCount = 0;
			ESP8266_Clear();
		}
		delay(0xAFC80);//延时10ms

7.先编译运行,鼠标按下f12, 跳转OneNet_SendData();

8.就会来到上传变量的位置

9.引入上传变量

extern int water_temp;		//水温
extern int heat_temp_th;	//加热阈值
extern int KeepWarm_th;		//保温阈值
extern int heat_mode;		//加热模式

因为童锁和红外线是定义在其他文件内, 所以我们包含其头文件即可

#include "infrared_cup.h"
#include "out_water.h"

10.严格按照下面格式进行填写:

11…强调:

最后一项,后面没逗号, 之前都有逗号

代码摘录:

extern int water_temp;		//水温
extern int heat_temp_th;	//加热阈值
extern int KeepWarm_th;		//保温阈值
extern int heat_mode;		//加热模式


uint16_t OneNet_FillBuf(char *buf)
{
	
	char text[48];
	
	memset(text, 0, sizeof(text));
	
	strcpy(buf, "{\"id\":\"123\",\"params\":{");
	
	memset(text, 0, sizeof(text));
	sprintf(text, "\"water_temp\":{\"value\":%d},", water_temp);
	strcat(buf, text);

	memset(text, 0, sizeof(text));
	sprintf(text, "\"heat_temp_th\":{\"value\":%d},", heat_temp_th);
	strcat(buf, text);	
	
	memset(text, 0, sizeof(text));
	sprintf(text, "\"KeepWarm_th\":{\"value\":%d},", KeepWarm_th);
	strcat(buf, text);

	memset(text, 0, sizeof(text));
	sprintf(text, "\"heat_mode\":{\"value\":%d},", heat_mode);
	strcat(buf, text);


	memset(text, 0, sizeof(text));
	sprintf(text, "\"cup_place\":{\"value\":%s},", cup_place_info.Status ? "true" : "false");
	strcat(buf, text);

	memset(text, 0, sizeof(text));
	sprintf(text, "\"out_water\":{\"value\":%s},", out_water_info.Status ? "true" : "false");
	strcat(buf, text);

	memset(text, 0, sizeof(text));
	sprintf(text, "\"children_lock\":{\"value\":%s}", child_lock_info.Status ? "true" : "false");
	strcat(buf, text);
	
	strcat(buf, "}}");
	
	return strlen(buf);

}

单片机拆包(解析下发变量):

(1)检测是否有下发信息

定义接受判断变量

unsigned char *dataPtr = NULL;	//标志位, 是否接受到onenet发送的数据

代码摘录:

//检测是否有下发信息
dataPtr = ESP8266_GetIPD(0);
if(dataPtr != NULL)
{
    OneNet_RevPro(dataPtr);
  //UsartPrintf(USART_DEBUG, "6666666666\r\n");
}
(2)配置CJSON拆包

1.编译,运行 ,然后f12跳转OneNet_RevPro(dataPtr);

2.这里就是接受数据包的函数

我们在这里利用cjson拆包

3.在此函数内,定义拆包所需要的cjson变量

4.①接下来解析原始字符串数据到raw_jason

raw_jason = cJSON_Parse(req_payload);//解析原始字符串数据到raw_jason

② 从处理的cjson数据中,从raw_jason中提取params字符段到params_jason

params_jason=cJSON_GetObjectItem(raw_jason,"params");

5.然后再从params_jason中提取要操作的饮水机变量

				water_temp_jason = cJSON_GetObjectItem(params_jason,"water_temp");
				heat_temp_th_jason = cJSON_GetObjectItem(params_jason,"heat_temp_th");
				KeepWarm_th_jason = cJSON_GetObjectItem(params_jason,"KeepWarm_th");
				heat_mode_jason = cJSON_GetObjectItem(params_jason,"heat_mode");
				children_lock_jason = cJSON_GetObjectItem(params_jason,"children_lock");				
				
				out_water_jason = cJSON_GetObjectItem(params_jason,"out_water");
				
				cup_place_jason = cJSON_GetObjectItem(params_jason,"cup_place");

6.判断对应cjson 标识符变量 是否为空, 不空,则代表有对应的下发信息

布尔类型, 需要对照其变量

if(out_water_jason != NULL)
{
    if(out_water_jason->type == cJSON_True ) 
    {
        OUT_water_Set(out_water_ON);
    }
    else
    {
        OUT_water_Set(out_water_OFF);
    }
}				


if(cup_place_jason != NULL)
{
    if(cup_place_jason->type == cJSON_True ) 
    {
        cup_place_info.Status = Cup_placement;
    }
    else
    {
        cup_place_info.Status = Cup_disappears;
    }
}								


if(children_lock_jason != NULL)
{
    if(children_lock_jason->type == cJSON_True ) 
    {
        if(child_lock_info.Status != lock_child)
        {
            child_lock_info.Status = lock_child;
        }
    }
    else
    {
        if(child_lock_info.Status != Unlock_child)
        {
            child_lock_info.Status = Unlock_child;
        }
    }
}

7.末尾释放解析字符串空间

cJSON_Delete(raw_jason);

此函数代码备份:

//==========================================================
//	函数名称:	OneNet_RevPro
//
//	函数功能:	平台返回数据检测
//
//	入口参数:	dataPtr:平台返回的数据
//
//	返回参数:	无
//
//	说明:		
//==========================================================

void OneNet_RevPro(unsigned char *cmd)
{
	
	char *req_payload = NULL;
	char *cmdid_topic = NULL;
	
	unsigned short topic_len = 0;
	unsigned short req_len = 0;
	
	unsigned char qos = 0;
	static unsigned short pkt_id = 0;
	
	unsigned char type = 0;
	
	short result = 0;
	
	//原始数据 -> cjson数据
	cJSON *raw_jason;
	// cjson中摘出标识符变量
	cJSON *params_jason;

	//这里定义 cjson格式的变量名,例如 *led_jason
	cJSON *water_temp_jason;
	cJSON *heat_temp_th_jason;
	cJSON *KeepWarm_th_jason;
	cJSON *heat_mode_jason;
	cJSON *children_lock_jason;
	
	cJSON *cup_place_jason;
	
	cJSON *out_water_jason;
	
	
	type = MQTT_UnPacketRecv(cmd);
	//UsartPrintf(USART_DEBUG, "688\r\n");
	switch(type)
	{
		case MQTT_PKT_PUBLISH:																//接收的Publish消息
//			UsartPrintf(USART_DEBUG, "689\r\n");
			result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id);
			
			if(result == 0)
			{

				
				UsartPrintf(USART_DEBUG, "topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n",
																	cmdid_topic, topic_len, req_payload, req_len);
				
				//将原始字符串信息,转化成cjson格式
				raw_jason = cJSON_Parse(req_payload);//解析原始字符串数据到raw_jason
				//从raw_jason中提取params字符段到params_jason
				params_jason = cJSON_GetObjectItem(raw_jason,"params");
				//然后再从params_jason中提取要操作的器件,

				water_temp_jason = cJSON_GetObjectItem(params_jason,"water_temp");
				heat_temp_th_jason = cJSON_GetObjectItem(params_jason,"heat_temp_th");
				KeepWarm_th_jason = cJSON_GetObjectItem(params_jason,"KeepWarm_th");
				heat_mode_jason = cJSON_GetObjectItem(params_jason,"heat_mode");
				children_lock_jason = cJSON_GetObjectItem(params_jason,"children_lock");				
				
				out_water_jason = cJSON_GetObjectItem(params_jason,"out_water");
				
				cup_place_jason = cJSON_GetObjectItem(params_jason,"cup_place");
				
				//判断对应cjson 标识符变量 是否为空, 不空,则代表有对应的下发信息

				if(water_temp_jason != NULL)
				{
					water_temp = water_temp_jason->valueint;
				}
				if(heat_temp_th_jason != NULL)
				{
					heat_temp_th = heat_temp_th_jason->valueint;
				}
				if(KeepWarm_th_jason != NULL)
				{
					KeepWarm_th = KeepWarm_th_jason->valueint;
				}
				if(heat_mode_jason != NULL)
				{
					heat_mode = heat_mode_jason->valueint;
				}				

				if(out_water_jason != NULL)
				{
					if(out_water_jason->type == cJSON_True ) 
					{
						OUT_water_Set(out_water_ON);
					}
					else
					{
						OUT_water_Set(out_water_OFF);
					}
				}				
				
				
				if(cup_place_jason != NULL)
				{
					if(cup_place_jason->type == cJSON_True ) 
					{
						cup_place_info.Status = Cup_placement;
					}
					else
					{
						cup_place_info.Status = Cup_disappears;
					}
				}								
				
				
				if(children_lock_jason != NULL)
				{
					if(children_lock_jason->type == cJSON_True ) 
					{
						if(child_lock_info.Status != lock_child)
						{
							child_lock_info.Status = lock_child;
						}
					}
					else
					{
						if(child_lock_info.Status != Unlock_child)
						{
							child_lock_info.Status = Unlock_child;
						}
					}
				}
				

				cJSON_Delete(raw_jason);
				
			}
			
		case MQTT_PKT_PUBACK:														//发送Publish消息,平台回复的Ack
//			UsartPrintf(USART_DEBUG, "111\r\n");
			if(MQTT_UnPacketPublishAck(cmd) == 0)
//				UsartPrintf(USART_DEBUG, "Tips:	MQTT Publish Send OK\r\n");
			
		break;
		
		case MQTT_PKT_SUBACK:																//发送Subscribe消息的Ack
//			UsartPrintf(USART_DEBUG, "222\r\n");
			if(MQTT_UnPacketSubscribe(cmd) == 0)
				UsartPrintf(USART_DEBUG, "Tips:	MQTT Subscribe OK\r\n");
			else
				UsartPrintf(USART_DEBUG, "Tips:	MQTT Subscribe Err\r\n");
		
		break;
		
		default:
//			UsartPrintf(USART_DEBUG, "333\r\n");
			result = -1;
		break;
	}
	
	ESP8266_Clear();									//清空缓存
	
	if(result == -1)
		return;
	

	
	if(type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH)
	{
		MQTT_FreeBuffer(cmdid_topic);
		MQTT_FreeBuffer(req_payload);
	}

}

烧录调试现象

1.烧录方法

2.esp32连线方法 , esp32上 RX 链接 PA2 ,TX链接PA3

GND–单片机gnd, vcc–单片机3.3v

3.手机wifi热点设置为 配置的

跳转单片机wifi设置(ctrl+鼠标左键跳转)

4.观察服务器设备在线情况

5.模拟下发设备信息,再次观察属性是否被修改,并上传

6.然后就按照我们之前的调试方法, 进行调试

饮水机复杂交互功能联网调试

https://blog.csdn/qq_57484399/article/details/141574662

本文标签: 饮水机代码oneNET