add notice after config

This commit is contained in:
2026-07-31 10:20:33 +08:00
parent 692d8dd18a
commit d45acaf50f
12 changed files with 2721 additions and 2277 deletions
+199 -190
View File
@@ -1,191 +1,200 @@
const fs = require("node:fs");
const path = require("node:path");
const { parse } = require("csv-parse/sync");
const { callServer } = require("./server-client");
const IDENTIFICATION_FIELDS = [
"q_in_val", "dt", "n_order", "t_c", "levels",
"dead_area", "xa_full", "V_val", "repeat"
];
function getDeviceId(options = {}) {
const deviceId = String(options.deviceId || process.env.REINLOOP_DEVICE_ID || "").trim();
if (!deviceId) throw new Error("发布或读取辨识配置时必须提供设备 ID");
return deviceId;
}
async function readParameters(filePath) {
if (!filePath) throw new Error("发布参数时必须提供 JSON 文件路径");
const content = await fs.promises.readFile(path.resolve(filePath), "utf8");
const config = JSON.parse(content);
return config.parameters || config;
}
async function uploadVolumeConfig(parameters, options = {}) {
const deviceId = getDeviceId(options);
const request = await callServer({
type: "getPendingVolumeConfigRequest",
deviceId
}, options);
if (!request.pending) {
throw new Error("ReinLoop 尚未发起容积参数请求,请先在客户端开始容积测试");
}
const result = await callServer({
type: "uploadDataFile",
fileName: "volume_measurement.json",
folder: `${deviceId}/volume_config_requests/${request.requestId}`
}, options);
const metadata = result.uploadMetadata;
if (!metadata || !metadata.url) {
throw new Error("server 未返回有效的配置上传地址");
}
const orderedConfig = {
q_in_val: parameters.q_in_val,
dt: parameters.dt,
p_max: parameters.p_max,
fit_low: parameters.fit_low,
fit_high: parameters.fit_high,
T_delta: parameters.T_delta,
xa_full: parameters.xa_full,
num_runs: parameters.num_runs
};
const form = new FormData();
form.append(
"file",
new Blob([`${JSON.stringify(orderedConfig, null, 2)}\n`], { type: "application/json" }),
"volume_measurement.json"
);
const uploadResponse = await fetch(metadata.url, { method: "POST", body: form });
if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
throw new Error(`配置文件直传失败: HTTP ${uploadResponse.status}`);
}
try {
return await callServer({
type: "submitVolumeConfigFile",
deviceId,
requestId: request.requestId,
fileID: result.fileID,
fileName: "volume_measurement.json"
}, options);
} catch (error) {
await callServer({ type: "deleteFile", fileID: result.fileID }, options).catch(() => {});
throw error;
}
}
function serializeIdentificationConfig(parameters) {
const rows = IDENTIFICATION_FIELDS.map((field) => {
const value = field === "levels" ? parameters[field].join(",") : parameters[field];
return `${field},${field === "levels" ? `"${value}"` : value}`;
});
return `parameter,value\n${rows.join("\n")}\n`;
}
async function uploadIdentificationConfig(parameters, options = {}) {
const result = await callServer({
type: "publishIdentificationConfig",
deviceId: getDeviceId(options),
parameters
}, options);
const metadata = result.uploadMetadata;
if (!metadata || !metadata.url) {
throw new Error("server 未返回有效的配置上传地址");
}
const form = new FormData();
form.append(
"file",
new Blob([serializeIdentificationConfig(parameters)], { type: "text/csv" }),
"identification_config.csv"
);
const uploadResponse = await fetch(metadata.url, { method: "POST", body: form });
if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
throw new Error(`配置文件直传失败: HTTP ${uploadResponse.status}`);
}
return result;
}
async function publishConfig(configType, parameters, options = {}) {
if (configType === "volume") {
return uploadVolumeConfig(parameters, options);
}
if (configType !== "identification") {
throw new Error(`不支持的配置类型: ${configType}`);
}
return uploadIdentificationConfig(parameters, options);
}
async function getConfig(configType, options = {}) {
if (configType === "identification") {
const result = await callServer({
type: "getIdentificationConfig",
deviceId: getDeviceId(options)
}, options);
const response = await fetch(result.url);
if (!response.ok) throw new Error(`配置文件下载失败: HTTP ${response.status}`);
const records = parse(await response.text(), { columns: true, skip_empty_lines: true });
const values = Object.fromEntries(records.map((record) => [record.parameter, record.value]));
return {
q_in_val: Number(values.q_in_val),
dt: Number(values.dt),
n_order: Number(values.n_order),
t_c: Number(values.t_c),
levels: String(values.levels).split(",").map(Number),
dead_area: Number(values.dead_area),
xa_full: Number(values.xa_full),
V_val: Number(values.V_val),
repeat: Number(values.repeat)
};
}
return callServer({ type: "getFunctionConfig", configType }, options);
}
async function main() {
const [command, filePath] = process.argv.slice(2);
const commands = {
"publish-volume": { action: "upload-volume", configType: "volume" },
"publish-identification": { action: "publish", configType: "identification" },
"get-volume": { action: "get", configType: "volume" },
"get-identification": { action: "get", configType: "identification" }
};
const selected = commands[command];
if (!selected) {
throw new Error(
"用法: node b-admin.js <publish-volume|publish-identification|get-volume|get-identification> [config.json]"
);
}
if (selected.action === "upload-volume") {
const result = await publishConfig(selected.configType, await readParameters(filePath));
console.log(`配置已上传: ${result.fileID}`);
return;
}
const result = selected.action === "publish"
? await publishConfig(selected.configType, await readParameters(filePath))
: await getConfig(selected.configType);
if (selected.action === "publish") {
console.log(`配置已上传: ${result.fileID}`);
return;
}
console.dir(result, { depth: null, colors: true });
}
if (require.main === module) {
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
}
module.exports = {
getConfig,
publishConfig,
readParameters,
serializeIdentificationConfig,
uploadIdentificationConfig,
uploadVolumeConfig
const fs = require("node:fs");
const path = require("node:path");
const { parse } = require("csv-parse/sync");
const { callServer } = require("./server-client");
const IDENTIFICATION_FIELDS = [
"q_in_val", "dt", "n_order", "t_c", "levels",
"dead_area", "xa_full", "V_val", "repeat"
];
function getDeviceId(options = {}) {
const deviceId = String(options.deviceId || process.env.REINLOOP_DEVICE_ID || "").trim();
if (!deviceId) throw new Error("发布或读取辨识配置时必须提供设备 ID");
return deviceId;
}
async function readParameters(filePath) {
if (!filePath) throw new Error("发布参数时必须提供 JSON 文件路径");
const content = await fs.promises.readFile(path.resolve(filePath), "utf8");
const config = JSON.parse(content);
return config.parameters || config;
}
async function uploadVolumeConfig(parameters, options = {}) {
const deviceId = getDeviceId(options);
const request = await callServer({
type: "getPendingVolumeConfigRequest",
deviceId
}, options);
if (!request.pending) {
throw new Error("ReinLoop 尚未发起容积参数请求,请先在客户端开始容积测试");
}
const result = await callServer({
type: "uploadDataFile",
fileName: "volume_measurement.json",
folder: `${deviceId}/volume_config_requests/${request.requestId}`
}, options);
const metadata = result.uploadMetadata;
if (!metadata || !metadata.url) {
throw new Error("server 未返回有效的配置上传地址");
}
const orderedConfig = {
q_in_val: parameters.q_in_val,
dt: parameters.dt,
p_max: parameters.p_max,
fit_low: parameters.fit_low,
fit_high: parameters.fit_high,
T_delta: parameters.T_delta,
xa_full: parameters.xa_full,
num_runs: parameters.num_runs
};
const form = new FormData();
form.append(
"file",
new Blob([`${JSON.stringify(orderedConfig, null, 2)}\n`], { type: "application/json" }),
"volume_measurement.json"
);
const uploadResponse = await fetch(metadata.url, { method: "POST", body: form });
if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
throw new Error(`配置文件直传失败: HTTP ${uploadResponse.status}`);
}
try {
return await callServer({
type: "submitVolumeConfigFile",
deviceId,
requestId: request.requestId,
fileID: result.fileID,
fileName: "volume_measurement.json"
}, options);
} catch (error) {
await callServer({ type: "deleteFile", fileID: result.fileID }, options).catch(() => {});
throw error;
}
}
function serializeIdentificationConfig(parameters) {
const rows = IDENTIFICATION_FIELDS.map((field) => {
const value = field === "levels" ? parameters[field].join(",") : parameters[field];
return `${field},${field === "levels" ? `"${value}"` : value}`;
});
return `parameter,value\n${rows.join("\n")}\n`;
}
async function uploadIdentificationConfig(parameters, options = {}) {
const result = await callServer({
type: "publishIdentificationConfig",
deviceId: getDeviceId(options),
parameters
}, options);
const metadata = result.uploadMetadata;
if (!metadata || !metadata.url) {
throw new Error("server 未返回有效的配置上传地址");
}
const form = new FormData();
form.append(
"file",
new Blob([serializeIdentificationConfig(parameters)], { type: "text/csv" }),
"identification_config.csv"
);
const uploadResponse = await fetch(metadata.url, { method: "POST", body: form });
if (uploadResponse.status !== 200 && uploadResponse.status !== 204) {
throw new Error(`配置文件直传失败: HTTP ${uploadResponse.status}`);
}
return result;
}
async function publishConfig(configType, parameters, options = {}) {
if (configType === "volume") {
return uploadVolumeConfig(parameters, options);
}
if (configType !== "identification") {
throw new Error(`不支持的配置类型: ${configType}`);
}
return uploadIdentificationConfig(parameters, options);
}
async function getConfig(configType, options = {}) {
if (configType === "identification") {
const result = await callServer({
type: "getIdentificationConfig",
deviceId: getDeviceId(options)
}, options);
const response = await fetch(result.url);
if (!response.ok) throw new Error(`配置文件下载失败: HTTP ${response.status}`);
const records = parse(await response.text(), { columns: true, skip_empty_lines: true });
const values = Object.fromEntries(records.map((record) => [record.parameter, record.value]));
return {
q_in_val: Number(values.q_in_val),
dt: Number(values.dt),
n_order: Number(values.n_order),
t_c: Number(values.t_c),
levels: String(values.levels).split(",").map(Number),
dead_area: Number(values.dead_area),
xa_full: Number(values.xa_full),
V_val: Number(values.V_val),
repeat: Number(values.repeat)
};
}
const result = await callServer({
type: "getVolumeConfigFile",
deviceId: getDeviceId(options)
}, options);
if (result.found === false || !result.url) {
throw new Error("当前产线尚无已成功提交的容积配置");
}
const response = await fetch(result.url);
if (!response.ok) throw new Error(`配置文件下载失败: HTTP ${response.status}`);
return JSON.parse(await response.text());
}
async function main() {
const [command, filePath] = process.argv.slice(2);
const commands = {
"publish-volume": { action: "upload-volume", configType: "volume" },
"publish-identification": { action: "publish", configType: "identification" },
"get-volume": { action: "get", configType: "volume" },
"get-identification": { action: "get", configType: "identification" }
};
const selected = commands[command];
if (!selected) {
throw new Error(
"用法: node b-admin.js <publish-volume|publish-identification|get-volume|get-identification> [config.json]"
);
}
if (selected.action === "upload-volume") {
const result = await publishConfig(selected.configType, await readParameters(filePath));
console.log(`配置已上传: ${result.fileID}`);
return;
}
const result = selected.action === "publish"
? await publishConfig(selected.configType, await readParameters(filePath))
: await getConfig(selected.configType);
if (selected.action === "publish") {
console.log(`配置已上传: ${result.fileID}`);
return;
}
console.dir(result, { depth: null, colors: true });
}
if (require.main === module) {
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
}
module.exports = {
getConfig,
publishConfig,
readParameters,
serializeIdentificationConfig,
uploadIdentificationConfig,
uploadVolumeConfig
};