local toolName = "TNS|DSM Forward Prog v0.54 (Text B&W) |TNE" local VERSION = "v0.54" ---- ######################################################################### ---- # # ---- # 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. # ---- # # ---- ######################################################################### ------------------------------------------------------------------------------ -- This script library is a rewrite of the original DSM forward programming Lua -- Script. The goal is to make it easier to understand, mantain, and to -- separate the GUI from the DSM Forward programming engine/logic -- in this way, GUIs can evolve independent. OpenTX Gui, EdgeTx GUI, Small Radios, etc. -- 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: don't show simulation menu (DEFAULT), TRUE: show simulation menu 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_PATH = "/SCRIPTS/TOOLS/DSMLIB/" local dsmLib = assert(loadScript(DSMLIB_PATH.."DsmSetupLib.lua"), "Not-Found: DSMLIB/DsmSetupLib.lua")(DEBUG_ON,SIMULATION_ON) local PHASE = dsmLib.PHASE local LINE_TYPE = dsmLib.LINE_TYPE local DISP_ATTR = dsmLib.DISP_ATTR local DSM_Context = dsmLib.DSM_Context local IS_EDGETX = false -- DEFAULT until Init changed it local LCD_W_USABLE = LCD_W-10 -- X for Menu Lines local LCD_X_LINE_MENU = 10 -- X offsets for (Title: [Value] debugInfo) lines local LCD_X_LINE_TITLE = 10 local LCD_X_LINE_VALUE = 230 local LCD_X_LINE_DEBUG = 390 -- Line Height: make it smaller debugging info tp LCD (some space buttom) local LCD_Y_LINE_HEIGHT = (DEBUG_ON_LCD and 23) or 27 -- if DEBUG 23 else 27 -- Y offsets local LCD_Y_MENU_TITLE = 20 -- Y offet local LCD_Y_LINE_FIRST = LCD_Y_MENU_TITLE + 30 local LCD_Y_LOWER_BUTTONS = LCD_Y_LINE_FIRST + 7 * LCD_Y_LINE_HEIGHT local LCD_W_BUTTONS = 47 local LCD_H_BUTTONS = 25 local LCD_X_RIGHT_BUTTONS = LCD_W - LCD_W_BUTTONS - 5 local TEXT_SIZE = 0 -- NORMAL local lastRefresh=0 -- Last time the screen was refreshed local REFRESH_GUI_MS = 500/10 -- 500ms.. Screen Refresh Rate.. to not use unneded CPU time (in 10ms units to be compatible with getTime()) local originalValue = nil local warningScreenON = true ------------------------------------------------------------------------------------------------------------ local function GUI_SwitchToRX() -- Force to refresh DSM Info in MODEL (dsmLib pointing to the setup Script) local dsmChannelInfo, description = dsmLib.CreateDSMPortChannelInfo() dsmLib.ReleaseConnection() dsmLib.LOG_close() SIMULATION_ON = false dsmLib = assert(loadScript(DSMLIB_PATH.."DsmFwPrgLib.lua"),"Not-Found: DSMLIB/DsmFwPrgLib.lua")(DEBUG_ON) DSM_Context = dsmLib.DSM_Context dsmLib.Init(toolName) -- Initialize Library dsmLib.SetDSMChannelInfo(dsmChannelInfo, description) -- send the dsmChannelInfo to new instance library dsmLib.StartConnection() DSM_Context.Refresh_Display = true end local function GUI_SwitchToSIM() dsmLib.ReleaseConnection() dsmLib.LOG_close() SIMULATION_ON = true dsmLib = assert(loadScript(DSMLIB_PATH.."DsmFwPrgSIMLib.lua"), "Not-Found: DSMLIB/DsmFwPrgSIMLib.lua")(DEBUG_ON) DSM_Context = dsmLib.DSM_Context dsmLib.Init(toolName) -- Initialize Library dsmLib.StartConnection() DSM_Context.Refresh_Display = true end local function openTx_lcd_sizeText(s) return string.len(s)*5 end local function GUI_Diplay_Button(x,y,w,h,text,selected) local attr = (selected) and INVERS or 0 -- INVERS if line Selected if (TEXT_SIZE~=SMLSIZE) then lcd.drawText(x+5,y+2, text, attr + TEXT_SIZE) lcd.drawRectangle(x, y, w, h, LINE_COLOR) else -- SMALL Screen lcd.drawText(x,y, text, attr + TEXT_SIZE) end end local function GUI_Display_Menu(menu) local ctx = DSM_Context local w= LCD_W_USABLE - LCD_W_BUTTONS - 10 -- usable Width for the Menu/Lines -- Center Header local tw = openTx_lcd_sizeText(menu.Text) local x = w/2 - tw/2 -- Center of Screen - Center of Text if (x < 0) then x=0 end -- in case text is too wide local bold = BOLD lcd.drawText(x,LCD_Y_MENU_TITLE,menu.Text,bold + TEXT_SIZE) -- Back if menu.BackId ~= 0 then GUI_Diplay_Button(LCD_X_RIGHT_BUTTONS,LCD_Y_MENU_TITLE,LCD_W_BUTTONS,LCD_H_BUTTONS,"Back",ctx.SelLine == dsmLib.BACK_BUTTON) end -- Next ? if menu.NextId ~= 0 then GUI_Diplay_Button(LCD_X_RIGHT_BUTTONS,LCD_Y_LOWER_BUTTONS,LCD_W_BUTTONS,LCD_H_BUTTONS,"Next",ctx.SelLine == dsmLib.NEXT_BUTTON) end -- Prev? if menu.PrevId ~= 0 then GUI_Diplay_Button(0,LCD_Y_LOWER_BUTTONS,LCD_W_BUTTONS,LCD_H_BUTTONS,"Prev",ctx.SelLine == dsmLib.PREV_BUTTON) end -- Debug into LCD if (DEBUG_ON_LCD) then lcd.drawText(0,LCD_Y_MENU_TITLE,dsmLib.phase2String(ctx.Phase),TEXT_SIZE + WARNING_COLOR) end -- Phase we are in if (DEBUG_ON_LCD) then lcd.drawText(LCD_X_LINE_MENU,240,dsmLib.menu2String(menu),TEXT_SIZE + WARNING_COLOR) end -- Menu Info end local function GUI_Display_Line_Menu(x,y,w,h,line,selected) local attr = (selected and INVERS) or 0 -- INVERS if line Selected local bold = 0 local text = line.Text if dsmLib.isSelectableLine(line) then -- Menu Line text = text .. " >" else -- SubHeaders and plain text lines 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 = openTx_lcd_sizeText(line.Text)+4 x = LCD_X_LINE_VALUE - tw -- Right elseif dsmLib.isDisplayAttr(line.TextAttr,DISP_ATTR._CENTER) then -- Center?? local tw = openTx_lcd_sizeText(line.Text) x = x + (LCD_X_LINE_VALUE - LCD_X_LINE_MENU)/2 - tw/2 -- Center - 1/2 Text end if (x < 0) then x=0 end -- in case text is too wide end lcd.drawText(x,y, text, attr + bold + TEXT_SIZE) end ------------------------------------------------------------------------------------------------------------ local function GUI_Display_Line_Value(lineNum, line, value, selected, editing) local bold = 0 local y = LCD_Y_LINE_FIRST+(LCD_Y_LINE_HEIGHT*lineNum) local x = LCD_X_LINE_TITLE ---------- NAME Part local header = line.Text -- ONLY do this for Flight Mode (Right Align or Centered) if (dsmLib.isFlightModeLine(line)) then -- Display Header + Value together header = dsmLib.GetFlightModeValue(line) -- Flight mode display attributes 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 = openTx_lcd_sizeText(header)+4 x = LCD_X_LINE_VALUE - tw -- Right elseif dsmLib.isDisplayAttr(line.TextAttr,DISP_ATTR._CENTER) then -- Centered local tw = openTx_lcd_sizeText(header) x = x + (LCD_X_LINE_VALUE - LCD_X_LINE_TITLE)/2 - tw/2 -- Center - 1/2 Text end if (x < 0) then x=0 end -- in case text is too wide else -- No Flight Mode, no effects here header = header .. ":" end lcd.drawText(x, y, header, bold + TEXT_SIZE) -- display Line Header --------- VALUE PART, Skip for Flight Mode since already show the value if not dsmLib.isFlightModeLine(line) then local attrib = 0 if selected then attrib = INVERS if editing then -- blink editing entry attrib = attrib + BLINK value = "[" .. value .. "]" end end value = value .. " " .. (line.Format or "") -- Append % if needed lcd.drawText(LCD_X_LINE_VALUE,y, value, attrib + TEXT_SIZE) -- display value end if (DEBUG_ON_LCD) then lcd.drawText(LCD_X_LINE_DEBUG,y, line.MinMaxDebug or "", TEXT_SIZE + WARNING_COLOR) end -- display debug end ------------------------------------------------------------------------------------------------------------ local function GUI_ShowBitmap(x,y,imgData) -- imgData format "bitmap.png|alt message" local f = string.gmatch(imgData, '([^%|]+)') -- Iterator over values split by '|' local imgName, imgMsg = f(), f() if (LCD_W > 128) then lcd.drawText(x, y, imgMsg or "", TEXT_SIZE) -- Alternate Image MSG else local f = string.gmatch(imgMsg, '([^%:]+)') -- Iterator over values split by ':' local msg1,msg2 = f(), f() lcd.drawText(x, y, (msg1 or "")..":", TEXT_SIZE) -- Alternate Image MSG lcd.drawText(x, y+10, msg2 or "", TEXT_SIZE) -- Alternate Image MSG end -- NO IMAGES in Text B&W --local imgPath = IMAGE_PATH .. (imgName or "") --local bitmap = Bitmap.open(imgPath) --if (bitmap~=nil) then -- lcd.drawBitmap(bitmap, x,y+20) --end end ------------------------------------------------------------------------------------------------------------ local function GUI_Display() local ctx = DSM_Context lcd.clear() local header = "DSM Fwrd Programming " if (TEXT_SIZE==SMLSIZE) then -- Small Screen no title header = "" end if ctx.Phase ~= PHASE.RX_VERSION then header = header .. ctx.RX.Name.." v"..ctx.RX.Version end --Draw title if (TEXT_SIZE~=SMLSIZE) then -- ignore tool title small size screens lcd.drawFilledRectangle(0, 0, LCD_W, 20, TITLE_BGCOLOR) lcd.drawText(5, 0, header, MENU_TITLE_COLOR + TEXT_SIZE) else -- Small Screen lcd.drawText(20, LCD_Y_LOWER_BUTTONS+1, header, TEXT_SIZE) end --Draw RX Menu if ctx.Phase == PHASE.RX_VERSION then if (ctx.isReset) then lcd.drawText(LCD_X_LINE_TITLE,50,dsmLib.Get_Text(0x301), BLINK + TEXT_SIZE) -- Resetting else lcd.drawText(LCD_X_LINE_TITLE,50,dsmLib.Get_Text(0x300), BLINK + TEXT_SIZE) -- Waiting for RX Version end 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(LCD_X_LINE_TITLE,255,dsmLib.menuLine2String(line),TEXT_SIZE + WARNING_COLOR) end end if line ~= nil and line.Type ~= 0 then if line.Type == LINE_TYPE.MENU then -- Menu Line GUI_Display_Line_Menu(LCD_X_LINE_MENU,LCD_Y_LINE_FIRST+(LCD_Y_LINE_HEIGHT*i), 350, LCD_Y_LINE_HEIGHT, line, i == ctx.SelLine) else -- list/value line local value = line.Val if line.Val ~= nil then if dsmLib.isListLine(line) then -- for Lists of Strings, get the text value = dsmLib.Get_List_Text(line.Val + line.TextStart) -- TextStart is the initial offset for text local imgData = dsmLib.Get_List_Text_Img(line.Val + line.TextStart) -- Complentary IMAGE for this value to Display?? if (imgData and i == ctx.SelLine) then -- Optional Image and Msg for selected value GUI_ShowBitmap(LCD_X_LINE_TITLE,LCD_Y_LINE_FIRST+LCD_Y_LINE_HEIGHT, imgData) 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 end 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 ------------------------------------------------------------------------------------------------------------ local function GUI_HandleEvent(event, touchState) local ctx = DSM_Context local menu = ctx.Menu local menuLines = ctx.MenuLines 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() else if ctx.isEditing() then -- Editing a Line, need to restore original value ctx.MenuLines[ctx.EditLine].Val = originalValue dsmLib.Value_Write_Validate(menuLines[ctx.EditLine]) else dsmLib.ChangePhase(PHASE.EXIT) -- 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, 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, 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 RX value as 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\n",dsmLib.phase2String(ctx.Phase)) end if ctx.SelLine == dsmLib.BACK_BUTTON then -- Back dsmLib.GotoMenu(menu.BackId,0x80) elseif ctx.SelLine == dsmLib.NEXT_BUTTON then -- Next dsmLib.GotoMenu(menu.NextId,0x82) elseif ctx.SelLine == dsmLib.PREV_BUTTON then -- Prev dsmLib.GotoMenu(menu.PrevId,0x81) elseif menuLines[ctx.SelLine].ValId ~= 0 then if menuLines[ctx.SelLine].Type == LINE_TYPE.MENU then -- Next menu exist if (menuLines[ctx.SelLine].ValId==0xFFF1) then -- SPECIAL Simulation menu to Simulator GUI_SwitchToSIM() elseif (menuLines[ctx.SelLine].ValId==0xFFF2) then -- SPECIAL Simulation menu to go to RX GUI_SwitchToRX() else dsmLib.GotoMenu(menuLines[ctx.SelLine].ValId, ctx.SelLine) -- ValId is the MenuId to navigate to end else -- Editing a Line???? if ctx.isEditing() then -- Change the Value and exit edit dsmLib.Value_Write_Validate(menuLines[ctx.SelLine]) else -- enter Edit the current line ctx.EditLine = ctx.SelLine originalValue = menuLines[ctx.SelLine].Val dsmLib.ChangePhase(PHASE.VALUE_CHANGING_WAIT) end end end end end local function init_screen_pos() -- osName in OpenTX is nil, otherwise is EDGETX local ver, radio, maj, minor, rev, osname = getVersion() if (osname==nil) then osname = "OpenTX" end -- OTX 2.3.14 and below returns nil IS_EDGETX = string.sub(osname,1,1) =='E' if LCD_W == 480 then -- TX16 -- use defaults in the script header elseif LCD_W == 128 then --TX12 (128x64) -- Still needs some work on the vertical DEBUG_ON_LCD = false -- no space for this TEXT_SIZE = SMLSIZE LCD_W_USABLE = 128 LCD_W_BUTTONS = 16 LCD_H_BUTTONS = 10 LCD_X_RIGHT_BUTTONS = 128 - LCD_W_BUTTONS - 3 LCD_X_LINE_MENU = 0 -- X offsets for (Title: [Value] debugInfo) lines LCD_X_LINE_TITLE = 0 LCD_X_LINE_VALUE = 75 LCD_X_LINE_DEBUG = 110 LCD_Y_LINE_HEIGHT = 7 LCD_Y_MENU_TITLE = 0 LCD_Y_LINE_FIRST = LCD_Y_MENU_TITLE + 8 LCD_Y_LOWER_BUTTONS = LCD_Y_LINE_FIRST + (7 * LCD_Y_LINE_HEIGHT) end end local function GUI_Warning(event) lcd.clear() local header = "DSM Forward Programming "..VERSION.." " --Draw title if (LCD_W > 128) then lcd.drawFilledRectangle(0, 0, LCD_W, 17, TITLE_BGCOLOR) lcd.drawText(5, 0, header, MENU_TITLE_COLOR + TEXT_SIZE) lcd.drawText(100,20,"INFO", BOLD) lcd.drawText(5,40,"DSM Forward programing shares TX Servo/Output settings", TEXT_SIZE) lcd.drawText(5,60,"with the RX. Make sure you setup your plane first in ", TEXT_SIZE) lcd.drawText(5,80,"the TX before your start programming your RX.", TEXT_SIZE) lcd.drawText(5,100,"Wing & Tail type can be configured using this tool.", TEXT_SIZE) lcd.drawText(5,150,"TX Servo settings are sent to the RX during 'Initial Setup'", TEXT_SIZE) lcd.drawText(5,170,"as well as when using RX menu 'Relearn Servo Settings'", TEXT_SIZE) lcd.drawText(5,200,"ALWAYS TEST Gyro reactions after this conditions before flying.", BOLD+TEXT_SIZE) lcd.drawText(100,250," OK ", INVERS + BOLD + TEXT_SIZE) else lcd.drawText(0,15,"Make sure you setup your plane", TEXT_SIZE) lcd.drawText(0,22,"first. Wing and Tail type.", TEXT_SIZE) lcd.drawText(0,30,"TX Servo settings are sent to ", TEXT_SIZE) lcd.drawText(0,37,"the RX during 'Initial Setup' and ", TEXT_SIZE) lcd.drawText(0,45,"ALWAYS TEST Gyro reactions", TEXT_SIZE) lcd.drawText(0,52,"before flying!!!", TEXT_SIZE) lcd.drawText(10,0," OK ", INVERS + BOLD + TEXT_SIZE) end if event == EVT_VIRTUAL_EXIT or event == EVT_VIRTUAL_ENTER then warningScreenON = false end return 0 end ------------------------------------------------------------------------------------------------------------ -- Init local function DSM_Init() init_screen_pos() dsmLib.Init(toolName) -- Initialize Library return dsmLib.StartConnection() end ------------------------------------------------------------------------------------------------------------ -- Main local function DSM_Run(event) local ctx = DSM_Context if event == nil then error("Cannot be run as a model script!") dsmLib.LOG_close() return 2 end if (warningScreenON) then return GUI_Warning(event) end GUI_HandleEvent(event) 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.EditLine or (ctx.Phase == PHASE.RX_VERSION)) then -- Editing or Requesting RX Version? ctx.Refresh_Display=true refreshInterval = 20 -- 200ms end if (not IS_EDGETX) then -- OPENTX NEEDS REFRESH ON EVERY CYCLE GUI_Display() -- Refresh display only if needed and no faster than 500ms, utilize more CPU to speedup DSM communications elseif (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 }