software copyright
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
/**
|
||||
* 自然写教室智能网关管理软件 V1.0
|
||||
*
|
||||
* gateway_config.c - 配置管理模块
|
||||
*
|
||||
* 功能说明:
|
||||
* - JSON配置文件读写
|
||||
* - 网关WiFi/网络配置
|
||||
* - MQTT服务器连接配置
|
||||
* - BLE扫描与连接参数
|
||||
* - 心跳间隔/缓冲区大小等运行参数
|
||||
* - 配置变更通知回调
|
||||
* - 运行时动态更新(通过MQTT云端下发)
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* ======================== 常量定义 ======================== */
|
||||
|
||||
/* 配置文件路径 */
|
||||
#define CONFIG_FILE_PATH "/etc/writech/gateway.json"
|
||||
#define CONFIG_BACKUP_PATH "/etc/writech/gateway.json.bak"
|
||||
|
||||
/* 配置项最大长度 */
|
||||
#define CONFIG_STRING_MAX 256
|
||||
#define CONFIG_MAX_ITEMS 64
|
||||
|
||||
/* 默认配置值 */
|
||||
#define DEFAULT_MQTT_PORT 8883 /* MQTT TLS端口 */
|
||||
#define DEFAULT_HEARTBEAT_SEC 15 /* 心跳间隔(秒) */
|
||||
#define DEFAULT_BLE_SCAN_SEC 10 /* BLE扫描窗口(秒) */
|
||||
#define DEFAULT_MAX_PENS 40 /* 最大连接笔数 */
|
||||
#define DEFAULT_BUFFER_SIZE_KB 2048 /* 环形缓冲区大小(KB) */
|
||||
#define DEFAULT_HTTP_PORT 8080 /* 本地管理Web端口 */
|
||||
#define DEFAULT_LOG_LEVEL 2 /* 日志级别(0=ERROR,1=WARN,2=INFO) */
|
||||
|
||||
/* ======================== 数据结构 ======================== */
|
||||
|
||||
/* 网络配置 */
|
||||
typedef struct {
|
||||
char wifi_ssid[64]; /* WiFi SSID */
|
||||
char wifi_password[64]; /* WiFi密码 */
|
||||
bool wifi_dhcp; /* 是否使用DHCP */
|
||||
char static_ip[16]; /* 静态IP地址 */
|
||||
char netmask[16]; /* 子网掩码 */
|
||||
char gateway_ip[16]; /* 网关IP */
|
||||
char dns_server[16]; /* DNS服务器 */
|
||||
} network_config_t;
|
||||
|
||||
/* MQTT配置 */
|
||||
typedef struct {
|
||||
char broker_host[CONFIG_STRING_MAX]; /* MQTT Broker地址 */
|
||||
uint16_t broker_port; /* MQTT Broker端口 */
|
||||
char username[64]; /* MQTT用户名 */
|
||||
char password[64]; /* MQTT密码 */
|
||||
char client_id[64]; /* MQTT客户端ID */
|
||||
bool use_tls; /* 是否启用TLS */
|
||||
char ca_cert_path[CONFIG_STRING_MAX]; /* CA证书路径 */
|
||||
char client_cert_path[CONFIG_STRING_MAX]; /* 客户端证书路径 */
|
||||
char client_key_path[CONFIG_STRING_MAX]; /* 客户端私钥路径 */
|
||||
uint16_t keepalive_sec; /* Keep-alive间隔 */
|
||||
uint8_t qos; /* 默认QoS等级 */
|
||||
} mqtt_config_t;
|
||||
|
||||
/* BLE配置 */
|
||||
typedef struct {
|
||||
uint16_t scan_window_ms; /* 扫描窗口(毫秒) */
|
||||
uint16_t scan_interval_ms; /* 扫描间隔(毫秒) */
|
||||
uint8_t max_connections; /* 最大连接数 */
|
||||
uint16_t conn_interval_min; /* 最小连接间隔 */
|
||||
uint16_t conn_interval_max; /* 最大连接间隔 */
|
||||
uint16_t supervision_timeout; /* 监控超时 */
|
||||
bool auto_reconnect; /* 自动重连 */
|
||||
uint8_t reconnect_max_retries; /* 最大重连次数 */
|
||||
} ble_config_t;
|
||||
|
||||
/* 运行参数配置 */
|
||||
typedef struct {
|
||||
uint16_t heartbeat_interval_sec; /* 心跳上报间隔 */
|
||||
uint32_t ring_buffer_size_kb; /* 环形缓冲区大小(KB) */
|
||||
uint16_t http_port; /* 本地管理HTTP端口 */
|
||||
uint8_t log_level; /* 日志级别 */
|
||||
bool compression_enabled; /* 数据压缩开关 */
|
||||
bool binary_protocol; /* 二进制协议开关 */
|
||||
char log_path[CONFIG_STRING_MAX]; /* 日志文件路径 */
|
||||
uint32_t log_max_size_mb; /* 单个日志文件最大大小 */
|
||||
uint8_t log_max_files; /* 日志文件最大数量 */
|
||||
} runtime_config_t;
|
||||
|
||||
/* 完整网关配置 */
|
||||
typedef struct {
|
||||
char gateway_id[32]; /* 网关唯一标识 */
|
||||
char device_serial[32]; /* 设备序列号 */
|
||||
uint16_t hw_version; /* 硬件版本 */
|
||||
network_config_t network; /* 网络配置 */
|
||||
mqtt_config_t mqtt; /* MQTT配置 */
|
||||
ble_config_t ble; /* BLE配置 */
|
||||
runtime_config_t runtime; /* 运行参数 */
|
||||
time_t last_modified; /* 最后修改时间 */
|
||||
uint32_t config_version; /* 配置版本号 */
|
||||
} gateway_config_t;
|
||||
|
||||
/* 配置变更回调函数类型 */
|
||||
typedef void (*config_change_callback_t)(const char *section,
|
||||
const gateway_config_t *config);
|
||||
|
||||
/* 全局配置实例 */
|
||||
static gateway_config_t g_config;
|
||||
static config_change_callback_t g_change_callback = NULL;
|
||||
static bool g_config_loaded = false;
|
||||
|
||||
/* ======================== 默认配置 ======================== */
|
||||
|
||||
/**
|
||||
* 设置默认配置值
|
||||
* 当配置文件不存在或损坏时使用
|
||||
*/
|
||||
static void set_default_config(gateway_config_t *cfg)
|
||||
{
|
||||
memset(cfg, 0, sizeof(gateway_config_t));
|
||||
|
||||
/* 基本信息 */
|
||||
strncpy(cfg->gateway_id, "GW-DEFAULT", sizeof(cfg->gateway_id));
|
||||
cfg->hw_version = 0x0100;
|
||||
|
||||
/* 网络默认配置 */
|
||||
cfg->network.wifi_dhcp = true;
|
||||
strncpy(cfg->network.dns_server, "8.8.8.8", sizeof(cfg->network.dns_server));
|
||||
|
||||
/* MQTT默认配置 */
|
||||
strncpy(cfg->mqtt.broker_host, "mqtt.writech.cn",
|
||||
sizeof(cfg->mqtt.broker_host));
|
||||
cfg->mqtt.broker_port = DEFAULT_MQTT_PORT;
|
||||
cfg->mqtt.use_tls = true;
|
||||
cfg->mqtt.keepalive_sec = 60;
|
||||
cfg->mqtt.qos = 1;
|
||||
strncpy(cfg->mqtt.ca_cert_path, "/etc/writech/certs/ca.pem",
|
||||
sizeof(cfg->mqtt.ca_cert_path));
|
||||
strncpy(cfg->mqtt.client_cert_path, "/etc/writech/certs/client.pem",
|
||||
sizeof(cfg->mqtt.client_cert_path));
|
||||
strncpy(cfg->mqtt.client_key_path, "/etc/writech/certs/client.key",
|
||||
sizeof(cfg->mqtt.client_key_path));
|
||||
|
||||
/* BLE默认配置 */
|
||||
cfg->ble.scan_window_ms = 30;
|
||||
cfg->ble.scan_interval_ms = 60;
|
||||
cfg->ble.max_connections = DEFAULT_MAX_PENS;
|
||||
cfg->ble.conn_interval_min = 7; /* 7.5ms (单位1.25ms) */
|
||||
cfg->ble.conn_interval_max = 15; /* 18.75ms */
|
||||
cfg->ble.supervision_timeout = 400; /* 4000ms (单位10ms) */
|
||||
cfg->ble.auto_reconnect = true;
|
||||
cfg->ble.reconnect_max_retries = 5;
|
||||
|
||||
/* 运行参数默认配置 */
|
||||
cfg->runtime.heartbeat_interval_sec = DEFAULT_HEARTBEAT_SEC;
|
||||
cfg->runtime.ring_buffer_size_kb = DEFAULT_BUFFER_SIZE_KB;
|
||||
cfg->runtime.http_port = DEFAULT_HTTP_PORT;
|
||||
cfg->runtime.log_level = DEFAULT_LOG_LEVEL;
|
||||
cfg->runtime.compression_enabled = true;
|
||||
cfg->runtime.binary_protocol = false;
|
||||
strncpy(cfg->runtime.log_path, "/var/log/writech/gateway.log",
|
||||
sizeof(cfg->runtime.log_path));
|
||||
cfg->runtime.log_max_size_mb = 10;
|
||||
cfg->runtime.log_max_files = 5;
|
||||
|
||||
cfg->config_version = 1;
|
||||
cfg->last_modified = time(NULL);
|
||||
}
|
||||
|
||||
/* ======================== 配置文件读写 ======================== */
|
||||
|
||||
/**
|
||||
* 从JSON配置文件加载配置
|
||||
* 使用简易JSON解析(无第三方库依赖)
|
||||
*/
|
||||
static int load_config_from_file(const char *path, gateway_config_t *cfg)
|
||||
{
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (fp == NULL) {
|
||||
printf("[配置] 无法打开配置文件: %s\n", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 获取文件大小 */
|
||||
fseek(fp, 0, SEEK_END);
|
||||
long file_size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
if (file_size <= 0 || file_size > 65536) {
|
||||
printf("[配置] 配置文件大小异常: %ld字节\n", file_size);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 读取JSON内容 */
|
||||
char *json_str = (char *)malloc(file_size + 1);
|
||||
if (json_str == NULL) {
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fread(json_str, 1, file_size, fp);
|
||||
json_str[file_size] = '\0';
|
||||
fclose(fp);
|
||||
|
||||
/* 简易JSON解析: 逐字段提取 */
|
||||
/* 解析gateway_id */
|
||||
char *pos = strstr(json_str, "\"gateway_id\"");
|
||||
if (pos) {
|
||||
pos = strchr(pos, ':');
|
||||
if (pos) {
|
||||
pos = strchr(pos, '"');
|
||||
if (pos) {
|
||||
pos++;
|
||||
char *end = strchr(pos, '"');
|
||||
if (end) {
|
||||
int len = end - pos;
|
||||
if (len < (int)sizeof(cfg->gateway_id)) {
|
||||
strncpy(cfg->gateway_id, pos, len);
|
||||
cfg->gateway_id[len] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 解析MQTT broker_host */
|
||||
pos = strstr(json_str, "\"broker_host\"");
|
||||
if (pos) {
|
||||
pos = strchr(pos + 13, '"');
|
||||
if (pos) {
|
||||
pos++;
|
||||
char *end = strchr(pos, '"');
|
||||
if (end) {
|
||||
int len = end - pos;
|
||||
if (len < (int)sizeof(cfg->mqtt.broker_host)) {
|
||||
strncpy(cfg->mqtt.broker_host, pos, len);
|
||||
cfg->mqtt.broker_host[len] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 解析MQTT broker_port */
|
||||
pos = strstr(json_str, "\"broker_port\"");
|
||||
if (pos) {
|
||||
pos = strchr(pos, ':');
|
||||
if (pos) {
|
||||
cfg->mqtt.broker_port = (uint16_t)atoi(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 解析heartbeat_interval */
|
||||
pos = strstr(json_str, "\"heartbeat_interval\"");
|
||||
if (pos) {
|
||||
pos = strchr(pos, ':');
|
||||
if (pos) {
|
||||
cfg->runtime.heartbeat_interval_sec = (uint16_t)atoi(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 解析max_connections */
|
||||
pos = strstr(json_str, "\"max_connections\"");
|
||||
if (pos) {
|
||||
pos = strchr(pos, ':');
|
||||
if (pos) {
|
||||
cfg->ble.max_connections = (uint8_t)atoi(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
free(json_str);
|
||||
|
||||
printf("[配置] 配置加载成功: gateway_id=%s, mqtt=%s:%d\n",
|
||||
cfg->gateway_id, cfg->mqtt.broker_host, cfg->mqtt.broker_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将配置保存到JSON文件
|
||||
* 先写入临时文件再重命名,防止断电导致配置损坏
|
||||
*/
|
||||
static int save_config_to_file(const char *path, const gateway_config_t *cfg)
|
||||
{
|
||||
char temp_path[CONFIG_STRING_MAX + 8];
|
||||
snprintf(temp_path, sizeof(temp_path), "%s.tmp", path);
|
||||
|
||||
FILE *fp = fopen(temp_path, "w");
|
||||
if (fp == NULL) {
|
||||
printf("[配置] 无法创建临时配置文件: %s\n", temp_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 生成JSON配置内容 */
|
||||
fprintf(fp, "{\n");
|
||||
fprintf(fp, " \"gateway_id\": \"%s\",\n", cfg->gateway_id);
|
||||
fprintf(fp, " \"device_serial\": \"%s\",\n", cfg->device_serial);
|
||||
fprintf(fp, " \"hw_version\": %u,\n", cfg->hw_version);
|
||||
fprintf(fp, " \"config_version\": %u,\n", cfg->config_version);
|
||||
|
||||
/* 网络配置 */
|
||||
fprintf(fp, " \"network\": {\n");
|
||||
fprintf(fp, " \"wifi_ssid\": \"%s\",\n", cfg->network.wifi_ssid);
|
||||
fprintf(fp, " \"wifi_dhcp\": %s,\n", cfg->network.wifi_dhcp ? "true" : "false");
|
||||
fprintf(fp, " \"static_ip\": \"%s\",\n", cfg->network.static_ip);
|
||||
fprintf(fp, " \"dns_server\": \"%s\"\n", cfg->network.dns_server);
|
||||
fprintf(fp, " },\n");
|
||||
|
||||
/* MQTT配置 */
|
||||
fprintf(fp, " \"mqtt\": {\n");
|
||||
fprintf(fp, " \"broker_host\": \"%s\",\n", cfg->mqtt.broker_host);
|
||||
fprintf(fp, " \"broker_port\": %u,\n", cfg->mqtt.broker_port);
|
||||
fprintf(fp, " \"use_tls\": %s,\n", cfg->mqtt.use_tls ? "true" : "false");
|
||||
fprintf(fp, " \"keepalive\": %u,\n", cfg->mqtt.keepalive_sec);
|
||||
fprintf(fp, " \"qos\": %u\n", cfg->mqtt.qos);
|
||||
fprintf(fp, " },\n");
|
||||
|
||||
/* BLE配置 */
|
||||
fprintf(fp, " \"ble\": {\n");
|
||||
fprintf(fp, " \"max_connections\": %u,\n", cfg->ble.max_connections);
|
||||
fprintf(fp, " \"scan_window_ms\": %u,\n", cfg->ble.scan_window_ms);
|
||||
fprintf(fp, " \"scan_interval_ms\": %u,\n", cfg->ble.scan_interval_ms);
|
||||
fprintf(fp, " \"auto_reconnect\": %s\n", cfg->ble.auto_reconnect ? "true" : "false");
|
||||
fprintf(fp, " },\n");
|
||||
|
||||
/* 运行参数 */
|
||||
fprintf(fp, " \"runtime\": {\n");
|
||||
fprintf(fp, " \"heartbeat_interval\": %u,\n", cfg->runtime.heartbeat_interval_sec);
|
||||
fprintf(fp, " \"buffer_size_kb\": %u,\n", cfg->runtime.ring_buffer_size_kb);
|
||||
fprintf(fp, " \"http_port\": %u,\n", cfg->runtime.http_port);
|
||||
fprintf(fp, " \"log_level\": %u,\n", cfg->runtime.log_level);
|
||||
fprintf(fp, " \"compression\": %s\n", cfg->runtime.compression_enabled ? "true" : "false");
|
||||
fprintf(fp, " }\n");
|
||||
|
||||
fprintf(fp, "}\n");
|
||||
fclose(fp);
|
||||
|
||||
/* 备份旧配置 */
|
||||
rename(path, CONFIG_BACKUP_PATH);
|
||||
|
||||
/* 原子重命名临时文件 */
|
||||
if (rename(temp_path, path) != 0) {
|
||||
printf("[配置] 重命名失败,恢复备份\n");
|
||||
rename(CONFIG_BACKUP_PATH, path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("[配置] 配置已保存: %s (版本=%u)\n", path, cfg->config_version);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ======================== 公共接口 ======================== */
|
||||
|
||||
/**
|
||||
* 初始化配置模块
|
||||
* 加载配置文件,若不存在则使用默认配置
|
||||
*/
|
||||
int gateway_config_init(void)
|
||||
{
|
||||
/* 先设置默认值 */
|
||||
set_default_config(&g_config);
|
||||
|
||||
/* 尝试从文件加载 */
|
||||
if (load_config_from_file(CONFIG_FILE_PATH, &g_config) == 0) {
|
||||
g_config_loaded = true;
|
||||
printf("[配置] 从文件加载配置成功\n");
|
||||
} else {
|
||||
/* 尝试从备份加载 */
|
||||
if (load_config_from_file(CONFIG_BACKUP_PATH, &g_config) == 0) {
|
||||
g_config_loaded = true;
|
||||
printf("[配置] 从备份文件加载配置成功\n");
|
||||
} else {
|
||||
/* 使用默认配置并保存 */
|
||||
printf("[配置] 使用默认配置\n");
|
||||
save_config_to_file(CONFIG_FILE_PATH, &g_config);
|
||||
g_config_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取只读配置引用
|
||||
*/
|
||||
const gateway_config_t *gateway_config_get(void)
|
||||
{
|
||||
return &g_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过MQTT云端指令更新配置
|
||||
* 解析JSON负载并更新对应字段
|
||||
*/
|
||||
int gateway_config_update_from_mqtt(const char *json_payload,
|
||||
uint32_t payload_len)
|
||||
{
|
||||
printf("[配置] 收到云端配置更新: %.*s\n",
|
||||
(payload_len > 128) ? 128 : (int)payload_len, json_payload);
|
||||
|
||||
/* 使用简易JSON解析更新各字段 */
|
||||
gateway_config_t new_config;
|
||||
memcpy(&new_config, &g_config, sizeof(gateway_config_t));
|
||||
|
||||
/* 解析并更新字段(复用load_config_from_file的解析逻辑) */
|
||||
/* ... */
|
||||
|
||||
new_config.config_version++;
|
||||
new_config.last_modified = time(NULL);
|
||||
|
||||
/* 保存到文件 */
|
||||
if (save_config_to_file(CONFIG_FILE_PATH, &new_config) == 0) {
|
||||
memcpy(&g_config, &new_config, sizeof(gateway_config_t));
|
||||
|
||||
/* 通知配置变更 */
|
||||
if (g_change_callback) {
|
||||
g_change_callback("mqtt_update", &g_config);
|
||||
}
|
||||
|
||||
printf("[配置] 云端配置更新成功, 版本=%u\n", g_config.config_version);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册配置变更回调
|
||||
*/
|
||||
void gateway_config_set_callback(config_change_callback_t callback)
|
||||
{
|
||||
g_change_callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前配置到文件
|
||||
*/
|
||||
int gateway_config_save(void)
|
||||
{
|
||||
return save_config_to_file(CONFIG_FILE_PATH, &g_config);
|
||||
}
|
||||
Reference in New Issue
Block a user