Author:teacherXue

一、什么是MQTT

  1. 定义

MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。——来自于百度百科。

  1. 特点

MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:

  • 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;

  • 对负载内容屏蔽的消息传输;

  • 使用 TCP/IP 提供网络连接;

  • 有三种消息发布服务质量;

  • 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;

  • 使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制。

  1. 基本工作结构

通过百度百科的解释,我们可以看到其运行模式非常适合物联网场景中的大量不同的物联网终端和其他应用端的数据交互需求。而运行该协议服务的服务器就称之为MQTT服务器。

其基本工作结构如下图(不考虑终端用户鉴权资源隔离等情况下),MCU硬件端(MCU端可以是任何具备智能网关功能的设备,ESP芯片、开发板、手机、甚至于PC)可以任意数量接入平台,发送任意传感器数据,并接受相关控制指令。同时遵循本协议开发的应用端可以自动匹配所接入的mcu以及相应的传感器和控制方式。平台结构图如下:

  1. 相关角色

1)MCU

微控制器单元,为具备互联网连接能力,以上报传感器数据为主要任务目标的,任意类型物联网接入端,如鸿蒙平台、esp平台、手机平台等。

2)物联网服务器

部署于公网之上的,提供mqtt消息的订阅和用户角色鉴权服务的平台,不进行存储,以获得最高的服务性能。

3)应用端

通过mqtt协议,获得相应mcu端数据,并根据业务的具体需要,提供数据的展示交互控制能力,可以是java、c#、python任意类型开发语言,业务逻辑由应用端实现。

二、mqtt消息模板和规范

大量的设备和应用都要通过MQTT消息来传输数据。那么我们需要保证数据结构的一致性,才可以保证其良好的通用性。如果使用第三方的MQTT物联网平台,则其本身对消息的协议标准还有具体要求。

本例中采用自己部署在公网上的MQTT服务器,因此协议的重点放在产品模板规定上,并适配了具有不同权限和应用的订阅地址进行隔离,有需要的同学可以参考标准订阅方式。

  1. mqtt服务器

如何构建服务器不是本章的重点,个人使用的是Eclipse Mosquitto。推荐使用cedalo综合平台,它包括一个Eclipse Mosquitto物联网服务,一个cedalo基于web的图形化管理web终端,以及EclipseStreamsheets数据可视化定制平台。其采用docker方式进行部署非常的便捷。

其安装也比较简单,重点是官方文档非常详细,提供windows、macos、树莓派、linux多种平台的安装教程。官网地址https://cedalo.com/ ,文档地址https://docs.cedalo.com/mosquitto/installation/。有需要的同学可以自己部署服务,但公网部署就要自己想办法了。

  1. 本次实验消息订阅规范

本文中公网服务器地址为实训中对学校提供,不对外无偿使用。以下协议规范考虑到了多学校、多班级、多组的共同实训,网络上的同学们需根据自己的情况修改。

1) 物联网服务器连接

访问地址:xue1024.XXXX.cn

端口:1883

通信协议标准:mqtt5.0、3.0、2.0

2)以下账户为实训院校账户

MCU用户名:院校缩写_年级_mcu

应用端用户名:院校缩写_年级_stu

密码:XXXXXXXX

3)订阅地址

消息,按不同组织、用图、产品线通过订阅地址进行区分,并按账户角色进行访问权限隔离,基础格式为:

MCU发送消息订阅地址(数据消息获得订阅与此相同):iss/行业/产品/平台/data/芯片ID(进行用户区分时,为iss/行业/产品/平台/data/用户token/芯片ID)。

应用端控制指令发送地址(MCU端控制指令订阅与此相同):iss/行业/产品/平台/order/芯片ID(进行用户区分时,为iss/行业/产品/平台/order/用户token/芯片ID)。

4)某院校实训平台订阅地址使用如下(有实训需求的院校可以和我联系):

MCU数据发布地址:iss/lot/院校缩写_年级/mcu/data/两位组号/MCU_ID/

控制指令订阅地址:iss/lot/院校缩写_年级/mcu/order/两位组号/MCU_ID/

  1. MCU端发送数据消息协议结构

此协议定义传输的数据如何封装和解析的规范,遵循本协议的MCU端和应用端可自动任意数量和传感器类型适配,开发者如使用自己的规范标准,需统MCU和应用端适配,消息长度超过部分MCU端消息默认长度,需对应修改。

协议规范标注

