
Smart Cabinet Fan
ginomcfino/smart-fan-controller
Dual-fan controller with temperature sensing, tachometer monitoring, overtemperature alarm, and manual override. Designed in KiCad.

ESP32-based CAN-bus companion for Renault Megane 2 infotainment displays.
Meganecan gw is an open-source printed circuit board by Barbedx, from the Barbedx/MeganeCAN repository on GitHub. It is a 2-layer board of 100 × 80 mm with 68 parts on 76 nets, routed with 573 tracks and 379 vias.
It is built around the SN65HVD230D, LM2596S-3.3 and ESP32-WROVER-IB, with the NUP2105L, 1812L110/16V and SMBJ24A among its other parts. Its parts include 18 capacitors, 16 resistors, 8 diodes, 7 connectors and 5 ICs. It belongs with the ESP32 and RISC-V boards of this library.
ESP32-based CAN-bus companion for Renault Megane 2 infotainment displays.
Connects an ESP32 to the car CAN bus and acts as a smart sidecar: renders custom menus and text on the OEM display, bridges phone media controls via Bluetooth (Apple Media Service or BLE Keyboard), exposes a web UI for live configuration, and supports OTA firmware updates. Optional ELM327/OBD diagnostics via WiFi.
🇺🇦 When you use this project, you automatically support Ukraine.
⚠️ Safety: hobby project for off-road / testing use. Do not interact with menus while driving.
| Category | Details |
|---|---|
| CAN bus | 500 Kbps, GPIO 3 (RX) / GPIO 4 (TX). Listens to OEM frames and replies to sync/ack. |
| Display | Emulates the head-unit radio over ISO-TP-style CAN to drive the OEM display. Two radio protocols: Carminat (large monochrome display only) and UpdateList (both the monochrome updatelist_menu and the 8-segment updatelist). |
| Bluetooth | AMS mode: advertises as a BLE peripheral — the iPhone pairs from Settings → Bluetooth, then the ESP reads now-playing (AMS), notifications (ANCS) and clock (CTS) as a GATT client over that link. Keyboard mode: BLE HID peripheral. |
| Web UI | PsychicHttp async server on port 80. Configure display type, BT mode, text, time. |
| OTA | ElegantOTA at /update. |
| Diagnostics | ELM327 over WiFi STA (V-LINK adapter). Queries ECUs and exposes JSON at /api/live. |
Minimum:
Do not add a second 120 Ω terminator if the bus is already terminated.
Pinout (ESP32-C3):
| Signal | GPIO |
|---|---|
| CAN RX | 3 |
| CAN TX | 4 |
IDisplay (pure virtual interface)
│ tick(), recv(), processEvents()
│ setText(), setState(), setTime()
│ showMenu(), ProcessKey(), onKeyPressed()
│
└── AffaDisplayBase (abstract — ISO-TP CAN send engine)
│ affa3_send() / affa3_do_send()
│ funcs[] array (registered CAN function IDs + ack state)
│ _sync_status (FAILED | PEER_ALIVE | START | FUNCSREG)
│ setMediaInfo() — virtual no-op, overridden by subclasses
│ tickMedia() — virtual no-op, overridden by subclasses
│ setKeyHandler() — installs the key callback
│
├── Affa2Base (Affa2 CAN protocol — basic text, fallback default)
│ funcs: { 0x121 SETTEXT, 0x1B1 DISPLAY_CTRL }
│ sync ping: 0x3DF filler: 0x81
│ │
│ ├── Affa2Display (8-segment — adds media title scroll)
│ │ + setMediaInfo() builds "Artist - Title" string
│ │ + tickMedia() scrolls 8-char window every 400 ms
│ │
│ └── Affa2MenuDisplay (full-LED — stub, same CAN protocol)
│
└── Affa3NavDisplay (Affa3Nav "Carminat" — full nav display) ┌──────────┐ CAN frame ┌──────────────────┐
│ CAN bus │ ────────────► │ gotFrame() │
└──────────┘ │ → display->recv() │
└──────────────────┘
┌───────────────┐ MediaInformation ┌─────────────────────────┐
│ BLE / AMS │ ─────────────────► │ onDataUpdateCallback() │
│ (iPhone) │ │ → display->setMediaInfo()│
└───────────────┘ └─────────────────────────┘
loop()
├── serialCommands.ReadSerial()
├── ElegantOTA.loop()
├── Bluetooth::Service() (AMS mode)
├── display->tickMedia() (non-blocking scroll)
├── auto-time sync via CTS (AMS mode, once per connection)
├── display->processEvents()
└── elmManager->tick() (OBD, when WiFi connected)| Physical key | AMS action | BLE Keyboard HID |
|---|---|---|
| Pause | TogglePlayPause | KEY_MEDIA_PLAY_PAUSE |
| RollUp | NextTrack | KEY_MEDIA_NEXT_TRACK |
| RollDown | PreviousTrack | KEY_MEDIA_PREVIOUS_TRACK |
| VolumeUp (hold) | VolumeUp ×15 | KEY_MEDIA_VOLUME_UP |
| Constant | ID | Direction | Purpose |
|---|---|---|---|
PACKET_ID_SYNC | 0x3DF | ESP32 → display | Alive ping |
PACKET_ID_SYNC_REPLY | 0x3CF | display → ESP32 | Sync ack |
PACKET_ID_SETTEXT | 0x121 | ESP32 → display | Set text |
PACKET_ID_DISPLAY_CTRL | 0x1B1 | ESP32 → display | Enable (0x02) / Disable (0x00) |
PACKET_ID_KEYPRESSED | 0x0A9 | display → ESP32 | Key press event |
PACKET_REPLY_FLAG | 0x400 | — | OR-mask on ID for ack frames |
PACKET_FILLER | 0x81 | — | Padding byte |
| Constant | ID | Direction | Purpose |
|---|---|---|---|
PACKET_ID_SYNC | 0x3AF | ESP32 → display | Alive ping |
PACKET_ID_SYNC_REPLY | 0x3CF | display → ESP32 | Sync ack |
PACKET_ID_SETTEXT / DISPLAY_CTRL | 0x151 | ESP32 → display | Text + enable/disable (same ID, different payload) |
PACKET_ID_NAV | 0x1F1 | ESP32 → display | Menu / nav data |
PACKET_ID_KEYPRESSED | 0x1C1 | display → ESP32 | Key press event |
PACKET_FILLER | 0x00 | — | Padding byte |
Scroll lock indicator byte (Affa3Nav showMenu):
| Value | Meaning |
|---|---|
0x00 | No scroll arrows |
0x07 | Scroll up arrow |
0x0B | Scroll down arrow |
0x0C | Both arrows |
Selected via NVS key config/display_type. Takes effect after restart.
| NVS value | Class | Notes |
|---|---|---|
carminat (default) | CarminatDisplay | Carminat protocol → large monochrome display: full media screen + interactive menu + notification popups |
updatelist_menu | UpdateListMenuDisplay | UpdateList protocol → monochrome LCD (menu); extends UpdateListDisplay |
updatelist | UpdateListDisplay | UpdateList protocol → 8-segment display; scrolls "Artist - Title" (8-char window, 400 ms/step) via AMS |
| (other / unset) | UpdateListBase | UpdateList protocol base — fallback |
Note: older docs/sections below may still use the pre-rename class names (
Affa2*/Affa3Nav*) and values (affa2/affa3). The authoritative model is the matrix above andCLAUDE.md.
| Namespace | Key | Type | Default | Values |
|---|---|---|---|---|
config | display_type | String | affa3nav | affa2 / affa2menu / affa3 / affa3nav |
config | bt_mode | String | ams | ams / keyboard |
config | auto_time | Bool | true | Sync display clock from phone via CTS |
display | autoRestore | Bool | false | Restore last text on boot |
display | lastText | String | — | Last text shown (restored on boot) |
display | welcomeText | String | — | Scroll on boot (overrides default) |
# Build
pio run
# Flash over USB
pio run -t upload
# Serial monitor (115200 baud)
pio device monitor
# Build + flash in one step
pio run -t upload && pio device monitorTarget: esp32-c3-devkitm-1 (see platformio.ini).
Edit src/secrets.h (not tracked by git):
#define Soft_AP_WIFI_SSID "MeganeCAN"
#define Soft_AP_WIFI_PASS "yourpassword"The file is ignored via:
git update-index --assume-unchanged src/secrets.hhttp://<device-ip>/)| Route | Method | Purpose |
|---|---|---|
/ | GET | Main configuration page |
/setDisplay | POST | Set display type, restarts ESP32 |
/setbtmode | POST | Set BT mode + auto_time, restarts ESP32 |
/config/restore | GET | Read (?enable absent) or write (?enable=1/0) auto-restore flag |
/static | GET | Show static text (?text=, ?save to persist) |
/scroll | GET | Scroll text (?text=, ?save to save as welcome) |
/settime | GET | Set display clock (?time=HHMM) |
/setVoltage | GET | Debug voltage override |
/getdisplaytype | GET | Returns current NVS display_type string |
/getbtmode | GET | Returns current NVS bt_mode string |
/getautotime | GET | Returns 1 or 0 |
/getlasttext | GET | Returns last saved text |
/getwelcometext | GET | Returns saved welcome text |
/api/live | GET | ELM327 diagnostic snapshot JSON |
/affa3/setMenu | GET | Send menu frame (?caption=, ?name1=, ?name2=, ?scrollLock=) |
/affa3test | GET | Affa3 display test page |
/emulate | GET | Button emulation page |
/emulate/key | POST | Inject a key press (?key=, ?hold=) |
/update | GET | ElegantOTA firmware upload |
| Command | Example | Effect |
|---|---|---|
e | e | Enable display |
d | d | Disable display |
st <HHMM> | st 1430 | Set display clock |
msr <text> [delay_ms] | msr HELLO 300 | Scroll text right |
msl <text> [delay_ms] | msl MEGANE 250 | Scroll text left |
Note: ELM327 connection is currently disabled in firmware (
connectToElm()is commented out). The infrastructure is complete but needs WiFi STA / BLE coexistence tuning before re-enabling.
WiFi STA connects to V-LINK (open network, TCP 192.168.0.10:35000). Cycles through PID plans querying:
| CAN header | ECU |
|---|---|
7E0 | Engine |
743 | Gearbox / transmission |
744 | HVAC |
745 | Additional |
74D | Gearbox / alternator |
Results exposed at GET /api/live as JSON. To add a PID plan: create PidPlan_XXX.h and register it in buildCombinedPlan() inside MyELMManager.h.
src/
├── main.cpp — setup(), loop(), HandleKey(), initDisplay()
├── apple_media_service.cpp/.h — BLE AMS client (NimBLE)
├── bluetooth.cpp/.h — BLE scan + connect + CTS
├── current_time_service.cpp/.h — BLE Current Time Service (clock sync)
├── secrets.h — WiFi credentials (not tracked)
│
├── display/
│ ├── IDisplay.h — pure virtual display interface
│ ├── AffaDisplayBase.cpp/.h — ISO-TP CAN engine, sync state machine
│ ├── AffaCommonConstants.h — shared enums (AffaKey, AffaError, SyncStatus…)
│ │
│ ├── Affa2/
│ │ ├── Affa2Constants.h — Affa2 CAN packet IDs + namespace
│ │ ├── Affa2Base.cpp/.h — Affa2 protocol implementation (base class)
│ │ ├── Affa2Display.h/.cpp — 8-segment variant, Spotify title scroll
│ │ └── Affa2MenuDisplay.h — full-LED variant stub
│ │
│ └── Affa3Nav/
│ ├── Affa3NavConstants.h
│ ├── Affa3NavDisplay.cpp/.h — full media screen + menu system
│ ├── AuxModeTracker.cpp/.h
│ └── Menu/
│ ├── Menu.cpp/.h — hierarchical menu (Field, List, MultiField)| Qty | Value | Footprint | References |
|---|---|---|---|
| 9 | 100nF/50V X7R | Capacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolder | C2, C5, C6, C8, C10, C12, C13, C15, C18 |
| 4 | 1k | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R7, R13, R14, R15 |
| 4 | 60.4R | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R9, R10, R11, R12 |
| 4 | MountingHole_3.2mm_M3 | MountingHole:MountingHole_3.2mm_M3 | H1, H2, H3, H4 |
| 3 | 10k | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R2, R3, R4 |
| 3 | 10uF/16V X5R | Capacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolder | C4, C9, C11 |
| 2 | 0R | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R5, R6 |
| 2 | 100uF/50V | Capacitor_THT:CP_Radial_D10.0mm_P5.00mm | C1, C16 |
| 2 | NUP2105L | Package_TO_SOT_SMD:SOT-23 | D7, D8 |
| 2 | SN65HVD230 | Package_SO:SOIC-8_3.9x4.9mm_P1.27mm | U3, U4 |
| 1 | 10uF/50V X7R 1210 | Capacitor_SMD:C_1210_3225Metric_Pad1.33x2.70mm_HandSolder | C17 |
| 1 | 1812L110/16 | Fuse:Fuse_1812_4532Metric_Pad1.30x3.40mm_HandSolder | F1 |
| 1 | 220R | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R8 |
| 1 | 220uF/6.3V low-ESR | Capacitor_THT:CP_Radial_D6.3mm_P2.50mm | C3 |
| 1 | 22uF/10V X7R 1210 | Capacitor_SMD:C_1210_3225Metric_Pad1.33x2.70mm_HandSolder | C7 |
| 1 | 33uH/3A | Inductor_SMD:L_12x12mm_H8mm | L1 |
| 1 | 470R | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R16 |
| 1 | 47k | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R1 |
| 1 | 47uF/10V | Capacitor_THT:CP_Radial_D5.0mm_P2.00mm | C14 |
| 1 | BLU | LED_SMD:LED_0805_2012Metric_Pad1.15x1.40mm_HandSolder | D5 |
| 1 | BOOT | Button_Switch_THT:SW_PUSH_6mm | SW2 |
| 1 | BUCK-ALT | Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical | J7 |
| 1 | ESP32-C3 SuperMini | MeganeCAN:MODULE_ESP32-C3-SUPERMINI | U2 |
| 1 | ESP32-WROVER-IB | MeganeCAN:ESP32-WROVER-IB | U1 |
| 1 | EXP | Connector_PinHeader_2.54mm:PinHeader_1x12_P2.54mm_Vertical | J6 |
| 1 | GRN | LED_SMD:LED_0805_2012Metric_Pad1.15x1.40mm_HandSolder | D4 |
| 1 | HU | Connector_JST:JST_XH_B3B-XH-A_1x03_P2.50mm_Vertical | J4 |
| 1 | JIG | Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Vertical | J5 |
| 1 | LM2596S-3.3 | Package_TO_SOT_SMD:TO-263-5_TabPin3 | U5 |
| 1 | MMCAN | TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-3-5.08_1x03_P5.08mm_Horizontal | J3 |
| 1 | MMCAN-TERM-H | Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm | JP3 |
| 1 | MMCAN-TERM-L | Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm | JP5 |
| 1 | Mini-360 (MP2307) | MeganeCAN:MINI360_4pad_Socket | PS1 |
| 1 | PWR | TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-3-5.08_1x03_P5.08mm_Horizontal | J1 |
| 1 | RESET | Button_Switch_THT:SW_PUSH_6mm | SW1 |
| 1 | SMBJ24A | Diode_SMD:D_SMB_Handsoldering | D2 |
| 1 | SRC-SEL | Jumper:SolderJumper-3_P1.3mm_Bridged12_RoundedPad1.0x1.5mm | JP1 |
| 1 | SS34 | Diode_SMD:D_SMA-SMB_Universal_Handsoldering | D3 |
| 1 | SS36 | Diode_SMD:D_SMA-SMB_Universal_Handsoldering | D6 |
| 1 | SS56 | Diode_SMD:D_SMA-SMB_Universal_Handsoldering | D1 |
| 1 | VCAN | TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-3-5.08_1x03_P5.08mm_Horizontal | J2 |
| 1 | VCAN-TERM-H | Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm | JP2 |
| 1 | VCAN-TERM-L | Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm | JP4 |

ginomcfino/smart-fan-controller
Dual-fan controller with temperature sensing, tachometer monitoring, overtemperature alarm, and manual override. Designed in KiCad.


shreyadascoder2004/stm32-can-modbus-gateway
4-layer STM32 industrial gateway bridging RS-485/Modbus-RTU and CAN — KiCad schematic/layout + C firmware




NGOQUOC98/ESP32_MODBUS_PCB_MASTER
This project was in development phase - we will remove this remark after release

mohamed-belal-10/can-motor-controller-node
CAN-connected closed-loop DC motor controller node — KiCad hardware (STM32F103, DRV8874, SN65HVD230) with an AS5600 encoder sub-PCB
It measures 100 × 80 mm and has 2 copper layers, 1.6 mm thick.
68 parts, built around the SN65HVD230D, LM2596S-3.3, ESP32-WROVER-IB and NUP2105L. 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.
From the Barbedx/MeganeCAN repository on GitHub, at the commit this page was made from: the board is hardware/meganecan-gw/meganecan-gw.kicad_pcb and its schematic hardware/meganecan-gw/meganecan-gw.kicad_sch. PCBKit opens the board in the browser, with nothing to install.
Its repository states no licence, so its author keeps all rights: ask Barbedx before reusing the design.