本文需要Web服务器开发基础,可参考下述博文:
一、数据显示界面与功能设计
1、功能设计说明
程序代码结构如下,调用关系见彩色部分标示。
数据显示界面功能通过以下2个程序实现:
data_show.html 程序文件
数据显示界面用于展示各种环境数据(温度、湿度、光强和气体浓度)的可视化界面。它使用了 Bootstrap 框架来进行页面布局,ECharts 库来绘制图表,并通过 AJAX 请求从服务器端的 cgi-bin/node_data.cgi
获取实时数据。
node_data.c 程序文件
主要实现了与共享内存交互,并将共享内存中的部分数据以 JSON 格式作为 HTTP 响应输出的功能。程序会创建或连接到一个共享内存段,读取其中的数据,修改部分数据,然后将温度、湿度、光照强度和气体浓度等信息以 JSON 格式输出,最后分离共享内存。
2、界面功能运行
程序编译完成后,上传到飞腾派开发板的https服务器的文件目录/var/www/,PC机浏览器访问开发板IP地址,显示如下界面。
注意:开发板如果没有通过网络连接到节点板,界面是没有相关数据显示。
完成后续终端节点开发,开发板服务器连接到节点板后,界面会显示实时采集到的环境数据。
二、data_show.html 程序分析与设计
1、程序流程分析与设计
2、程序功能分析与设计
HTML 头部部分
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>数据显示</title>
<link href="css/bootstrap.css" rel="stylesheet">
<script src="js/jquery-1.10.2.js" type="text/javascript"></script>
<script src="js/bootstrap.min.js" type="text/javascript"></script>
<script src="js/echarts.min.js" type="text/javascript"></script>
</head>
设置文档类型和字符编码。
引入 Bootstrap 样式表和相关 JavaScript 库,以及 ECharts 库。
HTML 主体部分
使用 Bootstrap 的网格系统创建页面布局。
包含温度、湿度、光强的仪表盘和气体浓度的折线图。
每个面板都有一个连接状态图标,初始状态为断开连接。
JavaScript 部分
<script type="text/javascript">
// 初始化变量
var gas_data_sender = 0;
var temp_val = 0;
var humi_val = 0;
var light_val = 0;
var gas_val = 0;
var alarm_line = 0;
var returnValue = 0;
var str_temp, str_humidity, str_light, str_gas;
// 根据页面宽度调整气体浓度折线图的高度
...
// 每秒发送一次 AJAX 请求获取数据
setInterval(function () {
$.ajax({
cache: false,
async: true,
dataType: 'json',
type: 'get',
url: "cgi-bin/node_data.cgi",
success: function (data) {
// 更新数据
temp_val = data.temp;
humi_val = data.humi;
light_val = data.light;
gas_val = data.gas;
// 打印数据到控制台
console.log("温度: " + temp_val);
console.log("湿度: " + humi_val);
console.log("光照: " + light_val);
console.log("气体: " + gas_val);
// 根据数据更新连接状态图标
...
},
error: function () {
// 请求失败时,将所有连接状态图标设置为断开连接
...
}
})
}, 1000)
// 初始化温度、湿度、光强仪表盘和气体浓度折线图
tempGauge("temp_gauge", "optionTemp", "temp");
humidityGauge("humidity_gauge", "optionHumidity", "humidity");
lightGauge("light_gauge", "optionLight", "light");
gasLine();
// 更新连接状态图标的函数
function is_conn(iid, bool) {
...
}
// 初始化温度仪表盘的函数
function tempGauge(tid, toption, str) {
str_temp = echarts.init(document.getElementById(tid));
toption = {
// 仪表盘配置项
series: [{
...
}]
};
// 每秒更新一次仪表盘数据
setInterval(function () {
toption.series[0].data[0].value = temp_val;
str_temp.setOption(toption, true);
}, 1000);
// 初始化仪表盘
if (toption && typeof toption === 'object') {
str_temp.setOption(toption);
}
}
// 初始化湿度仪表盘的函数,与温度仪表盘类似
function humidityGauge(tid, toption, str) {
str_humidity = echarts.init(document.getElementById(tid));
toption = {
series: [{
...
};
setInterval(function () {
toption.series[0].data[0].value = humi_val;
str_humidity.setOption(toption, true);
}, 1000);
if (toption && typeof toption === 'object') {
str_humidity.setOption(toption);
}
}
// 初始化光强仪表盘的函数,与温度仪表盘类似
function lightGauge(tid, toption, str) {
str_light = echarts.init(document.getElementById(tid));
toption = {
series: [{
...
};
setInterval(function () {
toption.series[0].data[0].value = light_val;
str_light.setOption(toption, true);
}, 1000);
if (toption && typeof toption === 'object') {
str_humidity.setOption(toption);
}
}
// 初始化气体浓度折线图的函数
function gasLine() {
str_gas = echarts.init(document.getElementById('gas_line'));
var data = [];
var now = new Date();
function randosmata() {
now = new Date(+now + 1000);
console.log("gas_val-----" + gas_val);
return {
name: now.toString(),
value: [now.getTime(), gas_val],
};
}
// 初始化折线图数据
for (var i = 0; i < 30; i++) {
data.push(randosmata());
}
option = {
// 折线图配置项
...
};
// 每秒更新一次折线图数据
setInterval(function () {
for (var i = 0; i < 1; i++) {
data.shift();
data.push(randosmata());
}
str_gas.setOption({
title: {
text: "当前气体浓度:" + gas_val,
...
});
}, 1000);
option && str_gas.setOption(option);
}
// 窗口大小改变时,调整图表大小
window.onresize = function () {
str_temp.resize(),
str_humidity.resize(),
str_light.resize(),
str_gas.resize();
}
</script>
- 初始化变量,用于存储数据和 ECharts 实例。
- 根据页面宽度调整气体浓度折线图的高度。
- 每秒发送一次 AJAX 请求,从服务器获取实时数据,并更新连接状态图标。
- 初始化温度、湿度、光强仪表盘和气体浓度折线图。
- 每秒更新一次仪表盘和折线图的数据。
- 窗口大小改变时,调整所有图表的大小。
三、node_data.c 程序分析与设计
程序主要实现了与共享内存交互,并将共享内存中的部分数据以 JSON 格式作为 HTTP 响应输出的功能。程序会创建或连接到一个共享内存段,读取其中的数据,修改部分数据,然后将温度、湿度、光照强度和气体浓度等信息以 JSON 格式输出,最后分离共享内存。
1、程序流程分析与设计
这个流程图展示了程序的主要执行流程,包括共享内存的设置、数据的读取和修改、HTTP 响应的输出以及共享内存的分离等步骤。如果在任何步骤中出现错误,程序会输出相应的错误信息并终止。
2、程序功能分析与设计
头文件包含部分
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <time.h>
这些头文件包含了程序所需的基本函数和数据类型定义,例如标准输入输出、内存管理、系统调用、信号处理等。
宏定义部分
#define CMD_DATA 0x01
#define CMD_CTL 0x02
#define CMD_SET 0x03
#define E53_IA1 0x01
#define E53_SF1 0x02
定义了一些常量,用于表示命令类型和节点类型。
共享内存数据结构定义
struct st_sys {
unsigned char temp_val; // 温度值
unsigned char humi_val; // 湿度值
unsigned char light_sw; // 灯光开关状态
unsigned char buzz_sw; // 蜂鸣器开关状态
unsigned int ill_val; // 光照强度值
unsigned int gas_val; // 气体浓度值
unsigned int ill_max; // 光照强度最大值
unsigned int gas_max; // 气体浓度最大值
unsigned char msg_type; //请求类型
unsigned char node_id; //节点ID
unsigned char data_flag;//数据更新标记
unsigned char control_flag;//控制更新标记
unsigned char set_flag;//设置更新标记
};
定义了一个结构体 st_sys
,用于表示共享内存中的数据结构,包含了温度、湿度、光照、气体浓度等信息,以及请求类型、节点 ID 和更新标记。
共享内存地址指针和全局变量
struct st_sys* shm_dev;
struct st_sys g_dev[1];
shm_dev
是一个指向共享内存的指针,g_dev
是一个用于存储从共享内存中读取的数据的数组。
设置共享内存函数
set_web_shm
void* set_web_shm(void) {
int shmid;
void* shmaddr = (void*)0;
// 创建或获取共享内存段
if ((shmid = shmget((key_t)3456, sizeof(struct st_sys), 0666 | IPC_CREAT)) < 0) {
perror("shmget error");
return NULL;
}
// 将共享内存段附加到当前进程的地址空间
if ((shmaddr = shmat(shmid, (void*)0, 0)) == (char*)-1) {
perror("shmat error");
return NULL;
}
return shmaddr;
}
该函数用于创建或获取一个共享内存段,并将其附加到当前进程的地址空间。如果创建或附加失败,会输出错误信息并返回 NULL
。
信号处理函数
signal_handler
void signal_handler(int signum) {
if (shmdt(shm_dev) == -1) {
perror("shmdt error");
}
exit(0);
}
当程序接收到信号时,该函数会尝试分离共享内存,并退出程序。如果分离失败,会输出错误信息。
主函数
main
int main() {
// 构建 JSON 格式的响应数据
char response[100];
// 设置共享内存
if ((shm_dev = (struct st_sys*)set_web_shm()) == NULL) {
fprintf(stderr, "Failed to set up shared memory.\n");
return 1;
}
// 从共享内存中读取数据
memcpy(g_dev, shm_dev, sizeof(struct st_sys));
shm_dev->msg_type = CMD_DATA;//命令类型
shm_dev->node_id = E53_IA1;//节点类型
shm_dev->data_flag++;//更新次数加1
// 设置 HTTP 响应头
printf("Content-type: application/json\n\n");
snprintf(response, sizeof(response), "{\"temp\": %d, \"humi\": %d, \"light\": %d, \"gas\": %d}", g_dev->temp_val, g_dev->humi_val,g_dev->ill_val, g_dev->gas_val);
// 输出响应数据
printf("%s", response);
//分离共享内存
if (shmdt(shm_dev) == -1) {
perror("shmdt error");
}
return 0;
}
主函数的主要流程如下:
定义一个字符数组
response
用于存储 JSON 格式的响应数据。调用
set_web_shm
函数设置共享内存,如果失败则输出错误信息并返回。从共享内存中读取数据到
g_dev
数组。修改共享内存中的请求类型、节点 ID 和更新标记。
设置 HTTP 响应头。
使用
snprintf
函数将温度、湿度、光照强度和气体浓度信息格式化为 JSON 字符串。输出 JSON 响应数据。
分离共享内存,如果失败则输出错误信息。
返回 0 表示程序正常结束。