{//协议版本号,发送方端和解析端需遵循相同标准*"protocol":"1.0",//iss_mcu芯片编号,取微控制器芯片MAC地址,同时是设备的局域网络接入名称*"chip_id": "value",//mcu型号,业务处理备用"chip_type": "芯片型号",//所属产品线,用以区分场景*"product_line": "产品线",//数据发送时间戳,long型*"timestamp": "发送时间戳",//微控制器接入网络的公网ip地址"public_ip": "公网ip",//内网IP地址,供客户端显示使用"private_ip": "内网IP",//提供给客户端做进一步鉴权验证使用"user_name": "用户名",//提供给客户端做进一步鉴权验证使用,用户名和密码加指令发送时间经md5加密后得到"user_token": "用户令牌",//MCU接驳传感器列表,数量任意,多数据传感器需拆分成独立逻辑传感器*"sensor_list": [{//mcu所接驳传感器在传感器上的调用编号,唯一整数*"number":  "value",//当前传感器型号,可重复*"type": "传感器型号",//当前传感器数据名*"name": "传感器名字",//返回消息备用"message": "消息",//传感器数据值*"data": {//数据列表,单一传感器有多个值统一控制时,例如灯光R、G、B、亮度。颜色名称、亮度等。*"data_vals":[{//数据名称*"data_name":"value",//数据值*"data_val":"value",//该数据的上阈值"max_val ":  "value",//该数据的下阈值"min_val ":  "value"},{"data_name":"value","data_val":"value","max_val ":"value","min_val ":"value"}],          //该数据类型读写状态*"data_type":  "read/write",//该数据的有效时间,具体时间时间戳的长整形"effective_time":  "value",//该数据的当前开关状态,开、关、不可控。例如当前灯光的开关状态"data_state":  "on/off/invalid"},//任务集合,集合方式数量任意,任务分单次任务和周期重复任务。此处为微控制器回传数据,任务列表作为应用端解析显示使用,无此需求的可以不用回传。"tasks": [{//任务类型,单次任务\周期任务\延迟任务*"task_type":  "single/repeat/delay",//任务编号,唯一整数*"task_number":  "value",//任务名称*"task_name":  "value",//任务状态,运行中、停止中、失效的(执行端因故未能运行)"task_state":  "run/stop/invalid",//任务状态为延时,延期执行时间,单位毫秒,也可以作为任务执行时长                  "delay_time":  "value",                  //任务状态为repeat时,覆盖每周几 ,全部则是每天 ,单次和延期任务时无此项目              "week_day": [  0, 1, 2, 3, 4, 5, 6 ],//任务开始时间,长整形,当前日期零时长整形到具体时间的差值,如时间段任务设置,需增加开始和结束两项任务."start_time":  "value",   //单次任务预定执行时间,长整形,如时间段任务设置,需增加开始和结束两项任务"single_time":"value",//任务指令,具体被控制的属性和值"exec_order": [{//控制名"order_name":  "value",//控制值"order_val": "value"},{  "order_name": "value","order_val":  "value"},{  "order_name": "value","order_val":  "value"}]},{            "task_type": "single/repeat/delay","task_number": "value","task_name": "value","task_state": "on/off/invalid","delay_time": "timestamp","repeat_type": "no/week","week_day": [ 0, 1, 2, 3, 4, 5, 6 ],"start_time": "value","single_time":"value","exec_order": [{"order_name":"value","order_val":  "value"},{"order_name":"value","order_val":  "value"},{"order_name":"value","order_val":  "value"}]},{             "task_type":  "single/repeat/delay","task_number": "value","task_name": "value","task_state": "on/off/invalid","delay_time": "timestamp","repeat_type": "no/week","week_day": [ 0, 1, 2, 3, 4, 5, 6 ],"start_time": "value","single_time":"value","exec_order": [{"order_name":"value","order_val":  "value"},{"order_name":"value","order_val":  "value"},{"order_name":"value","order_val":  "value"}]}]},{"...": "..."}]
}

不含注释版本

{"protocol":"1.0","chip_id":"value","chip_type":"芯片型号","product_line":"产品线","timestamp":"发送时间戳","public_ip":"公网ip","private_ip":"内网IP","user_name":"用户名","user_token":"用户令牌","sensor_list":[{"number":"value","type":"传感器型号","name":"传感器名字","message":"消息","data":{"data_vals":[{"data_name":"value","data_val":"value","max_val ":"value","min_val ":"value"},{"data_name":"value","data_val":"value","max_val ":"value","min_val ":"value"}],"data_type":"read/write","effective_time":"value","data_state":"on/off/invalid"},"tasks":[{"task_type":"single/repeat/delay","task_number":"value","task_name":"value","task_state":"run/stop/invalid","delay_time":"value","week_day":[0, 1, 2, 3, 4, 5, 6],"start_time":"value","single_time":"value","exec_order":[{"order_name":"value","order_val":"value"},{"order_name":"value","order_val":"value"},{"order_name":"value","order_val":"value"}]},{"task_type":"single/repeat/delay","task_number":"value","task_name":"value","task_state":"on/off/invalid","delay_time":"timestamp","repeat_type":"no/week","week_day":[0, 1, 2, 3, 4, 5, 6],"start_time":"value","single_time":"value","exec_order":[{"order_name":"value","order_val":"value"},{"order_name":"value","order_val":"value"},{"order_name":"value","order_val":"value"}]},{"task_type":"single/repeat/delay","task_number":"value","task_name":"value","task_state":"on/off/invalid","delay_time":"timestamp","repeat_type":"no/week","week_day":[0, 1, 2, 3, 4, 5, 6],"start_time":"value","single_time":"value","exec_order":[{"order_name":"value","order_val":"value"},{"order_name":"value","order_val":"value"},{"order_name":"value","order_val":"value"}]}]},{"...":"..."}]
}
  1. 协议项说明

协议名

作用

值列表

protocol

当前协议版本,为解析依据

1.0

chip_id

MCU唯一识别编号,为MAC地址,同时为MCU端的连接ID

Iss_十六进制

