224 lines
14 KiB
Markdown
224 lines
14 KiB
Markdown
# Knowing *where* a thing is: multiple fridges, shelves, and what it would cost
|
||
|
||
A feasibility note that has since grown a build. `pantry-vision` (Phase 17) knows
|
||
**what** the household has and **when** it expires. This asks what it would take to
|
||
also know **where** — which of several fridges, and how far inside one.
|
||
|
||
The recommendation at the bottom is deliberately smaller than the question, and the
|
||
reason is in the middle.
|
||
|
||
> **Status, 2026-08-10.** Steps 1 and 4 of the recommendation are now written:
|
||
> appliances as Grocy locations with a `POST /transfer` action and a "Move to…"
|
||
> control on the edit screen, and the door-sensor→camera→hint path
|
||
> (`pantry-vision/doorway.py`, `POST /doorway-event`). **Step 2 is hardware nobody has
|
||
> bought and step 3 is a month nobody has spent** — which means the camera half is
|
||
> written against an assumption that has never been tested: that a local vision model
|
||
> can identify an item in a moving hand at doorway distance. An appliance can be
|
||
> configured with a door sensor and no camera, and that remains the recommended way to
|
||
> start. Nothing about writing the code changes the order in which it is worth
|
||
> switching on.
|
||
|
||
## First, decide which question is actually being asked
|
||
|
||
These are three different problems with three different price tags, and they get
|
||
conflated constantly:
|
||
|
||
| Level | The question | Honest difficulty |
|
||
| --- | --- | --- |
|
||
| **L1 — which appliance** | "Is the mustard in the kitchen fridge or the loggia one?" | Solved today, no new hardware, one extra tap |
|
||
| **L2 — which zone** | "Door shelf, middle shelf, or the crisper drawer?" | Achievable at *hint* quality (60–80%), real hardware, real work |
|
||
| **L3 — exact position** | "Second from the left, behind the milk" | Not worth it. See "Occlusion" below |
|
||
|
||
The value is overwhelmingly at L1, and it is the level this household actually has a
|
||
problem at — the whole premise of the question is *multiple fridges*, because the
|
||
freezer compartment of the main one is too small. Somebody standing in the kitchen
|
||
wondering whether to walk to the loggia is asking an L1 question. L2 saves ten seconds
|
||
of looking; L3 saves none, because by the time you have the door open you can see.
|
||
|
||
**Do not build downward from L3.** Every camera-in-the-fridge design starts there,
|
||
because that is the impressive demo, and every one of them founders on the same rock.
|
||
|
||
## L1: available now, and mostly a data-model decision
|
||
|
||
`pantry-vision` already resolves the confirm screen's fridge/freezer/cupboard answer
|
||
to a **Grocy location**, created by name (`PLACEMENT_LOCATION_NAMES` in `server.py`,
|
||
`PANTRY_LOCATION_*` in the env file), and passes `location_id` on the stock add. Grocy
|
||
has modelled multiple locations natively since long before this project existed.
|
||
|
||
So L1 is not a vision problem at all. It is three small changes, and two and a half of
|
||
them are now written:
|
||
|
||
1. **The placement answer becomes the real appliances** — "Fridge (kitchen)", "Freezer
|
||
(loggia)", "Cupboard" — named in `PANTRY_LOCATION_*` and resolved to Grocy
|
||
locations. *Done, with a caveat*: the confirm screen still offers the four
|
||
categories (fridge / freezer / cupboard / counter) and each maps to one location
|
||
name, so a household with two fridges has to pick which one that word means and
|
||
move the other by hand. Reading the appliance list straight off Grocy's locations
|
||
is the obvious next step and has not been taken.
|
||
2. **`/inventory` carries the location through**, so every list says where. *Done* —
|
||
`location_id`/`location` on every row, `locations` on the response.
|
||
3. **Moving something between fridges needs a "moved" action**, or the data rots
|
||
within a fortnight. *Done* — `POST /transfer` onto Grocy's own transfer endpoint,
|
||
with a picker on the edit screen.
|
||
|
||
Point 3 is the whole ballgame, and it is the thing every camera proposal below is
|
||
really trying to buy its way out of: **the expensive part is not learning where things
|
||
are, it is noticing when they move.**
|
||
|
||
## L2/L3: what the camera approaches actually run into
|
||
|
||
### Occlusion is the rock
|
||
|
||
A single camera sees the front row. Groceries are stored in rows. The mustard behind
|
||
the milk is invisible, and no model — local, cloud, or otherwise — recovers it from a
|
||
picture that does not contain it. Multi-camera stereo does not fix this either; it is
|
||
not a depth problem, it is a line-of-sight problem.
|
||
|
||
This is why L3 is off the table and why L2 is a *hint*, not an assertion. Any design
|
||
that needs the camera to enumerate a shelf's contents is buying an answer that is
|
||
silently incomplete, which for an inventory is the worst failure available: "we have
|
||
no mustard" from a system that simply could not see it produces a second jar of
|
||
mustard.
|
||
|
||
### The interior-camera problems, in the order they will bite
|
||
|
||
- **Power.** Fridges have no interior outlet. The three options are a battery (poor:
|
||
cold cuts usable lithium capacity substantially, and this is a duty-cycled camera,
|
||
not a doorbell), a flat ribbon cable through the door gasket (works — this is how
|
||
retrofit fridge cams do it — but it is a modification to the seal of an appliance
|
||
that may be rented), or drilling a grommet (irreversible).
|
||
- **The freezer is out of scope for interior cameras, full stop.** Off-the-shelf
|
||
camera modules — ESP32-CAM, Pi camera, USB webcams — are specified to roughly 0 °C
|
||
at best. −18 °C is outside every consumer part's rating: lubricants stiffen,
|
||
electrolytics lose capacitance, and condensation cycling on every door open
|
||
eventually gets inside the enclosure. Industrial cold-chain cameras exist and cost
|
||
more than the freezer. **Given that the second appliance exists precisely because
|
||
the built-in freezer is too small, this rules interior cameras out of the exact
|
||
compartment the question started from.**
|
||
- **Condensation.** Every door opening puts warm humid air on a cold lens. It fogs,
|
||
and it fogs for minutes — longer than the interaction. Mitigations (sealed housing
|
||
with desiccant, conformal coating, a lens heater) all cost power, which brings back
|
||
problem one.
|
||
- **Light.** The interior lamp is on only while the door is open, so the only usable
|
||
frames are the ones during an opening — at which point the camera might as well be
|
||
outside.
|
||
|
||
Every one of those bullets is a *class* of problem, not a part-selection detail. Taken
|
||
together they say: **do not put the camera inside.**
|
||
|
||
### The approach that survives all four: watch the doorway, not the shelf
|
||
|
||
Point a camera at the **plane of the door opening**, from outside, and trigger on a
|
||
door sensor. Then you are not parsing a packed shelf — you are watching one item, held
|
||
in one hand, unoccluded, at a known moment, against a known background, with the
|
||
kitchen's own lighting. That is the *same* recognition problem `/identify` already
|
||
solves, which means it reuses the prompt, the model, and the "propose, never
|
||
auto-commit" guardrail instead of needing a new perception stack.
|
||
|
||
What it buys: **transactions, not state.** "A jar of X went into the loggia fridge at
|
||
18:42." Transactions are what keep L1 from rotting (the problem above), and a
|
||
sequence of them gives L2 for free if the camera can also see *which shelf the hand
|
||
went to* — a much weaker inference than reading the shelf, and one that degrades
|
||
gracefully into "somewhere in this fridge".
|
||
|
||
What it costs in honesty: it misses. Two items at once, an arm across the lens, a foot
|
||
closing the door. So its output is a **hint with a timestamp** — "last seen going into
|
||
the loggia fridge, Tuesday 18:42" — never an assertion the UI treats as fact. That
|
||
framing is what makes a 70%-accurate system useful instead of infuriating, and it is
|
||
the same distinction `digest-engine` already draws between what a source *says* and
|
||
what is *so*.
|
||
|
||
This is what `pantry-vision/doorway.py` implements. An HA automation on the contact
|
||
sensor POSTs `/doorway-event`, the service answers 202 and takes a short burst off the
|
||
request thread (three frames, stopping at the first that recognises anything), and the
|
||
result lands in **its own SQLite file** — not in Grocy, because Grocy owns stock and
|
||
has nowhere to put "a camera thinks it saw something like this go past that door, and
|
||
might be wrong". Hints expire after 30 days, because "last seen a month ago" tells
|
||
nobody anything they didn't know. The edit screen shows the sighting next to the
|
||
location Grocy records, and acting on it is a tap on *Move*.
|
||
|
||
### Where the cameras go, concretely
|
||
|
||
- **One camera per appliance**, mounted above the door on the hinge side, ~1.8–2 m,
|
||
angled down so its field of view is the door aperture and the ~40 cm in front of it.
|
||
Not inside the door swing. `docs/components.md` already lists **2× spare webcam
|
||
(destination TBD)** — this is a plausible destination for one of them, which makes
|
||
the experiment nearly free.
|
||
- **A contact sensor on every fridge and freezer door.** Zigbee, on the coordinator
|
||
this project already has. This is the single highest value-per-euro item in this
|
||
whole note and it is worth doing *even if no camera is ever installed*: it is what
|
||
triggers the capture, it is what tells you which appliance a transaction belongs to,
|
||
and on its own it already supports "the loggia freezer has been open for four
|
||
minutes" — which prevents more spoilage per euro than any amount of localisation.
|
||
- **No new inter-VLAN paths.** Both devices sit where the existing camera/IoT devices
|
||
sit; the capture is pulled by the container host, same direction as everything else.
|
||
|
||
### Approaches ruled out, and why
|
||
|
||
- **RFID/NFC tags per item.** The only technology that genuinely answers L3. It also
|
||
requires tagging every single item by hand, which is a *worse* manual step than the
|
||
one tap L1 costs — and a fridge is a metal box full of water, which detunes UHF tags
|
||
badly. Rules itself out twice.
|
||
- **Load cells under each shelf.** Excellent at "how much is left", blind to identity,
|
||
and a per-shelf retrofit on appliances that may be rented.
|
||
- **BLE/UWB.** Nothing to attach a transmitter to. Not applicable to passive goods.
|
||
|
||
## The compute question nobody asks until it's too late
|
||
|
||
Reading a shelf is not the same task as reading one held-up jar: small objects,
|
||
partial labels, angles, occlusion. A 7–13B local VLM will be markedly worse at it than
|
||
at the current task, and the honest architecture is detect-then-crop — a small object
|
||
detector proposing boxes, the VLM reading each crop — which is a Frigate-class,
|
||
always-on GPU workload.
|
||
|
||
There is one RTX 3060 in this design, and contention on it is already an open
|
||
question (project plan open decision #4, `MAX_LOADED_MODELS=1`). Adding a
|
||
continuous detector to a GPU that also has to answer Assist within voice latency and
|
||
run four digests a day is a scheduling problem, not a spare-capacity problem. **The
|
||
doorway-camera approach avoids this too**: it is event-driven, a handful of inferences
|
||
per day, on the model that is already loaded.
|
||
|
||
## Recommendation
|
||
|
||
In order, stopping wherever the household stops caring:
|
||
|
||
1. **Make L1 real in software** — appliances as Grocy locations, location shown in
|
||
every list, and a transfer action so moves get recorded. No hardware. This is the
|
||
only step that is unambiguously worth doing, and it must come first regardless:
|
||
without it there is nowhere to *put* a camera's answer. **Written.**
|
||
2. **Contact sensors on every fridge/freezer door.** Cheap, Zigbee, immediately useful
|
||
on their own, and the prerequisite for everything below. See
|
||
`docs/components.md`'s "Appliance door sensors" — ~€10–15 each, mounted on the
|
||
*outside* face of the door, because a coin cell at −18 °C is a false economy.
|
||
**Not bought.**
|
||
3. **Live with 1+2 for a month.** The real finding will be whether anyone ever wanted
|
||
L2, or whether "which fridge" plus "the door's been open" was the whole need. This
|
||
step is not padding — it is the only way to avoid building L2 for a question nobody
|
||
turned out to be asking. **Not done, and writing step 4 early does not skip it.**
|
||
4. **Only then**, a doorway camera on one appliance, producing timestamped *hints*,
|
||
reusing `/identify` unchanged. One appliance, not all of them, until it earns the
|
||
second. **Written** (`pantry-vision/doorway.py`), **untested against a real camera,
|
||
and configurable per appliance so it can stay off.**
|
||
5. **Never** an interior camera in the freezer, and probably not in the fridge either.
|
||
|
||
A related chore was added alongside this: `chores`' **`groceries_out_of_place`** watch
|
||
point, which notices food standing out on a counter and nudges whoever the camera last
|
||
saw — litter's twin, with a 30-minute fuse instead of four hours, because the failure
|
||
there is spoilage rather than untidiness. It is a *different* system looking at a
|
||
*different* surface for a *different* reason, and deliberately so: it knows food is
|
||
out, not what the food is, and it never touches Grocy. See `chores/README.md`.
|
||
|
||
## What in here is unverified
|
||
|
||
Everything with a number in it. Specifically: consumer camera modules' actual
|
||
low-temperature behaviour (the 0 °C figure is a datasheet-class generalisation, not a
|
||
part this project has picked or tested); how badly a lens really fogs on a door
|
||
opening in this kitchen's humidity; whether a local VLM can identify an item held in a
|
||
moving hand at doorway distance at all — that last one is the assumption step 4 lives
|
||
or dies on, and it is testable today with the kitchen display's existing camera and
|
||
about twenty minutes, long before anything is bought.
|
||
|
||
Related open decisions in `docs/project-plan.md`: #18 (no vision model picked,
|
||
benchmarked, or measured for latency) and #4 (GPU contention). Both apply here
|
||
unchanged, and neither gets easier with a second camera pointed at anything.
|