网页控制从机操作

2023.6.26 学习了整体的流程和框架的功能,理解了如何通过网页来控制从机
实现效果【CGI与modbus实现网页控制从机】 https://www.bilibili.com/video/BV1ns4y1r7om/?share_source=copy_web&vd_source=3ccffdb532ce0276d996e3a86b6444f4

说明

涉及技术点:CGI,modbus,线程,共享内存,消息队列
实现平台:linux
缺陷:服务器没有自己写,CGi和modbus都是框架下面添加功能,没有纯手撸。
效果展示:linux打开服务器,win打开从机。实现效果是点击获取温度,能获取到从机设备保持寄存器,网页点击蜂鸣器开关,从机实现线圈改变。
image.png

image.png

  • 网页浏览器:用户的操作界面,输入和得到相关直接消息
  • 服务器:响应浏览器的请求,回复数据给浏览器,收发消息给CGI
  • CGI:收发消息给服务器,通讯输入输出服务程序
  • 服务程序:读取控制modbus设备,输入输出数据给CGI
  • modbus设备:接受与发送数据给服务程序

上代码

1.服务程序

struct msgbuf
{
    long mtype;
    char mtext[128];
}msg;


char buf[32]="";
modbus_t* ctx; //同一个句柄


//线程函数
void *handler(void *arg)
{
    uint16_t dest[32]={0};
    key_t key;
    //1.创建一个唯一的key值
    key = ftok("/a.c", 'A');
    if (key < 0)
    {
        perror("ftok err");
        return 0;
    }
    printf("key=%#x\n", key);
    //2.创建共享内存或打开,返回一个共享内存的id
    int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0777);
    if (shmid < 0)
    {
        if (errno == EEXIST)
        {
            shmid = shmget(key, 128, 0777);
        }
        else
        {
            perror("shmget err");
            return 0;
        }
    }
    char *p = (char *)shmat(shmid, NULL, 0);
    printf("shmid=%d\n", shmid);
    //3.建立共享内存和虚拟地址得映射关系
    if (p == (char *)-1)
    {
        perror("shmat err");
        return 0;
    }
    // 将数据存入共享内存
    while(1)
    {
        sleep(1);
        modbus_read_registers(ctx, 0, 2, dest);
        
        sprintf(buf, "%d %d %d %d\n",dest[0],dest[1],dest[2],dest[3]);
	    //printf("%d %d %d %d",dest[0],dest[1],dest[2],dest[3]);
        strcpy(p,buf); //通过共享内存p来通信CGI
    }
}



int main(int argc, char const *argv[])
{
    ctx = modbus_new_tcp("192.168.50.151",502);
    modbus_set_slave(ctx,1);
    modbus_connect(ctx);

    pthread_t tid;
    if(pthread_create(&tid,NULL,handler,NULL) != 0)
    {
        printf("create thread err\n");
        modbus_free(ctx);
        return -1;
    }

    key_t key = ftok("/a.c", 'B');
    if (key < 0)
    {
        perror("ftok err.");
        return -1;
    }

    int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0777);
    if (msgid < 0)
    {
        if (errno == EEXIST)
        {
            msgid = msgget(key, 0777);
        }
        else
        {
            perror("msgget err.");
            return -1;
        }
    }
        
    while (1)
    {
        msgrcv(msgid,&msg,sizeof(msg),'B',0);//等待CGI传来消息,在执行操作
        if(strncmp(msg.mtext,"0 1",3) == 0)
        {
            modbus_write_bit(ctx, 0, 1);
        }
        else if(strncmp(msg.mtext,"0 0",3) == 0)
        {
            modbus_write_bit(ctx, 0, 0);
        }
        else if(strncmp(msg.mtext,"1 1",3) == 0)
        {
            modbus_write_bit(ctx, 1, 1);
        }
        else if(strncmp(msg.mtext,"1 0",3) == 0)
        {
            modbus_write_bit(ctx, 1, 0);
        }
    }
    modbus_close(ctx);
    modbus_free(ctx);

    pthread_join(tid, NULL);
    return 0;
}

2.CGI程序核心代码

struct msgbuf
{
    long mtype;
    char mtext[128];
};

