Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ webserver
client/dist
camera-daemon/build
survive-server/build
frodo-control/build
frodo-control/build-arm
frodo-control/build-arm-uclibc
141 changes: 139 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ SURVIVE_BUILD_DIR = $(SURVIVE_SERVER_DIR)/build
SURVIVE_BINARY = $(SURVIVE_BUILD_DIR)/survive-server
SURVIVE_CTL_SCRIPT = survive-server-ctl.sh

# Frodo control (FRC-based controller)
CONTROL_DIR = frodo-control
CONTROL_BUILD_DIR_ARM = $(CONTROL_DIR)/build-arm-uclibc
CONTROL_BUILD_DIR_X86 = $(CONTROL_DIR)/build
CONTROL_BINARY_ARM = $(CONTROL_BUILD_DIR_ARM)/frodo-control
CONTROL_BINARY_X86 = $(CONTROL_BUILD_DIR_X86)/frodo-control
CONTROL_TOOLCHAIN = $(CONTROL_DIR)/arm-uclibc-toolchain.cmake
CONTROL_REMOTE_PATH = /data/apps/frodo-control
CONTROL_LOG_PATH = /data/frodo-control.log
CONTROL_CTL_SCRIPT = frodo-control-ctl.sh
CONTROL_CTL_REMOTE = /data/apps/frodo-control-ctl.sh
ARM_SYSROOT_UCLIBC = $(PWD)/arm-sysroot-uclibc
UCLIBC_TOOLCHAIN = $(PWD)/toolchains/armv7-eabihf--uclibc--stable-2024.02-1

# Version information
VERSION := 1.0.0
BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S_UTC')
Expand All @@ -56,7 +70,7 @@ GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "no-git")
# Build flags
LDFLAGS := -X 'main.Version=$(VERSION)' -X 'main.BuildTime=$(BUILD_TIME)' -X 'main.GitHash=$(GIT_HASH)'

.PHONY: all build build-client build-camera build-chrony build-survive-server build-libsurvive deploy deploy-camera deploy-go2rtc deploy-tailscale deploy-chrony start-tailscale stop-tailscale status-tailscale up-tailscale down-tailscale logs-tailscale start-chrony stop-chrony restart-chrony status-chrony sources-chrony clients-chrony logs-chrony start-survive-server stop-survive-server restart-survive-server status-survive-server logs-survive-server update-client update-camera restart restart-camera logs logs-camera tail tail-camera status status-camera stop stop-camera clean clean-camera clean-survive-server help start update reset
.PHONY: all build build-client build-camera build-chrony build-survive-server build-libsurvive build-control deploy deploy-camera deploy-go2rtc deploy-tailscale deploy-chrony deploy-control start-tailscale stop-tailscale status-tailscale up-tailscale down-tailscale logs-tailscale start-chrony stop-chrony restart-chrony status-chrony sources-chrony clients-chrony logs-chrony start-survive-server stop-survive-server restart-survive-server status-survive-server logs-survive-server start-control stop-control restart-control status-control logs-control update-client update-camera restart restart-camera logs logs-camera tail tail-camera status status-camera stop stop-camera clean clean-camera clean-survive-server clean-control help start update reset

# Default target - build both webserver and camera
all: build build-camera
Expand Down Expand Up @@ -435,9 +449,118 @@ clean-survive-server:
rm -rf $(SURVIVE_BUILD_DIR)
@echo "✓ survive-server clean complete"

# ==================== Frodo Control Targets (FRC Controller - Device) ====================

# Build frodo-control for ARM (cross-compilation with static linking)
build-control-arm:
@echo "Building frodo-control for ARM with uClibc toolchain..."
@if [ ! -f "$(CONTROL_TOOLCHAIN)" ]; then \
echo "Error: ARM uClibc toolchain file not found: $(CONTROL_TOOLCHAIN)"; \
exit 1; \
fi
@if [ ! -d "$(ARM_SYSROOT_UCLIBC)" ]; then \
echo "Error: ARM uClibc sysroot not found: $(ARM_SYSROOT_UCLIBC)"; \
echo "Please build ARM dependencies first:"; \
echo " 1. Run: ./build-uclibc-deps.sh (protobuf)"; \
echo " 2. Run: ./build-wpilib-uclibc.sh (WPILib)"; \
exit 1; \
fi
@if [ ! -d "$(UCLIBC_TOOLCHAIN)" ]; then \
echo "Error: uClibc toolchain not found: $(UCLIBC_TOOLCHAIN)"; \
echo "Download from: https://toolchains.bootlin.com/downloads/releases/toolchains/armv7-eabihf/tarballs/"; \
exit 1; \
fi
@mkdir -p $(CONTROL_BUILD_DIR_ARM)
cd $(CONTROL_BUILD_DIR_ARM) && \
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../arm-uclibc-toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release && \
make -j$$(nproc)
@echo "✓ ARM uClibc binary built: $(CONTROL_BINARY_ARM) ($$(du -h $(CONTROL_BINARY_ARM) | cut -f1))"
@echo " Architecture: $$(file $(CONTROL_BINARY_ARM) | cut -d: -f2)"
@echo " Interpreter: $$(readelf -l $(CONTROL_BINARY_ARM) | grep interpreter | sed 's/.*\[\(.*\)\]/\1/')"
@echo " Dynamic deps: $$(readelf -d $(CONTROL_BINARY_ARM) | grep NEEDED | wc -l) (uClibc only)"

