init config_server project
This commit is contained in:
commit
8decb5cf31
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
build/
|
||||||
29
CMakeLists.txt
Normal file
29
CMakeLists.txt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
project(demo_config_server LANGUAGES CXX)
|
||||||
|
|
||||||
|
# C++ 标准
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
# 可执行文件输出到 ./bin 目录
|
||||||
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
|
||||||
|
|
||||||
|
# 源文件
|
||||||
|
add_executable(config_server
|
||||||
|
src/config_server.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# 头文件路径(httplib/json 都在这里)
|
||||||
|
target_include_directories(config_server
|
||||||
|
PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/include
|
||||||
|
)
|
||||||
|
|
||||||
|
# 如果用到了多线程(httplib 常见),顺手连上线程库,没用也没事
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
target_link_libraries(config_server
|
||||||
|
PRIVATE
|
||||||
|
Threads::Threads
|
||||||
|
)
|
||||||
296
bin/config.html
Normal file
296
bin/config.html
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>设备配置界面</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: "Segoe UI", "Helvetica Neue", Arial;
|
||||||
|
padding: 30px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
|
||||||
|
margin-bottom: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: linear-gradient(90deg, #e8ecf3, #dce3ee);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
overflow: hidden;
|
||||||
|
height: auto;
|
||||||
|
transition: height 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
padding: 15px 20px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 200px 1fr;
|
||||||
|
row-gap: 12px;
|
||||||
|
column-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsed {
|
||||||
|
height: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 25px;
|
||||||
|
padding: 12px 28px;
|
||||||
|
font-size: 16px;
|
||||||
|
background: #4a8df8;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #3b78d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 错误样式 */
|
||||||
|
.input-error {
|
||||||
|
border: 2px solid #ff5b5b !important;
|
||||||
|
background: #ffecec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: #d9534f;
|
||||||
|
font-size: 12px;
|
||||||
|
grid-column: 1 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast 提示 */
|
||||||
|
#toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.3s, transform 0.3s;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h2>设备配置界面</h2>
|
||||||
|
|
||||||
|
<div id="toast"></div>
|
||||||
|
<div id="form"></div>
|
||||||
|
|
||||||
|
<button onclick="saveConfig()">保存配置</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showToast(msg, error = false) {
|
||||||
|
let t = document.getElementById("toast");
|
||||||
|
t.innerText = msg;
|
||||||
|
t.style.background = error ? "#d9534f" : "#4CAF50";
|
||||||
|
t.classList.add("show");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
t.classList.remove("show");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
let resp = await fetch("/config/data");
|
||||||
|
let data = await resp.json();
|
||||||
|
|
||||||
|
let schema = data.schema;
|
||||||
|
let config = data.config;
|
||||||
|
|
||||||
|
window.schema_cache = schema;
|
||||||
|
window.title_map = {};
|
||||||
|
window.old_devno = config["vehicle"]["device_no"];
|
||||||
|
|
||||||
|
let html = "";
|
||||||
|
|
||||||
|
for (let section in schema) {
|
||||||
|
html += `
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header" onclick="toggleSection('${section}')">
|
||||||
|
[${section}]
|
||||||
|
<span id="${section}_arrow" class="arrow">▼</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="${section}_content" class="card-body">
|
||||||
|
<div class="content-wrapper">
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (let key in schema[section]) {
|
||||||
|
let item = schema[section][key];
|
||||||
|
let title = item.title;
|
||||||
|
let value = config[section][key];
|
||||||
|
|
||||||
|
window.title_map[title] = { section, key };
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<label>${title}</label>
|
||||||
|
<input id="${section}_${key}" value="${value}">
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("form").innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSection(section) {
|
||||||
|
let outer = document.getElementById(section + "_content");
|
||||||
|
let inner = outer.querySelector(".content-wrapper");
|
||||||
|
let arrow = document.getElementById(section + "_arrow");
|
||||||
|
|
||||||
|
if (outer.classList.contains("collapsed")) {
|
||||||
|
outer.classList.remove("collapsed");
|
||||||
|
outer.style.height = "0px";
|
||||||
|
outer.offsetHeight;
|
||||||
|
outer.style.height = inner.scrollHeight + "px";
|
||||||
|
arrow.textContent = "▼";
|
||||||
|
|
||||||
|
outer.addEventListener("transitionend", function done() {
|
||||||
|
outer.style.height = "auto";
|
||||||
|
outer.removeEventListener("transitionend", done);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
outer.style.height = inner.scrollHeight + "px";
|
||||||
|
outer.offsetHeight;
|
||||||
|
outer.style.height = "0";
|
||||||
|
outer.classList.add("collapsed");
|
||||||
|
arrow.textContent = "▶";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// 直接跳转到新域名
|
||||||
|
// ---------------------------
|
||||||
|
function jumpToNewDomain(newDevNo) {
|
||||||
|
let url = `http://${newDevNo}.local:18080/config`;
|
||||||
|
showToast(`设备编号已更新,跳转到 ${newDevNo}.local ...`);
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = url;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveConfig() {
|
||||||
|
let schema = window.schema_cache;
|
||||||
|
|
||||||
|
let new_config = {};
|
||||||
|
for (let section in schema) {
|
||||||
|
new_config[section] = {};
|
||||||
|
for (let key in schema[section]) {
|
||||||
|
new_config[section][key] = document.getElementById(`${section}_${key}`).value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let resp = await fetch("/config/save", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(new_config)
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = await resp.json();
|
||||||
|
|
||||||
|
if (result.status === "error") {
|
||||||
|
showToast(result.message, true);
|
||||||
|
|
||||||
|
let match = result.message.match(/字段 \[(.*)\]/);
|
||||||
|
if (match) {
|
||||||
|
let title = match[1];
|
||||||
|
let pos = window.title_map[title];
|
||||||
|
|
||||||
|
if (pos) {
|
||||||
|
let input = document.getElementById(`${pos.section}_${pos.key}`);
|
||||||
|
let outer = document.getElementById(pos.section + "_content");
|
||||||
|
let arrow = document.getElementById(pos.section + "_arrow");
|
||||||
|
|
||||||
|
outer.classList.remove("collapsed");
|
||||||
|
outer.style.height = "auto";
|
||||||
|
arrow.textContent = "▼";
|
||||||
|
|
||||||
|
document.querySelectorAll(".input-error").forEach(el => el.classList.remove("input-error"));
|
||||||
|
document.querySelectorAll(".error-text").forEach(el => el.remove());
|
||||||
|
|
||||||
|
input.classList.add("input-error");
|
||||||
|
|
||||||
|
let err = document.createElement("div");
|
||||||
|
err.className = "error-text";
|
||||||
|
err.innerText = result.message;
|
||||||
|
input.insertAdjacentElement("afterend", err);
|
||||||
|
|
||||||
|
input.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast("配置已保存");
|
||||||
|
|
||||||
|
// -------- 跳转到新域名 --------
|
||||||
|
let newDevNo = new_config["vehicle"]["device_no"];
|
||||||
|
if (newDevNo && newDevNo !== window.old_devno) {
|
||||||
|
jumpToNewDomain(newDevNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
28
bin/config.json
Normal file
28
bin/config.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"vehicle": {
|
||||||
|
"device_no": "KL001",
|
||||||
|
"vehicle_id": "V010001",
|
||||||
|
"vin": "LSV1234567890KUNL"
|
||||||
|
},
|
||||||
|
"cloud": {
|
||||||
|
"platform_ip": "36.137.175.196",
|
||||||
|
"platform_port": 11101,
|
||||||
|
"mqtt_ip": "192.168.4.196",
|
||||||
|
"mqtt_port": 1883,
|
||||||
|
"mqtt_username": "",
|
||||||
|
"mqtt_password": ""
|
||||||
|
},
|
||||||
|
"cockpit": {
|
||||||
|
"mqtt_ip": "192.168.1.110",
|
||||||
|
"mqtt_port": 1883
|
||||||
|
},
|
||||||
|
"serial": {
|
||||||
|
"dev_name": "/dev/ttyUSB3",
|
||||||
|
"baudrate": 115200
|
||||||
|
},
|
||||||
|
"tbox": {
|
||||||
|
"login_seq": 1,
|
||||||
|
"login_seq_date": "000000",
|
||||||
|
"heartbeat_interval": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
92
bin/config_schema.json
Normal file
92
bin/config_schema.json
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"vehicle": {
|
||||||
|
"device_no": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "KL001",
|
||||||
|
"title": "设备编号"
|
||||||
|
},
|
||||||
|
"vehicle_id": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "V010001",
|
||||||
|
"title": "车辆编号"
|
||||||
|
},
|
||||||
|
"vin": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "LSV1234567890KUNL",
|
||||||
|
"title": "车辆 VIN"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cloud": {
|
||||||
|
"platform_ip": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "36.137.175.196",
|
||||||
|
"title": "平台 IP"
|
||||||
|
},
|
||||||
|
"platform_port": {
|
||||||
|
"type": "int",
|
||||||
|
"default": 11101,
|
||||||
|
"title": "平台端口"
|
||||||
|
},
|
||||||
|
"mqtt_ip": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "192.168.4.196",
|
||||||
|
"title": "MQTT 服务器 IP"
|
||||||
|
},
|
||||||
|
"mqtt_port": {
|
||||||
|
"type": "int",
|
||||||
|
"default": 1883,
|
||||||
|
"title": "MQTT 端口"
|
||||||
|
},
|
||||||
|
"mqtt_username": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"title": "MQTT 用户名"
|
||||||
|
},
|
||||||
|
"mqtt_password": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"title": "MQTT 密码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cockpit": {
|
||||||
|
"mqtt_ip": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "192.168.1.110",
|
||||||
|
"title": "驾驶舱 MQTT IP"
|
||||||
|
},
|
||||||
|
"mqtt_port": {
|
||||||
|
"type": "int",
|
||||||
|
"default": 1883,
|
||||||
|
"title": "驾驶舱 MQTT 端口"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serial": {
|
||||||
|
"dev_name": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "/dev/ttyUSB3",
|
||||||
|
"title": "串口设备名"
|
||||||
|
},
|
||||||
|
"baudrate": {
|
||||||
|
"type": "int",
|
||||||
|
"default": 115200,
|
||||||
|
"title": "串口波特率"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tbox": {
|
||||||
|
"login_seq": {
|
||||||
|
"type": "int",
|
||||||
|
"default": 1,
|
||||||
|
"title": "登入序号"
|
||||||
|
},
|
||||||
|
"login_seq_date": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "000000",
|
||||||
|
"title": "登入日期"
|
||||||
|
},
|
||||||
|
"heartbeat_interval": {
|
||||||
|
"type": "int",
|
||||||
|
"default": 60,
|
||||||
|
"title": "心跳间隔(秒)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
bin/config_server
Executable file
BIN
bin/config_server
Executable file
Binary file not shown.
12337
include/httplib.h
Normal file
12337
include/httplib.h
Normal file
File diff suppressed because it is too large
Load Diff
25526
include/json.hpp
Normal file
25526
include/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
187
include/json_fwd.hpp
Normal file
187
include/json_fwd.hpp
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
|
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
|
|
||||||
|
#include <cstdint> // int64_t, uint64_t
|
||||||
|
#include <map> // map
|
||||||
|
#include <memory> // allocator
|
||||||
|
#include <string> // string
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This file contains all macro definitions affecting or depending on the ABI
|
||||||
|
|
||||||
|
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
|
||||||
|
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
|
||||||
|
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
|
||||||
|
#warning "Already included a different version of the library!"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
|
||||||
|
#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum)
|
||||||
|
#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum)
|
||||||
|
|
||||||
|
#ifndef JSON_DIAGNOSTICS
|
||||||
|
#define JSON_DIAGNOSTICS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
#define JSON_DIAGNOSTIC_POSITIONS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Construct the namespace ABI tags component
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
|
||||||
|
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
|
||||||
|
|
||||||
|
// Construct the namespace version component
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
_v ## major ## _ ## minor ## _ ## patch
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
|
||||||
|
|
||||||
|
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
|
||||||
|
NLOHMANN_JSON_VERSION_MINOR, \
|
||||||
|
NLOHMANN_JSON_VERSION_PATCH)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Combine namespace components
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE \
|
||||||
|
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS, \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
|
||||||
|
namespace nlohmann \
|
||||||
|
{ \
|
||||||
|
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS, \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION) \
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_END \
|
||||||
|
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
|
||||||
|
} // namespace nlohmann
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief namespace for Niels Lohmann
|
||||||
|
@see https://github.com/nlohmann
|
||||||
|
@since version 1.0.0
|
||||||
|
*/
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief default JSONSerializer template argument
|
||||||
|
|
||||||
|
This serializer ignores the template arguments and uses ADL
|
||||||
|
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
|
||||||
|
for serialization.
|
||||||
|
*/
|
||||||
|
template<typename T = void, typename SFINAE = void>
|
||||||
|
struct adl_serializer;
|
||||||
|
|
||||||
|
/// a class to store JSON values
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/
|
||||||
|
template<template<typename U, typename V, typename... Args> class ObjectType =
|
||||||
|
std::map,
|
||||||
|
template<typename U, typename... Args> class ArrayType = std::vector,
|
||||||
|
class StringType = std::string, class BooleanType = bool,
|
||||||
|
class NumberIntegerType = std::int64_t,
|
||||||
|
class NumberUnsignedType = std::uint64_t,
|
||||||
|
class NumberFloatType = double,
|
||||||
|
template<typename U> class AllocatorType = std::allocator,
|
||||||
|
template<typename T, typename SFINAE = void> class JSONSerializer =
|
||||||
|
adl_serializer,
|
||||||
|
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
|
||||||
|
class CustomBaseClass = void>
|
||||||
|
class basic_json;
|
||||||
|
|
||||||
|
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||||
|
/// @sa https://json.nlohmann.me/api/json_pointer/
|
||||||
|
template<typename RefStringType>
|
||||||
|
class json_pointer;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief default specialization
|
||||||
|
@sa https://json.nlohmann.me/api/json/
|
||||||
|
*/
|
||||||
|
using json = basic_json<>;
|
||||||
|
|
||||||
|
/// @brief a minimal map-like container that preserves insertion order
|
||||||
|
/// @sa https://json.nlohmann.me/api/ordered_map/
|
||||||
|
template<class Key, class T, class IgnoredLess, class Allocator>
|
||||||
|
struct ordered_map;
|
||||||
|
|
||||||
|
/// @brief specialization that maintains the insertion order of object keys
|
||||||
|
/// @sa https://json.nlohmann.me/api/ordered_json/
|
||||||
|
using ordered_json = basic_json<nlohmann::ordered_map>;
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|
||||||
|
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
BIN
mdns/avahi-autoipd_0.8-5ubuntu5.3_arm64.deb
Normal file
BIN
mdns/avahi-autoipd_0.8-5ubuntu5.3_arm64.deb
Normal file
Binary file not shown.
BIN
mdns/avahi-daemon_0.8-5ubuntu5.3_arm64.deb
Normal file
BIN
mdns/avahi-daemon_0.8-5ubuntu5.3_arm64.deb
Normal file
Binary file not shown.
BIN
mdns/avahi-utils_0.8-5ubuntu5.3_arm64.deb
Normal file
BIN
mdns/avahi-utils_0.8-5ubuntu5.3_arm64.deb
Normal file
Binary file not shown.
171
mdns/install_mdns.sh
Executable file
171
mdns/install_mdns.sh
Executable file
@ -0,0 +1,171 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 参数解析:必须指定 config.json 路径
|
||||||
|
# --------------------------------------------
|
||||||
|
CONFIG_PATH=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-c|--config)
|
||||||
|
CONFIG_PATH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "❌ 未知参数: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$CONFIG_PATH" ]]; then
|
||||||
|
echo "❌ 必须使用 -c <config.json 路径>"
|
||||||
|
echo "示例: sudo ./install_mdns.sh -c /home/aiec/demo/config.json"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$CONFIG_PATH" ]]; then
|
||||||
|
echo "❌ 找不到配置文件:"
|
||||||
|
echo " $CONFIG_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "📌 使用配置文件: $CONFIG_PATH"
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 1. 安装 avahi 组件
|
||||||
|
# --------------------------------------------
|
||||||
|
echo "=== 安装 Avahi 相关 deb 包 ==="
|
||||||
|
sudo dpkg -i avahi-daemon_*.deb avahi-utils_*.deb avahi-autoipd_*.deb libnss-mdns_*.deb || true
|
||||||
|
sudo apt --fix-broken install -y
|
||||||
|
|
||||||
|
|
||||||
|
echo "=== 启动 & 启用 avahi-daemon ==="
|
||||||
|
sudo systemctl enable avahi-daemon || true
|
||||||
|
sudo systemctl restart avahi-daemon
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 2. 安装 update_mdns.sh(核心逻辑)
|
||||||
|
# --------------------------------------------
|
||||||
|
echo "=== 写入 /usr/local/bin/update_mdns.sh ==="
|
||||||
|
|
||||||
|
sudo mkdir -p /usr/local/bin
|
||||||
|
|
||||||
|
sudo tee /usr/local/bin/update_mdns.sh >/dev/null <<'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
CONFIG="@CONFIG_PATH@"
|
||||||
|
TRIGGER="/run/update_mdns_request"
|
||||||
|
|
||||||
|
# --------------- 读取 device_no ---------------
|
||||||
|
DEVICE_NO=$(grep -oP '"device_no"\s*:\s*"\K[^"]+' "$CONFIG")
|
||||||
|
|
||||||
|
# 若触发文件存在,则优先使用触发文件内容
|
||||||
|
if [[ -f "$TRIGGER" ]]; then
|
||||||
|
FILE_VALUE=$(tr -d '\n' < "$TRIGGER")
|
||||||
|
if [[ -n "$FILE_VALUE" ]]; then
|
||||||
|
DEVICE_NO="$FILE_VALUE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$DEVICE_NO" ]]; then
|
||||||
|
echo "[update_mdns] 未找到有效 device_no"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[update_mdns] 更新 hostname = $DEVICE_NO"
|
||||||
|
|
||||||
|
# --------------- 设置 hostname ---------------
|
||||||
|
hostnamectl set-hostname "$DEVICE_NO"
|
||||||
|
|
||||||
|
# 修复 /etc/hosts
|
||||||
|
if grep -q "^127.0.1.1" /etc/hosts; then
|
||||||
|
sed -i "s/^127.0.1.1.*/127.0.1.1 $DEVICE_NO/" /etc/hosts
|
||||||
|
else
|
||||||
|
echo "127.0.1.1 $DEVICE_NO" >> /etc/hosts
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --------------- 更新 Avahi 服务 ---------------
|
||||||
|
mkdir -p /etc/avahi/services
|
||||||
|
tee /etc/avahi/services/webconfig.service >/dev/null <<EOF2
|
||||||
|
<?xml version="1.0" standalone='no'?>
|
||||||
|
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
|
||||||
|
|
||||||
|
<service-group>
|
||||||
|
<name replace-wildcards="yes">%h Config Page</name>
|
||||||
|
|
||||||
|
<service>
|
||||||
|
<type>_http._tcp</type>
|
||||||
|
<port>18080</port>
|
||||||
|
</service>
|
||||||
|
</service-group>
|
||||||
|
EOF2
|
||||||
|
|
||||||
|
systemctl restart avahi-daemon
|
||||||
|
|
||||||
|
# 删除触发文件,避免重复触发
|
||||||
|
rm -f "$TRIGGER"
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo chmod +x /usr/local/bin/update_mdns.sh
|
||||||
|
|
||||||
|
# 用真实 CONFIG_PATH 替换脚本中的占位符
|
||||||
|
sudo sed -i "s|@CONFIG_PATH@|$CONFIG_PATH|g" /usr/local/bin/update_mdns.sh
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 3. 安装 systemd service
|
||||||
|
# --------------------------------------------
|
||||||
|
echo "=== 创建 update-mdns.service ==="
|
||||||
|
|
||||||
|
sudo tee /etc/systemd/system/update-mdns.service >/dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Update hostname & mDNS when device_no changed
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/update_mdns.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 4. 安装 systemd path watcher
|
||||||
|
# --------------------------------------------
|
||||||
|
echo "=== 创建 update-mdns.path ==="
|
||||||
|
|
||||||
|
sudo tee /etc/systemd/system/update-mdns.path >/dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Watch for mDNS update request
|
||||||
|
|
||||||
|
[Path]
|
||||||
|
PathExists=/run/update_mdns_request
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable update-mdns.path
|
||||||
|
sudo systemctl start update-mdns.path
|
||||||
|
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "============================================="
|
||||||
|
echo "mDNS 自动更新机制安装完成!🎉"
|
||||||
|
echo "配置文件路径:$CONFIG_PATH"
|
||||||
|
echo ""
|
||||||
|
echo "当 C++ 程序写入: /run/update_mdns_request"
|
||||||
|
echo "内容 = 新设备号,例如: KL777"
|
||||||
|
echo "系统会立即更新:"
|
||||||
|
echo " • hostname"
|
||||||
|
echo " • /etc/hosts"
|
||||||
|
echo " • avahi 广播名字(xxx.local)"
|
||||||
|
echo "============================================="
|
||||||
BIN
mdns/libnss-mdns_0.15.1-1ubuntu1_arm64.deb
Normal file
BIN
mdns/libnss-mdns_0.15.1-1ubuntu1_arm64.deb
Normal file
Binary file not shown.
256
src/config_server.cpp
Normal file
256
src/config_server.cpp
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <optional>
|
||||||
|
#include "httplib.h"
|
||||||
|
#include "json.hpp"
|
||||||
|
|
||||||
|
using ordered_json = nlohmann::ordered_json; // 关键:保留 JSON 字段顺序
|
||||||
|
|
||||||
|
ordered_json schema;
|
||||||
|
ordered_json config;
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// 读取文件为字符串
|
||||||
|
// ---------------------------
|
||||||
|
std::string read_file(const std::string &path)
|
||||||
|
{
|
||||||
|
std::ifstream ifs(path);
|
||||||
|
if (!ifs.is_open())
|
||||||
|
return "";
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << ifs.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// 写文件
|
||||||
|
// ---------------------------
|
||||||
|
void write_file(const std::string &path, const std::string &data)
|
||||||
|
{
|
||||||
|
std::ofstream ofs(path);
|
||||||
|
ofs << data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// 加载 schema.json
|
||||||
|
// ---------------------------
|
||||||
|
void load_schema()
|
||||||
|
{
|
||||||
|
std::string txt = read_file("config_schema.json");
|
||||||
|
if (txt.empty())
|
||||||
|
{
|
||||||
|
std::cerr << "ERROR: config_schema.json not found!\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
schema = ordered_json::parse(txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// 加载 config.json,如果不存在则创建
|
||||||
|
// ---------------------------
|
||||||
|
void load_or_create_config()
|
||||||
|
{
|
||||||
|
std::string txt = read_file("config.json");
|
||||||
|
if (txt.empty())
|
||||||
|
{
|
||||||
|
std::cout << "config.json not found, creating default.\n";
|
||||||
|
// 自动根据 schema 生成一份初始配置
|
||||||
|
for (auto &[section, items] : schema.items())
|
||||||
|
{
|
||||||
|
for (auto &[key, meta] : items.items())
|
||||||
|
{
|
||||||
|
config[section][key] = meta["default"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_file("config.json", config.dump(4));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
config = ordered_json::parse(txt);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
std::cerr << "config.json corrupted, recreating.\n";
|
||||||
|
config.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动修复(补全缺失字段)
|
||||||
|
for (auto &[section, items] : schema.items())
|
||||||
|
{
|
||||||
|
for (auto &[key, meta] : items.items())
|
||||||
|
{
|
||||||
|
if (!config[section].contains(key))
|
||||||
|
{
|
||||||
|
config[section][key] = meta["default"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_file("config.json", config.dump(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// 保存配置(含自动类型校验)
|
||||||
|
// ---------------------------
|
||||||
|
std::optional<std::string> validate_config(
|
||||||
|
const ordered_json &incoming, ordered_json &out)
|
||||||
|
{
|
||||||
|
for (auto &[section, items] : schema.items())
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!incoming.contains(section))
|
||||||
|
return "缺少配置段:" + section;
|
||||||
|
|
||||||
|
out[section] = ordered_json::object();
|
||||||
|
|
||||||
|
for (auto &[key, meta] : items.items())
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!incoming[section].contains(key))
|
||||||
|
return "字段 [" + meta.value("title", key) + "] 缺失";
|
||||||
|
|
||||||
|
auto &value = incoming[section][key];
|
||||||
|
|
||||||
|
if (value.is_null())
|
||||||
|
return "字段 [" + meta.value("title", key) + "] 不能为空";
|
||||||
|
|
||||||
|
std::string type = meta["type"];
|
||||||
|
std::string title = meta.value("title", key);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (type == "int")
|
||||||
|
{
|
||||||
|
|
||||||
|
if (value.is_number_integer())
|
||||||
|
{
|
||||||
|
out[section][key] = value.get<int>();
|
||||||
|
}
|
||||||
|
else if (value.is_string())
|
||||||
|
{
|
||||||
|
out[section][key] = std::stoi(value.get<std::string>());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "字段 [" + title + "] 必须为整数";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (type == "bool")
|
||||||
|
{
|
||||||
|
if (value.is_boolean())
|
||||||
|
{
|
||||||
|
out[section][key] = value.get<bool>();
|
||||||
|
}
|
||||||
|
else if (value.is_string())
|
||||||
|
{
|
||||||
|
std::string v = value.get<std::string>();
|
||||||
|
if (v == "1" || v == "true")
|
||||||
|
out[section][key] = true;
|
||||||
|
else if (v == "0" || v == "false")
|
||||||
|
out[section][key] = false;
|
||||||
|
else
|
||||||
|
return "字段 [" + title + "] 必须为布尔值";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "字段 [" + title + "] 必须为布尔值";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{ // string
|
||||||
|
if (!value.is_string())
|
||||||
|
return "字段 [" + title + "] 必须为字符串";
|
||||||
|
|
||||||
|
out[section][key] = value.get<std::string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
return "字段 [" + title + "] 格式无效";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
load_schema();
|
||||||
|
load_or_create_config();
|
||||||
|
|
||||||
|
httplib::Server svr;
|
||||||
|
|
||||||
|
// 返回页面 HTML
|
||||||
|
svr.Get("/config", [](const httplib::Request &, httplib::Response &res)
|
||||||
|
{ res.set_content(read_file("config.html"), "text/html"); });
|
||||||
|
|
||||||
|
// 返回 schema + config
|
||||||
|
svr.Get("/config/data", [](const httplib::Request &, httplib::Response &res)
|
||||||
|
{
|
||||||
|
// ---- 动态加载 schema.json (热更新)----
|
||||||
|
ordered_json fresh_schema;
|
||||||
|
try {
|
||||||
|
fresh_schema = ordered_json::parse(read_file("config_schema.json"));
|
||||||
|
} catch(...) {
|
||||||
|
// schema 文件损坏或读取失败
|
||||||
|
fresh_schema = schema; // fallback 用旧 schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 自动修复 config,补齐 schema 里新增的字段 ----
|
||||||
|
for (auto &[section, items] : fresh_schema.items())
|
||||||
|
{
|
||||||
|
if (!config.contains(section))
|
||||||
|
config[section] = ordered_json::object();
|
||||||
|
|
||||||
|
for (auto &[key, meta] : items.items())
|
||||||
|
{
|
||||||
|
if (!config[section].contains(key))
|
||||||
|
config[section][key] = meta["default"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 覆盖全局 schema ----
|
||||||
|
schema = fresh_schema;
|
||||||
|
|
||||||
|
// ---- 返回 schema + config ----
|
||||||
|
ordered_json out;
|
||||||
|
out["schema"] = schema;
|
||||||
|
out["config"] = config;
|
||||||
|
res.set_content(out.dump(), "application/json");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
svr.Post("/config/save", [](const httplib::Request &req, httplib::Response &res)
|
||||||
|
{
|
||||||
|
ordered_json incoming = ordered_json::parse(req.body);
|
||||||
|
ordered_json validated;
|
||||||
|
|
||||||
|
auto err = validate_config(incoming, validated);
|
||||||
|
|
||||||
|
if (err.has_value()) {
|
||||||
|
ordered_json r;
|
||||||
|
r["status"] = "error";
|
||||||
|
r["message"] = err.value();
|
||||||
|
res.set_content(r.dump(), "application/json");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config = validated;
|
||||||
|
write_file("config.json", config.dump(4));
|
||||||
|
|
||||||
|
// ★★★ 写入 mDNS 更新请求 ★★★
|
||||||
|
std::string new_devno = config["vehicle"]["device_no"];
|
||||||
|
write_file("/run/update_mdns_request", new_devno);
|
||||||
|
|
||||||
|
res.set_content("{\"status\":\"ok\"}", "application/json"); });
|
||||||
|
|
||||||
|
std::cout << "Config server running at http://0.0.0.0:18080/config\n";
|
||||||
|
svr.listen("0.0.0.0", 18080);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user