工程提交
This commit is contained in:
BIN
4G/tools/resource/soc_script/v2025.12.31.22.zip
Normal file
BIN
4G/tools/resource/soc_script/v2025.12.31.22.zip
Normal file
Binary file not shown.
@@ -0,0 +1,60 @@
|
||||
--[[
|
||||
@module air153C_wtd
|
||||
@summary 添加软件看门狗功能,防止死机
|
||||
@data 2023.5.23
|
||||
@author 翟科研
|
||||
@usage
|
||||
--local air153C_wtd = require ("air153C_wtd")
|
||||
-- 用法实例
|
||||
-- sys.taskInit(function ()
|
||||
-- air153C_wtd.init(28)
|
||||
-- air153C_wtd.feed_dog(28,10)--28为看门狗引脚,10为设置喂狗时间
|
||||
-- --air153C_wtd.set_time(1)--开启定时模式再打开此代码,否则无效
|
||||
-- end)
|
||||
]]
|
||||
local sys = require "sys"
|
||||
_G.sysplus = require("sysplus")
|
||||
air153C_wtd={}
|
||||
--[[
|
||||
初始化引脚
|
||||
@api air153C_wtd.init(watchdogPin)
|
||||
@int 看门狗控制引脚
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
air153C_wtd.init(28)
|
||||
]]
|
||||
function air153C_wtd.init(watchdogPin)
|
||||
gpio.setup(watchdogPin,0,gpio.PULLDOWN)
|
||||
gpio.set(watchdogPin,0)
|
||||
end
|
||||
function air153C_wtd.callback(watchdogPin)
|
||||
gpio.set(watchdogPin,0)
|
||||
end
|
||||
--[[
|
||||
调用此函数进行喂狗
|
||||
@api air153C_wtd.feed_dog(watchdogPin)
|
||||
@int watchdogPin设置看门狗控制引脚
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
air153C_wtd.feed_dog(28)
|
||||
]]
|
||||
function air153C_wtd.feed_dog(watchdogPin)
|
||||
local watchdogFeedDuration = 400
|
||||
gpio.set(watchdogPin,1)
|
||||
sys.timerStart(air153C_wtd.callback,watchdogFeedDuration,watchdogPin)
|
||||
end
|
||||
--[[
|
||||
调用此函数关闭喂狗,谨慎使用!
|
||||
@api air153C_wtd.close_watch_dog(watchdogPin)
|
||||
@int watchdogPin设置看门狗控制引脚
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
air153C_wtd.close_watch_dog(28)
|
||||
]]
|
||||
function air153C_wtd.close_watch_dog(watchdogPin)
|
||||
local watchdogStopDuration = 700
|
||||
gpio.set(watchdogPin,1)
|
||||
sys.timerStart(air153C_wtd.callback,watchdogStopDuration,watchdogPin)
|
||||
end
|
||||
|
||||
return air153C_wtd
|
||||
239
4G/tools/resource/soc_script/v2025.12.31.22/lib/airlbs.lua
Normal file
239
4G/tools/resource/soc_script/v2025.12.31.22/lib/airlbs.lua
Normal file
@@ -0,0 +1,239 @@
|
||||
--[[
|
||||
@module airlbs
|
||||
@summary airlbs 定位服务(收费服务,需自行联系销售申请)
|
||||
@version 1.1
|
||||
@date 2024.12.30
|
||||
@author Dozingfiretruck
|
||||
@usage
|
||||
-- lbsloc 是异步回调接口,
|
||||
-- lbsloc2 是是同步接口。
|
||||
-- lbsloc比lbsloc2多了一个请求地址文本的功能。
|
||||
-- lbsloc 和 lbsloc2 都是免费LBS定位的实现方式;
|
||||
-- airlbs 扩展库是收费 LBS 的实现方式。
|
||||
]]
|
||||
|
||||
|
||||
sys = require("sys")
|
||||
sysplus = require("sysplus")
|
||||
libnet = require "libnet"
|
||||
|
||||
local airlbs_host = "airlbs.openluat.com"
|
||||
local airlbs_port = 12413
|
||||
|
||||
local lib_name = "airlbs"
|
||||
local lib_topic = lib_name .. "topic"
|
||||
|
||||
local location_data = 0
|
||||
local disconnect = -1
|
||||
local airlbs_timeout = 15000
|
||||
|
||||
local airlbs = {}
|
||||
|
||||
local function airlbs_task(task_name, buff, timeout, adapter)
|
||||
local netc = socket.create(nil, lib_name)
|
||||
socket.config(netc, nil, true) -- udp
|
||||
|
||||
sysplus.cleanMsg(lib_name)
|
||||
local result = libnet.connect(lib_name, 15000, netc, airlbs_host, airlbs_port)
|
||||
if result then
|
||||
log.info(lib_name, "服务器连上了")
|
||||
libnet.tx(lib_name, 0, netc, buff)
|
||||
else
|
||||
log.info(lib_name, "服务器没连上了!!!")
|
||||
sys.publish(lib_topic, disconnect)
|
||||
libnet.close(lib_name, 5000, netc)
|
||||
return
|
||||
end
|
||||
buff:del()
|
||||
while result do
|
||||
local succ, param = socket.rx(netc, buff)
|
||||
if not succ then
|
||||
log.error(lib_name, "服务器断开了", succ, param)
|
||||
sys.publish(lib_topic, disconnect)
|
||||
break
|
||||
end
|
||||
if buff:used() > 0 then
|
||||
local location = nil
|
||||
local data = buff:query(0, 1) -- 获取数据
|
||||
if data:toHex() == '00' then
|
||||
location = json.decode(buff:query(1))
|
||||
else
|
||||
log.error(lib_name, "not json data")
|
||||
end
|
||||
sys.publish(lib_topic, location_data, location)
|
||||
buff:del()
|
||||
break
|
||||
end
|
||||
result, param, param2 = libnet.wait(lib_name, timeout, netc)
|
||||
log.info(lib_name, "wait", result, param, param2)
|
||||
if param == false then
|
||||
log.error(lib_name, "服务器断开了", succ, param)
|
||||
sys.publish(lib_topic, disconnect)
|
||||
break
|
||||
end
|
||||
end
|
||||
libnet.close(lib_name, 5000, netc)
|
||||
end
|
||||
|
||||
-- 处理未识别的网络消息
|
||||
local function netCB(msg)
|
||||
log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
|
||||
end
|
||||
|
||||
--[[
|
||||
获取定位数据
|
||||
@api airlbs.request(param)
|
||||
@param table 参数(联系销售获取id与key) project_id:项目ID project_key:项目密钥 timeout:超时时间,单位毫秒 默认15000 adapter: 网络适配器id,可选,默认是平台自带的网络协议栈
|
||||
@return bool 成功返回true,失败会返回false
|
||||
@return table 定位成功生效,成功返回定位数据
|
||||
@usage
|
||||
--注意:函数内因使用了sys.waitUntil阻塞接口,所以api需要在协程中使用
|
||||
--注意:使用前需同步时间
|
||||
|
||||
local airlbs = require "airlbs"
|
||||
|
||||
sys.taskInit(function()
|
||||
-- 等待网络就绪
|
||||
sys.waitUntil("IP_READY")
|
||||
-- 执行时间同步
|
||||
socket.sntp()
|
||||
sys.waitUntil("NTP_UPDATE", 10000)
|
||||
while 1 do
|
||||
-- airlbs请求定位
|
||||
local result ,data = airlbs.request({
|
||||
project_id = airlbs_project_id,
|
||||
project_key = airlbs_project_key,
|
||||
timeout = 10000,
|
||||
adapter = socket.LWIP_STA
|
||||
})
|
||||
if result then
|
||||
log.info("airlbs", json.encode(data))
|
||||
end
|
||||
sys.wait(20000)
|
||||
end
|
||||
end)
|
||||
]]
|
||||
function airlbs.request(param)
|
||||
if not param or param.project_id == nil or param.project_key == nil then
|
||||
log.error(lib_name, "param error")
|
||||
return false
|
||||
end
|
||||
|
||||
if not mobile and not param.wifi_info then
|
||||
log.error(lib_name, "no mobile and no wifi_info")
|
||||
return false
|
||||
end
|
||||
|
||||
local udp_buff = zbuff.create(1500)
|
||||
local auth_type = 0x01
|
||||
local lbs_data_type = 0x00
|
||||
local project_id = param.project_id
|
||||
if project_id:len() ~= 6 then
|
||||
log.error("airlbs", "project_id len not 6")
|
||||
end
|
||||
local mac1 = netdrv.mac(socket.LWIP_STA)
|
||||
local mac = "MAC" .. mac1
|
||||
log.info("mac", mac)
|
||||
local timestamp = os.time()
|
||||
local project_key = param.project_key
|
||||
local nonce = crypto.trng(6)
|
||||
local hmac_data
|
||||
local bsp = rtos.bsp()
|
||||
log.info("硬件型号", rtos.bsp())
|
||||
if bsp == "Air8101" then
|
||||
-- 此处由于目前属于测试阶段,先将muid写死,后续会进行修改
|
||||
-- local muid = mcu.muid() or ""
|
||||
local muid = "12345678901234567890123456789012"
|
||||
log.info("muid", muid)
|
||||
hmac_data = crypto.hmac_sha1(project_id .. mac .. muid .. timestamp .. nonce, project_key)
|
||||
else
|
||||
local imei = mobile and mobile.imei() or ""
|
||||
local muid = mobile and mobile.muid() or ""
|
||||
hmac_data = crypto.hmac_sha1(project_id .. imei .. muid .. timestamp .. nonce, project_key)
|
||||
end
|
||||
-- log.debug(lib_name,"hmac_sha1", hmac_data)
|
||||
local lbs_data = {}
|
||||
if mobile then
|
||||
mobile.reqCellInfo(60)
|
||||
sys.waitUntil("CELL_INFO_UPDATE", param.timeout or airlbs_timeout)
|
||||
lbs_data.cells = {}
|
||||
-- log.info("cell", json.encode(mobile.getCellInfo()))
|
||||
for k, v in pairs(mobile.getCellInfo()) do
|
||||
lbs_data.cells[k] = {}
|
||||
lbs_data.cells[k][1] = v.mcc
|
||||
lbs_data.cells[k][2] = v.mnc
|
||||
lbs_data.cells[k][3] = v.tac
|
||||
lbs_data.cells[k][4] = v.cid
|
||||
lbs_data.cells[k][5] = v.rssi or v.rsrp
|
||||
lbs_data.cells[k][6] = v.snr
|
||||
lbs_data.cells[k][7] = v.pci
|
||||
lbs_data.cells[k][8] = v.rsrp
|
||||
lbs_data.cells[k][9] = v.rsrq
|
||||
lbs_data.cells[k][10] = v.earfcn
|
||||
end
|
||||
end
|
||||
if param.wifi_info and #param.wifi_info > 0 then
|
||||
lbs_data.macs = {}
|
||||
for k, v in pairs(param.wifi_info) do
|
||||
lbs_data.macs[k] = {}
|
||||
lbs_data.macs[k][1] = v.bssid:toHex():gsub("(%x%x)", "%1:"):sub(1, -2)
|
||||
lbs_data.macs[k][2] = v.rssi
|
||||
end
|
||||
end
|
||||
local lbs_jdata = json.encode(lbs_data)
|
||||
log.info("扫描出的数据",lbs_jdata)
|
||||
local bsp = rtos.bsp()
|
||||
if bsp == "Air8101" then
|
||||
-- 此处由于目前属于测试阶段,先将muid写死,后续会进行修改
|
||||
-- local muid = mcu.muid() or ""
|
||||
local muid = "12345678901234567890123456789012"
|
||||
udp_buff:write(string.char(auth_type) .. project_id .. mac .. muid .. timestamp .. nonce .. hmac_data:fromHex() .. string.char(lbs_data_type) .. lbs_jdata)
|
||||
else
|
||||
local imei = mobile and mobile.imei() or ""
|
||||
local muid = mobile and mobile.muid() or ""
|
||||
udp_buff:write(string.char(auth_type) .. project_id .. imei .. muid .. timestamp .. nonce .. hmac_data:fromHex() .. string.char(lbs_data_type) .. lbs_jdata)
|
||||
end
|
||||
|
||||
sysplus.taskInitEx(airlbs_task, lib_name, netCB, lib_name, udp_buff, param.timeout or airlbs_timeout, param.adapter)
|
||||
|
||||
while 1 do
|
||||
local result, tp, data = sys.waitUntil(lib_topic, param.timeout or airlbs_timeout)
|
||||
log.info("定位请求的结果", result, "超时时间", tp, data)
|
||||
if not result then
|
||||
return false, "timeout"
|
||||
elseif tp == location_data then
|
||||
if not data then
|
||||
log.error(lib_name, "无数据, 请检查project_id和project_key")
|
||||
return false
|
||||
-- data.result 0-找不到 1-成功 2-qps超限 3-欠费 4-其他错误
|
||||
elseif data.result == 0 then
|
||||
log.error(lib_name, "no location(基站定位服务器查询当前地址失败)")
|
||||
return false
|
||||
elseif data.result == 1 then
|
||||
log.info("多基站请求成功,服务器返回的原始数据", data)
|
||||
return true, {
|
||||
lng = data.lng,
|
||||
lat = data.lat
|
||||
}
|
||||
elseif data.result == 2 then
|
||||
log.error(lib_name, "qps limit(当前请求已到达限制,请检查当前请求是否过于频繁))")
|
||||
return false
|
||||
elseif data.result == 3 then
|
||||
log.error(lib_name, "当前设备已欠费,请联系销售充值")
|
||||
return false
|
||||
elseif data.result == 4 then
|
||||
log.error(lib_name, "other error")
|
||||
return false
|
||||
else
|
||||
log.error("其他错误,错误码", data.result, lib_name)
|
||||
end
|
||||
else
|
||||
log.error(lib_name, "net error")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return airlbs
|
||||
|
||||
26
4G/tools/resource/soc_script/v2025.12.31.22/lib/bf30a2.lua
Normal file
26
4G/tools/resource/soc_script/v2025.12.31.22/lib/bf30a2.lua
Normal file
@@ -0,0 +1,26 @@
|
||||
local config = {
|
||||
mode = 0,
|
||||
is_msb = 0,
|
||||
rx_bit = 1,
|
||||
seq_type = 0,
|
||||
is_ddr = 0,
|
||||
i2c_slave_addr = 0x6e,
|
||||
width = 240,
|
||||
height = 320,
|
||||
init_cmds = {{0xf2, 0x01}, {0xcf, 0xb0}, {0x12, 0x20}, {0x15, 0x80}, {0x6b, 0x71}, {0x00, 0x40}, {0x04, 0x00},
|
||||
{0x06, 0x26}, {0x08, 0x07}, {0x1c, 0x12}, {0x20, 0x20}, {0x21, 0x20}, {0x34, 0x02}, {0x35, 0x02},
|
||||
{0x36, 0x21}, {0x37, 0x13}, {0xca, 0x23}, {0xcb, 0x22}, {0xcc, 0x89}, {0xcd, 0x4c}, {0xce, 0x6b},
|
||||
{0xa0, 0x8e}, {0x01, 0x1b}, {0x02, 0x1d}, {0x13, 0x08}, {0x87, 0x13}, {0x8b, 0x08}, {0x70, 0x17},
|
||||
{0x71, 0x43}, {0x72, 0x0a}, {0x73, 0x62}, {0x74, 0xa2}, {0x75, 0xbf}, {0x76, 0x00}, {0x77, 0xcc},
|
||||
{0x40, 0x32}, {0x41, 0x28}, {0x42, 0x26}, {0x43, 0x1d}, {0x44, 0x1a}, {0x45, 0x14}, {0x46, 0x11},
|
||||
{0x47, 0x0f}, {0x48, 0x0e}, {0x49, 0x0d}, {0x4B, 0x0c}, {0x4C, 0x0b}, {0x4E, 0x0a}, {0x4F, 0x09},
|
||||
{0x50, 0x09}, {0x24, 0x30}, {0x25, 0x36}, {0x80, 0x00}, {0x81, 0x20}, {0x82, 0x40}, {0x83, 0x30},
|
||||
{0x84, 0x50}, {0x85, 0x30}, {0x86, 0xd8}, {0x89, 0x45}, {0x8a, 0x33}, {0x8f, 0x81}, {0x91, 0xff},
|
||||
{0x92, 0x08}, {0x94, 0x82}, {0x95, 0xfd}, {0x9a, 0x20}, {0x9e, 0xbc}, {0xf0, 0x87}, {0x51, 0x06},
|
||||
{0x52, 0x25}, {0x53, 0x2b}, {0x54, 0x0f}, {0x57, 0x2a}, {0x58, 0x22}, {0x59, 0x2c}, {0x23, 0x33},
|
||||
{0xa1, 0x93}, {0xa2, 0x0f}, {0xa3, 0x2a}, {0xa4, 0x08}, {0xa5, 0x26}, {0xa7, 0x80}, {0xa8, 0x80},
|
||||
{0xa9, 0x1e}, {0xaa, 0x19}, {0xab, 0x18}, {0xae, 0x50}, {0xaf, 0x04}, {0xc8, 0x10}, {0xc9, 0x15},
|
||||
{0xd3, 0x0c}, {0xd4, 0x16}, {0xee, 0x06}, {0xef, 0x04}, {0x55, 0x34}, {0x56, 0x9c}, {0xb1, 0x98},
|
||||
{0xb2, 0x98}, {0xb3, 0xc4}, {0xb4, 0x0c}, {0xa0, 0x8f}, {0x13, 0x07}}
|
||||
}
|
||||
return config
|
||||
395
4G/tools/resource/soc_script/v2025.12.31.22/lib/dhcpsrv.lua
Normal file
395
4G/tools/resource/soc_script/v2025.12.31.22/lib/dhcpsrv.lua
Normal file
@@ -0,0 +1,395 @@
|
||||
--[[
|
||||
@module dhcpsrv
|
||||
@summary DHCP服务器端
|
||||
@version 1.0.0
|
||||
@date 2025.04.15
|
||||
@author wendal
|
||||
@usage
|
||||
-- 参考dhcpsrv.create函数
|
||||
]]
|
||||
local dhcpsrv = {}
|
||||
|
||||
local udpsrv = require("udpsrv")
|
||||
|
||||
local TAG = "dhcpsrv"
|
||||
|
||||
----
|
||||
-- 参考地址
|
||||
-- https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol
|
||||
|
||||
local function dhcp_decode(buff)
|
||||
-- buff:seek(0)
|
||||
local dst = {}
|
||||
-- 开始解析dhcp
|
||||
dst.op = buff[0]
|
||||
dst.htype = buff[1]
|
||||
dst.hlen = buff[2]
|
||||
dst.hops = buff[3]
|
||||
buff:seek(4)
|
||||
dst.xid = buff:read(4)
|
||||
|
||||
_, dst.secs = buff:unpack(">H")
|
||||
_, dst.flags = buff:unpack(">H")
|
||||
dst.ciaddr = buff:read(4)
|
||||
dst.yiaddr = buff:read(4)
|
||||
dst.siaddr = buff:read(4)
|
||||
dst.giaddr = buff:read(4)
|
||||
dst.chaddr = buff:read(16)
|
||||
|
||||
-- 跳过192字节
|
||||
buff:seek(192, zbuff.SEEK_CUR)
|
||||
|
||||
-- 解析magic
|
||||
_, dst.magic = buff:unpack(">I")
|
||||
|
||||
-- 解析option
|
||||
local opt = {}
|
||||
while buff:len() > buff:used() do
|
||||
local tag = buff:read(1):byte()
|
||||
if tag ~= 0 then
|
||||
local len = buff:read(1):byte()
|
||||
if tag == 0xFF or len == 0 then
|
||||
break
|
||||
end
|
||||
local data = buff:read(len)
|
||||
if tag == 53 then
|
||||
-- 53: DHCP Message Type
|
||||
dst.msgtype = data:byte()
|
||||
end
|
||||
table.insert(opt, {tag, data})
|
||||
-- log.info(TAG, "tag", tag, "data", data:toHex())
|
||||
end
|
||||
end
|
||||
if dst.msgtype == nil then
|
||||
return -- 没有解析到msgtype,直接返回
|
||||
end
|
||||
dst.opts = opt
|
||||
return dst
|
||||
end
|
||||
|
||||
local function dhcp_buff2ip(buff)
|
||||
return string.format("%d.%d.%d.%d", buff:byte(1), buff:byte(2), buff:byte(3), buff:byte(4))
|
||||
end
|
||||
|
||||
local function dhcp_print_pkg(pkg)
|
||||
log.info(TAG, "XID", pkg.xid:toHex())
|
||||
log.info(TAG, "secs", pkg.secs)
|
||||
log.info(TAG, "flags", pkg.flags)
|
||||
log.info(TAG, "chaddr", pkg.chaddr:sub(1, pkg.hlen):toHex())
|
||||
log.info(TAG, "yiaddr", dhcp_buff2ip(pkg.yiaddr))
|
||||
log.info(TAG, "siaddr", dhcp_buff2ip(pkg.siaddr))
|
||||
log.info(TAG, "giaddr", dhcp_buff2ip(pkg.giaddr))
|
||||
log.info(TAG, "ciaddr", dhcp_buff2ip(pkg.ciaddr))
|
||||
log.info(TAG, "magic", string.format("%08X", pkg.magic))
|
||||
for _, opt in pairs(pkg.opts) do
|
||||
if opt[1] == 53 then
|
||||
log.info(TAG, "msgtype", opt[2]:byte())
|
||||
elseif opt[1] == 60 then
|
||||
log.info(TAG, "auth", opt[2])
|
||||
elseif opt[1] == 57 then
|
||||
log.info(TAG, "Maximum DHCP message size", opt[2]:byte() * 256 + opt[2]:byte(2))
|
||||
elseif opt[1] == 61 then
|
||||
log.info(TAG, "Client-identifier", opt[2]:toHex())
|
||||
elseif opt[1] == 55 then
|
||||
log.info(TAG, "Parameter request list", opt[2]:toHex())
|
||||
elseif opt[1] == 12 then
|
||||
log.info(TAG, "Host name", opt[2])
|
||||
-- elseif opt[1] == 58 then
|
||||
-- log.info(TAG, "Renewal (T1) time value", opt[2]:unpack(">I"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function dhcp_encode(pkg, buff)
|
||||
-- 合成DHCP包
|
||||
buff:seek(0)
|
||||
buff[0] = pkg.op
|
||||
buff[1] = pkg.htype
|
||||
buff[2] = pkg.hlen
|
||||
buff[3] = pkg.hops
|
||||
buff:seek(4)
|
||||
-- 写入XID
|
||||
buff:write(pkg.xid)
|
||||
-- 几个重要的参数
|
||||
buff:pack(">H", pkg.secs)
|
||||
buff:pack(">H", pkg.flags)
|
||||
buff:write(pkg.ciaddr)
|
||||
buff:write(pkg.yiaddr)
|
||||
buff:write(pkg.siaddr)
|
||||
buff:write(pkg.giaddr)
|
||||
-- 写入MAC地址
|
||||
buff:write(pkg.chaddr)
|
||||
-- 跳过192字节
|
||||
buff:seek(192, zbuff.SEEK_CUR)
|
||||
-- 写入magic
|
||||
buff:pack(">I", pkg.magic)
|
||||
-- 写入option
|
||||
for _, opt in pairs(pkg.opts) do
|
||||
buff:write(opt[1])
|
||||
buff:write(#opt[2])
|
||||
buff:write(opt[2])
|
||||
end
|
||||
buff:write(0xFF, 0x00)
|
||||
end
|
||||
|
||||
----
|
||||
|
||||
local function dhcp_send_x(srv, pkg, client, msgtype)
|
||||
local buff = zbuff.create(300)
|
||||
pkg.op = 2
|
||||
pkg.ciaddr = "\0\0\0\0"
|
||||
pkg.yiaddr = string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], client.ip)
|
||||
pkg.siaddr = string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])
|
||||
pkg.giaddr = "\0\0\0\0"
|
||||
pkg.secs = 0
|
||||
|
||||
pkg.opts = {} -- 复位option
|
||||
table.insert(pkg.opts, {53, string.char(msgtype)})
|
||||
table.insert(pkg.opts, {1, string.char(srv.opts.mark[1], srv.opts.mark[2], srv.opts.mark[3], srv.opts.mark[4])})
|
||||
table.insert(pkg.opts, {3, string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])})
|
||||
table.insert(pkg.opts, {51, "\x00\x00\x1E\x00"}) -- 7200秒, 大概
|
||||
table.insert(pkg.opts, {54, string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])})
|
||||
table.insert(pkg.opts, {6, string.char(223, 5, 5, 5)})
|
||||
table.insert(pkg.opts, {6, string.char(119, 29, 29, 29)})
|
||||
table.insert(pkg.opts, {6, string.char(srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], srv.opts.gw[4])})
|
||||
|
||||
dhcp_encode(pkg, buff)
|
||||
|
||||
local dst = "255.255.255.255"
|
||||
if 4 == msgtype then
|
||||
dst = string.format("%d.%d.%d.%d", srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], client.ip)
|
||||
end
|
||||
-- log.info(TAG, "发送", msgtype, dst, buff:query():toHex())
|
||||
srv.udp:send(buff, dst, 68)
|
||||
end
|
||||
|
||||
local function dhcp_send_offer(srv, pkg, client)
|
||||
dhcp_send_x(srv, pkg, client, 2)
|
||||
end
|
||||
|
||||
local function dhcp_send_ack(srv, pkg, client)
|
||||
dhcp_send_x(srv, pkg, client, 5)
|
||||
end
|
||||
|
||||
local function dhcp_send_nack(srv, pkg, client)
|
||||
dhcp_send_x(srv, pkg, client, 6)
|
||||
end
|
||||
|
||||
local function dhcp_handle_discover(srv, pkg)
|
||||
local mac = pkg.chaddr:sub(1, pkg.hlen)
|
||||
-- 看看是不是已经分配了ip
|
||||
for _, client in pairs(srv.clients) do
|
||||
if client.mac == mac then
|
||||
log.info(TAG, "发现已经分配的mac地址, send offer")
|
||||
dhcp_send_offer(srv, pkg, client)
|
||||
return
|
||||
end
|
||||
end
|
||||
-- TODO 清理已经过期的IP分配记录
|
||||
-- 分配一个新的ip
|
||||
if #srv.clients >= (srv.opts.ip_end - srv.opts.ip_start) then
|
||||
log.info(TAG, "没有可分配的ip了")
|
||||
return
|
||||
end
|
||||
local ip = nil
|
||||
for i = srv.opts.ip_start, srv.opts.ip_end, 1 do
|
||||
if srv.clients[i] == nil then
|
||||
ip = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if ip == nil then
|
||||
log.info(TAG, "没有可分配的ip了")
|
||||
return
|
||||
end
|
||||
log.info(TAG, "分配ip", mac:toHex(), string.format("%d.%d.%d.%d", srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], ip))
|
||||
local client = {
|
||||
mac = mac,
|
||||
ip = ip,
|
||||
tm = mcu.ticks() // mcu.hz(),
|
||||
stat = 1
|
||||
}
|
||||
srv.clients[ip] = client
|
||||
log.info(TAG, "send offer")
|
||||
dhcp_send_offer(srv, pkg, client)
|
||||
end
|
||||
|
||||
local function dhcp_handle_request(srv, pkg)
|
||||
local mac = pkg.chaddr:sub(1, pkg.hlen)
|
||||
-- 看看是不是已经分配了ip
|
||||
for _, client in pairs(srv.clients) do
|
||||
if client.mac == mac then
|
||||
log.info(TAG, "request,发现已经分配的mac地址, send ack", mac:toHex())
|
||||
client.tm = mcu.ticks() // mcu.hz()
|
||||
stat = 3
|
||||
dhcp_send_ack(srv, pkg, client)
|
||||
if srv.opts.ack_cb then
|
||||
local cip = string.format("%d.%d.%d.%d", srv.opts.gw[1], srv.opts.gw[2], srv.opts.gw[3], client.ip)
|
||||
srv.opts.ack_cb(cip, mac:toHex())
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
-- 没有找到, 那应该返回NACK
|
||||
log.info(TAG, "request,对应mac地址没有分配ip, send nack")
|
||||
dhcp_send_nack(srv, pkg, {ip=pkg.yiaddr:byte(1)})
|
||||
end
|
||||
|
||||
local function dhcp_pkg_handle(srv, pkg)
|
||||
-- 进行基本的检查
|
||||
if pkg.magic ~= 0x63825363 then
|
||||
log.warn(TAG, "dhcp数据包的magic不对劲,忽略该数据包", pkg.magic)
|
||||
return
|
||||
end
|
||||
if pkg.op ~= 1 then
|
||||
log.info(TAG, "op不对,忽略该数据包", pkg.op)
|
||||
return
|
||||
end
|
||||
if pkg.htype ~= 1 or pkg.hlen ~= 6 then
|
||||
log.warn(TAG, "htype/hlen 不认识, 忽略该数据包")
|
||||
return
|
||||
end
|
||||
-- 看看是不是能处理的类型, 当前只处理discover/request
|
||||
if pkg.msgtype == 1 or pkg.msgtype == 3 then
|
||||
else
|
||||
log.warn(TAG, "msgtype不是discover/request, 忽略该数据包", pkg.msgtype)
|
||||
return
|
||||
end
|
||||
-- 检查一下mac地址是否合法
|
||||
local mac = pkg.chaddr:sub(1, pkg.hlen)
|
||||
if mac == "\0\0\0\0\0\0" or mac == "\xFF\xFF\xFF\xFF\xFF\xFF" then
|
||||
log.warn(TAG, "mac地址为空, 忽略该数据包")
|
||||
return
|
||||
end
|
||||
|
||||
-- 处理discover包
|
||||
if pkg.msgtype == 1 then
|
||||
log.info(TAG, "是discover包", mac:toHex())
|
||||
dhcp_handle_discover(srv, pkg)
|
||||
elseif pkg.msgtype == 3 then
|
||||
log.info(TAG, "是request包", mac:toHex())
|
||||
dhcp_handle_request(srv, pkg)
|
||||
end
|
||||
-- TODO 处理结束, 打印一下客户的列表?
|
||||
end
|
||||
|
||||
local function dhcp_task(srv)
|
||||
while 1 do
|
||||
-- log.info("ulwip", "等待DHCP数据")
|
||||
local result, data = sys.waitUntil(srv.udp_topic, 1000)
|
||||
if result then
|
||||
-- log.info("ulwip", "收到dhcp数据包", data:toHex())
|
||||
-- 解析DHCP数据包
|
||||
local pkg = dhcp_decode(zbuff.create(#data, data))
|
||||
if pkg then
|
||||
-- dhcp_print_pkg(pkg)
|
||||
dhcp_pkg_handle(srv, pkg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
创建一个dhcp服务器
|
||||
@api dhcpsrv.create(opts)
|
||||
@table 选项,参考库的说明, 及demo的用法
|
||||
@return table 服务器对象
|
||||
@usage
|
||||
-- 创建一个dhcp服务器, 最简介的版本
|
||||
dhcpsrv.create({adapter=socket.LWIP_AP})
|
||||
-- 详细的版本
|
||||
-- 创建一个dhcp服务器
|
||||
local dhcpsrv_opts = {
|
||||
adapter=socket.LWIP_AP, -- 监听哪个网卡, 必须填写
|
||||
mark = {255, 255, 255, 0}, -- 网络掩码, 默认 255.255.255.0
|
||||
gw = {192, 168, 4, 1}, -- 网关, 默认自动获取网卡IP,如果获取失败则使用 192.168.4.1
|
||||
ip_start = 100, -- ip起始地址, 默认100
|
||||
ip_end = 200, -- ip结束地址, 默认200
|
||||
ack_cb = function(ip, mac) end, -- ack回调, 有客户端连接上来时触发, ip和mac地址会传进来
|
||||
}
|
||||
local mydhcpsrv = dhcpsrv.create(dhcpsrv_opts)
|
||||
|
||||
-- 以下是一个打印客户端列表的例子, 非必选, 仅供参考
|
||||
-- clients是一个table, 包含MAC和IP的对应关系, 注意, IP只记录了最后一段数字, 非完整IP
|
||||
-- 注意, clients是动态变化的过程, mydhcpsrv对象的其他属性切勿修改, 仅提供clients的只读功能
|
||||
sys.taskInit(function()
|
||||
while true do
|
||||
sys.wait(10000)
|
||||
-- 这里可以打印一下当前的客户端列表
|
||||
for ip, client in pairs(mydhcpsrv.clients) do
|
||||
log.info(TAG, "client", ip, client.mac:toHex(), client.tm, client.stat)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- 自动分配网段功能说明:
|
||||
-- 如果不指定gw参数,系统会自动获取网卡IP作为网关地址
|
||||
-- 这样可以确保DHCP分配的IP与网卡IP在同一网段
|
||||
]]
|
||||
function dhcpsrv.create(opts)
|
||||
local srv = {}
|
||||
if not opts then
|
||||
opts = {}
|
||||
end
|
||||
srv.udp_topic = "dhcpd_inc"
|
||||
|
||||
-- 自动获取网卡IP地址的函数
|
||||
local function get_adapter_ip()
|
||||
if not opts.adapter then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- 获取网卡IP地址
|
||||
local ip = netdrv.ipv4(opts.adapter)
|
||||
if not ip or ip == "0.0.0.0" then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- 简单解析IP地址:192.168.4.1 -> {192, 168, 4, 1}
|
||||
local a, b, c, d = ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")
|
||||
if a and b and c and d then
|
||||
return {tonumber(a), tonumber(b), tonumber(c), tonumber(d)}
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- 补充参数
|
||||
if not opts.mark then
|
||||
opts.mark = {255, 255, 255, 0}
|
||||
end
|
||||
|
||||
-- 如果没有指定网关,则自动获取网卡IP作为网关
|
||||
if not opts.gw then
|
||||
local adapter_ip = get_adapter_ip()
|
||||
if adapter_ip then
|
||||
opts.gw = adapter_ip
|
||||
log.info(TAG, "自动获取网卡IP作为网关", string.format("%d.%d.%d.%d", adapter_ip[1], adapter_ip[2], adapter_ip[3], adapter_ip[4]))
|
||||
else
|
||||
opts.gw = {192, 168, 4, 1}
|
||||
log.warn(TAG, "无法获取网卡IP,使用默认网关", string.format("%d.%d.%d.%d", opts.gw[1], opts.gw[2], opts.gw[3], opts.gw[4]))
|
||||
end
|
||||
end
|
||||
|
||||
if not opts.dns then
|
||||
opts.dns = opts.gw
|
||||
end
|
||||
|
||||
-- 根据网关IP自动设置IP分配范围
|
||||
if not opts.ip_start then
|
||||
opts.ip_start = 100
|
||||
end
|
||||
if not opts.ip_end then
|
||||
opts.ip_end = 200
|
||||
end
|
||||
|
||||
srv.clients = {}
|
||||
srv.opts = opts
|
||||
|
||||
srv.udp = udpsrv.create(67, srv.udp_topic, opts.adapter)
|
||||
srv.task = sys.taskInit(dhcp_task, srv)
|
||||
return srv
|
||||
end
|
||||
|
||||
|
||||
return dhcpsrv
|
||||
125
4G/tools/resource/soc_script/v2025.12.31.22/lib/dnsproxy.lua
Normal file
125
4G/tools/resource/soc_script/v2025.12.31.22/lib/dnsproxy.lua
Normal file
@@ -0,0 +1,125 @@
|
||||
--[[
|
||||
@module dnsproxy
|
||||
@summary DNS代理转发
|
||||
@version 1.0
|
||||
@date 2024.4.20
|
||||
@author wendal
|
||||
@demo socket
|
||||
@tag LUAT_USE_NETWORK
|
||||
@usage
|
||||
-- 具体用法请查阅demo
|
||||
]]
|
||||
|
||||
local sys = require "sys"
|
||||
|
||||
local dnsproxy = {
|
||||
server = "119.29.29.29",
|
||||
srvs = {},
|
||||
map = {},
|
||||
txid = 0x123,
|
||||
rxbuff = zbuff.create(1500)
|
||||
}
|
||||
|
||||
function dnsproxy.on_request(sc, event, adapter)
|
||||
if event == socket.EVENT then
|
||||
local rxbuff = dnsproxy.rxbuff
|
||||
while 1 do
|
||||
rxbuff:seek(0)
|
||||
local succ, data_len, remote_ip, remote_port = socket.rx(sc, rxbuff)
|
||||
if succ and data_len and data_len > 0 then
|
||||
-- log.info("dnsproxy", "收到DNS查询数据", rxbuff:query():toHex())
|
||||
if remote_ip and #remote_ip == 5 then
|
||||
local ip1,ip2,ip3,ip4 = remote_ip:byte(2),remote_ip:byte(3),remote_ip:byte(4),remote_ip:byte(5)
|
||||
remote_ip = string.format("%d.%d.%d.%d", ip1, ip2, ip3, ip4)
|
||||
local txid_request = rxbuff[0] + rxbuff[1] * 256
|
||||
local txid_map = dnsproxy.txid
|
||||
dnsproxy.txid = dnsproxy.txid + 1
|
||||
if dnsproxy.txid > 65000 then
|
||||
dnsproxy.txid = 0x123
|
||||
end
|
||||
table.insert(dnsproxy.map, {txid_request, txid_map, remote_ip, remote_port, adapter})
|
||||
rxbuff[0] = txid_map % 256
|
||||
rxbuff[1] = txid_map // 256
|
||||
socket.tx(dnsproxy.main_sc, rxbuff, dnsproxy.server or "223.5.5.5", 53)
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function dnsproxy.on_response(sc, event)
|
||||
if event == socket.EVENT then
|
||||
local rxbuff = dnsproxy.rxbuff
|
||||
while 1 do
|
||||
rxbuff:seek(0)
|
||||
local succ, data_len = socket.rx(sc, rxbuff)
|
||||
if succ and data_len and data_len > 0 then
|
||||
if true then
|
||||
-- local ip1,ip2,ip3,ip4 = remote_ip:byte(2),remote_ip:byte(3),remote_ip:byte(4),remote_ip:byte(5)
|
||||
-- remote_ip = string.format("%d.%d.%d.%d", ip1, ip2, ip3, ip4)
|
||||
local txid_resp = rxbuff[0] + rxbuff[1] * 256
|
||||
local index = -1
|
||||
for i, mapit in pairs(dnsproxy.map) do
|
||||
if mapit[2] == txid_resp then
|
||||
local txid_request = mapit[1]
|
||||
local remote_ip = mapit[3]
|
||||
local remote_port = mapit[4]
|
||||
rxbuff[0] = txid_request % 256
|
||||
rxbuff[1] = txid_request // 256
|
||||
local adapter = mapit[5]
|
||||
-- log.info("dnsproxy", "转发DNS响应数据", adapter, dnsproxy.srvs[adapter])
|
||||
socket.tx(dnsproxy.srvs[adapter], rxbuff, remote_ip, remote_port)
|
||||
index = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if index > 0 then
|
||||
table.remove(dnsproxy.map, index)
|
||||
end
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
创建UDP服务器
|
||||
@api dnsproxy.setup(adapter, main_adapter)
|
||||
@int 监听的网络适配器id
|
||||
@int 网络适配编号, 默认为nil,可选
|
||||
@return table UDP服务的实体, 若创建失败会返回nil
|
||||
]]
|
||||
function dnsproxy.setup(adapter, main_adapter)
|
||||
log.info("dnsproxy", adapter, main_adapter)
|
||||
if dnsproxy.main_sc == nil then
|
||||
dnsproxy.main_sc = socket.create(main_adapter, dnsproxy.on_response)
|
||||
socket.config(dnsproxy.main_sc, 1053, true)
|
||||
end
|
||||
if dnsproxy.srvs[adapter] == nil then
|
||||
dnsproxy.srvs[adapter] = socket.create(adapter, function(sc, event)
|
||||
dnsproxy.on_request(sc, event, adapter)
|
||||
end)
|
||||
socket.config(dnsproxy.srvs[adapter], 53, true)
|
||||
end
|
||||
dnsproxy.on_ip_ready()
|
||||
return true
|
||||
end
|
||||
|
||||
function dnsproxy.on_ip_ready()
|
||||
log.info("dnsproxy", "开始监听")
|
||||
if not dnsproxy.main_sc then return end
|
||||
socket.close(dnsproxy.main_sc)
|
||||
for k, v in pairs(dnsproxy.srvs) do
|
||||
socket.close(v)
|
||||
socket.connect(v, "255.255.255.255", 0)
|
||||
end
|
||||
socket.connect(dnsproxy.main_sc, dnsproxy.server or "223.5.5.5", 53)
|
||||
end
|
||||
|
||||
sys.subscribe("IP_READY", dnsproxy.on_ip_ready)
|
||||
|
||||
return dnsproxy
|
||||
544
4G/tools/resource/soc_script/v2025.12.31.22/lib/exaudio.lua
Normal file
544
4G/tools/resource/soc_script/v2025.12.31.22/lib/exaudio.lua
Normal file
@@ -0,0 +1,544 @@
|
||||
--[[
|
||||
@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
|
||||
exaudio.PCM_48000 = 6
|
||||
|
||||
|
||||
-- 默认配置参数
|
||||
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,exaudio.PCM_48000
|
||||
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 > 6 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
|
||||
elseif audio_record_param.format == exaudio.PCM_48000 then
|
||||
recod_format = 48000
|
||||
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
|
||||
425
4G/tools/resource/soc_script/v2025.12.31.22/lib/excamera.lua
Normal file
425
4G/tools/resource/soc_script/v2025.12.31.22/lib/excamera.lua
Normal file
@@ -0,0 +1,425 @@
|
||||
--[[
|
||||
@module excamera
|
||||
@summary excamera扩展库
|
||||
@version 1.0
|
||||
@date 2025.10.21
|
||||
@author 陈取德
|
||||
@usage
|
||||
用法实例
|
||||
注意:excamera.lua适用的产品范围
|
||||
Air780系列、Air700系列、Air8000系列:支持SPI摄像头
|
||||
Air8101系列:支持USB摄像、DVP摄像头
|
||||
合宙所有型号的soc产品都仅支持一路摄像头,所以excamera库不需要管理camera id,只需要调用摄像头的开关和拍照功能即可
|
||||
|
||||
使用excamera库时会有两种应用场景
|
||||
1、拍照模式:使用拍照模式时
|
||||
按照实际使用的摄像头类型填写配置表 - 创建摄像头excamera.open() - 拍照excamera.photo() - 关闭摄像头 excamera.close()的逻辑使用
|
||||
2、扫描模式:当前USB和DVP摄像头不支持扫描模式,仅SPI摄像头可使用
|
||||
按照实际使用的摄像头类型填写配置表 - 创建摄像头excamera.open() - 扫描excamera.scan() - 关闭摄像头 excamera.close()的逻辑使用
|
||||
|
||||
local excamera = require "excamera"
|
||||
|
||||
local spi_camera_param = {
|
||||
id = "gc032a", -- SPI摄像头仅支持"gc032a"、"gc0310"、"bf30a2",请带引号填写
|
||||
i2c_id = 1, -- 模块上使用的I2C编号
|
||||
work_mode = 0, -- 工作模式,0为拍照模式,1为扫描模式
|
||||
save_path = "ZBUFF", -- 拍照结果存储路径,可用"ZBUFF"交由excamera库内部管理
|
||||
camera_pwr = 2 , -- 摄像头使能管脚,填写GPIO号即可,无则填nil
|
||||
camera_pwdn = 5 , -- 摄像头pwdn开关脚,填写GPIO号即可,无则填nil
|
||||
camera_light = 25 -- 摄像头补光灯控制管脚,填写GPIO号即可,无则填nil
|
||||
}
|
||||
|
||||
local usb_camera_param = {
|
||||
id = camera.USB , -- 摄像头类型,默认camera.USB
|
||||
sensor_width = 1280, -- 摄像头像素宽度,根据摄像头实际参数填写数值
|
||||
sensor_height = 720, -- 摄像头像素高度,根据摄像头实际参数填写数值
|
||||
usb_port = 1 ,
|
||||
save_path = "/ram/test.jpg"
|
||||
}
|
||||
|
||||
local dvp_camera_param = {
|
||||
id = camera.DVP, -- 摄像头类型,默认camera.DVP
|
||||
sensor_width = 1280, -- 摄像头像素宽度,根据摄像头实际参数填写数值
|
||||
sensor_height = 720, -- 摄像头像素高度,根据摄像头实际参数填写数值
|
||||
save_path = "/ram/test.jpg"
|
||||
}
|
||||
|
||||
sys.taskInit(function()
|
||||
local camera_id
|
||||
while true do
|
||||
sys.waitUntil("ONCE_CAPTURE")
|
||||
camera_id = excamera.open(spi_camera_param)
|
||||
log.info("初始化状态", camera_id)
|
||||
local result ,data = excamera.photo()
|
||||
log.info("拍完了",data)
|
||||
excamera.close()
|
||||
end
|
||||
end)
|
||||
sys.run()
|
||||
]] --
|
||||
local excamera = {}
|
||||
local h, w
|
||||
local camera_id, path, camera_buff, camera_i2c, data, result
|
||||
local cam_pwr, cam_pwdn, cam_light
|
||||
|
||||
-- 设备打开函数:初始化指定类型的摄像头设备
|
||||
-- 参数:camera_param - 摄像头配置参数表,包含id、i2c_id、work_mode等配置
|
||||
-- 返回值:成功返回camera_id,失败返回false
|
||||
-- 支持SPI摄像、USB摄像头、DVP摄像头使用
|
||||
-- 自动处理异步回调函数,将摄像头业务流程改为同步流程
|
||||
-- 支持ZBUFF处理照片,支持文件路径处理照片
|
||||
function excamera.open(camera_param)
|
||||
-- 判断摄像头类型是否为字符串类型(用于支持不同型号的摄像头模块)
|
||||
if type(camera_param.id) == "string" then
|
||||
-- 判断是否需要管理供电使能
|
||||
if type(camera_param.camera_pwr) == "number" then
|
||||
cam_pwr = gpio.setup(camera_param.camera_pwr, 1)
|
||||
end
|
||||
-- 判断是否需要管理摄像头pwdn开关
|
||||
if type(camera_param.camera_pwdn) == "number" then
|
||||
cam_pwdn = gpio.setup(camera_param.camera_pwdn, 0)
|
||||
-- 为8000暂时兼容,后续版本会移除
|
||||
sys.wait(10)
|
||||
end
|
||||
-- 配置I2C接口,用于与摄像头通信
|
||||
if i2c.setup(camera_param.i2c_id, i2c.FAST) then
|
||||
-- 保存I2C接口ID到camera_i2c,用于局内调用
|
||||
camera_i2c = camera_param.i2c_id
|
||||
-- 保护执行配置文件加载,并赋值给camera_module,便于后续调用配置表信息
|
||||
local result, camera_module = pcall(require, camera_param.id)
|
||||
if not result then
|
||||
log.error("excamera.open", camera_param.id .. ".lua文件加载失败")
|
||||
return false
|
||||
end
|
||||
-- 通过摄像头配置表信息初始化摄像头
|
||||
camera_id = camera.init(1, 24000000, camera_module.mode, camera_module.is_msb, camera_module.rx_bit,
|
||||
camera_module.seq_type, camera_module.is_ddr, camera_param.work_mode, camera_param.work_mode,
|
||||
camera_module.width, camera_module.height)
|
||||
if not camera_id then
|
||||
log.error("excamera.open", "camera.init失败")
|
||||
return false
|
||||
end
|
||||
-- 通过I2C向摄像头发送配置信息
|
||||
for i = 1, #camera_module.init_cmds do
|
||||
result = i2c.send(camera_param.i2c_id, camera_module.i2c_slave_addr, camera_module.init_cmds[i], 1)
|
||||
if not result then
|
||||
log.error("excamera.open", "i2c.send失败")
|
||||
return false
|
||||
end
|
||||
end
|
||||
else
|
||||
-- I2C配置失败,记录错误日志
|
||||
log.info("I2C配置错误,请确认I2C接口配置是否正确")
|
||||
return false
|
||||
end
|
||||
else
|
||||
-- 如果不是SPI摄像头,则按照DVP/USB摄像头的初始化方式处理
|
||||
-- 如果既不是SPI摄像头,也不是DVP/USB摄像头,则返回错误
|
||||
if not camera.init(camera_param) then
|
||||
log.info(
|
||||
"配置表中“id”参数未配置正确,DVP/USB摄像头请使用camera.USB or camera.DVP这样的常量,不需要加引号,请检查配置表,选择正确类型的配置表填写")
|
||||
return false
|
||||
end
|
||||
camera_id = camera_param.id
|
||||
|
||||
end
|
||||
|
||||
-- 注册摄像头事件回调处理
|
||||
camera.on(camera_id, "scanned", function(id, str)
|
||||
-- 如果返回字符串,表示扫码成功并获得结果
|
||||
if type(str) == 'string' then
|
||||
log.info("扫码结果", str)
|
||||
sys.publish("SCAN_DONE", str)
|
||||
-- 如果返回false,表示摄像头没有有效数据
|
||||
elseif str == false then
|
||||
log.error("摄像头没有数据")
|
||||
-- 如果返回true或数字,表示成功捕获到图像文件大小
|
||||
elseif str == true or type(str) == 'number' then
|
||||
log.info("摄像头数据", str)
|
||||
-- 发布CAPTURE_DONE事件,通知其他任务拍照已完成
|
||||
sys.publish("CAPTURE_DONE", true)
|
||||
end
|
||||
end)
|
||||
-- 停止摄像头当前采集,释放内存空间
|
||||
camera.stop(camera_id)
|
||||
|
||||
-- 处理图像保存路径,支持内存缓冲区(ZBUFF)或文件路径
|
||||
if camera_param.save_path == "ZBUFF" then
|
||||
-- 根据摄像头型号设置图像分辨率
|
||||
if camera_param.id == "bf30a2" then
|
||||
h, w = 240, 320 -- BF30A2摄像头分辨率
|
||||
elseif camera_param.id == "gc032a" or "gc0310" then
|
||||
h, w = 640, 480 -- GC032A/GC0310摄像头分辨率
|
||||
elseif camera_param.id == camera.USB or camera.DVP then
|
||||
-- USB或DVP摄像头使用传入的分辨率参数
|
||||
h, w = camera_param.sensor_height, camera_param.sensor_width
|
||||
end
|
||||
|
||||
-- 创建ZBUFF内存缓冲区,用于存储图像数据
|
||||
-- 参数1: 缓冲区大小(宽*高*2,2字节/像素)
|
||||
-- 参数2: 对齐方式
|
||||
camera_buff = zbuff.create(h * w * 2, 0)
|
||||
if camera_buff == nil then
|
||||
-- 缓冲区创建失败
|
||||
log.info("ZBUFF创建失败")
|
||||
return false
|
||||
else
|
||||
-- 缓冲区创建成功,保存到path变量
|
||||
path = camera_buff
|
||||
end
|
||||
else
|
||||
-- 如果是文件路径则赋值到path,便于后面调用
|
||||
path = camera_param.save_path
|
||||
end
|
||||
-- 判断是否需要管理摄像头补光灯
|
||||
if type(camera_param.camera_light) == "number" then
|
||||
cam_light = gpio.setup(camera_param.camera_light, 0)
|
||||
end
|
||||
-- 返回初始化动作结果
|
||||
return true
|
||||
end
|
||||
|
||||
-- 拍照函数:使用指定摄像头拍摄照片并保存
|
||||
-- 参数:x, y, w, h - 可选,指定拍摄区域的起始坐标和尺寸(裁剪区域)
|
||||
-- 返回值:成功返回(true, 保存路径),失败返回false
|
||||
-- 使用ZBUFF处理照片时,每次调用该接口为了避免内存爆满,会覆盖写入ZBUFF区,保证ZBUFF区始终只有一张照片,处理上传或者存储后再调用该接口,避免照片丢失
|
||||
function excamera.photo(x, y, w, h)
|
||||
if not camera_id then
|
||||
log.info("摄像头初始化失败,请重新确认软硬件配置")
|
||||
return false
|
||||
end
|
||||
-- 开始摄像头图像采集
|
||||
camera.start(camera_id)
|
||||
-- 如果使用内存缓冲区保存,重置缓冲区位置指针到开始位置
|
||||
if type(path) == "userdata" then
|
||||
camera_buff:seek(0)
|
||||
end
|
||||
-- 保护执行打开补光灯,如果上面没有配置补光灯,该函数也不会报错
|
||||
pcall(cam_light, 1)
|
||||
log.info("照片存储路径", path)
|
||||
-- 执行拍照操作,保存到指定路径
|
||||
if camera.capture(camera_id, path, 1, x, y, w, h) then
|
||||
-- 等待拍照完成事件,超时时间5000ms
|
||||
result = sys.waitUntil("CAPTURE_DONE", 5000)
|
||||
-- 保护执行关闭补光灯,如果上面没有配置补光灯,该函数也不会报错
|
||||
pcall(cam_light, 0)
|
||||
-- 停止摄像头采集,释放内存空间
|
||||
camera.stop(camera_id)
|
||||
if result then
|
||||
-- 拍照成功
|
||||
log.info("拍照完成")
|
||||
else
|
||||
-- 拍照超时
|
||||
log.info("拍照成功,无照片生成")
|
||||
return false
|
||||
end
|
||||
else
|
||||
-- 保护执行关闭补光灯,如果上面没有配置补光灯,该函数也不会报错
|
||||
pcall(cam_light, 0)
|
||||
-- 停止摄像头采集,释放内存空间
|
||||
camera.stop(camera_id)
|
||||
-- 拍照操作失败
|
||||
log.info("拍照失败,请重试")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 返回成功状态和照片保存路径
|
||||
return true, path
|
||||
end
|
||||
|
||||
-- 扫描函数:使用摄像头进行扫描(如二维码/条形码扫描)
|
||||
-- 参数:扫描时长ms,单位毫秒
|
||||
-- 返回值:成功返回(true, 扫描数据),超时未有扫描结果返回false
|
||||
function excamera.scan(ms)
|
||||
if not camera_id then
|
||||
log.info("摄像头初始化失败,请重新确认软硬件配置")
|
||||
return false
|
||||
end
|
||||
-- 开始摄像头图像采集
|
||||
camera.start(camera_id)
|
||||
-- 保护执行打开补光灯,如果上面没有配置补光灯,该函数也不会报错
|
||||
pcall(cam_light, 1)
|
||||
-- 等待SCAN_DONE事件,超时时间根据用户配置
|
||||
result, data = sys.waitUntil("SCAN_DONE", ms)
|
||||
-- 停止摄像头采集,释放内存空间
|
||||
camera.stop(camera_id)
|
||||
-- 保护执行关闭补光灯,如果上面没有配置补光灯,该函数也不会报错
|
||||
pcall(cam_light, 0)
|
||||
if result then
|
||||
log.info("扫描完成,扫描结果为:", data)
|
||||
else
|
||||
log.info(ms .. "秒内未扫描成功,请将摄像头对准二维码")
|
||||
return false
|
||||
end
|
||||
-- 返回成功状态和扫描到的数据
|
||||
return true, data
|
||||
end
|
||||
|
||||
-- 录像函数:使用指定摄像头录制视频并存入tf卡中
|
||||
-- 参数:
|
||||
-- file_path - 视频保存路径,如"/sd/video.mp4"
|
||||
-- duration - 录制时长,单位毫秒
|
||||
-- fps - 可选,帧率配置
|
||||
-- 返回值:成功返回(true, 保存路径),失败返回false
|
||||
-- 注意:在使用此函数前,需要先使用excamera.open配置摄像头
|
||||
|
||||
-- spi_id,pin_cs
|
||||
local function fatfs_spi_pin()
|
||||
local rtos_bsp = rtos.bsp()
|
||||
if rtos_bsp == "AIR101" then
|
||||
return 0, pin.PB04
|
||||
elseif rtos_bsp == "AIR103" then
|
||||
return 0, pin.PB04
|
||||
elseif rtos_bsp == "AIR105" then
|
||||
return 2, pin.PB03
|
||||
elseif rtos_bsp == "ESP32C3" then
|
||||
return 2, 7
|
||||
elseif rtos_bsp == "ESP32S3" then
|
||||
return 2, 14
|
||||
elseif rtos_bsp == "EC618" then
|
||||
return 0, 8
|
||||
elseif string.find(rtos_bsp,"EC718") then
|
||||
return 0, 8
|
||||
elseif string.find(rtos_bsp,"Air810") then
|
||||
gpio.setup(13, 1, gpio.PULLUP)
|
||||
gpio.setup(28, 1, gpio.PULLUP)
|
||||
return 0, 3, fatfs.SDIO
|
||||
else
|
||||
log.info("main", "bsp not support")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- TF卡挂载函数
|
||||
local function mount_tf_card()
|
||||
-- 检查TF卡是否已经挂载
|
||||
local result = io.open("/sd/test.txt", "w")
|
||||
if result then
|
||||
result:close()
|
||||
os.remove("/sd/test.txt")
|
||||
log.info("excamera.mount_tf_card", "TF卡已经挂载")
|
||||
return true
|
||||
end
|
||||
|
||||
-- 尝试挂载TF卡
|
||||
local spi_id, pin_cs, tp = fatfs_spi_pin()
|
||||
if not spi_id then
|
||||
log.error("excamera.mount_tf_card", "不支持的平台")
|
||||
return false
|
||||
end
|
||||
|
||||
-- SPI模式需要初始化SPI总线
|
||||
if tp and tp == fatfs.SPI then
|
||||
spi.setup(spi_id, nil, 0, 0, 8, 400 * 1000)
|
||||
gpio.setup(pin_cs, 1)
|
||||
end
|
||||
|
||||
-- 挂载TF卡
|
||||
local ret = fatfs.mount(tp or fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
|
||||
if ret then
|
||||
log.info("excamera.mount_tf_card", "TF卡挂载成功")
|
||||
-- 检查空间
|
||||
local free_info = fatfs.getfree("/sd")
|
||||
if free_info then
|
||||
log.info("excamera.mount_tf_card", "剩余空间:", free_info.free_kb/1024, "MB")
|
||||
end
|
||||
return true
|
||||
else
|
||||
log.error("excamera.mount_tf_card", "TF卡挂载失败")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function excamera.video(file_path, duration, fps)
|
||||
|
||||
if not file_path or not duration then
|
||||
log.error("excamera.video", "参数错误")
|
||||
return false
|
||||
end
|
||||
|
||||
if not camera_id then
|
||||
log.error("excamera.video", "摄像头未初始化")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 如果文件路径以/sd开头,确保TF卡已挂载
|
||||
if string.sub(file_path, 1, 4) == "/sd/" then
|
||||
if not mount_tf_card() then
|
||||
log.error("excamera.video", "TF卡挂载失败,无法录制视频")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
log.info("excamera.video", "开始录制视频到", file_path)
|
||||
|
||||
-- 如果指定了帧率,则设置摄像头帧率
|
||||
if fps and fps > 0 then
|
||||
camera.config(camera_id, camera.CONF_UVC_FPS, fps)
|
||||
end
|
||||
|
||||
-- 打印内存信息
|
||||
log.info("excamera.video", "lua内存:", rtos.meminfo())
|
||||
log.info("excamera.video", "sys内存:", rtos.meminfo("sys"))
|
||||
|
||||
-- 1. 启动摄像头
|
||||
if camera.start(camera_id) then
|
||||
-- 2. 开始MP4录制
|
||||
if camera.capture(camera_id, file_path, 1) then
|
||||
-- 3. 等待录制时长
|
||||
sys.wait(duration)
|
||||
|
||||
-- 4. 停止录制
|
||||
camera.stop(camera_id)
|
||||
|
||||
-- 5. 关闭摄像头,释放资源
|
||||
camera.close(camera_id)
|
||||
|
||||
-- 再次打印内存信息
|
||||
log.info("excamera.video", "lua内存:", rtos.meminfo())
|
||||
log.info("excamera.video", "sys内存:", rtos.meminfo("sys"))
|
||||
|
||||
log.info("excamera.video", "视频录制完成", file_path)
|
||||
return true, file_path
|
||||
else
|
||||
-- 录制启动失败,关闭摄像头
|
||||
camera.stop(camera_id)
|
||||
camera.close(camera_id)
|
||||
log.error("excamera.video", "无法开始录制")
|
||||
return false
|
||||
end
|
||||
else
|
||||
log.error("excamera.video", "无法启动摄像头")
|
||||
camera.close(camera_id)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- 关闭函数:释放摄像头资源
|
||||
-- 参数:camera_id - 摄像头ID
|
||||
function excamera.close()
|
||||
if camera_id then
|
||||
-- 关闭摄像头,释放摄像头硬件资源
|
||||
camera.close(camera_id)
|
||||
end
|
||||
-- 关闭SPI摄像头时需要关闭I2C接口,释放通信总线资源
|
||||
-- USB和DVP摄像头不需要关闭i2c,所以需要判断摄像头ID返回值,USB为32,DVP为0,SPI为1
|
||||
if camera_id == 1 then
|
||||
i2c.close(camera_i2c)
|
||||
end
|
||||
-- 保护执行摄像头使能关闭,如果上面没有配置摄像头使能管脚,该函数也不会报错
|
||||
pcall(cam_pwr, 0)
|
||||
-- 保护执行摄像头开关关闭,如果上面没有配置摄像头开关管脚,该函数也不会报错
|
||||
pcall(cam_pwdn, 1)
|
||||
-- 如果使用了内存缓冲区,释放相关资源
|
||||
if type(path) == "userdata" then
|
||||
-- 置空缓冲区引用,便于垃圾回收
|
||||
camera_buff:free()
|
||||
camera_buff = nil
|
||||
path = nil
|
||||
-- 记录当前系统剩余内存情况
|
||||
log.info("剩余内存", rtos.meminfo("sys"))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
return excamera
|
||||
2484
4G/tools/resource/soc_script/v2025.12.31.22/lib/excloud.lua
Normal file
2484
4G/tools/resource/soc_script/v2025.12.31.22/lib/excloud.lua
Normal file
File diff suppressed because it is too large
Load Diff
4005
4G/tools/resource/soc_script/v2025.12.31.22/lib/exeasyui.lua
Normal file
4005
4G/tools/resource/soc_script/v2025.12.31.22/lib/exeasyui.lua
Normal file
File diff suppressed because it is too large
Load Diff
229
4G/tools/resource/soc_script/v2025.12.31.22/lib/exfotawifi.lua
Normal file
229
4G/tools/resource/soc_script/v2025.12.31.22/lib/exfotawifi.lua
Normal file
@@ -0,0 +1,229 @@
|
||||
--[[
|
||||
@module exfotawifi
|
||||
@summary 用于Air8000/8000A/8000W型号模组自动升级WIFI
|
||||
@version 1.0.3
|
||||
@date 2025.9.23
|
||||
@author 拓毅恒
|
||||
@usage
|
||||
注:使用时在创建的一个task处理函数中直接调用exfotawifi.request()即可开始执行WiFi升级任务
|
||||
升级完毕后最好取消调用,防止后期版本升级过高导致程序使用不稳定
|
||||
|
||||
-- 用法实例
|
||||
local exfotawifi = require("exfotawifi")
|
||||
|
||||
local function fota_wifi_task()
|
||||
-- ...此处省略很多代码
|
||||
|
||||
local result = exfotawifi.request()
|
||||
if result then
|
||||
log.info("exfotawifi", "升级任务执行成功")
|
||||
else
|
||||
log.info("exfotawifi", "升级任务执行失败")
|
||||
end
|
||||
|
||||
-- ...此处省略很多代码
|
||||
end
|
||||
|
||||
-- 启动WiFi自动更新任务
|
||||
sys.taskInit(fota_wifi_task)
|
||||
]]
|
||||
local exfotawifi = {}
|
||||
local is_request = false -- 标记是否正在执行request任务
|
||||
local fota_result = false -- 记录fota任务的执行结果
|
||||
|
||||
-- 判断是否为空
|
||||
local function is_nil(s)
|
||||
return s == nil or s == ""
|
||||
end
|
||||
|
||||
-- 判断json是否合法
|
||||
local function is_json(str)
|
||||
local success, result = pcall(json.decode, str)
|
||||
return success and type(result) == "table"
|
||||
end
|
||||
|
||||
-- 解析服务器响应的json数据
|
||||
local function parse_response(body)
|
||||
if not body or body == "" then
|
||||
log.error("exfotawifi", "返回的body为空")
|
||||
return nil
|
||||
end
|
||||
|
||||
local success, json_body = pcall(json.decode, body)
|
||||
if success and type(json_body) == "table" then
|
||||
log.info("exfotawifi", "解析服务器响应成功")
|
||||
return json_body
|
||||
else
|
||||
log.error("exfotawifi", "解析服务器响应失败,body内容:", body)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- 判断是否需要升级,返回true或false
|
||||
local function need_fota(version, server_version)
|
||||
local version_num = tonumber(version)
|
||||
local server_version_num = tonumber(server_version)
|
||||
if version_num < server_version_num then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 下载升级文件,支持断点续传
|
||||
local function download_file(url)
|
||||
local download_dir = "/http_download/"
|
||||
local result, reason = io.mkdir(download_dir)
|
||||
if not result then
|
||||
log.error("download_file","io.mkdir error", reason)
|
||||
end
|
||||
|
||||
local file_path = download_dir.."fotawifi.bin"
|
||||
local downloaded_size = 0
|
||||
|
||||
-- 检查文件是否存在,获取已下载的大小
|
||||
if io.exists(file_path) then
|
||||
downloaded_size = io.fileSize(file_path)
|
||||
log.info("exfotawifi", "检测到未完成的下载,已下载大小:", downloaded_size)
|
||||
end
|
||||
|
||||
-- 设置请求头,支持断点续传
|
||||
local headers = {}
|
||||
if downloaded_size > 0 then
|
||||
headers["Range"] = "bytes=" .. downloaded_size .. "-"
|
||||
end
|
||||
|
||||
local code, headers, body = http.request("GET", url, headers, nil, nil).wait()
|
||||
if code == 200 or code == 206 then
|
||||
-- 开始写入文件
|
||||
local file_mode = downloaded_size > 0 and "a+" or "w+"
|
||||
local file = io.open(file_path, file_mode)
|
||||
if file then
|
||||
file:seek("end", downloaded_size)
|
||||
file:write(body)
|
||||
file:close()
|
||||
|
||||
-- 判断文件是否下载完整
|
||||
local file_size = io.fileSize(file_path)
|
||||
local content_length = tonumber(headers["content-length"] or headers["Content-Length"])
|
||||
if file_size >= (content_length or file_size) then
|
||||
log.info("exfotawifi", "下载升级文件成功,文件路径:", file_path)
|
||||
return file_path
|
||||
else
|
||||
log.info("exfotawifi", "下载中...当前大小:", file_size, "目标大小:", content_length)
|
||||
end
|
||||
else
|
||||
log.error("exfotawifi", "无法创建文件")
|
||||
-- 删除不完整的文件
|
||||
os.remove(file_path)
|
||||
end
|
||||
else
|
||||
log.error("exfotawifi", "下载失败,状态码:", code)
|
||||
-- 删除不完整的文件
|
||||
if io.exists(file_path) then
|
||||
os.remove(file_path)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- 执行升级操作
|
||||
local function fota_start(file_path)
|
||||
-- 检查文件是否存在
|
||||
if not io.exists(file_path) then
|
||||
log.error("exfotawifi", "升级文件不存在")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 检查文件大小是否超过256K (256 * 1024 Bytes)
|
||||
local file_size = io.fileSize(file_path)
|
||||
if file_size < 256 * 1024 then
|
||||
log.error("exfotawifi", "升级文件大小不足256K,文件大小:", file_size)
|
||||
return false
|
||||
end
|
||||
|
||||
-- 执行airlink.sfota操作
|
||||
local result = airlink.sfota(file_path)
|
||||
if result then
|
||||
log.info("exfotawifi", "升级成功")
|
||||
-- 释放文件占用的空间
|
||||
-- 因为sfota是异步执行的,所以这里不能用os.remove()删除文件
|
||||
file_path = nil
|
||||
return true
|
||||
else
|
||||
log.error("exfotawifi", "升级失败")
|
||||
os.remove(file_path)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function exfotawifi.request()
|
||||
local result, ip, adapter = sys.waitUntil("IP_READY", 30000)
|
||||
if result then
|
||||
log.info("exfotawifi", "开始执行升级任务")
|
||||
|
||||
if is_request then
|
||||
log.warn("exfotawifi", "升级任务正在执行中,请勿重复调用")
|
||||
return false
|
||||
end
|
||||
|
||||
is_request = true
|
||||
fota_result = false
|
||||
|
||||
-- 构建请求URL
|
||||
local url = "http://wififota.openluat.com/air8000/update.json"
|
||||
local imei = is_nil(mobile.imei()) and "未知imei" or mobile.imei()
|
||||
local version = is_nil(airlink.sver()) and "未知版本" or airlink.sver()
|
||||
local muid = is_nil(mobile.muid()) and "未知muid" or mobile.muid()
|
||||
local hw = is_nil(hmeta.hwver()) and "未知硬件版本" or hmeta.hwver()
|
||||
local coreversion = is_nil(rtos.version()) and "未知4G固件版本" or rtos.version()
|
||||
local model = is_nil(hmeta.model()) and "未知4G设备型号" or hmeta.model()
|
||||
local request_url = string.format("%s?imei=%s&version=%s&muid=%s&hw=%s&coreversion=%s&model=%s", url, imei, version, muid, hw, coreversion, model)
|
||||
|
||||
log.info("exfotawifi", "正在请求升级信息, URL:", request_url)
|
||||
|
||||
-- 发送HTTP请求获取服务器响应
|
||||
local code, headers, body = http.request("GET", request_url, {}, nil, {timeout = 30000}).wait()
|
||||
if code == 200 then
|
||||
log.info("exfotawifi", "获取服务器响应成功")
|
||||
-- 打印返回的body内容
|
||||
-- log.info("exfotawifi", "body:", body)
|
||||
-- 解析服务器响应的json数据
|
||||
local response = parse_response(body)
|
||||
if response then
|
||||
-- 获取服务器返回的版本号和下载链接
|
||||
local server_version = response.version
|
||||
local download_url = response.url
|
||||
|
||||
-- 获取本地版本号
|
||||
local local_version = airlink.sver()
|
||||
|
||||
-- 判断是否需要升级
|
||||
if need_fota(local_version, server_version) then
|
||||
log.info("exfotawifi", "需要升级, 本地版本:", local_version, "服务器版本:", server_version)
|
||||
-- 下载升级文件
|
||||
local file_path = download_file(download_url)
|
||||
if file_path then
|
||||
-- 开始升级
|
||||
fota_result = fota_start(file_path)
|
||||
end
|
||||
else
|
||||
log.info("exfotawifi", "当前已是最新WIFI固件")
|
||||
fota_result = true
|
||||
end
|
||||
else
|
||||
log.error("exfotawifi", "解析服务器响应失败")
|
||||
end
|
||||
else
|
||||
log.error("exfotawifi", "获取服务器响应失败,状态码:", code)
|
||||
end
|
||||
else
|
||||
log.error("当前正在升级WIFI&蓝牙固件,请插入可以上网的SIM卡并重新启动")
|
||||
end
|
||||
|
||||
-- 释放请求标记
|
||||
is_request = false
|
||||
return fota_result
|
||||
end
|
||||
|
||||
return exfotawifi
|
||||
1029
4G/tools/resource/soc_script/v2025.12.31.22/lib/exgnss.lua
Normal file
1029
4G/tools/resource/soc_script/v2025.12.31.22/lib/exgnss.lua
Normal file
File diff suppressed because it is too large
Load Diff
327
4G/tools/resource/soc_script/v2025.12.31.22/lib/exlcd.lua
Normal file
327
4G/tools/resource/soc_script/v2025.12.31.22/lib/exlcd.lua
Normal file
@@ -0,0 +1,327 @@
|
||||
-- exlcd.lua
|
||||
--[[
|
||||
@module exlcd
|
||||
@summary LCD显示拓展库
|
||||
@version 1.0.5
|
||||
@date 2025.12.23
|
||||
@author 江访
|
||||
@usage
|
||||
本文件为LCD显示拓展库,核心业务逻辑为:
|
||||
1、初始化LCD显示屏,支持多种显示芯片
|
||||
2、管理屏幕背光亮度及开关状态
|
||||
3、提供屏幕状态管理功能
|
||||
4、支持根据lcd_model自动配置参数
|
||||
|
||||
本文件的对外接口有6个:
|
||||
1、exlcd.init(param):LCD初始化函数
|
||||
2、exlcd.set_bl(level):设置背光亮度接口,level为亮度级别(0-100)
|
||||
3、exlcd.get_bl():当前设置背光亮度级别查询
|
||||
4、exlcd.sleep():屏幕休眠
|
||||
5、exlcd.wakeup():屏幕唤醒
|
||||
6、exlcd.get_sleep():休眠状态查询
|
||||
]]
|
||||
|
||||
local exlcd = {}
|
||||
|
||||
-- 屏幕状态管理表
|
||||
local screen_state = {
|
||||
last_brightness = 100, -- 默认亮度100%
|
||||
backlight_on = true, -- 背光默认开启
|
||||
lcd_config = nil -- 存储LCD配置
|
||||
}
|
||||
|
||||
-- 预定义屏幕配置表
|
||||
local predefined_configs = {
|
||||
Air780EHM_LCD_4 = {
|
||||
lcd_model = "Air780EHM_LCD_4",
|
||||
pin_vcc = 24,
|
||||
pin_rst = 36,
|
||||
pin_pwr = 25,
|
||||
pin_pwm = 2,
|
||||
port = lcd.HWID_0,
|
||||
direction = 3,
|
||||
w = 480,
|
||||
h = 320,
|
||||
xoffset = 0,
|
||||
yoffset = 0,
|
||||
sleepcmd = 0X10,
|
||||
wakecmd = 0X11,
|
||||
},
|
||||
|
||||
AirLCD_1000 = {
|
||||
lcd_model = "AirLCD_1000",
|
||||
pin_vcc = 29,
|
||||
pin_rst = 36,
|
||||
pin_pwr = 30,
|
||||
pin_pwm = 1,
|
||||
port = lcd.HWID_0,
|
||||
direction = 0,
|
||||
w = 320,
|
||||
h = 480,
|
||||
xoffset = 0,
|
||||
yoffset = 0,
|
||||
sleepcmd = 0X10,
|
||||
wakecmd = 0X11,
|
||||
},
|
||||
|
||||
AirLCD_1010 = {
|
||||
lcd_model = "AirLCD_1010",
|
||||
pin_vcc = 141,
|
||||
pin_rst = 36,
|
||||
pin_pwr = 1,
|
||||
pin_pwm = 0,
|
||||
port = lcd.HWID_0,
|
||||
direction = 0,
|
||||
w = 320,
|
||||
h = 480,
|
||||
xoffset = 0,
|
||||
yoffset = 0,
|
||||
sleepcmd = 0X10,
|
||||
wakecmd = 0X11,
|
||||
},
|
||||
|
||||
AirLCD_1020 = {
|
||||
lcd_model = "AirLCD_1020",
|
||||
pin_pwr = 8,
|
||||
pin_pwm = 0,
|
||||
port = lcd.RGB,
|
||||
direction = 0,
|
||||
w = 800,
|
||||
h = 480,
|
||||
xoffset = 0,
|
||||
yoffset = 0,
|
||||
}
|
||||
}
|
||||
|
||||
--[[
|
||||
初始化LCD显示屏
|
||||
@api exlcd.init(param)
|
||||
@table param LCD配置参数,参考库的说明及demo用法
|
||||
@return bool 初始化成功返回true,失败返回false
|
||||
@usage
|
||||
-- 使用预定义配置初始化
|
||||
exlcd.init({lcd_model = "Air780EHM_LCD_4"})
|
||||
|
||||
-- 自定义参数初始化
|
||||
exlcd.init({
|
||||
lcd_model = "st7796",
|
||||
port = lcd.HWID_0,
|
||||
pin_rst = 36,
|
||||
pin_pwr = 25,
|
||||
pin_pwm = 2,
|
||||
w = 480,
|
||||
h = 320,
|
||||
direction = 0
|
||||
})
|
||||
]]
|
||||
function exlcd.init(param)
|
||||
if type(param) ~= "table" then
|
||||
log.error("exlcd", "参数必须为表")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 检查必要参数
|
||||
if not param.lcd_model then
|
||||
log.error("exlcd", "缺少必要参数: lcd_model")
|
||||
return false
|
||||
end
|
||||
|
||||
local config = {}
|
||||
|
||||
-- 根据lcd_model选择配置策略
|
||||
if param.lcd_model == "Air780EHM_LCD_4" then
|
||||
-- Air780EHM_LCD_4: 只使用lcd_model,其他参数固定
|
||||
config = predefined_configs.Air780EHM_LCD_4
|
||||
log.info("exlcd", "使用Air780EHM_LCD_4固定配置")
|
||||
elseif predefined_configs[param.lcd_model] then
|
||||
-- 其他预定义型号: 使用预定义配置作为基础,传入参数覆盖预定义配置
|
||||
config = {}
|
||||
|
||||
-- 复制预定义配置
|
||||
for k, v in pairs(predefined_configs[param.lcd_model]) do
|
||||
config[k] = v
|
||||
end
|
||||
|
||||
-- 用传入参数覆盖预定义配置
|
||||
for k, v in pairs(param) do
|
||||
if k ~= "lcd_model" or v ~= param.lcd_model then -- 避免重复设置lcd_model
|
||||
config[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
log.info("exlcd", "使用" .. param.lcd_model .. "基础配置,传入参数已覆盖")
|
||||
else
|
||||
-- 未知型号: 直接使用传入参数
|
||||
config = param
|
||||
log.info("exlcd", "使用传入参数配置")
|
||||
end
|
||||
|
||||
-- LCD型号映射表
|
||||
local lcd_models = {
|
||||
AirLCD_1000 = "st7796",
|
||||
Air780EHM_LCD_4 = "st7796",
|
||||
AirLCD_1010 = "st7796",
|
||||
AirLCD_1020 = "h050iwv"
|
||||
}
|
||||
|
||||
-- 确定LCD型号
|
||||
local lcd_model = lcd_models[config.lcd_model] or config.lcd_model
|
||||
|
||||
-- 存储LCD配置供其他函数使用
|
||||
screen_state.lcd_config = {
|
||||
pin_pwr = config.pin_pwr,
|
||||
pin_pwm = config.pin_pwm,
|
||||
model = lcd_model,
|
||||
lcd_model = config.lcd_model
|
||||
}
|
||||
|
||||
-- 设置电源引脚 (可选)
|
||||
if config.pin_vcc then
|
||||
gpio.setup(config.pin_vcc, 1, gpio.PULLUP)
|
||||
gpio.set(config.pin_vcc, 1)
|
||||
end
|
||||
|
||||
-- 设置背光电源引脚 (可选)
|
||||
if config.pin_pwr then
|
||||
gpio.setup(config.pin_pwr, 1, gpio.PULLUP)
|
||||
gpio.set(config.pin_pwr, 1) -- 默认开启背光
|
||||
end
|
||||
|
||||
-- 设置PWM背光引脚 (可选)
|
||||
if config.pin_pwm then
|
||||
pwm.setup(config.pin_pwm, 1000, screen_state.last_brightness)
|
||||
pwm.open(config.pin_pwm, 1000, screen_state.last_brightness)
|
||||
end
|
||||
|
||||
-- 屏幕初始化 (spi_dev和init_in_service为可选参数)
|
||||
local lcd_init = lcd.init(
|
||||
lcd_model,
|
||||
config,
|
||||
config.spi_dev and config.spi_dev or nil,
|
||||
config.init_in_service and config.init_in_service or nil
|
||||
)
|
||||
log.info("exlcd", "LCD初始化", lcd_init)
|
||||
|
||||
-- 自定义初始化完成确认
|
||||
if lcd_model == "custom" then
|
||||
lcd.user_done()
|
||||
end
|
||||
return lcd_init
|
||||
end
|
||||
|
||||
--[[
|
||||
设置背光亮度
|
||||
@api exlcd.set_bl(level)
|
||||
@number level 亮度级别,0-100,0表示关闭背光
|
||||
@return bool 设置成功返回true,失败返回false
|
||||
@usage
|
||||
-- 设置50%亮度
|
||||
exlcd.set_bl(50)
|
||||
|
||||
-- 关闭背光
|
||||
exlcd.set_bl(0)
|
||||
]]
|
||||
|
||||
function exlcd.set_bl(level)
|
||||
-- 检查PWM配置
|
||||
if not screen_state.lcd_config.pin_pwm then
|
||||
log.error("exlcd", "PWM配置不存在,无法调节背光")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 确保GPIO已关闭
|
||||
if screen_state.lcd_config.pin_pwr then
|
||||
gpio.close(screen_state.lcd_config.pin_pwr)
|
||||
end
|
||||
|
||||
-- 设置并开启PWM
|
||||
pwm.stop(screen_state.lcd_config.pin_pwm)
|
||||
pwm.close(screen_state.lcd_config.pin_pwm)
|
||||
pwm.setup(screen_state.lcd_config.pin_pwm, 1000, 100)
|
||||
pwm.open(screen_state.lcd_config.pin_pwm, 1000, level)
|
||||
screen_state.last_brightness = level
|
||||
screen_state.backlight_on = (level > 0)
|
||||
log.info("exlcd", "背光设置为", level, "%")
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
获取当前背光亮度
|
||||
@api exlcd.get_bl()
|
||||
@return number 当前背光亮度级别(0-100)
|
||||
@usage
|
||||
local brightness = exlcd.get_bl()
|
||||
log.info("当前背光亮度", brightness)
|
||||
]]
|
||||
function exlcd.get_bl()
|
||||
return screen_state.last_brightness
|
||||
end
|
||||
|
||||
--[[
|
||||
屏幕进入休眠状态
|
||||
@api exlcd.sleep()
|
||||
@usage
|
||||
exlcd.sleep()
|
||||
]]
|
||||
function exlcd.sleep()
|
||||
if not screen_state.is_sleeping then
|
||||
-- 关闭PWM背光 (如果配置了)
|
||||
if screen_state.lcd_config and screen_state.lcd_config.pin_pwm then
|
||||
pwm.close(screen_state.lcd_config.pin_pwm)
|
||||
end
|
||||
|
||||
-- 关闭背光电源 (如果配置了)
|
||||
if screen_state.lcd_config and screen_state.lcd_config.pin_pwr then
|
||||
gpio.setup(screen_state.lcd_config.pin_pwr, 1, gpio.PULLUP)
|
||||
gpio.set(screen_state.lcd_config.pin_pwr, 0)
|
||||
end
|
||||
|
||||
-- 执行LCD睡眠
|
||||
lcd.sleep()
|
||||
screen_state.is_sleeping = true
|
||||
log.info("exlcd", "LCD进入休眠状态")
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
屏幕从休眠状态唤醒
|
||||
@api exlcd.wakeup()
|
||||
@usage
|
||||
exlcd.wakeup()
|
||||
]]
|
||||
function exlcd.wakeup()
|
||||
if screen_state.is_sleeping then
|
||||
-- 开启背光电源 (如果配置了)
|
||||
if screen_state.lcd_config and screen_state.lcd_config.pin_pwr then
|
||||
gpio.set(screen_state.lcd_config.pin_pwr, 1)
|
||||
end
|
||||
|
||||
-- 唤醒LCD
|
||||
lcd.wakeup()
|
||||
sys.wait(100) -- 等待100ms稳定
|
||||
|
||||
-- 恢复背光设置 (如果配置了PWM引脚)
|
||||
if screen_state.lcd_config and screen_state.lcd_config.pin_pwm then
|
||||
pwm.setup(screen_state.lcd_config.pin_pwm, 1000, screen_state.last_brightness)
|
||||
pwm.open(screen_state.lcd_config.pin_pwm, 1000, screen_state.last_brightness)
|
||||
end
|
||||
|
||||
screen_state.is_sleeping = false
|
||||
log.info("exlcd", "LCD唤醒")
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
获取屏幕休眠状态
|
||||
@api exlcd.get_sleep()
|
||||
@return bool true表示屏幕处于休眠状态,false表示屏幕处于工作状态
|
||||
@usage
|
||||
if exlcd.get_sleep() then
|
||||
log.info("屏幕处于休眠状态")
|
||||
end
|
||||
]]
|
||||
function exlcd.get_sleep()
|
||||
return screen_state.is_sleeping
|
||||
end
|
||||
|
||||
return exlcd
|
||||
387
4G/tools/resource/soc_script/v2025.12.31.22/lib/exmodbus.lua
Normal file
387
4G/tools/resource/soc_script/v2025.12.31.22/lib/exmodbus.lua
Normal file
@@ -0,0 +1,387 @@
|
||||
--[[
|
||||
@module exmodbus
|
||||
@summary exmodbus 控制Modbus RTU/ASCII/TCP主站/从站通信
|
||||
@version 1.0
|
||||
@date 2025.
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本文件的对外接口有 5 个:
|
||||
1、exmodbus.create(config):创建 modbus 主站/从站,支持 RTU、ASCII、TCP 三种通信模式
|
||||
2、modbus:read(config):主站向从站发起读取请求(仅适用于 RTU、ASCII、TCP 主站模式)
|
||||
3、modbus:write(config):主站向从站发起写入请求(仅适用于 RTU、ASCII、TCP 主站模式)
|
||||
4、modbus:destroy():销毁 modbus 主站/从站实例对象
|
||||
5、modbus:on(callback):从站注册回调接口,用于处理主站发起的请求(仅适用于 RTU、ASCII、TCP 从站模式)
|
||||
]]
|
||||
local exmodbus = {}
|
||||
|
||||
-- 定义通信模式常量
|
||||
exmodbus.RTU_MASTER = 0 -- RTU 主站模式
|
||||
exmodbus.RTU_SLAVE = 1 -- RTU 从站模式
|
||||
exmodbus.ASCII_MASTER = 2 -- ASCII 主站模式
|
||||
exmodbus.ASCII_SLAVE = 3 -- ASCII 从站模式
|
||||
exmodbus.TCP_MASTER = 4 -- TCP 主站模式
|
||||
exmodbus.TCP_SLAVE = 5 -- TCP 从站模式
|
||||
|
||||
-- 定义数据类型常量
|
||||
exmodbus.COIL_STATUS = 0 -- 线圈状态
|
||||
exmodbus.INPUT_STATUS = 1 -- 离散输入状态
|
||||
exmodbus.HOLDING_REGISTER = 4 -- 保持寄存器
|
||||
exmodbus.INPUT_REGISTER = 3 -- 输入寄存器
|
||||
|
||||
-- 定义操作类型常量
|
||||
exmodbus.READ_COILS = 0x01 -- 读线圈状态
|
||||
exmodbus.READ_DISCRETE_INPUTS = 0x02 -- 读离散输入状态
|
||||
exmodbus.READ_HOLDING_REGISTERS = 0x03 -- 读保持寄存器
|
||||
exmodbus.READ_INPUT_REGISTERS = 0x04 -- 读输入寄存器
|
||||
exmodbus.WRITE_SINGLE_COIL = 0x05 -- 写单个线圈状态
|
||||
exmodbus.WRITE_SINGLE_HOLDING_REGISTER = 0x06 -- 写单个保持寄存器
|
||||
exmodbus.WRITE_MULTIPLE_HOLDING_REGISTERS = 0x10 -- 写多个保持寄存器
|
||||
exmodbus.WRITE_MULTIPLE_COILS = 0x0F -- 写多个线圈状态
|
||||
|
||||
-- 定义响应结果常量
|
||||
exmodbus.STATUS_SUCCESS = 0 -- 收到响应数据且数据有效
|
||||
exmodbus.STATUS_DATA_INVALID = 1 -- 收到响应数据但数据损坏/校验失败
|
||||
exmodbus.STATUS_EXCEPTION = 2 -- 收到标准异常响应码
|
||||
exmodbus.STATUS_TIMEOUT = 3 -- 超时未收到响应
|
||||
exmodbus.STATUS_PARAM_INVALID = 4 -- 请求参数不正确
|
||||
|
||||
-- 异常响应码常量
|
||||
exmodbus.ILLEGAL_FUNCTION = 0x01 -- 不支持请求的功能码
|
||||
exmodbus.ILLEGAL_DATA_ADDRESS = 0x02 -- 请求的数据地址无效或超出范围
|
||||
exmodbus.ILLEGAL_DATA_VALUE = 0x03 -- 请求的数据值无效
|
||||
exmodbus.SLAVE_DEVICE_FAILURE = 0x04 -- 从站在执行操作时发生内部错误
|
||||
exmodbus.ACKNOWLEDGE = 0x05 -- 请求已接受,但需要长时间处理
|
||||
exmodbus.SLAVE_DEVICE_BUSY = 0x06 -- 从站正忙,无法处理请求
|
||||
exmodbus.NEGATIVE_ACKNOWLEDGE = 0x07 -- 无法执行编程功能
|
||||
exmodbus.MEMORY_PARITY_ERROR = 0x08 -- 内存奇偶校验错误
|
||||
exmodbus.GATEWAY_PATH_UNAVAILABLE = 0x0A -- 网关路径不可用
|
||||
exmodbus.GATEWAY_TARGET_NO_RESPONSE = 0x0B -- 网关目标设备无响应
|
||||
|
||||
-- 全局队列与调度器;
|
||||
local request_queue = {}
|
||||
local next_request_id = 1
|
||||
local scheduler_started = false
|
||||
|
||||
-- 生成唯一请求 ID;
|
||||
local function gen_request_id()
|
||||
local id = next_request_id
|
||||
next_request_id = next_request_id + 1
|
||||
-- 确保请求 ID 在 32 位有符号整数范围内;
|
||||
if next_request_id == 0x7FFFFFFF then next_request_id = 1 end
|
||||
return id
|
||||
end
|
||||
|
||||
-- 处理队列中的请求;
|
||||
local function process_request_queue()
|
||||
while true do
|
||||
if #request_queue > 0 then
|
||||
local req = table.remove(request_queue, 1)
|
||||
local instance = req.instance
|
||||
local config = req.config
|
||||
local is_read = req.is_read
|
||||
local req_id = req.request_id
|
||||
|
||||
local result
|
||||
if is_read then
|
||||
result = instance:read_internal(config)
|
||||
else
|
||||
result = instance:write_internal(config)
|
||||
end
|
||||
|
||||
sys.publish("exmodbus/resp/" .. req_id, result)
|
||||
else
|
||||
sys.waitUntil("start_scheduler")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 启动调度器;
|
||||
local function start_scheduler()
|
||||
if scheduler_started then return end
|
||||
scheduler_started = true
|
||||
sys.taskInit(process_request_queue)
|
||||
end
|
||||
|
||||
-- 入队请求并等待响应;(内部使用)
|
||||
function exmodbus.enqueue_request(instance, config, is_read)
|
||||
-- 生成唯一请求 ID;
|
||||
local req_id = gen_request_id()
|
||||
|
||||
-- 检查队列是否为空;
|
||||
-- 如果为空,先入队,然后发布主题告知调度器开始处理;
|
||||
-- 如果不为空,则直接入队,不用告知调度器;
|
||||
if #request_queue == 0 then
|
||||
-- 入队请求;
|
||||
table.insert(request_queue, {
|
||||
instance = instance,
|
||||
config = config,
|
||||
is_read = is_read,
|
||||
request_id = req_id
|
||||
})
|
||||
|
||||
sys.publish("start_scheduler")
|
||||
else
|
||||
-- 入队请求;
|
||||
table.insert(request_queue, {
|
||||
instance = instance,
|
||||
config = config,
|
||||
is_read = is_read,
|
||||
request_id = req_id
|
||||
})
|
||||
end
|
||||
|
||||
-- 启动调度器;
|
||||
start_scheduler()
|
||||
local ok, result = sys.waitUntil("exmodbus/resp/" .. req_id)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--[[
|
||||
创建一个新的实例;
|
||||
@api exmodbus.create(config)
|
||||
@param config table 配置参数表,包含以下字段:
|
||||
mode number 通信模式,必须是 exmodbus 模块定义的常量(如 exmodbus.RTU_MASTER)
|
||||
uart_id number 串口 ID,uart0 写 0,uart1 写 1,以此类推
|
||||
baud_rate number 波特率
|
||||
data_bits number 数据位
|
||||
stop_bits number 停止位
|
||||
parity_bits number 校验位
|
||||
byte_order number 字节顺序
|
||||
rs485_dir_gpio number RS485 方向转换 GPIO 引脚
|
||||
rs485_dir_rx_level number RS485 接收方向电平
|
||||
adapter number 网卡 ID
|
||||
ip_address string 服务器 IP 地址
|
||||
port number 服务器端口号
|
||||
is_udp boolean 是否使用 UDP 协议
|
||||
is_tls boolean 是否使用加密传输
|
||||
keep_idle number 连接空闲多长时间后,开始发送第一个 keepalive 探针报文,单位:秒
|
||||
keep_interval number 发送第一个探针后,如果没收到 ACK 回复,间隔多久再发送下一个探针,单位:秒
|
||||
keep_cnt number 总共发送多少次探针后,如果依然没有回复,则判断连接已断开
|
||||
server_cert string TCP 模式下的服务器 CA 证书数据,UDP 模式下的 PSK
|
||||
client_cert string TCP 模式下的客户端证书数据,UDP 模式下的 PSK-ID
|
||||
client_key string TCP 模式下的客户端私钥加密数据
|
||||
client_password string TCP 模式下的客户端私钥口令数据
|
||||
@return table/nil 成功时返回实例对象,失败时返回 nil
|
||||
@usage
|
||||
RTU/ASCII 通信模式:
|
||||
local config = {
|
||||
mode = exmodbus.RTU_MASTER, -- 通信模式:RTU 主站
|
||||
uart_id = 1, -- 串口 ID:uart1
|
||||
baud_rate = 115200, -- 波特率:115200
|
||||
data_bits = 8, -- 数据位:8
|
||||
stop_bits = 1, -- 停止位:1
|
||||
parity_bits = uart.None, -- 校验位:无校验
|
||||
byte_order = uart.LSB, -- 字节顺序:小端序
|
||||
rs485_dir_gpio = 23, -- RS485 方向转换 GPIO 引脚
|
||||
rs485_dir_rx_level = 0 -- RS485 接收方向电平:0 为低电平,1 为高电平
|
||||
}
|
||||
local rtu_master = exmodbus.create(config)
|
||||
|
||||
TCP 通信模式:
|
||||
local config = {
|
||||
mode = exmodbus.TCP_MASTER, -- 通信模式:TCP 主站
|
||||
adapter = socket.LWIP_ETH, -- 网卡 ID:LwIP 协议栈的以太网卡
|
||||
ip_address = "192.168.1.100", -- 服务器 IP 地址:192.168.1.100(主站:服务器 IP;从站:本地 IP,从站可以不用填此参数)
|
||||
port = 502, -- 服务器端口号:502(主站:服务器端口;从站:本地端口)
|
||||
is_udp = false, -- 是否使用 UDP 协议:不使用 UDP 协议,false/nil 表示使用 TCP 协议
|
||||
is_tls = false, -- 是否使用加密传输:不使用加密传输,false/nil 表示不使用加密
|
||||
keep_idle = 300, -- 连接空闲多长时间后,开始发送第一个 keepalive 探针报文:300 秒
|
||||
keep_interval = 10, -- 发送第一个探针后,如果没收到 ACK 回复,间隔多久再发送下一个探针:10 秒
|
||||
keep_cnt = 3, -- 总共发送多少次探针后,如果依然没有回复,则判断连接已断开:3 次
|
||||
server_cert = nil, -- TCP 模式下的服务器 CA 证书数据,UDP 模式下的 PSK:如果客户端不需要验证服务器证书,则设为 nil 或空着
|
||||
client_cert = nil, -- TCP 模式下的客户端证书数据,UDP 模式下的 PSK-ID:如果服务器不需要验证客户端证书,则设为 nil 或空着
|
||||
client_key = nil, -- TCP 模式下的客户端私钥加密数据:如果服务器不需要验证客户端私钥,则设为 nil 或空着
|
||||
client_password = nil -- TCP 模式下的客户端私钥口令数据:如果服务器不需要验证客户端私钥口令,则设为 nil 或空着
|
||||
}
|
||||
local tcp_master = exmodbus.create(config)
|
||||
--]]
|
||||
function exmodbus.create(config)
|
||||
-- 检查配置参数是否有效;
|
||||
if not config or type(config) ~= "table" then
|
||||
log.error("exmodbus", "配置必须是表格类型")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 根据通信模式加载对应的模块;
|
||||
if config.mode == exmodbus.RTU_MASTER or config.mode == exmodbus.RTU_SLAVE or
|
||||
config.mode == exmodbus.ASCII_MASTER or config.mode == exmodbus.ASCII_SLAVE then
|
||||
local result, mod = pcall(require, "exmodbus_rtu_ascii")
|
||||
if not result then
|
||||
log.error("exmodbus", "加载 RTU/ASCII 模块失败")
|
||||
return false
|
||||
end
|
||||
return mod.create(config, exmodbus, gen_request_id)
|
||||
elseif config.mode == exmodbus.TCP_MASTER or config.mode == exmodbus.TCP_SLAVE then
|
||||
local result, mod = pcall(require, "exmodbus_tcp")
|
||||
if not result then
|
||||
log.error("exmodbus", "加载 TCP 模块失败")
|
||||
return false
|
||||
end
|
||||
return mod.create(config, exmodbus, gen_request_id)
|
||||
else
|
||||
log.error("exmodbus", "通信模式不支持")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
主站向从站发送读取请求(仅适用于 RTU、ASCII、TCP 主站模式)
|
||||
@api modbus:read(config)
|
||||
@param config table 配置参数表,包含以下字段:
|
||||
slave_id number 从站 ID
|
||||
reg_type number 寄存器类型
|
||||
start_addr number 寄存器起始地址
|
||||
reg_count number 寄存器数量
|
||||
raw_request string 原始请求帧
|
||||
timeout number 超时时间,单位:毫秒
|
||||
@return table 包含以下字段:
|
||||
status number 响应结果状态码,参考 exmodbus 模块定义的常量(如 exmodbus.STATUS_SUCCESS)
|
||||
execption_code number 异常码,仅在 status 为 exmodbus.STATUS_EXCEPTION 时有效
|
||||
data table 寄存器数值,仅在 status 为 exmodbus.STATUS_SUCCESS 时有效,包含以下字段
|
||||
[start_addr] number 寄存器数值,索引为寄存器地址,值为寄存器数值
|
||||
...
|
||||
raw_response string 原始响应帧
|
||||
@usage
|
||||
用户在传入 config 参数时,有 原始帧 和 字段参数 两种方式
|
||||
1. 原始帧方式
|
||||
local read_config = {
|
||||
raw_request = "010300000002C40B", -- 原始请求帧:01 03 00 00 00 02 C4 0B(读取保持寄存器 0x0000 开始的 2 个寄存器)
|
||||
timeout = 1000 -- 超时时间:1000 毫秒
|
||||
}
|
||||
local result = modbus:read(read_config)
|
||||
if result.status == exmodbus.STATUS_SUCCESS then
|
||||
log.info("exmodbus_test", "读取成功,原始响应帧: ", table.concat(result.raw_response, ", "))
|
||||
elseif result.status == exmodbus.STATUS_TIMEOUT then
|
||||
log.error("exmodbus_test", "读取请求超时")
|
||||
else
|
||||
log.error("exmodbus_test", "读取失败")
|
||||
end
|
||||
|
||||
2. 字段参数方式
|
||||
local read_config = {
|
||||
slave_id = 1, -- 从站 ID:1
|
||||
reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
|
||||
start_addr = 0x0000, -- 寄存器起始地址:0
|
||||
reg_count = 0x0002, -- 寄存器数量:2
|
||||
timeout = 1000 -- 超时时间:1000 毫秒
|
||||
}
|
||||
|
||||
local result = modbus:read(read_config)
|
||||
-- 根据返回状态处理结果
|
||||
if result.status == exmodbus.STATUS_SUCCESS then
|
||||
-- 数据解析:
|
||||
log.info("exmodbus_test", "成功读取到从站 1 保持寄存器 0-2 的值,寄存器 0 数值:", result.data[result.start_addr],
|
||||
",寄存器 1 数值:", result.data[result.start_addr + 1])
|
||||
elseif result.status == exmodbus.STATUS_DATA_INVALID then
|
||||
log.info("exmodbus_test", "收到从站 1 的响应数据但数据损坏/校验失败")
|
||||
elseif result.status == exmodbus.STATUS_EXCEPTION then
|
||||
log.info("exmodbus_test", "收到从站 1 的 modbus 标准异常响应,异常码为", result.execption_code)
|
||||
elseif result.status == exmodbus.STATUS_TIMEOUT then
|
||||
log.info("exmodbus_test", "未收到从站 1 的响应(超时)")
|
||||
end
|
||||
--]]
|
||||
-- 该接口在各个子文件中,此处仅用作注释
|
||||
-- function modbus:read(config) end
|
||||
|
||||
|
||||
--[[
|
||||
主站向从站发送写入请求(仅适用于 RTU、ASCII、TCP 主站模式)
|
||||
@api modbus:write(config)
|
||||
@param config table 配置参数表,包含以下字段:
|
||||
slave_id number 从站 ID
|
||||
reg_type number 寄存器类型
|
||||
start_addr number 寄存器起始地址
|
||||
reg_count number 寄存器数量
|
||||
data table 寄存器数值,包含以下字段:
|
||||
[start_addr] number 寄存器数值,索引为寄存器地址,值为寄存器数值
|
||||
...
|
||||
force_multiple boolean 是否强制使用写多个功能码进行写入单个寄存器操作
|
||||
raw_request string 原始请求帧
|
||||
timeout number 超时时间,单位:毫秒
|
||||
@return table 包含以下字段:
|
||||
status number 响应结果状态码,参考 exmodbus 模块定义的常量(如 exmodbus.STATUS_SUCCESS)
|
||||
execption_code number 异常码,仅在 status 为 exmodbus.STATUS_EXCEPTION 时有效
|
||||
raw_response string 原始响应帧
|
||||
@usage
|
||||
用户在传入 config 参数时,有 原始帧 和 字段参数 两种方式
|
||||
1. 原始帧方式
|
||||
local write_config = {
|
||||
raw_request = "011000000002007B01592471", -- 原始请求帧:01 10 00 00 00 02 00 7B 01 59 24 71(写入保持寄存器 0x0000 开始的 2 个寄存器,值为 0x007B 和 0x0159)
|
||||
timeout = 1000 -- 超时时间:1000 毫秒
|
||||
}
|
||||
local result = modbus:write(write_config)
|
||||
if result.status == exmodbus.STATUS_SUCCESS then
|
||||
log.info("exmodbus_test", "写入成功,原始响应帧: ", table.concat(result.raw_response, ", "))
|
||||
elseif result.status == exmodbus.STATUS_TIMEOUT then
|
||||
log.error("exmodbus_test", "写入请求超时")
|
||||
else
|
||||
log.error("exmodbus_test", "写入失败")
|
||||
end
|
||||
|
||||
2. 字段参数方式
|
||||
local write_config = {
|
||||
slave_id = 1, -- 从站 ID:1
|
||||
reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
|
||||
start_addr = 0x0000, -- 寄存器起始地址:0
|
||||
reg_count = 0x0002, -- 寄存器数量:2
|
||||
data = {
|
||||
[0x0000] = 0x007B, -- 寄存器 0 数值:0x007B
|
||||
[0x0001] = 0x0159, -- 寄存器 1 数值:0x0159
|
||||
},
|
||||
timeout = 1000 -- 超时时间:1000 毫秒
|
||||
}
|
||||
|
||||
local result = modbus:write(write_config)
|
||||
-- 根据返回状态处理结果
|
||||
if result.status == exmodbus.STATUS_SUCCESS then
|
||||
log.info("exmodbus_test", "成功写入从站 1 保持寄存器 0-2 的值")
|
||||
elseif result.status == exmodbus.STATUS_DATA_INVALID then
|
||||
log.info("exmodbus_test", "收到从站 1 的响应数据但数据损坏/校验失败")
|
||||
elseif result.status == exmodbus.STATUS_EXCEPTION then
|
||||
log.info("exmodbus_test", "收到从站 1 的 modbus 标准异常响应,异常码为", result.execption_code)
|
||||
elseif result.status == exmodbus.STATUS_TIMEOUT then
|
||||
log.info("exmodbus_test", "未收到从站 1 的响应(超时)")
|
||||
end
|
||||
--]]
|
||||
-- 该接口在各个子文件中,此处仅用作注释
|
||||
-- function modbus:write(config) end
|
||||
|
||||
|
||||
--[[
|
||||
销毁 modbus 主站/从站实例对象
|
||||
@api modbus:destroy()
|
||||
@return nil
|
||||
@usage
|
||||
modbus:destroy()
|
||||
--]]
|
||||
-- 该接口在各个子文件中,此处仅用作注释
|
||||
-- function modbus:destroy() end
|
||||
|
||||
|
||||
--[[
|
||||
从站注册回调接口,用于处理主站发起的请求(仅适用于 RTU、ASCII、TCP 从站模式)
|
||||
@api modbus:on(callback)
|
||||
@param callback function 回调函数,格式为:
|
||||
function callback(request)
|
||||
-- 用户代码
|
||||
end
|
||||
该回调函数接收 requset 一个参数,该参数为 table 类型,包含以下字段:
|
||||
slave_id number 从站 ID
|
||||
func_code number 功能码
|
||||
reg_type number 寄存器类型
|
||||
start_addr number 寄存器起始地址
|
||||
reg_count number 寄存器数量
|
||||
data table 寄存器数值,包含以下字段:
|
||||
[start_addr] number 寄存器数值,索引为寄存器地址,值为寄存器数值
|
||||
...
|
||||
@return nil
|
||||
@usage
|
||||
function callback(request)
|
||||
-- 用户处理代码
|
||||
end
|
||||
--]]
|
||||
-- 该接口在各个子文件中,此处仅用作注释
|
||||
-- modbus:on(callback)
|
||||
|
||||
return exmodbus
|
||||
File diff suppressed because it is too large
Load Diff
1247
4G/tools/resource/soc_script/v2025.12.31.22/lib/exmodbus_tcp.lua
Normal file
1247
4G/tools/resource/soc_script/v2025.12.31.22/lib/exmodbus_tcp.lua
Normal file
File diff suppressed because it is too large
Load Diff
789
4G/tools/resource/soc_script/v2025.12.31.22/lib/exmtn.lua
Normal file
789
4G/tools/resource/soc_script/v2025.12.31.22/lib/exmtn.lua
Normal file
@@ -0,0 +1,789 @@
|
||||
--[[
|
||||
@module exmtn
|
||||
@summary 运维日志扩展库,负责日志的持久化存储
|
||||
@version 1.0
|
||||
@date 2025.12.9
|
||||
@author zengeshuai
|
||||
@usage
|
||||
exmtn.init(1, 0) -- 初始化,1个块,缓存写入
|
||||
exmtn.log("info", "tag", "message", 123) -- 输出运维日志
|
||||
]]
|
||||
|
||||
local exmtn = {}
|
||||
|
||||
-- 常量定义
|
||||
local LOG_MTN_CACHE_SIZE = 4096
|
||||
local LOG_MTN_FILE_COUNT = 4
|
||||
local LOG_MTN_CONFIG_FILE = "/exmtn.trc"
|
||||
local LOG_MTN_DEFAULT_BLOCKS_DIVISOR = 40
|
||||
local LOG_MTN_ADD_WRITE_THRESHOLD = 256
|
||||
local LOG_MTN_CONFIG_VERSION = 1
|
||||
|
||||
-- 写入方式常量
|
||||
exmtn.CACHE_WRITE = 0
|
||||
exmtn.ADD_WRITE = 1
|
||||
|
||||
-- 内部状态
|
||||
local ctx = {
|
||||
inited = false,
|
||||
enabled = false,
|
||||
cur_index = 1, -- 1-4
|
||||
block_size = 4096, -- 默认块大小
|
||||
blocks_per_file = 1, -- 每文件块数
|
||||
file_limit = 4096, -- 每文件大小限制
|
||||
write_way = 0, -- 0=缓存写入, 1=直接追加
|
||||
cache = "", -- 缓存缓冲区
|
||||
cache_used = 0, -- 缓存已使用字节数
|
||||
}
|
||||
|
||||
-- 重置缓存
|
||||
local function reset_cache()
|
||||
ctx.cache = ""
|
||||
ctx.cache_used = 0
|
||||
end
|
||||
|
||||
-- 获取当前文件路径
|
||||
local function get_file_path(index)
|
||||
return string.format("/hzmtn%d.trc", index or ctx.cur_index)
|
||||
end
|
||||
|
||||
-- 获取当前文件大小
|
||||
local function get_current_file_size()
|
||||
local path = get_file_path()
|
||||
local file = io.open(path, "rb")
|
||||
if not file then
|
||||
return 0
|
||||
end
|
||||
local size = file:seek("end")
|
||||
file:close()
|
||||
-- file:seek("end") 返回文件大小,如果失败返回 nil
|
||||
if size and size > 0 then
|
||||
return size
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
-- 检查文件是否存在
|
||||
local function file_exists(path)
|
||||
local file = io.open(path, "rb")
|
||||
if file then
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 检查所有日志文件是否存在
|
||||
local function files_exist()
|
||||
for i = 1, LOG_MTN_FILE_COUNT do
|
||||
local path = get_file_path(i)
|
||||
if file_exists(path) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 删除所有日志文件
|
||||
local function remove_files()
|
||||
for i = 1, LOG_MTN_FILE_COUNT do
|
||||
local path = get_file_path(i)
|
||||
os.remove(path)
|
||||
end
|
||||
end
|
||||
|
||||
-- 创建空文件
|
||||
local function create_files()
|
||||
for i = 1, LOG_MTN_FILE_COUNT do
|
||||
local path = get_file_path(i)
|
||||
local file = io.open(path, "wb")
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 读取配置文件
|
||||
local function load_config()
|
||||
local file = io.open(LOG_MTN_CONFIG_FILE, "rb")
|
||||
if not file then
|
||||
return nil -- 文件不存在,返回 nil
|
||||
end
|
||||
|
||||
local content = file:read("*a")
|
||||
file:close()
|
||||
|
||||
if not content or #content == 0 then
|
||||
return nil -- 文件为空
|
||||
end
|
||||
|
||||
-- 解析配置:格式为 "VERSION=1\nINDEX=2\nBLOCKS=10\nWRITE_WAY=0\n"
|
||||
local config = {}
|
||||
for line in content:gmatch("[^\r\n]+") do
|
||||
-- 移除首尾空白字符
|
||||
line = line:match("^%s*(.-)%s*$") or line
|
||||
local key, value = line:match("([^=]+)=(.+)")
|
||||
if key and value then
|
||||
-- 移除 key 和 value 的首尾空白字符
|
||||
key = key:match("^%s*(.-)%s*$") or key
|
||||
value = value:match("^%s*(.-)%s*$") or value
|
||||
local num_value = tonumber(value)
|
||||
if num_value then
|
||||
config[key] = num_value
|
||||
else
|
||||
config[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 验证版本号
|
||||
if config.VERSION ~= LOG_MTN_CONFIG_VERSION then
|
||||
return nil -- 版本不匹配
|
||||
end
|
||||
|
||||
return config
|
||||
end
|
||||
|
||||
-- 保存配置文件
|
||||
local function save_config(index, blocks, write_way)
|
||||
local content = string.format("VERSION=%d\nINDEX=%d\nBLOCKS=%d\nWRITE_WAY=%d\n",
|
||||
LOG_MTN_CONFIG_VERSION, index, blocks, write_way)
|
||||
|
||||
local file = io.open(LOG_MTN_CONFIG_FILE, "wb")
|
||||
if not file then
|
||||
log.warn("exmtn", "无法打开配置文件: " .. LOG_MTN_CONFIG_FILE)
|
||||
return false
|
||||
end
|
||||
|
||||
local ok = file:write(content)
|
||||
file:close()
|
||||
|
||||
if not ok then
|
||||
log.warn("exmtn", "写入配置文件失败: " .. LOG_MTN_CONFIG_FILE)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- 更新索引(同时保存完整配置)
|
||||
local function update_index(index)
|
||||
return save_config(index, ctx.blocks_per_file, ctx.write_way)
|
||||
end
|
||||
|
||||
-- 格式化时间戳
|
||||
-- 返回格式: [2025-11-05 15:06:49.947][00000027.994]
|
||||
local function format_timestamp()
|
||||
-- 获取系统运行时间(毫秒)
|
||||
local ticks_ms = 0
|
||||
if mcu and mcu.ticks then
|
||||
local ticks = mcu.ticks()
|
||||
if ticks then
|
||||
ticks_ms = ticks
|
||||
end
|
||||
end
|
||||
|
||||
-- 获取当前日期时间
|
||||
local date_time_str = ""
|
||||
local ms = 0
|
||||
|
||||
if os and os.date then
|
||||
-- 获取当前日期时间字符串: 2025-11-05 15:06:49
|
||||
local dt = os.date("%Y-%m-%d %H:%M:%S")
|
||||
if dt then
|
||||
-- 计算毫秒:使用系统运行时间的毫秒部分
|
||||
-- 如果 RTC 已设置,时间会更准确
|
||||
ms = ticks_ms % 1000
|
||||
date_time_str = string.format("%s.%03d", dt, ms)
|
||||
end
|
||||
end
|
||||
|
||||
-- 如果无法获取日期时间,使用默认格式
|
||||
if date_time_str == "" then
|
||||
date_time_str = "1970-01-01 00:00:00.000"
|
||||
end
|
||||
|
||||
-- 计算系统运行时间(秒.毫秒)
|
||||
local uptime_sec = math.floor(ticks_ms / 1000)
|
||||
local uptime_ms = ticks_ms % 1000
|
||||
|
||||
-- 格式化运行时间部分: 00000027.994(固定宽度,9位整数+3位小数)
|
||||
local uptime_str = string.format("%09d.%03d", uptime_sec, uptime_ms)
|
||||
|
||||
-- 返回完整时间戳
|
||||
return string.format("[%s][%s]", date_time_str, uptime_str)
|
||||
end
|
||||
|
||||
-- 格式化调试信息
|
||||
local function format_debug_info(level, include_level)
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
if not info or not info.source then
|
||||
return nil
|
||||
end
|
||||
|
||||
local src = info.source
|
||||
-- 跳过第一个字符(@ 或 =)
|
||||
if src:sub(1, 1) == "@" or src:sub(1, 1) == "=" then
|
||||
src = src:sub(2)
|
||||
end
|
||||
|
||||
local line = info.currentline or 0
|
||||
if line > 64 * 1024 then
|
||||
line = 0
|
||||
end
|
||||
|
||||
if include_level and level then
|
||||
return string.format("%s/%s:%d", level, src, line)
|
||||
else
|
||||
return string.format("%s:%d", src, line)
|
||||
end
|
||||
end
|
||||
|
||||
-- 格式化消息(与 log.info/warn/error 格式一致,但添加时间戳前缀)
|
||||
local function format_message(level, tag, ...)
|
||||
local argc = select("#", ...)
|
||||
|
||||
-- 获取 log.style 配置
|
||||
local log_style = 0
|
||||
if log and log.style then
|
||||
log_style = log.style() or 0
|
||||
end
|
||||
|
||||
-- 根据级别确定日志标识
|
||||
local level_char = "I" -- 默认 info
|
||||
if level == "warn" then
|
||||
level_char = "W"
|
||||
elseif level == "error" then
|
||||
level_char = "E"
|
||||
end
|
||||
|
||||
local msg = ""
|
||||
local dbg_info_with_level = format_debug_info(level_char, true)
|
||||
local dbg_info_only = format_debug_info(nil, false)
|
||||
|
||||
if log_style == 0 then
|
||||
-- LOG_STYLE_NORMAL: "I/user.tag arg1 arg2 ...\n"
|
||||
msg = string.format("%s/user.%s", level_char, tag)
|
||||
for i = 1, argc do
|
||||
local arg = select(i, ...)
|
||||
msg = msg .. " " .. tostring(arg)
|
||||
end
|
||||
elseif log_style == 1 then
|
||||
-- LOG_STYLE_DEBUG_INFO: "I/file.lua:123 tag arg1 arg2 ...\n"
|
||||
if dbg_info_with_level then
|
||||
msg = dbg_info_with_level
|
||||
else
|
||||
msg = level_char
|
||||
end
|
||||
msg = msg .. " " .. tag
|
||||
for i = 1, argc do
|
||||
local arg = select(i, ...)
|
||||
msg = msg .. " " .. tostring(arg)
|
||||
end
|
||||
else
|
||||
-- LOG_STYLE_FULL: "I/user.tag file.lua:123 arg1 arg2 ...\n"
|
||||
msg = string.format("%s/user.%s", level_char, tag)
|
||||
if dbg_info_only then
|
||||
msg = msg .. " " .. dbg_info_only
|
||||
end
|
||||
for i = 1, argc do
|
||||
local arg = select(i, ...)
|
||||
msg = msg .. " " .. tostring(arg)
|
||||
end
|
||||
end
|
||||
|
||||
msg = msg .. "\n"
|
||||
|
||||
-- 添加时间戳前缀
|
||||
local timestamp = format_timestamp()
|
||||
return timestamp .. " " .. msg
|
||||
end
|
||||
|
||||
-- 刷新缓存到文件
|
||||
local function flush_cache()
|
||||
if ctx.cache_used == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
local path = get_file_path()
|
||||
local file = io.open(path, "ab")
|
||||
if not file then
|
||||
log.warn("exmtn", "无法打开文件: " .. path)
|
||||
return false
|
||||
end
|
||||
|
||||
-- file:write 返回 true/false 或 nil,不返回字节数
|
||||
local ok = file:write(ctx.cache)
|
||||
file:close()
|
||||
|
||||
if not ok then
|
||||
log.warn("exmtn", "写入文件失败: " .. path)
|
||||
return false
|
||||
end
|
||||
|
||||
reset_cache()
|
||||
return true
|
||||
end
|
||||
|
||||
-- 直接写入文件(ADD_WRITE 模式)
|
||||
local function direct_write(data)
|
||||
local path = get_file_path()
|
||||
local file = io.open(path, "ab")
|
||||
if not file then
|
||||
log.warn("exmtn", "无法打开文件: " .. path)
|
||||
return false
|
||||
end
|
||||
|
||||
-- file:write 返回 true/false 或 nil,不返回字节数
|
||||
local ok = file:write(data)
|
||||
file:close()
|
||||
|
||||
if not ok then
|
||||
log.warn("exmtn", "写入文件失败: " .. path)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- 将数据追加到缓存或直接写入
|
||||
local function buffer_append(data)
|
||||
if not data or #data == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
local len = #data
|
||||
|
||||
-- ADD_WRITE 模式:直接写入文件
|
||||
if ctx.write_way == exmtn.ADD_WRITE then
|
||||
-- 小数据先缓存,累积到阈值再写入
|
||||
if len < LOG_MTN_ADD_WRITE_THRESHOLD then
|
||||
if ctx.cache_used + len > LOG_MTN_CACHE_SIZE then
|
||||
if not flush_cache() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
ctx.cache = ctx.cache .. data
|
||||
ctx.cache_used = ctx.cache_used + len
|
||||
-- 如果累积到阈值,立即写入
|
||||
if ctx.cache_used >= LOG_MTN_ADD_WRITE_THRESHOLD then
|
||||
return flush_cache()
|
||||
end
|
||||
return true
|
||||
end
|
||||
-- 大数据直接写入
|
||||
return direct_write(data)
|
||||
end
|
||||
|
||||
-- CACHE_WRITE 模式:原有逻辑
|
||||
if len > LOG_MTN_CACHE_SIZE then
|
||||
-- 先刷新缓存
|
||||
if not flush_cache() then
|
||||
return false
|
||||
end
|
||||
-- 大数据直接写入
|
||||
return direct_write(data)
|
||||
end
|
||||
|
||||
-- 检查缓存是否足够
|
||||
if ctx.cache_used + len > LOG_MTN_CACHE_SIZE then
|
||||
if not flush_cache() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
ctx.cache = ctx.cache .. data
|
||||
ctx.cache_used = ctx.cache_used + len
|
||||
return true
|
||||
end
|
||||
|
||||
-- 写入日志到文件
|
||||
local function write_to_file(msg)
|
||||
if not ctx.enabled then
|
||||
return true -- 未启用时返回成功,不写入
|
||||
end
|
||||
|
||||
local len = #msg
|
||||
|
||||
-- CACHE_WRITE 模式
|
||||
if ctx.write_way == exmtn.CACHE_WRITE then
|
||||
-- 检查文件大小 + 缓存大小 + 当前数据是否会超过限制
|
||||
-- 如果会超过,先刷新缓存
|
||||
if ctx.cache_used > 0 then
|
||||
local file_sz = get_current_file_size()
|
||||
if file_sz + ctx.cache_used + len > ctx.file_limit then
|
||||
-- 先刷新缓存
|
||||
if not flush_cache() then
|
||||
return false
|
||||
end
|
||||
-- 重新获取文件大小
|
||||
file_sz = get_current_file_size()
|
||||
-- 检查文件是否已满
|
||||
if file_sz >= ctx.file_limit then
|
||||
-- 文件已满,切换到下一个文件
|
||||
ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
||||
local path = get_file_path()
|
||||
local file = io.open(path, "wb")
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
if not update_index(ctx.cur_index) then
|
||||
log.warn("exmtn", "更新索引失败")
|
||||
return false
|
||||
end
|
||||
reset_cache()
|
||||
end
|
||||
end
|
||||
else
|
||||
-- 缓存为空,检查文件大小
|
||||
local file_sz = get_current_file_size()
|
||||
if file_sz + len > ctx.file_limit then
|
||||
-- 文件已满,切换到下一个文件
|
||||
ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
||||
local path = get_file_path()
|
||||
local file = io.open(path, "wb")
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
if not update_index(ctx.cur_index) then
|
||||
log.warn("exmtn", "更新索引失败")
|
||||
return false
|
||||
end
|
||||
reset_cache()
|
||||
end
|
||||
end
|
||||
|
||||
-- 如果加入这条数据后缓存会满,先刷新缓存
|
||||
if ctx.cache_used + len > LOG_MTN_CACHE_SIZE then
|
||||
if not flush_cache() then
|
||||
return false
|
||||
end
|
||||
|
||||
-- 刷新后再次检查文件大小
|
||||
local file_sz = get_current_file_size()
|
||||
if file_sz >= ctx.file_limit then
|
||||
-- 文件已满,切换到下一个文件
|
||||
ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
||||
local path = get_file_path()
|
||||
local file = io.open(path, "wb")
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
if not update_index(ctx.cur_index) then
|
||||
log.warn("exmtn", "更新索引失败")
|
||||
return false
|
||||
end
|
||||
reset_cache()
|
||||
end
|
||||
end
|
||||
|
||||
-- 加入缓存
|
||||
return buffer_append(msg)
|
||||
else
|
||||
-- ADD_WRITE 模式:先刷新缓存,确保文件大小准确
|
||||
if ctx.cache_used > 0 then
|
||||
if not flush_cache() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- 获取当前文件大小
|
||||
local file_sz = get_current_file_size()
|
||||
|
||||
-- 检查当前文件是否已写满
|
||||
if file_sz >= ctx.file_limit then
|
||||
-- 文件已满,切换到下一个文件
|
||||
ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
||||
local path = get_file_path()
|
||||
local file = io.open(path, "wb")
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
if not update_index(ctx.cur_index) then
|
||||
log.warn("exmtn", "更新索引失败")
|
||||
return false
|
||||
end
|
||||
reset_cache()
|
||||
end
|
||||
|
||||
-- 检查当前数据是否能放入当前文件
|
||||
if file_sz + len > ctx.file_limit then
|
||||
-- 当前数据放不下,切换到下一个文件
|
||||
ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
||||
local path = get_file_path()
|
||||
local file = io.open(path, "wb")
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
if not update_index(ctx.cur_index) then
|
||||
log.warn("exmtn", "更新索引失败")
|
||||
return false
|
||||
end
|
||||
reset_cache()
|
||||
end
|
||||
|
||||
-- 加入缓存或直接写入(buffer_append 会根据大小决定)
|
||||
return buffer_append(msg)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
初始化运维日志
|
||||
@api exmtn.init(blocks, write_way)
|
||||
@int blocks 每个文件的块数,0表示禁用,正整数表示块数量
|
||||
@int write_way 写入方式,可选参数。exmtn.CACHE_WRITE(0)表示缓存写入,exmtn.ADD_WRITE(1)表示直接追加写入,默认为exmtn.CACHE_WRITE
|
||||
@return boolean 成功返回true,失败返回false
|
||||
@usage
|
||||
exmtn.init(1, exmtn.CACHE_WRITE) -- 初始化,1个块,缓存写入
|
||||
]]
|
||||
function exmtn.init(blocks, write_way)
|
||||
-- 参数校验
|
||||
if blocks == nil then
|
||||
blocks = 0
|
||||
end
|
||||
blocks = math.floor(blocks)
|
||||
if blocks < 0 then
|
||||
log.warn("exmtn", "无效的块数")
|
||||
return false
|
||||
end
|
||||
|
||||
write_way = write_way or exmtn.CACHE_WRITE
|
||||
if write_way ~= exmtn.CACHE_WRITE and write_way ~= exmtn.ADD_WRITE then
|
||||
write_way = exmtn.CACHE_WRITE
|
||||
end
|
||||
|
||||
-- 如果禁用
|
||||
if blocks == 0 then
|
||||
reset_cache()
|
||||
remove_files()
|
||||
ctx.enabled = false
|
||||
ctx.cur_index = 1
|
||||
-- 删除配置文件
|
||||
os.remove(LOG_MTN_CONFIG_FILE)
|
||||
ctx.inited = true
|
||||
return true
|
||||
end
|
||||
|
||||
-- 读取文件系统信息
|
||||
if not ctx.inited then
|
||||
ctx.block_size = 4096
|
||||
ctx.blocks_per_file = 1
|
||||
|
||||
-- 尝试获取文件系统信息(需要 fs 模块支持)
|
||||
-- fs.fsstat 返回: success, total_blocks, used_blocks, block_size, fs_type
|
||||
if fs and fs.fsstat then
|
||||
local success, total_blocks, used_blocks, block_size, fs_type = fs.fsstat("/")
|
||||
if success and block_size and block_size > 0 then
|
||||
ctx.block_size = block_size
|
||||
if total_blocks and total_blocks > 0 then
|
||||
local def_blocks = math.floor(total_blocks / LOG_MTN_DEFAULT_BLOCKS_DIVISOR)
|
||||
if def_blocks > 0 then
|
||||
ctx.blocks_per_file = def_blocks
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 读取配置文件(仅在首次初始化时读取)
|
||||
if not ctx.inited then
|
||||
local config = load_config()
|
||||
if config then
|
||||
-- 读取索引
|
||||
if config.INDEX and config.INDEX >= 1 and config.INDEX <= LOG_MTN_FILE_COUNT then
|
||||
ctx.cur_index = config.INDEX
|
||||
end
|
||||
|
||||
-- 读取块数配置
|
||||
if config.BLOCKS and config.BLOCKS > 0 then
|
||||
ctx.blocks_per_file = config.BLOCKS
|
||||
end
|
||||
|
||||
-- 读取写入方式配置
|
||||
if config.WRITE_WAY == 0 or config.WRITE_WAY == 1 then
|
||||
ctx.write_way = config.WRITE_WAY
|
||||
end
|
||||
|
||||
log.info("exmtn", "读取索引", ctx.cur_index)
|
||||
log.info("exmtn", "读取块数配置", ctx.blocks_per_file)
|
||||
log.info("exmtn", "读取写入方式配置", ctx.write_way)
|
||||
end
|
||||
end
|
||||
|
||||
-- 检查配置是否变化
|
||||
-- 如果已初始化,比较当前配置和新配置;如果未初始化,不需要判断(首次初始化总是"变化"的)
|
||||
local config_changed = false
|
||||
if ctx.inited then
|
||||
-- 已初始化:比较当前配置和新传入的配置
|
||||
config_changed = (ctx.blocks_per_file ~= blocks) or (ctx.write_way ~= write_way)
|
||||
end
|
||||
-- 未初始化:config_changed 保持为 false,因为首次初始化不算"变化"
|
||||
|
||||
log.info("exmtn", "配置变化", config_changed)
|
||||
-- 更新配置
|
||||
ctx.blocks_per_file = blocks
|
||||
ctx.write_way = write_way
|
||||
ctx.file_limit = ctx.block_size * ctx.blocks_per_file
|
||||
if ctx.file_limit == 0 then
|
||||
ctx.file_limit = LOG_MTN_CACHE_SIZE
|
||||
end
|
||||
|
||||
-- 处理文件的三种情况
|
||||
if config_changed then
|
||||
-- 情况1:配置变化,清空文件
|
||||
log.info("exmtn", "配置变化,清空文件")
|
||||
reset_cache()
|
||||
remove_files()
|
||||
create_files()
|
||||
ctx.cur_index = 1
|
||||
elseif files_exist() then
|
||||
-- 情况2:配置没有变化,文件存在,根据配置文件中保存的文件指针继续写
|
||||
log.info("exmtn", "配置未变化,文件存在,继续写入")
|
||||
-- ctx.cur_index 已经从配置文件读取(如果是首次初始化)或保持当前值(如果已初始化),不需要重置
|
||||
else
|
||||
-- 情况3:配置没有变化,文件不存在,创建文件
|
||||
log.info("exmtn", "配置未变化,文件不存在,创建文件")
|
||||
create_files()
|
||||
-- ctx.cur_index 已经从配置文件读取(如果是首次初始化)或保持当前值(如果已初始化),不需要重置
|
||||
end
|
||||
|
||||
-- 保存配置到文件
|
||||
if not save_config(ctx.cur_index, blocks, write_way) then
|
||||
log.warn("exmtn", "保存配置失败")
|
||||
return false
|
||||
end
|
||||
|
||||
ctx.enabled = true
|
||||
ctx.inited = true
|
||||
|
||||
-- 打印初始化信息
|
||||
if blocks > 0 then
|
||||
local total_size = ctx.file_limit * LOG_MTN_FILE_COUNT
|
||||
local file_size_mb = ctx.file_limit / (1024 * 1024)
|
||||
local total_size_mb = total_size / (1024 * 1024)
|
||||
local file_size_kb = ctx.file_limit / 1024
|
||||
local total_size_kb = total_size / 1024
|
||||
|
||||
if ctx.file_limit >= 1024 * 1024 then
|
||||
log.info("exmtn", string.format("初始化成功: 每个文件 %.2f MB (%d 块 × %d 字节), 总空间 %.2f MB (%d 个文件)",
|
||||
file_size_mb, ctx.blocks_per_file, ctx.block_size, total_size_mb, LOG_MTN_FILE_COUNT))
|
||||
elseif ctx.file_limit >= 1024 then
|
||||
log.info("exmtn", string.format("初始化成功: 每个文件 %.2f KB (%d 块 × %d 字节), 总空间 %.2f KB (%d 个文件)",
|
||||
file_size_kb, ctx.blocks_per_file, ctx.block_size, total_size_kb, LOG_MTN_FILE_COUNT))
|
||||
else
|
||||
log.info("exmtn", string.format("初始化成功: 每个文件 %d 字节 (%d 块 × %d 字节), 总空间 %d 字节 (%d 个文件)",
|
||||
ctx.file_limit, ctx.blocks_per_file, ctx.block_size, total_size, LOG_MTN_FILE_COUNT))
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
输出运维日志并写入文件
|
||||
@api exmtn.log(level, tag, ...)
|
||||
@string level 日志级别,必须是 "info", "warn", 或 "error"
|
||||
@string tag 日志标识,必须是字符串
|
||||
@... 需打印的参数
|
||||
@return boolean 成功返回true,失败返回false
|
||||
@usage
|
||||
exmtn.log("info", "message", 123)
|
||||
exmtn.log("warn", "message", 456)
|
||||
exmtn.log("error", "message", 789)
|
||||
]]
|
||||
function exmtn.log(level, tag, ...)
|
||||
if not level or type(level) ~= "string" then
|
||||
log.warn("exmtn", "level 必须是字符串")
|
||||
return false
|
||||
end
|
||||
|
||||
if not tag or type(tag) ~= "string" then
|
||||
log.warn("exmtn", "tag 必须是字符串")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 根据级别调用对应的底层函数(会被日志级别过滤)
|
||||
if level == "info" then
|
||||
log.info(tag, ...)
|
||||
elseif level == "warn" then
|
||||
log.warn(tag, ...)
|
||||
elseif level == "error" then
|
||||
log.error(tag, ...)
|
||||
else
|
||||
log.warn("exmtn", "level 必须是 'info', 'warn' 或 'error'")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 格式化消息(用于文件写入)
|
||||
local msg = format_message(level, tag, ...)
|
||||
if not msg then
|
||||
log.warn("exmtn", "格式化消息失败")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 写入文件(不受日志级别影响)
|
||||
return write_to_file(msg)
|
||||
end
|
||||
|
||||
--[[
|
||||
获取当前配置
|
||||
@api exmtn.get_config()
|
||||
@return table|nil 配置信息,失败返回nil
|
||||
@usage
|
||||
local config = exmtn.get_config()
|
||||
if config then
|
||||
log.info("exmtn", "blocks:", config.blocks, "write_way:", config.write_way)
|
||||
end
|
||||
]]
|
||||
function exmtn.get_config()
|
||||
if not ctx.inited then
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
enabled = ctx.enabled,
|
||||
cur_index = ctx.cur_index,
|
||||
block_size = ctx.block_size,
|
||||
blocks_per_file = ctx.blocks_per_file,
|
||||
file_limit = ctx.file_limit,
|
||||
write_way = ctx.write_way,
|
||||
}
|
||||
end
|
||||
|
||||
--[[
|
||||
清除所有运维日志文件
|
||||
@api exmtn.clear()
|
||||
@return boolean 成功返回true,失败返回false
|
||||
@usage
|
||||
local ok = exmtn.clear()
|
||||
if ok then
|
||||
log.info("exmtn", "日志文件已清除")
|
||||
end
|
||||
]]
|
||||
function exmtn.clear()
|
||||
-- 如果已初始化,先刷新缓存(确保数据不丢失)
|
||||
if ctx.inited and ctx.cache_used > 0 then
|
||||
if not flush_cache() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- 删除所有日志文件
|
||||
remove_files()
|
||||
|
||||
-- 重新创建空文件
|
||||
create_files()
|
||||
|
||||
-- 重置索引为1
|
||||
ctx.cur_index = 1
|
||||
|
||||
-- 更新配置文件
|
||||
if not save_config(1, ctx.blocks_per_file, ctx.write_way) then
|
||||
return false
|
||||
end
|
||||
|
||||
log.info("exmtn", "运维日志文件已清除")
|
||||
return true
|
||||
end
|
||||
|
||||
return exmtn
|
||||
|
||||
1135
4G/tools/resource/soc_script/v2025.12.31.22/lib/exnetif.lua
Normal file
1135
4G/tools/resource/soc_script/v2025.12.31.22/lib/exnetif.lua
Normal file
File diff suppressed because it is too large
Load Diff
591
4G/tools/resource/soc_script/v2025.12.31.22/lib/explorer.html
Normal file
591
4G/tools/resource/soc_script/v2025.12.31.22/lib/explorer.html
Normal file
@@ -0,0 +1,591 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LuatOS 文件管理系统</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.header {
|
||||
background: #2c3e50;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.login-form {
|
||||
max-width: 400px;
|
||||
margin: 100px auto;
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
input[type="text"], input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
.file-list {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.file-list th, .file-list td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.file-list th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: bold;
|
||||
}
|
||||
.file-list tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.download-btn {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.download-btn:hover {
|
||||
background-color: #219a52;
|
||||
}
|
||||
.delete-btn {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.delete-btn:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
.breadcrumb {
|
||||
padding: 10px 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.breadcrumb a {
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.breadcrumb a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.error {
|
||||
color: #e74c3c;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 登录页面 -->
|
||||
<div id="loginPage" class="login-form">
|
||||
<h2>LuatOS 文件管理系统登录</h2>
|
||||
<div class="form-group">
|
||||
<label for="username">用户名:</label>
|
||||
<input type="text" id="username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码:</label>
|
||||
<input type="password" id="password">
|
||||
</div>
|
||||
<button onclick="login()">登录</button>
|
||||
<div id="loginError" class="error hidden"></div>
|
||||
</div>
|
||||
|
||||
<!-- 文件管理页面 -->
|
||||
<div id="filePage" class="hidden">
|
||||
<div class="header">
|
||||
<h1>LuatOS 文件管理系统</h1>
|
||||
<div>
|
||||
<label for="uploadTarget" style="margin-right: 10px; color: white;">上传目标:</label>
|
||||
<select id="uploadTarget" style="margin-right: 10px; padding: 5px;">
|
||||
<option value="/luadb">内存</option>
|
||||
<option value="/sd">SD卡</option>
|
||||
</select>
|
||||
<button onclick="scanFiles()" style="margin-right: 10px;">扫描文件</button>
|
||||
<button onclick="logout()">退出登录</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumb" id="breadcrumb">
|
||||
<a onclick="navigateTo('/')">根目录</a>
|
||||
<span> | </span>
|
||||
<a onclick="navigateTo('/sd')">TF/SD目录</a>
|
||||
</div>
|
||||
|
||||
<!-- 文件上传区域 -->
|
||||
<div style="margin-bottom: 20px; padding: 15px; background-color: #f9f9f9; border-radius: 5px;">
|
||||
<div class="form-group">
|
||||
<label for="fileUpload">选择文件 (最大200KB):</label>
|
||||
<input type="file" id="fileUpload" accept="*/*">
|
||||
</div>
|
||||
<button onclick="uploadFile()" id="uploadBtn">上传文件</button>
|
||||
<div id="uploadError" class="error hidden"></div>
|
||||
<div id="uploadProgress" style="display: none; margin-top: 10px;">
|
||||
<div style="width: 100%; background-color: #ddd; border-radius: 4px;">
|
||||
<div id="progressBar" style="width: 0%; height: 20px; background-color: #4CAF50; border-radius: 4px; transition: width 0.3s;"></div>
|
||||
</div>
|
||||
<div id="progressText" style="text-align: center; margin-top: 5px;">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="file-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>大小</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="fileListBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPath = '/';
|
||||
let isLoggedIn = false;
|
||||
|
||||
function login() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include', // 确保发送和接收cookies
|
||||
body: JSON.stringify({username: username, password: password})
|
||||
})
|
||||
.then(response => {
|
||||
// 打印所有响应头,查看Cookie设置
|
||||
console.log('登录响应头:', response.headers);
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('登录响应数据:', data);
|
||||
if (data.success) {
|
||||
isLoggedIn = true;
|
||||
// 存储session_id到localStorage作为备用认证方式
|
||||
if (data.session_id) {
|
||||
localStorage.setItem('session_id', data.session_id);
|
||||
console.log('已存储SessionID到localStorage:', data.session_id);
|
||||
}
|
||||
document.getElementById('loginPage').classList.add('hidden');
|
||||
document.getElementById('filePage').classList.remove('hidden');
|
||||
loadFiles('/luadb');
|
||||
} else {
|
||||
document.getElementById('loginError').textContent = data.message || '登录失败';
|
||||
document.getElementById('loginError').classList.remove('hidden');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('登录请求错误:', error);
|
||||
document.getElementById('loginError').textContent = '登录请求失败';
|
||||
document.getElementById('loginError').classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
function logout() {
|
||||
fetch('/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include' // 确保发送cookies
|
||||
})
|
||||
.then(() => {
|
||||
isLoggedIn = false;
|
||||
// 清除localStorage中的session_id
|
||||
localStorage.removeItem('session_id');
|
||||
document.getElementById('filePage').classList.add('hidden');
|
||||
document.getElementById('loginPage').classList.remove('hidden');
|
||||
document.getElementById('username').value = '';
|
||||
document.getElementById('password').value = '';
|
||||
document.getElementById('loginError').classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// 扫描文件函数
|
||||
function scanFiles() {
|
||||
if (!isLoggedIn) return;
|
||||
|
||||
// 获取用户名和密码用于URL参数认证
|
||||
const username = document.getElementById('username')?.value || 'admin';
|
||||
const password = document.getElementById('password')?.value || '123456';
|
||||
|
||||
// 构建带认证参数的扫描请求URL
|
||||
const url = '/scan-files?username=' + encodeURIComponent(username) +
|
||||
'&password=' + encodeURIComponent(password);
|
||||
|
||||
console.log('发送文件扫描请求,URL:', url);
|
||||
|
||||
// 显示扫描提示
|
||||
alert('开始扫描文件,请查看系统日志了解扫描进度...');
|
||||
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('扫描请求错误: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('扫描响应:', data);
|
||||
if (data && data.success) {
|
||||
alert('文件扫描完成!已扫描到 ' + data.foundFiles + ' 个文件,显示扫描到的用户文件。');
|
||||
// 重新加载文件列表
|
||||
loadFiles(currentPath);
|
||||
} else {
|
||||
alert('文件扫描失败: ' + (data.message || '未知错误'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('扫描文件请求错误:', error);
|
||||
alert('扫描文件请求失败');
|
||||
});
|
||||
}
|
||||
|
||||
function loadFiles(path) {
|
||||
if (!isLoggedIn) return;
|
||||
|
||||
// 准备请求头
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// 由于传统认证方式不可靠,我们使用URL参数认证
|
||||
// 获取用户名和密码用于URL参数认证
|
||||
const username = document.getElementById('username')?.value || 'admin';
|
||||
const password = document.getElementById('password')?.value || '123456';
|
||||
|
||||
// 构建带认证参数的URL
|
||||
const url = '/list?path=' + encodeURIComponent(path) +
|
||||
'&username=' + encodeURIComponent(username) +
|
||||
'&password=' + encodeURIComponent(password);
|
||||
|
||||
console.log('使用URL参数认证,请求URL:', url);
|
||||
|
||||
fetch(url, {
|
||||
credentials: 'include', // 确保发送cookies
|
||||
headers: headers
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('网络响应错误: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('文件列表数据:', data);
|
||||
|
||||
// 只使用服务器返回的数据
|
||||
if (data && data.success && Array.isArray(data.files)) {
|
||||
displayFiles(data.files, path);
|
||||
} else {
|
||||
// 如果数据无效,显示空列表
|
||||
displayFiles([], path);
|
||||
}
|
||||
|
||||
updateBreadcrumb(path);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('加载文件列表错误:', error);
|
||||
// 发生错误时显示空列表
|
||||
displayFiles([], path);
|
||||
updateBreadcrumb(path);
|
||||
});
|
||||
}
|
||||
|
||||
function displayFiles(files, path) {
|
||||
const tbody = document.getElementById('fileListBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
// 确保files是数组
|
||||
if (!Array.isArray(files)) {
|
||||
files = [];
|
||||
}
|
||||
|
||||
console.log('显示文件数量:', files.length);
|
||||
|
||||
files.forEach(file => {
|
||||
// 确保文件对象有必要的属性
|
||||
const safeFile = {
|
||||
name: file.name || "未知文件名",
|
||||
size: file.size || 0,
|
||||
isDirectory: file.isDirectory || false,
|
||||
path: file.path || (path + '/' + (file.name || "未知文件名"))
|
||||
};
|
||||
|
||||
const row = document.createElement('tr');
|
||||
let nameCell, actionCell;
|
||||
|
||||
if (safeFile.isDirectory) {
|
||||
nameCell = `<td><a href="#" onclick="navigateTo('${encodeURIComponent(path + '/' + safeFile.name)}')">${safeFile.name}/</a></td>`;
|
||||
actionCell = '<td></td>';
|
||||
} else {
|
||||
nameCell = `<td>${safeFile.name}</td>`;
|
||||
// 为下载链接添加URL参数认证
|
||||
const username = document.getElementById('username')?.value || 'admin';
|
||||
const password = document.getElementById('password')?.value || '123456';
|
||||
const downloadUrl = '/download?path=' + encodeURIComponent(safeFile.path) +
|
||||
'&username=' + encodeURIComponent(username) +
|
||||
'&password=' + encodeURIComponent(password);
|
||||
|
||||
// 添加下载和删除按钮
|
||||
actionCell = `<td>
|
||||
<a href="${downloadUrl}" class="download-btn" style="margin-right: 5px;">下载</a>
|
||||
<button class="delete-btn" onclick="deleteFile('${encodeURIComponent(safeFile.path)}')">删除</button>
|
||||
</td>`;
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
${nameCell}
|
||||
<td>${formatSize(safeFile.size)}</td>
|
||||
${actionCell}
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// 删除文件函数
|
||||
function deleteFile(filePath) {
|
||||
if (confirm('确定要删除这个文件吗?')) {
|
||||
// 获取用户名和密码用于URL参数认证
|
||||
const username = document.getElementById('username')?.value || 'admin';
|
||||
const password = document.getElementById('password')?.value || '123456';
|
||||
|
||||
// 构建带认证参数的删除请求URL
|
||||
const url = '/delete?path=' + filePath +
|
||||
'&username=' + encodeURIComponent(username) +
|
||||
'&password=' + encodeURIComponent(password);
|
||||
|
||||
console.log('使用URL参数认证进行删除操作,请求URL:', url);
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// 删除成功后重新加载文件列表
|
||||
loadFiles(currentPath);
|
||||
} else {
|
||||
alert('删除失败: ' + (data.message || '未知错误'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('删除请求失败');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateBreadcrumb(path) {
|
||||
const breadcrumb = document.getElementById('breadcrumb');
|
||||
|
||||
// 先设置根目录和TF/SD目录链接
|
||||
breadcrumb.innerHTML = '<a onclick="navigateTo(\'\')">根目录</a><span> | </span><a onclick="navigateTo(\'/sd\')">TF/SD目录</a>';
|
||||
|
||||
// 然后添加当前路径的层次结构(如果不是根目录)
|
||||
if (path !== '/' && path !== '/sd') {
|
||||
const parts = path.split('/').filter(p => p);
|
||||
let current = '';
|
||||
|
||||
// 仅在非根目录和非SD目录时添加分隔符
|
||||
breadcrumb.innerHTML += ' > ';
|
||||
|
||||
parts.forEach((part, index) => {
|
||||
current += '/' + part;
|
||||
if (index > 0) {
|
||||
breadcrumb.innerHTML += ' > ';
|
||||
}
|
||||
breadcrumb.innerHTML += '<a onclick="navigateTo(\'' + current + '\')">' + part + '</a>';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(path) {
|
||||
currentPath = path;
|
||||
loadFiles(path);
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// 文件上传函数
|
||||
function uploadFile() {
|
||||
const fileInput = document.getElementById('fileUpload');
|
||||
const uploadTarget = document.getElementById('uploadTarget').value;
|
||||
const uploadBtn = document.getElementById('uploadBtn');
|
||||
const uploadError = document.getElementById('uploadError');
|
||||
const uploadProgress = document.getElementById('uploadProgress');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressText = document.getElementById('progressText');
|
||||
|
||||
// 重置错误信息
|
||||
uploadError.textContent = '';
|
||||
uploadError.classList.add('hidden');
|
||||
|
||||
// 检查是否选择了文件
|
||||
if (!fileInput.files || fileInput.files.length === 0) {
|
||||
uploadError.textContent = '请先选择要上传的文件';
|
||||
uploadError.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const file = fileInput.files[0];
|
||||
|
||||
// 检查文件大小(200KB限制)
|
||||
if (file.size > 200 * 1024) {
|
||||
uploadError.textContent = '文件大小超过200KB限制';
|
||||
uploadError.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户名和密码用于URL参数认证
|
||||
const username = document.getElementById('username')?.value || 'admin';
|
||||
const password = document.getElementById('password')?.value || '123456';
|
||||
|
||||
// 构建上传URL
|
||||
const url = '/upload?path=' + encodeURIComponent(uploadTarget) +
|
||||
'&filename=' + encodeURIComponent(file.name) +
|
||||
'&username=' + encodeURIComponent(username) +
|
||||
'&password=' + encodeURIComponent(password);
|
||||
|
||||
// 显示进度条
|
||||
uploadProgress.style.display = 'block';
|
||||
progressBar.style.width = '0%';
|
||||
progressText.textContent = '0%';
|
||||
uploadBtn.disabled = true;
|
||||
|
||||
// 创建FormData对象
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
// 使用XMLHttpRequest以便监控上传进度
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
// 监听上传进度
|
||||
xhr.upload.addEventListener('progress', function(event) {
|
||||
if (event.lengthComputable) {
|
||||
const percentComplete = Math.round((event.loaded / event.total) * 100);
|
||||
progressBar.style.width = percentComplete + '%';
|
||||
progressText.textContent = percentComplete + '%';
|
||||
}
|
||||
});
|
||||
|
||||
// 监听上传完成
|
||||
xhr.addEventListener('load', function() {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.success) {
|
||||
alert('文件上传成功!');
|
||||
// 重新加载当前路径的文件列表
|
||||
loadFiles(uploadTarget);
|
||||
// 重置文件选择
|
||||
fileInput.value = '';
|
||||
} else {
|
||||
uploadError.textContent = '上传失败: ' + (response.message || '未知错误');
|
||||
uploadError.classList.remove('hidden');
|
||||
}
|
||||
} catch (e) {
|
||||
uploadError.textContent = '上传成功但解析响应失败';
|
||||
uploadError.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
uploadError.textContent = '上传失败: 服务器响应错误';
|
||||
uploadError.classList.remove('hidden');
|
||||
}
|
||||
uploadBtn.disabled = false;
|
||||
});
|
||||
|
||||
// 监听上传错误
|
||||
xhr.addEventListener('error', function() {
|
||||
uploadError.textContent = '上传失败: 网络错误';
|
||||
uploadError.classList.remove('hidden');
|
||||
uploadBtn.disabled = false;
|
||||
});
|
||||
|
||||
// 发送请求
|
||||
xhr.open('POST', url);
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
// 启动后检查认证状态
|
||||
window.onload = function() {
|
||||
fetch('/check-auth', {
|
||||
credentials: 'include' // 确保发送cookies
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.authenticated) {
|
||||
isLoggedIn = true;
|
||||
document.getElementById('loginPage').classList.add('hidden');
|
||||
document.getElementById('filePage').classList.remove('hidden');
|
||||
loadFiles('/luadb');
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
473
4G/tools/resource/soc_script/v2025.12.31.22/lib/exremotecam.lua
Normal file
473
4G/tools/resource/soc_script/v2025.12.31.22/lib/exremotecam.lua
Normal file
@@ -0,0 +1,473 @@
|
||||
--[[
|
||||
@module exremotecam
|
||||
@summary exremotecam 远程摄像头OSD控制扩展库,提供摄像头OSD文字显示设置和拍照功能。
|
||||
@version 1.0
|
||||
@date 2025.12.29
|
||||
@author 拓毅恒
|
||||
@usage
|
||||
注:在使用exremotecam 扩展库时,需要确保网络连接正常,能够访问到目标摄像头。
|
||||
|
||||
本文件的对外接口有2个:
|
||||
1、exremotecam.OSDsetup(Brand, Host, channel, text, X, Y):设置摄像头OSD文字显示
|
||||
-- 参数说明:
|
||||
-- Brand: 摄像头品牌,当前仅支持"Dhua"(大华)
|
||||
-- Host: 摄像头/NVR的IP地址
|
||||
-- channel: 摄像头通道号
|
||||
-- text: OSD文本内容,需用竖线分隔,格式如"1111|2222|3333|4444"
|
||||
-- X: 显示位置的X坐标
|
||||
-- Y: 显示位置的Y坐标
|
||||
|
||||
2、exremotecam.getphoto(Brand, Host, channel):控制摄像头拍照
|
||||
-- 参数说明:
|
||||
-- Brand: 摄像头品牌,当前仅支持"Dhua"(大华)
|
||||
-- Host: 摄像头/NVR的IP地址
|
||||
-- channel: 摄像头通道号
|
||||
-- 返回:若SD卡可用,则图片保存为/sd/1.jpeg
|
||||
]]
|
||||
|
||||
--------------------------------各品牌摄像头HTTP参数配置--------------------------------
|
||||
-- 大华参数
|
||||
local DH_TextAlign = 0 -- 文本对齐方式,0左对齐,3右对齐 默认左对齐
|
||||
local DH_channel = 0 -- 通道号
|
||||
-- 大华OSD默认配置参数
|
||||
local dh_osd_param = {
|
||||
Host = "192.168.1.108",
|
||||
url = "/cgi-bin/configManager.cgi?",
|
||||
GetWidgest = "action=getConfig&name=VideoWidget",
|
||||
SetWidgest = "action=setConfig&VideoWidget[0].FontColorType=Adapt&VideoWidget[0].CustomTitle[1].PreviewBlend=true&VideoWidget[0].CustomTitle[1].EncodeBlend=true&VideoWidget[0].CustomTitle[1].TextAlign="..DH_TextAlign.."&VideoWidget[0].CustomTitle[1].Text=",
|
||||
Text = "NULL",
|
||||
Postion = "&VideoWidget[0].CustomTitle[1].Rect[0]=83&VideoWidget[0].CustomTitle[1].Rect[1]=169&VideoWidget[0].CustomTitle[1].Rect[2]=2666&VideoWidget[0].CustomTitle[1].Rect[3]=607"
|
||||
}
|
||||
-- 大华抓图默认配置参数
|
||||
local DAHUA_MD5Param = {
|
||||
username = "admin",
|
||||
password = "Air123456",
|
||||
realm = "Login to 7720fd71f7dd8d36eaabc67104aa4f38",--值要获取
|
||||
nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093", -- 示例nonce值
|
||||
method = "GET:", -- HTTP方法
|
||||
qop = "auth",
|
||||
nc = "00000001",
|
||||
cnonce = "KeA8e2Cy",
|
||||
response = "NULL",
|
||||
url = "/cgi-bin/snapshot.cgi?",
|
||||
timerul = "/cgi-bin/global.cgi?"
|
||||
}
|
||||
--------------------------------各品牌摄像头HTTP参数配置完毕--------------------------------
|
||||
|
||||
--[[
|
||||
按竖线(|)分割字符串,支持多种返回格式
|
||||
@api split_string_by_pipe(input_str,return_type)
|
||||
@string input_str 要分割的字符串,格式如"1111|2222|3333"
|
||||
@string/number return_type 返回类型,可选值:
|
||||
"all" - 返回完整拆分数组(默认值)
|
||||
"count" - 返回元素数量
|
||||
整数 - 返回指定索引的元素(索引从1开始)
|
||||
@return 根据return_type参数不同,返回不同结果:
|
||||
- "all": table - 包含所有分割元素的数组
|
||||
- "count": number - 分割后的元素数量
|
||||
- 整数索引: string - 指定索引的元素,索引越界时返回错误信息
|
||||
- 无效参数: string - 错误提示信息
|
||||
@usage:
|
||||
-- 示例1: 完整数组返回
|
||||
-- 输入: "OSD行1|OSD行2|OSD行3"
|
||||
-- 代码: local result = split_string_by_pipe("OSD行1|OSD行2|OSD行3")
|
||||
-- 输出: {"OSD行1", "OSD行2", "OSD行3"}
|
||||
|
||||
-- 示例2: 返回元素数量
|
||||
-- 输入: "OSD行1|OSD行2|OSD行3"
|
||||
-- 代码: local count = split_string_by_pipe("OSD行1|OSD行2|OSD行3", "count")
|
||||
-- 输出: 3
|
||||
|
||||
-- 示例3: 返回指定索引元素
|
||||
-- 输入: "OSD行1|OSD行2|OSD行3"
|
||||
-- 代码: local second_item = split_string_by_pipe("OSD行1|OSD行2|OSD行3", 2)
|
||||
-- 输出: "OSD行2"
|
||||
|
||||
-- 示例4: 在OSDsetup中的实际应用
|
||||
-- 代码: OSDsetup("Dhua", "192.168.1.108", 0, "温度|湿度|天气|风向", 0, 2000)
|
||||
-- 内部处理: split_string_by_pipe("温度|湿度|天气|风向") 得到 {"温度", "湿度", "天气", "风向"}
|
||||
-- 最终效果: 在大华摄像头OSD上显示这四行文字
|
||||
]]
|
||||
local function split_string_by_pipe(input_str, return_type)
|
||||
-- 处理默认参数(如果未指定 return_type,默认返回完整数组)
|
||||
return_type = return_type or "all"
|
||||
-- 存储拆分后的结果
|
||||
local split_result = {}
|
||||
|
||||
-- 核心拆分逻辑:遍历字符串,按 | 分割
|
||||
for item in string.gmatch(input_str, "[^|]+") do
|
||||
table.insert(split_result, item) -- 将匹配到的元素加入数组
|
||||
end
|
||||
|
||||
-- 根据 return_type 处理返回结果
|
||||
if return_type == "all" then
|
||||
-- 返回完整拆分数组
|
||||
return split_result
|
||||
elseif return_type == "count" then
|
||||
-- 返回元素数量(#split_result 是 Lua 获取数组长度的方式)
|
||||
return #split_result
|
||||
elseif type(return_type) == "number" then
|
||||
-- 返回指定索引的元素(Lua 数组索引从 1 开始)
|
||||
if return_type >= 1 and return_type <= #split_result then
|
||||
return split_result[return_type]
|
||||
else
|
||||
-- 处理索引越界
|
||||
return string.format("索引 %d 越界,当前只有 %d 个元素(索引 1 到 %d)",
|
||||
return_type, #split_result, #split_result)
|
||||
end
|
||||
else
|
||||
-- 处理无效的 return_type 参数
|
||||
return "return_type 无效!可选值:'all'、'count' 或整数索引"
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
解析并验证OSD显示元素,确保不超出最大显示行数
|
||||
@api ElementJudg(Data, number)
|
||||
@string Data 竖线分隔的OSD文本内容,格式如"1111|2222|3333"
|
||||
@number number 最大允许显示的行数
|
||||
@return table 分割后的所有OSD元素数组
|
||||
@usage
|
||||
local osd_elements = ElementJudg("行1|行2|行3|行4", 3)
|
||||
-- 输出: "超出显示的范围,只能显示3行"
|
||||
-- 返回: {"行1", "行2", "行3", "行4"}
|
||||
|
||||
注意事项:
|
||||
1. 函数会打印所有解析到的元素及其索引
|
||||
2. 当元素数量超过最大行数时,会记录警告日志
|
||||
3. 无论是否超出限制,都会返回完整的元素数组
|
||||
]]
|
||||
local function ElementJudg(Data,number)
|
||||
-- 使用split_string_by_pipe函数按竖线分割OSD数据
|
||||
local all_items = split_string_by_pipe(Data)
|
||||
|
||||
-- 遍历并打印所有解析到的OSD元素及其索引
|
||||
for i, item in ipairs(all_items) do
|
||||
log.info("元素解析", "索引", i, "值", item)
|
||||
end
|
||||
-- 获取OSD元素的总数
|
||||
local NUM = split_string_by_pipe(Data,"count")
|
||||
|
||||
-- 检查元素数量是否超过最大允许行数
|
||||
if NUM > number then
|
||||
-- 记录警告日志,提示超出显示范围
|
||||
log.info("超出显示的范围,只能显示"..number.."行")
|
||||
end
|
||||
|
||||
-- 返回完整的OSD元素数组(无论是否超出限制)
|
||||
return all_items
|
||||
end
|
||||
|
||||
--[[
|
||||
URL编码函数,用于将字符串转换为符合URL标准的编码格式
|
||||
@api urlencode(str)
|
||||
@string str 需要进行URL编码的字符串
|
||||
@return string 编码后的URL安全字符串,如果输入为nil则返回空字符串
|
||||
@usage:
|
||||
local encoded = urlencode("Hello World!")
|
||||
-- 输出: "Hello+World%21"
|
||||
]]
|
||||
local function urlencode(str)
|
||||
-- 检查输入参数是否存在
|
||||
if (str) then
|
||||
-- 将换行符转换为CRLF格式,符合HTTP标准
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
-- 对非字母数字和空格的字符进行%XX编码
|
||||
str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
|
||||
-- 将空格转换为+号,符合URL编码规范
|
||||
str = string.gsub(str, " ", "+")
|
||||
end
|
||||
-- 返回编码后的字符串或空字符串(如果输入为nil)
|
||||
return str or ""
|
||||
end
|
||||
|
||||
--[[
|
||||
计算Digest认证中的HA1值,用于网络摄像头的身份验证
|
||||
@api CameraHA1(username,realm,password)
|
||||
@string username 用户名
|
||||
@string realm 认证域,由服务器在401响应中提供
|
||||
@string password 用户密码
|
||||
@return string 计算得到的HA1值(小写的MD5哈希值)
|
||||
@usage:
|
||||
local ha1 = CameraHA1("admin", "realm", "123456")
|
||||
-- 输出: md5("admin:realm:123456")的小写哈希值
|
||||
]]
|
||||
local function CameraHA1(username,realm,password)
|
||||
-- 计算HA1值:MD5(用户名:认证域:密码),并转换为小写
|
||||
-- Digest认证标准要求使用小写的哈希值
|
||||
local ha1 = string.lower(crypto.md5(username..":"..realm..":"..password))
|
||||
-- 返回计算得到的HA1值
|
||||
return ha1
|
||||
end
|
||||
|
||||
--[[
|
||||
处理Digest认证,仅在收到401响应时调用
|
||||
@api handle_digest_auth(Host,url,params,headers,HA2)
|
||||
@string Host 摄像头的IP地址
|
||||
@string url 请求的URL路径
|
||||
@string params 请求参数
|
||||
@table headers 第一次HTTP请求返回的头部信息
|
||||
@string HA2 预先计算好的HA2值
|
||||
@return boolean, table 认证是否成功, 更新后的请求头部
|
||||
@usage:
|
||||
local code, headers, body = http.request("GET", "http://192.168.1.100/cgi-bin/test", initial_headers).wait()
|
||||
if code == 401 then
|
||||
local success, updated_headers = handle_digest_auth("192.168.1.100", "/cgi-bin/test", "param=value", headers, "ha2_value")
|
||||
if success then
|
||||
-- 使用更新后的头部发送第二次请求
|
||||
end
|
||||
end
|
||||
]]
|
||||
local function handle_digest_auth(Host, url, params, headers, HA2)
|
||||
-- 将headers转换为JSON格式以便解析
|
||||
local str = json.encode(headers)
|
||||
local Authenticate = json.decode(str)
|
||||
-- 获取WWW-Authenticate头信息
|
||||
local www = Authenticate["WWW-Authenticate"]
|
||||
|
||||
if not www then
|
||||
log.info("DigestAuth", "没有找到WWW-Authenticate头信息")
|
||||
return false, nil
|
||||
end
|
||||
|
||||
log.info("DigestAuth", "获取的鉴权信息:", www)
|
||||
|
||||
-- 从鉴权信息中提取所需参数
|
||||
DAHUA_MD5Param.realm = string.match(www,"realm=\"(.-)\"") -- 提取认证域
|
||||
DAHUA_MD5Param.nonce = string.match(www,"nonce=\"(.-)\"") -- 提取随机数
|
||||
|
||||
if not DAHUA_MD5Param.realm or not DAHUA_MD5Param.nonce then
|
||||
log.info("DigestAuth", "无法提取realm或nonce参数")
|
||||
return false, nil
|
||||
end
|
||||
|
||||
-- 计算HA1值(用户名、认证域、密码的MD5哈希)
|
||||
local HA1 = CameraHA1(DAHUA_MD5Param.username, DAHUA_MD5Param.realm, DAHUA_MD5Param.password)
|
||||
|
||||
-- 计算完整的response值(Digest认证的核心)
|
||||
-- response = MD5(HA1:nonce:nc:cnonce:qop:HA2)
|
||||
DAHUA_MD5Param.response = string.lower(crypto.md5(HA1..":"..DAHUA_MD5Param.nonce..":"..DAHUA_MD5Param.nc..":"..DAHUA_MD5Param.cnonce..":"..DAHUA_MD5Param.qop..":"..HA2))
|
||||
|
||||
-- 构建完整的Authorization头部
|
||||
local authorization_header = "Digest username=\"" .. DAHUA_MD5Param.username .. "\", realm=\"" .. DAHUA_MD5Param.realm .. "\", nonce=\"" .. DAHUA_MD5Param.nonce .. "\", uri=\"" .. url..params.. "\", qop=" .. DAHUA_MD5Param.qop .. ", nc=" .. DAHUA_MD5Param.nc .. ", cnonce=\"" .. DAHUA_MD5Param.cnonce .. "\", response=\"" .. DAHUA_MD5Param.response.."\""
|
||||
|
||||
-- 更新请求头部,添加认证信息
|
||||
local updated_headers = {['Host']=''..Host, ["Authorization"] = ''..authorization_header, ['Connection']='keep-alive'}
|
||||
log.info("DigestAuth", "鉴权信息重组完成")
|
||||
|
||||
return true, updated_headers
|
||||
end
|
||||
|
||||
--[[
|
||||
设置大华(Dahua)摄像头的OSD(屏幕显示)模块
|
||||
@api DH_set_osd_module(Host,Data,TextAlign,channel,x,y)
|
||||
@string Host 摄像头的IP地址
|
||||
@string Data 要显示的OSD文本内容
|
||||
@number TextAlign OSD文本对齐方式,默认为全局的DH_TextAlign
|
||||
@number channel 摄像头通道号,默认为全局的DH_channel
|
||||
@number x OSD显示的X坐标,默认为0
|
||||
@number y OSD显示的Y坐标,默认为0
|
||||
@return nil 无返回值,函数通过日志输出执行结果
|
||||
@usage:
|
||||
DH_set_osd_module("192.168.1.100", "温度: 25℃", 0, 1, 100, 200)
|
||||
-- 功能: 在IP为192.168.1.100的摄像头通道1上,坐标(100,200)处显示"温度: 25℃"
|
||||
]]
|
||||
local function DH_set_osd_module(Host,Data,TextAlign,channel,x,y)
|
||||
-- 设置默认参数值
|
||||
DH_TextAlign = TextAlign or DH_TextAlign -- 对齐方式 如果没填用默认值左对齐
|
||||
channel = channel or DH_channel -- 通道号 如果没填用默认值0
|
||||
x = x or 0 -- x坐标 如果没填用默认值为0
|
||||
y = y or 0 -- y坐标 如果没填用默认值为0
|
||||
|
||||
-- 构建OSD位置参数字符串
|
||||
dh_osd_param.Postion = "&VideoWidget["..channel.."].CustomTitle[1].Rect[0]="..x.."&VideoWidget["..channel.."].CustomTitle[1].Rect[1]="..y.."&VideoWidget["..channel.."].CustomTitle[1].Rect[2]=0".."&VideoWidget["..channel.."].CustomTitle[1].Rect[3]=0"
|
||||
-- 构建OSD设置参数字符串
|
||||
dh_osd_param.SetWidgest = "action=setConfig&VideoWidget["..channel.."].FontColorType=Adapt&VideoWidget["..channel.."].CustomTitle[1].PreviewBlend=true&VideoWidget["..channel.."].CustomTitle[1].EncodeBlend=true&VideoWidget["..channel.."].CustomTitle[1].TextAlign="..DH_TextAlign.."&VideoWidget["..channel.."].CustomTitle[1].Text="
|
||||
|
||||
-- 对OSD文本内容进行URL编码,确保特殊字符正确传输
|
||||
local OsdData = urlencode(Data)
|
||||
-- 拼接完整的OSD设置参数
|
||||
local OSDTEXT = dh_osd_param.SetWidgest ..OsdData
|
||||
---log.info("打印放置位置",dh_osd_param.Postion)
|
||||
|
||||
-- 计算HA2值,用于Digest认证
|
||||
-- HA2 = MD5(方法:URL路径:请求参数)
|
||||
local HA2 = string.lower(crypto.md5(DAHUA_MD5Param.method..dh_osd_param.url..OSDTEXT..dh_osd_param.Postion))
|
||||
-- 构建HTTP请求头部
|
||||
local Camera_header = {["Accept-Encoding"]="identity",["Host"]=""..Host}
|
||||
|
||||
-- 发送第一次HTTP请求,获取鉴权信息
|
||||
local full_params = OSDTEXT..dh_osd_param.Postion
|
||||
local full_url = "http://"..Host..dh_osd_param.url..full_params
|
||||
local code, headers, body = http.request("GET", full_url, Camera_header).wait()
|
||||
log.info("DHosd", "第一次请求http,code:", code, headers) -- 打印返回的状态码和头部信息
|
||||
|
||||
-- 处理HTTP请求返回结果
|
||||
if code == 401 then -- 401表示需要身份认证
|
||||
-- 使用Digest认证函数处理认证
|
||||
local success, updated_headers = handle_digest_auth(Host, dh_osd_param.url, full_params, headers, HA2)
|
||||
if success then
|
||||
-- 发送第二次HTTP请求,这次带有完整的认证信息
|
||||
local code, headers, body = http.request("GET", full_url, updated_headers).wait()
|
||||
log.info("DHosd", "第二次请求http,code:", code)
|
||||
else
|
||||
log.info("DHosd", "Digest认证失败")
|
||||
return
|
||||
end
|
||||
elseif code == -4 then
|
||||
-- 处理重组错误(参数错误)
|
||||
log.info("DHosd", "重组错误,请检查参数是否正确")
|
||||
return -- 退出函数,节省资源
|
||||
else
|
||||
-- 处理其他HTTP错误
|
||||
log.info("DHosd", "HTTP请求错误,code:", code)
|
||||
return -- 退出函数,节省资源
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
设置摄像头OSD(屏幕显示)文字功能
|
||||
@api OSDsetup(Brand,Host,channel,text,X,Y)
|
||||
@string Brand 摄像头品牌,当前仅支持: "Dhua" - 大华
|
||||
@string Host 摄像头/NVR的IP地址
|
||||
@number channel 摄像头通道号(主要用于NVR)
|
||||
@string text OSD文本内容,需用竖线分隔,格式如"1111|2222|3333|4444",大华最多显示13行
|
||||
@number X 显示位置的X坐标
|
||||
@number Y 显示位置的Y坐标
|
||||
@return 无 无返回值
|
||||
@usage
|
||||
-- 大华摄像头OSD测试
|
||||
OSDsetup("Dhua", "192.168.0.163", 0, "行1|行2|行3", 0, 2000)
|
||||
|
||||
-- 多通道NVR示例
|
||||
OSDsetup("Dhua", "192.168.0.200", 1, "温度: 25℃|湿度: 60%", 100, 50)
|
||||
]]
|
||||
local function OSDsetup(Brand,Host,channel,text,X,Y)
|
||||
-- 判断摄像头品牌
|
||||
if Brand == "Dhua" then
|
||||
log.info("osdsetup","检测到大华摄像头,开始初始化")
|
||||
-- 解析并验证OSD文本内容,大华摄像头最多支持13行
|
||||
ElementJudg(text,13)
|
||||
-- 调用大华摄像头OSD设置函数
|
||||
-- 参数:IP地址、OSD文本数组、对齐方式、通道号、X坐标、Y坐标
|
||||
DH_set_osd_module(Host,text,0,channel,X,Y)
|
||||
|
||||
-- 以下品牌型号暂不支持,代码已注释
|
||||
-- elseif Brand == "Hikvision" then
|
||||
-- log.info("osdsetup","检测到海康摄像头,开始初始化")
|
||||
-- local all_items = ElementJudg(Text,4)
|
||||
-- HKOSDBdoyGetFun(Host,channel,all_items[1],all_items[2],all_items[3],all_items[4],X,Y)
|
||||
-- elseif Brand == "Uniview" then
|
||||
-- log.info("osdsetup","检测到宇视摄像头,开始初始化")
|
||||
-- local all_items = ElementJudg(Text,6)
|
||||
-- EZ_OSDSETFun(Host,channel,all_items[1],all_items[2],all_items[3],all_items[4],all_items[5],all_items[6],X,Y)
|
||||
-- elseif Brand == "TianDiWeiye" then
|
||||
-- log.info("osdsetup","检测到天地伟业摄像头,开始初始化")
|
||||
-- local all_items = ElementJudg(Text,6)
|
||||
-- -- TDOSDModify(Host,t)
|
||||
else
|
||||
-- 处理不支持的品牌
|
||||
log.info("osdsetup","型号填写错误或暂不支持!!!")
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
大华摄像头拍照功能,获取指定通道的快照图片
|
||||
@api DHPicture(Host,channel)
|
||||
@string Host 摄像头/NVR的IP地址
|
||||
@number channel 摄像头通道号
|
||||
@return 无 无返回值,若SD卡可用则图片保存为/sd/1.jpeg
|
||||
@usage
|
||||
-- 获取大华摄像头通道0的快照图片
|
||||
DHPicture("192.168.1.108", 0)
|
||||
|
||||
-- 获取大华NVR通道1的快照图片
|
||||
DHPicture("192.168.0.200", 1)
|
||||
]]
|
||||
local function DHPicture(Host,channel)
|
||||
log.info("DHPicture","开始执行")
|
||||
|
||||
-- 构建拍照请求参数:通道号和图片类型(0表示快照)
|
||||
local resultStr = "channel="..channel.."&type=0"
|
||||
|
||||
-- 计算HA2值:对HTTP方法、URL路径和请求参数进行MD5加密
|
||||
local HA2 = string.lower(crypto.md5(DAHUA_MD5Param.method..DAHUA_MD5Param.url..resultStr))
|
||||
|
||||
-- 准备基础HTTP请求头部
|
||||
local Camera_header = {["Accept-Encoding"]="identity",["Host"]=""..Host}
|
||||
|
||||
-- 发送第一次HTTP请求,主要目的是获取Digest认证信息
|
||||
local full_url = "http://"..Host..DAHUA_MD5Param.url..resultStr
|
||||
local code, headers, body = http.request("GET", full_url, Camera_header).wait()
|
||||
log.info("DHPicture","第一次请求http,code:",code,headers)
|
||||
|
||||
-- 获取到鉴权信息
|
||||
if code ==401 then
|
||||
-- 使用统一的Digest认证函数处理认证
|
||||
local success, updated_headers = handle_digest_auth(Host, DAHUA_MD5Param.url, resultStr, headers, HA2)
|
||||
if success then
|
||||
Camera_header = updated_headers
|
||||
log.info("DHPicture","鉴权信息重组完成")
|
||||
else
|
||||
log.info("DHPicture", "Digest认证失败")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- 检查SD卡状态
|
||||
local can_save_to_sd = false
|
||||
local data, err = fatfs.getfree("/sd")
|
||||
if data then
|
||||
can_save_to_sd = true
|
||||
log.info("DHPicture", "SD卡可用空间信息:", json.encode(data))
|
||||
else
|
||||
log.info("DHPicture", "无法获取SD卡空间信息:", err)
|
||||
end
|
||||
|
||||
-- 根据SD卡状态发送请求
|
||||
local code, headers, body
|
||||
if can_save_to_sd then
|
||||
-- 发送第二次请求(带有完整的认证信息),获取图片并保存到/sd/1.jpeg
|
||||
code, headers, body = http.request("GET", full_url, Camera_header, nil, {dst = "/sd/1.jpeg"}).wait()
|
||||
else
|
||||
-- 发送第二次请求(带有完整的认证信息),不保存图片
|
||||
code, headers, body = http.request("GET", full_url, Camera_header).wait()
|
||||
log.info("DHPicture", "没有检测到SD卡,无法保存图片到SD卡中,请确认SD卡状态后重试")
|
||||
end
|
||||
|
||||
log.info("DHPicture","第二次请求http,code:", code, body)
|
||||
if code == 200 then
|
||||
log.info("DHPicture","拍照完成")
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
多品牌摄像头拍照通用接口,根据品牌调用对应厂商的拍照功能
|
||||
@api getphoto(Brand,Host,channel)
|
||||
@string Brand 摄像头品牌,当前仅支持: "Dhua" - 大华
|
||||
@string Host 摄像头/NVR的IP地址
|
||||
@number channel 摄像头通道号
|
||||
@return 无 无返回值,若SD卡可用则图片保存为/sd/1.jpeg
|
||||
@usage
|
||||
-- 获取大华摄像头通道0的快照图片
|
||||
getphoto("Dhua", "192.168.1.108", 1)
|
||||
|
||||
-- 获取大华NVR通道1的快照图片
|
||||
getphoto("Dhua", "192.168.0.200", 1)
|
||||
]]
|
||||
local function getphoto(Brand,Host,channel)
|
||||
-- 判断摄像头品牌
|
||||
if Brand == "Dhua" then
|
||||
log.info("getphoto","检测到大华摄像头,开始初始化")
|
||||
DHPicture(Host,channel)
|
||||
else
|
||||
-- 处理不支持的品牌
|
||||
log.info("getphoto","型号填写错误或暂不支持!!!")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
OSDsetup = OSDsetup,
|
||||
getphoto = getphoto
|
||||
}
|
||||
1604
4G/tools/resource/soc_script/v2025.12.31.22/lib/exremotefile.lua
Normal file
1604
4G/tools/resource/soc_script/v2025.12.31.22/lib/exremotefile.lua
Normal file
File diff suppressed because it is too large
Load Diff
588
4G/tools/resource/soc_script/v2025.12.31.22/lib/extalk.lua
Normal file
588
4G/tools/resource/soc_script/v2025.12.31.22/lib/extalk.lua
Normal file
@@ -0,0 +1,588 @@
|
||||
--[[
|
||||
@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
|
||||
431
4G/tools/resource/soc_script/v2025.12.31.22/lib/extp.lua
Normal file
431
4G/tools/resource/soc_script/v2025.12.31.22/lib/extp.lua
Normal file
@@ -0,0 +1,431 @@
|
||||
-- extp.lua - 触摸系统模块
|
||||
--[[
|
||||
@module extp
|
||||
@summary 触摸系统拓展库
|
||||
@version 1.1.1
|
||||
@date 2025.11.20
|
||||
@author 江访
|
||||
@usage
|
||||
本文件为触摸系统拓展库,核心业务逻辑为:
|
||||
1、初始化触摸设备,支持多种触摸芯片
|
||||
2、处理原始触摸数据并解析为各种手势事件
|
||||
3、通过统一消息接口发布触摸事件
|
||||
4、提供消息发布控制功能
|
||||
5、提供滑动和长按阈值配置功能
|
||||
|
||||
支持的触摸事件类型包括:
|
||||
RAW_DATA、TOUCH_DOWN、MOVE_X、MOVE_Y、SWIPE_LEFT、SWIPE_RIGHT、
|
||||
SWIPE_UP、SWIPE_DOWN、SINGLE_TAP、LONG_PRESS
|
||||
|
||||
本文件的对外接口有5个:
|
||||
1、extp.init(param):触摸设备初始化函数
|
||||
2、extp.set_publish_enabled(msg_type, enabled):设置消息发布状态
|
||||
3、extp.get_publish_enable(msg_type):获取消息发布状态
|
||||
4、extp.set_swipe_threshold(threshold):设置滑动判定阈值
|
||||
5、extp.set_long_press_threshold(threshold):设置长按判定阈值
|
||||
|
||||
所有触摸事件均通过sys.publish("BASE_TOUCH_EVENT", event_type, ...)发布
|
||||
]]
|
||||
|
||||
local extp = {}
|
||||
|
||||
-- 触摸状态变量
|
||||
local state = "IDLE" -- 当前状态:IDLE(空闲), DOWN(按下), MOVE(移动)
|
||||
local touch_down_x = 0 -- 按下时的X坐标
|
||||
local touch_down_y = 0 -- 按下时的Y坐标
|
||||
local touch_down_time = 0 -- 按下时的时间戳
|
||||
local swipe_threshold = 45 -- 滑动判定阈值(像素)
|
||||
local long_press_threshold = 500 -- 长按判定阈值(毫秒)
|
||||
local swipe_direction = nil -- 滑动方向(用于MOVE状态)
|
||||
|
||||
-- 消息发布控制表,默认全部打开
|
||||
local publish_control = {
|
||||
RAW_DATA = false, -- 原始触摸数据
|
||||
TOUCH_DOWN = false, -- 按下事件
|
||||
MOVE_X = false, -- 水平移动
|
||||
MOVE_Y = false, -- 垂直移动
|
||||
SWIPE_LEFT = false, -- 向左滑动
|
||||
SWIPE_RIGHT = false, -- 向右滑动
|
||||
SWIPE_UP = false, -- 向上滑动
|
||||
SWIPE_DOWN = false, -- 向下滑动
|
||||
SINGLE_TAP = true, -- 单击
|
||||
LONG_PRESS = true -- 长按
|
||||
}
|
||||
|
||||
-- 定义支持的触摸芯片配置
|
||||
local tp_configs = {
|
||||
cst820 = { i2c_speed = i2c.FAST, tp_model = "cst820" },
|
||||
gt9157 = { i2c_speed = i2c.FAST, tp_model = "gt9157" },
|
||||
cst9220 = { i2c_speed = i2c.SLOW, tp_model = "cst9220" },
|
||||
jd9261t = { i2c_speed = i2c.FAST, tp_model = "jd9261t" },
|
||||
gt911 = { i2c_speed = i2c.SLOW, tp_model = "gt911" },
|
||||
AirLCD_1010 = { i2c_speed = i2c.SLOW, tp_model = "gt911" },
|
||||
Air780EHM_LCD_4 = { i2c_speed = i2c.SLOW, tp_model = "gt911" },
|
||||
AirLCD_1020 = { i2c_speed = i2c.SLOW, tp_model = "gt911" }
|
||||
}
|
||||
|
||||
-- 特殊型号的默认配置
|
||||
local special_tp_configs = {
|
||||
Air780EHM_LCD_4 = {
|
||||
i2c_id = 1,
|
||||
pin_rst = 1,
|
||||
pin_int = 22
|
||||
},
|
||||
AirLCD_1010 = {
|
||||
i2c_id = 0,
|
||||
pin_rst = 20,
|
||||
pin_int = gpio.WAKEUP0
|
||||
},
|
||||
AirLCD_1020 = {
|
||||
i2c_id = i2c.createSoft(0, 1),
|
||||
pin_rst = 28,
|
||||
pin_int = 7
|
||||
}
|
||||
}
|
||||
|
||||
--[[
|
||||
设置消息发布状态
|
||||
@api extp.set_publish_enabled(msg_type, enabled)
|
||||
@string msg_type 消息类型,支持"ALL"或具体事件类型
|
||||
@bool enabled 是否启用发布
|
||||
@return bool 操作成功返回true,失败返回false
|
||||
@usage
|
||||
-- 启用单击事件
|
||||
extp.set_publish_enabled("SINGLE_TAP", true)
|
||||
|
||||
-- 禁用所有消息发布
|
||||
extp.set_publish_enabled("ALL", false)
|
||||
]]
|
||||
function extp.set_publish_enabled(msg_type, enabled)
|
||||
if msg_type == "ALL" then
|
||||
for k, _ in pairs(publish_control) do
|
||||
publish_control[k] = enabled
|
||||
end
|
||||
log.info("extp", "所有消息发布", enabled and "启用" or "禁用")
|
||||
return true
|
||||
elseif publish_control[msg_type] ~= nil then
|
||||
publish_control[msg_type] = enabled
|
||||
log.info("extp", msg_type, "消息发布", enabled and "启用" or "禁用")
|
||||
return true
|
||||
else
|
||||
log.error("extp", "未知的消息类型:", msg_type)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
获取消息发布状态
|
||||
@api extp.get_publish_enable(msg_type)
|
||||
@string msg_type 消息类型,"ALL"或具体事件类型
|
||||
@return bool|table 发布状态或所有状态表
|
||||
@usage
|
||||
-- 获取单击事件状态
|
||||
local enabled = extp.get_publish_enable("SINGLE_TAP")
|
||||
|
||||
-- 获取所有状态
|
||||
local all_status = extp.get_publish_enable("ALL")
|
||||
]]
|
||||
function extp.get_publish_enable(msg_type)
|
||||
if msg_type == "ALL" then
|
||||
-- 返回完整的发布控制表
|
||||
return publish_control
|
||||
elseif publish_control[msg_type] ~= nil then
|
||||
return publish_control[msg_type]
|
||||
else
|
||||
log.error("extp", "未知的消息类型:", msg_type)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
设置滑动判定阈值
|
||||
@api extp.set_swipe_threshold(threshold)
|
||||
@number threshold 滑动判定阈值(像素)
|
||||
@return bool 设置成功返回true,失败返回false
|
||||
@usage
|
||||
-- 设置滑动阈值为50像素
|
||||
extp.set_swipe_threshold(50)
|
||||
]]
|
||||
function extp.set_swipe_threshold(threshold)
|
||||
if type(threshold) == "number" and threshold > 0 then
|
||||
swipe_threshold = threshold
|
||||
log.info("extp", "滑动判定阈值设置为:", threshold)
|
||||
return true
|
||||
else
|
||||
log.error("extp", "无效的滑动阈值:", threshold)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
设置长按判定阈值
|
||||
@api extp.set_long_press_threshold(threshold)
|
||||
@number threshold 长按判定阈值(毫秒)
|
||||
@return bool 设置成功返回true,失败返回false
|
||||
@usage
|
||||
-- 设置长按阈值为800毫秒
|
||||
extp.set_long_press_threshold(800)
|
||||
]]
|
||||
function extp.set_long_press_threshold(threshold)
|
||||
if type(threshold) == "number" and threshold > 0 then
|
||||
long_press_threshold = threshold
|
||||
log.info("extp", "长按判定阈值设置为:", threshold)
|
||||
return true
|
||||
else
|
||||
log.error("extp", "无效的长按阈值:", threshold)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- 触摸回调函数
|
||||
-- 参数: tp_device-触摸设备对象, tp_data-触摸数据
|
||||
local function tp_callback(tp_device, tp_data)
|
||||
-- 发布原始数据
|
||||
if publish_control.RAW_DATA then
|
||||
sys.publish("BASE_TOUCH_EVENT", "RAW_DATA", tp_device, tp_data)
|
||||
end
|
||||
|
||||
if type(tp_data[1]) ~= "table" then return end
|
||||
|
||||
local event_type = tp_data[1].event
|
||||
local x = tp_data[1].x
|
||||
local y = tp_data[1].y
|
||||
-- 获取高精度时间戳
|
||||
local _, ms_l = mcu.ticks2(1)
|
||||
-- 使用系统时间戳或触摸数据中的时间戳
|
||||
local timestamp = ms_l or tp_data[1].timestamp
|
||||
if not event_type then return end
|
||||
|
||||
if event_type == 2 then -- 抬手事件
|
||||
if state == "DOWN" or state == "MOVE" then
|
||||
local moveX = x - touch_down_x
|
||||
local moveY = y - touch_down_y
|
||||
|
||||
if moveX < -swipe_threshold then
|
||||
if publish_control.SWIPE_LEFT then
|
||||
sys.publish("BASE_TOUCH_EVENT", "SWIPE_LEFT", moveX, 0)
|
||||
end
|
||||
elseif moveX > swipe_threshold then
|
||||
if publish_control.SWIPE_RIGHT then
|
||||
sys.publish("BASE_TOUCH_EVENT", "SWIPE_RIGHT", moveX, 0)
|
||||
end
|
||||
elseif moveY < -swipe_threshold then
|
||||
if publish_control.SWIPE_UP then
|
||||
sys.publish("BASE_TOUCH_EVENT", "SWIPE_UP", 0, moveY)
|
||||
end
|
||||
elseif moveY > swipe_threshold then
|
||||
if publish_control.SWIPE_DOWN then
|
||||
sys.publish("BASE_TOUCH_EVENT", "SWIPE_DOWN", 0, moveY)
|
||||
end
|
||||
else
|
||||
-- 计算按下时间
|
||||
local press_time = timestamp - touch_down_time
|
||||
|
||||
-- 判断是单击还是长按
|
||||
if press_time < long_press_threshold then
|
||||
if publish_control.SINGLE_TAP then
|
||||
sys.publish("BASE_TOUCH_EVENT", "SINGLE_TAP", touch_down_x, touch_down_y)
|
||||
end
|
||||
else
|
||||
if publish_control.LONG_PRESS then
|
||||
sys.publish("BASE_TOUCH_EVENT", "LONG_PRESS", touch_down_x, touch_down_y)
|
||||
end
|
||||
end
|
||||
end
|
||||
state = "IDLE"
|
||||
end
|
||||
elseif event_type == 1 or event_type == 3 then -- 按下或移动事件
|
||||
if state == "IDLE" and event_type == 1 then
|
||||
-- 从空闲状态接收到按下事件
|
||||
state = "DOWN"
|
||||
touch_down_x = x
|
||||
touch_down_y = y
|
||||
touch_down_time = timestamp
|
||||
swipe_direction = nil
|
||||
|
||||
-- 发布按下事件
|
||||
if publish_control.TOUCH_DOWN then
|
||||
sys.publish("BASE_TOUCH_EVENT", "TOUCH_DOWN", x, y)
|
||||
end
|
||||
elseif state == "DOWN" and event_type == 3 then
|
||||
-- 在按下状态下接收到移动事件
|
||||
if math.abs(x - touch_down_x) >= swipe_threshold or math.abs(y - touch_down_y) >= swipe_threshold then
|
||||
state = "MOVE"
|
||||
-- 确定滑动方向
|
||||
if math.abs(x - touch_down_x) > math.abs(y - touch_down_y) then
|
||||
-- 水平滑动
|
||||
if x - touch_down_x < 0 then
|
||||
swipe_direction = "LEFT"
|
||||
else
|
||||
swipe_direction = "RIGHT"
|
||||
end
|
||||
else
|
||||
-- 垂直滑动
|
||||
if y - touch_down_y < 0 then
|
||||
swipe_direction = "UP"
|
||||
else
|
||||
swipe_direction = "DOWN"
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif state == "MOVE" and event_type == 3 then
|
||||
-- 在移动状态下接收到移动事件
|
||||
-- 根据滑动方向发布相应的移动事件
|
||||
if swipe_direction == "LEFT" or swipe_direction == "RIGHT" then
|
||||
-- 水平滑动,发布MOVE_X事件
|
||||
if publish_control.MOVE_X then
|
||||
sys.publish("BASE_TOUCH_EVENT", "MOVE_X", x - touch_down_x, 0)
|
||||
end
|
||||
else
|
||||
-- 垂直滑动,发布MOVE_Y事件
|
||||
if publish_control.MOVE_Y then
|
||||
sys.publish("BASE_TOUCH_EVENT", "MOVE_Y", 0, y - touch_down_y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
初始化触摸设备
|
||||
@api extp.init(param)
|
||||
@table param 触摸芯片配置参数,参考库的说明及demo用法
|
||||
@return bool 初始化成功返回true,失败返回false
|
||||
@usage
|
||||
-- 基础触摸初始化
|
||||
extp.init({
|
||||
tp_model = "gt911",
|
||||
i2c_id = 0,
|
||||
pin_rst = 20,
|
||||
pin_int = 21
|
||||
})
|
||||
|
||||
-- 使用预定义配置
|
||||
extp.init({tp_model = "AirLCD_1010"})
|
||||
|
||||
-- 带屏幕尺寸的初始化
|
||||
extp.init({
|
||||
tp_model = "gt911",
|
||||
i2c_id = 0,
|
||||
pin_rst = 20,
|
||||
pin_int = 21,
|
||||
w = 480,
|
||||
h = 320
|
||||
})
|
||||
]]
|
||||
function extp.init(param)
|
||||
if type(param) ~= "table" then
|
||||
log.error("extp", "参数必须为表")
|
||||
return false
|
||||
end
|
||||
|
||||
-- 检查必要参数
|
||||
if not param.tp_model then
|
||||
log.error("extp", "缺少必要参数: tp_model")
|
||||
return false
|
||||
end
|
||||
|
||||
local tp_model = param.tp_model
|
||||
|
||||
-- 检查是否支持该型号
|
||||
local config = tp_configs[tp_model]
|
||||
if not config then
|
||||
log.error("extp", "不支持的触摸型号:", tp_model)
|
||||
return false
|
||||
end
|
||||
|
||||
-- 特殊型号参数处理
|
||||
local final_param = {}
|
||||
final_param.tp_model = tp_model
|
||||
|
||||
-- 处理特殊型号的默认配置
|
||||
if special_tp_configs[tp_model] then
|
||||
local default_config = special_tp_configs[tp_model]
|
||||
|
||||
if tp_model == "Air780EHM_LCD_4" then
|
||||
-- Air780EHM_LCD_4: 强制使用默认配置,忽略传入的其他参数
|
||||
final_param.i2c_id = default_config.i2c_id
|
||||
final_param.pin_rst = default_config.pin_rst
|
||||
final_param.pin_int = default_config.pin_int
|
||||
log.info("extp", "Air780EHM_LCD_4使用固定配置")
|
||||
else
|
||||
-- AirLCD_1010 和 AirLCD_1020: 使用传入参数,如果未传入则使用默认配置
|
||||
final_param.i2c_id = param.i2c_id or default_config.i2c_id
|
||||
final_param.pin_rst = param.pin_rst or default_config.pin_rst
|
||||
final_param.pin_int = param.pin_int or default_config.pin_int
|
||||
|
||||
-- 记录使用的配置来源
|
||||
if param.i2c_id then
|
||||
log.info("extp", tp_model, "使用传入的i2c_id")
|
||||
else
|
||||
log.info("extp", tp_model, "使用默认i2c_id")
|
||||
end
|
||||
|
||||
if param.pin_rst then
|
||||
log.info("extp", tp_model, "使用传入的pin_rst")
|
||||
else
|
||||
log.info("extp", tp_model, "使用默认pin_rst")
|
||||
end
|
||||
|
||||
if param.pin_int then
|
||||
log.info("extp", tp_model, "使用传入的pin_int")
|
||||
else
|
||||
log.info("extp", tp_model, "使用默认pin_int")
|
||||
end
|
||||
end
|
||||
else
|
||||
-- 其他型号:直接使用传入参数
|
||||
final_param.i2c_id = param.i2c_id
|
||||
final_param.pin_rst = param.pin_rst
|
||||
final_param.pin_int = param.pin_int
|
||||
end
|
||||
|
||||
local tp_i2c_id, tp_pin_rst, tp_pin_int = final_param.i2c_id, final_param.pin_rst or 255, final_param.pin_int
|
||||
|
||||
-- 构建tp.init的参数表,增加w和h可选参数
|
||||
local tp_init_params = {
|
||||
port = tp_i2c_id,
|
||||
pin_rst = tp_pin_rst,
|
||||
pin_int = tp_pin_int
|
||||
}
|
||||
|
||||
-- 如果传入了w和h参数,则添加到参数表中
|
||||
if param.w then
|
||||
tp_init_params.w = param.w
|
||||
log.info("extp", "设置屏幕宽度:", param.w)
|
||||
end
|
||||
|
||||
if param.h then
|
||||
tp_init_params.h = param.h
|
||||
log.info("extp", "设置屏幕高度:", param.h)
|
||||
end
|
||||
|
||||
-- 统一初始化流程
|
||||
if type(tp_i2c_id) ~= "userdata" and config.i2c_speed ~= nil then
|
||||
i2c.setup(tp_i2c_id, config.i2c_speed)
|
||||
end
|
||||
|
||||
-- 使用包含w和h参数的table调用tp.init
|
||||
local tp_device = tp.init(config.tp_model, tp_init_params, tp_callback)
|
||||
if tp_device ~= nil then return true end
|
||||
|
||||
if tp_device == nil then
|
||||
-- 如果第一次初始化失败,尝试不带pin_rst的初始化
|
||||
tp_init_params.pin_rst = 255
|
||||
local tp_device = tp.init(config.tp_model, tp_init_params, tp_callback)
|
||||
|
||||
if tp_device ~= nil then return true end
|
||||
end
|
||||
|
||||
-- 若硬件触摸初始化失败,尝试PC触摸回退
|
||||
log.warn("extp", "触摸初始化失败,尝试PC触摸回退")
|
||||
local ok_pc, dev_pc = pcall(tp.init, "pc", { port = 0 }, tp_callback)
|
||||
if ok_pc and dev_pc then
|
||||
log.info("extp", "PC触摸回退成功")
|
||||
return true
|
||||
end
|
||||
log.error("extp", "PC触摸回退失败")
|
||||
return false
|
||||
end
|
||||
|
||||
return extp
|
||||
313
4G/tools/resource/soc_script/v2025.12.31.22/lib/exvib.lua
Normal file
313
4G/tools/resource/soc_script/v2025.12.31.22/lib/exvib.lua
Normal file
@@ -0,0 +1,313 @@
|
||||
--[[
|
||||
@module exvib
|
||||
@summary exvib 三轴加速度传感器扩展库
|
||||
@version 1.0
|
||||
@date 2025.08.10
|
||||
@author 李源龙
|
||||
@usage
|
||||
-- 用法实例
|
||||
注意:
|
||||
|
||||
1. exvib.lua可适用于合宙内部集成了G-Sensor加速度传感器DA221的模组型号,
|
||||
目前仅有Air8000系列模组内置了DA221,Air7000推出时也会内置该型号G-Sensor;
|
||||
|
||||
2. DA221在Air8000内部通过I2C1与之通信,并通过WAKEUP2接收运动监测中断,
|
||||
如您使用合宙其它型号模组外接DA221时,比如Air780EGH,建议与Air8000保持一致也选用I2C1和WAKEUP2
|
||||
(该管脚即为Air780EGH的PIN79:USIM_DET),这样便可以无缝使用本扩展库,DA221的供应商为苏州明皜
|
||||
如需采购DA221或者其他更高端的加速度传感器可以联系他们;
|
||||
|
||||
3. DA221作为加速度传感器,LuatOS仅支持运动检测这一功能,主要用于震动检测,运动检测,跌倒检测,
|
||||
搭配GNSS实现震动然后定位的功能,其余功能请自行研究,合宙提供了三种应用场景,如果需要适配自己的场景需求,
|
||||
请参考手册参数自行修改代码,调试适合自己场景的传感器值,合宙不提供DA221任何其它功能的任何形式的技术支持;
|
||||
|
||||
关于exvib库的三种模式主要用于以下场景:
|
||||
1,微小震动检测,用于检测轻微震动的场景,例如用手敲击桌面;加速度量程2g;
|
||||
2,运动检测,用于电动车或汽车行驶时的检测和人行走和跑步时的检测;加速度量程4g;
|
||||
3,跌倒检测,用于人或物体瞬间跌倒时的检测;加速度量程8g;
|
||||
|
||||
exvib=require("exvib")
|
||||
|
||||
local intPin=gpio.WAKEUP2 --中断检测脚,内部固定wakeup2
|
||||
local tid --获取定时打开的定时器id
|
||||
local num=0 --计数器
|
||||
local ticktable={0,0,0,0,0} --存放5次中断的tick值,用于做有效震动对比
|
||||
local eff=false --有效震动标志位,用于判断是否触发定位
|
||||
|
||||
|
||||
--有效震动模式
|
||||
--tick计数器,每秒+1用于存放5次中断的tick值,用于做有效震动对比
|
||||
-- local function tick()
|
||||
-- num=num+1
|
||||
-- end
|
||||
-- --每秒运行一次计时
|
||||
-- sys.timerLoopStart(tick,1000)
|
||||
|
||||
-- --有效震动判断
|
||||
-- local function ind()
|
||||
-- log.info("int", gpio.get(intPin))
|
||||
-- if gpio.get(intPin) == 1 then
|
||||
-- --接收数据如果大于5就删掉第一个
|
||||
-- if #ticktable>=5 then
|
||||
-- log.info("table.remove",table.remove(ticktable,1))
|
||||
-- end
|
||||
-- --存入新的tick值
|
||||
-- table.insert(ticktable,num)
|
||||
-- log.info("tick",num,(ticktable[5]-ticktable[1]<10),ticktable[5]>0)
|
||||
-- log.info("tick2",ticktable[1],ticktable[2],ticktable[3],ticktable[4],ticktable[5])
|
||||
-- --表长度为5且,第5次中断时间间隔减去第一次间隔小于10s,且第5次值为有效值
|
||||
-- if #ticktable>=5 and (ticktable[5]-ticktable[1]<10 and ticktable[1]>0) then
|
||||
-- log.info("vib", "xxx")
|
||||
-- --是否要去触发有效震动逻辑
|
||||
-- if eff==false then
|
||||
-- sys.publish("EFFECTIVE_VIBRATION")
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- --设置30s分钟之后再判断是否有效震动函数
|
||||
-- local function num_cb()
|
||||
-- eff=false
|
||||
-- end
|
||||
|
||||
-- local function eff_vib()
|
||||
-- --触发之后eff设置为true,30分钟之后再触发有效震动
|
||||
-- eff=true
|
||||
-- --30分钟之后再触发有效震动
|
||||
-- sys.timerStart(num_cb,180000)
|
||||
-- end
|
||||
|
||||
-- sys.subscribe("EFFECTIVE_VIBRATION",eff_vib)
|
||||
|
||||
|
||||
|
||||
--持续震动模式
|
||||
|
||||
--持续震动模式中断函数
|
||||
local function ind()
|
||||
log.info("int", gpio.get(intPin))
|
||||
--上升沿为触发震动中断
|
||||
if gpio.get(intPin) == 1 then
|
||||
local x,y,z = exvib.read_xyz() --读取x,y,z轴的数据
|
||||
log.info("x", x..'g', "y", y..'g', "z", z..'g')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function vib_fnc()
|
||||
-- 1,微小震动检测,用于检测轻微震动的场景,例如用手敲击桌面;加速度量程2g;
|
||||
-- 2,运动检测,用于电动车或汽车行驶时的检测和人行走和跑步时的检测;加速度量程4g;
|
||||
-- 3,跌倒检测,用于人或物体瞬间跌倒时的检测;加速度量程8g;
|
||||
--打开震动检测功能
|
||||
exvib.open(1)
|
||||
--设置gpio防抖100ms
|
||||
gpio.debounce(intPin, 100)
|
||||
--设置gpio中断触发方式wakeup2唤醒脚默认为双边沿触发
|
||||
gpio.setup(intPin, ind)
|
||||
|
||||
end
|
||||
|
||||
sys.taskInit(vib_fnc)
|
||||
|
||||
]]
|
||||
local exvib={}
|
||||
local i2cId=0
|
||||
local bsp=rtos.bsp()
|
||||
if bsp:find("780") then
|
||||
i2cId = 1
|
||||
end
|
||||
local da221Addr = 0x27
|
||||
local soft_reset = {0x00, 0x24} -- 软件复位地址
|
||||
local chipid_addr = 0x01 -- 芯片ID地址
|
||||
local rangeaddr = {0x0f, 0x00} -- 设置加速度量程,默认2g
|
||||
-- local rangeaddr = {0x0f, 0x01} -- 设置加速度量程,默认4g
|
||||
-- local rangeaddr = {0x0f, 0x10} -- 设置加速度量程,默认8g
|
||||
local int_set1_reg = {0x16, 0x87} --设置x,y,z发生变化时,产生中断
|
||||
local int_set2_reg = {0x17, 0x10} --使能新数据中断,数据变化时,产生中断,本程序不设置
|
||||
local int_map1_reg = {0x19, 0x04} --运动的时候,产生中断
|
||||
local int_map2_reg = {0x1a, 0x01}
|
||||
|
||||
local active_dur_addr = {0x27, 0x01} -- 设置激活时间,默认0x01
|
||||
local active_ths_addr = {0x28, 0x33} -- 设置激活阈值,灵敏度最高
|
||||
-- local active_ths_addr = {0x28, 0x80} -- 设置激活阈值,灵敏度适中
|
||||
-- local active_ths_addr = {0x28, 0xFE} -- 设置激活阈值,灵敏度最低
|
||||
local odr_addr = {0x10, 0x08} -- 设置采样率 100Hz
|
||||
local mode_addr = {0x11, 0x00} -- 设置正常模式
|
||||
local int_latch_addr = {0x21, 0x02} -- 设置中断锁存
|
||||
|
||||
local x_lsb_reg = 0x02 -- X轴LSB寄存器地址
|
||||
local x_msb_reg = 0x03 -- X轴MSB寄存器地址
|
||||
local y_lsb_reg = 0x04 -- Y轴LSB寄存器地址
|
||||
local y_msb_reg = 0x05 -- Y轴MSB寄存器地址
|
||||
local z_lsb_reg = 0x06 -- Z轴LSB寄存器地址
|
||||
local z_msb_reg = 0x07 -- Z轴MSB寄存器地址
|
||||
|
||||
local active_state = 0x0b -- 激活状态寄存器地址
|
||||
local active_state_data
|
||||
|
||||
local rangemode=1
|
||||
local x_accel
|
||||
local y_accel
|
||||
local z_accel
|
||||
--[[
|
||||
获取da221的xyz轴数据
|
||||
@api exvib.read_xyz()
|
||||
@return number x轴数据,number y轴数据,number z轴数据
|
||||
@usage
|
||||
local x,y,z = exvib.read_xyz() --读取x,y,z轴的数据
|
||||
log.info("x", x..'g', "y", y..'g', "z", z..'g')
|
||||
]]
|
||||
function exvib.read_xyz()
|
||||
-- da221是LSB在前,MSB在后,每个寄存器都是1字节数据,每次读取都是6个寄存器数据一起获取
|
||||
-- 因此直接从X轴LSB寄存器(0x02)开始连续读取6字节数据(X/Y/Z各2字节),避免出现数据撕裂问题
|
||||
i2c.send(i2cId, da221Addr, x_lsb_reg, 1)
|
||||
local recv_data = i2c.recv(i2cId, da221Addr, 6)
|
||||
|
||||
-- LSB数据格式为: D[3] D[2] D[1] D[0] unused unused unused unused
|
||||
-- MSB数据格式为: D[11] D[10] D[9] D[8] D[7] D[6] D[5] D[4]
|
||||
-- 数据位为12位,需要将MSB数据左移4位,LSB数据右移4位,最后进行或运算
|
||||
-- 解析X轴数据 (LSB在前,MSB在后)
|
||||
|
||||
local x_data = (string.byte(recv_data, 2) << 4) | (string.byte(recv_data, 1) >> 4)
|
||||
|
||||
-- 解析Y轴数据 (LSB在前,MSB在后)
|
||||
local y_data = (string.byte(recv_data, 4) << 4) | (string.byte(recv_data, 3) >> 4)
|
||||
|
||||
-- 解析Z轴数据 (LSB在前,MSB在后)
|
||||
local z_data = (string.byte(recv_data, 6) << 4) | (string.byte(recv_data, 5) >> 4)
|
||||
|
||||
|
||||
-- 转换为12位有符号整数
|
||||
-- 判断X轴数据是否大于2047,若大于则表示数据为负数
|
||||
-- 因为12位有符号整数的范围是 -2048 到 2047,原始数据为无符号形式,大于2047的部分需要转换为负数
|
||||
-- 通过减去4096 (2^12) 将无符号数转换为对应的有符号负数
|
||||
if x_data > 2047 then x_data = x_data - 4096 end
|
||||
-- 判断Y轴数据是否大于2047,若大于则进行同样的有符号转换
|
||||
if y_data > 2047 then y_data = y_data - 4096 end
|
||||
-- 判断Z轴数据是否大于2047,若大于则进行同样的有符号转换
|
||||
if z_data > 2047 then z_data = z_data - 4096 end
|
||||
|
||||
-- 转换为加速度值(单位:g)
|
||||
|
||||
if rangemode == 1 then
|
||||
x_accel = x_data / 1024
|
||||
y_accel = y_data / 1024
|
||||
z_accel = z_data / 1024
|
||||
|
||||
elseif rangemode == 2 then
|
||||
x_accel = x_data / 512
|
||||
y_accel = y_data / 512
|
||||
z_accel = z_data / 512
|
||||
|
||||
elseif rangemode == 3 then
|
||||
x_accel = x_data / 256
|
||||
y_accel = y_data / 256
|
||||
z_accel = z_data / 256
|
||||
else
|
||||
x_accel = x_data / 1024
|
||||
y_accel = y_data / 1024
|
||||
z_accel = z_data / 1024
|
||||
end
|
||||
|
||||
-- 输出加速度值(单位:g)
|
||||
return x_accel, y_accel, z_accel
|
||||
end
|
||||
|
||||
--初始化da221
|
||||
local function da221_init()
|
||||
if bsp:find("780") then
|
||||
gpio.setup(23, 1, gpio.PULLUP) -- gsensor 开关
|
||||
else
|
||||
gpio.setup(24, 1, gpio.PULLUP) -- gsensor 开关
|
||||
end
|
||||
--关闭i2c
|
||||
i2c.close(i2cId)
|
||||
--重新打开i2c,i2c速度设置为低速
|
||||
i2c.setup(i2cId, i2c.SLOW)
|
||||
|
||||
sys.wait(50)
|
||||
i2c.send(i2cId, da221Addr, soft_reset, 1) --复位da221
|
||||
sys.wait(50)
|
||||
i2c.send(i2cId, da221Addr, chipid_addr, 1) --读取芯片id
|
||||
local chipid = i2c.recv(i2cId, da221Addr, 1) --接收返回的芯片id
|
||||
log.info("i2c", "chipid",chipid:toHex())
|
||||
if string.byte(chipid) == 0x13 then
|
||||
log.info("exvib init success")
|
||||
else
|
||||
log.info("exvib init fail")
|
||||
end
|
||||
-- 设置寄存器
|
||||
i2c.send(i2cId, da221Addr, rangeaddr, 1) --设置加速度量程,默认2g
|
||||
sys.wait(5)
|
||||
i2c.send(i2cId, da221Addr, int_set1_reg, 1) --设置x,y,z发生变化时,产生中断
|
||||
sys.wait(5)
|
||||
i2c.send(i2cId, da221Addr, int_map1_reg, 1)--运动的时候,产生中断
|
||||
sys.wait(5)
|
||||
i2c.send(i2cId, da221Addr, active_dur_addr, 1)-- 设置激活时间,默认0x00
|
||||
sys.wait(5)
|
||||
i2c.send(i2cId, da221Addr, active_ths_addr, 1)-- 设置激活阈值
|
||||
sys.wait(5)
|
||||
i2c.send(i2cId, da221Addr, mode_addr, 1)-- 设置模式
|
||||
sys.wait(5)
|
||||
i2c.send(i2cId, da221Addr, odr_addr, 1)-- 设置采样率
|
||||
sys.wait(5)
|
||||
i2c.send(i2cId, da221Addr, int_latch_addr, 1)-- 设置中断锁存 中断一旦触发将保持,直到手动清除
|
||||
sys.wait(5)
|
||||
end
|
||||
|
||||
--[[
|
||||
打开da221
|
||||
@api exvib.open(mode)
|
||||
@number da221模式设置,1,微小震动检测,用于检测轻微震动的场景,例如用手敲击桌面;加速度量程2g;
|
||||
2,运动检测,用于电动车或汽车行驶时的检测和人行走和跑步时的检测;加速度量程4g;
|
||||
3,跌倒检测,用于人或物体瞬间跌倒时的检测;加速度量程8g;
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
exvib.open(1)
|
||||
]]
|
||||
function exvib.open(mode)
|
||||
rangemode=mode
|
||||
if mode==1 or tonumber(mode)==1 then
|
||||
--轻微检测
|
||||
log.info("轻微检测")
|
||||
rangeaddr = {0x0f, 0x00} -- 设置加速度量程,默认2g
|
||||
active_ths_addr = {0x28, 0x33} -- 设置激活阈值
|
||||
odr_addr = {0x10, 0x04} -- 设置采样率 15.63Hz
|
||||
active_dur_addr = {0x27, 0x01} -- 设置激活时间
|
||||
elseif mode==2 or tonumber(mode)==2 then
|
||||
--常规检测
|
||||
log.info("运动检测")
|
||||
rangeaddr = {0x0f, 0x01} -- 设置加速度量程,默认4g
|
||||
active_ths_addr = {0x28, 0x26} -- 设置激活阈值
|
||||
odr_addr = {0x10, 0x08} -- 设置采样率 250Hz
|
||||
active_dur_addr = {0x27, 0x14} -- 设置激活时间
|
||||
elseif mode==3 or tonumber(mode)==3 then
|
||||
log.info("高动态检测")
|
||||
--高动态检测
|
||||
rangeaddr = {0x0f, 0x02} -- 设置加速度量程,默认8g
|
||||
active_ths_addr = {0x28, 0x80} -- 设置激活阈值
|
||||
odr_addr = {0x10, 0x0F} -- 设置采样率 1000Hz
|
||||
active_dur_addr = {0x27, 0x04} -- 设置激活时间
|
||||
end
|
||||
sys.taskInit(da221_init)
|
||||
end
|
||||
|
||||
--[[
|
||||
关闭da221
|
||||
@api exvib.close()
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
exvib.close()
|
||||
]]
|
||||
function exvib.close()
|
||||
if bsp:find("780") then
|
||||
gpio.close(23) -- gsensor供电关闭
|
||||
else
|
||||
gpio.close(24) -- gsensor供电关闭
|
||||
end
|
||||
gpio.close(24) -- gsensor供电关闭
|
||||
log.info("exvib close..")
|
||||
end
|
||||
|
||||
|
||||
return exvib
|
||||
211
4G/tools/resource/soc_script/v2025.12.31.22/lib/exvib1.lua
Normal file
211
4G/tools/resource/soc_script/v2025.12.31.22/lib/exvib1.lua
Normal file
@@ -0,0 +1,211 @@
|
||||
--[[
|
||||
@summary exvib1扩展库
|
||||
@version 1.0
|
||||
@date 2025.09.07
|
||||
@author 孟伟
|
||||
@usage
|
||||
-- 应用场景
|
||||
此库适用于滚珠震动传感器BL_2529,主要目的是对振动中断进行过滤,识别有效震动
|
||||
对于一些震动传感器的中断管脚算法处理,也可以用做参考。
|
||||
|
||||
实现的功能:
|
||||
1. GPIO 中断检测:通过 GPIO 引脚检测震动传感器产生的脉冲信号
|
||||
2. 双重消抖机制:
|
||||
- io中断消抖 gpio.debounce()
|
||||
3. 时间窗口检测:在指定时间窗口(time_window)内统计脉冲数量
|
||||
4. 阈值触发:当脉冲数超过设定阈值(pulse_threshold)时触发回调
|
||||
5. 脉冲超时机制:在检测状态下,如果超过pulse_timeout时间没有新的脉冲,则提前结束当前检测周期并判断是否触发回调
|
||||
|
||||
状态机工作流程:
|
||||
- IDLE状态:等待第一个有效脉冲
|
||||
- DETECTING状态:进入检测窗口,统计脉冲数量
|
||||
- 触发条件:
|
||||
时间窗口结束
|
||||
脉冲空闲时间超过设定超时
|
||||
- 结果判断:脉冲数≥阈值则调用用户回调
|
||||
|
||||
-- 用法实例
|
||||
本扩展库对外提供了以下2个接口:
|
||||
1)启动震动检测功能 exvib1.open(opts)
|
||||
2)停止震动检测功能 exvib1.close()
|
||||
|
||||
--加载exvib1扩展库
|
||||
local exvib1= require "exvib1"
|
||||
|
||||
-- 震动事件回调
|
||||
local function vibration_cb(pulse_cnt)
|
||||
log.info("VIB", "detected! pulses =", pulse_cnt)
|
||||
end
|
||||
--演示最简单的使用方法,都使用默认配置
|
||||
exvib1.open({
|
||||
gpio_pin = 24,
|
||||
on_event = vibration_cb,
|
||||
})
|
||||
|
||||
|
||||
以下为exvib1扩展库两个函数的详细说明及代码实现:
|
||||
]]
|
||||
|
||||
local exvib1 = {}
|
||||
|
||||
-- 默认配置
|
||||
local cfg = {
|
||||
gpio_pin = nil, -- 传感器中断所接 GPIO
|
||||
pull = gpio.PULLUP,
|
||||
trigger = gpio.RISING,
|
||||
debounce_irq = 100, -- gpio消抖时间,gpio.debounce 时间(ms)
|
||||
time_window = 1000, -- 检测窗口(ms)
|
||||
pulse_threshold = 3, -- 触发阈值
|
||||
pulse_timeout = 200, -- 脉冲超时(ms)
|
||||
poll_interval = 10, -- 状态机轮询(ms)
|
||||
on_event = nil, -- 用户回调
|
||||
}
|
||||
|
||||
-- 内部状态
|
||||
local st = {
|
||||
pulse_cnt = 0,
|
||||
last_valid = 0,
|
||||
detect_t0 = 0,
|
||||
state = "IDLE",
|
||||
}
|
||||
|
||||
-- 重置内部状态,将状态机置为空闲状态并清零脉冲计数
|
||||
local function reset()
|
||||
st.state = "IDLE"
|
||||
st.pulse_cnt = 0
|
||||
end
|
||||
|
||||
-- GPIO 中断处理函数,用于处理传感器的脉冲信号
|
||||
local function isr()
|
||||
local now = mcu.ticks()
|
||||
st.pulse_cnt = st.pulse_cnt + 1
|
||||
st.last_valid = now
|
||||
-- 如果当前状态为空闲状态
|
||||
if st.state == "IDLE" then
|
||||
-- 切换到检测状态
|
||||
st.state = "DETECTING"
|
||||
-- 记录检测开始时间
|
||||
st.detect_t0 = now
|
||||
end
|
||||
end
|
||||
|
||||
-- 状态机处理函数,用于检测是否满足震动触发条件
|
||||
local function fsm()
|
||||
-- 如果当前状态不是检测状态,则直接返回
|
||||
if st.state ~= "DETECTING" then return end
|
||||
local now = mcu.ticks()
|
||||
-- 处理时间戳溢出情况
|
||||
if now < st.detect_t0 or now < st.last_valid then
|
||||
st.detect_t0 = 0
|
||||
st.last_valid = 0
|
||||
return -- 等待下次调用重新判断
|
||||
end
|
||||
|
||||
-- 计算从检测开始到现在经过的时间
|
||||
local elapsed = now - st.detect_t0
|
||||
-- 判断是否脉冲空闲时间过长
|
||||
local idle_too_long = (now - st.last_valid) >= cfg.pulse_timeout
|
||||
-- 当检测窗口结束或者脉冲空闲时间过长时
|
||||
if elapsed >= cfg.time_window or idle_too_long then
|
||||
-- 检查脉冲计数是否达到触发阈值,并且用户回调函数存在
|
||||
if st.pulse_cnt >= cfg.pulse_threshold and st.on_event then
|
||||
-- 调用用户回调函数并传入脉冲计数值
|
||||
st.on_event(st.pulse_cnt)
|
||||
end
|
||||
-- 重置内部状态
|
||||
reset()
|
||||
end
|
||||
end
|
||||
--[[
|
||||
启动震动检测功能
|
||||
@api exvib1.open(opts)
|
||||
@table opts 配置参数表,用于自定义震动检测功能的各项属性。
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
-- 配置参数介绍
|
||||
--local otps = {
|
||||
-- gpio_pin --"传感器中断所接 GPIO 引脚号,默认值为 nil",
|
||||
-- pull --"上拉/下拉模式,可选 gpio.PULLUP 或 gpio.PULLDOWN,默认值为 gpio.PULLUP",
|
||||
-- trigger --"触发方式,可选 gpio.RISING 或 gpio.FALLING,默认值为 gpio.RISING",
|
||||
-- debounce_irq --"GPIO 消抖时间,单位为毫秒,默认值为 100",
|
||||
-- time_window --"检测窗口时间,单位为毫秒,默认值为 1000",
|
||||
-- pulse_threshold --"触发阈值,即连续脉冲次数,默认值为 3",
|
||||
-- pulse_timeout --"脉冲超时时间,单位为毫秒,默认值为 200",
|
||||
-- poll_interval --"状态机轮询时间,单位为毫秒,默认值为 10",
|
||||
-- on_event --"用户回调函数,用于处理检测到的震动事件,默认值为 nil",
|
||||
--}
|
||||
-- 震动事件回调
|
||||
local function vibration_cb(pulse_cnt)
|
||||
log.info("VIB", "detected! pulses =", pulse_cnt)
|
||||
end
|
||||
exvib1.open({
|
||||
gpio_pin = 24,
|
||||
on_event = vibration_cb,
|
||||
})
|
||||
--不同场景下的参数配置可参考下面的示例
|
||||
--高灵敏度,响应快,误触可能高
|
||||
exvib1.open({
|
||||
gpio_pin = 24,
|
||||
on_event = vibration_cb,
|
||||
time_window = 300, -- 检测窗口(ms)
|
||||
pulse_threshold = 1, -- 触发阈值
|
||||
pulse_timeout = 100, -- 脉冲超时(ms)
|
||||
})
|
||||
--默认配置,较高灵敏度
|
||||
exvib1.open({
|
||||
gpio_pin = 24,
|
||||
on_event = vibration_cb,
|
||||
time_window = 1000, -- 检测窗口(ms)
|
||||
pulse_threshold = 3, -- 触发阈值
|
||||
pulse_timeout = 200, -- 脉冲超时(ms)
|
||||
})
|
||||
|
||||
--中等灵敏度,
|
||||
exvib1.open({
|
||||
gpio_pin = 24,
|
||||
on_event = vibration_cb,
|
||||
time_window = 2000, -- 检测窗口(ms)
|
||||
pulse_threshold = 3, -- 触发阈值
|
||||
pulse_timeout = 300, -- 脉冲超时(ms)
|
||||
})
|
||||
--低灵敏度,减少误报
|
||||
exvib1.open({
|
||||
gpio_pin = 24,
|
||||
on_event = vibration_cb,
|
||||
time_window = 3000, -- 检测窗口(ms)
|
||||
pulse_threshold = 10, -- 触发阈值
|
||||
pulse_timeout = 500, -- 脉冲超时(ms)
|
||||
})
|
||||
]]
|
||||
-- 启动震动检测功能
|
||||
function exvib1.open(opts)
|
||||
-- 如果没有传入配置参数,则使用空表
|
||||
opts = opts or {}
|
||||
-- 用传入的配置参数更新默认配置
|
||||
for k, v in pairs(opts) do cfg[k] = v end
|
||||
-- 更新用户回调函数,如果传入了新的回调则使用新的,否则保持原有回调
|
||||
st.on_event = opts.on_event or st.on_event
|
||||
-- 配置 GPIO 消抖时间,设置中断处理函数、上拉模式和触发方式
|
||||
gpio.debounce(cfg.gpio_pin, cfg.debounce_irq)
|
||||
gpio.setup(cfg.gpio_pin, isr, cfg.pull, cfg.trigger)
|
||||
-- 启动定时器循环调用状态机处理函数
|
||||
sys.timerLoopStart(fsm, cfg.poll_interval)
|
||||
log.info("Vibration", "start on gpio", cfg.gpio_pin)
|
||||
end
|
||||
|
||||
--[[
|
||||
关闭震动检测功能
|
||||
@api exvib1.close()
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
exvib1.close() --关闭震动检测功能
|
||||
--]]
|
||||
function exvib1.close()
|
||||
-- 关闭 GPIO 引脚
|
||||
gpio.close(cfg.gpio_pin)
|
||||
-- 停止定时器
|
||||
sys.timerStop(fsm)
|
||||
reset()
|
||||
end
|
||||
|
||||
return exvib1
|
||||
55
4G/tools/resource/soc_script/v2025.12.31.22/lib/gc0310.lua
Normal file
55
4G/tools/resource/soc_script/v2025.12.31.22/lib/gc0310.lua
Normal file
@@ -0,0 +1,55 @@
|
||||
local config = {
|
||||
mode = 1,
|
||||
is_msb = 1,
|
||||
rx_bit = 2,
|
||||
seq_type = 1,
|
||||
is_ddr = 0x00010101,
|
||||
i2c_slave_addr = 0x21,
|
||||
width = 640,
|
||||
height = 480,
|
||||
init_cmds = {{0xfe, 0xf0}, {0xfe, 0xf0}, {0xfe, 0x00}, {0xfc, 0x16}, {0xfc, 0x16}, {0xf2, 0x07}, {0xf3, 0x83},
|
||||
{0xf5, 0x07}, {0xf7, 0x88}, {0xf8, 0x00}, {0xf9, 0x4f}, {0xfa, 0x11}, {0xfc, 0xce}, {0xfd, 0x00},
|
||||
|
||||
{0x00, 0x2f}, {0x01, 0x0f}, {0x02, 0x04}, {0x03, 0x02}, {0x04, 0x12}, {0x09, 0x00}, {0x0a, 0x00},
|
||||
{0x0b, 0x00}, {0x0c, 0x04}, {0x0d, 0x01}, {0x0e, 0xe8}, {0x0f, 0x02}, {0x10, 0x88}, {0x16, 0x00},
|
||||
{0x17, 0x14}, {0x18, 0x1a}, {0x19, 0x14}, {0x1b, 0x48}, {0x1c, 0x6c}, {0x1e, 0x6b}, {0x1f, 0x28},
|
||||
{0x20, 0x8b}, {0x21, 0x49}, {0x22, 0xd0}, {0x23, 0x04}, {0x24, 0xff}, {0x34, 0x20}, {0x26, 0x23},
|
||||
{0x28, 0xff}, {0x29, 0x00}, {0x32, 0x04}, {0x33, 0x10}, {0x37, 0x20}, {0x38, 0x10}, {0x47, 0x80},
|
||||
{0x4e, 0x66}, {0xa8, 0x02}, {0xa9, 0x80}, {0x40, 0xff}, {0x41, 0x21}, {0x42, 0xcf}, {0x44, 0x00},
|
||||
{0x45, 0xa0}, {0x46, 0x02}, {0x4a, 0x11}, {0x4b, 0x01}, {0x4c, 0x20}, {0x4d, 0x05}, {0x4f, 0x01},
|
||||
{0x50, 0x01}, {0x55, 0x01}, {0x56, 0xe0}, {0x57, 0x02}, {0x58, 0x80}, {0x70, 0x70}, {0x5a, 0x84},
|
||||
{0x5b, 0xc9}, {0x5c, 0xed}, {0x77, 0x74}, {0x78, 0x40}, {0x79, 0x5f}, {0x82, 0x14}, {0x83, 0x0b},
|
||||
{0x89, 0xf0}, {0x8f, 0xaa}, {0x90, 0x8c}, {0x91, 0x90}, {0x92, 0x03}, {0x93, 0x03}, {0x94, 0x05},
|
||||
{0x95, 0x65}, {0x96, 0xf0}, {0xfe, 0x00}, {0x9a, 0x20}, {0x9b, 0x80}, {0x9c, 0x40}, {0x9d, 0x80},
|
||||
{0xa1, 0x30}, {0xa2, 0x32}, {0xa4, 0x30}, {0xa5, 0x30}, {0xaa, 0x10}, {0xac, 0x22}, {0xfe, 0x00},
|
||||
{0xbf, 0x08}, {0xc0, 0x16}, {0xc1, 0x28}, {0xc2, 0x41}, {0xc3, 0x5a}, {0xc4, 0x6c}, {0xc5, 0x7a},
|
||||
{0xc6, 0x96}, {0xc7, 0xac}, {0xc8, 0xbc}, {0xc9, 0xc9}, {0xca, 0xd3}, {0xcb, 0xdd}, {0xcc, 0xe5},
|
||||
{0xcd, 0xf1}, {0xce, 0xfa}, {0xcf, 0xff}, {0xd0, 0x40}, {0xd1, 0x34}, {0xd2, 0x34}, {0xd3, 0x40},
|
||||
{0xd6, 0xf2}, {0xd7, 0x1b}, {0xd8, 0x18}, {0xdd, 0x03}, {0xfe, 0x01}, {0x05, 0x30}, {0x06, 0x75},
|
||||
{0x07, 0x40}, {0x08, 0xb0}, {0x0a, 0xc5}, {0x0b, 0x11}, {0x0c, 0x00}, {0x12, 0x52}, {0x13, 0x38},
|
||||
{0x18, 0x95}, {0x19, 0x96}, {0x1f, 0x20}, {0x20, 0xc0}, {0x3e, 0x40}, {0x3f, 0x57}, {0x40, 0x7d},
|
||||
{0x03, 0x60}, {0x44, 0x00}, {0xfe, 0x01}, {0x1c, 0x91}, {0x21, 0x15}, {0x50, 0x80}, {0x56, 0x04},
|
||||
{0x59, 0x08}, {0x5b, 0x02}, {0x61, 0x8d}, {0x62, 0xa7}, {0x63, 0xd0}, {0x65, 0x06}, {0x66, 0x06},
|
||||
{0x67, 0x84}, {0x69, 0x08}, {0x6a, 0x25}, {0x6b, 0x01}, {0x6c, 0x00}, {0x6d, 0x02}, {0x6e, 0xf0},
|
||||
{0x6f, 0x80}, {0x76, 0x80}, {0x78, 0xaf}, {0x79, 0x75}, {0x7a, 0x40}, {0x7b, 0x50}, {0x7c, 0x0c},
|
||||
{0x90, 0xc9}, {0x91, 0xbe}, {0x92, 0xe2}, {0x93, 0xc9}, {0x95, 0x1b}, {0x96, 0xe2}, {0x97, 0x49},
|
||||
{0x98, 0x1b}, {0x9a, 0x49}, {0x9b, 0x1b}, {0x9c, 0xc3}, {0x9d, 0x49}, {0x9f, 0xc7}, {0xa0, 0xc8},
|
||||
{0xa1, 0x00}, {0xa2, 0x00}, {0x86, 0x00}, {0x87, 0x00}, {0x88, 0x00}, {0x89, 0x00}, {0xa4, 0xb9},
|
||||
{0xa5, 0xa0}, {0xa6, 0xba}, {0xa7, 0x92}, {0xa9, 0xba}, {0xaa, 0x80}, {0xab, 0x9d}, {0xac, 0x7f},
|
||||
{0xae, 0xbb}, {0xaf, 0x9d}, {0xb0, 0xc8}, {0xb1, 0x97}, {0xb3, 0xb7}, {0xb4, 0x7f}, {0xb5, 0x00},
|
||||
{0xb6, 0x00}, {0x8b, 0x00}, {0x8c, 0x00}, {0x8d, 0x00}, {0x8e, 0x00}, {0x94, 0x55}, {0x99, 0xa6},
|
||||
{0x9e, 0xaa}, {0xa3, 0x0a}, {0x8a, 0x00}, {0xa8, 0x55}, {0xad, 0x55}, {0xb2, 0x55}, {0xb7, 0x05},
|
||||
{0x8f, 0x00}, {0xb8, 0xcb}, {0xb9, 0x9b}, {0xfe, 0x01}, {0xd0, 0x38}, {0xd1, 0x00}, {0xd2, 0x02},
|
||||
{0xd3, 0x04}, {0xd4, 0x38}, {0xd5, 0x12}, {0xd6, 0x30}, {0xd7, 0x00}, {0xd8, 0x0a}, {0xd9, 0x16},
|
||||
{0xda, 0x39}, {0xdb, 0xf8}, {0xfe, 0x01}, {0xc1, 0x3c}, {0xc2, 0x50}, {0xc3, 0x00}, {0xc4, 0x40},
|
||||
{0xc5, 0x30}, {0xc6, 0x30}, {0xc7, 0x10}, {0xc8, 0x00}, {0xc9, 0x00}, {0xdc, 0x20}, {0xdd, 0x10},
|
||||
{0xdf, 0x00}, {0xde, 0x00}, {0x01, 0x10}, {0x0b, 0x31}, {0x0e, 0x50}, {0x0f, 0x0f}, {0x10, 0x6e},
|
||||
{0x12, 0xa0}, {0x15, 0x60}, {0x16, 0x60}, {0x17, 0xe0}, {0xcc, 0x0c}, {0xcd, 0x10}, {0xce, 0xa0},
|
||||
{0xcf, 0xe6}, {0x45, 0xf7}, {0x46, 0xff}, {0x47, 0x15}, {0x48, 0x03}, {0x4f, 0x60}, {0xfe, 0x00},
|
||||
{0x05, 0x01}, {0x06, 0x32}, {0x07, 0x00}, {0x08, 0x0c}, {0xfe, 0x01}, {0x25, 0x00}, {0x26, 0x3c},
|
||||
{0x27, 0x01}, {0x28, 0xdc}, {0x29, 0x01}, {0x2a, 0xe0}, {0x2b, 0x01}, {0x2c, 0xe0}, {0x2d, 0x01},
|
||||
{0x2e, 0xe0}, {0x3c, 0x20}, -- SPI配置
|
||||
{0xfe, 0x03}, {0x52, 0xa2}, {0x53, 0x24}, {0x54, 0x20}, {0x55, 0x00}, {0x59, 0x1f}, {0x5a, 0x00}, {0x5b, 0x80},
|
||||
{0x5c, 0x02}, {0x5d, 0xe0}, {0x5e, 0x01}, {0x51, 0x03}, {0x64, 0x04}, {0xfe, 0x00}, {0x44, 0x02}}
|
||||
}
|
||||
return config
|
||||
60
4G/tools/resource/soc_script/v2025.12.31.22/lib/gc032a.lua
Normal file
60
4G/tools/resource/soc_script/v2025.12.31.22/lib/gc032a.lua
Normal file
@@ -0,0 +1,60 @@
|
||||
local config = {
|
||||
mode = 1,
|
||||
is_msb = 1,
|
||||
rx_bit = 2,
|
||||
seq_type = 1,
|
||||
is_ddr = 0x00010101,
|
||||
i2c_slave_addr = 0x21,
|
||||
width = 640,
|
||||
height = 480,
|
||||
init_cmds = {{0xf3, 0x83}, {0xf5, 0x08}, {0xf7, 0x01}, {0xf8, 0x01}, {0xf9, 0x4e}, {0xfa, 0x00}, {0xfc, 0x02},
|
||||
{0xfe, 0x02}, {0x81, 0x03}, {0xfe, 0x00}, {0x77, 0x64}, {0x78, 0x40}, {0x79, 0x60}, {0xfe, 0x00},
|
||||
{0x03, 0x01}, {0x04, 0xcb}, {0x05, 0x01}, {0x06, 0xb2}, {0x07, 0x00}, {0x08, 0x10}, {0x0a, 0x00},
|
||||
{0x0c, 0x00}, {0x0d, 0x01}, {0x0e, 0xe8}, {0x0f, 0x02}, {0x10, 0x88}, {0x17, 0x54}, {0x19, 0x08},
|
||||
{0x1a, 0x0a}, {0x1f, 0x40}, {0x20, 0x30}, {0x2e, 0x80}, {0x2f, 0x2b}, {0x30, 0x1a}, {0xfe, 0x02},
|
||||
{0x03, 0x02}, {0x05, 0xd7}, {0x06, 0x60}, {0x08, 0x80}, {0x12, 0x89}, {0xfe, 0x03}, {0x52, 0xba},
|
||||
{0x53, 0x24}, {0x54, 0x20}, {0x55, 0x00}, {0x59, 0x1f}, {0x5a, 0x00}, {0x5b, 0x80}, {0x5c, 0x02},
|
||||
{0x5d, 0xe0}, {0x5e, 0x01}, {0x51, 0x03}, {0x64, 0x04}, {0xfe, 0x00}, {0xfe, 0x00}, {0x18, 0x02},
|
||||
{0xfe, 0x02}, {0x40, 0x22}, {0x45, 0x00}, {0x46, 0x00}, {0x49, 0x20}, {0x4b, 0x3c}, {0x50, 0x20},
|
||||
{0x42, 0x10}, {0xfe, 0x01}, {0x0a, 0xc5}, {0x45, 0x00}, {0xfe, 0x00}, {0x40, 0xff}, {0x41, 0x25},
|
||||
{0x42, 0xef}, {0x43, 0x10}, {0x44, 0x83}, {0x46, 0x22}, {0x49, 0x03}, {0x52, 0x02}, {0x54, 0x00},
|
||||
{0xfe, 0x02}, {0x22, 0xf6}, {0xfe, 0x01}, {0xc1, 0x38}, {0xc2, 0x4c}, {0xc3, 0x00}, {0xc4, 0x2c},
|
||||
{0xc5, 0x24}, {0xc6, 0x18}, {0xc7, 0x28}, {0xc8, 0x11}, {0xc9, 0x15}, {0xca, 0x20}, {0xdc, 0x7a},
|
||||
{0xdd, 0xa0}, {0xde, 0x80}, {0xdf, 0x88}, {0xfe, 0x01}, {0x50, 0xc1}, {0x56, 0x34}, {0x58, 0x04},
|
||||
{0x65, 0x06}, {0x66, 0x0f}, {0x67, 0x04}, {0x69, 0x20}, {0x6a, 0x40}, {0x6b, 0x81}, {0x6d, 0x12},
|
||||
{0x6e, 0xc0}, {0x7b, 0x2a}, {0x7c, 0x0c}, {0xfe, 0x01}, {0x90, 0xe3}, {0x91, 0xc2}, {0x92, 0xff},
|
||||
{0x93, 0xe3}, {0x95, 0x1c}, {0x96, 0xff}, {0x97, 0x44}, {0x98, 0x1c}, {0x9a, 0x44}, {0x9b, 0x1c},
|
||||
{0x9c, 0x64}, {0x9d, 0x44}, {0x9f, 0x71}, {0xa0, 0x64}, {0xa1, 0x00}, {0xa2, 0x00}, {0x86, 0x00},
|
||||
{0x87, 0x00}, {0x88, 0x00}, {0x89, 0x00}, {0xa4, 0xc2}, {0xa5, 0x9b}, {0xa6, 0xc8}, {0xa7, 0x92},
|
||||
{0xa9, 0xc9}, {0xaa, 0x96}, {0xab, 0xa9}, {0xac, 0x99}, {0xae, 0xce}, {0xaf, 0xa9}, {0xb0, 0xcf},
|
||||
{0xb1, 0x9d}, {0xb3, 0xcf}, {0xb4, 0xac}, {0xb5, 0x00}, {0xb6, 0x00}, {0x8b, 0x00}, {0x8c, 0x00},
|
||||
{0x8d, 0x00}, {0x8e, 0x00}, {0x94, 0x55}, {0x99, 0xa6}, {0x9e, 0xaa}, {0xa3, 0x0a}, {0x8a, 0x00},
|
||||
{0xa8, 0x55}, {0xad, 0x55}, {0xb2, 0x55}, {0xb7, 0x05}, {0x8f, 0x00}, {0xb8, 0xc7}, {0xb9, 0xa0},
|
||||
|
||||
{0xfe, 0x01}, {0xd0, 0x40}, {0xd1, 0x00}, {0xd2, 0x00}, {0xd3, 0xfa}, {0xd4, 0x4a}, {0xd5, 0x02},
|
||||
|
||||
{0xd6, 0x44}, {0xd7, 0xfa}, {0xd8, 0x04}, {0xd9, 0x08}, {0xda, 0x5c}, {0xdb, 0x02}, {0xfe, 0x00},
|
||||
|
||||
{0xfe, 0x00}, {0xba, 0x00}, {0xbb, 0x04}, {0xbc, 0x0a}, {0xbd, 0x0e}, {0xbe, 0x22}, {0xbf, 0x30},
|
||||
{0xc0, 0x3d}, {0xc1, 0x4a}, {0xc2, 0x5d}, {0xc3, 0x6b}, {0xc4, 0x7a}, {0xc5, 0x85}, {0xc6, 0x90},
|
||||
{0xc7, 0xa5}, {0xc8, 0xb5}, {0xc9, 0xc2}, {0xca, 0xcc}, {0xcb, 0xd5}, {0xcc, 0xde}, {0xcd, 0xea},
|
||||
{0xce, 0xf5}, {0xcf, 0xff}, {0xfe, 0x00}, {0x5a, 0x08}, {0x5b, 0x0f}, {0x5c, 0x15}, {0x5d, 0x1c},
|
||||
{0x5e, 0x28}, {0x5f, 0x36}, {0x60, 0x45}, {0x61, 0x51}, {0x62, 0x6a}, {0x63, 0x7d}, {0x64, 0x8d},
|
||||
{0x65, 0x98}, {0x66, 0xa2}, {0x67, 0xb5}, {0x68, 0xc3}, {0x69, 0xcd}, {0x6a, 0xd4}, {0x6b, 0xdc},
|
||||
{0x6c, 0xe3}, {0x6d, 0xf0}, {0x6e, 0xf9}, {0x6f, 0xff}, {0xfe, 0x00}, {0x70, 0x50}, {0xfe, 0x00},
|
||||
{0x4f, 0x01}, {0xfe, 0x01}, {0x0c, 0x01}, {0x0d, 0x00}, {0x12, 0xa0}, {0x13, 0x38}, {0x1f, 0x40},
|
||||
{0x20, 0x40}, {0x23, 0x0a}, {0x26, 0x9a}, {0x3e, 0x20}, {0x3f, 0x2d}, {0x40, 0x40}, {0x41, 0x5b},
|
||||
{0x42, 0x82}, {0x43, 0xb7}, {0x04, 0x0a}, {0x02, 0x79}, {0x03, 0xc0}, {0xfe, 0x01}, {0xcc, 0x08},
|
||||
{0xcd, 0x08}, {0xce, 0xa4}, {0xcf, 0xec}, {0xfe, 0x00}, {0x81, 0xb8}, {0x82, 0x04}, {0x83, 0x10},
|
||||
{0x84, 0x01}, {0x86, 0x50}, {0x87, 0x18}, {0x88, 0x10}, {0x89, 0x70}, {0x8a, 0x20}, {0x8b, 0x10},
|
||||
{0x8c, 0x08}, {0x8d, 0x0a}, {0xfe, 0x00}, {0x8f, 0xaa}, {0x90, 0x1c}, {0x91, 0x52}, {0x92, 0x03},
|
||||
{0x93, 0x03}, {0x94, 0x08}, {0x95, 0x6a}, {0x97, 0x00}, {0x98, 0x00}, {0xfe, 0x00}, {0x9a, 0x30},
|
||||
{0x9b, 0x50}, {0xa1, 0x30}, {0xa2, 0x66}, {0xa4, 0x28}, {0xa5, 0x30}, {0xaa, 0x28}, {0xac, 0x32},
|
||||
|
||||
{0xfe, 0x00}, {0xd1, 0x3f}, {0xd2, 0x3f}, {0xd3, 0x38}, {0xd6, 0xf4}, {0xd7, 0x1d}, {0xdd, 0x72},
|
||||
{0xde, 0x84}, {0xfe, 0x00}, {0x05, 0x01}, {0x06, 0xad}, {0x07, 0x00}, {0x08, 0x10}, {0xfe, 0x01},
|
||||
{0x25, 0x00}, {0x26, 0x4d}, {0x27, 0x01}, {0x28, 0xce}, {0x29, 0x01}, {0x2a, 0xce}, {0x2b, 0x01},
|
||||
{0x2c, 0xce}, {0x2d, 0x01}, {0x2e, 0xce}, {0x2f, 0x01}, {0x30, 0xce}, {0x31, 0x01}, {0x32, 0xce},
|
||||
{0x33, 0x01}, {0x34, 0xce}, {0x3c, 0x10}, {0xfe, 0x00}, {0x44, 0x03}}
|
||||
}
|
||||
return config
|
||||
79
4G/tools/resource/soc_script/v2025.12.31.22/lib/httpdns.lua
Normal file
79
4G/tools/resource/soc_script/v2025.12.31.22/lib/httpdns.lua
Normal file
@@ -0,0 +1,79 @@
|
||||
--[[
|
||||
@module httpdns
|
||||
@summary 使用Http进行域名解析
|
||||
@version 1.0
|
||||
@date 2023.07.13
|
||||
@author wendal
|
||||
@usage
|
||||
-- 通过阿里DNS获取结果
|
||||
local ip = httpdns.ali("air32.cn")
|
||||
log.info("httpdns", "air32.cn", ip)
|
||||
|
||||
-- 通过腾讯DNS获取结果
|
||||
local ip = httpdns.tx("air32.cn")
|
||||
log.info("httpdns", "air32.cn", ip)
|
||||
]]
|
||||
|
||||
local httpdns = {}
|
||||
|
||||
--[[
|
||||
通过阿里DNS获取结果
|
||||
@api httpdns.ali(domain_name, opts)
|
||||
@string 域名
|
||||
@table opts 可选参数, 与http.request的opts参数一致
|
||||
@return string ip地址
|
||||
@usage
|
||||
local ip = httpdns.ali("air32.cn")
|
||||
log.info("httpdns", "air32.cn", ip)
|
||||
-- 指定网络适配器
|
||||
local ip = httpdns.ali("air32.cn", {adapter=socket.LWIP_STA, timeout=3000})
|
||||
log.info("httpdns", "air32.cn", ip)
|
||||
]]
|
||||
function httpdns.ali(n, opts)
|
||||
if n == nil then return end
|
||||
if opts == nil then
|
||||
opts = {timeout=3000}
|
||||
elseif opts.timeout == nil then
|
||||
opts.timeout = 3000
|
||||
end
|
||||
local code, _, body = http.request("GET", "http://223.5.5.5/resolve?short=1&name=" .. tostring(n), nil, nil, opts).wait()
|
||||
if code == 200 and body and #body > 2 then
|
||||
local jdata = json.decode(body)
|
||||
if jdata and #jdata > 0 then
|
||||
return jdata[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
通过腾讯DNS获取结果
|
||||
@api httpdns.tx(domain_name, opts)
|
||||
@string 域名
|
||||
@table opts 可选参数, 与http.request的opts参数一致
|
||||
@return string ip地址
|
||||
@usage
|
||||
local ip = httpdns.tx("air32.cn")
|
||||
log.info("httpdns", "air32.cn", ip)
|
||||
|
||||
-- 指定网络适配器
|
||||
local ip = httpdns.tx("air32.cn", {adapter=socket.LWIP_STA, timeout=3000})
|
||||
log.info("httpdns", "air32.cn", ip)
|
||||
]]
|
||||
function httpdns.tx(n, opts)
|
||||
if n == nil then return end
|
||||
if opts == nil then
|
||||
opts = {timeout=3000}
|
||||
elseif opts.timeout == nil then
|
||||
opts.timeout = 3000
|
||||
end
|
||||
local code, _, body = http.request("GET", "http://119.29.29.29/d?dn=" .. tostring(n), nil, nil, opts).wait()
|
||||
if code == 200 and body and #body > 2 then
|
||||
local tmp = body:split(",")
|
||||
if tmp then return tmp[1] end
|
||||
end
|
||||
end
|
||||
|
||||
return httpdns
|
||||
|
||||
|
||||
714
4G/tools/resource/soc_script/v2025.12.31.22/lib/httpplus.lua
Normal file
714
4G/tools/resource/soc_script/v2025.12.31.22/lib/httpplus.lua
Normal file
@@ -0,0 +1,714 @@
|
||||
--[[
|
||||
@module httpplus
|
||||
@summary http库的补充
|
||||
@version 1.0
|
||||
@date 2023.11.23
|
||||
@author wendal
|
||||
@demo httpplus
|
||||
@tag LUAT_USE_NETWORK
|
||||
@usage
|
||||
-- 本库支持的功能有:
|
||||
-- 1. 大文件上传的问题,不限大小
|
||||
-- 2. 任意长度的header设置
|
||||
-- 3. 任意长度的body设置
|
||||
-- 4. 鉴权URL自动识别
|
||||
-- 5. body使用zbuff返回,可直接传输给uart等库
|
||||
|
||||
-- 与http库的差异
|
||||
-- 1. 不支持文件下载
|
||||
-- 2. 不支持fota
|
||||
|
||||
-- 支持 http 1.0 和 http 1.1, 不支持http2.0
|
||||
-- 支持 GET/POST/PUT/DELETE/HEAD 等常用方法,也支持自定义method
|
||||
-- 支持 HTTP 和 HTTPS 协议
|
||||
-- 支持 IPv4 和 IPv6
|
||||
-- 支持 HTTP 鉴权
|
||||
-- 支持 multipart/form-data 上传文件和表单
|
||||
-- 支持 application/x-www-form-urlencoded 上传表单
|
||||
-- 支持 application/json 上传json数据
|
||||
-- 支持 自定义 body 上传任意数据
|
||||
-- 支持 自定义 headers
|
||||
-- 支持 大文件上传,不限大小
|
||||
-- 支持 zbuff 作为 body 上传和响应返回
|
||||
-- 支持 bodyfile 直接把文件内容作为body上传
|
||||
-- 支持 上传时使用自定义缓冲区, 2025.9.25 新增
|
||||
]]
|
||||
|
||||
|
||||
local httpplus = {}
|
||||
local TAG = "httpplus"
|
||||
|
||||
local function http_opts_parse(opts)
|
||||
if not opts then
|
||||
log.error(TAG, "opts不能为nil")
|
||||
return -100, "opts不能为nil"
|
||||
end
|
||||
if not opts.url or #opts.url < 5 then
|
||||
log.error(TAG, "URL不存在或者太短了", opts.url)
|
||||
return -100, "URL不存在或者太短了"
|
||||
end
|
||||
if not opts.headers then
|
||||
opts.headers = {}
|
||||
end
|
||||
|
||||
if opts.debug or httpplus.debug then
|
||||
if not opts.log then
|
||||
opts.log = log.debug
|
||||
end
|
||||
else
|
||||
opts.log = function()
|
||||
-- log.info(TAG, "无日志")
|
||||
end
|
||||
end
|
||||
|
||||
-- 解析url
|
||||
-- 先判断协议是否加密
|
||||
local is_ssl = false
|
||||
local tmp = ""
|
||||
if opts.url:startsWith("https://") then
|
||||
is_ssl = true
|
||||
tmp = opts.url:sub(9)
|
||||
elseif opts.url:startsWith("http://") then
|
||||
tmp = opts.url:sub(8)
|
||||
else
|
||||
tmp = opts.url
|
||||
end
|
||||
-- log.info("http分解阶段1", is_ssl, tmp)
|
||||
-- 然后判断host段
|
||||
local uri = ""
|
||||
local host = ""
|
||||
local port = 0
|
||||
if tmp:find("/") then
|
||||
uri = tmp:sub((tmp:find("/"))) -- 注意find会返回多个值
|
||||
tmp = tmp:sub(1, tmp:find("/") - 1)
|
||||
else
|
||||
uri = "/"
|
||||
end
|
||||
-- log.info("http分解阶段2", is_ssl, tmp, uri)
|
||||
if tmp == nil or #tmp == 0 then
|
||||
log.error(TAG, "非法的URL", opts.url)
|
||||
return -101, "非法的URL"
|
||||
end
|
||||
-- 有无鉴权信息
|
||||
if tmp:find("@") then
|
||||
local auth = tmp:sub(1, tmp:find("@") - 1)
|
||||
if not opts.headers["Authorization"] then
|
||||
opts.headers["Authorization"] = "Basic " .. auth:toBase64()
|
||||
end
|
||||
-- log.info("http鉴权信息", auth, opts.headers["Authorization"])
|
||||
tmp = tmp:sub(tmp:find("@") + 1)
|
||||
end
|
||||
-- 解析端口
|
||||
if tmp:find(":") then
|
||||
host = tmp:sub(1, tmp:find(":") - 1)
|
||||
port = tmp:sub(tmp:find(":") + 1)
|
||||
port = tonumber(port)
|
||||
else
|
||||
host = tmp
|
||||
end
|
||||
if not port or port < 1 then
|
||||
if is_ssl then
|
||||
port = 443
|
||||
else
|
||||
port = 80
|
||||
end
|
||||
end
|
||||
-- 收尾工作
|
||||
if not opts.headers["Host"] then
|
||||
if (is_ssl and port == 443) or ((not is_ssl) and port == 80) then
|
||||
opts.headers["Host"] = host
|
||||
else
|
||||
opts.headers["Host"] = string.format("%s:%d", host, port)
|
||||
end
|
||||
end
|
||||
-- Connection 必须关闭
|
||||
opts.headers["Connection"] = "Close"
|
||||
|
||||
-- 复位一些变量,免得判断出错
|
||||
opts.is_closed = nil
|
||||
opts.body_len = 0
|
||||
|
||||
-- multipart需要boundary
|
||||
local boundary = "------------------------16ef6e68ef" .. tostring(os.time())
|
||||
opts.boundary = boundary
|
||||
opts.mp = {}
|
||||
|
||||
if opts.files then
|
||||
-- 强制设置为true
|
||||
opts.multipart = true
|
||||
end
|
||||
|
||||
-- 表单数据
|
||||
if opts.forms then
|
||||
if opts.multipart then
|
||||
for kk, vv in pairs(opts.forms) do
|
||||
local tmp = string.format("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n", boundary, kk)
|
||||
table.insert(opts.mp, {vv, tmp, "form"})
|
||||
opts.body_len = opts.body_len + #tmp + #vv + 2
|
||||
-- log.info("当前body长度", opts.body_len, "数据长度", #vv)
|
||||
end
|
||||
else
|
||||
if not opts.headers["Content-Type"] then
|
||||
opts.headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"
|
||||
end
|
||||
local buff = zbuff.create(256)
|
||||
for kk, vv in pairs(opts.forms) do
|
||||
buff:copy(nil, string.urlEncode(tostring(kk)))
|
||||
buff:copy(nil, "=")
|
||||
buff:copy(nil, string.urlEncode(tostring(vv)))
|
||||
buff:copy(nil, "&")
|
||||
end
|
||||
if buff:used() > 0 then
|
||||
buff:del(-1, 1)
|
||||
opts.body = buff
|
||||
opts.body_len = buff:used()
|
||||
opts.log(TAG, "普通表单", opts.body)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if opts.files then
|
||||
-- 强制设置为true
|
||||
opts.multipart = true
|
||||
local contentType =
|
||||
{
|
||||
txt = "text/plain", -- 文本
|
||||
jpg = "image/jpeg", -- JPG 格式图片
|
||||
jpeg = "image/jpeg", -- JPEG 格式图片
|
||||
png = "image/png", -- PNG 格式图片
|
||||
gif = "image/gif", -- GIF 格式图片
|
||||
html = "text/html", -- HTML
|
||||
json = "application/json", -- JSON
|
||||
mp4 = "video/mp4", -- MP4 格式视频
|
||||
mp3 = "audio/mp3", -- MP3 格式音频
|
||||
webm = "video/webm", -- WebM 格式视频
|
||||
}
|
||||
for kk, vv in pairs(opts.files) do
|
||||
local ct = contentType[vv:match("%.(%w+)$")] or "application/octet-stream"
|
||||
local fname = vv:match("([^/\\]+)$") or vv
|
||||
local tmp = string.format("--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n", boundary, kk, fname, ct)
|
||||
-- log.info("文件传输头", tmp)
|
||||
table.insert(opts.mp, {vv, tmp, "file"})
|
||||
opts.body_len = opts.body_len + #tmp + io.fileSize(vv) + 2
|
||||
-- log.info("当前body长度", opts.body_len, "文件长度", io.fileSize(vv), fname, ct)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 如果multipart模式
|
||||
if opts.multipart then
|
||||
-- 如果没主动设置body, 那么补个结尾
|
||||
if not opts.body then
|
||||
opts.body_len = opts.body_len + #boundary + 2 + 2 + 2
|
||||
end
|
||||
-- Content-Type没设置? 那就设置一下
|
||||
if not opts.headers["Content-Type"] then
|
||||
opts.headers["Content-Type"] = "multipart/form-data; boundary="..boundary
|
||||
end
|
||||
end
|
||||
|
||||
-- 直接设置bodyfile
|
||||
if opts.bodyfile then
|
||||
local fd = io.open(opts.bodyfile, "rb")
|
||||
if not fd then
|
||||
log.error("httpplus", "bodyfile失败,文件不存在", opts.bodyfile)
|
||||
return -104, "bodyfile失败,文件不存在"
|
||||
end
|
||||
fd:close()
|
||||
opts.body_len = io.fileSize(opts.bodyfile)
|
||||
end
|
||||
|
||||
-- 有设置body, 而且没设置长度
|
||||
if opts.body and (not opts.body_len or opts.body_len == 0) then
|
||||
-- body是zbuff的情况
|
||||
if type(opts.body) == "userdata" then
|
||||
opts.body_len = opts.body:used()
|
||||
-- body是json的情况
|
||||
elseif type(opts.body) == "table" then
|
||||
opts.body = json.encode(opts.body, "7f")
|
||||
if opts.body then
|
||||
opts.body_len = #opts.body
|
||||
if not opts.headers["Content-Type"] then
|
||||
opts.headers["Content-Type"] = "application/json;charset=UTF-8"
|
||||
opts.log(TAG, "JSON", opts.body)
|
||||
end
|
||||
end
|
||||
-- 其他情况就只能当文本了
|
||||
else
|
||||
opts.body = tostring(opts.body)
|
||||
opts.body_len = #opts.body
|
||||
end
|
||||
end
|
||||
-- 一定要设置Content-Length,而且强制覆盖客户自定义的值
|
||||
-- opts.body_len = opts.body_len or 0
|
||||
opts.headers["Content-Length"] = tostring(opts.body_len or 0)
|
||||
|
||||
-- 如果没设置method, 自动补齐
|
||||
if not opts.method or #opts.method == 0 then
|
||||
if opts.body_len > 0 then
|
||||
opts.method = "POST"
|
||||
else
|
||||
opts.method = "GET"
|
||||
end
|
||||
else
|
||||
-- 确保一定是大写字母
|
||||
opts.method = opts.method:upper()
|
||||
end
|
||||
|
||||
if opts.debug then
|
||||
opts.log(TAG, is_ssl, host, port, uri, json.encode(opts.headers))
|
||||
end
|
||||
|
||||
|
||||
-- 把剩余的属性设置好
|
||||
opts.host = host
|
||||
opts.port = port
|
||||
opts.uri = uri
|
||||
opts.is_ssl = is_ssl
|
||||
|
||||
if not opts.timeout or opts.timeout == 0 then
|
||||
opts.timeout = 30
|
||||
end
|
||||
|
||||
return -- 成功完成,不需要返回值
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function zbuff_find(buff, str)
|
||||
-- log.info("zbuff查找", buff:used(), #str)
|
||||
if buff:used() < #str then
|
||||
return
|
||||
end
|
||||
local maxoff = buff:used()
|
||||
maxoff = maxoff - #str
|
||||
local tmp = zbuff.create(#str)
|
||||
tmp:write(str)
|
||||
-- log.info("tmp数据", tmp:query():toHex())
|
||||
for i = 0, maxoff, 1 do
|
||||
local flag = true
|
||||
for j = 0, #str - 1, 1 do
|
||||
-- log.info("对比", i, j, string.char(buff[i+j]):toHex(), string.char(tmp[j]):toHex(), buff[i+j] ~= tmp[j])
|
||||
if buff[i+j] ~= tmp[j] then
|
||||
flag = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if flag then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function resp_parse(opts)
|
||||
-- log.info("这里--------")
|
||||
local header_offset = zbuff_find(opts.rx_buff, "\r\n\r\n")
|
||||
-- log.info("头部偏移量", header_offset)
|
||||
if not header_offset then
|
||||
log.warn(TAG, "没有检测到http响应头部,非法响应")
|
||||
opts.resp_code = -198
|
||||
return
|
||||
end
|
||||
local state_line_offset = zbuff_find(opts.rx_buff, "\r\n")
|
||||
local state_line = opts.rx_buff:query(0, state_line_offset)
|
||||
local tmp = state_line:split(" ")
|
||||
if not tmp or #tmp < 2 then
|
||||
log.warn(TAG, "非法的响应行", state_line)
|
||||
opts.resp_code = -197
|
||||
return
|
||||
end
|
||||
local code = tonumber(tmp[2])
|
||||
if not code then
|
||||
log.warn(TAG, "非法的响应码", tmp[2])
|
||||
opts.resp_code = -196
|
||||
return
|
||||
end
|
||||
opts.resp_code = code
|
||||
opts.resp = {
|
||||
headers = {}
|
||||
}
|
||||
opts.log(TAG, "state code", code)
|
||||
-- TODO 解析header和body
|
||||
|
||||
opts.rx_buff:del(0, state_line_offset + 2)
|
||||
-- opts.log(TAG, "剩余的响应体", opts.rx_buff:query())
|
||||
|
||||
-- 解析headers(仅按首个冒号拆分,保留值中的冒号)
|
||||
while 1 do
|
||||
local offset = zbuff_find(opts.rx_buff, "\r\n")
|
||||
if not offset then
|
||||
log.warn(TAG, "不合法的剩余headers", opts.rx_buff:query())
|
||||
break
|
||||
end
|
||||
if offset == 0 then
|
||||
-- header的最后一个空行
|
||||
opts.rx_buff:del(0, 2)
|
||||
break
|
||||
end
|
||||
local line = opts.rx_buff:query(0, offset)
|
||||
opts.rx_buff:del(0, offset + 2)
|
||||
local name, value = line:match("^([^:]+):%s*(.*)$")
|
||||
if name and value then
|
||||
name = name:trim()
|
||||
value = value:trim()
|
||||
opts.log(TAG, name, value)
|
||||
opts.resp.headers[name] = value
|
||||
else
|
||||
opts.log(TAG, "忽略非法header行", line)
|
||||
end
|
||||
end
|
||||
|
||||
-- if opts.resp_code < 299 then
|
||||
-- 解析body
|
||||
-- 有Content-Length就好办
|
||||
if opts.resp.headers["Content-Length"] then
|
||||
opts.log(TAG, "有Content-Length", opts.resp.headers["Content-Length"])
|
||||
local declared = tonumber(opts.resp.headers["Content-Length"]) or 0
|
||||
if declared > 0 and opts.rx_buff:used() >= declared then
|
||||
opts.rx_buff:resize(declared)
|
||||
end
|
||||
opts.resp.body = opts.rx_buff
|
||||
elseif opts.resp.headers["Transfer-Encoding"] == "chunked" then
|
||||
-- 解析 chunked 编码:长度行(可含分号扩展)+ 数据 + CRLF,末块长度为0
|
||||
local function zbuff_find_from(buff, str, start_off)
|
||||
local used = buff:used()
|
||||
if used - start_off < #str then return end
|
||||
local maxoff = used - #str
|
||||
local tmp2 = zbuff.create(#str)
|
||||
tmp2:write(str)
|
||||
for i = start_off, maxoff, 1 do
|
||||
local ok = true
|
||||
for j = 0, #str - 1, 1 do
|
||||
if buff[i+j] ~= tmp2[j] then ok = false; break end
|
||||
end
|
||||
if ok then return i end
|
||||
end
|
||||
end
|
||||
local body = zbuff.create(opts.rx_buff:used())
|
||||
local pos = 0
|
||||
while true do
|
||||
local line_end = zbuff_find_from(opts.rx_buff, "\r\n", pos)
|
||||
if not line_end then
|
||||
log.error(TAG, "非法的chunk长度行")
|
||||
break
|
||||
end
|
||||
local len_line = opts.rx_buff:query(pos, line_end - pos)
|
||||
local semi = len_line:find(";")
|
||||
local hex = semi and len_line:sub(1, semi - 1) or len_line
|
||||
local clen = tonumber(hex, 16)
|
||||
if not clen then
|
||||
log.error(TAG, "非法的chunk长度值", len_line)
|
||||
break
|
||||
end
|
||||
pos = line_end + 2
|
||||
if clen == 0 then
|
||||
-- 末块:忽略后续 trailers
|
||||
break
|
||||
end
|
||||
if pos + clen > opts.rx_buff:used() then
|
||||
log.error(TAG, "chunk数据长度不足")
|
||||
break
|
||||
end
|
||||
local chunk = opts.rx_buff:query(pos, clen)
|
||||
body:copy(nil, chunk)
|
||||
pos = pos + clen + 2 -- 跳过数据及其后的CRLF
|
||||
end
|
||||
opts.resp.body = body
|
||||
end
|
||||
-- end
|
||||
|
||||
-- 清空rx_buff
|
||||
opts.rx_buff = nil
|
||||
|
||||
-- 完结散花
|
||||
end
|
||||
|
||||
-- socket 回调函数
|
||||
local function http_socket_cb(opts, event)
|
||||
opts.log(TAG, "tcp.event", string.format("%08X", event))
|
||||
if event == socket.ON_LINE then
|
||||
-- TCP链接已建立, 那就可以上行了
|
||||
-- opts.state = "ON_LINE"
|
||||
sys.publish(opts.topic)
|
||||
elseif event == socket.TX_OK then
|
||||
-- 数据传输完成, 如果是文件上传就需要这个消息
|
||||
-- opts.state = "TX_OK"
|
||||
sys.publish(opts.topic)
|
||||
elseif event == socket.EVENT then
|
||||
-- 收到数据或者链接断开了, 这里总需要读取一次才知道
|
||||
local succ, data_len = socket.rx(opts.netc, opts.rx_buff)
|
||||
if succ and data_len > 0 then
|
||||
opts.log(TAG, "收到数据", data_len, "总长", opts.rx_buff:used())
|
||||
-- opts.log(TAG, "数据", opts.rx_buff:query())
|
||||
else
|
||||
if not opts.is_closed then
|
||||
opts.log(TAG, "服务器已经断开了连接或接收出错")
|
||||
opts.is_closed = true
|
||||
sys.publish(opts.topic)
|
||||
end
|
||||
end
|
||||
elseif event == socket.CLOSED then
|
||||
log.info(TAG, "连接已关闭")
|
||||
opts.is_closed = true
|
||||
sys.publish(opts.topic)
|
||||
end
|
||||
end
|
||||
|
||||
local function http_exec(opts)
|
||||
local fail_check = true
|
||||
local netc = socket.create(opts.adapter, function(sc, event)
|
||||
if opts.netc then
|
||||
return http_socket_cb(opts, event)
|
||||
end
|
||||
end)
|
||||
if not netc then
|
||||
log.error(TAG, "创建socket失败了!!")
|
||||
return -102
|
||||
end
|
||||
opts.netc = netc
|
||||
opts.rx_buff = zbuff.create(1024)
|
||||
opts.topic = tostring(netc)
|
||||
socket.config(netc, nil,nil, opts.is_ssl)
|
||||
if opts.debug_socket then
|
||||
socket.debug(netc, true)
|
||||
end
|
||||
if not socket.connect(netc, opts.host, opts.port, opts.try_ipv6) then
|
||||
log.warn(TAG, "调用socket.connect返回错误了")
|
||||
return -103, "调用socket.connect返回错误了"
|
||||
end
|
||||
local ret = sys.waitUntil(opts.topic, 5000)
|
||||
if ret == false then
|
||||
log.warn(TAG, "建立连接超时了!!!")
|
||||
return -104, "建立连接超时了!!!"
|
||||
end
|
||||
|
||||
-- 首先是头部
|
||||
local line = string.format("%s %s HTTP/1.1\r\n", opts.method:upper(), opts.uri)
|
||||
-- opts.log(TAG, line)
|
||||
socket.tx(netc, line)
|
||||
for k, v in pairs(opts.headers) do
|
||||
line = string.format("%s: %s\r\n", k, v)
|
||||
socket.tx(netc, line)
|
||||
end
|
||||
line = "\r\n"
|
||||
socket.tx(netc, line)
|
||||
|
||||
-- 然后是body
|
||||
local rbody = ""
|
||||
local write_counter = 0
|
||||
local fbuf = nil
|
||||
if (opts.mp and #opts.mp > 0) or opts.bodyfile or (opts.body and type(opts.body) == "userdata" and opts.body:used() > 4*1024) then
|
||||
if opts.upload_file_buff then
|
||||
fbuf = opts.upload_file_buff
|
||||
else
|
||||
if hmeta and hmeta.chip and hmeta.chip() == "EC718HM" then
|
||||
fbuf = zbuff.create(1024 * 128, 0, zbuff.HEAP_PSRAM) -- 718hm可以128k的,放手去用
|
||||
elseif hmeta and hmeta.chip and hmeta.chip() == "EC718PM" then
|
||||
fbuf = zbuff.create(1024 * 64, 0, zbuff.HEAP_PSRAM) -- Air8101/7258可以128k的,放手去用
|
||||
elseif hmeta and hmeta.chip and hmeta.chip() == "BK7258" then
|
||||
fbuf = zbuff.create(1024 * 128, 0, zbuff.HEAP_PSRAM) -- Air8101/7258可以128k的,放手去用
|
||||
else
|
||||
fbuf = zbuff.create(1024 * 24, 0, zbuff.HEAP_PSRAM) -- 其他模组就是小的用吧
|
||||
end
|
||||
end
|
||||
if fbuf == nil then
|
||||
fbuf = zbuff.create(1024 * 8, 0, zbuff.HEAP_PSRAM) -- 创建一个小的,作为防御
|
||||
if fbuf == nil then
|
||||
fbuf = zbuff.create(1500, 0, zbuff.HEAP_PSRAM) -- 创建一个最小的,最后防御
|
||||
end
|
||||
end
|
||||
opts.log(TAG, "上传使用缓冲区", fbuf:len())
|
||||
end
|
||||
|
||||
if opts.mp and #opts.mp > 0 then
|
||||
opts.log(TAG, "执行mulitpart上传模式")
|
||||
for k, v in pairs(opts.mp) do
|
||||
fail_check = socket.tx(netc, v[2])
|
||||
write_counter = write_counter + #v[2]
|
||||
if v[3] == "file" then
|
||||
-- log.info("写入文件数据头", v[2])
|
||||
local fd = io.open(v[1], "rb")
|
||||
-- log.info("写入文件数据", v[1])
|
||||
if fd then
|
||||
local total = 0
|
||||
while not opts.is_closed do
|
||||
fbuf:seek(0)
|
||||
local ok, flen = fd:fill(fbuf)
|
||||
if not ok or flen <= 0 then
|
||||
break
|
||||
end
|
||||
fbuf:seek(flen)
|
||||
opts.log(TAG, "写入文件数据", "长度", flen, "总计", total)
|
||||
if socket.tx(netc, fbuf) == false then
|
||||
log.warn(TAG, "socket.tx返回错误了, 传送失败!!!!")
|
||||
fail_check = false
|
||||
break
|
||||
end
|
||||
write_counter = write_counter + flen
|
||||
-- 注意, 这里要等待TX_OK事件
|
||||
sys.waitUntil(opts.topic, 1000)
|
||||
end
|
||||
fd:close()
|
||||
end
|
||||
else
|
||||
socket.tx(netc, v[1])
|
||||
write_counter = write_counter + #v[1]
|
||||
end
|
||||
socket.tx(netc, "\r\n")
|
||||
write_counter = write_counter + 2
|
||||
end
|
||||
-- rbody = rbody .. "--" .. opts.boundary .. "--\r\n"
|
||||
socket.tx(netc, "--")
|
||||
socket.tx(netc, opts.boundary)
|
||||
socket.tx(netc, "--\r\n")
|
||||
write_counter = write_counter + #opts.boundary + 2 + 2 + 2
|
||||
elseif opts.bodyfile then
|
||||
local fd = io.open(opts.bodyfile, "rb")
|
||||
-- log.info("写入文件数据", v[1])
|
||||
if fd then
|
||||
local total = 0
|
||||
while not opts.is_closed do
|
||||
fbuf:seek(0)
|
||||
local ok, flen = fd:fill(fbuf)
|
||||
if not ok or flen <= 0 then
|
||||
break
|
||||
end
|
||||
fbuf:seek(flen)
|
||||
total = total + flen
|
||||
opts.log(TAG, "写入文件数据", "长度", flen, "总计", total)
|
||||
if socket.tx(netc, fbuf) == false then
|
||||
log.warn(TAG, "socket.tx返回错误了, 传送失败!!!!")
|
||||
fail_check = false
|
||||
break
|
||||
end
|
||||
write_counter = write_counter + flen
|
||||
-- 注意, 这里要等待TX_OK事件
|
||||
sys.waitUntil(opts.topic, 1000)
|
||||
end
|
||||
fd:close()
|
||||
end
|
||||
elseif opts.body then
|
||||
if type(opts.body) == "string" and #opts.body > 0 then
|
||||
socket.tx(netc, opts.body)
|
||||
write_counter = write_counter + #opts.body
|
||||
elseif type(opts.body) == "userdata" then
|
||||
opts.log(TAG, "使用zbuff上传数据", opts.body:used())
|
||||
write_counter = write_counter + opts.body:used()
|
||||
if opts.body:used() <= 4*1024 then
|
||||
fail_check = socket.tx(netc, opts.body)
|
||||
else
|
||||
local offset = 0
|
||||
local tmpbuff = opts.body
|
||||
local tsize = tmpbuff:used()
|
||||
while offset < tsize do
|
||||
-- TODO 应该使用fbuf来做缓冲区,而不是toStr
|
||||
opts.log(TAG, "body(zbuff)分段写入", offset, tsize)
|
||||
fbuf:seek(0)
|
||||
if tsize - offset > fbuf:len() then
|
||||
fbuf:copy(0, tmpbuff, offset, fbuf:len())
|
||||
fbuf:seek(fbuf:len())
|
||||
if socket.tx(netc, fbuf) == false then
|
||||
log.warn(TAG, "socket.tx返回错误了, 传送失败!!!!")
|
||||
fail_check = false
|
||||
break
|
||||
end
|
||||
offset = offset + fbuf:len()
|
||||
sys.waitUntil(opts.topic, 1000)
|
||||
else
|
||||
fbuf:copy(0, tmpbuff, offset, tsize - offset)
|
||||
fbuf:seek(tsize - offset)
|
||||
fail_check = socket.tx(netc, fbuf)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- log.info("写入长度", "期望", opts.body_len, "实际", write_counter)
|
||||
-- log.info("hex", rbody)
|
||||
if not fail_check then
|
||||
log.warn(TAG, "发送数据失败, 终止请求")
|
||||
opts.resp_code = -199
|
||||
return
|
||||
end
|
||||
|
||||
-- 处理响应信息
|
||||
while not opts.is_closed and opts.timeout > 0 do
|
||||
log.info(TAG, "等待服务器完成响应")
|
||||
sys.waitUntil(opts.topic, 1000)
|
||||
opts.timeout = opts.timeout - 1
|
||||
end
|
||||
log.info(TAG, "服务器已完成响应,开始解析响应")
|
||||
resp_parse(opts)
|
||||
-- log.info("执行完成", "返回结果")
|
||||
end
|
||||
|
||||
--[[
|
||||
执行HTTP请求
|
||||
@api httpplus.request(opts)
|
||||
@table 请求参数,是一个table,最起码得有url属性
|
||||
@return int 响应码,服务器返回的状态码>=100, 若本地检测到错误,会返回<0的值
|
||||
@return 服务器正常响应时返回结果, 否则是错误信息或者nil
|
||||
@usage
|
||||
-- 请求参数介绍
|
||||
local opts = {
|
||||
url = "https://httpbin.air32.cn/abc", -- 必选, 目标URL
|
||||
method = "POST", -- 可选,默认GET, 如果有body,files,forms参数,会设置成POST
|
||||
headers = {}, -- 可选,自定义的额外header
|
||||
files = {}, -- 可选,键值对的形式,文件上传,若存在本参数,会强制以multipart/form-data形式上传
|
||||
forms = {}, -- 可选,键值对的形式,表单参数,若存在本参数,如果不存在files,按application/x-www-form-urlencoded上传
|
||||
body = "abc=123",-- 可选,自定义body参数, 字符串/zbuff/table均可, 但不能与files和forms同时存在
|
||||
debug = false, -- 可选,打开调试日志,默认false
|
||||
try_ipv6 = false, -- 可选,是否优先尝试ipv6地址,默认是false
|
||||
adapter = nil, -- 可选,网络适配器编号, 默认是自动选
|
||||
timeout = 30, -- 可选,读取服务器响应的超时时间,单位秒,默认30
|
||||
bodyfile = "xxx", -- 可选,直接把文件内容作为body上传, 优先级高于body参数
|
||||
upload_file_buff = zbuff.create(1024*64) -- 可选,上传时使用的缓冲区,默认会根据型号创建一个buff
|
||||
}
|
||||
|
||||
local code, resp = httpplus.request({url="https://httpbin.air32.cn/get"})
|
||||
log.info("http", code)
|
||||
-- 返回值resp的说明
|
||||
-- 情况1, code >= 100 时, resp会是个table, 包含2个元素
|
||||
if code >= 100 then
|
||||
-- headers, 是个table
|
||||
log.info("http", "headers", json.encode(resp.headers))
|
||||
-- body, 是个zbuff
|
||||
-- 通过query函数可以转为lua的string
|
||||
log.info("http", "headers", resp.body:query())
|
||||
-- 也可以通过uart.tx等支持zbuff的函数转发出去
|
||||
-- uart.tx(1, resp.body)
|
||||
end
|
||||
-- 情况2, code < 0 时, resp会是个错误信息字符串
|
||||
|
||||
-- 对upload_file_buff参数的说明
|
||||
-- 1. 如果上传的文件比较大,建议传入这个参数,避免每次都创建和销毁缓冲区
|
||||
-- 2. 如果不传入这个参数,本库会根据不同的模组型号创建一个合适的缓冲区
|
||||
-- 3. 多个同时执行的httpplus请求,不可以共用同一个缓冲区
|
||||
]]
|
||||
function httpplus.request(opts)
|
||||
-- 参数解析
|
||||
local ret = http_opts_parse(opts)
|
||||
if ret then
|
||||
return ret
|
||||
end
|
||||
|
||||
-- 执行请求
|
||||
local ret, msg = pcall(http_exec, opts)
|
||||
if opts.netc then
|
||||
-- 清理连接
|
||||
if not opts.is_closed then
|
||||
socket.close(opts.netc)
|
||||
end
|
||||
socket.release(opts.netc)
|
||||
opts.netc = nil
|
||||
end
|
||||
-- 处理响应或错误
|
||||
if not ret then
|
||||
log.error(TAG, msg)
|
||||
return -199, msg
|
||||
end
|
||||
return opts.resp_code, opts.resp
|
||||
end
|
||||
|
||||
return httpplus
|
||||
283
4G/tools/resource/soc_script/v2025.12.31.22/lib/lbsLoc.lua
Normal file
283
4G/tools/resource/soc_script/v2025.12.31.22/lib/lbsLoc.lua
Normal file
@@ -0,0 +1,283 @@
|
||||
--[[
|
||||
@module lbsLoc
|
||||
@summary lbsLoc 发送基站定位请求
|
||||
@version 1.0
|
||||
@date 2022.12.16
|
||||
@author luatos
|
||||
@usage
|
||||
-- lbsloc 是异步回调接口,
|
||||
-- lbsloc2 是是同步接口。
|
||||
-- lbsloc比lbsloc2多了一个请求地址文本的功能。
|
||||
-- lbsloc 和 lbsloc2 都是免费LBS定位的实现方式;
|
||||
-- airlbs 扩展库是收费 LBS 的实现方式。
|
||||
|
||||
--注意:因使用了sys.wait()所有api需要在协程中使用
|
||||
--用法实例
|
||||
--注意:此处的PRODUCT_KEY仅供演示使用,不能用于生产环境
|
||||
--量产项目中一定要使用自己在iot.openluat.com中创建的项目productKey,项目详情里可以查看
|
||||
--基站定位的坐标系是 WSG84
|
||||
PRODUCT_KEY = "123"
|
||||
local lbsLoc = require("lbsLoc")
|
||||
-- 功能:获取基站对应的经纬度后的回调函数
|
||||
-- 参数:-- result:number类型,0表示成功,1表示网络环境尚未就绪,2表示连接服务器失败,3表示发送数据失败,4表示接收服务器应答超时,5表示服务器返回查询失败;为0时,后面的5个参数才有意义
|
||||
-- lat:string类型,纬度,整数部分3位,小数部分7位,例如031.2425864
|
||||
-- lng:string类型,经度,整数部分3位,小数部分7位,例如121.4736522
|
||||
-- addr:目前无意义
|
||||
-- time:string类型或者nil,服务器返回的时间,6个字节,年月日时分秒,需要转为十六进制读取
|
||||
-- 第一个字节:年减去2000,例如2017年,则为0x11
|
||||
-- 第二个字节:月,例如7月则为0x07,12月则为0x0C
|
||||
-- 第三个字节:日,例如11日则为0x0B
|
||||
-- 第四个字节:时,例如18时则为0x12
|
||||
-- 第五个字节:分,例如59分则为0x3B
|
||||
-- 第六个字节:秒,例如48秒则为0x30
|
||||
-- locType:numble类型或者nil,定位类型,0表示基站定位成功,255表示WIFI定位成功
|
||||
function getLocCb(result, lat, lng, addr, time, locType)
|
||||
log.info("testLbsLoc.getLocCb", result, lat, lng)
|
||||
-- 获取经纬度成功, 坐标系WGS84
|
||||
if result == 0 then
|
||||
log.info("服务器返回的时间", time:toHex())
|
||||
log.info("定位类型,基站定位成功返回0", locType)
|
||||
end
|
||||
end
|
||||
|
||||
sys.taskInit(function()
|
||||
sys.waitUntil("IP_READY", 30000)
|
||||
while 1 do
|
||||
mobile.reqCellInfo(15)
|
||||
sys.waitUntil("CELL_INFO_UPDATE", 3000)
|
||||
lbsLoc.request(getLocCb)
|
||||
sys.wait(60000)
|
||||
end
|
||||
end)
|
||||
]]
|
||||
|
||||
local sys = require "sys"
|
||||
local sysplus = require("sysplus")
|
||||
local libnet = require("libnet")
|
||||
|
||||
local lbsLoc = {}
|
||||
local d1Name = "lbsLoc"
|
||||
|
||||
--- ASCII字符串 转化为 BCD编码格式字符串(仅支持数字)
|
||||
-- @string inStr 待转换字符串
|
||||
-- @number destLen 转换后的字符串期望长度,如果实际不足,则填充F
|
||||
-- @return string data,转换后的字符串
|
||||
-- @usage
|
||||
local function numToBcdNum(inStr,destLen)
|
||||
local l,t,num = string.len(inStr or ""),{}
|
||||
destLen = destLen or (inStr:len()+1)/2
|
||||
for i=1,l,2 do
|
||||
num = tonumber(inStr:sub(i,i+1),16)
|
||||
if i==l then
|
||||
num = 0xf0+num
|
||||
else
|
||||
num = (num%0x10)*0x10 + (num-(num%0x10))/0x10
|
||||
end
|
||||
table.insert(t,num)
|
||||
end
|
||||
|
||||
local s = string.char(unpack(t))
|
||||
|
||||
l = string.len(s)
|
||||
if l < destLen then
|
||||
s = s .. string.rep("\255",destLen-l)
|
||||
elseif l > destLen then
|
||||
s = string.sub(s,1,destLen)
|
||||
end
|
||||
|
||||
return s
|
||||
end
|
||||
|
||||
--- BCD编码格式字符串 转化为 号码ASCII字符串(仅支持数字)
|
||||
-- @string num 待转换字符串
|
||||
-- @return string data,转换后的字符串
|
||||
-- @usage
|
||||
local function bcdNumToNum(num)
|
||||
local byte,v1,v2
|
||||
local t = {}
|
||||
|
||||
for i=1,num:len() do
|
||||
byte = num:byte(i)
|
||||
v1,v2 = bit.band(byte,0x0f),bit.band(bit.rshift(byte,4),0x0f)
|
||||
|
||||
if v1 == 0x0f then break end
|
||||
table.insert(t,v1)
|
||||
|
||||
if v2 == 0x0f then break end
|
||||
table.insert(t,v2)
|
||||
end
|
||||
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
|
||||
local function netCB(msg)
|
||||
--log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
|
||||
end
|
||||
|
||||
|
||||
local function enCellInfo(s)
|
||||
local ret,t,mcc,mnc,lac,ci,rssi,k,v,m,n,cntrssi = "",{}
|
||||
for k,v in pairs(s) do
|
||||
mcc,mnc,lac,ci,rssi = v.mcc,v.mnc,v.tac,v.cid,((v.rsrq + 144) >31) and 31 or (v.rsrq + 144)
|
||||
local handle = nil
|
||||
for k,v in pairs(t) do
|
||||
if v.lac == lac and v.mcc == mcc and v.mnc == mnc then
|
||||
if #v.rssici < 8 then
|
||||
table.insert(v.rssici,{rssi=rssi,ci=ci})
|
||||
end
|
||||
handle = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not handle then
|
||||
table.insert(t,{mcc=mcc,mnc=mnc,lac=lac,rssici={{rssi=rssi,ci=ci}}})
|
||||
end
|
||||
log.debug("rssi,mcc,mnc,lac,ci", rssi,mcc,mnc,lac,ci)
|
||||
end
|
||||
for k,v in pairs(t) do
|
||||
ret = ret .. pack.pack(">HHb",v.lac,v.mcc,v.mnc)
|
||||
for m,n in pairs(v.rssici) do
|
||||
cntrssi = bit.bor(bit.lshift(((m == 1) and (#v.rssici-1) or 0),5),n.rssi or n.rsrp)
|
||||
ret = ret .. pack.pack(">bi",cntrssi,n.ci)
|
||||
end
|
||||
end
|
||||
return string.char(#t)..ret
|
||||
end
|
||||
|
||||
local function enWifiInfo(tWifi)
|
||||
local ret,cnt = "", 0
|
||||
if tWifi then
|
||||
for k,v in pairs(tWifi) do
|
||||
-- log.info("lbsLoc.enWifiInfo",k,v)
|
||||
ret = ret..pack.pack("Ab",(k:gsub(":","")):fromHex(),(v<0) and (v+255) or v)
|
||||
cnt = cnt+1
|
||||
end
|
||||
end
|
||||
return string.char(cnt)..ret
|
||||
end
|
||||
|
||||
local function enMuid() --获取模块MUID
|
||||
local muid = mobile.muid()
|
||||
return string.char(muid:len())..muid
|
||||
end
|
||||
|
||||
local function trans(str)
|
||||
local s = str
|
||||
if str:len()<10 then
|
||||
s = str..string.rep("0",10-str:len())
|
||||
end
|
||||
|
||||
return s:sub(1,3).."."..s:sub(4,10)
|
||||
end
|
||||
|
||||
|
||||
local function taskClient(cbFnc, reqAddr, timeout, productKey, host, port,reqTime, reqWifi)
|
||||
if mobile.status() == 0 then
|
||||
if not sys.waitUntil("IP_READY", timeout) then return cbFnc(1) end
|
||||
sys.wait(500)
|
||||
end
|
||||
if productKey == nil then
|
||||
productKey = ""
|
||||
end
|
||||
local retryCnt = 0
|
||||
local reqStr = pack.pack("bAbAAAAA", productKey:len(), productKey,
|
||||
(reqAddr and 2 or 0) + (reqTime and 4 or 0) + 8 +(reqWifi and 16 or 0) + 32, "",
|
||||
numToBcdNum(mobile.imei()), enMuid(),
|
||||
enCellInfo(mobile.getCellInfo()),
|
||||
enWifiInfo(reqWifi))
|
||||
log.debug("reqStr", reqStr:toHex())
|
||||
local rx_buff = zbuff.create(17)
|
||||
-- sys.wait(5000)
|
||||
while true do
|
||||
local result,succ,param
|
||||
local netc = socket.create(nil, d1Name) -- 创建socket对象
|
||||
if not netc then cbFnc(6) return end -- 创建socket失败
|
||||
socket.debug(netc, false)
|
||||
socket.config(netc, nil, true, nil)
|
||||
--result = libnet.waitLink(d1Name, 0, netc)
|
||||
result = libnet.connect(d1Name, 5000, netc, host, port)
|
||||
if result then
|
||||
while true do
|
||||
-- log.info(" lbsloc socket_service connect true")
|
||||
result = libnet.tx(d1Name, 0, netc, reqStr) ---发送数据
|
||||
if result then
|
||||
result, param = libnet.wait(d1Name, 15000 + retryCnt * 5, netc)
|
||||
if not result then
|
||||
socket.close(netc)
|
||||
socket.release(netc)
|
||||
retryCnt = retryCnt+1
|
||||
if retryCnt>=3 then return cbFnc(4) end
|
||||
break
|
||||
end
|
||||
succ, param = socket.rx(netc, rx_buff) -- 接收数据
|
||||
-- log.info("是否接收和数据长度", succ, param)
|
||||
if param ~= 0 then -- 如果接收成功
|
||||
socket.close(netc) -- 关闭连接
|
||||
socket.release(netc)
|
||||
local read_buff = rx_buff:toStr(0, param)
|
||||
rx_buff:clear()
|
||||
log.debug("lbsLoc receive", read_buff:toHex())
|
||||
if read_buff:len() >= 11 and(read_buff:byte(1) == 0 or read_buff:byte(1) == 0xFF) then
|
||||
local locType = read_buff:byte(1)
|
||||
cbFnc(0, trans(bcdNumToNum(read_buff:sub(2, 6))),
|
||||
trans(bcdNumToNum(read_buff:sub(7, 11))), reqAddr and
|
||||
read_buff:sub(13, 12 + read_buff:byte(12)) or nil,
|
||||
reqTime and read_buff:sub(reqAddr and (13 + read_buff:byte(12)) or 12, -1) or "",
|
||||
locType)
|
||||
else
|
||||
log.warn("lbsLoc.query", "根据基站查询经纬度失败")
|
||||
if read_buff:byte(1) == 2 then
|
||||
log.warn("lbsLoc.query","main.lua中的PRODUCT_KEY和此设备在iot.openluat.com中所属项目的ProductKey必须一致,请去检查")
|
||||
else
|
||||
log.warn("lbsLoc.query","基站数据库查询不到所有小区的位置信息")
|
||||
-- log.warn("lbsLoc.query","在trace中向上搜索encellinfo,然后在电脑浏览器中打开http://bs.openluat.com/,手动查找encellinfo后的所有小区位置")
|
||||
-- log.warn("lbsLoc.query","如果手动可以查到位置,则服务器存在BUG,直接向技术人员反映问题")
|
||||
-- log.warn("lbsLoc.query","如果手动无法查到位置,则基站数据库还没有收录当前设备的小区位置信息,向技术人员反馈,我们会尽快收录")
|
||||
end
|
||||
cbFnc(5)
|
||||
end
|
||||
return
|
||||
else
|
||||
socket.close(netc)
|
||||
socket.release(netc)
|
||||
retryCnt = retryCnt+1
|
||||
if retryCnt>=3 then return cbFnc(4) end
|
||||
break
|
||||
end
|
||||
else
|
||||
socket.close(netc)
|
||||
socket.release(netc)
|
||||
retryCnt = retryCnt+1
|
||||
if retryCnt>=3 then return cbFnc(3) end
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
socket.close(netc)
|
||||
socket.release(netc)
|
||||
retryCnt = retryCnt + 1
|
||||
if retryCnt >= 3 then return cbFnc(2) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
发送基站定位请求
|
||||
@api lbsLoc.request(cbFnc,reqAddr,timeout,productKey,host,port,reqTime,reqWifi)
|
||||
@function cbFnc 用户回调函数,回调函数的调用形式为:cbFnc(result,lat,lng,addr,time,locType)
|
||||
@bool reqAddr 是否请求服务器返回具体的位置字符串信息,已经不支持,填false或者nil
|
||||
@number timeout 请求超时时间,单位毫秒,默认20000毫秒
|
||||
@string productKey IOT网站上的产品KEY,如果在main.lua中定义了PRODUCT_KEY变量,则此参数可以传nil
|
||||
@string host 服务器域名, 默认 "bs.openluat.com" ,可选备用服务器(不保证可用) "bs.air32.cn"
|
||||
@string port 服务器端口,默认"12411",一般不需要设置
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
-- 提醒: 返回的坐标值, 是WGS84坐标系
|
||||
]]
|
||||
function lbsLoc.request(cbFnc,reqAddr,timeout,productKey,host,port,reqTime,reqWifi)
|
||||
sysplus.taskInitEx(taskClient, d1Name, netCB, cbFnc, reqAddr,timeout or 20000,productKey or _G.PRODUCT_KEY,host or "bs.openluat.com",port or "12411", reqTime == nil and true or reqTime,reqWifi)
|
||||
end
|
||||
|
||||
return lbsLoc
|
||||
229
4G/tools/resource/soc_script/v2025.12.31.22/lib/lbsLoc2.lua
Normal file
229
4G/tools/resource/soc_script/v2025.12.31.22/lib/lbsLoc2.lua
Normal file
@@ -0,0 +1,229 @@
|
||||
--[[
|
||||
@module lbsLoc2
|
||||
@summary 基站定位v2
|
||||
@version 1.0
|
||||
@date 2023.5.23
|
||||
@author wendal
|
||||
@demo lbsLoc2
|
||||
@usage
|
||||
-- lbsloc 是异步回调接口,
|
||||
-- lbsloc2 是是同步接口。
|
||||
-- lbsloc比lbsloc2多了一个请求地址文本的功能。
|
||||
-- lbsloc 和 lbsloc2 都是免费LBS定位的实现方式;
|
||||
-- airlbs 扩展库是收费 LBS 的实现方式。
|
||||
|
||||
-- 注意:
|
||||
-- 1. 因使用了sys.wait()所有api需要在协程中使用
|
||||
-- 2. 仅支持单基站定位, 即当前联网的基站
|
||||
-- 3. 本服务当前处于测试状态
|
||||
sys.taskInit(function()
|
||||
sys.waitUntil("IP_READY", 30000)
|
||||
-- mobile.reqCellInfo(60)
|
||||
-- sys.wait(1000)
|
||||
while mobile do -- 没有mobile库就没有基站定位
|
||||
mobile.reqCellInfo(15)
|
||||
sys.waitUntil("CELL_INFO_UPDATE", 3000)
|
||||
local lat, lng, t = lbsLoc2.request(5000)
|
||||
-- local lat, lng, t = lbsLoc2.request(5000, "bs.openluat.com")
|
||||
log.info("lbsLoc2", lat, lng, (json.encode(t or {})))
|
||||
sys.wait(60000)
|
||||
end
|
||||
end)
|
||||
]]
|
||||
|
||||
local sys = require "sys"
|
||||
|
||||
local lbsLoc2 = {}
|
||||
|
||||
local function numToBcdNum(inStr,destLen)
|
||||
local l,t,num = string.len(inStr or ""),{}
|
||||
destLen = destLen or (inStr:len()+1)/2
|
||||
for i=1,l,2 do
|
||||
num = tonumber(inStr:sub(i,i+1),16)
|
||||
if i==l then
|
||||
num = 0xf0+num
|
||||
else
|
||||
num = (num%0x10)*0x10 + (num-(num%0x10))/0x10
|
||||
end
|
||||
table.insert(t,num)
|
||||
end
|
||||
|
||||
local s = string.char(unpack(t))
|
||||
|
||||
l = string.len(s)
|
||||
if l < destLen then
|
||||
s = s .. string.rep("\255",destLen-l)
|
||||
elseif l > destLen then
|
||||
s = string.sub(s,1,destLen)
|
||||
end
|
||||
|
||||
return s
|
||||
end
|
||||
|
||||
--- BCD编码格式字符串 转化为 号码ASCII字符串(仅支持数字)
|
||||
-- @string num 待转换字符串
|
||||
-- @return string data,转换后的字符串
|
||||
-- @usage
|
||||
local function bcdNumToNum(num)
|
||||
local byte,v1,v2
|
||||
local t = {}
|
||||
|
||||
for i=1,num:len() do
|
||||
byte = num:byte(i)
|
||||
v1,v2 = bit.band(byte,0x0f),bit.band(bit.rshift(byte,4),0x0f)
|
||||
|
||||
if v1 == 0x0f then break end
|
||||
table.insert(t,v1)
|
||||
|
||||
if v2 == 0x0f then break end
|
||||
table.insert(t,v2)
|
||||
end
|
||||
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
lbsLoc2.imei = numToBcdNum(mobile.imei())
|
||||
|
||||
local function enCellInfo(s)
|
||||
-- 改造成单基站, 反正服务器也只认单基站
|
||||
local v = s[1]
|
||||
log.info("cell", json.encode(v))
|
||||
local ret = pack.pack(">HHbbi",v.tac,v.mcc,v.mnc,31,v.cid)
|
||||
return string.char(1)..ret
|
||||
end
|
||||
|
||||
local function trans(str)
|
||||
local s = str
|
||||
if str:len()<10 then
|
||||
s = str..string.rep("0",10-str:len())
|
||||
end
|
||||
|
||||
return s:sub(1,3).."."..s:sub(4,10)
|
||||
end
|
||||
|
||||
--[[
|
||||
执行定位请求
|
||||
@api lbsLoc2.request(timeout, host, port, reqTime)
|
||||
@number 请求超时时间,单位毫秒,默认15000
|
||||
@number 服务器地址,有默认值,可以是域名,一般不需要填
|
||||
@number 服务器端口,默认12411,一般不需要填
|
||||
@bool 是否要求返回服务器时间
|
||||
@return string 若成功,返回定位坐标的纬度,否则会返还nil
|
||||
@return string 若成功,返回定位坐标的经度,否则会返还nil
|
||||
@return table 服务器时间,东八区时间. 当reqTime为true且定位成功才会返回
|
||||
@usage
|
||||
-- 关于坐标系
|
||||
-- 部分情况下会返回GCJ02坐标系, 部分情况返回的是WGS84坐标
|
||||
-- 历史数据已经无法分辨具体坐标系
|
||||
-- 鉴于两种坐标系之间的误差并不大,小于基站定位本身的误差, 纠偏的意义不大
|
||||
sys.taskInit(function()
|
||||
sys.waitUntil("IP_READY", 30000)
|
||||
-- mobile.reqCellInfo(60)
|
||||
-- sys.wait(1000)
|
||||
while mobile do -- 没有mobile库就没有基站定位
|
||||
mobile.reqCellInfo(15)
|
||||
sys.waitUntil("CELL_INFO_UPDATE", 3000)
|
||||
local lat, lng, t = lbsLoc2.request(5000)
|
||||
-- local lat, lng, t = lbsLoc2.request(5000, "bs.openluat.com")
|
||||
log.info("lbsLoc2", lat, lng, (json.encode(t or {})))
|
||||
sys.wait(60000)
|
||||
end
|
||||
end)
|
||||
]]
|
||||
function lbsLoc2.request(timeout, host, port, reqTime)
|
||||
if mobile.status() == 0 then
|
||||
return
|
||||
end
|
||||
local hosts = host and {host} or {"free.bs.air32.cn", "bs.openluat.com"}
|
||||
port = port and tonumber(port) or 12411
|
||||
local sc = socket.create(nil, function(sc, event)
|
||||
-- log.info("lbsLoc", "event", event, socket.ON_LINE, socket.TX_OK, socket.EVENT)
|
||||
if event == socket.ON_LINE then
|
||||
--log.info("lbsLoc", "已连接")
|
||||
sys.publish("LBS_CONACK")
|
||||
elseif event == socket.TX_OK then
|
||||
--log.info("lbsLoc", "发送完成")
|
||||
sys.publish("LBS_TX")
|
||||
elseif event == socket.EVENT then
|
||||
--log.info("lbsLoc", "有数据来")
|
||||
sys.publish("LBS_RX")
|
||||
end
|
||||
end)
|
||||
if sc == nil then
|
||||
return
|
||||
end
|
||||
-- socket.debug(sc, true)
|
||||
socket.config(sc, nil, true)
|
||||
local rxbuff = zbuff.create(64)
|
||||
for k, rhost in pairs(hosts) do
|
||||
local reqStr = string.char(0, (reqTime and 4 or 0) +8) .. lbsLoc2.imei
|
||||
local tmp = nil
|
||||
if mobile.scell then
|
||||
local scell = mobile.scell()
|
||||
if scell and scell.mcc then
|
||||
-- log.debug("lbsLoc2", "使用当前驻网基站的信息")
|
||||
tmp = pack.pack(">bHHbbi", 1, scell.tac, scell.mcc, scell.mnc, 31, scell.eci)
|
||||
end
|
||||
end
|
||||
if tmp == nil then
|
||||
local cells = mobile.getCellInfo()
|
||||
if cells == nil or #cells == 0 then
|
||||
socket.release(sc)
|
||||
return
|
||||
end
|
||||
reqStr = reqStr .. enCellInfo(cells)
|
||||
else
|
||||
reqStr = reqStr .. tmp
|
||||
end
|
||||
-- log.debug("lbsLoc2", "待发送数据", (reqStr:toHex()))
|
||||
log.debug("lbsLoc2", rhost, port)
|
||||
if socket.connect(sc, rhost, port) and sys.waitUntil("LBS_CONACK", 1000) then
|
||||
if socket.tx(sc, reqStr) and sys.waitUntil("LBS_TX", 1000) then
|
||||
socket.wait(sc)
|
||||
if sys.waitUntil("LBS_RX", timeout or 15000) then
|
||||
local succ, data_len = socket.rx(sc, rxbuff)
|
||||
-- log.debug("lbsLoc", "rx", succ, data_len)
|
||||
if succ and data_len > 0 then
|
||||
socket.close(sc)
|
||||
break
|
||||
else
|
||||
log.debug("lbsLoc", "rx数据失败", rhost)
|
||||
end
|
||||
else
|
||||
log.debug("lbsLoc", "等待数据超时", rhost)
|
||||
end
|
||||
else
|
||||
log.debug("lbsLoc", "tx调用失败或TX_ACK超时", rhost)
|
||||
end
|
||||
else
|
||||
log.debug("lbsLoc", "connect调用失败或CONACK超时", rhost)
|
||||
end
|
||||
socket.close(sc)
|
||||
--sys.wait(100)
|
||||
end
|
||||
sys.wait(100)
|
||||
socket.release(sc)
|
||||
if rxbuff:used() > 0 then
|
||||
local resp = rxbuff:toStr(0, rxbuff:used())
|
||||
log.debug("lbsLoc2", "rx", (resp:toHex()))
|
||||
if resp:len() >= 11 and(resp:byte(1) == 0 or resp:byte(1) == 0xFF) then
|
||||
local lat = trans(bcdNumToNum(resp:sub(2, 6)))
|
||||
local lng = trans(bcdNumToNum(resp:sub(7, 11)))
|
||||
local t = nil
|
||||
if resp:len() >= 17 then
|
||||
t = {
|
||||
year=resp:byte(12) + 2000,
|
||||
month=resp:byte(13),
|
||||
day=resp:byte(14),
|
||||
hour=resp:byte(15),
|
||||
min=resp:byte(16),
|
||||
sec=resp:byte(17),
|
||||
}
|
||||
end
|
||||
return lat, lng, t
|
||||
end
|
||||
end
|
||||
rxbuff:del()
|
||||
end
|
||||
|
||||
return lbsLoc2
|
||||
145
4G/tools/resource/soc_script/v2025.12.31.22/lib/libfota.lua
Normal file
145
4G/tools/resource/soc_script/v2025.12.31.22/lib/libfota.lua
Normal file
@@ -0,0 +1,145 @@
|
||||
--[[
|
||||
@module libfota
|
||||
@summary libfota fota升级
|
||||
@version 1.0
|
||||
@date 2023.02.01
|
||||
@author Dozingfiretruck
|
||||
@demo fota
|
||||
@usage
|
||||
--注意:因使用了sys.wait()所有api需要在协程中使用
|
||||
--用法实例
|
||||
local libfota = require("libfota")
|
||||
|
||||
-- 功能:获取fota的回调函数
|
||||
-- 参数:
|
||||
-- result:number类型
|
||||
-- 0表示成功
|
||||
-- 1表示连接失败
|
||||
-- 2表示url错误
|
||||
-- 3表示服务器断开
|
||||
-- 4表示接收报文错误
|
||||
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
|
||||
function libfota_cb(result)
|
||||
log.info("fota", "result", result)
|
||||
-- fota成功
|
||||
if result == 0 then
|
||||
rtos.reboot() --如果还有其他事情要做,就不要立刻reboot
|
||||
end
|
||||
end
|
||||
|
||||
--注意!!!:使用合宙iot平台,必须用luatools量产生成的.bin文件!!! 自建服务器可使用.ota文件!!!
|
||||
--注意!!!:使用合宙iot平台,必须用luatools量产生成的.bin文件!!! 自建服务器可使用.ota文件!!!
|
||||
--注意!!!:使用合宙iot平台,必须用luatools量产生成的.bin文件!!! 自建服务器可使用.ota文件!!!
|
||||
|
||||
--下方示例为合宙iot平台,地址:http://iot.openluat.com
|
||||
libfota.request(libfota_cb)
|
||||
|
||||
--如使用自建服务器,自行更换url
|
||||
-- 对自定义服务器的要求是:
|
||||
-- 若需要升级, 响应http 200, body为升级文件的内容
|
||||
-- 若不需要升级, 响应300或以上的代码,务必注意
|
||||
libfota.request(libfota_cb,"http://xxxxxx.com/xxx/upgrade?version=" .. _G.VERSION)
|
||||
|
||||
-- 若需要定时升级
|
||||
-- 合宙iot平台
|
||||
sys.timerLoopStart(libfota.request, 4*3600*1000, libfota_cb)
|
||||
-- 自建平台
|
||||
sys.timerLoopStart(libfota.request, 4*3600*1000, libfota_cb, "http://xxxxxx.com/xxx/upgrade?version=" .. _G.VERSION)
|
||||
]]
|
||||
|
||||
local sys = require "sys"
|
||||
local sysplus = require "sysplus"
|
||||
|
||||
local libfota = {}
|
||||
|
||||
|
||||
local function fota_task(cbFnc,storge_location, len, param1,ota_url,ota_port,libfota_timeout,server_cert, client_cert, client_key, client_password, show_otaurl)
|
||||
if cbFnc == nil then
|
||||
cbFnc = function() end
|
||||
end
|
||||
-- 若ota_url没有传,那就是用合宙iot平台
|
||||
if ota_url == nil then
|
||||
if _G.PRODUCT_KEY == nil then
|
||||
-- 必须在main.lua定义 PRODUCT_KEY = "xxx"
|
||||
-- iot平台新建项目后, 项目详情中可以查到
|
||||
log.error("fota", "iot.openluat.com need PRODUCT_KEY!!!")
|
||||
cbFnc(5)
|
||||
return
|
||||
else
|
||||
local x,y,z = string.match(_G.VERSION,"(%d+).(%d+).(%d+)")
|
||||
if x and y and z then
|
||||
local query = ""
|
||||
local firmware_name = _G.PROJECT.. "_" .. rtos.firmware()
|
||||
local version = _G.VERSION
|
||||
if mobile then
|
||||
query = "imei=" .. mobile.imei()
|
||||
version = rtos.version():sub(2) .. "." .. x .. "." .. z
|
||||
firmware_name = _G.PROJECT.. "_LuatOS-SoC_" .. rtos.bsp()
|
||||
elseif wlan and wlan.getMac then
|
||||
query = "mac=" .. wlan.getMac()
|
||||
version = rtos.version():sub(2) .. "." .. x .. "." .. z
|
||||
firmware_name = _G.PROJECT.. "_LuatOS-SoC_" .. rtos.bsp()
|
||||
else
|
||||
query = "uid=" .. mcu.unique_id():toHex()
|
||||
end
|
||||
local tmp = "http://iot.openluat.com/api/site/firmware_upgrade?project_key=%s&firmware_name=%s&version=%s&%s"
|
||||
ota_url = string.format(tmp, _G.PRODUCT_KEY, firmware_name, version, query)
|
||||
else
|
||||
log.error("fota", "_G.VERSION must be xxx.yyy.zzz!!!")
|
||||
cbFnc(5)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
local ret
|
||||
local opts = {timeout = libfota_timeout}
|
||||
if fota then
|
||||
opts.fota = true
|
||||
else
|
||||
os.remove("/update.bin")
|
||||
opts.dst = "/update.bin"
|
||||
end
|
||||
if show_otaurl == nil or show_otaurl == true then
|
||||
log.info("fota.url", ota_url)
|
||||
end
|
||||
local code, headers, body = http.request("GET", ota_url, nil, nil, opts, server_cert, client_cert, client_key, client_password).wait()
|
||||
log.info("http fota", code, headers, body)
|
||||
if code == 200 or code == 206 then
|
||||
if body == 0 then
|
||||
ret = 4
|
||||
else
|
||||
ret = 0
|
||||
end
|
||||
elseif code == -4 then
|
||||
ret = 1
|
||||
elseif code == -5 then
|
||||
ret = 3
|
||||
else
|
||||
ret = 4
|
||||
end
|
||||
cbFnc(ret)
|
||||
end
|
||||
|
||||
--[[
|
||||
fota升级
|
||||
@api libfota.request(cbFnc,ota_url,storge_location, len, param1,ota_port,libfota_timeout,server_cert, client_cert, client_key, client_password)
|
||||
@function cbFnc 用户回调函数,回调函数的调用形式为:cbFnc(result) , 必须传
|
||||
@string ota_url 升级URL, 若不填则自动使用合宙iot平台
|
||||
@number/string storge_location 可选,fota数据存储的起始位置<br>如果是int,则是由芯片平台具体判断<br>如果是string,则存储在文件系统中<br>如果为nil,则由底层决定存储位置
|
||||
@number len 可选,数据存储的最大空间
|
||||
@userdata param1,可选,如果数据存储在spiflash时,为spi_device
|
||||
@number ota_port 可选,请求端口,默认80
|
||||
@number libfota_timeout 可选,请求超时时间,单位毫秒,默认30000毫秒
|
||||
@string server_cert 可选,服务器ca证书数据
|
||||
@string client_cert 可选,客户端证书数据
|
||||
@string client_key 可选,客户端私钥加密数据
|
||||
@string client_password 可选,客户端私钥口令数据
|
||||
@boolean show_otaurl 可选,是否从日志中输出打印OTA升级包的URL路径,默认会打印
|
||||
@return nil 无返回值
|
||||
]]
|
||||
function libfota.request(cbFnc,ota_url,storge_location, len, param1,ota_port,libfota_timeout,server_cert, client_cert, client_key, client_password, show_otaurl)
|
||||
sys.taskInit(fota_task, cbFnc,storge_location, len, param1,ota_url, ota_port,libfota_timeout or 180000,server_cert, client_cert, client_key, client_password, show_otaurl)
|
||||
end
|
||||
|
||||
return libfota
|
||||
|
||||
219
4G/tools/resource/soc_script/v2025.12.31.22/lib/libfota2.lua
Normal file
219
4G/tools/resource/soc_script/v2025.12.31.22/lib/libfota2.lua
Normal file
@@ -0,0 +1,219 @@
|
||||
--[[
|
||||
@module libfota2
|
||||
@summary fota升级v2
|
||||
@version 1.1
|
||||
@date 2024.11.22
|
||||
@author wendal/HH
|
||||
@demo fota2
|
||||
@usage
|
||||
--用法实例
|
||||
local libfota2 = require("libfota2")
|
||||
|
||||
-- 功能:获取fota的回调函数
|
||||
-- 参数:
|
||||
-- result:number类型
|
||||
-- 0表示成功
|
||||
-- 1表示连接失败
|
||||
-- 2表示url错误
|
||||
-- 3表示服务器断开
|
||||
-- 4表示接收报文错误
|
||||
-- 5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
|
||||
function libfota_cb(result)
|
||||
log.info("fota", "result", result)
|
||||
-- fota成功
|
||||
if result == 0 then
|
||||
rtos.reboot() --如果还有其他事情要做,自行决定reboot的时机
|
||||
end
|
||||
end
|
||||
|
||||
--下方示例为合宙iot平台,地址:http://iot.openluat.com
|
||||
libfota2.request(libfota_cb)
|
||||
|
||||
--如使用自建服务器,自行更换url
|
||||
-- 对自定义服务器的要求是:
|
||||
-- 若需要升级, 响应http 200, body为升级文件的内容
|
||||
-- 若不需要升级, 响应300或以上的代码,务必注意
|
||||
local opts = {url="http://xxxxxx.com/xxx/upgrade"}
|
||||
-- opts的详细说明, 看后面的函数API文档
|
||||
libfota2.request(libfota_cb, opts)
|
||||
|
||||
-- 若需要定时升级
|
||||
-- 合宙iot平台
|
||||
sys.timerLoopStart(libfota2.request, 4*3600*1000, libfota_cb)
|
||||
-- 自建平台
|
||||
sys.timerLoopStart(libfota2.request, 4*3600*1000, libfota_cb, opts)
|
||||
]]
|
||||
|
||||
local sys = require "sys"
|
||||
require "sysplus"
|
||||
|
||||
local libfota2 = {}
|
||||
|
||||
-- 单独判断下服务器下发的数据是不是"{"开头"}"结尾的字符串
|
||||
local function isjson(str)
|
||||
local start, _ = string.find(str, "^%{")
|
||||
local _, end_ = string.find(str, "%}$")
|
||||
return start == 1 and end_ == #str and string.sub(str, 2, #str - 1):find("%B{") == nil
|
||||
end
|
||||
|
||||
local function fota_task(cbFnc, opts)
|
||||
local ret = 0
|
||||
local url = opts.url
|
||||
local code, headers, body = http.request(opts.method, opts.url, opts.headers, opts.body, opts, opts.server_cert,
|
||||
opts.client_cert, opts.client_key, opts.client_password).wait()
|
||||
-- log.info("http fota", code, headers, body)
|
||||
if code == 200 or code == 206 then
|
||||
if body == 0 then
|
||||
ret = 4
|
||||
else
|
||||
ret = 0
|
||||
end
|
||||
elseif code == -4 then
|
||||
ret = 1
|
||||
elseif code == -5 then
|
||||
ret = 3
|
||||
else
|
||||
log.info("libfota2", code, body)
|
||||
ret = 4
|
||||
local hziot = "iot.openluat.com"
|
||||
local msg, json_body, result
|
||||
if string.find(url, hziot) then
|
||||
log.info("使用合宙服务器,接下来解析body里的code")
|
||||
json_body, result = json.decode(body)
|
||||
-- 如果json解析失败,证明服务器下发的不是json
|
||||
if result == 1 and isjson(body) then
|
||||
code = json_body["code"]
|
||||
else
|
||||
-- 这个值随便取的,只要不和其他定义重复就行
|
||||
code = 1111111111111
|
||||
end
|
||||
if code == 43 then
|
||||
log.info("请等待",
|
||||
",云平台生成差分升级包需要等待,一到三分钟后云平台生成完成差分包便可以请求成功")
|
||||
elseif code == 3 then
|
||||
log.info("无效的设备", "检查请求键名(imei小写)正确性")
|
||||
elseif code == 17 then
|
||||
log.info("无权限",
|
||||
"设备会上报imei、固件名、项目key,服务器会以此查出设备、固件、项目三 条记录,如果 这三者不在同一个用户名下,就会认为无权限。设备不在项目key对应的账户下,可寻找合宙技术支持查询该设备在哪个账户下,核实情况后可修改设备归属")
|
||||
elseif code == 21 then
|
||||
log.info("不允许升级", "请检查IOT平台,是否对应imei被禁止了升级")
|
||||
elseif code == 25 then
|
||||
log.info("无效的项目",
|
||||
"productkey不一致,检查是否存在拼写错误,检查模块是否在本人账户下,若不在本人账户下,请联系合宙工作人员处理")
|
||||
elseif code == 26 then
|
||||
log.info("无效的固件",
|
||||
"固件名称错误,项目中没有对应的固件,也有可能是用户自己修改了固件名称,可对照升级日志中设备当前固件名与升级配置中固件名是否相同(固件名称,固件功能要完全一致,只是版本号不同)")
|
||||
elseif code == 27 then
|
||||
log.info("已是最新版本",
|
||||
"1.设备的固件/脚本版本高于或等于云平台上的版本号 2.用户项目升级配置中未添加该设备 3.云平台升级配置中,是否升级配置为否")
|
||||
elseif code == 40 then
|
||||
log.info("循环升级",
|
||||
"云平台进入设备列表搜索被禁止的imei,解除禁止升级即可. 云平台防止模块在升级失败后,反复请求升级导致流量卡流量耗尽,在模块一天请求升级六次后会禁止模块升级. 可在平台解除")
|
||||
elseif code == 1111111111111 then
|
||||
log.info("云平台下发的不是json", "我看看body是个什么东西", type(body), body)
|
||||
else
|
||||
log.info("不是上面的那些错误code", code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cbFnc(ret)
|
||||
end
|
||||
|
||||
--[[
|
||||
fota升级
|
||||
@api libfota2.request(cbFnc, opts)
|
||||
@function cbFnc 用户回调函数,回调函数的调用形式为:cbFnc(result) , 必须传
|
||||
@table fota参数, 后面有详细描述
|
||||
@return nil 无返回值
|
||||
@usaga
|
||||
|
||||
-- opts参数说明, 所有参数都是可选的
|
||||
-- 1. opts.url string 升级所需要的URL, 若使用合宙iot平台,则不需要填
|
||||
-- 2. opts.version string 版本号, 默认是 BSP版本号.x.z格式
|
||||
-- 3. opts.timeout int 请求超时时间, 默认300000毫秒,单位毫秒
|
||||
-- 4. opts.project_key string 合宙IOT平台的项目key, 默认取全局变量PRODUCT_KEY. 自建服务器不用填
|
||||
-- 5. opts.imei string 设备识别码, 默认取IMEI(Cat.1模块)或WLAN MAC地址(wifi模块)或MCU唯一ID
|
||||
-- 6. opts.firmware_name string 固件名称,默认是 _G.PROJECT.. "_LuatOS-SoC_" .. rtos.bsp()
|
||||
-- 7. opts.server_cert string 服务器证书, 默认不使用
|
||||
-- 8. opts.client_cert string 客户端证书, 默认不使用
|
||||
-- 9. opts.client_key string 客户端私钥, 默认不使用
|
||||
-- 10. opts.client_password string 客户端私钥口令, 默认不使用
|
||||
-- 11. opts.method string 请求方法, 默认是GET
|
||||
-- 12. opts.headers table 额外添加的请求头,默认不需要
|
||||
-- 13. opts.body string 额外添加的请求body,默认不需要
|
||||
]]
|
||||
function libfota2.request(cbFnc, opts)
|
||||
if not opts then
|
||||
opts = {}
|
||||
end
|
||||
if fota then
|
||||
opts.fota = true
|
||||
else
|
||||
os.remove("/update.bin")
|
||||
opts.dst = "/update.bin"
|
||||
end
|
||||
if not cbFnc then
|
||||
cbFnc = function(ret)
|
||||
end
|
||||
end
|
||||
-- 处理URL
|
||||
if not opts.url then
|
||||
opts.url = "http://iot.openluat.com/api/site/firmware_upgrade?"
|
||||
end
|
||||
local query = ""
|
||||
if opts.url:sub(1, 3) ~= "###" and not opts.url_done then
|
||||
-- 补齐project_key函数
|
||||
if not opts.project_key then
|
||||
opts.project_key = _G.PRODUCT_KEY
|
||||
if not opts.project_key then
|
||||
log.error("libfota2", "iot.openluat.com need PRODUCT_KEY!!!")
|
||||
cbFnc(5)
|
||||
return
|
||||
end
|
||||
end
|
||||
-- 补齐version参数
|
||||
if not opts.version then
|
||||
local x, y, z = string.match(_G.VERSION, "(%d+).(%d+).(%d+)")
|
||||
opts.version = rtos.version():sub(2) .. "." .. x .. "." .. z
|
||||
end
|
||||
-- 补齐firmware_name参数
|
||||
if not opts.firmware_name then
|
||||
opts.firmware_name = _G.PROJECT .. "_LuatOS-SoC_" .. rtos.bsp()
|
||||
end
|
||||
-- 补齐imei参数
|
||||
if not opts.imei then
|
||||
if mobile then
|
||||
query = "imei=" .. mobile.imei()
|
||||
elseif wlan and wlan.getMac then
|
||||
query = "mac=" .. wlan.getMac()
|
||||
else
|
||||
query = "uid=" .. mcu.unique_id():toHex()
|
||||
end
|
||||
end
|
||||
|
||||
-- 然后拼接到最终的url里
|
||||
if not opts.imei then
|
||||
opts.url = string.format("%s%s&project_key=%s&firmware_name=%s&version=%s", opts.url, query, opts.project_key, opts.firmware_name, opts.version)
|
||||
else
|
||||
opts.url = string.format("%simei=%s&project_key=%s&firmware_name=%s&version=%s", opts.url, opts.imei, opts.project_key, opts.firmware_name, opts.version)
|
||||
end
|
||||
else
|
||||
if opts.url:sub(1,3)=="###" then
|
||||
opts.url = opts.url:sub(4)
|
||||
end
|
||||
end
|
||||
opts.url_done = true
|
||||
-- 处理method
|
||||
if not opts.method then
|
||||
opts.method = "GET"
|
||||
end
|
||||
log.info("libfota2.url", opts.method, opts.url)
|
||||
log.info("libfota2.imei/mac/uid", query)
|
||||
log.info("libfota2.project_key", opts.project_key)
|
||||
log.info("libfota2.firmware_name", opts.firmware_name)
|
||||
log.info("libfota2.version", opts.version)
|
||||
sys.taskInit(fota_task, cbFnc, opts)
|
||||
end
|
||||
|
||||
return libfota2
|
||||
168
4G/tools/resource/soc_script/v2025.12.31.22/lib/libnet.lua
Normal file
168
4G/tools/resource/soc_script/v2025.12.31.22/lib/libnet.lua
Normal file
@@ -0,0 +1,168 @@
|
||||
--[[
|
||||
@module libnet
|
||||
@summary libnet 在socket库基础上的同步阻塞api,socket库本身是异步非阻塞api
|
||||
@version 1.0
|
||||
@date 2023.03.16
|
||||
@author lisiqi
|
||||
]]
|
||||
|
||||
local libnet = {}
|
||||
|
||||
--[[
|
||||
阻塞等待网卡的网络连接上,只能用于sysplus.taskInitEx创建的任务函数中
|
||||
@api libnet.waitLink(taskName,timeout,...)
|
||||
@string 任务标志
|
||||
@int 超时时间,如果==0或者空,则没有超时一致等待
|
||||
@... 其他参数和socket.linkup一致
|
||||
@return boolean 失败或者超时返回false 成功返回true
|
||||
]]
|
||||
function libnet.waitLink(taskName, timeout, ...)
|
||||
local succ, result = socket.linkup(...)
|
||||
if not succ then
|
||||
return false
|
||||
end
|
||||
if not result then
|
||||
result = sysplus.waitMsg(taskName, socket.LINK, timeout)
|
||||
else
|
||||
return true
|
||||
end
|
||||
if type(result) == 'table' and result[2] == 0 then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
阻塞等待IP或者域名连接上,如果加密连接还要等握手完成,只能用于sysplus.taskInitEx创建的任务函数中
|
||||
@api libnet.connect(taskName,timeout,...)
|
||||
@string 任务标志
|
||||
@int 超时时间,如果==0或者空,则没有超时一致等待
|
||||
@... 其他参数和socket.connect一致
|
||||
@return boolean 失败或者超时返回false 成功返回true
|
||||
]]
|
||||
function libnet.connect(taskName,timeout, ... )
|
||||
local succ, result = socket.connect(...)
|
||||
if not succ then
|
||||
return false
|
||||
end
|
||||
if not result then
|
||||
result = sysplus.waitMsg(taskName, socket.ON_LINE, timeout)
|
||||
else
|
||||
return true
|
||||
end
|
||||
if type(result) == 'table' and result[2] == 0 then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
阻塞等待客户端连接上,只能用于sysplus.taskInitEx创建的任务函数中
|
||||
@api libnet.listen(taskName,timeout,...)
|
||||
@string 任务标志
|
||||
@int 超时时间,如果==0或者空,则没有超时一致等待
|
||||
@... 其他参数和socket.listen一致
|
||||
@return boolean 失败或者超时返回false 成功返回true
|
||||
]]
|
||||
function libnet.listen(taskName,timeout, ... )
|
||||
local succ, result = socket.listen(...)
|
||||
if not succ then
|
||||
return false
|
||||
end
|
||||
if not result then
|
||||
result = sysplus.waitMsg(taskName, socket.ON_LINE, timeout)
|
||||
else
|
||||
return true
|
||||
end
|
||||
if type(result) == 'table' and result[2] == 0 then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
阻塞等待数据发送完成,只能用于sysplus.taskInitEx创建的任务函数中
|
||||
@api libnet.tx(taskName,timeout,...)
|
||||
@string 任务标志
|
||||
@int 超时时间,如果==0或者空,则没有超时一直等待
|
||||
@... 其他参数和socket.tx一致
|
||||
@return boolean 失败或者超时返回false,缓冲区满了或者成功返回true
|
||||
@return boolean 缓存区是否满了
|
||||
]]
|
||||
function libnet.tx(taskName,timeout, ...)
|
||||
local succ, is_full, result = socket.tx(...)
|
||||
if not succ then
|
||||
return false, is_full
|
||||
end
|
||||
if is_full then
|
||||
return true, true
|
||||
end
|
||||
if not result then
|
||||
result = sysplus.waitMsg(taskName, socket.TX_OK, timeout)
|
||||
else
|
||||
return true, is_full
|
||||
end
|
||||
if type(result) == 'table' and result[2] == 0 then
|
||||
return true, false
|
||||
else
|
||||
return false, is_full
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
阻塞等待新的网络事件,只能用于sysplus.taskInitEx创建的任务函数中,可以通过sysplus.sendMsg(taskName,socket.EVENT,0)或者sys_send(taskName,socket.EVENT,0)强制退出
|
||||
@api libnet.wait(taskName,timeout, netc)
|
||||
@string 任务标志
|
||||
@int 超时时间,如果==0或者空,则没有超时一致等待
|
||||
@userdata socket.create返回的netc
|
||||
@return boolean 网络异常返回false,其他返回true
|
||||
@return boolean 超时返回false,有新的网络事件到返回true
|
||||
]]
|
||||
function libnet.wait(taskName,timeout, netc)
|
||||
local succ, result = socket.wait(netc)
|
||||
if not succ then
|
||||
return false,false
|
||||
end
|
||||
if not result then
|
||||
result = sysplus.waitMsg(taskName, socket.EVENT, timeout)
|
||||
else
|
||||
return true,true
|
||||
end
|
||||
if type(result) == 'table' then
|
||||
if result[2] == 0 then
|
||||
return true, true
|
||||
else
|
||||
return false, false
|
||||
end
|
||||
else
|
||||
return true, false
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
阻塞等待网络断开连接,只能用于sysplus.taskInitEx创建的任务函数中
|
||||
@api libnet.close(taskName,timeout, netc)
|
||||
@string 任务标志
|
||||
@int 超时时间,如果==0或者空,则没有超时一致等待
|
||||
@userdata socket.create返回的netc
|
||||
]]
|
||||
function libnet.close(taskName,timeout, netc)
|
||||
local succ, result = socket.discon(netc)
|
||||
if not succ then
|
||||
socket.close(netc)
|
||||
return
|
||||
end
|
||||
if not result then
|
||||
result = sysplus.waitMsg(taskName, socket.CLOSED, timeout)
|
||||
else
|
||||
socket.close(netc)
|
||||
return
|
||||
end
|
||||
socket.close(netc)
|
||||
end
|
||||
|
||||
return libnet
|
||||
251
4G/tools/resource/soc_script/v2025.12.31.22/lib/netLed.lua
Normal file
251
4G/tools/resource/soc_script/v2025.12.31.22/lib/netLed.lua
Normal file
@@ -0,0 +1,251 @@
|
||||
--[[
|
||||
@module netLed
|
||||
@summary netLed 网络状态指示灯
|
||||
@version 1.0
|
||||
@date 2023.02.21
|
||||
@author DingHeng
|
||||
@usage
|
||||
--注意:因使用了sys.wait()所有api需要在协程中使用
|
||||
-- 用法实例
|
||||
local netLed = require ("netLed")
|
||||
|
||||
local LEDA = gpio.setup(27,1,gpio.PULLUP) --LED引脚判断赋值结束
|
||||
sys.taskInit(function()
|
||||
--呼吸灯
|
||||
sys.wait(5080)--延时5秒等待网络注册
|
||||
log.info("mobile.status()", mobile.status())
|
||||
while true do
|
||||
if mobile.status() == 1 then--已注册
|
||||
sys.wait(688)
|
||||
netLed.setupBreateLed(LEDA)
|
||||
end
|
||||
end
|
||||
end)
|
||||
]]
|
||||
|
||||
netLed = {}
|
||||
-- 引用sys库
|
||||
local sys = require("sys")
|
||||
|
||||
|
||||
local simError --SIM卡状态:true为异常,false或者nil为正常
|
||||
local flyMode --是否处于飞行模式:true为是,false或者nil为否
|
||||
local gprsAttached --是否附着上GPRS网络,true为是,false或者nil为否
|
||||
local socketConnected --是否有socket连接上后台,true为是,false或者nil为否
|
||||
|
||||
--[[
|
||||
网络指示灯表示的工作状态
|
||||
NULL:功能关闭状态
|
||||
FLYMODE:飞行模式
|
||||
SIMERR:未检测到SIM卡或者SIM卡锁pin码等SIM卡异常
|
||||
IDLE:未注册GPRS网络
|
||||
GPRS:已附着GPRS数据网络
|
||||
SCK:socket已连接上后台
|
||||
]]
|
||||
local ledState = "NULL"
|
||||
local ON,OFF = 1,2
|
||||
--各种工作状态下配置的点亮、熄灭时长(单位毫秒)
|
||||
local ledBlinkTime =
|
||||
{
|
||||
NULL = {0,0xFFFF}, --常灭
|
||||
FLYMODE = {0,0xFFFF}, --常灭
|
||||
SIMERR = {300,5700}, --亮300毫秒,灭5700毫秒
|
||||
IDLE = {300,3700}, --亮300毫秒,灭3700毫秒
|
||||
GPRS = {300,700}, --亮300毫秒,灭700毫秒
|
||||
SCK = {100,100}, --亮100毫秒,灭100毫秒
|
||||
}
|
||||
|
||||
|
||||
local ledSwitch = false --网络指示灯开关,true为打开,false或者nil为关闭
|
||||
local LEDPIN = 27 --网络指示灯默认PIN脚(GPIO27)
|
||||
local lteSwitch = false --LTE指示灯开关,true为打开,false或者nil为关闭
|
||||
local LTEPIN = 26 --LTE指示灯默认PIN脚(GPIO26)
|
||||
|
||||
|
||||
--[[
|
||||
更新网络指示灯表示的工作状态
|
||||
@api netLed.setState
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
netLed.setState()
|
||||
]]
|
||||
function netLed.setState()
|
||||
log.info("netLed.setState",ledSwitch,ledState,flyMode,simError,gprsAttached,socketConnected)
|
||||
if ledSwitch then
|
||||
local newState = "IDLE"
|
||||
if flyMode then
|
||||
newState = "FLYMODE"
|
||||
elseif simError then
|
||||
newState = "SIMERR"
|
||||
elseif socketConnected then
|
||||
newState = "SCK"
|
||||
elseif gprsAttached then
|
||||
newState = "GPRS"
|
||||
end
|
||||
--指示灯状态发生变化
|
||||
if newState~=ledState then
|
||||
ledState = newState
|
||||
sys.publish("NET_LED_UPDATE")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
网络指示灯模块的运行任务
|
||||
@api netLed.taskLed(ledPinSetFunc)
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
local LEDA = gpio.setup(27,1,gpio.PULLUP) --LED引脚判断赋值结束
|
||||
netLed.taskLed(LEDA)
|
||||
]]
|
||||
function netLed.taskLed(ledPinSetFunc)
|
||||
while true do
|
||||
--log.info("netLed.taskLed",ledPinSetFunc,ledSwitch,ledState)
|
||||
if ledSwitch then
|
||||
local onTime,offTime = ledBlinkTime[ledState][ON],ledBlinkTime[ledState][OFF]
|
||||
if onTime>0 then
|
||||
ledPinSetFunc(1)
|
||||
if not sys.waitUntil("NET_LED_UPDATE", onTime) then
|
||||
if offTime>0 then
|
||||
ledPinSetFunc(0)
|
||||
sys.waitUntil("NET_LED_UPDATE", offTime)
|
||||
end
|
||||
end
|
||||
else if offTime>0 then
|
||||
ledPinSetFunc(0)
|
||||
sys.waitUntil("NET_LED_UPDATE", offTime)
|
||||
end
|
||||
end
|
||||
else
|
||||
ledPinSetFunc(0)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
LTE指示灯模块的运行任务
|
||||
@api netLed.taskLte(ledPinSetFunc)
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
local LEDA = gpio.setup(27,1,gpio.PULLUP) --LED引脚判断赋值结束
|
||||
netLed.taskLte(LEDA)
|
||||
]]
|
||||
function netLed.taskLte(ledPinSetFunc)
|
||||
while true do
|
||||
local _,arg = sys.waitUntil("LTE_LED_UPDATE")
|
||||
if lteSwitch then
|
||||
ledPinSetFunc(arg and 1 or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
配置网络指示灯和LTE指示灯并且立即执行配置后的动作
|
||||
@api netLed.setup(flag,ledpin,ltepin)
|
||||
@bool flag 是否打开网络指示灯和LTE指示灯功能,true为打开,false为关闭
|
||||
@number ledPin 控制网络指示灯闪烁的GPIO引脚,例如pio.P0_1表示GPIO1
|
||||
@number ltePin 控制LTE指示灯闪烁的GPIO引脚,例如pio.P0_4表示GPIO4
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
netLed.setup(true,27,0)
|
||||
]]
|
||||
function netLed.setup(flag,ledPin,ltePin)
|
||||
--log.info("netLed.setup",flag,pin,ledSwitch)
|
||||
local oldSwitch = ledSwitch
|
||||
if flag~=ledSwitch then
|
||||
ledSwitch = flag
|
||||
sys.publish("NET_LED_UPDATE")
|
||||
end
|
||||
if flag and not oldSwitch then
|
||||
sys.taskInit(netLed.taskLed, gpio.setup(ledPin or LEDPIN, 0))
|
||||
end
|
||||
if flag~=lteSwitch then
|
||||
lteSwitch = flag
|
||||
end
|
||||
if flag and ltePin and not oldSwitch then
|
||||
sys.taskInit(netLed.taskLte, gpio.setup(ltePin, 0))
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
配置某种工作状态下指示灯点亮和熄灭的时长(如果用户不配置,使用netLed.lua中ledBlinkTime配置的默认值)
|
||||
@api netLed.setBlinkTime(state,on,off)
|
||||
@string state 某种工作状态,仅支持"FLYMODE"、"SIMERR"、"IDLE"、"GSM"、"GPRS"、"SCK"
|
||||
@number on 指示灯点亮时长,单位毫秒,0xFFFF表示常亮,0表示常灭
|
||||
@number off 指示灯熄灭时长,单位毫秒,0xFFFF表示常灭,0表示常亮
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
netLed.setBlinkTime(("FLYMODE",1000,500) --表示飞行模式工作状态下,指示灯闪烁规律为: 亮1秒,灭8.5秒
|
||||
]]
|
||||
function netLed.setBlinkTime(state,on,off)
|
||||
if not ledBlinkTime[state] then log.error("netLed.setBlinkTime") return end
|
||||
local updated
|
||||
if on and ledBlinkTime[state][ON]~=on then
|
||||
ledBlinkTime[state][ON] = on
|
||||
updated = true
|
||||
end
|
||||
if off and ledBlinkTime[state][OFF]~=off then
|
||||
ledBlinkTime[state][OFF] = off
|
||||
updated = true
|
||||
end
|
||||
--log.info("netLed.setBlinkTime",state,on,off,updated)
|
||||
if updated then sys.publish("NET_LED_UPDATE") end
|
||||
end
|
||||
|
||||
--[[
|
||||
呼吸灯
|
||||
@api netLed.setupBreateLed(ledPin)
|
||||
@function ledPin 呼吸灯的ledPin(1)用pins.setup注册返回的方法
|
||||
@return nil 无返回值
|
||||
@usage
|
||||
local netLed = require ("netLed")
|
||||
local LEDA = gpio.setup(27,1,gpio.PULLUP) --LED引脚判断赋值结束
|
||||
sys.taskInit(function()
|
||||
--呼吸灯
|
||||
sys.wait(5080)--延时5秒等待网络注册
|
||||
log.info("mobile.status()", mobile.status())
|
||||
while true do
|
||||
if mobile.status() == 1 then--已注册
|
||||
sys.wait(688)
|
||||
netLed.setupBreateLed(LEDA)
|
||||
end
|
||||
end
|
||||
end)
|
||||
]]
|
||||
function netLed.setupBreateLed(ledPin)
|
||||
-- 呼吸灯的状态、PWM周期
|
||||
local bLighting, bDarking, LED_PWM = false, true, 18
|
||||
if bLighting then
|
||||
for i = 1, LED_PWM - 1 do
|
||||
ledPin(0)
|
||||
sys.wait(i)
|
||||
ledPin(1)
|
||||
sys.wait(LED_PWM - i)
|
||||
end
|
||||
bLighting = false
|
||||
bDarking = true
|
||||
ledPin(0)
|
||||
sys.wait(700)
|
||||
end
|
||||
if bDarking then
|
||||
for i = 1, LED_PWM - 1 do
|
||||
ledPin(0)
|
||||
sys.wait(LED_PWM - i)
|
||||
ledPin(1)
|
||||
sys.wait(i)
|
||||
end
|
||||
bLighting = true
|
||||
bDarking = false
|
||||
ledPin(1)
|
||||
sys.wait(700)
|
||||
end
|
||||
end
|
||||
|
||||
sys.subscribe("FLYMODE", function(mode) if flyMode~=mode then flyMode=mode netLed.setState() end end)
|
||||
sys.subscribe("SIM_IND", function(para) if simError~=(para~="RDY") and simError~=(para~="GET_NUMBER") then simError=(para~="RDY") netLed.setState() end log.info("sim status", para) end)
|
||||
sys.subscribe("IP_LOSE", function() if gprsAttached then gprsAttached=false netLed.setState() end log.info("mobile", "IP_LOSE", (adapter or -1) == socket.LWIP_GP) end)
|
||||
sys.subscribe("IP_READY", function(ip, adapter) if gprsAttached~=adapter then gprsAttached=adapter netLed.setState() end log.info("mobile", "IP_READY", ip, (adapter or -1) == socket.LWIP_GP) end)
|
||||
sys.subscribe("SOCKET_ACTIVE", function(active) if socketConnected~=active then socketConnected=active netLed.setState() end end)
|
||||
|
||||
return netLed
|
||||
@@ -0,0 +1,26 @@
|
||||
-- screen_data_table.lua )
|
||||
-- 此文件只包含屏幕相关配置数据
|
||||
|
||||
local screen_data_table = {
|
||||
lcdargs = {
|
||||
LCD_MODEL = "AirLCD_1001",
|
||||
pin_vcc = 24,
|
||||
pin_rst = 36,
|
||||
pin_pwr = 25,
|
||||
pin_pwm = 2,
|
||||
port = lcd.HWID_0,
|
||||
direction = 0,
|
||||
w = 320,
|
||||
h = 480,
|
||||
xoffset = 0,
|
||||
yoffset = 0,
|
||||
},
|
||||
touch = {
|
||||
TP_MODEL = "Air780EHM_LCD_4", -- 触摸芯片型号
|
||||
i2c_id = 1, -- I2C总线ID
|
||||
pin_rst = 255, -- 触摸芯片复位引脚(非必须)
|
||||
pin_int = 22 -- 触摸芯片中断引脚
|
||||
},
|
||||
}
|
||||
|
||||
return screen_data_table
|
||||
101
4G/tools/resource/soc_script/v2025.12.31.22/lib/udpsrv.lua
Normal file
101
4G/tools/resource/soc_script/v2025.12.31.22/lib/udpsrv.lua
Normal file
@@ -0,0 +1,101 @@
|
||||
--[[
|
||||
@module udpsrv
|
||||
@summary UDP服务器
|
||||
@version 1.0
|
||||
@date 2023.7.28
|
||||
@author wendal
|
||||
@demo socket
|
||||
@tag LUAT_USE_NETWORK
|
||||
@usage
|
||||
-- 具体用法请查
|
||||
阅demo
|
||||
]]
|
||||
|
||||
local sys = require "sys"
|
||||
|
||||
local udpsrv = {}
|
||||
local srvs = {}
|
||||
|
||||
--[[
|
||||
创建UDP服务器
|
||||
@api udpsrv.create(port, topic, adapter)
|
||||
@int 端口号, 必填, 必须大于0小于65525
|
||||
@string 收取UDP数据的topic,必填
|
||||
@int 网络适配编号, 默认为nil,可选
|
||||
@return table UDP服务的实体, 若创建失败会返回nil
|
||||
]]
|
||||
function udpsrv.create(port, topic, adapter)
|
||||
local srv = {}
|
||||
-- udpsrv.port = port
|
||||
-- srv.topic = topic
|
||||
srv.rxbuff = zbuff.create(1500)
|
||||
local sc = socket.create(adapter, function(sc, event)
|
||||
-- log.info("udpsrv", sc, event, "EVENT", socket.EVENT, "CLOSED", socket.CLOSED)
|
||||
if event == socket.EVENT then
|
||||
local rxbuff = srv.rxbuff
|
||||
while 1 do
|
||||
local succ, data_len, remote_ip, remote_port = socket.rx(sc, rxbuff)
|
||||
-- log.info("udpsrv", "???", succ, data_len, remote_ip, remote_port)
|
||||
if succ and data_len and data_len > 0 then
|
||||
local resp = rxbuff:toStr(0, rxbuff:used())
|
||||
rxbuff:del()
|
||||
if remote_ip and #remote_ip == 5 then
|
||||
local ip1,ip2,ip3,ip4 = remote_ip:byte(2),remote_ip:byte(3),remote_ip:byte(4),remote_ip:byte(5)
|
||||
remote_ip = string.format("%d.%d.%d.%d", ip1, ip2, ip3, ip4)
|
||||
else
|
||||
remote_ip = nil
|
||||
end
|
||||
sys.publish(topic, resp, remote_ip, remote_port)
|
||||
else
|
||||
if not succ then
|
||||
socket.close(sc)
|
||||
socket.connect(sc, "255.255.255.255", 0)
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif event == socket.CLOSED then
|
||||
log.info("dhcpsrv", "网络中断,执行关闭流程")
|
||||
socket.close(self.sc)
|
||||
end
|
||||
end)
|
||||
if sc == nil then
|
||||
return
|
||||
end
|
||||
srv.sc = sc
|
||||
-- socket.debug(sc, true)
|
||||
socket.config(sc, port, true)
|
||||
if socket.connect(sc, "255.255.255.255", 0) then
|
||||
srv.send = function(self, data, ip, port)
|
||||
if self.sc and data then
|
||||
-- log.info("why?", self.sc, data, ip, port)
|
||||
return socket.tx(self.sc, data, ip, port)
|
||||
end
|
||||
end
|
||||
srv.close = function(self)
|
||||
socket.close(self.sc)
|
||||
-- sys.wait(200)
|
||||
socket.release(self.sc)
|
||||
srvs[self.sc] = nil
|
||||
self.sc = nil
|
||||
end
|
||||
srvs[srv.sc] = true
|
||||
-- log.info("udpsrv", "监听开始")
|
||||
return srv
|
||||
end
|
||||
socket.close(sc)
|
||||
-- sys.wait(200)
|
||||
socket.release(sc)
|
||||
-- log.info("udpsrv", "监听失败")
|
||||
end
|
||||
|
||||
sys.subscribe("IP_READY", function()
|
||||
for sc, value in pairs(srvs) do
|
||||
log.info("udpsrv", "自动重连udpsrv", sc)
|
||||
if sc then
|
||||
socket.connect(sc, "255.255.255.255", 0)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
return udpsrv
|
||||
222
4G/tools/resource/soc_script/v2025.12.31.22/lib/xmodem.lua
Normal file
222
4G/tools/resource/soc_script/v2025.12.31.22/lib/xmodem.lua
Normal file
@@ -0,0 +1,222 @@
|
||||
--[[
|
||||
@module xmodem
|
||||
@summary xmodem 协议
|
||||
@version 1.0
|
||||
@date 2025.10.17
|
||||
@author Dozingfiretruck
|
||||
@usage
|
||||
--加载xmodem模块
|
||||
xmodem=require ("xmodem")
|
||||
|
||||
--设置默认filepath为脚本区的send.bin文件
|
||||
local filepath="/luadb/send.bin"
|
||||
|
||||
local taskName = "xmodem_run"
|
||||
|
||||
|
||||
local uart_id = 1 --串口号
|
||||
|
||||
local baudrate = 115200 --波特率
|
||||
|
||||
local file_path=filepath --文件路径
|
||||
|
||||
local send_type=true --true表示单次发送128字节,false表示单次发送1024字节
|
||||
|
||||
local inform_data="wait C" --发送前提示信息,告知对方要发送C字符来接收文件
|
||||
|
||||
-- 处理未识别的消息
|
||||
local function xmodem_run_cb(msg)
|
||||
log.info("xmodem_run_cb", msg[1], msg[2], msg[3], msg[4])
|
||||
end
|
||||
|
||||
--http获取文件函数
|
||||
local function http_recived_cb()
|
||||
while not socket.adapter(socket.dft()) do
|
||||
log.warn("httpplus_app_task_func", "wait IP_READY", socket.dft())
|
||||
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
|
||||
-- 或者等待1秒超时退出阻塞等待状态;
|
||||
-- 注意:此处的1000毫秒超时不要修改的更长;
|
||||
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
|
||||
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
|
||||
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
|
||||
sys.waitUntil("IP_READY", 1000)
|
||||
end
|
||||
local path = "/send.bin"
|
||||
-- 以下链接仅用于测试,禁止用于生产环境
|
||||
local code, headers, body_size = http.request("GET", "http://airtest.openluat.com:2900/download/send.bin", nil, nil, {dst=path}).wait()
|
||||
log.info("http", code==200 and "success" or "error", code)
|
||||
if code==200 then
|
||||
log.info("HTTP receive ok",body_size)
|
||||
file = io.open(path, "rb")
|
||||
if file then
|
||||
content = file:read("*a")
|
||||
log.info("文件读取", "路径:" .. path, "内容:" .. content)
|
||||
file:close()
|
||||
else
|
||||
log.error("文件操作", "无法打开文件读取内容", "路径:" .. path)
|
||||
end
|
||||
file_path=path
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- 定义一个xmodem_run函数,用于用xmodem发送文件
|
||||
local function xmodem_run()
|
||||
--如果需要http下载文件,然后发送下载的文件,可以打开下面的http_recived_cb()函数
|
||||
-- http_recived_cb()
|
||||
|
||||
--启动xmodem发送
|
||||
local result=xmodem.send(uart_id,baudrate,file_path,send_type,inform_data)
|
||||
--等待时间12秒,等待接收方发送C字符启动发送,发送结束后接收端发送ACK:0x06表示接收完成,文件全部传输完成之后模块发送EOT:0x04表示传输结束,接收端返回0x06表示确认结束
|
||||
log.info("Xmodem", "start")
|
||||
|
||||
log.info("Xmodem", "send result", result)
|
||||
--判断是否传输成功,传输是否成功,都需要关闭xmodem
|
||||
if result then
|
||||
log.info("Xmodem", "send success")
|
||||
xmodem.close(uart_id)
|
||||
else
|
||||
log.info("Xmodem", "send failed")
|
||||
xmodem.close(uart_id)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--创建并且启动一个task
|
||||
--运行这个task的主函数xmodem_run
|
||||
sys.taskInit(xmodem_run, taskName,xmodem_run_cb)
|
||||
|
||||
|
||||
]]
|
||||
local xmodem = {}
|
||||
|
||||
local sys = require "sys"
|
||||
|
||||
local HEAD
|
||||
local DATA_SIZE
|
||||
|
||||
local SOH = 0x01 -- Modem数据头 128
|
||||
local STX = 0x02 -- Modem数据头 1K
|
||||
local EOT = 0x04 -- 发送结束
|
||||
local ACK = 0x06 -- 应答
|
||||
local NAK = 0x15 -- 非应答
|
||||
local CAN = 0x18 -- 取消发送
|
||||
local CTRLZ = 0x1A -- 填充
|
||||
local CRC_CHR = 0x43 -- C: ASCII字符C
|
||||
local CRC_SIZE = 2
|
||||
local FRAME_ID_SIZE = 2
|
||||
local DATA_SIZE_SOH = 128
|
||||
local DATA_SIZE_STX = 1024
|
||||
|
||||
|
||||
local function uart_cb(id, len)
|
||||
local data = uart.read(id, 1024)
|
||||
if #data == 0 then
|
||||
return
|
||||
end
|
||||
log.info("xmodem", "uart读取到数据:", data:toHex())
|
||||
data = data:byte(1)
|
||||
sys.publish("xmodem", data)
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
xmodem 发送文件
|
||||
@api xmodem.send(uart_id,baudrate,type,inform_data)
|
||||
@number uart_id uart端口号
|
||||
@number uart_br uart波特率
|
||||
@string file_path 文件路径
|
||||
@bool type 1k/128 默认1k
|
||||
@return bool 发送结果
|
||||
@usage
|
||||
xmodem.send(1, 115200, "/luadb/send.bin",true)
|
||||
]]
|
||||
|
||||
function xmodem.send(uart_id,baudrate,file_path,type,inform_data)
|
||||
local ret, flen, cnt, crc
|
||||
if type then
|
||||
HEAD = SOH
|
||||
DATA_SIZE = DATA_SIZE_SOH
|
||||
else
|
||||
HEAD = STX
|
||||
DATA_SIZE = DATA_SIZE_STX
|
||||
end
|
||||
local XMODEM_SIZE = 1+FRAME_ID_SIZE+DATA_SIZE+CRC_SIZE
|
||||
local packsn = 0
|
||||
local xmodem_buff = zbuff.create(XMODEM_SIZE)
|
||||
local data_buff = zbuff.create(DATA_SIZE)
|
||||
local fd = io.open(file_path, "rb")
|
||||
if fd then
|
||||
uart.setup(uart_id,baudrate)
|
||||
uart.on(uart_id, "receive", uart_cb)
|
||||
if inform_data and inform_data~="" then
|
||||
uart.write(uart_id,inform_data)
|
||||
end
|
||||
local result, data = sys.waitUntil("xmodem", 12000)
|
||||
if result and (data == CRC_CHR or data == NAK) then
|
||||
cnt = 1
|
||||
while true do
|
||||
data_buff:set(0, CTRLZ)
|
||||
ret, flen = fd:fill(data_buff,0,DATA_SIZE)
|
||||
log.info("xmodem", "发送第", cnt, "包")
|
||||
if flen > 0 then
|
||||
data_buff:seek(0)
|
||||
crc = crypto.crc16("XMODEM",data_buff)
|
||||
packsn = (packsn+1) & 0xff
|
||||
xmodem_buff[0] = 0x02
|
||||
xmodem_buff[1] = packsn
|
||||
xmodem_buff[2] = 0xff-xmodem_buff[1]
|
||||
data_buff:seek(DATA_SIZE)
|
||||
xmodem_buff:copy(3, data_buff)
|
||||
xmodem_buff[1027] = crc>>8
|
||||
xmodem_buff[1028] = crc&0xff
|
||||
xmodem_buff:seek(XMODEM_SIZE)
|
||||
-- log.info(xmodem_buff:used())
|
||||
:: RESEND ::
|
||||
uart.tx(uart_id, xmodem_buff)
|
||||
result, data = sys.waitUntil("xmodem", 10000)
|
||||
if result and data == ACK then
|
||||
cnt = cnt + 1
|
||||
elseif result and data == NAK then
|
||||
goto RESEND
|
||||
else
|
||||
uart.write(uart_id, string.char(EOT))
|
||||
log.info("xmodem", "发送失败")
|
||||
return false
|
||||
end
|
||||
if flen ~= DATA_SIZE then
|
||||
log.info("xmodem", "文件到头了")
|
||||
break
|
||||
end
|
||||
else
|
||||
log.info("xmodem", "文件到头了")
|
||||
break
|
||||
end
|
||||
end
|
||||
uart.write(uart_id, string.char(EOT))
|
||||
fd:close()
|
||||
return true
|
||||
else
|
||||
log.info("xmodem", "不支持的起始数据包",data)
|
||||
return false
|
||||
end
|
||||
else
|
||||
log.info("xmodem", "待传输的文件不存在")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
关闭xmodem
|
||||
@api xmodem.close(uart_id)
|
||||
@number uart_id uart端口号
|
||||
@usage
|
||||
-- 执行xmodem传输后, 无论是否传输成功, 都建议关闭xmodem上下文, 也会关闭uart
|
||||
xmodem.close(2)
|
||||
]]
|
||||
function xmodem.close(uart_id)
|
||||
uart.on(uart_id, "receive")
|
||||
uart.close(uart_id)
|
||||
end
|
||||
|
||||
return xmodem
|
||||
Reference in New Issue
Block a user