linux mqtt物联网系统

**

(本文以正点原子mqtt例程为基础,添加了cJSON格式的编码和解码,以及使用驱动的形式控制硬件设备)

**

1. MQTT
它是一种轻巧、开放、简单、规范的网络通信协议。与 HTTP 协议一样,MQTT 协议也是应用层协议,工作在 TCP/IP 四层模型中的最上层(应用层),构建于 TCP/IP 协议上。MQTT 最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服 务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

客户端
MQTT 客户端可以向服务端发布信息,也可以从服务端收取信息;我们把客户端发送信息的行为称为“发布”信息。而客户端要想从服务端收取信息,则首先要向服务端“订阅”信息。“订阅”信息这一操作 很像我们在使用微信时“关注”了某个公众号,当公众号的作者发布新的文章时,微信官方会向关注了该公 众号的所有用户发送信息,告诉他们有新文章更新了,以便用户查看。
MQTT主题
上面我们讲到了,客户端想要从服务器获取信息,首先需要订阅信息,那客户端如何订阅信息呢?这里我们要引入“主题(Topic)”的概念,“主题”在MQTT 通信中是一个非常重要的概念,客户端发布信息 以及订阅信息都是围绕“主题”来进行的,并且MQTT 服务端在管理MQTT 信息时,也是使用“主题”来 控制的。客户端发布消息时需要为消息指定一个“主题”,表示将消息发布到该主题;而对于订阅消息的客户端来说,可通过订阅“主题”来订阅消息,这样当其它客户端或自己(当前客户端)向该主题发布消息时,MQTT 服务端就会将该主题的信息发送给该主题的订阅者(客户端)。\

客户端相互独立
空间上分离
时间上可异步

2.连接MQTT服务端
①、首先客户端需要向服务端发送连接请求,这个连接请求实际上就是向服务端发送一个 CONNECT 报文,也就是发送了一个CONNECT 数据包。
②、MQTT 服务端收到连接请求后,会向客户端发送连接确认。连接确认实际上是向客户端发送一个 CONNACK报文,也就是CONNACK数据包。
在这里插入图片描述
MQTT 报文组成分为三个部分:固定头(Fixed header)、可变头(Variable header)以及有效载荷(Payload,消息体)。
⚫固定头(Fixed header):存在于所有MQTT 报文中,固定头中有报文类型标识,可用于识别是哪 种MQTT 报文,譬如该报文是CONNECT 报文还是CONNACK报文,亦或是其它类型报文。
⚫ 可变头(Variable header):存在于部分类型的MQTT 报文中,报文的类型决定了可变头是否存 在及其具体的内容。
⚫ 消息体(Payload):存在于部分类型的MQTT 报文中,payload 就是消息载体的意思。

clientId–客户端 id
clientId 是MQTT 客户端的标识,也就是MQTT 客户端的名字,MQTT 服务端可通过 clientId 来区分不同的客户端,MQTT 服务端用该标识来识别客户端。因此 clientId 必须是独立的,如果两个 MQTT 客户端 使用相同 clientId 标识,服务端会把它们当成同一个客户端来处理。通常 clientId 是由一串字符所构成的,譬如,在上面的示例中,clientId 是“client-id”。

keepAlive–心跳时间间隔
心跳机制其实就是用来 判断客户端是否与服务端保持着连接的一种方法。客户端 在没有向服务端发送信息时(空闲时),可以定时向服务端发送一个心跳数据包,这个心跳包也被称作心跳请求,心跳请求的作用正是用于告知服务端,当前客户端依然在线,服务端在收到客户端的心跳请求后,会 回复一条消息,这条回复消息被称作心跳响应。 关CONNECT 报文中的 keepAlive其实是指定了心跳时间间隔。譬如 keepAlive=60,表示告诉服务端,客户端将会每隔 60 秒左右向服务端发送心跳包。

