3 #include "rxtx_devLua.h"
5 #include "devServoOutput.h"
8 #define RX_HAS_SERIAL1 (GPIO_PIN_SERIAL1_TX != UNDEF_PIN || OPT_HAS_SERVO_OUTPUT)
10 extern void reconfigureSerial();
11 #if defined(PLATFORM_ESP32)
12 extern void reconfigureSerial1();
14 extern bool BindingModeRequest
;
16 static char modelString
[] = "000";
17 #if defined(GPIO_PIN_PWM_OUTPUTS)
18 static char pwmModes
[] = "50Hz;60Hz;100Hz;160Hz;333Hz;400Hz;10kHzDuty;On/Off;DShot;Serial RX;Serial TX;I2C SCL;I2C SDA;Serial2 RX;Serial2 TX";
21 static struct luaItem_selection luaSerialProtocol
= {
22 {"Protocol", CRSF_TEXT_SELECTION
},
24 "CRSF;Inverted CRSF;SBUS;Inverted SBUS;SUMD;DJI RS Pro;HoTT Telemetry;MAVLink;DisplayPort",
28 #if defined(PLATFORM_ESP32)
29 static struct luaItem_selection luaSerial1Protocol
= {
30 {"Protocol2", CRSF_TEXT_SELECTION
},
32 "Off;CRSF;Inverted CRSF;SBUS;Inverted SBUS;SUMD;DJI RS Pro;HoTT Telemetry;Tramp;SmartAudio;DisplayPort",
37 static struct luaItem_selection luaSBUSFailsafeMode
= {
38 {"SBUS failsafe", CRSF_TEXT_SELECTION
},
44 static struct luaItem_int8 luaTargetSysId
= {
45 {"Target SysID", CRSF_UINT8
},
48 (uint8_t)1, // value - default to 1
55 static struct luaItem_int8 luaSourceSysId
= {
56 {"Source SysID", CRSF_UINT8
},
59 (uint8_t)255, // value - default to 255
67 #if defined(POWER_OUTPUT_VALUES)
68 static struct luaItem_selection luaTlmPower
= {
69 {"Tlm Power", CRSF_TEXT_SELECTION
},
76 #if defined(GPIO_PIN_ANT_CTRL)
77 static struct luaItem_selection luaAntennaMode
= {
78 {"Ant. Mode", CRSF_TEXT_SELECTION
},
80 "Antenna A;Antenna B;Diversity",
86 #if defined(GPIO_PIN_NSS_2)
87 static struct luaItem_selection luaDiversityMode
= {
88 {"Rx Mode", CRSF_TEXT_SELECTION
},
95 static struct luaItem_folder luaTeamraceFolder
= {
96 {"Team Race", CRSF_FOLDER
},
99 static struct luaItem_selection luaTeamraceChannel
= {
100 {"Channel", CRSF_TEXT_SELECTION
},
102 "AUX2;AUX3;AUX4;AUX5;AUX6;AUX7;AUX8;AUX9;AUX10;AUX11;AUX12",
106 static struct luaItem_selection luaTeamracePosition
= {
107 {"Position", CRSF_TEXT_SELECTION
},
109 "Disabled;1/Low;2;3;Mid;4;5;6/High",
113 //----------------------------Info-----------------------------------
115 static struct luaItem_string luaModelNumber
= {
116 {"Model Id", CRSF_INFO
},
120 static struct luaItem_string luaELRSversion
= {
121 {version
, CRSF_INFO
},
125 //----------------------------Info-----------------------------------
127 //---------------------------- WiFi -----------------------------
130 //---------------------------- WiFi -----------------------------
132 //---------------------------- Output Mapping -----------------------------
134 #if defined(GPIO_PIN_PWM_OUTPUTS)
135 static struct luaItem_folder luaMappingFolder
= {
136 {"Output Mapping", CRSF_FOLDER
},
139 static struct luaItem_int8 luaMappingChannelOut
= {
140 {"Output Ch", CRSF_UINT8
},
143 (uint8_t)5, // value - start on AUX1, value is 1-16, not zero-based
145 PWM_MAX_CHANNELS
, // max
151 static struct luaItem_int8 luaMappingChannelIn
= {
152 {"Input Ch", CRSF_UINT8
},
157 CRSF_NUM_CHANNELS
, // max
163 static struct luaItem_selection luaMappingOutputMode
= {
164 {"Output Mode", CRSF_TEXT_SELECTION
},
170 static struct luaItem_selection luaMappingInverted
= {
171 {"Invert", CRSF_TEXT_SELECTION
},
177 static struct luaItem_command luaSetFailsafe
= {
178 {"Set Failsafe Pos", CRSF_COMMAND
},
183 #endif // GPIO_PIN_PWM_OUTPUTS
185 //---------------------------- Output Mapping -----------------------------
187 static struct luaItem_selection luaBindStorage
= {
188 {"Bind Storage", CRSF_TEXT_SELECTION
},
190 "Persistent;Volatile;Returnable",
194 static struct luaItem_command luaBindMode
= {
195 {STR_EMPTYSPACE
, CRSF_COMMAND
},
200 #if defined(GPIO_PIN_PWM_OUTPUTS)
201 static void luaparamMappingChannelOut(struct luaPropertiesCommon
*item
, uint8_t arg
)
203 bool sclAssigned
= false;
204 bool sdaAssigned
= false;
205 #if defined(PLATFORM_ESP32)
206 bool serial1rxAssigned
= false;
207 bool serial1txAssigned
= false;
210 const char *no1Option
= ";";
211 const char *no2Options
= ";;";
212 const char *dshot
= ";DShot";
213 const char *serial_RX
= ";Serial RX";
214 const char *serial_TX
= ";Serial TX";
215 const char *i2c_SCL
= ";I2C SCL;";
216 const char *i2c_SDA
= ";;I2C SDA";
217 const char *i2c_BOTH
= ";I2C SCL;I2C SDA";
218 #if defined(PLATFORM_ESP32)
219 const char *serial1_RX
= ";Serial2 RX;";
220 const char *serial1_TX
= ";;Serial2 TX";
221 const char *serial1_BOTH
= ";Serial2 RX;Serial2 TX";
224 const char *pModeString
;
227 // find out if use once only modes have already been assigned
228 for (uint8_t ch
= 0; ch
< GPIO_PIN_PWM_OUTPUTS_COUNT
; ch
++)
233 eServoOutputMode mode
= (eServoOutputMode
)config
.GetPwmChannel(ch
)->val
.mode
;
241 #if defined(PLATFORM_ESP32)
242 if (mode
== somSerial1RX
)
243 serial1rxAssigned
= true;
245 if (mode
== somSerial1TX
)
246 serial1txAssigned
= true;
250 setLuaUint8Value(&luaMappingChannelOut
, arg
);
252 // When the selected output channel changes, update the available PWM modes for that pin
253 // Truncate the select options before the ; following On/Off
256 #if defined(PLATFORM_ESP32)
257 // DShot output (1 option)
259 // ESP8266 enum skips this, so it is never present
260 if (GPIO_PIN_PWM_OUTPUTS
[arg
-1] != 0) // DShot doesn't work with GPIO0, exclude it
267 pModeString
= no1Option
;
269 strcat(pwmModes
, pModeString
);
271 // SerialIO outputs (1 option)
272 // ;[Serial RX] | [Serial TX]
273 if (GPIO_PIN_PWM_OUTPUTS
[arg
-1] == U0RXD_GPIO_NUM
)
275 pModeString
= serial_RX
;
277 else if (GPIO_PIN_PWM_OUTPUTS
[arg
-1] == U0TXD_GPIO_NUM
)
279 pModeString
= serial_TX
;
283 pModeString
= no1Option
;
285 strcat(pwmModes
, pModeString
);
287 // I2C pins (2 options)
288 // ;[I2C SCL] ;[I2C SDA]
289 if (GPIO_PIN_SCL
!= UNDEF_PIN
|| GPIO_PIN_SDA
!= UNDEF_PIN
)
291 // If the target defines SCL/SDA then those pins MUST be used
292 if (GPIO_PIN_PWM_OUTPUTS
[arg
-1] == GPIO_PIN_SCL
)
294 pModeString
= i2c_SCL
;
296 else if (GPIO_PIN_PWM_OUTPUTS
[arg
-1] == GPIO_PIN_SDA
)
298 pModeString
= i2c_SDA
;
302 pModeString
= no2Options
;
307 // otherwise allow any pin to be either SCL or SDA but only once
308 if (sclAssigned
&& !sdaAssigned
)
310 pModeString
= i2c_SDA
;
312 else if (sdaAssigned
&& !sclAssigned
)
314 pModeString
= i2c_SCL
;
316 else if (!sclAssigned
&& !sdaAssigned
)
318 pModeString
= i2c_BOTH
;
322 pModeString
= no2Options
;
325 strcat(pwmModes
, pModeString
);
327 // nothing to do for unsupported somPwm mode
328 strcat(pwmModes
, no1Option
);
330 #if defined(PLATFORM_ESP32)
331 // secondary Serial pins (2 options)
332 // ;[SERIAL2 RX] ;[SERIAL2_TX]
333 if (GPIO_PIN_SERIAL1_RX
!= UNDEF_PIN
|| GPIO_PIN_SERIAL1_TX
!= UNDEF_PIN
)
335 // If the target defines Serial2 RX/TX then those pins MUST be used
336 if (GPIO_PIN_PWM_OUTPUTS
[arg
-1] == GPIO_PIN_SERIAL1_RX
)
338 pModeString
= serial1_RX
;
340 else if (GPIO_PIN_PWM_OUTPUTS
[arg
-1] == GPIO_PIN_SERIAL1_TX
)
342 pModeString
= serial1_TX
;
346 pModeString
= no2Options
;
350 { // otherwise allow any pin to be either RX or TX but only once
351 if (serial1txAssigned
&& !serial1rxAssigned
)
353 pModeString
= serial1_RX
;
355 else if (serial1rxAssigned
&& !serial1txAssigned
)
357 pModeString
= serial1_TX
;
360 else if (!serial1rxAssigned
&& !serial1txAssigned
)
362 pModeString
= serial1_BOTH
;
366 pModeString
= no2Options
;
369 strcat(pwmModes
, pModeString
);
372 // trim off trailing semicolons (assumes pwmModes has at least 1 non-semicolon)
373 for (auto lastPos
= strlen(pwmModes
)-1; pwmModes
[lastPos
] == ';'; lastPos
--)
375 pwmModes
[lastPos
] = '\0';
378 // Trigger an event to update the related fields to represent the selected channel
379 devicesTriggerEvent();
382 static void luaparamMappingChannelIn(struct luaPropertiesCommon
*item
, uint8_t arg
)
384 const uint8_t ch
= luaMappingChannelOut
.properties
.u
.value
- 1;
385 rx_config_pwm_t newPwmCh
;
386 newPwmCh
.raw
= config
.GetPwmChannel(ch
)->raw
;
387 newPwmCh
.val
.inputChannel
= arg
- 1; // convert 1-16 -> 0-15
389 config
.SetPwmChannelRaw(ch
, newPwmCh
.raw
);
392 static void configureSerialPin(uint8_t sibling
, uint8_t oldMode
, uint8_t newMode
)
394 for (int ch
=0 ; ch
<GPIO_PIN_PWM_OUTPUTS_COUNT
; ch
++)
396 if (GPIO_PIN_PWM_OUTPUTS
[ch
] == sibling
)
398 // Retain as much of the sibling's current config as possible
399 rx_config_pwm_t siblingPinConfig
;
400 siblingPinConfig
.raw
= config
.GetPwmChannel(ch
)->raw
;
402 // If the new mode is serial, the sibling is also forced to serial
403 if (newMode
== somSerial
)
405 siblingPinConfig
.val
.mode
= somSerial
;
407 // If the new mode is not serial, and the sibling is serial, set the sibling to PWM (50Hz)
408 else if (siblingPinConfig
.val
.mode
== somSerial
)
410 siblingPinConfig
.val
.mode
= som50Hz
;
413 config
.SetPwmChannelRaw(ch
, siblingPinConfig
.raw
);
418 if (oldMode
!= newMode
)
420 deferExecutionMillis(100, [](){
426 static void luaparamMappingOutputMode(struct luaPropertiesCommon
*item
, uint8_t arg
)
429 const uint8_t ch
= luaMappingChannelOut
.properties
.u
.value
- 1;
430 rx_config_pwm_t newPwmCh
;
431 newPwmCh
.raw
= config
.GetPwmChannel(ch
)->raw
;
432 uint8_t oldMode
= newPwmCh
.val
.mode
;
433 newPwmCh
.val
.mode
= arg
;
435 // Check if pin == 1/3 and do other pin adjustment accordingly
436 if (GPIO_PIN_PWM_OUTPUTS
[ch
] == 1)
438 configureSerialPin(3, oldMode
, newPwmCh
.val
.mode
);
440 else if (GPIO_PIN_PWM_OUTPUTS
[ch
] == 3)
442 configureSerialPin(1, oldMode
, newPwmCh
.val
.mode
);
444 config
.SetPwmChannelRaw(ch
, newPwmCh
.raw
);
447 static void luaparamMappingInverted(struct luaPropertiesCommon
*item
, uint8_t arg
)
450 const uint8_t ch
= luaMappingChannelOut
.properties
.u
.value
- 1;
451 rx_config_pwm_t newPwmCh
;
452 newPwmCh
.raw
= config
.GetPwmChannel(ch
)->raw
;
453 newPwmCh
.val
.inverted
= arg
;
455 config
.SetPwmChannelRaw(ch
, newPwmCh
.raw
);
458 static void luaparamSetFailsafe(struct luaPropertiesCommon
*item
, uint8_t arg
)
460 luaCmdStep_e newStep
;
464 newStep
= lcsAskConfirm
;
465 msg
= "Set failsafe to curr?";
467 else if (arg
== lcsConfirmed
)
469 // This is generally not seen by the user, since we'll disconnect to commit config
470 // and the handset will send another lcdQuery that will overwrite it with idle
471 newStep
= lcsExecuting
;
472 msg
= "Setting failsafe";
474 for (int ch
=0; ch
<GPIO_PIN_PWM_OUTPUTS_COUNT
; ++ch
)
476 rx_config_pwm_t newPwmCh
;
477 // The value must fit into the 10 bit range of the failsafe
478 newPwmCh
.raw
= config
.GetPwmChannel(ch
)->raw
;
479 newPwmCh
.val
.failsafe
= CRSF_to_UINT10(constrain(ChannelData
[config
.GetPwmChannel(ch
)->val
.inputChannel
], CRSF_CHANNEL_VALUE_MIN
, CRSF_CHANNEL_VALUE_MAX
));
480 //DBGLN("FSCH(%u) crsf=%u us=%u", ch, ChannelData[ch], newPwmCh.val.failsafe+988U);
481 config
.SetPwmChannelRaw(ch
, newPwmCh
.raw
);
487 msg
= STR_EMPTYSPACE
;
490 sendLuaCommandResponse((struct luaItem_command
*)item
, newStep
, msg
);
493 #endif // GPIO_PIN_PWM_OUTPUTS
495 #if defined(POWER_OUTPUT_VALUES)
497 static void luaparamSetPower(struct luaPropertiesCommon
* item
, uint8_t arg
)
500 uint8_t newPower
= arg
+ POWERMGNT::getMinPower();
501 if (newPower
> POWERMGNT::getMaxPower())
503 newPower
= PWR_MATCH_TX
;
506 config
.SetPower(newPower
);
507 // POWERMGNT::setPower() will be called in updatePower() in the main loop
510 #endif // POWER_OUTPUT_VALUES
512 static void registerLuaParameters()
514 registerLUAParameter(&luaSerialProtocol
, [](struct luaPropertiesCommon
* item
, uint8_t arg
){
515 config
.SetSerialProtocol((eSerialProtocol
)arg
);
516 if (config
.IsModified()) {
517 deferExecutionMillis(100, [](){
523 #if defined(PLATFORM_ESP32)
526 registerLUAParameter(&luaSerial1Protocol
, [](struct luaPropertiesCommon
* item
, uint8_t arg
){
527 config
.SetSerial1Protocol((eSerial1Protocol
)arg
);
528 if (config
.IsModified()) {
529 deferExecutionMillis(100, [](){
530 reconfigureSerial1();
537 registerLUAParameter(&luaSBUSFailsafeMode
, [](struct luaPropertiesCommon
* item
, uint8_t arg
){
538 config
.SetFailsafeMode((eFailsafeMode
)arg
);
541 registerLUAParameter(&luaTargetSysId
, [](struct luaPropertiesCommon
* item
, uint8_t arg
){
542 config
.SetTargetSysId((uint8_t)arg
);
544 registerLUAParameter(&luaSourceSysId
, [](struct luaPropertiesCommon
* item
, uint8_t arg
){
545 config
.SetSourceSysId((uint8_t)arg
);
548 if (GPIO_PIN_ANT_CTRL
!= UNDEF_PIN
)
550 registerLUAParameter(&luaAntennaMode
, [](struct luaPropertiesCommon
* item
, uint8_t arg
){
551 config
.SetAntennaMode(arg
);
558 registerLUAParameter(&luaDiversityMode
, [](struct luaPropertiesCommon
* item
, uint8_t arg
){
559 config
.SetAntennaMode(arg
); // Reusing SetAntennaMode since both GPIO_PIN_ANTENNA_SELECT and GPIO_PIN_NSS_2 will not be defined together.
563 #if defined(POWER_OUTPUT_VALUES)
564 luadevGeneratePowerOpts(&luaTlmPower
);
565 registerLUAParameter(&luaTlmPower
, &luaparamSetPower
);
569 registerLUAParameter(&luaTeamraceFolder
);
570 registerLUAParameter(&luaTeamraceChannel
, [](struct luaPropertiesCommon
* item
, uint8_t arg
) {
571 config
.SetTeamraceChannel(arg
+ AUX2
);
572 }, luaTeamraceFolder
.common
.id
);
573 registerLUAParameter(&luaTeamracePosition
, [](struct luaPropertiesCommon
* item
, uint8_t arg
) {
574 config
.SetTeamracePosition(arg
);
575 }, luaTeamraceFolder
.common
.id
);
577 #if defined(GPIO_PIN_PWM_OUTPUTS)
578 if (OPT_HAS_SERVO_OUTPUT
)
580 luaparamMappingChannelOut(&luaMappingOutputMode
.common
, luaMappingChannelOut
.properties
.u
.value
);
581 registerLUAParameter(&luaMappingFolder
);
582 registerLUAParameter(&luaMappingChannelOut
, &luaparamMappingChannelOut
, luaMappingFolder
.common
.id
);
583 registerLUAParameter(&luaMappingChannelIn
, &luaparamMappingChannelIn
, luaMappingFolder
.common
.id
);
584 registerLUAParameter(&luaMappingOutputMode
, &luaparamMappingOutputMode
, luaMappingFolder
.common
.id
);
585 registerLUAParameter(&luaMappingInverted
, &luaparamMappingInverted
, luaMappingFolder
.common
.id
);
586 registerLUAParameter(&luaSetFailsafe
, &luaparamSetFailsafe
);
590 registerLUAParameter(&luaBindStorage
, [](struct luaPropertiesCommon
* item
, uint8_t arg
) {
591 config
.SetBindStorage((rx_config_bindstorage_t
)arg
);
593 registerLUAParameter(&luaBindMode
, [](struct luaPropertiesCommon
* item
, uint8_t arg
){
594 // Complete when TX polls for status i.e. going back to idle, because we're going to lose connection
595 if (arg
== lcsQuery
) {
596 deferExecutionMillis(200, EnterBindingModeSafely
);
598 sendLuaCommandResponse(&luaBindMode
, arg
< 5 ? lcsExecuting
: lcsIdle
, arg
< 5 ? "Entering..." : "");
601 registerLUAParameter(&luaModelNumber
);
602 registerLUAParameter(&luaELRSversion
);
603 registerLUAParameter(nullptr);
606 static void updateBindModeLabel()
608 if (config
.IsOnLoan())
609 luaBindMode
.common
.name
= "Return Model";
611 luaBindMode
.common
.name
= "Enter Bind Mode";
616 setLuaTextSelectionValue(&luaSerialProtocol
, config
.GetSerialProtocol());
617 #if defined(PLATFORM_ESP32)
620 setLuaTextSelectionValue(&luaSerial1Protocol
, config
.GetSerial1Protocol());
624 setLuaTextSelectionValue(&luaSBUSFailsafeMode
, config
.GetFailsafeMode());
626 if (GPIO_PIN_ANT_CTRL
!= UNDEF_PIN
)
628 setLuaTextSelectionValue(&luaAntennaMode
, config
.GetAntennaMode());
634 setLuaTextSelectionValue(&luaDiversityMode
, config
.GetAntennaMode()); // Reusing SetAntennaMode since both GPIO_PIN_ANTENNA_SELECT and GPIO_PIN_NSS_2 will not be defined together.
637 #if defined(POWER_OUTPUT_VALUES)
638 // The last item (for MatchTX) will be MaxPower - MinPower + 1
639 uint8_t luaPwrVal
= (config
.GetPower() == PWR_MATCH_TX
) ? POWERMGNT::getMaxPower() + 1 : config
.GetPower();
640 setLuaTextSelectionValue(&luaTlmPower
, luaPwrVal
- POWERMGNT::getMinPower());
644 setLuaTextSelectionValue(&luaTeamraceChannel
, config
.GetTeamraceChannel() - AUX2
);
645 setLuaTextSelectionValue(&luaTeamracePosition
, config
.GetTeamracePosition());
647 #if defined(GPIO_PIN_PWM_OUTPUTS)
648 if (OPT_HAS_SERVO_OUTPUT
)
650 const rx_config_pwm_t
*pwmCh
= config
.GetPwmChannel(luaMappingChannelOut
.properties
.u
.value
- 1);
651 setLuaUint8Value(&luaMappingChannelIn
, pwmCh
->val
.inputChannel
+ 1);
652 setLuaTextSelectionValue(&luaMappingOutputMode
, pwmCh
->val
.mode
);
653 setLuaTextSelectionValue(&luaMappingInverted
, pwmCh
->val
.inverted
);
657 if (config
.GetModelId() == 255)
659 setLuaStringValue(&luaModelNumber
, "Off");
663 itoa(config
.GetModelId(), modelString
, 10);
664 setLuaStringValue(&luaModelNumber
, modelString
);
666 setLuaTextSelectionValue(&luaBindStorage
, config
.GetBindStorage());
667 updateBindModeLabel();
669 if (config
.GetSerialProtocol() == PROTOCOL_MAVLINK
)
671 setLuaUint8Value(&luaSourceSysId
, config
.GetSourceSysId() == 0 ? 255 : config
.GetSourceSysId()); //display Source sysID if 0 display 255 to mimic logic in SerialMavlink.cpp
672 setLuaUint8Value(&luaTargetSysId
, config
.GetTargetSysId() == 0 ? 1 : config
.GetTargetSysId()); //display Target sysID if 0 display 1 to mimic logic in SerialMavlink.cpp
673 LUA_FIELD_SHOW(luaSourceSysId
)
674 LUA_FIELD_SHOW(luaTargetSysId
)
678 LUA_FIELD_HIDE(luaSourceSysId
)
679 LUA_FIELD_HIDE(luaTargetSysId
)
682 return DURATION_IMMEDIATELY
;
687 luaHandleUpdateParameter();
688 // Receivers can only `UpdateParamReq == true` every 4th packet due to the transmitter cadence in 1:2
689 // Channels, Downlink Telemetry Slot, Uplink Telemetry (the write command), Downlink Telemetry Slot...
690 // (interval * 4 / 1000) or 1 second if not connected
691 return (connectionState
== connected
) ? ExpressLRS_currAirRate_Modparams
->interval
/ 250 : 1000;
696 registerLuaParameters();
698 return DURATION_IMMEDIATELY
;
701 device_t LUA_device
= {
702 .initialize
= nullptr,