273 lines
10 KiB
C
273 lines
10 KiB
C
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
|
|
#include "ql_rtos.h"
|
|
#include "ql_rtc.h"
|
|
#include "ql_uart.h"
|
|
|
|
#include "factory_protocol.h"
|
|
|
|
#define FT_PROTOCOL_LINE_MAX 1024
|
|
#define FT_PROTOCOL_UART_PORT QL_DEBUG_UART_PORT
|
|
|
|
typedef struct {
|
|
char session[32];
|
|
char imei[32];
|
|
char iccid[32];
|
|
char nfc_uid[32];
|
|
char start_time[40];
|
|
u32 start_ms;
|
|
u32 seq;
|
|
ft_state_t nodes[FT_NODE_COUNT];
|
|
int cdc_open;
|
|
ql_mutex_t tx_mutex;
|
|
ql_task_t status_task;
|
|
volatile int status_broadcast;
|
|
int active_node;
|
|
} ft_protocol_context_t;
|
|
|
|
static ft_protocol_context_t g_protocol;
|
|
static void ft_emit(const char *fmt, ...);
|
|
u32 ft_now_ms(void);
|
|
|
|
static void ft_status_task(void *arg);
|
|
|
|
u32 ft_now_ms(void)
|
|
{
|
|
return ql_rtos_get_systicks() * 5U;
|
|
}
|
|
|
|
const char *ft_node_name(ft_node_t node)
|
|
{
|
|
static const char *names[FT_NODE_COUNT] = {
|
|
"BOOT_AUDIO", "POWER_ADC", "AUDIO_LOOPBACK",
|
|
"ESIM", "DATA_CALL", "NFC"
|
|
};
|
|
|
|
if (node < 0 || node >= FT_NODE_COUNT) {
|
|
return "UNKNOWN";
|
|
}
|
|
return names[node];
|
|
}
|
|
|
|
static const char *ft_state_name(ft_state_t state)
|
|
{
|
|
if (state == FT_STATE_PASS) return "PASS";
|
|
if (state == FT_STATE_FAIL) return "FAIL";
|
|
return "WAIT";
|
|
}
|
|
|
|
static void ft_emit(const char *fmt, ...)
|
|
{
|
|
char payload[FT_PROTOCOL_LINE_MAX - 16];
|
|
char line[FT_PROTOCOL_LINE_MAX];
|
|
va_list args;
|
|
int len;
|
|
|
|
va_start(args, fmt);
|
|
vsnprintf(payload, sizeof(payload), fmt, args);
|
|
va_end(args);
|
|
|
|
len = snprintf(line, sizeof(line), "FT1 %s\r\n", payload);
|
|
if (len < 0) return;
|
|
if (len >= (int)sizeof(line)) len = sizeof(line) - 1;
|
|
|
|
if (g_protocol.tx_mutex) ql_rtos_mutex_lock(g_protocol.tx_mutex, QL_WAIT_FOREVER);
|
|
if (FT_PROTOCOL_UART_PORT == QL_DEBUG_UART_PORT) {
|
|
/* DBG UART is the printf console on this board. Use the same
|
|
transport as the visible boot/application logs so FT1 frames are
|
|
actually received by the host terminal. */
|
|
printf("%s", line);
|
|
} else if (g_protocol.cdc_open) {
|
|
ql_uart_write(FT_PROTOCOL_UART_PORT, (unsigned char *)line, len);
|
|
}
|
|
if (g_protocol.tx_mutex) ql_rtos_mutex_unlock(g_protocol.tx_mutex);
|
|
}
|
|
|
|
static void ft_make_session(void)
|
|
{
|
|
ql_rtc_time_t rtc = {0};
|
|
u32 now = ft_now_ms();
|
|
|
|
g_protocol.start_ms = now;
|
|
if (ql_rtc_get_time(&rtc) == 0 && rtc.tm_year >= 2024) {
|
|
snprintf(g_protocol.start_time, sizeof(g_protocol.start_time),
|
|
"%04d-%02d-%02dT%02d:%02d:%02d",
|
|
rtc.tm_year, rtc.tm_mon, rtc.tm_mday,
|
|
rtc.tm_hour, rtc.tm_min, rtc.tm_sec);
|
|
snprintf(g_protocol.session, sizeof(g_protocol.session),
|
|
"%04d%02d%02d-%02d%02d%02d",
|
|
rtc.tm_year, rtc.tm_mon, rtc.tm_mday,
|
|
rtc.tm_hour, rtc.tm_min, rtc.tm_sec);
|
|
} else {
|
|
strcpy(g_protocol.start_time, "");
|
|
snprintf(g_protocol.session, sizeof(g_protocol.session), "BOOT-%010lu",
|
|
(unsigned long)now);
|
|
}
|
|
}
|
|
|
|
void ft_protocol_init(void)
|
|
{
|
|
int uart_ret;
|
|
memset(&g_protocol, 0, sizeof(g_protocol));
|
|
g_protocol.active_node = -1;
|
|
ql_rtos_mutex_create(&g_protocol.tx_mutex);
|
|
uart_ret = ql_uart_open(FT_PROTOCOL_UART_PORT, QL_UART_BAUD_115200, QL_FC_NONE);
|
|
g_protocol.cdc_open = (uart_ret == 0 || uart_ret == QL_UART_ALREAD_OPEN);
|
|
ft_make_session();
|
|
printf("[FT1] control UART port=%d open_ret=%d ready=%d\r\n",
|
|
FT_PROTOCOL_UART_PORT, uart_ret, g_protocol.cdc_open);
|
|
}
|
|
|
|
static void ft_status_task(void *arg)
|
|
{
|
|
(void)arg;
|
|
while (g_protocol.status_broadcast) {
|
|
int i;
|
|
char payload[FT_PROTOCOL_LINE_MAX - 16];
|
|
int offset = snprintf(payload, sizeof(payload),
|
|
"{\"v\":%d,\"type\":\"status_snapshot\",\"seq\":%lu,\"session\":\"%s\",\"uptime_ms\":%lu,\"active_node\":%d,\"nodes\":{",
|
|
FT_PROTOCOL_VERSION, (unsigned long)++g_protocol.seq,
|
|
g_protocol.session, (unsigned long)ft_now_ms(),
|
|
g_protocol.active_node);
|
|
for (i = 0; i < FT_NODE_COUNT && offset > 0 && offset < (int)sizeof(payload); ++i) {
|
|
offset += snprintf(payload + offset, sizeof(payload) - offset,
|
|
"%s\"%s\":\"%s\"",
|
|
i ? "," : "", ft_node_name((ft_node_t)i),
|
|
ft_state_name(g_protocol.nodes[i]));
|
|
}
|
|
if (offset > 0 && offset < (int)sizeof(payload)) {
|
|
snprintf(payload + offset, sizeof(payload) - offset, "}}");
|
|
ft_emit("%s", payload);
|
|
}
|
|
ql_rtos_task_sleep_ms(500);
|
|
}
|
|
}
|
|
|
|
void ft_protocol_start_status_broadcast(void)
|
|
{
|
|
if (g_protocol.status_broadcast) return;
|
|
g_protocol.status_broadcast = 1;
|
|
/* The snapshot formatter keeps a near-1 KiB JSON buffer on the task
|
|
stack; leave headroom for libc formatting and UART calls. */
|
|
ql_rtos_task_create(&g_protocol.status_task, 8192, 15,
|
|
"ft_status", ft_status_task, NULL);
|
|
}
|
|
|
|
static void ft_copy(char *dst, unsigned int dst_size, const char *src)
|
|
{
|
|
if (!dst || dst_size == 0) return;
|
|
if (!src) src = "";
|
|
snprintf(dst, dst_size, "%s", src);
|
|
}
|
|
|
|
void ft_protocol_set_imei(const char *imei)
|
|
{
|
|
ft_copy(g_protocol.imei, sizeof(g_protocol.imei), imei);
|
|
}
|
|
|
|
void ft_protocol_set_iccid(const char *iccid)
|
|
{
|
|
ft_copy(g_protocol.iccid, sizeof(g_protocol.iccid), iccid);
|
|
}
|
|
|
|
void ft_protocol_set_nfc_uid(const char *uid)
|
|
{
|
|
ft_copy(g_protocol.nfc_uid, sizeof(g_protocol.nfc_uid), uid);
|
|
}
|
|
|
|
const char *ft_protocol_imei(void) { return g_protocol.imei; }
|
|
const char *ft_protocol_iccid(void) { return g_protocol.iccid; }
|
|
const char *ft_protocol_nfc_uid(void) { return g_protocol.nfc_uid; }
|
|
|
|
void ft_protocol_session_start(void)
|
|
{
|
|
ft_emit("{\"v\":%d,\"type\":\"session_start\",\"seq\":%lu,"
|
|
"\"session\":\"%s\",\"uptime_ms\":%lu,\"imei\":\"%s\","
|
|
"\"iccid\":\"%s\",\"nfc_uid\":\"%s\",\"fw\":\"%s\","
|
|
"\"start_time\":%s%s%s}",
|
|
FT_PROTOCOL_VERSION, (unsigned long)++g_protocol.seq,
|
|
g_protocol.session, (unsigned long)ft_now_ms(), g_protocol.imei,
|
|
g_protocol.iccid, g_protocol.nfc_uid, FT_FIRMWARE_VERSION,
|
|
g_protocol.start_time[0] ? "\"" : "null",
|
|
g_protocol.start_time, g_protocol.start_time[0] ? "\"" : "");
|
|
}
|
|
|
|
void ft_protocol_node_start(ft_node_t node)
|
|
{
|
|
if (node >= 0 && node < FT_NODE_COUNT) g_protocol.active_node = node;
|
|
ft_emit("{\"v\":%d,\"type\":\"node_start\",\"seq\":%lu,"
|
|
"\"session\":\"%s\",\"uptime_ms\":%lu,\"imei\":\"%s\","
|
|
"\"iccid\":\"%s\",\"nfc_uid\":\"%s\",\"node\":%d,"
|
|
"\"name\":\"%s\"}",
|
|
FT_PROTOCOL_VERSION, (unsigned long)++g_protocol.seq,
|
|
g_protocol.session, (unsigned long)ft_now_ms(), g_protocol.imei,
|
|
g_protocol.iccid, g_protocol.nfc_uid, node, ft_node_name(node));
|
|
}
|
|
|
|
void ft_protocol_node_progress(ft_node_t node, int current, int required)
|
|
{
|
|
ft_emit("{\"v\":%d,\"type\":\"node_progress\",\"seq\":%lu,"
|
|
"\"session\":\"%s\",\"uptime_ms\":%lu,\"imei\":\"%s\","
|
|
"\"iccid\":\"%s\",\"nfc_uid\":\"%s\",\"node\":%d,"
|
|
"\"name\":\"%s\",\"result\":\"PASS\","
|
|
"\"data\":{\"current\":%d,\"required\":%d}}",
|
|
FT_PROTOCOL_VERSION, (unsigned long)++g_protocol.seq,
|
|
g_protocol.session, (unsigned long)ft_now_ms(), g_protocol.imei,
|
|
g_protocol.iccid, g_protocol.nfc_uid, node, ft_node_name(node),
|
|
current, required);
|
|
}
|
|
|
|
void ft_protocol_node_result(ft_node_t node, int passed, ft_error_t code,
|
|
u32 elapsed_ms, const char *data_json)
|
|
{
|
|
const char *result = passed ? "PASS" : "FAIL";
|
|
if (node >= 0 && node < FT_NODE_COUNT) {
|
|
g_protocol.nodes[node] = passed ? FT_STATE_PASS : FT_STATE_FAIL;
|
|
g_protocol.active_node = -1;
|
|
}
|
|
if (!data_json || !data_json[0]) data_json = "{}";
|
|
|
|
ft_emit("{\"v\":%d,\"type\":\"node_result\",\"seq\":%lu,"
|
|
"\"session\":\"%s\",\"uptime_ms\":%lu,\"imei\":\"%s\","
|
|
"\"iccid\":\"%s\",\"nfc_uid\":\"%s\",\"node\":%d,"
|
|
"\"name\":\"%s\",\"result\":\"%s\",\"code\":%d,"
|
|
"\"elapsed_ms\":%lu,\"data\":%s}",
|
|
FT_PROTOCOL_VERSION, (unsigned long)++g_protocol.seq,
|
|
g_protocol.session, (unsigned long)ft_now_ms(), g_protocol.imei,
|
|
g_protocol.iccid, g_protocol.nfc_uid, node, ft_node_name(node),
|
|
result, code, (unsigned long)elapsed_ms, data_json);
|
|
}
|
|
|
|
void ft_protocol_session_result(int passed, ft_error_t code)
|
|
{
|
|
ft_emit("{\"v\":%d,\"type\":\"session_result\",\"seq\":%lu,"
|
|
"\"session\":\"%s\",\"uptime_ms\":%lu,\"result\":\"%s\","
|
|
"\"fail_code\":%d,\"imei\":\"%s\",\"iccid\":\"%s\","
|
|
"\"nfc_uid\":\"%s\",\"start_time\":%s%s%s,"
|
|
"\"duration_ms\":%lu,\"nodes\":{"
|
|
"\"BOOT_AUDIO\":\"%s\",\"POWER_ADC\":\"%s\","
|
|
"\"AUDIO_LOOPBACK\":\"%s\",\"ESIM\":\"%s\","
|
|
"\"DATA_CALL\":\"%s\",\"NFC\":\"%s\"}}",
|
|
FT_PROTOCOL_VERSION, (unsigned long)++g_protocol.seq,
|
|
g_protocol.session, (unsigned long)ft_now_ms(), passed ? "PASS" : "FAIL",
|
|
code, g_protocol.imei, g_protocol.iccid, g_protocol.nfc_uid,
|
|
g_protocol.start_time[0] ? "\"" : "null", g_protocol.start_time,
|
|
g_protocol.start_time[0] ? "\"" : "",
|
|
(unsigned long)(ft_now_ms() - g_protocol.start_ms),
|
|
ft_state_name(g_protocol.nodes[0]), ft_state_name(g_protocol.nodes[1]),
|
|
ft_state_name(g_protocol.nodes[2]), ft_state_name(g_protocol.nodes[3]),
|
|
ft_state_name(g_protocol.nodes[4]), ft_state_name(g_protocol.nodes[5]));
|
|
}
|
|
|
|
void ft_protocol_power_off(void)
|
|
{
|
|
ft_emit("{\"v\":%d,\"type\":\"power_off\",\"seq\":%lu,"
|
|
"\"session\":\"%s\",\"uptime_ms\":%lu,\"imei\":\"%s\","
|
|
"\"iccid\":\"%s\",\"nfc_uid\":\"%s\",\"code\":0}",
|
|
FT_PROTOCOL_VERSION, (unsigned long)++g_protocol.seq,
|
|
g_protocol.session, (unsigned long)ft_now_ms(), g_protocol.imei,
|
|
g_protocol.iccid, g_protocol.nfc_uid);
|
|
}
|