基于MySQL的嵌入式Linux自动抄表系统设计与实现(附源码)

1. 系统设计

1.1. 服务端程序设计

服务器端程序具有如下功能:

① 同时具备通信和数据库管理功能,协议自定(即收到的哪个字段表示什么意思,为了
简化,可直接用普通字符串,不考虑通信开销问题);
② 绑定 IP、端口,等待接收客户端消息(UDP);
③ 收到用户(客户端)发过来的信息(需要携带用户号、总用电量)后,在数据库中更新该用户的总用电量、本年度用电量、电费余额(根据本次用电、阶梯电价和用户类型计算);
④ 若电费余额低于 50 元,向客户端发送缴费提醒(实际上应该发送给手机,实验中进行简化,直接发给电表);
⑤ 若电费余额低于 0,向客户端发送停电指令;
⑥ 如果服务器长时间没有收到客户端数据,说明出现了异常,可更新状态(需
要在数据表中增加用于记录是否异常的项);

1.2. 客户端程序设计

① 程序运行时需要指定参数(服务器 IP、端口);
② 客户端定时(例如每 10s 一次)向服务器发送当前的总用电量,每个时间周期新增的用电量可以用随机数来产生;
③ 收到服务器端发送的缴费提醒信息后,打印出提示信息;
④ 收到欠费停电指令后,打印出相应信息;

2. 服务端源码

/**
 * @brief   基于MySQL的自动抄表系统服务端
 * @author  Mculover666
 * @data    2020/04/23
 * @note    本系统使用开源库cjson解析数据
*/
 
#include <stdio.h>
#include <mysql.h>
#include <my_global.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include "cJSON.h"

#define DATABASE    "wangshiwei"
#define TABLE       "user961"
#define PORT        8901
#define VER         "1.2"
#define HOME_PRICE  0.8
#define IND_PRICE   1.2
#define BUS_PRICE   1.6

int parse_json_recv(const char* buf, char* id, int* total);
void finish_with_error(MYSQL *con);
int get_value(MYSQL *con, const char* table, const char* field, const char* id, char* buf);
int set_value(MYSQL *con, const char* table, const char* field, const char* id, char* buf);

int main()
{
    int menu;
    MYSQL *con = NULL;
    char id[20] = {0};
    char buf[20] = {0};
    char sql[100];

    /* 存放用户信息 */
    double balance = 0.0;
    double cur_balance = 0.0;
    int total = 0;
    int cur_total = 0;
    int amount = 0;
    char type[10]={0};

    int server_sock_fd;
    struct sockaddr_in server_addr, client_addr;
    char udp_buf[100];
    int nbytes = 0;
    socklen_t len = 0;

    /* 打印菜单 */
    printf("************************************************\n");
    printf("*                                              *\n");
    printf("*       欢迎使用自动抄表管理系统!(服务端)           *\n");
    printf("*                                              *\n");
    printf("*                                              *\n");
    printf("*       作者:Mculover666  UDP监听端口:%-4d      *\n", PORT);
    printf("*       数据库:MySQL      版本:v%3s            *\n", VER);
    printf("*                                              *\n");
    printf("************************************************\n");


    /* 初始化MYSQL变量并连接数据库 */
    con = mysql_init(NULL);
    if(con == NULL)
    {
        printf("MySQL init fail.\n");
        fprintf(stderr,"%s\n",mysql_error(con));
        return -1;
    }

    /* 连接数据库 */
    if(NULL == mysql_real_connect(con,"117.50.111.72","mculover666","mculover666","wangshiwei",3306,NULL,0))
    {
        printf("MySQL connect fail.\n");
        fprintf(stderr,"%s\n",mysql_error(con));
        mysql_close(con);
        return -1;
    }
    printf("[System]远程数据库登录成功!\n");

     /* 创建Server Socket */
    server_sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_sock_fd < 0)
    {
        printf("[System]服务端Socket创建失败\n");
        return -1;
    }

    /* 绑定ip和端口 */
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);//指定端口号
    bind(server_sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr));
    printf("[System]系统已启动监听端口%d!\n", PORT);

    while(1)
    {
        printf("************************************************\n");
        /* 
         * 接收UDP客户端的数据
         * 数据为JSON格式:{"id":"2016211961", "total":123} 
        */
        len = sizeof(client_addr);
        nbytes = recvfrom(server_sock_fd, udp_buf, 100, 0, (struct sockaddr *)&client_addr, &len);
        udp_buf[nbytes] = '\0';
        printf("[UDP Server]接收到来自电表终端(%s)的消息:%s\n", inet_ntoa(client_addr.sin_addr), udp_buf);

        //使用CJSON解析提取用户ID和用户总量
        if(-1 == parse_json_recv(udp_buf, id, &total))
        {
            printf("[CJSON]解析用户ID和用电总量失败!\n");
            return -1;
        }

        /* 查询此用户当前用电类型 */
        if(-1 != get_value(con, TABLE, "type", id, buf))
        {
            strcpy(type, buf);
            printf("[MySQL][用户%s]用电类型查询成功,当前用电类型为:%s\n", id, type);
        }
        else
        {
            printf("[MySQL][用户%s]用电类型查询失败!\n", id);
            return -1;
        }       

        /* 查询此用户当前用电总量 */
        if(-1 != get_value(con, TABLE, "total", id, buf))
        {
            cur_total = atoi(buf);
            printf("[MySQL][用户%s]用电总量查询成功,当前用电总量为:%d\n", id, cur_total);
        }
        else
        {
            printf("[MySQL][用户%s]用电总量查询失败!\n", id);
            return -1;
        }

        /* 查询用户当前余额 */
        if(-1 != get_value(con, TABLE, "balance", id, buf))
        {
            cur_balance = atof(buf);
            printf("[MySQL][用户%s]用电信息查询成功,当前账户余额为:%.2f\n", id, cur_balance);
        }
        else
        {
            printf("[MySQL][用户%s]用电余额查询失败!\n", id);
            return -1;
        }

        /* 计算本年度用电量并扣款 */
        amount = total - cur_total;
        if(strstr(type, "home"))
        {
            //家庭用电
            balance = cur_balance - (double)(amount * HOME_PRICE);
        }
        else if(strstr(type, "industry"))
        {
            //工业用电
            balance = cur_balance - (double)(amount * IND_PRICE);            
        }
        else if(strstr(type, "business"))
        {
            //商业用电
            balance = cur_balance - (double)(amount * BUS_PRICE);  
        }
        else
        {
            printf("[System]服务器不支持当前用电类型:%s价格计算!\n", type);
            return -1;
        }
        printf("[MySQL][用户%s]本年度用电总量:%d, 扣款后的余额为:%.2f\n", id, amount, balance);

        /* 更新数据库中的用电总量 */
        sprintf(buf, "%d", total);
        if(-1 != set_value(con, TABLE, "total", id, buf))
        {
            printf("[MySQL][用户%s]用电总量更新成功!更新后的值为:%d\n", id, total);
        }
        else
        {
            printf("[MySQL][用户%s]用电总量更新失败!\n", id);
            return -1;
        }
        //执行完之后再次查询
        if(-1 != get_value(con, TABLE, "total", id, buf))
        {
            printf("[MySQL][用户%s]用电信息查询成功,当前用电总量为:%s\n", id, buf);
        }
        else
        {
            printf("[MySQL][用户%s]用电信息查询失败!\n", id);
            return -1;
        }

        /* 更新数据库中的年度用电总量 */
        sprintf(buf, "%d", amount);
        if(-1 != set_value(con, TABLE, "amount", id, buf))
        {
            printf("[MySQL][用户%s]年度用电总量更新成功!更新后的值为:%d\n", id, amount);
        }
        else
        {
            printf("[MySQL][用户%s]年度用电总量更新失败!\n", id);
            return -1;
        }
        //执行完之后再次查询
        if(-1 != get_value(con, TABLE, "amount", id, buf))
        {
            printf("[MySQL][用户%s]年度用电总量查询成功,当前用电总量为:%s\n", id, buf);
        }
        else
        {
            printf("[MySQL][用户%s]年度用电总量查询失败!\n", id);
            return -1;
        }

        /* 更新数据库中的用电余额 */
        sprintf(buf, "%.2f", balance);
        if(-1 != set_value(con, TABLE, "balance", id, buf))
        {
            printf("[MySQL][用户%s]用电余额更新成功!更新后的值为:%.2f\n", id, balance);
        }
        else
        {
            printf("[MySQL][用户%s]用电余额更新失败!\n", id);
            return -1;
        }
        //执行完之后再次查询
        if(-1 != get_value(con, TABLE, "balance", id, buf))
        {
            printf("[MySQL][用户%s]用电信息再次查询成功,当前账户余额为:%s\n", id, buf);
        }
        else
        {
            printf("[MySQL][用户%s]用电信息查询失败!\n", id);
            return -1;
        }

        /* 判断是否需要发送缴费提醒 */
        if(balance < 0.0)
        {
            sprintf(udp_buf, "poweroff");
            sendto(server_sock_fd,udp_buf,strlen(udp_buf),0,(struct sockaddr *)&client_addr,len);
            printf("[UDP Server]系统已经向用电终端发送停电指令!\n");
        }
        else if(balance > 0.0 && balance < 50.0)
        {
            sprintf(udp_buf, "啊哦~您的当前余额不足50元(%.2f),记得及时充值哦!\n", balance);
            sendto(server_sock_fd,udp_buf,strlen(udp_buf),0,(struct sockaddr *)&client_addr,len);
            printf("[UDP Server]系统已经向用户%s发送充值提醒!\n", id);
        }
        else
        {
            sprintf(udp_buf, "正常");
            sendto(server_sock_fd,udp_buf,strlen(udp_buf),0,(struct sockaddr *)&client_addr,len);
        }
    }
    mysql_close(con);
    return 0; 
}
/** 
 * 使用cJSON从接收到数据中提取用户ID和用户用电总量
 * @param buf 需要处理的数据(json格式)
 * @param id 存放提取出的用户ID
 * @param toatal 存放用户用电总量
 * @return 解析成功返回0,解析失败返回-1
*/
int parse_json_recv(const char* buf, char* id, int* total)
{
    cJSON* cjson_all = NULL;
    cJSON* cjson_id = NULL;
    cJSON* cjson_total = NULL;
    
    /* 解析整段JSO数据 */
    cjson_all = cJSON_Parse(buf);
    if(cjson_all == NULL)
    {
        return -1;
    }

    /* 根据键值提取用户ID */
    cjson_id = cJSON_GetObjectItem(cjson_all, "id");
    strcpy(id, cjson_id->valuestring);

    /* 根据键值提取用户用电总量 */
    cjson_id = cJSON_GetObjectItem(cjson_all, "total");
    *total = cjson_id->valueint;

    /* 释放cjson占用内存 */
    cJSON_Delete(cjson_all);

    return 0;
}


/** 
 * 语句执行出错处理
 * @param con 需要处理的MYSQL变量
 * @return -1;
*/
void finish_with_error(MYSQL *con)
{
    fprintf(stderr,"%s\n",mysql_error(con));
    mysql_close(con);
}

/**
 * 从数据库中查询用户信息
 * @param con 成功连接的MySQL变量
 * @param table 需要查询的表
 * @param id 用户id
 * @param buf 存放查询结果的缓冲区
 * @return 成功返回0,失败则返回-1
*/
int get_value(MYSQL *con, const char* table, const char* field, const char* id, char* buf)
{
    char sql[100];
    MYSQL_RES *result = NULL;
    MYSQL_ROW row;

    //构造完整sql语句
    sprintf(sql, "select %s from %s where id=%11s;", field, table, id);
    //查询
    if(mysql_query(con,sql))
    {
        finish_with_error(con);
        return -1;
    }
    //获取并存储查询结果
    result = mysql_store_result(con);
    if(NULL == result)
    {
        finish_with_error(con);
        return -1;
    }
    //根据行数查询数据
    if(row = mysql_fetch_row(result))
    {
        if(row[0] != NULL)
        {
            strcpy(buf, row[0]);
        }
        else
        {
            strcpy(buf, "NULL");
        }
        //printf("用户%s的用电类型为:%s\n", id, );
    }
    mysql_free_result(result);

    return 0;
}
/**
 * 从数据库中更新用户信息
 * @param con 成功连接的MySQL变量
 * @param table 需要更新的表
 * @param id 用户id
 * @param buf 存放待更新数据的缓冲区
 * @return 成功返回0,失败则返回-1
*/
int set_value(MYSQL *con, const char* table, const char* field, const char* id, char* buf)
{
    char sql[100]; 
    
    //构造完整sql语句
    sprintf(sql, "update %s set %s=%s where id=%11s;", table, field, buf, id);
    //执行
    if(mysql_query(con,sql))
    {
        finish_with_error(con);
        return -1;
    }

    return 0;
}

cJSON使用:cJSON使用详细教程 | 一个轻量级C语言JSON解析器

编译:

CC = gcc
INCPATH = -I/usr/include/mysql
LIB = -lmysqlclient -L/usr/lib/mysql

server_wangshiwei: server_wangshiwei.o cJSON.o
	$(CC) server_wangshiwei.o cJSON.o $(LIB) -o server_wangshiwei

server_wangshiwei.o:server_wangshiwei.c
	$(CC) -c server_wangshiwei.c $(INCPATH) -o server_wangshiwei.o

cJSON.o:cJSON.c
	$(CC) -c cJSON.c -o cJSON.o

clean:
	rm -rf *.o server_wangshiwei

运行:

./wangshiwei

效果如下:
<待写>

3. 客户端源码(嵌入式Linux开发板)

/**
/**
 * @brief   基于MySQL的自动抄表系统客户端
 * @author  Mculover666
 * @data    2020/04/23
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define USER_ID "2016211961"

int main(int argc, char* argv[])
{
    int sock_fd;
    struct sockaddr_in server_addr;
    char recv_buf[100];
    int nbytes = 0;
    socklen_t len = 0;
    time_t cur_time;
    struct tm *p;

    //用户总用电量,数据库中初始化值给为0
    int total = 0;

    /* 打印启动界面 */
    printf("************************************************\n");
    printf("*                                              *\n");
    printf("*       欢迎使用自动抄表管理系统!(客户端)           *\n");
    printf("*                                              *\n");
    printf("*                                              *\n");
    printf("*       作者:Mculover666  计算机与信息学院        *\n");
    printf("*                                              *\n");
    printf("************************************************\n");

    /* 创建Socket */
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0)
    {
        printf("[UDP Client]客户端Socket创建失败!\n");
        return -1;
    }
    printf("[UDP Client]客户端Socket创建成功!\n");

    /* 绑定ip和端口 */
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    server_addr.sin_port = htons(atoi(argv[2]));//指定端口号

    /* 定时 每 10s一次向服务器发送当前的总用电量 */
    while(1)
    {
        /* 获取当前时间 */
        time(&cur_time);
        p = gmtime(&cur_time);
	    len = sizeof(server_addr);
        //发送数据给服务端
        sprintf(recv_buf, "{\"id\":\"%10s\",\"total\":%d}", USER_ID, total);
        sendto(sock_fd,recv_buf,sizeof(recv_buf),0,(struct sockaddr *)(&server_addr),len);
        printf("[UDP Client][%02d:%02d:%02d]发送数据:%s\n",p->tm_hour+8, p->tm_min, p->tm_sec, recv_buf);
        //用电量增加
        total += rand()%30;
        
        /* 接收UDP服务端返回的数据 */
        len = sizeof(server_addr);
        nbytes = recvfrom(sock_fd, recv_buf, 100, 0, (struct sockaddr *)&server_addr, &len);
        recv_buf[nbytes] = '\0';
        printf("[UDP Client]接收数据:%s\n", recv_buf);

        /* 判断是否需要停电 */
        if(strstr(recv_buf, "poweroff"))
        {
            printf("余额不足,已停电!请及时充值!");
            return -1;
        }

        /* 延时5s */
        sleep(5);
    }

    return 0;
}

编译:

CC = arm-linux-gnueabi-gcc

clientARM_lizhixin:client_lizhixin.c
	$(CC) client_lizhixin.c -o clientARM_lizhixin

clean:
	rm -rf *.o clientARM_lizhixin

运行:
<待写>

发布了285 篇原创文章 · 获赞 1036 · 访问量 37万+

猜你喜欢

转载自blog.csdn.net/Mculover666/article/details/105699697