ESP8266连接MQTT服务器并发送数据的步骤详解

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 = 0
      timeClient.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 reconnected
      while (!client.connected())
      {
        Serial.print("Attempting MQTT connection...");
        // Attempt to connect
        // chipId 在这里是客户端连接的sessionid,同账户名下,不能相同,这里用芯片id
       String 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 retrying
          delay(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;
      // 拼接芯片的hostname
      sprintf(chipId, "xm_%08X", ESP.getChipId());
      res = wm.autoConnect(chipId, "12345678"); // 密码认证模式的AP
      if (!res)
      {
        Serial.println("Failed to connect");
        // ESP.restart();
      }
      else
      {
        // if you get here you have connected to the WiFi
        WiFi.setHostname(chipId); // 从模式后设置设备名
        Serial.println("connected...yeey :)");
      }
      // 初始化NTP时间服务器连接
      timeClient.begin();
      // 设置时区偏差,中国地区偏差+8
      // GMT +1 = 3600
      // GMT +8 = 28800
      // GMT -1 = -3600
      // GMT 0 = 0
      timeClient.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 reconnected
      while (!client.connected())
      {
        Serial.print("Attempting MQTT connection...");
        // Attempt to connect
        // chipId 在这里是客户端连接的sessionid,同账户名下,不能相同,这里用芯片,固定配置下写死即可uc_01_keting_01
       String 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 retrying
          delay(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的亮度。

    物联沃分享整理
    物联沃-IOTWORD物联网 » ESP8266连接MQTT服务器并发送数据的步骤详解

    发表评论