目录
一、项目要求:
二、演示效果:
设备端:
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;}// 设置从机IDmodbus_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>
四、B站讲解视频:
https://www.bilibili.com/video/BV1cxtaeWEyA/?share_source=copy_web&vd_source=f25867d1b9870033386a3e5ea1bbca6e