移除固定的包间延迟配置 `OTA_PACKET_DELAY_MS`,采用基于SPI Link v2 CREDIT背压的动态节奏控制,在SpiMaster中通过remoteCredit==0时wait/拒发 实现流量控制,提高OTA传输稳定性。 refactor(stack): 统一任务栈大小配置并添加CPU统计功能 - 在app_config.h中集中定义所有任务栈大小常量,基于实测水位优化分配 - 新增monitor/cpu_stats.cpp组件,提供1Hz FreeRTOS CPU占用采样 - 更新main.cpp中的任务创建使用新的栈大小常量 - 调整health_monitor和ota_relay等任务的栈分配 perf(ota): 改进ESP32和GD32 OTA分区预擦策略 - ESP32 OTA从边写边擦改为整分区预擦模式,避免写入时的扇区擦除阻塞 - GD32 OTA在prepare阶段进行整分区预擦,提升后续写入吞吐量 - 优化OTA协调器状态管理,修复FINALIZING状态残留问题 fix(log): 改进UDP日志发送的堆内存管理 - 当堆内存不足时记录UDP发送跳过的告警信息到ring buffer - 降低UDP发送跳过的内存阈值从10KB到6KB - 降低日志排水最小内存阈值从8KB到4KB docs(readme): 更新OTA配置参数文档说明 移除已废弃的OTA_PACKET_DELAY_MS参数,更新包间节奏控制说明为 基于CREDIT背压机制的描述。
104 lines
4.1 KiB
JavaScript
104 lines
4.1 KiB
JavaScript
/**
|
||
* spi.js - SPI 帧构建与发送
|
||
*
|
||
* 操作 DOM: #spiType / #spiData / #spiTimeout / #spiHint / #spiResp / #spiRespText
|
||
*
|
||
* 复用的函数(通过 window.* 引用):
|
||
* window.getBaseUrl / window.showToast / window.addLog
|
||
*/
|
||
import { showToast } from '../common/toast.js';
|
||
import { addLog } from '../common/log.js';
|
||
|
||
function updateSpiHint() {
|
||
var typeHex = document.getElementById('spiType').value.trim();
|
||
var dataHex = document.getElementById('spiData').value.trim().replace(/\s/g, '');
|
||
var hint = document.getElementById('spiHint');
|
||
if (!typeHex || !/^[0-9a-fA-F]{1,2}$/.test(typeHex)) {
|
||
hint.className = 'helper-note err';
|
||
hint.innerHTML = 'type 需填 1 字节 hex(如 01, B0)。';
|
||
return;
|
||
}
|
||
if (dataHex && !/^[0-9a-fA-F]*$/.test(dataHex)) {
|
||
hint.className = 'helper-note err';
|
||
hint.innerHTML = '数据包含非 HEX 字符。';
|
||
return;
|
||
}
|
||
if (dataHex && dataHex.length % 2 !== 0) {
|
||
hint.className = 'helper-note err';
|
||
hint.innerHTML = 'HEX 长度必须为偶数。';
|
||
return;
|
||
}
|
||
var bytes = dataHex ? dataHex.length / 2 : 0;
|
||
var totalOnWire = 5 + bytes;
|
||
hint.className = 'helper-note ok';
|
||
hint.innerHTML = 'type=<code>0x' + typeHex.toUpperCase() + '</code>' +
|
||
(bytes > 0 ? ',payload: <b>' + bytes + '</b>B' : ',无 payload') +
|
||
',线上 <b>' + totalOnWire + '</b>B(SOF+TYPE+LEN+PAYLOAD+CRC)';
|
||
}
|
||
|
||
export async function sendSpiFrame() {
|
||
var typeHex = document.getElementById('spiType').value.trim();
|
||
var dataStr = document.getElementById('spiData').value.trim();
|
||
var timeout = parseInt(document.getElementById('spiTimeout').value) || 1000;
|
||
if (!typeHex || !/^[0-9a-fA-F]{1,2}$/.test(typeHex)) {
|
||
showToast('type 需填 1 字节 hex', 'error');
|
||
return;
|
||
}
|
||
var dataHex = dataStr.replace(/\s/g, '');
|
||
if (dataHex && !/^[0-9a-fA-F]*$/.test(dataHex)) {
|
||
showToast('数据包含非 HEX 字符', 'error');
|
||
return;
|
||
}
|
||
if (dataHex && dataHex.length % 2 !== 0) {
|
||
showToast('HEX 数据长度必须为偶数', 'error');
|
||
return;
|
||
}
|
||
var txBytes = dataHex ? dataHex.length / 2 : 0;
|
||
addLog('I', 'SPI', 'type=0x' + typeHex.toUpperCase() +
|
||
(txBytes > 0 ? ', payload=' + dataHex.toUpperCase() : ', 无负载'));
|
||
var body = { type: typeHex, data: dataHex.toUpperCase(), timeout_ms: timeout };
|
||
try {
|
||
var resp = await fetch(window.getBaseUrl() + '/spi/send', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body),
|
||
signal: AbortSignal.timeout(timeout + 2000)
|
||
});
|
||
var result = await resp.json();
|
||
if (result.status === 'ok') {
|
||
var recvHex = result.recv || '';
|
||
var respTypeName = {'80': 'ACK', '81': 'WAIT', '82': 'ERROR'};
|
||
var respTypeLabel = respTypeName[result.resp_type] || ('0x' + result.resp_type);
|
||
addLog('I', 'SPI', '→ ' + respTypeLabel +
|
||
(recvHex ? ' 0x' + recvHex : '') + ' (' + (result.resp_len || 0) + 'B)');
|
||
var rd = document.getElementById('spiResp');
|
||
rd.style.display = 'block';
|
||
document.getElementById('spiRespText').textContent =
|
||
'type=0x' + result.type + ' payload=' + result.payload_len + 'B → ' + respTypeLabel +
|
||
(recvHex ? ' 0x' + recvHex : '') +
|
||
' (' + (result.resp_len || 0) + 'B)';
|
||
showToast('SPI 发送成功', 'success');
|
||
} else {
|
||
addLog('E', 'SPI', '发送失败: code=' + result.code + ' ' + (result.message || ''));
|
||
var rd = document.getElementById('spiResp');
|
||
rd.style.display = 'block';
|
||
document.getElementById('spiRespText').textContent =
|
||
'错误: code=' + result.code + ' ' + (result.message || '');
|
||
showToast('SPI 发送失败', 'error');
|
||
}
|
||
} catch (e) {
|
||
addLog('E', 'SPI', '发送失败: ' + e.message);
|
||
showToast('SPI 发送失败: ' + e.message, 'error');
|
||
}
|
||
}
|
||
|
||
export function clearSpiFields() {
|
||
document.getElementById('spiType').value = '01';
|
||
document.getElementById('spiData').value = '';
|
||
document.getElementById('spiTimeout').value = '1000';
|
||
document.getElementById('spiResp').style.display = 'none';
|
||
updateSpiHint();
|
||
}
|
||
|
||
// 导出 updateSpiHint 供入口挂载 window.updateSpiHint
|
||
export { updateSpiHint }; |