//
//! Environmental monitor using Enviro+ sensor board and a Raspberry Pi Zero.
//
// GNU GPL v2
// nop.head@gmail.com
// hydraraptor.blogspot.com
//
// Top level model
//
$explode = 0; // [0, 1]
assembly = "main"; // [main, back, RPI, case, enviro_case, RPI_case]

/* [Hidden] */

show_box = true;
show_enviroplus = true;
$pp1_colour = "grey";
$extrusion_width = 0.5;

include <NopSCADlib/core.scad>
include <NopSCADlib/vitamins/pin_headers.scad>
include <NopSCADlib/vitamins/pcbs.scad>
include <NopSCADlib/vitamins/fans.scad>

use <NopSCADlib/vitamins/insert.scad>
use <NopSCADlib/vitamins/veroboard.scad>
use <NopSCADlib/utils/round.scad>
use <NopSCADlib/printed/foot.scad>
use <NopSCADlib/printed/printed_box.scad>
use <fan_controller.scad>

rpi = RPI0;
pcb = EnviroPlus;
fan = fan17x8;
foot = Foot(d = 13, h = 5, t = 2, r = 1, screw = M3_pan_screw);
module foot_stl() foot(foot);

rpi_offset = [0, -pcb_width(pcb) / 2 + 0.5, pcb_width(rpi) / 2 + 8.6];
rpi_holes = [0, 1];
wall = 2;
top_thickness = 2;
base_thickness = 2;
case_inner_rad = 8;

width = pcb_length(pcb) + 9 + 10;
pcb_x = -(width - pcb_length(pcb) - 9) / 2;
depth = pcb_width(pcb) + 9;
height = pcb_width(rpi) / 2 + rpi_offset.z + 9;
bulkhead_outset = 2;
bulkhead_inset = 0.5;
bulkhead_t = 2;
clearance = 0.1;
pcb_screw = alternate_screw(hs_pan, pcb_screw(pcb));

box = pbox(name = "enviro_plus_case", wall = wall, top_t = top_thickness, base_t = base_thickness, screw = M2_cap_screw, radius = case_inner_rad, ridges = [8, 1],
    size = [width,  depth, height]);

pms5003 = [50, 38, 21];
pms5003_wall = 1;
pms5003_pos = [width / 2 - pms5003.x / 2 - pms5003_wall - pbox_screw_inset(box) - insert_boss_radius(screw_insert(pbox_screw(box)), wall) - 0.5,
               depth / 2 - pms5003.z / 2 - wall - 1,
               height + top_thickness - pms5003.y / 2];


fan_duct_t = 1.7;
foam_t = 0;
fan_pos = [pms5003_pos.x - pms5003.x / 2 - pms5003_wall - fan_width(fan) / 2 - fan_duct_t, 1 + foam_t, 12 + top_thickness + fan_depth(fan) / 2];
fan_socket = 8;

module fan_shape() {
    w = fan_width(fan);
    p = fan_hole_pitch(fan);
    r = w / 2 - p;

    rounded_square([w, w], r);
}

module fan_duct_stl() {
    module frame(z, offset, y = -fan_pos.y)
        translate([0, y, z])
            linear_extrude(height = eps)
                offset(offset)
                    fan_shape();

    z = height + top_thickness - fan_pos.z + fan_depth(fan) / 2 - fan_socket;

    translate([0, -fan_pos.y, z])
        linear_extrude(height = fan_socket)
            difference() {
                fan_shape();

                round(1)
                    offset(-fan_duct_t)
                        fan_shape();
            }

    stl("fan_duct") union() {
        difference() {
            hull() {
                frame(z, 0);

                frame(fan_depth(fan), fan_duct_t + foam_t, 0);
            }
            hull() {
                frame(z, -fan_duct_t);

                frame(fan_depth(fan), foam_t -extrusion_width / 2, 0);
            }
        }

        linear_extrude(height = fan_depth(fan))
            difference() {
                offset(fan_duct_t + foam_t)
                    fan_shape();

                offset(foam_t)
                    fan_shape();
            }
    }
}

module pms5003() {
    vitamin("pms5003(): PMS5003 particle detector");

