项目分享|基于ELF 1开发板的MQTT远程温湿度监测系统

今天非常荣幸向各位小伙伴详细展示一个由共创社成员完成的MQTT远程温湿度监控系统项目。该项目借助ELF 1开发板作为核心技术支撑,成功实现了对各类环境空间中温湿度数据的实时、远程、稳定监测。该系统不仅集成了先进的数据采集模块,用于精确感知现场环境变化,同时利用MQTT协议的轻量级特性,确保了数据在复杂网络环境下的可靠传输。在此接下来,就为各位小伙伴详尽展示这一项目的相关细节

1、Linux开发板开发环境搭建

(1)开发板动态分配ip地址

(开发板与家用路由器连接,路由器支持DHCP自动IP地址分配) 

 root@ELF1:~# udhcpc -i eth0   

图片

(2)将nfs服务器挂载到开发板的/mnt目录

(其中“192.168.1.10”是Ubuntu的ens36的ip)

 root@ELF1:~# mount -t nfs -o nolock,vers=3 192.168.1.10:/home/book/nfs_rootfs /mnt

图片

可以看到开发板的/mnt目录已经有了文件 。

2、Ubuntu编译环境搭建:

(1)将paho mqtt的官方库克隆到Ubuntu的“~/nfs_rootfs”路径

book@100ask:~/nfs_rootfs$ git clone https://github.com/eclipse/paho.mqtt.c.git

(2)修改“~/nfs_rootfs/paho_mqtt/paho.mqtt.c”路径下的Makefile文件 

图片

修改prefix所代表的工具链路径:

图片

 修改编译器:

图片

(3)编译后得到链接库

book@100ask:~/nfs_rootfs/paho_mqtt/paho.mqtt.c$ make

arm-gcc编译生成的.so库文件,保存在paho.mqtt.c/build/output里面。

将.so库文件安装到本地PC

book@100ask:~/nfs_rootfs/paho_mqtt/paho.mqtt.c$ sudo make install

(4)将.so库文件安装到开发板的“/lib”路径,开发板才能运行paho mqtt编译后的可执行文件

root@ELF1:~# install /mnt/paho_mqtt/paho.mqtt.c/build/output/libpaho-mqtt3* /lib

以上开发环境搭建完成! 

3、工程文件的建立

(1)将“~/nfs_rootfs/paho_mqtt/paho.mqtt.c”

路径中的src文件夹拷贝到“~/nfs_rootfs/mqtt_iot”路径下

 book@100ask:~$ cp -r ~/nfs_rootfs/paho_mqtt/paho.mqtt.c ~/nfs_rootfs/mqtt_iot

(2)在“~/nfs_rootfs/mqtt_iot”路径下添加文件:

图片

4、阿里云服务器设置:

(1)服务器的注册及产品、设备设置可以参照ElfBoard官方文档:《01-1 ELF1、ELF1S开发板_软件教程_V1》第5章-5.4小节。

 (2)工程文件中所需要的服务器参数查找

查看服务器地址

图片

查看DeviceName、DeviceSecret

图片

查看MQTT连接参数

图片

5.工程文件的编译执行

(1)编译工程文件,得到可在Linux开发板上执行的Main二进制文件

book@100ask:~/nfs_rootfs/mqtt_iot$ arm-buildroot-linux-gnueabihf-gcc  main.c mqtt_iot.c -o main -lpaho-mqtt3c -lpthread

MQTT的异步通信收发,依赖的库是libpaho-mqtt3a,MQTT的同步通信收发,依赖的库就是libpaho-mqtt3c。此外工程编译的时候需要链接线程的库pthread,所以编译的时候要加上-lpthread。

(2)开发板运行程序

root@ELF1:/mnt/mqtt_iot# ./main

(3)实验结果

Linux开发板将采集到的温度、湿度数据每5s上传一次阿里服务器,串口窗口显示数据发送成功字符。此外通过阿里服务器日志服务可以看到湿度、温度数据。 

图片

图片

通过阿里服务器调试窗口给开发板发送LED1、LED2控制指令。

图片

图片

6、下面将贴出工程文件的代码,并介绍其思路

(1)main.c文件 

//main.c
//定义线程句柄
pthread_t discon_t;
pthread_t thread_AT20Read_t;
pthread_t thread_ledctrl_t;

static int isConnected = 0;//表明客户端和服务器是断开还是连接状态(1-连接状态,-1断开状态)

