From e4324e626fd92bfb6b2f85c3768e279ad9c13fae Mon Sep 17 00:00:00 2001 From: Hermes Date: Thu, 21 May 2026 21:50:26 +0000 Subject: [PATCH] feat: add 3D printable case design (OpenSCAD) and hardware assembly guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GoPro Hero 3 sleeve with lens/screen/USB cutouts - ESP32 D1 Mini electronics compartment (vented) - LiPo battery compartment with velcro strap slots - Bill of materials (~0 per camera node) - Wiring diagram (LiPo → dual buck converters → ESP32 + GoPro) - Field deployment workflow OpenSCAD model in hardware/case/remoterig-case.scad Assembly guide in hardware/README.md --- hardware/README.md | 140 ++++++++++++++++++++ hardware/case/remoterig-case.scad | 205 ++++++++++++++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 hardware/README.md create mode 100644 hardware/case/remoterig-case.scad diff --git a/hardware/README.md b/hardware/README.md new file mode 100644 index 0000000..1286c48 --- /dev/null +++ b/hardware/README.md @@ -0,0 +1,140 @@ +# RemoteRig — Camera Node Hardware Design + +> **Version:** 0.1.0 | **Status:** Draft +> **Target:** GoPro Hero 3 Black/Silver + ESP32 D1 Mini + 1000mAh LiPo + +## Overview + +Each camera node is a self-contained unit clipped onto a GoPro Hero 3. It provides: +- Camera control (start/stop recording) via Wi-Fi +- Status monitoring (battery, storage, recording state) +- MQTT communication to the central Pi Zero 2 W hub +- Battery power for both the ESP32 and GoPro + +## Physical Assembly + +``` +┌─────────────────────────────────┐ +│ GoPro Hero 3 │ +│ ┌─────────────────────────┐ │ +│ │ Lens (front) │ │ +│ └─────────────────────────┘ │ +│ ┌─────────────────────────┐ │ +│ │ Screen │ │ +│ └─────────────────────────┘ │ +│ ┌──────────┐ │ +│ 3D Sleeve ─────→│ ESP32 │ │ ← clips onto back/bottom +│ │ D1 Mini │ │ +│ └──────────┘ │ +│ ┌──────────┐ │ +│ │ LiPo │ │ ← slides under GoPro +│ │ 1000mAh │ │ +│ └──────────┘ │ +└─────────────────────────────────┘ +``` + +## Bill of Materials + +| Item | Qty | Cost | Notes | +|------|-----|------|-------| +| GoPro Hero 3 Black/Silver | 1 | Already owned | Target camera | +| ESP32 D1 Mini | 1 | ~$4 | Or NodeMCU-32S (~$5) | +| LiPo 3.7V 1000mAh | 1 | ~$8 | 50x34x8mm typical | +| 5V/3A buck converter | 1 | ~$2 | LiPo → GoPro USB | +| 3.3V buck converter | 1 | ~$1 | LiPo → ESP32 VIN | +| JST-XH 2-pin connectors | 2 | ~$1 | Battery quick-disconnect | +| Micro-USB right-angle cable | 1 | ~$2 | Buck → GoPro | +| Velcro strap (20cm) | 1 | ~$0.50 | Secure to GoPro | +| PETG filament | ~30g | ~$0.60 | 3D printed case | + +**Total per node:** ~$20 + +## 3D Printed Case + +The case consists of three parts (see `hardware/case/remoterig-case.scad`): + +### Part 1: GoPro Sleeve +Wraps around the GoPro body with cutouts for: +- Lens (front) +- Screen/viewfinder (back) +- USB port (side) +- Bottom mounting fingers +- Mounting ears for electronics compartment + +### Part 2: Electronics Compartment +Clips onto the sleeve's mounting ears. Holds: +- ESP32 D1 Mini board (recessed fit) +- USB cable routing (in → ESP32, out → GoPro) +- Ventilation slots (top) +- LED visibility window + +### Part 3: Battery Compartment +Slides under the GoPro. Contains: +- LiPo battery cavity +- Cable exits (to ESP32, to GoPro buck converter) +- Velcro strap slots + +### Print Settings +- **Material:** PETG (outdoor/heat resistant) or PLA+ +- **Layer height:** 0.2mm +- **Infill:** 20% gyroid +- **Supports:** Yes (for cable channels) +- **Bed adhesion:** Brim (5mm) for sleeve +- **Orientation:** Print sleeve on its back, compartments flat + +## Wiring + +``` +LiPo 3.7V + ├── JST-XH connector + │ + ├──→ 5V/3A Buck Converter → Micro-USB right-angle → GoPro USB port + │ (power only — no data over USB) + │ + └──→ 3.3V Buck Converter → ESP32 VIN + GND + (or ESP32 D1 Mini has built-in regulator — connect directly to 5V pin) +``` + +**Note:** ESP32 D1 Mini has an onboard 3.3V regulator. You can feed it 5V directly to the 5V pin if using a single 5V buck converter. This simplifies wiring: +``` +LiPo → 5V Buck → ├── ESP32 5V pin + └── GoPro USB port +``` + +## Wi-Fi Topology (No Cables for Camera Control) + +``` +GoPro Hero 3 ──(Wi-Fi AP @ 10.5.5.1)──→ ESP32 STA #1 + │ +Travel Router ──(Wi-Fi AP)─────────────────→ ESP32 STA #2 +(192.168.4.1) │ + │ + └──→ MQTT → Pi Hub (192.168.4.10) +``` + +The ESP32 has **no wired data connection** to the GoPro. All camera control is over Wi-Fi. The USB cable is **power only**. + +## Enclosure Dimensions + +| Component | W × H × D (mm) | +|-----------|-----------------| +| GoPro Hero 3 | 60 × 42 × 30 | +| ESP32 D1 Mini | 34 × 26 × 5 | +| LiPo 1000mAh | 50 × 34 × 8 | +| Full assembly | ~70 × 60 × 55 | + +## Usage in the Field + +1. **Pre-show:** Charge LiPos, flash ESP32 firmware, verify MQTT connectivity +2. **At venue:** Mount cameras, power on ESP32s (they auto-connect to travel router) +3. **Monitoring:** Open `http://192.168.4.10:8080` on laptop/kiosk +4. **Control:** Start/stop recording from dashboard +5. **Post-show:** Stop recording, power down, swap batteries for next session + +## Future Improvements + +- **Hot-swap battery:** Quick-release battery tray with spring contacts +- **Weather sealing:** O-ring groove in sleeve for outdoor rain protection +- **Lens hood:** Integrated sun shield for outdoor daytime recording +- **Mount adapter:** 1/4"-20 tripod mount thread on bottom +- **Antenna routing:** External antenna connector for improved Wi-Fi range in stadiums diff --git a/hardware/case/remoterig-case.scad b/hardware/case/remoterig-case.scad new file mode 100644 index 0000000..2c8b991 --- /dev/null +++ b/hardware/case/remoterig-case.scad @@ -0,0 +1,205 @@ +// RemoteRig — GoPro Hero 3 + ESP32 Camera Case +// ============================================== +// Sleeve that wraps around GoPro Hero 3 body with ESP32 + LiPo compartment. +// Designed for: ESP32 D1 Mini, 1000mAh LiPo, GoPro Hero 3 Black/Silver. +// +// Print settings: +// Material: PETG (outdoor/heat) or PLA+ (indoor) +// Layer: 0.2mm | Infill: 20% gyroid | Supports: yes (for cable channels) +// Nozzle: 0.4mm | Bed: 60°C (PLA) / 80°C (PETG) + +// ── GoPro Hero 3 Body (approximate) ── +gopro_width = 60; // mm — body width +gopro_height = 42; // mm — body height (top to bottom) +gopro_depth = 30; // mm — body depth (front to back) +gopro_lens_dia = 28; // mm — lens protrusion diameter +gopro_lens_offset = 18; // mm — lens center from top + +// ── ESP32 D1 Mini ── +esp_width = 34.2; +esp_height = 25.6; +esp_thick = 5; // board + components +usb_cutout_w = 10; +usb_cutout_h = 5; + +// ── LiPo Battery (1000mAh typical) ── +lipo_width = 35; +lipo_height = 25; +lipo_thick = 8; + +// ── Case parameters ── +wall = 2.0; // case wall thickness +tolerance = 0.3; // print tolerance for friction fit +compartment_height = max(esp_thick, lipo_thick) + 3; // internal compartment height + +// ── Cable channels ── +cable_dia = 4; // USB cable diameter +cable_channel_depth = 3; + +// ══════════════════════════════════════════════════════════════ +// MAIN ASSEMBLY +// ══════════════════════════════════════════════════════════════ + +// Uncomment the part you want to export: +gopro_sleeve(); +// translate([0, -20, 0]) electronics_compartment(); +// translate([0, 20, 0]) battery_compartment(); + +// ══════════════════════════════════════════════════════════════ +// GoPro Sleeve — wraps around the GoPro body +// ══════════════════════════════════════════════════════════════ + +module gopro_sleeve() { + union() { + // Main sleeve body — wraps around GoPro + difference() { + // Outer shell + rounded_cube( + gopro_width + wall*2, + gopro_height + wall*2, + gopro_depth + wall*2, + 4 // corner radius + ); + + // Inner cavity (GoPro body) + translate([0, 0, wall]) + rounded_cube( + gopro_width + tolerance, + gopro_height + tolerance, + gopro_depth + tolerance, + 3 + ); + + // Lens cutout (front face) + translate([0, gopro_height/2 - gopro_lens_offset, 0]) + rotate([90, 0, 0]) + cylinder(d=gopro_lens_dia + 4, h=wall*3, center=true); + + // Front screen/viewfinder cutout + translate([0, gopro_height/2 - gopro_lens_offset - 18, wall*2]) + cube([gopro_width - 10, gopro_height - 20, wall*4], center=true); + + // Bottom cutout (for GoPro mounting fingers) + translate([0, 0, gopro_depth/2 + wall]) + cube([gopro_width - 10, wall*4, wall*4], center=true); + + // USB port access (side) + translate([gopro_width/2 + wall, 0, -5]) + cube([wall*4, 16, 10], center=true); + + // Cable channel from ESP32 compartment to GoPro USB + translate([gopro_width/2 - 5, -gopro_height/2 + 10, -gopro_depth/2 + 5]) + rotate([0, 90, 0]) + cylinder(d=cable_dia, h=wall*3, center=true); + } + + // Mounting ears for electronics compartment + for (x = [-1, 1]) { + translate([x * (gopro_width/2 - 6), -gopro_height/2 - 6, 0]) + rotate([90, 0, 0]) + cylinder(d=8, h=10); + } + } +} + +// ══════════════════════════════════════════════════════════════ +// Electronics Compartment — holds ESP32 + routes cables +// ══════════════════════════════════════════════════════════════ + +module electronics_compartment() { + comp_w = max(esp_width, esp_height) + wall*2 + 10; + comp_h = compartment_height + wall*2; + comp_d = gopro_depth + wall*2; + + difference() { + union() { + // Main box + rounded_cube(comp_w, comp_d, comp_h, 3); + + // Mounting tabs (match GoPro sleeve ears) + for (x = [-1, 1]) { + translate([x * (gopro_width/2 - 6), 0, comp_h/2]) + rotate([0, 90, 0]) + cylinder(d=6, h=4, center=true); + } + } + + // Inner cavity + translate([0, 0, wall]) + rounded_cube(comp_w - wall*2, comp_d - wall*2, comp_h - wall, 2); + + // ESP32 board recess + translate([0, 5, wall + 1]) + cube([esp_width + tolerance, esp_height + tolerance, esp_thick + 1], center=true); + + // USB cable entry (side hole) + translate([comp_w/2, 15, comp_h/2]) + rotate([0, 90, 0]) + cylinder(d=usb_cutout_w, h=wall*3, center=true); + + // USB cable exit (to GoPro) + translate([comp_w/2, -15, comp_h/2]) + rotate([0, 90, 0]) + cylinder(d=cable_dia, h=wall*3, center=true); + + // Ventilation slots + for (y = [-1:2:1]) { + for (i = [-15:10:15]) { + translate([i, y * comp_d/3, comp_h - 2]) + cube([6, 1.5, wall*2], center=true); + } + } + + // LED window (thin wall for ESP32 LED visibility) + translate([0, 0, wall]) + cube([5, 5, wall], center=true); + } +} + +// ══════════════════════════════════════════════════════════════ +// Battery Compartment — holds LiPo under GoPro +// ══════════════════════════════════════════════════════════════ + +module battery_compartment() { + bat_w = lipo_width + wall*2 + tolerance; + bat_d = lipo_height + wall*2 + tolerance; + bat_h = lipo_thick + wall*2 + 4; + + difference() { + // Shell + rounded_cube(bat_w, bat_d, bat_h, 3); + + // Battery cavity + translate([0, 0, wall]) + rounded_cube(lipo_width + tolerance, lipo_height + tolerance, lipo_thick + tolerance, 1); + + // Cable exit (to ESP32 compartment) + translate([0, bat_d/2, bat_h/2]) + rotate([90, 0, 0]) + cylinder(d=cable_dia, h=wall*3, center=true); + + // Cable exit (to GoPro USB) + translate([bat_w/3, -bat_d/2, bat_h/2]) + rotate([90, 0, 0]) + cylinder(d=cable_dia, h=wall*3, center=true); + + // Strap slots (velcro strap to secure to GoPro) + for (x = [-bat_w/3, bat_w/3]) { + translate([x, -bat_d/2, bat_h/2]) + cube([8, wall*4, 3], center=true); + } + } +} + +// ══════════════════════════════════════════════════════════════ +// Utility: Rounded cube (positive X/Y/Z = full dimensions) +// ══════════════════════════════════════════════════════════════ + +module rounded_cube(w, d, h, r) { + hull() { + for (x = [-1, 1], y = [-1, 1], z = [-1, 1]) { + translate([x * (w/2 - r), y * (d/2 - r), z * (h/2 - r)]) + sphere(r=r, $fn=20); + } + } +}