Guition JC3636K718C - ESPHome working notes
SkillFiles & storageReference for building/editing ESPHome configs on the Guition JC3636K718C round knob display (ESP32-S3, 1.8" 360x360 ST77916, CST816 touch, rotary knob, PCM5100A DAC, PDM mic, WS2812 LED ring). Use whenever working on this board (or the base/core.yaml in this repo): correct pinout, ESPHome component choices, and the hard-won gotchas (knob isn't quadrature, GPIO0 ring strapping, 16 MB partitions need USB flash, LVGL performance limits, lambda/string pitfalls, battery is heuristic).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Guition JC3636K718C - ESPHome working notes skill
What this skill tells your AI
The instructions your AI receives, as published by michalzaniewicz/esphome-guition-jc3636k718c-va in skill/guition-jc3636k718c/SKILL.md and read by ahel’s review.
This is the K / Knob series board. Its pinout is different from the JC3636W518
- do NOT reuse W518 pins (display, backlight, touch all differ). The values below come
from the official manufacturer demo
JC3636K718_knob_EN(pincfg.h+scr_st77916.h) and from making this device actually work.
Board
- ESP32-S3 (240 MHz), 8 MB octal PSRAM @ 80 MHz, 16 MB flash.
- 1.8" round 360×360, driver ST77916 (QSPI), RGB565, colors inverted.
- Touch CST816 (I²C). Rotary knob (rotation only - not pressable).
- Audio out PCM5100A I²S DAC + speaker. Mic = I²S PDM.
- WS2812 LED ring, 13 LEDs, GRB.
- Radios: Wi-Fi 2.4 GHz b/g/n + Bluetooth LE 5.0 (ESP32-S3 has no Bluetooth Classic / BR-EDR → no A2DP; the speaker/mic are I²S, not Bluetooth audio). One shared 2.4 GHz antenna for both.
- microSD (SD_MMC). USB-C; some units have Li-ion + wireless charging.
Pinout (authoritative)
Display QSPI (ST77916): SCK=11 CS=12 D0=13 D1=14 D2=15 D3=16 RST=17 BLK=21(PWM)
Touch CST816 (I2C): SDA=9 SCL=10 INT=7 RST=8 (internal SDA/SCL pull-ups)
Knob: A=GPIO2 B=GPIO1 (see gotcha - NOT quadrature)
DAC PCM5100A (I2S): BCK=3 WS/LRCK=45 DO=42 MUTE=46 (LOW=mute, hold HIGH) no MCK
Microphone (I2S PDM): SD/data=4 SCK/clk=5
LED ring WS2812: DATA=0 (13 LEDs, GRB) -- GPIO0 = BOOT/strapping
Battery ADC: GPIO6 (~10k/10k divider -> x2)
microSD (SD_MMC): CLK=39 CMD=38 D0=40 D1=41 D2=48 D3=47
ESPHome component choices that work
- Display:
display: platform: mipi_spi,model: CUSTOM,bus_mode: quad,data_rate: 80MHz,invert_colors: true,color_order: rgb, with the fullinit_sequencefrombase/core.yaml(ends with3A=55for RGB565,21invert,11sleep-out, 120 ms delay,29display-on). The native ST77916 model was dev-only; CUSTOM + manual init runs on stable ESPHome. (qspi_dbiwas the platform before v2.2.9; it's deprecated upstream, no predefined ST77916 model exists onmipi_spieither.) - SPI:
type: quad,clk_pin: 11,data_pins: [13,14,15,16]. CS/RST go on thedisplay:block (cs_pin: 12,reset_pin: GPIO17), not onspi:. - Touch:
touchscreen: platform: cst816,interrupt_pin: 7,reset_pin: 8, on thei2cbussda: 9 / scl: 10. - Backlight:
output: ledconGPIO21→light: monochromatic. - Audio:
i2s_audio(out: bclk=3, lrclk=45; in: lrclk=5),speaker: i2s_audio(dout=42,dac_type: external),microphone: i2s_audio(din=4,pdm: true, 16 kHz). DAC MUTE on GPIO46 must be held HIGH → aswitch: gpiowithrestore_mode: ALWAYS_ON. - LED ring:
light: esp32_rmt_led_strip,pin: GPIO0,num_leds: 13,rgb_order: GRB,chipset: WS2812,rmt_symbols: 64. - Knob: two
binary_sensor: gpio(GPIO2 and GPIO1),inverted: true+pullup, act onon_press. See gotcha. - esp32:
flash_size: 16MB,partitions: partitions.csv, frameworkesp-idf.psram: mode: octal, speed: 80MHz.
Gotchas / lessons learned (the important part)
-
The knob is NOT a quadrature encoder. A PCNT
rotary_encoderreads zero. Direction is encoded by which pin pulses: left → GPIO2, right → GPIO1, one clean LOW pulse per detent. Read two independentbinary_sensoron_pressinstead. The knob can't be pressed (GPIO0 is BOOT) - trigger actions with a screen tap. -
Strapping-pin warnings are expected and harmless. GPIO0 (WS2812 ring), GPIO3 (DAC BCK), GPIO45 (DAC WS/LRCK) and GPIO46 (DAC MUTE) are all ESP32-S3 strapping pins used per the vendor pinout - they work fine after boot. There IS a per-pin suppress (
ignore_strapping_warning: true, needs the extendedpin: {number: ..., ignore_strapping_warning: true}form, not the shortpin: GPIOxx) - the config uses it on all four since 2.2.9, so the warnings no longer print. Separately, silence gfonts "missing glyphs" warnings withignore_missing_glyphs: true(GF_Latin_Core lists combining accents the static Roboto face lacks), or list exactglyphs:for pixel/icon fonts. -
16 MB partitions → first flash MUST be over USB.
partitions.csvuses big app slots (~7.9 MB). A partition-table change can't be applied via OTA; flash once over USB, then OTA works. -
LVGL performance is tight on this S3 + QSPI panel.
- Keep
lvgl: buffer_size: 24%(puts the draw buffer in fast internal RAM instead of slow PSRAM at the default 100%). - Keep
display: data_rate: 80MHz. - A rotating full-screen HUD image and an animated radial equalizer were both tried and removed as too laggy. Add heavy full-screen redraws only very carefully.
logger: level: INFO- DEBUG is very noisy during voice and adds overhead.
- Keep
-
Lambda string/symbol pitfalls (LVGL labels):
- For MDI/Unicode glyphs in C++ lambdas use the actual UTF-8 bytes or
chr(0xF....)style - declare each glyph in thefont:glyphs:list or it renders as tofu. \ninside a lambda works inline ('...\n...'); inside a block scalar (|-) a literal newline breaks YAML. Keep multi-line label text on one quoted line.- Montserrat has no
-; guard "no data yet" cases with an empty string, not a dash.
- For MDI/Unicode glyphs in C++ lambdas use the actual UTF-8 bytes or
-
Album art (
online_image) decodes fine but the LVGL widget won't redraw by itself - you must calllvgl.image.updateinon_download_finished. Usehttp_request: verify_ssl: falsefor self-signed HA. Skip reloading the same URL to cut flicker. Cover URL comes from the player entity'sentity_picture. -
Fonts, icons and game sprites are fetched at compile time (Google Fonts + MaterialDesign TTF, plus the PNGs under
assets/sprites/cool-cars/andspace-wars/from the GitHub repo), so the build host needs internet. They are baked into the firmware, so at runtime nothing depends on any server. -
Battery is a heuristic. ADC on GPIO6 (×2), 64-sample oversampling + moving average to fight ETA6003 ripple (no cap on BAT_ADC). Voltage→% is a calibrated lookup (
vt[]/st[]) - start ~4.10 V, brownout ~3.20 V - not finished, tune per unit. No STAT pin, so charging detection is "voltage rising or ≥4.13 V". -
Voice timers: use
voice_assistant:on_timer_started/updated/finished/ cancelled. There is noon_timer_tick- do smooth local countdown in anintervaland let HA firefinished. -
Wake word (
micro_wake_word) only runs while connected to HA. Start/stop it inon_client_connected/on_client_disconnectedand around the VA lifecycle. -
Debugging: API has no encryption here → stream logs with
python scripts/esplog.py <seconds>(set the device host in the script). Don't reflash blind - check logs / HA state first. -
Timezone resets to UTC at runtime. The
homeassistanttime platform syncs the epoch but not the zone, so settimezone:explicitly (IANA, e.g.Europe/Warsaw). Worse, ESPHome can reset the device TZ back to UTC mid-run (on a menu restart, or when a log/API client reconnects), so the clock reads UTC until the next sync. Self-heal: capture the resolved POSIX zone at boot viagetenv("TZ")into a global, then re-apply it every second withsetenv("TZ", g.c_str(), 1); tzset();. NoteRealTimeClockhasset_timezone()but noget_timezone(), and passing the IANA name at runtime won't work (newlib needs the POSIX string) - which is why you read it back from the env. -
OTA rollback reverts to the OLD firmware after a quick restart. On esp-idf the bootloader keeps a freshly-OTA'd image only once the boot is marked "good" (
safe_mode, default ~1 min). Restarting (e.g. from the on-screen menu) before that rolls back to the previous build - which looks exactly like "my changes didn't stick". Fix: add asafe_mode:block and callsafe_mode.mark_successfulonce the device is clearly up (this repo does it when the boot splash ends), and/or lowerboot_is_good_after. -
${substitution}inside a flow-mapping{ }breaks the YAML parse. The}that closes${...}is read as the end of the flow map, e.g.image: { id: x, src: avatar${n} }fails with "expected ',' or '}'". Use block style for any line whose value contains a${...}. -
Bluetooth is BLE-only and competes hard with voice/LVGL. The S3 has Bluetooth LE 5.0 and no Classic - so no Bluetooth audio (A2DP needs Classic; playback stays on the I²S DAC). What ESPHome can do here is BLE:
bluetooth_proxy(relay BLE devices to Home Assistant) oresp32_ble_tracker(beacons / BLE sensors), optionallyesp32_improv(BLE Wi-Fi provisioning). It is not enabled in this repo, and it is not free: the BLE stack + Wi-Fi/BLE coexistence share the single 2.4 GHz radio and CPU withmicro_wake_word, the voice pipeline and the QSPI/LVGL redraws, and the BLE stack wants internal RAM that is already squeezed (LVGL draw buffer at 24% internal). Expect audio/display hitches, higher heap pressure and possible OOM/brownouts. If you add it: prefer passive scanning, keep active connections to a minimum (bluetooth_proxyraises the BLE connection slots), test heap headroom (logger+ free-heap sensor), and consider dropping heavy screens. Treat it as untested on this exact build - validate on-device before relying on it.
Architecture of this repo (for edits)
- Modular.
base/core.yamlis the always-on core (clock, control tiles, settings, LED ring, voice). Optional screens are separate packages underbase/screens/*.yaml, pulled via a remotepackages:block in the thin user config (guition-va.yaml). A screen self-registers into the carousel (g_present[id]) and declares its own settings (theg_wgrp_*/g_wopt_*Widgets registry), so adding one does not touch the core. - UI is LVGL pages (not a top_layer overlay for modes):
page_boot(startup splash),page_home,page_player,page_mode(menu/timer/alarm/settings sub-screens),page_settings,page_timer,page_controls, plus one page per optional screen. - A mode state machine in
g_modedrivespage_mode; gestures are classified intouchscreen.on_release(swipe/tap/hold) and routed by a snapshotg_mode0. - A base carousel
g_base(clock / player / timer / games / weather / thermostat / sensors / demo, inscreen_order) on horizontal swipes; the knob drives per-screen actions when a screen captures it (g_knob_capture). - The LED ring is one HA light + reaction effects (
Listen/Think/Speak/Volume/Alarm/Timer) chosen byring_updatewith priority alarm > assistant > volume > timer > HA, snapshotting/restoring HA state. - Per-user things to change live in
substitutions:(HA URL, entity IDs) and a few hard-coded control-tile entities - see the Configuration wiki page.
When editing, change UI strings/labels only (not IDs or pin numbers), keep the performance settings above, and validate the YAML before flashing.
Signals
- GitHub stars
- 162
- Forks
- 17
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
guition-jc3636k718c- Source
- github.com/michalzaniewicz/esphome-guition-jc3636k718c-va