Skip to the content
PCBKitNew board
2 layers100 × 18.2 mmKnappepanel flexit, the printed circuit board in 3D

ESP32-C5 button panel for the Flexit Tradition-S ventilation unit — 5 illuminated buttons emulated through opto-couplers, Wi-Fi 2.4/5 GHz, and MQTT for smart-home integration. Web UI for Wi-Fi and broker configuration. KiCad hardware included.

Size
100 × 18.2 mm
Copper layers
2
Thickness
1.6 mm
Parts
26
Nets
30
Pads
96
Tracks, vias
343, 42
Pours
3
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
26 May 2026
Updated
24 Jul 2026

Overview

Knappepanel flexit is an open-source printed circuit board by KrHaaland, from the KrHaaland/Flexit-Button-controller repository on GitHub. It is a 2-layer board of 100 × 18.2 mm with 26 parts on 30 nets, routed with 343 tracks and 42 vias.

It is built around the AQY212S. Its parts include 6 ICs, 5 resistors, 5 diodes, 5 switches and 2 capacitors. It belongs with the home automation and IoT and radio and wireless boards and the ESP32 and RISC-V boards of this library.

Main components

1

README

README.md

Flexit-Button-controller

ESP32-C5 button panel for the Flexit Tradition-S ventilation unit. Replaces or parallels the original 5-button control panel with an isolated emulator that adds Wi-Fi (2.4 GHz + 5 GHz) and MQTT for smart-home integration.

The ESP32-C5 is currently one of the few cheap ESP modules with dual-band Wi-Fi 6, which lets the panel live on either band depending on your home network and signal coverage.

What it does

The board exposes 5 channels, each mirroring one button + indicator LED on the original Flexit Tradition-S panel:

  • 5 button outputs drive AQY212S solid-state opto-couplers in parallel with on-board tactile switches. A short pulse on a channel looks electrically identical to a finger press, in galvanic isolation from the Flexit unit.
  • 5 LED inputs read the indicator-LED state back from the panel net.

You get three ways to interact with the panel:

  1. The physical tactile buttons on the PCB (works without firmware).
  2. A self-served web UI (Wi-Fi setup, manual trigger, live LED dots, pulse tuning, MQTT setup).
  3. MQTT — subscribe to LED state changes, publish to fire buttons.

Hardware

  • Module: ESP32-C5-Mini (USB-C, dual-band Wi-Fi 6, 4 MB flash, no PSRAM)
  • Isolation: 5x AQY212S solid-state relays (LED-driven MOSFET output, ~3 ms switching)
  • Connector to Flexit: Micro-MaTch 2x6 (J1), carrying 5 button signals + 5 LED signals + 5 V supply + GND, mating with the cable on the back of the original Flexit panel.
  • PCB: KiCad files under PCB/ (Gerbers/BOM/positions for the fab in PCB/production/).

Pin mapping (set in FW/ESP32-C5-Flexit/src/main.cpp)

ChannelTrigger outLED in
1IO6IO4
2IO7IO3
3IO8IO2
4IO9IO1
5IO10IO5

Board revision matters: the table above is board rev 2 (all LED inputs on ADC-capable pins). The original rev-1 board wired LEDs to IO5–IO9 and triggers to IO4–IO0 — firmware ≤ 0.2.x fits rev 1 only, firmware ≥ 0.3.0 fits rev 2 only. Flashing the wrong pairing drives the opto-couplers from pins wired to LED sense lines.

Trigger outputs idle LOW and pulse HIGH for ~300 ms (configurable from the UI) to emulate a press.

