AP_Scripting: Add lua scripts for Rockblock modem

This commit is contained in:
Stephen Dade 2023-01-24 16:47:01 +11:00 committed by Peter Barker
parent 0d8fedd2d5
commit d4bb4e4526
2 changed files with 855 additions and 0 deletions

View File

@ -0,0 +1,814 @@
--[[ Lua script to send a recieve very basic MAVLink telemetry over a
Rockblock SBD satellite modem
Requires https://github.com/stephendade/rockblock2mav at the GCS end
Setup:
This script requires 1 serial port:
A "Script" to connect the RockBlock modem
Usage:
Use the MAVLink High Latency Control ("link hl on|off" in MAVProxy) to control
whether to send or not (or use "force_hl_enable")
Use the RCK_DEBUG param to view debugging statustexts at the GCS
Use the RCK_FORCEHL param to force-enable high latency mode, instead of enabling from GCS
Caveats:
This will *only* send HIGH_LATENCY2 packets via the SBD modem. No heartbeats,
no command acknowledgements, no statustexts, no parameters, etc
A single HIGH_LATENCY2 packet will be send every RCK_PERIOD seconds
MAVLink 1 will be used, as it's slightly more efficient (50 vs 52 bytes for a HL2 message)
Any incoming packets on the first mailbox check will be ignored (as these may be from a long time in the past)
Only 1 command can be sent at a time from the GCS. Any subsequent commands will overwrite the previous command
The param SCR_VM_I_COUNT may need to be increased in some circumstances
Written by Stephen Dade (stephen_dade@hotmail.com)
]]--
local PARAM_TABLE_KEY = 10
local PARAM_TABLE_PREFIX = "RCK_"
local port = serial:find_serial(0)
if not port or baud == 0 then
gcs:send_text(0, "Rockblock: No Scripting Serial Port")
return
end
port:begin(19200)
port:set_flow_control(0)
-- bind a parameter to a variable
function bind_param(name)
local p = Parameter()
assert(p:init(name), string.format('could not find %s parameter', name))
return p
end
-- add a parameter and bind it to a variable
function bind_add_param(name, idx, default_value)
assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format('could not add param %s', name))
return bind_param(PARAM_TABLE_PREFIX .. name)
end
-- setup RCK specific parameters
assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 4), 'could not add param table')
--[[
// @Param: RCK_FORCEHL
// @DisplayName: Force enable High Latency mode
// @Description: Automatically enables High Latency mode if not already enabled
// @Values: 0:Disabled,1:Enabled
// @User: Standard
--]]
RCK_FORCEHL = bind_add_param('FORCEHL', 1, 0)
--[[
// @Param: RCK_PERIOD
// @DisplayName: Update rate
// @Description: When in High Latency mode, send Rockblock updates every N seconds
// @Range: 0 600
// @Units: s
// @User: Standard
--]]
RCK_PERIOD = bind_add_param('PERIOD', 2, 30)
--[[
// @Param: RCK_DEBUG
// @DisplayName: Display Rockblock debugging text
// @Description: Sends Rockblock debug text to GCS via statustexts
// @Values: 0:Disabled,1:Enabled
// @User: Standard
--]]
RCK_DEBUG = bind_add_param('DEBUG', 3, 0)
--[[
// @Param: RCK_ENABLE
// @DisplayName: Enable Message transmission
// @Description: Enables the Rockblock sending and recieving
// @Values: 0:Disabled,1:Enabled
// @User: Standard
--]]
RCK_ENABLE = bind_add_param('ENABLE', 4, 1)
--[[
Lua Object for decoding and encoding MAVLink (V1 only) messages
--]]
local function MAVLinkProcessor()
-- public fields
local self = {
-- define MAVLink message id's
COMMAND_LONG = 76,
COMMAND_INT = 75,
HIGH_LATENCY2 = 235,
MISSION_ITEM_INT = 73
}
-- private fields
local _mavbuffer = ""
local _mavresult = {}
local _payload_len = 0
local _mavdecodestate = 0 -- 0=looking for marker, 1=getting header,2=getting payload,3=getting crc
PROTOCOL_MARKER_V1 = 0xFE
HEADER_LEN_V1 = 6
local _txseqid = 0
-- AUTOGEN from MAVLink generator
_crc_extra = {}
_crc_extra[75] = 0x9e
_crc_extra[76] = 0x98
_crc_extra[235] = 0xb3
_crc_extra[73] = 0x26
_messages = {}
_messages[75] = { -- COMMAND_INT
{"param1", "<f"}, {"param2", "<f"}, {"param3", "<f"}, {"param4", "<f"},
{"x", "<i4"}, {"y", "<i4"}, {"z", "<f"}, {"command", "<I2"},
{"target_system", "<B"}, {"target_component", "<B"}, {"frame", "<B"},
{"current", "<B"}, {"autocontinue", "<B"}
}
_messages[76] = { -- COMMAND_LONG
{"param1", "<f"}, {"param2", "<f"}, {"param3", "<f"}, {"param4", "<f"},
{"param5", "<f"}, {"param6", "<f"}, {"param7", "<f"},
{"command", "<I2"}, {"target_system", "<B"}, {"target_component", "<B"},
{"confirmation", "<B"}
}
_messages[235] = { -- HIGH_LATENCY2
{"timestamp", "<I4"}, {"latitude", "<i4"}, {"longitude", "<i4"},
{"custom_mode", "<I2"}, {"altitude", "<i2"}, {"target_altitude", "<i2"},
{"target_distance", "<I2"}, {"wp_num", "<I2"}, {"failure_flags", "<I2"},
{"type", "<B"}, {"autopilot", "<B"}, {"heading", "<B"},
{"target_heading", "<B"}, {"throttle", "<B"}, {"airspeed", "<B"},
{"airspeed_sp", "<B"}, {"groundspeed", "<B"}, {"windspeed", "<B"},
{"wind_heading", "<B"}, {"eph", "<B"}, {"epv", "<B"},
{"temperature_air", "<b"}, {"climb_rate", "<b"}, {"battery", "<b"},
{"custom0", "<B"}, -- should be <b (int8), but we're hacking this into a uint8 instead
{"custom1", "<b"}, {"custom2", "<b"}
}
_messages[73] = { -- MISSION_ITEM_INT
{"param1", "<f"}, {"param2", "<f"}, {"param3", "<f"}, {"param4", "<f"},
{"x", "<i4"}, {"y", "<i4"}, {"z", "<f"}, {"seq", "<I2"},
{"command", "<I2"}, {"target_system", "<B"}, {"target_component", "<B"},
{"frame", "<B"}, {"current", "<B"}, {"autocontinue", "<B"}
}
function self.getSeqID() return _txseqid end
function self.generateCRC(buffer)
-- generate the x25crc for a given buffer. Make sure to include crc_extra!
local crc = 0xFFFF
for i = 1, #buffer do
local tmp = string.byte(buffer, i, i) ~ (crc & 0xFF)
tmp = (tmp ~ (tmp << 4)) & 0xFF
crc = (crc >> 8) ~ (tmp << 8) ~ (tmp << 3) ~ (tmp >> 4)
crc = crc & 0xFFFF
end
return string.pack("<H", crc)
end
function self.parseMAVLink(byte)
-- parse a new byte and see if we've got MAVLink message
-- returns true if a packet was decoded, false otherwise
_mavbuffer = _mavbuffer .. string.char(byte)
-- parse buffer to find MAVLink packets
if #_mavbuffer == 1 and string.byte(_mavbuffer, 1) == PROTOCOL_MARKER_V1 and
_mavdecodestate == 0 then
-- we have a packet start
_mavdecodestate = 1
return
end
-- if we have a full header, try parsing
if #_mavbuffer == HEADER_LEN_V1 and _mavdecodestate == 1 then
local read_marker = 1
_, read_marker = string.unpack("<B", _mavbuffer, read_marker)
_payload_len, read_marker = string.unpack("<B", _mavbuffer,
read_marker) -- payload is always the second byte
-- fetch seq/sysid/compid
_mavresult.seq, _mavresult.sysid, _mavresult.compid, read_marker =
string.unpack("<BBB", _mavbuffer, read_marker)
-- fetch the message id
_mavresult.msgid, _ = string.unpack("<B", _mavbuffer, read_marker)
_mavdecodestate = 2
return
end
-- get payload
if _mavdecodestate == 2 and #_mavbuffer ==
(_payload_len + HEADER_LEN_V1) then
_mavdecodestate = 3
_mavresult.payload = string.sub(_mavbuffer, HEADER_LEN_V1 + 1)
return
end
-- get crc, then process if CRC ok
if _mavdecodestate == 3 and #_mavbuffer ==
(_payload_len + HEADER_LEN_V1 + 2) then
_mavdecodestate = 0
_mavresult.crc = string.sub(_mavbuffer, -2, -1)
local message_map = _messages[_mavresult.msgid]
if not message_map then
-- we don't know how to decode this message, bail on it
_mavbuffer = ""
return true
end
-- check CRC, if message defined
local crc_extra_msg = _crc_extra[_mavresult.msgid]
if crc_extra_msg ~= nil then
local calccrc = self.generateCRC(
string.sub(_mavbuffer, 2, -3) ..
string.char(crc_extra_msg))
if _mavresult.crc ~= calccrc then
gcs:send_text(3,
"Rockblock: Bad Mavlink CRC: " ..
self.bytesToString(_mavbuffer, -2, -1) ..
", " .. self.bytesToString(calccrc, 1, 2))
_mavbuffer = ""
return
end
end
-- map all the fields out
local offset = 1
for _, v in ipairs(message_map) do
if v[3] then
_mavresult[v[1]] = {}
for j = 1, v[3] do
_mavresult[v[1]][j], offset = string.unpack(v[2],
_mavresult.payload,
offset)
end
else
_mavresult[v[1]], offset = string.unpack(v[2],
_mavresult.payload,
offset)
end
end
-- only process COMMAND_LONG and COMMAND_INT and MISSION_ITEM_INT messages
if _mavresult.msgid == self.MISSION_ITEM_INT then
-- goto somewhere (guided mode target)
if _mavresult.command == 16 then -- MAV_CMD_NAV_WAYPOINT
local loc = Location()
loc:lat(_mavresult.x)
loc:lng(_mavresult.y)
loc:alt(_mavresult.z * 100)
if _mavresult.frame == 10 then -- MAV_FRAME_GLOBAL_TERRAIN_ALT
loc:terrain_alt(true)
elseif _mavresult.frame == 3 then -- MAV_FRAME_GLOBAL_RELATIVE_ALT
loc:relative_alt(true)
end
vehicle:set_target_location(loc)
end
elseif _mavresult.msgid == self.COMMAND_LONG or _mavresult.msgid ==
self.COMMAND_INT then
if _mavresult.command == 400 then -- MAV_CMD_COMPONENT_ARM_DISARM
if _mavresult.param1 == 1 then
arming:arm()
elseif _mavresult.param1 == 0 then
arming:disarm()
end
elseif _mavresult.command == 176 then -- MAV_CMD_DO_SET_MODE
vehicle:set_mode(_mavresult.param2)
elseif _mavresult.command == 20 then -- MAV_CMD_NAV_RETURN_TO_LAUNCH (Mode RTL) may vary depending on frame
if FWVersion:type() == 2 then -- copter
vehicle:set_mode(6)
elseif FWVersion:type() == 3 then -- plane
vehicle:set_mode(11)
elseif FWVersion:type() == 1 then -- rover
vehicle:set_mode(11)
end
elseif _mavresult.command == 21 then -- MAV_CMD_NAV_LAND (Mode LAND) may vary depending on frame
if FWVersion:type() == 2 then -- copter
vehicle:set_mode(9)
elseif FWVersion:type() == 12 then -- blimp
vehicle:set_mode(0)
end
elseif _mavresult.command == 22 then -- MAV_CMD_NAV_TAKEOFF
vehicle:start_takeoff(_mavresult.param7)
elseif _mavresult.command == 84 then -- MAV_CMD_NAV_VTOL_TAKEOFF
vehicle:start_takeoff(_mavresult.param7)
elseif _mavresult.command == 85 then -- MAV_CMD_NAV_VTOL_LAND (Mode QLAND)
vehicle:set_mode(20)
elseif _mavresult.command == 300 then -- MAV_CMD_MISSION_START --mode auto and then start mission
if FWVersion:type() == 2 then -- copter
vehicle:set_mode(3)
elseif FWVersion:type() == 3 then -- plane
vehicle:set_mode(10)
elseif FWVersion:type() == 1 then -- rover
vehicle:set_mode(10)
elseif FWVersion:type() == 7 then -- sub
vehicle:set_mode(3)
end
elseif _mavresult.command == 2600 then -- MAV_CMD_CONTROL_HIGH_LATENCY
if _mavresult.param1 == 1 then
gcs:enable_high_latency_connections(true)
else
gcs:enable_high_latency_connections(false)
end
end
end
_mavbuffer = ""
return true
end
-- packet too big ... start again
if #_mavbuffer > 263 then _mavbuffer = "" end
return false
end
function self.bytesToString(buf, start, stop)
local ret = ""
for idx = start, stop do
ret = ret .. string.format("0x%x ", buf:byte(idx), 1, -1) .. " "
end
return ret
end
function self.createMAVLink(message, msgid)
-- generate a mavlink message (V1 only)
-- create the payload
local message_map = _messages[msgid]
if not message_map then
-- we don't know how to encode this message, bail on it
gcs:send_text(3, "Rockblock: Unknown MAVLink message " .. msgid)
return nil
end
local packString = "<"
local packedTable = {}
local packedIndex = 1
for i, v in ipairs(message_map) do
if v[3] then
packString = (packString ..
string.rep(string.sub(v[2], 2), v[3]))
for j = 1, v[3] do
packedTable[packedIndex] = message[message_map[i][1]][j]
packedIndex = packedIndex + 1
end
else
packString = (packString .. string.sub(v[2], 2))
packedTable[packedIndex] = message[message_map[i][1]]
packedIndex = packedIndex + 1
end
end
local payload = string.pack(packString, table.unpack(packedTable))
-- create the header. Assume componentid of 1
local header = string.pack('<BBBBBB', PROTOCOL_MARKER_V1, #payload,
_txseqid, param:get('SYSID_THISMAV'), 1,
msgid)
-- generate the CRC
local crc_extra_msg = _crc_extra[msgid]
local crc = self.generateCRC(string.sub(header, 2) .. payload ..
string.char(crc_extra_msg))
-- iterate sequence id
_txseqid = (_txseqid + 1) % 255
return header .. payload .. crc
end
-- return the instance
return self
end
--[[
Lua Object for managing the RockBlock modem
--]]
local function RockblockModem()
-- public fields
local self = {
is_transmitting = false,
first_sucessful_mailbox_check = false,
time_last_tx = millis():tofloat() * 0.001,
last_modem_status_check = 0,
modem_detected = false,
in_read_cycle = false,
rx_len = 0
}
-- private fields
local _AT_query = "AT+CGMM\r"
local _AT_mailbox_check = "AT+SBDIXA\r"
local _AT_load_tx_buffer = "AT+SBDWB="
local _AT_read_rx_buffer = "AT+SBDRB\r"
local _AT_clear_tx_buffer = "AT+SBDD0\r"
local _AT_clear_rx_buffer = "AT+SBDD1\r"
local _modem_history = {}
local _modem_to_send = {}
local _str_recieved = ""
-- Get any incoming data
function self.rxdata(inchar)
read = string.char(inchar)
local maybepkt = nil
if _modem_history[#_modem_history] == 'AT+SBDRB' and self.in_read_cycle == false and self.rx_len > 0 then
-- read buffer may include /r and /n, so need a special cycle to capture all up to the self.rx_len
self.in_read_cycle = true
_str_recieved = _str_recieved .. read
elseif self.in_read_cycle and #_str_recieved == self.rx_len + 3 then
-- get last byte in read cycle
_str_recieved = _str_recieved .. read
self.in_read_cycle = false
self.rx_len = 0
table.insert(_modem_history, _str_recieved)
_str_recieved = ""
if RCK_DEBUG:get() == 1 then
gcs:send_text(3, "Rockblock: Modem rx msg: " ..
self.nicestring(_modem_history[#_modem_history]))
end
elseif (read == '\r' or read == '\n') and not self.in_read_cycle then
if #_str_recieved > 0 then
table.insert(_modem_history, _str_recieved)
if RCK_DEBUG:get() == 1 then
gcs:send_text(3, "Rockblock: Modem response: " ..
self.nicestring(_modem_history[#_modem_history]))
end
maybepkt = self.check_cmd_return()
end
_str_recieved = ""
else
_str_recieved = _str_recieved .. read
end
return maybepkt
end
-- Parse any incoming text from the modem
-- returns any payload data, nil otherwise
function self.check_cmd_return()
-- modem detection (response to AT_query)
if #_modem_history == 3 and _modem_history[1] == 'AT+CGMM' and
_modem_history[3] == 'OK' then
gcs:send_text(3, "Rockblock modem detected - " ..
self.nicestring(_modem_history[2]))
_modem_history = nil
_modem_history = {}
self.modem_detected = true
table.insert(_modem_to_send, _AT_clear_rx_buffer)
table.insert(_modem_to_send, _AT_clear_tx_buffer)
-- enable high latency mode, if desired
if RCK_FORCEHL:get() == 1 then gcs:enable_high_latency_connections(true) end
return nil
end
if self.modem_detected then
-- TX Buffer clear (response to AT_query)
if #_modem_history >= 3 and _modem_history[#_modem_history - 2] ==
'AT+SBDD0' and _modem_history[#_modem_history] == 'OK' then
if RCK_DEBUG:get() == 1 then
gcs:send_text(3, "Rockblock cleared modem TX buffer")
end
_modem_history = nil
_modem_history = {}
return nil
end
-- RX buffer clear (response to AT_query)
if #_modem_history >= 3 and _modem_history[#_modem_history - 2] ==
'AT+SBDD1' and _modem_history[#_modem_history] == 'OK' then
if RCK_DEBUG:get() == 1 then
gcs:send_text(3, "Rockblock cleared modem RX buffer")
end
_modem_history = nil
_modem_history = {}
return nil
end
-- Tx buffer loaded (response to AT command)
if #_modem_history >= 4 and
string.find(_modem_history[#_modem_history - 3], _AT_load_tx_buffer, 1,
true) and _modem_history[#_modem_history] == 'OK' then
if RCK_DEBUG:get() == 1 then
gcs:send_text(3, "Rockblock loaded packet into tx buffer, " ..
tostring(_modem_history[#_modem_history - 3]))
end
if _modem_history[#_modem_history - 1] ~= '0' then
gcs:send_text(3, "Rockblock Error loading packet into buffer")
end
_modem_history = nil
_modem_history = {}
return nil
end
-- Got received data (response to AT command)
if _modem_history[#_modem_history - 2] == 'AT+SBDRB' and
_modem_history[#_modem_history] == 'OK' then
-- Message format is { 2 byte msg length} + {message} + {2 byte checksum}
local totallen = #(_modem_history[#_modem_history - 1])
local len = string.unpack(">i2", _modem_history[#_modem_history - 1])
local msg = string.sub(_modem_history[#_modem_history - 1], 3,
totallen - 2)
local checksumrx = string.sub(_modem_history[#_modem_history - 1],
totallen - 1, totallen)
local highByte, lowByte = self.checksum(msg)
-- check that received message is OK, then send to MAVLink processor
if len ~= #msg then
gcs:send_text(3,
"Rockblock: Bad RX message length " .. tostring(len) ..
" vs actual " .. tostring(#msg))
elseif checksumrx ~= (tostring(highByte) .. tostring(lowByte)) then
gcs:send_text(3,
"Rockblock: Bad RX CRC " .. checksumrx .. " vs " ..
tostring(highByte) .. tostring(lowByte))
else
if RCK_DEBUG:get() == 1 then
gcs:send_text(3, "totmsg=" ..
self.nicestring(
_modem_history[#_modem_history - 1]))
end
end
_modem_history = nil
_modem_history = {}
return msg
end
-- Mailbox check (response to AT command) (can be a fail or success)
if _modem_history[#_modem_history - 2] == 'AT+SBDIXA' and
_modem_history[#_modem_history] == 'OK' then
-- Parse response (comma and : delimited, trimming whitespace)
local statusReponse = {}
for w in _modem_history[#_modem_history - 1]:gmatch("[^,:]+") do
table.insert(statusReponse, w)
end
if #statusReponse == 7 then
if tonumber(statusReponse[2]) < 5 then
gcs:send_text(3, "Rockblock Packet Transmitted successfully")
-- check for rx packet
if tonumber(statusReponse[4]) == 1 and self.first_sucessful_mailbox_check then
self.rx_len = tonumber(statusReponse[6])
gcs:send_text(3, "Rockblock Packet Received of len " .. tostring(self.rx_len))
-- read messages, if not first mailbox check
table.insert(_modem_to_send, _AT_read_rx_buffer)
elseif RCK_DEBUG:get() == 1 then
gcs:send_text(3, "Rockblock: No message to receive")
end
self.first_sucessful_mailbox_check = true
elseif tonumber(statusReponse[2]) == 32 then
gcs:send_text(3, "Rockblock Error: No network service")
else
gcs:send_text(3, "Rockblock Error: Unable to send")
end
end
_modem_history = nil
_modem_history = {}
self.is_transmitting = false
return nil
end
end
end
function self.checksum(bytes)
-- Checksum calculation for SBDWB
-- The checksum is the least significant 2-bytes of the summation of the entire SBD message
local SUM = 0
for idx = 1, #bytes do SUM = SUM + bytes:byte(idx) end
local SUM_H = (SUM & 0xFF << 8) >> 8
local SUM_L = SUM & 0xFF
if RCK_DEBUG:get() == 1 then
gcs:send_text(3, "Rockblock: Modem CRC: " .. string.char(SUM_H) .. " " ..
string.char(SUM_L))
end
return string.char(SUM_H), string.char(SUM_L)
end
function self.nicestring(instr)
-- make any strings printable to GCS (constrain to ASCII range)
local retstr = ""
for i = 1, #instr do
local c = string.byte(instr:sub(i, i))
if c < 0x20 or c > 0x7E then c = 0x5F end
retstr = retstr .. string.char(c)
end
return tostring(retstr)
end
function self.checkmodem()
--- send detect command to modem every 10 sec if not detected
if (millis():tofloat() * 0.001) - self.last_modem_status_check > 10 then
table.insert(_modem_to_send, _AT_query)
self.last_modem_status_check = millis():tofloat() * 0.001
end
end
function self.getnextcommandtosend()
--- get the next string to send to the modem
if #_modem_to_send == 0 then
return nil
end
local ret = _modem_to_send[1]
if RCK_DEBUG:get() == 1 then
gcs:send_text(3, "Rockblock: Sent to modem: " .. self.nicestring(_modem_to_send[1]))
end
table.remove(_modem_to_send, 1)
return ret
end
function self.loadtxbuffer(pkt)
-- Load the transmit buffer of the modem
-- Send as binary data, plus checksum
local highByte, lowByte = self.checksum(pkt)
table.insert(_modem_to_send, _AT_load_tx_buffer .. tostring(#pkt) .. '\r')
table.insert(_modem_to_send, pkt)
table.insert(_modem_to_send, highByte .. lowByte .. "\r")
self.time_last_tx = millis():tofloat() * 0.001
if RCK_ENABLE:get() == 1 then
table.insert(_modem_to_send, _AT_mailbox_check)
self.is_transmitting = true
end
end
-- return the instance
return self
end
-- Transmitted HIGH_LATENCY2 packet
local hl2 = {}
hl2.timestamp = 0
hl2.latitude = 0
hl2.longitude = 0
hl2.custom_mode = 0
hl2.altitude = 0
hl2.target_altitude = 0
hl2.target_distance = 0
hl2.wp_num = 0
hl2.failure_flags = 0
hl2.type = gcs:frame_type()
hl2.autopilot = 3 -- MAV_AUTOPILOT_ARDUPILOTMEGA
hl2.heading = 0
hl2.target_heading = 0
hl2.throttle = 0
hl2.airspeed = 0
hl2.airspeed_sp = 0
hl2.groundspeed = 0
hl2.windspeed = 0
hl2.wind_heading = 0
hl2.eph = 0
hl2.epv = 0
hl2.temperature_air = 0
hl2.climb_rate = 0
hl2.battery = 0
hl2.custom0 = 1 -- MAV_MODE_FLAG_CUSTOM_MODE_ENABLED
hl2.custom1 = 0
hl2.custom2 = 0
function wrap_360(angle)
local res = angle % 360
if res < 0 then res = res + 360 end
return res
end
-- Define the MAVLink processor
local mavlink = MAVLinkProcessor()
-- Define the RockBlock interface
local rockblock = RockblockModem()
function HLSatcom()
-- read in any bytes from rockblock and form into received commands
local n_bytes = port:available()
while n_bytes > 0 do
read = port:read()
pkt = rockblock.rxdata(read)
n_bytes = n_bytes - 1
-- we've got a MAVLink message from the GCS, parse
if pkt ~= nil then
for idx = 1, #pkt do
mavlink.parseMAVLink(pkt:byte(idx))
end
end
end
-- write out commands from send list. one cmd per loop
local cmd = rockblock.getnextcommandtosend()
if cmd ~= nil then
for idx = 1, #cmd do
port:write(cmd:byte(idx))
end
end
--- check if modem is there
if not rockblock.modem_detected then
gcs:send_text(3, "Rockblock: Trying to detect modem")
rockblock.checkmodem()
end
-- send HL2 packet every 30 sec, if not aleady in a mailbox check
if rockblock.modem_detected and gcs:get_high_latency_status() and
(millis():tofloat() * 0.001) - rockblock.time_last_tx > RCK_PERIOD:get() and not rockblock.is_transmitting then
-- update HL2 packet
hl2.timestamp = millis():tofloat()
local position = ahrs:get_location()
local wind = ahrs:wind_estimate()
if position then
hl2.latitude = tonumber(position:lat())
hl2.longitude = tonumber(position:lng())
hl2.altitude = math.floor(tonumber(position:alt()) * 0.01)
end
if wind then
wind_xy = Vector2f()
wind_xy:x(wind:x())
wind_xy:y(wind:y())
hl2.windspeed = math.abs(math.floor(wind_xy:length() * 5))
hl2.wind_heading = math.floor(wrap_360(wind_xy:angle()) / 2)
end
hl2.custom_mode = vehicle:get_mode()
if vehicle:get_wp_distance_m() ~= nil then
hl2.target_distance = math.floor(vehicle:get_wp_distance_m() / 10)
end
if mission:get_current_nav_index() ~= nil then
hl2.wp_num = mission:get_current_nav_index()
end
if vehicle:get_wp_bearing_deg() ~= nil then
hl2.target_heading = math.floor(wrap_360(
vehicle:get_wp_bearing_deg()) /
2)
end
-- failure flags
hl2.failure_flags = 0
if not ahrs:healthy() then
hl2.failure_flags = hl2.failure_flags + 4096 -- HL_FAILURE_FLAG_ESTIMATOR
end
if battery:num_instances() > 0 and not battery:healthy(0) then
hl2.failure_flags = hl2.failure_flags + 128 -- HL_FAILURE_FLAG_BATTERY
end
if gps:num_sensors() > 0 and gps:status(0) <= gps.NO_FIX then
hl2.failure_flags = hl2.failure_flags + 1 -- HL_FAILURE_FLAG_GPS
end
if (FWVersion:type() == 2 or FWVersion:type() == 3) and terrain:status() ==
terrain.TerrainStatusUnhealthy then
-- only for copter and plane
hl2.failure_flags = hl2.failure_flags + 64 -- HL_FAILURE_FLAG_TERRAIN
end
if not rc:has_valid_input() then
hl2.failure_flags = hl2.failure_flags + 256 -- HL_FAILURE_FLAG_RC_RECEIVER
end
hl2.heading = math.floor(wrap_360(math.deg(ahrs:get_yaw())) / 2)
hl2.throttle = math.floor(gcs:get_hud_throttle())
if ahrs:airspeed_estimate() ~= nil then
hl2.airspeed = math.abs(math.floor(ahrs:airspeed_estimate() * 5))
end
-- hl2.airspeed_sp = 0
hl2.groundspeed = math.abs(math.floor(
ahrs:groundspeed_vector():length() * 5))
hl2.temperature_air = math.floor(baro:get_external_temperature())
hl2.battery = battery:capacity_remaining_pct(0)
-- just sending armed state here for simplicity. Flight mode is in the custom_mode field
if arming:is_armed() then
hl2.custom0 = 129 -- MAV_MODE_FLAG_SAFETY_ARMED + MAV_MODE_FLAG_CUSTOM_MODE_ENABLED
else
hl2.custom0 = 1 -- MAV_MODE_FLAG_CUSTOM_MODE_ENABLED
end
local newpkt = mavlink.createMAVLink(hl2, mavlink.HIGH_LATENCY2)
if #newpkt > 50 then
gcs:send_text(3, "Rockblock: Tx packet > 50 bytes: " .. tostring(#newpkt))
end
-- send packet
rockblock.loadtxbuffer(newpkt)
end
end
-- wrapper around HLSatcom(). This calls HLSatcom() and if HLSatcom faults
-- then an error is displayed, but the script is not stopped
function protected_wrapper()
local success, err = pcall(HLSatcom)
if not success then
gcs:send_text(MAV_SEVERITY_ERROR, "Internal Error: " .. err)
-- when we fault we run the HLSatcom function again after 1s, slowing it
-- down a bit so we don't flood the console with errors
return protected_wrapper, 1000
end
return protected_wrapper, math.floor(1000 / 10)
end
-- start running HLSatcom loop
return protected_wrapper()

View File

@ -0,0 +1,41 @@
# RockBlock Lua Script
Lua script to send a recieve very basic MAVLink telemetry over a
Rockblock SBD satellite modem
Requires https://github.com/stephendade/rockblock2mav at the GCS end
Note that this uses MAVLink1 messaging, due to it's smaller message size compared
to MAVLink2.
Messages will only be send or received if High Latency Mode is enabled.
Setup:
This script requires 1 serial port:
- A "Script" port to connect the modem to
Caveats:
- This will *only* send HIGH_LATENCY2 packets via the SBD modem. No heartbeats, no command acknowledgements, no statustexts, no parameters, etc
- A single HIGH_LATENCY2 packet will be sent every RCK_PERIOD sec, in addition to receiving
a single MAVLink packet from the GCS. This is known as a mailbox check.
- Any incoming packets on the first mailbox check will be ignored (as these may be from a long time in the past)
- Only 1 command can be sent per mailbox check from the GCS. Any additional commands will overwrite the previous command
# Parameters
The script adds the following parameters:
## RCK_FORCEHL
Automatically enables High Latency mode if not already enabled
## RCK_PERIOD
When in High Latency mode, send Rockblock updates every RCK_PERIOD seconds
## RCK_DEBUG
Sends Rockblock debug text to GCS via statustexts
## RCK_ENABLE
Enables the modem transmission