Status: draft
Audience: firmware developers, hardware designers, production/test owners
Target release: DefCon 34 booth giveaway
Primary goal: open source Arduino-compatible firmware for a cartoon washer/dryer combo badge with an infinity-mirror "water in the drum" LED effect
The badge represents a cartoon version of the FirstBuild / GE Appliances washer/dryer combo unit. The visual centerpiece is a circular drum with approximately 8 addressable RGB LEDs behind or adjacent to an infinity-mirror surface. A 3-axis accelerometer provides the gravity vector. Firmware maps the gravity vector to the LED ring so blue LEDs appear to sit at the bottom of the washer drum. When the badge is jostled, the apparent water moves and damps like sloshing liquid.
When the badge remains stationary for a configurable period, it enters an automatic "cycle running" mode. In this mode the water gently tumbles, rocks, or spins to imply the washer/dryer is running even while the user is not moving the badge.
- Use Arduino-compatible tooling and libraries so attendees can understand, modify, and rebuild the firmware.
- Keep the firmware portable across MCU, accelerometer, and LED substitutions driven by pricing and availability.
- Prefer permissive open-source libraries for the default configuration.
- Avoid hard-coding board pinouts, LED count, sensor orientation, I2C addresses, or animation constants in core logic.
- Make the badge visually convincing with only about 8 RGB LEDs.
- Keep power draw acceptable for a small wearable giveaway battery.
- Provide a simple factory test mode for production bring-up.
- Publish code, build instructions, license notices, and tuning notes in the repository.
- No wireless features are required for the giveaway firmware.
- No companion phone app is required.
- No persistent attendee data collection.
- No dependency on cloud services.
- No complex physical fluid simulation; the effect should look good, run cheaply, and be easy to tune.
The firmware must support build-time hardware profiles so the final BOM can be selected late based on price and availability.
Each hardware profile must define:
- MCU platform and Arduino core.
- Board name for Arduino CLI and/or PlatformIO.
- CPU clock and relevant MCU feature flags.
- LED data pin.
- LED chipset/protocol, color order, and count.
- LED ring direction: clockwise or counterclockwise.
- LED index offset for the physical bottom/top orientation.
- LED ring plane and the two badge-coordinate axes that define the ring.
- Accelerometer type.
- Accelerometer transport: I2C preferred, SPI optional.
- Accelerometer I2C address or SPI pins.
- Sensor-to-badge axis mapping and sign inversion.
- Optional button, capacitive touch, or test pads.
- Battery voltage measurement pin, if populated.
- Power-control pins, if populated.
Core animation and behavior code must not directly include board-specific pin numbers or sensor orientation constants. Those values belong in hardware_profile.h or equivalent profile files.
Use Arduino as the primary developer-facing framework.
Prototype MCU recommendation: Adafruit KB2040 using the RP2040 Arduino core.
Recommended repository support:
- Arduino IDE compatibility via a standard
.inosketch or Arduino library layout. - Arduino CLI build instructions.
- Optional PlatformIO environment files for repeatable CI builds and easier multi-board support.
PlatformIO should be treated as a convenience layer, not the only supported path.
The KB2040 must be integrated as a hardware profile, not as a permanent firmware assumption. Core behavior code must not depend on RP2040-only APIs unless they are isolated behind a project-owned adapter, such as the LED backend or platform timing layer.
Default recommendation: FastLED.
Reasons:
- MIT license.
- Broad support for WS2812/SK6812-style addressable LEDs.
- Good color, brightness, and palette utilities.
- Familiar in badge, art, and wearable projects.
Alternative: Adafruit NeoPixel.
Use only if the chosen MCU/LED combination is better supported there or if the team accepts LGPL-3.0 compliance requirements. Since microcontroller firmware is often statically linked, legal review is recommended before shipping a closed binary that includes LGPL code.
Prototype accelerometer recommendation: LSM6DSOX using the Adafruit LSM6DS Arduino library.
Reasons:
- Available for the prototype build.
- Includes a 3-axis accelerometer, sufficient for gravity vector and shake detection.
- I2C support.
- BSD-style license in the Adafruit LSM6DS library.
- Good Arduino ecosystem support.
Acceptable alternatives:
- LIS3DH-compatible part with Adafruit LIS3DH library if the production BOM uses a cheaper accelerometer-only part.
- MPU6050 with Adafruit MPU6050 library if pricing/availability is better, but the gyro is unnecessary for this effect.
- Built-in IMU libraries for boards that include an accelerometer, provided licensing and Arduino support are acceptable.
- A custom minimal driver for a selected accelerometer if it reduces license risk or binary size.
The LSM6DSOX must be integrated through the MotionSensor abstraction, not called directly from animation, state-machine, or rendering code. The production accelerometer should be swappable by adding or selecting another driver adapter and changing the hardware profile.
Recommended project license for badge firmware: MIT or Apache-2.0.
Dependency policy:
- Prefer MIT, BSD, ISC, or Apache-2.0 libraries.
- LGPL libraries are allowed only with an explicit
THIRD_PARTY_NOTICES.mdentry and a release process that preserves license compliance. - GPL libraries are not allowed in shipped firmware unless the entire firmware license strategy is intentionally changed and approved.
- Vendor datasheets, board files, and binary blobs must be tracked separately from source licensing.
This section is engineering guidance, not legal advice.
Prototype MCU recommendation: Adafruit KB2040 / RP2040.
Reasons:
- Available and inexpensive for prototype work.
- No unused WiFi/BLE subsystem, keeping the prototype closer to a low-cost production badge.
- Strong Arduino support.
- Native USB bootloader and serial debugging.
- STEMMA QT connector for quick LSM6DSOX I2C bring-up.
- RP2040 PIO provides robust timing options for addressable LEDs if needed.
- More than enough RAM and flash for animation tuning while still being a simple microcontroller.
Minimum MCU capabilities:
- Arduino-compatible core.
- At least 32 KB flash after bootloader; 128 KB+ preferred.
- At least 4 KB RAM; 16 KB+ preferred.
- One LED data-capable GPIO.
- I2C bus for accelerometer.
- 3.3 V operation preferred.
- USB bootloader or reliable programming pads.
- Millisecond timer support.
- Optional ADC for battery measurement.
- Optional low-power sleep mode.
Recommended MCU families to evaluate:
| Family | Why Consider | Risks / Notes |
|---|---|---|
| RP2040 | Prototype target via KB2040; inexpensive, widely available, strong Arduino support, plenty of RAM/flash when paired with external flash | More pins/package than needed; power may be higher than minimal production MCUs |
| ESP32-C3 / ESP32-S3 | Cheap modules, strong Arduino ecosystem, USB options on newer parts | Wireless may be unused; power and boot strapping need careful board design |
| SAMD21 | Mature Arduino support and common in wearable boards | May be more expensive or less available than newer parts |
| ATtiny 0/1/2-series | Very cheap and compact | More constrained; verify LED timing, I2C, bootloader, and library support early |
The firmware must compile without changing core behavior code when moving between these families.
The prototype must use the KB2040 as a convenient RP2040 development board while preserving the ability to move to a cheaper production MCU later.
Recommended initial settings:
| Setting | Value | Reason |
|---|---|---|
| Prototype board | Adafruit KB2040 | Selected prototype MCU board |
| MCU family | RP2040 | Simple non-wireless microcontroller family with strong Arduino support |
| Arduino core | Earle Philhower RP2040 core or another documented RP2040 Arduino core | Common Arduino path for RP2040 boards |
| CPU clock | Default core setting, typically 125 MHz or 133 MHz | Avoid overclocking so timing and power remain realistic |
| USB | Native USB serial enabled for development builds | Simplifies logging and flashing |
| Wireless | None | Production firmware must not assume WiFi/BLE |
| I2C | STEMMA QT / Qwiic connector, 400 kHz initial bus speed | Direct prototype path for the LSM6DSOX breakout |
| LED data | One profile-defined GPIO | Keeps LED routing independent of board choice |
| LED timing backend | FastLED first; RP2040 PIO-backed backend allowed if needed | Keep API stable while allowing board-specific timing fixes |
| Onboard NeoPixel | Disabled in production behavior; optional status use in development/factory test | Avoid confusing the badge LED count and current estimates |
| Debug serial | Enabled in development, optional in release | Useful during tuning but not required for production |
| Low-power features | Do not rely on RP2040-specific sleep behavior for core UX | Later cheap MCU may have different sleep support |
All RP2040-specific code must be isolated in one of:
- hardware profile definitions,
- platform timing/serial helpers,
- LED backend implementation,
- factory-test helpers.
The application state machine, slosh model, gravity estimator, and idle-cycle controller must compile without RP2040-specific includes.
The ESP32-S3 QT Py is an acceptable fallback prototype board only if the KB2040 is unavailable. If used, wireless features must stay disabled and no production behavior may depend on ESP32-specific APIs.
Minimum accelerometer capabilities:
- 3-axis acceleration.
- I2C interface.
- 2 g or 4 g measurement range.
- 25 Hz to 100 Hz output data rate.
- Stable readings while stationary.
- Arduino library with acceptable license, or a simple datasheet suitable for a small custom driver.
Preferred accelerometer behavior:
- Configurable low-power mode.
- Hardware interrupt for motion/wake, if the PCB exposes it.
- Small package with good sourcing options.
- Known I2C address options to avoid conflicts.
Recommended part families to evaluate:
| Family | Why Consider | Firmware Notes |
|---|---|---|
| LSM6DSOX | Available for prototype, Arduino library support, accelerometer data is sufficient | Prototype target; use accelerometer only and disable unused features |
| LIS3DH | Simple, common, low power, library support | Good production candidate if price and availability work |
| LIS2DW12 / LIS2DH12 | Low-power successors/alternatives | Confirm library license and register compatibility before selection |
| KX13x / KXTJ3 | Often cost-effective | May require SparkFun or custom driver; verify license |
| MPU6050 | Widely available module ecosystem | Overkill because gyro is not needed; higher power possible |
The prototype must use the LSM6DSOX in the simplest accelerometer-only configuration so it better represents the eventual cheap production sensor.
Recommended initial settings:
| Setting | Value | Reason |
|---|---|---|
| Transport | I2C | Keeps wiring and firmware close to likely production accelerometers |
| I2C bus speed | 400 kHz | Standard fast-mode I2C; reduce to 100 kHz if wiring is noisy |
| I2C address | Hardware profile value, commonly 0x6A or 0x6B |
Depends on breakout/SA0 wiring |
| Accelerometer range | +/-2 g | Best resolution for gravity and normal badge motion |
| Accelerometer output data rate | 52 Hz | Closest LSM6DSOX setting to the 50 Hz firmware target |
| Gyroscope | Powered down / disabled | Not needed for gravity-vector water effect |
| FIFO | Disabled | Direct reads are simpler and closer to cheap accelerometer behavior |
| Embedded ML core / finite state machine | Disabled | Avoids depending on features absent from cheap parts |
| Tap, step, pedometer, orientation interrupts | Disabled | Keep motion interpretation in portable firmware |
| Block data update | Enabled when supported by the library/driver | Avoids mixed high/low-byte samples |
| Acceleration units exposed to app | g or m/s^2, normalized by driver |
Core logic must not depend on raw register scale |
| Sample quantization for prototype realism | Optional 10-bit equivalent in development builds | Helps simulate lower-cost accelerometer noise/resolution |
The driver may read the LSM6DSOX through Adafruit's unified sensor events, but it must convert those events immediately into the firmware's internal MotionSample format. No Adafruit-specific types should cross the MotionSensor boundary.
Minimum LED requirements:
- Approximately 8 addressable RGB LEDs in a circular ring.
- Single-wire protocol compatible with FastLED or Adafruit NeoPixel.
- Works at selected battery and logic voltage, or includes level shifting/power design support.
- Consistent color order documented in hardware profile.
Recommended LED families:
| Family | Why Consider | Firmware Notes |
|---|---|---|
| WS2812B / WS2812C | Very common and cheap | Timing sensitive; usually well supported |
| SK6812 RGB | Common alternative, often similar firmware behavior | Confirm color order |
| APA106 / PL9823-style | Through-hole options for novel mechanical layouts | Confirm FastLED support and timing |
RGBW LEDs are not required. If RGBW is selected, the renderer must ignore or intentionally use the white channel through a hardware-specific LED adapter.
BadgeApp
- Owns setup and main loop.
- Initializes profile, sensor, LEDs, settings, and runtime state machine.
- Calls all periodic updates with elapsed time.
HardwareProfile
- Compile-time configuration for board, pins, LED type, count, color order, sensor type, I2C address, axis mapping, and production options.
- Provides constants only; it should not contain animation logic.
MotionSensor
- Abstract interface for accelerometer reads.
- Returns acceleration in badge coordinates, not raw chip coordinates.
- Reports sensor health and last-read age.
- Exposes normalized samples through a small project-owned type, such as
MotionSample, so sensor libraries can be replaced without touching application logic. - Owns sensor-specific setup, output data rate, range, and power-mode configuration.
GravityEstimator
- Low-pass filters acceleration into a stable gravity vector.
- Separates gravity from high-frequency jostle energy.
- Provides board-plane vector components, angle, magnitude, stability score, and shake score.
LedRenderer
- Converts the circular water direction and projected gravity magnitude to LED colors.
- Applies configurable water width, brightness limiting, color palettes, and optional mirror-specific tuning.
- Owns the LED library calls.
IdleCycleController
- Detects stationary periods.
- Transitions between interactive slosh and automatic cycle modes.
- Generates virtual drum motion during idle cycle mode.
RuntimeStateMachine
- Owns high-level states: boot, interactive, idle cycle, factory test, and fault.
- Applies transition rules and keeps mode-specific timing out of rendering code.
PowerManager
- Applies brightness caps.
- Optionally enters sleep or dim modes.
- Optionally samples battery voltage.
FactoryTest
- Provides LED chase, color order test, accelerometer axis test, and idle-cycle test.
- Activated by compile-time flag, button/test pad at boot, or serial command.
Production poll loop rate: 125 Hz. MXC6655XA sample processing rate: 100 Hz.
Required behavior:
- Poll the MXC6655XA every 8 ms so each fixed-rate 100 Hz DRDY event is captured.
- Update gravity filters only for fresh samples, using elapsed sample time.
- Render LEDs from the 125 Hz application loop.
- Avoid blocking
delay()in normal operation. - Keep LED
show()duration considered in timing budget.
Pseudo-flow:
void loop() {
const uint32_t now = millis();
const float dt = clock.step(now);
if (motionSensor.update(now)) {
gravityEstimator.update(motionSensor.sample(), sampleClock.step(now));
}
idleCycleController.update(gravityEstimator, dt);
updateWaterDirection(gravityEstimator.result(), dt);
ledRenderer.renderGravity(waterDirection, gravityEstimator.result().ringMagnitude);
powerManager.update(now);
}Required states:
| State | Entry Trigger | Exit Trigger | Behavior |
|---|---|---|---|
Boot |
Power-on or reset | Init complete, factory request, or fault | Low-brightness startup pattern and hardware init |
InteractiveSlosh |
Normal boot, user movement, recovered sensor | Stationary timeout, factory request, or fault | Gravity-driven slosh effect |
IdleCycle |
Stationary timeout | Movement or fault | Simulated washer cycle while still biased by gravity |
FactoryTest |
Test flag, boot button/test pad, or serial command | Power cycle or explicit command | Production verification patterns |
Fault |
Sensor init/read failure or unrecoverable profile issue | Fault clears or power cycle | Visible fault pattern and periodic retry |
State transitions must be deterministic and non-blocking. Animation phase should be reset only when entering a state, not on every loop.
On boot:
- Initialize serial if enabled.
- Initialize LEDs to a short, low-brightness startup animation.
- Initialize I2C and accelerometer.
- If sensor init fails, show a fault pattern and continue retrying.
- If factory test mode is requested, enter factory test instead of normal operation.
Startup animation:
- Duration: less than 2 seconds.
- Brightness: capped to avoid a power surge.
- Visual: blue ring fill or short washer "ready" glint.
This is the default mode when motion is detected or shortly after boot.
Inputs:
- Gravity vector angle.
- Gravity magnitude.
- Shake score.
- Current slosh angle and velocity.
Required effect:
- The brightest blue LEDs appear at the physical bottom of the ring.
- As badge orientation changes, the "water" follows with a small lag.
- Jostling adds temporary wave amplitude and overshoot.
- Motion damps smoothly after the badge stops.
Recommended model:
- Map the raw accelerometer reading into badge coordinates.
- Project gravity onto the LED ring plane.
- Compute target angle from the projected gravity vector:
targetAngle = atan2(ringX, ringY). - Use a spring-damper to move water toward
targetAngle. - Add energy when high-pass acceleration exceeds the shake threshold.
- Decay wave energy over time.
If the projected gravity vector is too small, such as when the badge is face-up or face-down, the angle is low confidence. In that case the model must hold the last valid target angle, decay wave energy, and avoid snapping the water to a random LED.
Suggested starting constants:
| Setting | Initial Value | Notes |
|---|---|---|
| Sensor rate | 52 Hz for LSM6DSOX, approximately 50 Hz target otherwise | Good enough for hand motion |
| Gravity low-pass alpha | 0.08 | Tune per loop rate |
| Slosh stiffness | 18.0 | Higher tracks gravity faster |
| Slosh damping | 5.5 | Higher reduces overshoot |
| Shake threshold | 0.18 g | Tune from real hardware |
| Ring projection min | 0.20 g | Below this, hold last angle |
| Wave decay | 1.8 per second | Larger damps faster |
| Water fill fraction | 0.38 | About 3 of 8 LEDs visibly wet |
| Max brightness | 80/255 | Start conservative for battery and mirror |
For each LED:
- Convert LED index to ring angle using profile offset and direction.
- Compute angular distance from water-bottom angle.
- Convert distance to water intensity with a smooth falloff.
- Add wave shimmer from slosh energy.
- Render dry LEDs as very dim teal/navy or off.
- Render water LEDs as saturated blue/cyan with intensity variation.
The renderer must support LED counts from 8 to 16 without changing physics logic.
Recommended 8-LED look:
- Bottom 1-2 LEDs: bright blue/cyan.
- Adjacent lower-side LEDs: medium blue.
- Upper LEDs: off or very faint deep blue.
- During jostle: one side briefly becomes brighter to imply a wave crest.
Mirror-specific tuning:
- Use lower maximum brightness than a direct-view LED product.
- Avoid pure white flashes in normal operation.
- Keep background LEDs dim enough that the infinity mirror reads as depth, not a flat ring.
The badge is stationary when all are true:
- Gravity angle variance remains below threshold.
- Shake score remains below threshold.
- Gravity magnitude is plausibly near 1 g.
- Duration exceeds
stationaryStartMs.
Initial value: 12 seconds.
Any clear movement exits stationary state immediately or within 300 ms.
When stationary timeout expires, the badge starts an automatic washer cycle.
Required visual:
- Water moves without user motion.
- Movement should feel like a washer drum: gentle rocking, tumbling, then occasional more energetic swish.
- The physical gravity direction should still influence the average bottom so the illusion is not completely detached from orientation.
Recommended cycle pattern:
- Fill/settle: 1.5 seconds of water brightening.
- Agitate: 8-12 seconds of sinusoidal rocking around the gravity bottom.
- Tumble: 4-6 seconds of slow rotation with damped wave.
- Pause: 1-3 seconds of settle.
- Repeat with small randomized timing variation.
Movement should stop and return to interactive slosh when real movement exceeds the shake threshold.
Optional features may be included if hardware supports them:
- Button cycles through brightness levels.
- Long press enters demo mode.
- Low battery dim mode.
- Serial tuning shell for development builds.
- Hidden DefCon easter egg animation triggered by a motion pattern.
Optional features must not make the default build harder to understand or flash.
All tunable constants must live in one of:
hardware_profile.h: physical configuration.behavior_config.h: animation and timing defaults.build_flags: feature inclusion/exclusion.
Required configuration values:
constexpr McuFamily MCU_FAMILY = McuFamily::RP2040;
constexpr BoardProfile BOARD_PROFILE = BoardProfile::AdafruitKB2040;
constexpr uint32_t CPU_CLOCK_HZ = 125000000;
constexpr bool HAS_NATIVE_USB = true;
constexpr bool HAS_WIFI = false;
constexpr bool HAS_BLE = false;
constexpr bool DEBUG_SERIAL_ENABLED = true;
constexpr uint8_t LED_PIN = 5;
constexpr uint8_t LED_COUNT = 8;
constexpr LedChipset LED_CHIPSET = LedChipset::WS2812;
constexpr ColorOrder LED_COLOR_ORDER = ColorOrder::GRB;
constexpr int8_t LED_INDEX_OFFSET = 0;
constexpr bool LED_CLOCKWISE = true;
constexpr RingPlane LED_RING_PLANE = RingPlane::BadgeXY;
constexpr SensorType SENSOR_TYPE = SensorType::LSM6DSOX;
constexpr SensorTransport SENSOR_TRANSPORT = SensorTransport::I2C;
constexpr uint8_t SENSOR_I2C_ADDRESS = 0x6A;
constexpr uint32_t SENSOR_I2C_HZ = 400000;
constexpr float SENSOR_ACCEL_RANGE_G = 2.0f;
constexpr float SENSOR_ACCEL_ODR_HZ = 52.0f;
constexpr bool SENSOR_GYRO_ENABLED = false;
constexpr bool SENSOR_FIFO_ENABLED = false;
constexpr bool SENSOR_EMBEDDED_FEATURES_ENABLED = false;
constexpr bool SENSOR_SIMULATE_LOW_COST_ACCEL = false;
constexpr AxisMap SENSOR_AXIS_MAP = {
.x = Axis::ChipX,
.y = Axis::ChipY,
.z = Axis::ChipZ,
.invertX = false,
.invertY = false,
.invertZ = false,
};
constexpr uint32_t STATIONARY_START_MS = 12000;
constexpr uint8_t MAX_BRIGHTNESS = 80;Actual defaults must be updated after PCB orientation and LED index direction are known.
The production hardware profile may change MCU_FAMILY, board FQBN, clock, pins, LED backend, serial behavior, and power features. That change must not require edits to GravityEstimator, IdleCycleController, or LedRenderer.
The production hardware profile may change SENSOR_TYPE, I2C address, axis map, range, and output data rate. That change must not require edits to GravityEstimator, IdleCycleController, or LedRenderer.
If accelerometer initialization or reads fail:
- Show a visible fault pattern: slow red pulse on one LED or alternating red ring.
- Retry sensor init periodically.
- If sensor recovers, return to normal operation.
- Factory test must expose this condition clearly.
The firmware cannot reliably detect single-wire LED hardware failures without extra circuitry. Factory test must provide patterns that make assembly faults obvious:
- Red, green, blue full-ring tests.
- One-pixel chase to verify order and soldering.
- Bottom-index marker to verify mechanical orientation.
If battery measurement hardware exists:
- Dim LEDs below warning threshold.
- Show an optional amber/red low battery pulse.
- Avoid high-current full-white patterns.
Factory test must be fast enough for production use.
Activation options:
- Compile
FACTORY_TEST=1. - Hold test pad/button during boot.
- Send serial command
testwithin first 3 seconds after boot.
Test sequence:
- All LEDs red, green, blue, then blue at production brightness.
- Single LED chase through the ring.
- Bottom LED marker using configured index offset.
- Accelerometer live orientation: bottom LED follows gravity.
- Shake detection: flash cyan when jostled.
- Idle-cycle preview: run 5 seconds of automatic cycle.
Pass/fail criteria:
- All LEDs light in expected order.
- Color order is correct.
- Gravity bottom points down when badge is upright.
- Sensor responds to rotation.
- No reset during max expected brightness.
Recommended structure:
firmware/
defcon34_washer_badge/
defcon34_washer_badge.ino
hardware_profile.h
hardware_profiles/
adafruit_kb2040_lsm6dsox.h
production_candidate.h
behavior_config.h
BadgeApp.h/.cpp
MotionSensor.h/.cpp
sensors/
Lsm6dsoxMotionSensor.h/.cpp
Lis3dhMotionSensor.h/.cpp
Mpu6050MotionSensor.h/.cpp
GravityEstimator.h/.cpp
LedRenderer.h/.cpp
IdleCycleController.h/.cpp
FactoryTest.h/.cpp
docs/
software-spec.md
hardware-profiles.md
tuning-guide.md
factory-test.md
THIRD_PARTY_NOTICES.md
LICENSE
README.md
The first implementation may collapse .cpp files if Arduino IDE friendliness demands it, but module boundaries should remain clear.
Minimum supported developer flow:
arduino-cli core install rp2040:rp2040
arduino-cli lib install FastLED
arduino-cli lib install "Adafruit LSM6DS"
arduino-cli compile --fqbn rp2040:rp2040:adafruit_kb2040 firmware/defcon34_washer_badge
arduino-cli upload --fqbn rp2040:rp2040:adafruit_kb2040 -p <port> firmware/defcon34_washer_badgeOptional PlatformIO flow:
pio run -e <hardware-profile>
pio run -e <hardware-profile> -t uploadRelease artifacts:
- Source tag.
- Prebuilt firmware binaries for finalized hardware profiles.
- Bill of materials profile name used for each binary.
- Third-party notices.
- Flashing instructions.
- Factory test instructions.
- Firmware builds from a clean checkout using documented Arduino-compatible tooling.
- Badge initializes LEDs and accelerometer on boot.
- Water appears at the physical bottom of the ring within 500 ms after orientation changes.
- Jostling produces visible slosh and damped settling.
- Stationary badge enters cycle mode after configured timeout.
- Movement exits cycle mode and returns to interactive slosh.
- Factory test verifies LEDs, color order, LED index direction, and accelerometer orientation.
- Face-up and face-down orientations do not produce random LED jumps.
- At least two hardware profiles can compile from the same core logic, even if only one is production hardware.
- LED count can be changed from 8 to 12 or 16 with no physics rewrite.
- Sensor axis mapping can be changed in configuration without editing the driver.
- Sensor part changes require only a hardware profile and
MotionSensordriver adapter change. - MCU pin, clock, board FQBN, serial, and wireless-capability changes require only hardware profile and platform-adapter edits.
- Production firmware does not depend on WiFi, BLE, PSRAM, dual-core behavior, or board-specific storage.
- Slosh model can be tested on a host machine without physical LEDs.
- Angle wrapping is covered by tests crossing 0/360 degrees.
- Stationary detection is covered by synthetic accelerometer samples.
- Face-up low-confidence orientation is covered by tests.
- LED renderer can be smoke-tested with a fake LED backend.
- Repository includes clear build instructions.
- Repository includes a license.
- Third-party dependencies and licenses are listed.
- Default dependencies are suitable for commercial giveaway use or explicitly documented with compliance notes.
- No private credentials, internal-only paths, or proprietary product assets are required to build firmware.
Tuning must happen on real mechanical samples because the infinity mirror, LED spacing, diffuser, and badge orientation will materially affect the result.
Prototype tuning steps:
- Confirm LED order and color order with factory test.
- Confirm KB2040 profile: RP2040 board FQBN, default clock, USB serial, I2C pins, LED data pin, and onboard NeoPixel disabled in normal behavior.
- Confirm LSM6DSOX I2C address, accelerometer-only mode, +/-2 g range, 52 Hz output data rate, disabled gyro, and disabled FIFO.
- Confirm accelerometer axis map by rotating the badge upright, left, right, upside down, and face-up.
- Tune maximum brightness for mirror depth, battery life, and eye comfort.
- Tune water fill fraction until approximately the lower third of the drum appears filled.
- Tune spring/damping until slosh looks responsive but not noisy.
- Tune stationary threshold using real booth handling scenarios.
- Tune idle-cycle motion so it reads as a washer cycle at arm's length.
- Measure current draw in boot, interactive, idle cycle, and factory test.
Use this matrix when selecting final parts. Scores should be refreshed near purchase time because price and availability change.
Scale: 1 = poor, 3 = acceptable, 5 = strong.
| Category | Weight | MCU A | MCU B | Accelerometer A | Accelerometer B | LED A | LED B |
|---|---|---|---|---|---|---|---|
| Unit cost at target quantity | 5 | ||||||
| Distributor availability | 5 | ||||||
| Second-source availability | 4 | ||||||
| Arduino support quality | 5 | ||||||
| Library license fit | 5 | ||||||
| Power consumption | 4 | ||||||
| PCB/package fit | 4 | ||||||
| Firmware complexity | 3 | ||||||
| Factory programming ease | 3 | ||||||
| Community familiarity | 2 |
Selection rule:
- Any part with unacceptable license risk, unavailable Arduino support, or unreliable sourcing is disqualified.
- Among remaining parts, choose the highest weighted score.
- If two options are close, prefer the one with simpler firmware and clearer open-source examples.
- FastLED license: https://github.com/FastLED/FastLED/blob/master/LICENSE
- Adafruit NeoPixel repository and LGPL-3.0 license notice: https://github.com/adafruit/Adafruit_NeoPixel
- Adafruit KB2040 product page: https://www.adafruit.com/product/5302
- Adafruit QT Py ESP32-S3 product page: https://www.adafruit.com/product/5700
- Adafruit LSM6DS repository and BSD license note: https://github.com/adafruit/Adafruit_LSM6DS
- Adafruit LIS3DH repository and BSD license note: https://github.com/adafruit/Adafruit_LIS3DH
- Adafruit MPU6050 repository and BSD license note: https://github.com/adafruit/Adafruit_MPU6050
- Arduino LSM6DS3 repository: https://github.com/arduino-libraries/Arduino_LSM6DS3