Board rev 2 routes all five LED inputs onto IO1–IO5 — the only pins the ESP32-C5's SAR ADC reaches — so every LED is read as analog. An unlit LED doesn't conduct, which would leave its sense line floating — out of spec for the ADC (the C5 datasheet characterizes it with a 100 nF cap on the input) and the source of wildly wandering readings during bring-up. The firmware therefore keeps the internal ~45 kΩ pulldown enabled on every sense line (re-enabled after ADC attach, which turns digital pulls off): an unlit line reads a solid 0, while a lit line sits at ~460 counts (≈1.5 V — the LED's forward voltage; its mA-scale drive is unaffected by the pulldown). Each line is sampled with a short ADC burst every ~100 ms (first sample discarded for mux settling). Classification is two-layered: a windowed minimum over ~10 s with hysteresis (280/320 counts) as the safety net, plus a fast path — a floor reading means UNLIT immediately, and a reading at lit level while another line just showed the floor means LIT immediately — so both transitions normally report within ~0.2–0.5 s. Known trade-off: the Flexit occasionally biases an unlit line to ~V_f for a few seconds (invisible µA through the LED), which the fast path reports as a short lit blip; debounce sub-5 s pulses in automations if that matters. /status exposes "adc" (windowed min per channel) and "raw" (last burst); /adcsolo?ch=N samples a single channel for diagnostics (ch=-1 restores round-robin). (readLed() auto-detects which pins are ADC-capable via digitalPinToAnalogChannel(); a pin without ADC would fall back to a digital read.)

Build & flash

Requires PlatformIO (pip install platformio or the VSCode extension).

bash
cd FW/ESP32-C5-Flexit
pio run                       # compile
pio run -t upload             # flash via USB-C
pio device monitor -b 115200  # serial log

The C5 enumerates as /dev/ttyACM* (built-in USB Serial JTAG, no driver needed).

First-time setup

  1. Power the ESP32-C5 over USB-C, or via the J1 connector once mounted.
  2. With no saved Wi-Fi, the board comes up as an access point:
    • SSID: Flexit-Setup
    • Password: fibonacci (default; the Wi-Fi form lets you set a custom one if you don't want the documented value).
  3. Join that AP and open http://192.168.4.1/ (also reachable as http://flexit.local/ via mDNS).
  4. In the Wi-Fi section, enter your home SSID + password and click Save & reboot. You can optionally set a custom AP fallback password in the same form — it persists in NVS. The board joins your network and is then reachable on its STA IP or via flexit.local.

Wi-Fi credentials and the AP-password override are stored in NVS and survive a reflash. Forget & reboot to AP wipes the Wi-Fi creds but keeps the AP password override.

If the home network is briefly unreachable at boot, the board lands in AP mode but periodically retries (every 5 min) so a transient router outage doesn't strand it.

Web interface

SectionWhat it does
Knapper5 trigger buttons + 5 live LED indicators (polled every 400 ms).
Pulse durationLength of the simulated button press (20–2000 ms, default 300 ms). Useful if a Flexit function requires a long-press.
MQTTBroker host/port, optional user/password, base topic, heartbeat republish interval, live connection status, MAC-derived client ID.
Wi-FiSSID + password, Save & reboot into STA, Forget clears creds. Banner at top shows current STA/AP state, RSSI, IP.

Everything except Wi-Fi changes applies live without reboot.

HTTP API

Everything the web UI does goes through these endpoints, so they can be scripted directly:

EndpointMethodWhat it does
/statusGETJSON: leds (0/1 ×5), pulses (active ×5), adc (windowed-min counts ×5), raw (last burst ×5).
/triggerPOSTsw=1..5 — fire a button pulse. Rejected with 503 while an OTA upload is running.
/configGET/POSTPulse duration (pulseMs=20..2000), persisted in NVS.
/mqttGET/POSThost, port, user, pass, base (topic prefix), pubIntervalSec (heartbeat).
/wifiPOSTssid, pass (+ optional apPass fallback override) — saves and reboots into STA.
/wifi/clearPOSTForget Wi-Fi credentials, reboot to setup AP.
/netinfoGETCurrent mode (STA/AP), SSID, IP, RSSI.
/versionGETFW version, build timestamp, SDK, chip, flash size, free heap/slot.
/updatePOSTMultipart firmware.bin upload (OTA) + reboot.
/adcsoloGETDiagnostic: ch=0..4 samples only that LED channel (others untouched), ch=-1 restores round-robin.

MQTT

When the board is in STA mode (joined to your Wi-Fi) and a broker host is configured, it connects to the broker and stays connected with auto-reconnect and a Last Will. With base topic flexit (default — configurable in the UI):

TopicDirectionPayloadNotes
flexit/led/1..5/statepublishON / OFFRetained. Sent on every transition; also republished periodically if heartbeat > 0.
flexit/sw/1..5/triggersubscribeany (optional <ms>)Any payload triggers a pulse on that channel. A numeric payload overrides the configured pulse duration for that single trigger.
flexit/statuspublishonline / offlineLWT, retained.

Client ID is flexit-<MAC> (12 hex chars from the chip's eFuse MAC) — globally unique per ESP32. Use it in your broker's ACL.

Home Assistant example

yaml
mqtt:
  binary_sensor:
    - name: "Flexit LED 1"
      state_topic: "flexit/led/1/state"
      payload_on: "ON"
      payload_off: "OFF"
    # ... repeat for 2..5
  button:
    - name: "Flexit button 1"
      command_topic: "flexit/sw/1/trigger"
    # ... repeat for 2..5

Firmware updates (OTA)

Once the device is on your Wi-Fi (STA mode), you don't need USB again — there are two ways to push a new build:

Web upload

  1. Build locally: cd FW/ESP32-C5-Flexit && pio run. The artifact is .pio/build/esp32-c5-devkitc1-n4/firmware.bin.
  2. Open the device's web UI (http://flexit.local/ or its STA IP).
  3. Scroll to the Firmware section. The current version, build timestamp and free slot space are shown.
  4. Pick the new firmware.bin, click Upload & reboot. The progress bar tracks the upload, then the board reboots into the new image. The page auto-reloads after ~8 s.

PlatformIO push

bash
cd FW/ESP32-C5-Flexit
pio run -e esp32-c5-devkitc1-n4-ota -t upload

This uses the espota protocol over mDNS at flexit.local:3232.

Partition layout

Switched from the Arduino default (default.csv, 1.25 MB per app slot) to min_spiffs.csv (1.875 MB per slot, two slots, plus 128 KB SPIFFS). NVS stays at offset 0x9000 so saved Wi-Fi / MQTT / pulse settings survive the migration when you flash the OTA-enabled image for the first time.

Robustness

The HTTP OTA path is hardened against the common failure modes:

  • The 5 button-trigger pins are forced LOW at the start of every upload, and /trigger + MQTT-fired triggers are ignored during the upload — a press in flight can't keep the opto-coupler latched through the multi-second multipart parse.
  • Idle timeout (10 s) and total timeout (120 s) abort slow-loris uploads.
  • A sticky abort flag short-circuits the rest of the upload as soon as Update.begin, a short write, or Update.end reports an error — no point in streaming 1 MB just to fail at the end.
  • MQTT publishes offline retained to <base>/status and disconnects gracefully before the reboot, so subscribers see the flap immediately instead of waiting out the 30 s keepalive.
  • Wi-Fi disconnect + server.close() + an 800 ms settle before ESP.restart() give the TCP FIN and the HTTP 200 response enough time to flush on a marginal link.
  • A Wi-Fi event handler re-binds mDNS and ArduinoOTA whenever the STA interface gets a new IP, so wireless reflash keeps working after a router reboot or AP roam.

Security model

There's no authentication on either OTA path or on the other config endpoints. The assumption is that your Wi-Fi LAN is trusted (this is a home device behind your router, not on the open internet). Anyone with route to the device on TCP 80 or 3232 can re-flash it.

The AP fallback PSK is fibonacci by default — yes, the same one this README mentions, on purpose. The Wi-Fi form lets you override it per device if you'd rather not advertise it in the source. Override is persisted in NVS.

If you need stricter controls (HTTP Basic auth on /update, an ArduinoOTA password, signed firmware images, broker ACLs, VLANs), open an issue.

Repo layout

text
.
├── FW/ESP32-C5-Flexit/      PlatformIO project (Arduino framework)
│   ├── src/main.cpp         Firmware
│   └── src/index_html.h     Embedded web UI (single page)
├── PCB/                     KiCad schematic + PCB design
│   └── production/          Gerbers, BOM, positions for fab
└── knappepanel_flexit.kicad_pro

Notes & quirks

  • The board has 5 on-board tactile switches in parallel with the opto outputs, and 5 indicator LEDs on the LED nets, so it doubles as a stand-alone bench panel.
  • ESP32-C5 boot prints MSPI Timing: Failed to allocate dummy cacheline for PSRAM memory barrier! — harmless, the C5-Mini has no PSRAM but the SDK looks for it.
  • Verified against a Flexit Tradition-S. Other Flexit models with a different button matrix or LED drive scheme may need pin or polarity changes (see LED_ACTIVE_HIGH in src/main.cpp).

License

No license set yet — treat as "all rights reserved" until one is added. Open an issue if you'd like a specific one (MIT / Apache-2.0 / CC-BY-SA for the hardware, etc.).

Parts

26
QtyValueFootprintReferences
5AQY212SKicad8_multilib:AQY212SU1, U2, U3, U4, U5
5LEDLED_SMD:LED_1206_3216MetricD1, D2, D3, D4, D5
5RResistor_SMD:R_0805_2012MetricR1, R2, R3, R4, R5
5Tactile SW 6x6mmKicad8_multilib:Tactile SW 6x6mmSW1, SW2, SW3, SW4, SW5
210uFCapacitor_SMD:C_0805_2012MetricC1, C2
2TestPointTestPoint:TestPoint_Pad_1.5x1.5mmTP1, TP2
1MicroMatch_2x6Kicad8_multilib:Micromatch_2x6J1
1~Kicad8_multilib:ESP32-C5MiniU7

Questions

How big is the Knappepanel flexit PCB?

It measures 100 × 18.2 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Knappepanel flexit use?

26 parts, built around the AQY212S. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Knappepanel flexit KiCad files?

From the KrHaaland/Flexit-Button-controller repository on GitHub, at the commit this page was made from: the board is PCB/knappepanel_flexit.kicad_pcb and its schematic FW/ESP32-C5-Flexit/knappepanel_flexit.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Knappepanel flexit design in my own project?

Its repository states no licence, so its author keeps all rights: ask KrHaaland before reusing the design.

Similar boards

  • 2 layers250 × 120 mmDriveboardUSB, the printed circuit board in 3D

    DriveboardUSB

    nortd/driveboard

    Hardware for the DriveboardApp

    75 parts3
  • 4 layers75 × 90 mmVehiclePrecharge PCB, the printed circuit board in 3D

    VehiclePrecharge PCB

    nsm81/VehiclePrecharge_PCB

    The precharge board contains the precharge resistors, precharge relay, main relay drivers (with economisers), and a controlling micro.

    56 parts
  • 2 layers48 × 28 mmPir camper am312, the printed circuit board in 3D

    Pir camper am312

    samba09/camper-pir-switch

    PIR motion switch for Revotion NODE Ambient (AM312, PhotoMOS, KiCad)

    28 partsMIT
  • 2 layers147.3 × 94 mmDriveboard, the printed circuit board in 3D

    Driveboard

    bullestock/lasersaur

    53 parts4
  • 2 layers57.3 × 19 mm11ua, the printed circuit board in 3D

    11ua

    rene-dev/stmbl

    AC Servo Driver for STM32F4

    26 parts990GPL-3.0
  • 2 layers98 × 59.5 mmMCP23017 PCB, the printed circuit board in 3D

    MCP23017 PCB

    hackclub/OnBoard

    💡 Join 1,000 teenagers and make your first PCB with a $100 grant!

    26 parts915
  • 2 layers142.9 × 98.5 mmCantor MX, the printed circuit board in 3D

    Cantor MX

    diepala/cantor

    Cantor keyboard, a 42 key diodeless split keyboard.

    26 parts756GPL-3.0