加载中…
正文 字体大小:

LUAT实例教程:儿童手表源码介绍

(2018-03-23 10:45:11)
分类: GPRS模块

本教程目的:介绍儿童手表功能的代码实现,主要是UI显示,触屏消息处理,通过MQTT来实现手表和APP数据交互。

手表包含功能

1.待机模拟时钟。
2.待机界面,包含时间日期,网络信号和电池电量等显示。
3.拨号功能,可以按拨号键拨出电话。也可以通过成长线索APP设置关闭此功能。
4.电话本,通过绑定手表来添加管理员。管理员可以通过APP添加号码到电话本。可以通过电话本,直接拨出电话。
5.微聊功能,按住说话可以发送微聊到APP,也可以接受微聊发送过来的语音。
6.相机功能,可以拍照片发送到APP。
7.工具箱,包含相册,秒表,主题,音量,屏幕亮度。
8.小游戏,包含数学,英语,语文等小游戏。
9.设备信息,包含APP下载,注册码,微信看位置。
10.手电筒,可以打开用来照明。

APP包含功能
1.定位,监听,课程表,留言。
2.寻找手表,远程关机,远程拍照,智能提醒和各种设置等。

手表图片

APP截图

代码结构截图

代码架构介绍

1.audio文件夹,存放需要的音频文件。如:开关机,来电铃音。
2.font文件夹,存放用来显示待机界面时间和秒表的大字体。
3.image文件夹,存放需要用来显示的图片资源。如:开关机动画。
4.lib文件夹,存放脚本的库文件。如:sys.lua,mqtt.lua。
5.src文件夹,存放一些通用,协议相关的脚本。如:ui.lua,display.lua,linkair.lua,protoair.lua。
6.tool文件夹,存放生成模拟时钟的python脚本。
7.windows文件夹,存放各个功能模块的界面处理脚本。如:idle.lua
8.main.lua脚本,入口脚本。会require需要的各个功能模块。

代码说明

1.模拟时钟实现 clock.lua
通过入口函数enter(clock())进入模拟时钟界面。
function clock() 显示和按键和触摸消息的处理都是通过此函数实现的。
ui.window 创建窗口对象。


return ui.window {
        lockscreen = true,
        enter = function()
            ui.onkey(ui.KEY_POWER, ui.KEY_DOWN, function() end)
            ui.onkey(ui.KEY_POWER, ui.KEY_UP, lcd.off) -- 锁屏界面下按关机键灭屏
        end,
        draw = function()
            disp.setactivelayer(2)
            disp.clear()
            disp.putimage('clock.png', 5, 5)
            disp.setlayerstartpos(2, VSCREEN_X, VSCREEN_Y)
            disp.setactivelayer(0)
            disp.setbkcolor(BLACK)
            disp.clear()
            disp.setlayerstartpos(0, VSCREEN_X, VSCREEN_Y)
            disp.setlayershow(true, false, true)
            redraw()
        end,
        penup = function()
            ui.goback()
        end,
}

enter = function() 进入窗口,通过ui.onkey(ui.KEY_POWER, ui.KEY_UP, lcd.off)注册按键处理函数。

draw = function() 绘制窗口界面,主要用到disp相关接口进行图层设置和画图片,写文字。如:disp.putimage('clock.png', 5, 5)。会从屏的x=5,y=5的开始显示clock.png对应的时钟表盘图片。

penup = function() 触屏抬起消息处理。

timer = ui.timer { interval = 1000, callback = redraw }
定时器处理1S钟会刷新一次界面。

local function redraw() 根据当前时间算出对应的坐标,画出对应的时,分,秒对应的指针图片。

2.待机实现 idle.lua
通过ui.enter(idle())进入待机窗口

ui.window 通过这个创建窗口对象

enter = function() 进入窗口后的按键注册消息注册处理。
如:ui.onkey(ui.KEY_POWER, ui.KEY_UP, lcd.off) -- 待机界面下按关机键灭屏
sys.subscribe('BATT_VOLT_IND', drawbatt) --电池上报的消息注册函数,有电量变化是会调用drawbatt函数去画电池电量显示图标。

