运行/自检记录与待机屏:sys_log定长64B日志引擎(/log/*.log,FTP可直下)+记录页x2+待机屏+密码错误提示;字体122字/217字形

This commit is contained in:
2026-09-14 19:05:00 +08:00
parent 0ec4737418
commit 5b93c6d296
37 changed files with 2101 additions and 109 deletions
+16 -3
View File
@@ -8,6 +8,7 @@
* v1.0 2026-07-19 王建锋 创建初始版本
* v1.1 2026-08-29 王建锋 将原 main.c 中各外设初始化统一收归 app_main_init,
* 并按"时间子系统优先"的顺序编排启动流程
* v1.2 2026-09-14 王建锋 新增自检埋点(RTC/NAND/ADC 失败 → sys_log 自检记录)+ syslog_mcu_init()
*/
#include "app_main.h"
@@ -20,6 +21,7 @@
#include "sys_clock.h"
#include "gd5f2gq5ue.h"
#include "tpafe5160.h"
#include "sys_log.h"
#define DBG_TAG "[APP]"
#include "dbg_log.h"
@@ -41,13 +43,18 @@
* 6) 触发首次 ADC 转换
*/
int app_main_init(void) {
/* 日志引擎初始化(创建待发队列;须先于任何 syslog_post) */
(void)syslog_mcu_init();
/*
* 1) 时间子系统初始化(尽早完成,后续所有日志带时间戳)
* sd2506_init() 从 RTC 读取初始时间
* sys_clock_init() 将 RTC 时间转为 Unix epoch,建立墙钟基准
*/
sd2506_init();
sys_clock_init();
if (sys_clock_init() != 0) {
syslog_post(SYSLOG_SELFTEST, "时钟芯片异常");
}
/* 2) RS-485 初始化(UART5 + PD0 方向控制),初始化即自动开启接收 */
rs485_init(&g_rs485, &huart5, ST_DIR4_GPIO_Port, ST_DIR4_Pin);
@@ -61,13 +68,19 @@ int app_main_init(void) {
(ret == GD5F_BUSY_TIMEOUT) ? "BUSY TIMEOUT" :
(ret == GD5F_ECC_ERROR) ? "ECC ERROR" :
(ret == GD5F_PROGRAM_FAIL) ? "PROGRAM FAIL" :
(ret == GD5F_ERASE_FAIL) ? "ERASE FAIL" :
(ret == GD5F_ID_MISMATCH) ? "ID MISMATCH" : "UNKNOWN");
(ret == GD5F_ERASE_FAIL) ? "ERASE FAIL" :
(ret == GD5F_ID_MISMATCH) ? "ID MISMATCH" : "UNKNOWN");
if (ret != GD5F_OK) {
syslog_post(SYSLOG_SELFTEST, "NAND异常");
}
/* 4) TPAFE5160 ADC 测试 */
int tp_ret = tpafe5160_init();
DBG_INFO("ADC init: %s",
(tp_ret == TPAFE5160_OK) ? "OK" : "TIMEOUT");
if (tp_ret != TPAFE5160_OK) {
syslog_post(SYSLOG_SELFTEST, "ADC异常");
}
/* 5) SD2506 RTC 诊断信息 */
{
+126
View File
@@ -0,0 +1,126 @@
/*
* 模块名称:System Log — 运行/自检记录引擎(双端共享核心)
* 模块功能:定长 64B 文本日志记录的公共 API、格式常量与行拆分;
* MCU 后端 = FatFS /log 目录日志文件(sys_log_mcu.c),模拟器后端 = 宿主文件(sim_syslog.c)
* 适用平台:STM32F407ZGTx + PC 模拟器(共享头,无 OS/LVGL 依赖)
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本(设计见 docs/运行与自检记录设计说明.md §4)
*/
#ifndef SYS_LOG_H
#define SYS_LOG_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
/*
* 日志 ID:决定落盘文件与容量上限
*/
typedef enum {
SYSLOG_RUN = 0u, /* 运行记录 /log/run.log:上电/重启事件 */
SYSLOG_SELFTEST, /* 自检记录 /log/selftest.log:开机自检故障(仅故障才写) */
SYSLOG_COUNT /* 哨兵值,非有效 ID */
} syslog_id_t;
/*
* 记录格式(定长 64B,一行一条;设计文档 §4.2):
* [0..18] "YYYY-MM-DD HH:MM:SS"(ISO 8601 空格变体,19 字符;墙钟无效 = "1970-01-01 08:00:00")
* [19] ' '
* [20..29] epoch 秒 ASCII "%010u"(机器解析/上电重启分类用;墙钟无效 = "0000000000")
* [30] ' '
* [31..58] 描述 UTF-8(≤28B = 9 汉字),不足补 '\0'
* [59..63] '\0' 填充
*/
#define SYSLOG_REC_SIZE 64u /* 单条记录字节数(定长) */
#define SYSLOG_TS_LEN 19u /* 时间戳字段长度 "YYYY-MM-DD HH:MM:SS" */
#define SYSLOG_EPOCH_OFFS 20u /* epoch ASCII 字段偏移 */
#define SYSLOG_EPOCH_LEN 10u /* epoch ASCII 字段长度(2026 年 Unix 秒 = 10 位) */
#define SYSLOG_DESC_OFFS 31u /* 描述字段偏移 */
#define SYSLOG_DESC_MAX 28u /* 描述最大字节数(受框架行缓冲 CP_ROW_BUF_LEN=32 约束) */
/*
* 容量上限(条数;满时删除重建,设计文档 §4.3)
*/
#define SYSLOG_RUN_MAX 1024u /* /log/run.log:64KB */
#define SYSLOG_SELFTEST_MAX 512u /* /log/selftest.log:32KB */
/*
* 上电/重启分类窗口(秒):run.log 末条 epoch 距 now < 该值 → "装置重启记录",否则 "装置上电记录"
*/
#define SYSLOG_REBOOT_WINDOW_S 600u
/*
* 函数功能:投递一条日志(任意上下文;非阻塞,队列满则丢弃)
* 入口参数:id - 日志 ID syslog_id_t
* desc - 描述串(UTF-8,≤SYSLOG_DESC_MAX 字节;超长截断) const char *
* 返回值:无
* 限定条件:MCU 端 FreeRTOS 启动前调用走静态待发数组(由 defaultTask 挂载后落盘)
*/
void syslog_post(syslog_id_t id, const char *desc);
/*
* 函数功能:获取记录条数(= filesize / SYSLOG_REC_SIZE,O(1))
* 入口参数:id - 日志 ID syslog_id_t
* 返回值:条数;文件缺失/未挂载返回 0 uint32_t
*/
uint32_t syslog_count(syslog_id_t id);
/*
* 函数功能:读取第 idx 条记录(时间戳 + 描述,均 NUL 结尾)
* 入口参数:id - 日志 ID syslog_id_t
* idx - 记录序号(0 起) uint32_t
* p_ts - 时间戳输出缓冲(≥SYSLOG_TS_LEN+1 字节;不可为 NULL) char *
* ts_len- p_ts 容量 size_t
* p_desc- 描述输出缓冲(≥SYSLOG_DESC_MAX+1 字节;不可为 NULL) char *
* desc_len - p_desc 容量 size_t
* 返回值:0 - 成功;-1 - 越界/读失败 int
*/
int syslog_get_record(syslog_id_t id, uint32_t idx, char *p_ts, size_t ts_len, char *p_desc, size_t desc_len);
/*
* 函数功能:读取末条记录的 epoch 秒(上电/重启分类用)
* 入口参数:id - 日志 ID syslog_id_t
* p_epoch- 输出缓冲(不可为 NULL;无记录时置 0) uint32_t *
* 返回值:0 - 成功读到末条;-1 - 文件缺失/空 int
*/
int syslog_last_epoch(syslog_id_t id, uint32_t *p_epoch);
/*
* MCU 后端专属 API(sys_log_mcu.c;模拟器端无对应实现,勿在共享代码调用)
*/
/*
* 函数功能:日志引擎初始化(创建 FreeRTOS 消息队列)
* 入口参数:无
* 返回值:0 - 成功;-1 - 队列创建失败 int
* 限定条件:app_main_init() 起始处调用,先于任何 syslog_post()
*/
int syslog_mcu_init(void);
/*
* 函数功能:卷挂载成功后的一次性处理(建 /log 目录 + 写"上电/重启"分类记录)
* 入口参数:无
* 返回值:无
* 限定条件:defaultTask f_mount/f_mkfs 成功且置位 SYS_EVT_STORAGE_READY 前调用一次
*/
void syslog_mcu_mounted(void);
/*
* 函数功能:排空待发日志并落盘(启动前数组 + 运行期队列,FIFO)
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask(唯一写者)调用;卷未就绪时直接返回(条目滞留待重试)
*/
void syslog_drain(void);
#ifdef __cplusplus
}
#endif
#endif /* SYS_LOG_H */
+321
View File
@@ -0,0 +1,321 @@
/*
* 模块名称:System Log (MCU 后端) — 运行/自检记录落盘
* 模块功能:FatFS /log 目录日志文件(run.log/selftest.log)单写者落盘;启动前静态待发数组 + FreeRTOS 消息队列,
* defaultTask(唯一写者)排空落盘;容量满删除重建;上电/重启分类
* 适用平台:STM32F407ZGTx
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本(设计见 docs/运行与自检记录设计说明.md §4.4)
*/
/* 头文件包含区 */
#include <stdio.h>
#include <string.h>
#define DBG_TAG "[SYSLOG]"
#include "dbg_log.h"
#include "sys_log.h"
#include "ff.h"
#include "cmsis_os.h"
#include "sys_clock.h"
/* 静态变量定义区 */
static const char *const s_paths[SYSLOG_COUNT] = { "/log/run.log", "/log/selftest.log" };
static const uint32_t s_caps[SYSLOG_COUNT] = { SYSLOG_RUN_MAX, SYSLOG_SELFTEST_MAX };
#define SYSLOG_QUEUE_MAX 8u
/* 待发条目:desc 为调用方静态串(Flash 字面量),只存指针不拷贝 */
typedef struct {
uint8_t id;
const char *desc;
} syslog_entry_t;
static syslog_entry_t s_preboot[SYSLOG_QUEUE_MAX]; /* 启动前待发数组(RTOS 前单生产者/后单消费者,免锁) */
static volatile uint8_t s_preboot_cnt; /* 数组条目数 */
static osMessageQueueId_t s_queue; /* 运行期消息队列(syslog_mcu_init 创建) */
static volatile uint8_t s_fs_ready; /* 卷挂载 + /log 目录就绪后置位 */
/* 函数定义区 */
/*
* 函数功能:追加一条记录(格式化 64B + FA_OPEN_APPEND;容量满先删除重建)
* 入口参数:id - 日志 ID syslog_id_t
* desc - 描述串(UTF-8,超长截断至 SYSLOG_DESC_MAX) const char *
* 返回值:0 - 成功;-1 - 写失败 int
* 限定条件:仅 defaultTask(唯一写者)调用;卷已挂载
*/
static int s_append(syslog_id_t id, const char *desc) {
FIL f;
FRESULT fr;
UINT bw = 0u;
uint8_t rec[SYSLOG_REC_SIZE];
char ts[SYSLOG_TS_LEN + 1u];
uint32_t epoch;
size_t dl;
if ((uint32_t)id >= (uint32_t)SYSLOG_COUNT || desc == NULL) {
return -1;
}
if (syslog_count(id) >= s_caps[id]) {
fr = f_unlink(s_paths[id]);
DBG_INFO("cap full, recreate %s", s_paths[id]);
if (fr != FR_OK && fr != FR_NO_FILE) {
return -1;
}
}
memset(rec, 0u, sizeof(rec));
if (sys_clock_get_str(ts, sizeof(ts)) == NULL) {
memcpy(ts, "1970-01-01 08:00:00", SYSLOG_TS_LEN);
}
ts[SYSLOG_TS_LEN] = '\0';
epoch = sys_clock_get();
if (epoch == UINT32_MAX) {
epoch = 0u;
}
memcpy(rec, ts, SYSLOG_TS_LEN);
rec[SYSLOG_TS_LEN] = ' ';
(void)snprintf((char *)(rec + SYSLOG_EPOCH_OFFS), SYSLOG_EPOCH_LEN + 1u, "%010u", (unsigned int)epoch);
rec[SYSLOG_DESC_OFFS - 1u] = ' ';
dl = strlen(desc);
if (dl > SYSLOG_DESC_MAX) {
dl = SYSLOG_DESC_MAX;
}
memcpy(rec + SYSLOG_DESC_OFFS, desc, dl);
fr = f_open(&f, s_paths[id], FA_OPEN_APPEND);
if (fr != FR_OK) {
DBG_ERROR("open %s fail (%d)", s_paths[id], (int)fr);
return -1;
}
fr = f_write(&f, rec, SYSLOG_REC_SIZE, &bw);
(void)f_close(&f);
if (fr != FR_OK || bw != SYSLOG_REC_SIZE) {
DBG_ERROR("write %s fail (%d)", s_paths[id], (int)fr);
return -1;
}
return 0;
}
/*
* 函数功能:读取第 idx 条原始记录(64B)
* 入口参数:id - 日志 ID syslog_id_t
* idx - 记录序号(0 起) uint32_t
* p_rec- 输出缓冲(SYSLOG_REC_SIZE 字节,不可为 NULL) uint8_t *
* 返回值:0 - 成功;-1 - 越界/读失败 int
*/
static int s_read_raw(syslog_id_t id, uint32_t idx, uint8_t *p_rec) {
FIL f;
FRESULT fr;
UINT br = 0u;
if (p_rec == NULL || idx >= syslog_count(id)) {
return -1;
}
fr = f_open(&f, s_paths[id], FA_READ);
if (fr != FR_OK) {
return -1;
}
fr = f_lseek(&f, (FSIZE_t)idx * SYSLOG_REC_SIZE);
if (fr == FR_OK) {
fr = f_read(&f, p_rec, SYSLOG_REC_SIZE, &br);
}
(void)f_close(&f);
return (fr == FR_OK && br == SYSLOG_REC_SIZE) ? 0 : -1;
}
/*
* 函数功能:日志引擎初始化(创建 FreeRTOS 消息队列)
* 入口参数:无
* 返回值:0 - 成功;-1 - 队列创建失败 int
* 限定条件:app_main_init() 起始处调用,先于任何 syslog_post()
*/
int syslog_mcu_init(void) {
static const osMessageQueueAttr_t s_queue_attr = { .name = "syslog" };
s_queue = osMessageQueueNew(SYSLOG_QUEUE_MAX, sizeof(syslog_entry_t), &s_queue_attr);
if (s_queue == NULL) {
DBG_ERROR("queue create fail");
return -1;
}
return 0;
}
/*
* 函数功能:卷挂载成功后的一次性处理(建 /log 目录 + 写"上电/重启"分类记录)
* 入口参数:无
* 返回值:无
* 限定条件:defaultTask f_mount/f_mkfs 成功且置位 SYS_EVT_STORAGE_READY 前调用一次
*/
void syslog_mcu_mounted(void) {
FILINFO finfo;
uint32_t last = 0u;
const char *desc = "装置上电记录";
if (f_stat("/log", &finfo) != FR_OK) {
FRESULT fr = f_mkdir("/log");
if (fr != FR_OK && fr != FR_EXIST) {
DBG_ERROR("mkdir /log fail (%d)", (int)fr);
return;
}
}
s_fs_ready = 1u;
/* 上电/重启分类:末条 epoch 距 now < SYSLOG_REBOOT_WINDOW_S → 重启,否则上电 */
if (syslog_last_epoch(SYSLOG_RUN, &last) == 0 && sys_clock_get() != UINT32_MAX) {
if ((uint32_t)(sys_clock_get() - last) < SYSLOG_REBOOT_WINDOW_S) {
desc = "装置重启记录";
}
}
(void)s_append(SYSLOG_RUN, desc);
DBG_INFO("runlog boot entry: %s", desc);
}
/*
* 函数功能:排空待发日志并落盘(启动前数组 + 运行期队列,FIFO)
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask(唯一写者)调用;卷未就绪时直接返回(条目滞留待重试)
*/
void syslog_drain(void) {
syslog_entry_t e;
if (!s_fs_ready || s_queue == NULL) {
return;
}
while (s_preboot_cnt > 0u) {
e = s_preboot[0];
if (s_append((syslog_id_t)e.id, e.desc) != 0) {
break; /* 落盘失败:保留剩余条目,下轮重试 */
}
memmove(&s_preboot[0], &s_preboot[1], sizeof(syslog_entry_t) * (size_t)(s_preboot_cnt - 1u));
s_preboot_cnt--;
}
for (;;) {
if (osMessageQueueGet(s_queue, &e, 0u, 0u) != osOK) {
break; /* 队列空 */
}
if (s_append((syslog_id_t)e.id, e.desc) != 0) {
DBG_ERROR("drop entry id=%d", (int)e.id);
break; /* 落盘失败:停止排空,避免连续丢弃 */
}
}
}
/*
* 函数功能:投递一条日志(任意上下文;非阻塞,队列满则丢弃)
* 入口参数:id - 日志 ID syslog_id_t
* desc - 描述串(UTF-8,静态存储期,超长截断) const char *
* 返回值:无
* 限定条件:FreeRTOS 启动前走静态待发数组;desc 必须为静态/常量串
*/
void syslog_post(syslog_id_t id, const char *desc) {
syslog_entry_t e;
if ((uint32_t)id >= (uint32_t)SYSLOG_COUNT || desc == NULL) {
return;
}
e.id = (uint8_t)id;
e.desc = desc;
if (s_queue != NULL && osKernelGetState() == osKernelRunning) {
if (osMessageQueuePut(s_queue, &e, 0u, 0u) != osOK) {
DBG_ERROR("queue full, drop: %s", desc);
}
} else {
uint8_t n = s_preboot_cnt;
if (n < SYSLOG_QUEUE_MAX) {
s_preboot[n] = e;
s_preboot_cnt = n + 1u;
} else {
DBG_ERROR("preboot full, drop: %s", desc);
}
}
}
/*
* 函数功能:获取记录条数(= filesize / SYSLOG_REC_SIZE,O(1))
* 入口参数:id - 日志 ID syslog_id_t
* 返回值:条数;文件缺失/未挂载返回 0 uint32_t
*/
uint32_t syslog_count(syslog_id_t id) {
FILINFO finfo;
if ((uint32_t)id >= (uint32_t)SYSLOG_COUNT) {
return 0u;
}
if (f_stat(s_paths[id], &finfo) != FR_OK) {
return 0u;
}
return (uint32_t)finfo.fsize / SYSLOG_REC_SIZE;
}
/*
* 函数功能:读取第 idx 条记录(时间戳 + 描述,均 NUL 结尾)
* 入口参数:id - 日志 ID syslog_id_t
* idx - 记录序号(0 起) uint32_t
* p_ts - 时间戳输出缓冲(≥SYSLOG_TS_LEN+1 字节,不可为 NULL) char *
* ts_len - p_ts 容量 size_t
* p_desc - 描述输出缓冲(≥SYSLOG_DESC_MAX+1 字节,不可为 NULL) char *
* desc_len - p_desc 容量 size_t
* 返回值:0 - 成功;-1 - 越界/读失败 int
*/
int syslog_get_record(syslog_id_t id, uint32_t idx, char *p_ts, size_t ts_len, char *p_desc, size_t desc_len) {
uint8_t rec[SYSLOG_REC_SIZE];
size_t dl = 0u;
if (p_ts == NULL || p_desc == NULL || ts_len <= SYSLOG_TS_LEN || desc_len <= SYSLOG_DESC_MAX) {
return -1;
}
if (s_read_raw(id, idx, rec) != 0) {
return -1;
}
memcpy(p_ts, rec, SYSLOG_TS_LEN);
p_ts[SYSLOG_TS_LEN] = '\0';
while (dl < SYSLOG_DESC_MAX && rec[SYSLOG_DESC_OFFS + dl] != '\0') {
dl++;
}
if (dl >= desc_len) {
dl = desc_len - 1u;
}
memcpy(p_desc, rec + SYSLOG_DESC_OFFS, dl);
p_desc[dl] = '\0';
return 0;
}
/*
* 函数功能:读取末条记录的 epoch 秒(上电/重启分类用)
* 入口参数:id - 日志 ID syslog_id_t
* p_epoch- 输出缓冲(不可为 NULL;无记录时置 0) uint32_t *
* 返回值:0 - 成功读到末条;-1 - 文件缺失/空 int
*/
int syslog_last_epoch(syslog_id_t id, uint32_t *p_epoch) {
uint8_t rec[SYSLOG_REC_SIZE];
uint32_t v = 0u;
uint32_t i;
if (syslog_count(id) == 0u) {
if (p_epoch != NULL) {
*p_epoch = 0u;
}
return -1;
}
if (s_read_raw(id, syslog_count(id) - 1u, rec) != 0) {
if (p_epoch != NULL) {
*p_epoch = 0u;
}
return -1;
}
for (i = 0u; i < SYSLOG_EPOCH_LEN; i++) {
char c = (char)rec[SYSLOG_EPOCH_OFFS + i];
if (c >= '0' && c <= '9') {
v = v * 10u + (uint32_t)(c - '0');
}
}
if (p_epoch != NULL) {
*p_epoch = v;
}
return 0;
}
+9 -2
View File
@@ -10,6 +10,7 @@
* v1.1 2026-07-19 王建锋 migrate from freertos.c StartNetTask
* v1.2 2026-07-21 王建锋 网络初始化移入本任务(net_init + TCP 监听)
* v1.3 2026-07-22 王建锋 单连接模式重构,增加大文件/边界/KeepAlive 测试
* v1.4 2026-09-14 王建锋 新增自检埋点:PHY 链路 3s 未建立 → sys_log "通讯异常"
*/
#include <string.h>
@@ -20,6 +21,7 @@
#include "net_cfg.h"
#include "ch395f.h"
#include "sys_events.h"
#include "sys_log.h"
#define DBG_TAG "[NET_TASK]"
#include "dbg_log.h"
@@ -107,8 +109,13 @@ int net_stack_bring_up(void) {
(phy == CH395F_PHY_100M_FULL) ? "100M FULL" :
(phy == CH395F_PHY_100M_HALF) ? "100M HALF" :
(phy == CH395F_PHY_10M_FULL) ? "10M FULL" :
(phy == CH395F_PHY_10M_HALF) ? "10M HALF" :
(phy == CH395F_PHY_DISCONN) ? "DISCONNECT" : "UNKNOWN");
(phy == CH395F_PHY_10M_HALF) ? "10M HALF" :
(phy == CH395F_PHY_DISCONN) ? "DISCONNECT" : "UNKNOWN");
/* 自检埋点:链路超时未建立 → 记入自检记录(仅故障才写) */
if (phy == CH395F_PHY_DISCONN) {
syslog_post(SYSLOG_SELFTEST, "通讯异常");
}
/* 网络已重建完成、对外可用:递增代次号,通知各 TCP 服务(FTP/对时/远程显示)
* 旧 Socket 已随芯片复位失效、应重建监听 */
+22
View File
@@ -5,15 +5,32 @@
* 作者:王建锋
* 创建日期:2026-08-30
* 修改记录:
* v1.1 2026-09-14 王建锋 新增待机显示页初始化(ui_standby_init,空闲 60s 切入)
*/
#include "ui_task.h"
#include <string.h>
#include "cmsis_os.h"
#include "sys_events.h"
#include "ui_port.h"
#include "ui_menu.h"
#include "ui_net_sink.h"
#include "ui_standby.h"
#include "sys_clock.h"
/*
* 函数功能:待机屏底部时钟提供回调(平台差异:MCU = sys_clock_get_str)
* 入口参数:buf - 时间串输出缓冲 char *
* len - buf 容量 size_t
* 返回值:无
*/
static void ui_task_time_cb(char *buf, size_t len) {
if (sys_clock_get_str(buf, len) == NULL && len > 0u) {
buf[0] = '\0'; /* 墙钟无效:空串(待机屏时钟行留白) */
}
}
#define DBG_TAG "[UI_TASK]"
#include "dbg_log.h"
@@ -59,6 +76,11 @@ void ui_task_func(void *argument) {
for (;;) { osDelay(1000); }
}
DBG_INFO("UI menu init: OK");
if (ui_standby_init(60000u, ui_task_time_cb) != 0) {
DBG_ERROR("UI standby init: FAIL");
} else {
DBG_INFO("UI standby: ready (idle=60s)");
}
if (ui_net_sink_start(UI_NET_SINK_PORT) != 0) {
DBG_ERROR("UI net sink: start FAIL");
}