generated from CubeCraft-Creations/Tracehound
feat: add 3D printable case design (OpenSCAD) and hardware assembly guide
- 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
This commit is contained in:
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user