mirror of
https://github.com/pascallanger/DIY-Multiprotocol-TX-Module.git
synced 2025-02-04 22:58:10 +00:00
952 lines
30 KiB
Lua
952 lines
30 KiB
Lua
|
local toolName = "TNS|DSM Frwd Prog v0.56a (MIN-SETUP)|TNE"
|
||
|
|
||
|
---- #########################################################################
|
||
|
---- # #
|
||
|
---- # 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. #
|
||
|
---- # #
|
||
|
---- #########################################################################
|
||
|
|
||
|
|
||
|
local VERSION = "v0.56"
|
||
|
local DSMLIB_PATH = "/SCRIPTS/TOOLS/DSMLIB/"
|
||
|
local DATA_PATH = "/MODELS/DSMDATA"
|
||
|
|
||
|
local LOG_FILE = "/LOGS/dsm_min_log.txt"
|
||
|
|
||
|
-- Phase
|
||
|
local PH_INIT = 0
|
||
|
local PH_RX_VER, PH_TITLE = 1, 2
|
||
|
local PH_VAL_CHANGING, PH_VAL_EDITING, PH_VAL_EDIT_END = 6, 7, 8
|
||
|
local PH_WAIT_CMD, PH_EXIT_REQ, PH_EXIT_DONE = 9, 10, 11
|
||
|
|
||
|
-- Line Types
|
||
|
local LT_MENU, LT_LIST_NC = 0x1C, 0x6C
|
||
|
|
||
|
local Phase = PH_INIT
|
||
|
|
||
|
local Text = {}
|
||
|
local List_Text = {}
|
||
|
local List_Text_Img = {}
|
||
|
|
||
|
local originalValue = 0
|
||
|
|
||
|
local ctx_SelLine = 0 -- Current Selected Line
|
||
|
local ctx_EditLine = nil -- Current Editing Line
|
||
|
|
||
|
local Menu = { MenuId = 0, Text = "", TextId = 0, PrevId = 0, NextId = 0, BackId = 0 }
|
||
|
local MenuLines = {}
|
||
|
|
||
|
local logFile = nil
|
||
|
|
||
|
local LCD_W_BUTTONS = 19
|
||
|
local LCD_H_BUTTONS = 10
|
||
|
|
||
|
local LCD_X_MAX = 128
|
||
|
local LCD_X_RIGHT_BUTTONS = LCD_X_MAX - LCD_W_BUTTONS - 1
|
||
|
|
||
|
local LCD_Y_LINE_HEIGHT = 7
|
||
|
local LCD_Y_LOWER_BUTTONS = (8 * LCD_Y_LINE_HEIGHT) + 2
|
||
|
|
||
|
local TEXT_ATTR = SMLSIZE
|
||
|
|
||
|
local TX_CHANNELS = 12
|
||
|
|
||
|
local AT_PLANE = 0
|
||
|
|
||
|
local aircraft_type_text = {[0]="Plane","Heli","Glider","Drone"}
|
||
|
|
||
|
local WT_A1 = 0
|
||
|
local WT_A2 = 1
|
||
|
local WT_FLPR = 2
|
||
|
local WT_A1_F1 = 3
|
||
|
local WT_A2_F1 = 4
|
||
|
local WT_A2_F2 = 5
|
||
|
local WT_ELEVON_A = 6
|
||
|
local WT_ELEVON_B = 7
|
||
|
|
||
|
local wing_type_text = {[0]="Normal","Dual Ail","Flapperon", "Ail + Flp","Dual Ail + Flp","Dual Ail/Flp","Elevon A","Elevon B"}
|
||
|
|
||
|
local TT_R1 = 0
|
||
|
local TT_R1_E1 = 1
|
||
|
local TT_R1_E2 = 2
|
||
|
local TT_R2_E1 = 3
|
||
|
local TT_R2_E2 = 4
|
||
|
local TT_VT_A = 5
|
||
|
local TT_VT_B = 6
|
||
|
local TT_TLRN_A = 7
|
||
|
local TT_TLRN_B = 8
|
||
|
local TT_TLRN_A_R2 = 9
|
||
|
local TT_TLRN_B_R2 = 10
|
||
|
|
||
|
local tail_type_text = {[0]="Rud Only","Normal","Rud + Dual Ele","Dual Rud + Elv","Dual Rud/Ele",
|
||
|
"VTail A","VTail B","Taileron A","Taileron B",
|
||
|
"Taileron A + Dual Rud","Taileron B + Dual Rud"
|
||
|
}
|
||
|
|
||
|
local MT_NORMAL = 0
|
||
|
local MT_REVERSE = 1
|
||
|
|
||
|
local P1 = 0
|
||
|
local P2 = 1
|
||
|
local P3 = 2
|
||
|
local P4 = 3
|
||
|
local P5 = 4
|
||
|
local P6 = 5
|
||
|
local P7 = 6
|
||
|
local P8 = 7
|
||
|
--local P9 = 8
|
||
|
--local P10 = 9
|
||
|
|
||
|
local MV_AIRCRAFT_TYPE = 1001
|
||
|
local MV_WING_TYPE = 1002
|
||
|
local MV_TAIL_TYPE = 1003
|
||
|
|
||
|
local MV_CH_BASE = 1010
|
||
|
local MV_CH_THR = 1010
|
||
|
|
||
|
local MV_CH_L_AIL = 1011
|
||
|
local MV_CH_R_AIL = 1012
|
||
|
local MV_CH_L_FLP = 1013
|
||
|
local MV_CH_R_FLP = 1014
|
||
|
|
||
|
local MV_CH_L_RUD = 1015
|
||
|
local MV_CH_R_RUD = 1016
|
||
|
local MV_CH_L_ELE = 1017
|
||
|
local MV_CH_R_ELE = 1018
|
||
|
|
||
|
local MV_PORT_BASE = 1020
|
||
|
local MV_P1_MODE = 1020
|
||
|
--local MV_P2_MODE = 1021
|
||
|
--local MV_P3_MODE = 1022
|
||
|
--local MV_P4_MODE = 1023
|
||
|
--local MV_P5_MODE = 1024
|
||
|
local MV_P6_MODE = 1025
|
||
|
--local MV_P7_MODE = 1026
|
||
|
--local MV_P8_MODE = 1027
|
||
|
--local MV_P9_MODE = 1028
|
||
|
--local MV_P10_MODE = 1029
|
||
|
|
||
|
local MV_DATA_END = 1040
|
||
|
|
||
|
-- MENU DATA Management
|
||
|
local M_DB = {} -- Store the variables used in the Menus.
|
||
|
|
||
|
local lastGoodMenu=0
|
||
|
|
||
|
local currATyp = -1
|
||
|
local currTTyp = -1
|
||
|
local currWTyp = -1
|
||
|
|
||
|
local menuDataChanged = false
|
||
|
|
||
|
local MODEL = {
|
||
|
modelName = "", -- The name of the model comming from OTX/ETX
|
||
|
hashName = nil,
|
||
|
modelOutputChannel = {}, -- Output information from OTX/ETX
|
||
|
|
||
|
TX_CH_TEXT= { },
|
||
|
PORT_TEXT = { },
|
||
|
|
||
|
DSM_ChannelInfo = {} -- Data Created by DSM Configuration Script
|
||
|
}
|
||
|
|
||
|
local function gc()
|
||
|
collectgarbage("collect")
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
local function gcTable(t)
|
||
|
if type(t)=="table" then
|
||
|
for i,v in pairs(t) do
|
||
|
if type(v) == "table" then
|
||
|
gcTable(v)
|
||
|
end
|
||
|
t[i] = nil
|
||
|
end
|
||
|
end
|
||
|
gc()
|
||
|
return t
|
||
|
end
|
||
|
--]]
|
||
|
|
||
|
local function LOG_open()
|
||
|
logFile = io.open(LOG_FILE, "w") -- Truncate Log File
|
||
|
end
|
||
|
|
||
|
local function LOG_write(...)
|
||
|
if (logFile == nil) then LOG_open() end
|
||
|
local str = string.format(...)
|
||
|
io.write(logFile, str)
|
||
|
end
|
||
|
|
||
|
local function LOG_close()
|
||
|
if (logFile ~= nil) then io.close(logFile) end
|
||
|
end
|
||
|
|
||
|
-- Saves MENU_DATA to a file
|
||
|
local function ST_SaveFileData()
|
||
|
local fname = MODEL.hashName
|
||
|
|
||
|
print("Saving File:"..fname)
|
||
|
local dataFile = assert(io.open(DATA_PATH .. "/" .. fname, "w"),"Please create "..DATA_PATH.." folder") -- write File
|
||
|
|
||
|
-- Foreach MENU_DATA with a value write Var_Id:Value into file
|
||
|
for i = 0, MV_DATA_END do
|
||
|
if (M_DB[i]~=nil) then
|
||
|
io.write(dataFile,string.format("%s:%s\n",i,M_DB[i]))
|
||
|
end
|
||
|
end
|
||
|
io.close(dataFile)
|
||
|
end
|
||
|
|
||
|
local function tailTypeCompatible(a,b)
|
||
|
|
||
|
local function normalize(tt)
|
||
|
if (tt==TT_TLRN_A or tt==TT_TLRN_B) then
|
||
|
return TT_TLRN_A
|
||
|
elseif (tt==TT_TLRN_A_R2 or tt==TT_TLRN_B_R2) then
|
||
|
return TT_TLRN_A_R2
|
||
|
elseif (tt==TT_VT_A or tt==TT_VT_B) then
|
||
|
return TT_VT_A
|
||
|
else
|
||
|
return tt
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return (normalize(a)==normalize(b))
|
||
|
end
|
||
|
|
||
|
local function ST_PlaneWingInit(wingType)
|
||
|
--print("Change Plane WingType:"..wing_type_text[wingType])
|
||
|
|
||
|
M_DB[MV_WING_TYPE] = wingType
|
||
|
|
||
|
-- Clear all Wing Data
|
||
|
M_DB[MV_CH_L_AIL] = nil
|
||
|
M_DB[MV_CH_R_AIL] = nil
|
||
|
M_DB[MV_CH_L_FLP] = nil
|
||
|
M_DB[MV_CH_R_FLP] = nil
|
||
|
|
||
|
M_DB[MV_CH_THR] = P1
|
||
|
|
||
|
-- Default Channel Assisgments for each Wing type
|
||
|
|
||
|
if (wingType==WT_A1) then
|
||
|
M_DB[MV_CH_L_AIL] = P2
|
||
|
elseif (wingType==WT_A2 or wingType==WT_FLPR) then
|
||
|
M_DB[MV_CH_L_AIL] = P6
|
||
|
M_DB[MV_CH_R_AIL] = P2
|
||
|
elseif (wingType==WT_A1_F1) then
|
||
|
M_DB[MV_CH_L_AIL] = P2
|
||
|
M_DB[MV_CH_L_FLP] = P6
|
||
|
elseif (wingType==WT_A2_F1) then
|
||
|
M_DB[MV_CH_L_AIL] = P6
|
||
|
M_DB[MV_CH_R_AIL] = P2
|
||
|
M_DB[MV_CH_L_FLP] = P5
|
||
|
elseif (wingType==WT_A2_F2) then
|
||
|
M_DB[MV_CH_L_AIL] = P6
|
||
|
M_DB[MV_CH_R_AIL] = P2
|
||
|
M_DB[MV_CH_R_FLP] = P5
|
||
|
M_DB[MV_CH_L_FLP] = P7
|
||
|
elseif (wingType==WT_ELEVON_A) then
|
||
|
M_DB[MV_CH_L_AIL] = P2
|
||
|
M_DB[MV_CH_R_AIL] = P3
|
||
|
elseif (wingType==WT_ELEVON_B) then
|
||
|
M_DB[MV_CH_L_AIL] = P3
|
||
|
M_DB[MV_CH_R_AIL] = P2
|
||
|
else -- Assume normal
|
||
|
print("ERROR: Invalid Wing Type")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function ST_PlaneTailInit(tailType)
|
||
|
if (M_DB[MV_WING_TYPE]==WT_ELEVON_A or
|
||
|
M_DB[MV_WING_TYPE]==WT_ELEVON_B) then
|
||
|
tailType = TT_R1 -- Delta only have ruder
|
||
|
end
|
||
|
|
||
|
--print("Change Plane Tail Type:"..tail_type_text[tailType])
|
||
|
|
||
|
-- Clear all data for Tail
|
||
|
M_DB[MV_TAIL_TYPE] = tailType
|
||
|
M_DB[MV_CH_L_ELE] = nil
|
||
|
M_DB[MV_CH_R_ELE] = nil
|
||
|
M_DB[MV_CH_L_RUD] = nil
|
||
|
M_DB[MV_CH_R_RUD] = nil
|
||
|
|
||
|
-- Setup Channels for different Tail types
|
||
|
if (tailType == TT_R1) then
|
||
|
M_DB[MV_CH_L_RUD] = P4
|
||
|
elseif (tailType == TT_R1_E1) then
|
||
|
M_DB[MV_CH_L_ELE] = P3
|
||
|
M_DB[MV_CH_L_RUD] = P4
|
||
|
elseif (tailType == TT_R1_E2) then
|
||
|
M_DB[MV_CH_L_ELE] = P5
|
||
|
M_DB[MV_CH_R_ELE] = P3
|
||
|
M_DB[MV_CH_L_RUD] = P4
|
||
|
elseif (tailType == TT_R2_E1) then
|
||
|
M_DB[MV_CH_L_ELE] = P3
|
||
|
M_DB[MV_CH_L_RUD] = P4
|
||
|
M_DB[MV_CH_R_RUD] = P5
|
||
|
elseif (tailType == TT_R2_E2) then
|
||
|
M_DB[MV_CH_L_ELE] = P5
|
||
|
M_DB[MV_CH_R_ELE] = P3
|
||
|
M_DB[MV_CH_L_RUD] = P4
|
||
|
M_DB[MV_CH_R_RUD] = P6
|
||
|
elseif (tailType == TT_VT_A or tailType == TT_VT_B) then
|
||
|
M_DB[MV_CH_L_ELE] = P3
|
||
|
M_DB[MV_CH_R_ELE] = P4
|
||
|
elseif (tailType == TT_TLRN_A or tailType == TT_TLRN_B or
|
||
|
tailType == TT_TLRN_A_R2 or tailType == TT_TLRN_B_R2) then
|
||
|
M_DB[MV_CH_L_RUD] = P4
|
||
|
M_DB[MV_CH_L_ELE] = P5
|
||
|
M_DB[MV_CH_R_ELE] = P3
|
||
|
else -- Assume Normal
|
||
|
print("ERROR:invalid Tail Type")
|
||
|
end
|
||
|
|
||
|
if (tailType == TT_TLRN_A_R2 or tailType == TT_TLRN_B_R2) then
|
||
|
M_DB[MV_CH_R_RUD] = P8
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function ST_AircraftInit(aircraftType)
|
||
|
M_DB[MV_AIRCRAFT_TYPE] = aircraftType
|
||
|
ST_PlaneWingInit(WT_A1)
|
||
|
ST_PlaneTailInit(TT_R1_E1)
|
||
|
end
|
||
|
|
||
|
|
||
|
-- Setup Initial Default Data for the Menus
|
||
|
local function ST_Default_Data()
|
||
|
ST_AircraftInit(AT_PLANE)
|
||
|
|
||
|
for i=0,9 do
|
||
|
M_DB[MV_P1_MODE+i] = MT_NORMAL + MODEL.modelOutputChannel[P1+i].revert
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function MenuLinePostProcessing(line)
|
||
|
line.MenuId = Menu.MenuId
|
||
|
|
||
|
if line.Type == LT_MENU then
|
||
|
-- nothing to do on menu entries
|
||
|
line.Val=nil
|
||
|
elseif line.Type == LT_LIST_NC then
|
||
|
-- Normalize Min/Max to be relative to Zero
|
||
|
line.TextStart = line.Min
|
||
|
line.Def = line.Def - line.Min -- normalize default value
|
||
|
line.Max = line.Max - line.Min -- normalize max index
|
||
|
line.Min = 0 -- min index
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
local function portUse(p)
|
||
|
local out = nil
|
||
|
if p==M_DB[MV_CH_THR] then out = "Thr"
|
||
|
elseif p == M_DB[MV_CH_L_AIL] then
|
||
|
out=(M_DB[MV_CH_R_AIL] and "Ail_L") or "Ail"
|
||
|
elseif p == M_DB[MV_CH_R_AIL] then out="Ail_R"
|
||
|
elseif p == M_DB[MV_CH_L_ELE] then
|
||
|
out=(M_DB[MV_CH_R_ELE] and "Ele_L") or "Ele"
|
||
|
elseif p == M_DB[MV_CH_R_ELE] then out="Ele_R"
|
||
|
elseif p == M_DB[MV_CH_L_RUD] then
|
||
|
out=(M_DB[MV_CH_R_RUD] and "Rud_L") or "Rud"
|
||
|
elseif p == M_DB[MV_CH_R_RUD] then out="Rud-R"
|
||
|
elseif p == M_DB[MV_CH_L_FLP] then
|
||
|
out=(M_DB[MV_CH_R_FLP] and "Flp_L") or "Flp"
|
||
|
elseif p == M_DB[MV_CH_R_FLP] then out="Flp_R"
|
||
|
else
|
||
|
out = ""
|
||
|
end
|
||
|
return out
|
||
|
end
|
||
|
|
||
|
-- Creates the menus to Render with the GUI
|
||
|
local function ST_LoadMenu(menuId)
|
||
|
|
||
|
local function Header(p)
|
||
|
return MODEL.PORT_TEXT[p].." "..portUse(p)
|
||
|
end
|
||
|
|
||
|
local function generateGyroReverse(menuId, P_BASE, V_BASE)
|
||
|
for i=0,4 do
|
||
|
MenuLines[i] = { Type = LT_LIST_NC, Text=Header(P_BASE+i), TextId = 0, ValId = V_BASE+i, Min=45, Max=46, Def=45, Val=M_DB[V_BASE+i] }
|
||
|
end
|
||
|
|
||
|
MenuLines[5] = { Type = LT_MENU, Text="Only TAER affects AS3X/SAFE react dir", TextId = 0, ValId = menuId }
|
||
|
MenuLines[6] = { Type = LT_MENU, Text="If changes, RX 'Relearn Servo'", TextId = 0, ValId = menuId }
|
||
|
|
||
|
ctx_SelLine = 0
|
||
|
end
|
||
|
|
||
|
-- Begin
|
||
|
for i = 0, 6 do -- clear menu
|
||
|
MenuLines[i] = { MenuId = 0, lineNum = 0, Type = 0 }
|
||
|
end
|
||
|
|
||
|
|
||
|
if (menuId==0x1000) then -- MAIN MENU
|
||
|
Menu = { MenuId = 0x1000, Text = "Save-Exit ("..MODEL.modelName..")", PrevId = 0, NextId = 0, BackId = 0, TextId=0 }
|
||
|
|
||
|
if (true) then
|
||
|
MenuLines[4] = { Type = LT_MENU, Text="Save Changes", TextId = 0, ValId = 0x1005 }
|
||
|
MenuLines[5] = { Type = LT_MENU, Text="Discard Changes", TextId = 0, ValId = 0xFFF9 }
|
||
|
ctx_SelLine = 4
|
||
|
end
|
||
|
lastGoodMenu = menuId
|
||
|
elseif (menuId==0x1001) then -- MODEL SETUP
|
||
|
local backId = 0xFFF9 -- No changes, just exit
|
||
|
local title = "Setup ("..MODEL.modelName..")"
|
||
|
if (menuDataChanged) then
|
||
|
backId = 0x1000 -- Go to Save menu
|
||
|
title = title.." *"
|
||
|
end
|
||
|
Menu = { MenuId = 0x1001, Text = title, PrevId = 0, NextId = 0, BackId = backId, TextId=0 }
|
||
|
MenuLines[0] = { Type = LT_MENU, Text = "Aircraft Setup", ValId = 0x1010,TextId=0 }
|
||
|
MenuLines[1] = { Type = LT_MENU, Text = "Wing & Tail Channels ", ValId = 0x1020, TextId=0 }
|
||
|
MenuLines[3] = { Type = LT_MENU, Text = "Gyro Channel Reverse", ValId = 0x1030, TextId=0 }
|
||
|
MenuLines[5] = { Type = LT_MENU, Text = "WARNING: Changing of Aircraft", ValId = 0x1001, TextId=0 }
|
||
|
MenuLines[6] = { Type = LT_MENU, Text = "deletes prev Ch/Port assgmt.", ValId = 0x1001, TextId=0 }
|
||
|
|
||
|
ctx_SelLine = 0
|
||
|
lastGoodMenu = menuId
|
||
|
elseif (menuId==0x1005) then
|
||
|
ST_SaveFileData()
|
||
|
menuDataChanged = false
|
||
|
|
||
|
local msg1 = "Data saved to: "
|
||
|
local msg2 = " ../DSMLIB/"..MODEL.hashName
|
||
|
|
||
|
Menu = { MenuId = 0x1005, Text = "Config Saved", PrevId = 0, NextId = 0, BackId = 0, TextId=0 }
|
||
|
MenuLines[2] = { Type = LT_MENU, Text=msg1, TextId = 0, ValId = 0x1005 }
|
||
|
MenuLines[3] = { Type = LT_MENU, Text=msg2, TextId = 0, ValId = 0x1005 }
|
||
|
MenuLines[6] = { Type = LT_MENU, Text="Complete", TextId = 0, ValId = 0xFFF9 }
|
||
|
ctx_SelLine = 6
|
||
|
lastGoodMenu = menuId
|
||
|
elseif (menuId==0x1010) then
|
||
|
Menu = { MenuId = 0x1010, Text = "Aircraft", PrevId = 0, NextId = 0x1011, BackId = 0x1001, TextId=0 }
|
||
|
MenuLines[5] = { Type = LT_LIST_NC, Text="Aircraft Type", TextId = 0, ValId = MV_AIRCRAFT_TYPE, Min=15, Max=15, Def=15, Val=M_DB[MV_AIRCRAFT_TYPE] }
|
||
|
ctx_SelLine = 5
|
||
|
lastGoodMenu = menuId
|
||
|
elseif (menuId==0x1011) then
|
||
|
Menu = { MenuId = 0x1011, Text = "Model Type: "..aircraft_type_text[currATyp], PrevId = 0, NextId = 0x1020, BackId = 0x1010, TextId=0 }
|
||
|
MenuLines[5] = { Type = LT_LIST_NC, Text="Wing Type", TextId = 0, ValId = MV_WING_TYPE, Min=20, Max=27, Def=20, Val=M_DB[MV_WING_TYPE] }
|
||
|
MenuLines[6] = { Type = LT_LIST_NC, Text="Tail Type", TextId = 0, ValId = MV_TAIL_TYPE, Min=30, Max=40, Def=30, Val=M_DB[MV_TAIL_TYPE] }
|
||
|
ctx_SelLine = 5
|
||
|
lastGoodMenu = menuId
|
||
|
elseif (menuId==0x1020) then
|
||
|
------ WING SETUP -------
|
||
|
local thr = M_DB[MV_CH_THR]
|
||
|
local leftAil = M_DB[MV_CH_L_AIL]
|
||
|
local rightAil = M_DB[MV_CH_R_AIL]
|
||
|
local leftFlap = M_DB[MV_CH_L_FLP]
|
||
|
local rightFlap = M_DB[MV_CH_R_FLP]
|
||
|
|
||
|
local thrText = "Thr"
|
||
|
local leftAilText = "Left Ail"
|
||
|
local rightAilText = "Right Ail"
|
||
|
local leftFlapText = "Left Flap"
|
||
|
local rightFlapText = "Right Flap"
|
||
|
|
||
|
if (rightAil==nil) then leftAilText = "Aileron" end
|
||
|
if (rightFlap==nil) then leftFlapText = "Flap" end
|
||
|
|
||
|
local title = aircraft_type_text[currATyp].." Wing:"..wing_type_text[currWTyp]
|
||
|
|
||
|
Menu = { MenuId = 0x1020, Text = title, PrevId = 0, NextId = 0x1021, BackId = 0x1011, TextId=0 }
|
||
|
|
||
|
MenuLines[0] = { Type = LT_LIST_NC, Text=thrText, TextId = 0, ValId = MV_CH_THR, Min=0, Max=10, Def=0, Val= thr }
|
||
|
|
||
|
MenuLines[2] = { Type = LT_LIST_NC, Text=leftAilText, TextId = 0, ValId = MV_CH_L_AIL, Min=0, Max=9, Def=0, Val= leftAil }
|
||
|
|
||
|
if (rightAil) then
|
||
|
MenuLines[3] = { Type = LT_LIST_NC, Text=rightAilText, TextId = 0, ValId = MV_CH_R_AIL, Min=0, Max=9, Def=0, Val= rightAil }
|
||
|
end
|
||
|
|
||
|
if (leftFlap) then
|
||
|
MenuLines[4] = { Type = LT_LIST_NC, Text=leftFlapText, TextId = 0, ValId = MV_CH_L_FLP, Min=0, Max=9, Def=0, Val= leftFlap }
|
||
|
end
|
||
|
if (rightFlap) then
|
||
|
MenuLines[5] = { Type = LT_LIST_NC, Text=rightFlapText, TextId = 0, ValId = MV_CH_R_FLP, Min=0, Max=9, Def=0, Val= rightFlap }
|
||
|
end
|
||
|
|
||
|
ctx_SelLine = 0
|
||
|
lastGoodMenu = menuId
|
||
|
|
||
|
elseif (menuId==0x1021) then
|
||
|
------ TAIL SETUP -------
|
||
|
local leftRud = M_DB[MV_CH_L_RUD]
|
||
|
local rightRud = M_DB[MV_CH_R_RUD]
|
||
|
local leftEle = M_DB[MV_CH_L_ELE]
|
||
|
local rightEle = M_DB[MV_CH_R_ELE]
|
||
|
|
||
|
local leftRudText = "Left Rud"
|
||
|
local rightRudText = "Right Rud"
|
||
|
|
||
|
local leftElvText = "Left Ele"
|
||
|
local rightElvText = "Right Ele"
|
||
|
|
||
|
if (rightRud==nil) then leftRudText = "Rud" end
|
||
|
if (rightEle==nil) then leftElvText = "Ele" end
|
||
|
|
||
|
local title = aircraft_type_text[currATyp].." Tail:"..tail_type_text[currTTyp]
|
||
|
|
||
|
Menu = { MenuId = 0x1021, Text = title, PrevId = 0, NextId = 0x1001, BackId = 0x1020, TextId=0 }
|
||
|
if (leftRud) then
|
||
|
MenuLines[1] = { Type = LT_LIST_NC, Text=leftRudText, TextId = 0, ValId = MV_CH_L_RUD, Min=0, Max=9, Def=0, Val= leftRud}
|
||
|
end
|
||
|
|
||
|
if (rightRud) then
|
||
|
MenuLines[2] = { Type = LT_LIST_NC, Text=rightRudText, TextId = 0, ValId = MV_CH_R_RUD, Min=0, Max=9, Def=0, Val=rightRud }
|
||
|
end
|
||
|
|
||
|
if (leftEle) then
|
||
|
MenuLines[4] = { Type = LT_LIST_NC, Text=leftElvText, TextId = 0, ValId = MV_CH_L_ELE, Min=0, Max=9, Def=0, Val=leftEle }
|
||
|
end
|
||
|
|
||
|
if (rightEle) then
|
||
|
MenuLines[5] = { Type = LT_LIST_NC, Text=rightElvText, TextId = 0, ValId = MV_CH_R_ELE, Min=0, Max=9, Def=0, Val=rightEle }
|
||
|
end
|
||
|
|
||
|
ctx_SelLine = 1
|
||
|
lastGoodMenu = menuId
|
||
|
|
||
|
elseif (menuId==0x1030) then
|
||
|
Menu = { MenuId = 0x1030, Text = "Gyro Reverse (Port 1-5)", PrevId = 0, NextId = 0x1031, BackId = 0x1001, TextId=0 }
|
||
|
generateGyroReverse(menuId,P1,MV_P1_MODE)
|
||
|
lastGoodMenu = menuId
|
||
|
elseif (menuId==0x1031) then
|
||
|
Menu = { MenuId = 0x1031, Text = "Gyro Reverse (Port 6-10)", PrevId = 0x1030, NextId = 0, BackId = 0x1001, TextId=0 }
|
||
|
generateGyroReverse(menuId,P6,MV_P6_MODE)
|
||
|
lastGoodMenu = menuId
|
||
|
elseif (menuId==0xFFF9) then
|
||
|
ChangePhase(PH_EXIT_DONE)
|
||
|
return
|
||
|
else
|
||
|
Menu = { MenuId = 0x0002, Text = "NOT IMPLEMENTED", TextId = 0, PrevId = 0, NextId = 0, BackId = lastGoodMenu }
|
||
|
ctx_SelLine = -1 -- BACK BUTTON
|
||
|
end
|
||
|
|
||
|
for i = 0, 6 do
|
||
|
if (MenuLines[i].Type > 0) then
|
||
|
MenuLinePostProcessing(MenuLines[i])
|
||
|
end
|
||
|
end
|
||
|
gc()
|
||
|
end
|
||
|
|
||
|
-- Inital List and Image Text for this menus
|
||
|
local function ST_Init_Text(rxId)
|
||
|
-- Channel Names use the Port Text Retrived from OTX/ETX
|
||
|
local p = 0
|
||
|
|
||
|
for i = 0, 9 do List_Text[i] = MODEL.PORT_TEXT[i] end
|
||
|
List_Text[10] = "--"
|
||
|
|
||
|
-- Aircraft Type
|
||
|
List_Text[15+AT_PLANE] = "Airplane";
|
||
|
|
||
|
-- Wing Types
|
||
|
p = 20+WT_A1; List_Text[p] = "Single Ail"; --List_Text_Img[p] = "x.png|Single Aileron"
|
||
|
p = 20+WT_A2; List_Text[p] = "Dual Ail"; --List_Text_Img[p] = "x.png|Dual Aileron"
|
||
|
p = 20+WT_FLPR; List_Text[p] = "Flaperon"; --List_Text_Img[p] = "x.png|Flaperon"
|
||
|
p = 20+WT_A1_F1; List_Text[p] = "Ail + Flap"; --List_Text_Img[p] = "x.png|Aileron + Flap"
|
||
|
p = 20+WT_A2_F1; List_Text[p] = "Dual Ail + Flap"; --List_Text_Img[p] = "x.png|Dual Aileron + Flap"
|
||
|
p = 20+WT_A2_F2; List_Text[p] = "Dual Ail + Dual Flap"; --List_Text_Img[p] = "x.png|Dual Aileron + Dual Flap"
|
||
|
p = 20+WT_ELEVON_A; List_Text[p] = "Delta A"; --List_Text_Img[p] = "x.png|Delta/Elevon A"
|
||
|
p = 20+WT_ELEVON_B; List_Text[p] = "Delta B"; --List_Text_Img[p] = "x.png|Delta/Elevon B"
|
||
|
|
||
|
-- Tail Types
|
||
|
p = 30+TT_R1; List_Text[p] = "Rudder Only"; --List_Text_Img[p] = "x.png|Rudder Only"
|
||
|
p = 30+TT_R1_E1; List_Text[p] = "Rud + Ele"; --List_Text_Img[p] = "x.png|Tail Normal"
|
||
|
p = 30+TT_R1_E2; List_Text[p] = "Rud + Dual Ele"; --List_Text_Img[p] = "x.png|Rud + Dual Ele"
|
||
|
p = 30+TT_R2_E1; List_Text[p] = "Dual Rud + Ele"; --List_Text_Img[p] = "x.png|Dual Rud + Ele"
|
||
|
p = 30+TT_R2_E2; List_Text[p] = "Dual Rud + Dual Ele"; --List_Text_Img[p] = "x.png|Dual Rud + Dual Elev"
|
||
|
p = 30+TT_VT_A; List_Text[p] = "V-Tail A"; --List_Text_Img[p] = "x.png|V-Tail A"
|
||
|
p = 30+TT_VT_B; List_Text[p] = "V-Tail B"; --List_Text_Img[p] = "x.png|V-Tail B"
|
||
|
p = 30+TT_TLRN_A; List_Text[p] = "Taileron A"; --List_Text_Img[p] = "x.png|Taileron A"
|
||
|
p = 30+TT_TLRN_B; List_Text[p] = "Taileron B"; --List_Text_Img[p] = "x.png|Taileron B"
|
||
|
p = 30+TT_TLRN_A_R2; List_Text[p] = "Taileron A + 2x Rud"; --List_Text_Img[p] = "x.png|Taileron A + Dual Rud"
|
||
|
p = 30+TT_TLRN_B_R2; List_Text[p] = "Taileron B + 2x Rud"; --List_Text_Img[p] = "x.png|Taileron B + Dual Rud"
|
||
|
|
||
|
-- Servo Reverse
|
||
|
List_Text[45+MT_NORMAL] = "Normal"
|
||
|
List_Text[45+MT_REVERSE] = "Reverse"
|
||
|
end
|
||
|
|
||
|
-- Initial Setup
|
||
|
local function ST_Init()
|
||
|
ST_Init_Text(0)
|
||
|
gc()
|
||
|
|
||
|
-- Setup default Data if no data loaded
|
||
|
menuDataChanged = false
|
||
|
if (M_DB[MV_AIRCRAFT_TYPE]==nil) then
|
||
|
ST_Default_Data()
|
||
|
menuDataChanged = true
|
||
|
end
|
||
|
|
||
|
currATyp = M_DB[MV_AIRCRAFT_TYPE]
|
||
|
currWTyp = M_DB[MV_WING_TYPE]
|
||
|
currTTyp = M_DB[MV_TAIL_TYPE]
|
||
|
|
||
|
Phase = PH_RX_VER
|
||
|
end
|
||
|
|
||
|
----- Line Type
|
||
|
|
||
|
local function isSelectable(line)
|
||
|
if (line.Type == LT_MENU and line.ValId == line.MenuId) then return false end -- Menu to same page
|
||
|
if (line.Type ~= LT_MENU and line.Max == 0) then return false end -- Read only data line
|
||
|
if (line.Type ~= 0 and line.TextId < 0x8000) then return true end -- Not Flight Mode
|
||
|
return false;
|
||
|
end
|
||
|
|
||
|
local function isListLine(line)
|
||
|
return line.Type==LT_LIST_NC
|
||
|
end
|
||
|
|
||
|
local function isEditing()
|
||
|
return ctx_EditLine ~= nil
|
||
|
end
|
||
|
|
||
|
-----------------------
|
||
|
local function Get_Text(index)
|
||
|
local out = Text[index] or string.format("Unknown_%X", index)
|
||
|
return out
|
||
|
end
|
||
|
|
||
|
local function Get_Text_Value(index)
|
||
|
local out = List_Text[index] or Get_Text(index)
|
||
|
return out
|
||
|
end
|
||
|
|
||
|
function ChangePhase(newPhase)
|
||
|
Phase = newPhase
|
||
|
end
|
||
|
|
||
|
local function Value_Add(dir)
|
||
|
local line = MenuLines[ctx_SelLine]
|
||
|
local inc = dir
|
||
|
|
||
|
line.Val = line.Val + inc
|
||
|
|
||
|
if line.Val > line.Max then
|
||
|
line.Val = line.Max
|
||
|
elseif line.Val < line.Min then
|
||
|
line.Val = line.Min
|
||
|
end
|
||
|
end
|
||
|
--------------
|
||
|
|
||
|
local function GotoMenu(menuId, lastSelectedLine)
|
||
|
Menu.MenuId = menuId
|
||
|
ctx_SelLine = lastSelectedLine
|
||
|
ChangePhase(PH_TITLE)
|
||
|
end
|
||
|
|
||
|
local function DSM_HandleEvent(event)
|
||
|
if event == EVT_VIRTUAL_EXIT then
|
||
|
if Phase == PH_RX_VER then
|
||
|
Phase = PH_EXIT_DONE -- Exit program
|
||
|
else
|
||
|
if isEditing() then -- Editing a Line, need to restore original value
|
||
|
MenuLines[ctx_EditLine].Val = originalValue
|
||
|
event = EVT_VIRTUAL_ENTER
|
||
|
else
|
||
|
if (Menu.BackId > 0 ) then -- Back??
|
||
|
ctx_SelLine = -1 --Back Button
|
||
|
event = EVT_VIRTUAL_ENTER
|
||
|
else
|
||
|
ChangePhase(PH_EXIT_REQ)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end -- Exit
|
||
|
|
||
|
if Phase == PH_RX_VER then return end -- nothing else to do
|
||
|
|
||
|
if event == EVT_VIRTUAL_NEXT then
|
||
|
if isEditing() then -- Editting?
|
||
|
Value_Add(1)
|
||
|
else
|
||
|
if ctx_SelLine < 7 then -- On a regular line
|
||
|
local num = ctx_SelLine -- Find the prev selectable
|
||
|
for i = ctx_SelLine + 1, 6, 1 do
|
||
|
local line = MenuLines[i]
|
||
|
if isSelectable(line) then
|
||
|
ctx_SelLine = i
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
if num == ctx_SelLine then -- No Selectable Line
|
||
|
if Menu.NextId ~= 0 then
|
||
|
ctx_SelLine = 7 -- Next
|
||
|
elseif Menu.PrevId ~= 0 then
|
||
|
ctx_SelLine = 8 -- Prev
|
||
|
end
|
||
|
end
|
||
|
elseif Menu.PrevId ~= 0 then
|
||
|
ctx_SelLine = 8 -- Prev
|
||
|
end
|
||
|
end
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if event == EVT_VIRTUAL_PREV then
|
||
|
if isEditing() then -- In Edit Mode
|
||
|
Value_Add(-1)
|
||
|
else
|
||
|
if ctx_SelLine == 8 and Menu.NextId ~= 0 then
|
||
|
ctx_SelLine = 7 -- Next
|
||
|
elseif ctx_SelLine > 0 then
|
||
|
if ctx_SelLine > 6 then
|
||
|
ctx_SelLine = 7 --NEXT
|
||
|
end
|
||
|
local num = ctx_SelLine -- Find Prev Selectable line
|
||
|
for i = ctx_SelLine - 1, 0, -1 do
|
||
|
local line = MenuLines[i]
|
||
|
if isSelectable(line) then
|
||
|
ctx_SelLine = i
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
if num == ctx_SelLine then -- No Selectable Line
|
||
|
if (Menu.BackId > 0) then
|
||
|
ctx_SelLine = -1 -- Back
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
ctx_SelLine = -1 -- Back
|
||
|
end
|
||
|
end
|
||
|
return
|
||
|
end
|
||
|
|
||
|
|
||
|
if event == EVT_VIRTUAL_ENTER then
|
||
|
if ctx_SelLine == -1 then -- Back
|
||
|
GotoMenu(Menu.BackId, 0x80)
|
||
|
elseif ctx_SelLine == 7 then -- Next
|
||
|
GotoMenu(Menu.NextId, 0x82)
|
||
|
elseif ctx_SelLine == 8 then -- Prev
|
||
|
GotoMenu(Menu.PrevId, 0x81)
|
||
|
elseif ctx_SelLine >= 0 and MenuLines[ctx_SelLine].Type == LT_MENU then
|
||
|
GotoMenu(MenuLines[ctx_SelLine].ValId, ctx_SelLine) -- ValId is the next menu
|
||
|
else
|
||
|
-- value entry
|
||
|
if isEditing() then
|
||
|
ctx_EditLine = nil -- Done Editting
|
||
|
ChangePhase(PH_VAL_EDIT_END)
|
||
|
else -- Start Editing
|
||
|
ctx_EditLine = ctx_SelLine
|
||
|
originalValue = MenuLines[ctx_SelLine].Val
|
||
|
ChangePhase(PH_VAL_EDITING)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function DSM_Send_Receive()
|
||
|
|
||
|
if Phase == PH_RX_VER then -- request RX version
|
||
|
Phase = PH_TITLE
|
||
|
Menu.MenuId = 0x01001
|
||
|
Refresh_Display = true
|
||
|
elseif Phase == PH_WAIT_CMD then
|
||
|
|
||
|
elseif Phase == PH_TITLE then -- request menu title
|
||
|
ST_LoadMenu(Menu.MenuId)
|
||
|
if (Phase~=PH_EXIT_DONE) then
|
||
|
Phase = PH_WAIT_CMD
|
||
|
end
|
||
|
Refresh_Display = true
|
||
|
elseif Phase == PH_VAL_EDIT_END then -- send value
|
||
|
local line = MenuLines[ctx_SelLine] -- Updated Value of SELECTED line
|
||
|
|
||
|
-- Update the menu data from the line
|
||
|
if (M_DB[line.ValId] ~= line.Val ) then
|
||
|
M_DB[line.ValId] = line.Val
|
||
|
print(string.format("MENU_DATA[%d/%s]=%d",line.ValId,line.Text, line.Val))
|
||
|
menuDataChanged=true
|
||
|
end
|
||
|
|
||
|
-- Did the Wing type change?
|
||
|
local wt = M_DB[MV_WING_TYPE]
|
||
|
if (currWTyp ~= wt) then
|
||
|
currWTyp = wt
|
||
|
ST_PlaneWingInit(currWTyp)
|
||
|
|
||
|
-- DELTA has only RUDER
|
||
|
if (currWTyp==WT_ELEVON_A or currWTyp==WT_ELEVON_B) then
|
||
|
M_DB[MV_TAIL_TYPE] = TT_R1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Did the tail changed?
|
||
|
local tt = M_DB[MV_TAIL_TYPE]
|
||
|
if (currTTyp ~= tt) then
|
||
|
if (not tailTypeCompatible(currTTyp,tt)) then
|
||
|
ST_PlaneTailInit(tt)
|
||
|
end
|
||
|
currTTyp = tt
|
||
|
end
|
||
|
|
||
|
Phase = PH_WAIT_CMD
|
||
|
elseif Phase == PH_EXIT_REQ then
|
||
|
Phase=PH_EXIT_DONE
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-----
|
||
|
|
||
|
local function showBitmap(x, y, imgDesc)
|
||
|
local f = string.gmatch(imgDesc, '([^%|]+)') -- Iterator over values split by '|'
|
||
|
local imgName, imgMsg = f(), f()
|
||
|
|
||
|
f = string.gmatch(imgMsg or "", '([^%:]+)') -- Iterator over values split by ':'
|
||
|
local p1, p2 = f(), f()
|
||
|
|
||
|
lcd.drawText(x, y, p1 or "", TEXT_ATTR) -- Alternate Image MSG
|
||
|
lcd.drawText(x, y + LCD_Y_LINE_HEIGHT, p2 or "", TEXT_ATTR) -- Alternate Image MSG
|
||
|
gc()
|
||
|
end
|
||
|
|
||
|
|
||
|
local function drawButton(x, y, text, active)
|
||
|
local attr = TEXT_ATTR
|
||
|
if (active) then attr = attr + INVERS end
|
||
|
lcd.drawText(x, y, text, attr)
|
||
|
end
|
||
|
|
||
|
local function DSM_Display()
|
||
|
lcd.clear()
|
||
|
--Draw RX Menu
|
||
|
if Phase == PH_RX_VER then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- display Program version or RX version
|
||
|
local msg = "FProg "..VERSION
|
||
|
lcd.drawText(40, LCD_Y_LOWER_BUTTONS, msg, TEXT_ATTR)
|
||
|
|
||
|
if Menu.MenuId == 0 then return end; -- No Title yet
|
||
|
|
||
|
-- Got a Menu
|
||
|
lcd.drawText(1, 0, Menu.Text, TEXT_ATTR + INVERS)
|
||
|
|
||
|
local y = LCD_Y_LINE_HEIGHT + 2
|
||
|
for i = 0, 6 do
|
||
|
local attrib = TEXT_ATTR
|
||
|
if (i == ctx_SelLine) then attrib = attrib + INVERS end -- Selected Line
|
||
|
|
||
|
local line = MenuLines[i]
|
||
|
|
||
|
if line ~= nil and line.Type ~= 0 then
|
||
|
local heading = line.Text
|
||
|
|
||
|
local text = nil
|
||
|
if line.Type ~= LT_MENU then -- list/value
|
||
|
if line.Val ~= nil then
|
||
|
if isListLine(line) then
|
||
|
local textId = line.Val + line.TextStart
|
||
|
text = Get_Text_Value(textId)
|
||
|
|
||
|
--local imgDesc = List_Text_Img[textId]
|
||
|
|
||
|
--if (imgDesc and i == ctx_SelLine) then -- Optional Image and Msg for selected value
|
||
|
-- showBitmap(1, 20, imgDesc)
|
||
|
--end
|
||
|
else
|
||
|
text = line.Val
|
||
|
end
|
||
|
end -- if is Value
|
||
|
|
||
|
if (ctx_EditLine == i) then -- Editing a Line
|
||
|
attrib = BLINK + INVERS + TEXT_ATTR
|
||
|
end
|
||
|
lcd.drawText(LCD_X_MAX, y, text or "--", attrib + RIGHT) -- display value
|
||
|
--lcd.drawText(LCD_X_MAX, y, line.Format or "", TEXT_ATTR + RIGHT) -- display Format
|
||
|
attrib = TEXT_ATTR
|
||
|
end
|
||
|
|
||
|
lcd.drawText(1, y, heading, attrib) -- display text
|
||
|
end
|
||
|
y = y + LCD_Y_LINE_HEIGHT
|
||
|
end -- for
|
||
|
|
||
|
if Menu.BackId~=0 then
|
||
|
drawButton(LCD_X_RIGHT_BUTTONS, 0, "Back", ctx_SelLine == -1)
|
||
|
end
|
||
|
|
||
|
if Menu.NextId~=0 then
|
||
|
drawButton(LCD_X_RIGHT_BUTTONS, LCD_Y_LOWER_BUTTONS, "Next", ctx_SelLine == 7)
|
||
|
end
|
||
|
|
||
|
if Menu.PrevId~=0 then
|
||
|
drawButton(1, LCD_Y_LOWER_BUTTONS, "Prev", ctx_SelLine == 8)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
------------------------------------------------------------------------------------------------------------
|
||
|
-- Init
|
||
|
local function DSM_Init()
|
||
|
--LOG_open()
|
||
|
ST_Init()
|
||
|
gc()
|
||
|
|
||
|
if (LCD_W > 128) then
|
||
|
TEXT_ATTR = 0
|
||
|
LCD_Y_LINE_HEIGHT = 25
|
||
|
LCD_X_MAX = 300
|
||
|
LCD_X_RIGHT_BUTTONS = LCD_X_MAX - 30
|
||
|
|
||
|
LCD_Y_LOWER_BUTTONS = (8 * LCD_Y_LINE_HEIGHT) + 2
|
||
|
end
|
||
|
|
||
|
Phase = PH_RX_VER
|
||
|
end
|
||
|
|
||
|
-- Main
|
||
|
|
||
|
local function DSM_Run(event)
|
||
|
if event == nil then
|
||
|
error("Cannot be run as a model script!")
|
||
|
return 2
|
||
|
end
|
||
|
|
||
|
DSM_Display()
|
||
|
DSM_HandleEvent(event)
|
||
|
DSM_Send_Receive()
|
||
|
gc()
|
||
|
|
||
|
if Phase == PH_EXIT_DONE then
|
||
|
LOG_close()
|
||
|
return 2
|
||
|
else
|
||
|
return 0
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---
|
||
|
-- Load Model Config
|
||
|
gc()
|
||
|
local r = assert(loadScript(DSMLIB_PATH.."DsmMIN_P1.lua"), "Not-Found: DSMLIB/DsmMIN_P1.lua")
|
||
|
(MODEL,M_DB, LOG_write)
|
||
|
gc()
|
||
|
----
|
||
|
return { init = DSM_Init, run = DSM_Run }
|