chip_type

//mcu型号,业务处理备用

product_line

所属产品线,用以区分场景

智慧农业、智慧家居、智慧园区…

timestamp

数据发送时间戳,long型

public_ip

MCU接入时的公网IP地址,可以大数据做地域分析。应用端可以根据地域查询地域公共信息。

private_ip

MCU接入局域网IP地址,应用端如需管理设备时使用。

user_name

用户名,应用端做进一步鉴权使用

user_token

用户访问令牌

sensor_list[ ]

MCU接驳传感器列表,应用端获得MCU所挂载的传感器集合

传感器

{ sensor_list }number

传感器在MCU上管理的序号,MCU端唯一。

{ sensor_list }type

传感器型号,应用端做界面构建时的类型判断。

例如lamp(灯)、led(led灯)、T(温度)、H(湿度)、PW(电源)、RGB(三色全彩灯)、LUX(光照)、GAS(气体)、curtain(窗帘)、motor(电机)、lamp_n(不可调光),voice(声音),flame(火焰),smoke(烟雾),PIR(红外遥控器),buzzer(蜂鸣器),body(人体),knob(旋钮控制器),water(水位)、soil(土壤)、hm(距离)、valve(阀门控制),具体取值参考设备名称对应表。

{ sensor_list }name

传感器名字,应用端做界面构建时控制类型判断

{ sensor_list }message

返回消息备用

字符长度小于等于50

{ sensor_list }data

传感器数据值,用于返回检测数据

data_vals子对象集合

{ sensor_list }.{data}data_vals[ ]

数据列表,单一传感器有多个值统一控制时,

例如灯光R、G、B、亮度。颜色名称、亮度等

{sensor_list}.{data}.[data_vals] data_name

传感器数据对应名称

例如可控灯的亮度、色温。具体取值参考控制参数名称对应列表。

{ sensor_list }.{data}.[data_vals]data_val

传感器对应数据值。

{ sensor_list }.{data}.[data_vals]max_val

该数据的上阈值

{ sensor_list }.{data}.[data_vals]min_val

该数据的下阈值

{ sensor_list }.{data}data_type

传感器数据类型,是否可读可控

r w rw

{ sensor_list }.{data}effective_time

数据发送后是否有有效时间

毫秒为单位的长整形

{ sensor_list }.{data}data_state

该传感器此数据项状态

开、关、不可控,具体取值参考控制参数名称对应列表。

{ sensor_list }.{ tasks }[ ]

当前传感器任务集合,集合方式数量任意,任务分单次任务和周期重复任务。此处为微控制器回传数据,任务列表作为应用端解析显示使用,无此需求的可以不用回传。

任务项集合

{ sensor_list }.{ tasks }[ ] task_type

任务类型

任务类型,单次任务\周期任务\延迟任务,single/repeat/delay,具体取值参考控制参数名称对应列表。

{ sensor_list }.{ tasks }[ ] task_number

任务编号,当传感器唯一整数

{ sensor_list }.{ tasks }[ ] task_name

任务名称

{ sensor_list }.{ tasks }[ ] task_state

任务状态

运行中、停止中、失效的(执行端因故未能运行),run/stop/invalid,具体取值参考控制参数名称对应列表。

{ sensor_list }.{ tasks }[ ] delay_time

任务状态为延时,延期执行时间,单位毫秒,也可以作为任务执行时长

毫秒为单位的长整形

{ sensor_list }.{ tasks }[ ] week_day[ ]

任务状态为repeat时,覆盖每周几 ,全部则是每天 ,单次和延期任务时无此项目

0~6序列,代表周日到周六。

0,1,2,3,4,5,6

{ sensor_list }.{ tasks }[ ] start_time

任务开始时间,长整形,当前日期零时长整形到具体时间的差值,如时间段任务设置,需增加开始和结束两项任务.

{ sensor_list }.{ tasks }[ ] single_time

单次任务预定执行时间,长整形,如时间段任务设置,需增加开始和结束两项任务

{ sensor_list }.{ tasks }[ ] exec_order[ ]

任务指令,具体被控制的属性和值

控制项:控制值

{ sensor_list }.{ tasks }[ ] exec_order[ ] order_name

控制项名称,

如转速、预定角度、亮度,具体取值参考控制参数名称对应列表。

{ sensor_list }.{ tasks }[ ] exec_order[ ] order_val

控制值

  1. 传感器名称和数据对应表

以下规范为传感器数据特性,非产品本身,产品可以是多个传感器控制器的封装。MCU端提报数据时按此协议规范,包括传感器数据和控制器当前状态数据。开发人员可根据自身平台设备进行扩充定义,如命名方式尊循该规范可以获得更好的适配性(on/off no/yes 为布尔型)。

以下规范为传感器数据特性,非产品本身,产品可以是多个传感器控制器的封装。MCU端提报数据时按此协议规范,包括传感器数据和控制器当前状态数据。开发人员可根据自身平台设备进行扩充定义,如命名方式尊循该规范可以获得更好的适配性(on/off no/yes 为布尔型)。

  • 智能家居类别

名称

设备类型

值类型

控制参数说明

灯光

控制类

lamp_no

不可调光灯光

开关状态

data_vals[{"data_name": “state” ,"data_val":“on/off”}]

