├── .gitattributes ├── ha_rpi_case.scad ├── ha_rpi_case_mk0_frame.stl ├── ha_rpi_case_mk1_frame.stl ├── ha_rpi_case_mk2_cameramount_p1.stl ├── ha_rpi_case_mk2_cameramount_p2.stl ├── ha_rpi_case_mk2_face.stl ├── ha_rpi_case_mk2_frame.stl ├── ha_rpi_case_mk2_frame_poe.stl ├── ha_rpi_case_mk2_sensormount.stl ├── ha_rpi_case_mk2_shell.stl ├── ha_rpi_kiosk_control.py └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.stl filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /ha_rpi_case.scad: -------------------------------------------------------------------------------- 1 | /* 2 | * Raspberry Pi Wall Mount case 3 | * Licenced under CC BY-NC-SA 4.0 4 | * By: TSnoad 5 | * https://github.com/tsnoad/rpi_wall_case 6 | * https://hackaday.io/project/197028-raspberry-pi-wall-panel 7 | */ 8 | 9 | $fn = 18; //rendering resolution 10 | 11 | m3_v_r = (3+0.5-0.1)/2; //radius of m3 bolts, including clearance 12 | 13 | casecrn_rad = 20; //radius of the corners of the case 14 | 15 | case_proj = 35; //how far does the case stick out 16 | case_tilt = -5; //how tilted is the case 17 | 18 | faceplate_thk = 2; //how thick is the facplate 19 | 20 | 21 | //assembled(); 22 | 23 | /*components = [ 24 | "ha_rpi_case_mk2_frame", 25 | "ha_rpi_case_mk2_shell", 26 | "ha_rpi_case_mk2_cameramount_p1", 27 | "ha_rpi_case_mk2_cameramount_p2", 28 | "ha_rpi_case_mk2_sensormount", 29 | "ha_rpi_case_mk2_face", 30 | ];*/ 31 | 32 | printable(/*components*/); 33 | 34 | 35 | module printable(components=[]) { 36 | *frame(); 37 | *translate([0,200,0]) walls(); 38 | 39 | *rotate([0,180,0]) translate([0,200+50,12-(3+0.6+1)]) camera_plate(); 40 | *translate([0,200+50+30,12-(3+0.6+1)]) camera_plate2(); 41 | *translate([0,200+50+30+185,6.2]) sensor_plate(); 42 | 43 | *trans_tilt() faceplate(); 44 | //echo(search(components, "ha_rpi_case_mk2_frame")); 45 | } 46 | 47 | module assembled() { 48 | frame(); 49 | *walls(); 50 | 51 | *trans_tilt() camera_plate(); 52 | *trans_tilt() camera_plate2(); 53 | *trans_tilt() sensor_plate(); 54 | 55 | *trans_tilt() faceplate(); 56 | 57 | //placeholder components 58 | *union() { 59 | trans_tilt() rotate([0,0,0]) screen_co(); 60 | translate([0,0,8]) poe_co(); 61 | trans_tilt() camera_pos() camera_co(); 62 | rasp_trans() rotate([0,0,-90]) translate([-56/2,-85/2,4+2.8]) { 63 | translate([0,85-20,0]) cube([56,20,17.4]); 64 | cube([56,85,5]); 65 | } 66 | } 67 | } 68 | 69 | /* 70 | $vpr = [90-7.5, 0, 30]; 71 | $vpt = [0, -25, -12.5]; 72 | $vpd = 650; 73 | $t=1; 74 | 75 | rotate([90,0,0]) union() { 76 | drop_assmb(1) import("/Users/tsnoad/Desktop/3d Parts/Homeassistant Case/ha_rpi_case_mk2_frame.stl"); 77 | 78 | 79 | drop_assmb(3) trans_tilt() import("/Users/tsnoad/Desktop/3d Parts/Homeassistant Case/ha_rpi_case_mk2_cameramount_p1.stl"); 80 | drop_assmb(4) trans_tilt() import("/Users/tsnoad/Desktop/3d Parts/Homeassistant Case/ha_rpi_case_mk2_cameramount_p2.stl"); 81 | 82 | 83 | drop_assmb(2) import("/Users/tsnoad/Desktop/3d Parts/Homeassistant Case/ha_rpi_case_mk2_shell.stl"); 84 | 85 | drop_assmb(5) trans_tilt() import("/Users/tsnoad/Desktop/3d Parts/Homeassistant Case/ha_rpi_case_mk2_sensormount.stl"); 86 | 87 | *drop_assmb(6) trans_tilt() import("/Users/tsnoad/Desktop/3d Parts/Homeassistant Case/ha_rpi_case_mk2_face.stl"); 88 | } 89 | 90 | module drop_assmb(num) { 91 | translate([0,0,500*pow(min(0,$t-(num+3)/(6+3)),2)]) children(); 92 | } 93 | */ 94 | 95 | 96 | 97 | 98 | module frame() difference() { 99 | union() { 100 | base(); 101 | bosses(); 102 | } 103 | 104 | base_co(); 105 | 106 | poe_pos() translate([0,0,8+0.01]) poe_co(); 107 | 108 | trans_tilt() presence_sensor_pos() translate([0,0,0.01]) presence_sensor_co(); 109 | } 110 | 111 | 112 | module bosses() difference() { 113 | union() { 114 | trans_tilt() intersection() { 115 | union() { 116 | camera_boss(); 117 | sensor_boss(); 118 | } 119 | 120 | *hull() { 121 | crns_case(true,casecrn_rad); 122 | } 123 | 124 | 125 | trans_untilt() linear_extrude(height=50) projection(cut=true) { 126 | trans_tilt() hull() { 127 | crns_case(true,casecrn_rad-2.4-0.4); 128 | } 129 | } 130 | } 131 | trans_tilt() corner_bosses(); 132 | 133 | poe_boss(); 134 | } 135 | 136 | 137 | trans_tilt() { 138 | translate([0,0,0.01]) { 139 | screen_co(); 140 | hull() for(ix=[-(164.9/2),(164.9/2)]) for(iy=[-124.27/2,124.27/2]) translate([ix,iy,-(1.6+6.2)]) cylinder(r=0.5,10); 141 | } 142 | 143 | camera_pos() translate([0,0,0.01]) camera_co(); 144 | 145 | translate([0,0,-(6.2)]) { 146 | for(ix=[-80,80]) for(iy=[-85]) translate([ix,iy,0]) screw_co(8,1.2,4); 147 | for(ix=[30]) for(iy=[-85]) translate([ix,iy,0]) screw_co(8,1.2,6.2); 148 | } 149 | } 150 | 151 | translate([0,0,-200]) cylinder(r=200,h=200); 152 | } 153 | 154 | module sensor_plate() difference() { 155 | union() { 156 | translate([0,0,-(6.2)]) { 157 | hull() for(ix=[-25,70]) for(iy=[-85-10,-85+10]) translate([ix,iy,0]) cylinder(r=5,h=6.2); 158 | 159 | hull() { 160 | for(ix=[-75,75]) for(iy=[-85-10,-85+10]) translate([ix,iy,0]) cylinder(r=5,h=4); 161 | for(ix=[-80,80]) for(iy=[-85]) translate([ix,iy,0]) cylinder(r=7.5,h=4); 162 | } 163 | } 164 | 165 | touch_sensor_pos() translate([0,0,-0.2]) { 166 | hull() for(ix=[-11/2,11/2]) for(iy=[-11/2,-11/2+15]) translate([ix,iy,-5]) cylinder(r=0.5+2.4,h=5-0.4); 167 | } 168 | } 169 | 170 | translate([0,0,0.01]) touch_sensor_pos() touch_sensor_co(); 171 | 172 | presence_sensor_pos() translate([0,0,-0.01]) presence_sensor_co(); 173 | 174 | 175 | translate([0,0,-(6.2)]) { 176 | for(ix=[-80,80]) for(iy=[-85]) translate([ix,iy,0]) screw_co(8,1.2,4); 177 | for(ix=[30]) for(iy=[-85]) translate([ix,iy,0]) screw_co(8,1.2,6.2); 178 | } 179 | } 180 | 181 | module walls() { 182 | difference() { 183 | trans_tilt() { 184 | intersection() { 185 | hull() crns_case(true,casecrn_rad); 186 | hull() crns_case() translate([0,0,-100]) cylinder(r1=casecrn_rad-0.8+100,r2=casecrn_rad-0.8,h=100); 187 | } 188 | 189 | //bulge for screen cables 190 | hull() for(iz=[0,-(22+6.2)]) for(iy=[-124.27/2+70,124.27/2-17]) { 191 | translate([(164.9/2)+12+0.5-10,iy,iz]) { 192 | sphere(r=10+1.6); 193 | translate([-10,0,-10]) sphere(r=10+1.6); 194 | } 195 | } 196 | } 197 | //cut off top 198 | trans_tilt() cylinder(r=200,h=200); 199 | //cut out body 200 | trans_tilt() { 201 | hull() crns_case(true,casecrn_rad-2.4); 202 | 203 | //bulge for screen cables 204 | hull() for(iz=[0,-(22+6.2)]) for(iy=[-124.27/2+70,124.27/2-17]) { 205 | translate([(164.9/2)+12+0.5-10,iy,iz]) { 206 | sphere(r=10); 207 | translate([-10,0,-10]) sphere(r=10); 208 | } 209 | } 210 | } 211 | //cut out bottom 212 | translate([0,0,-200+1]) cylinder(r=200,h=200); 213 | 214 | //trans_tilt() translate([0,0,0.01]) screen_co(); 215 | 216 | *rasp_cab_co(true); 217 | 218 | 219 | //ventilation holes 220 | *for(ixm=[0,1]) mirror([ixm,0,0]) hull() for(ix=[40,55]) translate([ix,70,4+2.5]) rotate([-90,0,0]) cylinder_oh(2.5,50); 221 | } 222 | 223 | intersection() { 224 | trans_tilt() hull() crns_case(true,casecrn_rad); 225 | 226 | wall_attach_pos() difference() { 227 | hull() for(ix=[0,20]) translate([ix,0,1]) { 228 | cylinder(r=5+0.25+2.4+0.25-0.25,h=-1+4+4+2-0.75); 229 | translate([0,0,0.25]) { 230 | cylinder(r=5+0.25+2.4+0.25-0.75,h=-1+4+4+2-0.25); 231 | cylinder(r=5+0.25+2.4+0.25,h=-1+4+4+2-0.25-0.75); 232 | } 233 | } 234 | translate([0,0,8]) screw_co(8,2+0.4,50); 235 | 236 | cylinder(r=5+0.25,h=4+4); 237 | 238 | hull() { 239 | cylinder(r=5+0.25+0.25,h=1); 240 | cylinder(r=5+0.25,h=1+0.25); 241 | } 242 | 243 | translate([-1.75,-1.75,0]) cube([2*1.75,2*1.75,8+0.4]); 244 | translate([-sqrt(pow(5+0.25,2)-pow(1.75,2)),-1.75,0]) cube([2*sqrt(pow(5+0.25,2)-pow(m3_v_r,2)),2*1.75,8+0.2]); 245 | } 246 | } 247 | } 248 | 249 | module wall_attach_pos() { 250 | for(ixm=[0,1]) mirror([ixm,0,0]) { 251 | for(iy=(ixm==0?[-32.5]:[-40,35])) translate([82.5,iy,0]) { 252 | children(); 253 | } 254 | } 255 | } 256 | 257 | 258 | 259 | 260 | module camera_plate() difference() { 261 | camera_pos() translate([0,0,1]) difference() { 262 | hull() for(ix=[-(17.5-5),(17.5-5)]) for(iy=[-5,10]) translate([ix,iy,0]) cylinder(r=5,h=3+0.6); 263 | 264 | translate([0,0,-1]) camera_co(); 265 | } 266 | screen_co(); 267 | 268 | camera_pos() translate([0,0,1]) for(ix=[-(17.5),(17.5)]) translate([ix,0,-1]) cylinder(r=5+0.25,h=100); 269 | 270 | //translate([0,0,-200]) cylinder(r=200,h=200); 271 | } 272 | 273 | 274 | module camera_plate2() intersection() { 275 | pcb_m = 0.5; 276 | 277 | camera_pos() translate([0,0,1]) difference() { 278 | translate([0,0,3+0.6]) { 279 | hull() for(ix=[-20,20]) for(iy=[-5,10]) translate([ix,iy,0]) cylinder(r=5,h=4); 280 | 281 | for(iz=[-(-12+1+3+0.6)+faceplate_thk]) hull() for(ix=[0,1]) mirror([ix,0,0]) for(iy=[0,1]) mirror([0,iy,0]) { 282 | translate([(iz)*tan(62.2/2)+4-2,(iz)*tan(48.4/2)+4-2,0]) { 283 | cylinder(r=2+pcb_m+4-0.4,h=iz); 284 | cylinder(r=2+pcb_m+4,h=iz-0.4); 285 | } 286 | } 287 | } 288 | 289 | translate([0,0,3+0.6]) { 290 | hull() for(iz=[-(-12+1+3+0.6)+faceplate_thk-0.6,-(-12+1+3+0.6)+faceplate_thk]) for(ix=[0,1]) mirror([ix,0,0]) for(iy=[0,1]) mirror([0,iy,0]) { 291 | translate([(iz)*tan(62.2/2)+4-2,(iz)*tan(48.4/2)+4-2,iz]) { 292 | cylinder(r=2+pcb_m,h=10); 293 | } 294 | } 295 | } 296 | 297 | translate([0,0,-1-0.01]) camera_co(); 298 | } 299 | 300 | camera_pos() translate([0,0,1]) { 301 | translate([0,0,3+0.6]) { 302 | hull() for(ix=[-20,20]) for(iy=[-5,12.5]) translate([ix,iy,0]) cylinder(r1=5,r2=5+20*tan(25),h=20); 303 | } 304 | } 305 | //screen_co(); 306 | 307 | //translate([0,0,-200]) cylinder(r=200,h=200); 308 | } 309 | 310 | 311 | //screen mounting bosses 312 | module corner_bosses() { 313 | intersection() { 314 | for(ixm=[0,1]) mirror([ixm,0,0]) for(iym=[0,1]) mirror([0,iym,0]) { 315 | hull() for(ib=[0,5]) intersection() { 316 | translate([156.9/2,114.96/2,0]) { 317 | translate([0,0,-100-(6.2)]) { 318 | for(ix=[-5,5]) for(iy=[0,30]) translate([ix,iy,0]) { 319 | if (ix == -5 && iy == 0) { 320 | translate([5,0,0]) cylinder(r=5+ib,h=100); 321 | translate([0,5,0]) cylinder(r=5+ib,h=100); 322 | } else if (ix == -5 && iy == 30) { 323 | translate([5,0,0]) cylinder(r=5+ib,h=100); 324 | translate([0,-5,0]) cylinder(r=5+ib,h=100); 325 | } else { 326 | cylinder(r=5+ib,h=100); 327 | } 328 | } 329 | } 330 | } 331 | mirror([0,iym,0]) trans_untilt() translate([-200,-200,-100]) cube([400,400,100+4+(5-ib)]); 332 | } 333 | translate([156.9/2,114.96/2,0]) { 334 | translate([0,0,-100-(6.2)]) { 335 | hull() for(ix=[-5,5]) for(iy=[0,30]) translate([ix,iy,0]) { 336 | if (ix == -5 && iy == 0) { 337 | translate([5,0,0]) cylinder(r=5,h=100); 338 | translate([0,5,0]) cylinder(r=5,h=100); 339 | } else if (ix == -5 && iy == 30) { 340 | translate([5,0,0]) cylinder(r=5,h=100); 341 | translate([0,-5,0]) cylinder(r=5,h=100); 342 | } else { 343 | cylinder(r=5,h=100); 344 | } 345 | } 346 | } 347 | } 348 | } 349 | 350 | trans_untilt() linear_extrude(height=50) projection(cut=true) { 351 | trans_tilt() hull() { 352 | crns_case(true,casecrn_rad-2.4-0.4); 353 | } 354 | } 355 | } 356 | 357 | for(ixm=[0,1]) mirror([ixm,0,0]) hull() { 358 | translate([0,0,-5-(6.2)]) { 359 | for(ix=[80,80-2.5]) for(iy=[-85,-85+10]) { 360 | for(iz=[0,-7.5]) { 361 | translate([ix+iz*tan(30)*cos(45),iy-iz*tan(30)*sin(45),iz]) cylinder(r=5,h=5); 362 | } 363 | } 364 | } 365 | } 366 | } 367 | 368 | module camera_boss() { 369 | difference() { 370 | union() { 371 | hull() for(ix=[-20,20]) for(iy=[65-10,65+5]) translate([ix,iy,-100-12+1]) cylinder(r=5,h=100+(3+0.6)-0.4); 372 | 373 | hull() for(ib=[0,5]) intersection() { 374 | for(ix=[-20,20]) for(iy=[65-10,65+5]) translate([ix,iy,-100-12+1]) cylinder(r=5+ib,h=100+(3+0.6)-0.4); 375 | trans_untilt() translate([-200,0,-100]) cube([400,200,100+4+(5-ib)]); 376 | } 377 | } 378 | 379 | camera_pos() translate([0,0,1]) difference() { 380 | hull() for(ix=[-(17.5-5),(17.5-5)]) for(iy=[-20,12.5]) translate([ix,iy,0]) cylinder(r=5+0.4,h=50); 381 | } 382 | } 383 | 384 | camera_pos() translate([0,0,-100+1]) { 385 | for(ix=[-(17.5),(17.5)]) translate([ix,0,0]) cylinder(r=5,h=100+(3+0.6)-0.4); 386 | } 387 | } 388 | 389 | module sensor_boss() { 390 | hull() { 391 | for(ix=[-5,0]) for(iy=[0,7.5]) translate([30+ix,-85+iy,-100-(6.2)]) cylinder(r=5,h=100); 392 | for(ix=[-7.5*tan(45)-5]) for(iy=[7.5]) translate([30+ix,-85+iy,-100-(6.2)]) cylinder(r=5,h=100); 393 | } 394 | 395 | hull() for(ib=[0,5]) intersection() { 396 | union() { 397 | for(ix=[-5,0]) for(iy=[0,7.5]) translate([30+ix,-85+iy,-100-(6.2)]) cylinder(r=5+ib,h=100); 398 | for(ix=[-7.5*tan(45)-5]) for(iy=[7.5]) translate([30+ix,-85+iy,-100-(6.2)]) cylinder(r=5+ib,h=100); 399 | } 400 | 401 | trans_untilt() translate([-200,-200,-100]) cube([400,400,100+4+(5-ib)]); 402 | } 403 | } 404 | 405 | 406 | module touch_sensor_pos() { 407 | for(ix2=[-17.5,17.5]) translate([ix2,-85,faceplate_thk-0.4]) children(); 408 | } 409 | 410 | //datum is top of pcb 411 | module touch_sensor_co(not_faceplate=true) { 412 | hull() for(ix=[-11/2,11/2]) for(iy=[-11/2,-11/2+15]) translate([ix,iy,-1]) cylinder(r=0.5,h=1); 413 | 414 | hull() for(ix=[-(11/2-0.75),(11/2-0.75)]) for(iy=[-(11/2-0.75),-11/2+15-1.5]) translate([ix,iy,-1-4]) cylinder(r=0.5,h=1+4); 415 | 416 | hull() for(ix=[-3.75,3.75]) for(iy=[-11/2+15-5,-11/2+15+20]) translate([ix,iy,-1-4]) cylinder(r=0.5,h=1+4-faceplate_thk+0.4); 417 | 418 | if(not_faceplate) { 419 | hull() for(ix=[-3.75,3.75]) for(iy=[-11/2+15-5,-11/2+15+2.4+0.5]) translate([ix,iy,-1-4]) cylinder(r=0.5,h=4+1); 420 | 421 | //faceplate 422 | } else { 423 | hull() for(ix=[-11/2,11/2]) for(iy=[-11/2,-11/2+15]) translate([ix,iy,-1-4]) cylinder(r=0.5+2.5+0.25,h=1+4-0.4); 424 | } 425 | } 426 | 427 | module presence_sensor_pos() { 428 | translate([55,-85,-6.2+1.2+2.4]) children(); 429 | } 430 | 431 | //datum is top of pcb 432 | module presence_sensor_co() { 433 | co_h = 6.2-1.2-2.4; 434 | 435 | hull() for(ix=[-22/2,22/2]) for(iy=[-16/2,16/2]) translate([ix,iy,-1.2]) cylinder(r=0.5,h=co_h+1.2); 436 | 437 | translate([0,0,-10+0.01]) { 438 | hull() { 439 | for(ix=[-(22/2-0.25-2.5),(22/2-0.25-2.5)]) for(iy=[-(16/2-0.25),(16/2-0.25)]) translate([ix,iy,0]) cylinder(r=0.5,h=10); 440 | for(ix=[-(22/2-0.25),(22/2-0.25)]) for(iy=[-(16/2-0.25-2.5),(16/2-0.25-2.5)]) translate([ix,iy,0]) cylinder(r=0.5,h=10); 441 | } 442 | 443 | hull() for(ix=[-5,5]) for(iy=[16/2,16/2+2]) translate([ix+sign(ix)*(iy==16/2?2:0),iy,0]) cylinder(r=0.5,h=10+co_h); 444 | } 445 | 446 | hull() for(ix=[0,1]) mirror([ix,0,0]) translate([22/2+1.6+1.25,1.5,0]) { 447 | translate([0,0,-1.2]) cylinder(r=1.25,h=co_h+1.2); 448 | } 449 | for(ix=[0,1]) mirror([ix,0,0]) translate([22/2+1.6+1.25,1.5,0]) { 450 | translate([0,0,-8]) cylinder(r=1.25,h=10); 451 | } 452 | hull() for(ix=[0,1]) mirror([ix,0,0]) translate([22/2+1.6+1.25,1.5,0]) { 453 | translate([0,0,-8]) cylinder(r=1.75,h=8-2.4-1.2); 454 | translate([0,0,-8-1.75]) cylinder(r=0.01,h=8-2.4-1.2); 455 | } 456 | hull() for(ix=[0,1]) mirror([ix,0,0]) translate([22/2+1.6+1.25,1.5,0]) { 457 | translate([0,0,-8]) cylinder(r=1.25,h=10); 458 | } 459 | hull() for(ix=[0,1]) mirror([ix,0,0]) translate([22/2+1.6+1.25,1.5,0]) { 460 | cylinder(r=5,h=co_h); 461 | } 462 | 463 | hull() for(ix=[-22/2,22/2]) for(iy=[-16/2,16/2]) translate([ix,iy,0]) { 464 | cylinder(r1=0.5,r2=0.5+co_h,h=co_h); 465 | translate([0,0,co_h]) { 466 | cylinder(r=0.5+co_h-0.4,h=faceplate_thk-0.4); 467 | cylinder(r=0.5+co_h,h=faceplate_thk-0.4-0.4); 468 | } 469 | } 470 | } 471 | 472 | module foo() difference() { 473 | *trans_tilt() { 474 | hull() mirror_xy([164.9/2,124.27/2,-(1.6+6.2)]) cylinder(r=0.8,20); 475 | hull() mirror_xy([164.9/2,107/2,-(7+1.6+6.2)]) cylinder(r=0.8,20); 476 | } 477 | } 478 | 479 | 480 | 481 | module base() { 482 | difference() { 483 | union() { 484 | *difference() { 485 | trans_tilt() hull() { 486 | crns_case(true,casecrn_rad); 487 | } 488 | translate([0,0,4]) cylinder(r=200,h=200); 489 | translate([0,0,-200]) cylinder(r=200,h=200); 490 | } 491 | 492 | linear_extrude(height=4) projection(cut=true) { 493 | trans_tilt() hull() { 494 | crns_case(true,casecrn_rad-2.4-0.4); 495 | } 496 | } 497 | translate([0,-30+(30+5)/2,0]) for(ixm=[0,1]) mirror([ixm,0,0]) { 498 | mnt_x = 85; 499 | translate([mnt_x,0,0]) cylinder(r=2+2.4,h=4); 500 | } 501 | 502 | 503 | //raspberry pi mount 504 | rasp_trans() rasp_mnt() { 505 | cylinder(r=1.25+1.6,h=4+2.8); 506 | hull() { 507 | cylinder(r=1.25+1.6,h=4+2); 508 | cylinder(r=1.25+1.6+2,h=4); 509 | } 510 | } 511 | } 512 | 513 | rasp_trans() translate([0,0,-1]) rasp_mnt() { 514 | cylinder(r=1.25,h=20); 515 | } 516 | 517 | *rasp_cab_co(); 518 | 519 | //vent ramps 520 | for(ixm=[0,1]) mirror([ixm,0,0]) translate([46.25,71.25,0.2]) mk2_vent_co(25); 521 | mirror([0,1,0]) translate([-27.5,87.5,0.2]) mk2_vent_co(50); 522 | 523 | 524 | //mounting holes and alignment markers 525 | translate([0,-30+(30+5)/2,0]) for(ixm=[0,1]) mirror([ixm,0,0]) { 526 | mnt_x = 85; 527 | 528 | rotate([0,0,-5]) rotate_extrude(angle=2*5,$fn=$fn*16) translate([mnt_x-2,-1]) square([2*2,10]); 529 | for(ia=[-1,1]) rotate([0,0,5*ia]) translate([mnt_x,0,-1]) cylinder(r=2,h=10); 530 | 531 | for(ia=[-1,0,1]) rotate([0,0,5*ia]) translate([mnt_x,0,4-0.6]) hull() { 532 | for(ix=[0,-7.5]) translate([ix,0,0]) cylinder(r1=0,r2=1,h=1); 533 | } 534 | for(ia=[-0.5,0.5]) rotate([0,0,5*ia]) translate([mnt_x,0,4-0.6]) hull() { 535 | for(ix=[0,-5]) translate([ix,0,0]) cylinder(r1=0,r2=1,h=1); 536 | } 537 | } 538 | 539 | //cable port and cable tie holes 540 | translate([0,-7.5,-0.01]) { 541 | translate([0,7.5+(-30+(30+5)/2),0]) { 542 | hull() for(ix=[-4,4]) for(iy=[-2,7.5-2]) translate([ix,iy,0]) cylinder(r=3.75,h=50); 543 | hull() for(ix=[-4,4]) for(iy=[-2,7.5-2]) translate([ix,iy,0]) cylinder(r1=3.75+0.5,r2=3.75,h=0.5); 544 | hull() for(ix=[-4,4]) for(iy=[-2,7.5-2]) translate([ix,iy,4-0.5+2*0.01]) cylinder(r1=3.75,r2=3.75+0.5,h=0.5); 545 | } 546 | 547 | for(ix=[25,25+15]) { 548 | for(iy=[0,1]) mirror([0,iy,0]) translate([ix,2+1.2,0]) cylinder(r=2,h=50); 549 | 550 | for(iy=[0,1]) mirror([0,iy,0]) hull() { 551 | translate([ix-(2-0.5),2+1.2,0]) cylinder(r=0.5,h=2); 552 | translate([ix+(2-0.5),2+1.2,0]) cylinder(r=0.5,h=2); 553 | 554 | translate([ix-(2-0.5),2+1.2-(2-0.5),0]) cylinder(r=0.5,h=2); 555 | translate([ix+(2-0.5),2+1.2-(2-0.5),0]) cylinder(r=0.5,h=2); 556 | } 557 | 558 | hull() for(iy=[0,1]) mirror([0,iy,0]) translate([ix,2+1.2,0]) cylinder(r=2,h=2-0.2); 559 | hull() for(iy=[0,1]) mirror([0,iy,0]) translate([ix,2+1.2,0]) cylinder(r1=2+0.5,r2=2,h=0.5); 560 | } 561 | } 562 | 563 | //cutouts for attaching wall to base 564 | wall_attach_pos() { 565 | hull() for(ix=[0,20]) translate([ix,0,1]) { 566 | cylinder(r=5+0.25+2.4+0.5-0.25,h=50); 567 | translate([0,0,0.25]) cylinder(r=5+0.25+2.4+0.5,h=50); 568 | } 569 | translate([0,0,8]) screw_co(8,1.2,50); 570 | } 571 | } 572 | 573 | //studs for attaching wall to base 574 | wall_attach_pos() difference() { 575 | union() { 576 | hull() { 577 | cylinder(r=5-0.25-0.25,h=4+4); 578 | cylinder(r=5-0.25,h=4+4-0.25); 579 | } 580 | hull() { 581 | cylinder(r=5-0.25+0.25,h=1); 582 | cylinder(r=5-0.25,h=1+0.25); 583 | } 584 | } 585 | translate([0,0,8]) screw_co(8,1.2,50); 586 | } 587 | 588 | *intersection() { 589 | translate([0,-30+(30+5)/2,0]) for(ixm=[0,1]) mirror([ixm,0,0]) { 590 | mnt_x = 85; 591 | 592 | shrd_h = 4+5; 593 | 594 | difference() { 595 | union() { 596 | rotate([0,0,-5]) rotate_extrude(angle=2*5,$fn=$fn*16) translate([mnt_x-7.5-(2+6.25+4),0]) square([7.5+2*(2+6.25+4)+20,shrd_h]); 597 | 598 | for(ia=[-1,1]) rotate([0,0,5*ia]) hull() { 599 | translate([mnt_x-7.5,0,0]) cylinder(r=2+6.25+4,h=shrd_h); 600 | translate([mnt_x+20,0,0]) cylinder(r=2+6.25+4,h=shrd_h); 601 | } 602 | } 603 | 604 | rotate([0,0,-5]) rotate_extrude(angle=2*5,$fn=$fn*16) translate([mnt_x-7.5-(2+6.25),-1]) square([7.5+2*(2+6.25),50]); 605 | 606 | for(ia=[-1,1]) rotate([0,0,5*ia]) hull() { 607 | translate([mnt_x-7.5,0,-1]) cylinder(r=2+6.25,h=50); 608 | translate([mnt_x,0,-1]) cylinder(r=2+6.25,h=50); 609 | } 610 | } 611 | } 612 | trans_tilt() hull() { 613 | crns_case(true,casecrn_rad); 614 | } 615 | } 616 | } 617 | 618 | module base_co() { 619 | //top cutout 620 | hull() for(ix=[-60,60]) for(iy=[5+14+5+5,40]) { 621 | translate([ix,iy,-1]) cylinder(r=5,h=50); 622 | } 623 | hull() for(ix=[-65+51.5+2*5,60]) for(iy=[5,40]) { 624 | translate([ix,iy,-1]) cylinder(r=5,h=50); 625 | } 626 | translate([-65+51.5+2*5-5,5+14+5,-1]) linear_extrude(height=50) difference() { 627 | translate([-5,-5]) square([2*5,2*5]); 628 | translate([-5,-5]) circle(r=5); 629 | } 630 | 631 | rasp_trans() rotate([0,0,-90]) translate([-56/2,-85/2,-10]) { 632 | //cutout under rpi, including sd card 633 | hull() for(ix=[-10,10]) for(iy=[-50,20]) { 634 | translate([56/2+ix,(87.1-15)/2+iy,-1]) cylinder(r=5,h=50); 635 | } 636 | //cutout under rpi ports 637 | hull() for(ix=[-17.5,25]) for(iy=[50,67.5]) { 638 | translate([56/2+ix,(87.1-15)/2+iy,-1]) cylinder(r=5,h=50); 639 | } 640 | } 641 | } 642 | 643 | 644 | module mk2_vent_co(wid=10) rotate([0,0,90]) { 645 | rotate([90,0,0]) rotate_extrude(angle=90-atan((4-0.2)/10)) mk2_vent_co_cs(wid); 646 | translate([0,0,-10+0.01]) linear_extrude(height=10) mk2_vent_co_cs(wid); 647 | rotate([0,-(90-atan((4-0.2)/10)),0]) translate([0,0,-0.01]) linear_extrude(height=20) mk2_vent_co_cs(wid); 648 | } 649 | 650 | module mk2_vent_co_cs(wid) hull() { 651 | translate([2,wid/2]) circle(r=2); 652 | translate([2,-wid/2]) circle(r=2); 653 | 654 | translate([20,wid/2]) circle(r=2); 655 | translate([20,-wid/2]) circle(r=2); 656 | } 657 | 658 | 659 | //frontplate 660 | module faceplate() union() { 661 | difference() { 662 | union() { 663 | hull() crns_case() { 664 | cylinder(r=casecrn_rad,h=faceplate_thk-1.2); 665 | cylinder(r=casecrn_rad-1.2,h=faceplate_thk); 666 | } 667 | crns_screws() hull() { 668 | cylinder(r=5+6.2,h=2); 669 | translate([0,0,-6.2]) cylinder(r=5,h=2+6.2); 670 | } 671 | 672 | *camera_pos() translate([0,0,0.2]) { 673 | hull() for(ix=[-10,10]) for(iy=[-5,10]) translate([ix,iy,1+3+0.6]) { 674 | translate([0,0,0.5]) cylinder(r=5,h=-(-12+1+3+0.6)+2-0.2-0.5); 675 | cylinder(r=5-0.5,h=-(-12+1+3+0.6)+2-0.2); 676 | } 677 | } 678 | *camera_pos() translate([0,0,12]) { 679 | hull() for(ix=[-10,10]) for(iy=[-5,10]) translate([ix,iy,0]) { 680 | translate([0,0,-0.5]) cylinder(r=5,h=2+0.5); 681 | cylinder(r=5+0.5,h=2); 682 | } 683 | } 684 | 685 | *translate([55,-85,-5]) hull() for(ix=[-15,15]) for(iy=[-10,10]) translate([ix,iy,0]) { 686 | translate([0,0,0.5]) cylinder(r=5,h=5+2-0.2-0.5); 687 | cylinder(r=5-0.5,h=5+2-0.2); 688 | } 689 | *translate([55,-85,0]) hull() for(ix=[-15,15]) for(iy=[-10,10]) translate([ix,iy,0]) { 690 | translate([0,0,-0.5]) cylinder(r=5,h=2+0.5); 691 | cylinder(r=5+0.5,h=2); 692 | } 693 | } 694 | 695 | screen_co(); 696 | 697 | *camera_pos() translate([0,0,-0.01]) camera_co(0.5,false); 698 | 699 | pcb_m = 0.5; 700 | camera_pos() translate([0,0,12]) { 701 | for(iz=[-(-12+1+3+0.6)+faceplate_thk]) { 702 | hull() for(ix=[0,1]) mirror([ix,0,0]) for(iy=[0,1]) mirror([0,iy,0]) { 703 | translate([(iz)*tan(62.2/2)+4-2,(iz)*tan(48.4/2)+4-2,-1]) { 704 | cylinder(r=2+pcb_m+4+0.25,h=iz); 705 | //cylinder(r=2+pcb_m+4,h=iz-0.4); 706 | } 707 | } 708 | hull() for(ix=[0,1]) mirror([ix,0,0]) for(iy=[0,1]) mirror([0,iy,0]) { 709 | translate([(iz)*tan(62.2/2)+4-2,(iz)*tan(48.4/2)+4-2,faceplate_thk]) { 710 | translate([0,0,-0.4]) cylinder(r=2+pcb_m+4+0.25,h=iz); 711 | cylinder(r=2+pcb_m+4+0.25+0.4,h=iz); 712 | } 713 | } 714 | } 715 | } 716 | 717 | translate([0,0,-0.01]) touch_sensor_pos() touch_sensor_co(false); 718 | presence_sensor_pos() translate([0,0,-0.01]) presence_sensor_co(); 719 | 720 | *for(ix2=[-17.5,17.5]) translate([ix2,-85,1.6-0.4]) { 721 | hull() for(ix=[-11/2,11/2]) for(iy=[-11/2,-11/2+15]) translate([ix,iy,-10]) cylinder(r=0.5+2.4+0.5,h=10); 722 | } 723 | *for(ix2=[-17.5,17.5]) translate([ix2,-85,1.6]) { 724 | hull() for(ix=[-11/2,11/2]) for(iy=[-11/2,-11/2+15]) translate([ix,iy,-10]) cylinder(r=0.5,h=10); 725 | } 726 | 727 | for(ix2=[-17.5,17.5]) translate([ix2,-85,2-0.4]) difference() { 728 | hull() for(iy=[-5,5]) for(ix=[-2.5,2.5]) translate([ix,iy,0]) { 729 | cylinder(r1=7.5+1,r2=7.5+1+5,h=5); 730 | } 731 | hull() for(iy=[-5,5]) for(ix=[-2.5,2.5]) translate([ix,iy,-0.01]) { 732 | cylinder(r1=7.5,r2=7.5-2,h=2); 733 | } 734 | } 735 | 736 | *translate([55,-85,-5-0.01]) { 737 | hull() for(iz=[0,5+2-0.4]) for(ix=[0,1]) mirror([ix,0,0]) for(iy=[0,1]) mirror([0,iy,0]) { 738 | translate([(iz)*tan(60/2)+22/2-2-1,(iz)*tan(60/2)+16/2-2-1,iz]) cylinder(r=2,h=0.01); 739 | } 740 | hull() for(ix=[0,1]) mirror([ix,0,0]) for(iy=[0,1]) mirror([0,iy,0]) { 741 | translate([22/2-2-1,16/2-2-1,-10]) cylinder(r=2+0.4,h=10+5); 742 | } 743 | 744 | hull() for(ix=[0,1]) mirror([ix,0,0]) translate([22/2+1.6+1.25,1.5,0]) { 745 | translate([0,0,-10]) cylinder(r=3,h=10+3); 746 | } 747 | } 748 | 749 | //translate([0,-200,-100]) cube([200,400,200]); 750 | //translate([-(156.9/2),-200-114.96/2,-100]) cube([200,400,200]); 751 | } 752 | 753 | 754 | 755 | difference() { 756 | hull() { 757 | crns_case(true,casecrn_rad-2.4-0.4); 758 | } 759 | hull() { 760 | crns_case(true,casecrn_rad-2.4-0.4-2.4); 761 | } 762 | translate([0,0,2]) cylinder(r=200,h=200); 763 | translate([0,0,-200-1.6]) cylinder(r=200,h=200); 764 | } 765 | } 766 | 767 | module crns_screws() { 768 | mirror_xy([156.9/2,114.96/2,0]) children(); 769 | } 770 | module crns_viewport() { 771 | translate([-(3+5)/2+3,-(12+4)/2+12,0]) mirror_xy([(164.9-3-5)/2,(107-12-4)/2,0]) children(); 772 | } 773 | module crns_case(case_shape=false, sphere_r=casecrn_rad) { 774 | pcb_w = 164.9; 775 | pcb_h = 124.27; 776 | 777 | for(ix=[-(pcb_w/2-5),(pcb_w/2-5+0.75/*for cable clearance*/)]) { 778 | for(iy=[-(pcb_h/2+30),(pcb_h/2+5)]) { 779 | translate([ix,iy,0]) { 780 | if (case_shape) rotate([(iy>0?-1:1)*17.5-case_tilt,0,0]) { 781 | sphere(r=sphere_r); 782 | //translate([0,0,-100]) cylinder(r=sphere_r,h=100); 783 | rotate([0,0,atan2(sign(iy),sign(ix))]) translate([-100,0,0]) rotate([-90,0,0]) rotate_extrude(angle=45,$fn=$fn*4) translate([100,0]) circle(r=sphere_r,$fn=$fn/4); 784 | } else { 785 | children(); 786 | } 787 | } 788 | } 789 | } 790 | } 791 | 792 | /*module case_spheres(sphere_r=casecrn_rad) { 793 | for(ix=[0,1]) mirror([ix,0,0]) { 794 | for(iy=[-(124.27/2+20),(124.27/2-5)]) { 795 | translate([164.9/2-5,iy,0]) { 796 | sphere(r=sphere_r); 797 | translate([0,0,-100]) cylinder(r=sphere_r,h=100); 798 | } 799 | } 800 | } 801 | 802 | //crns_case() sphere(r=sphere_r); 803 | //crns_case() translate([5,5,-75]) sphere(r=sphere_r-20); 804 | 805 | //casecrn_trans_array = [106/2-5,68/2+20,0]; 806 | //translate([0,0,-sqrt(pow(casecrn_trans_array[0],2)+pow(casecrn_trans_array[1],2))/tan(22.5)]) sphere(r=sphere_r); 807 | }*/ 808 | 809 | 810 | module rasp_trans() { 811 | translate([-5,25-65,0]) children(); 812 | } 813 | 814 | module rasp_mnt() rotate([0,0,-90]) translate([-56/2,-85/2,0]) { 815 | translate([3.5,3.5,0]) children(); 816 | translate([3.5,3.5+58,0]) children(); 817 | translate([3.5+49,3.5+58,0]) children(); 818 | translate([3.5+49,3.5,0]) children(); 819 | } 820 | 821 | module rasp_cab_co(wall_co=false) { 822 | rasp_trans() translate([-85/2+3.5+7.7,-45,0]) { 823 | if (wall_co) { 824 | hull() for(iz=[-10,4+2.5]) translate([0,0,iz]) rotate([90,0,0]) cylinder_oh(2.5,50); 825 | 826 | for(iz=[4+2.8+1.6+3.31/2]) translate([0,0,iz]) hull() rotate([90,0,0]) cylinder_oh((2.8+1.6+3.31/2),50); 827 | } else { 828 | hull() { 829 | translate([0,0,-10]) cylinder(r=2.5,h=50); 830 | translate([0,-50,-10]) cylinder(r=2.5,h=50); 831 | } 832 | hull() { 833 | translate([0,0,-10]) cylinder(r=2.5,h=50); 834 | rotate([0,0,30]) translate([17.5,0,-10]) cylinder(r=2.5,h=50); 835 | } 836 | rotate([0,0,30]) translate([17.5,0,-10]) cylinder(r=7.5,h=50); 837 | } 838 | } 839 | } 840 | 841 | module trans_tilt() { 842 | translate([0,0,case_proj]) rotate([case_tilt,0,0]) children(); 843 | } 844 | 845 | module trans_untilt() { 846 | rotate([-case_tilt,0,0]) translate([0,0,-case_proj]) children(); 847 | } 848 | 849 | 850 | module mirror_xy(trans_vec=[0,0,0]) { 851 | for(ix=[0,1]) mirror([ix,0,0]) { 852 | for(iy=[0,1]) mirror([0,iy,0]) { 853 | translate(trans_vec) children(); 854 | } 855 | } 856 | } 857 | 858 | //camera mount test 859 | *union() { 860 | difference() { 861 | hull() for(ix=[-20,20]) for(iy=[-20,20]) translate([ix,iy,0]) cylinder(r=5,h=20); 862 | 863 | rotate([5,0,0]) translate([0,0,10]) { 864 | cylinder(r=50,h=50); 865 | translate([0,0,-1+0.01]) camera_co(); 866 | } 867 | } 868 | 869 | 870 | //rotate([5,0,0]) translate([0,0,10]) 871 | rotate([180,0,0]) translate([-55,0,-(3+0.6)]) difference() { 872 | hull() for(ix=[-20,20]) for(iy=[-20,20]) translate([ix,iy,0]) cylinder(r=5,h=3+0.6); 873 | 874 | translate([0,0,-1]) camera_co(); 875 | } 876 | 877 | //rotate([5,0,0]) translate([0,0,10]) 878 | rotate([180,0,0]) translate([55,0,-(3+0.6)+1-5]) difference() { 879 | translate([0,0,3+0.6]) hull() for(ix=[-20,20]) for(iy=[-20,20]) translate([ix,iy,-1]) cylinder(r=5+0.8+1.6,h=5); 880 | 881 | translate([0,0,3+0.6]) hull() for(ix=[-20,20]) for(iy=[-20,20]) translate([ix,iy,-10]) cylinder(r=5+0.8,h=10); 882 | 883 | translate([0,0,-1]) camera_co(); 884 | } 885 | } 886 | 887 | module screen_co(pcb_m=0.5) { 888 | //pcb 889 | hull() for(ix=[(164.9/2),(164.9/2-8)]) for(iy=[-124.27/2,124.27/2]) translate([ix,iy,-(1.6+6.2)]) cylinder(r=pcb_m,1.6); 890 | mirror([1,0,0]) hull() for(ix=[(164.9/2),(164.9/2-8)]) for(iy=[-124.27/2,124.27/2]) translate([ix,iy,-(1.6+6.2)]) cylinder(r=pcb_m,1.6); 891 | hull() for(ix=[(164.9/2),-(164.9/2)]) for(iy=[-107/2,107/2]) translate([ix,iy,-(1.6+6.2)]) cylinder(r=pcb_m,1.6); 892 | 893 | //screen co 894 | hull() for(ix=[(164.9/2),-(164.9/2)]) for(iy=[-124.27/2+10,124.27/2-10.5]) translate([ix,iy,-(1.6+6.2)]) cylinder(r=pcb_m,1.6+6.2); 895 | 896 | //bottom components 897 | hull() for(ix=[(164.9/2),(164.9/2)-12]) for(iy=[-107/2+53,107/2-9]) translate([ix,iy,-(7+1.6+6.2)]) cylinder(r=pcb_m,7+1.6); 898 | hull() for(ix=[-(164.9/2),(164.9/2)]) for(iy=[-107/2+20,107/2-8]) translate([ix,iy,-(3+1.6+6.2)]) cylinder(r=pcb_m,3+1.6); 899 | 900 | //plug clearance 901 | hull() for(ix=[(164.9/2)+12,(164.9/2)]) for(iy=[-124.27/2+70,124.27/2-17]) translate([ix,iy,-(22+6.2)]) cylinder(r=pcb_m,22); 902 | 903 | //viewport 904 | hull() for(ix=[-(164.9/2)+3,(164.9/2)-6]) for(iy=[-124.27/2+23.5,124.27/2-14]) translate([ix,iy,-1]) cylinder(r=pcb_m,20); 905 | hull() for(ix=[-(164.9/2)+3,(164.9/2)-6]) for(iy=[-124.27/2+23.5,124.27/2-14]) translate([ix,iy,0.4]) cylinder(r1=pcb_m,r2=pcb_m+20,20); 906 | 907 | //screws 908 | for(ix=[-(156.9/2),(156.9/2)]) for(iy=[-114.96/2,114.96/2]) translate([ix,iy,-(6.2)-1.6]) { 909 | screw_co(8,1.6+1.2,1.6+6.2+faceplate_thk); 910 | } 911 | } 912 | 913 | 914 | module screw_co(screw_len=8,h2,h3) { 915 | h1 = 0; //transition from 1.25 to 1.75 916 | //h2 = //screw head 917 | //h3 = //bevel for head cutout 918 | 919 | translate([0,0,h2]) hull() { 920 | translate([0,0,-8]) cylinder(r=1.25,8); 921 | translate([0,0,-8-1.25]) cylinder(r=0.01,8); 922 | } 923 | translate([0,0,h1]) hull() { 924 | translate([0,0,0]) cylinder(r=1.25+0.5,20); 925 | translate([0,0,-0.5]) cylinder(r=1.25,20); 926 | } 927 | translate([0,0,h2]) { 928 | translate([0,0,0]) cylinder(r=1.75,20); 929 | 930 | cylinder(r=3,20); 931 | translate([-1.75,-1.75,-0.4]) cube([2*1.75,2*1.75,20]); 932 | translate([-sqrt(pow(3,2)-pow(1.75,2)),-1.75,-0.2]) cube([2*sqrt(pow(3,2)-pow(m3_v_r,2)),2*1.75,20]); 933 | } 934 | translate([0,0,h3]) { 935 | hull() translate([0,0,0]) { 936 | translate([0,0,0]) cylinder(r=3+0.6,20); 937 | translate([0,0,-0.6]) cylinder(r=3,20); 938 | } 939 | } 940 | } 941 | 942 | module camera_pos() { 943 | translate([0,65,-12]) children(); 944 | } 945 | 946 | module camera_co(pcb_m=0.5,include_screws=true) { 947 | //pcb 948 | hull() for(ix=[-12.5,12.5]) for(iy=[-9.5,14.5]) translate([ix,iy,0]) cylinder(r=pcb_m,h=1); 949 | 950 | //top components 951 | hull() for(ix=[0,12.5-3]) for(iy=[14.5-5,14.5]) translate([ix,iy,0]) cylinder(r=pcb_m,h=1+pcb_m); 952 | 953 | //bottom components 954 | hull() for(ix=[-(12.5-3),(12.5-3)]) for(iy=[-9.5,14.5]) translate([ix,iy,-1.6]) cylinder(r=pcb_m,h=1.6+1); 955 | 956 | //cable port 957 | hull() for(ix=[-(12.5-1),(12.5-1)]) for(iy=[-9.5-50,-9.5+8]) translate([ix,iy,-2.4]) cylinder(r=pcb_m,h=2.4+1); 958 | 959 | //camera body 960 | hull() for(ix=[-4,4]) for(iy=[-4,4]) translate([ix,iy,0]) cylinder(r=pcb_m,h=1+3); 961 | 962 | //camera cable 963 | hull() for(ix=[-4,4]) for(iy=[-4,14.5]) translate([ix,iy,0]) cylinder(r=pcb_m,h=1+1.4); 964 | 965 | //camera lens 966 | cylinder(r=8/2+pcb_m,h=1+3+5); 967 | 968 | 969 | 970 | /*translate([0,0,1+3+3]) hull() { 971 | sphere(r=5); 972 | for(ix=[-62.2/2,62.2/2]) rotate([0,ix,0]) for(iy=[-48.4/2,48.4/2]) rotate([iy,0,0]) cylinder(r=5,h=20); 973 | }*/ 974 | 975 | translate([0,0,1+3+0.6]) { 976 | for(iz=[0:0.8:10]) hull() for(ix=[0,1]) mirror([ix,0,0]) for(iy=[0,1]) mirror([0,iy,0]) { 977 | translate([(iz)*tan(62.2/2)+4-2,(iz)*tan(48.4/2)+4-2,iz]) { 978 | cylinder(r=2+pcb_m,h=10); 979 | //if(iz>0) translate([-0.8*tan(62.2/2),-0.8*tan(48.4/2),-0.8+0.4]) cylinder(r=2+pcb_m,h=10); 980 | } 981 | } 982 | } 983 | 984 | if (include_screws) { 985 | /*for(ix=[-17.5,17.5]) translate([ix,0,1]) { 986 | hull() { 987 | translate([0,0,-8]) cylinder(r=1.25,8+0.01); 988 | translate([0,0,-8-1.25]) cylinder(r=0.01,8); 989 | } 990 | } 991 | 992 | 993 | for(ix=[-17.5,17.5]) translate([ix,0,1]) { 994 | hull() { 995 | translate([0,0,0]) cylinder(r=1.25+0.5,2); 996 | translate([0,0,-0.5]) cylinder(r=1.25,2); 997 | } 998 | 999 | translate([0,0,-0.01]) cylinder(r=1.75,h=20); 1000 | 1001 | translate([0,0,1]) cylinder(r=3,h=20); 1002 | translate([-1.75,-1.75,1-0.4]) cube([2*1.75,2*1.75,20]); 1003 | translate([-sqrt(pow(3,2)-pow(1.75,2)),-1.75,1-0.2]) cube([2*sqrt(pow(3,2)-pow(m3_v_r,2)),2*1.75,20]); 1004 | }*/ 1005 | for(ix=[-17.5,17.5]) translate([ix,0,1-0.01]) { 1006 | //cylinder(r=5+0.25,h=3+0.6+2*0.01); 1007 | translate([0,0,3+0.6]) screw_co(8,1.2,50); 1008 | } 1009 | } 1010 | } 1011 | 1012 | module poe_pos() { 1013 | translate([-65,5,0]) children(); 1014 | } 1015 | 1016 | module poe_boss() { 1017 | poe_pos() hull() for(ix=[0,51.5]) for(iy=[0,14]) translate([ix,iy,0]) cylinder(r=5,8); 1018 | poe_pos() hull() for(ix=[0,51.5]) for(iy=[-7.5,14]) translate([ix,iy,0]) cylinder(r=5,8); 1019 | } 1020 | 1021 | module poe_co(pcb_m=0.5) { 1022 | difference() { 1023 | hull() for(ix=[0,51.5]) for(iy=[0,14]) translate([ix,iy,-1.6-2.4]) cylinder(r=pcb_m,1.6+2.4); 1024 | 1025 | hull() for(ix=[-5,4.2-1.6]) for(iy=[2.3+1.6,9.9-1.6]) translate([ix,iy,-10-1.6]) cylinder(r=1.6,h=10); 1026 | 1027 | hull() for(ix=[15.6+1.6,35.7-1.6]) for(iy=[14-1.2+1.6]) translate([ix,iy,-10-1.6]) cylinder(r=1.6,h=10); 1028 | 1029 | hull() for(ix=[30+1.6,33.8-1.6]) for(iy=[-5,3.2-1.6]) translate([ix,iy,-10-1.6]) cylinder(r=1.6,h=10); 1030 | } 1031 | 1032 | for(iy=[-1.75,14+1.75]) translate([37,iy,-25]) cylinder(r=1.25,h=50); 1033 | hull() for(iy=[-1.75,14+1.75]) translate([37,iy,-4]) cylinder(r=1.75,h=4); 1034 | 1035 | //transformer 1036 | hull() for(ix=[11.7,11.7+13.6]) for(iy=[0,14]) translate([ix,iy,-1.6]) cylinder(r=pcb_m,13.5); 1037 | 1038 | //semiconductor 1039 | hull() for(ix=[17,17+6]) for(iy=[0,6.4]) translate([ix,iy,-4]) cylinder(r=pcb_m,4); 1040 | 1041 | //vent co 1042 | hull() { 1043 | for(ix=[11.7,11.7+13.6]) for(iy=[0,14+20]) translate([ix,iy,-(4+4-0.6)]) cylinder(r=pcb_m,(4+4-0.6)); 1044 | for(ix=[11.7,11.7+13.6]) for(iy=[-7.5-5+2.5,14+20]) translate([ix,iy,-4]) cylinder(r=pcb_m,4); 1045 | } 1046 | hull() for(ix=[11.7,11.7+13.6]) for(iy=[-7.5-5,14+20]) translate([ix,iy,-4]) cylinder(r=pcb_m,4); 1047 | 1048 | for(ix=[11.7,11.7+13.6]) translate([ix+(ix==11.7?-1:1)*pcb_m,-7.5-5,-4]) linear_extrude(height=4) difference() { 1049 | translate([-1,-1]) square([2*1,2*1]); 1050 | translate([(ix==11.7?-1:1),1]) circle(r=1); 1051 | } 1052 | 1053 | //pins 1054 | hull() for(ix=[0,1.4+2.5*2.54]) for(iy=[14,14-(1.4+0.5*2.54)]) translate([ix,iy,-4]) cylinder(r=pcb_m,4); 1055 | 1056 | hull() for(ix=[51.5,51.5-(1.8+3.5*2.54)]) for(iy=[14,14-(1.4+0.5*2.54)]) translate([ix,iy,-4]) cylinder(r=pcb_m,4); 1057 | } 1058 | 1059 | 1060 | module cylinder_oh(radius,height) { 1061 | cylinder(r=radius,h=height); 1062 | translate([-radius*tan(22.5),-radius,0]) cube([2*radius*tan(22.5),2*radius,height]); 1063 | } -------------------------------------------------------------------------------- /ha_rpi_case_mk0_frame.stl: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:225634394954131014f327f9033c319a9e3b3171e2a5a727f75d073f820febb1 3 | size 1164584 4 | -------------------------------------------------------------------------------- /ha_rpi_case_mk1_frame.stl: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4a9656295603cf3ad13463471f4f41c0fc8091a20a9277caa5ad5e9ec7fae469 3 | size 1247284 4 | -------------------------------------------------------------------------------- /ha_rpi_case_mk2_cameramount_p1.stl: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:92f01879d750efb4ca0184eb8ab45fbe7315060bc54d1bc570e794a550615bfd 3 | size 82284 4 | -------------------------------------------------------------------------------- /ha_rpi_case_mk2_cameramount_p2.stl: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e977e2ebe9fe051221135a3728a3061dc964fcdab268d816340e5a7ab50ff98c 3 | size 299684 4 | -------------------------------------------------------------------------------- /ha_rpi_case_mk2_face.stl: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e713d8042c230b6e069991dfd29bfa0b929ca1100f1a0ef173bd10b6ca660209 3 | size 621684 4 | -------------------------------------------------------------------------------- /ha_rpi_case_mk2_frame.stl: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1fb0adc468a5c22a1c6dba5c98eda342187918f12ccad4e97f0e164b745e8eea 3 | size 1446384 4 | -------------------------------------------------------------------------------- /ha_rpi_case_mk2_frame_poe.stl: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:695c247a3e24c01fb54ffbf1261ccd7abc0bd8791cd599552627f5248f0fd2b4 3 | size 1576184 4 | -------------------------------------------------------------------------------- /ha_rpi_case_mk2_sensormount.stl: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b1b150ef5426609189ff5678fd6df22f001e9d447536bba97fbdd86ead5043bc 3 | size 340884 4 | -------------------------------------------------------------------------------- /ha_rpi_case_mk2_shell.stl: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0967c77fcdab70e85377d3a234140994254e27b51453cb64fc5ba6a9a1be5eb8 3 | size 541984 4 | -------------------------------------------------------------------------------- /ha_rpi_kiosk_control.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Instructions for backlight control 4 | # 5 | # This script uses the Waveshare RPi USB Brightness library available at https://github.com/waveshare/RPi-USB-Brightness 6 | # This library appears to work well with Raspbian Bookworm, running Wayland (instead of X), and controlling the 7 inch HDMI screen 7 | # 8 | # Download the library: 9 | # git clone https://github.com/waveshare/RPi-USB-Brightness 10 | # cd RPi-USB-Brightness/64/desktop/ 11 | # Install: 12 | # chmod +x install.sh 13 | # ./install.sh 14 | # Test the library by setting brightness to 1 (10%), then to 10 (100%) 15 | # cd ../lite/ 16 | # ./Raspi_USB_Backlight_nogui -B 1 17 | # ./Raspi_USB_Backlight_nogui -b 10 18 | 19 | import RPi.GPIO as GPIO 20 | import time 21 | import subprocess 22 | 23 | #set up gpio 24 | GPIO.setmode(GPIO.BOARD) #use GPIO numbers instead of pin numbers 25 | GPIO.setup(7, GPIO.IN) #presence sensor 26 | 27 | #we'll need to keep track of whether we've already turned the screen on or off 28 | screen_on = True 29 | 30 | #continuous loop 31 | while True: 32 | #is someone present? 33 | presence_value = GPIO.input(7) 34 | print(presence_value) 35 | 36 | #if someone is present, but the screen is off, or vise-versa 37 | if screen_on ^ presence_value: 38 | #create a range which we'll use to slowly change the brightness of the screen 39 | if presence_value: 40 | brightness_steps = range(0,10) 41 | else: 42 | brightness_steps = reversed(range(0,10)) 43 | 44 | #slowly change the brightness of the screen 45 | for i in brightness_steps: 46 | subprocess.run(["./RPi-USB-Brightness/64/lite/Raspi_USB_Backlight_nogui", "-b", str(i)]) 47 | time.sleep(0.01) 48 | 49 | #remember if we've turned the screen on or off 50 | screen_on = presence_value 51 | 52 | #wait a bit before we run the loop again 53 | time.sleep(1) 54 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi Wall Panel for Homeassistant\* 2 | 3 | \*Or really anything you want to run on a Raspberry Pi kiosk attached to a wall 4 | 5 | ![Picture of the Raspberry Pi Kiosk mounted to a wall and displaying a Homeassistant window](https://cdn.hackaday.io/images/842931727075724187.jpeg) 6 | 7 | ## Project Background and More Information 8 | You can find more information about the project on the [Hackaday.io project page](https://hackaday.io/project/197028-raspberry-pi-wall-panel) 9 | 10 | Essentially this is a case for a Raspberry Pi and a 7 inch Waveshare touchscreen, intended to be used as a wall-mounted kiosk for Homeassistant or anything else you might want to look at on a wall. The case is designed to have support for the following features: 11 | 12 | * Presence sensing using a LD-2410C-P sensor 13 | * Touch-sensitive buttons using TTP223 sensors 14 | * Power over Ethernet using a RT9460 PoE module 15 | 16 | ## Project Components 17 | This project consists of three main parts as follows: 18 | 19 | 1. 3D files for the case that can be 3D printed. This repository includes both the parametric OpenSCAD file, as well as STL files that can be sliced and printed. The OpenSCAD file can potentially be changed to add additional sensors or remove unnecessary ones, or just about anything else. 20 | 2. `ha_rpi_kiosk_control.py`, a Python script that controls the screen backlight based on human presence as determined by the presence sensor. In order to use this script, some additional setup is required as detailed in the [next section](#presence-sensing-and-backlight-control) 21 | 3. Instructions on how to get various kiosk functions working in Raspberry Pi OS, and in the Wayland display server used in Raspberry Pi OS 12 Bookworm (Wayland has replaced the X window system) 22 | 23 | ## Presence Sensing and Backlight Control 24 | The `ha_rpi_kiosk_control.py` Python script reads the signal from the LD-2410C-P presence sensor, and then turns on the backlight when a human is present, and off when not. To do so, it uses the Waveshare RPi USB Brightness library available at (https://github.com/waveshare/RPi-USB-Brightness). There are more details about installation in the comments in `ha_rpi_kiosk_control.py`, but it can be installed with the following process: 25 | ``` 26 | # Download the library: 27 | git clone https://github.com/waveshare/RPi-USB-Brightness 28 | cd RPi-USB-Brightness/64/desktop/ 29 | # Install: 30 | chmod +x install.sh 31 | ./install.sh 32 | # Test the library by setting brightness to 1 (10%), then to 10 (100%) 33 | cd ../lite/ 34 | ./Raspi_USB_Backlight_nogui -B 1 35 | ./Raspi_USB_Backlight_nogui -b 10 36 | ``` 37 | 38 | Now you will just need to get the python script from this repository, make it executable, and then give it a test run: 39 | ``` 40 | wget https://github.com/tsnoad/rpi_wall_case/raw/refs/heads/main/ha_rpi_kiosk_control.py 41 | chmod +x ha_rpi_kiosk_control.py 42 | ./ha_rpi_kiosk_control.py 43 | ``` 44 | I keep this file in the home directory of my Rapberry Pi, but you can put it anywhere. Also, instead of using `wget`, you could also clone the full repository like this `git clone https://github.com/tsnoad/rpi_wall_case.git`, which will make it easier to get any potential updated versions. Just remember to update any commands that refer back to the location of the script. 45 | 46 | The script will run continually until you quit it, or shutdown the Pi, or if something goes wrong. At the moment there is no Daemon or watchdog to make sure it is always running, and restart it if necessary. I'm hoping to add this in the future 47 | 48 | ## Raspberry Pi OS and Wayland Configuration 49 | This section has instructions on how to get the following features running: 50 | * Set up multiple workspaces so we can have multiple apps running in full screen (for example Homeassistant, Klipper, etc). 51 | * Allow the RPi to read signals from the TTP223 touch sensors on the front panel via the GPIO pins, and treat this input like a keyboard key. These keys will then be used to move between the fullscreen workspaces 52 | * Automatically start the backlight control script when the Raspberry Pi boots, as well as any other apps that are desired. Ideally these apps will each be assigned to a different workspace, but I'm still trying to ge this to work 53 | 54 | ### Setup and Configuration 55 | First, open the Raspberry Pi config.txt file like this `sudo nano /boot/firmware/config.txt` and add the following lines 56 | ``` 57 | #Assign GPIO to a key 58 | #https://www.reddit.com/r/raspberry_pi/comments/1c9omhy/turning_a_gpio_button_input_into_a_simulated/ 59 | # KEY_PREVIOUSSONG 165 60 | # GPIO.board=21 GPIO.bcm=9 61 | dtoverlay=gpio-key,gpio=9,active_low=1,gpio_pull=up,keycode=165 62 | # KEY_NEXTSONG 163 63 | # GPIO.board=19 GPIO.bcm=10 64 | dtoverlay=gpio-key,gpio=10,active_low=1,gpio_pull=up,keycode=163 65 | ``` 66 | 67 | This will tell the Raspberry Pi to read the signal from the TTP223 touch sensors on GPIO pins `19` and `21`, and then when a sensor is triggered, act as if either the `KEY_PREVIOUSSONG` or `KEY_NEXTSONG` keyboard key has been pressed. Both the GPIO pins and keyboard keycodes can be changed as needed. After updating the config, the Raspberry Pi will need to be restarted 68 | 69 | Next, open the wayland config file like this `sudo nano .config/wayfire.ini` and add this to the `[core]` section: 70 | ``` 71 | [core] 72 | plugins = alpha animate autostart autostart-static command cube pixdecor expo fast-switcher fisheye grid idle invert move oswitch place resize switcher vswitch window-rules wm-actions wrot zoom winshadows 73 | vwidth = 4 74 | vheight = 1 75 | ``` 76 | 77 | As we're going to be using a number of Wayland plugins, these *may* need to be enabled, which is achieved by setting the `plugins = ` line. The trick here is that Wayland won't let you just specify a plugin you want to enable, you have to either use the default set of plugins, or specify every plugin that will be enabled, and if you miss one, you will run into all kinds of issues. The list above worked for me, but what you should do is get the default list like this `nano /etc/wayfire/defaults.ini` and copy the `plugins = ` line from there into `.config/wayfire.ini`. Then you will just need to make sure that the plugins we want are in the list, and add them if not. The plugins we need are `expo`, `vswitch`, `window-rules`, and `autostart`. 78 | 79 | The `vwidth = 4` and `vheight = 1` lines set the number of workspaces, and how they're organised. This setup gives a single row of 4 workspaces, but other configurations are possible, however the organisation isn't really important. The number of workspaces can be increased or decreased as needed. 80 | 81 | Next, add the following lines to the `.config/wayfire.ini` file: 82 | ``` 83 | [vswitch] 84 | binding_left = KEY_PREVIOUSSONG 85 | binding_right = KEY_NEXTSONG 86 | ``` 87 | This will take the touch sensor keyboard inputs we set up earlier, and tell wayfire to use these keys to switch workspaces left or right. 88 | 89 | Optionally, you can also add the following lines: 90 | ``` 91 | [expo] 92 | toggle = KEY_SPACE 93 | ``` 94 | This will trigger the expo view which shows an overview of all workspaces. At the moment this will be triggered by pressing `` and `SPACE` on a keyboard - if you have a keyboard attached - but this could be configured to another touch sensor or other GPIO input 95 | 96 | Next, add the following lines to the `.config/wayfire.ini` file: 97 | ``` 98 | [autostart] 99 | # Automatically open some apps in browser windows 100 | browser_workspace1 = chromium --new-window --window-name="workspace1" --start-maximized --start-fullscreen "http://homeassistant.local:8123" 101 | browser_workspace2 = chromium --new-window --window-name="workspace2" --start-maximized --start-fullscreen "http://homeassistant.local:8123/dashboard-weather" 102 | browser_workspace3 = chromium --new-window --window-name="workspace3" --start-maximized --start-fullscreen "http://klipper.local" 103 | # Automatically start the backlight control script when the Raspberry Pi boots up 104 | kiosk_script = lxterminal -e "~/ha_rpi_kiosk_control.py" 105 | top_script = lxterminal -e "top" #optional 106 | ``` 107 | This will automatically start the backlight control script when the Raspberry Pi boots, and will also start a couple of browser-based apps. You will need to change these lines depending on the apps you want to run, and what apps or sites you want them to display. The important part is `--window-name="workspace[1-9]"` which we'll use to put each window where we want it in the next step 108 | 109 | Finally, add the following lines to the `.config/wayfire.ini` file: 110 | ``` 111 | [window-rules] 112 | # Move each browser window to its own workspace 113 | browser_workspace1_move = on created if title is "workspace1" then assign_workspace 0 0 114 | browser_workspace2_move = on created if title is "workspace2" then assign_workspace 1 0 115 | browser_workspace3_move = on created if title is "workspace3" then assign_workspace 2 0 116 | # Move the terminal running the backlight control script to its own workspace 117 | kiosk_script_maximize = on created if app_id is "lxterminal" then maximize 118 | kiosk_script_move = on created if app_id is "lxterminal" then assign_workspace 3 0 119 | ``` 120 | These window rules will tell Wayland to move an app to a particular workspace when it starts. Eg, `lxterminal` (in which the backlight control script should be running) is moved to workspace `3 0`, meaning column 4, row 1 of the grid of workspaces. 121 | 122 | Now, restart the Raspberry Pi and wait to see if all of this has worked as expected... Good luck! 123 | --------------------------------------------------------------------------------