# Build frodo-control for x86_64 (local testing)
build-control-x86:
@echo "Building frodo-control for x86_64 (local testing)..."
@mkdir -p $(CONTROL_BUILD_DIR_X86)
cd $(CONTROL_BUILD_DIR_X86) && \
cmake .. -DCMAKE_BUILD_TYPE=Release && \
make -j$$(nproc)
@echo "✓ x86_64 binary built: $(CONTROL_BINARY_X86)"

# Alias for backward compatibility and quick ARM build
build-control: build-control-arm

# Deploy frodo-control to device
deploy-control: build-control-arm
@echo "Connecting to device at $(DEVICE_IP)..."
adb connect $(DEVICE_IP)
@echo "Pushing frodo-control ARM binary to device..."
adb push $(CONTROL_BINARY_ARM) $(CONTROL_REMOTE_PATH)
adb shell "chmod +x $(CONTROL_REMOTE_PATH)"
@echo "Pushing control script..."
adb push $(CONTROL_CTL_SCRIPT) $(CONTROL_CTL_REMOTE)
adb shell "chmod +x $(CONTROL_CTL_REMOTE)"
@echo "✓ Frodo control deployed"
@echo ""
@echo "Next steps:"
@echo " 1. Start webserver: make start"
@echo " 2. Start frodo-control: make start-control"
@echo " 3. Check status: make status-control"

# Start frodo-control
start-control:
@echo "Starting frodo control on $(DEVICE_IP)..."
adb connect $(DEVICE_IP)
adb shell "$(CONTROL_CTL_REMOTE) start"

# Stop frodo-control
stop-control:
@echo "Stopping frodo control on $(DEVICE_IP)..."
adb connect $(DEVICE_IP)
adb shell "$(CONTROL_CTL_REMOTE) stop"

# Restart frodo-control
restart-control:
@echo "Restarting frodo control on $(DEVICE_IP)..."
adb connect $(DEVICE_IP)
adb shell "$(CONTROL_CTL_REMOTE) restart"

# Show frodo-control status
status-control:
@echo "Checking frodo control status on $(DEVICE_IP)..."
adb connect $(DEVICE_IP)
adb shell "$(CONTROL_CTL_REMOTE) status"

# Show frodo-control logs
logs-control:
@echo "Following frodo control logs from $(DEVICE_IP) (Ctrl+C to exit)..."
adb connect $(DEVICE_IP)
adb shell "$(CONTROL_CTL_REMOTE) logs"

# Clean frodo-control build files
clean-control:
@echo "Cleaning frodo-control build files..."
rm -rf $(CONTROL_BUILD_DIR_ARM) $(CONTROL_BUILD_DIR_X86)
@echo "✓ frodo-control clean complete"

# Clean frodo-control ARM build only
clean-control-arm:
@echo "Cleaning ARM build..."
rm -rf $(CONTROL_BUILD_DIR_ARM)
@echo "✓ ARM build clean complete"

# Clean frodo-control x86 build only
clean-control-x86:
@echo "Cleaning x86 build..."
rm -rf $(CONTROL_BUILD_DIR_X86)
@echo "✓ x86 build clean complete"

# Show help
help:
@echo "Frodobot Web Server & Camera Daemon - Makefile Commands"
@echo "Frodobot Web Server, Camera Daemon & FRC Control - Makefile Commands"
@echo ""
@echo "Usage: make [target] [DEVICE_IP=x.x.x.x]"
@echo ""
Expand Down Expand Up @@ -480,6 +603,20 @@ help:
@echo " tail-survive-server - Follow survive-server logs"
@echo " clean-survive-server - Clean survive-server build"
@echo ""
@echo "=== Frodo Control Targets (FRC Controller - Device) ==="
@echo " build-control-arm - Build ARM binary with static linking (for device)"
@echo " build-control-x86 - Build x86_64 binary for local testing"
@echo " build-control - Alias for build-control-arm"
@echo " deploy-control - Build and deploy ARM binary to device"
@echo " start-control - Start frodo-control on device"
@echo " stop-control - Stop frodo-control on device"
@echo " restart-control - Restart frodo-control on device"
@echo " status-control - Check frodo-control status"
@echo " logs-control - Follow frodo-control logs"
@echo " clean-control - Clean all frodo-control build files"
@echo " clean-control-arm - Clean ARM build only"
@echo " clean-control-x86 - Clean x86 build only"
@echo ""
@echo "=== Other Targets ==="
@echo " reset - Reset ADB connection (useful when ADB gets stuck)"
@echo " test-local - Run webserver locally on Mac"
Expand Down
87 changes: 87 additions & 0 deletions client/src/views/PlotView/PlotView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class PlotView implements View {
private velocityDataSeries: Map<string, XyDataSeries> = new Map();
private positionXYDataSeries?: XyDataSeries;
private positionXYCurrentDataSeries?: XyDataSeries;
private headingArrowSvg?: SVGSVGElement;
private positionTimeDataSeries: Map<string, XyDataSeries> = new Map();
private gpsCoordDataSeries: Map<string, XyDataSeries> = new Map();
private estimatedVelDataSeries: Map<string, XyDataSeries> = new Map();
Expand Down Expand Up @@ -374,6 +375,9 @@ export class PlotView implements View {
});
sciChartSurface.renderableSeries.add(currentPositionLine);