int parse_and_process(char *input) //这个input是服务器输入的
{
    char val_buf[2048] = {0};
    strcpy(val_buf, input);
    // 这里可以根据接收的数据请求进行处理
    // 判断input的值
    if (strncmp(val_buf, "get", 3) == 0) //判断是不是get命令
    {
        // 创建唯一的key值
        key_t key = ftok("/a.c", 'A');
        if (key < 0)
        {
            perror("ftok err");
            return -1;
        }

        // 创建共享内存
        int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0777);
        if (shmid < 0)
        {
            if (errno == EEXIST)
            {
                shmid = shmget(key, 128, 0777);
            }
            else
            {
                perror("shmget err");
                return -1;
            }
        }
        // 建立共享内存和虚拟地址的映射关系
        char *p = (char *)shmat(shmid, NULL, 0);
        if (p == (char *)-1)
        {
            perror("shmat err");
            return -1;
        }

        // 将数据写入stdout
        strcpy(val_buf, p);  //将服务程序拿到的数值给val_buf,这样传给服务器
        log_console("%s\n", val_buf); // 打印语句判断是否写入成功
    }
    else
    {
        // 消息队列
        key_t key = ftok("/a.c", 'B');
        if (key < 0)
        {
            perror("ftok err");
            return -1;
        }
        // 创建消息队列
        int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0777);
        if (msgid < 0)
        {
            if (errno == EEXIST)
            {
                msgid = msgget(key, 0777);
            }
            else
            {
                perror("msgget err");
                return -1;
            }
        }
        // 添加消息
        if (strncmp(val_buf, "1 1", 3) == 0)
        {
            struct msgbuf msg1 = {'B', "1 1"};
            msgsnd(msgid, &msg1, sizeof(msg1), 0);
            strcpy(val_buf, "打开灯");

        }
        else if (strncmp(val_buf, "1 0", 3) == 0)
        {
            struct msgbuf msg2 = {'B', "1 0"};
            msgsnd(msgid, &msg2, sizeof(msg2), 0);
            strcpy(val_buf, "关闭灯");
        }
        else if (strncmp(val_buf, "0 1", 3) == 0)
        {
            struct msgbuf msg3 = {'B', "0 1"};
            msgsnd(msgid, &msg3, sizeof(msg3), 0);
            strcpy(val_buf, "打开蜂鸣器");
        }
        else if (strncmp(val_buf, "0 0", 3) == 0)
        {
            struct msgbuf msg4 = {'B', "0 0"};
            msgsnd(msgid, &msg4, sizeof(msg4), 0);
            strcpy(val_buf, "关闭蜂鸣器");
        }
    }

    

    // 数据处理完成后,需要给服务器回复,回复内容按照http协议格式
    char reply_buf[HTML_SIZE] = {0};
    sprintf(reply_buf, "%sContent-Length: %ld\r\n\r\n", HTML_HEAD, strlen(val_buf));
    strcat(reply_buf, val_buf);
    log_console("post json_str = %s", reply_buf);

    // 向标准输出写内容(标准输出服务器已做重定向)
    fputs(reply_buf, stdout);  //输出到服务器

    return 0;
}

3.网页代码核心代码

<!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>数据采集系统</title>
</head>

<!--指定文件路径-->
<script src="js/xhr.js"></script>
<script>
	function fun(obj){
		//console.log(obj);
		//调用post接口  obj浏览器发送得数据  info服务器回复得数据
		XHR.post('/cgi-bin/web.cgi',obj,function(x,info){
			console.log("info:",info);
		});
		
	}
	function fun1(obj){
		//console.log(obj);
		//调用post接口  obj浏览器发送得数据  info服务器回复得数据
		XHR.post
		('/cgi-bin/web.cgi',obj,function(x,info)
			{
				console.log("info:",info);
			var v1 = document.getElementById("tem");
			console.log(v1);
			var arr = info.split(" "); 
			var buf = arr[0].split(":");
			v1.value = buf[0];
			}
		);
		
	}
	function fun2(obj){
		//console.log(obj);
		//调用post接口  obj浏览器发送得数据  info服务器回复得数据
		XHR.post('/cgi-bin/web.cgi',obj,function(x,info){
			console.log("info:",info);
		var v2 = document.getElementById("hum");
		console.log(v2);
		var arr = info.split(" ");
		var buf = arr[1].split(":");
		v2.value = buf[1];
		});
		
	}
</script>