lamp_ctl

可调光

开关状态

百分比亮度值

data_vals[

{"data_name": “state” ,"data_val": “on/off”},

{"data_name": “extent” ,"data_val": “0~100”}

]

lamp_cct

色温可调灯光

开关状态

百分比亮度值

百分比色温值

data_vals[

{"data_name": “state” ,"data_val": “on/off”},

{"data_name": “extent” ,"data_val": “0~100”},

{"data_name": “cct_val” ,"data_val": “0~100”}

]

lamp_rgb

全彩可调灯光

开关状态

百分比亮度值

百分比色温值

颜色名称/

红色通道

绿色通道

蓝色通道/

H色调通道

S饱和度通道

V明度通道

data_vals[

{"data_name": “state” ,"data_val": “on/off”},

{"data_name": “extent” ,"data_val": “0~100”},

{"data_name": “cct_val” ,"data_val": “0~100”},

{"data_name": “color”,"data_val": “red/blue/golden…标准颜色名称”},

{"data_name": “r_val” ,"data_val": “0~255”},

{"data_name": “g_val” ,"data_val": “0~255”},

{"data_name": “b_val” ,"data_val": “0~255”},

{"data_name": “h_val” ,"data_val": “0~255”},

{"data_name": “s_val” ,"data_val": “0~255”},

{"data_name": “v_val” ,"data_val": “0~255”}

]

lamp_pro

程控灯光

由对应设备开发人员自行定义控制项目

开关伺服

控制类

pw_sw

电源开关

开关状态

data_vals[{"data_name": “state” ,"data_val": “on/off”}]

knob_ctl

旋转开关

开关状态

开关幅度

data_vals[

{"data_name": “state” ,"data_val": “on/off”},

{"data_name": “extent” ,"data_val": “0~100”}

]

valve_ctl

可控阀门

阀门打开程度

data_vals[{"data_name": “state” ,"data_val":“0~100”}]

curtain_ctl

可控窗帘

窗帘打开幅度

data_vals[{"data_val": “0~100”}]

jalousie_ctl

可控百叶窗帘

窗帘打开幅度

百叶方向

百叶偏转幅度

data_vals[

{"data_name": “extent” ,"data_val": “0~100”},

{"data_name": “direction” ,"data_val": “up/down”},

{"data_name": “angle” ,"data_val": “0~100”}

]

window_ctl

可控窗户

窗户打开幅度

data_vals[{"data_val": “0~100”}]

buzzer

蜂鸣器

蜂鸣状态

data_vals[{"data_val": “on/off”}]

servo

舵机执行器

舵机角度

data_vals[

{"data_val": “浮点数”}

]

step_motor

步进电机

当前步数

目标步数

速度

data_vals[

{"data_name": “val” ,"data_val": “整数},

{"data_name": “target” ,"data_val": “整数},

{"data_name": “speed” ,"data_val": “整数”}

]

door_ctl

可控门

门打开幅度

data_vals[{"data_val": “0~100”}]

数据传感器类

temp

温度传感器

温度值

data_vals[{"data_val": “有符号浮点数”}]

hum

湿度传感器

湿度值

data_vals[{"data_val": “有符号浮点数”}]

th

温湿度传感器

温度值

湿度值

data_vals[

{"data_name": “t_val” ,"data_val": “有符号浮点数”},

{"data_name": “h_val” ,"data_val": “有符号浮点数”}

]

lx

光照强度传感器

照度值

data_vals[

{"data_val": “整数”}]

voice

声音传感器

声响响应

模拟量值

data_vals[

{"data_name": “rps” ,"data_val": “no/yes”},

{"data_name": “val” ,"data_val": “浮点数”}

]

smoke

烟雾传感器

烟雾响应

模拟量值

data_vals[

{"data_name": “rps” ,"data_val": “no/yes”},

{"data_name": “val” ,"data_val": “浮点数”}

]

body

人体传感器

人体响应

data_vals[{"data_val": “no/yes”}]

flame

火焰传感器

火焰响应

模拟量值

data_vals[{"data_name": “rps” ,"data_val": “no/yes”},

{"data_name": “val” ,"data_val": “浮点数”}

]

water

水传感器

浸润响应

模拟量值

data_vals[{"data_name": “rps” ,"data_val": “no/yes”},

{"data_name": “val” ,"data_val": “浮点数”}

]

water_lv

水位传感器

水位值

data_vals[{"data_val": “有符号浮点数”}]

raindrop

雨滴传感器

下雨状态

模拟量值

data_vals[

{"data_name": “rps” ,"data_val": “no/yes”},

{"data_name": “val” ,"data_val": “浮点数”}

]

hpa

气压传感器

气压值

data_vals[{"data_val": “有符号浮点数”}]

fuelgas

燃气传感器

燃气报警

模拟量值

data_vals[

{"data_name": “rps” ,"data_val": “no/yes”},

{"data_name": “val” ,"data_val": “浮点数”}

]

soil

土壤传感器

土壤湿度响应

模拟量值

data_vals[

{"data_name": “rps” ,"data_val": “no/yes”},

{"data_name": “val” ,"data_val": “浮点数”}

]

hm

距离传感器

距离响应状态

距离值

距离单位

