树莓派DS18B20温度采样上报服务器
目录
流程图
客户端功能:
*1.判断是否存在数据库db文件和表,不存在就创建,存在就不创建
*2.支持命令行传ip地址,端口号,域名,产品序列号,间隔时间,等参数
*3.客户端网络断开时自动保存断开时数据
*4.连接服务器时判断是否有断电时保留的数据
*5.有保留的数据就将数据发送给服务器
*6.在服务器网络恢复时自动重连服务器
*7.保证在发送数据库数据时能够精确采样数据
初始化部分
命令行解析
void useage(char *progname)
{
printf("%s useage:\n",progname);
printf("-i(--ipaddr):specify server IP address or Domain name\n");//服务器IP地址或域名
printf("-p(--port):specify server port\n");//服务器端口
printf("-h(--help):print this help information\n");//打印帮助信息
printf("-d(--daemon):Let the Program run in the background\n");//进入后台运行
printf("-s(--sn):product ID\n");//指定产品序列号
printf("-t(--time):Sampling interval time\n");//指定采样间隔
printf("for example:proname -i 127.0.0.0 -p 1111 (-s [int i]) (-d) (-t [int i]) ");
return;
}
//命令行解析
while((opt = getopt_long(argc,argv,"i:p:hds:t:",opts,&idx)) != -1)
{
switch(opt)
{
case 'i':
servip = optarg;
break;
case 'p':
port = atoi(optarg);
break;
case 'h':
useage(argv[0]);
return 0;
case 'd':
daemon_ctl=1;
break;
case 's':
sn_tm_intv.sn = atoi(optarg);
break;
case 't':
sn_tm_intv.interval=atoi(optarg);
break;
}
}
if(!(servip&&port))
{
useage(argv[0]);
return 0;
}
注意细节:
1.参数解析判断的位置,应该放在程序的最前面,其他初始化应该放在其之后,因为,如果用户指向使用-h查看使用帮助,就没必要将其他模块初始化。
2. getopt_long函数中第三个参数的设置。
3.命令行参数合法性判断。
各个模块初始化
//是否进入守护进程,后台运行
rv=daemon_switch(daemon_ctl);
if(rv<0)
log_error("daemon_switch failure!\n");
//初始化信号,并安装信号
signal_init();
//设置日志文件信息,初始化
if( logger_init("stdout", LOG_LEVEL_INFO) < 0)
{
fprintf(stderr, "initial logger system failure\n");
return -1;
}
//create a database flie and a table
sql_create_db_tb("temp.db");
//获取一个最初时间以便实现开机就采样
sn_tm_intv.last_time=0;
注意细节:
1.daemon函数的使用
2.初始化信号,安装SIGTERM,SIGINT,SIGPIPE 三个信号,如有更多考虑可以安装更多信号。SIGTERM、SIGINT的安装能够使程序优雅退出;SIGPIPE的安装,将碰到这个信号时处理改为SIG_IGN,忽略掉才能实现断线后不掉线。
3.将当前时间设置为0,以便在采样判断时,能够实现开机即采样。
while循环部分
连接检查
/********************************
*检查是否连接,检查连接是否断开
*断开重连,断开且sockfd大于0重置sockfd
********************************/
if(sockfd<0)
{
sockfd = socket_connect_server(servaddr,port,servip);
}
else if(socket_check_connect(sockfd)<0)
{
socket_term(&sockfd);
}
注意细节:
1.sockfd就算大于0也可能会断开连接,应该检查sockfd状态。
采样判断
/********************************
*到达采样时间
*将数据放入数据库,连接成功是发送数据库
********************************/
if(sampling_timer(&sn_tm_intv))
{
//组合温度数据
rv = compose_information(buf,&temp_infor,&sn_tm_intv);
if(rv<0)
break;
if(sockfd<0)
{
log_error("The socket connect failure\n");
rv=sql_insert_one_data(&temp_infor);
if(rv<0)
{
log_error("Insert data failure");
continue;
}
}
else
{
log_info("Connect server[%s:%d] successfully!\n",servip,port);
rv=socket_write(&sockfd,buf,strlen(buf));
if(rv<0)
continue;
max_row=sql_number_of_row();
//遍历数据库数据并发送
for(i=0;i<max_row;i++)
{
rv=sql_select_one_data(&temp_infor);
if(rv>0)//读取数据,存在temp_infor中成功返回0
{
memset(buf,0,sizeof(buf));
snprintf(buf,sizeof(buf),"%s/%s/%s",temp_infor.prod_id,temp_infor.ttime,temp_infor.temp);
//发送
rv=socket_write(&sockfd,buf,strlen(buf));
if(rv<0)
{
rv=socket_check_connect(sockfd);
if(rv<0)
break;
}
sql_delete_one_data();
}
else
continue;
//发送数据时同时也监控采样定时器是否采样数据到来
rv = sampling_timer(&sn_tm_intv);
if(rv)
{
compose_information(buf,&temp_infor,&sn_tm_intv);
rv=socket_write(&sockfd,buf,strlen(buf));
if(rv<0)
{
rv=socket_check_connect(sockfd);
if(rv<0)
break;
}
}
}
//遍历发送完之后删除表中所有数据
//sql_delete_all_data();
log_info("All data in the database has been sent!\n");
}
注意细节:
1.发送数据库数据时,也得实时判断是否到达采样时刻。因为,如果客户端三天三夜没连上服务器,导致数据库中存储了成千上万条数据,当下一次连上服务器并发送数据库数据时,一次性发完所有数据会占用很多时间,导致采样时刻丢失,采样数据缺失,所有应该在发送数据库数据时,实时判断采样时刻。
2.发送数据库数据,应该发一条删一条,不能全部发完,全部删。因为,如果还是上述的原因,导致数据库数据成千上万条,在发送数据时,服务器再次断开,客户端数据没有发送完,数据要么保留,要么就被全部删除,这样会导致第二次连接时发给服务器端的数据冗余或者缺失。所以应该使用发一条删一条的方式(或者也有更好的办法)。
git地址: 猪突猛进进进/DS18B20_temperatura_report - 码云 - 开源中国 (gitee.com)