// Create SVG overlay for heading indicator
this.createHeadingArrowOverlay(chartDiv);

// Add interactivity
sciChartSurface.chartModifiers.add(
new ZoomPanModifier(),
Expand All @@ -388,6 +392,60 @@ export class PlotView implements View {
);
}

private createHeadingArrowOverlay(chartDiv: HTMLDivElement): void {
// Create SVG overlay positioned absolutely over the chart
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.position = 'absolute';
svg.style.top = '0';
svg.style.left = '0';
svg.style.width = '100%';
svg.style.height = '100%';
svg.style.pointerEvents = 'none'; // Allow mouse events to pass through to chart
svg.style.zIndex = '10'; // Place above chart

// Create arrow group
const arrowGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
arrowGroup.setAttribute('id', 'headingArrow');
arrowGroup.style.display = 'none'; // Hidden until we have data

// Create arrow shaft (line)
const arrowLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
arrowLine.setAttribute('x1', '0');
arrowLine.setAttribute('y1', '0');
arrowLine.setAttribute('x2', '30'); // 30px length in SVG space
arrowLine.setAttribute('y2', '0');
arrowLine.setAttribute('stroke', '#f59e0b');
arrowLine.setAttribute('stroke-width', '3');
arrowLine.setAttribute('stroke-linecap', 'round');

// Create arrowhead (triangle)
const arrowHead = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
arrowHead.setAttribute('points', '30,0 22,-5 22,5'); // Triangle at the tip
arrowHead.setAttribute('fill', '#f59e0b');
arrowHead.setAttribute('stroke', '#f59e0b');
arrowHead.setAttribute('stroke-width', '1');

// Create arrow tail (circle at base)
const arrowTail = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
arrowTail.setAttribute('cx', '0');
arrowTail.setAttribute('cy', '0');
arrowTail.setAttribute('r', '4');
arrowTail.setAttribute('fill', '#f59e0b');
arrowTail.setAttribute('stroke', '#fff');
arrowTail.setAttribute('stroke-width', '1.5');

arrowGroup.appendChild(arrowLine);
arrowGroup.appendChild(arrowHead);
arrowGroup.appendChild(arrowTail);
svg.appendChild(arrowGroup);

// Add SVG to chart container
chartDiv.style.position = 'relative'; // Ensure container is positioned
chartDiv.appendChild(svg);

this.headingArrowSvg = svg;
}

private async createPositionTimeChart(): Promise<void> {
const chartDiv = document.getElementById('positionTimePlot') as HTMLDivElement;
if (!chartDiv) return;
Expand Down Expand Up @@ -942,6 +1000,35 @@ export class PlotView implements View {
this.positionXYCurrentDataSeries.clear();
this.positionXYCurrentDataSeries.append(x, y);

// Update SVG heading indicator if rotation data is available
if (this.headingArrowSvg && data.survive_pose.object.rotation &&
Array.isArray(data.survive_pose.object.rotation) &&
data.survive_pose.object.rotation.length >= 4) {
// Quaternion format is [w, x, y, z] as documented in survive_client.go:74
const [qw, qx, qy, qz] = data.survive_pose.object.rotation;

// Convert quaternion to yaw angle in radians
const heading_libsurvive = Math.atan2(2.0 * (qw * qz + qx * qy), 1.0 - 2.0 * (qy * qy + qz * qz));

// Transform heading from LibSurvive frame to plot frame (+90 degrees)
const heading = heading_libsurvive + Math.PI / 2.0;

// Convert data coordinates to pixel coordinates
const xAxis = this.positionXYChart.xAxes.get(0);
const yAxis = this.positionXYChart.yAxes.get(0);
const pixelX = xAxis.getCurrentCoordinateCalculator().getCoordinate(x);
const pixelY = yAxis.getCurrentCoordinateCalculator().getCoordinate(y);

// Update SVG arrow position and rotation
const arrowGroup = this.headingArrowSvg.querySelector('#headingArrow') as SVGElement;
if (arrowGroup) {
arrowGroup.style.display = 'block';
// Convert heading from radians to degrees, and flip Y axis for SVG coordinates
const headingDegrees = -(heading * 180 / Math.PI);
arrowGroup.setAttribute('transform', `translate(${pixelX}, ${pixelY}) rotate(${headingDegrees})`);
}
}

// In follow mode, limit data points. In all data mode, keep everything
if (this.followMode && this.positionXYDataSeries.count() > this.maxDataPoints) {
const removeCount = this.positionXYDataSeries.count() - this.maxDataPoints;
Expand Down
Loading