Explorar el Código

update:修改readme相关描述

13917187172 hace 2 meses
padre
commit
981bf3a2fd

+ 51 - 0
module/Air8000/demo/airtalk/audio_drv.lua

@@ -0,0 +1,51 @@
+--[[
+@module  audio_drv
+@summary 音频设备管理模块,负责音频设备的初始化和控制
+@version 2.0
+@date    2025.12.02
+@author  陈媛媛
+@usage
+本模块提供以下功能:
+1、定义所有硬件引脚常量
+2、使用exaudio扩展库初始化音频设备
+]]
+
+local audio_drv = {}
+local exaudio = require "exaudio"
+local _initialized = false
+
+-- 音频初始化参数(Air8000配置)
+local audio_setup_param = {
+    model = "es8311",       -- 音频编解码类型,可填入"es8311","es8211"
+    i2c_id = 0,             -- i2c_id,可填入0,1 并使用pins工具配置对应的管脚
+    --Air8000开发板pa_ctrl和dac_ctrl配置
+     pa_ctrl = 162,         -- 音频放大器电源控制管脚
+     dac_ctrl = 164,        -- 音频编解码芯片电源控制管脚   
+    --Air8000核心板pa_ctrl和dac_ctrl配置
+    -- pa_ctrl = 17,         -- 音频放大器电源控制管脚
+    -- dac_ctrl = 16,        -- 音频编解码芯片电源控制管脚   
+
+}
+
+-- 初始化音频设备
+function audio_drv.init()
+    if _initialized then
+        log.info("audio_drv", "音频设备已经初始化")
+        return true
+    end
+    
+    log.info("audio_drv", "开始初始化音频设备")
+    
+    local audio_init_ok = exaudio.setup(audio_setup_param)
+    
+    if audio_init_ok then
+        _initialized = true
+        log.info("audio_drv", "音频设备初始化成功")
+        return true
+    else
+        log.error("audio_drv", "音频设备初始化失败")
+        return false
+    end
+end
+
+return audio_drv

+ 0 - 541
module/Air8000/demo/airtalk/exaudio.lua