    w = pms5003.x;
    d = pms5003.y;
    h = pms5003.z;
    t = 0.1;
    color("silver")
        difference() {
            linear_extrude(height = h, center = true)
                difference() {
                    square([w, d], center = true);

                    for(x = [-1, 1], y = [-1, 1])
                        translate([x * w / 2, y * d / 2])
                            square(2, center = true);
                }
            cube([w + 2 * eps, d + 2 * eps, t * 2], center = true);
         }

    color("black")
        rounded_rectangle([w - 2 * t, d - 2 * t, h - 2 * t], r = 1, center = true);
}

module pcb_shape(offset)
    rounded_square([pcb_length(pcb) + 2 * offset, pcb_width(pcb) + 2 * offset], pcb_radius(pcb) + offset);

module gap() {
    gap = pcb_component_position(pcb, "-chip");

    translate([gap.x, -gap.y])
        square([17.5, 20], center = true);
}

module rpi_holes()
    for(i = rpi_holes) {
        hole = pcb_coord(rpi, pcb_holes(rpi)[i]);
        translate([hole.x, rpi_offset.y, -hole.y + rpi_offset.z + top_thickness])
            rotate([90, 0, 0])
                children();
    }

module feet_positions() {
    clearance = 2;
    foot_r = foot_diameter(foot) / 2;
    x_inset = case_inner_rad + foot_r - pbox_ridges(box).y;
    z_inset = foot_r + clearance;
    h = height + base_thickness;

    for(p = [[-1, -1], [1, -1], [0, 1]])
        translate([p.x * (width / 2 - x_inset), depth / 2 + wall + pbox_ridges(box).y, top_thickness + h / 2 + p.y * (h / 2 - z_inset)])
            rotate([90, 0, 0])
                children();
}

module slots(h = 10, expand = 0) {
    for(comp = [["uSD", 2, 11, 0.7], ["flat_flex", 1, 12, 0.5]]) {
        p = pcb_component_position(rpi, comp[0]);
        translate([sign(p.x) * (width / 2 + wall / 2 + pbox_ridges(box).y / 2), rpi_offset.y + pcb_thickness(rpi) + comp[3], top_thickness + rpi_offset.z - p.y])
            rotate([90, 0, 90])
                hull() {
                    vertical_tearslot(r = comp[1] + expand, l = comp[2], h = h);

                    if(expand)
                         vertical_tearslot(r = comp[1] + expand + h / 2, l = comp[2], h = eps);
                }
     }
}

module box_internal_additions() {
    translate([pcb_x, 0]) {
        linear_extrude(height = top_thickness + bulkhead_t)
            difference() {
                pcb_shape(bulkhead_outset + wall + pbox_ridges(box).y + eps);

                gap();

                pcb_shape(bulkhead_outset + clearance);
            }

        rpi_holes()
            hull() {
                wall = 1.8;
                r =  screw_radius(pcb_screw) + wall;
                z = depth / 2 + rpi_offset.y + pbox_ridges(box).y + eps;
                offset = max(r, z);
                cylinder(r =r, h = z);

                translate([0, -offset - r, z])
                    cube(eps);
            }
    }
    d = washer_diameter(screw_washer(foot_screw(foot))) + 1;
    h = pbox_ridges(box).y;
    feet_positions()
        translate_z(wall - eps)
            cylinder(d2 = d, d1 = d + 2 * h, h = h);

    slots(wall + pbox_ridges(box).y + eps, 1);
}

module box_external_additions() {
    amp = pbox_ridges(box).y + eps;
    d = foot_diameter(foot) + 1;
    feet_positions()
        cylinder(d1 = d, d2 = d + 2 * amp, h = amp);

    slots(wall + pbox_ridges(box).y + eps, 1);
}


module box_holes() {
    translate([pcb_x, 0]) {
        linear_extrude(height = top_thickness + eps)
            pcb_shape(clearance);

        rpi_holes()
            teardrop_plus(r = screw_pilot_hole(pcb_screw), h = 7 - 1.6, center = false);
    }
    feet_positions()
        teardrop(r = screw_pilot_hole(foot_screw(foot)), h = 10, center = true);

