Research notes, Fri 9 Oct 2026 (week 41), written for AI Foundry. AI Foundry work. Scope: continuous monitoring of the BACK of an ET-SoC-1 PCIe card's PCB at 3–10 cm in the Studio 45 rack. Builds on three prior gyroscope reports rather than repeating them: - 3 Oct, camera + Pixel streaming — the rig, the hub, ScreenStream, P2 Pro vs P3. - 6 Oct, P3 reading list — lock-in precedents, the research lineage, emissivity. - 6 Oct, riser + mount §4 — the box contents, cable chain, long-run rules.
1. Optics — pixel pitch settled by geometry
The vendor FOV and focal length over-determine the pitch, so it can be derived rather than trusted:
| pitch | array size | HFOV from 2·atan(W/2f), f = 4.3 mm |
matches vendor 40° × 30.2°? |
|---|---|---|---|
| 12 µm | 3.072 × 2.304 mm | 39.31° × 30.00° | yes, within 1.7% |
| 17 µm | 4.352 × 3.264 mm | 53.68° | no, off by 34% |
→ 12 µm confirmed, and the vendor says so too. The P-Series manual's spec table (rev. 28 Mar 2025)
reads Detector VOx 256×192 @12µm, NETD ≤35mK, 25Hz, FOV 40°(H)x30.2°(V),
−20°C~600°C, ±2°C (±2% of reading), Manual Focusing Lens, body 57 × 27 × 17.2 mm
(manual PDF;
vendor page — note its slug is
p3-pcb-master: PCB work is the marketed use). Band is 8–14 µm (manual FAQ ⑤). IFOV = 12 µm / 4.3 mm =
2.79 mrad from the pitch, 2.73 mrad from 40°/256 — the same number twice. The camera reports its
sensor as InfiRay RTC2121W in register 0x0e
(p3-ir-camera issue #2); USB VID 0x3474 is
registered to Yantai Raytron, InfiRay's parent, and the interface string is com.thermalmaster.pios3
(Gough Lui's USBTreeView dump). The likely module is the
InfiRay Tiny1-C,
whose lens options are 2.0 / 3.2 / 4.3 mm — unconfirmed.
Three corrections to the working spec:
- The range is two bands, not one sweep. −20…150 °C and ~100…550 °C, not selectable together; the
advertised 600 °C does not appear in the high band as reviewers saw it
(LON.TV,
IT-Connect).
This matches p3-ir-camera's high/low gain exactly. Use the −20…150 °C band.
- The f-number is published nowhere. The DOF column below assumes f/1.0; at f/1.2 every DOF figure is
20% larger. The detector class is spec'd at f/1.0 and commercial 4.3 mm LWIR optics run f/1.0–f/1.2
(MKS SUPIR 4.3 mm f/1.2).
- Neither vendor app exports per-pixel data. PCWorld "was unable to find an option to export the
measurement data as a CSV file" (review);
video is burned-in palette only. That alone decides the streaming question: anything radiometric has to
come from the open drivers, not the app.
Footprint against distance
Two columns, because at these distances the far-field formula is wrong. W = 2·d·tan(FOV/2) assumes
d ≫ f; the thin-lens result is W = array · (d − f)/f, which subtracts one focal length (4.3 mm) of
standoff. The difference is 23% at 2 cm and under 3% past 15 cm.
| distance | far-field W × H (mm) | thin-lens W × H (mm) | mm/pixel | magnification | DOF at f/1.0 | 45 mm package spans |
|---|---|---|---|---|---|---|
| 2 cm | 14.6 × 10.8 | 11.2 × 8.4 | 0.044 | 0.274× | 0.41 mm | 1027 px (overflows) |
| 3 cm | 21.8 × 16.2 | 18.4 × 13.8 | 0.072 | 0.167× | 1.00 mm | 627 px (overflows) |
| 5 cm | 36.4 × 27.0 | 32.6 × 24.5 | 0.128 | 0.094× | 2.97 mm | 353 px (overflows) |
| 6.2 cm | 45.1 × 33.5 | 41.2 × 30.9 | 0.161 | 0.075× | 4.64 mm | 279 px — just fills the frame |
| 8 cm | 58.2 × 43.2 | 54.1 × 40.6 | 0.211 | 0.057× | 7.86 mm | 213 px |
| 10 cm | 72.8 × 54.0 | 68.4 × 51.3 | 0.267 | 0.045× | 12.4 mm | 168 px |
| 15 cm | 109.2 × 80.9 | 104.1 × 78.1 | 0.407 | 0.030× | 28.4 mm | 111 px |
| 20 cm | 145.6 × 107.9 | 139.8 × 104.9 | 0.546 | 0.022× | 50.8 mm | 82 px |
| 37 cm (shelf edge, 3 Oct plan) | 269.3 × 199.7 | 261.3 × 195.9 | 1.021 | 0.012× | 176 mm | 44 px |
Derived with f = 4.3 mm, 12 µm pitch, blur circle = one pixel, f/1.0 assumed (the P3's aperture is not
published). DOF is the binding constraint, not resolution. At 3 cm the whole depth of field is 1.0 mm,
so a fixed fixture would have to hold its standoff to ±0.5 mm across hours of thermal expansion — not
achievable with printed plastic beside a 60–80 °C board. At 8 cm the DOF is ±4 mm, which a printed
fixture holds easily.
Design distance: 7–8 cm. It puts the 45 mm package on 213 pixels at 0.21 mm/pixel, keeps the whole package plus its decoupling field in frame, and has enough DOF to survive the fixture creeping.
The "focus from 8 mm" claim is the one spec to distrust
It is not in the manual at all, and the vendor page gives three mutually inconsistent ranges on one page — "8mm … to 80yd", "3-in to 33-ft", "From 8mm to 30m" (vendor page). No reviewer has measured the near end. Gough Lui only repeats "as close as 8mm" and calls it "ridiculously small".
The physics says why to be careful: at an 8 mm object distance a 4.3 mm lens sits at 1.16× magnification, needing the lens to travel 5.0 mm past its infinity position inside a 17.2 mm body. The field would be 2.64 × 1.98 mm, i.e. 10 µm per pixel, with 0.4 mm of DOF — and above 1:1 the effective f-number roughly doubles, costing about 4× the signal and visibly spoiling the 35 mK figure. Treat 8 mm as an optical limit, not a working spec. Measure it on day one with a ruler in frame; it changes nothing about the 7–8 cm design point.
There is no macro accessory for the P3 — its macro is the manual focus ring. The siblings P2 Pro / P2 Max take a magnetic macro lens, which the same manual rates at a 4 cm working distance resolving 0.25 mm; it is not sold separately at any published price. So nothing to buy here.
2. The protocol and the drivers (verified against the repos)
p3-ir-camera (github.com/jvdillon/p3-ir-camera, Apache-2.0,
95★, 37 commits, last commit 2026-02-07 e3205dc, no releases, Python ≥3.10, classifier
POSIX :: Linux only):
- 197,632-byte frame =
2 · (2·192 + 2) · 256, i.e. 386 rows × 256 cols of little-endianu2. Rows 0–191 IR brightness (hardware AGC, 8-bit in the low byte), rows 192–193 metadata, rows 194–385 the 16-bit raw thermal field (P3_PROTOCOL.md). °C = raw/64 − 273.15— raw is 1/64 kelvin.- Bulk IN
0x81, two transfers per frame (197,644 then 12), 12-byte markers with a counter that wraps at 2048 and advances ~40 per frame; desync is detectable from it. - Control:
bRequest 0x20 / bmRequestType 0x41writes an 18-byte command,0x21/0xC1reads,0xEE/0x40 wIndex=1starts streaming. CRC16-CCITT is computed but the camera does not verify it. - Gain: high −20…150 °C, low 0…550 °C. Use high gain for a 50–90 °C board.
- Shutter/NUC fires about every 90 s even when streaming is stopped, and emits 3 odd-sized transfers (204,800 / 2,060 / 12). Log those timestamps so lock-in skips them.
- Emissivity is not a camera setting. Changing it in the vendor Windows tool produces no USB traffic
(issue #2);
EnvParamsexists but is never sent (#4). All emissivity correction is host-side — which is good news: it can be applied after the fact to stored raw frames. - Open issues: #17
trigger_shuttercrashes on the P1 (ValueError: memoryview assignment), with a second silent defect — the shutter segment constants are P3 measurements and on other models read past the buffer, so memoryview clamps and the frame silently loses a segment. #20 a large unanswered refactor PR. Closed: #5 corrupted frames on Windows, #6/#7USBErrorcrashes, fixed by 16 KB chunked reads with end-marker resync ("On Mac the error says 'overflow'; on Windows 'the device is not working', and is not recoverable" — Linux recovers either way). - macOS works unofficially with
brew install libusb(#12, Tahoe 26.1 on M1 Pro; also #3). PR #16 "Add support for MacOS" was closed unmerged. LOCK-IN.md(link): skeleton from diminDDL/IR-Py-Thermal, an ESP32C3 reading1/0on serial drives a MOSFET;--period 1.0s,--integration 60.0s; four panels (in-phase, quadrature, amplitude, phase); 330 µW in a 33 kΩ resistor clearly visible, far below the single-frame noise floor. The README still documents the old--frequency 0.1flag, renamed to--period.
Six more independent drivers now exist, all newer than jvdillon's:
| repo | lang / licence | ★ | last commit | what it adds |
|---|---|---|---|---|
| skywalker1905/thermal-camera-viewer | Python / Apache-2.0 | 16 | 2026-09-21 | PyQt5 GUI, ROI stats, 6 palettes, MP4 via ffmpeg, .deb, writes to v4l2loopback /dev/video10. 0 open issues |
| ferrolho/thermal-master-viewer | JS / Apache-2.0 | 0 | 2026-08-16 | WebUSB, in a browser, works on Android. PNG + per-pixel CSV export, hover temperature, wake-lock |
| xaionaro-go/thermalmaster | Go / CC0-1.0 | 9 | 2026-09-06 | v4l2loopback, 40+ colormaps, joint bilateral upsampling |
| josepgomis/unofficial-thermalmaster-p3-sdk | Python / Apache-2.0 | 1 | 2026-10-02 | ROS 2 Humble, 3474:45a2 only. The most recently maintained |
| heatpeek/HeatPeek | Swift / MIT | 0 | 2026-09-23 | macOS via IOUSBHost, no libusb; also handles the P2 Pro |
| Phalphy/OpenP3 | Python / MIT | 17 | 2026-01-15 | abandoned at 2 commits |
Not UVC, under any mode — three independent confirmations. The interfaces are vendor class 0xFF:
iface 0 alt 0 commands, iface 1 alt 0 idle, iface 1 alt 1 video with ep 0x81 IN / 0x02 OUT
(ferrolho README, which also
explicitly rejects a community claim of bInterfaceClass=0x0E); OpenP3 calls it "a proprietary USB
handshake that prevents it from working with standard UVC drivers"; HeatPeek's table lists the P3 as
"vendor protocol" against the P2 Pro's "UVC". Every "UVC" in this ecosystem is a synthetic
v4l2loopback sink the host writes into, not the camera presenting itself. Class 0xFF is the reason this
is tractable: it is not in WebUSB's protected-class list and no OS driver binds to it, so a browser can
claim it (Chromium intent-to-ship on interface-class filtering).
Android: no native open-source app or library handles 3474:45A2. The nearest analogue,
JAMBD/android_infiray_viewer, hard-filters
0x0BDA:0x5830 and uses a UVC-class device_filter.xml that can never match the P3's 0xFF interfaces.
So the 6 Oct finding stands — a native Kotlin app would be the first. But it is now unnecessary (§3).
3. The geometry problem the mount has to solve first
From the 3 Oct rack survey: each ET card stands at the back of its open frame, its PCB back facing the CPU tower cooler a few cm away, with a 120 mm fan against the heatsink side. So the 3–10 cm working distance in the ask is not a preference — it is all the gap there is. Three ways out, in increasing cost:
- Move the card to
PCIEX8, slot 4, about 61 mm farther from the CPU — free, keeps Gen4 x8, already worked out in the 6 Oct report §3. This is what turns a 3 cm gap into a ~9 cm one and makes the whole 7–8 cm design point possible. - A 45° fold mirror — a periscope, so the camera lies flat alongside the card instead of facing it. The optical path still measures 7–8 cm, but the required gap collapses to the mirror's diagonal (about 35 mm for a 25 × 25 mm mirror). This is the cheapest fix if the card cannot move. - Use bare polished aluminium or front-surface gold, never an SiO/SiO₂-protected mirror. A DTIC study measured protected aluminium falling to 71% at 45° (and 54% at 60°) in the 8–12 µm band for one polarisation, while bare aluminium changes only ~0.5% between 0° and 60° (ADA113288); Tydex attributes the dip to the Si–O bond absorbing at 9–11 µm and recommends unprotected aluminium for in-instrument use. Diamond-polished 6061 measures about 95% over 7–14 µm (Michigan State, 2002); protected gold averages >98% from 0° to 60° (Excelitas, Edmund). - The mirror's own 2–5% emission adds a bias at the mirror's temperature, so keep it out of the card's plume and log its temperature; and check the sightline, because a fold mirror can return the camera's own cold housing into the frame (narcissus).
- A riser or cable kit — the 6 Oct report's §3 options, €120–140, which also buys a place for a power meter and a hard cut-off.
A fourth option is to accept an oblique view. At 30–45° off normal the footprint keystones and the apparent emissivity of FR4 falls, but for differential work (this pixel vs that pixel, this second vs last second) it is nearly free. Say so in the record rather than pretending the geometry is clean.
The camera's own thermal envelope — the constraint that sizes the mount
The P-Series manual states a recommended service temperature of −10 °C to +55 °C and a storage range of −40 °C to +85 °C (manual PDF, Precautions and spec table). Parked 7 cm from a 60–80 °C board inside a rack, the camera's metal body sits in the plume. Two consequences:
- The mount must keep the camera in moving room air, not in the card's exhaust. The 6 Oct report already said "keep it out of the exhaust… a microbolometer drifts with its own temperature"; the manual turns that from advice into a limit. A thin printed heat break between the fixture and the camera cradle, and a sightline that crosses the plume rather than sitting in it, are the two cheap measures.
- Log the camera's own temperature. The firmware log readable from the status register carries shutter events (P3_PROTOCOL.md); an external DS18B20 taped to the body is the simpler route, and it gives the drift regressor the covariate it needs.
The manual also confirms that emissivity, distance, ambient temperature, humidity and reflected temperature are all host-side corrections ("Temperature Correction: Emissivity, Distance, Environmental temperature"), consistent with issue #2 finding no USB traffic when emissivity changes. Capture raw and correct later.
4. The physics that sets the standoff — and it is not 3 cm
The PCB is a low-pass filter in both space and time, and its blur, not the camera's pixel, is what limits
what the backside can show. Treating the card as a conducting plate losing heat from both faces, the
lateral spreading length is L = sqrt(k_eff·t / 2h).
For a 6-layer, 1.57 mm card with 6 × 35 µm copper, the in-plane effective conductivity is 52 W/m·K (13% copper by thickness; FR4 in-plane ~0.9, through-plane ~0.3, copper 385 W/m·K):
| convection on the back | h (W/m²K) | spreading length L | steady-state hot-spot FWHM |
|---|---|---|---|
| still air | 5 | 91 mm | ~210 mm |
| natural convection | 10 | 64 mm | ~150 mm |
| light airflow in the rack | 30 | 37 mm | ~87 mm |
| the card's 120 mm fan | 60 | 26 mm | ~61 mm |
So in steady state the backside is a board-wide blob, 60–210 mm across, wider than the 45 mm package. No standoff and no pixel pitch recovers per-shire structure from a static backside image. That matches the lab's own 30 Sep finding that a single frame of the lid "shows nearly one temperature", and the 6 Oct reading list's conclusion that lock-in is the right tool for this view.
Lock-in changes the answer, because modulation frequency sets the resolution. The thermal diffusion
length is µ = sqrt(α/(π·f)), with in-plane α = 3.1 × 10⁻⁵ m²/s:
| modulation | period | in-plane µ (resolution) | camera pixels per µ at 8 cm | at 20 cm |
|---|---|---|---|---|
| 0.05 Hz | 20 s | 14 mm | 66 | 26 |
| 0.1 Hz | 10 s | 9.9 mm | 47 | 18 |
| 0.5 Hz | 2 s | 4.4 mm | 21 | 8 |
| 1 Hz | 1 s | 3.1 mm | 15 | 6 |
| 2 Hz | 0.5 s | 2.2 mm | 10 | 4 |
Against this there is a ceiling: the heat has to cross the board, and τ = t²/α_z low-passes it.
With bare FR4 (k_z ≈ 0.3) τ ≈ 14 s, corner 0.011 Hz; under a BGA's dense via field (k_z ≈ 5–20)
τ ≈ 0.2–0.8 s, corner 0.19–0.75 Hz. The usable window is roughly 0.1–1 Hz, giving 3–10 mm of
effective spatial resolution — which is shire-scale, so the experiment is worth doing. LOCK-IN.md's
--period 1.0 default sits at the top of that window; plan to sweep 1, 2, 5, 10 and 20 s periods and
expect amplitude to rise and resolution to fall as the period lengthens.
Three conclusions that change the hardware plan
- The camera massively oversamples the physics. At 8 cm, 0.21 mm/pixel against a 3–10 mm diffusion
length is 15–47× oversampling. Bin the frames instead: 4 × 4 binning turns 35 mK of temporal
noise into 8.8 mK (√16) at 0.84 mm/pixel, still 4–12 px per µ, for the cost of a
reshape(...).mean(). ⚠️ But see §10: temporal noise is not the limit — a 0.6 °C fixed-pattern non-uniformity is, and binning does not touch it. Lock-in does. - 15–20 cm is sufficient, and safer. At 20 cm the pixel is 0.55 mm, 2 × 2 binning gives 1.1 mm at 17 mK, and 6–18 px per µ — still oversampled. In exchange the camera leaves the plume (the manual's 55 °C limit), the DOF grows to ±25 mm so no fixture tolerance matters, the whole 146 × 108 mm frame covers the card, and the mount becomes a clamp on a shelf post instead of a fixture inside the gap. If the goal is die-activity mapping, mount at 15–20 cm and don't build the close-up fixture at all.
- The close-up fixture is for a different target. 7–8 cm earns its keep on discrete heat sources that really are small and self-emitting — the VRM FETs and inductors on the back, individual decoupling caps, a solder defect, a bare package lid or a decapped die. Those are not blurred by the board, because they are the source. Build the fixture if that is the experiment; don't build it for the die map.
5. Streaming — the decisive finding is WebUSB
Neither vendor app helps. No casting, no RTSP, no radiometric export: PCWorld could find no CSV export (review), video is burned-in palette, and the Android still is a 1440 × 1080 "radiometric" JPEG while Windows writes 512 × 384 — two formats neither of which the other can re-analyse (Gough Lui). Turn X³ off: "The X³ image enhancement mode did not deliver significant benefits… while increasing the chances of image artifacts" (same review).
But the camera's vendor interface class 0xFF means a browser can claim it. WebUSB's protected-class
filter blocks audio, video, HID, mass storage, smart card and wireless-controller interfaces; vendor-specific
is not on that list (Chromium intent-to-ship),
and no OS driver binds to class 0xFF, so the interface is free. Chrome for Android has supported WebUSB
since Chrome 61 (MDN compat);
Android WebView does not, and Firefox and Safari have no WebUSB at all.
ferrolho/thermal-master-viewer (Apache-2.0,
pushed 2026-08-16) already does this, and its README's platform table says of Android: "works — plug the
camera straight into the phone and open the page." It gives 25 fps at 256 × 192, hover for per-pixel
temperature, live max/min/centre/average, freeze, one-click PNG and per-pixel CSV in °C, and a Screen
Wake Lock to keep the display on. A hosted copy answers HTTP 200 at
https://ferrolho.github.io/thermal-master-viewer/ (checked Fri 9 Oct 2026). It needs a secure context —
https:// or http://localhost; a file:// URL will not work — and on Linux the same udev rule for
3474:45a2.
That is a zero-install path to a live thermal view on the Pixel today, with radiometric data the vendor app cannot give. It is not yet streaming off the phone, which is the gap to close.
Closing the gap: a WebSocket push is a small fork
The repo is 11 small ES modules; web/js/usb.js (10 KB) is the whole device layer and its Camera class
hands each frame out as {ir, thermal, w, h} with thermal a Uint16Array(256*192)
(source). Adding
ws.send(thermal.buffer) in the frame loop is tens of lines. Bandwidth: 98,304 B/frame, so
2.4 MB/s at 25 Hz, 0.5 MB/s at 5 Hz — fine over Wi-Fi, and 5 Hz is the sane default given §4's
0.1–1 Hz physics.
The blocker is HTTPS, and it is a one-line fix with a prerequisite. A page on
https://ferrolho.github.io cannot open a ws:// socket to a tailnet host — mixed content. Options:
- Enable HTTPS on the tailnet and host the fork there. tailscale serve already fronts
http://intel/ on port 80 → 127.0.0.1:31080, and I verified on this machine (Fri 9 Oct) that
nothing answers on port 443, so HTTPS certificates are not yet provisioned. Turning them on is an
admin-console toggle plus tailscale cert (Enabling HTTPS,
Tailscale Serve). Then the phone loads
https://<host>.tailf64870.ts.net/thermal/, which is a secure context, and wss:// to the same origin
needs no extra certificate work. Note the hostname lands in public Certificate Transparency logs.
- Serve the page from http://localhost on the phone (Termux + python3 -m http.server), which also
counts as a secure context, and ws:// from an http: page is not mixed content. Clunkier, no new certs.
Why the phone is still the wrong permanent host
- Chrome must stay in the foreground with the screen on (the wake lock is why the repo ships one), so burn-in and throttling both apply, and the 3 Oct report's reboot problem stands: nobody is at the lab to tap anything.
- The phone has to be mounted in the rack too, and the camera's 48–50 cm in-box extension is the only cable the 6 Oct report would trust without an adapter chain.
- A Linux host needs none of this: the drivers are native,
systemdrestarts them, and the data never leaves a machine that is already on the tailnet.
So: phone for the walk-around and the first pictures today; Linux for the permanent rig.
6. Material for a printed fixture
Numbers from manufacturer data, with the load condition stated, because HDT without a load is meaningless:
| material | heat deflection / softening | source | verdict for this job |
|---|---|---|---|
| PLA | ~55–60 °C Tg, softens in warm air | — | no. Creeps under its own weight beside this card |
| PETG | HDT 68 °C at 0.45 MPa, 62 °C at 1.8 MPa | Prusament PETG TDS v1.1 (ISO 75, reseller-hosted copy) | marginal. Fine for an unloaded cradle, not for a cantilever |
| ASA | ~93 °C at 0.45 MPa ("no visible deformation up to nearly 93 °C", Prusa) | Prusament ASA | primary choice. Needs an enclosure and ≥250 °C hotend |
| PC Blend | "heat tolerance up to 113 °C" (test condition unstated) | Prusament PC Blend | fallback if ASA warps; hygroscopic, 270 °C+ |
| PA / nylon, PPS, PEEK | higher still | — | overkill, and nylon absorbs water and moves |
Creep, not Tg, is the failure mode. A part at 50 °C under constant load relaxes over days even well below its HDT, and §1's DOF table says the standoff has to hold to a few mm. Two measures beat any material choice: keep the plastic unloaded (let it locate the camera, not span a distance) and make the span metal. Print the cradle and the focus-ring collar; buy the arm.
7. Design brief for the fixture
Body to design around: 57 × 27 × 17.2 mm, metal, ~26 g, USB-C female socket at one end, manual focus ring, no tripod thread and no clip (manual spec table; the 6 Oct report's box inventory). Assume nothing about the focus ring's diameter until it is measured with calipers.
- Design distance 7–8 cm, with 15–20 cm as the default for die-map work (§4). At 7–8 cm the DOF is ±4 mm, which printed plastic holds; at 3 cm it is ±0.5 mm, which it does not.
- Anchor to the card's own PCIe bracket, not the case frame. Counter-intuitive but right: what matters is relative motion between camera and target. Tied to the bracket, camera and board shake together and the image is still; tied to the frame, every fan pulse moves the target across 0.2 mm pixels. The 3 Oct note about clamping to a shelf post applies to the 37 cm standoff, where the pixel is 1 mm and the card is far away; it inverts at 8 cm. Keep a second, independent mount point on the frame only as a safety tether.
- Never touch the board. Locate off the bracket's screw hole and the slot's own geometry; keep ≥5 mm clearance to every component and ≥10 mm to anything with a heatsink.
- Thermal break between fixture and cradle. The camera is rated −10…+55 °C (§1) and its own drift is a first-order error term. A narrow, thin-walled neck between the hot-side anchor and the camera cradle, plus an open cradle that lets room air over the body, costs nothing. Do not shroud the camera itself.
- Baffle selectively, never fully. A full hood traps heat (breaking 4) and changes the convection the experiment is measuring. Use a single flat blade between the camera and the nearest hot reflector — the CPU cooler — and leave the rest open. The thing being excluded is the camera's own cold image and the cooler's, reflected off copper and connectors; an emissivity patch on the target (Scotch 88 vinyl tape, ε = 0.96, FLIR) does far more for that than any baffle.
- Focus-ring lock as a printed split collar with one M3 screw, not tape. Tape was the 6 Oct recommendation and it works, but a collar is repeatable and survives being knocked. Print it in the same material; add a scribed index mark so the setting can be written down.
- Strain relief in two places: a printed P-clip holding the camera's in-box 48–50 cm extension to the fixture within 30 mm of the plug, and a second clip where the extension leaves the chassis. The plug must never carry load — the EEVblog thread traced intermittent disconnects on this camera to plug engagement (thread).
- Print it matte black and keep it out of frame. Any printed surface inside the field of view is a high-emissivity object at fixture temperature and will bias the scene's apparent background.
- Mark it. The 3 Oct report's label rule: "IR camera, please don't move — ping Yaroslav", and tell Nate.
The split that makes the material problem go away: print only the cradle, the collar and the clips (all unloaded, ASA); buy the span. A steel super-clamp and arm does not creep, does not care about 50 °C, and can be re-aimed without reprinting.
8. Day-one measurements (all cheap, all settle an open question)
- Ruler in frame at 5, 8, 10 and 20 cm. Settles the footprint table against the real lens and whatever "distance" the vendor means. Five minutes.
- Close-focus limit. Wind the ring to its near stop and find the distance where a 1 mm feature is sharp. Settles the "8 mm" claim.
- The gap. Card back to CPU cooler, card top to the shelf above, bracket screw to package centre. This is the measurement the whole mount depends on, and the 3 Oct report already flagged it as a one-minute check nobody has done.
- Camera body temperature after an hour in position, against the 55 °C limit.
- Shutter cadence and frame loss over 10 minutes — confirm ~90 s, count dropped frames.
- Warm-up drift: 30 minutes staring at a fixed, taped target; plot the apparent temperature. The 6 Oct rule is to throw the first 20 minutes away; this says whether that is enough.
- A plugged-and-unplugged test on whatever host is chosen — the camera must come back without hands.
- Binning check: compute the pixel-to-pixel standard deviation on a flat taped target at 1 × 1 and 4 × 4; confirm the √16 improvement toward ~9 mK.
9. Risks and unknowns
Protocol and software
- p3-ir-camera has had no commit since 7 Feb 2026 and has an unanswered refactor PR
(#20). It is one person's reverse engineering; the
mitigation is that five independent re-implementations now exist in Python, Go, JS and Swift, so the
protocol knowledge is no longer single-sourced.
- Every published observation is from firmware v00.00.02.17. No repo records a protocol-breaking
update, and none of them would know. The 3 Oct rule stands: do not let the vendor app update the
camera's firmware.
- USBError crashes were real and were fixed by chunked reads, but recovery is Linux-only: on macOS
the error is an overflow and on Windows "the device is not working, and is not recoverable"
(#7). Any multi-hour logger needs a supervisor
that reconnects, as the 6 Oct report already said.
- The shutter-segment constants are P3-measured and silently wrong on other models
(#17) — a reminder that this code can lose a
frame segment without raising anything. Validate frame sizes, do not trust them.
Measurement - ±2 °C is useless for absolute die temperature and fine for differential work. Everything in §4 depends only on differences. Never publish an absolute number from this camera without a reference. - The P3's own body is the coldest thing in its field of view. Copper, connectors and the heatsink will mirror it. Tape what matters; state emissivity assumptions. - X³ super-resolution must be off, both because Gough Lui found it adds artefacts and because binning (§4) is the opposite operation and the one that helps. - Camera drift over hours against the 55 °C service limit is, on current evidence, the largest uncontrolled error term. It has no published characterisation for this camera.
Still unknown after this research
- The f-number (sets every DOF figure in §1 to within 20%).
- The real close-focus limit and whether 1.16× magnification is achievable.
- The focus ring's diameter and travel — needed before any collar is drawn.
- The card's actual layer count and copper fraction, which set §4's spreading length. The 52 W/m·K figure
is a 6-layer assumption, not a measurement of this board.
- Whether 0xFF interface-0 also carries bulk endpoints 0x84/0x05: ferrolho says yes, jvdillon
documents interface 0 as control-only. Unreconciled; lsusb -v on the real device settles it.
- Whether the Pixel enumerates the camera through a PD hub at all. No published report of a Pixel plus
this camera plus a hub exists; the 3 Oct report flagged the same gap and it is still open.
10. What practitioners actually get, and the correction it forces
The physics converges from two directions
My §4 fin calculation (steady-state blur 60–210 mm) and the photothermal literature agree once you separate
DC from modulated measurement. Burgholzer et al., Heat diffusion blurs photothermal images with
increasing depth, give the depth-dependent resolution limit
δr(x) = π·x / ln(SNR) — blur grows linearly with depth and SNR helps only logarithmically, with
lateral resolution 2.4× worse than axial. At x = 1.6 mm and SNR 1000 that is ~0.73 mm axial, ~1.8 mm
lateral; at SNR 100, ~2.6 mm. My µ = sqrt(α/πf) gives 3.1 mm at 1 Hz. Two independent methods,
the same answer: 1–3 mm is the floor from the backside, and the camera over-resolves it by ~10×.
Timing agrees too: through 1.6 mm of FR-4, L²/α ≈ 10–20 s with the slowest mode at 1–2 s, and TI's
TMP108 datasheet measures a 68 s board time constant on a 32-mil board. FR-4 is through-plane
0.29–0.343 and in-plane 0.81–1.059 W/m·K (FR-4); copper-heavy power
regions reach ~100 W/m·K and signal layers 10–50 (US9113557),
i.e. ~67× anisotropy, which smears laterally exactly as §4 predicts. A compute burst shows on the
back over 1–10 s, never milliseconds.
🛑 The correction: fixed-pattern non-uniformity, not NETD, is the noise floor
Gough Lui's flat-field test on the P3 read 25.0–25.6 °C across a uniform target — a 0.6 °C spread, about 17× the 35 mK NETD, and it is spatially fixed, so it looks exactly like a hot spot (review). This breaks §4's binning claim as stated: binning and frame averaging reduce only the temporal noise; they do nothing about a fixed offset. Three consequences:
- Lock-in is not a nice-to-have, it is the only way to beat the dominant error. Demodulating at a known frequency rejects every static offset, including FPN, housing drift and the reflected scene. That is why the two hobby results exist at all: 330 µW on a 33 kΩ resistor (LOCK-IN.md) and 1.6 mW on a VBUS divider plus a readable decapped AD9042 die (Dmytro Engineering, Hackaday 28 Oct 2024). Neither publishes a mK floor, and the naive 35 mK/√1500 ≈ 0.9 mK is wrong for exactly this reason.
- Every DC image needs a reference frame, captured with the shutter closed, and re-captured after each
automatic shutter event. Dmytro's T2S+ work does this by forcing the shutter shut and subtracting; the
P3 has a documented shutter command (
0136…) for the same purpose. - Research-grade lock-in reaches 20–35 µK after 1000 s on 128×128 cooled systems (MPI Halle); a low-cost microbolometer rig works only over 0.01–1 Hz, with phase contrast approaching the detection limit above that (TUM). That window is the same one §4 derived from the board. Everything points at 0.01–1 Hz.
Practitioner settings worth copying
EEVblog's Fraser, the Thermal Imaging moderator, runs the two best threads on this (PCBA profiling and repair, minimum specification):
- NETD better than ~80 mK is sufficient for PCB work; 45 vs 60 mK is irrelevant. The P3's 35 mK is already past the point of diminishing return — which supports standing off at 15–20 cm (§4).
- With the board cold, a part 1 °C warmer is visible, and at 25 fps he watched "die areas becoming active and pulsing" inside ICs. A stressed MOSFET's fault showed through to the hidden side of the PCB.
- Use manual range, never autoranging — a hot part in frame hides small deltas.
- He masks parts with black tape for better temperature resolution, and prefers a parallel-arm boom to a gooseneck, which matches §7's "buy the span".
- His blackbody check of the P3: ε set to 0.97, 0.5 m, 15 min camera warm-up, 40 °C target read 39.9–40.0 °C.
- Super-resolution relies on hand movement, so it does nothing on a rigid stand — a second reason X³ is irrelevant here.
The closest published precedent to this experiment
Igor's Lab imaged the rear of an RTX 5000 PCB behind the NVVDD VRM and found 78–80.5 °C, attributed to via-field current concentration and "limited horizontal copper spreading" — with no stated emissivity and no validation against telemetry. Guru3D notes a backplate makes rear readings "indicative only". That is the honest ceiling of the genre, and it is still a useful result.
Surface prep
- FLIR's own PCB guidance is crude: ε 0.9 for plastic, ~0.15 for metal (FLIR support). IC mould compound measures ~0.97 over 40–200 °C; solder joints 0.17–0.35; polished copper 0.02–0.05, heavily oxidised 0.6–0.8 (AMETEK). An ε uncertainty of ±0.05 is a 5% error at ε = 0.95 and a 100% error at ε = 0.05 — never measure below ε ≈ 0.5, coat instead.
- Scotch 88 black vinyl tape, ε = 0.96 is the standard fix; flat beats glossy, metallic paints are out, and thin vinyl can be IR-transmissive (FLIR).
- The professional method is whole-area normalisation, not spot taping: coat the region with one uniform coating of known ε (two measured at 0.80 and 0.73), ramp power slowly, compare like with like (FLIR/ITC). The same note warns that thick or conductive coatings reduce definition and can cool small parts, and that "very thin boards can be warmed from the back" — and a sprayed high-ε layer measurably spreads heat (ARMMS). Wrong ε has produced 300% error (NYCU).
- The JEDEC document for IR die temperature is JEP138, not JESD51-1 (which is the electrical forward-voltage method) (JEP138, JESD51-1). TI's SNVA951 rates IR highly for Tj work but stresses it reads only the outer surface.
Nobody has published continuous PCB-backside monitoring to infer die activity with one of these cameras. The practitioner base is fault-finding and spot checks. That remains true after this search, as it was after the 6 Oct one.
11. Mount hardware: what exists
MakerWorld blocks automated fetching (HTTP 403 on makerworld.com/en/models/2283135, checked
Fri 9 Oct 2026 — the same wall the 6 Oct session hit). From search indexing, model 2283135 is a
"Thermal Master P3 protective cover system": the designer's stated motive is that the factory frame is
too fragile, it adds a rotating lens cover for storage, and the rear piece is explicitly meant to be
adapted — the listing invites users to make their own fittings, and a GoPro-mount version is part of it.
Print profile: 0.4 mm nozzle, 0.1 mm layers, 3 walls, 15% infill, ~2.1 h. That is close to the ideal
starting point for §7: an enclosing cradle with a user-replaceable back.
makerworld.com/models/2283135 — open it in a browser and check
the fit against calipers before printing; the file list could not be read.
MakerWorld 2903257 is a handheld grip whose oversized focus knob is worth copying (6 Oct report).
Thermal Master's own accessory line is thinner than the reviews imply. Its site lists a Phone
Holder at $39.99 and nothing else for the P3; PCWorld mentions a bundle "with a 6D inspection stand"
but names no price, and a site search for stand and 6D finds no such product — treat the 6D stand as
unconfirmed. Fraser, on a desk mount: Thermal Master offers none for the P3
(EEVblog).
Buy the span, print the cradle. The 3 Oct report already specified the metalwork and it is still the right answer: SmallRig 2058 super clamp + SmallRig 2065 140 mm magic arm, $55–85 together, plus a ~$5 GoPro-to-¼"-20 adapter to meet the printed cover. Fraser independently prefers a parallel-arm boom over a gooseneck for exactly this job. Manfrotto's 035 clamp + 237HD arm is the heavier equivalent.
Cable: use the in-box 48–50 cm USB-C male-to-female extension and nothing else at the camera end. Leave the USB-A adapter in the box — EEVblog traced intermittent disconnects to its shallow plug, one user reporting disconnection "with the slightest lateral movement". For a longer run, the 6 Oct chain is hub → 2–3 m USB 2.0 A-to-A extension → a good C-female-to-A-male adapter → the in-box extension → camera.
12. The Linux path (the permanent rig)
Bandwidth is a non-issue. 197,632 B × 25 fps = 4.94 MB/s, about 8% of USB 2.0 High-Speed's nominal 480 Mbps, and the thermal half alone is 2.46 MB/s. Nothing here stresses a Pi 5 or a lab host.
Least-code route: thermal-camera-viewer's v4l2 sink.
skywalker1905/thermal-camera-viewer (Apache-2.0,
v3.3.0, last commit 2026-09-21, 0 open issues) installs as a .deb on Ubuntu, Debian and Raspberry Pi
OS and ships thermal-camera-viewer-uvc, which writes frames to /dev/video10. Its README is explicit
that it does not create the node — v4l2loopback does. From there any standard streamer works, which
means no new code at all for a first picture on the tailnet.
v4l2loopback status (2026). Upstream is umlaeute/v4l2loopback;
0.15.4 was packaged by several distributions mid-2026 (Arch utils built 2026-06-28,
Alpine edge 0.15.4-r0, 2026-07-11),
Debian trixie carries 0.15.0-2 and sid 0.15.1-1 (tracker).
⚠️ Ubuntu 24.04 is still on the 0.12.7 line (0.12.7-2ubuntu5.1, changelog 2026-01-20, which fixes a
Linux 6.16 build failure and extends compatibility to 6.15+ —
ubuntuupdates); Ubuntu
25.10's DKMS package is on 0.15.0. Check apt policy v4l2loopback-dkms and uname -r on the actual
host before promising this works, and expect a DKMS rebuild on every kernel update — which is the main
argument against putting it on a shared lab machine.
Avoiding the kernel module entirely. Two of the drivers need no v4l2loopback:
- josepgomis/unofficial-thermalmaster-p3-sdk
(Apache-2.0, last commit 2026-10-02 — the most actively maintained of the lot), validated on ROS 2
Humble, 3474:45a2 only.
- xaionaro-go/thermalmaster (Go, CC0-1.0, 2026-09-06), a
single static binary, which is attractive for an unattended service.
A WebSocket + canvas dashboard shipping the raw 16-bit half is the right end state, because it keeps
per-pixel temperature in the browser (hover, ROI, colormap changes without touching the capture) and costs
98,304 B/frame — 2.4 MB/s at 25 Hz, 0.49 MB/s at 5 Hz, which §4 says is all the physics justifies.
No such dashboard exists for the P3; ferrolho's viewer is the nearest thing and already does the
browser-side half of the work (§5).
No pre-built Pi or web dashboard exists for this camera — searched and not found. The three
Linux-capable drivers are desktop viewers; a small aiohttp/FastAPI layer is a few hundred lines.
Dedicated Pi 5 over the lab host, for three reasons that are not about performance: a kernel module and a udev rule on a shared lab box need Roman's agreement (the 6 Oct report flagged this); a multi-day USB capture that wedges should not be on the machine running the workload; and the Pi can live on whichever tailnet the viewer is on. Against that, putting the capture on the lab host itself gives lock-in timing for free, because the workload and the camera share one clock — which for the lock-in experiment is a real advantage worth the permission conversation.