diff --git a/audio_prompts/factory_test/customer_fs/factory_boot_zh.mp3 b/audio_prompts/factory_test/customer_fs/factory_boot_zh.mp3 new file mode 100644 index 0000000..4de60b0 Binary files /dev/null and b/audio_prompts/factory_test/customer_fs/factory_boot_zh.mp3 differ diff --git a/audio_prompts/factory_test/customer_fs/factory_pass_zh.mp3 b/audio_prompts/factory_test/customer_fs/factory_pass_zh.mp3 new file mode 100644 index 0000000..2da96ef Binary files /dev/null and b/audio_prompts/factory_test/customer_fs/factory_pass_zh.mp3 differ diff --git a/audio_prompts/factory_test/customer_fs/factory_shutdown_zh.mp3 b/audio_prompts/factory_test/customer_fs/factory_shutdown_zh.mp3 new file mode 100644 index 0000000..572512c Binary files /dev/null and b/audio_prompts/factory_test/customer_fs/factory_shutdown_zh.mp3 differ diff --git a/docs/factory_test.md b/docs/factory_test.md new file mode 100644 index 0000000..64d6ffb --- /dev/null +++ b/docs/factory_test.md @@ -0,0 +1,213 @@ +# 班班硬件独立产测程序 + +## 1. 目标和边界 + +本分支提供独立于主业务程序的硬件产测固件。`ql-application/threadx/Makefile` +只编译 `interface/factory_test`,不会启动原 `custom_app`、MQTT、WSS、FOTA、 +业务按键或业务 NFC 线程。 + +产测固件复用 SDK 底层接口、ES8311 配置、DP1312EA 驱动和 Type A 读卡实现。 +测试结果通过 USB CDC 和调试串口输出。上位机只需解析以 `FT1 ` 开头的行, +其他 SDK 调试信息可以忽略。 + +默认通信参数: + +- USB CDC,115200 8N1; +- 同一条协议也通过 `printf` 输出到调试日志; +- 每条协议消息为一行 UTF-8/ASCII JSON,以 `\r\n` 结束; +- 协议前缀固定为 `FT1 `,协议版本为 `1`。 + +## 2. 测试流程 + +```text +长按电源键开机 + -> 等待音频子系统就绪 + -> ES8311 ID/初始化,设置所有播放音量为 100% + -> 播放开机提示音 + -> 电源 ADC + -> 按住对话键录音,松开后回放,最多录 5 秒 + -> 工人判断音质 + 正常:按另一个按键三次 + 异常:不再操作,60 秒后 KEY_TRIPLE 失败 + -> eSIM READY 和 ICCID + -> 4G 注册、PDP 激活、获取 IPv4 + -> Ping 业务域名 + -> 前面全部通过后启用 NFC 测试 + -> 读取 NTAG213 UID + -> 播放“全部测试通过” + -> 播放关机提示音 + -> 输出最终报告 + -> 正常关机 +``` + +任何节点失败后立即输出 `node_result=FAIL` 和 `session_result=FAIL`,随后停留在 +当前失败状态,不继续后续测试、不开放 NFC、也不自动关机。重新测试需要重新开机。 + +## 3. 节点定义 + +| ID | 名称 | 测试方法 | 通过条件 | 超时 | +|---:|---|---|---|---:| +| 0 | `BOOT_AUDIO` | 读取 ES8311 ID,初始化外置 Codec,播放 `U:/factory_boot_zh.mp3` | Codec ID 为 `0x83`,文件存在且播放结束 | 15 秒播放超时 | +| 1 | `POWER_ADC` | 读取 6 次 VBAT,去掉最大值和最小值后平均 | `3500mV <= 平均值 <= 4200mV` | - | +| 2 | `AUDIO_LOOPBACK` | 按住电源/对话键录音,松开后按原音量回放 | 录音和回放 API 成功且至少有一帧 PCM | 等待按键 60 秒,录音最长 5 秒 | +| 3 | `KEY_TRIPLE` | GPIO84 按下并释放三次,每次输出进度 PASS | 60 秒内累计三次有效按键 | 60 秒 | +| 4 | `ESIM` | 查询 SIM 状态并读取 ICCID | `READY`,ICCID 为 18-22 位十进制数字 | 30 秒 | +| 5 | `DATA_CALL` | 等待注册、激活 CID 1 IPv4 PDP | 返回非 `0.0.0.0` 的 IPv4 | 注册等待 120 秒 | +| 6 | `PING` | Ping `banban.api.talkingq.com` 3 次 | 至少收到 2 次回复 | 单包 4 秒,总等待 20 秒 | +| 7 | `NFC` | 检查 DP1312EA 版本并轮询 NTAG213 | 版本 `0x82`,成功读取 UID | 60 秒 | + +说明:`AUDIO_LOOPBACK` 的软件通过仅代表数据链路完成。最终声学判断由工人完成; +如果声音异常,工人不按三击键,随后 `KEY_TRIPLE` 超时,整机产测失败。这避免了 +上位机额外的人工确认按钮。 + +## 4. 音频和文件系统接口 + +固件只通过以下固定路径访问提示音: + +```text +U:/factory_boot_zh.mp3 +U:/factory_pass_zh.mp3 +U:/factory_shutdown_zh.mp3 +``` + +对应资源源目录为: + +```text +audio_prompts/factory_test/customer_fs/ +``` + +资源为 16kHz、单声道、48kbps MP3。代码不会下载提示音,也不会在运行时回退到 +业务网络资源。缺少开机提示音会返回 `1002`;最终提示音缺失或播放失败会返回 +`1801`,并禁止自动关机。 + +本分支不生成、不覆盖 `ql-config/config/EG800AKCN_11LG/customer_fs.bin`。 +Windows 打包方法见 [factory_test_windows_fs.md](factory_test_windows_fs.md)。 + +## 5. 上位机协议 + +### 5.1 公共字段 + +| 字段 | 说明 | +|---|---| +| `v` | 协议版本,当前为 1 | +| `type` | `session_start`、`node_start`、`node_progress`、`node_result`、`session_result` 或 `power_off` | +| `seq` | 本次开机内单调递增的消息序号 | +| `session` | RTC 有效时使用时间生成,否则为 `BOOT-` | +| `uptime_ms` | 开机后的毫秒数,不依赖网络校时 | +| `imei` | 模组 IMEI;读取失败时为空字符串 | +| `iccid` | eSIM 节点通过后填写,此前为空字符串 | +| `nfc_uid` | NFC 节点通过后填写,此前为空字符串 | +| `code` / `fail_code` | 0 表示成功,非零值见失败码表 | +| `elapsed_ms` | 单个节点耗时 | +| `duration_ms` | 整次产测耗时 | + +### 5.2 会话开始 + +```text +FT1 {"v":1,"type":"session_start","seq":1,"session":"20260923-163010","uptime_ms":2050,"imei":"860123456789012","iccid":"","nfc_uid":"","fw":"FACTORY_TEST_1.0.0","ping_host":"banban.api.talkingq.com","start_time":"2026-09-23T16:30:10"} +``` + +RTC 无效时 `start_time` 为 `null`。RTC 无效不会导致产测失败;测试耗时始终以 +`uptime_ms` 为准。 + +### 5.3 节点开始 + +```text +FT1 {"v":1,"type":"node_start","seq":2,"session":"20260923-163010","uptime_ms":2100,"imei":"860123456789012","iccid":"","nfc_uid":"","node":1,"name":"POWER_ADC"} +``` + +### 5.4 ADC 通过 + +```text +FT1 {"v":1,"type":"node_result","seq":3,"session":"20260923-163010","uptime_ms":2450,"imei":"860123456789012","iccid":"","nfc_uid":"","node":1,"name":"POWER_ADC","result":"PASS","code":0,"elapsed_ms":350,"data":{"voltage_mv":3865,"sample_min_mv":3858,"sample_max_mv":3873,"limit_min_mv":3500,"limit_max_mv":4200}} +``` + +### 5.5 三击按键进度 + +每次完整按下并释放都输出一条 `node_progress`: + +```text +FT1 {"v":1,"type":"node_progress","seq":8,"session":"20260923-163010","uptime_ms":12300,"imei":"860123456789012","iccid":"","nfc_uid":"","node":3,"name":"KEY_TRIPLE","result":"PASS","data":{"current":1,"required":3}} +``` + +第三次松开后再输出正式的 `node_result=PASS`。 + +### 5.6 网络和 Ping + +```text +FT1 {"v":1,"type":"node_result","seq":14,"session":"20260923-163010","uptime_ms":28000,"imei":"860123456789012","iccid":"89860xxxxxxxxxxxxxxx","nfc_uid":"","node":5,"name":"DATA_CALL","result":"PASS","code":0,"elapsed_ms":8200,"data":{"ip":"10.23.45.67","ip_type":"IPv4"}} +``` + +```text +FT1 {"v":1,"type":"node_result","seq":16,"session":"20260923-163010","uptime_ms":31500,"imei":"860123456789012","iccid":"89860xxxxxxxxxxxxxxx","nfc_uid":"","node":6,"name":"PING","result":"PASS","code":0,"elapsed_ms":3400,"data":{"host":"banban.api.talkingq.com","sent":3,"received":3,"lost":0,"avg_rtt_ms":86}} +``` + +### 5.7 NFC 和最终报告 + +```text +FT1 {"v":1,"type":"node_result","seq":18,"session":"20260923-163010","uptime_ms":34000,"imei":"860123456789012","iccid":"89860xxxxxxxxxxxxxxx","nfc_uid":"04A1B2C3D4E5F6","node":7,"name":"NFC","result":"PASS","code":0,"elapsed_ms":1900,"data":{"chip_version":"0x82","uid":"04A1B2C3D4E5F6"}} +``` + +最终报告包含 IMEI、ICCID、NFC UID、失败码、开始时间、总耗时和全部节点状态: + +```text +FT1 {"v":1,"type":"session_result","seq":19,"session":"20260923-163010","uptime_ms":40120,"result":"PASS","fail_code":0,"imei":"860123456789012","iccid":"89860xxxxxxxxxxxxxxx","nfc_uid":"04A1B2C3D4E5F6","start_time":"2026-09-23T16:30:10","duration_ms":38070,"nodes":{"BOOT_AUDIO":"PASS","POWER_ADC":"PASS","AUDIO_LOOPBACK":"PASS","KEY_TRIPLE":"PASS","ESIM":"PASS","DATA_CALL":"PASS","PING":"PASS","NFC":"PASS"}} +``` + +上位机收到 `power_off` 后应立即落盘本次记录: + +```text +FT1 {"v":1,"type":"power_off","seq":20,"session":"20260923-163010","uptime_ms":40150,"imei":"860123456789012","iccid":"89860xxxxxxxxxxxxxxx","nfc_uid":"04A1B2C3D4E5F6","code":0} +``` + +## 6. 失败码 + +| 失败码 | 含义 | +|---:|---| +| 0 | 成功 | +| 1001 | ES8311 初始化或设备 ID 检查失败 | +| 1002 | 开机提示音文件不存在 | +| 1003 | 开机提示音播放失败或超时 | +| 1101 | ADC 初始化或读取失败 | +| 1102 | 电压低于 3500mV | +| 1103 | 电压高于 4200mV | +| 1201 | 录音句柄或 160KB 缓冲区创建失败 | +| 1202 | 没有录到一帧有效 PCM | +| 1203 | PCM 回放失败 | +| 1204 | 60 秒内未按下对话键 | +| 1301 | 音质异常未继续,或 60 秒内未完成三击 | +| 1401 | eSIM 未进入 READY | +| 1402 | ICCID 读取失败 | +| 1403 | ICCID 长度或字符格式错误 | +| 1501 | 4G 网络注册超时 | +| 1502 | IPv4 PDP 激活失败 | +| 1503 | 未获取有效 IPv4 地址 | +| 1601 | Ping 上下文或同步对象创建失败 | +| 1602 | Ping 启动或等待回调失败 | +| 1603 | 3 包中收到少于 2 包 | +| 1701 | 前置节点未全部通过,NFC 锁定 | +| 1702 | DP1312EA 版本寄存器不是 `0x82` | +| 1703 | 60 秒内没有成功刷卡 | +| 1704 | NFC UID 无效(预留) | +| 1801 | 最终通过/关机提示音播放失败 | +| 1802 | 正常关机接口返回(理论上不应返回) | + +## 7. 代码位置和可调参数 + +- `interface/factory_test/factory_test.c`:顺序状态机和具体测试; +- `interface/factory_test/factory_audio.c`:ES8311、100% 音量、MP3、录音回放; +- `interface/factory_test/factory_protocol.c`:FT1 JSON 日志; +- `interface/factory_test/factory_test.h`:阈值、超时、业务域名、GPIO 和文件路径; +- `interface/factory_test/Makefile`:独立产测编译单元。 + +修改 Ping 地址时只需修改 `FT_PING_HOST`。ADC 阈值、按键超时和 NFC 超时也集中在 +`factory_test.h`。GPIO84 是第二个按键,GPIO68 是功放控制;产测代码不会把 GPIO68 +当作 LED,也不会把 GPIO86 当作红色 LED,避免和 NFC 复位冲突。 + +## 8. 上板前检查 + +1. 确认 PCB 上对话键确实连接模组 PWRKEY,第二按键连接 GPIO84。 +2. 确认 GPIO68 为 NS4150B `PA_CTL`,GPIO86 为 NFC Reset。 +3. 将三段 MP3 打入 U 盘文件系统,不能只更新 APP。 +4. 首板先观察 ES8311 ID、ADC 原始值、IP 和 DP1312EA 版本日志。 +5. 上位机只把 `FT1 ` 行作为协议消息,SDK 的普通 `printf` 不应进入数据库。 diff --git a/docs/factory_test_windows_fs.md b/docs/factory_test_windows_fs.md new file mode 100644 index 0000000..6abc933 --- /dev/null +++ b/docs/factory_test_windows_fs.md @@ -0,0 +1,136 @@ +# Windows 生成产测 customer_fs.bin + +## 1. 说明 + +产测固件从 U 盘文件系统根目录读取以下三个文件: + +```text +factory_boot_zh.mp3 +factory_pass_zh.mp3 +factory_shutdown_zh.mp3 +``` + +仓库中的待打包目录是: + +```text +audio_prompts\factory_test\customer_fs +``` + +SDK 自带的 Windows 工具是: + +```text +ql-cross-tool\win32\mklfs-v2.exe +``` + +工具的命令格式为: + +```text +mklfs-v2.exe -c <待打包目录> -s <文件系统字节数> -i <输出镜像> +``` + +当前 `EG800AKCN_11LG/customer_fs.bin` 的大小是 262144 字节,因此产测镜像也使用 +262144。不要改变该容量,除非同时确认分区表允许更大的 customer_fs。 + +## 2. 检查资源 + +在 Windows `cmd.exe` 中进入仓库根目录: + +```bat +cd /d D:\你的路径\banban +dir audio_prompts\factory_test\customer_fs +``` + +应看到且只需要打包以下文件: + +```text +factory_boot_zh.mp3 +factory_pass_zh.mp3 +factory_shutdown_zh.mp3 +``` + +代码访问的是文件系统根目录,所以不要在 `customer_fs` 目录里面再创建一层同名目录。 + +## 3. 备份现有镜像 + +现有镜像属于原业务固件。先备份,避免之后无法恢复: + +```bat +copy /Y ql-config\config\EG800AKCN_11LG\customer_fs.bin ^ + ql-config\config\EG800AKCN_11LG\customer_fs.bin.before-factory-test +``` + +本仓库提交不会替你执行此覆盖操作。 + +## 4. 生成到临时输出目录 + +先生成到独立目录,不要让打包工具直接覆盖配置目录: + +```bat +if not exist output\factory-test mkdir output\factory-test + +ql-cross-tool\win32\mklfs-v2.exe ^ + -c audio_prompts\factory_test\customer_fs ^ + -s 262144 ^ + -i output\factory-test\customer_fs.bin +``` + +工具应正常退出,并生成: + +```text +output\factory-test\customer_fs.bin +``` + +检查镜像大小: + +```bat +for %I in (output\factory-test\customer_fs.bin) do @echo %~zI bytes +``` + +结果必须是: + +```text +262144 bytes +``` + +如果把命令写进 `.bat` 文件,`%I` 要写成 `%%I`。 + +## 5. 放入项目配置目录 + +确认临时镜像大小正确后再复制: + +```bat +copy /Y output\factory-test\customer_fs.bin ^ + ql-config\config\EG800AKCN_11LG\customer_fs.bin +``` + +此时再执行产测 APP 和整包构建: + +```bat +build.bat app +build.bat firmware +``` + +`build_package.bat` 会从下面的位置取 customer_fs: + +```text +ql-config\config\EG800AKCN_11LG\customer_fs.bin +``` + +## 6. 烧录后验证 + +1. 长按开机后应听到“设备已开机,开始硬件测试啦”。 +2. 串口应出现 `BOOT_AUDIO` 的 `node_result=PASS`。 +3. 如果返回 `1002`,说明 APP 已更新但 customer_fs 没有更新,或文件名/层级错误。 +4. 如果返回 `1003`,文件存在但无法解码或播放超时;检查 MP3 是否为完整文件。 +5. 通过 USB CDC 抓取 `FT1 ` 开头的完整日志,确认 IMEI、ICCID、NFC UID 和耗时均出现。 + +## 7. 恢复业务文件系统 + +需要恢复原业务固件时: + +```bat +copy /Y ql-config\config\EG800AKCN_11LG\customer_fs.bin.before-factory-test ^ + ql-config\config\EG800AKCN_11LG\customer_fs.bin +``` + +然后重新执行 `build.bat firmware`。不要把产测 customer_fs 和正式业务 APP 混合发布。 diff --git a/ql-application/threadx/Makefile b/ql-application/threadx/Makefile index ff57b70..23a8892 100644 --- a/ql-application/threadx/Makefile +++ b/ql-application/threadx/Makefile @@ -12,8 +12,7 @@ endif #------------------------------------------------------------------------------- # Configure compile directories #------------------------------------------------------------------------------- -COMMPILE_DIRS:= \ - interface/custom_app \ +COMMPILE_DIRS:= interface/factory_test diff --git a/ql-application/threadx/interface/factory_test/Makefile b/ql-application/threadx/interface/factory_test/Makefile new file mode 100644 index 0000000..0d18af2 --- /dev/null +++ b/ql-application/threadx/interface/factory_test/Makefile @@ -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 diff --git a/ql-application/threadx/interface/factory_test/factory_audio.c b/ql-application/threadx/interface/factory_test/factory_audio.c new file mode 100644 index 0000000..747e972 --- /dev/null +++ b/ql-application/threadx/interface/factory_test/factory_audio.c @@ -0,0 +1,261 @@ +#include +#include +#include + +#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; +} diff --git a/ql-application/threadx/interface/factory_test/factory_audio.h b/ql-application/threadx/interface/factory_test/factory_audio.h new file mode 100644 index 0000000..5a88ba2 --- /dev/null +++ b/ql-application/threadx/interface/factory_test/factory_audio.h @@ -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 diff --git a/ql-application/threadx/interface/factory_test/factory_nfc_compat.h b/ql-application/threadx/interface/factory_test/factory_nfc_compat.h new file mode 100644 index 0000000..1cc9f86 --- /dev/null +++ b/ql-application/threadx/interface/factory_test/factory_nfc_compat.h @@ -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 + +#include "TypeA.h" + +void handle_nfc_bind(const char *uid); +void handle_nfc_trigger(const char *uid); + +#endif diff --git a/ql-application/threadx/interface/factory_test/factory_protocol.c b/ql-application/threadx/interface/factory_test/factory_protocol.c new file mode 100644 index 0000000..889fee6 --- /dev/null +++ b/ql-application/threadx/interface/factory_test/factory_protocol.c @@ -0,0 +1,217 @@ +#include +#include +#include + +#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); +} diff --git a/ql-application/threadx/interface/factory_test/factory_protocol.h b/ql-application/threadx/interface/factory_test/factory_protocol.h new file mode 100644 index 0000000..a53a8e4 --- /dev/null +++ b/ql-application/threadx/interface/factory_test/factory_protocol.h @@ -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 diff --git a/ql-application/threadx/interface/factory_test/factory_test.c b/ql-application/threadx/interface/factory_test/factory_test.c new file mode 100644 index 0000000..7a88378 --- /dev/null +++ b/ql-application/threadx/interface/factory_test/factory_test.c @@ -0,0 +1,352 @@ +#include +#include + +#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); diff --git a/ql-application/threadx/interface/factory_test/factory_test.h b/ql-application/threadx/interface/factory_test/factory_test.h new file mode 100644 index 0000000..aa13073 --- /dev/null +++ b/ql-application/threadx/interface/factory_test/factory_test.h @@ -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