// ============================================================
// charging_dock_505.scad — 24V Charging Dock Station
// Issue: #505  Agent: sl-mechanical  Date: 2026-03-06
// ============================================================
//
// 24V upgraded dock (forked from Issue #159 5V design).
// Robot drives forward into V-guide funnel; spring-loaded pogo pins
// make contact with the robot receiver plate (charging_dock_receiver.scad).
//
// Power:   24 V / 10 A (240 W) via 2× high-current pogo pins (+/-)
// Alignment tolerance: ±20 mm lateral (V-guide funnels to centre)
//
// Dock architecture (top view):
//
//   ┌─────────────────────────────────┐  ← back wall (robot stops here)
//   │  PSU shelf                      │
//   │  [PSU]         [LED ×4]         │
//   │         [POGO+][POGO-]          │  ← pogo face (robot contact)
//   └────\                   /────────┘
//         \  V-guide rails  /
//          \               /
//           ╲             ╱  ← dock entry, ±20 mm funnel
//
// Components (this file):
//   Part A — dock_base()         weighted base plate with ballast pockets
//   Part B — back_wall()         upright back panel + pogo housing + LED bezel
//   Part C — guide_rail(side)    V-funnel guide rail, L/R (print 2×)
//   Part D — aruco_mount()       ArUco marker frame at dock entrance
//   Part E — psu_bracket()       PSU retention bracket (rear of base)
//   Part F — led_bezel()         4-LED status bezel
//
// Robot-side receiver → see charging_dock_receiver.scad
//
// Coordinate system:
//   Z = 0 at dock floor (base plate top face)
//   Y = 0 at back wall front face (robot approaches from +Y)
//   X = 0 at dock centre
//   Robot drives in -Y direction to dock.
//
// RENDER options:
//   "assembly"       full dock preview (default)
//   "base_stl"       base plate (print 1×)
//   "back_wall_stl"  back wall + pogo housing (print 1×)
//   "guide_rail_stl" V-guide rail (print 2×, mirror for R side)
//   "aruco_mount_stl" ArUco marker frame (print 1×)
//   "psu_bracket_stl" PSU mounting bracket (print 1×)
//   "led_bezel_stl"  LED status bezel (print 1×)
//
// Export commands (Issue #505 24V variant):
//   openscad charging_dock_505.scad -D 'RENDER="base_stl"'       -o dock_505_base.stl
//   openscad charging_dock_505.scad -D 'RENDER="back_wall_stl"'  -o dock_505_back_wall.stl
//   openscad charging_dock_505.scad -D 'RENDER="guide_rail_stl"' -o dock_505_guide_rail.stl
//   openscad charging_dock_505.scad -D 'RENDER="aruco_mount_stl"' -o dock_505_aruco_mount.stl
//   openscad charging_dock_505.scad -D 'RENDER="psu_bracket_stl"' -o dock_505_psu_bracket.stl
//   openscad charging_dock_505.scad -D 'RENDER="led_bezel_stl"'  -o dock_505_led_bezel.stl
// ============================================================

$fn = 64;
e   = 0.01;

// ── Base plate dimensions ─────────────────────────────────────────────────────
// NOTE: Enlarged for 24V PSU (IRM-240-24: 210×108×56 mm vs. IRM-30-5: 63×45×28 mm)
BASE_W   = 340.0;    // base width  (X) — increased for larger PSU bracket
BASE_D   = 320.0;    // base depth  (Y, extends behind and in front of back wall)
BASE_T   =  12.0;    // base thickness
BASE_R   =  10.0;    // corner radius

// Ballast pockets (for steel hex bar / bolt weights):
// 4× pockets in base underside, accept M20 hex nuts (30 mm AF) stacked
BALLAST_N   = 4;
BALLAST_W   = 32.0;   // pocket width  (hex nut AF + 2 mm)
BALLAST_D   = 32.0;   // pocket depth
BALLAST_T   =  8.0;   // pocket depth (≤ BASE_T/2)
BALLAST_INSET_X = 50.0;
BALLAST_INSET_Y = 40.0;

// Floor bolt holes (M8, for bolting dock to bench/floor — optional)
FLOOR_BOLT_D   = 8.5;
FLOOR_BOLT_INSET_X = 30.0;
FLOOR_BOLT_INSET_Y = 25.0;

