目录
一、项目要求:
二、演示效果:
设备端:
Modbus用户控制端:
服务器端:
网页端:
三、 项目代码:
Modbus用户控制端代码:
服务器端代码:
网页端代码:
四、B站讲解视频:
一、项目要求:
设备端:有温湿度传感器采集温度和湿度以及LED灯,和BEEP蜂鸣器(使用Modbus slave来模拟);
虚拟机端:通过modbus采集和控制信息的用户控制端和采集数据保存历史纪录的服务器端,利用进程间通信实现连接;
网页端:利用html5写一个网页,显示采集的温湿度信息和能够控制LED灯和BEEP蜂鸣器的开关,通过http协议与服务器连接。
二、演示效果:
设备端:
显示采集的温湿度和led,蜂鸣器的状态(0为关闭,1为开启)
Modbus用户控制端:
每隔两秒采集一次数据并将数据打印至终端
服务器端:
显示通过网页传递过来的指令和用户控制端进行联系,并且存入历史记录中
网页端:
按下温湿度采集按钮,显示采集到的温度和湿度,能够控制LED灯和蜂鸣器的开关,按下历史记录查询按钮可以显示历史记录
三、 项目代码:
Modbus用户控制端代码:
#include <stdio.h>
#include "modbus-tcp.h"
#include "modbus.h"
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <sys/shm.h>
#include <stdio.h>
union val
{
int i_val;
float f_val;
};
typedef struct dev
{
long op;
char dev_name[32]; // 设备名称
union val v; // 存放具体数据
} DEV;
struct data
{
char buf[32];
};
struct data *p = NULL;
key_t key, key1;
int msgid;
int shmid;
void *kongzhi(void *arg)
{
modbus_t *ctx = (modbus_t *)arg;
DEV dev;
while (1)
{
msgrcv(msgid, &dev, sizeof(dev) - sizeof(dev.op), 100, 0);
printf("%s %d\n", dev.dev_name, dev.v.i_val);
if (strcmp(dev.dev_name, "led") == 0)
{
modbus_write_bit(ctx, 0, dev.v.i_val);
printf("led灯%s\n", dev.v.i_val ? "开启" : "关闭");
}
else if (strcmp(dev.dev_name, "beep") == 0)
{
modbus_write_bit(ctx, 1, dev.v.i_val);
printf("蜂鸣器%s\n", dev.v.i_val ? "开启" : "关闭");
}
}
}
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("用法 <ip>\n");
return -1;
}
modbus_t *ctx;
ctx = modbus_new_tcp(argv[1], 502);
if (ctx == NULL)
{
perror("modbus new tcp失败");
return -1;
}
// 设置从机ID
modbus_set_slave(ctx, 1);
// 建立连接
if (modbus_connect(ctx) < 0)
{
printf("modbus connect失败\n");
modbus_free(ctx);
return -1;
}
printf("connect ok\n");
key = ftok("./test", 'a');
if (key < 0)
{
perror("创建key值失败");
return -1;
}
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
{
shmid = shmget(key, 128, 0666);
}
else
{
perror("创建共享内存失败");
return -1;
}
}
p = (struct data *)shmat(shmid, NULL, 0);
if (p == (struct data *)-1)
{
perror("映射共享内存失败");
return -1;
}
key1 = ftok("./test1", 'a');
if (key1 < 0)
{
perror("创建key值失败");
return -1;
}
msgid = msgget(key1, IPC_CREAT | IPC_EXCL | 0666);
if (msgid <= 0)
{
if (errno == EEXIST)
{
msgid = msgget(key1, 0666);
}
else
{
perror("创建消息队列失败");
return -1;
}
}
pthread_t kongzhitid;
// 创建线程
if (pthread_create(&kongzhitid, NULL, kongzhi, ctx) != 0)
{
perror("创建控制线程失败");
modbus_free(ctx);
modbus_close(ctx);
return -1;
}
uint16_t dest[4];
DEV tem, hum;
while (1)
{
int mrr = modbus_read_registers(ctx, 0, 4, dest);
if (mrr <= 0)
{
perror("读保持寄存器的值失败");
exit(0);
}
else
{
strcpy(tem.dev_name, "温度");
tem.v.f_val = modbus_get_float_dcba(dest);
strcpy(hum.dev_name, "温度");
hum.v.f_val = modbus_get_float_dcba(dest + 2);
sprintf(p->buf, "温度:%.2f℃ 湿度:%.2f\%\n", tem.v.f_val, hum.v.f_val);
printf("%s",p->buf);
}
sleep(2); // 每2秒采集一次
}
modbus_free(ctx);
modbus_close(ctx);
return 0;
}
服务器端代码:
#include <sys/types.h>
#include <sys/socket.h>
#include "custom_handle.h"
struct data *p = NULL;
DEV dev;
extern int len;
sqlite3 *db;
#define KB 1024
#define HTML_SIZE (64 * KB)
// 普通的文本回复需要增加html头部
#define HTML_HEAD "Content-Type: text/html\r\n" \
"Connection: close\r\n"
/**
* @brief 处理自定义请求,在这里添加进程通信
* @param input
* @return
*/
int parse_and_process(int sock, const char *query_string, const char *input)
{
// 打开数据库
if (sqlite3_open("./history.db", &db) < 0)
{
printf("打开数据库失败: %s\n", sqlite3_errmsg(db));
return -1;
}
key_t key, key1;
int shmid, msgid;
key = ftok("./test", 'a');
if (key < 0)
{
perror("创建key值失败");
return -1;
}
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
{
shmid = shmget(key, 128, 0666);
}
else
{
perror("创建共享内存失败");
return -1;
}
}
p = (struct data *)shmat(shmid, NULL, 0);
if (p == (struct data *)-1)
{
perror("映射共享内存失败");
return -1;
}
key1 = ftok("./test1", 'a');
if (key1 < 0)
{
perror("创建key值失败");
return -1;
}
msgid = msgget(key1, IPC_CREAT | IPC_EXCL | 0666);
if (msgid <= 0)
{
if (errno == EEXIST)
{
msgid = msgget(key1, 0666);
}
else
{
perror("创建消息队列失败");
return -1;
}
}
char sql[1024];
char *errmsg = NULL;
char **result = NULL;
int rows, columns;
time_t t;
struct tm *timeinfo;
char date[100];
time(&t);
timeinfo = localtime(&t);
snprintf(date, sizeof(date), "%d-%d-%d %d:%d:%d", timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
if (strstr(input, "get"))
{
send(sock, p->buf, strlen(p->buf), 0);
snprintf(sql, sizeof(sql), "insert into history values ('%s','%s');", date, p->buf);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != 0)
{
printf("放入历史记录失败: %s", errmsg);
return -1;
}
memset(sql, 0, sizeof(sql));
memset(p->buf, 0, sizeof(p->buf));
}
else if (strstr(input, "history"))
{
memset(p->buf, 0, sizeof(p->buf));
snprintf(sql, sizeof(sql), "select * from history;");
int rc = sqlite3_get_table(db, sql, &result, &rows, &columns, &errmsg);
if (rc != 0)
{
printf("查询历史记录失败: %s", errmsg);
}
else
{
if (rows > 0)
{
for (int i = 1; i <= rows; ++i)
{
for (int j = 0; j < columns; ++j)
{
strcat(p->buf, result[i * columns + j]);
strcat(p->buf, " ");
}
strcat(p->buf, "\n");
}
}
}
send(sock, p->buf, strlen(p->buf), 0);
memset(sql, 0, sizeof(sql));
memset(p->buf, 0, sizeof(p->buf));
}
else
{
memset(dev.dev_name, 0, sizeof(dev.dev_name));
// for (int i = 0; i < len - 2; i++)
// {
// dev.dev_name[i] = input[i];
// }
// char i = input[len - 1];
// dev.v.i_val = i - '0';
// printf("%s %d\n", dev.dev_name, dev.v.i_val);
char i;
sscanf(input, "%s %s", dev.dev_name, &i);
dev.v.i_val = i - '0';
printf("%s %d\n", dev.dev_name, dev.v.i_val);
dev.op = 100;
msgsnd(msgid, &dev, sizeof(dev) - sizeof(dev.op), 0);
if (strcmp(dev.dev_name, "led") == 0)
{
sprintf(p->buf, "led灯%s\n", dev.v.i_val ? "开启" : "关闭");
// send(sock, p->buf, strlen(p->buf), 0);
snprintf(sql, sizeof(sql), "insert into history values ('%s','%s');", date, p->buf);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != 0)
{
printf("放入历史记录失败: %s", errmsg);
return -1;
}
printf("%s\n", sql);
memset(sql, 0, sizeof(sql));
}
else if (strcmp(dev.dev_name, "beep") == 0)
{
sprintf(p->buf, "蜂鸣器%s\n", dev.v.i_val ? "开启" : "关闭");
// send(sock, p->buf, strlen(p->buf), 0);
snprintf(sql, sizeof(sql), "insert into history values ('%s','%s');", date, p->buf);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != 0)
{
printf("放入历史记录失败: %s", errmsg);
return -1;
}
printf("%s\n", sql);
memset(sql, 0, sizeof(sql));
}
else
{
sprintf(p->buf, "输入错误,请重新输入\n");
// send(sock, p->buf, strlen(p->buf), 0);
}
}
return 0;
}
网页端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工业信息采集系统</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.section {
margin-bottom: 20px;
}
.button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
}
.button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.status {
margin-top: 10px;
padding: 10px;
background-color: #f8f9fa;
border: 1px solid #ced4da;
border-radius: 5px;
}
</style>
<script>
// //设置定时器,定时5秒调用一次函数refreshPage
// setInterval(refreshPage, 5000);
// function refreshPage() {
// //TODO
// }
function sendledon() {
var buf = "led 1";
var xhr = new XMLHttpRequest();
var url = "";
xhr.open("post", url, true);
xhr.send(buf);
}
function sendledoff() {
var buf = "led 0";
var xhr = new XMLHttpRequest();
var url = "";
xhr.open("post", url, true);
xhr.send(buf);
}
function sendbeepon() {
var buf1 = "beep 1";
var xhr = new XMLHttpRequest();
var url = "";
xhr.open("post", url, true);
xhr.send(buf1);
}
function sendbeepoff() {
var buf1 = "beep 0";
var xhr = new XMLHttpRequest();
var url = "";
xhr.open("post", url, true);
xhr.send(buf1);
}
function sendcaiji() {
var buf = "get";
var xhr = new XMLHttpRequest();
var url = "";
xhr.open("post", url, true);
xhr.send(buf);
xhr.onreadystatechange = function () {
var response = xhr.responseText;
document.getElementById('temhum').innerText = response;
};
}
function sendhistory() {
var buf = "history";
var xhr = new XMLHttpRequest();
var url = "";
xhr.open("post", url, true);
xhr.send(buf);
xhr.onreadystatechange = function () {
var response = xhr.responseText;
document.getElementById('lishi').innerText = response;
};
}
</script>
</head>
<body>
<div class="container">
<h1>工业信息采集系统</h1>
<!-- 温湿度采集 -->
<div class="section">
<input class="button" type="button" name="caiji" value="温湿度采集" onclick="sendcaiji()" />
<div class="status" id="wenshidu">
<p><span id="temhum"></p>
</div>
</div>
<!-- LED灯控制 -->
<div class="section">
<h2>LED灯控制</h2>
<label for="ledon">
开<input type="radio" name="led" id="ledon" value="led 1" onclick="sendledon()" />
</label>
<label for="ledoff">
关<input type="radio" name="led" id="ledoff" checked="checked" value="led 0"
onclick="sendledoff()" /><br />
</label>
</div>
<!-- 蜂鸣器控制 -->
<div class="section">
<h2>蜂鸣器控制</h2>
<label for="ledon">
开<input type="radio" name="beep" id="beepon" value="beep 1" onclick="sendbeepon()" />
</label>
<label for="ledoff">
关<input type="radio" name="beep" id="beepoff" checked="checked" value="beep 0"
onclick="sendbeepoff()" /><br />
</label>
</div>
<!-- 历史记录查询 -->
<div class="section">
<input type="button" class="button" name="history" value="历史记录查询" onclick="sendhistory()" />
<div class="status" id="history-records">
<p><strong>历史记录:</strong></p>
<ul id="lishi"></ul>
</div>
</div>
</div>
</body>
</html>