static void *thread_AT20Read(void *paramater)
{
    int fd = -1;
  unsigned int databuf[2];
  int c1,t1;
  float hum,temp;
  int ret = 0;
    msgbuf pubMsg = {2, 0};

    while(fd < 0){
        fd = open(AHT20_DEV, O_RDWR);
        if(fd < 0){
            printf("can't open file %s\r\n", AHT20_DEV);
            sleep(1);
        }else{
            printf("open file %s successfully\r\n", AHT20_DEV);
        }
    }

  while(1){
    ret = read(fd, databuf, sizeof(databuf));
    if(ret == 0){

            c1 = databuf[0]*1000/1024/1024;
            t1 = databuf[1] *200*10/1024/1024-500;
            hum = (float)c1/10.0;
            temp = (float)t1/10.0;
            //printf("hum = %0.2f temp = %0.2f \r\n",hum,temp);

            pubMsg.mtext[0] = (unsigned int)(hum*100);
            pubMsg.mtext[1] = (unsigned int)(temp*100);
            int ret1 = msgsnd(pubmsg_d, &pubMsg.mtype, sizeof(pubMsg.mtext), IPC_NOWAIT);  // 非阻塞发送
            if(ret1 != 0)
            {
                printf("Failed to send message.\r\n");
            }

    }
        sleep(5);
  }
}

static void *thread_ledctrl(void *paramater)
{
  int on=1;
  int led;
  int fd;
    msgbuf subMsg = {1, 0};
  fd = open(LED_BRIGHTNESS, O_WRONLY);

  if(fd < 0)
  {
      perror("open device leds");
      exit(1);
  }
    system(LED1_OFF);
  system(LED2_OFF);
  while(1)
  {
        int res = msgrcv(submsg_d, &subMsg, sizeof(subMsg.mtext), 0, 0);//阻塞
        if(res < 0)
            continue;
        else{
            if((subMsg.mtext[0] & 0x01)== 1){
                system(LED1_ON);
            }else{
                system(LED1_OFF);
            }

            if((subMsg.mtext[0] & 0x02)== 0x02){
                system(LED2_ON);
            }else{
                system(LED2_OFF);
            }
        }
  }

}

//断开和mqtt服务器连接的线程入口函数
static void *mqtt_disconnect_t(void* argv)
{
    int retval;
    while(1)
    {
        char ch;
        ch = getchar();
        if(ch=='Q' || ch=='q')
        {
            printf("Try to exit mqtt task\n");
            if(mqtt_disconnect() == EXIT_SUCCESS)   break;
        }
    }
    isConnected = -1;
    pthread_exit(&retval);  // 退出线程
    return NULL;
}

int main(void)
{
    //初始化mqtt成功建立客户端和服务器的连接后,将主动断开服务器的任务放到一个线程里面去
    //成功建立客户端和服务器的连接且订阅主题后才创建断开连接的线程
    if(mqtt_iot() == 0)
    {
        isConnected = 1;
        pthread_create(&discon_t, 0, mqtt_disconnect_t, NULL);
    }

    //AT20 read thread
    int ret = pthread_create(&thread_AT20Read_t, NULL, thread_AT20Read, NULL);
    if(ret != 0)
    {
        printf("Failed to create AT20Read thread.\n");
        return -1;
    }

    //led control thread
    ret = pthread_create(&thread_ledctrl_t, NULL, thread_ledctrl, NULL);
    if(ret != 0)
    {
        printf("Failed to create ledctrl thread.\n");
        return -1;
    }

    while(1)
    {
        //printf("isConnected state:%d\n",isConnected);
        sleep(5);
    }

    return 0;
}

(2)mqtt_iot.c文件 

//mqtt_iot.c
volatile MQTTClient_deliveryToken deliveredtoken;

pthread_t threads[2];
sem_t discon_sem;//信号量
int pubmsg_d = -1;
int submsg_d = -1;
msgbuf subMsg = {1, 0};
msgbuf pubMsg = {2, 0};
pthread_t thread_mqtt_publish_t;

MQTTClient client;  //定义一个MQTT客户端client
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;


//传递给MQTTClient_setCallbacks的回调函数,消息发送成功后,调用此回调函数
void delivered(void *context, MQTTClient_deliveryToken dt)
{
  printf("Message with token value %d delivery confirmed\n", dt);
  deliveredtoken = dt;
}

