JMT Prop Addons

Optional ProffieOS extensions for chassis, charging, gestures, and favorites

JMT Prop Addons is a collection of optional ProffieOS extensions. The included wrapper layers new behavior on top of the saber_fett263_buttons prop without modifying that prop or any core ProffieOS source. The original files stay untouched. The goal is to add features to an already excellent prop while preserving everything that makes it work.

Each feature is opt-in via a #define. Charge detection, chassis detection, gesture-based preset switching while the chassis is removed, blade ID-based blade detect (OS8+), and a favorites system can be enabled independently or combined as needed. Nothing activates unless you explicitly enable it in a config.

Easiest path: use JMT Studio's Add JMT Features button on any installed ProffieOS version. The wrapper, helper headers, and config entries are placed for you and stay in sync with future updates.

Setup

Add this include inside your config's CONFIG_PROP block. The wrapper internally defines saber_fett263_buttons as its base, so this line replaces your existing prop include. Only one prop can be active in a build, so leaving another #include "props/..." in place will cause a compile error.

#ifdef CONFIG_PROP
#include "../props/jmt_fett263_wrapper.h"
#endif

From there, opt into any feature below by adding its #define to your config. All defines and tunables are listed inside each feature.

Optional: enable charge state in styles

If any of your blade styles use the ChargeFullPropF helper (see Style Helpers), add this include inside your config's CONFIG_PRESETS block. It must be processed after the prop include so the shared charge-state symbols are available.

#ifdef CONFIG_PRESETS
#include "../functions/charge_full_prop.h"
#endif

Files included with the addons

JMT Studio's Add JMT Features button places all three of these for you in the right directories. If you are installing manually from GitHub, make sure each file lives in the directory shown.

Features

Chassis Detection Detect when the chassis is removed or inserted, with optional swing-to-ignite suppression.
Enable #define CHASSIS_DETECT_PIN <pin>

Adds a hardware input that tells the prop whether the removable chassis is inserted into the hilt. State changes play chassisin.wav or chassisout.wav and cancel any pending favorite-reset hold action (if enabled). While the chassis is out and the saber is off, swing-to-ignite is suppressed so the saber will not light up just from being handled. Twist on/off remains available if enabled, or may be naturally suppressed by blade-detect behavior depending on the install.

Wiring

  • Default (active LOW): uses INPUT_PULLUP. Chassis detected when the pin is shorted to GND.
  • Active HIGH: add #define CHASSIS_DETECT_PIN_HIGH. Requires an external pulldown so the pin sits LOW when no chassis is present.

Options

  • JMT_CHASSIS_WAKE: wake motion detection on chassis state change. Helpful if the board is in a deep idle state.

SD card sounds

Place chassisin.wav and chassisout.wav in your saber's common/ folder on the SD card. You can use any compatible WAVs, or preview and grab the defaults below.

Includes a fixed 30ms software debounce. No tuning is normally required.

Charge Detection Hardware-level detection of when the saber is plugged in to a charger.
Enable #define CHARGE_DETECT_PIN <pin>

Required for all charge-related features below. When the charger is detected the prop plays chargebegin.wav, cancels any pending favorite reset, and prevents normal MOTION_TIMEOUT / IDLE_OFF_TIME behavior (if enabled) while charging so charge styles remain active and visible.

A silent boot-time sync prevents chargebegin.wav from playing if charge detect is already active at startup.

Two global flags are tracked and declared extern in common/charge_state.h:

  • g_charging: true while the charger is detected. Available to prop and config code.
  • g_charge_full: true once the battery has been at full for the configured dwell time. Blade styles can react to this via the bundled ChargeFullPropF helper (see Style Helpers).

SD card sound

Place chargebegin.wav in your saber's common/ folder on the SD card. The wrapper does not install this for you. You can use any compatible WAV, or preview and grab the default below.

Active LOW input using INPUT_PULLUP with fixed 30ms debounce.

Charge Full Tracking Reliable detection of when the battery has reached a stable full-charge state.
Enable Automatic when CHARGE_DETECT_PIN is defined

