feat: add standalone factory test firmware

This commit is contained in:
chenjintao
2026-09-23 17:12:54 +08:00
parent 1f55943c29
commit 3e86e6cd66
14 changed files with 1349 additions and 2 deletions
@@ -0,0 +1,38 @@
#-------------------------------------------------------------------------------
# Standalone factory-test application.
# This target intentionally replaces custom_app on the test/factory-test branch.
#-------------------------------------------------------------------------------
TOP_DIR:=$(TOP_DIR)
ROOT_DIR:=$(TOP_DIR)/../..
MAKEFILE_PATH :=$(subst $(strip \),/,$(abspath $(lastword $(MAKEFILE_LIST))))
vpath %.c ../custom_app ../custom_app/lib/libnfc
SRC_FILES+= \
factory_test.c \
factory_protocol.c \
factory_audio.c \
hal_i2c.c \
DP1312EA.c \
TypeA.c \
INC_DIRS+= \
-I${TOP_DIR}/ \
-I${TOP_DIR}/common/include \
-I${TOP_DIR}/common/include/lwipv4v6 \
-I${TOP_DIR}/common/include/fs \
-I../custom_app/inc \
-I../custom_app/lib/libnfc \
-I. \
U_FLAGS:= -include factory_nfc_compat.h
U_CPPFLAGS:=
DFLAGS+= \
-DFACTORY_TEST_BUILD \
-D__TM_ZONE=tm_zone \
-D__TM_GMTOFF=tm_gmtoff \
U_LIBS:=
include ${TOP_DIR}/config/common/makefile.mk
@@ -0,0 +1,261 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ql_audio.h"
#include "ql_fs.h"
#include "ql_gpio.h"
#include "ql_power.h"
#include "ql_rtos.h"
#include "factory_audio.h"
#include "factory_test.h"
#define ES8311_ADDR 0x18
#define ES8311_DEVICE_ID_REG 0xFD
#define ES8311_DEVICE_ID 0x83
#define ES8311_VOLUME_100 0x8E
#define FT_PCM_CHUNK_BYTES 640
#define FT_MP3_TIMEOUT_TICKS (15000 / 5)
extern void sw_i2c_init(void);
extern int8 sw_i2c_start(void);
extern void sw_i2c_stop(void);
extern int8 sw_i2c_write_byte(uint8_t data);
extern uint8_t sw_i2c_read_byte(uint8_t ack);
static const uint16_t g_es8311_init_regs[] = {
0x0045, 0x3001, 0x1002, 0x0002, 0x1003, 0x2416, 0x2004, 0x0005,
0x2006, 0x0007, 0xff08, 0x0f09, 0x0f0a, 0x000b, 0x000c, 0x1310,
0x7f11, 0x3f01, 0x8000, 0x010d, 0x1014, 0x2812, 0x1013, 0x020e,
0x440f, 0x0015, 0x0a1b, 0x6a1c, 0x0837, 0x0044, 0xa717, 0x8e32,
0x0018
};
static ql_sem_t g_mp3_sem;
static volatile int g_mp3_result;
static Mp3PlaybackHandle g_mp3_handle;
static int es8311_write(uint8_t reg, uint8_t value)
{
if (sw_i2c_start() != 0) return -1;
if (sw_i2c_write_byte((uint8_t)(ES8311_ADDR << 1)) != 0 ||
sw_i2c_write_byte(reg) != 0 || sw_i2c_write_byte(value) != 0) {
sw_i2c_stop();
return -1;
}
sw_i2c_stop();
return 0;
}
static int es8311_read(uint8_t reg, uint8_t *value)
{
if (!value || sw_i2c_start() != 0) return -1;
if (sw_i2c_write_byte((uint8_t)(ES8311_ADDR << 1)) != 0 ||
sw_i2c_write_byte(reg) != 0 || sw_i2c_start() != 0 ||
sw_i2c_write_byte((uint8_t)((ES8311_ADDR << 1) | 1)) != 0) {
sw_i2c_stop();
return -1;
}
*value = sw_i2c_read_byte(0);
sw_i2c_stop();
return 0;
}
static void factory_pa_control(unsigned int on)
{
ql_gpio_init(FT_PA_PIN, PIN_DIRECTION_OUT, PIN_PULL_DISABLE,
on ? PIN_LEVEL_HIGH : PIN_LEVEL_LOW);
}
static void factory_mp3_callback(Mp3PlayEventType event, int value)
{
if (event != MP3_PLAYBACK_EVENT_STATUS) return;
if (value == MP3_PLAYBACK_STATUS_STARTED) {
g_mp3_result = 1;
} else if (value == MP3_PLAYBACK_STATUS_ENDED) {
g_mp3_result = 2;
if (g_mp3_sem) ql_rtos_semaphore_release(g_mp3_sem);
} else if (value == MP3_PLAYBACK_STATUS_TIMEOUT) {
g_mp3_result = -1;
if (g_mp3_sem) ql_rtos_semaphore_release(g_mp3_sem);
}
}
static void factory_track_callback(audio_track_handle handle,
acm_audio_track_event_t event)
{
(void)handle;
(void)event;
}
static void factory_record_callback(audio_record_handle handle,
acm_audio_record_event_t event)
{
(void)handle;
(void)event;
}
int factory_audio_init(void)
{
uint8_t id = 0;
unsigned int i;
u32 start = ft_now_ms();
ql_codecpcm_config pcm_config[] = {
PCM_FS_256, PCM_CODEC_SAMPLE_16000, 0
};
while (!ql_get_audio_state()) {
if (ft_now_ms() - start > 10000) return -1;
ql_rtos_task_sleep_ms(50);
}
sw_i2c_init();
if (es8311_read(ES8311_DEVICE_ID_REG, &id) != 0 || id != ES8311_DEVICE_ID) {
return -1;
}
ql_bind_speakerpa_cb(factory_pa_control);
ql_codec_choose(AUD_EXTERNAL_CODEC, pcm_config);
for (i = 0; i < sizeof(g_es8311_init_regs) / sizeof(g_es8311_init_regs[0]); ++i) {
if (es8311_write((uint8_t)(g_es8311_init_regs[i] & 0xff),
(uint8_t)(g_es8311_init_regs[i] >> 8)) != 0) {
return -1;
}
}
ql_set_audio_path_speaker();
ql_set_volume(AUDIOHAL_SPK_VOL_11);
es8311_write(0x32, ES8311_VOLUME_100);
factory_pa_control(0);
if (!g_mp3_sem && ql_rtos_semaphore_create(&g_mp3_sem, 0) != 0) {
return -1;
}
return 0;
}
int factory_audio_play_prompt(const char *path)
{
Mp3PlayConfigInfo config;
int ret;
if (!path || ql_access(path, 0) != 0) return -2;
while (ql_rtos_semaphore_wait(g_mp3_sem, QL_NO_WAIT) == 0) { }
memset(&config, 0, sizeof(config));
config.listener = factory_mp3_callback;
config.sync_mode = 0;
g_mp3_result = 0;
es8311_write(0x32, ES8311_VOLUME_100);
ql_set_volume(AUDIOHAL_SPK_VOL_11);
factory_pa_control(1);
ret = ql_play_mp3((char *)path, &g_mp3_handle, &config);
if (ret != 0) {
factory_pa_control(0);
return -1;
}
ret = ql_rtos_semaphore_wait(g_mp3_sem, FT_MP3_TIMEOUT_TICKS);
if (ret != 0 || g_mp3_result != 2) {
ql_stop_mp3_play(g_mp3_handle, 0);
factory_pa_control(0);
return -1;
}
factory_pa_control(0);
return 0;
}
static int wait_pwrkey_level(unsigned char expected, u32 timeout_ms)
{
u32 start = ft_now_ms();
while (ql_get_pwrkey_status() != expected) {
if (ft_now_ms() - start >= timeout_ms) return -1;
ql_rtos_task_sleep_ms(10);
}
ql_rtos_task_sleep_ms(40);
return ql_get_pwrkey_status() == expected ? 0 : -1;
}
int factory_audio_wait_and_loopback(u32 wait_timeout_ms, u32 *recorded_ms)
{
unsigned char *buffer = NULL;
unsigned int total = 0;
unsigned int request;
unsigned int offset;
audio_record_handle record_handle = 0;
audio_track_handle track_handle = 0;
int ret = -1;
if (recorded_ms) *recorded_ms = 0;
if (wait_pwrkey_level(0, 10000) != 0 ||
wait_pwrkey_level(1, wait_timeout_ms) != 0) {
return -4;
}
buffer = (unsigned char *)malloc(FT_AUDIO_BUFFER_BYTES);
if (!buffer) return -1;
record_handle = ql_audio_record_open(FT_AUDIO_RATE_HZ, FT_AUDIO_CHANNELS,
0, factory_record_callback);
if (!record_handle) goto cleanup;
while (ql_get_pwrkey_status() && total < FT_AUDIO_BUFFER_BYTES) {
request = FT_PCM_CHUNK_BYTES;
if (request > FT_AUDIO_BUFFER_BYTES - total) {
request = FT_AUDIO_BUFFER_BYTES - total;
}
if (ql_audio_record_read(record_handle, (char *)(buffer + total), &request) == 0 &&
request > 0) {
total += request;
}
ql_rtos_task_sleep_ms(10);
}
ql_audio_record_close(record_handle, 0);
record_handle = 0;
if (total == FT_AUDIO_BUFFER_BYTES && ql_get_pwrkey_status()) {
if (wait_pwrkey_level(0, 30000) != 0) goto cleanup;
} else if (wait_pwrkey_level(0, 3000) != 0) {
goto cleanup;
}
if (total < FT_PCM_CHUNK_BYTES) {
ret = -2;
goto cleanup;
}
if (recorded_ms) {
*recorded_ms = total * 1000U / (FT_AUDIO_RATE_HZ * 2U * FT_AUDIO_CHANNELS);
}
es8311_write(0x32, ES8311_VOLUME_100);
ql_set_volume(AUDIOHAL_SPK_VOL_11);
factory_pa_control(1);
track_handle = ql_audio_track_open(FT_AUDIO_RATE_HZ, FT_AUDIO_CHANNELS,
0, factory_track_callback);
if (!track_handle) {
ret = -3;
goto cleanup;
}
for (offset = 0; offset < total; offset += FT_PCM_CHUNK_BYTES) {
unsigned int size = total - offset;
if (size > FT_PCM_CHUNK_BYTES) size = FT_PCM_CHUNK_BYTES;
if (ql_audio_track_write(track_handle, (char *)(buffer + offset), size) != 0) {
ret = -3;
goto cleanup;
}
}
ql_rtos_task_sleep_ms(total * 1000U /
(FT_AUDIO_RATE_HZ * 2U * FT_AUDIO_CHANNELS) + 300U);
ret = 0;
cleanup:
if (record_handle) ql_audio_record_close(record_handle, 1);
if (track_handle) ql_audio_track_close(track_handle, ret == 0 ? 0 : 1);
factory_pa_control(0);
if (buffer) free(buffer);
return ret;
}
@@ -0,0 +1,10 @@
#ifndef BANBAN_FACTORY_AUDIO_H
#define BANBAN_FACTORY_AUDIO_H
#include "ql_type.h"
int factory_audio_init(void);
int factory_audio_play_prompt(const char *path);
int factory_audio_wait_and_loopback(u32 wait_timeout_ms, u32 *recorded_ms);
#endif
@@ -0,0 +1,16 @@
#ifndef BANBAN_FACTORY_NFC_COMPAT_H
#define BANBAN_FACTORY_NFC_COMPAT_H
/*
* The production DP1312EA source relies on declarations supplied by other
* business modules. Factory test links only the NFC driver, so provide the
* small declaration bridge it needs without pulling in the main program.
*/
#include <string.h>
#include "TypeA.h"
void handle_nfc_bind(const char *uid);
void handle_nfc_trigger(const char *uid);
#endif
@@ -0,0 +1,217 @@
#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
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;
} ft_protocol_context_t;
static ft_protocol_context_t g_protocol;
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", "KEY_TRIPLE",
"ESIM", "DATA_CALL", "PING", "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;
printf("%s", line);
if (g_protocol.cdc_open) {
ql_uart_write(QL_USB_CDC_PORT, (unsigned char *)line, len);
}
}
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)
{
memset(&g_protocol, 0, sizeof(g_protocol));
g_protocol.cdc_open =
(ql_uart_open(QL_USB_CDC_PORT, QL_UART_BAUD_115200, QL_FC_NONE) == 0);
ft_make_session();
}
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\","
"\"ping_host\":\"%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,
FT_PING_HOST, g_protocol.start_time[0] ? "\"" : "null",
g_protocol.start_time, g_protocol.start_time[0] ? "\"" : "");
}
void ft_protocol_node_start(ft_node_t 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;
}
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\",\"KEY_TRIPLE\":\"%s\","
"\"ESIM\":\"%s\",\"DATA_CALL\":\"%s\","
"\"PING\":\"%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]),
ft_state_name(g_protocol.nodes[6]), ft_state_name(g_protocol.nodes[7]));
}
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);
}
@@ -0,0 +1,28 @@
#ifndef BANBAN_FACTORY_PROTOCOL_H
#define BANBAN_FACTORY_PROTOCOL_H
#include "factory_test.h"
typedef enum {
FT_STATE_WAIT = 0,
FT_STATE_PASS,
FT_STATE_FAIL
} ft_state_t;
void ft_protocol_init(void);
void ft_protocol_set_imei(const char *imei);
void ft_protocol_set_iccid(const char *iccid);
void ft_protocol_set_nfc_uid(const char *uid);
void ft_protocol_session_start(void);
void ft_protocol_node_start(ft_node_t node);
void ft_protocol_node_progress(ft_node_t node, int current, int required);
void ft_protocol_node_result(ft_node_t node, int passed, ft_error_t code,
u32 elapsed_ms, const char *data_json);
void ft_protocol_session_result(int passed, ft_error_t code);
void ft_protocol_power_off(void);
const char *ft_protocol_imei(void);
const char *ft_protocol_iccid(void);
const char *ft_protocol_nfc_uid(void);
#endif
@@ -0,0 +1,352 @@
#include <stdio.h>
#include <string.h>
#include "ql_adc.h"
#include "ql_application.h"
#include "ql_data_call.h"
#include "ql_dev.h"
#include "ql_gpio.h"
#include "ql_iic.h"
#include "ql_nw.h"
#include "ql_ping.h"
#include "ql_power.h"
#include "ql_rtos.h"
#include "ql_sim.h"
#include "sockets.h"
#include "DP1312EA.h"
#include "factory_audio.h"
#include "factory_protocol.h"
#include "factory_test.h"
#include "nfc.h"
#include "reg.h"
#define FT_PING_COUNT 3
#define FT_PING_REQUIRED 2
#define FT_PING_TIMEOUT_MS 4000
#define FT_PING_WAIT_TICKS (20000 / 5)
#define FT_NFC_VERSION_EXPECTED 0x82
int g_testmode = 1;
int g_nfc_bind_status = 0;
extern int8 output_uuid[64];
extern s8 Read_NTAG213(u8 *uid_buf, u8 *data_buf, u16 *data_len);
void handle_nfc_bind(const char *uid) { (void)uid; }
void handle_nfc_trigger(const char *uid) { (void)uid; }
static ql_sem_t g_ping_sem;
static ql_ping_stat_t g_ping_stats;
static volatile int g_ping_finished;
static char g_ip_address[64];
static int iccid_valid(const char *iccid)
{
unsigned int i;
unsigned int len;
if (!iccid) return 0;
len = strlen(iccid);
if (len < 18 || len > 22) return 0;
for (i = 0; i < len; ++i) {
if (iccid[i] < '0' || iccid[i] > '9') return 0;
}
return 1;
}
static ft_error_t run_boot_audio(char *data, unsigned int data_size)
{
int ret = factory_audio_init();
if (ret != 0) return FT_ERR_AUDIO_CODEC_INIT;
ret = factory_audio_play_prompt(FT_AUDIO_BOOT_PATH);
if (ret == -2) return FT_ERR_AUDIO_FILE_MISSING;
if (ret != 0) return FT_ERR_AUDIO_PLAY;
snprintf(data, data_size, "{\"path\":\"%s\",\"volume_percent\":100}",
FT_AUDIO_BOOT_PATH);
return FT_OK;
}
static ft_error_t run_power_adc(char *data, unsigned int data_size)
{
unsigned short samples[FT_ADC_SAMPLE_COUNT];
unsigned short min_value = 0;
unsigned short max_value = 0;
unsigned int sum = 0;
unsigned int i;
unsigned int average;
if (ql_adc_init() != 0) return FT_ERR_ADC_READ;
for (i = 0; i < FT_ADC_SAMPLE_COUNT; ++i) {
ql_adc_onff(1);
ql_rtos_task_sleep_ms(5);
ql_adc_onff(0);
samples[i] = ql_get_battery_vol();
if (samples[i] == 0) return FT_ERR_ADC_READ;
if (i == 0 || samples[i] < min_value) min_value = samples[i];
if (i == 0 || samples[i] > max_value) max_value = samples[i];
sum += samples[i];
ql_rtos_task_sleep_ms(45);
}
average = (sum - min_value - max_value) / (FT_ADC_SAMPLE_COUNT - 2);
snprintf(data, data_size,
"{\"voltage_mv\":%u,\"sample_min_mv\":%u,"
"\"sample_max_mv\":%u,\"limit_min_mv\":%d,"
"\"limit_max_mv\":%d}",
average, min_value, max_value, FT_ADC_MIN_MV, FT_ADC_MAX_MV);
if (average < FT_ADC_MIN_MV) return FT_ERR_ADC_LOW;
if (average > FT_ADC_MAX_MV) return FT_ERR_ADC_HIGH;
return FT_OK;
}
static ft_error_t run_audio_loopback(char *data, unsigned int data_size)
{
u32 recorded_ms = 0;
int ret = factory_audio_wait_and_loopback(FT_DIALOG_KEY_TIMEOUT_MS, &recorded_ms);
snprintf(data, data_size,
"{\"recorded_ms\":%lu,\"max_ms\":%d,\"volume_percent\":100}",
(unsigned long)recorded_ms, FT_AUDIO_MAX_MS);
if (ret == -4) return FT_ERR_DIALOG_KEY_TIMEOUT;
if (ret == -1) return FT_ERR_RECORD_OPEN;
if (ret == -2) return FT_ERR_RECORD_EMPTY;
if (ret != 0) return FT_ERR_LOOPBACK_PLAY;
return FT_OK;
}
static int wait_gpio_level(unsigned int pin, PIN_LEVEL_E expected, u32 timeout_ms)
{
PIN_LEVEL_E level = PIN_LEVEL_HIGH;
u32 start = ft_now_ms();
do {
ql_gpio_get_level(pin, &level);
if (level == expected) {
ql_rtos_task_sleep_ms(40);
ql_gpio_get_level(pin, &level);
if (level == expected) return 0;
}
ql_rtos_task_sleep_ms(10);
} while (ft_now_ms() - start < timeout_ms);
return -1;
}
static ft_error_t run_key_triple(char *data, unsigned int data_size)
{
int count = 0;
u32 start = ft_now_ms();
ql_gpio_init(FT_TRIPLE_KEY_PIN, PIN_DIRECTION_IN, PIN_PULL_PU, PIN_LEVEL_HIGH);
if (wait_gpio_level(FT_TRIPLE_KEY_PIN, PIN_LEVEL_HIGH, 5000) != 0) {
return FT_ERR_TRIPLE_KEY_TIMEOUT;
}
while (count < 3 && ft_now_ms() - start < FT_TRIPLE_KEY_TIMEOUT_MS) {
u32 remaining = FT_TRIPLE_KEY_TIMEOUT_MS - (ft_now_ms() - start);
if (wait_gpio_level(FT_TRIPLE_KEY_PIN, PIN_LEVEL_LOW, remaining) != 0) break;
++count;
ft_protocol_node_progress(FT_NODE_KEY_TRIPLE, count, 3);
remaining = FT_TRIPLE_KEY_TIMEOUT_MS - (ft_now_ms() - start);
if (wait_gpio_level(FT_TRIPLE_KEY_PIN, PIN_LEVEL_HIGH, remaining) != 0) break;
}
snprintf(data, data_size, "{\"press_count\":%d,\"required\":3}", count);
return count == 3 ? FT_OK : FT_ERR_TRIPLE_KEY_TIMEOUT;
}
static ft_error_t run_esim(char *data, unsigned int data_size)
{
int status = QL_SIM_STATUS_UNKNOWN;
int ret = -1;
char iccid[32] = {0};
u32 start = ft_now_ms();
do {
ret = ql_sim_get_card_status(&status);
if (ret == QL_SIM_SUCCESS && status == QL_SIM_STATUS_READY) break;
ql_rtos_task_sleep_ms(500);
} while (ft_now_ms() - start < FT_SIM_TIMEOUT_MS);
if (ret != QL_SIM_SUCCESS || status != QL_SIM_STATUS_READY) {
snprintf(data, data_size, "{\"sim_status\":%d}", status);
return FT_ERR_SIM_NOT_READY;
}
if (ql_sim_get_iccid(iccid, sizeof(iccid)) != QL_SIM_SUCCESS) {
return FT_ERR_ICCID_READ;
}
if (!iccid_valid(iccid)) {
snprintf(data, data_size, "{\"sim_status\":%d,\"iccid\":\"%s\"}",
status, iccid);
return FT_ERR_ICCID_FORMAT;
}
ft_protocol_set_iccid(iccid);
snprintf(data, data_size, "{\"sim_status\":\"READY\",\"iccid\":\"%s\"}",
iccid);
return FT_OK;
}
static ft_error_t run_data_call(char *data, unsigned int data_size)
{
struct ql_data_call_info info;
int ret;
int attempt;
if (ql_network_register_wait(120) != 0) return FT_ERR_NETWORK_REGISTER;
ql_set_data_call_asyn_mode(0, NULL);
ret = ql_start_data_call(1, 0, NULL, NULL, NULL, 0);
if (ret != 0) return FT_ERR_PDP_ACTIVATE;
memset(&info, 0, sizeof(info));
memset(g_ip_address, 0, sizeof(g_ip_address));
for (attempt = 0; attempt < 10; ++attempt) {
if (ql_get_data_call_info(1, 0, &info) == 0 && info.v4.state) {
inet_ntop(AF_INET, &info.v4.addr.ip, g_ip_address, sizeof(g_ip_address));
if (g_ip_address[0] && strcmp(g_ip_address, "0.0.0.0") != 0) break;
}
ql_rtos_task_sleep_ms(1000);
}
snprintf(data, data_size, "{\"ip\":\"%s\",\"ip_type\":\"IPv4\"}",
g_ip_address);
if (!g_ip_address[0] || strcmp(g_ip_address, "0.0.0.0") == 0) {
return FT_ERR_IP_INVALID;
}
return FT_OK;
}
static void factory_ping_callback(ql_ping_state_e state, int err,
ql_ping_stat_t *stats)
{
(void)err;
if (stats) memcpy(&g_ping_stats, stats, sizeof(g_ping_stats));
if (state == QL_PING_FINISHED) {
g_ping_finished = 1;
if (g_ping_sem) ql_rtos_semaphore_release(g_ping_sem);
}
}
static ft_error_t run_ping(char *data, unsigned int data_size)
{
ql_ping_ctx_t *ctx;
ql_ping_cfg_t cfg = {
.pdpCid = 1,
.host = FT_PING_HOST,
.timeout = FT_PING_TIMEOUT_MS,
.dataSize = 32,
.pingNum = FT_PING_COUNT
};
int ret;
memset(&g_ping_stats, 0, sizeof(g_ping_stats));
g_ping_finished = 0;
if (ql_rtos_semaphore_create(&g_ping_sem, 0) != 0) return FT_ERR_PING_INIT;
ctx = ql_ping_init(cfg, factory_ping_callback);
if (!ctx) {
ql_rtos_semaphore_delete(g_ping_sem);
g_ping_sem = NULL;
return FT_ERR_PING_INIT;
}
ret = ql_ping(ctx);
if (ret == 0) ret = ql_rtos_semaphore_wait(g_ping_sem, FT_PING_WAIT_TICKS);
ql_ping_deinit(ctx);
ql_rtos_semaphore_delete(g_ping_sem);
g_ping_sem = NULL;
snprintf(data, data_size,
"{\"host\":\"%s\",\"sent\":%u,\"received\":%u,"
"\"lost\":%u,\"avg_rtt_ms\":%lu}",
FT_PING_HOST, g_ping_stats.sentNum, g_ping_stats.rcvdNum,
g_ping_stats.lostNum, (unsigned long)g_ping_stats.avgRtt);
if (ret != 0 || !g_ping_finished) return FT_ERR_PING_START;
if (g_ping_stats.rcvdNum < FT_PING_REQUIRED) return FT_ERR_PING_LOSS;
return FT_OK;
}
static ft_error_t run_nfc(char *data, unsigned int data_size)
{
u8 uid[7] = {0};
u8 ntag_data[180] = {0};
u16 ntag_len = 0;
u8 version;
u32 start = ft_now_ms();
DP1312EA_Reset();
version = DP1312EA_ReadRawRC(VersionReg);
if (version != FT_NFC_VERSION_EXPECTED) {
snprintf(data, data_size, "{\"chip_version\":\"0x%02X\"}", version);
return FT_ERR_NFC_CHIP;
}
do {
memset(output_uuid, 0, 64);
if (Read_NTAG213(uid, ntag_data, &ntag_len) == MI_OK && output_uuid[0]) {
ft_protocol_set_nfc_uid((char *)output_uuid);
snprintf(data, data_size,
"{\"chip_version\":\"0x%02X\",\"uid\":\"%s\"}",
version, (char *)output_uuid);
return FT_OK;
}
ql_rtos_task_sleep_ms(200);
} while (ft_now_ms() - start < FT_NFC_TIMEOUT_MS);
return FT_ERR_NFC_TIMEOUT;
}
static ft_error_t run_node(ft_node_t node, char *data, unsigned int data_size)
{
data[0] = '\0';
switch (node) {
case FT_NODE_BOOT_AUDIO: return run_boot_audio(data, data_size);
case FT_NODE_POWER_ADC: return run_power_adc(data, data_size);
case FT_NODE_AUDIO_LOOPBACK: return run_audio_loopback(data, data_size);
case FT_NODE_KEY_TRIPLE: return run_key_triple(data, data_size);
case FT_NODE_ESIM: return run_esim(data, data_size);
case FT_NODE_DATA_CALL: return run_data_call(data, data_size);
case FT_NODE_PING: return run_ping(data, data_size);
case FT_NODE_NFC: return run_nfc(data, data_size);
default: return FT_ERR_NFC_LOCKED;
}
}
static void factory_test_task(void *arg)
{
char imei[32] = {0};
char data[384];
ft_node_t node;
ft_error_t result = FT_OK;
u32 start;
int audio_ret;
(void)arg;
ql_rtos_task_sleep_s(2);
ft_protocol_init();
if (ql_dev_get_imei(imei, sizeof(imei)) == 0) {
ft_protocol_set_imei(imei);
}
ft_protocol_session_start();
for (node = FT_NODE_BOOT_AUDIO; node < FT_NODE_COUNT; ++node) {
ft_protocol_node_start(node);
start = ft_now_ms();
result = run_node(node, data, sizeof(data));
ft_protocol_node_result(node, result == FT_OK, result,
ft_now_ms() - start, data);
if (result != FT_OK) {
ft_protocol_session_result(0, result);
while (1) ql_rtos_task_sleep_s(60);
}
}
audio_ret = factory_audio_play_prompt(FT_AUDIO_PASS_PATH);
if (audio_ret == 0) {
ql_rtos_task_sleep_ms(300);
audio_ret = factory_audio_play_prompt(FT_AUDIO_SHUTDOWN_PATH);
}
if (audio_ret != 0) {
ft_protocol_session_result(0, FT_ERR_FINAL_AUDIO);
while (1) ql_rtos_task_sleep_s(60);
}
ft_protocol_session_result(1, FT_OK);
ft_protocol_power_off();
ql_rtos_task_sleep_ms(500);
ql_power_down(1);
ft_protocol_session_result(0, FT_ERR_POWER_DOWN);
while (1) ql_rtos_task_sleep_s(60);
}
application_init(factory_test_task, "factory_test", 10, 0);
@@ -0,0 +1,77 @@
#ifndef BANBAN_FACTORY_TEST_H
#define BANBAN_FACTORY_TEST_H
#include "ql_type.h"
#include "ql_gpio.h"
#define FT_PROTOCOL_VERSION 1
#define FT_FIRMWARE_VERSION "FACTORY_TEST_1.0.0"
#define FT_PING_HOST "banban.api.talkingq.com"
#define FT_ADC_MIN_MV 3500
#define FT_ADC_MAX_MV 4200
#define FT_ADC_SAMPLE_COUNT 6
#define FT_AUDIO_RATE_HZ 16000
#define FT_AUDIO_CHANNELS 1
#define FT_AUDIO_MAX_MS 5000
#define FT_AUDIO_BUFFER_BYTES (FT_AUDIO_RATE_HZ * 2 * FT_AUDIO_MAX_MS / 1000)
#define FT_DIALOG_KEY_TIMEOUT_MS 60000
#define FT_TRIPLE_KEY_TIMEOUT_MS 60000
#define FT_SIM_TIMEOUT_MS 30000
#define FT_NFC_TIMEOUT_MS 60000
#define FT_TRIPLE_KEY_PIN GPIO_PIN_NO_84
#define FT_PA_PIN GPIO_PIN_NO_68
#define FT_AUDIO_BOOT_PATH "U:/factory_boot_zh.mp3"
#define FT_AUDIO_PASS_PATH "U:/factory_pass_zh.mp3"
#define FT_AUDIO_SHUTDOWN_PATH "U:/factory_shutdown_zh.mp3"
typedef enum {
FT_NODE_BOOT_AUDIO = 0,
FT_NODE_POWER_ADC,
FT_NODE_AUDIO_LOOPBACK,
FT_NODE_KEY_TRIPLE,
FT_NODE_ESIM,
FT_NODE_DATA_CALL,
FT_NODE_PING,
FT_NODE_NFC,
FT_NODE_COUNT
} ft_node_t;
typedef enum {
FT_OK = 0,
FT_ERR_AUDIO_CODEC_INIT = 1001,
FT_ERR_AUDIO_FILE_MISSING = 1002,
FT_ERR_AUDIO_PLAY = 1003,
FT_ERR_ADC_READ = 1101,
FT_ERR_ADC_LOW = 1102,
FT_ERR_ADC_HIGH = 1103,
FT_ERR_RECORD_OPEN = 1201,
FT_ERR_RECORD_EMPTY = 1202,
FT_ERR_LOOPBACK_PLAY = 1203,
FT_ERR_DIALOG_KEY_TIMEOUT = 1204,
FT_ERR_TRIPLE_KEY_TIMEOUT = 1301,
FT_ERR_SIM_NOT_READY = 1401,
FT_ERR_ICCID_READ = 1402,
FT_ERR_ICCID_FORMAT = 1403,
FT_ERR_NETWORK_REGISTER = 1501,
FT_ERR_PDP_ACTIVATE = 1502,
FT_ERR_IP_INVALID = 1503,
FT_ERR_PING_INIT = 1601,
FT_ERR_PING_START = 1602,
FT_ERR_PING_LOSS = 1603,
FT_ERR_NFC_LOCKED = 1701,
FT_ERR_NFC_CHIP = 1702,
FT_ERR_NFC_TIMEOUT = 1703,
FT_ERR_NFC_UID = 1704,
FT_ERR_FINAL_AUDIO = 1801,
FT_ERR_POWER_DOWN = 1802
} ft_error_t;
const char *ft_node_name(ft_node_t node);
u32 ft_now_ms(void);
#endif