@@ -1,541 +0,0 @@
---[[
-@module exaudio
-@summary exaudio扩展库
-@version 1.1
-@date    2025.09.01
-@author  梁健
-@usage
-]]
-local exaudio = {}
-
--- 常量定义
-local I2S_ID = 0
-local I2S_MODE = 0          -- 0:主机 1:从机
-local I2S_SAMPLE_RATE = 16000
-local I2S_CHANNEL_FORMAT = i2s.MONO_R  
-local I2S_COMM_FORMAT = i2s.MODE_LSB   -- 可选MODE_I2S, MODE_LSB, MODE_MSB
-local I2S_CHANNEL_BITS = 16
-local MULTIMEDIA_ID = 0
-local EX_MSG_PLAY_DONE = "playDone"
-local ES8311_ADDR = 0x18    -- 7位地址
-local CHIP_ID_REG = 0x00    -- 芯片ID寄存器地址
-
--- 模块常量
-exaudio.PLAY_DONE = 1         --   音频播放完毕的事件之一
-exaudio.RECORD_DONE = 1       --   音频录音完毕的事件之一  
-exaudio.AMR_NB = 0
-exaudio.AMR_WB = 1
-exaudio.PCM_8000 = 2
-exaudio.PCM_16000 = 3 
-exaudio.PCM_24000 = 4
-exaudio.PCM_32000 = 5
-
-
--- 默认配置参数
-local audio_setup_param = {
-    model = "es8311",         -- dac类型: "es8311","es8211"
-    i2c_id = 0,               -- i2c_id: 0,1
-    pa_ctrl = 0,              -- 音频放大器电源控制管脚
-    dac_ctrl = 0,             -- 音频编解码芯片电源控制管脚
-    dac_delay = 3,            -- DAC启动前冗余时间(100ms)
-    pa_delay = 100,           -- DAC启动后延迟打开PA的时间(ms)
-    dac_time_delay = 600,     -- 播放完毕后PA与DAC关闭间隔(ms)
-    bits_per_sample = 16,     -- 采样位数
-    pa_on_level = 1           -- PA打开电平 1:高 0:低        
-}
-
-local audio_play_param = {
-    type = 0,                 -- 0:文件 1:TTS 2:流式
-    content = nil,            -- 播放内容
-    cbfnc = nil,              -- 播放完毕回调
-    priority = 0,             -- 优先级(数值越大越高)
-    sampling_rate = 16000,    -- 采样率(仅流式)
-    sampling_depth = 16,      -- 采样位深(仅流式)
-    signed_or_unsigned = true -- PCM是否有符号(仅流式)
-}
-
-local audio_record_param = {
-    format = 0,               -- 录制格式,支持exaudio.AMR_NB,exaudio.AMR_WB,exaudio.PCM_8000,exaudio.PCM_16000,exaudio.PCM_24000,exaudio.PCM_32000
-    time = 5,                 -- 录制时间(秒)
-    path = nil,               -- 文件路径或流式回调
-    cbfnc = nil               -- 录音完毕回调
-}
-
--- 内部变量
-local pcm_buff0 = nil
-local pcm_buff1 = nil
-local voice_vol = 55
-local mic_vol = 80
-
--- 定义全局队列表
-local audio_play_queue = {
-    data = {},       -- 存储字符串的数组
-    sequenceIndex = 1  -- 用于跟踪插入顺序的索引
-}
-
--- 向队列中添加字符串(按调用顺序插入)
-local function audio_play_queue_push(str)
-    if type(str) == "string" then
-        -- 存储格式: {index = 顺序索引, value = 字符串值}
-        table.insert(audio_play_queue.data, {
-            index = audio_play_queue.sequenceIndex,
-            value = str
-        })
-        audio_play_queue.sequenceIndex = audio_play_queue.sequenceIndex + 1
-        return true
-    end
-    return false
-end
-
--- 从队列中取出最早插入的字符串(按顺序取出)
-local function audio_play_queue_pop()
-    if #audio_play_queue.data > 0 then
-        -- 取出并移除第一个元素
-        local item = table.remove(audio_play_queue.data, 1)
-        return item.value  -- 返回值
-    end
-    return nil
-end
--- 清空队列中所有数据
-function audio_queue_clear()
-    -- 清空数组
-    audio_play_queue.data = {}
-    -- 重置顺序索引
-    audio_play_queue.sequenceIndex = 1
-    return true
-end
-
--- 工具函数:参数检查
-local function check_param(param, expected_type, name)
-    if type(param) ~= expected_type then
-        log.error(string.format("参数错误: %s 应为 %s 类型", name, expected_type))
-        return false
-    end
-    return true
-end
-
--- 音频回调处理
-local function audio_callback(id, event, point)
-    -- log.info("audio_callback", "event:", event, 
-    --         "MORE_DATA:", audio.MORE_DATA, 
-    --         "DONE:", audio.DONE,
-    --         "RECORD_DATA:", audio.RECORD_DATA,
-    --         "RECORD_DONE:", audio.RECORD_DONE)
-
-    if event == audio.MORE_DATA then
-        audio.write(MULTIMEDIA_ID,audio_play_queue_pop())
-    elseif event == audio.DONE then
-        if type(audio_play_param.cbfnc) == "function" then
-            audio_play_param.cbfnc(exaudio.PLAY_DONE)
-        end
-        audio_queue_clear()  -- 清空流式播放数据队列
-        sys.publish(EX_MSG_PLAY_DONE)
-        
-    elseif event == audio.RECORD_DATA then
-        if type(audio_record_param.path) == "function" then
-            local buff, len = point == 0 and pcm_buff0 or pcm_buff1,
-                             point == 0 and pcm_buff0:used() or pcm_buff1:used()
-            audio_record_param.path(buff, len)
-        end
-        
-    elseif event == audio.RECORD_DONE then
-        if type(audio_record_param.cbfnc) == "function" then
-            audio_record_param.cbfnc(exaudio.RECORD_DONE)
-        end
-    end
-end
-
--- 读取ES8311芯片ID
-local function read_es8311_id()
-
-
-    -- 发送读取请求
-    local send_ok = i2c.send(audio_setup_param.i2c_id, ES8311_ADDR, CHIP_ID_REG)
-    if not send_ok then
-        log.error("发送芯片ID读取请求失败")
-        return false
-    end
-
-    -- 读取数据
-    local data = i2c.recv(audio_setup_param.i2c_id, ES8311_ADDR, 1)
-    if data and #data == 1 then
-        return true
-    end
-
-    log.error("读取ES8311芯片ID失败")
-    return false
-end
-
--- 音频硬件初始化
-local function audio_setup()
-    -- I2C配置
-    if not i2c.setup(audio_setup_param.i2c_id, i2c.FAST) then
-        log.error("I2C初始化失败")
-        return false
-    end
-    -- 初始化I2S
-    local result, data = i2s.setup(
-        I2S_ID, 
-        I2S_MODE, 
-        I2S_SAMPLE_RATE, 
-        audio_setup_param.bits_per_sample, 
-        I2S_CHANNEL_FORMAT, 
-        I2S_COMM_FORMAT,
-        I2S_CHANNEL_BITS
-    )
-
-    if not result then
-        log.error("I2S设置失败")
-        return false
-    end
-    -- 配置音频通道
-    audio.config(
-        MULTIMEDIA_ID, 
-        audio_setup_param.pa_ctrl, 
-        audio_setup_param.pa_on_level, 
-        audio_setup_param.dac_delay, 
-        audio_setup_param.pa_delay, 
-        audio_setup_param.dac_ctrl, 
-        1,  -- power_on_level
-        audio_setup_param.dac_time_delay
-    )
-    -- 设置总线
-    audio.setBus(
-        MULTIMEDIA_ID, 
-        audio.BUS_I2S,
-        {
-            chip = audio_setup_param.model,
-            i2cid = audio_setup_param.i2c_id,
-            i2sid = I2S_ID,
-            voltage = audio.VOLTAGE_1800
-        }
-    )
-
-  
-    -- 设置音量
-    audio.vol(MULTIMEDIA_ID, voice_vol)
-    audio.micVol(MULTIMEDIA_ID, mic_vol)
-    audio.pm(MULTIMEDIA_ID, audio.RESUME)
-    
-    -- 检查芯片连接
-    if audio_setup_param.model == "es8311" and not read_es8311_id() then
-        log.error("ES8311通讯失败,请检查硬件")
-        return false
-    end
-
-    -- 注册回调
-    audio.on(MULTIMEDIA_ID, audio_callback)
-    return true
-end
-
--- 模块接口:初始化
-function exaudio.setup(audioConfigs)
-    -- 检查必要参数
-    if not  audio  then
-        log.error("不支持audio 库,请选择支持audio 的core")
-        return false
-    end
-    if not audioConfigs or type(audioConfigs) ~= "table" then
-        log.error("配置参数必须为table类型")
-        return false
-    end
-    -- 检查codec型号
-    if not audioConfigs.model or 
-       (audioConfigs.model ~= "es8311" and audioConfigs.model ~= "es8211") then
-        log.error("请指定正确的codec型号(es8311或es8211)")
-        return false
-    end
-    audio_setup_param.model = audioConfigs.model
-    -- 针对ES8311的特殊检查
-    if audioConfigs.model == "es8311" then
-        if not check_param(audioConfigs.i2c_id, "number", "i2c_id") then
-            return false
-        end
-        audio_setup_param.i2c_id = audioConfigs.i2c_id
-    end
-
-    -- 检查功率放大器控制管脚
-    if audioConfigs.pa_ctrl == nil then
-        log.warn("pa_ctrl(功率放大器控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
-    end
-    audio_setup_param.pa_ctrl = audioConfigs.pa_ctrl
-
-    -- 检查功率放大器控制管脚
-    if audioConfigs.dac_ctrl == nil then
-        log.warn("dac_ctrl(音频编解码控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
-    end
-    audio_setup_param.dac_ctrl = audioConfigs.dac_ctrl
-
-
-    -- 处理可选参数
-    local optional_params = {
-        {name = "dac_delay", type = "number"},
-        {name = "pa_delay", type = "number"},
-        {name = "dac_time_delay", type = "number"},
-        {name = "bits_per_sample", type = "number"},
-        {name = "pa_on_level", type = "number"}
-    }
-
-    for _, param in ipairs(optional_params) do
-        if audioConfigs[param.name] ~= nil then
-            if check_param(audioConfigs[param.name], param.type, param.name) then
-                audio_setup_param[param.name] = audioConfigs[param.name]
-            else
-                return false
-            end
-        end
-    end
-
-    -- 确保采样位数有默认值
-    audio_setup_param.bits_per_sample = audio_setup_param.bits_per_sample or 16
-    return audio_setup()
-end
-
--- 模块接口:开始播放
-function exaudio.play_start(playConfigs)
-    if not playConfigs or type(playConfigs) ~= "table" then
-        log.error("播放配置必须为table类型")
-        return false
-    end
-
-    -- 检查播放类型
-    if not check_param(playConfigs.type, "number", "type") then
-        log.error("type必须为数值(0:文件,1:TTS,2:流式)")
-        return false
-    end
-    audio_play_param.type = playConfigs.type
-
-    -- 处理优先级
-    if playConfigs.priority ~= nil then
-        if check_param(playConfigs.priority, "number", "priority") then
-            if playConfigs.priority > audio_play_param.priority then
-                log.error("是否完成播放",audio.isEnd(MULTIMEDIA_ID))
-                if not audio.isEnd(MULTIMEDIA_ID) then
-                    if audio.play(MULTIMEDIA_ID) ~= true then
-                        return false
-                    end
-                    sys.waitUntil(EX_MSG_PLAY_DONE)
-                end
-                audio_play_param.priority = playConfigs.priority
-            end
-        else
-            return false
-        end
-    end
-
-    -- 处理不同播放类型
-    local play_type = audio_play_param.type
-    if play_type == 0 then  -- 文件播放
-        if not playConfigs.content then
-            log.error("文件播放需要指定content(文件路径或路径表)")
-            return false
-        end
-
-        local content_type = type(playConfigs.content)
-        if content_type == "table" then
-            for _, path in ipairs(playConfigs.content) do
-                if type(path) ~= "string" then
-                    log.error("播放列表元素必须为字符串路径")
-                    return false
-                end
-            end
-        elseif content_type ~= "string" then
-            log.error("文件播放content必须为字符串或路径表")
-            return false
-        end
-
-        audio_play_param.content = playConfigs.content
-        if audio.play(MULTIMEDIA_ID, audio_play_param.content) ~= true then
-            return false
-        end
-
-    elseif play_type == 1 then  -- TTS播放
-        if not audio.tts then
-            log.error("本固件不支持TTS,请更换支持TTS 的固件")
-            return false
-        end
-        if not check_param(playConfigs.content, "string", "content") then
-            log.error("TTS播放content必须为字符串")
-            return false
-        end
-        audio_play_param.content = playConfigs.content
-        if audio.tts(MULTIMEDIA_ID, audio_play_param.content)  ~= true  then
-            return false
-        end
-
-    elseif play_type == 2 then  -- 流式播放
-        if not check_param(playConfigs.sampling_rate, "number", "sampling_rate") then
-            return false
-        end
-        if not check_param(playConfigs.sampling_depth, "number", "sampling_depth") then
-            return false
-        end
-
-        audio_play_param.content = playConfigs.content
-        audio_play_param.sampling_rate = playConfigs.sampling_rate
-        audio_play_param.sampling_depth = playConfigs.sampling_depth
-        
-        if playConfigs.signed_or_unsigned ~= nil then
-            audio_play_param.signed_or_unsigned = playConfigs.signed_or_unsigned
-        end
-
-        audio.start(
-            MULTIMEDIA_ID, 
-            audio.PCM, 
-            1, 
-            playConfigs.sampling_rate, 
-            playConfigs.sampling_depth, 
-            audio_play_param.signed_or_unsigned
-        )
-        -- 发送初始数据
-        if audio.write(MULTIMEDIA_ID, string.rep("\0", 512)) ~= true then
-            return false
-        end
-    end
-
-    -- 处理回调函数
-    if playConfigs.cbfnc ~= nil then
-        if check_param(playConfigs.cbfnc, "function", "cbfnc") then
-            audio_play_param.cbfnc = playConfigs.cbfnc
-        else
-            return false
-        end
-    else
-        audio_play_param.cbfnc = nil
-    end
-    return true
-end
-
--- 模块接口:流式播放数据写入
-function exaudio.play_stream_write(data)
-    audio_play_queue_push(data)
-    return true
-end
-
--- 模块接口:停止播放
-function exaudio.play_stop()
-    return audio.play(MULTIMEDIA_ID)
-end
-
--- 模块接口:检查播放是否结束
-function exaudio.is_end()
-    return audio.isEnd(MULTIMEDIA_ID)
-end
-
--- 模块接口:获取错误信息
-function exaudio.get_error()
-    return audio.getError(MULTIMEDIA_ID)
-end
-
--- 模块接口:开始录音
-function exaudio.record_start(recodConfigs)
-    if not recodConfigs or type(recodConfigs) ~= "table" then
-        log.error("录音配置必须为table类型")
-        return false
-    end
-    -- 检查录音格式
-    if recodConfigs.format == nil or type(recodConfigs.format) ~= "number" or recodConfigs.format > 5 then
-        log.error("请指定正确的录音格式")
-        return false
-    end
-    audio_record_param.format = recodConfigs.format
-
-    -- 处理录音时间
-    if recodConfigs.time ~= nil then
-        if check_param(recodConfigs.time, "number", "time") then
-            audio_record_param.time = recodConfigs.time
-        else
-            return false
-        end
-    else
-        audio_record_param.time = 0
-    end
-
-    -- 处理存储路径/回调
-    if not recodConfigs.path then
-        log.error("必须指定录音路径或流式回调函数")
-        return false
-    end
-    audio_record_param.path = recodConfigs.path
-
-    -- 转换录音格式
-    local recod_format, amr_quailty
-    if audio_record_param.format == exaudio.AMR_NB then
-        recod_format = audio.AMR_NB
-        amr_quailty = 7
-    elseif audio_record_param.format == exaudio.AMR_WB then
-        recod_format = audio.AMR_WB
-        amr_quailty = 8
-    elseif audio_record_param.format == exaudio.PCM_8000 then
-        recod_format = 8000
-    elseif audio_record_param.format == exaudio.PCM_16000 then
-        recod_format = 16000
-    elseif audio_record_param.format == exaudio.PCM_24000 then
-        recod_format = 24000
-    elseif audio_record_param.format == exaudio.PCM_32000 then
-        recod_format = 32000
-    end
-
-    -- 处理回调函数
-    if recodConfigs.cbfnc ~= nil then
-        if check_param(recodConfigs.cbfnc, "function", "cbfnc") then
-            audio_record_param.cbfnc = recodConfigs.cbfnc
-        else
-            return false
-        end
-    else
-        audio_record_param.cbfnc = nil
-    end
-    -- 开始录音
-    local path_type = type(audio_record_param.path)
-    if path_type == "string" then
-        return audio.record(
-            MULTIMEDIA_ID, 
-            recod_format, 
-            audio_record_param.time, 
-            amr_quailty, 
-            audio_record_param.path
-        )
-    elseif path_type == "function" then
-        -- 初始化缓冲区
-        if not pcm_buff0 or not pcm_buff1 then
-            pcm_buff0 = zbuff.create(16000)
-            pcm_buff1 = zbuff.create(16000)
-        end
-        return audio.record(
-            MULTIMEDIA_ID, 
-            recod_format, 
-            audio_record_param.time, 
-            amr_quailty, 
-            nil, 
-            3,
-            pcm_buff0,
-            pcm_buff1
-        )
-    end
-    log.error("录音路径必须为字符串或函数")
-    return false
-end
-
--- 模块接口:停止录音
-function exaudio.record_stop()
-    return audio.recordStop(MULTIMEDIA_ID)
-end
-
--- 模块接口:设置音量
-function exaudio.vol(play_volume)
-    if check_param(play_volume, "number", "音量值") then
-        return audio.vol(MULTIMEDIA_ID, play_volume)
-    end
-    return false
-end
-
--- 模块接口:设置麦克风音量
-function exaudio.mic_vol(record_volume)
-    if check_param(record_volume, "number", "麦克风音量值") then
-        return audio.micVol(MULTIMEDIA_ID, record_volume)  
-    end
-    return false
-end
-
-return exaudio

+ 0 - 588
module/Air8000/demo/airtalk/extalk.lua

@@ -1,588 +0,0 @@
---[[
-@module extalk
-@summary extalk扩展库
-@version 1.1.1
-@date    2025.09.18
-@author  梁健
-@usage
-    local extalk = require "extalk"
-    -- 配置并初始化
-    extalk.setup({
-        key = "your_product_key",
-        heart_break_time = 30,
-        contact_list_cbfnc = function(dev_list) end,
-        state_cbfnc = function(state) end
-    })
-    -- 发起对讲
-    extalk.start("remote_device_id")
-    -- 结束对讲
-    extalk.stop()
-]]
-
-local extalk = {}
-
--- 模块常量(保留原始数据结构)
-extalk.START = 1     -- 通话开始
-extalk.STOP = 2      -- 通话结束
-extalk.UNRESPONSIVE = 3  -- 未响应
-extalk.ONE_ON_ONE = 5  -- 一对一来电
-extalk.BROADCAST = 6 -- 广播
-
-local AIRTALK_TASK_NAME = "airtalk_task"
-
--- 消息类型常量(保留原始数据结构)
-local MSG_CONNECT_ON_IND = 0
-local MSG_CONNECT_OFF_IND = 1
-local MSG_AUTH_IND = 2
-local MSG_SPEECH_ON_IND = 3
-local MSG_SPEECH_OFF_IND = 4
-local MSG_SPEECH_CONNECT_TO = 5
-local MSG_SPEECH_STOP_TEST_END = 22
-
--- 设备状态常量(保留原始数据结构)
-local SP_T_NO_READY = 0           -- 离线状态无法对讲
-local SP_T_IDLE = 1               -- 对讲空闲状态
-local SP_T_CONNECTING = 2         -- 主动发起对讲
-local SP_T_CONNECTED = 3          -- 对讲中
-
-local SUCC = "success"
-
--- 全局状态变量(保留原始数据结构)
-local g_state = SP_T_NO_READY   -- 设备状态
-local g_mqttc = nil             -- mqtt客户端
-local g_local_id                -- 本机ID
-local g_stask_start = false                -- 本机ID
-local g_remote_id               -- 对端ID
-local g_s_type                  -- 对讲的模式,字符串形式
-local g_s_topic                 -- 对讲用的topic
-local g_s_mode                  -- 对讲的模式
-local g_dev_list                -- 对讲列表
-local g_dl_topic                -- 下行消息topic模板
-
--- 配置参数
-local extalk_configs_local = {
-    key = 0,               -- 项目key,一般需要和main的PRODUCT_KEY保持一致
-    heart_break_time = 0,  -- 心跳间隔(单位秒)
-    contact_list_cbfnc = nil, -- 联系人回调函数,含设备号和昵称
-    state_cbfnc = nil,  -- 状态回调,分为对讲开始,对讲结束,未响应
-}
-
--- 工具函数:参数检查
-local function check_param(param, expected_type, name)
-    if type(param) ~= expected_type then
-        log.error(string.format("参数错误: %s 应为 %s 类型,实际为 %s", 
-            name, expected_type, type(param)))
-        return false
-    end
-    return true
-end
-
--- MQTT消息发布函数,集中处理所有发布操作并打印日志
-local function publish_message(topic, payload)
-    if g_mqttc then
-        log.info("MQTT发布 - 主题:", topic, "内容:", payload)
-        g_mqttc:publish(topic, payload)
-    else
-        log.error("MQTT客户端未初始化,无法发布消息")
-    end
-end
-
-
--- 对讲超时处理
-function extalk.wait_speech_to()
-    log.info("主动请求对讲超时无应答")
-    extalk.speech_off(true, false)
-end
-
-
--- 发送鉴权消息
-local function auth()
-    if g_state == SP_T_NO_READY and g_mqttc then
-        local topic = string.format("ctrl/uplink/%s/0001", g_local_id)
-        local payload = json.encode({
-            ["key"] = extalk_configs_local.key, 
-            ["device_type"] = 1
-        })
-        publish_message(topic, payload)
-    end
-end
-
--- 发送心跳消息
-local function heart()
-    if  g_mqttc then
-        adc.open(adc.CH_VBAT)
-        local vbat = adc.get(adc.CH_VBAT)
-        adc.close(adc.CH_VBAT)
-        local topic = string.format("ctrl/uplink/%s/0005", g_local_id)
-        local payload = json.encode({
-            ["csq"] = mobile.csq(), 
-            ["battery"] = vbat
-        })
-        publish_message(topic, payload)
-    end
-end
-
--- 开始对讲
-local function speech_on(ssrc, sample)
-    g_state = SP_T_CONNECTED
-    g_mqttc:subscribe(g_s_topic)
-    airtalk.set_topic(g_s_topic)
-    airtalk.set_ssrc(ssrc)
-    log.info("对讲模式", g_s_mode)
-    airtalk.speech(true, g_s_mode, sample)
-    sys.sendMsg(AIRTALK_TASK_NAME, MSG_SPEECH_ON_IND, true) 
-    -- sys.timerLoopStart(heart, extalk_configs_local.heart_break_time * 1000)
-    sys.timerStopAll(extalk.wait_speech_to)
-end
-
--- 结束对讲
-function extalk.speech_off(need_upload, need_ind)
-    if g_state == SP_T_CONNECTED then
-        g_mqttc:unsubscribe(g_s_topic)
-        airtalk.speech(false)
-        g_s_topic = nil
-    end
-    
-    g_state = SP_T_IDLE
-    sys.timerStopAll(auth)
-
-    sys.timerStopAll(extalk.wait_speech_to)
-    
-    if need_upload and g_mqttc then
-        local topic = string.format("ctrl/uplink/%s/0004", g_local_id)
-        publish_message(topic, json.encode({["to"] = g_remote_id}))
-    end
-
-    if need_ind then
-        sys.sendMsg(AIRTALK_TASK_NAME, MSG_SPEECH_OFF_IND, true)
-    end
-end
-
-
--- 命令处理:请求对讲应答
-local function handle_speech_response(obj)
-    if g_state ~= SP_T_CONNECTING then
-        log.error("state", g_state, "need", SP_T_CONNECTING)
-        return
-    end
-
-    if obj and obj["result"] == SUCC and g_s_topic == obj["topic"] then
-        -- 开始对讲
-        local sample_rate = obj["audio_code"] == "amr-nb" and 8000 or 16000
-        speech_on(obj["ssrc"], sample_rate)
-        return
-    else
-        log.info(obj["result"], obj["topic"], g_s_topic)
-        sys.sendMsg(AIRTALK_TASK_NAME, MSG_SPEECH_ON_IND, false)
-    end
-    
-    g_s_topic = nil
-    g_state = SP_T_IDLE
-end
-
--- 命令处理:对端来电
-local function handle_incoming_call(obj)
-    if not obj or not obj["topic"] or not obj["ssrc"] or not obj["audio_code"] or not obj["type"] then
-        local response = {
-            ["result"] = "failed", 
-            ["topic"] = obj and obj["topic"] or "", 
-            ["info"] = "无效的请求参数"
-        }
-        publish_message(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
-        return
-    end
-
-    -- 非空闲状态无法接收来电
-    if g_state ~= SP_T_IDLE then
-        log.error("state", g_state, "need", SP_T_IDLE)
-        local response = {
-            ["result"] = "failed", 
-            ["topic"] = obj["topic"], 
-            ["info"] = "device is busy"
-        }
-        publish_message(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
-        return
-    end
-
-    local response, from = {}, nil
-    
-    -- 提取对端ID
-    from = string.match(obj["topic"], "audio/(.*)/.*/.*")
-    if not from then
-        response = {
-            ["result"] = "failed", 
-            ["topic"] = obj["topic"], 
-            ["info"] = "topic error"
-        }
-        publish_message(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
-        return
-    end
-
-    -- 处理一对一通话
-    if obj["type"] == "one-on-one" then
-        g_s_topic = obj["topic"]
-        g_remote_id = from
-        g_s_type = "one-on-one"
-        g_s_mode = airtalk.MODE_PERSON
-        
-        -- 触发回调
-        if extalk_configs_local.state_cbfnc then
-            extalk_configs_local.state_cbfnc({
-                state = extalk.ONE_ON_ONE, 
-                id = from 
-            })
-        end
-        
-        response = {["result"] = SUCC, ["topic"] = obj["topic"], ["info"] = ""}
-        local sample_rate = obj["audio_code"] == "amr-nb" and 8000 or 16000
-        speech_on(obj["ssrc"], sample_rate)
-    end
-
-    -- 处理广播
-    if obj["type"] == "broadcast" then
-        g_s_topic = obj["topic"]
-        g_remote_id = from
-        g_s_mode = airtalk.MODE_GROUP_LISTENER
-        g_s_type = "broadcast"
-        
-        -- 触发回调
-        if extalk_configs_local.state_cbfnc then
-            extalk_configs_local.state_cbfnc({
-                state = extalk.BROADCAST, 
-                id = from 
-            })
-        end
-        
-        response = {["result"] = SUCC, ["topic"] = obj["topic"], ["info"] = ""}
-        local sample_rate = obj["audio_code"] == "amr-nb" and 8000 or 16000
-        speech_on(obj["ssrc"], sample_rate)
-    end
-
-    -- 发送响应
-    publish_message(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
-end
-
--- 命令处理:对端挂断
-local function handle_remote_hangup(obj)
-    local response = {}
-    
-    if g_state == SP_T_IDLE then
-        response = {["result"] = "failed", ["info"] = "no speech"}
-    else
-        log.info("0103", obj, obj["type"], g_s_type)
-        if obj and obj["type"] == g_s_type then
-            response = {["result"] = SUCC, ["info"] = ""}
-            extalk.speech_off(false, true)
-        else
-            response = {["result"] = "failed", ["info"] = "type mismatch"}
-        end
-    end
-    
-    publish_message(string.format("ctrl/uplink/%s/8103", g_local_id), json.encode(response))
-end
-
--- 命令处理:更新设备列表
-local function handle_device_list_update(obj)
-    local response = {}
-    if obj then
-        g_dev_list = obj["dev_list"]
-        response = {["result"] = SUCC, ["info"] = ""}
-    else
-        response = {["result"] = "failed", ["info"] = "json info error"}
-    end
-    
-    publish_message(string.format("ctrl/uplink/%s/8101", g_local_id), json.encode(response))
-end
-
--- 命令处理:鉴权结果
-local function handle_auth_result(obj)
-    if obj and obj["result"] == SUCC then
-        publish_message(string.format("ctrl/uplink/%s/0002", g_local_id), "")  -- 更新列表
-        sys.timerLoopStart(heart, extalk_configs_local.heart_break_time * 1000)   --  发起心跳
-    else
-        sys.sendMsg(AIRTALK_TASK_NAME, MSG_AUTH_IND, false, 
-            "鉴权失败" .. (obj and obj["info"] or "")) 
-        log.error("鉴权失败,可能是没有修改PRODUCT_KEY")
-    end
-end
-
--- 命令处理:设备列表更新应答
-local function handle_device_list_response(obj)
-    if obj and obj["result"] == SUCC then
-        g_dev_list = obj["dev_list"]
-        if extalk_configs_local.contact_list_cbfnc then
-            extalk_configs_local.contact_list_cbfnc(g_dev_list)
-        end
-        g_state = SP_T_IDLE
-        sys.sendMsg(AIRTALK_TASK_NAME, MSG_AUTH_IND, true)  -- 完整登录流程结束
-    else
-        sys.sendMsg(AIRTALK_TASK_NAME, MSG_AUTH_IND, false, "更新设备列表失败") 
-    end
-end
-
--- 命令解析路由表
-local cmd_handlers = {
-    ["8003"] = handle_speech_response,  -- 请求对讲应答
-    ["0102"] = handle_incoming_call,    -- 平台通知对端对讲开始
-    ["0103"] = handle_remote_hangup,    -- 平台通知终端对讲结束
-    ["0101"] = handle_device_list_update,-- 平台通知终端更新对讲设备列表
-    ["8001"] = handle_auth_result,      -- 平台对鉴权应答
-    ["8002"] = handle_device_list_response -- 平台对终端获取终端列表应答
-}
-
--- 解析接收到的消息
-local function analyze_v1(cmd, topic, obj)
-    -- 忽略心跳和结束对讲的应答
-    if cmd == "8005" or cmd == "8004" then
-        return
-    end
-    
-    -- 查找并执行对应的命令处理器
-    local handler = cmd_handlers[cmd]
-    if handler then
-        handler(obj)
-    else
-        log.warn("未处理的命令", cmd)
-    end
-end
-
--- MQTT回调处理
-local function mqtt_cb(mqttc, event, topic, payload)
-    log.info(event, topic or "")
-    
-    if event == "conack" then
-        -- MQTT连接成功,开始自定义鉴权流程
-        sys.sendMsg(AIRTALK_TASK_NAME, MSG_CONNECT_ON_IND)
-        g_mqttc:subscribe("ctrl/downlink/" .. g_local_id .. "/#")
-    elseif event == "suback" then
-        if g_state == SP_T_NO_READY then
-            if topic then
-                auth()
-            else
-                sys.sendMsg(AIRTALK_TASK_NAME, MSG_AUTH_IND, false, 
-                    "订阅失败" .. "ctrl/downlink/" .. g_local_id .. "/#") 
-            end
-        elseif g_state == SP_T_CONNECTED and not topic then
-            extalk.speech_off(false, true)
-        end
-    elseif event == "recv" then
-        local result = string.match(topic, g_dl_topic)
-        if result then 
-            local obj = json.decode(payload)
-            analyze_v1(result, topic, obj)
-        end
-    elseif event == "disconnect" then
-        extalk.speech_off(false, true)
-        g_state = SP_T_NO_READY
-    elseif event == "error" then
-        log.error("MQTT错误发生",topic,payload)
-    end
-end
-
--- 任务消息处理
-local function task_cb(msg)
-    if msg[1] == MSG_SPEECH_CONNECT_TO then
-        extalk.speech_off(true, false)
-    else
-        log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
-    end
-end
-
--- 对讲事件回调
-local function airtalk_event_cb(event, param)
-    log.info("airtalk event", event, param)
-    if event == airtalk.EVENT_ERROR then
-        if param == airtalk.ERROR_NO_DATA  and g_s_mode == airtalk.MODE_PERSON then
-            log.error("长时间没有收到音频数据")
-            extalk.speech_off(true, true)
-        end
-    end
-end
-
--- MQTT任务主循环
-local function airtalk_mqtt_task()
-    if g_stask_start  then
-        log.info("airtalk task 已经初始化了")
-        return true
-    end
-    
-    g_stask_start = true
-    local msg, online = nil, false
-    
-    -- 初始化本地ID
-    g_local_id = mobile.imei()
-    g_dl_topic = "ctrl/downlink/" .. g_local_id .. "/(%w%w%w%w)"
-    
-    -- 创建MQTT客户端
-    g_mqttc = mqtt.create(nil, "mqtt.airtalk.luatos.com", 1883, false, {rxSize = 32768})
-    
-    -- 配置对讲参数
-    airtalk.config(airtalk.PROTOCOL_MQTT, g_mqttc, 200) -- 缓冲至少200ms播放
-    airtalk.on(airtalk_event_cb)
-    airtalk.start()
-    
-    -- 配置MQTT客户端
-    g_mqttc:auth(g_local_id, g_local_id, mobile.muid())
-    g_mqttc:keepalive(240) -- 默认值240s
-    g_mqttc:autoreconn(true, 15000) -- 自动重连机制
-    g_mqttc:debug(false)
-    g_mqttc:on(mqtt_cb)
-    
-    log.info("设备信息", g_local_id, mobile.muid())
-    
-    -- 开始连接
-    g_mqttc:connect()
-    online = false
-    
-    while true do
-        -- 等待MQTT连接成功
-        msg = sys.waitMsg(AIRTALK_TASK_NAME, MSG_CONNECT_ON_IND)
-        log.info("connected")
-        
-        -- 处理登录流程
-        while not online do
-            msg = sys.waitMsg(AIRTALK_TASK_NAME, MSG_AUTH_IND, 30000) -- 30秒超时
-            
-            if type(msg) == 'table' then
-                online = msg[2]
-                if online then
-                    -- 鉴权通过,60分钟后重新鉴权
-                    sys.timerLoopStart(auth, 3600000)
-                else
-                    log.info(msg[3])
-                    -- 鉴权失败,5分钟后重试
-                    sys.timerLoopStart(auth, 300000)
-                end
-            else
-                -- 超时未收到鉴权结果,重新发送
-                auth()
-            end
-        end
-        
-        log.info("对讲管理平台已连接")
-        
-        -- 处理在线状态下的消息
-        while online do
-            msg = sys.waitMsg(AIRTALK_TASK_NAME)
-            
-            if type(msg) == 'table' and type(msg[1]) == "number" then
-                if msg[1] == MSG_SPEECH_STOP_TEST_END then
-                    if g_state ~= SP_T_CONNECTING and g_state ~= SP_T_CONNECTED then
-                        log.info("没有对讲", g_state)
-                    else
-                        extalk.speech_off(true, false)
-                    end
-                elseif msg[1] == MSG_SPEECH_ON_IND then
-                    if extalk_configs_local.state_cbfnc then
-                        local state = msg[2] and extalk.START or extalk.UNRESPONSIVE
-                        extalk_configs_local.state_cbfnc({state = state})
-                    end
-                elseif msg[1] == MSG_SPEECH_OFF_IND then
-                    if extalk_configs_local.state_cbfnc then
-                        extalk_configs_local.state_cbfnc({state = extalk.STOP})
-                    end
-                elseif msg[1] == MSG_CONNECT_OFF_IND then
-                    log.info("connect", msg[2])
-                    online = msg[2]
-                end
-            else
-                log.info(type(msg), type(msg and msg[1]))
-            end
-            
-            msg = nil -- 清理引用
-        end
-        
-        online = false -- 重置在线状态
-    end
-end
-
--- 模块初始化
-function extalk.setup(extalk_configs)
-
-    if not extalk_configs or type(extalk_configs) ~= "table" then
-        log.error("AirTalk配置必须为table类型")
-        return false
-    end
-
-    -- 检查配置参数
-    if not check_param(extalk_configs.key, "string", "key") then
-        return false
-    end
-    extalk_configs_local.key = extalk_configs.key
-
-    if not check_param(extalk_configs.heart_break_time, "number", "heart_break_time") then
-        return false
-    end
-    extalk_configs_local.heart_break_time = extalk_configs.heart_break_time
-
-    if not check_param(extalk_configs.contact_list_cbfnc, "function", "contact_list_cbfnc") then
-        return false
-    end
-    extalk_configs_local.contact_list_cbfnc = extalk_configs.contact_list_cbfnc
-
-    if not check_param(extalk_configs.state_cbfnc, "function", "state_cbfnc") then
-        return false
-    end
-    extalk_configs_local.state_cbfnc = extalk_configs.state_cbfnc
-
-    -- 启动任务
-    sys.taskInitEx(airtalk_mqtt_task, AIRTALK_TASK_NAME, task_cb)
-    return true
-end
-
--- 开始对讲
-function extalk.start(id)
-
-    if g_state ~= SP_T_IDLE then
-        log.warn("正在对讲无法开始,当前状态:", g_state)
-        return false
-    end
-
-    if id == nil then
-        -- 广播模式
-        g_remote_id = "all"
-        g_state = SP_T_CONNECTING
-        g_s_mode = airtalk.MODE_GROUP_SPEAKER
-        g_s_type = "broadcast"
-        g_s_topic = string.format("audio/%s/all/%s", 
-            g_local_id, string.sub(tostring(mcu.ticks()), -4, -1))
-        
-        publish_message(string.format("ctrl/uplink/%s/0003", g_local_id), 
-            json.encode({["topic"] = g_s_topic, ["type"] = g_s_type}))
-        sys.timerStart(extalk.wait_speech_to, 15000)
-    else
-        -- 一对一模式
-        log.info("向", id, "主动发起对讲")
-        if id == g_local_id then
-            log.error("不允许本机给本机拨打电话")
-            return false
-        end
-        
-        g_state = SP_T_CONNECTING
-        g_remote_id = id
-        g_s_mode = airtalk.MODE_PERSON
-        g_s_type = "one-on-one"
-        g_s_topic = string.format("audio/%s/%s/%s", 
-            g_local_id, id, string.sub(tostring(mcu.ticks()), -4, -1))
-        
-        publish_message(string.format("ctrl/uplink/%s/0003", g_local_id), 
-            json.encode({["topic"] = g_s_topic, ["type"] = g_s_type}))
-        sys.timerStart(extalk.wait_speech_to, 15000)
-    end
-    
-    return true
-end
-
--- 结束对讲
-function extalk.stop()
-    if g_state ~= SP_T_CONNECTING and g_state ~= SP_T_CONNECTED then
-        log.info("没有对讲,当前状态:", g_state)
-        return false
-    end
-
-    log.info("主动断开对讲")
-    extalk.speech_off(true, false)
-    return true
-end
-
-return extalk

+ 27 - 15
module/Air8000/demo/airtalk/main.lua

@@ -1,13 +1,13 @@
 --[[
 @module  main
-@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@summary LuatOS语音对讲应用主入口,负责加载功能模块
 @version 1.0
-@date    2025.09.19
-@author  梁健
+@date    2025.11.26
+@author 陈媛媛
 @usage
 本demo演示的核心功能为:
-
-airtalk.lua: 进行airtalk 对讲业务,相关使用说明,请见https://docs.openluat.com/value/airtalk/
+1、audio_drv:音频设备初始化与控制
+2、talk:airtalk 对讲业务逻辑处理。
 
 更多说明参考本目录下的readme.md文件
 ]]
@@ -23,14 +23,13 @@ VERSION:项目版本号,ascii string类型
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
 ]]
 
---[[
-本demo可直接在Air8000整机开发板上运行
-]]
+PROJECT = "extalk"
+VERSION = "001.000.000"
+
+--到 iot.openluat.com 创建项目,获取正确的项目key
+PRODUCT_KEY =  "5544VIDOIHH9Nv8huYVyEIGT4tCvldxI"
 
-PROJECT = "audio"
-VERSION = "1.0.0"
-PRODUCT_KEY =  "NrkXcjWwjcc5EFdCrrYnvypBCyJlEaIO"
--- 在日志中打印项目名和项目版本号
+--在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
 -- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
@@ -42,7 +41,21 @@ if wdt then
     sys.timerLoopStart(wdt.feed, 3000)
 end
 
-require "talk"            --  启动airtalk
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+require "audio_drv"        -- 音频驱动模块
+require "talk"             -- 对讲主模块
 
 -- 音频对内存影响较大,不断的打印内存,用于判断是否异常
 sys.timerLoopStart(function()
@@ -53,5 +66,4 @@ sys.timerLoopStart(function()
 -- 用户代码已结束---------------------------------------------
 -- 结尾总是这一句
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!
-
+-- sys.run()之后后面不要加任何语句!!!!!

+ 259 - 62
module/Air8000/demo/airtalk/readme.md

@@ -4,36 +4,52 @@
 
 2、talk.lua:airtalk 对讲业务核心模块
 
+3、audio_drv:音频设备初始化与控制
+
 ## 常量的介绍
 
-1. extalk.START = 1     -- 通话开始
+1. extalk.START            -- 通话开始
 
-2. extalk.STOP = 2      -- 通话结束
+2. extalk.STOP             -- 通话结束
 
-3. extalk.UNRESPONSIVE = 3  -- 对端未响应
+3. extalk.UNRESPONSIVE     -- 对端未响应
 
-4. extalk.ONE_ON_ONE = 5  -- 一对一来电
+4. extalk.ONE_ON_ONE       -- 一对一来电
 
-5. extalk.BROADCAST = 6 -- 广播
+5. extalk.BROADCAST        -- 广播
 
 ## 演示功能概述
 
-1、talk.lua 实现AirTalk对讲核心业务,包括联系人列表管理、对讲状态监控、音频设备控制等功能,实时显示对讲状态和设备信息。
+1、talk.lua 实现AirTalk对讲核心业务。
+
+- 包括群组内联系人列表信息显示、对讲状态监控、音频设备控制等功能,实时显示对讲状态和设备信息。
+
+- 按键处理:
+
+(1)主动发起对讲:按一次Boot键选择指定设备,开始1对1对讲,再按一次Boot键或powerkey键结束对讲;按一次powerkey键开始一对多广播,再按一次Boot键或powerkey键结束广播。
 
-2、main.lua 启动AirTalk对讲服务,按一次Boot键选择群组内第一个联系人,开始1对1对讲,再按一次Boot键结束对讲;按一次powerkey键开始一对多广播,再按一次powerkey或Boot键结束对讲。
+(2)被动接听对讲:当其他设备呼叫本机时,自动接听对讲;按任意键(Boot或Power键)即可结束当前对讲。
 
-3、当收到对讲信息的时候,LED灯常亮,关闭对讲的时候LED 灯灭。
+3、audio_drv:定义所有硬件引脚常量,使用exaudio扩展库初始化音频设备。
+
+2、main.lua 启动AirTalk对讲服务。
 
 ## 演示硬件环境
 
-Air8000开发板一块+喇叭
+Air8000开发板
 
 ![alt text](https://docs.openLuat.com/cdn/image/Air8000%E5%BC%80%E5%8F%91%E6%9D%BF.jpg )
-
-或者Air8000核心板+AirAUDIO_1010 音频配件板+喇叭
+ 
+ 或者Air8000核心板+AirAUDIO_1010 音频扩展板+喇叭
 
 ![alt text]( https://docs.openLuat.com/cdn/image/Air8000%E6%A0%B8%E5%BF%83%E6%9D%BF+1010.jpg)
 
+2、TYPE-C USB数据线一根
+- Air8000开发板/核心板通过 TYPE-C USB 口供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+3、可选AirAudio_1010 配件板一块
+
 Air8000核心板和AirAudio_1010 配件板的硬件接线方式为:
 
 |  Air8000核心板   | AirAUDIO_1010配件板 |
@@ -43,92 +59,273 @@ Air8000核心板和AirAudio_1010 配件板的硬件接线方式为:
 | 19/I2S_LRCK     | I2S_LRCK            |
 | 20/I2S_DIN      | I2S_DIN             |
 | 21/I2S_DOUT     | I2S_DOUT            |
-| 80/I2C_SCL      | I2C_SCL             |
-| 81/I2C_SDA      | I2C_SDA             |
+| 80/I2C0_SCL     | I2C_SCL             |
+| 81/I2C0_SDA     | I2C_SDA             |
 | 82/GPIO17       | PA_EN               |
 | 83/GPIO16       | 8311_EN             |
 | VDD_EXT         | VCC                 |
 | GND             | GND                 |
 
-2、TYPE-C USB数据线一根
-
-- Air8000开发板/核心板通过 TYPE-C USB 口供电;
-
-- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
-
 ## 演示软件环境
 
-1、[Luatools下载调试工具](https://docs.openluat.com/air780epm/common/Luatools/)
+1、[Luatools下载调试工具](https://docs.openluat.com/air780epm/common/Luatools/) 
 
-2、Air8000 V2018版本固件,选择支持TTS功能的固件。不同版本区别参考[Air8000 LuatOS固件版本](https://docs.openluat.com/air8000/luatos/firmware/)。
+2、Air8000 V2018版本固件,选择支持对讲功能的固件。不同版本区别请见https://docs.openluat.com/air8000/luatos/firmware/
 
 3、 luatos需要的脚本和资源文件
 - 脚本和资源文件[点我浏览所有文件](https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/airtalk)
 
-- 准备好软件环境之后,接下来查看[如何烧录项目文件到Air8000核心板中](https://docs.openluat.com/air8000/luatos/common/download/) 或者查看 [Air8000 产品手册](https://docs.openluat.com/air8000/product/shouce/) 中“Air8000 整机开发板使用手册 -> 使用说明”,将本篇文章中演示使用的项目文件烧录到 Air8000 开发板中。
+- 准备好软件环境之后,接下来查看[如何烧录项目文件到Air8000核心板中](https://docs.openluat.com/air8000/luatos/common/hwenv/) 或者查看 Air8000 产品手册 中“Air8000 整机开发板使用手册 -> 使用说明”,将本篇文章中演示使用的项目文件烧录到 Air8000 开发板中。
 
-4、[合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明](https://docs.openluat.com/air780epm/common/luatio/)
-
-5、 lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
+4、lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
 
 ## 演示核心步骤
 
 1、搭建好硬件环境
 
-2、创建群组:详情请见:https://docs.openluat.com/value/airtalk/
-
-3、main.lua中,修改 PRODUCT_KEY 为 IoT 平台对应项目中获取的项目 key。
+2、创建群组:详情请见:[Airtalk](https://docs.openluat.com/value/airtalk/)  第 5.2 章节--创建群组
+
+3、main.lua 中,修改 PRODUCT_KEY 。
+ ``` lua
+ --到 iot.openluat.com 创建项目,获取正确的项目key
+ PRODUCT_KEY =  "5544VIDOIHH9Nv8huYVyEIGT4tCvldxI"
+  ``` 
+
+4、talk.lua 中,修改目标设备终端ID。 
+  ``` lua
+  -- 目标设备终端ID,修改为你想要对讲的终端ID
+  TARGET_DEVICE_ID = "78122397"  -- 请替换为实际的目标设备终端ID
+  ``` 
+
+5、talk.lua 中,修改WiFi连接参数。 
+ ``` lua
+-- WiFi连接参数(如果需要使用WiFi联网,请取消注释use_wifi函数调用)
+local WIFI_CONFIG = {
+    ssid = "茶室-降功耗,找合宙!",  -- WiFi SSID
+    password = "Air123456",         -- WiFi密码
+}
+ ``` 
+6、audio_drv中,根据硬件环境修改pa_ctrl和dac_ctrl配置
+ - Air8000开发板pa_ctrl和dac_ctrl配置
+  ``` lua
+  pa_ctrl = 162,         -- 音频放大器电源控制管脚
+  dac_ctrl = 164,        -- 音频编解码芯片电源控制管脚  
+   ``` 
+- Air8000核心板pa_ctrl和dac_ctrl配置
+ ``` lua
+  pa_ctrl = 17,         -- 音频放大器电源控制管脚
+  dac_ctrl = 16,        -- 音频编解码芯片电源控制管脚 
+   ```
+7、Luatools烧录内核固件和修改后的demo脚本代码
+
+8、烧录成功后,自动开机运行
+- 对讲模块初始化
+- 启动对讲系统
+- LED指示灯初始化
+- 初始化音频设备(通过I2C配置ES8311编解码芯片)
+
+ luatools会打印以下日志
+``` lua
+I/talk.lua:428 对讲模块初始化...
+I/talk.lua:371 启动对讲系统...
+I/talk.lua:377 LED指示灯初始化完成 - GPIO146
+I/talk.lua:383 初始化音频设备...
+I/audio_drv.lua:37 audio_drv 开始初始化音频设备
+I2C_MasterSetup 426:I2C0, Total 65 HCNT 22 LCNT 40
+D/audio codec init es8311 
+I/audio_drv.lua:43 audio_drv 音频设备初始化成功
+I/talk.lua:388 音频初始化成功
+```
 
-4、Luatools烧录内核固件和修改后的demo脚本代码
+9、联网配置:
 
-5、烧录成功后,自动开机运行,会打印以下日志
+9.1 WiFi联网(STA模式)
+- 配置待连接的WiFi名称(SSID)和密码
+- 订阅网络状态变化事件,等待连接就绪
+- 连接成功,等待从AP(路由器)通过DHCP获取IP地址
+- 收到IP就绪事件,获得完整的网络配置(IP、掩码、网关、DNS)
 
+ luatools会打印以下日志
 ``` lua
- I/talk.lua:185 音频初始化成功
- I/talk.lua:193 extalk初始化成功
- I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/866965083769676/0001 内容: {"key":"5544VIDOIHH9Nv8huYVyEIGT4tCvldxI","device_type":2}
- I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/866965083769676/0002 内容: 
- I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/866965083769676/0002 内容: 
- I/talk.lua:37 联系人列表更新:
- I/talk.lua:39   1. ID: 861556079986013, 名称: 
- I/talk.lua:39   2. ID: 74959320, 名称: 866965083769676
- I/extalk.lua:462 对讲管理平台已连接
+ I/exnetif.lua:372 WiFi名称: HONOR
+ I/exnetif.lua:373 密码     : iot12345678
+ I/exnetif.lua:374 ping_ip  : nil
+ I/exnetif.lua:387 WiFi STA初始化完成
+ I/exnetif.lua:64 netdrv 订阅socket连接状态变化事件 WiFi
+ change from 1 to 2
+ I/exnetif.lua:611 notify_status function
+ I/talk.lua:161 WiFi STA事件 CONNECTED HONOR
+ I/talk.lua:163 WiFi已连接,等待获取IP地址
+ D/airlink wifi sta上线了
+ D/netdrv 网卡(2)设置为UP
+ D/ulwip adapter 2 dhcp start netif c11169c
+ D/DHCP dhcp discover C8C2C68F09CE
+ D/ulwip 收到DHCP数据包(len=303)
+ D/DHCP find ip d02ba8c0 192.168.43.208
+ D/DHCP DHCP get ip ready
+ D/ulwip adapter 2 ip 192.168.43.208
+ D/ulwip adapter 2 mask 255.255.255.0
+ D/ulwip adapter 2 gateway 192.168.43.1
+ D/ulwip adapter 2 DNS1:192.168.43.1
+ D/netdrv IP_READY 2 192.168.43.208
+ I/talk.lua:169 IP就绪事件 192.168.43.208 2
+ I/talk.lua:171 IP地址已获取,网络就绪
 ```
 
-6、 点击BOOT 按键,会选择联系人列表第一个人,进行一对一对讲。
+9.2 4G蜂窝网络联网
+  - 系统加载完成后,开始等待网络连接就绪
+  - 默认网卡被设置为4G适配器(ID:1)
+  - 系统周期性主动检查网络状态,初始状态为“未获取到有效IP”
+  - 等待移动网络模块(mobile)完成链路激活和IP地址分配
+  -  网络就绪后,获取到运营商分配的IP地址(如10.90.8.191)
+
+  luatools会打印以下日志
+  ``` lua
+  I/talk.lua:337 等待网络连接就绪...
+  I/talk.lua:438 talk.lua加载完成
+  I/talk.lua:293 主动检查网络状态...
+  I/talk.lua:305 当前默认网卡: 4G 适配器ID: 1
+  W/talk.lua:313 默认网卡未获取到有效IP地址
+  (等待并多次检查...)
+  D/mobile cid1, state0
+  D/mobile bearer act 0, result 0
+  D/mobile NETIF_LINK_ON -> IP_READY
+  D/mobile TIME_SYNC 0
+  I/talk.lua:305 当前默认网卡: 4G 适配器ID: 1
+  I/talk.lua:310 IP地址: 10.90.8.191 子网掩码: 255.255.255.255 网关: 0.0.0.0
+  I/talk.lua:354 主动检查发现网络已就绪  
+  ```
+10、 网络就绪,对讲核心功能初始化
+  - 网络连接成功后(无论是WiFi或4G),系统初始化extalk对讲核心功
+  - 读取并上报本机设备信息(IMEI和设备密钥)
+  - 通过DNS解析并连接至对讲管理平台的MQTT服务器(mqtt.airtalk.luatos.com)
+  - 与服务器完成MQTT协议握手、订阅主题、并上报设备信息进行认证
+  - 从服务器获取并更新当前账号下的联系人/设备列表
+  - 对讲管理平台连接成功,设备进入可操作状态
+
+  luatools会打印以下日志
+  ``` lua
+  --无论通过WiFi还是4G联网,都会触发此流程
+  I/talk.lua:324 初始化extalk对讲功能...
+  I/extalk.lua:431 设备信息 864793080177038 20250722144318A235862A2562652411
+  dns_run 676:mqtt.airtalk.luatos.com state 0 id 1 ipv6 0 use dns serverX, try 0
+  D/net adapter X connect 121.196.102.79:1883 TCP // X为网卡ID
+  I/talk.lua:331 extalk初始化成功
+  I/talk.lua:414 对讲系统准备就绪,等待按键操作...
+  I/extalk.lua:351 conack
+  I/extalk.lua:440 connected
+  I/extalk.lua:351 suback true
+  I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/864793080177038/0001 内容: {"key":"5544VIDOIHH9Nv8huYVyEIGT4tCvldxI","device_type":1}
+  I/extalk.lua:351 sent 0
+  I/extalk.lua:351 recv ctrl/downlink/864793080177038/8001
+  I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/864793080177038/0002 内容:
+  I/extalk.lua:351 sent 0
+  I/extalk.lua:351 recv ctrl/downlink/864793080177038/8002
+  I/talk.lua:64 联系人列表更新:
+  I/talk.lua:66   1. ID: 78122397, 名称: 对讲
+  I/talk.lua:66   2. ID: 46365487, 名称: 46365487
+   ... 
+  I/extalk.lua:462 对讲管理平台已连接
+
+
+11、点击BOOT 按键,会选择指定IMEI的目标设备,进行一对一对讲,再按一次Boot键或powerkey键结束对讲。
+- 按下Boot键,启动一对一对讲流程
+- 向指定IMEI设备(终端ID:78122397)发起对讲请求
+- 通过MQTT向服务器发送一对一对讲请求,包含音频通道信息
+- 进入一对一对讲模式
+- 对讲连接建立成功,开始语音传输
+- 系统状态更新为对讲中
+- 再次按Boot或powerkey键,主动结束对讲
+
 luatools会打印以下日志
 ``` lua
-I/talk.lua:154 开始一对一对讲
-I/extalk.lua:555 向 861556079986013 主动发起对讲
-I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/866965083769676/0003 内容: {"type":"one-on-one","topic":"audio\/866965083769676\/861556079986013\/5360"}
+I/talk.lua:136 boot_key_callback
+I/talk.lua:217 开始一对一对讲,目标设备: 78122397
+I/extalk.lua:555 向 78122397 主动发起对讲
+I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/864793080177038/0003 内容: {"type":"one-on-one","topic":"audio\/864793080177038\/78122397\/6818"}
+I/extalk.lua:351 recv ctrl/downlink/864793080177038/8003
 I/extalk.lua:131 对讲模式 0
-I/talk.lua:54 对讲开始
-……
-I/talk.lua:143 结束当前对讲
+I/talk.lua:80 对讲开始
+I/talk.lua:119 当前对讲状态: 正在对讲
+// ... 音频设备切换、RTP流同步等底层日志
+// 用户按键结束对讲
+I/talk.lua:136 boot_key_callback //或 I/talk.lua:142 power_key_callback
+I/talk.lua:202 结束当前对讲
 I/extalk.lua:583 主动断开对讲
+I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/864793080177038/0004 内容: {"to":"78122397"}
+I/extalk.lua:351 recv ctrl/downlink/864793080177038/8004
+I/talk.lua:60 对讲结束 
 ```
 
-7、 点击POWERKEY按键,会进行广播,所有群组内的人,都会收到对讲消息。
+12、 点击POWERKEY按键,会进行广播,所有群组内的人,都会收到对讲消息,再按一次Boot键或powerkey键结束广播。
+- 按下Power键,启动广播对讲
+- 通过MQTT向服务器发送广播请求,音频通道主题包含"all"标识
+- 对讲模式1,进入广播对讲模式
+- 广播连接建立成功,开始向所有设备广播
+- 系统状态更新为对讲中
+- 再次按Boot或powerkey键,结束广播对讲
+
 luatools会打印以下日志
+
 ``` lua
- I/talk.lua:150 开始一对多广播
- I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/866965083769676/0003 内容: {"type":"broadcast","topic":"audio\/866965083769676\/all\/7287"}
- I/extalk.lua:131 对讲模式 1
- I/talk.lua:54 对讲开始
- ……
- I/talk.lua:143 结束当前对讲
- I/extalk.lua:583 主动断开对讲
-```
+I/talk.lua:142 power_key_callback
+I/talk.lua:210 开始一对多广播
+I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/864793080177038/0003 内容: {"type":"broadcast","topic":"audio\/864793080177038\/all\/8618"}
+I/extalk.lua:351 recv ctrl/downlink/864793080177038/8003
+I/extalk.lua:131 对讲模式 1
+I/talk.lua:80 对讲开始
+I/talk.lua:119 当前对讲状态: 正在对讲
+// ... 音频设备切换等底层日志
+// 用户按键结束广播
+I/talk.lua:136 boot_key_callback
+I/talk.lua:202 结束当前对讲
+I/extalk.lua:583 主动断开对讲
+I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/864793080177038/0004 内容: {"to":"all"}
+I/extalk.lua:351 recv ctrl/downlink/864793080177038/8004
+I/talk.lua:60 对讲结束 //或状态变为空闲
+ ```
+
+13、当其他设备或手机/PC的web网页端对设备发起一对一对讲。
+- 收到其他设备的对讲呼叫请求,系统自动接听对讲(无需用户按键操作)
+- 进入一对一对讲模式
+- 通过MQTT通知服务器接听成功
+- 确认对讲开始,语音通道建立
+- 系统状态更新为对讲中
+- 对方结束对讲,本机对讲也随之结束
 
-8、当手机端对设备发起对讲,luatools会打印以下日志
+luatools会打印以下日志
 ``` lua
-I/talk.lua:73 对讲测试 来电
+I/extalk.lua:351 recv ctrl/downlink/864793080177038/0102
+I/talk.lua:103 对讲 来电
+I/talk.lua:119 当前对讲状态: 正在对讲
 I/extalk.lua:131 对讲模式 0
-I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/866965083769676/8102 内容: {"result":"success","info":"","topic":"audio\/74959320\/866965083769676\/au4p"}
-I/talk.lua:54 对讲开始
-……
-I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/866965083769676/8103 内容: {"info":"","result":"success"}
-I/talk.lua:57 对讲结束
+I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/864793080177038/8102 内容: {"result":"success","info":"","topic":"audio\/78122397\/864793080177038\/akmu"}
+I/talk.lua:80 对讲开始
+// ... 音频设备切换、RTP流同步等底层日志
+// 对方挂断,本机收到结束指令
+I/extalk.lua:351 recv ctrl/downlink/864793080177038/8004 //或其他结束指令
+I/talk.lua:60 对讲结束
 ```
 
+14、当其他设备或手机/PC的web网页端对设备发起广播。
+- 收到其他设备的广播邀请
+- 系统自动加入广播(无需按键操作)
+- 对讲模式2,进入被动接听广播模式
+- 通过MQTT通知服务器加入广播成功
+- 确认广播对讲开始
+- 系统状态更新为对讲中
+- 广播结束,本机对讲也随之结束
 
+luatools会打印以下日志
+``` lua
+I/extalk.lua:351 recv ctrl/downlink/864793080177038/0102
+I/talk.lua:116 对讲 开始广播 //注意:此处指“开始接收广播”
+I/talk.lua:119 当前对讲状态: 正在对讲
+I/extalk.lua:131 对讲模式 2
+I/extalk.lua:83 MQTT发布 - 主题: ctrl/uplink/864793080177038/8102 内容: {"result":"success","info":"","topic":"audio\/78122397\/all\/yne7"}
+I/talk.lua:80 对讲开始
+// ... 音频设备切换、RTP流同步等底层日志
+// 广播结束,本机收到结束指令
+I/extalk.lua:351 recv ctrl/downlink/864793080177038/8004
+I/talk.lua:60 对讲结束
+```

+ 299 - 114
module/Air8000/demo/airtalk/talk.lua

@@ -1,67 +1,61 @@
 --[[
-    演示airtalk基本功能
-    1.  按键操作
-    按一次boot,开始1对1对讲,再按一次boot,结束对讲
-    按一次powerkey,开始1对多对讲,再按一次powerkey或者boot,结束对讲
-    2. 指示灯
-    当收到对讲信息的时候,LED灯常亮,关闭对讲的时候LED 灯灭
+@module  talk
+@summary Airtalk 对讲业务核心模块
+@date    2025.12.03
+@author  陈媛媛
+@usage
+本demo演示的核心功能为:
+1. 支持广播对讲(一对多)和一对一对讲;
+2. 自动设备发现和管理;
+3. 按一次Boot键选择指定设备,开始1对1对讲,再按一次Boot键或powerkey键结束对讲;
+4. 按一次powerkey键开始一对多广播,再按一次Boot键或powerkey键结束广播;
+5. 通过LED指示灯显示对讲状态(亮:对讲中,灭:空闲);
+6. 支持目标设备ID指定呼叫,可配置TARGET_DEVICE_ID呼叫特定设备;
+7. 支持4G和WiFi两种联网方式,默认使用4G网络。
 ]]
 
--- 引入必要模块
-
 local extalk = require "extalk"
-local exaudio = require "exaudio"
+local audio_drv = require "audio_drv"  -- 引入音频驱动模块
 
 -- 配置日志格式
 log.style(1)
 
 -- 常量定义
-local USER_TASK_NAME = "user_task"
-local MSG_KEY_PRESS = 12  -- 按键消息
-
--- 全局状态变量
-local g_dev_list = nil    -- 设备列表
-local g_speech_active = false  -- 对讲状态标记
-
--- 音频初始化参数
-local audio_setup_param = {
-    model = "es8311",       -- 音频编解码类型,可填入"es8311","es8211"
-    i2c_id = 0,             -- i2c_id,可填入0,1 并使用pins工具配置对应的管脚
-    pa_ctrl = 162,         -- 音频放大器电源控制管脚
-    dac_ctrl = 164,        --  音频编解码芯片电源控制管脚   
-}
-
---  因为8000,8000A,8000W,支持Wifi ,可以使用WIFI 来联网
-local function use_wifi()
-    exnetif = require("exnetif")
-
-    exnetif.set_priority_order({ { -- 次优先级:WiFi
-        WIFI = {
-
-            ssid = "机房-降功耗,找合宙!",
+local USER_TASK_NAME = "user_task"  -- 用户任务名称
+local MSG_KEY_PRESS = 12            -- 按键消息类型
+local MSG_NETWORK_READY = 13        -- 网络就绪消息类型
 
-            password = "Air123456", 
+-- 目标设备ID,修改为你想要对讲的终端ID
+TARGET_DEVICE_ID = "78122397"
 
-        }
-    }})
+-- 全局状态变量
+local g_dev_list = nil              -- 设备列表,存储所有可用对讲设备
+local g_speech_active = false       -- 对讲状态标记,true表示正在对讲中
+local g_network_ready = false       -- 网络就绪标志
 
-    -- 设置网络状态回调
+-- 指示灯配置
+-- Air8000核心板:GPIO20(核心板板载LED指示灯)
+-- Air8000开发板:GPIO146(开发板上的LED指示灯)
+local LED_GPIO = 146
+local LED = nil
 
-    exnetif.notify_status(function(net_type, adapter)
+-- ========================== 联网方式配置 ==========================
 
-        log.info("网络切换至:", net_type)
+-- WiFi连接参数(如果需要使用WiFi联网,请取消注释use_wifi函数调用)
+local WIFI_CONFIG = {
+    ssid = "茶室-降功耗,找合宙!",
+    password = "Air123456",
 
-    end)
+}
 
-    -- wifi的STA相关事件
-    sys.subscribe("WLAN_STA_INC", function(evt, data)
-        -- evt 可能的值有: "CONNECTED", "DISCONNECTED"
-        -- 当evt=CONNECTED, data是连接的AP的ssid, 字符串类型
-        -- 当evt=DISCONNECTED, data断开的原因, 整数类型
-        log.info("收到STA事件", evt, data)
-    end)
+-- 低功耗模式配置
+local POWER_SAVE_CONFIG = {
+    enable_wifi_low_power = true,   -- 启用WiFi低功耗
+    enable_4g_low_power = true,     -- 启用4G低功耗
+    pause_airlink = true,           -- 暂停airlink通信
+}
 
-end
+-- ========================== 联系人列表回调 ==========================
 
 -- 联系人列表回调函数
 local function contact_list_callback(dev_list)
@@ -77,10 +71,7 @@ local function contact_list_callback(dev_list)
     end
 end
 
-local gpio_number = 20 -- air8000 核心板上的23 管脚
-
-LED = gpio.setup(gpio_number, 1) -- 设置为LED输出模式,用于指示对讲功能
-
+-- ========================== 对讲状态回调 ==========================
 
 -- 对讲状态回调函数
 local function speech_state_callback(event_table)
@@ -88,18 +79,19 @@ local function speech_state_callback(event_table)
     
     if event_table.state == extalk.START then
         log.info("对讲开始")
-        LED(1)
+        if LED then LED(1) end  -- LED亮
         g_speech_active = true
     elseif event_table.state == extalk.STOP then
-        LED(0)
+        if LED then LED(0) end  -- LED灭
         log.info("对讲结束")
         g_speech_active = false
     elseif event_table.state == extalk.UNRESPONSIVE then
+        if LED then LED(0) end  -- LED灭
         log.info("对端未响应")
         g_speech_active = false
     elseif event_table.state == extalk.ONE_ON_ONE then
+        if LED then LED(1) end  -- LED亮
         g_speech_active = true
-        LED(1)
         local dev_name = "未知设备"
         if g_dev_list then
             for i = 1, #g_dev_list do
@@ -111,8 +103,8 @@ local function speech_state_callback(event_table)
         end
         log.info(string.format("%s 来电", dev_name))
     elseif event_table.state == extalk.BROADCAST then
+        if LED then LED(1) end  -- LED亮
         g_speech_active = true
-        LED(1)
         local dev_name = "未知设备"
         if g_dev_list then
             for i = 1, #g_dev_list do
@@ -124,65 +116,93 @@ local function speech_state_callback(event_table)
         end
         log.info(string.format("%s 开始广播", dev_name))
     end
+    
+    log.info("当前对讲状态:", g_speech_active and "正在对讲" or "空闲")
 end
 
+-- ========================== extalk配置 ==========================
+
 -- extalk配置参数
 local extalk_configs = {
-    key = PRODUCT_KEY,
-    heart_break_time = 120,  -- 心跳间隔(单位秒)
+    key = PRODUCT_KEY,           -- 产品密钥,从main.lua传入
+    heart_break_time = 120,      -- 心跳间隔(单位秒)
     contact_list_cbfnc = contact_list_callback,
     state_cbfnc = speech_state_callback,
 }
 
--- 按键回调函数 - Boot键
+-- ========================== 按键处理 ==========================
+
+-- Boot键回调函数
 local function boot_key_callback()
-    log.info("boot_key_callback++++++")
+    log.info("boot_key_callback")
     sys.sendMsg(USER_TASK_NAME, MSG_KEY_PRESS, false)  -- false表示Boot键
 end
 
--- 按键回调函数 - Power键
+-- Power键回调函数
 local function power_key_callback()
-    log.info("power_key_callback++++++")
+    log.info("power_key_callback")
     sys.sendMsg(USER_TASK_NAME, MSG_KEY_PRESS, true)   -- true表示Power键
 end
 
+-- 网络状态回调函数
+local function network_status_callback(net_type, adapter)
+    log.info("网络切换至:", net_type)
+    if net_type and not g_network_ready then
+        log.info("网络已就绪,准备初始化对讲功能")
+        g_network_ready = true
+        sys.sendMsg(USER_TASK_NAME, MSG_NETWORK_READY, {net_type = net_type, adapter = adapter})
+    end
+end
+
+-- WiFi STA连接事件回调函数
+local function wlan_sta_callback(evt, data)
+    -- evt 可能的值有: "CONNECTED", "DISCONNECTED"
+    -- 当evt=CONNECTED, data是连接的AP的ssid, 字符串类型
+    -- 当evt=DISCONNECTED, data断开的原因, 整数类型
+    log.info("WiFi STA事件", evt, data)
+    if evt == "CONNECTED" then
+        log.info("WiFi已连接,等待获取IP地址")
+    end
+end
+
+-- IP就绪事件回调
+local function ip_ready_callback(ip, adapter)
+    log.info("IP就绪事件", ip, adapter)
+    if not g_network_ready and ip and ip ~= "0.0.0.0" then
+        log.info("IP地址已获取,网络就绪")
+        g_network_ready = true
+        
+        -- 获取网卡类型名称
+        local adapter_names = {
+            [socket.LWIP_ETH] = "以太网",
+            [socket.LWIP_STA] = "WiFi",
+            [socket.LWIP_GP] = "4G",
+            [socket.LWIP_USER1] = "8101SPI以太网"
+        }
+        local adapter_name = adapter_names[adapter] or "未知"
+        
+        sys.sendMsg(USER_TASK_NAME, MSG_NETWORK_READY, {net_type = adapter_name, adapter = adapter, ip = ip})
+    end
+end
+
 -- 初始化按键
 local function init_buttons()
-    -- 配置Boot键 (GPIO0)
+    -- 配置Boot键 (GPIO0),下拉电阻,上升沿触发
     gpio.setup(0, boot_key_callback, gpio.PULLDOWN, gpio.RISING)
     gpio.debounce(0, 200, 1)  -- 200ms去抖
     
-    -- 配置Power键
+    -- 配置Power键,上拉电阻,下降沿触发
     gpio.setup(gpio.PWR_KEY, power_key_callback, gpio.PULLUP, gpio.FALLING)
     gpio.debounce(gpio.PWR_KEY, 200, 1)  -- 200ms去抖
 end
 
--- 查找第一个可用的对端设备ID
-local function find_first_remote_device()
-    if not g_dev_list or #g_dev_list == 0 then
-        log.warn("没有找到可用的设备")
-        return nil
-    end
-    
-    local local_id = mobile.imei()
-    for i = 1, #g_dev_list do
-        local dev_id = g_dev_list[i]["id"]
-        if dev_id and dev_id ~= local_id then
-            return dev_id
-        end
-    end
-    
-    log.warn("没有找到其他可用设备")
-    return nil
-end
-
 -- 处理按键消息
 local function handle_key_press(is_power_key)
     if g_speech_active then
         -- 当前正在对讲,按任何键都结束对讲
         log.info("结束当前对讲")
         extalk.stop()
-        LED(0)       -- 关闭LED 灯
+        if LED then LED(0) end  -- 关闭LED
         g_speech_active = false
     else
         -- 当前未在对讲,根据按键类型开始不同对讲
@@ -192,62 +212,227 @@ local function handle_key_press(is_power_key)
             extalk.start()  -- 不带参数表示广播
         else
             -- Boot键:开始一对一对讲
-            log.info("开始一对一对讲")
-            local remote_id = find_first_remote_device()
-            if remote_id then
-                extalk.start(remote_id)
+            -- 只呼叫指定的目标设备,不查找其他设备
+            if TARGET_DEVICE_ID and TARGET_DEVICE_ID ~= "" then
+                -- 直接呼叫指定设备
+                log.info("开始一对一对讲,目标设备:", TARGET_DEVICE_ID)
+                extalk.start(TARGET_DEVICE_ID)
             else
-                log.error("无法开始一对一对讲,没有找到可用设备")
+                log.error("无法开始一对一对讲,未配置目标设备ID")
+                log.error("请在talk.lua中设置TARGET_DEVICE_ID变量")
             end
         end
     end
 end
 
+-- ========================== 联网功能 ==========================
+
+-- WiFi功能(可选)
+local function use_wifi()
+    log.info("配置WiFi联网...")
+    local exnetif = require("exnetif")
+
+    -- 设置网络优先级,WiFi作为次优先级
+    exnetif.set_priority_order({ { 
+        WIFI = {
+            ssid = WIFI_CONFIG.ssid,
+            password = WIFI_CONFIG.password,
+        }
+    }})
 
-local function lower_enter()     -- 如果需要进入低功耗,请在task 中调用此函数
-    -- WiFi模组进入低功耗模式
-    pm.power(pm.WORK_MODE, 1, 1)
-    -- 同时4G进入低功耗模式
-    pm.power(pm.WORK_MODE, 1)
-    sys.wait(20)
-    -- 暂停airlink通信,进一步降低功耗
-    airlink.pause(1)
+    -- 设置网络状态回调
+    exnetif.notify_status(network_status_callback)
+
+    -- 订阅WiFi STA连接事件
+    sys.subscribe("WLAN_STA_INC", wlan_sta_callback)
     
+    -- 订阅IP就绪事件(作为备份,以防exnetif回调不触发)
+    sys.subscribe("IP_READY", ip_ready_callback)
+end
+
+-- 等待低功耗模式设置的函数
+local function wait_low_power_setup()
+    -- 使用sys.wait等待低功耗模式设置生效
+    -- 原因:需要等待硬件完成低功耗切换,通常需要几毫秒到几十毫秒
+    -- 使用20ms的等待时间,确保设置生效
+    sys.wait(20)  -- 等待20ms,确保低功耗模式设置生效
+end
+
+-- 低功耗模式(可选)
+local function lower_enter()
+    log.info("进入低功耗模式...")
+    
+    if POWER_SAVE_CONFIG.enable_wifi_low_power then
+        -- WiFi模组进入低功耗模式
+        pm.power(pm.WORK_MODE, 1, 1)
+        log.info("WiFi低功耗模式已启用")
+    end
+    
+    if POWER_SAVE_CONFIG.enable_4g_low_power then
+        -- 4G模组进入低功耗模式
+        pm.power(pm.WORK_MODE, 1)
+        log.info("4G低功耗模式已启用")
+    end
+    
+    -- 等待低功耗模式设置生效
+    wait_low_power_setup()
+    
+    if POWER_SAVE_CONFIG.pause_airlink then
+        -- 暂停airlink通信,进一步降低功耗
+        airlink.pause(1)
+        log.info("airlink通信已暂停")
+    end
+    
+    log.info("低功耗模式配置完成")
+end
+
+-- ========================== 主任务 ==========================
+
+-- 检查网络状态
+local function check_network_status()
+    log.info("主动检查网络状态...")
+    
+    -- 获取当前默认网卡
+    local default_adapter = socket.dft()
+    if default_adapter then
+        local adapter_names = {
+            [socket.LWIP_ETH] = "以太网",
+            [socket.LWIP_STA] = "WiFi",
+            [socket.LWIP_GP] = "4G",
+            [socket.LWIP_USER1] = "8101SPI以太网"
+        }
+        local adapter_name = adapter_names[default_adapter] or "未知"
+        log.info("当前默认网卡:", adapter_name, "适配器ID:", default_adapter)
+        
+        -- 获取当前默认网卡的IP地址
+        local ip, mask, gw = socket.localIP()
+        if ip and ip ~= "0.0.0.0" then
+            log.info("IP地址:", ip, "子网掩码:", mask, "网关:", gw)
+            return true, {net_type = adapter_name, adapter = default_adapter, ip = ip}
+        else
+            log.warn("默认网卡未获取到有效IP地址")
+        end
+    else
+        log.warn("无法获取当前默认网卡")
+    end
+    
+    return false
+end
+
+-- 初始化对讲功能
+local function init_extalk()
+    log.info("初始化extalk对讲功能...")
+    
+    local extalk_init_ok = extalk.setup(extalk_configs)
+    if not extalk_init_ok then
+        log.error("extalk初始化失败")
+        return false
+    end
+    log.info("extalk初始化成功")
+    return true
+end
+
+-- 等待网络就绪
+local function wait_for_network()
+    log.info("等待网络连接就绪...")
+    
+    -- 等待网络就绪消息,但也会主动检查
+    local max_wait_time = 30000  -- 最长等待30秒
+    local start_time = mcu.ticks()
+    local network_ready = false
+    
+    while not network_ready and (mcu.ticks() - start_time < max_wait_time) do
+        -- 等待网络就绪消息或主动检查
+        local msg = sys.waitMsg(USER_TASK_NAME, MSG_NETWORK_READY, 2000)
+        if msg and msg[1] == MSG_NETWORK_READY then
+            log.info("收到网络就绪消息:", msg[2].net_type, "IP:", msg[2].ip or "未知")
+            network_ready = true
+        else
+            -- 主动检查网络状态
+            local status_ok, network_info = check_network_status()
+            if status_ok and not network_ready then
+                log.info("主动检查发现网络已就绪")
+                network_ready = true
+                sys.sendMsg(USER_TASK_NAME, MSG_NETWORK_READY, network_info)
+            end
+        end
+        
+        -- 超时检查
+        if mcu.ticks() - start_time > max_wait_time then
+            log.warn("网络连接超时,尝试强制初始化对讲")
+            break
+        end
+    end
+    
+    return network_ready
 end
 
 -- 用户主任务
 local function user_main_task()
-    -- 初始化音频
-    local audio_init_ok = exaudio.setup(audio_setup_param)
-    if not audio_init_ok then
+    log.info("启动对讲系统...")
+    
+    -- 初始化LED指示灯
+    LED = gpio.setup(LED_GPIO, 1)
+    if LED then
+        LED(0)  -- 初始状态关闭
+        log.info("LED指示灯初始化完成 - GPIO"..LED_GPIO)
+    else
+        log.warn("LED初始化失败,GPIO"..LED_GPIO)
+    end
+    
+    -- 初始化音频设备
+    log.info("初始化音频设备...")
+    if not audio_drv.init() then
         log.error("音频初始化失败")
         return
     end
     log.info("音频初始化成功")
+
+    -- 【可选】使用WiFi联网(取消注释以启用)
+    -- use_wifi()
     
-    -- 初始化extalk
-    local extalk_init_ok = extalk.setup(extalk_configs)
-    if not extalk_init_ok then
-        log.error("extalk初始化失败")
-        return
+    -- 【可选】进入低功耗模式(取消注释以启用)
+    -- lower_enter()
+    
+    -- 等待网络就绪
+    local network_ready = wait_for_network()
+    
+    -- 如果网络就绪,初始化对讲功能
+    if network_ready then
+        if not init_extalk() then
+            log.error("对讲功能初始化失败,系统无法正常工作")
+            return
+        end
+    else
+        log.warn("网络未就绪,但尝试强制初始化对讲")
+        if not init_extalk() then
+            log.error("对讲功能初始化失败,系统无法正常工作")
+            return
+        end
     end
-    log.info("extalk初始化成功")
-    LED(0)
-    -- use_wifi()                  -- 使用wifi 方式联网
-    -- lower_enter()               -- 需要进入低功耗,请打开此函数
-    -- 等待按键消息并处理
+    
+    log.info("对讲系统准备就绪,等待按键操作...")
+    
+    -- 主消息循环 - 等待和处理按键消息
     while true do
         local msg = sys.waitMsg(USER_TASK_NAME, MSG_KEY_PRESS)
         if msg and msg[1] == MSG_KEY_PRESS then
-            handle_key_press(msg[2])  -- msg[2]区分是Power键(true)还是Boot键(false)
+            handle_key_press(msg[2])  -- msg[2]区分Power键(true)和Boot键(false)
         end
     end
 end
 
--- 初始化按键
-init_buttons()
+-- ========================== 初始化 ==========================
 
--- 启动用户任务
-sys.taskInitEx(user_main_task, USER_TASK_NAME)
+-- 系统初始化
+local function init()
+    log.info("对讲模块初始化...")
+    init_buttons()
+    -- 使用sys.taskInitEx创建支持waitMsg的任务
+    sys.taskInitEx(user_main_task, USER_TASK_NAME)
+end
 
+-- 直接初始化,无需等待
+init()
 
+log.info("talk.lua加载完成")