Monitors battery level and determines when charging is considered complete. Uses two thresholds and a dwell timer to avoid rapid full/not-full toggling near 100%. Drives the g_charge_full global, which blade styles can consume via the included ChargeFullPropF style helper (see Style Helpers).

Tunables(32768 scale - 0–100%)

MacroDefaultMeaning
CHARGE_FULL_ENTER32700Level required before full-charge dwell timing begins
CHARGE_FULL_EXIT32000Drop-out threshold. Must be below ENTER.
CHARGE_FULL_DWELL_MS30000How long ENTER must be held before declaring full

When charging is no longer detected, the full state and dwell timer are cleared immediately.

Charge Complete Announcement Play a sound effect when the battery reaches full charge.
Enable #define JMT_CHARGE_COMPLETE_ANNOUNCE

Plays chargecomplete.wav once when g_charge_full first becomes true (see Charge Full Tracking for the timing). Resets when charging is no longer detected, allowing the next charge cycle to announce again.

SD card sound

Place chargecomplete.wav in your saber's common/ folder on the SD card. You can use any compatible WAV, or preview and grab the default below.

Requires: CHARGE_DETECT_PIN.

Charge Button Lockout Disable button input while charging so a bumped button cannot ignite a docked saber.
Enable #define JMT_CHARGE_LOCKOUT

While charging is active, this feature suppresses nearly all button events. The single exception is BUTTON_POWER, which announces battery level instead of igniting the saber (rate limited to once every 300ms).

If BLADE_DETECT_PIN is defined, blade-detect events are also ignored during charging so blade-detect events cannot trigger behaviors while charging.

Requires: CHARGE_DETECT_PIN.

Recommendation: also enable FETT263_SAVE_GESTURE_OFF so prior gesture state is fully preserved across a charge cycle. The build will succeed without it, but a warning is emitted.

Charge Style Preset Switch to a dedicated charging preset when charging, then reboot when no longer charging.
Enable #define JMT_CHARGE_STYLE_PRESET

Treats the last preset in your preset array as a dedicated charge preset. When charging is detected, this feature switches to that preset and triggers EFFECT_BOOT. When no longer charging, the board reboots.

Why a full reboot on unplug?

Normal preset switching does not re-run ProffieOS startup behavior such as font scanning, motion initialization, and boot effects. Rebooting guarantees a clean post-charge state and avoids subtle inconsistencies in motion and audio.

Tip: consistent boot audio

Charge-preset entry and unplug-reboot both play a boot sound. ProffieOS uses the font's boot.wav if present, falling back to font.wav otherwise. To give every font a dedicated boot sound, place a generic boot.wav in your saber's common/ folder. Fonts without their own will use it instead of falling back to font.wav.

Bonus behavior

If a user manually navigates and lands on the charge preset while not charging, this feature detects scroll direction and skips past it. The charge preset cannot be selected as a normal preset.

Requires: CHARGE_DETECT_PIN.

Roll-to-Change-Preset Preset navigation for removable chassis builds where buttons are inaccessible.
Enable #define JMT_ROLL_PRESETS

Designed primarily for removable chassis setups handled outside the hilt. While the chassis is out and the saber is not ignited, rolling it forward or backward through a large rotation changes the preset.

With the chassis held roughly horizontal (within ±7° of level), the feature captures a baseline roll and watches for large rotations. A positive roll delta beyond 170° advances to the next preset; a negative roll delta beyond 170° selects the previous preset. The baseline resets after each trigger so successive flips continue scrolling through presets.

Roll direction

  • Forward roll: emitter toward the left hand, speaker toward the right hand, rolling away from the user.
  • Reverse roll: opposite direction.

The threshold is intentionally set slightly below 180° so a full flip gesture remains reliable without requiring an exact angle boundary.

Active only when: saber not ignited and chassis removed.

Requires: CHASSIS_DETECT_PIN.

Mutually exclusive with: JMT_FLICK_PRESETS.

