LED and Buzzer on 3 Position Switch

Hello everyone, migrating from inav to my new ardupilot wing i was trying to set up a single 3 position switch on my transmitter, using only one channel, to control my onboard LEDs and Buzzer. After a quick look around i realized that that was not an easy thing to do (correct me if i am wrong). So i ended up writing a lua script to get it done. I am using a matek H-743 board and some 2812 matek leds (neopixel). Board uses servo_13 as an LED port and i am using 12 LEDs in line across the leading edge of each wing. The two lines of leds (one on the left and one on the right wing) are connected with a Y cable to servo_13.

If pwm on the selected channel is <1300 then both leds and buzzer are OFF.
If pwm on the selected channel is >1300 and <1600 leds are ON (red) and buzzer is OFF.
If pwm on the selected channel is >1600 buzzer is ON and LEDs are Flashing (white).

I am posting it in case someone finds it useful or has a more effective way to do it!

local channel = 6  -- rc channel controling LEDs and Buzzer. (Need to be calibrated in the radio calibration 1000-2000us)
local led_pin = 13  --LEDs servo pin (SERVO13 for MATEK-H743)

serialLED:set_num_neopixel(led_pin,  12)  --"neopixel" for Matek-2812 using "12" LEDs. "profiled" might be your led type

local led_sw_on = false
local buz_sw_on = false
local led_status = false
local led_flash = false
local buz_status = false

function update()
    local pwm_val = rc:get_pwm(channel)

    if pwm_val < 1300 then
        led_sw_on = false
        buz_sw_on = false
    elseif pwm_val > 1300 and pwm_val < 1600 then
        led_sw_on = true
        buz_sw_on = false
    elseif pwm_val > 1600 then
        led_sw_on = false
        buz_sw_on = true
    end

    if led_sw_on and not led_status then
        gcs:send_text(6, "Turning LEDs: ON")
        serialLED:set_RGB(led_pin, -1, 255, 0, 0)
        led_status = true
    elseif not led_sw_on and led_status then
        gcs:send_text(6, "Turning LEDs: OFF")
        serialLED:set_RGB(led_pin, -1, 0, 0, 0)
        led_status = false
    end
    serialLED:send(led_pin)

    if buz_sw_on  then
        if not buz_status then
            gcs:send_text(6, "Turning BUZZER: ON")
            buz_status = true
        end

        notify:play_tune("MBT200>C#2A#2C#2A#2")

        if not led_flash then 
            serialLED:set_RGB(led_pin, -1, 255, 255, 255)
            led_flash = true
        else
            serialLED:set_RGB(led_pin, -1, 0, 0, 0)
            led_flash = false
        end
        serialLED:send(led_pin)
    else
        if buz_status then
            gcs:send_text(6, "Turning BUZZER: OFF")
            buz_status = false
            serialLED:set_RGB(led_pin, -1, 0, 0, 0)
            serialLED:send(led_pin)
        end
    end

  return update, 1000 --check every 1 seconds
end

return update, 5000 --run 5 seconds after boot
1 Like

You should make use of the aux switch binding rather than reading PWM directly.

Also, it appears you aren’t enabling and disabling the onboard buzzer but rather sounding a tune (that repeats incessantly via the MB prefix) every second when the switch position is high. There is already a “Lost Vehicle” aux function that accomplishes similar.

Regardless, here is a script that avoids clunky, nested if/then/else logic through the use of a state machine. I’m fairly certain that it retains functionality as you desire. Set RC6_OPTION=300 to make it work with your configuration.

When satisfied with its behavior, set DEBUG = false to get rid of nuisance GCS messages (since LED and buzzer state should make things apparent without using text messages).

led_buzzer.lua (1.9 KB)

You should make use of the aux switch binding rather than reading PWM directly.

Thanks for that, a had try it but the documentation was saying that the return would be (-1,0,1) but saw you where using (0,1,2), so that’s working now.

Also, it appears you aren’t enabling and disabling the onboard buzzer but rather sounding a tune (that repeats incessantly via the MB prefix) every second when the switch position is high. There is already a “Lost Vehicle” aux function that accomplishes similar.

The hole point is to use 1 radio channel for both functions. Setting a RCx_OPTION to Lost Plane would have restrict from using the same channel for the LEDs (i guess…?). Also, i dont think that the Lost Plane fuction does anything more than “Plays the lost copter alarm though the buzzer”.

Regardless, here is a script that avoids clunky, nested if/then/else logic through the use of a state machine.

Is there a way to see the impact a script has in my boards processing power, memory, etc ?

Thank you for your reply!!!

You could use the Lost Plane feature and also poll that switch from within your script, retaining single channel behavior. Change local RC_OPTION = 300 to local RC_OPTION = 30. In my opinion, the Lost Plane tone is FAR preferable to that piercing mess you made with the tone generator…:smiley:

You can view free memory in Mission Planner on the Quick tab. You can also enable script debugging to get some more info as it runs.

This simple script will have a near negligible impact on an H743 based autopilot.

I missed the fact that you wanted different colors when steady vs flashing. Here’s an update that shows a method of accomplishing that.
led_buzzer.lua (1.9 KB)

I will try to merge our scripts keeping the parts that suites me! it’s my first time writing in lua so that will take some time :sweat_smile:

question,
whats does the ", 0 " part doing after the function?

if sw_pos ~= 0 then return do_transition(sw_pos), 0 end

Schedules the function returned by do_transition with no delay.

There’s really nothing to merge between the two scripts. The logic mechanism is entirely different.