losefocus = function() 窗口失去焦点去处理的函数
如:sys.unsubscribe('BATT_VOLT_IND', drawbatt) --注销电池电量上报消息。

draw = redraw 窗口绘制函数

pendown = function() 窗口触屏按下消息处理
penup = function(x, y) 窗口触屏抬起消息处理
penmove = function() 窗口触屏移动消息处理


        penup = function(x, y)
            if moved then
                ui.enter(mainmenu())
                return
            end
        end,

待机界面移动抬起的情况下会进入主菜单界面。

local function drawDateTime() 时间绘制函数
local function drawSignal() 信号强度绘制函数
local function drawbatt(lvl) 电池电量绘制函数
local function drawIPStatus() IP状态绘制函数
local function drawChatStatus() 微聊状态绘制函数
local function drawAlarmStatus() 智能提醒状态绘制函数

3.主实现 mainmenu.lua


    local items = {
        'dial', 'contact', 'wechat', 'camera', 'toolbox', 'minigame','deviceinfo', 'flashlight'
    }

items主菜单对应的菜单项:拨号,电话本,微聊,相机,工具箱,小游戏,设备信息,手电筒。

ui.window 窗体的创建也是通过这个创建的。

ui.enter(_Gitems[highlight + 1]) 通过这进入不同的主菜单界面
local highlight = 0 -- 高亮菜单项
local delta = offset > 0 and 1 or -1 -- 图层偏移方向 往右滑动为正与触屏坐标增加方向一致

highlight = (highlight - delta) % #items 根据移动方向算出当前高亮菜单项


    local function reload(right)
        disp.layerbuffermove(layer, right and 0 or 1)
        -- 预加载数据到后一屏缓冲中
        putimage(right and 0 or 2)
    end

reload(delta == 1) -- 右为正 左为负 与图层偏移方向一致

4.拨号实现 call.lua
注册 CALL_INCOMING 来的消息


sys.subscribe('CALL_INCOMING', function(number)
    if not manage.isContact(number) then cc.hangup(number) return end -- 非电话本联系人来电自动拒接
    ui.enter(call(number, cc.INCOMING))
end)

manage.isContact(number) 判断是否为电话本联系人来电
cc.hangup(number) 非电话本来的自动拒接
ui.enter(call(number, cc.INCOMING)) 是电话本来进入来电界面

sys.subscribe('CALL_CONNECTED', onConnect) 注册电话接通处理函数
sys.subscribe('CALL_DISCONNECTED', onDisconnect) 注册电话断开处理函数

窗口函数和之前一样就不做说明了。
下面的主菜单的窗体的创建和窗口函数处理都一样,就不一一做介绍了。

5.mqtt实现 linkair.lua


sys.taskInit(
    function()
        while true do
            while not socket.isReady() do sys.waitUntil('IP_READY_IND') end
            local imei = misc.getimei()
            local mqttc = mqtt.client(imei,600,imei,enPwd(imei))
            --阻塞执行MQTT CONNECT动作,直至成功
            while not mqttc:connect(nvm.get("addr"),nvm.get("port"),nvm.get("prot")) do
                sys.wait(2000)
            end
            ready = true
            --订阅主题
            if mqttc:subscribe(
                {
                    [linkout.enTopic("devparareq/+")]=1,
                    [linkout.enTopic("deveventreq/+")]=1,
                    [linkout.enTopic("set")]=1,
                    [linkout.enTopic("updpbreq/+")]=1,
                    [linkout.enTopic2("+",misc.getimei(),"single/stod/+")]=1,
                    [linkout.enTopic2("+",misc.getimei(),"group/+/stod/+")]=1
                }
            ) then
                linkout.init()
                while true do
                    if not linkin.procMsg(mqttc) then log.error("linkin.procMsg error") break end
                    if not linkout.procMsg(mqttc) then log.error("linkout.procMsg error") break end
                    coroutine.resume(co_monitor, 'feed monitor')
                end
                linkout.unInit()
            end
            ready = false
            --断开MQTT连接
            mqttc:disconnect()
        end
    end
)

