LUA attempt to index global nil value (global 'CAN')

I am trying to send data to my pixhawk over the CAN bus. I googled and saw that lua scripting can help me without building the code
(I saw someone already succeeded but how
CAN Bus Arduino communication) ?

Watched videos installed vscode and add 2 lua files:

For testing added main.lua ; and used first line of example CAN_read.lua

local driver1 = CAN:get_device(5)

and this line gives error.

attempt to index global nil value (global ‘CAN’)

Does anybody know why this is happening?

If I can build sample code (for example CAN_read.lua) will start command/response over the CAN bus. Thanks for support.


Are you running this script on your autopilot or with an interpreter on a PC?

It must be run on the autopilot itself.

I just installed VScode and added 2 lua files
-docs.lua (from github)
-main.lua (for test)

What do you mean “It must be run on the autopilot itself.”, You mean I should test with real hardware and it will work? Adding 2 lua (or 1 ?) under APM->scripts ?

May i use CANBUS as command/response messaging without build code, is it possible or not ?

Site says: " without modifying the core flight code"

" ArduPilot has introduced support for Lua scripting. Scripting provides a safe, “sandboxed” environment for new behaviors to be added to the autopilot without modifying the core flight code. Scripts are stored on the SD card and run in parallel with the flight code."

My understanding is if I need to update code there is windows based WSL, bunch of Linux package , GitHub source code installation; if I am using lua this is not necessary, is that right ?

Yes, test your script on real hardware. Add main.lua to APM/scripts and enable scripting.

There is no need to set up a build environment.

Thanks Yuri, I will (already tested hello_world.lua it works), but this bring another question.

How can I debug and test script?

In my case can bus work in command /response mechanism so I need send 8 bytes and capture 8 bytes responses. Maybe i will not see datas on ground, coming from canbus ? Then what ?

Because you are essentially writing a hardware driver, you need to test and debug on live hardware. I recommend creating a bench test rig with just an autopilot and the minimum required peripheral hardware connected.

Use the gcs:send_text and gcs:send_named_float() bindings to your advantage while debugging.

1 Like

Thanks Yuri, I understand and will try to capture.

Do you advice me a Can Bus template code for (maybe max 15 lines) sending 8 bytes 0xaabbccddeeffgghh datas and reading 8 bytes 0xkkllmmnnssttrrvv datas and sending ground.


The examples you’re already referencing do exactly that.

1 Like

Ok I will, one last question may I assign datas on Status window values, if so how ?

I assume you mean on Mission Planner’s “Quick” tab?

gcs:send_named_float('NAME', value) is the method for that. Scripted values will show up as MAV_xxx in the selection window.

1 Like

My new problem is I need to send canbus message as 4 bytes extended id instead 2 bytes.

target_ID = uint32_t (0xAABBCCDD)
msg = CANFrame ()
msg:id ()

i capture 2 bytes node is as 0x0001, instead i expect to capture 0XAABBCCDD how can solve this problem?

EDIT: see further discussion below.

Thanks for response Yuri.
I have tested it many times, but i see 0x0001 likes overflow in canbus monitor tool.

Others CAN libraries have message.extended= boolean similar attributes but in our case docs.lua seems unfortunately nothing.

In your example, you aren’t setting the id at all (the intended value must be inside the parentheses). But we were missing a key piece of the puzzle as well.

The CAN_write example shows the proper way to set an extended frame ID (line 26):

ardupilot/CAN_write.lua at master · ArduPilot/ardupilot (

Specifically, (uint32_t(1) << 31) sets the flag for an extended frame ID.

We do have an isExtended() method that will allow you to check whether the ID has been properly formatted.

Thanks to @iampete for a brief discussion to sort that out.

1 Like

In my example i filled msg:id(target_id) so thats not problem.

Wow, great catch!

But seems most significant bit set 1, 8 bits sets priority, 8 bits message id and node id for 8 bits?

How will i fit this to my extended 4 bytes node id 0×AABBCCDD (i don’t have priority or message id)?


A 32 bit frame ID is invalid. At most, it can be 29 bits (the least significant 29 bits of the uint32_t data type). Set the most significant bit high to indicate an extended data frame, as in the example.

With your example ID of 0xAABBCCDD, the third most significant bit is also set high, probably causing the issue. You could try 0x8ABBCCDD to further debug.

Per your message (please just post here), if the intended ID is 0x0AB20001, then you can set the frame ID like so, just like in the example:

msg:id( (uint32_t(1) << 31) | (uint32_t(0x0AB20001) )


It is done and next problem is sending 4 messages.

I got timeout and just respondes msg4, i changed timeout param many times but couldn’t worked, should i add delay, sleep or another function mechanism?

Locale frame_send1=driver:write_frame(msg1,10000)
Locale frame_send2=driver:write_frame(msg2,10000)
Locale frame_send3=driver:write_frame(msg3,10000)
Locale frame_send4=driver:write_frame(msg4,10000)

There shouldn’t be an issue sending consecutive messages, but if you need to add a delay, you can write a set of functions like this. Note, this is not a complete script and will require your edits to work properly.

local RUN_INTERVAL_MS = 100
local DELAY_MS = 10
local msg_index  = 1

-- create a table of CAN frames
local messages = { CANFrame(), CANFrame(), CANFrame(), CANFrame() }

function send_messages()
    -- send a single CAN frame and increment the table index
    driver:write_frame(messages[msg_index], 10000)
    msg_index = msg_index + 1

    -- if we've sent all the messages, return to the main loop
    if msg_index > #messages then return update, DELAY_MS end

    -- otherwise, send another message
    return send_messages, DELAY_MS

function update()
    -- craft CAN frames
    messages[1]:id( (uint32_t(1) << 31) | uint32_t(0x0AB20001) )
    messages[2]:id( (uint32_t(1) << 31) | uint32_t(0x0AB20001) )
    messages[3]:id( (uint32_t(1) << 31) | uint32_t(0x0AB20001) )
    messages[4]:id( (uint32_t(1) << 31) | uint32_t(0x0AB20001) )
    -- set data fields as well here

    -- if we don't need to send the frames on every loop execution,
    -- use this "do_send" flag to indicate when it's time to send
    local do_send = true

    -- reset the table index and call the send function
    if do_send then
        msg_index = 1
        return send_messages, DELAY_MS

    return update, RUN_INTERVAL_MS
1 Like

Thanks is there also an elegant way to capture messages according to nodeid and processing data.