cleanSession–清除会话
如果连接服务端时 cleanSession=0,当MQTT 客户 端由离线(与服务端断开连接)再次上线时,离线期间发给客户端的所有 QoS>0 的消息仍然可以接收到;如果连接服务端时 cleanSession=1,当MQTT 客户端由离线(与服务端断开连接)再次上线时,离线期间发给客户端的所有消息一律接收不到。如果 cleanSession=1,客户端既无法接收到离线消息、服务端也不会记住该客户端所订阅的主题,服务 端不会保存客户端的会话状态,每次连接都是一次新的会话;
在这里插入图片描述
CONNACK报文包括两个信息,一个是 returnCode(连接返回码),另一个是 sessionPresent。
在这里插入图片描述
在这里插入图片描述
在 cleanSession=0 的情况下,当客户端连接到服务器之后,可通过 CONNACK 报文中返回的 sessionPresent 来查询服务端是否为客户端保存了会话状态(客户端上一次连接时的会话状态信息),如果 服务端已为客户端保存了上一次连接时的会话状态,则 sessionPresent=1,如果没有保存会话状态,则sessionPresent=0。

QoS 是什么? QoS 是Quality of Service 的缩写,所以中文名便是服务质量。一个物联网通信中有些信息非常重要,我们需要确保这类重要信息可以准确无误的发送和接收,而有些信息则相对不那么重要,这类信息如果在传 输中丢失不会影响系统的运行;QoS 便用于告诉客户端或服务器哪些信息是重要信息,需要准确无误的传 输、不可丢失;哪些信息不是那么重要,即使在传输过程中丢失也无妨! MQTT 设计了一套保证消息稳定传输的机制,包括消息应答、存储和重传。在这套机制下,提供了三种不同级别的QoS(Quality of Service),也就是MQTT 协议有三种服务质量等级:
⚫ QoS = 0:最多发一次;
⚫ QoS = 1:最少发一次;
⚫ QoS = 2:保证收一次。
以上三种不同的服务质量级别意味着不同的MQTT 传输流程。对于较为重要的MQTT 消息,我们通常会选择QoS>0 的服务级别(即QoS 为 1 或 2)。
发送端向接收端发送 PUBLISH 报文,当接收端收到PUBLISH报文后会向发送端回复一个 PUBACK报 文,如果发送端收到 PUBACK报文,那么它就知道消息已经被接收端成功接收!
QoS=0 的情况下,MQTT 服务端和客户端不会对消息传输是否成功进行确认和检查。消息能否成功 传输全看网络环境是否稳定。
Qos=1 时,MQTT服务器是不会进行去重的,只要发布者或者服务器没有收到 PUBACK报文, 就认为主题消息没有发送成功进入重发;服务器或者订阅者,不会根据 dup 标志的值进行去重(也就是说协议本身不会去重),需要我们的客户端应用程序去进行判断、处理。
在这里插入图片描述
QoS=2。当MQTT 服务质量为 2 级时,MQTT 协议可以确保接收端 只接收一次消息(注意是只接收到一次,在QoS=1 的情况下,接收端接收到消息的次数可能不止一次:>=1)。 为了确保接收端只接收到一次消息,PUBLISH报文的收发过程相对更加复杂。发送端需要接收端进行两次消息确认,

在这里插入图片描述

移植 MQTT 客户端库到我们的开发板:
下载 MQTT 客户端库源码
Releases · eclipse/paho.mqtt.c · GitHub
下载并解压 paho.mqtt.c-1.3.8.tar.gz
进入到 cmake 目录下,新建 arm-linux-setup.cmake 文件,并输入以下内容:

################################## # 配置ARM交叉编译 ################################# 
set(CMAKE_SYSTEM_NAME Linux)
#设置目标系统名字 
set(CMAKE_SYSTEM_PROCESSOR arm)
#设置目标处理器架构
# 指定编译器的 sysroot 路径 
set(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots)
set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi)
# 指定交叉编译器arm-linux-gcc 
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/armpoky-linux-gnueabi-gcc)
# 为编译器添加编译选项 
set(CMAKE_C_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
#################################
# end
##################################

回到工程的顶层目录,新建一个名为 build 的目录,进入并执行cmake命令:(按照自己的cmake路径设置)

MQTTC cmake 配置命令(名字一定要一样)

/home/alientek/linux/tool/cmake-3.16.0-Linux-x86_64/bin/cmake -DBUILD_SHARED_LIBS=1 -DCMAKE_INSTALL_PREFIX=~linux/tool/paho.mqtt.c-1.3.8/install/ -DCMAKE_C_COMPILER=/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc  -DCMAKE_CXX_COMPILER=arm-linux-g++ ../

之后进行

make 
make install

在 install 文件夹中将所有库文件拷贝到开发板linux系统的 /usr/lib目录下。注意不要破坏原有的链接关系,建议在 操作之前,先将库文件进行打包。
在这里插入图片描述
创建文件夹
在这里插入图片描述
arm-linux-setup.cmake文件:

##################################
# 配置ARM交叉编译
#################################
set(CMAKE_SYSTEM_NAME Linux)    #设置目标系统名字
set(CMAKE_SYSTEM_PROCESSOR arm) #设置目标处理器架构

# 指定编译器的sysroot路径
set(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots)
set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi)

# 指定交叉编译器arm-linux-gcc
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gcc)

