I’m sorry to bug you guys with something so trivial but I can not figure it out. This has got to be baby’s first steps when it comes to what people do when they first get a Jetson.
This kind of works.
The Cube can see the Here4. The Jetson can see the cube. I can run “candump can0” on the Jetson and get a stream of data. Here’s some:
can0 184E270A [8] A1 5E 00 02 64 19 10 99
can0 184E270A [8] 00 00 00 00 00 00 00 79
can0 184E270A [8] A1 5E 00 02 64 19 10 9A
can0 184E270A [8] 00 00 00 00 00 00 00 7A
can0 184E270A [8] A1 5E 00 02 64 19 10 9B
can0 184E270A [8] 00 00 00 00 00 00 00 7B
can0 184E270A [8] A1 5E 00 02 64 19 10 9C
can0 184E270A [8] 00 00 00 00 00 00 00 7C
can0 184E270A [8] A1 5E 00 02 64 19 10 9D
can0 184E270A [8] 00 00 00 00 00 00 00 7D
can0 184E270A [8] A1 5E 00 02 64 19 10 9E
can0 184E270A [8] 00 00 00 00 00 00 00 7E
can0 1804390A [4] 00 63 20 D0
can0 184E270A [8] A1 5E 00 02 64 19 10 9F
can0 184E270A [8] 00 00 00 00 00 00 00 7F
can0 18044C0A [2] 00 D5
can0 184E200A [2] 00 D5
can0 184E270A [8] A1 5E 00 02 64 19 10 80
can0 184E270A [8] 00 00 00 00 00 00 00 60
can0 184E270A [8] A1 5E 00 02 64 19 10 81
can0 184E270A [8] 00 00 00 00 00 00 00 61
can0 184E270A [8] A1 5E 00 02 64 19 10 82
can0 184E270A [8] 00 00 00 00 00 00 00 62
can0 1804390A [4] 00 00 00 D1
can0 184E270A [8] A1 5E 00 02 64 19 10 83
can0 184E270A [8] 00 00 00 00 00 00 00 63
can0 184E270A [8] A1 5E 00 02 64 19 10 84
can0 184E270A [8] 00 00 00 00 00 00 00 64
can0 184E270A [8] A1 5E 00 02 64 19 10 85
can0 184E270A [8] 00 00 00 00 00 00 00 65
can0 184E270A [8] A1 5E 00 02 64 19 10 86
can0 184E270A [8] 00 00 00 00 00 00 00 66
can0 1804390A [4] 00 63 20 D2
can0 184E270A [8] A1 5E 00 02 64 19 10 87
can0 184E270A [8] 00 00 00 00 00 00 00 67
can0 184E270A [8] A1 5E 00 02 64 19 10 88
can0 184E270A [8] 00 00 00 00 00 00 00 68
can0 1F01550A [8] 80 01 00 00 00 00 00 DF
can0 104E2D0A [2] 00 DB
can0 184E270A [8] A1 5E 00 02 64 19 10 89
can0 184E270A [8] 00 00 00 00 00 00 00 69
can0 18044C0A [2] 00 D6
can0 184E200A [2] 00 D6
can0 184E270A [8] A1 5E 00 02 64 19 10 8A
can0 184E270A [8] 00 00 00 00 00 00 00 6A
can0 1804390A [4] 00 00 00 D3
can0 184E270A [8] A1 5E 00 02 64 19 10 8B
can0 184E270A [8] 00 00 00 00 00 00 00 6B
can0 184E270A [8] A1 5E 00 02 64 19 10 8C
can0 184E270A [8] 00 00 00 00 00 00 00 6C
can0 184E270A [8] A1 5E 00 02 64 19 10 8D
can0 184E270A [8] 00 00 00 00 00 00 00 6D
can0 184E270A [8] A1 5E 00 02 64 19 10 8E
can0 184E270A [8] 00 00 00 00 00 00 00 6E
can0 184E270A [8] A1 5E 00 02 64 19 10 8F
can0 184E270A [8] 00 00 00 00 00 00 00 6F
can0 184E270A [8] A1 5E 00 02 64 19 10 90
can0 184E270A [8] 00 00 00 00 00 00 00 70
can0 184E270A [8] A1 5E 00 02 64 19 10 91
can0 184E270A [8] 00 00 00 00 00 00 00 71
can0 1804390A [4] 00 63 20 D4
can0 184E270A [8] A1 5E 00 02 64 19 10 92
can0 184E270A [8] 00 00 00 00 00 00 00 72
can0 184E270A [8] A1 5E 00 02 64 19 10 93
can0 184E270A [8] 00 00 00 00 00 00 00 73
can0 18044C0A [2] 00 D7
can0 184E200A [2] 00 D7
can0 184E270A [8] A1 5E 00 02 64 19 10 94
can0 184E270A [8] 00 00 00 00 00 00 00 74
can0 184E270A [8] A1 5E 00 02 64 19 10 95
can0 184E270A [8] 00 00 00 00 00 00 00 75
can0 1804390A [4] 00 00 00 D5
can0 184E270A [8] A1 5E 00 02 64 19 10 96
can0 184E270A [8] 00 00 00 00 00 00 00 76
The goal here is to generate orthomosaics using WebODM and photos taken from a (not shown) camera rig attached to the Jetson. The CAN connection is supposed to stream GNSS data from the GPS to the Jetson via the Cube.
So far I have:
Verified that setting the parameter CAN_D1_UC_OPTION to any value other than 32 disables the CAN data stream.
Set ARMING_SKIPCHK to -1 in case the Cube needs to be armed to stream GNSS data.
Set EK3_SRC1_YAW to 0 in case not having a good magnetometer signal could cause it to not stream data.
The GPS is sitting in my window right now. It can see 18 satellites and has a 3D dgps fix. If I were only interested in the “data” tab in mission planner, I would think that the Cube is satisfied with its position lock.
Also, I only know how to “Vibe” code. I’m sorry. I’m trying to learn but the python script I’m using is AI generated. It’s possible (or even likely) that this is where the problem actually lies. But the LLM that wrote the program doesn’t see a problem and I do not know where to turn from there but here. I know, “If you couldn’t be bothered to write it, I can’t be bothered to read it.” And that’s perfectly valid criticism. But in case this is where my problem is, here’s the script I’m using:
import socket
import struct
import sys
import math
# DroneCAN v0 Message Type IDs
GNSS_FIX_MESSAGE_TYPE = 20007
AHRS_SOL_MESSAGE_TYPE = 1000
try:
sock = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
sock.bind(("can0",))
print("=========================================================")
print(" 🛰️ MAPPING TELEMETRY LOGGER (WEBODM READY) ")
print("=========================================================")
print("Assembling spatial maps from active CAN buses...\n")
except Exception as e:
print(f"Failed to bind raw SocketCAN socket: {e}")
sys.exit(1)
# Global vehicle state dictionaries
gps_state = {"lat": 0.0, "lon": 0.0, "alt": 0.0, "sats": 0, "fix": "NO FIX"}
imu_state = {"yaw": 0.0, "pitch": 0.0, "roll": 0.0}
def quaternion_to_euler(x, y, z, w):
"""
Converts standard DroneCAN xyzw normalized quaternions into
Aerospace Euler angles (Roll, Pitch, Yaw) in degrees.
"""
# Roll calculation (x-axis rotation)
sinr_cosp = 2 * (w * x + y * z)
cosr_cosp = 1 - 2 * (x * x + y * y)
roll = math.atan2(sinr_cosp, cosr_cosp)
# Pitch calculation (y-axis rotation)
sinp = 2 * (w * y - z * x)
if abs(sinp) >= 1:
pitch = math.copysign(math.pi / 2, sinp) # Use 90 degrees if out of bounds
else:
pitch = math.asin(sinp)
# Yaw calculation (z-axis rotation)
siny_cosp = 2 * (w * z + x * y)
cosy_cosp = 1 - 2 * (y * y + z * z)
yaw = math.atan2(siny_cosp, cosy_cosp)
# Convert radians to degrees
return math.degrees(roll), math.degrees(pitch), math.degrees(yaw)
try:
while True:
can_bytes = sock.recv(16)
if len(can_bytes) < 16:
continue
can_id, dlc, raw_payload = struct.unpack("<IB3x8s", can_bytes)
clean_id = can_id & socket.CAN_EFF_MASK
# Deconstruct DroneCAN Bitmask to extract fields
message_type_id = (clean_id >> 8) & 0xFFFF
# --------------------------------------------------------------------
# HOOK 1: PARSE GNSS DATA (Message 20007)
# --------------------------------------------------------------------
if message_type_id == GNSS_FIX_MESSAGE_TYPE:
# For simplicity, extract non-fragmented positions directly
# or map assembled arrays from your multi-frame buffer logic
if dlc >= 7:
# (Assuming coordinate buffer extraction loop updates state)
pass
# --------------------------------------------------------------------
# HOOK 2: PARSE IMU ATTITUDE DATA (Message 1000)
# --------------------------------------------------------------------
elif message_type_id == AHRS_SOL_MESSAGE_TYPE:
if len(raw_payload) >= 8:
try:
import numpy as np
# Unpack the 4 raw uint16 quaternion channels
q_raw = struct.unpack("<HHHH", raw_payload[:8])
# Map each unique index channel directly into its float16 view
qx = np.array([q_raw[0]], dtype=np.uint16).view(np.float16)[0]
qy = np.array([q_raw[1]], dtype=np.uint16).view(np.float16)[0]
qz = np.array([q_raw[2]], dtype=np.uint16).view(np.float16)[0]
qw = np.array([q_raw[3]], dtype=np.uint16).view(np.float16)[0]
# Run the mathematical conversion to Euler degrees
roll, pitch, yaw = quaternion_to_euler(qx, qy, qz, qw)
# Update active vehicle states dynamically
imu_state["roll"] = roll
imu_state["pitch"] = pitch
imu_state["yaw"] = yaw
except Exception as e:
pass
# --------------------------------------------------------------------
# APPLICATION LEVEL: STREAM UNIFIED METRICS TO SCREEN
# --------------------------------------------------------------------
# In practice, you trigger this print line inside your actual camera shutter callback routine.
# This mirrors the exact formatting blueprint WebODM expects inside geo.txt.
sys.stdout.write(
f"\r📝 [GEO_READY] Lon: {gps_state['lon']:.7f}° | Lat: {gps_state['lat']:.7f}° | "
f"Alt: {gps_state['alt']:.2f}m | Yaw: {imu_state['yaw']:.1f}° | "
f"Pitch: {imu_state['pitch']:.1f}° | Roll: {imu_state['roll']:.1f}°"
)
sys.stdout.flush()
except KeyboardInterrupt:
print("\nExiting Telemetry Logging Module.")
sock.close()
But all it does is sit there saying:
[GEO_READY] Lon: 0.0000000° | Lat: 0.0000000° | Alt: 0.00m | Yaw: 0.0° | Pitch: 0.0° | Roll: 0.0°
TL;DR: CAN is streaming something, but it’s not what I want. How do I make it send GNSS data?
