-
Notifications
You must be signed in to change notification settings - Fork 18
Description
Hey Tate! I love the ShotStopper! I'm using it daily with my LM Linea Micra and Bookoo Themis Mini.
I'm actually building an espresso shot tracking app that connects to the ShotStopper via
BLE from an iOS app. Writing the goal weight to 0xFF11 works perfectly. But after a shot, I still
have to manually read extraction time and actual yield from the scale display and type them into the
app.
The firmware already tracks all of this internally (shot.shotTimer, currentWeight, shot.brewing) it just isn't exposed over BLE.
Adding a few notify characteristics would enable fully automated shot
logging: app sends goal weight → shot runs → app receives results automatically.
I'd be happy to submit a PR with the implementation if you're open to this. Here's what me and my helper Claude had in mind:
Suggested Implementation
Three new BLENotify characteristics on the existing 0x0FFE service fully backward-compatible
(apps that only use 0xFF11 continue to work unchanged):
// Existing
BLEByteCharacteristic weightCharacteristic("0xFF11", BLEWrite | BLERead);
// New: Shot status (0=idle, 1=brewing, 2=completed)
BLEByteCharacteristic shotStatusCharacteristic("0xFF12", BLERead | BLENotify);
// New: Extraction time in tenths of seconds (e.g. 285 = 28.5s)
BLEUnsignedShortCharacteristic shotTimeCharacteristic("0xFF13", BLERead | BLENotify);
// New: Actual yield in tenths of grams (e.g. 362 = 36.2g), measured after DRIP_DELAY_S
BLEUnsignedShortCharacteristic shotYieldCharacteristic("0xFF14", BLERead | BLENotify);
Where to update values
In setBrewingState(true) (shot started):
shotStatusCharacteristic.writeValue(1); // brewing
shotTimeCharacteristic.writeValue(0);
shotYieldCharacteristic.writeValue(0);
During brewing (in the scale.newWeightAvailable() block):
shotTimeCharacteristic.writeValue((uint16_t)(shot.shotTimer * 10));
In setBrewingState(false) (shot ended):
shotStatusCharacteristic.writeValue(2); // completed
shotTimeCharacteristic.writeValue((uint16_t)(shot.end_s * 10));
After DRIP_DELAY_S (in the shot analysis section where final weight is measured):
shotYieldCharacteristic.writeValue((uint16_t)(currentWeight * 10));
shotStatusCharacteristic.writeValue(0); // back to idle
Registration (in setup()):
weightService.addCharacteristic(weightCharacteristic);
weightService.addCharacteristic(shotStatusCharacteristic);
weightService.addCharacteristic(shotTimeCharacteristic);
weightService.addCharacteristic(shotYieldCharacteristic);
Summary
┌───────────────────────┬────────┬────────────────┬────────────┬─────────────────────────────────┐
│ Characteristic │ UUID │ Type │ Direction │ Content │
├───────────────────────┼────────┼────────────────┼────────────┼─────────────────────────────────┤
│ Goal Weight │ 0xFF11 │ uint8 │ App → │ Target weight in grams │
│ (existing) │ │ │ ESP32 │ │
├───────────────────────┼────────┼────────────────┼────────────┼─────────────────────────────────┤
│ Shot Status (new) │ 0xFF12 │ uint8 + Notify │ ESP32 → │ 0=idle, 1=brewing, 2=completed │
│ │ │ │ App │ │
├───────────────────────┼────────┼────────────────┼────────────┼─────────────────────────────────┤
│ Extraction Time (new) │ 0xFF13 │ uint16 + │ ESP32 → │ Time in 0.1s (e.g. 285 = 28.5s) │
│ │ │ Notify │ App │ │
├───────────────────────┼────────┼────────────────┼────────────┼─────────────────────────────────┤
│ Actual Yield (new) │ 0xFF14 │ uint16 + │ ESP32 → │ Weight in 0.1g (e.g. 362 = │
│ │ │ Notify │ App │ 36.2g) │
└───────────────────────┴────────┴────────────────┴────────────┴─────────────────────────────────┘
Let me know if you like the idea 👍 happy to implement and test this!
(this is mir first gh issue/feature request ever btw)