// CieloTrack receiver enclosure — CARRIER-BOARD, EXTERNAL-DIPOLE, VERTICAL-WALL mount. // Ears moved to the -Y (front) face so the box hangs floor-down on a vertical wall: // drain stays at the true low point, antennas point up-and-out on the far (+Y) side. // Same board fit as cielotrack-carrier-case.scad, but instead of radiating through a // plain front wall it carries TWO tilted SMA bulkheads on the back wall: a u.FL->SMA // pigtail runs from each module out to a screw-on 2.4GHz dipole. The two antennas are // spaced apart to cut BLE/Wi-Fi desense. // // --- (based on the on-board-antenna revision below) --- // CieloTrack receiver enclosure — CARRIER-BOARD revision. // // FINALIZED 2026-08-24 against the routed Flux board: outline 60x30 mm and the four // M3 mounting holes at (+/-26.5, +/-11.5) mm are confirmed, not placeholders. // // This supersedes cielotrack-case.scad (which was built for the STACKED, hand-wired // pair). The receiver is now one flat PCB: two Seeed XIAO ESP32-S3 modules side by // side on a 60 x 30 mm carrier (Flux project "Cielo Track Receiver"), a 2-pin power // header, and four M3 mounting holes. So the box is short and wide, not tall. // // openscad -D 'part="base"' -o base.stl cielotrack-carrier-case.scad // openscad -D 'part="lid"' -o lid.stl cielotrack-carrier-case.scad // // Print in PETG or ASA, never PLA: an enclosed box in the Texas sun passes PLA's // softening point and sags. Never carbon-filled filament — it is conductive and will // shield and detune the two on-board antennas, and the board will look healthy while // hearing less. /* [Board — from the finished Flux PCB] */ board_l = 60.0; // X - CONFIRMED from the Flux board outline board_w = 30.0; // Y - CONFIRMED from the Flux board outline board_t = 1.6; // PCB thickness // Tallest thing standing above the PCB. Two XIAOs SOCKETED on 2x7 female headers // dominate this. If you solder the modules flat instead, drop this to about 4. comp_h = 12.0; // MEASURE the populated board and set this /* [Board mounting — MATCH the four M3 holes in Flux] */ // CONFIRMED from Flux: four M3 NPTH holes at (+/-26.5, +/-11.5) mm, 3.2 mm dia // (H1 Top-Left, H2 Top-Right, H3 Bottom-Left, H4 Bottom-Right). inset 3.5 reproduces // them exactly (30-3.5=26.5, 15-3.5=11.5). The board holes are clearance; the M3 screw // passes through them and threads into the boss pilot below. hole_inset_x = 3.5; hole_inset_y = 3.5; boss_h = 4.0; // standoff under the board: clears the through-hole pin tails below boss_d = 6.0; boss_pilot = 2.6; // pilot for an M3 self-tapper (or open to 4.0 for a heat-set insert) /* [Fit] */ clearance = 1.0; // around the board, per side wall = 2.4; // 3 perimeters at a 0.4 nozzle; weatherproof rather than minimal floor_t = 2.4; lid_t = 2.4; lip_h = 3.0; // lid skirt depth lip_gap = 0.25; // printed fit; loosen to 0.35 if your printer runs tight /* [Which edge faces out] */ // The XIAO USB-C and its on-board 2.4 GHz antenna share the module's short end, and // both modules face the SAME board edge. That edge is the "front": RF radiates through // it, and it is where the USB ports sit. front = -1 is the -Y wall, +1 the +Y wall. // Flip it to match however the board sits in your build. front = -1; /* [Openings] */ // USB: default is NO permanent USB holes — you flash by lifting the lid (occasional), // and a sealed box beats two more water paths. Set usb_slots=true for standing ports. usb_slots = false; usb_offset = 13.0; // |X| of each module's USB port from board centre — VERIFY on PCB usb_w = 11.0; // USB-C plug body plus a cable boot usb_slot_h = 8.0; // Power (the 2-pin header) leaves through one gland low on the BACK wall, so water // cannot track in along the cable. J1 sits at the board's front-centre, so the power // wires make a short internal run back to the gland (kept clear of the antennas). gland_d = 6.0; gland_from_bottom = 5.0; vent_d = 3.0; // low on both short walls; nothing open facing up drain_d = 4.0; // one hole at the lowest point /* [External dipole antennas] */ // Two angled SMA bulkheads on the BACK wall. Tilted up, never straight up: it sheds water // and this site's signal peaks around 30-40 deg elevation. Spaced apart so the two 2.4GHz // antennas do not couple. Each takes a u.FL->SMA pigtail from one module's u.FL connector. sma_d = 6.6; // bulkhead nut bore (typical 6.5 mm) - check yours sma_tilt = 35; // degrees up from horizontal sma_boss_d = 13.0; // flat seat for the nut sma_boss_h = 4.5; sma_sep = 40.0; // centre-to-centre spacing of the two antennas /* [Mounting ears] */ ear_t = 3.0; ear_r = 7.0; screw_d = 4.4; // M4 wall-mount, outside the sealed volume /* [Render] */ part = "both"; // "base" | "lid" | "both" $fn = 48; // ---- derived ------------------------------------------------------------------ inner_l = board_l + 2*clearance; inner_w = board_w + 2*clearance; inner_h = boss_h + board_t + comp_h + 2; // 2 mm headroom under the lid outer_l = inner_l + 2*wall; outer_w = inner_w + 2*wall; outer_h = inner_h + floor_t; hx = board_l/2 - hole_inset_x; hy = board_w/2 - hole_inset_y; holes = [[hx,hy],[hx,-hy],[-hx,hy],[-hx,-hy]]; module rrect(l,w,h,r=2.5){ hull() for(x=[r-l/2,l/2-r], y=[r-w/2,w/2-r]) translate([x,y,0]) cylinder(r=r,h=h); } module base(){ difference(){ union(){ rrect(outer_l, outer_w, outer_h); // wall-mount ears on the -Y face: flat tabs that lie against a vertical wall for(s=[-1,1]) translate([s*(outer_l/2 + ear_r - 1.5), -outer_w/2, outer_h/2]) rotate([-90,0,0]) cylinder(r=ear_r, h=ear_t); // two angled SMA bosses on the back wall, spaced apart for(sx=[-1,1]) translate([sx*sma_sep/2, outer_w/2 - 1, floor_t + inner_h/2]) rotate([90 - sma_tilt, 0, 0]) cylinder(d=sma_boss_d, h=sma_boss_h*2, center=true); } // cavity translate([0,0,floor_t]) rrect(inner_l, inner_w, inner_h + 1, r=2.0); // USB-C openings in the front wall (optional) if(usb_slots) for(sx=[-1,1]) translate([sx*usb_offset, front*outer_w/2, floor_t + boss_h + board_t + usb_slot_h/2]) cube([usb_w, wall*3, usb_slot_h], center=true); // power-cable gland, low on the back wall (opposite the RF/USB front) translate([0, -front*outer_w/2, floor_t + gland_from_bottom]) rotate([90,0,0]) cylinder(d=gland_d, h=wall*3, center=true); // vents low on both short walls for(s=[-1,1]) for(i=[-1,0,1]) translate([s*outer_l/2, i*8, floor_t + 3]) rotate([0,90,0]) cylinder(d=vent_d, h=wall*3, center=true); // SMA bores through the angled bosses (back wall), tilted up for(sx=[-1,1]) translate([sx*sma_sep/2, outer_w/2 - 1, floor_t + inner_h/2]) rotate([90 - sma_tilt, 0, 0]) cylinder(d=sma_d, h=40, center=true); // drain at the lowest point translate([0,0,-1]) cylinder(d=drain_d, h=floor_t+2); // ear screw holes (through the -Y-face tabs, into the wall) for(s=[-1,1]) translate([s*(outer_l/2 + ear_r - 1.5), -outer_w/2 + 1, outer_h/2]) rotate([-90,0,0]) cylinder(d=screw_d, h=ear_t+2); } // board-support bosses (added AFTER the difference so their pilots survive the cavity) for(h=holes) translate([h[0], h[1], floor_t]) difference(){ cylinder(d=boss_d, h=boss_h); translate([0,0,-1]) cylinder(d=boss_pilot, h=boss_h+2); } } module lid(){ difference(){ union(){ rrect(outer_l, outer_w, lid_t); // skirt that drops into the cavity translate([0,0,-lip_h]) rrect(inner_l - lip_gap*2, inner_w - lip_gap*2, lip_h, r=2.0); } // hollow the skirt so it is a lip, not a plug translate([0,0,-lip_h-0.5]) rrect(inner_l - lip_gap*2 - 2*1.6, inner_w - lip_gap*2 - 2*1.6, lip_h+1, r=1.2); } } if (part == "base") base(); else if (part == "lid") lid(); else { base(); translate([0, outer_w + 6, 0]) lid(); }