feat(wave): 故障录波数据面、接线与单通道回放页(软件闭环交付)
数据面(App/algo,纯逻辑,双端编译) - wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask 唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃 兜底——下一次真故障无波可录比丢一段更贵 - wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带 锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度 - 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式 - 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样 纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉 - test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条 自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合 - 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定) 接线(任务层) - adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图), 段头按最低有效位精确记账,不经浮点回算 - adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实; 台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行 - defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到 关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms 补一条告警并记历史峰值(喂狗余量可核) - Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留 超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的, 半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进 - Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到 上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数; 段文件字节布局零改动,三份合成段哈希逐字节不变 - protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体 零改动 人机(160×160 单色屏) - 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器 得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30 - 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与 垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、 单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据 - ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、 手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口 - 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有 60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形 - 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变 - 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏 22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、 仪器用已在跑的单轮工作耗时探针,不新加埋点 台账与文档 - 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936 字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名; 散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建 重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置) - 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据); §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径 风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」 - 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项 - 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划 索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动) - 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件 形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记 「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场 - 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行 227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退 为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与 体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账 同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道 编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串 生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* 模块名称:UI Page wave_play (录波回放内容页)
|
||||
* 模块功能:记录查询 → 录波回放 叶子的内容页——单通道阶梯波形回放(设计
|
||||
* docs/子系统设计/故障录波设计说明.md §8.1 元素表逐条落地):
|
||||
* 顶行 = 通道名 + 段号 + 触发源字母(M 手动 / P 保护 / T 定时);
|
||||
* 波形区 = 两周波 40 点、每点占 3 px 的**阶梯线**(1 bpp 下平滑
|
||||
* 折线的过零平缓段会被阈值吃掉,ui_deco.h 实测口径;阶梯线每段
|
||||
* 非水平即垂直,天然绕开,也更贴近「离散采样」的事实);
|
||||
* 参考线 = 0 线(虚线)+ 窗口峰值标注(定点 mV,ASCII);
|
||||
* 底行 = 单位徽标(只读段头 eng,eng 绝不参与算式——§6.3 钉死,
|
||||
* PC 道同一口径);形状受限通道右上打「削」徽标;
|
||||
* 空态显式文案「无录波段」/「该段不可读」——画成一条直线等于
|
||||
* 伪造证据(§8.1 空态行,此判据优先于「页面上总得画点东西」)。
|
||||
* 键位:上下切通道、左右切段(key_intercept=1 才能把 LEFT 从
|
||||
* 「关页」手里要来);ESC 关页。
|
||||
* 取数纪律:本文件零 App include——段号/段头/窗口码值/峰值 mV 全部经
|
||||
* ui_data.h 录波注入表进来(波形算式与定点换算在执行侧,页面
|
||||
* 连 b_codes 都不碰,收到的码值已并偏移);首帧绘制才懒取数
|
||||
* (开页链零 I/O,绘制定律不破)。
|
||||
* 显示窗口与光标(与落盘道/PC 道的同一裁定,写在这里免得两头漂):
|
||||
* 窗口 = 触发点前一周波 + 后一周波共 40 点,窗口起点 = 段内点号
|
||||
* 「预窗点数 减 二十」(即执行侧 seg_win 的固定窗);虚线竖光标
|
||||
* 落在窗内第 二十 与第 廿一 点之间(0 基第 19/20 点边界,即
|
||||
* 波形区正中 x)——依据 = 设计 §4.2 触发点位置 + App/algo/
|
||||
* wave_ring.h 的点序注释(触发 tick 采样 = 预窗最后一点),
|
||||
* 页面不复述任何换算式。
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-29
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建(故障录波道 UI 侧)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include "ui_page_wave_play.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "ui_content_page.h"
|
||||
#include "ui_data.h"
|
||||
#include "ui_tokens.h"
|
||||
|
||||
/* 私有宏定义区 */
|
||||
|
||||
/* 绘制区几何(屏宽 160:波形区左右各留 20 px 边;高 90 px) */
|
||||
#define WP_WX0 20 /* 波形区左边界 x */
|
||||
#define WP_PX_PER_PT 3 /* 每点占宽(§8.1 阶梯线口径) */
|
||||
#define WP_WTOP 34 /* 波形区顶 y(信息行下方) */
|
||||
#define WP_WBOT 124 /* 波形区底 y(0 线最多贴到这里) */
|
||||
#define WP_WSPANS 90 /* 纵向可用像素 = WP_WBOT - WP_WTOP */
|
||||
#define WP_CUR_X (WP_WX0 + (UI_WAVE_WIN_PTS / 2u) * WP_PX_PER_PT)
|
||||
/* 触发虚线光标 x(窗口正中,理由见文件头) */
|
||||
#define WP_INFO_Y 20 /* 顶行(通道/段号/触发 + 削徽标)条带 y */
|
||||
#define WP_HINT_Y 128 /* 倒数第二行(峰值/键位提示)条带 y */
|
||||
#define WP_ENG_Y 143 /* 底行(单位徽标)条带 y(页脚已隐藏) */
|
||||
#define WP_EMPTY_Y 60 /* 空态文案条带 y(近屏中部) */
|
||||
|
||||
/* 页面数据态:未取数前不画任何波形元素 */
|
||||
#define WP_ST_IDLE 0u /* 未取数/无段(空态文案) */
|
||||
#define WP_ST_OK 1u /* 段完整,可画波形 */
|
||||
#define WP_ST_BAD 2u /* 段不可读(显式文案,绝不画线) */
|
||||
|
||||
/* 私有函数声明区 */
|
||||
|
||||
static void wp_load_seg(void);
|
||||
static void wp_ensure(void);
|
||||
static void wp_draw_text(lv_layer_t *a_layer, const char *a_text,
|
||||
int32_t a_x, int32_t a_y, uint8_t a_align,
|
||||
const lv_area_t *a_clip);
|
||||
static int32_t wp_map_y(int32_t a_v, int32_t a_dmin, int32_t a_span);
|
||||
|
||||
/* 私有变量定义区 */
|
||||
|
||||
/*
|
||||
* 通道名(ASCII 段名 = 语义取数层的通道语义名,页面自带表避免为了
|
||||
* 六个字母再开一条注入边;与段头通道表的语义顺序一致:三相电压/零序/
|
||||
* 三相电流在前,形状受限路按段头 conv 位区分——名字本身两端一致)
|
||||
*/
|
||||
static const char *const s_ch_names[UI_WAVE_CH_CNT] = {
|
||||
"UA", "UB", "UC", "UN", "IA", "IB", "IC", "IN"
|
||||
};
|
||||
|
||||
/* 触发源字母(值域 = 段头 trig_src;未识别给 '?' 不给猜) */
|
||||
static const char s_trig_letters[] = { '?', 'M', 'P', 'T' };
|
||||
#define WP_TRIG_MAX ((uint8_t)(sizeof(s_trig_letters) - 1u))
|
||||
|
||||
static uint8_t s_state; /* WP_ST_* 三态 */
|
||||
static uint8_t s_idx; /* 当前段号(盘上编号,0..99) */
|
||||
static uint8_t s_ch; /* 当前通道(上下键切) */
|
||||
static uint8_t s_eng; /* 段头单位口径(只读徽标) */
|
||||
static uint8_t s_trig; /* 段头触发源 */
|
||||
static uint8_t s_clip; /* 段头形状受限位图(位号=通道) */
|
||||
static int16_t s_codes[UI_WAVE_WIN_PTS]; /* 当前窗码值(执行侧已并偏移) */
|
||||
static int32_t s_peak_milli; /* 窗口峰值 mV(执行侧算好) */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:取当前段的头与窗口(三态落到 s_state,坏态一律显式文案)
|
||||
* 入口参数:无(读 s_idx / s_ch)
|
||||
* 返回值:无
|
||||
* 限定条件:仅在 uiTask 绘制/按键链内调用(经注入表触到执行侧)
|
||||
* 函数说明:头都读不成时连顶行都不画——通道名以外的字段全来自段头,
|
||||
* 缺一半字段还硬凑顶行等于把「读不到」伪装成「读到了」
|
||||
*/
|
||||
static void wp_load_seg(void) {
|
||||
int rc = ui_data_wave_seg_head(s_idx, &s_eng, &s_trig, &s_clip);
|
||||
|
||||
if (rc != UI_WAVE_OK) {
|
||||
s_state = (rc == UI_WAVE_BAD) ? WP_ST_BAD : WP_ST_IDLE;
|
||||
return;
|
||||
}
|
||||
rc = ui_data_wave_seg_win(s_idx, s_ch, s_codes, &s_peak_milli);
|
||||
if (rc != UI_WAVE_OK) {
|
||||
s_state = (rc == UI_WAVE_BAD) ? WP_ST_BAD : WP_ST_IDLE;
|
||||
return;
|
||||
}
|
||||
s_state = WP_ST_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:首帧懒取数(开页链零 I/O 的落地方式:真正的卷读取推迟到
|
||||
* 第一次绘制——模拟器截图与真机重绘走同一条链,行为一致)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:draw_cb 开头调用
|
||||
* 函数说明:取不到段就停在空态;此后再按左右键仍会重试(手动录波从
|
||||
* 触发到成段有一秒级延迟,页开着等段出现是回放页的正常用法,
|
||||
* 不做「试过就没有了」的缓存)
|
||||
*/
|
||||
static void wp_ensure(void) {
|
||||
uint8_t idx = 0u;
|
||||
int rc;
|
||||
|
||||
if (s_state != WP_ST_IDLE) {
|
||||
return;
|
||||
}
|
||||
rc = ui_data_wave_seg_first(&idx);
|
||||
if (rc == UI_WAVE_OK) {
|
||||
s_idx = idx;
|
||||
wp_load_seg();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:单行文本绘制(对齐可选;ui_page_action 私有拷贝同法,此处
|
||||
* 多一个左/右对齐是因为顶行要把「削」徽标钉到右上角)
|
||||
* 入口参数:a_layer - 绘制层;a_text - Flash 字面量;a_x - 锚点 x(对齐
|
||||
* 含义:左=起点 中=屏宽轴 右=终点);a_y - 条带顶;
|
||||
* a_align - 0 左 / 1 中 / 2 右;a_clip - 重绘区
|
||||
* 返回值:无
|
||||
* 限定条件:a_text 非空;条带与 clip 不相交即返回(culling)
|
||||
* 函数说明:lv_text_get_width 必须传 strlen(传 0 不测量、传 -1 死循环,
|
||||
* 框架同款警示);本 LVGL 版条带矩形给 lv_draw_label 做底色无关
|
||||
* 文本定位用,字模颜色固定 UI_COL_FG
|
||||
*/
|
||||
static void wp_draw_text(lv_layer_t *a_layer, const char *a_text,
|
||||
int32_t a_x, int32_t a_y, uint8_t a_align,
|
||||
const lv_area_t *a_clip) {
|
||||
lv_area_t band;
|
||||
lv_draw_label_dsc_t ld;
|
||||
int32_t w = (int32_t)lv_text_get_width(a_text, (uint32_t)strlen(a_text),
|
||||
LV_FONT_DEFAULT, 0);
|
||||
|
||||
band.x1 = 0;
|
||||
band.x2 = UI_SCREEN_W - 1;
|
||||
band.y1 = a_y;
|
||||
band.y2 = a_y + UI_TEXT_BAND_H - 1;
|
||||
if ((band.x1 > a_clip->x2) || (band.x2 < a_clip->x1) ||
|
||||
(band.y1 > a_clip->y2) || (band.y2 < a_clip->y1)) {
|
||||
return;
|
||||
}
|
||||
lv_draw_label_dsc_init(&ld);
|
||||
ld.base.layer = a_layer;
|
||||
ld.text = a_text;
|
||||
ld.text_static = 1u;
|
||||
ld.color = UI_COL_FG;
|
||||
ld.ofs_x = (a_align == 1u) ? (a_x - (w / 2))
|
||||
: ((a_align == 2u) ? (a_x - w) : a_x);
|
||||
ld.ofs_y = 0;
|
||||
lv_draw_label(a_layer, &ld, &band);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:顶行触发字母(越界值给 '?' 不猜)
|
||||
* 入口参数:a_trig - 段头 trig_src 原值
|
||||
* 返回值:字母 ASCII char
|
||||
* 限定条件:仅绘制路径
|
||||
*/
|
||||
static char wp_trig_letter(uint8_t a_trig) {
|
||||
return (a_trig <= WP_TRIG_MAX) ? s_trig_letters[a_trig] : '?';
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:码值域到屏幕 y 的线性映射(0 基线必在域内:域取
|
||||
* 「min(0, 窗最小) .. max(0, 窗最大)」,削顶钉下轨的通道也能
|
||||
* 一眼看出贴的是 0 线还是别的什么)
|
||||
* 入口参数:a_v - 码值;a_dmin - 域底;a_span - 域高(域宽,恒 >0)
|
||||
* 返回值:波形区内 y 像素(WP_WBOT = 域底,WP_WTOP = 域顶) int32_t
|
||||
* 限定条件:绘制路径纯算术,零除由调用侧钳 span 保证
|
||||
* 函数说明:整数乘除即可——域高不超过 65534,先乘后除无溢出风险
|
||||
*/
|
||||
static int32_t wp_map_y(int32_t a_v, int32_t a_dmin, int32_t a_span) {
|
||||
return WP_WBOT - (((a_v - a_dmin) * WP_WSPANS) / a_span);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:波形区自绘——阶梯线 + 0 线(虚线)+ 触发点虚线竖光标
|
||||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||||
* 返回值:无
|
||||
* 限定条件:仅 s_state == WP_ST_OK 时被调用;s_codes 已备
|
||||
* 函数说明:阶梯 = 每点先画 3 px 水平段、再在点边界补一段垂直连线,
|
||||
* 全程只有水平与垂直两种线段(1 bpp 抗阈值裁定的直接落地);
|
||||
* 0 线与光标用 dash(ui_deco 先例只画过实线,dash 字段是 LVGL
|
||||
* 线段 dsc 自带的,单色层同样按位落墨);整区一次 cull,区内
|
||||
* 不再逐线判交——波形区就 120x90,逐线判交省不了多少,判错
|
||||
* 反而留下半截线
|
||||
*/
|
||||
static void wp_draw_wave(lv_layer_t *a_layer, const lv_area_t *a_clip) {
|
||||
lv_area_t band;
|
||||
lv_draw_line_dsc_t ld;
|
||||
int32_t vmin = s_codes[0];
|
||||
int32_t vmax = s_codes[0];
|
||||
int32_t dmin;
|
||||
int32_t dmax;
|
||||
int32_t span;
|
||||
int32_t i;
|
||||
int32_t y;
|
||||
int32_t y_prev = 0;
|
||||
|
||||
band.x1 = 0;
|
||||
band.x2 = UI_SCREEN_W - 1;
|
||||
band.y1 = WP_WTOP;
|
||||
band.y2 = WP_WBOT;
|
||||
if ((band.x1 > a_clip->x2) || (band.x2 < a_clip->x1) ||
|
||||
(band.y1 > a_clip->y2) || (band.y2 < a_clip->y1)) {
|
||||
return;
|
||||
}
|
||||
for (i = 1; i < (int32_t)UI_WAVE_WIN_PTS; i++) {
|
||||
int32_t v = s_codes[i];
|
||||
if (v < vmin) {
|
||||
vmin = v;
|
||||
}
|
||||
if (v > vmax) {
|
||||
vmax = v;
|
||||
}
|
||||
}
|
||||
dmin = (vmin < 0) ? vmin : 0;
|
||||
dmax = (vmax > 0) ? vmax : 0;
|
||||
span = dmax - dmin;
|
||||
if (span < 1) {
|
||||
span = 1; /* 全零窗:0 线与波形线重合贴在区底,诚实且无除零 */
|
||||
}
|
||||
|
||||
lv_draw_line_dsc_init(&ld);
|
||||
ld.base.layer = a_layer;
|
||||
ld.color = UI_COL_FG;
|
||||
ld.width = 1;
|
||||
|
||||
/* 0 线(虚线):恒在域内,永远画得出来 */
|
||||
ld.dash_width = 2;
|
||||
ld.dash_gap = 2;
|
||||
y = wp_map_y(0, dmin, span);
|
||||
ld.p1.x = WP_WX0 - 2;
|
||||
ld.p1.y = y;
|
||||
ld.p2.x = WP_WX0 + ((int32_t)UI_WAVE_WIN_PTS * WP_PX_PER_PT) + 1;
|
||||
ld.p2.y = y;
|
||||
lv_draw_line(a_layer, &ld);
|
||||
|
||||
/* 触发点竖光标(虚线):第 二十/廿一 点之间的点边界(见文件头裁定) */
|
||||
ld.p1.x = WP_CUR_X;
|
||||
ld.p1.y = WP_WTOP;
|
||||
ld.p2.x = WP_CUR_X;
|
||||
ld.p2.y = WP_WBOT;
|
||||
lv_draw_line(a_layer, &ld);
|
||||
|
||||
/* 阶梯线(实线) */
|
||||
ld.dash_width = 0;
|
||||
ld.dash_gap = 0;
|
||||
for (i = 0; i < (int32_t)UI_WAVE_WIN_PTS; i++) {
|
||||
int32_t x;
|
||||
|
||||
x = WP_WX0 + (i * WP_PX_PER_PT);
|
||||
y = wp_map_y((int32_t)s_codes[i], dmin, span);
|
||||
ld.p1.x = x;
|
||||
ld.p1.y = y;
|
||||
ld.p2.x = x + WP_PX_PER_PT - 1;
|
||||
ld.p2.y = y;
|
||||
lv_draw_line(a_layer, &ld);
|
||||
if ((i > 0) && (y != y_prev)) {
|
||||
ld.p1.x = x;
|
||||
ld.p1.y = y_prev;
|
||||
ld.p2.x = x;
|
||||
ld.p2.y = y;
|
||||
lv_draw_line(a_layer, &ld);
|
||||
}
|
||||
y_prev = y;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:单位徽标取词(只读 eng 选词,eng 不进任何算式——§6.3 口径;
|
||||
* 未识别值按最保守的「引脚域」渲染,不猜高口径)
|
||||
* 入口参数:a_eng - 段头 eng 原值
|
||||
* 返回值:徽标文案(Flash 字面量) const char *
|
||||
* 限定条件:仅绘制路径
|
||||
*/
|
||||
static const char *wp_eng_word(uint8_t a_eng) {
|
||||
if (a_eng == (uint8_t)UI_DATA_ENG_SEC) {
|
||||
return "二次值";
|
||||
}
|
||||
if (a_eng == (uint8_t)UI_DATA_ENG_PRI) {
|
||||
return "一次值";
|
||||
}
|
||||
return "引脚域";
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:开页复位(框架 init_cb)——通道回到 UA、数据回到未取态
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:框架开页链内;只清状态零 I/O(懒取数在首帧绘制)
|
||||
*/
|
||||
static void pg_init(void) {
|
||||
s_state = WP_ST_IDLE;
|
||||
s_ch = 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:自绘(框架 draw_cb)
|
||||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||||
* 返回值:无
|
||||
* 限定条件:框架绘制链末尾调用
|
||||
*/
|
||||
static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) {
|
||||
char buf[UI_BUF_STRLEN];
|
||||
|
||||
wp_ensure();
|
||||
if (s_state == WP_ST_BAD) {
|
||||
wp_draw_text(a_layer, "该段不可读", UI_SCREEN_W / 2, WP_EMPTY_Y, 1u,
|
||||
a_clip);
|
||||
return;
|
||||
}
|
||||
if (s_state != WP_ST_OK) {
|
||||
wp_draw_text(a_layer, "无录波段", UI_SCREEN_W / 2, WP_EMPTY_Y, 1u,
|
||||
a_clip);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 顶行:左 = 通道 + 段号 + 触发字母;右 = 形状受限「削」徽标 */
|
||||
(void)lv_snprintf(buf, sizeof(buf), "%s SEG%u %c", s_ch_names[s_ch],
|
||||
(unsigned int)s_idx, wp_trig_letter(s_trig));
|
||||
wp_draw_text(a_layer, buf, UI_LIST_LEFT_X, WP_INFO_Y, 0u, a_clip);
|
||||
if ((s_clip & (uint8_t)(1u << s_ch)) != 0u) {
|
||||
wp_draw_text(a_layer, "削", UI_SCREEN_W - 1, WP_INFO_Y, 2u, a_clip);
|
||||
}
|
||||
|
||||
wp_draw_wave(a_layer, a_clip);
|
||||
|
||||
/* 倒数第二行:左 = 键位提示(ASCII);右 = 窗口峰值 mV(定点,ASCII) */
|
||||
wp_draw_text(a_layer, "UD=CH LR=SEG", UI_LIST_LEFT_X, WP_HINT_Y, 0u,
|
||||
a_clip);
|
||||
(void)lv_snprintf(buf, sizeof(buf), "PK%dmV", (int)s_peak_milli);
|
||||
wp_draw_text(a_layer, buf, UI_SCREEN_W - 1, WP_HINT_Y, 2u, a_clip);
|
||||
|
||||
/* 底行:单位徽标(eng) */
|
||||
wp_draw_text(a_layer, wp_eng_word(s_eng), UI_SCREEN_W / 2, WP_ENG_Y, 1u,
|
||||
a_clip);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按键处理(框架 key_cb)——上下切通道、左右切段
|
||||
* 入口参数:a_key - LVGL 键值 lv_key_t
|
||||
* 返回值:true = 本页已消费;false = 交回框架 bool
|
||||
* 限定条件:框架按键链内(ESC 不到这里;LEFT 因 key_intercept=1 先到这里)
|
||||
* 函数说明:环游语义 = 上下在八路间绕回、左右在现存段间绕回;切段失败
|
||||
* (卷忙/无邻段)保持当前画面不动——「按了没动」比「画面被
|
||||
* 忙态清成空」诚实,忙不忙在回放页本来就不是用户要的答案
|
||||
*/
|
||||
static bool pg_key(lv_key_t a_key) {
|
||||
uint8_t next;
|
||||
int rc;
|
||||
|
||||
switch (a_key) {
|
||||
case LV_KEY_UP:
|
||||
case LV_KEY_DOWN:
|
||||
next = (a_key == LV_KEY_UP)
|
||||
? (uint8_t)((s_ch + UI_WAVE_CH_CNT - 1u) % UI_WAVE_CH_CNT)
|
||||
: (uint8_t)((s_ch + 1u) % UI_WAVE_CH_CNT);
|
||||
s_ch = next;
|
||||
if (s_state == WP_ST_OK) {
|
||||
rc = ui_data_wave_seg_win(s_idx, s_ch, s_codes, &s_peak_milli);
|
||||
if (rc != UI_WAVE_OK) {
|
||||
s_state = (rc == UI_WAVE_BAD) ? WP_ST_BAD : WP_ST_IDLE;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case LV_KEY_LEFT:
|
||||
case LV_KEY_RIGHT:
|
||||
if (s_state == WP_ST_IDLE) {
|
||||
wp_ensure(); /* 空态下按左右 = 再探一次盘(新段可能已落) */
|
||||
return true;
|
||||
}
|
||||
rc = (a_key == LV_KEY_RIGHT) ? ui_data_wave_seg_next(s_idx, &next)
|
||||
: ui_data_wave_seg_prev(s_idx, &next);
|
||||
if (rc == UI_WAVE_OK) {
|
||||
s_idx = next;
|
||||
s_state = WP_ST_IDLE; /* 先回空态再载,坏段不会留着旧画面 */
|
||||
wp_load_seg();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=wave_play 绑定引用)
|
||||
* 查看页形态:无浅 V、无页脚(段号在顶行,页脚「共 N 页」与段号两套计数
|
||||
* 反而打架)、不可选(单元素自绘页)、可编辑位 0(本页零写动作);
|
||||
* key_intercept=1 是本页存在的理由级配置:LEFT 要拿来切上一段,不能让
|
||||
* 框架默认的「LEFT 关页」吃掉
|
||||
*/
|
||||
const content_page_def_t ui_page_wave_play_desc = {
|
||||
.id = "wave_play",
|
||||
.title = "录波回放",
|
||||
.hide_deco = 1u,
|
||||
.hide_footer = 1u,
|
||||
.selectable = 0u,
|
||||
.key_intercept = 1u,
|
||||
.edit_capable = 0u,
|
||||
.init_cb = pg_init,
|
||||
.draw_cb = pg_draw,
|
||||
.key_cb = pg_key,
|
||||
};
|
||||
Reference in New Issue
Block a user