SmartestHome/pebble-presence/README.md

5.0 KiB
Raw Permalink Blame History

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; 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. 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.

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

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.