This commit is contained in:
Joshua King
2026-10-02 12:23:58 -04:00
parent f0af1dbbcb
commit ad1e7e6d01
219 changed files with 488314 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Concealed chair camera pod — first-fit prototype
The Freddy mask will sit in a skeleton's lap on the single wicker chair shown in [Joshua's actual-chair photo](../../reference_images/wicker_chair_skeleton.png). The chair faces open concrete. A first position to **test** is beneath the **front half of the armrest on the right side of the photo**, with the lens and IR openings just below the woven front/outer lip. This keeps the camera above the seat underside and away from the cushion. The skeleton's hand rests farther back on that armrest, so the pod must stay clear of its fingers and any hanging decorations. This is a candidate position, not a verified installation; camera frames must confirm both approaches are visible. The camera can supply target coordinates to Freddy's eyes after calibration.
![Actual-chair placement sketch](chair-camera-placement.svg)
## Files and size
- `chair_camera_pod.scad` is the editable master. Set `part` to `tray` or `cover` to export each part in a CAD tool.
- `chair_camera_tray_draft.stl` and `chair_camera_cover_draft.stl` are printable first-fit exports. They use a 0.4 mm grid because OpenSCAD is unavailable on this machine; re-export from the CAD master for smooth final holes.
- `export_draft_stl.py` regenerates those prototype STLs.
The tray is approximately **78 × 54 × 13 mm** including its rear strap ears. The removable front cover is 54 × 54 × 2.4 mm. Two 5 mm strap slots at the sides let soft hook-and-loop bands pass through sturdy parts of the armrest's underside weave or internal frame. Keep the straps and cable beneath the armrest so they do not cross its visible top. Avoid tightening a zip tie against a single wicker strand. The board rests on 3 mm spacers and has adjustable corner slots with nominal hole centers 15–19 mm from the center. Check the actual V1.1 board before choosing M2.5 nylon screws.
The front cover uses an 18 mm lens opening and two vertical IR openings. Their placement is estimated from the [DFRobot front-board illustration](https://dfimg.dfrobot.com/5d57611a3416442fa39bffca/wiki/21557c4dd23b63b915462b8f108380f9_0x0.png.webp), which is marked V1.0; verify against Joshua's V1.1 hardware. The openings are separate and unglazed to avoid the close-window IR reflection seen during bench testing. Leave the cover off for the first live camera test and install it only after confirming no vignetting, LED blockage, or lens contact. The USB-C edge points downward so its cable can exit through the open bottom and form a drip loop.
## Placement and fit check
1. Temporarily hold the camera under the front half of the photo-right armrest. Keep the lens and IR openings just below its front/outer wicker lip, looking toward the open path rather than through the weave or at the skeleton's hand. Check that the 78 × 54 mm tray fits that underside without protruding into normal visitor view.
2. Capture frames with a person approaching from the left, center, and right at the real walkway distance. Check that the armrest edge, skeleton hand/leg, and mask do not block the tracking zone. If they do, slide the pod forward or adjust its angle before choosing another concealed point on this chair.
3. Repeat after dark with the onboard IR on. Check both the camera image and whether the pod is noticeable from the walking path. Adjust the angle using a temporary foam wedge before printing a permanent angled bracket.
4. Once the position works, thread two soft straps through the rear ears and through sturdy underside sections of the armrest or its frame. Add a thin non-slip pad between the pod and wicker, route the cable toward the back of the chair with a drip loop, and protect the electrical connection from wind-driven rain.
This housing is **not waterproof**. It relies on the covered porch and chair seat for shelter; the front optical openings must remain clear. The final angle and strap routing depend on a real fit check on the chair.
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 560" role="img" aria-labelledby="title desc">
<title id="title">Concealed camera candidate beneath the actual chair's photo-right armrest</title>
<desc id="desc">Front-view placement sketch of the single wicker chair, skeleton, and a small camera pod tucked beneath the front half of the right armrest as seen in Joshua's photo.</desc>
<rect width="900" height="560" fill="#f6f4ef"/>
<text x="42" y="47" font-family="Arial,sans-serif" font-size="26" font-weight="700" fill="#17222b">Actual chair: camera placement to test</text>
<text x="42" y="77" font-family="Arial,sans-serif" font-size="16" fill="#4d5960">Front view toward the seated skeleton · photo-right is diagram-right</text>
<path d="M170 478 H725" stroke="#bbb7ae" stroke-width="3"/>
<rect x="280" y="150" width="332" height="310" rx="22" fill="#50463e"/>
<rect x="312" y="157" width="268" height="243" rx="15" fill="#e8e6df"/>
<rect x="308" y="345" width="274" height="77" rx="11" fill="#f5f3ee"/>
<rect x="211" y="253" width="94" height="206" rx="11" fill="#3a312d"/>
<rect x="586" y="253" width="94" height="206" rx="11" fill="#3a312d"/>
<path d="M211 264 H305 M586 264 H680" stroke="#736055" stroke-width="16" stroke-linecap="round"/>
<circle cx="447" cy="169" r="35" fill="#d8c99e"/>
<path d="M446 205 L446 241 M392 245 Q446 213 499 245 M392 245 L369 288 M499 245 L610 286 M405 261 Q447 301 487 261 M446 283 L446 353 M446 350 L392 386 M446 350 L501 386 M392 386 L375 467 M501 386 L518 467" fill="none" stroke="#d8c99e" stroke-width="13" stroke-linecap="round"/>
<path d="M607 283 L630 279" stroke="#d8c99e" stroke-width="12" stroke-linecap="round"/>
<rect x="616" y="291" width="46" height="23" rx="4" fill="#1c2528" stroke="#1c9b81" stroke-width="3"/>
<circle cx="640" cy="302" r="7" fill="#10171a" stroke="#79dec7" stroke-width="2"/>
<path d="M640 303 L741 369 M640 303 L741 233" stroke="#1c9b81" stroke-width="2" stroke-dasharray="7 6"/>
<circle cx="680" cy="348" r="3" fill="#1c9b81"/>
<path d="M663 302 H770" stroke="#17222b" stroke-width="2"/>
<text x="680" y="286" font-family="Arial,sans-serif" font-size="16" font-weight="700" fill="#17222b">Pod beneath front half</text>
<text x="680" y="308" font-family="Arial,sans-serif" font-size="14" fill="#3a4b53">of photo-right armrest</text>
<text x="42" y="516" font-family="Arial,sans-serif" font-size="16" fill="#4d5960">Keep lens + IR below wicker lip; confirm both approach directions with live frames.</text>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1,67 @@
// Concealed DFRobot DFR1154 V1.1 chair camera pod, prototype v1.
// Strap beneath the walkway-side wicker armrest; +Z faces the walkway.
// Board and optic positions MUST be verified on the physical V1.1 unit.
part = "tray"; // "tray", "cover", or "assembly"
$fn = 64;
module rounded_slot_x(x0, x1, y, diameter, z0, height) {
hull() {
translate([x0,y,z0]) cylinder(d=diameter,h=height);
translate([x1,y,z0]) cylinder(d=diameter,h=height);
}
}
module rounded_slot_y(x, y0, y1, diameter, z0, height) {
hull() {
translate([x,y0,z0]) cylinder(d=diameter,h=height);
translate([x,y1,z0]) cylinder(d=diameter,h=height);
}
}
module tray() {
difference() {
union() {
// Square rear plate and short side/top shroud. Bottom stays open
// for the board's USB-C plug and a downward cable drip loop.
translate([-27,-27,0]) cube([54,54,3]);
translate([-27,-27,3]) cube([3,54,10]);
translate([24,-27,3]) cube([3,54,10]);
translate([-27,24,3]) cube([54,3,10]);
// Four board spacers (3 mm high).
for (x=[-17,17], y=[-17,17])
translate([x,y,3]) cylinder(d=7,h=3);
// Two rear ears accept soft hook-and-loop straps threaded through
// the wicker frame. They stay behind the camera's front plane.
translate([-39,-18,0]) cube([13,36,4]);
translate([26,-18,0]) cube([13,36,4]);
}
// Adjustable PCB slots accommodate 30-38 mm horizontal hole pitch.
for (y=[-17,17], s=[-1,1])
rounded_slot_x(s*15,s*19,y,3.0,-1,9);
// Strap slots in the ears.
for (x=[-33,33])
rounded_slot_y(x,-11,11,5,-1,6);
// M2 cover screws: pilot bores into the side-wall corner lands.
for (x=[-25.5,25.5], y=[-23,23])
translate([x,y,5]) cylinder(d=1.6,h=9);
}
}
module cover() {
difference() {
translate([-27,-27,0]) cube([54,54,2.4]);
// Approximate source-board layout: lens ~6 mm above board center;
// two IR emitters on each side. Kept as separate unglazed openings
// so the IR cannot reflect from a shared close window into the lens.
translate([0,6,-1]) cylinder(d=18,h=5);
for (x=[-15.5,15.5])
rounded_slot_y(x,0,11,7,-1,5);
// The four side-wall screws secure a removable faceplate.
for (x=[-25.5,25.5], y=[-23,23])
translate([x,y,-1]) cylinder(d=2.4,h=5);
}
}
if (part == "tray") tray();
else if (part == "cover") cover();
else { tray(); translate([0,0,13]) cover(); }
@@ -0,0 +1,101 @@
"""Grid-export prototype STLs for chair_camera_pod.scad (0.4 mm pitch)."""
from pathlib import Path
import struct
import numpy as np
HERE = Path(__file__).resolve().parent
STEP = 0.4
def grid(lo, hi):
axes = [np.arange(a,b,STEP,dtype=np.float32)+STEP/2 for a,b in zip(lo,hi)]
return np.meshgrid(*axes,indexing="ij",sparse=True)
def box(X,Y,Z,x0,x1,y0,y1,z0,z1):
return ((X>=x0)&(X<x1)&(Y>=y0)&(Y<y1)&(Z>=z0)&(Z<z1))
def disk(X,Y,Z,x,y,r,z0,z1):
return ((X-x)**2+(Y-y)**2<=r*r)&(Z>=z0)&(Z<z1)
def xslot(X,Y,Z,a,b,y,r,z0,z1):
return (box(X,Y,Z,min(a,b),max(a,b),y-r,y+r,z0,z1)
|disk(X,Y,Z,a,y,r,z0,z1)|disk(X,Y,Z,b,y,r,z0,z1))
def yslot(X,Y,Z,x,a,b,r,z0,z1):
return (box(X,Y,Z,x-r,x+r,min(a,b),max(a,b),z0,z1)
|disk(X,Y,Z,x,a,r,z0,z1)|disk(X,Y,Z,x,b,r,z0,z1))
def tray():
lo,hi=(-39,-27,0),(39,27,14)
X,Y,Z=grid(lo,hi)
solid=box(X,Y,Z,-27,27,-27,27,0,3)
solid|=box(X,Y,Z,-27,-24,-27,27,3,13)
solid|=box(X,Y,Z,24,27,-27,27,3,13)
solid|=box(X,Y,Z,-27,27,24,27,3,13)
solid|=box(X,Y,Z,-39,-26,-18,18,0,4)
solid|=box(X,Y,Z,26,39,-18,18,0,4)
for x in (-17,17):
for y in (-17,17): solid|=disk(X,Y,Z,x,y,3.5,3,6)
for x in (-17,17):
for y in (-17,17): solid&=~xslot(X,Y,Z,x-2,x+2,y,1.5,-1,9)
for x in (-33,33): solid&=~yslot(X,Y,Z,x,-11,11,2.5,-1,6)
for x in (-25.5,25.5):
for y in (-23,23): solid&=~disk(X,Y,Z,x,y,0.8,5,14)
return solid,lo
def cover():
lo,hi=(-27,-27,0),(27,27,2.4)
X,Y,Z=grid(lo,hi)
solid=box(X,Y,Z,-27,27,-27,27,0,2.4)
solid&=~disk(X,Y,Z,0,6,9,-1,4)
for x in (-15.5,15.5):solid&=~yslot(X,Y,Z,x,0,11,3.5,-1,4)
for x in (-25.5,25.5):
for y in (-23,23):solid&=~disk(X,Y,Z,x,y,1.2,-1,4)
return solid,lo
def write_stl(path,solid,lo):
exposed=[]
for axis in range(3):
for sign in (-1,1):
neighbor=np.roll(solid,-sign,axis=axis)
face=solid&~neighbor
edge=[slice(None)]*3;edge[axis]=0 if sign==-1 else -1
face[tuple(edge)]=solid[tuple(edge)]
exposed.append((axis,sign,np.argwhere(face)))
facets=sum(2*len(v) for _,_,v in exposed)
with path.open("wb") as out:
out.write(b"Freddy concealed chair pod prototype".ljust(80,b"\0"))
out.write(struct.pack("<I",facets))
for axis,sign,points in exposed:
normal=[0.,0.,0.];normal[axis]=float(sign)
for i,j,k in points:
x0,y0,z0=(lo[0]+i*STEP,lo[1]+j*STEP,lo[2]+k*STEP)
x1,y1,z1=x0+STEP,y0+STEP,z0+STEP
if axis==0:
x=x1 if sign==1 else x0
quad=[(x,y0,z0),(x,y1,z0),(x,y1,z1),(x,y0,z1)]
elif axis==1:
y=y1 if sign==1 else y0
quad=[(x0,y,z0),(x0,y,z1),(x1,y,z1),(x1,y,z0)]
else:
z=z1 if sign==1 else z0
quad=[(x0,y0,z),(x1,y0,z),(x1,y1,z),(x0,y1,z)]
if sign==-1:quad.reverse()
for a,b,c in ((0,1,2),(0,2,3)):
out.write(struct.pack("<12fH",*normal,*quad[a],*quad[b],*quad[c],0))
print(path.name,facets,"facets",round(path.stat().st_size/1e6,1),"MB")
if __name__=="__main__":
for name,build in (("chair_camera_tray_draft.stl",tray),
("chair_camera_cover_draft.stl",cover)):
solid,lo=build();write_stl(HERE/name,solid,lo)