//传递给MQTT-Client_setCallbacks的回调函数 消息到达后,调用此回调函数
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
  printf("---------------------------------------------------------------\n");
  printf("Message arrived\n");
  printf(" topic: %s\n", topicName);
  printf(" message: %.*s\n", message->payloadlen, (char*)message->payload);
  printf("---------------------------------------------------------------\n");

  subMsg.mtext[0] = 0;
  unsigned short len = message->payloadlen;
  char *buf = (char*)message->payload;
  for(unsigned short i=0; i<len; i++)
  {
    if(buf[i] == '\0')  break;
    if(buf[i]<='9' && buf[i]>='0')
      subMsg.mtext[0] = subMsg.mtext[0]*10 + buf[i] - '0';
  }

  int ret = msgsnd(submsg_d, &subMsg.mtype, sizeof(subMsg.mtext), IPC_NOWAIT);  // 非阻塞发送
  if(ret != 0)
  {
    printf("Failed to send message.\r\n");
  }

  MQTTClient_freeMessage(&message);  // 释放消息
  MQTTClient_free(topicName);  // 释放主题名
  return 1;
}

//传递给MQTTClient_setCallbacks的回调函数 连接异常断开后调用此回调函数
void connlost(void *context, char *cause)
{
  printf("\nConnection lost\n");
  printf(" cause: %s\n", cause);
}

//实现MQTT的发布
void *mqtt_publish(void *argv)
{
  MQTTClient_message pubmsg = MQTTClient_message_initializer;
  MQTTClient_deliveryToken token;
  char data[9];
  int rc;
  pubmsg.qos = QOS;
  pubmsg.retained = 0;
  while(1)
    {
    //接收消息(消息队列的ID,存放消息的指针,指定接收消息的大小,0-读取消息队列中第一个数据,阻塞)
    int res = msgrcv(pubmsg_d, &pubMsg, sizeof(pubMsg.mtext), 0, 0);
    if(res < 0)  continue;
    {
      //printf("Publish_hum: %d\n", pubMsg.mtext[0]);
      //printf("Publish_temp: %d\n", pubMsg.mtext[1]);
      sprintf(data, "%d,%d", pubMsg.mtext[0],pubMsg.mtext[1]);
      pubmsg.payload = data;
      pubmsg.payloadlen = sizeof(data);
      if((rc = MQTTClient_publishMessage(client, PUB_TOPIC, &pubmsg, &token)) != MQTTCLIENT_SUCCESS)
      {
        printf("Failed to publish message, return code %d\n", rc);
        break;
      }
      rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
      printf("Message with delivery token %d delivered\n", token);
    }
  }

    if((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)  //断开和服务器的连接
  {
    printf("Failed to disconnect, return code %d\n", rc);
  }
    pthread_exit(&threads[PubThread]);
  return NULL;
}

//封装主动断开连接服务器的函数
int mqtt_disconnect(void)
{
    int rc = EXIT_SUCCESS;

  //两个参数:MQTT客户端和断开连接超时时间
  if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)  //断开和服务器的连接
  {
    printf("Failed to disconnect, return code %d\n", rc);
    rc = EXIT_FAILURE;
  }
  else
  {
    printf("MQTT disconnect success\n");
    MQTTClient_destroy(&client);
  }

  return rc;
}