// ── Back wall (upright panel) ─────────────────────────────────────────────────
WALL_W   = 250.0;   // wall width (X) — same as guide entry span
WALL_H   =  85.0;   // wall height (Z)
WALL_T   =  10.0;   // wall thickness (Y)

// Back wall Y position relative to base rear edge
// Wall sits at Y=0 (its front face); base extends behind it (-Y) and in front (+Y)
BASE_REAR_Y  = -80.0;   // base rear edge Y coordinate

// ── Pogo pin housing (in back wall front face) ────────────────────────────────
// High-current pogo pins: Ø5.5 mm body, 20 mm long (compressed), 4 mm spring travel
// Rated 5 A each; 2× pins for +/- power
POGO_D         =  5.5;    // pogo pin body OD
POGO_BORE_D    =  5.7;    // bore diameter (0.2 mm clearance)
POGO_L         = 20.0;    // pogo full length (uncompressed)
POGO_TRAVEL    =  4.0;    // spring travel
POGO_FLANGE_D  =  8.0;    // pogo flange / retention shoulder OD
POGO_FLANGE_T  =  1.5;    // flange thickness
POGO_SPACING   = 20.0;    // CL-to-CL spacing between + and - pins
POGO_Z         = 35.0;    // pogo CL height above dock floor
POGO_PROTRUDE  =  8.0;    // pogo tip protrusion beyond wall face (uncompressed)
// Wiring channel behind pogo (runs down to base)
WIRE_CH_W      =  8.0;
WIRE_CH_H      = POGO_Z + 5;

// ── LED bezel (4 status LEDs in back wall, above pogo pins) ───────────────────
// LED order (left to right): Searching | Aligned | Charging | Full
// Colours (suggested): Red | Yellow | Blue | Green
LED_D          =  5.0;    // 5 mm through-hole LED
LED_BORE_D     =  5.2;    // bore diameter
LED_BEZEL_W    = 80.0;    // bezel plate width
LED_BEZEL_H    = 18.0;    // bezel plate height
LED_BEZEL_T    =  4.0;    // bezel plate thickness
LED_SPACING    = 16.0;    // LED centre-to-centre
LED_Z          = 65.0;    // LED centre height above floor
LED_INSET_D    =  2.0;    // LED recess depth (LED body recessed for protection)

// ── V-guide rails ─────────────────────────────────────────────────────────────
// Robot receiver width (contact block): 30 mm.
// Alignment tolerance: ±20 mm → entry gap = 30 + 2×20 = 70 mm.
// Guide rail tapers from 70 mm entry (at Y = GUIDE_L) to 30 mm exit (at Y=0).
// Each rail is a wedge-shaped wall.
GUIDE_L     = 100.0;   // guide rail length (Y depth, from back wall)
GUIDE_H     =  50.0;   // guide rail height (Z)
GUIDE_T     =   8.0;   // guide rail wall thickness
RECV_W      =  30.0;   // robot receiver contact block width
ENTRY_GAP   =  70.0;   // guide entry gap (= RECV_W + 2×20 mm tolerance)
EXIT_GAP    =  RECV_W + 2.0;   // guide exit gap (2 mm clearance on each side)
// Derived: half-gap at entry = 35 mm, at exit = 16 mm; taper = 19 mm over 100 mm
// Half-angle = atan(19/100) ≈ 10.8° — gentle enough for reliable self-alignment

// ── ArUco marker mount ────────────────────────────────────────────────────────
// Mounted at dock entry arch (forward of guide rails), tilted 15° back.
// Robot camera acquires marker for coarse approach alignment.
// ArUco marker ID 42 (DICT_4X4_250), 100×100 mm (printed/laminated on paper).
ARUCO_MARKER_W  = 100.0;
ARUCO_MARKER_H  = 100.0;
ARUCO_FRAME_T   =   3.0;   // frame plate thickness
ARUCO_FRAME_BDR =  10.0;   // frame border around marker
ARUCO_SLOT_T    =   1.5;   // marker slip-in slot depth
ARUCO_MAST_H    =  95.0;   // mast height above base (centres marker at camera height)
ARUCO_MAST_W    =  10.0;
ARUCO_TILT      =  15.0;   // backward tilt (degrees) — faces approaching robot
ARUCO_Y         = GUIDE_L + 60;  // mast Y position (in front of guide entry)

