Luat开源硬件

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

2018-03-23  本文已影响21人  Luat物联网通信模块

本教程目的:介绍儿童手表功能的代码实现,主要是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) 处理当前窗口对应触屏消息函数。

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

上一篇下一篇

猜你喜欢

热点阅读