目录
2.服务器程序<--ModbusTCP-->Modbus slave
项目基本流程
主要是完成网页对Modbus设备(寄存器)信息的读取(例如光照强度、加速度等)以及通过网页点击开关对Modbus设备(线圈)的控制,用Modbus slave来代替Modbus设备。
Webserver服务器
Web Server中文名称叫网页服务器或web服务器。WEB服务器也称为WWW(WORLD WIDE WEB)服务器,主要功能是提供网上信息浏览服务。
Web服务器可以解析HTTP协议。当Web服务器接收到一个HTTP请求,会返回一个HTTP响应,例如送回一个HTML页面。
在嵌入式中常见的轻量级的服务器有:Lighttpd、 Shttpd,、Thttpd、Boa、Mini_httpd、Appweb、Goahead
Lighttpd服务器
LigHttpd是一个开源的轻量级嵌入式Web server,是提供一个专门针对高性能网站,安全、快速、兼容性好并且灵活的web server环境。具有非常低的内存开销,cpu占用率低,效能好,以及丰富的模块等特点。
运行lighttpd服务器并测试
1.运行
cd ~/work/web
sudo sbin/lighttpd -f config/lighttpd.conf -m lib/
(结束进程为:pkill lighttpd)
2.测试
将index.html文件放到www/htdocs目录下
打开浏览器,在地址栏输入服务器的IP地址(虚拟机IP)即可看到主页。
CGI特点
CGI是Web服务器和一个独立的进程之间的协议,它通过环境变量及标准输入/输出和服务器之间进行数据交互。
- 通过环境变量可以获得网页的请求方式、地址等
- 通过标准输入可以获取网页的消息正文
- 通过标准输出可以发送网页请求的数据(响应)
主要实现代码
1.Modbus 设备---Modbus slave
创建两个Modbus设备,ID=1
功能码:03 表示:读保持寄存器
功能码:01表示:线圈
2.服务器程序<--ModbusTCP-->Modbus slave
创建两个进程
子进程每隔5s通过共享内存向cgi传递数据;
父进程使用消息队列接收cgi写线圈的命令。
#include <stdio.h>
#include <stdlib.h>
#include <modbus.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
//消息队列结构体
struct msgbuf
{
long mtype; //消息类型
char buf[64]; //消息正文
} msg;
//编译时:gcc xx.c -lmodbus
int main(int argc, char const *argv[])
{
modbus_t *ctx;
uint16_t dest[64] = {};
//1.以TCP方式创建Modbus实例,并初始化 主机ip地址和端口号502
ctx = modbus_new_tcp(argv[1], atoi(argv[2]));
if (ctx == NULL)
{
perror("modbus create err");
return -1;
}
//2.设置从机ID
int set_id = modbus_set_slave(ctx, 1);
if (set_id != 0)
{
perror("set id err");
return -1;
}
//3.和从机(slave)建立连接
int con = modbus_connect(ctx);
if (con != 0)
{
perror("connect err");
return -1;
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
while (1)
{
//1.创建key值
key_t key = ftok("/home", '2');
if (key < 0)
{
perror("ftok err");
return -1;
}
//2.创建或打开共享内存
int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0)
{
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
//3.映射共享内存和虚拟地址,NULL:由系统自动完成映射,0可读可写
char *p = (char *)shmat(shmid, NULL, 0);
if (p == (char *)-1)
{
perror("shmat err");
return -1;
}
//4.寄存器操作 modbus实例 起始地址 寄存器个数 存储值
modbus_read_registers(ctx, 0, 4, dest);
sprintf(p, "%d X:%d Y:%d Z:%d\n", dest[0], dest[1], dest[2], dest[3]);
printf("%s\n", p);
sleep(5);
}
}
else
{
while (1)
{
struct msgbuf msg;
key_t key = ftok("/home", '1');
if (key < 0)
{
perror("ftok err");
return -1;
}
//1.创建或打开消息队列
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid <= 0)
{
if (errno == EEXIST)
msgid = msgget(key, 0666);
else
{
perror("msgget err");
return -1;
}
}
//读取信息
msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 1, 0);
printf("msgbuf:%s\n", msg.buf);
if (msg.buf[4] == '0' && msg.buf[6] == '1')
{
modbus_write_bit(ctx, 0, 1);
}
else if (msg.buf[4] == '0' && msg.buf[6] == '0')
{
modbus_write_bit(ctx, 0, 0);
}
else if (msg.buf[4] == '1' && msg.buf[6] == '1')
{
modbus_write_bit(ctx, 1, 1);
}
else if (msg.buf[4] == '1' && msg.buf[6] == '0')
{
modbus_write_bit(ctx, 1, 0);
}
}
}
//5.关闭套接字
modbus_close(ctx);
//6.释放实例
modbus_free(ctx);
wait(NULL);
exit(0);
return 0;
}
3.CGI程序
通过共享内存读取设备信息,向消息队列中发送网页控制线圈的命令
注意:服务程序与CGI程序中的共享内存和消息队列Key值要一致
(1)main.c
#include "req_handle.h"
#include "log_console.h"
int main(int argc, char *argv[])
{
//先初始化log,标准输出已被重定向到网络
int ret = log_console_init();
if(ret < 0)
{
perror("open console err");
system("echo open log err > err.log");
exit(-1);
}
//argc和argv web server会自动传给cgi程序
//获取网页给服务器发送的数据中,请求头(环境变量)和请求正文(标准输入)信息
handle_request(argc, argv);
return 0;
}
(2)log_console.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include "log_console.h"
static int console_fd = -1;
/**
* @brief 初始化log_console
* @return 0 -1
*/
int log_console_init()
{
console_fd = open(LOG_CONSOLE, O_WRONLY);//只写的方式打开小终端
if (console_fd < 0)
{
printf("open %s error\n", LOG_CONSOLE);
return -1;
}
return 0;
}
static ssize_t log_console_write(const void *buf, size_t count)
{
int ret = write(console_fd, buf, count);//根据传过来的数据大小写到小终端
if(ret < 0)
{
perror("write err");
}
}
#define MAX_LOG_BUF_LEN (20*1024) //打印内容不能超过这个
//自定义输出函数打印信息到小终端
//(因为标准输入标准输出已重定向,所以需要log_console函数将信息输出到小终端中)
int log_console(const char *format, ...)
{
char str_tmp[MAX_LOG_BUF_LEN];//数组
va_list arg;
int len;
va_start(arg, format);
len = vsnprintf(str_tmp, MAX_LOG_BUF_LEN, format, arg);
va_end(arg);
str_tmp[len] = '\0';
return log_console_write(str_tmp, strlen(str_tmp));
}
(3)log_console.h
设置小终端路径,uxterm命令
(4)custom_handle.c
#include "req_handle.h"
#include "log_console.h"
#include <stdio.h>
#include <modbus.h>
#include <modbus-version.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#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
*/
//消息队列结构体
struct msgbuf
{
long mtype; //消息类型
char buf[64]; //消息正文
}msg;
int parse_and_process(char *input)//input:网页发送的正文
{
char val_buf[2048] = {0};//存放的是给服务器回复的正文响应的信息
//这里可以根据接收的数据请求进行处理
//例如:读传感器数据:get 共享内存
//控制硬件设备:set=1 0 消息队列
if (strcmp(input, "get") == 0) {
//1.创建key值
key_t key = ftok("/home", '2');
if (key < 0){
perror("ftok err");
return -1;
}
//2.创建或打开共享内存
int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0){
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else{
perror("shmget err");
return -1;
}
}
log_console("shmid:%d\n",shmid);
//3.映射共享内存和虚拟地址,NULL:由系统自动完成映射,0可读可写
char *p = (char *)shmat(shmid, NULL, 0);
if (p == (char *)-1){
perror("shmat err");
return -1;
}
log_console("%s",p);
strcpy(val_buf, p);
}
else{
key_t key = ftok("/home", '1');
if (key < 0) {
perror("ftok err");
return -1;
}
//1.创建或打开消息队列
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid <= 0){
if (errno == EEXIST)
msgid = msgget(key, 0666);
else{
perror("msgget err");
return -1;
}
}
msg.mtype = 1;
strcpy(msg.buf, input);
msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0);
log_console("msgbuf:%s\n",msg.buf);
strcpy(val_buf,input);
}
//数据处理完成后,需要给服务器回复,回复内容按照http协议格式
char reply_buf[HTML_SIZE] = {0};
//将头部信息%s、正文长度Content-Length放到reply_buf中
sprintf(reply_buf, "%sContent-Length: %d\r\n\r\n", HTML_HEAD, strlen(val_buf));
strcat(reply_buf, val_buf); //把数据拼接到reply_buf后面 reply_buf:存放头部信息、正文
log_console("post json_str = %s", reply_buf);
//将reply_buf中的内容(头部信息、正文)写到标准输出中(即服务器)(标准输出服务器已做重定向)
fputs(reply_buf, stdout);
return 0;
}
4.网页HTML
开发环境:vscode
1.在某路径下新建文件夹,打开VScode打开文件夹,新建文件,文件命名为index.html
2. 安装库 open in browser
库安装完成后,在编写文本位置右击->open in other browser->选择合适的浏览器即可在网页显示html标签内容
输入html,选择html:5进行框架搭建
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="js/xhr.js"></script>
<!--请求数据调用函数的路径-->
<script>
function get_info() {
var v = document.getElementsByName("username");
XHR.post('/cgi-bin/web.cgi', "get", function (x, info) {
console.log(info);
var val = info.split(" ");
v[0].value = val[0];
v[1].value = val[1] + " " + val[2] + " " + val[3];
})
}
function set(obj) {
XHR.post('/cgi-bin/web.cgi', obj, function (x, info) {
if (obj == 'set=0 1') {
console.log("LED灯打开!");
} else if (obj == 'set=0 0') {
console.log("LED灯关闭!");
} else if (obj == 'set=1 1') {
console.log("蜂鸣器打开!");
} else if (obj = 'set=1 0') {
console.log("蜂鸣器关闭!");
}
})
}
</script>
</head>
<body>
<div style="color:chocolate;">
<h1>基于Webserver的工业数据采集系统</h1>
</div>
<div style="color:black;background: cornsilk">
<h2>信息采集</h2>
get<input type="button" name="flash" onclick="get_info()">
<h4>光照强度<input type="text" name="username" value="">
加速度<input type="text" name="username" value=""></h4>
<h2>设备控制</h2>
<h4>LED灯
<input type="radio" name="LED" id="set=0 1" onclick="set(id)">on
<input type="radio" name="LED" id="set=0 0" onclick="set(id)">off</h4>
<h4>蜂鸣器
<input type="radio" name="BEEP" id="set=1 1" onclick="set(id)">on
<input type="radio" name="BEEP" id="set=1 0" onclick="set(id)">off</h4>
</div>
</body>
</html>