Stacked follow-up to #887. Adds the rest of the Yiciyuan stroker family by translating the official app's mixins/os_base.js model-dispatch logic, with each model marked as source-extracted but not hardware-verified.
This is filed as a separate issue (and will land as a separate PR) so #887 can ship on its own merit if FJB-03's distinct wire format raises concerns.
What's added
| Model |
Wire frame |
Stroke range |
Vibe / axis_c range |
| YCY-FJB-03 |
6-byte: [0x35, 0x12, stroke, vibe, axis_c, sum&0xFF] |
0..=40 (doubled) |
0..=20 |
| YS-TD-01 / -02 / -03 |
16-byte: [0x35, 0x12, stroke, vibe, axis_c, 0×11] |
0..=20 |
0..=20 |
Same BLE service / characteristic UUIDs (ff40 / ff41 / ff42) across the whole family — the app uses a unified _ble_write_os writer that varies the payload encoding per model, not the BLE topology.
App-source evidence
FJB-03 wire-format branch (mixins/os_base.js ~ line 257 in the decoded bundle):
"YCY-FJB-03"==a ? (i = (i+Array(10).join(\"0\")).slice(0,10), i += this._checksum(i))
: (i+Array(32).join(\"0\")).slice(0,32)
Translation: when the connected device's localName is YCY-FJB-03, pad the hex payload to 10 chars (5 bytes), then append the modular checksum (2 hex chars). Otherwise pad to 32 chars (16 bytes), no checksum.
_checksum from mixins/pump.js (also inherited by os_base.js):
_checksum: function(n) {
n = n.replace(/\s+/g, \"\");
var i = 0;
for (var e = 0; e < n.length; e += 2) i += parseInt(n.substr(e, 2), 16);
return (255 & i).toString(16).padStart(2, \"0\").toUpperCase();
}
i.e. sum every 2-hex-char byte, mod 256, hex-encode. Matched in yiciyuan.rs as body.iter().fold(0u16, |acc, b| acc + *b as u16) as u8.
Stroke range branch (same file, near the level-clamping block):
var A = \"YCY-FJB-03\" == info?.localName;
t < 0 && (t = A ? 20+Math.abs(t) : Math.abs(t)),
A ? t > 40 && (t = 40) : t > 20 && (t = 20)
i.e. FJB-03 clamps to 0..=40, other models clamp to 0..=20. (The negative-stroke handling for reverse direction is omitted from this PR — Buttplug's Oscillate is unsigned. Forward-only stroke is what the YAML exposes.)
YS-TD family routing:
{ suffix: \"_td\", vuexPrefix: \"bt_os_td\",
deviceNames: [\"YS-TD-01\", \"YS-TD-02\", \"YS-TD-03\"], sbType: \"td\" }
_ble_write_os_td is dynamically generated by mixins/os_base.js from the same _ble_write_os${suffix} factory; the suffix only changes vuex namespacing, not the wire path. YS-TD models fall through to the "else" branch above → 16-byte frame.
Implementation
Branch SanJerry007:add-yiciyuan-extended (depends on #887's branch landing first):
protocol_impl/yiciyuan.rs gains a is_fjb03 flag stamped by YiciyuanInitializer::initialize from hardware.name(). Per-axis clamp and build_packet() branch on it; no allocation overhead for the common case.
device-config-v4/protocols/yiciyuan.yml adds 4 new configurations entries (FJB-03, YS-TD-01/02/03) under the existing defaults block and 4 names into the btle names list.
- Two new test fixtures register against
test_device_protocols (FJB-03 with checksum verification + max-range demo; one YS-TD-01 to verify the new identifier route).
848 / 848 device protocol tests pass (cargo test -p buttplug_tests --test test_device_protocols).
Explicit caveats
- I own and tested FJB-01. FJB-03 and YS-TD-0x have NOT been physically verified. The protocol module is correct against the app's reverse-engineered source code; that does not guarantee against firmware quirks the app source happens to not exercise.
- If reviewer prefers untested device identifiers to live as commented-out YAML stubs or as a
# UNVERIFIED: section, I can restructure.
- Happy to drop FJB-03 (the model with the most unique code path) and ship only the YS-TD trio if there's a preference for the safer subset.
Open questions
- OK to land as-is, with the model-detection-at-connect pattern +
is_fjb03 flag? Alternative is splitting into yiciyuan.rs and yiciyuan_fjb03.rs (analogous to galaku.rs vs galaku_pump.rs) — happy to do that if you prefer.
- Any blockers on landing source-extracted-but-unverified configurations in principle?
- Pump variants (YISK-003V3 plaintext + YISKJ-003 / -003V2 AES with hard-coded key) are also source-extracted but need an HCI capture to nail down the BLE service UUID — I'd rather not guess there. Would a third follow-up PR be welcome once that's pinned down?
Stacked follow-up to #887. Adds the rest of the Yiciyuan stroker family by translating the official app's
mixins/os_base.jsmodel-dispatch logic, with each model marked as source-extracted but not hardware-verified.This is filed as a separate issue (and will land as a separate PR) so #887 can ship on its own merit if FJB-03's distinct wire format raises concerns.
What's added
[0x35, 0x12, stroke, vibe, axis_c, sum&0xFF][0x35, 0x12, stroke, vibe, axis_c, 0×11]Same BLE service / characteristic UUIDs (ff40 / ff41 / ff42) across the whole family — the app uses a unified
_ble_write_oswriter that varies the payload encoding per model, not the BLE topology.App-source evidence
FJB-03 wire-format branch (
mixins/os_base.js~ line 257 in the decoded bundle):Translation: when the connected device's
localNameisYCY-FJB-03, pad the hex payload to 10 chars (5 bytes), then append the modular checksum (2 hex chars). Otherwise pad to 32 chars (16 bytes), no checksum._checksumfrommixins/pump.js(also inherited by os_base.js):i.e. sum every 2-hex-char byte, mod 256, hex-encode. Matched in
yiciyuan.rsasbody.iter().fold(0u16, |acc, b| acc + *b as u16) as u8.Stroke range branch (same file, near the level-clamping block):
i.e. FJB-03 clamps to 0..=40, other models clamp to 0..=20. (The negative-stroke handling for reverse direction is omitted from this PR — Buttplug's
Oscillateis unsigned. Forward-only stroke is what the YAML exposes.)YS-TD family routing:
_ble_write_os_tdis dynamically generated bymixins/os_base.jsfrom the same_ble_write_os${suffix}factory; the suffix only changes vuex namespacing, not the wire path. YS-TD models fall through to the "else" branch above → 16-byte frame.Implementation
Branch
SanJerry007:add-yiciyuan-extended(depends on #887's branch landing first):protocol_impl/yiciyuan.rsgains ais_fjb03flag stamped byYiciyuanInitializer::initializefromhardware.name(). Per-axis clamp andbuild_packet()branch on it; no allocation overhead for the common case.device-config-v4/protocols/yiciyuan.ymladds 4 newconfigurationsentries (FJB-03, YS-TD-01/02/03) under the existingdefaultsblock and 4 names into the btlenameslist.test_device_protocols(FJB-03 with checksum verification + max-range demo; one YS-TD-01 to verify the new identifier route).848 / 848 device protocol tests pass (
cargo test -p buttplug_tests --test test_device_protocols).Explicit caveats
# UNVERIFIED:section, I can restructure.Open questions
is_fjb03flag? Alternative is splitting intoyiciyuan.rsandyiciyuan_fjb03.rs(analogous togalaku.rsvsgalaku_pump.rs) — happy to do that if you prefer.