本文档依据当前工程中的 src/util/Util.h 和 src/util/Util.cpp 编写,以 .cpp 的实际实现为准。文档覆盖 Util.h 中的公共类与接口:
UtilKVUtilSysInfoIniFile 与 iniFileUtilDEBUG 与 DEBUG::MEMINDEV、KEYPADER、BUTTONER、ENCODERNumPadDEBUG::CPU 在头文件中没有 public:,其成员是私有成员,外部代码不能直接调用;本文只在注意事项中说明,不把它作为公共 API。
#include "util/Util.h"
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
using stt::KV_Str_Str;
using stt::util::BUTTONER;
using stt::util::DEBUG;
using stt::util::ENCODER;
using stt::util::INDEV;
using stt::util::IniFile;
using stt::util::KEYPADER;
using stt::util::KVUtil;
using stt::util::NumPad;
using stt::util::SysInfo;
using stt::util::Util;
using stt::util::iniFileUtil;
这些工具类的大部分方法都是静态方法,无需构造对象:
uint64_t now = Util::getCurrentTimerMS();
std::string os = SysInfo::getOS();
实现通过 __arm__ 区分 ARM 真机和 Windows 模拟器。部分 API 在模拟器上只是占位实现:
| API | ARM | Windows 模拟器 |
|---|---|---|
SysInfo 的型号、序列号、版本信息 |
读取真机信息 | 多数返回空字符串 |
SysInfo::getOS() |
返回 Linux |
仍返回 Linux |
iniFileUtil::readIniValue / writeIniValue |
真正读写 | 直接返回 0,但不读写数据 |
iniFileUtil::load |
返回对象 | 返回 nullptr |
changeCfgPassword |
真正修改 | 直接返回 0 |
enableWatchdog |
真正写入配置 | 直接返回 true |
setMmaSize* |
修改启动参数 | 返回 -1 |
DEBUG::MEM |
读取 /proc/meminfo |
不执行实际监控 |
不能仅凭模拟器上的“成功”返回值断言真机操作已经正确执行。
DEBUG::MEM 使用分离线程,停止方法只修改标志,不等待线程退出。Util::exe 启动分离线程执行 shell 命令,返回时命令通常尚未结束。DEBUG::lastErrorString、监控标志、输入设备状态等是全局静态状态,没有统一的线程同步保护。| 功能 | 方法 |
|---|---|
| BCD 转换 | DecimalToBCD、BCDtoDecimal、BCD16toDecimal、DecimalToBCD16、BCD32toDecimal、DecimalToBCD32 |
| JSON | getValFromJsonc、getValsFromJsonc、getArrayFromJsonc、convertArrayToJson |
| TCP | socketConnect、socketCreate、socketAccept、socketClose、socketWrite、socketReadAll |
| 串口 | openComPort、uartWrite、uartClose、uartAvailable、uartRead、uartReadAll |
| 时间与休眠 | getCurrentTimerMS、sleepfor |
| 动态库 | dllopen、dllclose、dllsym、dllerror、loadDll、loadFunc |
| UI/颜色 | rgb、getScrSize |
| 命令执行 | exe、exeSync |
| 系统配置 | changeCfgPassword、checkPassword、enableWatchdog、setMmaSize、setMmaSizeSafe |
| 其他 | randnum、simpleCalc |
DecimalToBCD 与 BCDtoDecimalstatic int DecimalToBCD(int decimal);
static unsigned int BCDtoDecimal(unsigned int bcd);
DecimalToBCD 把十进制数字逐位编码到 4 bit BCD;BCDtoDecimal 执行反向转换。
int bcd = Util::DecimalToBCD(1234); // 0x1234
unsigned int value = Util::BCDtoDecimal(bcd); // 1234
注意事项:
DecimalToBCD 只适合非负整数。负数会直接得到 0。BCDtoDecimal 不校验每个半字节是否为 0..9;例如非法 BCD 0x1A 仍会参与计算。BCD16toDecimal 与 DecimalToBCD16static int16_t BCD16toDecimal(int16_t bcd);
static int16_t DecimalToBCD16(int16_t decimal);
16 位版本使用最高位作为符号位,因此有效十进制范围是 -7999..7999。
int16_t bcd = Util::DecimalToBCD16(-1234);
int16_t value = Util::BCD16toDecimal(bcd); // -1234
行为说明:
-7999 或 7999 后转换。BCD16toDecimal 发现任意 BCD 位大于 9 时返回 -7999。-7999 同时是合法结果和错误哨兵,调用方无法仅凭返回值区分两者。BCD32toDecimal 与 DecimalToBCD32static int32_t BCD32toDecimal(int32_t bcd);
static int32_t DecimalToBCD32(int32_t decimal);
32 位版本的有效十进制范围为 -79999999..79999999,规则与 16 位版本相同。
int32_t bcd = Util::DecimalToBCD32(20261020);
int32_t value = Util::BCD32toDecimal(bcd); // 20261020
非法 BCD 返回 -79999999,该值同样可能是合法结果。关键业务应在调用前自行逐半字节验证 BCD。
所有 JSON 接口基于 json-c。KV_Str_Str 的 k 表示键,v 表示字符串形式的值。
getValFromJsoncstatic string getValFromJsonc(
const string& json,
const string& key);
读取 JSON 对象第一层中的单个键。
std::string json = R"({"name":"motor","speed":1500,"enabled":true})";
std::string name = Util::getValFromJsonc(json, "name"); // motor
std::string speed = Util::getValFromJsonc(json, "speed"); // 1500
std::string enabled = Util::getValFromJsonc(json, "enabled"); // true
JSON 无效、键不存在或值为 null 时都返回空字符串,因此不能用空字符串区分这三种情况。方法不支持 a.b 形式的嵌套路径。
getValsFromJsoncstatic bool getValsFromJsonc(
const string& json,
vector<KV_Str_Str>& kvs);
有两种模式。
模式一:kvs 预先包含键,只填充这些键的 v:
std::vector<KV_Str_Str> fields;
KVUtil::build(fields, "name,speed,missing", "");
bool ok = Util::getValsFromJsonc(
R"({"name":"motor","speed":1500})",
fields);
if (ok) {
std::cout << KVUtil::getVal(fields, "name") << std::endl;
std::cout << KVUtil::getVal(fields, "speed") << std::endl;
// missing 不存在,对应值保持为空字符串。
}
模式二:传入空列表,枚举对象第一层的全部属性:
std::vector<KV_Str_Str> all;
bool ok = Util::getValsFromJsonc(
R"({"name":"motor","enabled":true,"count":3})",
all);
注意事项:
kvs 追加数据;如果要覆盖旧结果,调用前先 clear()。true/false;空列表模式中布尔值为 1/0,两种模式不一致。null 的属性。getArrayFromJsoncstatic bool getArrayFromJsonc(
const string& json,
vector<vector<KV_Str_Str>>& kvs);
用于解析“对象数组”:
std::vector<std::vector<KV_Str_Str>> rows;
bool ok = Util::getArrayFromJsonc(
R"([{"id":1,"name":"A"},{"id":2,"name":"B"}])",
rows);
if (ok) {
for (std::vector<KV_Str_Str>& row : rows) {
std::cout
<< KVUtil::getVal(row, "id") << ": "
<< KVUtil::getVal(row, "name") << std::endl;
}
}
JSON 为空、语法无效或根节点不是数组时返回 false。结果会追加到 kvs,不会先清空。实现假设数组元素都是对象;不要传数字、字符串或 null 元素组成的数组。
convertArrayToJsonstatic bool convertArrayToJson(
vector<vector<KV_Str_Str>>& kvs,
string& json);
std::vector<KV_Str_Str> row1;
KVUtil::push_back(row1, "id", "1");
KVUtil::push_back(row1, "name", "A");
std::vector<KV_Str_Str> row2;
KVUtil::push_back(row2, "id", "2");
KVUtil::push_back(row2, "name", "B");
std::vector<std::vector<KV_Str_Str>> rows = {row1, row2};
std::string json;
bool ok = Util::convertArrayToJson(rows, json);
// 结果形如:[{"id":"1","name":"A"},{"id":"2","name":"B"}]
输入为空时返回 false。输出通过 append 写入,因此调用前应 json.clear()。普通值一律输出为 JSON 字符串;只有值本身以 {...} 或 [...] 包裹时才原样作为嵌套 JSON 插入。
当前序列化底层没有对键和值中的双引号、反斜杠或控制字符做完整 JSON 转义。外部输入包含这些字符时,生成结果可能不是合法 JSON。
这些是低层接口,调用方负责生命周期、协议分帧、重试和缓冲区安全。
socketConnectstatic int socketConnect(const char* hostname, int port);
成功返回 socket 描述符,失败返回 -1。hostname 实际只支持点分十进制 IPv4 地址,不做 DNS 解析。
int sock = Util::socketConnect("192.168.1.100", 9000);
if (sock < 0) {
std::cerr << "连接失败" << std::endl;
return;
}
// 使用完成后关闭。
Util::socketClose(sock);
限制:
hostname 长度小于或等于 5 会直接失败。nullptr。socketCreate 与 socketAcceptstatic int socketCreate(int port, bool block = false);
static int socketAccept(int listen_st);
socketCreate 绑定所有本机地址并监听,成功返回监听 socket,失败返回 0。block == false 时监听 socket 为非阻塞。
socketAccept 成功返回客户端 socket;失败或非阻塞模式下暂时没有连接时返回 -1。
int listener = Util::socketCreate(9000, true);
if (listener == 0) {
std::cerr << "监听失败" << std::endl;
return;
}
int client = Util::socketAccept(listener);
if (client >= 0) {
// 处理 client...
Util::socketClose(client);
}
Util::socketClose(listener);
部分 socketCreate 失败分支没有关闭刚创建的 socket,频繁失败重试可能泄漏资源。调用方应避免无间隔循环创建。
socketWritestatic int socketWrite(
int64_t handler,
char* buff,
int len,
int flag = 0);
该方法直接调用 send,返回本次写入字节数或底层错误值。一次调用不保证写完全部数据。
std::string payload = "PING\n";
int written = Util::socketWrite(
sock,
payload.data(),
static_cast<int>(payload.size()),
0);
if (written != static_cast<int>(payload.size())) {
// 非阻塞 socket 需要根据错误码和已写字节做重试。
}
socketReadAllstatic int socketReadAll(
int64_t handle,
char* buff,
uint8_t recvLen,
uint16_t timeOut,
bool printinfo = false);
目标是在 timeOut 毫秒内累计读取至少 recvLen 字节。成功返回累计字节数,超时返回 -1。
由于当前实现每次固定请求 256 字节,并没有按缓冲区剩余容量限制读取长度,调用方即使只请求少量数据也必须提供至少 512 字节可写空间:
char buffer[512] = {0};
int received = Util::socketReadAll(
sock,
buffer,
static_cast<uint8_t>(8),
static_cast<uint16_t>(1000),
false);
if (received >= 8) {
// 只按实际协议处理 received 字节,不能假定结尾有 '\0'。
}
重要限制:
recvLen 是 uint8_t,最大只能表示 255。recvLen。recv 返回错误或暂时无数据时,它会继续忙循环直到超时。printinfo 当前没有实际输出效果。socketClosestatic int socketClose(int st);
描述符小于或等于 0 时直接返回 0;其他情况返回底层关闭结果。Windows 上每次关闭还会调用 WSACleanup()。
if (sock > 0) {
Util::socketClose(sock);
sock = -1;
}
openComPortstatic int64_t openComPort(
const char* pFileName,
int baudRate,
int byteSize,
int parity,
int stopBits);
参数:
pFileName:ARM 示例 /dev/ttyS1;Windows 示例 COM8,较大编号可使用系统接受的设备格式。baudRate:波特率。byteSize:数据位,通常为 5..8。parity:0 无校验,1 偶校验,2 奇校验。stopBits:1 或 2。成功返回句柄,失败返回负数。
int64_t port = Util::openComPort(
"/dev/ttyS1",
115200,
8,
0,
1);
if (port < 0) {
std::cerr << "串口打开失败" << std::endl;
return;
}
ARM 对常见波特率使用 termios,其他波特率尝试 termios2/BOTHER。参数合法性没有统一前置校验,调用方应只传上述支持值。
uartWritestatic int uartWrite(
int64_t handle,
unsigned char* bytes,
int offset,
int size);
unsigned char frame[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x02};
int written = Util::uartWrite(port, frame, 0, sizeof(frame));
if (written != static_cast<int>(sizeof(frame))) {
std::cerr << "串口写入不完整" << std::endl;
}
Windows 实现按最多 256 字节分块并使用 offset;ARM 实现一次调用 write,忽略 offset。为了跨平台一致,建议总是传 offset == 0,如需偏移则由调用方调整指针。
uartAvailablestatic int uartAvailable(int64_t handle);
返回接收队列中当前可读的字节数。Windows 查询失败返回 -1;ARM 查询失败返回 0,会与“当前无数据”混淆。
int available = Util::uartAvailable(port);
if (available > 0) {
// 可以开始读取。
}
uartReadstatic int uartRead(
int64_t handle,
unsigned char* bytes,
int offset,
int size);
当前实现忽略 offset。更重要的是,传给底层读取的长度表达式实际会令 size <= 256 时仍读取最多 256 字节。因此缓冲区至少应有 256 字节:
unsigned char buffer[256] = {0};
int count = Util::uartRead(port, buffer, 0, sizeof(buffer));
if (count > 0) {
// 处理 count 字节。
}
不要传小于 256 字节的缓冲区。Windows 异步读内部可能无限等待完成;ARM 的单次 select 等待时间很短,无数据时返回 0。
uartReadAllstatic int uartReadAll(
int64_t handle,
char* buff,
uint8_t recvLen,
uint16_t timeOut,
bool printinfo = false);
接口语义与 socketReadAll 类似。因为内部每次调用 uartRead(..., 256),缓冲区应至少为 512 字节:
char response[512] = {0};
int count = Util::uartReadAll(port, response, 8, 1000, false);
if (count >= 8) {
// 处理二进制响应。
}
超时返回 -1,不会返回已经累计的部分长度;printinfo 当前未使用。
uartClosestatic void uartClose(int64_t handle);
Util::uartClose(port);
port = -1;
该方法没有返回值,也不检查句柄是否合法。一个句柄只能关闭一次。
bool queryDevice() {
int64_t port = Util::openComPort("/dev/ttyS1", 9600, 8, 0, 1);
if (port < 0) {
return false;
}
unsigned char request[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01};
int written = Util::uartWrite(port, request, 0, sizeof(request));
if (written != static_cast<int>(sizeof(request))) {
Util::uartClose(port);
return false;
}
char response[512] = {0};
int received = Util::uartReadAll(port, response, 7, 1000, false);
Util::uartClose(port);
return received >= 7;
}
getCurrentTimerMSstatic uint64_t getCurrentTimerMS(char* szTimer = NULL);
返回 Unix 时间的毫秒数,并可选地写出十进制字符串。
uint64_t timestamp = Util::getCurrentTimerMS();
char text[32] = {0};
Util::getCurrentTimerMS(text);
std::cout << text << std::endl;
szTimer 没有长度参数,调用方应至少提供 21 字节,推荐 32 字节。该时间是墙上时钟,不是单调时钟;系统校时后可能前跳或后退,不适合测量严格超时。
sleepforstatic void sleepfor(int ms);
Util::sleepfor(20); // 阻塞当前线程约 20 ms
不要在 UI 线程中长时间调用,否则界面和事件处理会卡住。负值没有显式校验。
randnumstatic int randnum(int max = 0);
max > 0:返回 [0, max),不包含 max。max == 0:返回底层 rand() 的非负结果。max < 0:原样返回 max。int index = Util::randnum(10); // 0..9
实现每次调用都会用当前秒重新执行 srand,同一秒内多次调用很可能得到相同结果。它不适合高频随机、统计模拟、密码或安全令牌。
static void* dllopen(const char* file, int mode);
static int dllclose(void* handle);
static void* dllsym(void* handle, const char* name);
static char* dllerror(void);
这些方法直接封装 dlopen、dlclose、dlsym、dlerror,Windows 依赖工程中的兼容层。
void* lib = Util::dllopen("./lib/libdemo.so", RTLD_LAZY | RTLD_GLOBAL);
if (!lib) {
std::cerr << Util::dllerror() << std::endl;
return;
}
void* symbol = Util::dllsym(lib, "demo_init");
if (!symbol) {
std::cerr << Util::dllerror() << std::endl;
}
Util::dllclose(lib);
lib = nullptr; // dllclose 内部不能把调用方变量置空。
函数指针转换必须与导出函数的 ABI、调用约定和签名完全一致。
loadDll 与 loadFuncstatic void* loadDll(void* pdll, const char* path);
static void* loadFunc(void* lib, const char* funcname);
loadDll 在传入句柄非空时直接返回原句柄,避免重复加载。loadFunc 在库或符号无效时返回 nullptr 并输出警告。
using AddFn = int (*)(int, int);
void* lib = nullptr;
lib = Util::loadDll(lib, "./lib/libdemo.so");
if (!lib) {
return;
}
AddFn add = reinterpret_cast<AddFn>(
Util::loadFunc(lib, "demo_add"));
if (add) {
std::cout << add(2, 3) << std::endl;
}
Util::dllclose(lib);
lib = nullptr;
动态库必须在所有由它取得的函数指针不再使用后才能关闭。
rgbstatic int rgb(int r, int g, int b);
当前实现是未完成的占位函数,无论输入什么都返回 0:
int color = Util::rgb(255, 0, 0); // 当前仍为 0,不是红色
在实现完成前不要使用该方法生成颜色。工程其他接口通常直接接受 0xRRGGBB:
int red = 0xFF0000;
getScrSizestatic Area getScrSize();
返回当前 LVGL 活动屏幕的宽高:
stt::Area size = Util::getScrSize();
std::cout << size.w << " x " << size.h << std::endl;
必须在 LVGL 已初始化且活动屏幕存在时调用,通常应在 UI 线程中使用。它返回当前活动屏幕对象尺寸,不一定等于物理面板原始分辨率。
exe:异步执行static int exe(const string& cmdline, const string& mode);
参数非空时创建分离线程调用 popen,并立即返回 0;参数为空返回 1。
int rc = Util::exe("sync", "r");
if (rc != 0) {
std::cerr << "命令未发起" << std::endl;
}
返回 0 只表示线程已创建,不表示命令启动成功或退出码为零。无法等待结束,也无法读取输出。
exeSync:同步执行并读取输出static bool exeSync(
const string& cmdline,
const string& mode,
string& result);
std::string output;
bool started = Util::exeSync("uname -a", "r", output);
if (started) {
std::cout << output << std::endl;
}
注意事项:
result 是追加写入,调用前应 clear()。fgets 读到的内容后再次追加 \n,输出可能出现双换行。true 只表示 popen 成功;实现忽略 pclose 返回的进程退出码。"r"。"w" 模式下代码仍尝试读取流,语义不可靠。cmdline 会交给 shell。严禁直接拼接未校验的用户输入、文件名、网络数据或数据库内容,否则存在命令注入风险。changeCfgPasswordstatic int changeCfgPassword(
const string& oldPwd,
const string& newPwd);
ARM 返回值:
| 返回值 | 含义 |
|---|---|
0 |
成功 |
1 |
已配置密码且旧密码不匹配 |
2 |
写入新密码失败 |
int rc = Util::changeCfgPassword("old-pass", "new-pass");
if (rc == 1) {
std::cerr << "原密码错误" << std::endl;
} else if (rc != 0) {
std::cerr << "密码保存失败" << std::endl;
}
Windows 模拟器无条件返回 0,但不会保存密码。密码以环境配置值形式处理,不应假定使用了安全哈希存储。
checkPasswordstatic bool checkPassword(const string& pwd);
if (!Util::checkPassword(inputPassword)) {
std::cerr << "密码错误" << std::endl;
}
未配置密码,即存储值为空时,任何输入都返回 true。该语义适合“未启用密码保护”,不适合作为必须认证的安全边界。
enableWatchdogstatic bool enableWatchdog(int timeout);
timeout == 0:关闭看门狗。timeout == 1..100:设置守护秒数。0 或大于 100:返回 false。bool ok = Util::enableWatchdog(30);
Windows 模拟器直接返回 true,不执行任何操作。此方法只写配置;具体喂狗机制由系统其他组件负责。
setMmaSize 与 setMmaSizeSafestatic int setMmaSize(int mSize);
static int setMmaSizeSafe(int mSize);
两个方法都只在 ARM 上有效,目标范围为 4..70 MB;超出范围会被钳位,不是直接拒绝。成功返回 0,失败返回 -1。
int rc = Util::setMmaSizeSafe(32);
if (rc != 0) {
std::cerr << "MMA 大小修改失败" << std::endl;
}
应优先使用 setMmaSizeSafe,它会:
mma_heap= 参数范围内的 sz=0x...。这是修改 bootargs 的高风险操作,可能影响设备启动和显示内存。不要在 C 系列等不支持的平台调用;修改前应保留原 bootargs,并按设备发布流程验证和重启。
simpleCalcstatic string simpleCalc(const string& str);
实现把表达式拼接成 SELECT <表达式>,交给默认 SQLite 数据库计算。
std::string result = Util::simpleCalc("(10 + 2) * 3");
// 预期结果:36,具体字符串格式由 DBSqlite::calc 决定。
空字符串返回空字符串。该方法依赖默认数据库已经初始化,而且没有检查数据库指针是否为空。
虽然名称是“简单表达式”,实质上执行 SQLite SELECT 片段。严禁传入不可信输入;分号、子查询或 SQLite 函数可能扩大执行范围。它也不是通用 C++ 表达式解析器。
push_backstatic void push_back(
vector<KV_Str_Str>& vct,
const string& k,
const string& v);
std::vector<KV_Str_Str> items;
KVUtil::push_back(items, "name", "motor");
KVUtil::push_back(items, "speed", "1500");
方法只追加,不检查重复键。
buildstatic void build(
vector<KV_Str_Str>& vct,
const string& keys,
const string& vals);
std::vector<KV_Str_Str> items;
KVUtil::build(items, "name, speed,enabled", "motor,1500,1");
keys 中的所有普通空格都会被删除;vals 中空格保留。值数量不足时补空字符串,值多于键时忽略多余部分。结果追加到 vct,不会清空。
该接口只是逗号分割,不支持带引号的逗号值。
buildFromRowstatic vector<KV_Str_Str> buildFromRow(
TTable& tbl,
int sel);
把表格某行转换成“列名 -> 单元格值”。
std::vector<KV_Str_Str> row = KVUtil::buildFromRow(table, 0);
std::string name = KVUtil::getVal(row, "NAME");
sel >= table.size() 时返回空列表。当前实现没有检查 sel < 0;负数可能继续传给 getRow,调用方必须先保证 0 <= sel < table.size()。
getVal 与 setValstatic string getVal(
vector<KV_Str_Str>& vct,
const string& key);
static bool setVal(
vector<KV_Str_Str>& vct,
const string& key,
string value);
std::string speed = KVUtil::getVal(items, "speed");
bool changed = KVUtil::setVal(items, "speed", "1600");
两者只处理第一个匹配键。getVal 在键不存在和键存在但值为空时都返回空字符串;setVal 不会自动插入缺失键。
getKVector 重载static vector<string> getKVector(
vector<KV_Str_Str>& vct,
bool k = true);
static vector<string> getKVector(
vector<KV_Str_Str>& vct,
const string& deli,
const string& padl = "",
const string& padr = "");
std::vector<std::string> keys = KVUtil::getKVector(items, true);
std::vector<std::string> values = KVUtil::getKVector(items, false);
std::vector<std::string> assignments =
KVUtil::getKVector(items, "=", "'", "'");
// 例如:name='motor'
第二个重载只是字符串拼接,不执行 SQL 转义。不要把其结果直接用于含不可信数据的 SQL。
getVal 与 setValstatic string getVal(
const string& key,
const string& defv);
static void setVal(
const string& key,
const string& val);
操作默认数据库中的 S_CFG(K, V) 表:
std::string theme = KVUtil::getVal("theme", "light");
KVUtil::setVal("theme", "dark");
键和值会通过 StringUtil::sqlEsc 转义。调用前必须保证默认数据库已创建且 S_CFG 表存在。写接口没有返回值,无法直接判断数据库执行是否成功;并发写入也没有额外事务保护。
static string getHMIModel();
static string getSerialNumber();
static string getSysVersion();
static string getSysDay();
static string getOS();
static string getFwVersion();
static string getFwDay();
std::cout << "型号: " << SysInfo::getHMIModel() << '\n';
std::cout << "序列号: " << SysInfo::getSerialNumber() << '\n';
std::cout << "系统版本: " << SysInfo::getSysVersion() << '\n';
std::cout << "系统发布日期: " << SysInfo::getSysDay() << '\n';
std::cout << "OS: " << SysInfo::getOS() << '\n';
std::cout << "固件版本: " << SysInfo::getFwVersion() << '\n';
std::cout << "固件发布日期: " << SysInfo::getFwDay() << '\n';
ARM 上版本信息来自 /customer/cfginfo.ini 的 [stt_hmi] 段。读取失败返回空字符串。模拟器上除 getOS() 固定返回 Linux 外,其余通常为空。
static vector<string> getUdisks();
static vector<string> getUdisksHot();
static vector<string> getSDCards();
static void setHotPlugin(bool b);
static bool getHotPlugin();
std::vector<std::string> disks;
if (SysInfo::getHotPlugin()) {
disks = SysInfo::getUdisksHot();
} else {
disks = SysInfo::getUdisks();
}
for (const std::string& path : disks) {
std::cout << path << std::endl;
}
getUdisks 枚举 /vendor/ 下名称匹配 udisk_* 的项。getSDCards 枚举 /vendor/ 下类似 sd、sda、sda1 的名称。getUdisksHot 会在每个候选 U 盘上创建并删除测试目录,只返回可写项。它会产生真实闪存写入,不应高频轮询。setHotPlugin 只设置进程内全局标志,本身不执行设备扫描。static vector<string> getPrinters();
static vector<string> getInputDevs();
static vector<string> getSerialComs();
static vector<string> getEths();
static vector<string> getWLan();
static vector<string> getFGs();
当前实现全部直接返回空列表:
std::vector<std::string> ports = SysInfo::getSerialComs();
// 当前实现中 ports 为空,不能据此断定设备不存在。
这些接口尚未实现,不应作为设备发现或状态判断依据。
static int getProcessByName(const string& name);
static void killProcessByName(const string& name);
int pid = SysInfo::getProcessByName("demo_service");
if (pid > 0) {
SysInfo::killProcessByName("demo_service");
}
仅 ARM 执行真实操作;模拟器查询返回 0,终止方法不执行任何操作。killProcessByName 没有返回值,无法确认目标是否存在或终止是否成功。终止进程是破坏性操作,只能传受信任的固定服务名。
INI 键使用 section:key 格式,例如 wifi:enabled。
iniFileUtilstatic int readIniValue(
const string& key,
string& value,
const string& file);
static int writeIniValue(
const string& key,
const string& value,
const string& file);
std::string value;
int rc = iniFileUtil::readIniValue(
"wifi:enabled",
value,
"/customer/wifi.ini");
if (rc == 0) {
std::cout << value << std::endl;
}
rc = iniFileUtil::writeIniValue(
"wifi:enabled",
"1",
"/customer/wifi.ini");
ARM 上 0 表示成功。读取失败会把 value 清空。写入接口拒绝空 key、空 value 或空文件名,因此不能用它写入空字符串值。
Windows 模拟器中两个方法都直接返回 0:读取不会设置 value,写入不会修改文件。这是最容易造成误判的平台差异之一。
load、IniFile 与 freeIniFile(const string& path);
~IniFile();
static IniFile* iniFileUtil::load(const string& path);
static void iniFileUtil::free(IniFile* f);
bool IniFile::readString(
const string& key,
string& value,
const string& defaultValue);
bool IniFile::writeString(
const string& key,
const string& value);
IniFile 构造函数加载完整文件到内存字典;析构函数把字典整体写回原文件并释放字典。通常不直接构造,而是使用 iniFileUtil::load 与 iniFileUtil::free,这也与工程现有调用方式一致。
IniFile* ini = iniFileUtil::load("/customer/app.ini");
if (!ini) {
std::cerr << "INI 加载失败或当前平台不支持" << std::endl;
return;
}
std::string server;
bool exists = ini->readString(
"network:server",
server,
"127.0.0.1");
if (!exists) {
server = "127.0.0.1"; // ARM 上键不存在时应显式采用默认值。
}
ini->writeString("network:server", "192.168.1.10");
// 析构时才把完整字典写回文件。
iniFileUtil::free(ini);
ini = nullptr;
注意事项:
load 在 Windows 模拟器返回 nullptr。IniFile*,但其内部字典为空,读写返回 false。writeString 只修改内存字典;free/析构时才覆盖写回原文件。fopen 是否成功,路径失效时存在风险。delete 后继续使用指针;推荐统一用 iniFileUtil::free 并置空。writeString 内部把 key 作为 sprintf 格式字符串使用。键名包含 % 时存在未定义行为,只能使用受信任的字母、数字、下划线和 : 键名。static string lastErrorString;
static void setLastErrorString(string s);
static string getLastErrorString();
DEBUG::setLastErrorString("配置无效");
std::string error = DEBUG::getLastErrorString();
这是进程级静态字符串,不是线程局部状态。成功操作不会自动清空旧错误,多线程读写存在竞争。业务代码必须先看当前 API 的返回值,再把错误字符串作为补充信息。
DEBUG::setstatic void set(
int level,
bool verbose = false,
bool retarget = false);
级别定义:
| 值 | DebugLevel |
输出范围 |
|---|---|---|
0 |
NO |
关闭分级日志;LOGD 仍按 NO 路径输出 |
1 |
LOG |
LOG |
2 |
WARN |
LOG、WARN |
3 |
ERR |
LOG、WARN、ERR |
DEBUG::set(stt::constant::DebugLevel::WARN, true, false);
verbose == true 时输出文件名、行号和级别;retarget 在 ARM 实现中仍是 TODO,没有真正通过网络重定向。
DEBUG::LOG 与宏static void LOG(
int level,
const char* file,
int line,
const char* format,
...);
通常不应手工填写文件和行号,而应使用宏:
LOGD("value=%d", value); // level 0
_LOG_("connected: %s", host); // level 1
_WARN_("retry=%d", retry); // level 2
_ERR_("open failed: %s", path); // level 3
_LOG_、_WARN_、_ERR_ 定义在 base/internal/StaticMember.h,并非只包含 Util.h 就一定可用。
格式字符串和参数类型必须严格匹配。当前 DEBUG::LOG 的可变参数处理会重复消费同一个 va_list,尤其在 ARM 上存在未定义行为;复杂日志或关键诊断信息不应只依赖该方法。
DEBUG::MEMstatic void startMemoryMonitor(int ms = 100);
static void stopMemoryMonitor();
static void printAvailableMemory();
static string availableMemory;
DEBUG::MEM::startMemoryMonitor(1000); // 实际单位是毫秒
// ...运行一段时间...
std::cout << DEBUG::MEM::availableMemory << std::endl;
DEBUG::MEM::stopMemoryMonitor();
ms 实际是毫秒,头文件注释中的“每 n 秒”不准确。stopMemoryMonitor 不等待线程退出,且标志/字符串没有原子或互斥保护。ms <= 0 会导致高频循环,应使用合理正数,例如 1000。printAvailableMemory 只读取和打印一次 /proc/meminfo 中的 MemAvailable。DEBUG::CPU 当前是私有嵌套类,外部无法调用;即使实现文件有监控代码,也不属于可用公共接口。
以下 API 直接操作 LVGL 输入设备、全局焦点组和 HMI 运行时,必须在运行时初始化完成后调用。推荐仅在 UI 线程调用。类型约定:
| 类型 | 含义 |
|---|---|
1 |
指针类设备,包括触摸、鼠标及实现定义的硬按键 |
2 |
键盘 |
3 |
外部按键 Buttoner |
4 |
编码器 |
static void deleteIndev(int type);
static void enableIndev(int type, bool en);
static void enableExtDriver();
static void disableExtDriver();
static void encoderNext();
static void encoderPrev();
static void encoderEnter();
INDEV::enableExtDriver();
INDEV::encoderNext();
INDEV::encoderPrev();
INDEV::encoderEnter();
INDEV::disableExtDriver();
deleteIndev 会永久从 LVGL 删除匹配类型的全部输入设备,通常无法仅靠 enableIndev 恢复。enableIndev 只启用/禁用当前已注册的匹配设备;找不到时静默不操作。enableExtDriver 会启用编码器及光标、外部按键和键盘,并把键码类型设为 2。disableExtDriver 会禁用这些设备,并把键码类型恢复为 0。static void setKeycodeType(int type);
static void enableInputMode(bool en);
setKeycodeType:
0:SDL 原键码。1:SDL 原始扫描码。2:STT 外部按键顺序。KEYPADER::setKeycodeType(2);
KEYPADER::enableInputMode(false);
关闭输入模式可避免一次物理按键既触发 Buttoner/Encoder 事件,又向输入框输入字符。实现没有范围校验,只应传 0..2。
static bool enabled;
static void resetPoints();
static void sendId(int id);
static void enableButtoner(bool b);
static void sendCode(uint8_t code);
启用外部按键时会自动启用编码器:
BUTTONER::enableButtoner(true);
BUTTONER::sendCode(10); // 查找 buttonerCode == 10 的控件并触发
sendId 的 id 与当前 LVGL 编码器组中的控件顺序相关。sendCode 则先按控件配置的 buttonerCode 查找控件,再换算组索引。
注意事项:
resetPoints 会根据组中控件当前坐标重建按钮点数组。sendCode 和 resetPoints 都依赖运行时控件映射与 LVGL 对象仍然有效。enabled 会绕过驱动启停逻辑,应使用 enableButtoner。static void enableEncoder(bool b);
static void sendKey(uint32_t key);
static void sendChar(uint32_t c);
static int lastKey;
static bool enabled;
编码器键值:0 无按键、1 确认、2 上一个、3 下一个。
ENCODER::enableEncoder(true);
ENCODER::sendKey(3); // 下一个
ENCODER::sendKey(1); // 确认
ENCODER::sendChar('A');
禁用时 sendKey、sendChar 等大多数方法静默返回。应通过 enableEncoder 修改状态,不要直接写 enabled。
static void addObject(STTWidget* w, STTPage* frm = NULL);
static void addObjects(vector<void*> vec);
static void addObjects(vector<STTWidget*> vec);
static void removeObject(STTWidget* w);
static vector<void*> clearObjects();
static void focusObject(STTWidget* w);
static STTWidget* getFocused();
static void reset();
页面初始化示例:
ENCODER::enableEncoder(true);
ENCODER::clearObjects();
ENCODER::addObject(btnBack, currentPage);
ENCODER::addObject(txtName, currentPage);
ENCODER::addObject(btnOk, currentPage);
ENCODER::focusObject(btnBack);
clearObjects 返回清除前的原始 LVGL 对象指针列表,可用于临时保存和恢复:
std::vector<void*> oldGroup = ENCODER::clearObjects();
ENCODER::addObject(dialogOk);
// 对话框结束后:
ENCODER::clearObjects();
ENCODER::addObjects(oldGroup);
注意事项:
addObject 没有判空,w 必须有效。vector<void*> 重载要求每个指针都是真实有效的 lv_obj_t*。removeObject 不会同步清理页面保存的 encoder group 列表。getFocused 在编码器禁用、无焦点或对象无法映射回 STT 控件时返回 nullptr。reset 使用最后页面保存的焦点组和最后焦点恢复状态。static void focusNext();
static void focusPrev();
static void enter();
static void focusFreeze(bool b);
static void wrap(bool b);
static void editing(bool b);
ENCODER::wrap(true); // 最后一项之后回到第一项
ENCODER::editing(false); // 浏览模式
ENCODER::focusNext();
ENCODER::focusFreeze(true); // 暂时冻结当前焦点
ENCODER::focusFreeze(false);
ENCODER::enter();
这些方法作用于全局 LVGL group,不是每个页面独立的 group。
static void showCursor(bool b);
static void setCursorType(int type);
static void showOutline(bool b);
static void setOutline(int color, int width);
static void showWidgetOutline(STTWidget* w);
static int color;
static int width;
static bool isShowOutline;
ENCODER::showCursor(true);
ENCODER::setCursorType(1); // 0 箭头,1 手指
ENCODER::setOutline(0x00FF00, 3);
ENCODER::showOutline(true);
ENCODER::showWidgetOutline(widget);
showOutline 只修改全局标志,不会自动遍历已有控件应用样式;具体控件需要调用 showWidgetOutline。不要直接修改 color、width 或 isShowOutline。
static void addIme();
static void removeIme();
static void focusToImeOkButton();
static void openImeOnce(bool b);
static bool isImeOnce();
ENCODER::addIme();
ENCODER::focusToImeOkButton();
ENCODER::openImeOnce(true);
bool once = ENCODER::isImeOnce();
ENCODER::removeIme();
focusToImeOkButton 假设默认输入法对象、按键矩阵和 OK 键都存在,内部没有空指针检查。只能在输入法已经打开后调用。
NumPad 定义了以下类别:
KEY_0..KEY_9:ASCII 数字。KEY_A..KEY_Z:ASCII 大写字母。KEY_a..KEY_z:ASCII 小写字母。KEY_Point:.。KEY_BACK:退格。KEY_TAB:Tab,目前没有专门分支,会因不在可打印范围而不操作。KEY_ENTER:发送 LV_EVENT_VALUE_CHANGED。KEY_ESC:Escape,目前没有专门分支而不操作。KEY_DEL:向前删除。KEY_SIGN:正负号切换。KEY_CANCEL:发送 LV_EVENT_CANCEL。sendCodestatic void sendCode(uint8_t key);
NumPad::sendCode(NumPad::KEY_1);
NumPad::sendCode(NumPad::KEY_2);
NumPad::sendCode(NumPad::KEY_Point);
NumPad::sendCode(NumPad::KEY_5);
NumPad::sendCode(NumPad::KEY_SIGN);
NumPad::sendCode(NumPad::KEY_ENTER);
方法把输入发送给全局当前输入框 STTTextAreaInfo::currentInputTa。没有当前输入框时,大多数键静默忽略。
行为说明:
KEY_SIGN 在开头的 + 和 - 间切换;没有符号时插入 -,并同步系统变量 CurrentInput。KEY_BACK 删除光标前字符。KEY_DEL 删除光标后字符。KEY_ENTER 和 KEY_CANCEL 发送事件,不直接关闭键盘。STTTextArea 自身配置处理。应在 UI 线程调用,不要保存并跨页面复用旧的 currentInputTa 指针。
clear(),除非明确需要追加。socketReadAll 和 uartReadAll 至少提供 512 字节缓冲区,uartRead 至少提供 256 字节。\0 结束。simpleCalc、动态库路径、INI 键和进程名只能来自可信输入。getScrSize 和 NumPad 只在运行时及 LVGL 初始化完成后从 UI 线程调用。setMmaSizeSafe 优先于 setMmaSize,并纳入设备恢复和重启验证流程。DEBUG、BUTTONER、ENCODER 的公共静态状态字段,使用对应方法维持驱动状态一致。DEBUG::lastErrorString 不作线程安全和自动清理假设。rgb、设备列表接口和私有的 DEBUG::CPU 目前不应作为有效功能使用。