data_vals[

{"data_name": “rps” ,"data_val": “no/yes”},

{"data_name": “val” ,"data_val": “浮点数”},

{"data_name": “unit” ,"data_val": “value”}

]

voltage

电压传感器

电压值

data_vals[

{"data_val": “浮点数”}

]

current

电流传感器

电流值

data_vals[

{"data_val": “浮点数”}

]

  1. 应用端指令发送协议结构

协议规范

{

//芯片ID

"chip_id":value,

//发送时间戳

"timestamp":value,

//指令列表

"task_list":[

//指令1

{

//要控制的外设编号,和MCU端外设匹配

"number":value,

//控制项和控制值,处理时要先判断是否有值,再进行处理

"控制项1": value },

//指令2

{

//要控制的外设编号,和MCU端外设匹配

"number": value,

//控制项和控制值,处理时要先判断是否有值,再进行处理

"控制项1": value,

//可以有多个控制项,控制项参考传感器数据定义协议

"控制项2": value

}

]}

案例

{

"chip_id": "xm_00749E03",

"timestamp": 1677856857,

"task_list": [{

"number": 1,

"state": true

},

{

"number": 2,

"state": true,

"extent": 30

}

]

}

三、ESP8266实现MQTT消息发送

下面我们将按照预定标准,使用之前章节构建的平台进行mqtt数据的发送。此实验网络中上的小伙伴需自行解决MQTT服务器问题,可以自己安装。

  1. 创建项目

1)新建项目Lot_mqtt_json_test_v2.0,我们将在上一章节项目上修改。

2)配置串口波特率115200

3)需要通过扩展库管理器安装如下扩展库

  • DHT sensor library

  • ArduinoJson

  • TaskScheduler

  • JC_Button

  • NTPClient

  • PubSubClient:MQTT订阅扩展库,本例中数据较大,初始化时需定义缓冲大小。

4)在项目lib文件夹本地安装库的方式安装以下扩展库:

  • WiFiManager:如果无高需求,也可以采用上面扩展库安装方式。

  1. 代码解析

1)在上一章节项目中增加头文件导入mqtt消息库,以及网络时间服务的NTPClient,以及所需的UDP库

#include <Arduino.h>
#include <JC_Button.h>
#include <WiFiManager.h>
// 导入多任务库
#include <TaskScheduler.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
// 导入NTP网络时间服务扩展库
#include <NTPClient.h>
// 导入UDP扩展库,
#include <WiFiUdp.h>
// json对象处理类,6.x 和5.x不一样 ,这里用的是6.19
#include <ArduinoJson.h>
#include <PubSubClient.h> // mqtt消息订阅库

2)声明mqtt消息服务所需的参数

// mqtt服务器连接信息
// xue1024.tpddns.cn
const char *mqttServer = "xue1024.XXXXX.cn"; // 个人mqtt服务器
const int mqttPort = 1883;                    // mqtt端口号
const char *mqttUser = "院校缩写_年级_mcu";        // 用户名
const char *mqttPassword = "你的密码";      // 密码

3)准备存储订阅发送和订阅接收的消息地址缓冲

// 传感器数据订阅地址,mcu向里面发数据,客户端去里面取数据
char outTopic[50]; // 订阅发送地址
// 控制指令订阅地址,mcu订阅,客户端发送
char inTopic[50]; // 订阅接收地址缓存

4)创建新增扩展库对象

// mqtt连接对象
WiFiClient espClient;
PubSubClient client(espClient);// 创建UDP对象
WiFiUDP ntpUDP;
//NTP时间服务器对象
NTPClient timeClient(ntpUDP, "ntp.aliyun.com");

5)函数前置声明,其中callback是mqtt接收到消息后执行的回调函数,reconnect为保持mqtt连接的方法,publishMQTT为发送消息的方法。

// 前置函数声明
void getTH();
void getKnob();
void getJson();
void callback(char *topic, byte *payload, unsigned int length); // mqtt回调函数
void reconnect();                        // mqtt保持连接
void publishMQTT();                      // 发送mqtt数据

6)上一章节的getJson不在需要任务调度,由消息发送时调度生成即可。

Task t3(5000, TASK_FOREVER, &publishMQTT); // 任务名称t3,间隔5秒,一直执行。

7)Begin方法中增加ntp时间服务器对象的初始化,并调整中国地区的时区差,标准时区+8。

// 初始化NTP时间服务器连接timeClient.begin();// 设置时区偏差,中国地区偏差+8// GMT +1 = 3600// GMT +8 = 28800// GMT -1 = -3600// GMT 0 = 0timeClient.setTimeOffset(28800);

8)Setup方法内增加订阅消息地址的完善,修改mqtt消息大小和连接超时时间。

//-------------- 订阅地址(xm替换为组号)--------------sprintf(inTopic, "iss/lot/xust_19/mcu/order/两位组号/%s/", chipId);sprintf(outTopic, "iss/lot/xust_19/mcu/data/两位组号/%s/", chipId);Serial.printf("inTopic:%s\n", inTopic);Serial.printf("outTopic:%s\n", outTopic);// mqtt连接client.setServer(mqttServer, mqttPort);client.setBufferSize(2048);//设置mqtt消息传输包的大小client.setSocketTimeout(60);//设置mqtt连接超时client.setCallback(callback);//接收到消息后的回调处理方法//-----------------------------------------------

