Skip to content

Device support: Yiciyuan stroker family (FJB-03, YS-TD-01/02/03) #888

@SanJerry007

Description

@SanJerry007

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

  1. 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.
  2. Any blockers on landing source-extracted-but-unverified configurations in principle?
  3. 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions