项目目的:
做这个项目,主要是为了熟悉A53开发板得使用,并且熟悉关于嵌入式的一些东西,我自己觉得这些东西很有意思
项目描述:
基于A53编写打卡机程序,在自己的云服务器上完成了前端界面的设计,以及后端逻辑的梳理,使用nfs挂载技术将开发板可以运行的可执行文件挂载到A53开发板上,实现了员工打卡,管理员登录网页看到员工的打卡记录,并且员工打卡时,开发板发出对应的声音
1、基于A53编写打卡机程序使用htm完成的前端界面设计,
实现RFID 数据读取,基于Mysql数据库员工信息检索,数据Qt界面显示,打卡日志写入,管理员界面登录可对员工信息以及打卡日志进行基本的数据检索和删改操作,以及数据图表查看操作,员工打卡信息通过UDP协议上传至服务器。
2、编写linux服务器程序通过UDP自定义协议读取各打卡器打卡信息,并将数据写入sqlite3数据库。
3、编写CGI程序,
实现前端指令的解析执行和数据库操作,实现管理员和普通员工的权限管理、管理员用户和员工用户信息增删改查处理、基于部门的检索管理、员工考勤信息(迟到、早退的信息检索上传)、事件(事假、调休、补卡)申请与处理(申请提交,管理员界面事件驳回和允许)、员工考勤日志、事件申请日志、管理员操作日志等信息入库和上传;员工注册,刷卡RFID识别卡号,通过消息队列等进程通信方式与服务器调用的CGI程序进行控制指令实时通信。
4、数据库表的设计
admin表:主要记录管理员的用户名以及密码
person表:主要记录打卡用户名以及密码以及卡号
card表:主要记录卡号以及时间,还有员工id
输入云服务器ip进入员工注册界面:员工点击卡号,然后刷卡,输入账号密码,进行注册,刷卡时,我已经完成了刷卡卡号显示到界面上得逻辑处理,所以直接点击注册,然后自己写的后端程序查询数据库中是否有员工信息等,如果有显示员工以及注册,或者 卡号已经注册。
管理员登录界面
管理员操作界面
项目流程我画出来是这个样子的
目录文件大概有这些
A53开发板的代码
几个html的代码就不复制过来了,需要的可以直接去我的gitee上看,
gitee地址如下
最难的一点可以说是,员工注册的逻辑实现了
首先云服务器端,创建文件夹和udp_server.c文件,其中udp_server的作用是为了和A53开发板进行通信,这样的话用户通过使用IC卡来刷RFID阅读器,将获得的卡号,以及对应的消息序列发给udpserver 这样我们的服务端就拿到了数据,并且udpserver是通过消息队列与前端进行间接通信的,获取到前端的请求后,接下来向A53开发板发送需要卡号的请求
udpserver.c的代码如下
#include <stdio.h>
#include <sys/socket.h> //socket
#include <unistd.h> //close
#include <string.h> //bzero
#include <netinet/in.h> //struct sockaddr_in
#include <arpa/inet.h> //inet_pton
#include <stdlib.h> //atoi
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "sqlite3.h"
#include <time.h>
// 定义一个消息类型
typedef struct
{
long mtype;
char text[64];
} MSG;
int msg_id;
int sockfd;
sqlite3 *db = NULL;
struct sockaddr_in dev_addr[8];
int find_card_from_db(sqlite3 *db, char *card);
int insert_card_from_db(sqlite3 *db, char *card,char * time);
void* get_cgi_func(void *arg)
{
//获取消息队列的内容
while(1)
{
//接收udp_server的应答
MSG recv_msg;
msgrcv(msg_id, &recv_msg, sizeof(MSG) - sizeof(long), 10, 0);
printf("udp_server获取CGI的数据:%s\n",recv_msg.text);
//报名CGI需要获取卡号
//将请求发送给A53
sendto(sockfd, recv_msg.text,strlen(recv_msg.text),0,\
(struct sockaddr *)(&dev_addr[0]),sizeof(dev_addr[0]) );
}
}
int main(int argc, char const *argv[])
{
// 运行时传端口./a.out 8000
if (argc != 2)
{
printf("./a.out 8000\n");
return 0;
}
// 1、创建一个通信的套接字(一般一个套接字就够)
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket");
return 0;
}
// 2、bind绑定固定的端口IP
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(atoi(argv[1]));
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (ret != 0)
{
perror("bind");
return 0;
}
//打开数据库
int result = sqlite3_open("db",&db);
if(result != SQLITE_OK){
printf("open error\n");
exit(-1);
}
//创建消息队列
key_t key = ftok("/", 2023);
msg_id = msgget(key, IPC_CREAT | 0666);
//创建线程 获取cgi的消息
pthread_t tid;
pthread_create(&tid,NULL, get_cgi_func, NULL);
pthread_detach(tid);
//3、接收数据
//send:0:card表示A53的设备0发来卡号
//up:0表明 有设备0上线了
while(1)
{
struct sockaddr_in from_addr;
socklen_t from_len = sizeof(from_addr);
char buf[1024] = "";
// char cpy[1024] = "";
recvfrom(sockfd, buf, sizeof(buf), 0,
(struct sockaddr *)&from_addr, &from_len);
if(strncmp(buf,"send",4) == 0)//card表示A53的设备0发来卡号
{
//收到A53发来的card号放入消息队列中:send:card
printf("-----udp_server-------%s\n",buf);
MSG msg;
msg.mtype = 20;
//解析卡号,解析时间
//从数据库中查找 card是否存在??????
int ret = find_card_from_db(db, buf+5);
if(ret == 1)//存在
{
sprintf(msg.text,"1");
//插入卡号和时间
char time1[1024]="";
time_t current_time;
struct tm* timeinfo;
time(¤t_time);
timeinfo = localtime(¤t_time);
// 格式化时间并存储在 'time' 变量中
strftime(time1, sizeof(time1), "%Y-%m-%d %H:%M:%S", timeinfo);
//插入数据库
insert_card_from_db(db, buf + 5, time1);
//sscanf(buf+5,"%[^:]:%s",cpy,time);
//插入数据库record中更新记录
}
else
{
//f7aa97ba
sprintf(msg.text,"0:%s", buf+5);
//sprintf(msg.text, "0:%s", cpy);
msgsnd(msg_id, &msg, sizeof(MSG) - sizeof(long), 0);
}
printf("即将写入队列的数据:%s\n", msg.text);
}
else if(strncmp(buf,"up",2) == 0)//up:0表明 有A53设备0上线了
{
dev_addr[0] = from_addr;
unsigned short port = ntohs(from_addr.sin_port);
char ip_str[16]="";
inet_ntop(AF_INET, &from_addr.sin_addr.s_addr, ip_str,16);
printf("%s---%hu设备上线了\n",ip_str,port);
}
}
close(sockfd);
}
int find_card_from_db(sqlite3 *db, char *card)
{
int nRow, nColumn;
char **dbResult;
char sql[128] = "";
//select * from person where card='123456789';
sprintf(sql,"select * from person where card=\'%s\';", card);
sqlite3_get_table(db, sql,&dbResult, &nRow, &nColumn,NULL);
printf("row=%d\n", nRow);
if(nRow > 0)
return 1;//存在
return 0;//不存在
}
//插入数据库的函数
int insert_card_from_db(sqlite3 *db, char *card,char * time){
//select * from person where card='123456789';
//构建插入数据库的报文
char sql[256]="";
sprintf(sql,"insert into record values(\'%s\',\'%s\');", card,time);
sqlite3_exec(db,sql,NULL,NULL,NULL);
return 1;
}
A53开发板:开了一个线程,用于接收数据其中定义一个标志位,正常情况下为0,如果收到服务器需要卡号的请求的话将他改为1,然后主线程负责给服务器发送信息是普通请求,也就是正常的打卡,将打卡记录插入数据库card,如果是标志位为1的话,那么就是员工的注册,将卡号发送给服务器然后服务器进行逻辑判断,最后将数据插入到数据库中,如果标志位为0的话表示的
中主函数代码main.c代码如下
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "rfid_lib.h"
#include <sys/socket.h> //socket
#include <netinet/in.h> //struct sockaddr_in
#include <arpa/inet.h> //inet_pton
#include <pthread.h>
#include <time.h>
//串口号
#if 0
#define UART_DEV "/dev/ttyUSB0"
#else
#define UART_DEV "/dev/ttySAC2"
#endif
int sockfd;
//服务器端口和服务器IP
unsigned short server_port = 8000;
char server_ip[16]="1.94.28.227";
int net_flag = 0;//0表示没有网络数据 1表示有网络数据(服务器要卡号)
//接收服务器的应答
void *get_server_udp(void *arg)
{
while(1)
{
char buf[128]="";
recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
if(strncmp(buf,"get", 3) == 0)
{
net_flag=1;
}
}
return NULL;
}
int main(void)
{
int len,i;char type;
unsigned char id[18] = {0};
//捕获信号
uart_rfid_init(UART_DEV);
//创建udp客户端
// 1、创建一个通信的套接字(一般一个套接字就够)
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket");
return 0;
}
//2、上报设备信息
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(server_port);
ser_addr.sin_addr.s_addr = inet_addr(server_ip);
sendto(sockfd,"up:0",4,0,(struct sockaddr *)&ser_addr, sizeof(ser_addr));
//创建线程获取服务器的应答
pthread_t tid;
pthread_create(&tid,NULL, get_server_udp, NULL);
pthread_detach(tid);
while(1)
{
char card_buf[128]="";
if(len = get_rfid_card_id(id,&type)){
printf("%c类卡卡号:",type);
for(i=0;i<len;i++)
sprintf(card_buf+strlen(card_buf),"%02x",id[i]);
printf("卡号:%s\n",card_buf);
//正常打卡 还是 网页注册
if(net_flag == 1)//网页注册 发送send:卡号 给udp服务器
{
char buf[256]="";
int len = sprintf(buf,"send:%s",card_buf);
sendto(sockfd,buf,len,0,(struct sockaddr *)&ser_addr, sizeof(ser_addr));
net_flag=0;
}
else if(net_flag == 0)//普通打开
{
char buf[256]="";
int len = sprintf(buf,"send:%s",card_buf);
//发送时间和卡号
// 获取新的字符串长度
printf("%s\n",buf);
//发给服务器卡号和时间,让服务器进行判断
sendto(sockfd,buf,len,0,(struct sockaddr *)&ser_addr, sizeof(ser_addr));
//不存在 提示无效卡
}
}
}
return 0;
}
get_card.c
消息队列的代码如下
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
// 定义一个消息类型
typedef struct
{
long mtype;
char text[64];
} MSG;
int main(int argc,char *argv[])
{
printf("content-type:text/html\n\n");
//以GET的方式 获取服务器的数据"get:0"
char *data = getenv("QUERY_STRING");
//创建消息队列
key_t key = ftok("/", 2023);
int msg_id = msgget(key, IPC_CREAT | 0666);
//给udp_server发送获取卡号的数据
MSG msg;
msg.mtype = 10;
strcpy(msg.text,data);
msgsnd(msg_id, &msg, sizeof(MSG) - sizeof(long), 0);
//接收udp_server的应答
MSG recv_msg;
msgrcv(msg_id, &recv_msg, sizeof(MSG) - sizeof(long), 20, 0);
//可能recv_msg.text的结果可能是0:card 1失败
if(recv_msg.text[0] == '0')
printf("%s\n", recv_msg.text+2);
else
printf("%s\n","1");
return 0;
}
记得将他编译成cgi文件,来响应
首先需要注意的是管理员注册逻辑
1、首先根据这个函数来获取浏览器请求对象
function getXMLHttpRequest() {
var xmlhttp = null;
if (window.XMLHttpRequest)//自动检测当前浏览器的版本,如果是 IE5.0 以上的高版本的浏览器
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();//创建请求对象
}
else如果浏览器是底版本的
{// code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");//创建请求对象
}
return xmlhttp;//返回请求对象
}
2、点击按钮后,先跳转另一个注册界面,然后根据逻辑的响应。给boa服务器发送get请求,要求相应cgi的响应,然后在后端的.c文件中处理逻辑,编译生成可执行的二进制文件,
//管理员注册函数
function go_administrator() {
window.location.href = "./admin_register.html";//在当前网页显示
//1、创建对象
var xmlHttp = getXMLHttpRequest();
//2、设置回调函数
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
//获取服务器的应答(假如服务器的应答是字符串格式)
var ret = xmlHttp.responseText;
alert(ret);
}
}
//3、创建请求 GET方式 异步
//g获取用户名 密码 卡号
var user = document.getElementById("user").value;
var pwd = document.getElementById("pwd").value;
var url = "/cgi-bin/admin_register.cgi?";
url += user + ":";
url += pwd + ":";
xmlHttp.open("GET", url, true);
//4、发送请求
xmlHttp.send();
alert(url);
}
这样的的话整个注册逻辑就弄好了,接下来就是其他业务的拓展