write_mac_tool/update_mac_pdd.c
2025-12-22 13:40:30 +08:00

1358 lines
49 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#include <fcntl.h>
#include <linux/input.h>
#include <poll.h>
#include <pthread.h>
#include <time.h>
#include <sys/wait.h>
#include "hiredis.h"
// 前置声明:黄灯控制接口
static void start_yellow_blink(void);
static void start_yellow_fast_blink(void);
static void stop_yellow_blink(void);
static void set_yellow_off(void);
static void set_yellow_on(void);
static void* reset_key_monitor_thread(void* arg);
static int detect_resetkey_gesture(void);
static int read_resetkey_value(void);
static void keepalive_forever(void);
static int get_hw_mac_from_interfaces(const char* interfaces_path, char* out_mac, size_t out_size);
// 前置声明Pigeon3.PDD.dll 检查和更新功能
static int calculate_file_md5(const char* filepath, char* out_md5, size_t out_size);
static int get_remote_file_md5(const char* host, const char* remote_path, char* out_md5, size_t out_size);
static int copy_remote_directory(const char* host, const char* remote_dir, const char* local_dir);
static int check_and_update_pigeon_dll(void);
static int check_program_upgrade(char** argv);
static int is_hex(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
static void to_upper_str(char* s) {
for (; *s; ++s) *s = (char)toupper((unsigned char)*s);
}
// 新增检测是否包含显式MAC带分隔符的形式用于避免误把批次号当MAC
static int has_explicit_mac_pattern(const char* input) {
if (!input) return 0;
size_t n = strlen(input);
// 1) 冒号分隔
for (size_t i = 0; i + 17 <= n; ++i) {
int ok = 1;
for (int seg = 0; seg < 6 && ok; ++seg) {
size_t pos = i + seg * 3;
if (!is_hex(input[pos]) || !is_hex(input[pos + 1])) { ok = 0; break; }
if (seg != 5) { if (input[pos + 2] != ':') { ok = 0; break; } }
}
if (ok) return 1;
}
// 2) 连字符分隔
for (size_t i = 0; i + 17 <= n; ++i) {
int ok = 1;
for (int seg = 0; seg < 6 && ok; ++seg) {
size_t pos = i + seg * 3;
if (!is_hex(input[pos]) || !is_hex(input[pos + 1])) { ok = 0; break; }
if (seg != 5) { if (input[pos + 2] != '-') { ok = 0; break; } }
}
if (ok) return 1;
}
return 0;
}
static void keepalive_forever(void) {
while (1) sleep(60);
}
// Remove separators and validate exactly 12 hex chars
static int normalize_raw_mac(const char* in, char* raw12, size_t raw12_size) {
if (!in || !raw12 || raw12_size < 13) return -1;
size_t len = strlen(in);
size_t j = 0;
for (size_t i = 0; i < len; ++i) {
char c = in[i];
if (c == ':' || c == '-' || c == ' ') continue;
if (!is_hex(c)) return -1;
if (j >= 12) return -1; // too long
raw12[j++] = c;
}
if (j != 12) return -1;
raw12[12] = '\0';
return 0;
}
static int format_mac_12_to_colon(const char* raw12, char* out, size_t out_size) {
if (!raw12 || !out || out_size < 18) return -1; // XX:XX:XX:XX:XX:XX\0
char upper[13];
strncpy(upper, raw12, 12);
upper[12] = '\0';
to_upper_str(upper);
snprintf(out, out_size, "%c%c:%c%c:%c%c:%c%c:%c%c:%c%c",
upper[0], upper[1], upper[2], upper[3], upper[4], upper[5],
upper[6], upper[7], upper[8], upper[9], upper[10], upper[11]);
return 0;
}
// Try to extract MAC from input string in various common formats
static int extract_mac(const char* input, char* out_mac, size_t out_size) {
if (!input || !out_mac || out_size < 18) return -1;
size_t n = strlen(input);
// 1) Look for colon-separated MAC: ([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}
for (size_t i = 0; i + 17 <= n; ++i) {
int ok = 1;
for (int seg = 0; seg < 6 && ok; ++seg) {
size_t pos = i + seg * 3;
if (!is_hex(input[pos]) || !is_hex(input[pos + 1])) { ok = 0; break; }
if (seg != 5) { if (input[pos + 2] != ':') { ok = 0; break; } }
}
if (ok) {
// Copy and uppercase
strncpy(out_mac, input + i, 17);
out_mac[17] = '\0';
to_upper_str(out_mac);
return 0;
}
}
// 2) Look for hyphen-separated MAC: ([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}
for (size_t i = 0; i + 17 <= n; ++i) {
int ok = 1;
for (int seg = 0; seg < 6 && ok; ++seg) {
size_t pos = i + seg * 3;
if (!is_hex(input[pos]) || !is_hex(input[pos + 1])) { ok = 0; break; }
if (seg != 5) { if (input[pos + 2] != '-') { ok = 0; break; } }
}
if (ok) {
char tmp[18];
strncpy(tmp, input + i, 17);
tmp[17] = '\0';
// Normalize to 12 hex then colon
char raw12[13];
if (normalize_raw_mac(tmp, raw12, sizeof(raw12)) == 0 &&
format_mac_12_to_colon(raw12, out_mac, out_size) == 0) {
return 0;
}
}
}
// 3) JSON-like: "mac":"XXXXXXXXXXXX" or with colons
const char* key_variants[] = { "\"mac\"", "MAC", "mac", "Mac" };
for (size_t kv = 0; kv < sizeof(key_variants)/sizeof(key_variants[0]); ++kv) {
const char* p = strstr(input, key_variants[kv]);
if (p) {
// find delimiter ':' then take next token until quote/space
p = strchr(p, ':');
if (p) {
++p; // at value start (maybe quotes)
while (*p == ' ' || *p == '"') ++p;
// copy until non-hex or separator
char rawbuf[64] = {0};
size_t j = 0;
while (*p && j < sizeof(rawbuf)-1) {
if (is_hex(*p) || *p == ':' || *p == '-' ) {
rawbuf[j++] = *p;
++p;
} else break;
}
rawbuf[j] = '\0';
char raw12[13];
if (normalize_raw_mac(rawbuf, raw12, sizeof(raw12)) == 0 &&
format_mac_12_to_colon(raw12, out_mac, out_size) == 0) {
return 0;
}
}
}
}
// 4) Fallback: first 12 consecutive hex chars
for (size_t i = 0; i < n; ++i) {
if (is_hex(input[i])) {
size_t j = 0;
char raw12[13];
size_t k = i;
while (k < n && is_hex(input[k]) && j < 12) {
raw12[j++] = input[k++];
}
if (j == 12) {
raw12[12] = '\0';
if (format_mac_12_to_colon(raw12, out_mac, out_size) == 0) {
return 0;
}
}
}
}
return -1; // not found
}
// 新增:提取批次号(支持 batch=XXXX / Batch:XXXX / 扫码包含“批次”关键字)
static int extract_batch(const char* input, char* out_batch, size_t out_size) {
if (!input || !out_batch || out_size < 16) return -1;
size_t n = strlen(input);
// 主匹配:任意位置出现 'D' 或 'd' 后跟 14 位数字
for (size_t i = 0; i + 15 <= n; ++i) {
char c = input[i];
if (c == 'D' || c == 'd') {
int ok = 1;
for (int k = 1; k <= 14; ++k) {
char d = input[i + k];
if (d < '0' || d > '9') { ok = 0; break; }
}
if (ok) {
out_batch[0] = 'D';
memcpy(out_batch + 1, input + i + 1, 14);
out_batch[15] = '\0';
return 0;
}
}
}
// 回退按键名解析batch/Batch/BATCH/批次)
const char* keys[] = {"batch", "Batch", "BATCH", "批次"};
for (size_t i = 0; i < sizeof(keys)/sizeof(keys[0]); ++i) {
const char* p = strstr(input, keys[i]);
if (!p) continue;
// 跳过键名后的分隔符
p += strlen(keys[i]);
while (*p == ' ' || *p == '=' || *p == ':' || (unsigned char)*p == 0xEF) {
if ((unsigned char)*p == 0xEF) { p++; if ((unsigned char)*p == 0xBC) { p++; if ((unsigned char)*p == 0x9A) { p++; } } }
else { p++; }
}
// 读取字母数字序列作为批次号
size_t j = 0;
while (*p && j < out_size - 1) {
if ((*p >= '0' && *p <= '9') || (*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || *p == '_' || *p == '-') {
out_batch[j++] = *p++;
} else {
break;
}
}
out_batch[j] = '\0';
if (j > 0) {
// 规范化:若形如 d+14位数字转为大写D
if (j >= 15 && (out_batch[0] == 'd' || out_batch[0] == 'D')) {
int ok2 = 1;
for (int k = 1; k <= 14; ++k) {
char d = out_batch[k];
if (d < '0' || d > '9') { ok2 = 0; break; }
}
if (ok2) out_batch[0] = 'D';
}
return 0;
}
}
const char* p = strstr(input, "batch");
if (p) {
p += 5;
size_t j = 0;
while (*p && j < out_size - 1 && ((*p >= '0' && *p <= '9') || (*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z'))) {
out_batch[j++] = *p++;
}
out_batch[j] = '\0';
if (j > 0) {
if (j >= 15 && (out_batch[0] == 'd' || out_batch[0] == 'D')) {
int ok2 = 1;
for (int k = 1; k <= 14; ++k) {
char d = out_batch[k];
if (d < '0' || d > '9') { ok2 = 0; break; }
}
if (ok2) out_batch[0] = 'D';
}
return 0;
}
}
return -1;
}
// 通过redis-cli查询批次映射得到MAC
static int redis_query_mac(const char* host_opt, const char* port_opt, const char* db_opt,
const char* pool_opt, const char* batch, char* out_mac, size_t out_size) {
if (!batch || !out_mac || out_size < 18) return -1;
const char* mock = getenv("REDIS_MOCK_MAC");
if (mock && *mock) {
fprintf(stderr, "[redis] use REDIS_MOCK_MAC=%s\n", mock);
if (extract_mac(mock, out_mac, out_size) == 0) return 0;
strncpy(out_mac, mock, out_size - 1);
out_mac[out_size - 1] = '\0';
return 0;
}
const char* host = host_opt ? host_opt : (getenv("REDIS_HOST") ? getenv("REDIS_HOST") : "180.163.74.83");
int port = port_opt ? atoi(port_opt) : (getenv("REDIS_PORT") ? atoi(getenv("REDIS_PORT")) : 6379);
int db = db_opt ? atoi(db_opt) : (getenv("REDIS_DB") ? atoi(getenv("REDIS_DB")) : 0);
const char* pool = pool_opt ? pool_opt : (getenv("REDIS_POOL") ? getenv("REDIS_POOL") : "batch_sn_mapping_pdd");
const char* auth = getenv("REDIS_AUTH");
if (!auth || !*auth) auth = "Zzh08165511";
fprintf(stderr, "[redis] connect host=%s port=%d db=%d pool=%s batch=%s\n", host, port, db, pool, batch);
struct timeval tv; tv.tv_sec = 3; tv.tv_usec = 0;
redisContext* c = redisConnectWithTimeout(host, port, tv);
if (!c || c->err) {
fprintf(stderr, "[redis] connect error: %s\n", c ? c->errstr : "unknown");
if (c) redisFree(c);
return -1;
}
if (auth && *auth) {
redisReply* ra = (redisReply*)redisCommand(c, "AUTH %s", auth);
if (!ra || (ra->type == REDIS_REPLY_ERROR)) {
fprintf(stderr, "[redis] AUTH error: %s\n", ra && ra->str ? ra->str : "no-reply");
if (ra) freeReplyObject(ra);
redisFree(c);
return -1;
}
fprintf(stderr, "[redis] AUTH ok\n");
freeReplyObject(ra);
}
if (db > 0) {
redisReply* rs = (redisReply*)redisCommand(c, "SELECT %d", db);
if (!rs || (rs->type == REDIS_REPLY_ERROR)) {
fprintf(stderr, "[redis] SELECT %d error: %s\n", db, rs && rs->str ? rs->str : "no-reply");
if (rs) freeReplyObject(rs);
redisFree(c);
return -1;
}
fprintf(stderr, "[redis] SELECT %d ok\n", db);
freeReplyObject(rs);
}
// 尝试 HGET pool batch
redisReply* r = (redisReply*)redisCommand(c, "HGET %s %s", pool, batch);
if (r) {
fprintf(stderr, "[redis] HGET %s %s -> type=%d len=%ld\n", pool, batch, r->type, (long)(r->type==REDIS_REPLY_STRING ? r->len : 0));
if (r->type == REDIS_REPLY_STRING && r->str && r->len > 0) {
fprintf(stderr, "[redis] HGET value: %.*s\n", (int)((r->len>128)?128:r->len), r->str);
if (extract_mac(r->str, out_mac, out_size) == 0) { freeReplyObject(r); redisFree(c); return 0; }
}
freeReplyObject(r);
} else {
fprintf(stderr, "[redis] HGET no-reply\n");
}
// 回退 GET pool:batch
r = (redisReply*)redisCommand(c, "GET %s:%s", pool, batch);
if (r) {
fprintf(stderr, "[redis] GET %s:%s -> type=%d len=%ld\n", pool, batch, r->type, (long)(r->type==REDIS_REPLY_STRING ? r->len : 0));
if (r->type == REDIS_REPLY_STRING && r->str && r->len > 0) {
fprintf(stderr, "[redis] GET value: %.*s\n", (int)((r->len>128)?128:r->len), r->str);
if (extract_mac(r->str, out_mac, out_size) == 0) { freeReplyObject(r); redisFree(c); return 0; }
}
freeReplyObject(r);
} else {
fprintf(stderr, "[redis] GET no-reply\n");
}
fprintf(stderr, "[redis] not found or value not parsable as MAC\n");
redisFree(c);
return -1;
}
// 审计在Redis记录每次使用的MAC、批次与时间
static int redis_audit_log(const char* host_opt, const char* port_opt, const char* db_opt,
const char* audit_key_opt, const char* batch, const char* mac, const char* note_opt) {
if (!batch || !*batch || !mac || !*mac) return -1;
const char* host = host_opt ? host_opt : (getenv("REDIS_HOST") ? getenv("REDIS_HOST") : "180.163.74.83");
int port = port_opt ? atoi(port_opt) : (getenv("REDIS_PORT") ? atoi(getenv("REDIS_PORT")) : 6379);
int db = db_opt ? atoi(db_opt) : (getenv("REDIS_DB") ? atoi(getenv("REDIS_DB")) : 0);
const char* auth = getenv("REDIS_AUTH");
if (!auth || !*auth) auth = "Zzh08165511";
const char* audit_key = audit_key_opt ? audit_key_opt : (getenv("REDIS_AUDIT_KEY") ? getenv("REDIS_AUDIT_KEY") : "mac_batch_audit_pdd");
struct timeval tv; tv.tv_sec = 3; tv.tv_usec = 0;
redisContext* c = redisConnectWithTimeout(host, port, tv);
if (!c || c->err) {
fprintf(stderr, "[redis-audit] connect error: %s\n", c ? c->errstr : "unknown");
if (c) redisFree(c);
return -1;
}
if (auth && *auth) {
redisReply* ra = (redisReply*)redisCommand(c, "AUTH %s", auth);
if (!ra || (ra->type == REDIS_REPLY_ERROR)) {
fprintf(stderr, "[redis-audit] AUTH error: %s\n", ra && ra->str ? ra->str : "no-reply");
if (ra) freeReplyObject(ra);
redisFree(c);
return -1;
}
freeReplyObject(ra);
}
if (db > 0) {
redisReply* rs = (redisReply*)redisCommand(c, "SELECT %d", db);
if (!rs || (rs->type == REDIS_REPLY_ERROR)) {
fprintf(stderr, "[redis-audit] SELECT %d error: %s\n", db, rs && rs->str ? rs->str : "no-reply");
if (rs) freeReplyObject(rs);
redisFree(c);
return -1;
}
freeReplyObject(rs);
}
// 优先使用 Redis 服务器时间,避免设备本地时间不准
long long sv_secs = -1;
{
redisReply* tr = (redisReply*)redisCommand(c, "TIME");
if (tr && tr->type == REDIS_REPLY_ARRAY && tr->elements >= 2 &&
tr->element[0] && tr->element[0]->str) {
sv_secs = atoll(tr->element[0]->str);
} else {
fprintf(stderr, "[redis-audit] TIME failed, fallback to local time\n");
}
if (tr) freeReplyObject(tr);
}
time_t base = (sv_secs > 0) ? (time_t)sv_secs : time(NULL);
// 中国时区(+08:00不依赖设备TZ格式为 YYYY-MM-DD HH:MM:SS
time_t base_cn = base + 8 * 3600;
struct tm tm_sv_cn; gmtime_r(&base_cn, &tm_sv_cn);
char ts_cn[24];
strftime(ts_cn, sizeof(ts_cn), "%Y-%m-%d %H:%M:%S", &tm_sv_cn);
char val[320];
snprintf(val, sizeof(val), "ts_cn=%s batch=%s mac=%s%s%s", ts_cn, batch, mac,
(note_opt && *note_opt) ? " note=" : "",
(note_opt && *note_opt) ? note_opt : "");
// 写入总审计列表
redisReply* r = (redisReply*)redisCommand(c, "LPUSH %s %s", audit_key, val);
if (!r || r->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "[redis-audit] LPUSH %s failed: %s\n", audit_key, r && r->str ? r->str : "no-reply");
if (r) freeReplyObject(r);
redisFree(c);
return -1;
}
freeReplyObject(r);
// 同时按批次维度记录key为 <audit_key>:<batch>
char batch_key[128];
snprintf(batch_key, sizeof(batch_key), "%s:%s", audit_key, batch);
r = (redisReply*)redisCommand(c, "LPUSH %s %s", batch_key, val);
if (!r || r->type == REDIS_REPLY_ERROR) {
fprintf(stderr, "[redis-audit] LPUSH %s failed: %s\n", batch_key, r && r->str ? r->str : "no-reply");
if (r) freeReplyObject(r);
redisFree(c);
return -1;
}
freeReplyObject(r);
redisFree(c);
fprintf(stderr, "[redis-audit] logged: %s | %s\n", batch, mac);
return 0;
}
static int read_file(const char* path, char** out_buf, size_t* out_len) {
FILE* f = fopen(path, "rb");
if (!f) return -1;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; }
long sz = ftell(f);
if (sz < 0) { fclose(f); return -1; }
rewind(f);
char* buf = (char*)malloc((size_t)sz + 1);
if (!buf) { fclose(f); return -1; }
size_t n = fread(buf, 1, (size_t)sz, f);
fclose(f);
buf[n] = '\0';
*out_buf = buf;
if (out_len) *out_len = n;
return 0;
}
static int write_file(const char* path, const char* buf, size_t len) {
FILE* f = fopen(path, "wb");
if (!f) return -1;
size_t n = fwrite(buf, 1, len, f);
// 确保数据写入到内核缓冲区
fflush(f);
int rc = 0;
// 将数据与元数据刷新到磁盘,避免重启丢失
int fd = fileno(f);
if (fd >= 0) {
if (fsync(fd) != 0) rc = -1;
}
fclose(f);
if (n != len) rc = -1;
return rc;
}
static int update_hwaddress_in_interfaces(const char* interfaces_path, const char* mac_colon) {
// 如果当前MAC与目标一致则不改写
char current[64] = {0};
if (get_hw_mac_from_interfaces(interfaces_path, current, sizeof(current)) == 0) {
to_upper_str(current);
if (strcmp(current, mac_colon) == 0) {
printf("MAC 未变化,跳过写入: %s\n", mac_colon);
return 0;
}
}
const char* tmpl =
"# interfaces(5) file used by ifup(8) and ifdown(8)\n"
"# Include files from /etc/network/interfaces.d:\n"
"source-directory /etc/network/interfaces.d\n"
"auto lo\n"
"iface lo inet loopback\n"
"auto eth0\n"
"iface eth0 inet static\n"
"address 10.10.12.12\n"
"netmask 255.255.255.0\n"
"gateway 10.10.12.1\n"
"hwaddress ether %s\n";
char* newbuf = (char*)malloc(1024);
if (!newbuf) {
fprintf(stderr, "malloc failed\n");
return -1;
}
int n = snprintf(newbuf, 1024, tmpl, mac_colon);
if (n <= 0) {
free(newbuf);
return -1;
}
int rc = write_file(interfaces_path, newbuf, (size_t)n);
if (rc == 0) {
sync();
printf("写入完整 interfaces 文件并更新MAC: %s\n", mac_colon);
} else {
fprintf(stderr, "写入失败 %s\n", interfaces_path);
}
free(newbuf);
return rc;
}
static int get_hw_mac_from_interfaces(const char* interfaces_path, char* out_mac, size_t out_size) {
char* content = NULL; size_t len = 0;
if (read_file(interfaces_path, &content, &len) != 0 || !content) {
return -1;
}
const char* needle = "hwaddress ether";
char* pos = strstr(content, needle);
if (!pos) { free(content); return -1; }
pos += strlen(needle);
while (*pos == ' ') ++pos;
size_t j = 0;
while (*pos && *pos != '\n' && j < out_size - 1) {
if (*pos == ' ' || *pos == '\r' || *pos == '\t') break;
out_mac[j++] = *pos++;
}
out_mac[j] = '\0';
free(content);
return j > 0 ? 0 : -1;
}
static int scanner_read_event2(char* out, size_t out_size) {
const char* force_stdin = getenv("SCANNER_FORCE_STDIN");
if (force_stdin && strcmp(force_stdin, "1") == 0) return -1;
int fd = open("/dev/input/event2", O_RDONLY);
if (fd < 0) return -1;
struct input_event ev;
size_t j = 0;
int shift = 0;
while (1) {
ssize_t r = read(fd, &ev, sizeof(ev));
if (r <= 0) continue;
if (ev.type != EV_KEY) continue;
// 维护Shift状态
if (ev.code == KEY_LEFTSHIFT || ev.code == KEY_RIGHTSHIFT) {
if (ev.value == 1) shift = 1; // 按下
else if (ev.value == 0) shift = 0; // 松开
continue;
}
// 只在松开(key up)时采集字符,避免重复
if (ev.value != 0) {
if (ev.code == KEY_ENTER || ev.code == KEY_KPENTER) {
break;
}
continue;
}
char ch = 0;
switch (ev.code) {
// 数字键行
case KEY_1: ch = shift ? '!' : '1'; break;
case KEY_2: ch = shift ? '@' : '2'; break;
case KEY_3: ch = shift ? '#' : '3'; break;
case KEY_4: ch = shift ? '$' : '4'; break;
case KEY_5: ch = shift ? '%' : '5'; break;
case KEY_6: ch = shift ? '^' : '6'; break;
case KEY_7: ch = shift ? '&' : '7'; break;
case KEY_8: ch = shift ? '*' : '8'; break;
case KEY_9: ch = shift ? '(' : '9'; break;
case KEY_0: ch = shift ? ')' : '0'; break;
// 小键盘数字
case KEY_KP1: ch = '1'; break; case KEY_KP2: ch = '2'; break; case KEY_KP3: ch = '3'; break;
case KEY_KP4: ch = '4'; break; case KEY_KP5: ch = '5'; break; case KEY_KP6: ch = '6'; break;
case KEY_KP7: ch = '7'; break; case KEY_KP8: ch = '8'; break; case KEY_KP9: ch = '9'; break;
case KEY_KP0: ch = '0'; break;
// 字母(显式映射,避免假设键码连续)
case KEY_A: ch = shift ? 'A' : 'a'; break;
case KEY_B: ch = shift ? 'B' : 'b'; break;
case KEY_C: ch = shift ? 'C' : 'c'; break;
case KEY_D: ch = shift ? 'D' : 'd'; break;
case KEY_E: ch = shift ? 'E' : 'e'; break;
case KEY_F: ch = shift ? 'F' : 'f'; break;
case KEY_G: ch = shift ? 'G' : 'g'; break;
case KEY_H: ch = shift ? 'H' : 'h'; break;
case KEY_I: ch = shift ? 'I' : 'i'; break;
case KEY_J: ch = shift ? 'J' : 'j'; break;
case KEY_K: ch = shift ? 'K' : 'k'; break;
case KEY_L: ch = shift ? 'L' : 'l'; break;
case KEY_M: ch = shift ? 'M' : 'm'; break;
case KEY_N: ch = shift ? 'N' : 'n'; break;
case KEY_O: ch = shift ? 'O' : 'o'; break;
case KEY_P: ch = shift ? 'P' : 'p'; break;
case KEY_Q: ch = shift ? 'Q' : 'q'; break;
case KEY_R: ch = shift ? 'R' : 'r'; break;
case KEY_S: ch = shift ? 'S' : 's'; break;
case KEY_T: ch = shift ? 'T' : 't'; break;
case KEY_U: ch = shift ? 'U' : 'u'; break;
case KEY_V: ch = shift ? 'V' : 'v'; break;
case KEY_W: ch = shift ? 'W' : 'w'; break;
case KEY_X: ch = shift ? 'X' : 'x'; break;
case KEY_Y: ch = shift ? 'Y' : 'y'; break;
case KEY_Z: ch = shift ? 'Z' : 'z'; break;
// 其他符号
case KEY_MINUS: ch = shift ? '_' : '-'; break;
case KEY_SEMICOLON: ch = shift ? ':' : ';'; break;
case KEY_APOSTROPHE: ch = shift ? '"' : '\''; break;
case KEY_BACKSLASH: ch = shift ? '|' : '\\'; break;
case KEY_EQUAL: ch = shift ? '+' : '='; break;
default: break;
}
if (ch) {
if (j < out_size - 1) out[j++] = ch;
}
}
out[j] = '\0';
close(fd);
return j > 0 ? 0 : -1;
}
static int read_qrcode_string(char* out, size_t out_size) {
// Try hardware scanner first
printf("尝试读取硬件扫码内容...\n");
if (scanner_read_event2(out, out_size) == 0) return 0;
// Fallback: read a line from stdin (useful for piping during tests)
fprintf(stdout, "请在终端输入扫码内容并回车...\n");
if (fgets(out, (int)out_size, stdin) == NULL) return -1;
// trim newline
size_t n = strlen(out);
if (n && out[n-1] == '\n') out[n-1] = '\0';
return strlen(out) > 0 ? 0 : -1;
}
// 计算文件的MD5值
static int calculate_file_md5(const char* filepath, char* out_md5, size_t out_size) {
if (!filepath || !out_md5 || out_size < 33) return -1;
char cmd[512];
snprintf(cmd, sizeof(cmd), "md5sum '%s' 2>/dev/null | cut -d' ' -f1", filepath);
FILE* fp = popen(cmd, "r");
if (!fp) {
fprintf(stderr, "无法执行md5sum命令计算文件MD5\n");
return -1;
}
if (fgets(out_md5, (int)out_size, fp) == NULL) {
pclose(fp);
fprintf(stderr, "无法读取文件MD5值: %s\n", filepath);
return -1;
}
pclose(fp);
// 去除换行符
size_t len = strlen(out_md5);
if (len > 0 && out_md5[len-1] == '\n') {
out_md5[len-1] = '\0';
}
// 验证MD5格式32个十六进制字符
if (strlen(out_md5) != 32) {
fprintf(stderr, "本地文件MD5格式无效: %s\n", out_md5);
return -1;
}
return 0;
}
// 获取远程文件的MD5值
static int get_remote_file_md5(const char* host, const char* remote_path, char* out_md5, size_t out_size) {
if (!host || !remote_path || !out_md5 || out_size < 33) return -1;
char cmd[512];
snprintf(cmd, sizeof(cmd), "/opt/sshpass_arm64 -p 'Zzh08165511' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@%s 'md5sum %s 2>/dev/null | cut -d\" \" -f1'", host, remote_path);
FILE* fp = popen(cmd, "r");
if (!fp) {
fprintf(stderr, "无法执行SSH命令获取远程文件MD5\n");
return -1;
}
if (fgets(out_md5, (int)out_size, fp) == NULL) {
pclose(fp);
fprintf(stderr, "无法读取远程文件MD5值\n");
return -1;
}
pclose(fp);
// 去除换行符
size_t len = strlen(out_md5);
if (len > 0 && out_md5[len-1] == '\n') {
out_md5[len-1] = '\0';
}
// 验证MD5格式32个十六进制字符
if (strlen(out_md5) != 32) {
fprintf(stderr, "远程文件MD5格式无效: %s\n", out_md5);
return -1;
}
return 0;
}
// 复制远程目录到本地
static int copy_remote_directory(const char* host, const char* remote_dir, const char* local_dir) {
if (!host || !remote_dir || !local_dir) return -1;
char cmd[512];
snprintf(cmd, sizeof(cmd), "/opt/sshpass_arm64 -p 'Zzh08165511' scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -r root@%s:%s/* %s/", host, remote_dir, local_dir);
fprintf(stdout, "正在复制远程目录: %s:%s -> %s\n", host, remote_dir, local_dir);
int result = system(cmd);
if (result != 0) {
fprintf(stderr, "复制远程目录失败,返回码: %d\n", result);
return -1;
}
fprintf(stdout, "远程目录复制完成\n");
// 同步文件系统,确保数据写入磁盘
fprintf(stdout, "正在同步文件系统...\n");
sync();
fprintf(stdout, "文件系统同步完成\n");
return 0;
}
// 检查并更新Pigeon3.PDD.dll
static int check_and_update_pigeon_dll(void) {
const char* remote_host = "180.163.74.83";
const char* remote_file = "/opt/pdd_update/Pigeon3.PDD.dll";
const char* local_file = "/root/estation/Pigeon3.PDD.dll";
const char* remote_dir = "/opt/pdd_update";
const char* local_dir = "/root/estation";
char remote_md5[64] = {0};
char local_md5[64] = {0};
fprintf(stdout, "开始检查Pigeon3.PDD.dll文件...\n");
// 获取远程文件MD5
if (get_remote_file_md5(remote_host, remote_file, remote_md5, sizeof(remote_md5)) != 0) {
fprintf(stderr, "获取远程文件MD5失败\n");
return -1;
}
fprintf(stdout, "远程文件MD5: %s\n", remote_md5);
// 获取本地文件MD5
if (calculate_file_md5(local_file, local_md5, sizeof(local_md5)) != 0) {
fprintf(stderr, "本地文件不存在或无法读取,将进行强制更新\n");
strcpy(local_md5, ""); // 设置为空,强制更新
} else {
fprintf(stdout, "本地文件MD5: %s\n", local_md5);
}
// 比较MD5值
if (strcmp(remote_md5, local_md5) == 0) {
fprintf(stdout, "文件MD5相同无需更新\n");
return 0;
}
fprintf(stdout, "文件MD5不同开始强制覆盖更新...\n");
start_yellow_fast_blink();
sleep(2);
// 复制远程目录到本地
if (copy_remote_directory(remote_host, remote_dir, local_dir) != 0) {
fprintf(stderr, "更新失败\n");
return -1;
}
// 验证更新后的文件MD5
char updated_md5[64] = {0};
if (calculate_file_md5(local_file, updated_md5, sizeof(updated_md5)) == 0) {
if (strcmp(remote_md5, updated_md5) == 0) {
fprintf(stdout, "文件更新成功MD5验证通过\n");
} else {
fprintf(stderr, "警告更新后文件MD5不匹配\n");
}
}
set_yellow_on();
sleep(2);
return 0;
}
// 检查程序是否需要升级
static int check_program_upgrade(char** argv) {
const char* remote_host = "180.163.74.83";
const char* remote_program = "/opt/pdd_update/Pigeon3.PDD.dll";
const char* local_program = "/root/estation/Pigeon3.PDD.dll";
char remote_md5[64] = {0};
char local_md5[64] = {0};
fprintf(stdout, "检查程序是否需要升级...\n");
// 获取远程程序文件MD5
if (get_remote_file_md5(remote_host, remote_program, remote_md5, sizeof(remote_md5)) != 0) {
fprintf(stderr, "获取远程程序文件MD5失败跳过升级检查\n");
return 0; // 不影响主程序运行
}
fprintf(stdout, "远程程序MD5: %s\n", remote_md5);
// 获取本地程序文件MD5
if (calculate_file_md5(local_program, local_md5, sizeof(local_md5)) != 0) {
fprintf(stderr, "本地程序文件不存在或无法读取,将进行程序更新\n");
strcpy(local_md5, ""); // 设置为空,强制更新
} else {
fprintf(stdout, "本地程序MD5: %s\n", local_md5);
}
// 比较MD5值
if (strcmp(remote_md5, local_md5) == 0) {
fprintf(stdout, "程序无需升级\n");
return 0;
}
fprintf(stdout, "发现程序更新,开始复制远程目录...\n");
start_yellow_fast_blink();
const char* remote_dir = "/opt/pdd_update";
const char* local_dir = "/root/estation";
// 复制远程目录到本地
if (copy_remote_directory(remote_host, remote_dir, local_dir) != 0) {
fprintf(stderr, "程序升级失败\n");
stop_yellow_blink();
return -1;
}
// 验证更新后的文件MD5
char updated_md5[64] = {0};
if (calculate_file_md5(local_program, updated_md5, sizeof(updated_md5)) == 0) {
if (strcmp(remote_md5, updated_md5) == 0) {
fprintf(stdout, "程序升级成功MD5验证通过\n");
} else {
fprintf(stderr, "警告升级后程序MD5不匹配\n");
}
}
set_yellow_on();
sync();
sleep(2);
fprintf(stdout, "程序升级完成\n");
return 0;
}
int main(int argc, char** argv) {
const char* interfaces_path = (argc >= 3) ? argv[2] : "/etc/network/interfaces";
setvbuf(stderr, NULL, _IONBF, 0);
{
pthread_t reset_thread;
if (pthread_create(&reset_thread, NULL, reset_key_monitor_thread, NULL) == 0) {
pthread_detach(reset_thread);
}
}
if (argc >= 2 && strcmp(argv[1], "--monitor") == 0) {
while (1) sleep(60);
}
if (argc < 2) {
// No scan input provided: check current MAC and if default, loop until scanned and updated
char current_mac[64] = {0};
if (get_hw_mac_from_interfaces(interfaces_path, current_mac, sizeof(current_mac)) != 0) {
fprintf(stderr, "无法读取 %s 中的MAC\n", interfaces_path);
return 1;
}
to_upper_str(current_mac);
if (strcmp(current_mac, "90:A9:F7:30:00:00") == 0) {
fprintf(stdout, "检测到默认MAC %s开始等待扫码...\n", current_mac);
start_yellow_blink();
while (1) {
char scanbuf[512] = {0};
if (read_qrcode_string(scanbuf, sizeof(scanbuf)) != 0) {
fprintf(stderr, "读取扫码内容失败,重试...\n");
continue;
}
fprintf(stdout, "扫码原始内容: %s\n", scanbuf);
// 优先按批次从Redis查询目标MAC
char batch[128] = {0};
char mac_from_redis[32] = {0};
if (extract_batch(scanbuf, batch, sizeof(batch)) == 0) {
fprintf(stdout, "识别到批次号: %s正在查询Redis...\n", batch);
if (redis_query_mac(NULL, NULL, NULL, NULL, batch, mac_from_redis, sizeof(mac_from_redis)) == 0) {
fprintf(stdout, "Redis返回MAC: %s\n", mac_from_redis);
to_upper_str(mac_from_redis);
if (strcmp(mac_from_redis, "90:A9:F7:30:00:00") == 0) {
fprintf(stderr, "Redis返回为默认MAC请确认并重新扫码...\n");
continue;
}
if (update_hwaddress_in_interfaces(interfaces_path, mac_from_redis) == 0) {
stop_yellow_blink();
sleep(2);
fprintf(stdout, "已更新MAC为: %s\n", mac_from_redis);
// 记录审计批次、MAC、时间
redis_audit_log(NULL, NULL, NULL, NULL, batch, mac_from_redis, "auto-loop");
sync();
// 检查并更新Pigeon3.PDD.dll
fprintf(stdout, "MAC写入成功开始检查Pigeon3.PDD.dll...\n");
if (check_and_update_pigeon_dll() != 0) {
fprintf(stderr, "Pigeon3.PDD.dll检查/更新失败但MAC已成功写入\n");
}
char verify_mac[64] = {0};
if (get_hw_mac_from_interfaces(interfaces_path, verify_mac, sizeof(verify_mac)) == 0) {
to_upper_str(verify_mac);
if (strcmp(verify_mac, "90:A9:F7:30:00:00") != 0) {
set_yellow_off();
fprintf(stdout, "文件更新完成黄灯已常亮MAC非默认\n");
} else {
fprintf(stderr, "写入后仍为默认MAC黄灯保持关闭或闪烁。\n");
}
}
break;
} else {
fprintf(stderr, "写入失败,重试...\n");
continue;
}
} else {
// Redis失败时若仅有批次号且不含显式MAC不再回退使用批次内容当MAC
if (!has_explicit_mac_pattern(scanbuf)) {
fprintf(stderr, "Redis未查询到该批次对应MAC或连接失败且扫码内容不含显式MAC如 12:34:56:78:9A:BC请重新扫码或确保Redis可用。\n");
continue;
}
fprintf(stderr, "Redis未查询到该批次对应MAC或连接失败回退使用扫码中的MAC...\n");
}
}
// 若未识别到批次号,给出提示
if (extract_batch(scanbuf, batch, sizeof(batch)) != 0) {
fprintf(stderr, "未识别到批次号回退使用扫码中的MAC...\n");
}
// 回退直接从扫码内容提取MAC此处允许无分隔的12位HEX
char mac_colon[32] = {0};
if (extract_mac("90:A9:F7:30:00:00", mac_colon, sizeof(mac_colon)) != 0) {
fprintf(stderr, "未识别到有效MAC重试...\n");
continue;
}
to_upper_str(mac_colon);
if (strcmp(mac_colon, "90:A9:F7:30:00:00") == 0) {
fprintf(stderr, "扫描到默认MAC请重新扫码...\n");
continue;
}
if (update_hwaddress_in_interfaces(interfaces_path, mac_colon) == 0) {
fprintf(stdout, "已更新MAC为: %s\n", mac_colon);
// 检查并更新Pigeon3.PDD.dll
fprintf(stdout, "MAC写入成功开始检查Pigeon3.PDD.dll...\n");
if (check_and_update_pigeon_dll() != 0) {
fprintf(stderr, "Pigeon3.PDD.dll检查/更新失败但MAC已成功写入\n");
}
set_yellow_off();
fprintf(stdout, "文件更新完成,黄灯已常亮。\n");
break;
} else {
fprintf(stderr, "写入失败,重试...\n");
}
}
keepalive_forever();
return 0;
} else {
// 若非默认MAC确保黄灯关闭
fprintf(stdout, "当前MAC非默认(%s),无需更新。\n", current_mac);
// MAC非默认时检查程序是否需要升级
if (check_program_upgrade(argv) != 0) {
fprintf(stderr, "程序升级检查失败\n");
}
set_yellow_off();
keepalive_forever();
return 0;
}
}
// Legacy path: user provides scan string directly
fprintf(stderr, "Example: %s 'batch=202501;MAC=90A9F7F032E8' ./interfaces\n", argv[0]);
const char* scan = argv[1];
// 先按批次从Redis查询
char batch[128] = {0};
char mac_from_redis[32] = {0};
fprintf(stdout, "扫码原始内容: %s\n", scan);
if (extract_batch(scan, batch, sizeof(batch)) == 0) {
fprintf(stdout, "识别到批次号: %s正在查询Redis...\n", batch);
if (redis_query_mac(NULL, NULL, NULL, NULL, batch, mac_from_redis, sizeof(mac_from_redis)) == 0) {
fprintf(stdout, "Redis返回MAC: %s\n", mac_from_redis);
if (update_hwaddress_in_interfaces(interfaces_path, mac_from_redis) != 0) {
return 3;
}
// 记录审计批次、MAC、时间
redis_audit_log(NULL, NULL, NULL, NULL, batch, mac_from_redis, "legacy");
// 检查并更新Pigeon3.PDD.dll
fprintf(stdout, "MAC写入成功开始检查Pigeon3.PDD.dll...\n");
if (check_and_update_pigeon_dll() != 0) {
fprintf(stderr, "Pigeon3.PDD.dll检查/更新失败但MAC已成功写入\n");
}
char verify_mac[64] = {0};
if (get_hw_mac_from_interfaces(interfaces_path, verify_mac, sizeof(verify_mac)) == 0) {
to_upper_str(verify_mac);
if (strcmp(verify_mac, "90:A9:F7:30:00:00") != 0) {
set_yellow_off();
fprintf(stdout, "文件更新完成黄灯已常亮MAC非默认\n");
} else {
fprintf(stderr, "写入后仍为默认MAC黄灯保持关闭或闪烁。\n");
}
}
return 0;
} else {
if (!has_explicit_mac_pattern(scan)) {
fprintf(stderr, "Redis未查询到该批次对应MAC或连接失败且扫码内容不含显式MAC如 12:34:56:78:9A:BC请重新扫码或确保Redis可用。\n");
return 2;
}
fprintf(stderr, "Redis未查询到该批次对应MAC或连接失败回退使用扫码中的MAC...\n");
}
}
// 若未识别到批次号,给出提示
if (extract_batch(scan, batch, sizeof(batch)) != 0) {
fprintf(stderr, "未识别到批次号回退使用扫码中的MAC...\n");
}
char mac_colon[32] = {0};
if (extract_mac(scan, mac_colon, sizeof(mac_colon)) != 0) {
fprintf(stderr, "Failed to extract MAC from input: %s\n", scan);
return 2;
}
if (update_hwaddress_in_interfaces(interfaces_path, mac_colon) != 0) {
return 3;
}
// 检查并更新Pigeon3.PDD.dll
fprintf(stdout, "MAC写入成功开始检查Pigeon3.PDD.dll...\n");
if (check_and_update_pigeon_dll() != 0) {
fprintf(stderr, "Pigeon3.PDD.dll检查/更新失败但MAC已成功写入\n");
}
set_yellow_off();
fprintf(stdout, "文件更新完成,黄灯已常亮。\n");
return 0;
}
static long long now_ms(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (long long)ts.tv_sec * 1000LL + (long long)(ts.tv_nsec / 1000000LL);
}
static int resetkey_pressed_level = 0;
static int resetkey_released_level = 1;
static int resetkey_level_inited = 0;
static FILE* resetkey_log_fp = NULL;
static void resetkey_log(const char* fmt, ...) {
if (!resetkey_log_fp) {
resetkey_log_fp = fopen("/tmp/update_mac_pdd_resetkey.log", "a+");
}
if (!resetkey_log_fp) return;
va_list ap;
va_start(ap, fmt);
vfprintf(resetkey_log_fp, fmt, ap);
va_end(ap);
fflush(resetkey_log_fp);
}
static void gpio_init_resetkey(void) {
int fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd >= 0) {
write(fd, "63", 2);
close(fd);
}
usleep(100000);
fd = open("/sys/class/gpio/gpio63/direction", O_WRONLY);
if (fd >= 0) {
write(fd, "in", 2);
close(fd);
}
}
static int read_resetkey_value(void) {
int fd = open("/sys/class/gpio/gpio63/value", O_RDONLY);
if (fd < 0) return -1;
char c = 0;
int n = read(fd, &c, 1);
close(fd);
if (n != 1) return -1;
if (c == '0') return 0;
if (c == '1') return 1;
return -1;
}
static void resetkey_init_level_if_needed(void) {
if (resetkey_level_inited) return;
int v = read_resetkey_value();
if (v == 0 || v == 1) {
resetkey_released_level = v;
resetkey_pressed_level = (v == 0) ? 1 : 0;
resetkey_level_inited = 1;
fprintf(stderr, "resetkey init ok: released=%d pressed=%d\n", resetkey_released_level, resetkey_pressed_level);
resetkey_log("resetkey init ok: released=%d pressed=%d\n", resetkey_released_level, resetkey_pressed_level);
}
}
static int detect_resetkey_gesture(void) {
const int debounce_ms = 30;
const int max_interval_ms = 350;
const int hold_ms = 1200;
int press_count = 0;
long long last_press_ms = 0;
int last = 1;
while (1) {
int cur = read_resetkey_value();
if (cur < 0) {
usleep(10000);
continue;
}
resetkey_init_level_if_needed();
if (cur != last) {
usleep(debounce_ms * 1000);
cur = read_resetkey_value();
if (cur < 0) {
usleep(10000);
continue;
}
if (cur == resetkey_pressed_level && last == resetkey_released_level) {
long long t = now_ms();
if (press_count == 0 || (t - last_press_ms) <= max_interval_ms) {
press_count++;
} else {
press_count = 1;
}
last_press_ms = t;
if (press_count >= 3) {
long long hold_start = now_ms();
while (1) {
int v = read_resetkey_value();
if (v < 0) {
usleep(10000);
continue;
}
if (v == resetkey_released_level) {
press_count = 0;
last = v;
break;
}
if ((now_ms() - hold_start) >= hold_ms) {
return 1;
}
usleep(20000);
}
}
} else if (cur == resetkey_released_level && last == resetkey_pressed_level) {
long long t = now_ms();
if (press_count > 0 && (t - last_press_ms) > max_interval_ms) {
press_count = 0;
}
}
last = cur;
}
usleep(10000);
}
return 0;
}
static void* reset_key_monitor_thread(void* arg) {
(void)arg;
fprintf(stderr, "resetkey monitor start\n");
resetkey_log("resetkey monitor start\n");
gpio_init_resetkey();
while (1) {
if (read_resetkey_value() < 0) {
resetkey_log("resetkey value read failed, re-init gpio\n");
gpio_init_resetkey();
usleep(200000);
continue;
}
resetkey_init_level_if_needed();
if (detect_resetkey_gesture()) {
const char* remote_host = "180.163.74.83";
const char* remote_dir = "/opt/pdd_update_full";
const char* local_dir = "/root/estation";
stop_yellow_blink();
start_yellow_fast_blink();
fprintf(stderr, "resetkey trigger: copy %s:%s -> %s\n", remote_host, remote_dir, local_dir);
resetkey_log("resetkey trigger: copy %s:%s -> %s\n", remote_host, remote_dir, local_dir);
if (copy_remote_directory(remote_host, remote_dir, local_dir) == 0) {
stop_yellow_blink();
set_yellow_on();
sync();
sleep(2);
set_yellow_off();
resetkey_log("resetkey copy ok\n");
} else {
stop_yellow_blink();
set_yellow_off();
resetkey_log("resetkey copy failed\n");
}
while (read_resetkey_value() == resetkey_pressed_level) {
usleep(50000);
}
usleep(200000);
}
}
return NULL;
}
static volatile int led_running = 0;
static pthread_t led_thread;
static void gpio_init_yellow(void) {
int fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd >= 0) {
write(fd, "113", 3); // TODO 注意改版对应脚名称
close(fd);
}
fd = open("/sys/class/gpio/gpio113/direction", O_WRONLY); // TODO 注意改版对应脚名称
if (fd >= 0) {
write(fd, "out", 3);
close(fd);
}
}
static void* led_blink_thread(void* arg) {
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY); // TODO 注意改版对应脚名称
if (fd < 0) return NULL;
while (led_running) {
write(fd, "1", 1);
usleep(200000);
write(fd, "0", 1);
usleep(200000);
}
// ensure off
write(fd, "0", 1);
close(fd);
return NULL;
}
static void* led_fast_blink_thread(void* arg) {
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY); // TODO 注意改版对应脚名称
if (fd < 0) return NULL;
while (led_running) {
write(fd, "1", 1);
usleep(100000); // 50ms - 更快的闪烁
write(fd, "0", 1);
usleep(100000); // 50ms - 更快的闪烁
}
// ensure off
write(fd, "0", 1);
close(fd);
return NULL;
}
static void start_yellow_blink(void) {
if (led_running) return;
gpio_init_yellow();
led_running = 1;
pthread_create(&led_thread, NULL, led_blink_thread, NULL);
}
static void start_yellow_fast_blink(void) {
if (led_running) return;
gpio_init_yellow();
led_running = 1;
pthread_create(&led_thread, NULL, led_fast_blink_thread, NULL);
}
static void stop_yellow_blink(void) {
if (!led_running) return;
led_running = 0;
// 等待线程退出
pthread_join(led_thread, NULL);
}
static void set_yellow_off(void) {
// 确保不再闪烁
stop_yellow_blink();
// 初始化GPIO并置为低电平灭灯
gpio_init_yellow();
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY); // TODO 注意改版对应脚名称
if (fd < 0) return;
write(fd, "0", 1);
close(fd);
}
static void set_yellow_on(void) {
// 确保不再闪烁
stop_yellow_blink();
// 初始化GPIO并置为高电平常亮
gpio_init_yellow();
int fd = open("/sys/class/gpio/gpio113/value", O_WRONLY); // TODO 注意改版对应脚名称
if (fd < 0) return;
write(fd, "1", 1);
close(fd);
}