local toolName = "TNS|DSM Forward Prog v0.5 (EdgeTx) |TNE" local VERSION = "v0.5" ---- ######################################################################### ---- # # ---- # Copyright (C) OpenTX # -----# # ---- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html # ---- # # ---- # This program is free software; you can redistribute it and/or modify # ---- # it under the terms of the GNU General Public License version 2 as # ---- # published by the Free Software Foundation. # ---- # # ---- # This program is distributed in the hope that it will be useful # ---- # but WITHOUT ANY WARRANTY; without even the implied warranty of # ---- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # ---- # GNU General Public License for more details. # ---- # # ---- ######################################################################### -- Code is based on the code/work by: Pascal Langer (Author of the Multi-Module) -- Rewrite/Enhancements By: Francisco Arzu local SIMULATION_ON = false -- FALSE: use real communication to DSM RX (DEFAULT), TRUE: use a simulated version of RX local DEBUG_ON = 1 -- 0=NO DEBUG, 1=HIGH LEVEL 2=LOW LEVEL (Debug logged into the /LOGS/dsm.log) local DEBUG_ON_LCD = false -- Interactive Information on LCD of Menu data from RX local dsmLib if (SIMULATION_ON) then -- library with SIMILATION VERSION. Works really well in Companion for GUI development dsmLib = loadScript("/SCRIPTS/TOOLS/DSMLIB/DsmFwPrgSIMLib.lua")(DEBUG_ON) else dsmLib = loadScript("/SCRIPTS/TOOLS/DSMLIB/DsmFwPrgLib.lua")(DEBUG_ON) end local PHASE = dsmLib.PHASE local LINE_TYPE = dsmLib.LINE_TYPE local DISP_ATTR = dsmLib.DISP_ATTR local DSM_Context = dsmLib.DSM_Context local LCD_X_LINE_MENU = 30 local LCD_X_LINE_TITLE = 30 local LCD_X_LINE_VALUE = 230 local LCD_X_LINE_DEBUG = 390 local LCD_Y_MENU_TITLE = 20 local LCD_Y_LINE_START = LCD_Y_MENU_TITLE + 30 local LCD_Y_LINE_HEIGHT = (DEBUG_ON_LCD and 23) or 27 -- if DEBUG 23 else 27 local LCD_Y_LOWER_BUTTONS = LCD_Y_LINE_START + 3 + (7 * LCD_Y_LINE_HEIGHT) local lastRefresh=0 -- Last time the screen was refreshed local REFRESH_GUI_MS = 300/10 -- 300ms.. Screen Refresh Rate.. to not waste CPU time (in 10ms units to be compatible with getTime()) local originalValue = nil local touchButtonArea = {} local EDIT_BUTTON = { DEFAULT=1001, DEC_10=1002, DEC_1=1003, INC_1=1004, INC_10=5, OK=1006, ESC=1007 } local function GUI_SwitchSimulationOFF() dsmLib.ReleaseConnection() dsmLib.LOG_close() SIMULATION_ON = false dsmLib = loadScript("/SCRIPTS/TOOLS/DSMLIB/DsmFwPrgLib.lua")(DEBUG_ON) DSM_Context = dsmLib.DSM_Context dsmLib.Init(toolName) -- Initialize Library dsmLib.StartConnection() DSM_Context.Refresh_Display = true end --------------------- Toucch Button Helpers ------------------------------------------------------------ local function GUI_addTouchButton(x,y,w,h,line) -- Add new button info to end of the array touchButtonArea[#touchButtonArea+1] = {x=x, y=y, w=w, h=h, line=line} end local function GUI_getTouchButton(x,y) for i = 1, #touchButtonArea do local button = touchButtonArea[i] -- is the coordinate inside the button area?? if (x >= button.x and x <= (button.x+button.w) and y >= button.y and (y <= button.y+button.h)) then return button.line end end return nil end local function GUI_clearTouchButtons() touchButtonArea = {} end ---------- Return Color to display Menu Lines ---------------------------------------------------------------- local function GUI_GetTextColor(lineNum) local ctx = DSM_Context local txtColor = BLACK -- Gray Out any other line except the one been edited if (ctx.isEditing() and ctx.EditLine~=lineNum) then txtColor=LIGHTGREY end return txtColor end local function GUI_GetFrameColor(lineNum) -- Frame Color for Value/Menu Boxes local ctx = DSM_Context local txtColor = LIGHTGREY --ORANGE -- Gray Out any other line except the one been edited if (ctx.isEditing() and ctx.EditLine~=lineNum) then txtColor=LIGHTGREY end return txtColor end -------------------------------------------------------------------------------------------------------- -- Display Text inside a Rectangle. Inv: true means solid rectangle, false=only perimeter local function GUI_Display_Boxed_Text(lineNum,x,y,w,h,text,inv) local txtColor = GUI_GetTextColor(lineNum) local frameColor = GUI_GetFrameColor(lineNum) if (inv) then txtColor = WHITE lcd.drawFilledRectangle(x-5, y-2, w, h, ORANGE) else lcd.drawRectangle(x-5, y-2, w, h, frameColor) end lcd.drawText(x , y, text, txtColor) end ------ Display Pre/Next/Back buttons local function GUI_Diplay_Button(x,y,w,h,text,selected) GUI_Display_Boxed_Text(-1,x,y,w,h,text,selected) end ------ Display MENU type of lines (Navigation, SubHeaders, and plain text comments) local function GUI_Display_Line_Menu(lineNum,line,selected) -- Menu Lines can be navidation to other Menus (if Selectable) -- Or SubHeaders or Messages local txtColor = GUI_GetTextColor(lineNum) local y = LCD_Y_LINE_START+(LCD_Y_LINE_HEIGHT*lineNum) local x = LCD_X_LINE_MENU if dsmLib.isSelectableLine(line) then -- Draw Selectable Menus in Boxes GUI_Display_Boxed_Text(lineNum,x, y, 350, LCD_Y_LINE_HEIGHT, line.Text,selected) GUI_addTouchButton(x, y, 350, LCD_Y_LINE_HEIGHT,lineNum) else -- Non Selectable Menu Lines, plain text -- Can be use for sub headers or just regular text lines (like warnings) local bold = (dsmLib.isDisplayAttr(line.TextAttr,DISP_ATTR.BOLD) and BOLD) or 0 if dsmLib.isDisplayAttr(line.TextAttr,DISP_ATTR.RIGHT) then -- Right Align??? local tw = lcd.sizeText(line.Text)+4 x = LCD_X_LINE_VALUE - tw -- Right elseif dsmLib.isDisplayAttr(line.TextAttr,DISP_ATTR.CENTER) then -- Center?? local tw = lcd.sizeText(line.Text) x = x + (LCD_X_LINE_VALUE - LCD_X_LINE_MENU)/2 - tw/2 -- Center - 1/2 Text end lcd.drawText(x, y, line.Text, txtColor + bold) end end ------ Display NAME : VALUES type of lines local function GUI_Display_Line_Value(lineNum, line, value, selected, editing) -- This Displays Name and Value Pairs local txtColor = GUI_GetTextColor(lineNum) local bold = 0 local y = LCD_Y_LINE_START+(LCD_Y_LINE_HEIGHT*lineNum) local x = LCD_X_LINE_TITLE --if (editing) then -- Any Special color/effect when editing?? -- value = "["..value .. "]" --end ---------- NAME Part local header = line.Text -- ONLY do this for Flight Mode (Right Align or Centered) if (dsmLib.isFlightModeText(line.TextId)) then -- Display Header + Value together header = header .. " " .. value -- Bold Text??? bold = (dsmLib.isDisplayAttr(line.TextAttr,DISP_ATTR.BOLD) and BOLD) or 0 if dsmLib.isDisplayAttr(line.TextAttr,DISP_ATTR.RIGHT) then -- Right Align local tw = lcd.sizeText(header)+4 x = LCD_X_LINE_VALUE - tw -- Right elseif dsmLib.isDisplayAttr(line.TextAttr,DISP_ATTR.CENTER) then -- Centered local tw = (lcd.sizeText(header)) x = x + (LCD_X_LINE_VALUE - LCD_X_LINE_TITLE)/2 - tw/2 -- Center - 1/2 Text end else -- No Flight Mode, no effects here header = header .. ":" end lcd.drawText(x, y, header, txtColor + bold) -- display Line Header --------- VALUE PART, Skip for Flight Mode since already show the value if not dsmLib.isFlightModeText(line.TextId) then value = value .. (line.Format or "") -- Append % if needed if dsmLib.isSelectableLine(line) then -- Can select/edit value, Box it local tw = lcd.sizeText(value)+10 -- Width of the Text in the lcd GUI_Display_Boxed_Text(lineNum,LCD_X_LINE_VALUE,y,tw,LCD_Y_LINE_HEIGHT,value,selected) GUI_addTouchButton(LCD_X_LINE_VALUE,y,tw,LCD_Y_LINE_HEIGHT,lineNum) else -- Not Editable, Plain Text lcd.drawText(LCD_X_LINE_VALUE, y, value, txtColor) end end -- Debug info for line Value RANGE when Debug on LCD if (DEBUG_ON_LCD) then lcd.drawText(LCD_X_LINE_DEBUG, y, line.MinMaxDebug or "", SMLSIZE + BLUE) end -- display debug Min/Max end local function GUI_Display_Menu(menu) local ctx = DSM_Context local w= LCD_W-100 -- usable Width for the Menu/Lines -- Center Header local tw = lcd.sizeText(menu.Text) local x = w/2 - tw/2 -- Center of Screen - Center of Text lcd.drawFilledRectangle(0, LCD_Y_MENU_TITLE-2, w, LCD_Y_LINE_HEIGHT-2, DARKGREY) lcd.drawText(x,LCD_Y_MENU_TITLE,menu.Text, WHITE + BOLD) -- orig MIDSIZE -- Back Button if menu.BackId ~= 0 then GUI_Diplay_Button(437-5,LCD_Y_MENU_TITLE+3,47,LCD_Y_LINE_HEIGHT,"Back",ctx.SelLine == dsmLib.BACK_BUTTON) GUI_addTouchButton(437-5,LCD_Y_MENU_TITLE+3,47,LCD_Y_LINE_HEIGHT,dsmLib.BACK_BUTTON) end -- Next Button if menu.NextId ~= 0 then GUI_Diplay_Button(437-5,LCD_Y_LOWER_BUTTONS,47,LCD_Y_LINE_HEIGHT,"Next",ctx.SelLine == dsmLib.NEXT_BUTTON) GUI_addTouchButton(437-5,LCD_Y_LOWER_BUTTONS,47,LCD_Y_LINE_HEIGHT,dsmLib.NEXT_BUTTON) end -- Prev Button if menu.PrevId ~= 0 then GUI_Diplay_Button(10,LCD_Y_LOWER_BUTTONS,47,LCD_Y_LINE_HEIGHT,"Prev",ctx.SelLine == dsmLib.PREV_BUTTON) GUI_addTouchButton(10,LCD_Y_LOWER_BUTTONS,47,LCD_Y_LINE_HEIGHT,dsmLib.PREV_BUTTON) end -- Debug on LCD, Show the menu Indo and Phase we are on if (DEBUG_ON_LCD) then lcd.drawText(0,LCD_Y_MENU_TITLE,dsmLib.phase2String(ctx.Phase),SMLSIZE+BLUE) end -- Phase we are in if (DEBUG_ON_LCD) then lcd.drawText(0,240,dsmLib.menu2String(menu),SMLSIZE+BLUE) end -- Menu Info end ------------------------------------------------------------------------------------------------------------ -- Display the EDIT mode buttons when editing a value local function GUI_Display_Edit_Buttons(line) GUI_clearTouchButtons() -- Only this buttons can be touched local x = 15 -- Inittial X position local w = 55 -- Width of the buttons local showPrev = line.Val > line.Min local showNext = line.Val < line.Max GUI_Diplay_Button(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT,"ESC",true) GUI_addTouchButton(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT,EDIT_BUTTON.ESC) x=x+w+10 GUI_Diplay_Button(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT," Def",true) GUI_addTouchButton(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT,EDIT_BUTTON.DEFAULT) x=x+w+10 if (not dsmLib.isListLine(line)) then GUI_Diplay_Button(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT," << ",showPrev) GUI_addTouchButton(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT,EDIT_BUTTON.DEC_10) end x=x+w+10 GUI_Diplay_Button(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT," <",showPrev) GUI_addTouchButton(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT,EDIT_BUTTON.DEC_1) x=x+w+10 GUI_Diplay_Button(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT," >",showNext) GUI_addTouchButton(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT,EDIT_BUTTON.INC_1) x=x+w+10 if (not dsmLib.isListLine(line)) then GUI_Diplay_Button(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT," >>",showNext) GUI_addTouchButton(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT,EDIT_BUTTON.INC_10) end x=x+w+10 GUI_Diplay_Button(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT," OK",true) GUI_addTouchButton(x,LCD_Y_LOWER_BUTTONS,w,LCD_Y_LINE_HEIGHT,EDIT_BUTTON.OK) end ------------------------------------------------------------------------------------------------------------ local function GUI_Display() local ctx = DSM_Context lcd.clear() GUI_clearTouchButtons() if LCD_W == 480 then local header = "DSM Forward Programming "..VERSION.." " if ctx.Phase ~= PHASE.RX_VERSION then header = header .. "RX "..ctx.RX.Name.." v"..ctx.RX.Version end --Draw title lcd.drawFilledRectangle(0, 0, LCD_W, 17, TITLE_BGCOLOR) lcd.drawText(5, 0, header, MENU_TITLE_COLOR + SMLSIZE) --Draw RX Menu if ctx.Phase == PHASE.RX_VERSION then lcd.drawText(LCD_X_LINE_TITLE,100,"No compatible DSM RX...", BLINK) else local menu = ctx.Menu if menu.Text ~= nil then GUI_Display_Menu(menu) for i = 0, dsmLib.MAX_MENU_LINES do local line = ctx.MenuLines[i] if i == ctx.SelLine then -- DEBUG: Display Selected Line info for ON SCREEN Debugging if (DEBUG_ON_LCD) then lcd.drawText(0,255,dsmLib.menuLine2String(line),SMLSIZE+BLUE) end end if line ~= nil and line.Type ~= 0 then if line.Type == LINE_TYPE.MENU then GUI_Display_Line_Menu(i, line, i == ctx.SelLine) else -- list/value line local value, imgValue = line.Val, nil if line.Val ~= nil then if dsmLib.isListLine(line) then -- for Lists of Strings, get the text value = dsmLib.Get_Text(line.Val + line.TextStart) -- TextStart is the initial offset for text imgValue = dsmLib.Get_Text_Img(line.Val + line.TextStart) -- Complentary IMAGE for this value to Display?? if (imgValue) then -- Optional Image for a Value --TODO: Pending feature.. create images and put bitmap instead of a message --Display the image/Alternate Text lcd.drawText(LCD_X_LINE_TITLE, LCD_Y_LINE_START+LCD_Y_LINE_HEIGHT, "Img:"..imgValue) end end GUI_Display_Line_Value(i, line, value, i == ctx.SelLine, i == ctx.EditLine) end end -- if ~MENU end -- if Line[i]~=nil end -- for if ctx.isEditing() then GUI_Display_Edit_Buttons(ctx.MenuLines[ctx.EditLine]) end end end else -- Different Resolution.. Maybe just adjusting some of the constants will work, adjust it in DSM_Init?? -- LCD_X_LINE_TITLE, LCD_Y_LINE_START, etc end end ------------------------------------------------------------------------------------------------------------- local function GUI_RotEncVal(dir) -- return encoder speed to inc or dec values local inc = 0 local Speed = getRotEncSpeed() if Speed == ROTENC_MIDSPEED then inc = (5 * dir) elseif Speed == ROTENC_HIGHSPEED then inc = (15 * dir) else inc = dir end return inc end ------------------------------------------------------------------------------------ -- Translate Tap/Touch of EDIT buttons to equivalent Key events local function GUI_Translate_Edit_Buttons(button) local event = EVT_TOUCH_TAP local editInc = nil if (button==EDIT_BUTTON.ESC) then -- ESC event = EVT_VIRTUAL_EXIT elseif (button==EDIT_BUTTON.DEFAULT) then -- Default event = EVT_VIRTUAL_ENTER_LONG elseif (button==EDIT_BUTTON.DEC_10) then -- -10 event = EVT_VIRTUAL_PREV editInc = -10 elseif (button==EDIT_BUTTON.DEC_1) then -- -1 event = EVT_VIRTUAL_PREV editInc = -1 elseif (button==EDIT_BUTTON.INC_1) then -- +1 event = EVT_VIRTUAL_NEXT editInc = 1 elseif (button==EDIT_BUTTON.INC_10) then -- + 10 event = EVT_VIRTUAL_NEXT editInc = 10 elseif (button==EDIT_BUTTON.OK) then -- OK event = EVT_VIRTUAL_ENTER else end return event, editInc end ------------------------------------------------------------------------------------------------------------ -- Handle Events comming from the GUI local function GUI_HandleEvent(event, touchState) local ctx = DSM_Context local menu = ctx.Menu local menuLines = ctx.MenuLines local editInc = nil if (event == EVT_TOUCH_TAP and ctx.isEditing()) then -- Touch and Editing local button = GUI_getTouchButton(touchState.x, touchState.y) if (button) then event, editInc = GUI_Translate_Edit_Buttons(button) end end if (event == EVT_TOUCH_TAP or event == EVT_TOUCH_FIRST) and not ctx.isEditing() then -- Touch and NOT editing if (DEBUG_ON) then dsmLib.LOG_write("%s: EVT_TOUCH_TAP %d,%d\n",dsmLib.phase2String(ctx.Phase),touchState.x, touchState.y) end local button = GUI_getTouchButton(touchState.x, touchState.y) if button then -- Found a valid line ctx.SelLine = button ctx.Refresh_Display=true if event == EVT_TOUCH_TAP then -- EVT_TOUCH_FIRST only move focus event = EVT_VIRTUAL_ENTER end end end if event == EVT_VIRTUAL_EXIT then ctx.Refresh_Display=true if (DEBUG_ON) then dsmLib.LOG_write("%s: EVT_VIRTUAL_EXIT\n",dsmLib.phase2String(ctx.Phase)) end if ctx.Phase == PHASE.RX_VERSION then dsmLib.ReleaseConnection() -- Just Exit the Script else if ctx.isEditing() then -- Editing a Line, need to restore original value ctx.MenuLines[ctx.EditLine].Val = originalValue dsmLib.ChangePhase(PHASE.VALUE_CHANGE_END) -- Update + Validate value in RX ctx.EditLine = nil -- Exit Edit Mode (By clearing the line editing) else dsmLib.ChangePhase(PHASE.EXIT) end end return end if event == EVT_VIRTUAL_NEXT then ctx.Refresh_Display=true if (DEBUG_ON) then dsmLib.LOG_write("%s: EVT_VIRTUAL_NEXT\n",dsmLib.phase2String(ctx.Phase)) end if ctx.isEditing() then -- Editing a Line, need to inc the value local line=ctx.MenuLines[ctx.EditLine] dsmLib.Value_Add(line, editInc or GUI_RotEncVal(1)) else -- not editing, move selected line to NEXT dsmLib.MoveSelectionLine(1) end return end if event == EVT_VIRTUAL_PREV then ctx.Refresh_Display=true if (DEBUG_ON) then dsmLib.LOG_write("%s: EVT_VIRTUAL_PREV\n",dsmLib.phase2String(ctx.Phase)) end if ctx.isEditing() then -- Editiing a line, need to dec the value local line=ctx.MenuLines[ctx.EditLine] dsmLib.Value_Add(line, editInc or GUI_RotEncVal(-1)) else -- not editing, move selected line to PREV dsmLib.MoveSelectionLine(-1) end return end if event == EVT_VIRTUAL_ENTER_LONG then ctx.Refresh_Display=true if (DEBUG_ON) then dsmLib.LOG_write("%s: EVT_VIRTUAL_ENTER_LONG\n",dsmLib.phase2String(ctx.Phase)) end if ctx.isEditing() then -- reset the value to default dsmLib.Value_Default(menuLines[ctx.EditLine]) -- Update value in RX if needed end return end if event == EVT_VIRTUAL_ENTER then ctx.Refresh_Display=true if (DEBUG_ON) then dsmLib.LOG_write("%s: EVT_VIRTUAL_ENTER, SelLine=%d\n",dsmLib.phase2String(ctx.Phase), ctx.SelLine) end if ctx.SelLine == dsmLib.BACK_BUTTON then -- Back dsmLib.GotoMenu(menu.BackId) elseif ctx.SelLine == dsmLib.NEXT_BUTTON then -- Next dsmLib.GotoMenu(menu.NextId) elseif ctx.SelLine == dsmLib.PREV_BUTTON then -- Prev dsmLib.GotoMenu(menu.PrevId) elseif menuLines[ctx.SelLine].ValId ~= 0 then -- Menu or Value if menuLines[ctx.SelLine].Type == LINE_TYPE.MENU then -- Navigate to Menu if (SIMULATION_ON and menuLines[ctx.SelLine].ValId==0xFFFF) then -- SPECIAL Simulation menu to Exit Simulation GUI_SwitchSimulationOFF() else dsmLib.GotoMenu(menuLines[ctx.SelLine].ValId) -- ValId is the MenuId to navigate to end else -- Enter on a Value if ctx.isEditing() then -- already editing a Line???? ctx.EditLine = nil -- Exit Edit Mode (By clearing the line editing) dsmLib.ChangePhase(PHASE.VALUE_CHANGE_END) -- Request change the value on RX else -- Edit the current value ctx.EditLine = ctx.SelLine originalValue = menuLines[ctx.SelLine].Val end end end end end ------------------------------------------------------------------------------------------------------------ -- Init local function DSM_Init() dsmLib.Init(toolName) -- Initialize Library return dsmLib.StartConnection() end ------------------------------------------------------------------------------------------------------------ -- Main local function DSM_Run(event,touchState) local ctx = DSM_Context if event == nil then error("Cannot be run as a model script!") dsmLib.LOG_close() return 2 end GUI_HandleEvent(event,touchState) dsmLib.Send_Receive() -- Handle Send and Receive DSM Forward Programming Messages local refreshInterval = REFRESH_GUI_MS -- When using LCD BLINK attribute, we need faster refresh for BLINK to SHOW on LCD if (ctx.Phase == PHASE.RX_VERSION) then -- Requesting RX Message Version usea BLINK? ctx.Refresh_Display=true refreshInterval = 20 -- 200ms end -- Refresh display only if needed and no faster than 300ms, utilize more CPU to speedup DSM communications if (ctx.Refresh_Display and (getTime()-lastRefresh) > refreshInterval) then --300ms from last refresh GUI_Display() ctx.Refresh_Display=false lastRefresh=getTime() end if ctx.Phase == PHASE.EXIT_DONE then dsmLib.LOG_close() return 2 else return 0 end end return { init=DSM_Init, run=DSM_Run }