// ── PSU bracket ───────────────────────────────────────────────────────────────
// Mean Well IRM-240-24 (24V 10A 240W): 210×108×56 mm body — Issue #505 upgrade
// Bracket sits behind back wall, on base plate.
PSU_W   = 220.0;   // bracket internal width (+5 mm clearance per side for 210 mm PSU)
PSU_D   = 118.0;   // bracket internal depth (+5 mm clearance per side for 108 mm PSU)
PSU_H   = 66.0;    // bracket internal height (+5 mm top clearance for 56 mm PSU + ventilation)
PSU_T   =  4.0;    // bracket wall thickness (thicker for larger PSU mass)
PSU_Y   = BASE_REAR_Y + PSU_D/2 + PSU_T + 10;   // PSU Y centre

// ── Fasteners ─────────────────────────────────────────────────────────────────
M3_D = 3.3;
M4_D = 4.3;
M5_D = 5.3;
M8_D = 8.5;

// ============================================================
// RENDER DISPATCH
// ============================================================
RENDER = "assembly";

if      (RENDER == "assembly")       assembly();
else if (RENDER == "base_stl")       dock_base();
else if (RENDER == "back_wall_stl")  back_wall();
else if (RENDER == "guide_rail_stl") guide_rail("left");
else if (RENDER == "aruco_mount_stl") aruco_mount();
else if (RENDER == "psu_bracket_stl") psu_bracket();
else if (RENDER == "led_bezel_stl")  led_bezel();

// ============================================================
// ASSEMBLY PREVIEW
// ============================================================
module assembly() {
    // Base plate
    color("SaddleBrown", 0.85) dock_base();

    // Back wall
    color("Sienna", 0.85)
        translate([0, 0, BASE_T])
            back_wall();

    // Left guide rail
    color("Peru", 0.85)
        translate([0, 0, BASE_T])
            guide_rail("left");

    // Right guide rail (mirror in X)
    color("Peru", 0.85)
        translate([0, 0, BASE_T])
            mirror([1, 0, 0])
                guide_rail("left");

    // ArUco mount
    color("DimGray", 0.85)
        translate([0, 0, BASE_T])
            aruco_mount();

    // PSU bracket
    color("DarkSlateGray", 0.80)
        translate([0, PSU_Y, BASE_T])
            psu_bracket();

    // LED bezel
    color("LightGray", 0.90)
        translate([0, -WALL_T/2, BASE_T + LED_Z])
            led_bezel();

    // Ghost robot receiver approaching from +Y
    %color("SteelBlue", 0.25)
        translate([0, GUIDE_L + 30, BASE_T + POGO_Z])
            cube([RECV_W, 20, 8], center = true);

    // Ghost pogo pins
    for (px = [-POGO_SPACING/2, POGO_SPACING/2])
        %color("Gold", 0.60)
            translate([px, -POGO_PROTRUDE, BASE_T + POGO_Z])
                rotate([90, 0, 0])
                    cylinder(d = POGO_D, h = POGO_L);
}

