Skip to content

Shot protocol v2: higher-resolution encoding and extended pump modes - #5

Open
ChampionDesigns wants to merge 1 commit into
ben/detect-protocolfrom
ben/shot-protocol-v2
Open

ChampionDesigns wants to merge 1 commit into
ben/detect-protocolfrom
ben/shot-protocol-v2

Conversation

@ChampionDesigns

Copy link
Copy Markdown
Owner

Filed inside the fork so the diff shows only this PR's own changes.
It will be re-filed against decentespresso/de1app once its base has merged there.

merges. 536 insertions, 2 files.

Summary

Two changes to profile-frame encoding, both selected per connection by the negotiated
protocol version. A DE1 keeps today's encoding byte for byte.

A. The v2 flow and pressure encoding

Those fields are one byte. v1 uses U8P4: step 0.0625, ceiling 15.9375, which suits a
DE1 whose pump tops out at 8 mL/s. A Bengle reaches 20 and cannot be commanded above
15.9375. v2 uses U8D1: step 0.1, ceiling 25.5, with HeaderV 2 so the firmware knows
which to expect.

Coarser near zero, and that is the trade — 0.0625 against 0.1 is well inside pump
repeatability, and the ceiling is the limit that bites. decentespresso/decaid makes the
same choice on the same rule.

B. Extended pump modes

Four of T_ShotExtFrame's five spare bytes:

offset field encoding
2 Mode U8
3 ModeMaxP U8D1, bar
4 LeverSpring U8D1, bar per 10 mL
5 LeverGive U8D1, bar·s per mL
6 Reserved U8, preserved verbatim
value mode
0 legacy — pressure or flow, as today
1 power — hold a watt target, capped by ModeMaxP
2 lever — spring lever from P0 with spring rate and give
3, 4, 5 HOLD-P / HOLD-F / HOLD-W — hold the previous frame's measured value

Two new exit conditions, power_under and power_over, use DC_ComparePower (0x80).
It deliberately does not set DoCompare, so firmware that does not know the mode
ignores the threshold rather than reading watts as bar.

The byte offsets, the mode numbering and the flag bit are the firmware's, taken from
T_ShotExtFrame and T_E_FrameFlags in APIDataTypes.hpp, not invented here.

Firmware requirement

Part A's U8D1 SetVal is on firmware master. Part B's Mode fields and the 0x80 flag
are on the Bengle integration branch
, released to testers and not yet merged to firmware
master. Reviewers testing part B need that build.

Compatibility

  • A frame using no v2 feature encodes identically to today; the extension frame is emitted
    only when something needs it.
  • validate_shot_protocol_compatibility refuses to encode any v2 feature under HeaderV
    1, failing loudly rather than writing a frame the machine will misread.
  • HOLD is rejected in frame 0, where there is no prior value to latch.
  • An unknown mode keeps the authored legacy command, which is also the firmware's fallback,
    and Reserved survives a decode and re-encode — so an older client can round-trip a
    profile it does not understand.
  • require_finite_range rejects infinities and out-of-range values before the encoder sees
    them. The fixed-point converters clamp by design, which is right for telemetry and wrong
    for an authored profile.

A note on profile.tcl

It carries its own copies of three small mode helpers because it loads before binary.tcl,
so save, load and display cannot depend on the encoder being present. The duplication is
avoidable if the helpers move to a file both can require — happy to restructure if you
prefer.

Impact on a DE1

use_ble_v2 is false, so HeaderV stays 1 and both byte converters stay U8P4. A profile
with no v2 feature produces identical bytes. One that uses a v2 feature is refused at
encode time with a clear error.

Test plan

  • Both ceilings, both encodings and decodings at 6.0 and 20, clamping at each limit,
    all six mode names, the unknown-mode passthrough, which modes require a pressure cap,
    and the range validator — all run in tclsh.
  • Encode the stock profile set on a DE1 and compare frame bytes against a build without
    this PR. They must be identical.
  • Confirm a v2 profile is refused on a DE1 with a readable error.
  • Save and reload a power profile; confirm the watt target survives.
  • Round-trip a profile with an unknown mode and non-zero Reserved.
  • TODO (Ben): pull a shot on a Bengle with a flow command above 15.9375 mL/s.
  • TODO (Ben): pull one shot in each of power, lever, HOLD-P, HOLD-F and HOLD-W.
  • TODO (Ben): confirm a power_under exit fires at the authored threshold.

Testing

Manual verification plus the tclsh harness above. There is no automated test coverage for
these Tcl paths in the repo.

Two changes to profile-frame encoding, both selected per connection by the
negotiated protocol version. A DE1 keeps today's encoding byte for byte.

Part A -- the v2 flow and pressure encoding. Those fields are one byte. v1
uses U8P4: step 0.0625, ceiling 15.9375, which suits a DE1 whose pump tops
out at 8 mL/s. A Bengle reaches 20 and cannot be commanded above 15.9375.
v2 uses U8D1: step 0.1, ceiling 25.5, with HeaderV 2 so the firmware knows
which to expect. Coarser near zero; the ceiling is the limit that bites.
decentespresso/decaid makes the same choice on the same rule.

Part B -- four new per-frame pump modes and hydraulic-power exits, using
four of T_ShotExtFrame's five spare bytes:

  [2] Mode  [3] ModeMaxP  [4] LeverSpring  [5] LeverGive  [6] Reserved

  0 legacy, 1 power, 2 lever, 3 HOLD-P, 4 HOLD-F, 5 HOLD-W, >= 6 unknown

Power exits use DC_ComparePower (0x80), which deliberately does NOT set
DoCompare so firmware that does not know the mode ignores the threshold
rather than reading watts as bar.

The byte offsets, the mode numbering and the flag bit are the firmware's,
taken from T_ShotExtFrame and T_E_FrameFlags in APIDataTypes.hpp. Part A's
U8D1 SetVal is on firmware master; Part B's Mode fields are on the Bengle
integration branch and in tester builds.

A frame using no v2 feature encodes identically to today; the extension
frame is emitted only when something needs it.
validate_shot_protocol_compatibility refuses to encode any v2 feature
under HeaderV 1 rather than writing a frame the machine will misread. HOLD
is rejected in frame 0, where there is no prior value to latch. An unknown
mode keeps the authored legacy command, and Reserved survives a decode and
re-encode, so an older client can round-trip a profile it does not know.

require_finite_range rejects infinities and out-of-range values before the
encoder sees them: the fixed-point converters clamp by design, which is
right for telemetry and wrong for an authored profile.

Verified in tclsh: both ceilings, both encodings and decodings at 6.0 and
20, clamping at each limit, all six mode names, the unknown-mode
passthrough, which modes require a pressure cap, and the range validator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants