Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Checks: >

WarningsAsErrors: '*'

HeaderFilterRegex: 'include/.*\.h$'
HeaderFilterRegex: 'mujoco_drones/include/.*\.h$'

Copilot AI Mar 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HeaderFilterRegex is now hard-coded to mujoco_drones/include/..., which will stop clang-tidy from analyzing project headers when the repo is checked out into a differently named directory (common in local builds and some CI setups). Prefer a path-agnostic regex (e.g., matching /include/ anywhere) and exclude external headers via a negative match instead of baking in the repo directory name.

Suggested change
HeaderFilterRegex: 'mujoco_drones/include/.*\.h$'
HeaderFilterRegex: '^(?!.*(/third_party/|/external/|/build/)).*/include/.*\.h$'

Copilot uses AI. Check for mistakes.

CheckOptions:
- key: readability-braces-around-statements.ShortStatementLines
Expand Down
4 changes: 3 additions & 1 deletion include/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ void ctrl_update(sim_t *sim);
void quat_to_euler(const double q[4], double *roll, double *pitch, double *yaw);

static inline double clampd(double v, double lo, double hi) {
return v < lo ? lo : (v > hi ? hi : v);
if (v < lo) return lo;
if (v > hi) return hi;
return v;
}

#endif
8 changes: 5 additions & 3 deletions include/foxglove/foxglove.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
#include <stdbool.h>
#include <stdint.h>

#define FG_DEFAULT_PORT 8765
#define FG_MAX_CLIENTS 4
#define FG_MAX_CHANNELS 16
enum {
FG_DEFAULT_PORT = 8765,
FG_MAX_CLIENTS = 4,
FG_MAX_CHANNELS = 16,
};

typedef struct foxglove_bridge foxglove_bridge_t;

Expand Down
2 changes: 1 addition & 1 deletion include/foxglove/proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ typedef struct {
uint32_t channel_id;
} fg_subscription_t;

#define FG_MAX_SUBS 32
enum { FG_MAX_SUBS = 32 };

typedef struct {
int fd;
Expand Down
12 changes: 7 additions & 5 deletions include/foxglove/ws.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
#include <stdint.h>
#include <sys/types.h>

#define WS_OP_TEXT 0x1
#define WS_OP_BIN 0x2
#define WS_OP_CLOSE 0x8
#define WS_OP_PING 0x9
#define WS_OP_PONG 0xA
enum {
WS_OP_TEXT = 0x1,
WS_OP_BIN = 0x2,
WS_OP_CLOSE = 0x8,
WS_OP_PING = 0x9,
WS_OP_PONG = 0xA,
};

int ws_send_frame(int fd, uint8_t opcode, const void *data, size_t len);
ssize_t ws_recv_frame(int fd, uint8_t *buf, size_t buf_sz, uint8_t *opcode);
Expand Down
28 changes: 20 additions & 8 deletions include/sensors/noise.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,34 @@
#define MUJOCO_DRONES_NOISE_H

#include <math.h>
#include <stdlib.h>
#include <stdint.h>

static inline void noise_seed(uint64_t seed) {
srand48((long)seed);
typedef struct {
uint64_t state;
} noise_rng_t;

static inline void noise_seed(noise_rng_t *rng, uint64_t seed) {
rng->state = seed;
}

static inline double noise_uniform(noise_rng_t *rng) {
uint64_t z = (rng->state += 0x9e3779b97f4a7c15ULL);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
z ^= z >> 31;
return (double)(z >> 11) * 0x1.0p-53;
}

static inline double noise_gaussian(double stddev) {
double u1 = drand48();
double u2 = drand48();
static inline double noise_gaussian(noise_rng_t *rng, double stddev) {
double u1 = noise_uniform(rng);
double u2 = noise_uniform(rng);
if (u1 < 1e-15) u1 = 1e-15;
return stddev * sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2);
}

static inline double noise_random_walk(double *state, double stddev, double dt) {
*state += noise_gaussian(stddev * sqrt(dt));
static inline double noise_random_walk(noise_rng_t *rng, double *state,
double stddev, double dt) {
*state += noise_gaussian(rng, stddev * sqrt(dt));
return *state;
}

Expand Down
2 changes: 1 addition & 1 deletion include/sensors/sensor_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ typedef struct {
double altitude_m;
} sensor_baro_t;

#define LIDAR_MAX_RAYS 360
enum { LIDAR_MAX_RAYS = 360 };

typedef struct {
sensor_header_t header;
Expand Down
3 changes: 3 additions & 0 deletions include/sensors/sensors.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <stdint.h>

#include "sensors/sensor_types.h"
#include "sensors/noise.h"
#include "transport/transport.h"
#include "setpoint.h"

Expand Down Expand Up @@ -98,6 +99,8 @@ typedef struct {

double gyro_bias[3];
double accel_bias[3];

noise_rng_t rng;
} sensor_mgr_t;

sensor_config_t sensor_default_config(void);
Expand Down
2 changes: 1 addition & 1 deletion include/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#define MOTOR_KF 8.54858e-6
#define MAX_OMEGA 838.0
#define MAX_THRUST (MOTOR_KF * MAX_OMEGA * MAX_OMEGA)
#define NUM_ROTORS 4
enum { NUM_ROTORS = 4 };

typedef struct {
double kp_z, kd_z, ki_z;
Expand Down
Empty file modified run.sh
100644 → 100755
Empty file.
20 changes: 10 additions & 10 deletions src/foxglove/base64.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ static const char b64_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

size_t fg_base64_encode(const uint8_t *in, size_t len, char *out) {
size_t o = 0;
size_t pos = 0;
for (size_t i = 0; i < len; i += 3) {
uint32_t v = (uint32_t)in[i] << 16;
if (i + 1 < len) v |= (uint32_t)in[i+1] << 8;
if (i + 2 < len) v |= (uint32_t)in[i+2];
uint32_t val = (uint32_t)in[i] << 16;
if (i + 1 < len) val |= (uint32_t)in[i+1] << 8;
if (i + 2 < len) val |= (uint32_t)in[i+2];

out[o++] = b64_table[(v >> 18) & 0x3FU];
out[o++] = b64_table[(v >> 12) & 0x3FU];
out[o++] = (char)((i + 1 < len) ? b64_table[(v >> 6) & 0x3FU] : '=');
out[o++] = (char)((i + 2 < len) ? b64_table[v & 0x3FU] : '=');
out[pos++] = b64_table[(val >> 18) & 0x3FU];
out[pos++] = b64_table[(val >> 12) & 0x3FU];
out[pos++] = (char)((i + 1 < len) ? b64_table[(val >> 6) & 0x3FU] : '=');
out[pos++] = (char)((i + 2 < len) ? b64_table[val & 0x3FU] : '=');
}
out[o] = '\0';
return o;
out[pos] = '\0';
return pos;
}
144 changes: 78 additions & 66 deletions src/foxglove/bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,80 @@ static const struct {
};
#define NUM_DRONE_TOPICS (int)(sizeof(DRONE_TOPICS) / sizeof(DRONE_TOPICS[0]))

static void bridge_accept_connection(foxglove_bridge_t *br) {
int cfd = accept(br->listen_fd, NULL, NULL);
if (cfd < 0) return;

int slot = -1;
for (int i = 0; i < FG_MAX_CLIENTS; i++) {
if (!br->clients[i].alive) { slot = i; break; }
}

if (slot < 0 || ws_accept_handshake(cfd) != 0) {
close(cfd);
return;
}

br->clients[slot].fd = cfd;
br->clients[slot].alive = true;
br->clients[slot].num_subs = 0;

fg_send_server_info(cfd);
fg_send_advertise(cfd, br->channels, br->num_channels);
fprintf(stderr, "[foxglove] client connected (slot %d)\n", slot);
}

static void bridge_handle_client_data(foxglove_bridge_t *br, int ci,
uint8_t *recv_buf, size_t buf_sz) {
uint8_t opcode = 0;
ssize_t plen = ws_recv_frame(br->clients[ci].fd,
recv_buf, buf_sz, &opcode);

if (plen < 0 || opcode == WS_OP_CLOSE) {
fprintf(stderr, "[foxglove] client disconnected (slot %d)\n", ci);
fg_close_client(&br->clients[ci]);
} else if (opcode == WS_OP_TEXT && plen > 0) {
fg_handle_client_message(&br->clients[ci],
(const char *)recv_buf, (size_t)plen);
} else if (opcode == WS_OP_PING) {
ws_send_frame(br->clients[ci].fd, WS_OP_PONG,
recv_buf, (size_t)plen);
}
}

static void bridge_broadcast_channel(foxglove_bridge_t *br, int ch,
uint8_t *msg_buf, size_t msg_buf_sz,
char *json_buf, size_t json_buf_sz) {
size_t out_len = 0;
int rc = transport_read_next(&br->subs[ch],
msg_buf, msg_buf_sz, &out_len);
if (rc != 0 || out_len == 0) return;

int jlen = fg_serializers[ch](msg_buf, json_buf, json_buf_sz);
if (jlen <= 0 || jlen >= (int)json_buf_sz) return;

uint64_t ts = 0;
if (out_len >= sizeof(sensor_header_t)) {
const sensor_header_t *hdr = (const sensor_header_t *)msg_buf;
ts = hdr->timestamp_ns;
}

uint32_t ch_id = br->channels[ch].id;
for (int ci = 0; ci < FG_MAX_CLIENTS; ci++) {
if (!br->clients[ci].alive) continue;

const fg_subscription_t *sub = fg_find_sub(&br->clients[ci], ch_id);
if (!sub) continue;

if (fg_send_message(br->clients[ci].fd, sub->sub_id,
ts, json_buf, (size_t)jlen) != 0) {
fprintf(stderr, "[foxglove] send failed, dropping client %d\n",
ci);
fg_close_client(&br->clients[ci]);
}
}
}

static void *bridge_thread(void *arg) {
foxglove_bridge_t *br = arg;

Expand All @@ -72,83 +146,21 @@ static void *bridge_thread(void *arg) {
int ready = poll(pfds, (nfds_t)nfds, 10);

if (ready > 0 && (pfds[0].revents & POLLIN)) {
int cfd = accept(br->listen_fd, NULL, NULL);
if (cfd >= 0) {
int slot = -1;
for (int i = 0; i < FG_MAX_CLIENTS; i++) {
if (!br->clients[i].alive) { slot = i; break; }
}

if (slot < 0 || ws_accept_handshake(cfd) != 0) {
close(cfd);
} else {
br->clients[slot].fd = cfd;
br->clients[slot].alive = true;
br->clients[slot].num_subs = 0;

fg_send_server_info(cfd);
fg_send_advertise(cfd, br->channels, br->num_channels);

fprintf(stderr, "[foxglove] client connected (slot %d)\n",
slot);
}
}
bridge_accept_connection(br);
}

int pfd_idx = 1;
for (int i = 0; i < FG_MAX_CLIENTS; i++) {
if (!br->clients[i].alive) continue;

if (pfd_idx < nfds && (pfds[pfd_idx].revents & POLLIN)) {
uint8_t opcode = 0;
ssize_t plen = ws_recv_frame(br->clients[i].fd,
recv_buf, sizeof(recv_buf),
&opcode);
if (plen < 0 || opcode == WS_OP_CLOSE) {
fprintf(stderr, "[foxglove] client disconnected (slot %d)\n",
i);
fg_close_client(&br->clients[i]);
} else if (opcode == WS_OP_TEXT && plen > 0) {
fg_handle_client_message(&br->clients[i],
(const char *)recv_buf, (size_t)plen);
} else if (opcode == WS_OP_PING) {
ws_send_frame(br->clients[i].fd, WS_OP_PONG,
recv_buf, (size_t)plen);
}
bridge_handle_client_data(br, i, recv_buf, sizeof(recv_buf));
}
pfd_idx++;
}

for (int ch = 0; ch < br->num_channels; ch++) {
size_t out_len = 0;
int rc = transport_read_next(&br->subs[ch],
msg_buf, sizeof(msg_buf),
&out_len);
if (rc != 0 || out_len == 0) continue;

int jlen = fg_serializers[ch](msg_buf, json_buf, sizeof(json_buf));
if (jlen <= 0 || jlen >= (int)sizeof(json_buf)) continue;

uint64_t ts = 0;
if (out_len >= sizeof(sensor_header_t)) {
const sensor_header_t *hdr = (const sensor_header_t *)msg_buf;
ts = hdr->timestamp_ns;
}

uint32_t ch_id = br->channels[ch].id;
for (int ci = 0; ci < FG_MAX_CLIENTS; ci++) {
if (!br->clients[ci].alive) continue;

const fg_subscription_t *sub = fg_find_sub(&br->clients[ci], ch_id);
if (!sub) continue;

if (fg_send_message(br->clients[ci].fd, sub->sub_id,
ts, json_buf, (size_t)jlen) != 0) {
fprintf(stderr, "[foxglove] send failed, dropping "
"client %d\n", ci);
fg_close_client(&br->clients[ci]);
}
}
bridge_broadcast_channel(br, ch, msg_buf, sizeof(msg_buf),
json_buf, sizeof(json_buf));
}
}

Expand Down
30 changes: 15 additions & 15 deletions src/foxglove/proto.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ int fg_send_message(int fd, uint32_t sub_id, uint64_t timestamp_ns,
}

uint32_t fg_parse_uint(const char *s, const char *key) {
const char *p = strstr(s, key);
if (!p) return 0;
p += strlen(key);
while (*p && (*p == ':' || *p == ' ' || *p == '"')) p++;
return (uint32_t)strtoul(p, NULL, 10);
const char *pos = strstr(s, key);
if (!pos) return 0;
pos += strlen(key);
while (*pos && (*pos == ':' || *pos == ' ' || *pos == '"')) pos++;
return (uint32_t)strtoul(pos, NULL, 10);
}

void fg_handle_client_message(fg_client_t *client, const char *msg,
Expand All @@ -98,23 +98,23 @@ void fg_handle_client_message(fg_client_t *client, const char *msg,
client->num_subs++;
}
} else if (strstr(buf, "\"unsubscribe\"")) {
const char *p = strstr(buf, "\"subscriptionIds\"");
if (p) {
p = strchr(p, '[');
if (p) {
p++;
while (*p && *p != ']') {
const char *pos = strstr(buf, "\"subscriptionIds\"");
if (pos) {
pos = strchr(pos, '[');
if (pos) {
pos++;
while (*pos && *pos != ']') {
char *end = NULL;
uint32_t sub_id = (uint32_t)strtoul(p, &end, 10);
if (end == p) break;
uint32_t sub_id = (uint32_t)strtoul(pos, &end, 10);
if (end == pos) break;
for (int i = 0; i < client->num_subs; i++) {
if (client->subs[i].sub_id == sub_id) {
client->subs[i] = client->subs[--client->num_subs];
break;
}
}
p = end;
while (*p == ',' || *p == ' ') p++;
pos = end;
while (*pos == ',' || *pos == ' ') pos++;
}
}
}
Expand Down
Loading
Loading