    grills = 7;
    bar = 1.6;
    x = pcb_x + pcb_length(pcb) / 2 + 2 * wall +  pbox_ridges(box).y + 2 * eps;
    w = width / 2 - x;
    d = depth - case_inner_rad;
    pitch = d / (grills - 1);
    for(i = [0 : grills -1])
        let(w = i == 0 || i == grills -1 ? w - 1 : w)
            translate([x + w / 2, -d / 2 + i * pitch])
                rounded_rectangle([w, pitch - bar, 10], 1);

    slots();
}

module bulkhead_stl() {
    holes = pcb_holes(pcb);
    pitch = max([for(h = holes) pcb_coord(pcb, h).x]) - min([for(h = holes)  pcb_coord(pcb, h).x]);
    boss_r = (pitch - 51.4) / 2;

    stl("bulkhead")
        linear_extrude(height = bulkhead_t)
            difference() {
                pcb_shape(bulkhead_outset);

                gap();

                difference() {
                    pcb_shape(-bulkhead_inset);

                    pcb_screw_positions(pcb)
                        rotate($i * 90)
                            hull() {
                                circle(boss_r);

                                translate([boss_r - eps, -boss_r])
                                    square([eps, boss_r]);

                                translate([-boss_r, boss_r - eps])
                                    square([boss_r, eps]);

                                translate([-boss_r, -boss_r])
                                    square(eps);
                            }
                }

                pcb_screw_positions(pcb)
                    poly_circle(screw_clearance_radius(pcb_screw));
            }
}

module enviro_plus_case_stl() {
     pbox(box) {
        box_internal_additions();
        box_holes();
        box_external_additions();
     }
}

module base_additions() {
    w = pms5003.x + clearance;
    d = pms5003.z + clearance;
    wall = pms5003_wall;

    translate([pms5003_pos.x, -pms5003_pos.y])
        linear_extrude(height = base_thickness + pms5003.y)
            difference() {
                square([w + 2 * wall, d + 2 * wall], center = true);
                square([w, d], center = true);
            }

    translate([fan_pos.x, 0])
        linear_extrude(height = top_thickness + fan_socket)
            difference() {
                offset(wall)
                    fan_shape();

                fan_shape();
            }
}

module base_holes() {
    for(i = [0 : 1]) {
        p = pcb_component_position(rpi, "usb_uA", i);
        translate([p.x + pcb_x, -rpi_offset.y - pcb_thickness(rpi) - 1.3])
            rounded_rectangle([11,7, 100], 0.5, true);
    }

    translate([pms5003_pos.x, -pms5003_pos.y])
        rounded_rectangle([pms5003.x - 2 ,pms5003.z - 2, 100], 2, true);

    translate([fan_pos.x, 0])
        linear_extrude(height = 2 * base_thickness + eps, center = true)
            round(1)
                offset(-fan_duct_t)
                    fan_shape();
}

module enviro_plus_case_base_stl()
    pbox_base(box) {
        base_additions();
        base_holes();
    }

//! * Solder a right angle connector to the Raspberry Pi Zero.
module RPI_assembly()
assembly("RPI") {
    pcb(rpi);

    pcb_grid(rpi, 9.5, 0.5, -0.6)
        rotate(180)
            explode(20)
                pin_header(2p54header, 20, 2, right_angle = true);
}

//! 1. Solvent weld or glue the bulkhead into the recess in the bottom of the case.
//! 1. Fit the heatfit inserts with a soldering iron with a conical bit heated to about 200&deg;C.
//! 1. Tap the three holes for the feet with an M3 tap.
//! 1. Screw on the three feet with M3 x 6mm pan screws and washers.
module case_assembly() pose([ 29.60, 0.00, 158.00 ], exploded = true) pose([ 198.00, 0.00, 352.80 ], exploded = false)
assembly("case") {
    if(show_box)
        stl_colour(pp1_colour) render() enviro_plus_case_stl();
    else
        render() difference() {
            enviro_plus_case_stl();

            translate_z(height / 2)
                cube([100, 100, height - 20], center = true);
        }

    pbox_inserts(box);

    translate([pcb_x, 0, top_thickness])
        explode(20)
            stl_colour(pp2_colour) render() bulkhead_stl();

    feet_positions() {
        foot_assembly(0, foot);

        vflip()
            explode(20, explode_children = true)
                translate_z(foot_thickness(foot))
                    screw_and_washer(foot_screw(foot), 6);
    }

}

