Skip to content

Commit d4e5afe

Browse files
committed
AP_Scripting: example of using DroneCAN messaging
1 parent ee265dc commit d4e5afe

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
--[[
2+
example of creating and sending DroneCAN messages
3+
--]]
4+
5+
local MAGNETICFIELDSTRENGTHHIRES_ID = 1043
6+
local MAGNETICFIELDSTRENGTHHIRES_SIGNATURE = uint64_t(0x3053EBE3, 0xD750286F)
7+
8+
local RAWAIRDATA_ID = 1027
9+
local RAWAIRDATA_SIGNATURE = uint64_t(0xC77DF38B, 0xA122F5DA)
10+
11+
local PARAM_GETSET_ID = 11
12+
local PARAM_GETSET_SIGNATURE = uint64_t(0xA7B622F9, 0x39D1A4D5)
13+
14+
local NODESTATUS_ID = 341
15+
local NODESTATUS_SIGNATURE = uint64_t(0x0F0868D0, 0xC1A7C6F1)
16+
17+
local MAV_SEVERITY = {EMERGENCY=0, ALERT=1, CRITICAL=2, ERROR=3, WARNING=4, NOTICE=5, INFO=6, DEBUG=7}
18+
19+
-- a handle we will use for broadcasting
20+
local dc_handle = DroneCAN_Handle(0)
21+
22+
-- a handle for fetching parameters
23+
local param_handle = DroneCAN_Handle(0)
24+
param_handle:data_type(PARAM_GETSET_ID)
25+
param_handle:signature(PARAM_GETSET_SIGNATURE)
26+
27+
--[[
28+
setup subscription to NodeStatus
29+
--]]
30+
local nodestatus_handle = DroneCAN_Handle(0)
31+
nodestatus_handle:data_type(NODESTATUS_ID)
32+
nodestatus_handle:signature(NODESTATUS_SIGNATURE)
33+
nodestatus_handle:subscribe()
34+
35+
--[[
36+
setup subscription to raw air data
37+
--]]
38+
local airspeed_handle = DroneCAN_Handle(0)
39+
airspeed_handle:data_type(RAWAIRDATA_ID)
40+
airspeed_handle:signature(RAWAIRDATA_SIGNATURE)
41+
airspeed_handle:subscribe()
42+
43+
-- table of all nodes
44+
local node_status = {}
45+
46+
47+
--[[
48+
send highres mag using a global handle
49+
--]]
50+
local function send_mag_highres()
51+
dc_handle:signature(MAGNETICFIELDSTRENGTHHIRES_SIGNATURE)
52+
dc_handle:data_type(MAGNETICFIELDSTRENGTHHIRES_ID)
53+
local payload = string.pack("Bfff", 7, 1, 2, 3)
54+
dc_handle:broadcast(payload)
55+
gcs:send_text(MAV_SEVERITY.INFO, "mag highres broadcast done")
56+
end
57+
58+
--[[
59+
send highres mag using a handle that will be closed after being used
60+
--]]
61+
local function send_mag_highres2()
62+
local h = DroneCAN_Handle(0)
63+
h:signature(MAGNETICFIELDSTRENGTHHIRES_SIGNATURE)
64+
h:data_type(MAGNETICFIELDSTRENGTHHIRES_ID)
65+
local payload = string.pack("Bfff", 8, 10, 11, 12)
66+
h:broadcast(payload)
67+
h:close()
68+
gcs:send_text(MAV_SEVERITY.INFO, "mag highres broadcast2 done")
69+
end
70+
71+
--[[
72+
unpack a float16 into a floating point number
73+
--]]
74+
local function unpackFloat16(v16)
75+
-- Extract the sign (bit 15), exponent (bits 10–14) and fraction (bits 0–9)
76+
local sign = (v16 >> 15) & 0x1
77+
local exponent = (v16 >> 10) & 0x1F
78+
local fraction = v16 & 0x3FF
79+
80+
local value
81+
if exponent == 0 then
82+
if fraction == 0 then
83+
-- Zero (positive or negative)
84+
value = 0.0
85+
else
86+
-- Subnormal numbers (exponent = -14, no implicit leading 1)
87+
value = (fraction / 1024.0) * 2.0^-14
88+
end
89+
elseif exponent == 0x1F then
90+
if fraction == 0 then
91+
-- Infinity (positive or negative)
92+
value = math.huge
93+
else
94+
-- NaN (Not a Number)
95+
value = 0/0
96+
end
97+
else
98+
-- Normalized numbers: implicit 1 before the fraction and exponent bias of 15.
99+
value = (1 + fraction / 1024.0) * 2.0^(exponent - 15)
100+
end
101+
102+
-- Apply the sign bit
103+
if sign == 1 then
104+
value = -value
105+
end
106+
107+
return value
108+
end
109+
110+
--[[
111+
check for incoming airspeed broadcast messages
112+
--]]
113+
local function check_airspeed()
114+
local payload, nodeid = airspeed_handle:check_message()
115+
if payload then
116+
return
117+
end
118+
local flags, static_pressure, differential_pressure, static_pressure_sensor_temperature,
119+
differential_pressure_sensor_temperature, static_air_temperature, pitot_temperature = string.unpack("BffHHHH", payload)
120+
if flags then
121+
local temp_C = unpackFloat16(static_air_temperature) - 273.15;
122+
gcs:send_text(MAV_SEVERITY.INFO, string.format("Rawairdata(%u): %f %.2fC",
123+
nodeid, differential_pressure, temp_C))
124+
end
125+
end
126+
127+
--[[
128+
parse a parameter GetSet NumericValue
129+
--]]
130+
local function parse_param_NumericValue(payload, byte_offset)
131+
local vtype = string.unpack("B", payload, byte_offset)
132+
if vtype == 0 then
133+
return nil, byte_offset+1
134+
elseif vtype == 1 then
135+
-- integer (treat as 32 bit for now, actually 64 bit)
136+
return string.unpack("i", payload, byte_offset+1), byte_offset+9
137+
elseif vtype == 2 then
138+
-- float32
139+
return string.unpack("f", payload, byte_offset+1), byte_offset+5
140+
else
141+
return nil
142+
end
143+
end
144+
145+
--[[
146+
parse a parameter GetSet Value
147+
--]]
148+
local function parse_param_Value(payload, byte_offset)
149+
local vtype = string.unpack("B", payload, byte_offset)
150+
if vtype == 0 then
151+
return nil, byte_offset+1
152+
elseif vtype == 1 then
153+
-- integer (treat as 32 bit for now, actually 64 bit)
154+
return string.unpack("i", payload, byte_offset+1), byte_offset+9
155+
elseif vtype == 2 then
156+
-- float32
157+
return string.unpack("f", payload, byte_offset+1), byte_offset+5
158+
elseif vtype == 3 then
159+
-- bool
160+
local v = string.unpack("B", payload, byte_offset+1), byte_offset+2
161+
return v == 1
162+
elseif vtype == 4 then
163+
-- string
164+
local slen = string.unpack("B", payload, byte_offset+1)
165+
return string.sub(payload, byte_offset+2, slen+2), byte_offset+2+slen
166+
else
167+
return nil
168+
end
169+
end
170+
171+
172+
--[[
173+
parse a parameter GetSet reply
174+
--]]
175+
local function parse_param_reply(payload)
176+
local byte_offset = 1
177+
local value, byte_offset = parse_param_Value(payload, byte_offset)
178+
local default_value, byte_offset = parse_param_Value(payload, byte_offset)
179+
local max_value, byte_offset = parse_param_NumericValue(payload, byte_offset)
180+
local min_value, byte_offset = parse_param_NumericValue(payload, byte_offset)
181+
local name = string.sub(payload, byte_offset, #payload)
182+
return name, value, default_value, min_value, max_value
183+
end
184+
185+
local next_param_index = 0
186+
187+
--[[
188+
encode a 16 bit number as a DroneCAN int13
189+
--]]
190+
local function encode_int13(v)
191+
return (v & 0xFF) | (v&0xFF00)<<3
192+
end
193+
194+
local function fetch_param()
195+
local payload, nodeid = param_handle:check_message()
196+
if payload then
197+
local pname, pvalue = parse_param_reply(payload)
198+
if not pname or not pvalue then
199+
gcs:send_text(MAV_SEVERITY.INFO, string.format("param restart loop %u", next_param_index))
200+
next_param_index = 0
201+
else
202+
gcs:send_text(MAV_SEVERITY.INFO, string.format("got param reply from %u idx=%u '%s' : %f", nodeid, next_param_index, pname, pvalue))
203+
next_param_index = next_param_index + 1
204+
end
205+
end
206+
param_handle:request(125, string.pack("H",encode_int13(next_param_index)))
207+
end
208+
209+
--[[
210+
check for new NodeStatus messages
211+
--]]
212+
local function check_node_status()
213+
local payload, nodeid = nodestatus_handle:check_message()
214+
if not payload then
215+
return
216+
end
217+
local uptime_sec, bits, vssc = string.unpack("IBH", payload)
218+
local health = bits&3
219+
local mode = (bits>>2)&7
220+
local sub_mode = (bits>>5)&7
221+
if not node_status[nodeid] then
222+
gcs:send_text(MAV_SEVERITY.INFO, string.format("Found node %u", nodeid))
223+
end
224+
node_status[nodeid] = { uptime_sec=uptime_sec, health=health, mode=mode, sub_mode=sub_mode }
225+
end
226+
227+
local last_low_rate_ms = uint32_t(0)
228+
229+
local function update()
230+
local now = millis()
231+
if now - last_low_rate_ms >= 1000 then
232+
last_low_rate_ms = now
233+
send_mag_highres()
234+
send_mag_highres2()
235+
check_airspeed()
236+
end
237+
check_node_status()
238+
fetch_param()
239+
240+
return update, 10
241+
end
242+
243+
return update, 1000

0 commit comments

Comments
 (0)