13-ESP8266连接MQTT服务器发送数据

2023-11-08

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的亮度。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

13-ESP8266连接MQTT服务器发送数据 的相关文章

  • 如何解决java.lang.NoClassDefFoundError--第二部分

    如何解决NoClassDefFoundError 第二部分 第一部分请看 http vipcowrie iteye com blog 1561291 本文面向的是JAVA初学者 建议你们自己编译和运行例子程序 本文包含了NoClassDef
  • c++单链表的简单实现(内含实现代码)

    考研报名等待之时闲来无事 写了一个简单的单链表 简单实现了以下功能 头插法建立单链表 按序遍历链表 单链表原地排序 不使用额外的空间 单链表按序删除 单链表原地倒置 附上代码如下 节点结构体定义 typedef int ElemType t
  • uniapp uview内置样式记录

    uview内置样式 文字省略 u line 1 u line 2 u line 3 u line 4 u line 5五个类名在文字超出内容盒子时 分别只显示一行 两行 三行 四行 五行 省略号 定位 uView内置了关于相对和绝对定位的两

随机推荐

  • 基于springboot的旅游信息管理系统完整源码

    基于springboot的旅游信息管理系统完整源码 技术栈 jdk1 8 mysql8 maven3 6 0 idea 功能模块 旅游路线 旅游景点 旅游酒店 旅游车票 旅游保险 旅游策略 订单管理 留言管理 数据分析等等 项目下载 htt
  • Matlab 如何生成一个[a,b]范围内随机整数的2种方法【已经解决】

    如何使用MATLAB生成一个 a b 范围内的随机整数 比如 随机生成 9 13 范围内的一个 或多个 整数 首先感谢 slandarer的指正 现已经更改 round 为四舍五入取整 而非向上取整 方法1为一个较为不错的方法 方法1 ra
  • 游戏开发-虚幻引擎天源了 [分享]

    https www unrealengine com zh CN 虚幻引擎4现在可供每个人免费使用 而且所有未来的更新都将免费 您可以下载引擎并将其用于游戏开发的各个方面 包括教育 建筑以及可视化 甚至虚拟现 实 电影和动画 当您发布游戏或
  • 计算机图形学入门(十六)-光线追踪(渲染方程)

    本部分主要介绍了渲染方程的逐步完善和简单的推导过程 从BRDF开始 到反射公式的推导再到渲染方程的完善 最后展示了实际渲染的例子 学习视频 GAMES101 现代计算机图形学入门 闫令琪 哔哩哔哩 bilibilihttps www bil
  • leaftlet 显示个性化图标、旋转图标

    1 引用leaftlet 高版本 比如1 8 3 在map js 中定义图标 L marker geo rotationAngle 270 icon L AwesomeMarkers icon icon awesomeIcon prefix
  • 创建匿名线程的5种方式

    package mythread 使用匿名内部类开启线程 public class Demo02anonymous thread public static void main String args 方式一 使用匿名内部类创建线程的子类对
  • java 是静态语言还是动态_java是动态语言还是静态语言?,

    java是动态语言还是静态语言 Java是动态语言还是静态语言 Java是一种静态语言 Java是编译时确定的变量类型 不能在运行时更改 在类型转换中也是强制的 例如 当大规模整数类型转换为小规模整数类型时 必须进行强转换 比如int必须强
  • 【TVM 学习资料】用 Schedule 模板和 AutoTVM 优化算子

    本篇文章译自英文文档 Optimizing Operators with Schedule Templates and AutoTVM 作者是 Lianmin Zheng Chris Hoge 更多 TVM 中文文档 访问 TVM 中文站
  • torch的GLU函数

    import torch nn as nn nn GLU GLU门控线性单元的描述 API
  • memcached查看所有的key

    memcached list all keys 1 打开命令窗口输入 gt telnet 127 0 0 1 11211 2 查找 输入 stats items 回车 显示各个slab 中 item 的数目和存储时长 最后一次访问距离现在的
  • 企业消息转发服务器,Python构建企业微信自动消息转发服务端

    目前有在项目分组 就小组成员中 微信群消息回复较多的情况下 想根据组来转发特定消息 包含文字 图片 语言等 在此只是自己实现仅供参考 可以根据自身需求修改更多功能 二 代码 2 1 企业微信相关信息 企业ID corpid 自建应用appi
  • ThinkPHP 的join关联查询不使用默认的表前缀

    关于ThinkPHP 的关联查询 官方文档是这样描述的 上述join函数中需要三个参数 分别是 join 要关联的 完整 表名以及别名 支持三种写法 写法1 完整表名或者子查询 gt 别名 写法2 完整表名 别名 写法3 不带数据表前缀的表
  • 100 个 Python 小项目源码,总有一个用得到

    学习 Python 会有这么一个阶段 太简单的程序看不上眼 复杂的开源项目又有点力不从心 这个时候 你就需要接触点简单的 Python 小项目来提升 Python 技能 碰巧 GitHub 上有这样一个项目 收集了 100 个简单的 Pyt
  • <并发编程>学习笔记------(一) 并发相关理论

    前面 并发编程可以总结为三个核心问题 分工指的是如何高效地拆解任务并分配给线程 同步指的是线程之间如何协作 互斥则是保证同一时刻只允许一个线程访问共享资源 并发相关理论 可见性 原子性和有序性 核心矛盾 CPU 内存 I O 设备的速度差异
  • OpenWrt通过终端查询版本

    一 cat etc banner 二 cat proc version 三 cat etc openwrt release 四 uname a 五 opkg version
  • go操作数据库Null类型转换失败问题

    sql NullString 解决数据库中null值的问题 go操作DB需要注意的
  • 向MySQL数据库表内导入txt和csv文件数据

    本文总结了在CentOS7上使用 LOAD DATA
  • iOS开发时如何使用 Launch Screen Storyboard

    原文 http useyourloaf com blog using a launch screen storyboard 静态启动图片 启动图片是iOS加载App的时候系统响应的直观呈现 近几年 随着屏幕尺寸的增多 制作相应的静态图片就变
  • Netty消息接收类故障案例分析

    Netty 进阶之路 分布式服务框架原理与实践 作者李林锋深入剖析Netty消息接收类故障案例 李林锋此后还将在 InfoQ 上开设 Netty 专题持续出稿 感兴趣的同学可以持续关注 1 背景 1 1 消息接收类故障 尽管Netty应用广
  • 13-ESP8266连接MQTT服务器发送数据

    Author teacherXue 一 什么是MQTT 定义 MQTT是一个基于客户端 服务器的消息发布 订阅传输协议 MQTT协议是轻量 简单 开放和易于实现的 这些特点使它适用范围非常广泛 在很多情况下 包括受限的环境中 如 机器与机器