From 34c39d63fad332d9de30c543e5f9096172e004af Mon Sep 17 00:00:00 2001 From: ELynx <1621251+ELynx@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:46:11 +0200 Subject: [PATCH 1/6] [Codex assisted] RadioLink / DumboRC command packages bridge Pass "special" packages to and from the module Allows to replicate "Set Failsafe", "Set Gyro Settings" and "Set Gyro Endpoints" from DDF-350 --- Multiprotocol/Multiprotocol.h | 8 +++++++- Multiprotocol/Multiprotocol.ino | 14 ++++++++++++++ Multiprotocol/Telemetry.ino | 17 +++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Multiprotocol/Multiprotocol.h b/Multiprotocol/Multiprotocol.h index 18832a080..61d68fdab 100644 --- a/Multiprotocol/Multiprotocol.h +++ b/Multiprotocol/Multiprotocol.h @@ -584,6 +584,7 @@ enum MultiPacketTypes MULTI_TELEMETRY_MLINK = 15, MULTI_TELEMETRY_CONFIG = 16, MULTI_TELEMETRY_PROTO = 17, + MULTI_TELEMETRY_RLINK = 18, }; // Macros @@ -1227,6 +1228,7 @@ Serial: 100000 Baud 8e2 _ xxxx xxxx p -- FrSkyX and FrSkyX2: Stream[27..34] during normal operation unstuffed SPort data to be sent HoTT: Stream[27] 1 byte for telemetry type DSM: Stream[27..33] Forward Programming + RadioLink/DumboRC: Stream[27..35] raw command payload, used to send failsafe and gyro settings */ /* Multiprotocol telemetry/command definition for OpenTX and erskyTX @@ -1377,5 +1379,9 @@ Serial: 100000 Baud 8e2 _ xxxx xxxx p -- data[n+2] = number of sub protocols data[n+3] = sub protocols text length, only sent if nbr_sub != 0 data[n+4..] = sub protocol names, only sent if nbr_sub != 0 - + + Type 0x12 RadioLink/DumboRC raw command payload + length: variable + data[0..] = raw command payload, used to send failsafe and gyro settings + */ diff --git a/Multiprotocol/Multiprotocol.ino b/Multiprotocol/Multiprotocol.ino index fae9502cf..7233cea2b 100644 --- a/Multiprotocol/Multiprotocol.ino +++ b/Multiprotocol/Multiprotocol.ino @@ -265,6 +265,11 @@ uint8_t packet_in[TELEMETRY_BUFFER_SIZE];//telemetry receiving packets uint8_t CONFIG_SerialRX_val[7]; bool CONFIG_SerialRX=false; #endif + #ifdef RLINK_HUB_TELEMETRY + uint8_t RLINK_SerialRX_val[8]; + uint8_t RLINK_SerialRX_len=0; + bool RLINK_SerialRX=false; + #endif #endif // TELEMETRY uint8_t multi_protocols_index=0xFF; @@ -1577,6 +1582,15 @@ void update_serial_data() CONFIG_SerialRX=true; } #endif + #ifdef RLINK_HUB_TELEMETRY + if(protocol==PROTO_RLINK && sub_protocol==RLINK_DUMBORC + && rx_len>27 && rx_len<=27+sizeof(RLINK_SerialRX_val)) + {//DumboRC raw special payload from Lua/multiBuffer bridge + RLINK_SerialRX_len=rx_len-27; + memcpy(RLINK_SerialRX_val, (const void *)&rx_ok_buff[27], RLINK_SerialRX_len); + RLINK_SerialRX=true; + } + #endif } RX_DONOTUPDATE_off; diff --git a/Multiprotocol/Telemetry.ino b/Multiprotocol/Telemetry.ino index cb651ee38..b6554d57e 100644 --- a/Multiprotocol/Telemetry.ino +++ b/Multiprotocol/Telemetry.ino @@ -315,6 +315,15 @@ static void multi_send_status() } #endif +#ifdef RLINK_HUB_TELEMETRY + void RLINK_raw_frame() + { + multi_send_header(MULTI_TELEMETRY_RLINK, packet_in[0]); + for (uint8_t i = 1; i <= packet_in[0]; i++) // raw DumboRC reply payload + Serial_write(packet_in[i]); + } +#endif + static void multi_send_frskyhub() { multi_send_header(MULTI_TELEMETRY_HUB, 9); @@ -992,6 +1001,14 @@ void TelemetryUpdate() return; } #endif + #if defined RLINK_HUB_TELEMETRY + if(telemetry_link == 2 && protocol == PROTO_RLINK) + { + RLINK_raw_frame(); + telemetry_link=0; + return; + } + #endif #if defined SCANNER_TELEMETRY if (telemetry_link && protocol == PROTO_SCANNER) { From be6b66b1e627d639d5369a3ba6e457281b279daf Mon Sep 17 00:00:00 2001 From: ELynx <1621251+ELynx@users.noreply.github.com> Date: Fri, 17 Oct 2025 21:58:33 +0200 Subject: [PATCH 2/6] RadioLink / DumboRC command packages TX / RX --- Multiprotocol/RadioLink_cc2500.ino | 317 +++++++++++++++++++++-------- 1 file changed, 228 insertions(+), 89 deletions(-) diff --git a/Multiprotocol/RadioLink_cc2500.ino b/Multiprotocol/RadioLink_cc2500.ino index df59ff80a..a59907666 100644 --- a/Multiprotocol/RadioLink_cc2500.ino +++ b/Multiprotocol/RadioLink_cc2500.ino @@ -12,7 +12,7 @@ You should have received a copy of the GNU General Public License along with Multiprotocol. If not, see . */ -// Radiolink surface protocol. TXs: RC4GS,RC6GS. Compatible RXs:R7FG(Std),R6FG,R6F,R8EF,R8FM,R8F,R4FGM +// Radiolink surface protocol. TXs: RC4GS,RC6GS. Compatible RXs:R7FG(Std),R6FG,R6F,R8EF,R8FM,R8F,R4FGM #if defined(RLINK_CC2500_INO) @@ -37,6 +37,7 @@ enum { uint32_t RLINK_rand1; uint32_t RLINK_rand2; +uint32_t RLINK_pseudo; static uint32_t __attribute__((unused)) RLINK_prng_next(uint32_t r) { @@ -84,7 +85,7 @@ static void __attribute__((unused)) RLINK_shuffle_freqs(uint32_t seed) static void __attribute__((unused)) RLINK_hop() { uint8_t inc=3*(rx_tx_addr[0]&3); - + // init hop table for(uint8_t i=0; i>= 4; + debug("C:%02X RSSI:%02X",ch,val); + if(val_low > val) + { + debug(" OK"); + val_low = val; + hopping_frequency[ch<0x63?0:1]=ch; //save best channel + } + debugln(""); + } + CC2500_WriteReg(CC2500_17_MCSM1,0x30); + CC2500_Strobe(CC2500_SIDLE); + CC2500_SetTxRxMode(TX_EN); + #ifdef RLINK_RC4G_FORCE_ID + hopping_frequency[0] = 0x03; + hopping_frequency[1] = 0x6F; + #endif +} + +// calc next pseudo random value +static void __attribute__((unused)) RLINK_next_pseudo() +{ + RLINK_pseudo = ((RLINK_pseudo * 0xAA) + 0x03) % 0x7673; +} + +static void __attribute__((unused)) RLINK_set_next_channel() +{ + CC2500_WriteReg(CC2500_0A_CHANNR, hopping_frequency[RLINK_pseudo & 0x0F]); +} + +static uint8_t __attribute__((unused)) RLINK_checksum(const uint8_t *data, uint8_t payload_len, bool include_id=false) +{ + uint8_t sum=0; + for(uint8_t i=0;i>= 4; - debug("C:%02X RSSI:%02X",ch,val); - if(val_low > val) - { - debug(" OK"); - val_low = val; - hopping_frequency[ch<0x63?0:1]=ch; //save best channel - } - debugln(""); - } - CC2500_WriteReg(CC2500_17_MCSM1,0x30); - CC2500_Strobe(CC2500_SIDLE); - CC2500_SetTxRxMode(TX_EN); - #ifdef RLINK_RC4G_FORCE_ID - hopping_frequency[0] = 0x03; - hopping_frequency[1] = 0x6F; - #endif - } + RLINK_hop(); #ifdef RLINK_DEBUG debug("ID:"); @@ -198,15 +224,14 @@ static void __attribute__((unused)) RLINK_rf_init() } else if(sub_protocol==RLINK_RC4G) CC2500_WriteReg(5, 0xA5); - + CC2500_WriteReg(CC2500_0C_FSCTRL0, option); - + CC2500_SetTxRxMode(TX_EN); } static void __attribute__((unused)) RLINK_send_packet() { - static uint32_t pseudo=0; uint32_t bits = 0; uint8_t bitsavailable = 0; uint8_t idx = 6; @@ -225,7 +250,7 @@ static void __attribute__((unused)) RLINK_send_packet() { case RLINK_SURFACE: packet[1] |= 0x01; - //radiolink additionnal ID which is working only on a small set of RXs + //radiolink additional ID which is working only on a small set of RXs //if(RX_num) packet[1] |= ((RX_num+2)<<4)+4; // RX number limited to 10 values, 0 is a wildcard break; case RLINK_AIR: @@ -235,7 +260,7 @@ static void __attribute__((unused)) RLINK_send_packet() packet[1] |= 0x01; //always 0x00 on dump but does appear to support telemtry on newer transmitters break; } - + // ID memcpy(&packet[2],rx_tx_addr,RLINK_TX_ID_LEN); @@ -256,31 +281,28 @@ static void __attribute__((unused)) RLINK_send_packet() bitsavailable -= 8; } } - + // hop - pseudo=((pseudo * 0xAA) + 0x03) % 0x7673; // calc next pseudo random value - CC2500_WriteReg(CC2500_0A_CHANNR, hopping_frequency[pseudo & 0x0F]); - packet[28]= pseudo; - packet[29]= pseudo >> 8; + RLINK_next_pseudo(); + RLINK_set_next_channel(); + packet[28]= RLINK_pseudo; + packet[29]= RLINK_pseudo >> 8; packet[30]= 0x00; // unknown packet[31]= 0x00; // unknown packet[32]= rf_ch_num; // index of value changed in the RF table - + // check - uint8_t sum=0; - for(uint8_t i=1;i<33;i++) - sum+=packet[i]; - packet[33]=sum; + packet[33]=RLINK_checksum(&packet[1], RLINK_TX_PACKET_LEN-1); // send packet CC2500_WriteData(packet, RLINK_TX_PACKET_LEN+1); - + // packets type packet_count++; if(packet_count>5) packet_count=0; #ifdef RLINK_DEBUG - debugln("C= 0x%02X",hopping_frequency[pseudo & 0x0F]); + debugln("C= 0x%02X",hopping_frequency[RLINK_pseudo & 0x0F]); debug("P="); for(uint8_t i=1;i sizeof(RLINK_SerialRX_val)) + return false; + + CC2500_Strobe(CC2500_SIDLE); + + RLINK_next_pseudo(); + RLINK_set_next_channel(); + + packet[0] = RLINK_SerialRX_len; + memcpy(&packet[1], RLINK_SerialRX_val, RLINK_SerialRX_len); + packet[2] = RLINK_pseudo; + packet[3] = RLINK_pseudo >> 8; + // special packages have id check embedded in checksum + packet[RLINK_SerialRX_len] = RLINK_checksum(&packet[1], RLINK_SerialRX_len - 1, true); + + CC2500_WriteData(packet, RLINK_SerialRX_len + 1); + + packet_count++; + if(packet_count>5) packet_count=0; + + #ifdef RLINK_DEBUG + debugln("C= 0x%02X",hopping_frequency[RLINK_pseudo & 0x0F]); + debug("DumboRC command="); + for(uint8_t i=1;i 90) + rssi=90; + return ((90 - rssi) * 100) / 75; +} + +static bool __attribute__((unused)) RLINK_DUMBORC_validate_telemetry_packet(const uint8_t *data) +{ + const uint8_t declaredLen = data[0]; + + if(data[1] == 0x00) + { + // telemetry package follows base radiolink procotol with slightly less rules + if(declaredLen != RLINK_RX_PACKET_LEN || memcmp(&data[2], rx_tx_addr, RLINK_TX_ID_LEN) != 0) + return false; + + // telemetry packages do not have id check embeeded in checksum, just like base RadioLink + return data[RLINK_RX_PACKET_LEN] == RLINK_checksum(&data[1], RLINK_RX_PACKET_LEN - 1); + } + + // special packages have id check embedded in checksum + return data[declaredLen] == RLINK_checksum(&data[1], declaredLen - 1, true); +} + #ifndef MULTI_AIR static void __attribute__((unused)) RLINK_RC4G_send_packet() { @@ -311,7 +402,7 @@ static void __attribute__((unused)) RLINK_RC4G_send_packet() packet[5+i*2] = val; packet[8+i ] |= (val>>4) & 0xF0; } - //special channel which is linked to gyro on the orginal TX but allocating it on CH5 here + //special channel which is linked to gyro on the original TX but allocating it on CH5 here packet[10] = convert_channel_16b_limit(CH5,0,100); //failsafe for(uint8_t i=0;i<4;i++) @@ -332,10 +423,15 @@ static void __attribute__((unused)) RLINK_RC4G_send_packet() } #endif +#if defined RLINK_HUB_TELEMETRY +uint16_t RLINK_timing_last_rfsend = 0; +#endif + #define RLINK_TIMING_PROTO 20000-100 // -100 for compatibility with R8EF #define RLINK_TIMING_RFSEND 10500 #define RLINK_TIMING_CHECK 2000 #define RLINK_RC4G_TIMING_PROTO 14460 +#define RLINK_DUMBORC_COMMAND_RFSEND 5000 uint16_t RLINK_callback() { if(sub_protocol == RLINK_RC4G) @@ -360,48 +456,91 @@ uint16_t RLINK_callback() #endif CC2500_SetPower(); CC2500_SetFreqOffset(); - RLINK_send_packet(); #if not defined RLINK_HUB_TELEMETRY - return RLINK_TIMING_PROTO; + RLINK_send_packet(); + return RLINK_TIMING_PROTO; // RLINK_DATA #else + if(RLINK_DUMBORC_send_command()) + { + phase++; // RX1 + RLINK_timing_last_rfsend = RLINK_DUMBORC_COMMAND_RFSEND; + return RLINK_timing_last_rfsend; + } + + RLINK_send_packet(); + if(!(packet[1]&0x02)) - return RLINK_TIMING_PROTO; //Normal packet - //Telemetry packet + return RLINK_TIMING_PROTO; // Normal packet -> RLINK_DATA + // Telemetry packet phase++; // RX1 - return RLINK_TIMING_RFSEND; + RLINK_timing_last_rfsend = RLINK_TIMING_RFSEND; + return RLINK_timing_last_rfsend; case RLINK_RX1: CC2500_Strobe(CC2500_SIDLE); CC2500_Strobe(CC2500_SFRX); CC2500_SetTxRxMode(RX_EN); CC2500_Strobe(CC2500_SRX); phase++; // RX2 - return RLINK_TIMING_PROTO-RLINK_TIMING_RFSEND-RLINK_TIMING_CHECK; + return RLINK_TIMING_PROTO-RLINK_timing_last_rfsend-RLINK_TIMING_CHECK; case RLINK_RX2: - len = CC2500_ReadReg(CC2500_3B_RXBYTES | CC2500_READ_BURST) & 0x7F; - if (len == RLINK_RX_PACKET_LEN + 1 + 2) //Telemetry frame is 15 bytes + 1 byte for length + 2 bytes for RSSI&LQI&CRC + len = CC2500_ReadReg(CC2500_3B_RXBYTES | CC2500_READ_BURST) & 0x7F; + //Telemetry frame is 15 bytes + 1 byte for length + 2 bytes for RSSI&LQI&CRC + const bool rlink_telem_len = sub_protocol != RLINK_DUMBORC && len == RLINK_RX_PACKET_LEN + 1 + 2; + // length byte + type byte + checksum byte + RSSI/LQI/CRC + const bool dumborc_len = sub_protocol == RLINK_DUMBORC && len >= 5 && len <= sizeof(packet_in); + if (rlink_telem_len || dumborc_len) { #ifdef RLINK_DEBUG_TELEM debug("Telem:"); #endif CC2500_ReadData(packet_in, len); - if(packet_in[0]==RLINK_RX_PACKET_LEN && (packet_in[len-1] & 0x80) && memcmp(&packet[2],rx_tx_addr,RLINK_TX_ID_LEN)==0 && (packet_in[6]==packet[1] || sub_protocol == RLINK_DUMBORC)) - {//Correct telemetry received: length, CRC, ID and type - //packet_in[6] is 0x00 on almost all DumboRC RX so assume it is always valid + if(len >= 3 && packet_in[0] == len - 3 && (packet_in[len-1] & 0x80)) + {//Telemetry received with correct length and CC2500 CRC #ifdef RLINK_DEBUG_TELEM for(uint8_t i=0;i=128) - TX_RSSI -= 128; - else - TX_RSSI += 128; - RX_RSSI=packet_in[7]&0x7F; //Should be packet_in[7]-256 but since it's an uint8_t... - v_lipo1=packet_in[8]<<1; //RX Batt - v_lipo2=packet_in[9]; //Batt - telemetry_link=1; //Send telemetry out - pps_counter++; - packet_count=0; + bool valid_telem=false; + if(sub_protocol == RLINK_DUMBORC) + { + if(RLINK_DUMBORC_validate_telemetry_packet(packet_in)) + { + if(packet_in[1] == 0x00) + { + uint8_t tele_rssi = RLINK_DUMBORC_tele_rssi_as_percent(packet_in[7]); + uint16_t ext_v = packet_in[9] | (((uint16_t)packet_in[10]) << 8); + uint16_t direct_rssi = packet_in[11] | (((uint16_t)packet_in[12]) << 8); + direct_rssi = direct_rssi > 100 ? 100 : direct_rssi; + RX_RSSI = direct_rssi ? direct_rssi : tele_rssi; + v_lipo1 = ext_v > 255 ? 255 : ext_v; // FrSkyD A1 is 8-bit, 0.1V units. + v_lipo2 = 0; // DumboRC has no confirmed second voltage. + valid_telem=true; + } + else + { + telemetry_link=2; // Raw DumboRC packet to Lua/multiBuffer handling. + pps_counter++; + } + } + } + else if(packet_in[0] == RLINK_RX_PACKET_LEN && memcmp(&packet[2],rx_tx_addr,RLINK_TX_ID_LEN)==0 && packet_in[6]==packet[1]) + { + RX_RSSI=packet_in[7]&0x7F; //Should be packet_in[7]-256 but since it's an uint8_t... + v_lipo1=packet_in[8]<<1; //RX Batt + v_lipo2=packet_in[9]; //Batt + valid_telem=true; + } + if(valid_telem) + { + TX_RSSI = packet_in[len-2]; + if(TX_RSSI >=128) + TX_RSSI -= 128; + else + TX_RSSI += 128; + telemetry_link=1; //Send telemetry out + pps_counter++; + packet_count=0; + } } #ifdef RLINK_DEBUG_TELEM debugln(""); @@ -435,4 +574,4 @@ void RLINK_init() phase = RLINK_DATA; } -#endif \ No newline at end of file +#endif From 379327bd13937bad830ca4cdc8a8ad75e19caf8a Mon Sep 17 00:00:00 2001 From: ELynx <1621251+ELynx@users.noreply.github.com> Date: Sun, 7 Jun 2026 16:55:12 +0200 Subject: [PATCH 3/6] RadioLink / DumboRC Helper script channel names Has max 10 channels Has no failsafe Older X series have gyro sense fixed to CH8, also can be used as regular channel --- Lua_scripts/MultiChan.txt | 2 +- Lua_scripts/MultiChannelsUpdater.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lua_scripts/MultiChan.txt b/Lua_scripts/MultiChan.txt index c4ec7c81c..bec27a0ae 100644 --- a/Lua_scripts/MultiChan.txt +++ b/Lua_scripts/MultiChan.txt @@ -163,7 +163,7 @@ 72,0,Q90C,Std,0,FMode,VTX+ 74,0,RadioLink,Surface,0,CH5,CH6,CH7,CH8,FS_CH1,FS_CH2,FS_CH3,FS_CH4,FS_CH5,FS_CH6,FS_CH7,FS_CH8 74,1,RadioLink,Air,0,CH5,CH6,CH7,CH8,FS_CH1,FS_CH2,FS_CH3,FS_CH4,FS_CH5,FS_CH6,FS_CH7,FS_CH8 -74,2,RadioLink,DumboRC,0,CH5,CH6,CH7,CH8,FS_CH1,FS_CH2,FS_CH3,FS_CH4,FS_CH5,FS_CH6,FS_CH7,FS_CH8 +74,2,RadioLink,DumboRC,0,CH5,CH6,CH7,CH8GY,CH9,CH10,n-a,n-a,n-a,n-a,n-a,n-a 74,3,RadioLink,RC4G,0,CH5,FS_CH1,FS_CH2,FS_CH3,FS_CH4 76,0,Realacc,Std,1,Flip,Light,Calib,HLess,RTH,ThCut,Rotat 50,0,Redpine,Fast,0,sCH5,sCH6,sCH7,sCH8,sCH9,sCH10,sCH11,sCH12,sCH13,sCH14,sCH15,sCH16 diff --git a/Lua_scripts/MultiChannelsUpdater.lua b/Lua_scripts/MultiChannelsUpdater.lua index a905cd8f1..8c793d399 100644 --- a/Lua_scripts/MultiChannelsUpdater.lua +++ b/Lua_scripts/MultiChannelsUpdater.lua @@ -280,7 +280,7 @@ local function Multi_Init() end --Exceptions on first 4 channels... - if ( protocol == 73 or (protocol == 74 and sub_protocol == 0) or (protocol == 60 and sub_protocol == 2) or protocol == 89) then -- Kyosho or RadioLink Surface or Pelikan/SCX24 or Losi + if ( protocol == 73 or (protocol == 74 and (sub_protocol == 0 or sub_protocol == 2)) or (protocol == 60 and sub_protocol == 2) or protocol == 89) then -- Kyosho or RadioLink (Surface or DumboRc) or Pelikan/SCX24 or Losi channel_names[1] = "ST" channel_names[2] = "THR" channel_names[3] = "CH3" From 08c7d0e14ad785b8e611e2b3d2fd57775740831c Mon Sep 17 00:00:00 2001 From: ELynx <1621251+ELynx@users.noreply.github.com> Date: Sat, 6 Jun 2026 01:58:01 +0200 Subject: [PATCH 4/6] [Codex] DumboRC P Series settings script Add script that mimics interaction between DumboRC DDF-350 transmitter and P Series receivers --- Lua_scripts/DumboRC P Series.lua | 298 +++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 Lua_scripts/DumboRC P Series.lua diff --git a/Lua_scripts/DumboRC P Series.lua b/Lua_scripts/DumboRC P Series.lua new file mode 100644 index 000000000..b8bd8554a --- /dev/null +++ b/Lua_scripts/DumboRC P Series.lua @@ -0,0 +1,298 @@ +local toolName = "TNS|DumboRC P series|TNE" + +-- EdgeTX/Multi bridge: +-- Multi_Buffer[4] is RF payload length, [5..12] is RF payload. +-- Multi rewrites the 00 00 token and checksum from its hop state. +-- Special1 payload: 18 00 00 arg_l arg_h 00. Args: 1 poll, 2 Set GY EPA, 3 Set FS. +-- Special2 payload: 28 00 00 gyro0 gyro1 gyro2 gyro3 00. +-- Gyro word: bit0 enable, bit1 phase, bits2..5 SEN channel selector +-- (0=NULL, 1..7=CH2..CH8), bits6..13 SEN, bits14..21 PCA, +-- bits22..29 AGS, bits30..31 servo frequency (67HZ,250HZ,50HZ,300HZ). + +local PROTO_RLINK = 74 +local SUB_DUMBORC = 2 + +local TX_READY = 4 +local TX_PAYLOAD = 5 +local TX_MAX_PAYLOAD = 8 +local RX_READY = 14 +local RX_PAYLOAD = 15 +local RX_MAX_PAYLOAD = 32 + +local servoHz = { "67HZ", "250HZ", "50HZ", "300HZ" } + +local gyro = { enable = 0, phase = 0, senCh = 0, sen = 0, pca = 0, ags = 0, flags = 0 } +local lines = {} +local sel = 1 +local top = 1 +local edit = false +local blink = 0 +local moduleOk = false +local haveValues = false + +local function limit(v, mn, mx) + if v < mn then return mn end + if v > mx then return mx end + return v +end + +local function byte(v) + return limit(math.floor(tonumber(v) or 0), 0, 255) +end + +local function percent(v) + return limit(math.floor(tonumber(v) or 0), 0, 100) +end + +local function gyroRaw(v) + return percent(v) * 2 +end + +local function gyroUi(v) + return limit(math.floor((tonumber(v) or 0) / 2), 0, 100) +end + +local function screenLines() + if LCD_W >= 480 then return limit(math.floor(((LCD_H or 272) - 46) / 20), 8, 12) end + return 6 +end + +local function drawTitle(title) + if LCD_W >= 480 and lcd.drawFilledRectangle then + lcd.drawFilledRectangle(0, 0, LCD_W, 30, TITLE_BGCOLOR or 0) + lcd.drawText(3, 5, title, MENU_TITLE_COLOR or 0) + elseif lcd.drawScreenTitle then + lcd.drawScreenTitle(title, 0, 0) + else + lcd.drawText(0, 0, title, INVERS) + end +end + +local function findModule() + for i = 0, 1 do + local m = model.getModule(i) + if m and m["Type"] == 6 and m["protocol"] == PROTO_RLINK and m["subProtocol"] == SUB_DUMBORC then + return true + end + end + return false +end + +local function initBuffer() + multiBuffer(0, string.byte("R")) + if multiBuffer(0) ~= string.byte("R") then + error("Not enough memory!") + return + end + multiBuffer(1, string.byte("L")) + multiBuffer(2, string.byte("n")) + multiBuffer(3, string.byte("k")) + multiBuffer(TX_READY, 0) + multiBuffer(RX_READY, 0) +end + +local function release() + for i = 0, RX_PAYLOAD + RX_MAX_PAYLOAD - 1 do + multiBuffer(i, 0) + end +end + +local function sendPayload(payload) + local len = #payload + if len < 1 or len > TX_MAX_PAYLOAD or multiBuffer(TX_READY) ~= 0 then + return false + end + for i = 1, TX_MAX_PAYLOAD do + multiBuffer(TX_PAYLOAD + i - 1, 0) + end + for i = 1, #payload do + multiBuffer(TX_PAYLOAD + i - 1, byte(payload[i])) + end + multiBuffer(TX_READY, len) + return true +end + +local function sendSpecial1(arg) + return sendPayload({ 0x18, 0, 0, arg % 256, math.floor(arg / 256), 0 }) +end + +local function gyroSelector() + if gyro.senCh < 2 or gyro.senCh > 8 then return 0 end + return gyro.senCh - 1 +end + +local function sendGyro() + local sen = gyroRaw(gyro.sen) + local pca = gyroRaw(gyro.pca) + local ags = gyroRaw(gyro.ags) + local b0 = gyro.enable + gyro.phase * 2 + gyroSelector() * 4 + (sen % 4) * 64 + local b1 = math.floor(sen / 4) + (pca % 4) * 64 + local b2 = math.floor(pca / 4) + (ags % 4) * 64 + local b3 = math.floor(ags / 4) + limit(gyro.flags, 0, 3) * 64 + sendPayload({ 0x28, 0, 0, b0, b1, b2, b3, 0 }) +end + +local function decodeGyroBytes(b0, b1, b2, b3) + b0 = byte(b0); b1 = byte(b1); b2 = byte(b2); b3 = byte(b3) + gyro.enable = b0 % 2 + gyro.phase = math.floor(b0 / 2) % 2 + local selector = math.floor(b0 / 4) % 16 + gyro.senCh = selector == 0 and 0 or limit(selector + 1, 2, 8) + gyro.sen = gyroUi(math.floor(b0 / 64) + (b1 % 64) * 4) + gyro.pca = gyroUi(math.floor(b1 / 64) + (b2 % 64) * 4) + gyro.ags = gyroUi(math.floor(b2 / 64) + (b3 % 64) * 4) + gyro.flags = math.floor(b3 / 64) % 4 + haveValues = true +end + +local function pollRx() + local len = multiBuffer(RX_READY) + if not len or len == 0 then return end + if len > RX_MAX_PAYLOAD then + multiBuffer(RX_READY, 0) + return + end + + local p = {} + for i = 1, len do + p[i] = multiBuffer(RX_PAYLOAD + i - 1) + end + multiBuffer(RX_READY, 0) + + if len == 7 and p[1] == 0x01 then + decodeGyroBytes(p[3], p[4], p[5], p[6]) + end +end + +local function chText() + if gyro.senCh == 0 then return "NULL" end + return "CH" .. gyro.senCh +end + +local function rebuildLines() + lines = { + { "Poll RX", "action", function() sendSpecial1(1) end }, + { "Gyro", "toggle", "enable" }, + { "Phase", "toggle", "phase" }, + { "SEN Ch", "channel", "senCh" }, + { "SEN", "percent", "sen" }, + { "PCA", "percent", "pca" }, + { "AGS", "percent", "ags" }, + { "Servo Hz", "flags", "flags" }, + { "Send Gyro", "action", sendGyro }, + { "Set GY EPA", "action", function() sendSpecial1(2) end }, + { "Set FS", "action", function() sendSpecial1(3) end }, + } +end + +local function lineValue(line) + if line[2] == "action" then return ">" end + if not haveValues then return "--" end + if line[2] == "toggle" then return gyro[line[3]] == 0 and "Off" or "On" end + if line[2] == "channel" then return chText() end + if line[2] == "percent" then return tostring(percent(gyro[line[3]])) end + if line[2] == "flags" then return servoHz[gyro.flags + 1] or tostring(gyro.flags) end + return "" +end + +local function changeValue(dir, fast) + local line = lines[sel] + if not line then return end + local step = fast and 10 or 1 + haveValues = true + + if line[2] == "toggle" then + gyro[line[3]] = gyro[line[3]] == 0 and 1 or 0 + elseif line[2] == "channel" then + local vals = { 0, 2, 3, 4, 5, 6, 7, 8 } + local pos = 1 + for i = 1, #vals do + if vals[i] == gyro.senCh then pos = i end + end + gyro.senCh = vals[limit(pos + dir, 1, #vals)] + elseif line[2] == "percent" then + gyro[line[3]] = percent(gyro[line[3]] + dir * step) + elseif line[2] == "flags" then + gyro.flags = limit(gyro.flags + dir, 0, 3) + end +end + +local function handleEvent(event) + if event == EVT_VIRTUAL_NEXT then + if edit then changeValue(1, false) else sel = limit(sel + 1, 1, #lines) end + elseif event == EVT_VIRTUAL_PREV then + if edit then changeValue(-1, false) else sel = limit(sel - 1, 1, #lines) end + elseif event == EVT_VIRTUAL_NEXT_PAGE then + if edit then changeValue(1, true) else sel = limit(sel + screenLines(), 1, #lines) end + elseif event == EVT_VIRTUAL_PREV_PAGE then + if edit then changeValue(-1, true) else sel = limit(sel - screenLines(), 1, #lines) end + elseif event == EVT_VIRTUAL_ENTER then + local line = lines[sel] + if line and line[2] == "action" then + line[3]() + elseif line then + edit = not edit + end + end + + local count = screenLines() + if sel < top then top = sel end + if sel >= top + count then top = sel - count + 1 end +end + +local function draw() + lcd.clear() + drawTitle("DumboRC P series") + + local font = LCD_W >= 480 and 0 or SMLSIZE + local x = 2 + local y = LCD_W >= 480 and 34 or 9 + local dy = LCD_W >= 480 and 20 or 8 + local valueX = LCD_W >= 480 and 150 or 82 + + if not moduleOk then + if LCD_W >= 480 then + lcd.drawText(x, y + dy, "Select Multi RadLink/DumboRC", font + BLINK) + else + lcd.drawText(x, y + dy, "Select RadLink", font + BLINK) + lcd.drawText(x, y + dy * 2, "DumboRC", font + BLINK) + end + return + end + + for i = top, math.min(#lines, top + screenLines() - 1) do + local attr = font + if i == sel then + attr = attr + INVERS + if edit then + blink = (blink + 1) % 30 + if blink > 15 then attr = font end + end + end + local yy = y + (i - top) * dy + lcd.drawText(x, yy, lines[i][1], attr) + lcd.drawText(valueX, yy, lineValue(lines[i]), attr) + end +end + +local function init() + moduleOk = findModule() + rebuildLines() + initBuffer() +end + +local function run(event) + if event == nil then + error("Cannot be run as a model script!") + return 2 + elseif event == EVT_VIRTUAL_EXIT then + release() + return 2 + end + pollRx() + handleEvent(event) + draw() + return 0 +end + +return { init = init, run = run } From 67dca5a72c16484831b4458df1728af608aba6b4 Mon Sep 17 00:00:00 2001 From: ELynx <1621251+ELynx@users.noreply.github.com> Date: Thu, 16 Jul 2026 23:32:08 +0200 Subject: [PATCH 5/6] [Codex assisted] Split P series into separate subprotocol Split command exchange into separate subprotocol Update protocol documentation --- Lua_scripts/DumboRC P Series.lua | 8 +++---- Lua_scripts/MultiChan.txt | 1 + Lua_scripts/MultiChannelsUpdater.lua | 2 +- Multiprotocol/Multi.txt | 2 +- Multiprotocol/Multi_Protos.ino | 4 ++-- Multiprotocol/Multiprotocol.h | 7 ++++-- Multiprotocol/Multiprotocol.ino | 4 ++-- Multiprotocol/RadioLink_cc2500.ino | 25 ++++++++++++--------- Multiprotocol/_Config.h | 1 + Protocols_Details.md | 33 ++++++++++++++++++++++++++-- 10 files changed, 63 insertions(+), 24 deletions(-) diff --git a/Lua_scripts/DumboRC P Series.lua b/Lua_scripts/DumboRC P Series.lua index b8bd8554a..4f9045365 100644 --- a/Lua_scripts/DumboRC P Series.lua +++ b/Lua_scripts/DumboRC P Series.lua @@ -10,7 +10,7 @@ local toolName = "TNS|DumboRC P series|TNE" -- bits22..29 AGS, bits30..31 servo frequency (67HZ,250HZ,50HZ,300HZ). local PROTO_RLINK = 74 -local SUB_DUMBORC = 2 +local SUB_DUMBORC_P = 4 local TX_READY = 4 local TX_PAYLOAD = 5 @@ -71,7 +71,7 @@ end local function findModule() for i = 0, 1 do local m = model.getModule(i) - if m and m["Type"] == 6 and m["protocol"] == PROTO_RLINK and m["subProtocol"] == SUB_DUMBORC then + if m and m["Type"] == 6 and m["protocol"] == PROTO_RLINK and m["subProtocol"] == SUB_DUMBORC_P then return true end end @@ -252,10 +252,10 @@ local function draw() if not moduleOk then if LCD_W >= 480 then - lcd.drawText(x, y + dy, "Select Multi RadLink/DumboRC", font + BLINK) + lcd.drawText(x, y + dy, "Select Multi RadLink/Dumbo_P", font + BLINK) else lcd.drawText(x, y + dy, "Select RadLink", font + BLINK) - lcd.drawText(x, y + dy * 2, "DumboRC", font + BLINK) + lcd.drawText(x, y + dy * 2, "Dumbo_P", font + BLINK) end return end diff --git a/Lua_scripts/MultiChan.txt b/Lua_scripts/MultiChan.txt index bec27a0ae..74b9369a2 100644 --- a/Lua_scripts/MultiChan.txt +++ b/Lua_scripts/MultiChan.txt @@ -165,6 +165,7 @@ 74,1,RadioLink,Air,0,CH5,CH6,CH7,CH8,FS_CH1,FS_CH2,FS_CH3,FS_CH4,FS_CH5,FS_CH6,FS_CH7,FS_CH8 74,2,RadioLink,DumboRC,0,CH5,CH6,CH7,CH8GY,CH9,CH10,n-a,n-a,n-a,n-a,n-a,n-a 74,3,RadioLink,RC4G,0,CH5,FS_CH1,FS_CH2,FS_CH3,FS_CH4 +74,4,RadioLink,Dumbo_P,0,CH5,CH6,CH7,CH8GY,CH9,CH10,n-a,n-a,n-a,n-a,n-a,n-a 76,0,Realacc,Std,1,Flip,Light,Calib,HLess,RTH,ThCut,Rotat 50,0,Redpine,Fast,0,sCH5,sCH6,sCH7,sCH8,sCH9,sCH10,sCH11,sCH12,sCH13,sCH14,sCH15,sCH16 50,1,Redpine,Slow,0,sCH5,sCH6,sCH7,sCH8,sCH9,sCH10,sCH11,sCH12,sCH13,sCH14,sCH15,sCH16 diff --git a/Lua_scripts/MultiChannelsUpdater.lua b/Lua_scripts/MultiChannelsUpdater.lua index 8c793d399..c5afb5477 100644 --- a/Lua_scripts/MultiChannelsUpdater.lua +++ b/Lua_scripts/MultiChannelsUpdater.lua @@ -280,7 +280,7 @@ local function Multi_Init() end --Exceptions on first 4 channels... - if ( protocol == 73 or (protocol == 74 and (sub_protocol == 0 or sub_protocol == 2)) or (protocol == 60 and sub_protocol == 2) or protocol == 89) then -- Kyosho or RadioLink (Surface or DumboRc) or Pelikan/SCX24 or Losi + if ( protocol == 73 or (protocol == 74 and (sub_protocol == 0 or sub_protocol == 2 or sub_protocol == 4)) or (protocol == 60 and sub_protocol == 2) or protocol == 89) then -- Kyosho or RadioLink (Surface or DumboRC/P) or Pelikan/SCX24 or Losi channel_names[1] = "ST" channel_names[2] = "THR" channel_names[3] = "CH3" diff --git a/Multiprotocol/Multi.txt b/Multiprotocol/Multi.txt index a7fb6076a..47ae172cb 100644 --- a/Multiprotocol/Multi.txt +++ b/Multiprotocol/Multi.txt @@ -71,7 +71,7 @@ 71,JJRC345,JJRC345,SkyTmblr 72,Q90C 73,Kyosho,FHSS,Hype -74,RadioLink,Surface,Air,DumboRC,RC4G +74,RadioLink,Surface,Air,DumboRC,RC4G,Dumbo_P 75,--- 76,Realacc 77,OMP diff --git a/Multiprotocol/Multi_Protos.ino b/Multiprotocol/Multi_Protos.ino index b7b3b59c6..e9a6ef228 100644 --- a/Multiprotocol/Multi_Protos.ino +++ b/Multiprotocol/Multi_Protos.ino @@ -180,7 +180,7 @@ const char STR_SUBTYPE_WFLY2[] = "\x05""RF20x"; const char STR_SUBTYPE_HOTT[] = "\x07""Sync\0 ""No_Sync"; const char STR_SUBTYPE_PELIKAN[] = "\x05""Pro\0 ""Lite\0""SCX24"; const char STR_SUBTYPE_V761[] = "\x05""3ch\0 ""4ch\0 ""TOPRC"; -const char STR_SUBTYPE_RLINK[] = "\x07""Surface""Air\0 ""DumboRC""RC4G\0 "; +const char STR_SUBTYPE_RLINK[] = "\x07""Surface""Air\0 ""DumboRC""RC4G\0 ""Dumbo_P"; const char STR_SUBTYPE_KYOSHO[] = "\x04""FHSS""Hype"; const char STR_SUBTYPE_KYOSHO2[] = "\x05""KT-17"; const char STR_SUBTYPE_KYOSHO3[] = "\x03""ASF"; @@ -460,7 +460,7 @@ const mm_protocol_definition multi_protocols[] = { {PROTO_Q90C, STR_Q90C, NO_SUBTYPE, 0, OPTION_RFTUNE, 0, 0, SW_NRF, Q90C_init, Q90C_callback }, #endif #if defined(RLINK_CC2500_INO) - {PROTO_RLINK, STR_RLINK, STR_SUBTYPE_RLINK, 4, OPTION_RFTUNE, 0, 0, SW_CC2500, RLINK_init, RLINK_callback }, + {PROTO_RLINK, STR_RLINK, STR_SUBTYPE_RLINK, 5, OPTION_RFTUNE, 0, 0, SW_CC2500, RLINK_init, RLINK_callback }, #endif #if defined(REALACC_NRF24L01_INO) {PROTO_REALACC, STR_REALACC, NO_SUBTYPE, 0, OPTION_NONE, 0, 0, SW_NRF, REALACC_init, REALACC_callback }, diff --git a/Multiprotocol/Multiprotocol.h b/Multiprotocol/Multiprotocol.h index 61d68fdab..cd244323a 100644 --- a/Multiprotocol/Multiprotocol.h +++ b/Multiprotocol/Multiprotocol.h @@ -473,6 +473,7 @@ enum RLINK RLINK_AIR = 1, RLINK_DUMBORC = 2, RLINK_RC4G = 3, + RLINK_DUMBORC_P = 4, }; enum MOULDKG { @@ -1201,6 +1202,8 @@ Serial: 100000 Baud 8e2 _ xxxx xxxx p -- RLINK_SURFACE 0 RLINK_AIR 1 RLINK_DUMBORC 2 + RLINK_RC4G 3 + RLINK_DUMBORC_P 4 Power value => 0x80 0=High/1=Low Stream[3] = option_protocol; @@ -1228,7 +1231,7 @@ Serial: 100000 Baud 8e2 _ xxxx xxxx p -- FrSkyX and FrSkyX2: Stream[27..34] during normal operation unstuffed SPort data to be sent HoTT: Stream[27] 1 byte for telemetry type DSM: Stream[27..33] Forward Programming - RadioLink/DumboRC: Stream[27..35] raw command payload, used to send failsafe and gyro settings + RadioLink/DumboRC P: Stream[27..35] raw command payload, used to send failsafe and gyro settings */ /* Multiprotocol telemetry/command definition for OpenTX and erskyTX @@ -1380,7 +1383,7 @@ Serial: 100000 Baud 8e2 _ xxxx xxxx p -- data[n+3] = sub protocols text length, only sent if nbr_sub != 0 data[n+4..] = sub protocol names, only sent if nbr_sub != 0 - Type 0x12 RadioLink/DumboRC raw command payload + Type 0x12 RadioLink/DumboRC P raw command payload length: variable data[0..] = raw command payload, used to send failsafe and gyro settings diff --git a/Multiprotocol/Multiprotocol.ino b/Multiprotocol/Multiprotocol.ino index 7233cea2b..61d0325fc 100644 --- a/Multiprotocol/Multiprotocol.ino +++ b/Multiprotocol/Multiprotocol.ino @@ -1583,9 +1583,9 @@ void update_serial_data() } #endif #ifdef RLINK_HUB_TELEMETRY - if(protocol==PROTO_RLINK && sub_protocol==RLINK_DUMBORC + if(protocol==PROTO_RLINK && sub_protocol==RLINK_DUMBORC_P && rx_len>27 && rx_len<=27+sizeof(RLINK_SerialRX_val)) - {//DumboRC raw special payload from Lua/multiBuffer bridge + {//DumboRC P raw command payload from Lua/multiBuffer bridge RLINK_SerialRX_len=rx_len-27; memcpy(RLINK_SerialRX_val, (const void *)&rx_ok_buff[27], RLINK_SerialRX_len); RLINK_SerialRX=true; diff --git a/Multiprotocol/RadioLink_cc2500.ino b/Multiprotocol/RadioLink_cc2500.ino index a59907666..698027c51 100644 --- a/Multiprotocol/RadioLink_cc2500.ino +++ b/Multiprotocol/RadioLink_cc2500.ino @@ -217,7 +217,7 @@ static void __attribute__((unused)) RLINK_rf_init() for (uint8_t i = 0; i < 39; ++i) CC2500_WriteReg(i, pgm_read_byte_near(&RLINK_init_values[i])); - if(sub_protocol==RLINK_DUMBORC) + if(sub_protocol == RLINK_DUMBORC || sub_protocol == RLINK_DUMBORC_P) { CC2500_WriteReg(4, 0xBA); CC2500_WriteReg(5, 0xDC); @@ -257,6 +257,7 @@ static void __attribute__((unused)) RLINK_send_packet() packet[1] |= 0x21; //air 0x21 on dump but it looks to support telemetry at least RSSI break; case RLINK_DUMBORC: + case RLINK_DUMBORC_P: packet[1] |= 0x01; //always 0x00 on dump but does appear to support telemtry on newer transmitters break; } @@ -314,7 +315,7 @@ static void __attribute__((unused)) RLINK_send_packet() static bool __attribute__((unused)) RLINK_DUMBORC_send_command() { - if(!RLINK_SerialRX || sub_protocol != RLINK_DUMBORC) + if(!RLINK_SerialRX || sub_protocol != RLINK_DUMBORC_P) return false; RLINK_SerialRX=false; @@ -341,7 +342,7 @@ static bool __attribute__((unused)) RLINK_DUMBORC_send_command() #ifdef RLINK_DEBUG debugln("C= 0x%02X",hopping_frequency[RLINK_pseudo & 0x0F]); - debug("DumboRC command="); + debug("DumboRC P command="); for(uint8_t i=1;i= 5 && len <= sizeof(packet_in); + const bool dumborc_len = dumborc_family && len >= 5 && len <= sizeof(packet_in); if (rlink_telem_len || dumborc_len) { #ifdef RLINK_DEBUG_TELEM @@ -501,7 +506,7 @@ uint16_t RLINK_callback() debug(" %02X",packet_in[i]); #endif bool valid_telem=false; - if(sub_protocol == RLINK_DUMBORC) + if(dumborc_family) { if(RLINK_DUMBORC_validate_telemetry_packet(packet_in)) { @@ -512,13 +517,13 @@ uint16_t RLINK_callback() uint16_t direct_rssi = packet_in[11] | (((uint16_t)packet_in[12]) << 8); direct_rssi = direct_rssi > 100 ? 100 : direct_rssi; RX_RSSI = direct_rssi ? direct_rssi : tele_rssi; - v_lipo1 = ext_v > 255 ? 255 : ext_v; // FrSkyD A1 is 8-bit, 0.1V units. - v_lipo2 = 0; // DumboRC has no confirmed second voltage. + v_lipo1 = 0; //Has no RX batt + v_lipo2 = ext_v > 255 ? 255 : ext_v; //Batt in same position as base radiolink valid_telem=true; } - else + else if(sub_protocol == RLINK_DUMBORC_P) { - telemetry_link=2; // Raw DumboRC packet to Lua/multiBuffer handling. + telemetry_link=2; // Raw DumboRC P packet to Lua/multiBuffer handling. pps_counter++; } } diff --git a/Multiprotocol/_Config.h b/Multiprotocol/_Config.h index 5e3ba599f..9529e0e7a 100644 --- a/Multiprotocol/_Config.h +++ b/Multiprotocol/_Config.h @@ -830,6 +830,7 @@ const PPM_Parameters PPM_prot[14*NBR_BANKS]= { RLINK_AIR RLINK_DUMBORC RLINK_RC4G + RLINK_DUMBORC_P PROTO_SCANNER NONE PROTO_SCORPIO diff --git a/Protocols_Details.md b/Protocols_Details.md index 1bd7ce251..45c21395b 100644 --- a/Protocols_Details.md +++ b/Protocols_Details.md @@ -137,7 +137,7 @@ CFlie|AIR|38|CFlie||||||||NRF24L01| [Q2X2](Protocols_Details.md#Q2X2---29)||29|Q222|Q242|Q282||||||NRF24L01| [Q303](Protocols_Details.md#Q303---31)||31|Q303|CX35|CX10D|CX10WD|||||NRF24L01|XN297 [Q90C](Protocols_Details.md#Q90C---72)||72|Q90C*||||||||NRF24L01|XN297 -[RadioLink](Protocols_Details.md#RadioLink---74)||74|Surface|Air|DumboRC|RC4G|||||CC2500| +[RadioLink](Protocols_Details.md#RadioLink---74)||74|Surface|Air|DumboRC|RC4G|Dumbo_P||||CC2500| [Realacc](Protocols_Details.md#Realacc---76)||76|R11||||||||NRF24L01| [Redpine](Protocols_Details.md#Redpine---50)||50|FAST|SLOW|||||||NRF24L01|XN297 [Scanner](Protocols_Details.md#Scanner---54)||54|||||||||CC2500| @@ -1063,7 +1063,21 @@ Air protocol. TXs: T8FB,T8S. Compatible RXs: R8EF,R8FM,R8SM,R4FG,R4F Telemetry: RX_RSSI (for the original value add -256), TX_RSSI, TX_QLY (0..100%) ### Sub_protocol DumboRC - *2* -Compatible RXs: X6/X6F/X6FG +Compatible RXs: +* X6/X6F/X6FG/X6DC/X6DCG/X10F/X10FG (Other X Series should work as well) +* P6F/P6FG/P6DC/P6DCG/P6FP/P10F/P10FG (Other P Series should work as well) + +For P series specific features, see subprotocol 4 below. + +CH1|CH2|CH3|CH4|CH5|CH6|CH7|CH8|CH9|CH10 +---|---|---|---|---|---|---|---|---|---- +CH1|CH2|CH3|CH4|CH5|CH6|CH7|CH8/Gyro gain|CH9|CH10 + +Telemetry: +* RX_RSSI uses the receiver's direct percentage when available +* TX_RSSI is the module-side received RSSI +* TX_QLY is 0..100% +* A2=external battery voltage in 0.1V units (set the ratio to 25.5 and adjust with offset) ### Sub_protocol RC4G - *3* Compatible RXs: R4EH-G(/R4EH-H) @@ -1076,6 +1090,21 @@ FS=FailSafe CH5 is driven by CH3 on the original TX, gyro sensitivity? +### Sub_protocol Dumbo_P - *4* +Compatible RXs: P6F/P6FG/P6DC/P6DCG/P6FP/P10F/P10FG (Other P Series should work as well) + +P series supports configuring receiver from transmitter. Originally, this logic appeared on DumboRC DDF-350 transmitter in 1.1.5 firmware version. + +You can adjust these settings by using "DumboRC P Series.lua" script. + +Settings include: +* gyro on/off +* gyro phase +* gyro tuning +* gyro gain channel +* setting gyro endpoints +* setting failsafe values + ## Futaba - *21* Also called SFHSS depending on radio version. From 91ec737b7d69917760c0023183186e5f7ea6df7f Mon Sep 17 00:00:00 2001 From: ELynx <1621251+ELynx@users.noreply.github.com> Date: Fri, 17 Jul 2026 01:40:28 +0200 Subject: [PATCH 6/6] [Codex] Improve LUA script Clean up, better event handling --- Lua_scripts/DumboRC P Series.lua | 71 ++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/Lua_scripts/DumboRC P Series.lua b/Lua_scripts/DumboRC P Series.lua index 4f9045365..b6366e89f 100644 --- a/Lua_scripts/DumboRC P Series.lua +++ b/Lua_scripts/DumboRC P Series.lua @@ -18,6 +18,7 @@ local TX_MAX_PAYLOAD = 8 local RX_READY = 14 local RX_PAYLOAD = 15 local RX_MAX_PAYLOAD = 32 +local colorLcd = type(lcd.RGB) == "function" local servoHz = { "67HZ", "250HZ", "50HZ", "300HZ" } @@ -40,27 +41,24 @@ local function byte(v) return limit(math.floor(tonumber(v) or 0), 0, 255) end -local function percent(v) - return limit(math.floor(tonumber(v) or 0), 0, 100) -end - local function gyroRaw(v) - return percent(v) * 2 + return limit(math.floor(tonumber(v) or 0), 0, 200) end -local function gyroUi(v) - return limit(math.floor((tonumber(v) or 0) / 2), 0, 100) +local function gyroText(v) + local raw = gyroRaw(v) + return tostring(math.floor(raw / 2)) .. (raw % 2 == 0 and ".0" or ".5") end local function screenLines() - if LCD_W >= 480 then return limit(math.floor(((LCD_H or 272) - 46) / 20), 8, 12) end + if colorLcd then return limit(math.floor(((LCD_H or 272) - 46) / 20), 8, 12) end return 6 end local function drawTitle(title) - if LCD_W >= 480 and lcd.drawFilledRectangle then - lcd.drawFilledRectangle(0, 0, LCD_W, 30, TITLE_BGCOLOR or 0) - lcd.drawText(3, 5, title, MENU_TITLE_COLOR or 0) + if colorLcd and lcd.drawFilledRectangle then + lcd.drawFilledRectangle(0, 0, LCD_W, 30, COLOR_THEME_SECONDARY1) + lcd.drawText(3, 5, title, COLOR_THEME_PRIMARY2) elseif lcd.drawScreenTitle then lcd.drawScreenTitle(title, 0, 0) else @@ -138,9 +136,9 @@ local function decodeGyroBytes(b0, b1, b2, b3) gyro.phase = math.floor(b0 / 2) % 2 local selector = math.floor(b0 / 4) % 16 gyro.senCh = selector == 0 and 0 or limit(selector + 1, 2, 8) - gyro.sen = gyroUi(math.floor(b0 / 64) + (b1 % 64) * 4) - gyro.pca = gyroUi(math.floor(b1 / 64) + (b2 % 64) * 4) - gyro.ags = gyroUi(math.floor(b2 / 64) + (b3 % 64) * 4) + gyro.sen = gyroRaw(math.floor(b0 / 64) + (b1 % 64) * 4) + gyro.pca = gyroRaw(math.floor(b1 / 64) + (b2 % 64) * 4) + gyro.ags = gyroRaw(math.floor(b2 / 64) + (b3 % 64) * 4) gyro.flags = math.floor(b3 / 64) % 4 haveValues = true end @@ -190,7 +188,7 @@ local function lineValue(line) if not haveValues then return "--" end if line[2] == "toggle" then return gyro[line[3]] == 0 and "Off" or "On" end if line[2] == "channel" then return chText() end - if line[2] == "percent" then return tostring(percent(gyro[line[3]])) end + if line[2] == "percent" then return gyroText(gyro[line[3]]) end if line[2] == "flags" then return servoHz[gyro.flags + 1] or tostring(gyro.flags) end return "" end @@ -198,11 +196,11 @@ end local function changeValue(dir, fast) local line = lines[sel] if not line then return end - local step = fast and 10 or 1 + local step = fast and 20 or 1 haveValues = true if line[2] == "toggle" then - gyro[line[3]] = gyro[line[3]] == 0 and 1 or 0 + gyro[line[3]] = dir > 0 and 1 or 0 elseif line[2] == "channel" then local vals = { 0, 2, 3, 4, 5, 6, 7, 8 } local pos = 1 @@ -211,21 +209,28 @@ local function changeValue(dir, fast) end gyro.senCh = vals[limit(pos + dir, 1, #vals)] elseif line[2] == "percent" then - gyro[line[3]] = percent(gyro[line[3]] + dir * step) + gyro[line[3]] = gyroRaw(gyro[line[3]] + dir * step) elseif line[2] == "flags" then gyro.flags = limit(gyro.flags + dir, 0, 3) end end +local function fastRotary() + return getRotEncSpeed() > 1 +end + local function handleEvent(event) - if event == EVT_VIRTUAL_NEXT then - if edit then changeValue(1, false) else sel = limit(sel + 1, 1, #lines) end - elseif event == EVT_VIRTUAL_PREV then - if edit then changeValue(-1, false) else sel = limit(sel - 1, 1, #lines) end + local nextEvent = edit and EVT_VIRTUAL_INC or EVT_VIRTUAL_NEXT + local prevEvent = edit and EVT_VIRTUAL_DEC or EVT_VIRTUAL_PREV + if event == nextEvent then + if edit then changeValue(1, fastRotary()) else sel = limit(sel + 1, 1, #lines) end + elseif event == prevEvent then + if edit then changeValue(-1, fastRotary()) else sel = limit(sel - 1, 1, #lines) end elseif event == EVT_VIRTUAL_NEXT_PAGE then if edit then changeValue(1, true) else sel = limit(sel + screenLines(), 1, #lines) end elseif event == EVT_VIRTUAL_PREV_PAGE then if edit then changeValue(-1, true) else sel = limit(sel - screenLines(), 1, #lines) end + killEvents(event) elseif event == EVT_VIRTUAL_ENTER then local line = lines[sel] if line and line[2] == "action" then @@ -244,14 +249,14 @@ local function draw() lcd.clear() drawTitle("DumboRC P series") - local font = LCD_W >= 480 and 0 or SMLSIZE + local font = colorLcd and 0 or SMLSIZE local x = 2 - local y = LCD_W >= 480 and 34 or 9 - local dy = LCD_W >= 480 and 20 or 8 - local valueX = LCD_W >= 480 and 150 or 82 + local y = colorLcd and 34 or 9 + local dy = colorLcd and 20 or 8 + local valueX = colorLcd and 150 or 82 if not moduleOk then - if LCD_W >= 480 then + if colorLcd then lcd.drawText(x, y + dy, "Select Multi RadLink/Dumbo_P", font + BLINK) else lcd.drawText(x, y + dy, "Select RadLink", font + BLINK) @@ -276,9 +281,9 @@ local function draw() end local function init() - moduleOk = findModule() + moduleOk = type(multiBuffer) == "function" and findModule() rebuildLines() - initBuffer() + if moduleOk then initBuffer() end end local function run(event) @@ -286,11 +291,13 @@ local function run(event) error("Cannot be run as a model script!") return 2 elseif event == EVT_VIRTUAL_EXIT then - release() + if moduleOk then release() end return 2 end - pollRx() - handleEvent(event) + if moduleOk then + pollRx() + handleEvent(event) + end draw() return 0 end