//! * Solder the fan_controller to the Enviro+ expansion connector at the 5V, GND and #4 pins.
module enviro_assembly() hflip(exploded())
assembly("enviro") {
    if(show_enviroplus)
        pcb(pcb);

    pcb_grid(pcb, 5.5, 2.5, -pcb_thickness(pcb)) {
        hflip()
            explode(15)
                fan_controller_assembly();
    }
    if(!exploded())
        for(x = [8, 7, 3])
            pcb_grid(pcb, x, 0, 0.05)
                solder_meniscus(pcb, 0.39, 1);
}

//! * Screw the Enviro+ PCB to the front of the case using M2.5 x 8mm pan screws with washer and nuts on the inside.
module enviro_case_assembly() pose([ 231.40, 0.00, 1.20 ])
assembly("enviro_case") {
    case_assembly();

    translate([pcb_x, 0, top_thickness])
        vflip() explode(20, explode_children = true) {
            t = pcb_thickness(pcb);
            nut = screw_nut(pcb_screw);
            washer = screw_washer(pcb_screw);

            no_explode() enviro_assembly();

            pcb_screw_positions(pcb) {
                translate_z(t)
                    screw(pcb_screw, screw_longer_than(t + bulkhead_t + nut_thickness(nut, true) + washer_thickness(washer)));

                translate_z(-bulkhead_t)
                    vflip()
                        explode(20, explode_children = true)
                            nut_and_washer(nut, true);
            }
        }
}

//! * Plug the RPi into the Enviro+ socket and secure with two screws self tapped into the bosses in the case.
module RPI_case_assembly() pose([ 20.50, 0.00, 153.30 ])
assembly("RPI_case") {
    enviro_case_assembly();

    translate([pcb_x, 0]) {
        translate_z(top_thickness) {

            translate(rpi_offset)
                rotate([-90, 0, 0])
                    RPI_assembly();
        }
        rpi_holes()
            vflip()
                translate_z(pcb_thickness(rpi))
                    screw(pcb_screw, 6.4);
    }
}

//! 1. Print the fan duct in flexible TPE with low infill.
//! 1. Slide the pms5003 into the printed receptacle with the fan to the outside. Secure with tape if it is loose.
//! 1. Slide the fan into the fan duct.
//! 1. Slide the fan duct into the printed recepacle.
module back_assembly() pose([ 228.60, 0.00, 24.30 ])
assembly("back") {
    translate(pms5003_pos)
        explode(-50)
            rotate([90, 0, 0])
                pms5003();

    explode(-20, explode_children = true)
        translate(fan_pos) {
            explode(-20)
                fan(fan);

            translate_z(-fan_depth(fan) / 2)
                stl_colour(pp4_colour)
                    render()
                        fan_duct_stl();
        }

    translate_z(height + top_thickness + base_thickness + eps) vflip()
        stl_colour(show_box ? pp1_colour : pp2_colour) render() enviro_plus_case_base_stl();
}

//! * Solder the fan wires to the veroboard assembly
//!
//! ![FanWires](docs/fan_connection.jpg)
//!
//! * Slide the back assembly into the case and secure with four M2 x 6mm cap screws and washers.
module main_assembly() pose([170, 335, 0], exploded = false) pose([ 42.20, 0.00, 159.60 ], exploded = true)
assembly("main") {
    RPI_case_assembly();

    explode(20, explode_children = true) {
        pbox_base_screws(box);

        back_assembly();
    }
}

if($preview) {
    if(assembly == "main")
        main_assembly();
    else if(assembly == "case")
        case_assembly();
    else if(assembly == "back")
        back_assembly();
    else if(assembly == "RPI")
        RPI_assembly();
    else if(assembly == "RPI_case")
        RPI_case_assembly();
    else if(assembly == "enviro_case")
        enviro_case_assembly();
}
else {
    gap = 5;

    enviro_plus_case_stl();

    translate([0, -depth - gap])
        enviro_plus_case_base_stl();

    translate([0, -2 * (depth + gap)])
        bulkhead_stl();
}