Flick-to-Change-Preset Pose-driven preset navigation for unconventional hilt geometry.
Enable #define JMT_FLICK_PRESETS

Designed primarily for removable chassis setups where presets must be changed without button access, but full roll gestures feel awkward or unnatural due to hilt shape, board orientation, or install geometry.

Unlike traditional roll gestures, this feature establishes a known neutral arm pose first. Optional pitch/roll offsets can calibrate that pose for curved hilts, angled chassis installs, or rotated board mounting, so preset navigation feels natural without changing the rest of ProffieOS motion behavior.

From the arm pose, twist right and return to center to advance presets. Twist left and return to center to move backward. The preset change is not committed until the chassis returns to the arm pose, which reduces accidental activations during handling.

When to choose flick over roll

  • Curved hilts (Count Dooku-style builds)
  • Crossguard sabers
  • Angled chassis installs
  • Rotated board orientations
  • Unconventional grip or emitter geometry
  • Accessibility-focused builds where large wrist rolls are uncomfortable

Gesture mechanics

Arm pose: pitch near 0°, roll near ±180°. Tolerance ±18° pitch, ±25° roll.

Twist pose: tolerance ±22° pitch, ±22° roll. Eligibility window opens 700ms after entering arm pose.

Commit: return to arm pose within 500ms after the twist pose.

Direction: roll near -90° then return selects the next preset. Roll near +90° then return selects the previous preset.

Optional tuning

JMT_PITCH_OFFSET and JMT_ROLL_OFFSET calibrate the flick gesture matcher after ORIENTATION_ROTATION has already been applied. They do not change the IMU orientation globally and do not affect other ProffieOS motion behavior.

Use these offsets when a rotated board or angled chassis makes your natural arm pose read somewhere other than pitch 0°, roll ±180°. The offsets shift only the flick preset target poses so your physical neutral position matches the gesture matcher.

MacroNotes
JMT_PITCH_OFFSET Degrees added to measured pitch for flick-preset matching. Requires ORIENTATION_ROTATION.
JMT_ROLL_OFFSET Degrees added to measured roll for flick-preset matching. Requires ORIENTATION_ROTATION.

Think of these as calibration offsets for this gesture only, not a replacement for ORIENTATION_ROTATION.

Active only when: saber not ignited and chassis removed.

Requires: CHASSIS_DETECT_PIN.

Mutually exclusive with: JMT_ROLL_PRESETS.

Software Blade Detect Blade insertion/removal via Blade ID, no detect pin needed. (ProffieOS 8.0+)
Enable #define JMT_BLADE_DETECT

Uses the Blade ID scan result instead of a hardware detect pin. The prop considers the blade removed when the detected Blade ID resolves to NO_BLADE.

Requires: ProffieOS 8.0 or higher.

Behavior

On insertion:

  • Re-enables gesture control
  • Requests motion wake-up
  • Cancels any pending favorite-reset hold action

On removal:

  • Disables gesture control
  • Cancels any pending favorite-reset hold action

If FETT263_SAVE_GESTURE_OFF is defined, prior gesture state is saved on removal and restored on insertion. Otherwise gestures are force-enabled on insertion.

Blade-detect processing is skipped while charging when CHARGE_DETECT_PIN is defined and charging is active.

Required infrastructure

  • ENABLE_POWER_FOR_ID
  • BLADE_ID_SCAN_MILLIS > 0
  • BLADE_ID_TIMES > 0

Mutually exclusive with: BLADE_DETECT_PIN.

Hardware Blade Detect Enhancements Additional behavior layered on standard hardware blade detect.
Enable Automatic when BLADE_DETECT_PIN is defined

If BLADE_DETECT_PIN is already present in your config, this feature adds two small behavior improvements:

  • Blade insertion and removal events cancel any pending favorite-reset hold action.
  • On blade insertion, a motion wake request is fired. No ignition and no sound. Useful for boards that have entered motion timeout or idle-off behavior.

Nothing additional to configure. No new macros required.

Mutually exclusive with: JMT_BLADE_DETECT.

