ardupilot开发 --- Lua脚本篇

1. 一些概念

  • ArduPilot引入了对Lua脚本的支持;
  • 可以同时运行多个脚本;
  • Lua脚本存放在 SD card 中;
  • Copter-4.0 及以上版本才支持Lua脚本;
  • scripting API ?scripting applets ?
  • 飞控条件:2 MB of flash and 70 kB of memory ;
  • 将Lua脚本上传到 SD card’s APM/scripts 文件夹中,在Mission Planner使用MAVFTP可以上传文件;

2. Lua的作用?

Multiple scripts can be run at once
Monitor the vehicle state
Start to manipulate vehicle state

3. 什么时候开始执行Lua脚本?

当自动驾驶仪通电时,它将加载并启动所有脚本。
并在以下目录中寻找脚本:

  • 它将在ROMFS文件系统
  • SD卡上的 APM/scripts
  • 如果是SITL模拟,则是启动模拟的 base directory

这可以通过使用 SCR_DIR_DISABLE参数进行修改。

4. 相关参数

  • 使能:SCR_ENABLE
  • 此外,还有四个专用的脚本参数可用: SCR_USER1 ~ SCR_USER4, 使用与任何其他参数相同的方法访问,但这些参数是为脚本使用而保留的。脚本还可以生成自己的参数(请参阅Accessing/Adding Parameters via Scripts),以便在脚本中使用。
  • 可以调整 SCR_HEAP_SIZE 以增加或减少可用于脚本的内存量。默认值从43K到204.8K不等,具体取决于使用的cpu,对于小脚本来说,最小值(43K)就足够了,但许多小程序需要更多(一些小程序现在需要300K)。自动驾驶仪的空闲内存在很大程度上取决于启用了哪些功能和外围设备。如果此参数设置得太低,脚本可能无法运行,并出现内存不足的预启动错误。如果设置过高,其他自动驾驶功能,如地形跟随,甚至EKF可能无法初始化。在配备STM32F4微控制器的自动驾驶仪上,几乎总是需要禁用Smart RTL(漫游者、科普特)和Terrain Following(飞机、科普特(Copter))。这些功能通常在默认情况下启用,设置SRTL_PPOINTS=0,TERRAIN_ENABLE=0)

5. RCx_OPTION 配置成 Scriptingx 后如何在脚本中使用

通过遥控器通道控制脚本的执行;
local rc_switch = rc:find_channel_for_option(24)
local sw_pos = rc_switch:get_aux_switch_pos() %%0,1,2 低位中位高位
例如,300即Scripting1,301即Scripting2:

-- example of getting RC input

local scripting_rc_1 = rc:find_channel_for_option(300)
local scripting_rc_2 = rc:find_channel_for_option(301)
local flip_flop = 0

function update()
  pwm1 = rc:get_pwm(1)
  pwm2 = rc:get_pwm(2)
  pwm3 = rc:get_pwm(3)
  pwm4 = rc:get_pwm(4)
  gcs:send_text(0, "RCIN 1:" .. tostring(pwm1) .. " 2:" .. tostring(pwm2).. " 3:" .. tostring(pwm3).. " 4:" .. tostring(pwm4))

  -- read normalized input from designated scripting RCx_OPTION
  if scripting_rc_1 then
    gcs:send_text(0, "Scripting in 1:" .. tostring(scripting_rc_1:norm_input()))
  end

  -- read switch input from second designated scripting RCx_OPTION
  if scripting_rc_2 then
    local sw_pos = scripting_rc_2:get_aux_switch_pos()
    if sw_pos == 0 then 
      gcs:send_text(0, "Scripting switch is low")
    elseif sw_pos == 1 then
      gcs:send_text(0, "Scripting switch is middle")
    else
      gcs:send_text(0, "Scripting switch is high")
    end
  end

  -- we can also call functions that are available to RC switches
  -- 28 is Relay one
  rc:run_aux_function(28, flip_flop)

  if (flip_flop == 0) then
    flip_flop = 2 -- switch high
  else
    flip_flop = 0 -- switch low
  end


  return update, 1000 -- reschedules the loop
end

return update()

6. 使用文档、参考例程

文档
例程:ardupilot\libraries\AP_Scripting\examples