9) 保持mqtt连接的方法,首次连接时也是此方法

// mqtt保持连接方法
void reconnect()
{// Loop until we're reconnectedwhile (!client.connected()){Serial.print("Attempting MQTT connection...");// Attempt to connect// chipId 在这里是客户端连接的sessionid,同账户名下,不能相同,这里用芯片idString clientId = String(chipId)+"-"+String(random(0xffff), HEX);if (client.connect(clientId.c_str(), mqttUser, mqttPassword)){Serial.println("connected...");}else{Serial.print("failed, rc=");Serial.print(client.state());Serial.println(" try again in 5 seconds");// Wait 5 seconds before retryingdelay(5000);//如果失败延迟五秒后连接}}
}

10) 发布mqtt消息的方法

// 发布mqtt消息
void publishMQTT()
{getJson(); // 生成要发送的json数据
//保持连接if (!client.connected()){reconnect();}client.loop();// 发送mqtt数据client.publish(outTopic, messageBuff, true);// Serial.println(messageBuff);//client.subscribe(inTopic);//关闭连接client.endPublish();  // 发送完成后清除缓存数据memset(messageBuff, 0, 1024);
}

11) 完整代码

#include <Arduino.h>
#include <JC_Button.h>
#include <WiFiManager.h>
// 导入多任务库
#include <TaskScheduler.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
// 导入NTP网络时间服务扩展库
#include <NTPClient.h>
// 导入UDP扩展库,
#include <WiFiUdp.h>
// json对象处理类,6.x 和5.x不一样 ,这里用的是6.19
#include <ArduinoJson.h>
#include <PubSubClient.h> // mqtt消息订阅库#define LED_1 D5
#define LED_2 D6
#define BUTTON_PIN D4
#define DHTPIN D3      // DHT温湿度传感器引脚
#define analogInPin A0 // 模拟输入引脚A0#define DHTTYPE DHT22     // 声明DHT传感器类型
DHT dht(DHTPIN, DHTTYPE); // 创建DHT对象
Scheduler runner;         // 任务调度器对象
Button myBtn(BUTTON_PIN); // 按钮对象// 温湿度值全局保存
float t = 0.0;
float h = 0.0;
// led灯光状态
bool led1state = false;
bool led2state = false;
// 旋钮值和灯光亮度的保存
unsigned int knobValue = 0;
unsigned int dutyCycle = 0;// 存放芯片ID的缓冲
char chipId[10];
// 消息发送缓冲,json最后字符串方式传输
char messageBuff[1024];// mqtt服务器连接信息
// xue1024.tpddns.cn
const char *mqttServer = "xue1024.tpddns.cn"; // 个人mqtt服务器
const int mqttPort = 1883;                    // mqtt端口号
const char *mqttUser = "shixun_admin";        // 用户名
const char *mqttPassword = "teacherxue";      // 密码
// long mqtt_interval = 2000;
// long mqtt_comiit = 0;
// 传感器数据订阅地址,mcu向里面发数据,客户端去里面取数据
char outTopic[50]; // 发送地址
// 控制指令订阅地址,mcu订阅,客户端发送
char inTopic[50]; // 订阅地址缓存
// mqtt连接对象
WiFiClient espClient;
PubSubClient client(espClient);// 创建UDP对象
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "ntp.aliyun.com");// 前置函数声明
void getTH();
void getKnob();
void getJson();
void callback(char *topic, byte *payload, unsigned int length); // mqtt回调函数
void reconnect();                                               // mqtt保持连接
void publishMQTT();                                             // 发送mqtt数据Task t1(2000, TASK_FOREVER, &getTH);       // 任务名称t1,间隔2秒一直执行.
Task t2(30, TASK_FOREVER, &getKnob);       // 任务名称t2,间隔30毫秒,一直执行。
Task t3(5000, TASK_FOREVER, &publishMQTT); // 任务名称t3,间隔5秒,一直执行。
// Task t3(3000, TASK_FOREVER, &getJson);void setup()
{Serial.begin(115200);pinMode(LED_1, OUTPUT);pinMode(LED_2, OUTPUT);// led1为关灯digitalWrite(LED_1, LOW);// led2使用pwm调光analogWrite(LED_2, 0);dht.begin();    // DHT传感器对象工作myBtn.begin();  // button按钮运作WiFiManager wm; // wifi管理对象,配网用bool res;// 拼接芯片的hostnamesprintf(chipId, "xm_%08X", ESP.getChipId());res = wm.autoConnect(chipId, "12345678"); // 密码认证模式的APif (!res){Serial.println("Failed to connect");// ESP.restart();}else{// if you get here you have connected to the WiFiWiFi.setHostname(chipId); // 从模式后设置设备名Serial.println("connected...yeey :)");}// 初始化NTP时间服务器连接timeClient.begin();// 设置时区偏差,中国地区偏差+8// GMT +1 = 3600// GMT +8 = 28800// GMT -1 = -3600// GMT 0 = 0timeClient.setTimeOffset(28800);// 初始化任务调度器,规划任务链runner.init();runner.addTask(t1);runner.addTask(t2);runner.addTask(t3);t1.enable();t2.enable();t3.enable();//-------------- 订阅地址(xm替换为组号)-------------------sprintf(inTopic, "iss/lot/xust_19/mcu/order/xm/%s/", chipId);sprintf(outTopic, "iss/lot/xust_19/mcu/data/xm/%s/", chipId);Serial.printf("inTopic:%s\n", inTopic);Serial.printf("outTopic:%s\n", outTopic);// mqtt连接  client.setServer(mqttServer, mqttPort);client.setBufferSize(2048);//设置mqtt消息传输包的大小client.setSocketTimeout(60);//设置mqtt连接超时client.setCallback(callback);//-----------------------------------------------
}void loop()
{runner.execute(); // 执行任务// 处理按钮事件myBtn.read();if (myBtn.wasPressed()){led1state = !led1state;    }
digitalWrite(LED_1, led1state);analogWrite(LED_2, dutyCycle); // 根据状态控灯
}// 获得温湿度
void getTH()
{h = dht.readHumidity();// Read temperature as Celsius (the default)t = dht.readTemperature();
}// 旋钮操作
void getKnob()
{// 关灯时不做任何调整// if (!led2state)// {//   return;// }knobValue = analogRead(analogInPin);dutyCycle = map(knobValue, 15, 1008, 0, 255);
}// 获得json封装的数据
void getJson()
{//------------封装json对象传递----------------------------/* 申明一个大小为1K的DynamicJsonDocument对象JSON_Buffer,用于存储反序列化后的(即由字符串转换成JSON格式的)JSON报文,*/// DynamicJsonDocument  doc(2048);// 创建json对象StaticJsonDocument<1024> doc;// 创建json根节点对象JsonObject root = doc.to<JsonObject>();// root节点下创建子节点并赋值root["protocol"] = "1.0";root["chip_id"] = chipId;root["chip_type"] = "ESP8266-12E";root["product_line"] = "xust_19_teacher";root["private_ip"] = WiFi.localIP();root["public_ip"] = "";timeClient.update();root["timestamp"] = timeClient.getEpochTime();// 创建json对象集合,存放该mcu节点下的所有传感器列表JsonArray sensors = root.createNestedArray("sensor_list");// 集合节点,创建子节点对象// 1.不可调光主灯JsonObject s1 = sensors.createNestedObject();s1["number"] = 1;s1["type"] = "amp_no";s1["name"] = "厨房灯";JsonObject s1_data = s1.createNestedObject("data");JsonArray s1_data_ls = s1_data.createNestedArray("data_vals");JsonObject s1_data1 = s1_data_ls.createNestedObject();s1_data1["data_name"] = "state";s1_data1["data_val"] = led1state;// 2.可调光主灯JsonObject s2 = sensors.createNestedObject();s2["number"] = 2;s2["type"] = "lamp_ctl";s2["name"] = "主卧灯";JsonObject s2_data = s2.createNestedObject("data");JsonArray s2_data_ls = s2_data.createNestedArray("data_vals");JsonObject s2_data1 = s2_data_ls.createNestedObject();s2_data1["data_name"] = "state";s2_data1["data_val"] = led2state;JsonObject s2_data2 = s2_data_ls.createNestedObject();s2_data2["data_name"] = "extent";s2_data2["data_val"] = dutyCycle;// 3.温湿度传感器JsonObject s3 = sensors.createNestedObject();s3["number"] = 3;s3["type"] = "th";s3["name"] = "温湿度";JsonObject s3_data = s3.createNestedObject("data");JsonArray s3_data_ls = s3_data.createNestedArray("data_vals");JsonObject s3_data1 = s3_data_ls.createNestedObject();s3_data1["data_name"] = "t_val";s3_data1["data_val"] = t;JsonObject s3_data2 = s3_data_ls.createNestedObject();s3_data2["data_name"] = "h_val";s3_data2["data_val"] = h;
}// mqtt保持连接方法
void reconnect()
{// Loop until we're reconnectedwhile (!client.connected()){Serial.print("Attempting MQTT connection...");// Attempt to connect// chipId 在这里是客户端连接的sessionid,同账户名下,不能相同,这里用芯片,固定配置下写死即可uc_01_keting_01String clientId = String(chipId)+"-"+String(random(0xffff), HEX);if (client.connect(clientId.c_str(), mqttUser, mqttPassword)){Serial.println("connected...");}else{Serial.print("failed, rc=");Serial.print(client.state());Serial.println(" try again in 5 seconds");// Wait 5 seconds before retryingdelay(5000);}}
}// 发布mqtt消息
void publishMQTT()
{getJson(); // 生成要发送的json数据if (!client.connected()){reconnect();}client.loop();// 发送mqtt数据client.publish(outTopic, messageBuff, true);Serial.print("messageBuff:");Serial.println(messageBuff);//关闭连接client.endPublish();  // 发送完成后清除缓存数据memset(messageBuff, 0, 1024);
}// mqtt回调函数_这里未做进一步处理,下一章节进行解析
void callback(char *topic, byte *payload, unsigned int length)
{Serial.print("Message arrived [");Serial.print(topic);Serial.print("] ");payload[length] = '\0'; // 追加字符串结束字符for (int i = 0; i < length; i++){Serial.print((char)payload[i]);}Serial.print("\n");Serial.println();}
  1. 补全说明

本例重点为常规数据的发送,无紧急需求,所以采取发送时才建立连接,像ESP01芯片单独部署的温湿度传感器5分钟上报一次数据已经足够。下一章节中接收指令时,如果要求有即时性的命令,改为保持连接。

本例中未处理控制指令,LED2没有对APP控制做出兼容。下一章节将完整实现远程和本地的旋钮都可以控制LED2的亮度。

13-ESP8266连接MQTT服务器发送数据相关推荐

  1. esp8266连接mqtt服务器

    1.MQTT ESP8266库 菜单"项目"-"加载库"-"管理库",搜索安装"PubSubClient" PubSub ...

  2. 【STM32 x ESP8266】连接 MQTT 服务器(报文,附部分源码解析)

    MQTT 协议作为物联网非常重要的传输协议,如何使用它十分重要,如果有不理解的同学可以点击这里学习,这里只是简单介绍一下.同时这里附上MQTT 3.1.1协议中文版 pdf 的链接,对协议底层感兴趣的 ...

  3. ESP8266入门教程11:连接MQTT服务器

    将第三方库PubSubClient下载到lib文件夹 git clone https://github.com/knolleary/pubsubclient.git 一.阿里云IOT使用 1.打开阿里 ...

  4. ESP8266给服务器发送数据

    如标题,这里介绍如何通过esp8266给服务器发送消息,然后服务器处理这些消息 这里硬件方面的编译环境采用的arduinoIDE,服务端选择使用python3接收处理这些消息 esp8266是可以和服 ...

  5. 使用python编写mqtt客户端向EMQX服务器发送数据

    摘要:本文介绍如何用python编写一个mqtt客户端向EMQX服务器发送数据,实现一个简易的本地物联网服务器. 上一篇文章讲到使用mqtt.fx软件来发布消息. (1条消息) 使用mqtt.fx向E ...

  6. ESP8266与MQTT服务器收发送数据

    陈拓 2021/04/20-2021/04/25 1. 概述 我们在<Win10-Ubuntu子系统构建ESP8266_RTOS_SDK开发环境> https://zhuanlan.zhi ...

  7. 【Android RTMP】RTMPDump 推流过程 ( 独立线程推流 | 创建推流器 | 初始化操作 | 设置推流地址 | 启用写出 | 连接 RTMP 服务器 | 发送 RTMP 数据包 )

    文章目录 安卓直播推流专栏博客总结 一. Java 层传入的 RTMP 推流地址处理 二. RTMPDump 推流线程 三. 创建 RTMP 对象 四. 初始化 RTMP 对象 五. 设置 RTMP ...

  8. STM32F103C8T6+ESP-01S+MQTT服务器实现数据上传和接收(一)

    STM32F103C8T6+ESP-01S+MQTT服务器实现数据的上传和接收(一) 前言 ESP-01S固件烧录 主要AT指令 手写简单的库函数 常量定义 全局变量声明 串口初始化 关闭AT指令回显 ...

  9. ESP8266 连接 MQTT

    ESP8266 连接 MQTT 主控芯片:MM32F2377 (MB-039) WiFi 适配器:ESP8266 开发环境:IAR 7.80.4 MQTT 模拟服务器:MQTT.fx MQTT MQT ...

最新文章

  1. 企业怎样管理员工才会更有效?
  2. Callable、Future和FutureTask
  3. c语言 计算坐标,求助,有关坐标计算的
  4. leetcode —— 面试题29. 顺时针打印矩阵
  5. mysql索引别名_Mysql索引知识详谈
  6. go语言之数组与切片
  7. java加载publickey,比较java中的PublicKey对象
  8. Linux 下安装Postgresql
  9. 通过真实项目截图讲解MDT 2010部署windows 7的具体过程(3)
  10. php fckeditor,PHP FCKeditor使用说明
  11. 【ESP 保姆级教程】疯狂传感器篇 —— 案例:ESP8266 + 光敏光照传感器 + 串口输出
  12. 让“施工进度计划”真正产生价值——不是为了做计划而计划
  13. jq onclick 定义_jq中的onclick绑定事件
  14. 云刷工具q币android版,交流电app下载2021-交流电交友手机版v3.2.4最新版-游吧乐下载...
  15. 零基础学python这本书怎么样-怎样学 Python?
  16. [转载]般若波罗蜜多心经
  17. Android Studio查看SQLite数据库方法大全
  18. typora+阿里云图床+印象笔记+OneDrive安全保存你的文章
  19. 二十年架构师整理出的Java学习路线,学完年薪百万
  20. do…while循环语句

热门文章

  1. parser.add_argument()用法
  2. Android nomedia问题分析
  3. 如何生成IEEE论文源文件提交的高分辨率PDF?
  4. 67.220.92.12 forum index.php,index.asp
  5. redis使用IP地址访问
  6. 浙江省诸暨市诸暨荣怀学校四4班余波我家有一只可爱的小花猫
  7. word文档设置锚点
  8. double java 精度丢失_java中double和float精度丢失问题及解决方法
  9. Wannafly挑战赛13 B:Jxc军训(逆元)
  10. Mysql 1067系统错误终极解决方案全集