// ============================================================
// PART A — DOCK BASE PLATE
// ============================================================
module dock_base() {
    difference() {
        // ── Main base block (rounded rect) ──────────────────────────
        linear_extrude(BASE_T)
            minkowski() {
                square([BASE_W - 2*BASE_R,
                        BASE_D - 2*BASE_R], center = true);
                circle(r = BASE_R);
            }

        // ── Ballast pockets (underside) ──────────────────────────────
        // 4× pockets: 2 front, 2 rear
        for (bx = [-1, 1])
        for (by = [-1, 1])
            translate([bx * (BASE_W/2 - BALLAST_INSET_X),
                       by * (BASE_D/2 - BALLAST_INSET_Y),
                       -e])
                cube([BALLAST_W, BALLAST_D, BALLAST_T + e], center = true);

        // ── Floor bolt holes (M8, 4 corners) ────────────────────────
        for (bx = [-1, 1])
        for (by = [-1, 1])
            translate([bx * (BASE_W/2 - FLOOR_BOLT_INSET_X),
                       by * (BASE_D/2 - FLOOR_BOLT_INSET_Y), -e])
                cylinder(d = FLOOR_BOLT_D, h = BASE_T + 2*e);

        // ── Back wall attachment slots (M4, top face) ─────────────────
        for (bx = [-WALL_W/2 + 30, 0, WALL_W/2 - 30])
            translate([bx, -BASE_D/4, BASE_T - 3])
                cylinder(d = M4_D, h = 4 + e);

        // ── Guide rail attachment holes (M4) ──────────────────────────
        for (side = [-1, 1])
        for (gy = [20, GUIDE_L - 20])
            translate([side * (EXIT_GAP/2 + GUIDE_T/2), gy, BASE_T - 3])
                cylinder(d = M4_D, h = 4 + e);

        // ── Cable routing slot (from pogo wires to PSU, through base) ─
        translate([0, -WALL_T - 5, -e])
            cube([WIRE_CH_W, 15, BASE_T + 2*e], center = true);

        // ── Anti-skid texture (front face chamfer) ───────────────────
        // Chamfer front-bottom edge for easy robot approach
        translate([0, BASE_D/2 + e, -e])
            rotate([45, 0, 0])
                cube([BASE_W + 2*e, 5, 5], center = true);
    }
}

// ============================================================
// PART B — BACK WALL (upright panel)
// ============================================================
module back_wall() {
    difference() {
        union() {
            // ── Wall slab ────────────────────────────────────────────
            translate([-WALL_W/2, -WALL_T, 0])
                cube([WALL_W, WALL_T, WALL_H]);

            // ── Pogo pin housing bosses (front face) ─────────────────
            for (px = [-POGO_SPACING/2, POGO_SPACING/2])
                translate([px, -WALL_T, POGO_Z])
                    rotate([90, 0, 0])
                        cylinder(d = POGO_FLANGE_D + 6,
                                 h = POGO_PROTRUDE);

            // ── Wiring channel reinforcement (inside wall face) ───────
            translate([-WIRE_CH_W/2 - 2, -WALL_T, 0])
                cube([WIRE_CH_W + 4, 4, WIRE_CH_H]);
        }

        // ── Pogo pin bores (through wall into housing boss) ───────────
        for (px = [-POGO_SPACING/2, POGO_SPACING/2])
            translate([px, POGO_PROTRUDE + e, POGO_Z])
                rotate([90, 0, 0]) {
                    // Main bore (full depth through wall + boss)
                    cylinder(d = POGO_BORE_D,
                             h = WALL_T + POGO_PROTRUDE + 2*e);
                    // Flange shoulder counterbore (retains pogo from pulling out)
                    translate([0, 0, WALL_T + POGO_PROTRUDE - POGO_FLANGE_T - 1])
                        cylinder(d = POGO_FLANGE_D + 0.4,
                                 h = POGO_FLANGE_T + 2);
                }

        // ── Wiring channel (vertical slot, inside face → base cable hole) ─
        translate([-WIRE_CH_W/2, 0 + e, 0])
            cube([WIRE_CH_W, WALL_T/2, WIRE_CH_H]);

        // ── LED bezel recess (in front face, above pogo) ──────────────
        translate([-LED_BEZEL_W/2, -LED_BEZEL_T, LED_Z - LED_BEZEL_H/2])
            cube([LED_BEZEL_W, LED_BEZEL_T + e, LED_BEZEL_H]);

        // ── M4 base attachment bores (3 through bottom of wall) ───────
        for (bx = [-WALL_W/2 + 30, 0, WALL_W/2 - 30])
            translate([bx, -WALL_T/2, -e])
                cylinder(d = M4_D, h = 8 + e);

        // ── Cable tie slots (in wall body, for neat wire routing) ─────
        for (cz = [15, POGO_Z - 15])
            translate([WIRE_CH_W/2 + 3, -WALL_T/2, cz])
                cube([4, WALL_T + 2*e, 3], center = true);

        // ── Lightening cutout (rear face pocket) ──────────────────────
        translate([-WALL_W/2 + 40, 0, 20])
            cube([WALL_W - 80, WALL_T/2 + e, WALL_H - 30]);
    }
}