Favorites System Mark presets as favorites, then jump between them with simple tilt gestures.
Enable Automatic (disabled on single-button builds)

A tilt-driven favorites system that lets a user mark presets and cycle through just those, without scrolling the full preset list. Triggered by AUX + EVENT_FIRST_HELD_LONG while the saber is off. The action depends on the saber's orientation when the trigger fires.

Tilt actions

OrientationAction
Pointing straight down (within ±5° of -90°)Arm favorites reset confirmation
Tilted up (beyond +5°)Jump to next favorite
Tilted down (beyond -5°, not the reset zone)Jump to previous favorite
Roughly level (within ±5°)Toggle current preset as favorite

Audio cues

  • Add favorite: one short 2kHz beep.
  • Remove favorite: two short 2kHz beeps.
  • List full: one long 2kHz beep.
  • List empty (on next/prev): five fast beeps.
  • Reset armed: three beeps plus mconfirm.wav and mdefault.wav.
  • Reset confirmed: two beeps plus mdefault.wav.
  • Reset cancelled or aborted: mcancel.wav.

Reset confirmation flow

  • BUTTON_POWER press confirms (clears favorites, writes empty file).
  • BUTTON_AUX press cancels.
  • All other input is ignored while reset confirmation is active.
  • Auto-cancels on chassis change, charge start, or blade-detect change.

Storage

Stored as favorites.ini on the SD card, with favorites.bak written as a best-effort backup after every successful save. The loader falls back to the backup if the primary fails and rewrites the primary if needed. Malformed lines are dropped and the file is rewritten cleanly.

Tuning

MacroDefaultMeaning
JMT_MAX_PRESET_FAVORITES10Maximum favorite slots
JMT_DISABLE_FAVORITESoffManually disable favorites entirely

Single-button builds (NUM_BUTTONS == 1) automatically disable favorites since the system relies on AUX.

Style Helpers

Style helpers expose runtime state from the prop into the blade style engine. They live in the functions/ directory and are enabled via an optional include in your config (see Setup). Once enabled, any blade style can reference the helper's function directly.

ChargeFullPropF Style-side helper that lets a blade style react to a confirmed full-charge state.
Returns 0 when not full, 32768 when full
Drop-in for Mix<>, AlphaL<>, masks, transitions, selectors
Enable Add the optional include shown in Setup

When to use it (vs DisplayBattery)

DisplayBattery
What is the current estimated battery level?
Use for live battery meters and continuous indicators.
ChargeFullPropF
Has the charge handler confirmed a stable full-charge condition?
Use for visuals that switch only after charge is genuinely complete.

Why styles can trust it

By the time ChargeFullPropF returns 32768, the wrapper has already validated four conditions:

  • Charger physically present and debounced
  • Battery level above the enter threshold
  • Level held continuously through the dwell window
  • Exit hysteresis on the way down to prevent flicker

Thresholds and timing live in the Charge Full Tracking feature. Style code does not need its own smoothing.

Common pattern: switch between two styles

Mix<ChargeFullPropF, NOT_FULL_STYLE, FULL_STYLE>

When charge is not complete, the first branch shows. Once g_charge_full becomes true, the style instantly switches to the second branch.

Examples

Hard color swap

Amber while charging, green when full. The simplest possible use.

StylePtr<
  Mix<ChargeFullPropF,
    Rgb<255, 50, 0>,   // shown when charging (function = 0)
    Rgb<0, 255, 50>    // shown when full     (function = 32768)
  >
>()
AlphaL overlay

Amber base with a green pulse layered on top when full. AlphaL<> uses ChargeFullPropF as the alpha source for the pulse layer: invisible while charging, fully visible when full.

StylePtr<Layers<
  // Base: amber while charging
  RotateColorsX<Variation, Rgb<255, 80, 0>>,

  // When g_charge_full == true, fade in a green pulse overlay
  AlphaL<
    Pulsing<Rgb<0,255,0>, Black, 1200>,
    ChargeFullPropF
  >
>>()
Full preset with battery meter and boot wipe