// mqtt建立客户端、连接服务器、订阅主题的封装入口函数
int mqtt_iot(void)
{
    int rc = EXIT_SUCCESS;
  //创建客户端
  if ((rc = MQTTClient_create(&client, ADDRESS, CLIENTID,
          MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS)
  {
    printf("Failed to create client, return code %d\n", rc);
    goto exit;
  }

  //设置回调函数(连接丢失处理回调函数,处理订阅消息的回调函数,成功发布消息后的回调函数)
  if((rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered)) != MQTTCLIENT_SUCCESS)
  {
    printf("Failed to set callbacks, return code %d\n", rc);
    goto destroy_exit;
  }

    conn_opts.username = USERNAME;
    conn_opts.password = PASSWORD;
  conn_opts.keepAliveInterval = 60;//保活周期,客户端向服务器发送心跳包的周期,单位秒
  conn_opts.cleansession = 1;
  //连接服务器
  if((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
  {
    printf("Failed to connect, return code %d\n", rc);
    goto destroy_exit;
  }

    //订阅主题(传入客户端句柄、订阅的主题以及消息质量)
  if ((rc = MQTTClient_subscribe(client, SUB_TOPIC, QOS)) != MQTTCLIENT_SUCCESS)
  {
    printf("Failed to subscribe, return code %d\n", rc);
        goto destroy_exit;
  }

  //初始化信号量
  if(sem_init(&discon_sem, 1, 0) != 0)
  {
    printf("Failed to init semaphore\n");
    return -1;
  }
  //创建队列
  pubmsg_d = msgget(0x1234, IPC_CREAT);
  submsg_d = msgget(0x5678, IPC_CREAT);
  if(pubmsg_d == -1 || submsg_d==-1)  //返回错误码-1
  {
    printf("Failed to create a mqtt message, pubid:%d, subid:%d\n", pubmsg_d, submsg_d);
    return -1;
  }
  else
  {
    printf("Publish message id: %d\n", pubmsg_d);
    printf("Subscribe message id: %d\n", submsg_d);
  }

    int ret = pthread_create(&thread_mqtt_publish_t, NULL, mqtt_publish, NULL);
    if(ret != 0)
    {
        printf("Failed to create mqtt_publish thread.\n");
        return -1;
    }

  printf("MQTT connect success, press 'Q' or 'q' to disconnect mqtt server\n");
    return 0;

destroy_exit:
  MQTTClient_destroy(&client); //释放客户端的资源, 参数-同步客户端的句柄
    return -1;
exit:
    return -1;
} 

至此,就完成了关于ELF 1开发板研发的MQTT远程温湿度监测系统介绍。希望这套实践案例能够成为各位小伙伴的宝贵参考,启迪创新思维,推进各位嵌入式爱好者在学习的道路上不断前进。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/575073.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniapp问题归类

最近使用uniapp中&#xff0c;遇到了一些问题&#xff0c;这边mark下。 1. 启动页变形 设置启动页的时候发现在部分android手机上启动页被拉伸了&#xff0c;最后看了下官方建议使用9.png图 生成9.png地址&#xff0c;推荐图片大小为1080x2340 uniapp推荐官方地址传送门 我…

JAVA实现easyExcel动态生成excel

添加pom依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version> </dependency><!--工具类--> <dependency><groupId>cn.hutool</groupId><…

在Mac M1笔记本上跑大语言模型llama3的4个步骤?(install、pull、run、ask)

要点 Ollama一个功能强大的本地大语言模型LLM运行工具&#xff0c;支持很多模型&#xff0c;并且操作极其简单快速回忆步骤&#xff1a; 下载ollama工具&#xff1a;https://ollama.com/download 下载模型&#xff1a;ollama pull llama3 #根据libs列表直接指定名字 运行模型…

安卓studio插件开发(一)本地搭建工程

下载idea 社区版本 建立IDE Plugin工程 点击create就行&#xff0c;新建立的工程长这样 比较重要的文件 build.gradle&#xff1a;配置工程的参数 plugin.xml&#xff1a;设置插件的Action位置 build.gradle.kts内容如下&#xff1a; plugins {id("java")id(&quo…

常用的时间序列分析方法总结和代码示例

时间序列是最流行的数据类型之一。视频&#xff0c;图像&#xff0c;像素&#xff0c;信号&#xff0c;任何有时间成分的东西都可以转化为时间序列。 在本文中将在分析时间序列时使用的常见的处理方法。这些方法可以帮助你获得有关数据本身的见解&#xff0c;为建模做好准备并…

网站建设价格多少合理

网站建设价格多少合理&#xff0c;是很多企业和个人在寻找网站建设服务时&#xff0c;最为关心的问题之一。在选择好的网站建设服务商前&#xff0c;了解合理的网站建设价格&#xff0c;对于选择合适的网站建设服务商具有重要的参考作用。下面我们就来讨论一下&#xff0c;网站…

vue+element 树形结构 改成懒加载模式(原理element有),这里只做个人理解笔记

1 找到属性标签添加 lazy 和 :load"loadNode" 这两个属性 2 引入树形接口,并和后端约定好传值,(拿我的举例 第一次获取全部父级默认第一次传参数:{ parentId : 0},可获取全部父级 第二次通过点击的子级把子级id传进去,这一步就用到了:load"loadNode&quo…

区块链技术与应用学习笔记(10-11节)——北大肖臻课程

目录 10.分岔 ①什么是分叉&#xff1f; ②导致分叉的原因&#xff1f; ③在比特币新共识规则发布会会导致什么分叉&#xff1f; 什么是硬分叉&#xff1f; 硬分叉例子&#xff1f; 什么是软分叉&#xff1f; 软分叉和硬分叉区别&#xff1f; 软分叉实例 11.问答 转…

在no branch上commmit后,再切换到其他分支,找不到no branch分支的修改怎么办?

解决办法 通过git reflog我们可以查看历史提交记录&#xff0c;这里的第二条提交&#xff08;fbd3ea8&#xff09;就是我在no branch上的提交。 再通过git checkout -b backup fbd3ea8&#xff0c;恢复到上次提交的状态&#xff0c;并且为其创建个分支backup&#xff0c;此时…

ES6要点

ES6/ES7内容解析 一、变量/赋值1、变量2、解构赋值 二、函数1、箭头函数2、默认参数3、参数展开&#xff08;剩余参数&#xff0c;数组展开&#xff09; 三、数组/JSON1、 数组2、JSON 四、字符串1、字符串模版2、字符串方法 五、面向对象1、类2、bind()3、箭头函数的this 六、…

【Python特征工程系列】递归特征消除法分析特征重要性-SVC模型为例(案例+源码)

这是我的第268篇原创文章。 一、引言 递归特征消除&#xff08;RFE&#xff09;是一种高效的特征选择方法&#xff0c;它通过递归减少特征的数量来找出模型最重要的特征。本文基于支持向量机分类器作为选择器的基模型&#xff0c;采用递归消除法进行特征筛选。 二、实现过程 2…

HTTP与HTTPS 对比,区别详解(2024-04-25)

一、简介 HTTP&#xff08;超文本传输协议&#xff0c;Hypertext Transfer Protocol&#xff09;是一种用于从网络传输超文本到本地浏览器的传输协议。它定义了客户端与服务器之间请求和响应的格式。HTTP 工作在 TCP/IP 模型之上&#xff0c;通常使用端口 80。 HTTPS&#xf…

Jmeter(十九) - 从入门到精通 - JMeter监听器 -上篇(详解教程)

宏哥微信粉丝群&#xff1a;https://bbs.csdn.net/topics/618423372 有兴趣的可以扫码加入 1.简介 监听器用来监听及显示JMeter取样器测试结果&#xff0c;能够以树、表及图形形式显示测试结果&#xff0c;也可以以文件方式保存测试结果&#xff0c;JMeter测试结果文件格式多样…

使用docker安装本地pdf工具集合Stirling-PDF

平时工作中需要处理pdf&#xff0c;市面上的很多工具都需要充会员才能使用&#xff0c;偶然发现了一个可私有化部署且易于使用的PDF在线工具&#xff0c;使用docker部署&#xff0c;使用起来非常方便&#xff0c;而且功能齐全。 这里是官网&#xff1a; https://pdf.errui.cc/…

任务调度xxljob的使用记录

1.基本使用 a.下载代码&#xff0c;地址&#xff1a;https://gitee.com/xuxueli0323/xxl-job.git b.执行sql&#xff0c;修改配置&#xff0c;启动任务调度中心的代码 启动代码后任务调度中心访问地址&#xff1a;http://localhost:8080/xxl-job-admin&#xff08;自己机器…

D-Wave 推出快速退火功能,扩大量子计算性能增益

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨浪味仙 排版丨沛贤 深度好文&#xff1a;1400字丨6分钟阅读 摘要&#xff1a;量子计算公司 D-Wave 宣布在其 Leap™ 实时量子云服务中的所有量子处理单元 (QPU) 上推出新的快速退火功能。…

30 OpenCV 点多边形测试

文章目录 点多边形测试pointPolygonTest示例 点多边形测试 pointPolygonTest pointPolygonTest( InputArray contour,// 输入的轮廓 Point2f pt, // 测试点 bool measureDist // 是否返回距离值&#xff0c;如果是false&#xff0c;1表示在内面&#xff0c;0表示在边界上&a…

“一个有趣的C语言代码”分析

“一个有趣的C语言代码” 一个有趣的C语言代码-流浪的海豚-ChinaUnix博客 #include <stdio.h> int print() {printf("hello world!\n");return 0; } int main(void) {long base[0];long* result base3;*(result1) *result;*result (long)print;return 0; …

如何安装最新版Docker Compose?

Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。通过 Compose&#xff0c;您可以使用 YAML 文件来配置应用服务&#xff0c;然后只需一个简单的命令便能创建和启动所有服务。在本篇博客中&#xff0c;我们将详细介绍如何在 Linux 系统上安装 Docker Compos…

Hive中几个非常重要的问题

1、Hive 有哪些方式保存元数据&#xff0c;各有哪些优缺点 (1).DerBy数据库&#xff1a;默认自带 优点&#xff1a;使用简单&#xff0c;不需要额外的配置。 缺点&#xff1a;只有一个客户端&#xff0c;多个客户访问会报错。 (2).使用MySql数据库存储 优点&#xff1a;单独的…