7. 函数绑定

  • 绑定的代码实现:
    libraries\AP_Scripting\generator\description\bindings.desc
  • 类的“别名
    在绑定中,通常会给“”(原文称group)取别名,如:
    AP_AHRS 的别名为 ahrs
    AP_GPS 的别名为 gps
    定义方式:
    singleton AP_AHRS rename ahrs

    singleton AP_AHRS alias ahrs
  • 如何绑定函数
    为什么要绑定函数?因为只有绑定了该函数,该函数才能在lua脚本中被调用!
    绑定格式:
    singleton 类名 method 函数名 函数返回值类型 输入参数类型/范围
    e.g :
    • 无输入参数:
      singleton AP_Arming method is_armed boolean
    • 有一个输入参数,该参数有取值范围:0 ~ AP_RELAY_NUM_RELAYS
      singleton AP_Relay method on void uint8_t 0 AP_RELAY_NUM_RELAYS
    • 输入参数可为null:
      singleton AP_AHRS method get_variances boolean float’Null float’Null float’Null Vector3f’Null float’Null
  • userdata的使用
    功能:假如要绑定的函数的输出或返回值类型是结构体或类,怎么办?这时就需要使用userdata。
    以下面这个函数绑定为例:
    singleton AP_Mission method set_item boolean uint16_t’skip_check mavlink_mission_item_int_t
    显而易见输入参数 mavlink_mission_item_int_t 是个类或结构体类型
    用userdata分解式定义这个参数:
    如何知道mavlink_mission_item_int_t 都包含哪些内容呢?去C++源码中搜索发现找不到这个类型的定义,只有一堆引用,她实质上是MavLink库中的一个数据类型,去MavLink网站中搜索关键词“MISSION_ITEM_INT”可知:
    在这里插入图片描述
    用userdata分解式定义这个参数:
userdata mavlink_mission_item_int_t field param1 float'skip_check read write
userdata mavlink_mission_item_int_t field param2 float'skip_check read write
userdata mavlink_mission_item_int_t field param3 float'skip_check read write
userdata mavlink_mission_item_int_t field param4 float'skip_check read write
userdata mavlink_mission_item_int_t field x int32_t'skip_check read write
userdata mavlink_mission_item_int_t field y int32_t'skip_check read write
userdata mavlink_mission_item_int_t field z float'skip_check read write
userdata mavlink_mission_item_int_t field seq uint16_t'skip_check read write
userdata mavlink_mission_item_int_t field command uint16_t'skip_check read write
-- userdata mavlink_mission_item_int_t field target_system uint8_t'skip_check read write
-- userdata mavlink_mission_item_int_t field target_component uint8_t'skip_check read write
userdata mavlink_mission_item_int_t field frame uint8_t'skip_check read write
userdata mavlink_mission_item_int_t field current uint8_t'skip_check read write
-- userdata mavlink_mission_item_int_t field autocontinue uint8_t'skip_check read write
  • 如何使用某类中的函数?
    给类定义好别名并且绑定该函数后,就可以通过下面方式调用该函数,如:
    roll = ahrs:get_roll()

  • 使用示例:

  local item = mavlink_mission_item_int_t() 
  item:seq(index+1)
  item:frame(3)
  item:command(16)
  item:param1(0.0)
  item:param2(0.0)
  item:x(0.0)
  item:y(0.0) 
  loc1:lat(lat_of_center)
  loc1:lng(lon_of_center)
  local offset_x,offset_y
  mission:set_item(mission:num_commands(),item)

item:command(16) 即:
在这里插入图片描述

但MAV_CMD_NAV_WAYPOINT中并没有x,y这两个参数?原来是lua重新定义参数结构体了:

在这里插入图片描述
去AP_Mission.cpp中找到set_item函数查看具体用法,逻辑,最后会跳转到以下函数中:

MAV_MISSION_RESULT AP_Mission::mavlink_int_to_mission_cmd(const mavlink_mission_item_int_t& packet, AP_Mission::Mission_Command& cmd)
{ 
    cmd.id = packet.command;   
    // command specific conversions from mavlink packet to mission command
    switch (cmd.id) {  
	    case MAV_CMD_NAV_WAYPOINT: { 
	    ...
	    }
    }
    ...
    // copy location from mavlink to command
    cmd.content.location.lat = packet.x;
    cmd.content.location.lng = packet.y;
    ...
}

8. 自己写的一个例子

功能:用lua脚本生成风机航迹点
https://download.csdn.net/download/weixin_43321489/88647127

9. LUA中添加参数并在地面站中显示

参考:https://ardupilot.org/dev/docs/common-scripting-parameters.html