# 为编译器添加编译选项
set(CMAKE_C_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
#################################
# end
##################################

CMakeLists.txt 文件 :

#*******************************************************************************
#  Copyright © ALIENTEK Co., Ltd. 1998-2021. All rights reserved.
#
#  顶层CMakeLists.txt
#  All rights reserved. This program and the accompanying materials
#  are made available under the terms of the Eclipse Public License v2.0
#  and Eclipse Distribution License v1.0 which accompany this distribution.
#*******************************************************************************/
cmake_minimum_required(VERSION 2.8.12)
project(MQTTClient C)
message(STATUS "CMake version: " ${CMAKE_VERSION})
message(STATUS "CMake system name: " ${CMAKE_SYSTEM_NAME})
message(STATUS "CMake system processor: " ${CMAKE_SYSTEM_PROCESSOR})

# 设置可执行文件输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

add_executable(mymqtt2 mymqtt2.c cJSON.c cJSON.h)

target_include_directories(mymqtt2 PRIVATE /home/alientek/linux/tool/paho.mqtt.c-1.3.8/install/include) #MQTT头文件搜索路径
target_link_directories(mymqtt2 PRIVATE /home/alientek/linux/tool/paho.mqtt.c-1.3.8/install/lib)        #MQTT库文件搜索路径
target_link_libraries(mymqtt2 PRIVATE paho-mqtt3c)                #MQTT链接库 libpaho-mqtt3c.so

进入build目录中执行cmake

~/linux/tool/cmake-3.16.0-Linux-x86_64/bin/cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-linux-setup.cmake -DCMAKE_BUILD_TYPE=Release ..

执行make编译:
在这里插入图片描述

#include <stdio.h>		
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "cJSON.h"
#include "MQTTClient.h"                //包含MQTT客户端库头文件

/* ########################宏定义##################### */
#define BROKER_ADDRESS        "ws://xxxxxxx:xxxx"        //MQTT服务器地址

/* 客户端id、用户名、密码 *
 * 的客户端连接认证信息:也就是客户端id、用户名和密码
 * 后续我们使用MQTT.fx或MQTTool的时候 也需要使用一组连接认证信息
 * 去连接社区版MQTT服务器! */
#define CLIENTID                    "mqtt_linux"                //客户端id(随便写,由字符串和数字组成)
#define USERNAME                "xxxx"                                //用户名(可以不写)
#define PASSWORD                "xxxxx"                        //密码(可以不写)

/* topic
 */
#define ORDER_TOPIC                "dt_mqtt/order"                //接收主题
#define HOME_TOPIC                "dt_mqtt/smarthome"                //发送主题
/* ################################################# */

static volatile        int led_value;      //led驱动加载后,默认led灯不亮,led_value = 0,高电平

/* 于msgarrvd 函数有两个点需要注意: 
        ⚫ 退出函数之前需要释放消息的内存空间,必须调用 MQTTClient_freeMessage 函数;
                同时也要释放 主题名称占用的内存空间,必须调用MQTTClient_free。
        ⚫ 函数的返回值。此函数的返回值必须是 0 或 1,返回 1 表示消息已经成功处理;
                返回 0 则表示消息 处理存在问题,在这种情况下,客户端库将重新调用 MQTTClient_messageArrived()以尝试再次将 消息传递给客户端应用程序,
                所以返回 0 时不要释放消息和主题所占用的内存空间,否则重新投递失败。*/
static int msgarrvd(void *context, char *topicName, int topicLen,
        MQTTClient_message *message)
{        
        if (!strcmp(topicName, ORDER_TOPIC)) {//校验消息的主题
                int fd;
                unsigned char databuf[2];

        char * data=(char *)message->payload;
        cJSON* pose=cJSON_Parse(data);
        
        cJSON *item_led = cJSON_GetObjectItem(pose,"led"); //获取这个对象成员
        double pose_led=item_led->valuedouble;
        if(led_value != pose_led)
        {
                printf("pose_led = %d\r\n",(int)pose_led);
                fd = open("/dev/dtsplatled", O_RDWR);
                if (pose_led == 0)        
                        databuf[0] = 0;        /* 要执行的操作:打开或关闭 */
                        write(fd, databuf, sizeof(databuf));
                        led_value = 0;
                if (pose_led == 1) {//如果是"1"则LED常亮
                        databuf[0] = 1;        /* 要执行的操作:打开或关闭 */
                        write(fd, databuf, sizeof(databuf));
                        led_value = 1;
                }
                close(fd); /* 关闭文件 */
                // 接收到其它数据 不做处理,可以自行扩展
        }

        }

        /* 释放占用的内存空间 */
        MQTTClient_freeMessage(&message);
        MQTTClient_free(topicName);

        /* 退出 */
        return 1;
}

static void connlost(void *context, char *cause)
{
        printf("\nConnection lost\n");
        printf("    cause: %s\n", cause);
}

int main(int argc, char *argv[])
{
        MQTTClient client;
        MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
        MQTTClient_willOptions will_opts = MQTTClient_willOptions_initializer;
        MQTTClient_message pubmsg = MQTTClient_message_initializer;
        int rc;
        led_value = 0;
        /* 创建mqtt客户端对象,做收发信息用,最主要的是客户端ID和服务器IP, 返回值:连接成功返回MQTTCLIENT_SUCCESS,是否返回错误码,有对应的错误类型 */
        if (MQTTCLIENT_SUCCESS !=
                (rc = MQTTClient_create(&client, BROKER_ADDRESS, CLIENTID,
                MQTTCLIENT_PERSISTENCE_NONE, NULL))) {
                printf("Failed to create client, return code %d\n", rc);
                rc = EXIT_FAILURE;
                goto exit;
        }

        /* 调用MQTTClient_setCallbacks 函数设置回调必须在连接服务器之前完成!!! 设置回调
                断开连接时的回调函数 cl 当客户端检测到自己掉线时会执行该函数,如果将其设置为NULL表示应用程序不处理断线的情况
                接收消息的回调函数 ma 当客户端接收到服务端发送过来的消息时执行该函数,必须设置此函数否则客户端无法接收消息
                发布消息的回调函数 dc 当客户端发布的消息已经确认发送时执行该回调函数,如果你的应用程序采用同步方式发布消息或者您不想检查是否成功发送时, 您可以将此设置为NULL*/
        if (MQTTCLIENT_SUCCESS !=
                (rc = MQTTClient_setCallbacks(client, NULL, connlost,
                msgarrvd, NULL))) {
                printf("Failed to set callbacks, return code %d\n", rc);
                rc = EXIT_FAILURE;
                goto destroy_exit;
        }

        /* 连接MQTT服务器 */
        conn_opts.keepAliveInterval = 30;        //心跳包间隔时间
        conn_opts.cleansession = 0;                        //cleanSession标志
        conn_opts.username = USERNAME;                //用户名
        conn_opts.password = PASSWORD;                //密码
        if (MQTTCLIENT_SUCCESS !=
                (rc = MQTTClient_connect(client, &conn_opts))) {                //连接成功返回MQTTCLIENT_SUCCESS,是否返回错误码
                printf("Failed to connect, return code %d\n", rc);
                rc = EXIT_FAILURE;
                goto destroy_exit;
        }

        printf("MQTT服务器连接成功!\n");


        /* 订阅主题 dt_mqtt/led */
        if (MQTTCLIENT_SUCCESS !=
                (rc = MQTTClient_subscribe(client, ORDER_TOPIC, 0))) {
                printf("Failed to subscribe, return code %d\n", rc);
                rc = EXIT_FAILURE;
                goto disconnect_exit;
        }

        /* 向服务端发布芯片温度信息 */
        for ( ; ; ) {

        MQTTClient_message tempmsg = MQTTClient_message_initializer;
        char temp_str[5] = {0};             
        int fd;

        // /* 打开led驱动 */
        // fd = open("/dev/dtsplatled", O_RDWR);        
        // // databuf[0] = atoi('0');        /* 要执行的操作:打开或关闭 */
        // // write(fd, databuf, sizeof(databuf));
        // databuf[0] =  read(fd, led_str, sizeof(led_str));
        // close(fd); /* 关闭文件 */
		//读取驱动,因为led没有写读取函数因此没有写这部分

        cJSON *pose;
        pose = cJSON_CreateObject();
        //pose x=1,y=2,yaw=30
        cJSON_AddStringToObject(pose,"x", "1");
        cJSON_AddStringToObject(pose,"temp",temp_str);
        cJSON_AddNumberToObject(pose,"led",led_value);

        //2.cjson to char*
        char* buff = cJSON_Print(pose);
        if(NULL == buff)
        {
            continue;
        }


        /* 发布温度信息 */
        // tempmsg.payload = temp_str;        //消息的内容
        // tempmsg.payloadlen = strlen(temp_str);                //内容的长度
        tempmsg.payload = buff;        //消息的内容
        tempmsg.payloadlen = strlen(buff);                //内容的长度
        tempmsg.qos = 0;                                //QoS等级
        tempmsg.retained = 1;                //保留消息
        if (MQTTCLIENT_SUCCESS !=
                (rc = MQTTClient_publishMessage(client, HOME_TOPIC, &tempmsg, NULL))) {
                printf("Failed to publish message, return code %d\n", rc);
                rc = EXIT_FAILURE;
                goto unsubscribe_exit;
        }

                 printf("%s\n",buff);

        cJSON_Delete(pose);
        free(buff);


        sleep(5);                //每隔30秒 更新一次数据
        }

unsubscribe_exit:
        if (MQTTCLIENT_SUCCESS !=
                (rc = MQTTClient_unsubscribe(client, ORDER_TOPIC))) {
                printf("Failed to unsubscribe, return code %d\n", rc);
                rc = EXIT_FAILURE;
        }
disconnect_exit:
        if (MQTTCLIENT_SUCCESS !=
                (rc = MQTTClient_disconnect(client, 10000))) {
                printf("Failed to disconnect, return code %d\n", rc);
                rc = EXIT_FAILURE;
        }
destroy_exit:
        MQTTClient_destroy(&client);
exit:
        return rc;
}

//增加了cJSON解码编码,事先加载了 led驱动 “/dev/dtsplatformled”

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值