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.
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.
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 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.
props/jmt_fett263_wrapper.h The wrapper itself. This is the file your config includes. common/charge_state.h Holds the shared g_charging and g_charge_full state declarations the wrapper relies on. functions/charge_full_prop.h Optional. Only needed if a blade style references ChargeFullPropF to react to charge-complete. 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.
INPUT_PULLUP. Chassis detected when the pin is shorted to GND.#define CHASSIS_DETECT_PIN_HIGH. Requires an external pulldown so the pin sits LOW when no chassis is present.JMT_CHASSIS_WAKE: wake motion detection on chassis state change. Helpful if the board is in a deep idle state.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.
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).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.
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).
| Macro | Default | Meaning |
|---|---|---|
CHARGE_FULL_ENTER | 32700 | Level required before full-charge dwell timing begins |
CHARGE_FULL_EXIT | 32000 | Drop-out threshold. Must be below ENTER. |
CHARGE_FULL_DWELL_MS | 30000 | How long ENTER must be held before declaring full |
When charging is no longer detected, the full state and dwell timer are cleared immediately.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| Macro | Notes |
|---|---|
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.
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.
On insertion:
On removal:
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.
ENABLE_POWER_FOR_IDBLADE_ID_SCAN_MILLIS > 0BLADE_ID_TIMES > 0Mutually exclusive with: BLADE_DETECT_PIN.
If BLADE_DETECT_PIN is already present in your config, this feature adds two small behavior improvements:
Nothing additional to configure. No new macros required.
Mutually exclusive with: JMT_BLADE_DETECT.
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.
| Orientation | Action |
|---|---|
| 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 |
mconfirm.wav and mdefault.wav.mdefault.wav.mcancel.wav.BUTTON_POWER press confirms (clears favorites, writes empty file).BUTTON_AUX press cancels.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.
| Macro | Default | Meaning |
|---|---|---|
JMT_MAX_PRESET_FAVORITES | 10 | Maximum favorite slots |
JMT_DISABLE_FAVORITES | off | Manually disable favorites entirely |
Single-button builds (NUM_BUTTONS == 1) automatically disable favorites since the system relies on AUX.
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.
0 when not full, 32768 when full
Mix<>, AlphaL<>, masks, transitions, selectors
DisplayBatteryChargeFullPropFBy the time ChargeFullPropF returns 32768, the wrapper has already validated four conditions:
Thresholds and timing live in the Charge Full Tracking feature. Style code does not need its own smoothing.
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.
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)
>
>()
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
>
>>()
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
>
>
>()
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.
These combinations will fail to compile. The error message points at the conflict directly.
JMT_ROLL_PRESETS and JMT_FLICK_PRESETS defined: Choose one gesture systemJMT_PITCH_OFFSET or JMT_ROLL_OFFSET without JMT_FLICK_PRESETS: Offsets only apply to the flick gestureJMT_PITCH_OFFSET or JMT_ROLL_OFFSET without ORIENTATION_ROTATION: Required for offset mathBLADE_DETECT_PIN and JMT_BLADE_DETECT defined: Pick one blade detection methodJMT_CHARGE_LOCKOUT without CHARGE_DETECT_PIN: Charge features need a detect pinJMT_CHARGE_STYLE_PRESET without CHARGE_DETECT_PIN: Charge features need a detect pinJMT_CHARGE_COMPLETE_ANNOUNCE without CHARGE_DETECT_PIN: Charge features need a detect pinJMT_BLADE_DETECT without ENABLE_POWER_FOR_ID: Required infrastructureJMT_BLADE_DETECT without BLADE_ID_SCAN_MILLIS: Required infrastructureJMT_BLADE_DETECT without BLADE_ID_TIMES: Required infrastructureBLADE_ID_SCAN_MILLIS ≤ 0 with JMT_BLADE_DETECT: Must be greater than 0BLADE_ID_TIMES ≤ 0 with JMT_BLADE_DETECT: Must be greater than 0CHARGE_FULL_EXIT ≥ CHARGE_FULL_ENTER: Hysteresis requiredCHARGE_FULL_DWELL_MS ≤ 0: Must be greater than 0Recommended 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 cycleNo 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.