一、环境介绍
单片机采用:STM32F103C8T6
上网方式:采用ESP8266,也可以使用其他设备代替,只要支持TCP协议即可。比如:GSM模块、有线网卡等。
开发软件:keil5
物联网平台: 腾讯IOT物联网物联网平台。腾讯的物联网平台比起其他厂家的物联网平台更加有优势,腾讯物联网平台可以将数据推到微信小程序上,用户可以直接使用小程序绑定设备,完成与设备之间交互,现在用户基本都会使用微信,所以使用起来非常方便。
二、功能介绍
本文章接下会介绍如何在腾讯物联网平台上创建设备、配置设备、推送到微信小程序、并编写STM32设备端代码,使用ESP8266联网登录腾讯物联网平台,完成数据交互。
功能: STM32采集环境温度、湿度、光照强度实时上传至物联网平台,在微信小程序页面上,用户可以实时查看这些数据,并且可以通过界面上的按钮控制设备端的电机、LED灯的开关,完成数据上传和远程控制。
说明: STM32设备端所有代码均有自己全部编写,没有使用任何厂家的SDK,MQTT协议也是参考MQTT官方文档编写;ESP8266也没有使用任何专用固件,所以代码的移植性非常高。 任何能够联网的设备都可以参考本篇文章代码连接腾讯物联网平台,达到相同的效果。
三、登录腾讯物联网平台创建设备
腾讯云官网:腾讯云 - 产业智变 云启未来
下面是手机上的截图:操作过程
现在设备是离线状态,是无法查看的,接下来就使用MQTT客户端模拟设备,登录测试。
四、使用MQTT客户端模拟设备--测试
4.1 下载MQTT客户端
MQTT客户端可执行文件下载地址(.exe): MQTT客户端_v2.4(协议3.1.1).exe_stc15单片机+esp8266+腾讯连连小程序(三)-桌面系统文档类资源-CSDN下载
这个MQTT客户端采用QT开发,如果需要了解它的源码,请看这里:QT应用编程: 编写MQTT客户端登录OnetNet服务器完成主题订阅与发布_DS小龙哥的专栏-CSDN博客_onenet topic订阅与发布
4.2 查看物联网平台端口号与域名(IP地址)
官方文档:物联网通信 设备基于 TCP 的 MQTT 接入 - 开发者手册 - 文档中心 - 腾讯云
通过这里得到信息: 如果是广州域的设备(其实哪里都一样,只是服务器距离的远近),就填入
查看产品ID的方法:
得打产品ID之后,那么要连接我的设备,域名就填:8O76VHCU7Y.iotcloud.tencentdevices.com 端口就填: 1883
由于我的测试用的MQTT客户端不支持域名输入,只支持IP地址输入,所有我这里需要先将域名转为IP地址在进行下面的测试,ESP8266内部支持域名解析的,所有可以直接输入域名即可,不需要做这一步。
在线解析域名的网址:ip地址查询 ip查询 查ip 公网ip地址归属地查询 网站ip查询 同ip网站查询 iP反查域名 iP查域名 同ip域名
得到广州腾讯云的IP地址为: 106.55.124.154
4.3 生成MQTT登录参数
就像我们登录QQ、登录微信需要账号密码一样,设备登录物联网平台也需要类似的东西。
官方文档地址:物联网通信 设备基于 TCP 的 MQTT 接入 - 开发者手册 - 文档中心 - 腾讯云
上面需要的参数,在设备调试页面,点击具体的设备进行查看:
Python源代码:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import base64
import hashlib
import hmac
import random
import string
import time
import sys
# 生成指定长度的随机字符串
def RandomConnid(length):
return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length))
# 生成接入物联网通信平台需要的各参数
def IotHmac(productID, devicename, devicePsk):
# 1. 生成 connid 为一个随机字符串,方便后台定位问题
connid = RandomConnid(5)
# 2. 生成过期时间,表示签名的过期时间,从纪元1970年1月1日 00:00:00 UTC 时间至今秒数的 UTF8 字符串
expiry = int(time.time()) + 30*24*60 * 60
# 3. 生成 MQTT 的 clientid 部分, 格式为 ${productid}${devicename}
clientid = '{}{}'.format(productID, devicename)
# 4. 生成 MQTT 的 username 部分, 格式为 ${clientid};${sdkappid};${connid};${expiry}
username = '{};12010126;{};{}'.format(clientid, connid, expiry)
# 5. 对 username 进行签名,生成token
secret_key = devicePsk.encode('utf-8') # convert to bytes
data_to_sign = username.encode('utf-8') # convert to bytes
secret_key = base64.b64decode(secret_key) # this is still bytes
token = hmac.new(secret_key, data_to_sign, digestmod=hashlib.sha256).hexdigest()
# 6. 根据物联网通信平台规则生成 password 字段
password = '{};{}'.format(token, 'hmacsha256')
return {
'clientid' : clientid,
'username' : username,
'password' : password
}
if __name__ == '__main__':
# 参数分别填入: 产品ID,设备名称,设备密匙
print(IotHmac('8O76VHCU7Y','SmartAgriculture','OHXqYLklNBU4xLqqoZbXMQ=='))
得到的登录信息如下:
clientid: 8O76VHCU7YSmartAgriculture
username: 8O76VHCU7YSmartAgriculture;12010126;J4MCD;1623766532
password: a962b484079864239148b255281d54372aa66247aa8d6259d11aa6fef650fd5b;hmacsha256
4.4 了解主题上报和订阅的格式
登录之前需要先了解如何订阅设备的主题和上报的数据流格式。
如果设备端想要得到APP页面的按钮状态就需要订阅属性下发和属性上报的响应,主题格式就是这样的:
格式: $thing/down/property/8O76VHCU7Y/设备名称 示例: $thing/down/property/8O76VHCU7Y/SmartAgriculture
如果设备端想要像APP页面上传数据,那么就需要使用属性上报--发布主题:
格式: $thing/up/property/8O76VHCU7Y/${deviceName} 示例: $thing/up/property/8O76VHCU7Y/SmartAgriculture
设备端向APP页面上报属性时,需要上传具体的数据,数据流的格式如下:
官方文档: 物联网开发平台 物模型协议 - 云端开发指南 - 文档中心 - 腾讯云
比如: 我的产品里有温度、湿度、电机三个设备,我可以选择一次上传3个设备的信息,数据格式就这样写:
{'method':'report','clientToken':'123','params':{'temperature':20.23,'humidity':50,'Motor':1}}
其中:'temperature'、'humidity'、'Motor' 是设备的标识符,根据自己的情况修改,冒号后面就是给这个设备上传的具体数据。
4.5使用MQTT客户端登录设备测试
万事俱备,下面就使用MQTT客户端进行登录测试。
MQTT客户端操作步骤:
1. 填写相关参数
2. 点击登录
3. 订阅主题
4. 发布主题
5. 去APP页面查看信息
4.6 微信小程序效果
已经收到MQTT客户端上传的数据,点击按钮,MQTT客户端也会收到按钮下发的数据。
五、STM32设备端代码
本文章配套使用的STM32设备端完整源代码下载地址: STM32+MQTT协议连接腾讯物联网平台_完成主题订阅与发布(MQTT).zip_腾讯物联网平台-桌面系统文档类资源-CSDN下载
5.1 下载程序
5.2 连接状态
STM32设备上按下按键后,手机打开微信小程序可以看到实时上传的数据,速度非常快。
5.3 main.c文件
#include 'stm32f10x.h'
#include 'led.h'
#include 'delay.h'
#include 'key.h'
#include 'usart.h'
#include
#include 'timer.h'
#include 'bluetooth.h'
#include 'esp8266.h'
#include 'mqtt.h'
//腾讯物联网服务器的设备信息
#define MQTT_ClientID '8O76VHCU7YSmartAgriculture'
#define MQTT_UserName '8O76VHCU7YSmartAgriculture;12010126;J4MCD;1623766532'
#define MQTT_PassWord 'a962b484079864239148b255281d54372aa66247aa8d6259d11aa6fef650fd5b;hmacsha256'
//订阅与发布的主题
#define SET_TOPIC '$thing/down/property/8O76VHCU7Y/SmartAgriculture' //订阅
#define POST_TOPIC '$thing/up/property/8O76VHCU7Y/SmartAgriculture' //发布
char mqtt_message[200];//上报数据缓存区
int main()
{
u32 time_cnt=0;
u32 i;
u8 key;
LED_Init();
BEEP_Init();
KEY_Init();
USART1_Init(115200);
TIMER1_Init(72,20000); //超时时间20ms
USART2_Init(9600);//串口-蓝牙
TIMER2_Init(72,20000); //超时时间20ms
USART3_Init(115200);//串口-WIFI
TIMER3_Init(72,20000); //超时时间20ms
USART1_Printf('正在初始化WIFI请稍等.n');
if(ESP8266_Init())
{
USART1_Printf('ESP8266硬件检测错误.n');
}
else
{
//加密端口
//USART1_Printf('WIFI:%dn',ESP8266_STA_TCP_Client_Mode('OnePlus5T','1126626497','183.230.40.16',8883,1));
//非加密端口
USART1_Printf('WIFI:%dn',ESP8266_STA_TCP_Client_Mode('CMCC-Cqvn','99pu58cb','106.55.124.154',1883,1));
}
//2. MQTT协议初始化
MQTT_Init();
//3. 连接OneNet服务器
while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord))
{
USART1_Printf('服务器连接失败,正在重试...n');
delay_ms(500);
}
USART1_Printf('服务器连接成功.n');
//3. 订阅主题
if(MQTT_SubscribeTopic(SET_TOPIC,0,1))
{
USART1_Printf('主题订阅失败.n');
}
else
{
USART1_Printf('主题订阅成功.n');
}
while(1)
{
key=KEY_Scan(0);
if(key==2)
{
time_cnt=0;
sprintf(mqtt_message,'{'method':'report','clientToken':'123','params':{'temperature':20.23,'humidity':50,'Motor':1}}');
MQTT_PublishData(POST_TOPIC,mqtt_message,0);
USART1_Printf('发送状态1rn');
}
else if(key==3)
{
time_cnt=0;
sprintf(mqtt_message,'{'method':'report','clientToken':'123','params':{'temperature':10.23,'humidity':60,'Motor':0}}');
MQTT_PublishData(POST_TOPIC,mqtt_message,0);
USART1_Printf('发送状态0rn');
}
if(USART3_RX_FLAG)
{
USART3_RX_BUFFER[USART3_RX_CNT]='�';
for(i=0;i
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png
5.4 mqtt.c
复制
#include 'mqtt.h'
u8 *mqtt_rxbuf;
u8 *mqtt_txbuf;
u16 mqtt_rxlen;
u16 mqtt_txlen;
u8 _mqtt_txbuf[256];//发送数据缓存区
u8 _mqtt_rxbuf[256];//接收数据缓存区
typedef enum
{
//名字 值 报文流动方向 描述
M_RESERVED1 =0 , // 禁止 保留
M_CONNECT , // 客户端到服务端 客户端请求连接服务端
M_CONNACK , // 服务端到客户端 连接报文确认
M_PUBLISH , // 两个方向都允许 发布消息
M_PUBACK , // 两个方向都允许 QoS 1消息发布收到确认
M_PUBREC , // 两个方向都允许 发布收到(保证交付第一步)
M_PUBREL , // 两个方向都允许 发布释放(保证交付第二步)
M_PUBCOMP , // 两个方向都允许 QoS 2消息发布完成(保证交互第三步)
M_SUBSCRIBE , // 客户端到服务端 客户端订阅请求
M_SUBACK , // 服务端到客户端 订阅请求报文确认
M_UNSUBSCRIBE , // 客户端到服务端 客户端取消订阅请求
M_UNSUBACK , // 服务端到客户端 取消订阅报文确认
M_PINGREQ , // 客户端到服务端 心跳请求
M_PINGRESP , // 服务端到客户端 心跳响应
M_DISCONNECT , // 客户端到服务端 客户端断开连接
M_RESERVED2 , // 禁止 保留
}_typdef_mqtt_message;
//连接成功服务器回应 20 02 00 00
//客户端主动断开连接 e0 00
const u8 parket_connetAck[] = {0x20,0x02,0x00,0x00};
const u8 parket_disconnet[] = {0xe0,0x00};
const u8 parket_heart[] = {0xc0,0x00};
const u8 parket_heart_reply[] = {0xc0,0x00};
const u8 parket_subAck[] = {0x90,0x03};
void MQTT_Init(void)
{
//缓冲区赋值
mqtt_rxbuf = _mqtt_rxbuf;
mqtt_rxlen = sizeof(_mqtt_rxbuf);
mqtt_txbuf = _mqtt_txbuf;
mqtt_txlen = sizeof(_mqtt_txbuf);
memset(mqtt_rxbuf,0,mqtt_rxlen);
memset(mqtt_txbuf,0,mqtt_txlen);
//无条件先主动断开
MQTT_Disconnect();
delay_ms(100);
MQTT_Disconnect();
delay_ms(100);
}
/*
函数功能: 登录服务器
函数返回值: 0表示成功 1表示失败
*/
u8 MQTT_Connect(char *ClientID,char *Username,char *Password)
{
u8 i,j;
int ClientIDLen = strlen(ClientID);
int UsernameLen = strlen(Username);
int PasswordLen = strlen(Password);
int DataLen;
mqtt_txlen=0;
//可变报头+Payload 每个字段包含两个字节的长度标识
DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2);
//固定报头
//控制报文类型
mqtt_txbuf[mqtt_txlen++] = 0x10; //MQTT Message Type CONNECT
//剩余长度(不包括固定头部)
do
{
u8 encodedByte = DataLen % 128;
DataLen = DataLen / 128;
// if there are more data to encode, set the top bit of this byte