function readPara()
    local DIST_HUB = Parameter()
    DIST_HUB:init('WPCRT_DIST_HUB')
    distFromHub = DIST_HUB:get()

    local AG_EAST = Parameter()
    AG_EAST:init('WPCRT_AG_EAST')
    angleFirstBladeRotateToEast = AG_EAST:get()

    local AG_LEVEL = Parameter()
    AG_LEVEL:init('WPCRT_AG_LEVEL')
    angleFirstBladeFromLevel = AG_LEVEL:get()

    local BL_LEN = Parameter()
    BL_LEN:init('WPCRT_BL_LEN')
    bladeLength = BL_LEN:get()

    local MIN_ALT = Parameter()
    MIN_ALT:init('WPCRT_MIN_ALT')
    wayPointMinAlt = MIN_ALT:get()

    if distFromHub>0 and angleFirstBladeRotateToEast~=nil and angleFirstBladeFromLevel~=nil and bladeLength>0 and wayPointMinAlt>0 then 
        gcs:send_text(0, string.format("MISSION CREATION START!!"))
        gcs:send_text(0,string.format("distFromHub: %f ",distFromHub))
        gcs:send_text(0,string.format("angleFirstBladeRotateToEast: %f ",angleFirstBladeRotateToEast))
        gcs:send_text(0,string.format("angleFirstBladeFromLevel: %f ",angleFirstBladeFromLevel))
        gcs:send_text(0,string.format("bladeLength: %f ",bladeLength))
        gcs:send_text(0,string.format("wayPointMinAlt: %f ",wayPointMinAlt))
        return true
    else
        gcs:send_text(0, string.format("参数异常!!"))
        gcs:send_text(0, string.format("MISSION CREATION START!!"))
        gcs:send_text(0,string.format("distFromHub: %f ",distFromHub))
        gcs:send_text(0,string.format("angleFirstBladeRotateToEast: %f ",angleFirstBladeRotateToEast))
        gcs:send_text(0,string.format("angleFirstBladeFromLevel: %f ",angleFirstBladeFromLevel))
        gcs:send_text(0,string.format("bladeLength: %f ",bladeLength))
        gcs:send_text(0,string.format("wayPointMinAlt: %f ",wayPointMinAlt))
        return false 
    end
end
function createPara()
    local PARAM_TABLE_KEY = 0 --0~200
    assert(param:add_table(PARAM_TABLE_KEY, "WPCRT_", 10), 'could not add param table') -- WPCRT: way points create
    assert(param:add_param(PARAM_TABLE_KEY, 1,  'DIST_HUB', 50), 'could not add param DIST_HUB')
    assert(param:add_param(PARAM_TABLE_KEY, 2,  'AG_EAST', 0), 'could not add param AG_EAST')
    assert(param:add_param(PARAM_TABLE_KEY, 3,  'AG_LEVEL', 0), 'could not add param AG_LEVEL')
    assert(param:add_param(PARAM_TABLE_KEY, 4,  'BL_LEN', 20), 'could not add param BL_LEN')
    assert(param:add_param(PARAM_TABLE_KEY, 5,  'MIN_ALT', 15), 'could not add param MIN_ALT')
end

10. 系统函数

已经定义好的函数,可以直接用:
在docs.lua中查看例程

lua引擎中没有定义uint64_t的数据类型,如何绑定具有uint64_t数据的函数?

用char * 代替uint64_t
例如:
char* AP_RTC::get_utc_usecStr( )
{
if (rtc_source_type == SOURCE_NONE) {
std::string tmp=“false”;
strcpy(luaData,tmp.c_str());
return luaData;
}
uint64_t usec = AP_HAL::micros64() + rtc_shift;
std::string usecStr = std::to_string(usec);
strcpy(luaData,usecStr.c_str());
return luaData;
}
其中:luaData定义在AP_RTC类中: char luaData[64];

11. 编程知识点拾遗

lua的数据类型只有以下几种,没有int和uint:
在这里插入图片描述
但是int和uint_32这种可以定义成userdata。
local a=1 和 local a=1.0 是一样的,a的数据类型都是number。
打印时,%u,%d,%f都可以用来显示a,只是精度不同而已!!!
常用函数:

-- create uint32_t_ud with optional value
---@param value? number|integer
---@return uint32_t_ud
function uint32_t(value) end

-- Convert to number
---@return number
function uint32_t_ud:tofloat() end

-- Convert to integer
---@return integer
function uint32_t_ud:toint() end

例子:

local n = 1
n = n+3.14
local m= uint32_t(n):toint()
gcs:send_text(0, string.format("n is %f ", n))
gcs:send_text(0, string.format("m is %f ", m))  
gcs:send_text(0, string.format("type(n) is %s ", type(n)))
gcs:send_text(0, string.format("type(m) is %s ", type(m)))

结果:
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值