i know there might be a million ways to do this but here is a much simpler approach, at list from my original script. 39 versus 70 lines script…

local rc_option = 300  -- script useses channel with RXcx_OPTION option from 300-307 value.
local led_pin = 13  --LEDs servo pin (SERVO13 for MATEK-H743)

serialLED:set_num_neopixel(led_pin,  12)  --"neopixel" for Matek-2812 using "12" LEDs. "profiled" might be your led type
local channel = rc:find_channel_for_option(rc_option)
local led_flash = false
local sw_state = -1

function update()
    local sw_pos = channel:get_aux_switch_pos() --return input on a channel from as (0,1,2), centered on the trim.
    if sw_state ~= sw_pos or sw_pos == 2 then
        if sw_pos == 0 then
            gcs:send_text(6, "LEDs: OFF | BUZZER: OFF")
            serialLED:set_RGB(led_pin, -1, 0, 0, 0)
            serialLED:send(led_pin)
        elseif sw_pos == 1 then
            gcs:send_text(6, "LEDs: ON | BUZZER: OFF")
            serialLED:set_RGB(led_pin, -1, 255, 0, 0)
            serialLED:send(led_pin)
        elseif sw_pos == 2 then
            if sw_state ~= sw_pos then
                gcs:send_text(6, "LEDs: FLASHING | BUZZER: ON")
            end
            notify:play_tune("MFT200>C#2A#2")
            if led_flash == false then
                serialLED:set_RGB(led_pin, -1, 255, 255, 255)
                serialLED:send(led_pin)
                led_flash = true
            else
                serialLED:set_RGB(led_pin, -1, 0, 0, 0)
                serialLED:send(led_pin)
                led_flash = false
            end
        end
        sw_state = sw_pos
    end
    return update, 2000 --check every 2 seconds
end
return update, 5000 --run 5 seconds after boot

Well, that’s a much better variation than your first attempt. I encourage you to digest the examples I gave if you’d like to improve your Lua skills overall.

And remember, fewer lines doesn’t always mean better code…

i was trying all night but the hole function thing didn’t worked out. I need to do some reading on Lua and on the ardupilot Lua documentation (not really much there).

I am going to try to write another one switching on takeoff and land modes checking if the plane is armed, airborne, in which mode etc… Dont know if thats even possible actually :sweat_smile:

All of that is entirely possible. Look through all of the examples.

Here’s one that demonstrates that shorter isn’t always better - same functionality as your last variation in 27 lines:

local flash_state = 0
local last_sw_pos = -1
serialLED:set_num_neopixel(13,  12)

local func_table = {
    [0] = function(announce)
        serialLED:set_RGB(13, -1, 0, 0, 0)
        if announce then gcs:send_text(6, 'LEDs: OFF | BUZZER: OFF') end
    end,
    [1] = function(announce)
        serialLED:set_RGB(13, -1, 255, 0, 0)
        if announce then gcs:send_text(6, 'LEDs: ON | BUZZER: OFF') end
    end,
    [2] = function(announce)
        notify:play_tune('MBT200>C#2A#2C#2A#2')
        flash_state = flash_state ~ 1
        serialLED:set_RGB(13, -1, flash_state * 255, flash_state * 255, flash_state * 255)
        if announce then gcs:send_text(6, 'LEDs: FLASHING | BUZZER: ON') end
    end }

function update()
    local sw_pos = rc:find_channel_for_option(300):get_aux_switch_pos()
    func_table[sw_pos](sw_pos ~= last_sw_pos)
    last_sw_pos = sw_pos
    serialLED:send(13)
    return update, 2000
end
return update, 5000

And if we remove the GCS messages, we truncate down to 17 lines with no loss of functionality:

local flash_state = 0
serialLED:set_num_neopixel(13,  12)

local func_table = {
    [0] = function() serialLED:set_RGB(13, -1, 0, 0, 0) end,
    [1] = function() serialLED:set_RGB(13, -1, 255, 0, 0) end,
    [2] = function() notify:play_tune('MBT200>C#2A#2C#2A#2')
        flash_state = flash_state ~ 1
        serialLED:set_RGB(13, -1, flash_state * 255, flash_state * 255, flash_state * 255)
    end }

function update()
    func_table[rc:find_channel_for_option(300):get_aux_switch_pos()]()
    serialLED:send(13)
    return update, 2000
end
return update, 5000

Again, I’d encourage you to dissect these examples and see how they work. The table of functions isn’t a bad method of garnering switch/case-like behavior in Lua, but these are the real sins:

  • I’ve hardcoded all the literals rather than using variables to define things like num_leds
  • I execute find_channel_for_option() on every iteration rather than storing its result
  • I index the table via a function call that ought to be broken into constituent parts
  • I use a boolean comparison as an argument, which is obfuscating and ugly
  • I multiply flash_state 3 times in a single line rather than using a variable for that result

If you’d like a little “homework” to improve your skills, turn the examples in this post into code that is actually legible/maintainable while minimizing the number of “if” statements and eliminating any use of “else.”

1 Like
Again, I’d encourage you to dissect these examples and see how they work. 

ALREADY ON IT! THANK YOU SO MUCH!

I didn’t mean to say that all “If” statements are bad - they just seem to be the only tool in your kit. So, if you actively avoid using them, you’ll learn more techniques to incorporate logic. There is at least one school of thought, however, that all “else” statements are to be avoided.

Just for grins, here’s another “good” example that minimizes nested logic statements and, at least in my opinion, maximizes readability.

led_buzzer_again.lua (1.1 KB)