├── .gitignore ├── README.md ├── SCRIPTS ├── TELEMETRY │ └── ledvtx.lua └── TOOLS │ ├── ledvtx.lua │ └── ledvtx │ ├── com.lua │ ├── config.lua │ ├── config.txt │ ├── gui.lua │ └── msp.lua ├── screenshot.png └── screenshot_color.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.luac 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Lua LED & VTX Switch 2 | 3 | Скрипт для переключения каналов видеопередатчика и цвета светодиодов с экрана аппаратуры. 4 | 5 | 6 | 7 | ### Установка скрипта OpenTX 8 | 9 | 1) Скачать [zip-архив](https://github.com/alexeystn/lua-vtx-switch/archive/refs/heads/master.zip) и распаковать. 10 | 2) Скопировать содержимое папки `SCRIPTS/TOOLS` из архива в папку `SCRIPTS/TOOLS` на SD карте. 11 | 3) На аппаратуре открыть меню `TOOLS` (долгим нажатием кнопки `Menu`) и выбрать `LED & VTX setup` 12 | 4) *(Опционально)*. Для быстрого доступа к скрипту на экране телеметрии 13 | 1) Положить `ledvtx.lua` из папки `SCRIPTS/TELEMETRY` из архива в папку `SCRIPTS/TELEMETRY` на SD-карте. 14 | 2) В настройках модели на странице `DISPLAY` выбрать `Script: ledvtx` для любого из экранов. 15 | 16 | ### Настройка Betaflight 17 | 18 | 1) Настроить режим светодиодов `set ledstrip_profile = STATUS`. 19 | 2) *(Опционально)*. Для более точных оттенков и равномерной яркости свечения можно использовать таблицу цветов: 20 | 21 |
22 | Таблица 23 | 24 | ``` 25 | color 1 30,100,120 26 | color 2 0,0,240 27 | color 3 10,0,220 28 | color 4 30,0,180 29 | color 5 90,0,180 30 | color 6 120,0,240 31 | color 7 150,0,180 32 | color 8 180,0,120 33 | color 9 210,0,180 34 | color 10 240,0,240 35 | color 11 270,0,180 36 | color 12 300,0,120 37 | color 13 330,0,180 38 | save 39 | ``` 40 | 41 |
42 | 43 | ### Возможные проблемы 44 | 45 | * Если изменяется цвет только первого светодиода, поменяйте в файле `SCRIPTS/TOOLS/ledvtx.lua` параметр `ledCount = 1` на нужное количество диодов в ленте. 46 | * Если используется Betaflight 4.4 или более ранняя версия, установите в файле `SCRIPTS/TOOLS/ledvtx.lua` для параметра `mspApiVersion` значение `45` вместо `46`. 47 | * При совместном использовании с ELRS VTX Administrator при установке видеоканала в скрипте следует выбирать вариант `* * * *`. 48 | -------------------------------------------------------------------------------- /SCRIPTS/TELEMETRY/ledvtx.lua: -------------------------------------------------------------------------------- 1 | local script = assert(loadScript("/SCRIPTS/TOOLS/ledvtx.lua"))() 2 | 3 | return { run=script.run, init=script.init} 4 | -------------------------------------------------------------------------------- /SCRIPTS/TOOLS/ledvtx.lua: -------------------------------------------------------------------------------- 1 | chdir("/SCRIPTS/TOOLS/LEDVTX") 2 | 3 | local toolName = "TNS|LED & VTX setup|TNE" 4 | 5 | local gui = assert(loadScript("gui.lua"))() 6 | local config = assert(loadScript("config.lua"))() 7 | local com = assert(loadScript("com.lua"))() 8 | 9 | local ledCount = 1 -- Default: 1. 10 | local mspApiVersion = 46 -- Default: 46. Set 45 if using BF4.4 or earlier. 11 | 12 | local colorNames = { "Red", "Orange", "Yellow", "Green", "Cyan", "Blue", "Violet", "White", "Black", " * * * *" } 13 | local colorIds = { 2, 3, 4, 6, 8, 10, 13, 1, 0, nil } 14 | 15 | local bandNames = { "Raceband", "Fatshark", "Lowband", " * * * *" } 16 | local bandIds = { 5, 4, 6, nil } 17 | 18 | local ledColor = 1 19 | local vtxBand = 1 20 | local vtxChannel = 1 21 | 22 | local menuPosition = 1 23 | local menuLength = 3 24 | local isItemActive = false 25 | 26 | local ITEM_LED = 1 27 | local ITEM_VTX = 2 28 | local ITEM_SAVE = 3 29 | 30 | local IDLE=1 31 | local BUSY=2 32 | local DONE=3 33 | local FAIL=4 34 | 35 | local state = IDLE 36 | 37 | 38 | local function itemIncrease() 39 | if menuPosition == ITEM_LED then 40 | if ledColor < #colorNames then 41 | ledColor = ledColor + 1 42 | end 43 | end 44 | if menuPosition == ITEM_VTX then 45 | if vtxChannel < 8 then 46 | vtxChannel = vtxChannel + 1 47 | elseif vtxBand < #bandNames then 48 | vtxBand = vtxBand + 1 49 | vtxChannel = 1 50 | end 51 | if bandIds[vtxBand] == nil then 52 | vtxChannel = 1 53 | end 54 | end 55 | end 56 | 57 | 58 | local function itemDecrease() 59 | if menuPosition == ITEM_LED then 60 | if ledColor > 1 then 61 | ledColor = ledColor - 1 62 | end 63 | end 64 | if menuPosition == ITEM_VTX then 65 | if vtxChannel > 1 then 66 | vtxChannel = vtxChannel - 1 67 | elseif vtxBand > 1 then 68 | vtxBand = vtxBand - 1 69 | vtxChannel = 8 70 | end 71 | end 72 | end 73 | 74 | 75 | local function menuMoveDown() 76 | if menuPosition < menuLength then 77 | menuPosition = menuPosition + 1 78 | end 79 | end 80 | 81 | 82 | local function menuMoveUp() 83 | if menuPosition > 1 then 84 | menuPosition = menuPosition - 1 85 | end 86 | end 87 | 88 | 89 | local function drawDisplay() 90 | lcd.clear() 91 | gui.drawSelector(1, colorNames[ledColor], menuPosition==1, isItemActive) 92 | if bandIds[vtxBand] then 93 | gui.drawSelector(2, bandNames[vtxBand] .. " " .. tostring(vtxChannel), menuPosition==2, isItemActive) 94 | else 95 | gui.drawSelector(2, bandNames[vtxBand], menuPosition==2, isItemActive) 96 | end 97 | local text, event 98 | text, event = com.getStatus() 99 | sel = (not text) and (menuPosition == ITEM_SAVE) 100 | if not text then 101 | if event == 1 then 102 | state = DONE 103 | elseif event == -1 then 104 | state = FAIL 105 | end 106 | if state == DONE then 107 | text = "Done" 108 | elseif state == FAIL then 109 | text = "Failed" 110 | else 111 | text = "Save" 112 | end 113 | else 114 | state = BUSY 115 | end 116 | gui.drawButton(text, sel) 117 | gui.drawStatus() 118 | end 119 | 120 | 121 | local function processEnterPress() 122 | if menuPosition < menuLength then 123 | isItemActive = not isItemActive 124 | else 125 | state = BUSY 126 | config.save(ledColor, vtxBand, vtxChannel) 127 | com.sendLedVtxConfig(colorIds[ledColor], bandIds[vtxBand], vtxChannel, ledCount, mspApiVersion) 128 | end 129 | end 130 | 131 | 132 | local function run_func(event) 133 | com.mainLoop() 134 | if state ~= BUSY then 135 | if isItemActive then 136 | if event == EVT_ROT_RIGHT or event == EVT_PLUS_FIRST or event == EVT_PLUS_REPT then 137 | itemIncrease() 138 | end 139 | if event == EVT_ROT_LEFT or event == EVT_MINUS_FIRST or event == EVT_MINUS_REPT then 140 | itemDecrease() 141 | end 142 | if event == EVT_EXIT_BREAK then 143 | isItemActive = false 144 | end 145 | else 146 | if event == EVT_ROT_RIGHT or event == EVT_MINUS_FIRST or event == EVT_MINUS_REPT then 147 | menuMoveDown() 148 | end 149 | if event == EVT_ROT_LEFT or event == EVT_PLUS_FIRST or event == EVT_PLUS_REPT then 150 | menuMoveUp() 151 | end 152 | if event == EVT_EXIT_BREAK then 153 | return -1 154 | end 155 | end 156 | end 157 | if event == EVT_ENTER_BREAK then 158 | processEnterPress() 159 | end 160 | if event == EVT_MENU_BREAK then 161 | com.setDebug() 162 | end 163 | if event == EVT_EXIT_BREAK then 164 | com.cancel() 165 | state = IDLE 166 | end 167 | if ((state == DONE) or (state == FAIL)) and (event == EVT_ROT_LEFT or event == EVT_ROT_RIGHT) then 168 | state = IDLE 169 | end 170 | drawDisplay() 171 | return 0 172 | end 173 | 174 | 175 | local function bg_func() 176 | com.bgLoop() 177 | end 178 | 179 | 180 | local function init_func() 181 | ledColor, vtxBand, vtxChannel = config.load_(#colorNames, #bandNames) 182 | end 183 | 184 | 185 | return { run=run_func, background=bg_func, init=init_func} 186 | -------------------------------------------------------------------------------- /SCRIPTS/TOOLS/ledvtx/com.lua: -------------------------------------------------------------------------------- 1 | local msp = assert(loadScript("msp.lua"))() 2 | 3 | local MSP_VTX_SET_CONFIG = 89 4 | local MSP_EEPROM_WRITE = 250 5 | local MSP_SET_LED_STRIP = 49 6 | local MSP_SET_RTC = 246 7 | 8 | local isBusy = false 9 | local retryCount = 0 10 | local maxRetries = 4 11 | local retryTimeout = 200 12 | local nextTryTime = 0 13 | local nextRtcTime = 0 14 | 15 | local successFlag = false 16 | local failedFlag = false 17 | 18 | local commandSequence = {} 19 | local commandPointer = 0 20 | local currentCommand = {} 21 | 22 | local debugButtonState = false 23 | 24 | local function getDebugButtonState() 25 | if debugButtonState then 26 | debugButtonState = false 27 | return true 28 | else 29 | return false 30 | end 31 | end 32 | 33 | local function setDebugButtonState() 34 | debugButtonState = true 35 | end 36 | 37 | 38 | local function sendCurrentCommand() 39 | retryCount = retryCount + 1 40 | if retryCount > maxRetries then 41 | isBusy = false 42 | failedFlag = true 43 | end 44 | if currentCommand.write then 45 | msp.write(currentCommand.header, currentCommand.payload) 46 | else 47 | msp.read(currentCommand.header, currentCommand.payload) 48 | end 49 | nextTryTime = getTime() + retryTimeout 50 | print(currentCommand.text) 51 | end 52 | 53 | 54 | local function gotoNextCommand() 55 | if commandPointer < #commandSequence then 56 | commandPointer = commandPointer + 1 57 | currentCommand = commandSequence[commandPointer] 58 | retryCount = 0 59 | sendCurrentCommand() 60 | else 61 | successFlag = true 62 | currentCommand = nil 63 | isBusy = false 64 | end 65 | end 66 | 67 | 68 | function processMspReply(cmd, rx_buf) 69 | local key = getDebugButtonState() 70 | if (cmd == nil or rx_buf == nil) and not key then 71 | return 72 | end 73 | if isBusy and (key or (cmd == currentCommand.header)) then 74 | gotoNextCommand() 75 | end 76 | end 77 | 78 | 79 | local function startTransmission(commands) 80 | commandSequence = commands 81 | commandPointer = 0 82 | isBusy = true 83 | gotoNextCommand() 84 | end 85 | 86 | 87 | local function prepareLedCommand(color, n, version) 88 | local cmd = {} 89 | cmd.header = MSP_SET_LED_STRIP 90 | enableLarsonScanner = 0 91 | 92 | if version < 46 then 93 | cmd.payload = { n-1, (n-1)*16, 16*enableLarsonScanner, color*4, 0 } 94 | else 95 | cmd.payload = { n-1, (n-1)*16, 16*enableLarsonScanner, bit32.lshift(bit32.band(color, 0x03), 6), bit32.rshift(color, 2)} 96 | end 97 | cmd.write = true 98 | cmd.text = "Switching LED " .. tostring(n) 99 | return cmd 100 | end 101 | 102 | 103 | local function prepareVtxCommand(band, channel) 104 | local cmd = {} 105 | cmd.header = MSP_VTX_SET_CONFIG 106 | cmd.payload = { (band-1)*8 + (channel-1), 0, 1, 0 } 107 | cmd.write = true 108 | cmd.text = "Switching VTX" 109 | return cmd 110 | end 111 | 112 | 113 | local function prepareSaveCommand() 114 | local cmd = {} 115 | cmd.header = MSP_EEPROM_WRITE 116 | cmd.payload = nil 117 | cmd.write = false 118 | cmd.text = "Saving" 119 | return cmd 120 | end 121 | 122 | 123 | local function prepareRtcCommand() 124 | local now = getRtcTime() 125 | local values = {} 126 | for i = 1, 4 do 127 | values[i] = bit32.band(now, 0xFF) 128 | now = bit32.rshift(now, 8) 129 | end 130 | values[5] = 0 131 | values[6] = 0 132 | cmd = {} 133 | cmd.header = MSP_SET_RTC 134 | cmd.payload = values 135 | cmd.write = true 136 | cmd.text = "RTC" 137 | return cmd 138 | end 139 | 140 | 141 | local function sendLedVtxConfig(color, band, channel, count, version) 142 | retryCount = 0 143 | local cmd = {} 144 | if band then 145 | cmd[#cmd+1] = prepareVtxCommand(band, channel) 146 | end 147 | if color then 148 | for i = 1, count do 149 | cmd[#cmd+1] = prepareLedCommand(color, i, version) 150 | end 151 | end 152 | cmd[#cmd+1] = prepareSaveCommand() 153 | startTransmission(cmd) 154 | end 155 | 156 | 157 | local function getStatus() 158 | local text = nil 159 | local flag = 0 160 | if isBusy then 161 | if currentCommand then 162 | text = currentCommand.text .. " (" .. tostring(retryCount) .. ")" 163 | end 164 | end 165 | if successFlag then 166 | flag = 1 167 | successFlag = false 168 | end 169 | if failedFlag then 170 | flag = -1 171 | failedFlag = false 172 | end 173 | return text, flag 174 | end 175 | 176 | 177 | function comMainLoop() 178 | if isBusy then 179 | currentTime = getTime() 180 | if currentTime > nextTryTime then 181 | sendCurrentCommand() 182 | end 183 | end 184 | msp.processTxQ() 185 | processMspReply(msp.pollReply()) 186 | end 187 | 188 | 189 | function cancel() 190 | isBusy = false 191 | end 192 | 193 | 194 | function comBgLoop() 195 | if getTime() > nextRtcTime then 196 | nextRtcTime = getTime() + 500 197 | rtcCommand = prepareRtcCommand() 198 | msp.write(rtcCommand.header, rtcCommand.payload) 199 | print(rtcCommand.text) 200 | end 201 | msp.processTxQ() 202 | processMspReply(msp.pollReply()) 203 | end 204 | 205 | 206 | return { sendLedVtxConfig = sendLedVtxConfig, mainLoop = comMainLoop, getStatus=getStatus, 207 | cancel=cancel, bgLoop=comBgLoop, setDebug=setDebugButtonState} 208 | -------------------------------------------------------------------------------- /SCRIPTS/TOOLS/ledvtx/config.lua: -------------------------------------------------------------------------------- 1 | local configPath = "config.txt" 2 | 3 | 4 | local function checkLimits(value, maxValue) 5 | if not value then 6 | return 1 7 | elseif value > maxValue then 8 | return maxValue 9 | elseif value < 1 then 10 | return 1 11 | else 12 | return value 13 | end 14 | end 15 | 16 | 17 | local function loadConfig(colorsCount, bandsCount) 18 | local f = io.open(configPath, "r") 19 | if f then 20 | local savedColor = tonumber(io.read(f, 2)) 21 | io.read(f, 1) 22 | local savedBand = tonumber(io.read(f, 1)) 23 | io.read(f, 1) 24 | local savedChannel = tonumber(io.read(f, 1)) 25 | savedColor = checkLimits(savedColor, colorsCount) 26 | savedBand = checkLimits(savedBand, bandsCount) 27 | savedChannel = checkLimits(savedChannel, 8) 28 | return savedColor, savedBand, savedChannel 29 | end 30 | return 1, 1, 1 31 | end 32 | 33 | 34 | local function saveConfig(color, band, channel) 35 | local f = io.open(configPath, "w") 36 | io.write(f, string.format("%2d %1d %1d",color, band, channel)) 37 | io.close(f) 38 | end 39 | 40 | 41 | return { save=saveConfig, load_=loadConfig } 42 | -------------------------------------------------------------------------------- /SCRIPTS/TOOLS/ledvtx/config.txt: -------------------------------------------------------------------------------- 1 | 1 1 1 -------------------------------------------------------------------------------- /SCRIPTS/TOOLS/ledvtx/gui.lua: -------------------------------------------------------------------------------- 1 | local LCD_C = LCD_W / 2 + 1 2 | 3 | local w = 0 -- large screen flag 4 | if LCD_H > 96 then w = 1 end 5 | 6 | 7 | local function drawArrow(x, y, dir) 8 | for i = 0, 4+w*6 do 9 | lcd.drawLine(x+i*dir, y-i, x+i*dir, y+i, SOLID, 0) 10 | end 11 | end 12 | 13 | 14 | local function drawSelector(pos, text, isSelected, isActive) 15 | local flags = 0 16 | local offset = 0 17 | if w == 0 then 18 | flags = MIDSIZE 19 | offset = math.floor(string.len(text) * 7 / 2) + 1 20 | if isSelected then 21 | flags = flags + INVERS 22 | lcd.drawFilledRectangle(LCD_C-50, 18*(pos-1)+5, 100, 17, SOLID) 23 | end 24 | else 25 | flags = DBLSIZE + CENTER 26 | if isSelected then 27 | lcd.drawRectangle(LCD_C-120, 50*(pos-1)+60, 240, 44, SOLID, 2) 28 | end 29 | end 30 | if isActive and isSelected then 31 | drawArrow(LCD_C-57-83*w, (18+32*w)*(pos-1)+13+68*w, 1) 32 | drawArrow(LCD_C+56+83*w, (18+32*w)*(pos-1)+13+68*w, -1) 33 | end 34 | lcd.drawText(LCD_C-offset, (pos-1)*(18+32*w)+7+57*w, text, flags) 35 | end 36 | 37 | 38 | local function drawButton(text, isSelected) 39 | local flags = 0 40 | local offset = 0 41 | if w == 0 then 42 | offset = math.floor((string.len(text)*5)/2) 43 | if isSelected then 44 | flags = INVERS 45 | lcd.drawFilledRectangle(LCD_C-30, 46, 60, 12, SOLID) 46 | end 47 | else 48 | flags = MIDSIZE + CENTER 49 | if isSelected then 50 | lcd.drawRectangle(LCD_C-50, 169, 100, 32, SOLID, 2) 51 | end 52 | end 53 | lcd.drawText(LCD_C-offset, 48+122*w, text, flags) 54 | end 55 | 56 | local function drawStatus() 57 | if getRSSI() > 0 then 58 | for i = 0, 3 do 59 | x = LCD_W - 7 + i*2 60 | lcd.drawLine(x, 7, x, 7 - i*2, SOLID, 0) 61 | end 62 | end 63 | end 64 | 65 | return { drawSelector = drawSelector, drawButton = drawButton, drawStatus = drawStatus } 66 | -------------------------------------------------------------------------------- /SCRIPTS/TOOLS/ledvtx/msp.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | The following code is the part of Betaflight TX Lua Scripts: 3 | https://github.com/betaflight/betaflight-tx-lua-scripts 4 | ]] 5 | 6 | 7 | -- Protocol version 8 | local MSP_VERSION = bit32.lshift(1,5) 9 | local MSP_STARTFLAG = bit32.lshift(1,4) 10 | 11 | -- Sequence number for next MSP packet 12 | local mspSeq = 0 13 | local mspRemoteSeq = 0 14 | local mspRxBuf = {} 15 | local mspRxSize = 0 16 | local mspRxCRC = 0 17 | local mspRxReq = 0 18 | local mspStarted = false 19 | local mspLastReq = 0 20 | local mspTxBuf = {} 21 | local mspTxIdx = 1 22 | local mspTxCRC = 0 23 | 24 | local maxTxBufferSize = 8 25 | local maxRxBufferSize = 58 26 | 27 | function mspProcessTxQ() 28 | if (#(mspTxBuf) == 0) then 29 | return false 30 | end 31 | if not crossfireTelemetryPush() then 32 | return true 33 | end 34 | local payload = {} 35 | payload[1] = mspSeq + MSP_VERSION 36 | mspSeq = bit32.band(mspSeq + 1, 0x0F) 37 | if mspTxIdx == 1 then 38 | -- start flag 39 | payload[1] = payload[1] + MSP_STARTFLAG 40 | end 41 | local i = 2 42 | while (i <= maxTxBufferSize) and mspTxIdx <= #mspTxBuf do 43 | payload[i] = mspTxBuf[mspTxIdx] 44 | mspTxIdx = mspTxIdx + 1 45 | mspTxCRC = bit32.bxor(mspTxCRC,payload[i]) 46 | i = i + 1 47 | end 48 | if i <= maxTxBufferSize then 49 | payload[i] = mspTxCRC 50 | mspSend(payload) 51 | mspTxBuf = {} 52 | mspTxIdx = 1 53 | mspTxCRC = 0 54 | return false 55 | end 56 | mspSend(payload) 57 | return true 58 | end 59 | 60 | function mspSendRequest(cmd, payload) 61 | -- busy 62 | if #(mspTxBuf) ~= 0 or not cmd then 63 | return nil 64 | end 65 | mspTxBuf[1] = #(payload) 66 | mspTxBuf[2] = bit32.band(cmd,0xFF) -- MSP command 67 | for i=1,#(payload) do 68 | mspTxBuf[i+2] = bit32.band(payload[i],0xFF) 69 | end 70 | mspLastReq = cmd 71 | return mspProcessTxQ() 72 | end 73 | 74 | function mspReceivedReply(payload) 75 | local idx = 1 76 | local status = payload[idx] 77 | local err = bit32.btest(status, 0x80) 78 | local version = bit32.rshift(bit32.band(status, 0x60), 5) 79 | local start = bit32.btest(status, 0x10) 80 | local seq = bit32.band(status, 0x0F) 81 | idx = idx + 1 82 | if err then 83 | mspStarted = false 84 | return nil 85 | end 86 | if start then 87 | mspRxBuf = {} 88 | mspRxSize = payload[idx] 89 | mspRxReq = mspLastReq 90 | idx = idx + 1 91 | if version == 1 then 92 | mspRxReq = payload[idx] 93 | idx = idx + 1 94 | end 95 | mspRxCRC = bit32.bxor(mspRxSize, mspRxReq) 96 | if mspRxReq == mspLastReq then 97 | mspStarted = true 98 | end 99 | elseif not mspStarted then 100 | return nil 101 | elseif bit32.band(mspRemoteSeq + 1, 0x0F) ~= seq then 102 | mspStarted = false 103 | return nil 104 | end 105 | while (idx <= maxRxBufferSize) and (#mspRxBuf < mspRxSize) do 106 | mspRxBuf[#mspRxBuf + 1] = payload[idx] 107 | mspRxCRC = bit32.bxor(mspRxCRC, payload[idx]) 108 | idx = idx + 1 109 | end 110 | if idx > maxRxBufferSize then 111 | mspRemoteSeq = seq 112 | return true 113 | end 114 | mspStarted = false 115 | -- check CRC 116 | if mspRxCRC ~= payload[idx] and version == 0 then 117 | return nil 118 | end 119 | return mspRxBuf 120 | end 121 | 122 | function mspPollReply() 123 | while true do 124 | local ret = mspPoll() 125 | if type(ret) == "table" then 126 | mspLastReq = 0 127 | return mspRxReq, ret 128 | else 129 | break 130 | end 131 | end 132 | return nil 133 | end 134 | 135 | 136 | -- CRSF Devices 137 | local CRSF_ADDRESS_BETAFLIGHT = 0xC8 138 | local CRSF_ADDRESS_RADIO_TRANSMITTER = 0xEA 139 | -- CRSF Frame Types 140 | local CRSF_FRAMETYPE_MSP_REQ = 0x7A -- response request using msp sequence as command 141 | local CRSF_FRAMETYPE_MSP_RESP = 0x7B -- reply with 60 byte chunked binary 142 | local CRSF_FRAMETYPE_MSP_WRITE = 0x7C -- write with 60 byte chunked binary 143 | 144 | crsfMspCmd = 0 145 | 146 | function mspSend(payload) 147 | local payloadOut = { CRSF_ADDRESS_BETAFLIGHT, CRSF_ADDRESS_RADIO_TRANSMITTER } 148 | for i=1, #(payload) do 149 | payloadOut[i+2] = payload[i] 150 | end 151 | return crossfireTelemetryPush(crsfMspCmd, payloadOut) 152 | end 153 | 154 | function mspRead(cmd) 155 | crsfMspCmd = CRSF_FRAMETYPE_MSP_REQ 156 | return mspSendRequest(cmd, {}) 157 | end 158 | 159 | function mspWrite(cmd, payload) 160 | crsfMspCmd = CRSF_FRAMETYPE_MSP_WRITE 161 | return mspSendRequest(cmd, payload) 162 | end 163 | 164 | function mspPoll() 165 | local command, data = crossfireTelemetryPop() 166 | if command == CRSF_FRAMETYPE_MSP_RESP then 167 | if data[1] == CRSF_ADDRESS_RADIO_TRANSMITTER and data[2] == CRSF_ADDRESS_BETAFLIGHT then 168 | local mspData = {} 169 | for i=3, #(data) do 170 | mspData[i-2] = data[i] 171 | end 172 | return mspReceivedReply(mspData) 173 | end 174 | end 175 | return nil 176 | end 177 | 178 | 179 | return { processTxQ = mspProcessTxQ, pollReply = mspPollReply, write = mspWrite, read = mspRead } 180 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexeystn/lua-vtx-switch/646db58eda1224dc2b7e93a49a2c6e2e59d97f51/screenshot.png -------------------------------------------------------------------------------- /screenshot_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexeystn/lua-vtx-switch/646db58eda1224dc2b7e93a49a2c6e2e59d97f51/screenshot_color.png --------------------------------------------------------------------------------