A complete charging preset. Boot-wipe animation, then a live red-to-green battery meter while charging. Switches to a calm pulsing green when charge is confirmed complete.

StylePtr<
  Mix<
    ChargeFullPropF,

    // NOT FULL: battery bar with boot wipe
    Layers<
      Mix<
        PulsingF<Int<5000>>,
        Mix<
          SmoothStep<DisplayBattery, Int<-1>>,
          Black,
          Mix<DisplayBattery, Red, Green>
        >,
        Mix<
          SmoothStep<DisplayBattery, Int<-1>>,
          Black,
          Mix<DisplayBattery, Red, Green>
        >
      >,

      // BOOT: hold black during wipe setup
      TransitionEffectL<
        TrConcat<TrInstant, Black, TrDelay<5000>>,
        EFFECT_BOOT
      >,

      // BOOT: wipe battery bar upward
      TransitionEffectL<
        TrConcat<
          TrWipe<5000>,
          Mix<
            SmoothStep<DisplayBattery, Int<-1>>,
            Black,
            Mix<DisplayBattery, Red, Green>
          >,
          TrInstant
        >,
        EFFECT_BOOT
      >
    >,

    // FULL: pulsing green
    Pulsing<
      Green,
      Mix<Int<18000>, Green, Black>,
      4000
    >
  >
>()
Tip: pair with the charge style preset

If you also enable JMT_CHARGE_STYLE_PRESET, the typical setup is: the last preset in your array is your charging visual, and that preset's style uses ChargeFullPropF to switch to a "fully charged" appearance. The saber jumps to that preset automatically when plugged in, then visually announces when it is done charging without any user interaction.

Build Behavior

Hard Errors Combinations that fail to compile

These combinations will fail to compile. The error message points at the conflict directly.

  • Both JMT_ROLL_PRESETS and JMT_FLICK_PRESETS defined: Choose one gesture system
  • JMT_PITCH_OFFSET or JMT_ROLL_OFFSET without JMT_FLICK_PRESETS: Offsets only apply to the flick gesture
  • JMT_PITCH_OFFSET or JMT_ROLL_OFFSET without ORIENTATION_ROTATION: Required for offset math
  • Both BLADE_DETECT_PIN and JMT_BLADE_DETECT defined: Pick one blade detection method
  • JMT_CHARGE_LOCKOUT without CHARGE_DETECT_PIN: Charge features need a detect pin
  • JMT_CHARGE_STYLE_PRESET without CHARGE_DETECT_PIN: Charge features need a detect pin
  • JMT_CHARGE_COMPLETE_ANNOUNCE without CHARGE_DETECT_PIN: Charge features need a detect pin
  • JMT_BLADE_DETECT without ENABLE_POWER_FOR_ID: Required infrastructure
  • JMT_BLADE_DETECT without BLADE_ID_SCAN_MILLIS: Required infrastructure
  • JMT_BLADE_DETECT without BLADE_ID_TIMES: Required infrastructure
  • BLADE_ID_SCAN_MILLIS ≤ 0 with JMT_BLADE_DETECT: Must be greater than 0
  • BLADE_ID_TIMES ≤ 0 with JMT_BLADE_DETECT: Must be greater than 0
  • CHARGE_FULL_EXITCHARGE_FULL_ENTER: Hysteresis required
  • CHARGE_FULL_DWELL_MS ≤ 0: Must be greater than 0
Warnings Build succeeds, with a warning emitted

Recommended to address before flashing, but not blocking.

  • JMT_CHARGE_LOCKOUT without FETT263_SAVE_GESTURE_OFF: Prior gesture state will not be fully preserved across a charge cycle
Automatic Behaviors Defaults and silent toggles you don't have to set

No action required. Listed here for transparency.

  • NUM_BUTTONS == 1 automatically enables JMT_DISABLE_FAVORITES. The favorites system relies on AUX, so it cannot run on a single-button build.
  • JMT_PITCH_OFFSET and JMT_ROLL_OFFSET default to 0 when JMT_FLICK_PRESETS is enabled.