温度采集实时上报——客户端

本文介绍了一款基于树莓派的温度监测客户端程序,该程序通过socket通信定时向服务器上报温度数据,包括功能分析、代码模块说明及运行结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 客户端要求

  1. 树莓派上运行socket客户端程序,每隔30秒(可指定)以字符串“ID/时间/温度”形式上报采样温度,其中ID为树莓派的编号,便于服务器端区别是哪个树莓派客户端,如RPI0001/2019-01-05 11:40:30/30.0C
  2. 通过命令行参数指定服务器IP地址和端口以及间隔采样时间, getopt或getopt_long;
  3. 程序放到后台运行(daemon),并通过syslog记录程序的运行出错、调试日志;
  4. 程序能够捕捉kill信号正常退出;

2. 客户端流程图

在这里插入图片描述

3. 功能分析

  1. 建立socket通信,此处编写一个client_main()函数,与server进行connect;使用snprintf()函数进行格式化字符串,以达到指定输出格式;为了获取时间和温度样本,此处分别编写get_temper()函数和get_time()函数。
  2. 客户端连接服务器,将其打包成client_init()函数,直接调用。
  3. 通过调用long_options()函数进行命令行参数解析,并编写打印帮助信息函数print_usage()函数。
  4. 要是程序后台运行,即须调用守护进程deamon()函数,并与之配合建立log日志系统。
  5. 捕捉信号,即须安装相关信号,调用signal()函数,并编写相应回调函数signal_stop(),使程序正常退出。
  6. 再将这些调用函数的声明全部放在头文件header.h中,在主函数client_main()中包含该头文件即可。

4. 代码模块

header.h头文件

/********************************************************************************
 *      Copyright:  (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
 *                  All rights reserved.
 *
 *       Filename:  header.h
 *    Description:  This head file is temperature project head file  
 *
 *        Version:  1.0.0(11/12/2019)
 *         Author:  Tang Zhiqiang <t_zhiqiang@163.com>
 *      ChangeLog:  1, Release initial version on "11/12/2019 07:29:02 PM"
 *                 
 ********************************************************************************/

void print_usage(char *progname);
void get_time(char *data_time);
int get_temper(float *temper);
int client_init(int port, char * serv_ip);

get_temper函数获取DS18B20采集的数据

/*********************************************************************************
 *      Copyright:  (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
 *                  All rights reserved.
 *
 *       Filename:  get_temper.c
 *    Description:  This file is get RPI temperature in file.
 *                 
 *        Version:  1.0.0(11/09/2019)
 *         Author:  Tang Zhiqiang <t_zhiqiang@163.com>
 *      ChangeLog:  1, Release initial version on "11/09/2019 06:00:53 PM"
 *                 
 ********************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include "header.h"

//#define filepath /sys/bus/w1/devices/28-041731f7c0ff/

//int get_temper(float *temper);

int get_temper(float *temper)
{
    char 	        filepath[120]="/sys/bus/w1/devices/";
    char	        f_name[50];
    char            data_array[1024];
    char            *data_p=NULL;
    struct dirent   *file=NULL;
    DIR             *dir=NULL;
    int             data_fd = -1;
    int             found = 0;
    //float           *temper;
    
    //opendir打开目录失败,返回NULL  成功:返回该路径下的所有文件和目录
    if((dir = opendir(filepath)) == NULL)
    {
        printf("opendir file failure: %s\n",strerror(errno));
        return -1;
    }

    while((file = readdir(dir)) != NULL)
    {
        //if((strcmp(file->d_name, ".", 1) == 0) || (strcmp(file->d_name, ".", 1) == 0))
        //continue;               //ignore '.' and '..' file
        if(strstr(file->d_name, "28-")) 
        {   //memset(f_name, 0, sizeof(f_name));
            strncpy(f_name, file->d_name, sizeof(f_name));       //locate reserve temperature data file path
            found = 1;      //设置found为1,即代表找到相应文件夹
            printf("reserve temperature data file path: %s\n",f_name);
        }
        //closedir(dir);
    }
    closedir(dir);
    /* found == 0; 未找到目的文件夹 */
    if(!found) 
    {
        printf("Can not find the folder\n");   
        return 0;
    }
    
    /* 找到相应文件夹后,切换至该文件夹下以获取温度数据 */
    strncat(filepath, f_name, sizeof(filepath)-strlen(filepath));  //将文件夹名连接到filepath路径后;sizeof计算数据总体大小,strlen计算当前存放字符串的大小
    strncat(filepath, "/w1_slave", sizeof(filepath)-strlen(filepath));  //将设备文件夹下存放温度的文件连接到filepath路径后

    data_fd=open(filepath, O_RDONLY);
    if(data_fd < 0) 
    {
        printf("open file failure: %s\n", strerror(errno));
        return -2;
    }
    memset(data_array, 0, sizeof(data_array));
    if(read(data_fd, data_array, sizeof(data_array)) < 0)
    {
        printf("read file failure: %s\n", strerror(errno));
        return -3;
    }
    /* data_p指针后移两个字符单位,其后即为温度数据 */
    data_p=strstr(data_array, "t=");    //strst()成功:返回第一次出现该字符串位置的指针
    data_p += 2;        //local temperature data 
    *temper=atof(data_p)/1000;    //"()" priority super "/"
    
    close(data_fd);

    return 0;
}