<body>
	<div  style="color: bisque;background-color: burlywood;">
	<!-- input标签类型为文本输入框,name:控件名称、value:默认值、id:标识 -->
	温度
	<input type="text" name="template" value="" id="tem">
	<input type="button" name="tem"   value="获取温度"  checked="checked" id="get" onclick="fun1(id)">
	<br>
	湿度
	<input type="text" name="humhum" value="" id="hum">
	<input type="button" name="hum" value="获取湿度" checked="checked" id="get" onclick="fun2(id)">


	<!-- input标签类型为单选按钮,name:控件名称、checked表示默认选中 -->
	<!-- label标签将文本和按钮进行绑定,当点击文本时自动聚焦到按钮上
	for属性值要和input标签中的id值一致 -->
	<br>
	<label for="LED 1">LED灯  开:</label>
	<input type="radio" name="LED" value="1" checked="checked" id="LED 1" onclick="fun(id)">
	<label for="LED 0">   关:</label>
	<input type="radio" name="LED" value="0" checked="checked" id="LED 0" onclick="fun(id)">
	<br>
	<label for="BEEP 1"> 蜂鸣器  开:   </label>
	<input type="radio" name="BEEP" value="1" id="BEEP 1" onclick="fun(id)">
	<label for="BEEP 0">   关:</label>
	<input type="radio" name="BEEP" value="0" id="BEEP 0" onclick="fun(id)">
</div>
</body>


</html>



总结

image.png
用户看到的内容是一个网页,得到效果是点击获取温度,就会得到从机检测的温度,选择LED灯,从机就会打开或者关闭。
那么当用户按下获取温度按键后,执行了这些操作:

<input type="button" name="tem"   value="获取温度"  checked="checked" id="get" 
    onclick="fun1(id)">

在html文件中,这条代码被执行,然后进入到fun1(id)中。

function fun1(obj){
		//console.log(obj);
		//调用post接口  obj浏览器发送得数据  info服务器回复得数据
		XHR.post
		('/cgi-bin/web.cgi',obj,function(x,info)
			{
				console.log("info:",info);
			var v1 = document.getElementById("tem");
			console.log(v1);
			var arr = info.split(" "); 
			var buf = arr[0].split(":");
			v1.value = buf[0];
			}
		);

在这里面,调用了XHR函数,可以看到第一个为URL,传递参数为dataobjid(也就是get),然后回调函数,其中info为服务器回复数据

XHR = function()
{
    this.post = function(url, data, callback)
    {}	
}

于是当给了post接口,给了参数get,就是服务器写入了get参数

//其中stdin已经重定向了,为服务器写入
ret = fread(request_content + len, 1, content_length - len, stdin);
ret = parse_and_process(request_content);//写入的值作为参数传入到这个函数内

上面就可以得到request_content的现在已经写入了get这个参数

int parse_and_process(char *input)
{
    char val_buf[2048] = {0};
    strcpy(val_buf, input);
    // 这里可以根据接收的数据请求进行处理
    // 判断input的值
    if (strncmp(val_buf, "get", 3) == 0)
    {
        
    }
}

然后传入的值get就能作为判断执行操作

// 向标准输出写内容(标准输出服务器已做重定向)
    fputs(reply_buf, stdout);

操作完毕就向服务器输出拿到的结果。

XHR.post
		('/cgi-bin/web.cgi',obj,function(x,info)
			{
				console.log("info:",info);
			var v1 = document.getElementById("tem");
			console.log(v1);
			var arr = info.split(" "); 
			var buf = arr[0].split(":");
			v1.value = buf[0];
			}
		);

info为服务器回复的内容,现在就已经收到了具体的内容,然后用变量进行接收,接受前也进行了一下数据的处理

温度
	<input type="text" name="template" value="" id="tem">
	<input type="button" name="tem"   value="获取温度"  checked="checked" id="get" onclick="fun1(id)">
	<br>

对应上面的 document.getElementById("tem");找到了value就赋值了,id为tem,所以这个时候上代码中的value=“” 变为了value=“buf[0]” 然后我们才在网页上看到了显示的值。
这其中经历了,网页给服务器传值,服务器给CGI传值,CGI拿用户程序的值,用户查询一直在拿modbus设备的值。

posted @ 2023-06-26 20:48  moveddowm  阅读(10)  评论(0编辑  收藏  举报