From 3e86e6cd66c8f7c20e12be2346ccedf16c45c49c Mon Sep 17 00:00:00 2001 From: chenjintao Date: Wed, 23 Sep 2026 17:12:54 +0800 Subject: [PATCH] feat: add standalone factory test firmware --- .../customer_fs/factory_boot_zh.mp3 | Bin 0 -> 18621 bytes .../customer_fs/factory_pass_zh.mp3 | Bin 0 -> 15813 bytes .../customer_fs/factory_shutdown_zh.mp3 | Bin 0 -> 17325 bytes docs/factory_test.md | 213 +++++++++++ docs/factory_test_windows_fs.md | 136 +++++++ ql-application/threadx/Makefile | 3 +- .../threadx/interface/factory_test/Makefile | 38 ++ .../interface/factory_test/factory_audio.c | 261 +++++++++++++ .../interface/factory_test/factory_audio.h | 10 + .../factory_test/factory_nfc_compat.h | 16 + .../interface/factory_test/factory_protocol.c | 217 +++++++++++ .../interface/factory_test/factory_protocol.h | 28 ++ .../interface/factory_test/factory_test.c | 352 ++++++++++++++++++ .../interface/factory_test/factory_test.h | 77 ++++ 14 files changed, 1349 insertions(+), 2 deletions(-) create mode 100644 audio_prompts/factory_test/customer_fs/factory_boot_zh.mp3 create mode 100644 audio_prompts/factory_test/customer_fs/factory_pass_zh.mp3 create mode 100644 audio_prompts/factory_test/customer_fs/factory_shutdown_zh.mp3 create mode 100644 docs/factory_test.md create mode 100644 docs/factory_test_windows_fs.md create mode 100644 ql-application/threadx/interface/factory_test/Makefile create mode 100644 ql-application/threadx/interface/factory_test/factory_audio.c create mode 100644 ql-application/threadx/interface/factory_test/factory_audio.h create mode 100644 ql-application/threadx/interface/factory_test/factory_nfc_compat.h create mode 100644 ql-application/threadx/interface/factory_test/factory_protocol.c create mode 100644 ql-application/threadx/interface/factory_test/factory_protocol.h create mode 100644 ql-application/threadx/interface/factory_test/factory_test.c create mode 100644 ql-application/threadx/interface/factory_test/factory_test.h 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 0000000000000000000000000000000000000000..4de60b0b3e9087a8b2b86d42375f9f3424b130da GIT binary patch literal 18621 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2da96efa03f2820caf2e526cfefad08327215281 GIT binary patch literal 15813 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 等待音频子系统就绪 + -> 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