get_time函数获取当前系统时间,并将其返回成需要的格式

/*********************************************************************************
 *      Copyright:  (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
 *                  All rights reserved.
 *
 *       Filename:  get_time.c
 *    Description:  This file is get current systrm time.
 *                 
 *        Version:  1.0.0(11/09/2019)
 *         Author:  Tang Zhiqiang <t_zhiqiang@163.com>
 *      ChangeLog:  1, Release initial version on "11/09/2019 05:59:44 PM"
 *                 
 ********************************************************************************/

#include <time.h>
#include <stdio.h>
#include "header.h"

//#define ip  TZQ

//void get_time(char *data_time);

void get_time(char *data_time)
{
    struct tm   *p;
    time_t      timep;
    //char        datime[50];

    time(&timep);       // 获取当前的系统时间(CUT时间)              
    p=gmtime(&timep);   //gmtime()函数参数timep 所指的time_t 结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果由结构strcut tm*返回
    snprintf(data_time, 50, "%d-%02d-%02d  %02d:%02d:%02d", (1900+p->tm_year),  (1+p->tm_mon), p->tm_mday, (p->tm_hour+8), p->tm_min, p->tm_sec);
    //小时+8是因为我们是+8区,比CUT多8个小时
    return ;
}

client_init函数初始化客户端,连接其服务端

/*********************************************************************************
 *      Copyright:  (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
 *                  All rights reserved.
 *
 *       Filename:  client_init.c
 *    Description:  This file is cliet initial function.
 *                 
 *        Version:  1.0.0(11/09/2019)
 *         Author:  Tang Zhiqiang <t_zhiqiang@163.com>
 *      ChangeLog:  1, Release initial version on "11/09/2019 05:57:49 PM"
 *                 
 ********************************************************************************/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <netinet/in.h>
#include "header.h"

//int client_init(int port, char *serv_ip);

