SmartestHome/pebble-presence/README.md

104 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Who's home — a Pebble watchapp
The household floorplan on your wrist, with who is in each room. Renders the same
`GET /floorplan/presence` payload as `render/floorplan-3d/`, so if the two ever
disagree, one of them is lying.
Designed in [`docs/pebble-presence-watchface.md`](../docs/pebble-presence-watchface.md);
this is the build.
## It is an app, not a face
Watchfaces get **no button events** (Select opens the app menu, Up/Down are system
shortcuts) and Pebble restricts touch to apps as well. Cycling through rooms is the
point, so it is a watchapp. What that costs, stated plainly because it is the thing
you will notice:
- You launch it from the menu rather than seeing it by raising your wrist.
- Its JS — and therefore its data — only lives while it is open, so **every launch
begins with a fetch**.
So the last plan is persisted and drawn **immediately, with its age on screen**, then
repainted when the fetch lands. A spinner on a screen that already has something true
to say is worse than slightly stale truth, clearly labelled.
A watchface variant later is a second `main()` and a build target, not a second
project: the rendering, the parser and all the JS are shared.
## Screens
**Plan** — the floorplan. Occupied rooms are drawn **light**, empty rooms dark, and
rooms HA never reports on get a **dotted edge**: "nobody is in the study" and "nothing
can see the study" are different sentences. Occupancy is carried in *lightness*, never
hue, because colour already means *who* and the two would compete on a 64-colour panel.
- `Select` → the room detail
- `Up`/`Down` → ask the phone for a fresh fetch
**Detail** — one room at a time, `Up`/`Down` cycles and wraps at both ends. Each person
is a coloured dot plus their name; the dot stays even with room for the name, because
it is what ties the line back to the marker on the plan and to the same person in the
admin panel. **"Nobody here" is printed** — a blank list reads as a fault.
The last entry in the cycle is **"Somewhere in the house"**: everyone who is home but
not locatable. They are part of the same cycle deliberately — they are exactly who you
picked the watch up to find.
## Positions, when there are any
A person fused to a single radar target (see `identity`'s position fusion) is drawn at
their coordinate. An unattributed target is a **hollow ring** — no colour, no initial,
because every visual language here for a *person* is reserved for people the system can
name. A radar target **lights the room** even with nobody named: somebody is in there.
## The wire format is the fragile part
[`WIREFORMAT.md`](WIREFORMAT.md). One packed byte array, written by
`src/pkjs/index.js` and read by `src/c/main.c` — two implementations of one format, in
two languages, that never run in the same process. Nothing but a test catches them
drifting, and drift decodes as *plausible-looking garbage* rather than as an error.
```sh
test/run-tests.sh # needs node + cc; no Pebble SDK
```
It builds a payload with the real writer, compiles the real reader against a stub SDK,
and checks the decode — including that a **truncated** payload is refused wholesale
(half a plan looks exactly like a whole one) and that a **version mismatch** is refused
rather than decoded.
That test has already earned itself once: it caught a disagreement about whether a
radar target in a room HA doesn't report should read as *occupied* or *unknown*. The
code was right (a positive detection outranks no-data); the test's expectation was
wrong.
## Building for a real watch
```sh
pebble build && pebble install --phone <ip>
```
**Unverified, in the order it will bite:**
1. **The Pebble Round 2's platform name.** `package.json` targets
`basalt/chalk/diorite/emery`; `chalk` is the *old* round (180×180) and `emery` the
old Time 2 (200×228). The new 260×260 Round 2 may need a platform this list does not
have. Nothing in `main.c` hardcodes a resolution — every position comes from
`layer_get_bounds()` — so adding the platform should be a one-line change, but the
name has to come from whatever SDK Core Devices ships.
2. **Round detection in the JS** keys off `platform === "chalk"`, and will need the new
identifier too. Get it wrong and the plan is drawn to the full rectangle on a round
screen, so the corners of the house fall off the edge.
3. **Nothing has run on a watch, in an emulator, or against a real `identity`.** The
wire format is round-trip tested and the C type-checks against a stub; that is the
whole of it.
4. **The token lives in the phone's app config.** It reads the household's presence
history — treat it as the credential it is.
## And the thing no amount of watch code fixes
This is only as right as **room-level presence**, which has never been measured in this
house. If everyone resolves to "home, room unknown", this is a picture of a floorplan
with everybody in the *Somewhere in the house* list. Test that first: the admin panel's
floorplan tab, **Live** ticked, walk between two rooms, see whether the marker moves.