通过创建一个TASK来实践MQTT的连接,订阅,接收和发送消息

local mqttc = mqtt.client(imei,600,imei,enPwd(imei)) MQTT的创建
mqttc:connect(nvm.get("addr"),nvm.get("port"),nvm.get("prot")) 连接MQTT


            if mqttc:subscribe(
                {
                    [linkout.enTopic("devparareq/+")]=1,
                    [linkout.enTopic("deveventreq/+")]=1,
                    [linkout.enTopic("set")]=1,
                    [linkout.enTopic("updpbreq/+")]=1,
                    [linkout.enTopic2("+",misc.getimei(),"single/stod/+")]=1,
                    [linkout.enTopic2("+",misc.getimei(),"group/+/stod/+")]=1
                }

MQTT主题订阅

linkout.procMsg(mqttc) 通过mqttc:publish 发送MQTT消息
linkin.procMsg(mqttc) 通过mqttc:receive(2000) 来接收MQTT发送的消息

6.微聊实现 wechat.lua
微聊主要是通过录音来实现的,介绍下用到的接口。
record.start 开始录音
record.stop() 停止录音
record.isBusy() 录音中

sys.publish("SND_NEW_CHAT_REQ", contacts[1].phone[1]) 录音完成通过publish "SND_NEW_CHAT_REQ" 来发送录音到服务器


sys.subscribe("SND_NEW_CHAT_CNF", function(result)
    recordSending = false
    ui.popup(result and '微聊发送成功' or '微聊发送失败')
end)

注册微聊发送结果消息"SND_NEW_CHAT_CNF" 做对应的提示

微聊消息接收处理函数


function rcvrcd(ctyp, cid, mid, tm, seq, total, cur, typ, tmlen, dat)
    log.info("manage.rcvrcd", collectgarbage("count"), cid, mid, seq, total, cur, typ)
    collectgarbage()
    if mid == misc.getimei() then return end
    local pth, name = CHAT_DIR .. "/" .. mid:sub(-10,-1) .. "_" .. tm .. "." .. (typ == 0 and "amr" or (typ == 1 and "mp3" or "wav"))
    name = split.merge(cid .. mid .. seq, pth, total, cur, dat)
    if name then
        savercd(cid, mid, tmlen, name, true)
        --cid表示联系人的id(单聊时为联系人的号码,群聊时为群组的id)
        sys.publish("RCV_NEW_CHAT_IND", cid)
    end
    collectgarbage()
end

收到微聊数据后会保存为.amr格式文件。
用audio.play()播放收到微聊。

7.相机实现 camera.lua
用到的相关接口
打开相机窗口的时候做下列动作
disp.cameraopen() 打开相机
disp.camerapreview(VSCREEN_X, VSCREEN_Y, 0, 0, 127, 127) 预览设置

退出相机窗口或失去焦点的时候做下列动作
disp.camerapreviewclose() 关闭预览
disp.cameraclose() 关闭相机

点相机图片拍照的时候做下列动作
disp.cameracapture(128, 128) 抓拍照片
disp.camerasavephoto('/ldata/PHOTO.jpg') 保存照片
disp.camerapreview(VSCREEN_X, VSCREEN_Y, 0, 0, 127, 127) 重新设置预览

8.ui实现 ui.lua


local windowmt = { __index = function() return empty end }

--- 创建窗口
-- @param 无
-- @return 窗口对象
-- @usage ui.window{draw=function() end}
function window(o)
    setmetatable(o, windowmt)
    return o
end

通过setmetatable设置元表来创建窗口


--- 进入新的窗口
-- @param w 新窗口对象ui.window
-- @return 无
-- @usage ui.enter(ui.window{draw=function() end})
function enter(w)
    if w.popup == true or w.notice == true then lcd.turnon() end -- UI通知可能会在熄屏状态下弹出,因此需要打开背光

    if #wstack ~= 0 then
        if wstack[#wstack].popup == true then -- 进入新窗口时如果有popup自动清除掉
            table.remove(wstack, #wstack)
        elseif wstack[#wstack].notice == true and w.notice == true then -- 产生不同的通知窗口时自动清除旧的通知窗口
            table.remove(wstack, #wstack).exit()
        else
            wstack[#wstack].losefocus()
        end
    end
    table.insert(wstack, w)
    __enter(w, true) -- 为新创建的窗口在调用enter回调时增加一个标记
    draw()
end

local wstack = {} -- 窗口栈
先看 #wstack 是否为0,如不为0,根据窗口类型进行不同的处理
然后把当前窗口放入堆栈。
local function __enter(w, isNew)
在这个函数里会调用窗口对象的enter函数w.enter(isNew)


local function draw()
    if reentries == 0 and #wstack ~= 0 then
        -- 绘制新窗口总是设置为第一个图层显示且为黑底白字
        disp.setlayershow(true, false, false)
        disp.setactivelayer(0)
        disp.setlayerstartpos(0, VSCREEN_X, VSCREEN_Y)
        disp.setbkcolor(BLACK)
        disp.setcolor(WHITE)
        update()
    end
end

draw()函数会设置图层参数,背景色和字体颜色。


function update()
    if locked then return end
    wstack[#wstack].draw()
end

update函数会调当前窗口对象的draw函数wstack[#wstack].draw()。


local pressedKey
rtos.init_module(rtos.MOD_KEYPAD, 0, 0x07, 0x07)
rtos.on(rtos.MSG_KEYPAD, function(msg)
    if locked then return end
    local key = msg.key_matrix_row * 256 + msg.key_matrix_col
    log.debug('ui.KEYPAD', msg.key_matrix_row, msg.key_matrix_col, msg.pressed)

    if msg.pressed then
        if pressedKey == KEY_CALL and key == KEY_POWER then
            if nvm.get('ft_result') ~= 'pass' then
                ui.enter(functiontest())
            end
        end
        pressedKey = key
        keyWindow = wuid
        sys.timer_start(onLongpress, 2000, key)
        if not pm.isleep('lcd') then lcd.turnon('key') end
    else
        pressedKey = nil
        sys.timer_stop(onLongpress, key)
        lcd.turnoff('key')
        if keyWindow ~= wuid then -- 不在一个界面下的完整按键流程不处理
            return
        end
    end

    handleKey(key, msg.pressed and KEY_DOWN or KEY_UP)
end)

按键处理rtos.init_module(rtos.MOD_KEYPAD, 0, 0x07, 0x07)
根据按键所在行列去初始化按键。
rtos.on(rtos.MSG_KEYPAD, function(msg))
按键消息回调函数注册。


-- 注册触屏消息
local touchEvent = { 'pendown', 'penmove', 'penup' }
local touchWindow
rtos.init_module(rtos.MOD_TP)
rtos.on(rtos.MSG_TP, function(msg)
    if pm.isleep('lcd') or locked then return end -- 灭屏状态下不处理任何触屏消息

    local event = touchEvent[msg.pen_state + 1]
    log.debug('ui.KEYPAD', event, msg.x, msg.y)
    local x, y = checkCoord(msg.x), checkCoord(msg.y)

    if event == 'pendown' then
        touchWindow = wuid
        lcd.turnon('touch')
    else
        lcd.turnoff('touch')
        if touchWindow ~= wuid then -- 如果是之前窗口发生的触摸消息,则忽略掉
            return
        end
    end

    if x == 100 and y == 140 then
        handleKey(KEY_HOME, event == 'pendown' and KEY_DOWN or KEY_UP)
        return
    end
    wstack[#wstack][event](x, y)
end)

触屏处理
local touchEvent = { 'pendown', 'penmove', 'penup' }
触屏消息类型。
rtos.init_module(rtos.MOD_TP) 初始化触屏模块。
rtos.on(rtos.MSG_TP, function(msg))注册触屏消息回调函数。
wstack[#wstack][event](x, y) 处理当前窗口对应触屏消息函数。

关于手表的源码介绍就先介绍这么多,其它的部分大家可以分析源码来学习。

0

阅读 评论 收藏 转载 喜欢 打印举报
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4006900000 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有