int client_init(int port, char *serv_ip)
{
    int                 con_fd = -1;
    int 		        rv = -1;
    struct sockaddr_in  serv_addr;

    con_fd=socket(AF_INET, SOCK_STREAM, 0);

    if(con_fd < 0)
    { 
        printf("Create socket failure: %s\n", strerror(errno));
        return -1;
    }
    printf("Create socket[%d] sucessfully!\n", con_fd); 
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_port=htons(port);
    inet_aton(serv_ip, &serv_addr.sin_addr);
    
    if(con_fd >= 0)
    {
        rv=connect(con_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
        if(rv < 0)
        {
            printf("Connect to server[%s:%d] failure: %s\n", serv_ip, port, strerror(errno));
            return -2;
        }
        printf("Connect to server[%s:%d] sucessfully!\n", serv_ip, port);
    }

    return con_fd;
}

client_main函数为客户端的主函数

/*********************************************************************************
 *      Copyright:  (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
 *                  All rights reserved.
 *
 *       Filename:  client_main.c
 *    Description:  This file socket client monitor RPI temperature.
 *                 
 *        Version:  1.0.0(10/22/2019)
 *         Author:  Tang Zhiqiang <t_zhiqiang@163.com>
 *      ChangeLog:  1, Release initial version on "10/22/2019 04:32:59 PM"
 *                 
 ********************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <getopt.h>
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#include <sqlite3.h>
#include "header.h"

#define     id  "t_zhiqang"  

int     g_sigstop = 0;

//void print_usage(char *progname);

void signal_stop(int signum)
{
    if(SIGTERM == signum)
    {
        printf("SIGTERM signal detected\n");
        g_sigstop = 1;
    }
}

int main(int argc, char *argv[])
{
    int                 sock_fd = -1;
    int                 rv = -1;
    struct sockaddr_in  serv_addr;
    char                *serv_ip = NULL;
    int                 serv_port = 0;
    char                buf[1024];
    int                 ch;
    char                da_time[50];                
    int                 interv_time = 0;   
    float               temper;
    int                 daemon_run = 0;
    int                 log_fd = -1;

    static struct option long_options[] = {
            {"d", no_argument, NULL, 'd'},
            {"ipaddr", required_argument, NULL, 'i'},
            {"port", required_argument, NULL, 'p'},
            {"help", no_argument, NULL, 'h'},
            {"interv_time", required_argument, NULL, 't'},
            {0, 0, 0, 0}
        };

    while((ch=getopt_long(argc, argv, "di:p:ht:", long_options, NULL)) != -1)
        {
            switch (ch) 
            {
                case 'd':
                    daemon_run = 1;
                    
                    break;
                case 'i': 
                    serv_ip=optarg;
                    break;
                case 'p': 
                    serv_port=atoi(optarg); //optarg返回的是字符串,atoi()将char转换为int型
                    break;
                case 'h': 
                    print_usage(argv[0]);
                    break;
                case 't':
                    interv_time=atoi(optarg);
                    break;
                default:
                    print_usage(argv[0]);
                    exit(0);
                    break;
            }
            
        }
        if(!serv_ip || !serv_port)
        {
            print_usage(argv[0]);
            return -1;
        }

        //openlog("socket_client_RPI", LOG_CONS | LOG_PID, 0);    //可选
        //printf("Program %s running failure: %s\n",argv[0], strerror(errno)); //将错误或调试信息写入系统自带的syslog日志
        
        signal(SIGTERM, signal_stop);   //安装signal信号
        signal(SIGPIPE, SIG_IGN);   //屏蔽SIGPIPE信号,因为当服务器主动断开socket连接后,客户端会接收到SIGPIPE信号,自动退出。
                                    //为达到服务器主动断开socket连接后,客户端重连服务器端的目的,这里必须屏蔽掉SIGPIPE信号

        //守护进程函数
        if (daemon_run)         
        {
            printf("Program %s is running at the background now\n", argv[0]);
            //创建日志系统,程序后台运行后,将所有打印信息打印在日志文件中
            log_fd = open("receive_temper.log",  O_CREAT|O_RDWR, 0666);
            if (log_fd < 0)
            {
                printf("Open the logfile failure : %s\n", strerror(errno));

                return 0;
            }
            //标准输出及标准出错重定向,重定向至日志文件
            dup2(log_fd, STDOUT_FILENO);
            dup2(log_fd, STDERR_FILENO);
            //设置deamon()函数两个参数为1
            //即保持当前目录不变,并且使标准输出及标准出错重定向仍打印输出信息,只不过此时打印信息将会全部打印至日志文件中!
            if ((daemon(1, 1)) < 0)
            {
                printf("Deamon failure : %s\n", strerror(errno));
                return 0;
            }
	} 
    while (!g_sigstop) 
    {   
            /*采样SN、时间、温度...*/  
            get_time(da_time);
            if((get_temper(&temper)) < 0)
	        {
	    	    printf("Get temperature failure: %s\n", strerror(errno));
		        continue;
	        }
            
            memset(buf, 0, sizeof(buf));
            snprintf(buf, sizeof(buf), "%s/%s/%.2f%c", id, da_time, temper, 'C');
            
            //未连接到服务器
            if(sock_fd < 0)
            {   
                //调用client_ini()函数,连接服务器
                if((sock_fd=client_init(serv_port, serv_ip)) < 0)    
                {
                    printf("connect server point  fialure: %s\n", strerror(errno));
                    continue;   //连接服务端失败,重新连接
                }
            }
            /* connect server OK!*/
            if(sock_fd >= 0)
            {
                if(write(sock_fd, buf, sizeof(buf)) < 0)
                {
                    printf("write data to server fialure: %s\n", strerror(errno));
                    close(sock_fd);
                    sock_fd = -1;
                    return -3;
                }
            }
            printf("Send messege to server sucessfully!\n");

            sleep(interv_time); //命令行参数设定上传数据间隔时间   
        }

        //closelog();
        close(sock_fd);
	    //sqlite3_close(db);

        return 0;
}