// ============================================================
// PART C — V-GUIDE RAIL
// ============================================================
// Print 2×; mirror in X for right side.
// Rail tapers from ENTRY_GAP/2 (at Y=GUIDE_L) to EXIT_GAP/2 (at Y=0).
// Inner (guiding) face is angled; outer face is vertical.
module guide_rail(side = "left") {
    // Inner face X at back wall  = EXIT_GAP/2
    // Inner face X at entry      = ENTRY_GAP/2
    x_back  = EXIT_GAP/2;        // 16 mm
    x_entry = ENTRY_GAP/2;       // 35 mm

    difference() {
        union() {
            // ── Main wedge body ─────────────────────────────────────
            // Hull between two rectangles: narrow at Y=0, wide at Y=GUIDE_L
            hull() {
                // Back end (at Y=0, flush with back wall)
                translate([x_back, 0, 0])
                    cube([GUIDE_T, e, GUIDE_H]);
                // Entry end (at Y=GUIDE_L)
                translate([x_entry, GUIDE_L, 0])
                    cube([GUIDE_T, e, GUIDE_H]);
            }

            // ── Entry flare (chamfered lip at guide entry for bump-entry) ─
            hull() {
                translate([x_entry, GUIDE_L, 0])
                    cube([GUIDE_T, e, GUIDE_H]);
                translate([x_entry + 15, GUIDE_L + 20, 0])
                    cube([GUIDE_T, e, GUIDE_H * 0.6]);
            }
        }

        // ── M4 base attachment bores ─────────────────────────────────
        for (gy = [20, GUIDE_L - 20])
            translate([x_back + GUIDE_T/2, gy, -e])
                cylinder(d = M4_D, h = 8 + e);

        // ── Chamfer on inner top corner (smooth robot entry) ─────────
        translate([x_back - e, -e, GUIDE_H - 5])
            rotate([0, -45, 0])
                cube([8, GUIDE_L + 30, 8]);
    }
}

// ============================================================
// PART D — ArUco MARKER MOUNT
// ============================================================
// Free-standing mast at dock entry. Mounts to base plate.
// Marker face tilted 15° toward approaching robot.
// Accepts 100×100 mm printed/laminated paper marker in slot.
module aruco_mount() {
    frame_w  = ARUCO_MARKER_W + 2*ARUCO_FRAME_BDR;
    frame_h  = ARUCO_MARKER_H + 2*ARUCO_FRAME_BDR;
    mast_y   = ARUCO_Y;

    union() {
        // ── Mast column ───────────────────────────────────────────────
        translate([-ARUCO_MAST_W/2, mast_y - ARUCO_MAST_W/2, 0])
            cube([ARUCO_MAST_W, ARUCO_MAST_W, ARUCO_MAST_H]);

        // ── Marker frame (tilted back ARUCO_TILT°) ────────────────────
        translate([0, mast_y, ARUCO_MAST_H])
            rotate([-ARUCO_TILT, 0, 0]) {
                difference() {
                    // Frame plate
                    translate([-frame_w/2, -ARUCO_FRAME_T, -frame_h/2])
                        cube([frame_w, ARUCO_FRAME_T, frame_h]);

                    // Marker window (cutout for marker visibility)
                    translate([-ARUCO_MARKER_W/2, -ARUCO_FRAME_T - e,
                               -ARUCO_MARKER_H/2])
                        cube([ARUCO_MARKER_W,
                              ARUCO_FRAME_T + 2*e,
                              ARUCO_MARKER_H]);

                    // Marker slip-in slot (insert from side)
                    translate([-frame_w/2 - e,
                               -ARUCO_SLOT_T - 0.3,
                               -ARUCO_MARKER_H/2])
                        cube([frame_w + 2*e,
                              ARUCO_SLOT_T + 0.3,
                              ARUCO_MARKER_H]);
                }
            }

        // ── Mast base foot (M4 bolts to dock base) ────────────────────
        difference() {
            translate([-20, mast_y - 20, 0])
                cube([40, 40, 5]);
            for (fx = [-12, 12]) for (fy = [-12, 12])
                translate([fx, mast_y + fy, -e])
                    cylinder(d = M4_D, h = 6 + e);
        }
    }
}

