generated from CubeCraft-Creations/Tracehound
Dev #26
+64
-99
@@ -1,144 +1,109 @@
|
|||||||
# RemoteRig — Camera Node Hardware Design
|
# RemoteRig — Camera Node Hardware Design
|
||||||
|
|
||||||
> **Version:** 0.1.0 | **Status:** Draft
|
> **Version:** 0.2.0 | **Status:** Draft
|
||||||
> **Target:** GoPro Hero 3 Black/Silver + ESP32 D1 Mini + 1000mAh LiPo
|
> **Target:** GoPro Hero 3 Black/Silver + ESP8266 + ESP32 + USB power bank
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Each camera node is a self-contained unit clipped onto a GoPro Hero 3. It provides:
|
Each camera node is two ESP boards in a small case that clips to the tripod/stand. The case **does not attach to the camera** — only to the stand. Powered by a standard USB power bank.
|
||||||
- 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 │
|
│ USB Power Bank │── USB ──→ GoPro (power only)
|
||||||
│ ┌─────────────────────────┐ │
|
│ (off-the-shelf)│── USB ──→ ESP32 + ESP8266 (shared)
|
||||||
│ │ Lens (front) │ │
|
└─────────────────┘
|
||||||
│ └─────────────────────────┘ │
|
│
|
||||||
│ ┌─────────────────────────┐ │
|
┌────────┴────────┐
|
||||||
│ │ Screen │ │
|
│ Tripod Case │ ← clips to stand leg
|
||||||
│ └─────────────────────────┘ │
|
│ ┌────────────┐ │
|
||||||
│ ┌──────────┐ │
|
│ │ ESP8266 │ │ ← Wi-Fi → GoPro AP (10.5.5.1)
|
||||||
│ 3D Sleeve ─────→│ ESP8266 │ │ ← Camera bridge (GoPro Wi-Fi)
|
│ │ (camera) │ │
|
||||||
│ │ D1 Mini │ │
|
│ ├────────────┤ │ ← UART between boards
|
||||||
│ ├──────────┤ │
|
│ │ ESP32 │ │ ← Wi-Fi → Travel Router
|
||||||
│ │ ESP32 │ │ ← MQTT bridge (travel router)
|
│ │ (MQTT) │ │
|
||||||
│ │ Dev │ │
|
│ └────────────┘ │
|
||||||
│ ├──────────┤ │
|
└─────────────────┘
|
||||||
│ │ LiPo │ │ ← Shared power
|
|
||||||
│ │ 1000mAh │ │
|
|
||||||
│ └──────────┘ │
|
|
||||||
└─────────────────────────────────┘
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bill of Materials
|
## Bill of Materials
|
||||||
|
|
||||||
| Item | Qty | Cost | Notes |
|
| Item | Qty | Cost | Notes |
|
||||||
|------|-----|------|-------|
|
|------|-----|------|-------|
|
||||||
| GoPro Hero 3 Black/Silver | 1 | Already owned | Target camera |
|
|
||||||
| ESP32 Dev Board | 1 | ~$5 | MQTT bridge — talks to hub |
|
| ESP32 Dev Board | 1 | ~$5 | MQTT bridge — talks to hub |
|
||||||
| ESP8266 D1 Mini | 1 | ~$3 | Camera bridge — talks to GoPro |
|
| ESP8266 D1 Mini | 1 | ~$3 | Camera bridge — talks to GoPro |
|
||||||
| LiPo 3.7V 1000mAh | 1 | ~$8 | 50x34x8mm typical |
|
| USB power bank (5000mAh+) | 1 | ~$10 | Powers both boards + GoPro |
|
||||||
| 3.3V buck converter | 1 | ~$1 | LiPo → both boards (shared VIN) |
|
| Micro-USB cable (short) | 2 | ~$2 | Power bank → boards + GoPro |
|
||||||
| 5V/3A buck converter | 1 | ~$2 | LiPo → GoPro USB (power only) |
|
| Jumper wires F-F | 3 | ~$0.25 | UART TX/RX/GND between boards |
|
||||||
| JST-XH 2-pin connectors | 2 | ~$1 | Battery quick-disconnect |
|
| PETG filament | ~25g | ~$0.50 | 3D printed case |
|
||||||
| Micro-USB right-angle cable | 1 | ~$2 | Buck → GoPro |
|
| Velcro strap (small) | 1 | ~$0.25 | Secure power bank to stand |
|
||||||
| Jumper wires (female-female) | 4 | ~$0.50 | UART + GND between boards |
|
|
||||||
| Velcro strap (20cm) | 1 | ~$0.50 | Secure to GoPro |
|
|
||||||
| PETG filament | ~35g | ~$0.70 | 3D printed case |
|
|
||||||
|
|
||||||
**Total per node:** ~$24
|
**Total per node:** ~$21 (+ GoPro already owned)
|
||||||
|
|
||||||
## 3D Printed Case
|
## 3D Printed Case
|
||||||
|
|
||||||
The case consists of three parts (see `hardware/case/remoterig-case.scad`):
|
**File:** `hardware/case/tripod-case.scad`
|
||||||
|
|
||||||
### Part 1: GoPro Sleeve
|
Three parts:
|
||||||
Wraps around the GoPro body with cutouts for:
|
1. **Case body** — holds both boards stacked, cable ports, rail for clip
|
||||||
- Lens (front)
|
2. **Case lid** — screw-on cover with ventilation
|
||||||
- Screen/viewfinder (back)
|
3. **Tripod clip** — C-clamp for 20-35mm poles, slides into case rail
|
||||||
- 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
|
### Print Settings
|
||||||
- **Material:** PETG (outdoor/heat resistant) or PLA+
|
- **Material:** PETG (outdoor/heat) or PLA+
|
||||||
- **Layer height:** 0.2mm
|
- **Layer:** 0.2mm | **Infill:** 20% gyroid
|
||||||
- **Infill:** 20% gyroid
|
- **Supports:** Yes (for clip overhang)
|
||||||
- **Supports:** Yes (for cable channels)
|
- **Post-processing:** M3x8mm screws for lid (4x)
|
||||||
- **Bed adhesion:** Brim (5mm) for sleeve
|
|
||||||
- **Orientation:** Print sleeve on its back, compartments flat
|
|
||||||
|
|
||||||
## Wiring
|
## Wiring
|
||||||
|
|
||||||
```
|
```
|
||||||
LiPo 3.7V
|
USB Power Bank
|
||||||
├── JST-XH connector
|
├── USB-A → Micro-USB cable → ESP32 USB port
|
||||||
|
│ (powers ESP32, shared 5V rail)
|
||||||
│
|
│
|
||||||
├──→ 3.3V Buck Converter → ESP8266 VIN + GND
|
├── USB-A → Micro-USB cable → GoPro USB port
|
||||||
│ → ESP32 VIN + GND
|
│ (power only — no data)
|
||||||
│ (both boards share the same 3.3V rail)
|
|
||||||
│
|
│
|
||||||
└──→ 5V/3A Buck Converter → Micro-USB right-angle → GoPro USB port
|
└── (ESP8266 powered via ESP32 3.3V pin, or via shared USB)
|
||||||
(power only — no data over USB)
|
|
||||||
|
|
||||||
UART (ESP8266 ↔ ESP32):
|
UART (inside case):
|
||||||
ESP8266 TX (GPIO1) ──→ ESP32 RX (GPIO16)
|
ESP8266 TX (GPIO1) ──→ ESP32 RX (GPIO16)
|
||||||
ESP8266 RX (GPIO3) ←── ESP32 TX (GPIO17)
|
ESP8266 RX (GPIO3) ←── ESP32 TX (GPIO17)
|
||||||
ESP8266 GND ─────────── ESP32 GND
|
ESP8266 GND ─────────── ESP32 GND
|
||||||
```
|
```
|
||||||
|
|
||||||
## Wi-Fi Topology (No Cables for Camera Control)
|
**Power note:** Both boards can be powered from a single USB cable if the ESP32's VIN/5V pin is bridged to the ESP8266's VIN. Alternatively, use a USB Y-splitter cable.
|
||||||
|
|
||||||
|
## Wi-Fi Topology
|
||||||
|
|
||||||
```
|
```
|
||||||
GoPro Hero 3 ──(Wi-Fi AP @ 10.5.5.1)──→ ESP32 STA #1
|
GoPro Hero 3 ──(AP @ 10.5.5.1)──→ ESP8266 (camera bridge)
|
||||||
│
|
│
|
||||||
Travel Router ──(Wi-Fi AP)─────────────────→ ESP32 STA #2
|
UART │ (inside case)
|
||||||
|
│
|
||||||
|
Travel Router ──(AP)─────────────────→ ESP32 (MQTT bridge)
|
||||||
(192.168.4.1) │
|
(192.168.4.1) │
|
||||||
│
|
│
|
||||||
└──→ MQTT → Pi Hub (192.168.4.10)
|
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**.
|
The ESP8266 and GoPro talk over Wi-Fi — **no data cable between them**. The only cable to the GoPro is USB power from the battery pack.
|
||||||
|
|
||||||
## Enclosure Dimensions
|
## Field Setup
|
||||||
|
|
||||||
| Component | W × H × D (mm) |
|
1. **Mount GoPro** on tripod/stand
|
||||||
|-----------|-----------------|
|
2. **Clip case** to tripod leg
|
||||||
| GoPro Hero 3 | 60 × 42 × 30 |
|
3. **Connect power bank** via USB to case + GoPro
|
||||||
| ESP32 D1 Mini | 34 × 26 × 5 |
|
4. **Power on** — ESP32 auto-connects to travel router, ESP8266 auto-connects to GoPro
|
||||||
| LiPo 1000mAh | 50 × 34 × 8 |
|
5. **Monitor** from `http://192.168.4.10:8080`
|
||||||
| Full assembly | ~70 × 60 × 55 |
|
|
||||||
|
|
||||||
## Usage in the Field
|
## Case Dimensions
|
||||||
|
|
||||||
1. **Pre-show:** Charge LiPos, flash ESP32 firmware, verify MQTT connectivity
|
| | W × D × H (mm) |
|
||||||
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
|
| Case external | ~64 × 38 × 27 |
|
||||||
4. **Control:** Start/stop recording from dashboard
|
| Case internal | ~57 × 31 × 18 |
|
||||||
5. **Post-show:** Stop recording, power down, swap batteries for next session
|
| Fits poles | 20-35mm diameter |
|
||||||
|
| Total weight | ~50g (case + boards, without power bank) |
|
||||||
## 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
|
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
// 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
|
|
||||||
|
|
||||||
// ── ESP8266 D1 Mini + ESP32 Dev Board (stacked) ──
|
|
||||||
esp8266_width = 34.2;
|
|
||||||
esp8266_height = 25.6;
|
|
||||||
esp8266_thick = 5; // board + components
|
|
||||||
|
|
||||||
esp32_width = 52; // ESP32 Dev Board is larger
|
|
||||||
esp32_height = 28;
|
|
||||||
esp32_thick = 5;
|
|
||||||
|
|
||||||
// Combined stack
|
|
||||||
board_width = max(esp8266_width, esp32_width);
|
|
||||||
board_height = max(esp8266_height, esp32_height);
|
|
||||||
board_thick = esp8266_thick + esp32_thick + 3; // 3mm gap between boards
|
|
||||||
|
|
||||||
// ── 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 = board_thick + 5; // internal compartment height for stacked boards
|
|
||||||
|
|
||||||
// ── 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 ESP8266 + ESP32 stacked
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
module electronics_compartment() {
|
|
||||||
comp_w = board_width + wall*2 + 8;
|
|
||||||
comp_h = compartment_height + wall*2;
|
|
||||||
comp_d = board_height + wall*2 + 8;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Bottom board (ESP32 — larger) recess
|
|
||||||
translate([0, 5, wall + 1])
|
|
||||||
cube([esp32_width + tolerance, esp32_height + tolerance, esp32_thick + 1], center=true);
|
|
||||||
|
|
||||||
// Top board (ESP8266 — smaller) recess
|
|
||||||
translate([0, 5, wall + esp32_thick + 4])
|
|
||||||
cube([esp8266_width + tolerance, esp8266_height + tolerance, esp8266_thick + 1], center=true);
|
|
||||||
|
|
||||||
// UART wire channel (between boards)
|
|
||||||
translate([comp_w/2, 0, wall + esp32_thick + 1])
|
|
||||||
cube([wall*3, 6, 3], center=true);
|
|
||||||
|
|
||||||
// USB cable entry (power to boards)
|
|
||||||
translate([comp_w/2, 15, comp_h/2])
|
|
||||||
rotate([0, 90, 0])
|
|
||||||
cylinder(d=6, 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 windows (thin walls for ESP LEDs)
|
|
||||||
translate([0, 0, wall])
|
|
||||||
cube([5, 5, wall], center=true);
|
|
||||||
translate([0, 0, wall + esp32_thick + 4])
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
// RemoteRig — Dual-ESP Tripod Case
|
||||||
|
// =================================
|
||||||
|
// Small box that clips onto a tripod leg or light stand pole.
|
||||||
|
// Holds ESP8266 D1 Mini + ESP32 Dev Board (stacked).
|
||||||
|
// Powered by standard USB battery pack. No camera sleeve needed.
|
||||||
|
//
|
||||||
|
// Print settings:
|
||||||
|
// Material: PETG | Layer: 0.2mm | Infill: 20% gyroid
|
||||||
|
// Supports: yes (for clip overhang) | Brim: 5mm
|
||||||
|
|
||||||
|
// ── Board dimensions ──
|
||||||
|
esp8266_w = 34.2; esp8266_d = 25.6; esp8266_h = 5;
|
||||||
|
esp32_w = 52; esp32_d = 28; esp32_h = 5;
|
||||||
|
board_gap = 3; // air gap between stacked boards
|
||||||
|
stack_h = esp8266_h + esp32_h + board_gap;
|
||||||
|
inner_w = max(esp8266_w, esp32_w);
|
||||||
|
inner_d = max(esp8266_d, esp32_d);
|
||||||
|
inner_h = stack_h + 2;
|
||||||
|
|
||||||
|
// ── Case parameters ──
|
||||||
|
wall = 2.0;
|
||||||
|
tol = 0.4;
|
||||||
|
outer_w = inner_w + wall*2 + tol*2;
|
||||||
|
outer_d = inner_d + wall*2 + tol*2;
|
||||||
|
outer_h = inner_h + wall*2;
|
||||||
|
|
||||||
|
// ── Tripod clip parameters ──
|
||||||
|
pole_min_dia = 20; // smallest pole
|
||||||
|
pole_max_dia = 35; // largest pole
|
||||||
|
clip_width = 12; // clip width
|
||||||
|
clip_thick = 3; // clip arm thickness
|
||||||
|
clip_grip = 2; // grip ridges
|
||||||
|
|
||||||
|
// ── Cable ports ──
|
||||||
|
usb_port_w = 12; usb_port_h = 6;
|
||||||
|
uart_port_w = 6; uart_port_h = 4;
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// MAIN — render the full case
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// Uncomment to render individual parts:
|
||||||
|
full_case();
|
||||||
|
// case_body();
|
||||||
|
// case_lid();
|
||||||
|
// tripod_clip();
|
||||||
|
|
||||||
|
module full_case() {
|
||||||
|
case_body();
|
||||||
|
// Lid positioned above (for visualization)
|
||||||
|
translate([0, 0, outer_h + 2])
|
||||||
|
case_lid();
|
||||||
|
// Clip on the back
|
||||||
|
translate([0, outer_d/2 + pole_max_dia/2 + clip_thick, outer_h/2])
|
||||||
|
tripod_clip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// Case Body — holds both boards, cable ports
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
module case_body() {
|
||||||
|
difference() {
|
||||||
|
// Outer shell
|
||||||
|
rounded_cube(outer_w, outer_d, outer_h, 3);
|
||||||
|
|
||||||
|
// Inner cavity
|
||||||
|
translate([0, 0, wall])
|
||||||
|
rounded_cube(inner_w + tol, inner_d + tol, inner_h + tol, 2);
|
||||||
|
|
||||||
|
// ── Board recesses ──
|
||||||
|
|
||||||
|
// Bottom: ESP32 (larger board)
|
||||||
|
translate([0, 0, wall + 1])
|
||||||
|
cube([esp32_w + tol, esp32_d + tol, esp32_h + 1], center=true);
|
||||||
|
|
||||||
|
// Top: ESP8266 (smaller board)
|
||||||
|
translate([0, 0, wall + esp32_h + board_gap + 1])
|
||||||
|
cube([esp8266_w + tol, esp8266_d + tol, esp8266_h + 1], center=true);
|
||||||
|
|
||||||
|
// ── Cable ports ──
|
||||||
|
|
||||||
|
// USB power IN (from battery pack → ESP32)
|
||||||
|
translate([0, outer_d/2, outer_h/3])
|
||||||
|
cube([usb_port_w, wall*3, usb_port_h], center=true);
|
||||||
|
|
||||||
|
// USB power OUT (from battery pack → GoPro)
|
||||||
|
translate([0, -outer_d/2, outer_h/3])
|
||||||
|
cube([usb_port_w, wall*3, usb_port_h], center=true);
|
||||||
|
|
||||||
|
// UART wire channel (ESP8266 → ESP32 internal)
|
||||||
|
translate([outer_w/2, 0, outer_h/2])
|
||||||
|
cube([wall*3, uart_port_w, uart_port_h], center=true);
|
||||||
|
|
||||||
|
// ── Ventilation slots (top edge) ──
|
||||||
|
for (x = [-outer_w/4, 0, outer_w/4]) {
|
||||||
|
translate([x, 0, outer_h - wall])
|
||||||
|
cube([8, outer_d*0.6, 2], center=true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Screw posts for lid ──
|
||||||
|
for (x = [-1, 1], y = [-1, 1]) {
|
||||||
|
translate([x*(outer_w/2 - 5), y*(outer_d/2 - 5), outer_h/2])
|
||||||
|
cylinder(d=3.2, h=outer_h, center=true, $fn=16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── LED window (thin spot to see board LEDs) ──
|
||||||
|
translate([-outer_w/4, -outer_d/2, wall])
|
||||||
|
cube([6, 1, 3], center=true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tripod clip mount (rail on back) ──
|
||||||
|
translate([0, outer_d/2, outer_h/2])
|
||||||
|
rotate([90, 0, 0])
|
||||||
|
difference() {
|
||||||
|
cube([clip_width + 4, outer_h*0.7, 6], center=true);
|
||||||
|
// T-slot for clip to slide in
|
||||||
|
cube([clip_width + 1, outer_h*0.7 + 1, 4], center=true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// Case Lid — snap-fit or screw-on cover
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
module case_lid() {
|
||||||
|
difference() {
|
||||||
|
rounded_cube(outer_w, outer_d, wall*2, 2);
|
||||||
|
|
||||||
|
// Screw holes (match body posts)
|
||||||
|
for (x = [-1, 1], y = [-1, 1]) {
|
||||||
|
translate([x*(outer_w/2 - 5), y*(outer_d/2 - 5), 0])
|
||||||
|
cylinder(d=3.2, h=wall*3, center=true, $fn=16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ventilation slots (match body)
|
||||||
|
for (x = [-outer_w/4, 0, outer_w/4]) {
|
||||||
|
translate([x, 0, 0])
|
||||||
|
cube([8, outer_d*0.6, 3], center=true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// Tripod Clip — C-clamp for pole mounting
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
module tripod_clip() {
|
||||||
|
difference() {
|
||||||
|
union() {
|
||||||
|
// Main body
|
||||||
|
hull() {
|
||||||
|
translate([0, -pole_max_dia/2 - clip_thick, 0])
|
||||||
|
cube([clip_width, clip_thick*2, outer_h*0.7], center=true);
|
||||||
|
|
||||||
|
translate([0, pole_max_dia/2 + clip_thick, 0])
|
||||||
|
cube([clip_width, clip_thick*2, outer_h*0.7], center=true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top arm (flexible)
|
||||||
|
translate([0, -pole_max_dia/2 - clip_thick, outer_h*0.35])
|
||||||
|
cube([clip_width, pole_max_dia + clip_thick*4, clip_thick], center=true);
|
||||||
|
|
||||||
|
// Bottom arm
|
||||||
|
translate([0, -pole_max_dia/2 - clip_thick, -outer_h*0.35])
|
||||||
|
cube([clip_width, pole_max_dia + clip_thick*4, clip_thick], center=true);
|
||||||
|
|
||||||
|
// Mounting tab (slides into case rail)
|
||||||
|
translate([0, -pole_max_dia/2 - clip_thick*3, 0])
|
||||||
|
cube([clip_width + 1, clip_thick*2, outer_h*0.7], center=true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pole hole
|
||||||
|
cylinder(d=pole_max_dia + 2, h=outer_h*1.5, center=true, $fn=32);
|
||||||
|
|
||||||
|
// Grip ridges on inner surface
|
||||||
|
for (z = [-outer_h*0.25, 0, outer_h*0.25]) {
|
||||||
|
translate([0, 0, z])
|
||||||
|
rotate_extrude(angle=180, $fn=32)
|
||||||
|
translate([pole_max_dia/2 + 0.5, 0])
|
||||||
|
circle(d=1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry slot (pole slides in from front)
|
||||||
|
translate([0, pole_max_dia/2 + clip_thick, 0])
|
||||||
|
cube([clip_width + 2, pole_max_dia + 10, outer_h*0.7], center=true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// Utility: rounded cube
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
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