void print_usage(char *progname)
{
    printf("%s usage.\n", progname);     
    printf("%s is a socket client progname, which used to verify server and echo back string from it\n", progname);
    printf("\nMandatory arguments to long options are mandatory for short option too:\n");
    printf("-b[darmon] set program running on backgroud\n");
    printf("-t[interv_time] RPI temperature interval time\n");
    printf("-p[port] Socket client port address\n");
    printf("-i[ip] Socket client ip address\n");
    printf("-h[help] Display this help information\n");
    printf("\nExample: %s -t 30 -p 8088 -i 192.168.174.5\n", progname);
    return;
}


5. 客户端运行结果

运行结果需要在服务器运行的前提下才可以,运行结果为:
这里需要注意:环境生产可执行文件后,环境变量需要加进去,不然找不到程序header.h头文件。(这个没有写进makefile中,环境变量只是临时生效,重启失效)
在这里插入图片描述

6. 方便查看,贴出多进程的服务器运行结果:

具体可以看下面的详解
服务器详解

在这里插入图片描述

### 实现ESP8266透传模式上传DHT11温湿度数据和LED状态 为了实现ESP8266通过透传模式采集DHT11温湿度数据及LED状态上报至云平台,并接收云端指令控制LED,可以按照如下方法进行开发。 #### 环境准备 硬件方面需准备好ESP8266模块、DHT11传感器以及用于指示的三个LED灯。对于软件环境,则推荐采用Arduino IDE作为编程工具,在其中安装必要的库文件来支持ESP8266的操作[^2]。 #### 连接配置 确保ESP8266与DHT11之间正确连线,同时将三个LED分别连接到不同的GPIO引脚以便独立控制其开关状态。此外还需设置Wi-Fi参数使ESP8266能够成功连入互联网并访问目标云服务平台。 #### 编写代码逻辑 编写初始化函数完成对各组件的基础设定工作;定义读取温度湿度的方法获取当前测量值;构建发送HTTP请求或利用MQTT协议向指定服务器提交收集的数据包;最后设计监听机制响应来自网络的服务端命令改变相应LED的工作情况。 ```cpp #include <ESP8266WiFi.h> #include <PubSubClient.h> // MQTT客户端库 #include "DHT.h" // WiFi 和 MQTT 配置 const char* ssid = "your_SSID"; const char* password = "your_PASSWORD"; const char* mqtt_server = "mqtt.yourserver.com"; // DHT 传感器配置 #define DHTPIN 2 // GPIO 引脚号 #define DHTTYPE DHT11 // 温度计型号, 可选 DHT11 或者 DHT22 DHT dht(DHTPIN, DHTTYPE); // LED 控制引脚定义 int ledPins[] = {5, 4, 0}; WiFiClient espClient; PubSubClient client(espClient); long lastMsg = 0; void setup_wifi() { delay(10); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } randomSeed(micros()); Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } void reconnect() { // Loop until we're reconnected while (!client.connected()) { Serial.print("Attempting MQTT connection..."); if (client.connect("ESP8266Client")) { Serial.println("connected"); // Once connected, subscribe to the topic for controlling LEDs client.subscribe("led/control"); } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); delay(5000); } } } void callback(char* topic, byte* payload, unsigned int length) { String message; for (unsigned int i = 0; i < length; i++) { message += (char)payload[i]; } // 解析消息以更新LED的状态 handleLedControl(message); } void handleLedControl(String command){ // 处理从云端收到的消息,转换成对应的LED操作 // 假设格式为:"ON_0", "OFF_1"... int pinIndex = command.charAt(command.length()-1)-'0'; bool state = command.startsWith("ON"); digitalWrite(ledPins[pinIndex],state?HIGH:LOW); } void setup() { pinMode(ledPins[0], OUTPUT); pinMode(ledPins[1], OUTPUT); pinMode(ledPins[2], OUTPUT); dht.begin(); setup_wifi(); client.setServer(mqtt_server, 1883); client.setCallback(callback); } void loop() { if (!client.connected()) { reconnect(); } client.loop(); long now = millis(); if (now - lastMsg > 2000) { lastMsg = now; float h = dht.readHumidity(); // 获取湿度 float t = dht.readTemperature(); // 获取摄氏温度 if (isnan(h) || isnan(t)) { Serial.println("Failed to read from DHT sensor!"); return; } // 发送温湿度信息给OneNet或其他云服务提供商 publishDataToCloud(h,t); } } ``` 此段代码实现了基本的功能需求——即定期检测室内空气状况并将结果推送到远程数据库中存储起来供后续分析处理之用;同时也允许管理员经由Web界面下发特定指令调整现场灯光效果达到节能降耗的目的[^3].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值