// ============================================================
// PART E — PSU BRACKET
// ============================================================
// Open-top retention bracket for PSU module.
// PSU slides in from top; 2× M3 straps or cable ties retain it.
// Bracket bolts to base plate via 4× M4 screws.
module psu_bracket() {
    difference() {
        union() {
            // ── Outer bracket box (open top) ─────────────────────────
            _box_open_top(PSU_W + 2*PSU_T,
                          PSU_D + 2*PSU_T,
                          PSU_H + PSU_T);

            // ── Base flange ──────────────────────────────────────────
            translate([-(PSU_W/2 + PSU_T + 8),
                       -(PSU_D/2 + PSU_T + 8), -PSU_T])
                cube([PSU_W + 2*PSU_T + 16,
                      PSU_D + 2*PSU_T + 16, PSU_T]);
        }

        // ── PSU cavity ───────────────────────────────────────────────
        translate([0, 0, PSU_T])
            cube([PSU_W, PSU_D, PSU_H + e], center = true);

        // ── Ventilation slots (sides) ─────────────────────────────────
        for (a = [0, 90, 180, 270])
            rotate([0, 0, a])
                translate([0, (PSU_D/2 + PSU_T)/2, PSU_H/2 + PSU_T])
                    for (sz = [-PSU_H/4, 0, PSU_H/4])
                        translate([0, 0, sz])
                            cube([PSU_W * 0.5, PSU_T + 2*e, 5],
                                 center = true);

        // ── Cable exit slot (bottom) ──────────────────────────────────
        translate([0, 0, -e])
            cube([15, PSU_D + 2*PSU_T + 2*e, PSU_T + 2*e],
                 center = true);

        // ── Base flange M4 bolts ──────────────────────────────────────
        for (fx = [-1, 1]) for (fy = [-1, 1])
            translate([fx * (PSU_W/2 + PSU_T + 4),
                       fy * (PSU_D/2 + PSU_T + 4),
                       -PSU_T - e])
                cylinder(d = M4_D, h = PSU_T + 2*e);

        // ── Cable tie slots ───────────────────────────────────────────
        for (sz = [PSU_H/3, 2*PSU_H/3])
            translate([0, 0, PSU_T + sz])
                cube([PSU_W + 2*PSU_T + 2*e, 4, 4], center = true);
    }
}

module _box_open_top(w, d, h) {
    difference() {
        cube([w, d, h], center = true);
        translate([0, 0, PSU_T + e])
            cube([w - 2*PSU_T, d - 2*PSU_T, h], center = true);
    }
}

// ============================================================
// PART F — LED STATUS BEZEL
// ============================================================
// 4 × 5 mm LEDs in a row. Press-fits into recess in back wall.
// LED labels (L→R): SEARCHING | ALIGNED | CHARGING | FULL
// Suggested colours:     Red  |  Yellow  |   Blue   | Green
module led_bezel() {
    difference() {
        // Bezel plate
        cube([LED_BEZEL_W, LED_BEZEL_T, LED_BEZEL_H], center = true);

        // 4× LED bores
        for (i = [-1.5, -0.5, 0.5, 1.5])
            translate([i * LED_SPACING, -LED_BEZEL_T - e, 0])
                rotate([90, 0, 0]) {
                    // LED body bore (recess, not through)
                    cylinder(d = LED_BORE_D + 1,
                             h = LED_INSET_D + e);
                    // LED pin bore (through bezel)
                    translate([0, 0, LED_INSET_D])
                        cylinder(d = LED_BORE_D,
                                 h = LED_BEZEL_T + 2*e);
                }

        // Label recesses between LEDs (for colour-dot stickers or printed inserts)
        for (i = [-1.5, -0.5, 0.5, 1.5])
            translate([i * LED_SPACING, LED_BEZEL_T/2, LED_BEZEL_H/2 - 3])
                cube([LED_SPACING - 3, 1 + e, 5], center = true);

        // M3 mounting holes (2× into back wall)
        for (mx = [-LED_BEZEL_W/2 + 6, LED_BEZEL_W/2 - 6])
            translate([mx, -LED_BEZEL_T - e, 0])
                rotate([90, 0, 0])
                    cylinder(d = M3_D, h = LED_BEZEL_T + 2*e);
    }
}
