|
| 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