添加遥控业务代码
CI / L0 静态门禁 (pre-commit) (push) Failing after 2m51s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 18s

This commit is contained in:
STF
2026-10-30 20:03:19 -07:00
parent 7596f3371a
commit cd42b4d61d
126 changed files with 70586 additions and 149 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
const fs = require('node:fs');
const path = require('node:path');
const root = path.resolve(__dirname, '../..');
const document = fs.readFileSync(path.join(root, 'docs/104/IEC104_API接口汇总.md'), 'utf8');
const headers = [];
function walk(folder) {
for (const entry of fs.readdirSync(folder, { withFileTypes: true })) {
const location = path.join(folder, entry.name);
if (entry.isDirectory()) walk(location);
else if (entry.name.endsWith('.h') && !entry.name.endsWith('_Internal.h')) headers.push(location);
}
}
walk(path.join(root, '104'));
walk(path.join(root, 'common'));
const stripComments = source => source.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\/\/[^\n]*/g, '')
.replace(/__attribute__\s*\(\(format\(printf,\s*2,\s*3\)\)\)/g, '');
const signaturePattern = /^[ \t]*((?:extern\s+)?(?:const\s+)?[A-Za-z_]\w*(?:[ \t]+[A-Za-z_]\w*)*[ \t*]+(IEC(?:104|60870)_[A-Za-z0-9_]+)\s*\([^;{}]*\)\s*;)/gm;
const tokens = source => (source.replace(/^extern\s+/, '').match(/[A-Za-z_][A-Za-z0-9_]*|[0-9]+|[^\s]/g) || []).join(' ');
const declarationOnly = new Set(['IEC60870_Clock_RTC_Read', 'IEC60870_Clock_RTC_Set']);
const documented = new Map();
for (const block of document.matchAll(/```c\n([\s\S]*?)```/g)) {
for (const match of stripComments(block[1]).matchAll(signaturePattern)) {
if (/^typedef\b/.test(match[1])) continue;
if (!documented.has(match[2])) documented.set(match[2], new Set());
documented.get(match[2]).add(tokens(match[1]));
}
}
const missing = [], mismatched = [];
let checked = 0;
for (const header of headers) {
for (const match of stripComments(fs.readFileSync(header, 'utf8')).matchAll(signaturePattern)) {
if (/^typedef\b/.test(match[1])) continue;
const name = match[2];
if (declarationOnly.has(name)) continue;
++checked;
if (!documented.has(name)) missing.push({ header: path.relative(root, header), name });
else if (!documented.get(name).has(tokens(match[1]))) {
mismatched.push({ header: path.relative(root, header), name, declaration: match[1], documented: [...documented.get(name)] });
}
}
}
const commandHeader = stripComments(fs.readFileSync(path.join(root, '104/business/IEC104_Command.h'), 'utf8'));
const commandReference = document.slice(document.indexOf('## 25. IEC104_Command.c'));
const missingFields = [];
let checkedCommandFields = 0;
for (const structure of commandHeader.matchAll(/typedef\s+struct\s*\{([^{}]*)\}\s*(IEC104_Command\w+_t)\s*;/g)) {
for (const declaration of structure[1].split(';').map(x => x.trim()).filter(Boolean)) {
const names = declaration.split(',').map(x => /([A-Za-z_]\w*)\s*$/.exec(x)[1]);
for (const name of names) {
++checkedCommandFields;
if (!new RegExp(`\\b${name}\\b`).test(commandReference)) missingFields.push(`${structure[2]}.${name}`);
}
}
}
console.log(JSON.stringify({ checked, documented: documented.size, declarationOnly: [...declarationOnly],
missing, mismatched, checkedCommandFields, missingFields }, null, 2));
process.exitCode = missing.length || mismatched.length || missingFields.length ? 1 : 0;
@@ -0,0 +1,46 @@
const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert/strict');
const root = path.resolve(__dirname, '../..');
const files = ['docs/104/IEC104_API接口汇总.md', 'docs/104/IEC104_完整调用流程.md'];
for (const file of files) {
const bytes = fs.readFileSync(path.join(root, file));
const source = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
const baseline = fs.readFileSync(path.join(__dirname, path.basename(file) + '.before'));
assert.equal(bytes.subarray(0, 3).equals(Buffer.from([0xef, 0xbb, 0xbf])),
baseline.subarray(0, 3).equals(Buffer.from([0xef, 0xbb, 0xbf])), `${file}: BOM changed`);
assert(!source.includes('\ufffd') && !source.includes('\r'), `${file}: encoding or line ending`);
assert(source.endsWith('\n') && !/[\t ]+$/m.test(source), `${file}: whitespace`);
const ids = new Set();
let fence = '', details = 0, blocks = 0;
const tableProblems = [];
let tableColumns = 0;
for (const [index, line] of source.split('\n').entries()) {
const marker = /^(`{3,}|~{3,})/.exec(line);
if (marker) {
if (!fence) { fence = marker[1]; blocks++; }
else if (marker[1][0] === fence[0] && marker[1].length >= fence.length) fence = '';
tableColumns = 0;
continue;
}
if (fence) continue;
for (const match of line.matchAll(/<a\s+id="([^"]+)"\s*>/g)) {
assert(!ids.has(match[1]), `${file}:${index + 1}: duplicate id ${match[1]}`);
ids.add(match[1]);
}
if (line.includes('<details>')) details++;
if (line.includes('</details>')) details--;
assert(details >= 0, `${file}:${index + 1}: unexpected details close`);
if (/^\|/.test(line)) {
const columns = (line.replace(/\\\|/g, '').match(/\|/g) || []).length;
if (tableColumns && columns !== tableColumns) tableProblems.push(index + 1);
tableColumns = columns;
} else tableColumns = 0;
}
assert(!fence && details === 0, `${file}: unclosed fence/details`);
assert.deepEqual(tableProblems, [], `${file}: inconsistent table column count`);
for (const id of baseline.toString('utf8').matchAll(/<a\s+id="([^"]+)"\s*>/g)) {
assert(ids.has(id[1]), `${file}: removed existing anchor ${id[1]}`);
}
console.log(`${file}: ${blocks} fenced blocks, ${ids.size} stable anchors, UTF-8/LF and tables OK`);
}
@@ -0,0 +1,13 @@
#include "IEC104_Command.h"
static IEC104_Command_t *Create_Command(IEC104_Slave_t *slave)
{
IEC104_CommandConfig_t config;
IEC104_Command_Config_Default(&config);
return IEC104_Command_Create(slave, &config);
}
int main(void)
{
return Create_Command(NULL) != NULL;
}