michalzaniewicz/esphome-guition-jc3636k718c-va/guition-jc3636k718c

Claude Code and agent skill by michalzaniewicz/esphome-guition-jc3636k718c-va

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Reference 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).

Install

npx vskill@latest install michalzaniewicz/esphome-guition-jc3636k718c-va/guition-jc3636k718c

SKILL.md

Guition JC3636K718C - ESPHome working notes

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 full init_sequence from base/core.yaml (ends with 3A=55 for RGB565, 21 invert, 11 sleep-out, 120 ms delay, 29 display-on). The native ST77916 model was dev-only; CUSTOM + manual init runs on stable ESPHome. (qspi_dbi was the platform before v2.2.9; it's deprecated upstream, no predefined ST77916 model exists on mipi_spi either.)

  • SPI: type: quad, clk_pin: 11, data_pins: [13,14,15,16]. **CS/RST go on the

display: block** (cs_pin: 12, reset_pin: GPIO17), not on spi:.

  • Touch: touchscreen: platform: cst816, interrupt_pin: 7, reset_pin: 8,

on the i2c bus sda: 9 / scl: 10.

  • Backlight: output: ledc on GPIO21 → 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 → a switch: gpio with restore_mode: ALWAYS_ON.

  • LED ring: light: esp32_rmt_led_strip, pin: GPIO0, num_leds: 13,

channel_colors: GRB (ESPHome 2026.8.0+; older uses rgb_order: GRB), chipset: WS2812, rmt_symbols: 64.

  • Knob: two binary_sensor: gpio (GPIO2 and GPIO1), inverted: true + pullup,

act on on_press. See gotcha.

  • esp32: flash_size: 16MB, partitions: partitions.csv, framework esp-idf.

psram: mode: octal, speed: 80MHz.

Gotchas / lessons learned (the important part)

  1. The knob is NOT a quadrature encoder. A PCNT rotary_encoder reads zero.

Direction is encoded by which pin pulses: left → GPIO2, right → GPIO1, one clean LOW pulse per detent. Read two independent binary_sensor on_press instead. The knob can't be pressed (GPIO0 is BOOT) - trigger actions with a screen tap.

  1. 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 extended pin: {number: ..., ignore_strapping_warning: true} form, not the short pin: GPIOxx) - the config uses it on all four since 2.2.9, so the warnings no longer print. Separately, silence gfonts "missing glyphs" warnings with ignore_missing_glyphs: true (GF_Latin_Core lists combining accents the static Roboto face lacks), or list exact glyphs: for pixel/icon fonts.

  1. 16 MB partitions → first flash MUST be over USB. partitions.csv uses big app

slots (~7.9 MB). A partition-table change can't be applied via OTA; flash once over USB, then OTA works.

  1. 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.
  1. 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 the font: glyphs: list or it renders as tofu.

  • \n inside 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.
  1. Album art (`online_image`) decodes fine but the LVGL widget **won't redraw by

itself** - you must call lvgl.image.update in on_download_finished. Use http_request: verify_ssl: false for self-signed HA. Skip reloading the same URL to cut flicker. Cover URL comes from the player entity's entity_picture.

  1. Fonts, icons and game sprites are fetched at compile time (Google Fonts +

MaterialDesign TTF, plus the PNGs under assets/sprites/cool-cars/ and space-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.

  1. 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".

  1. Voice timers: use voice_assistant: `on_timer_started/updated/finished/

cancelled. There is **no on_timer_tick** - do smooth local countdown in an interval and let HA fire finished`.

  1. Wake word (micro_wake_word) only runs while connected to HA. Start/stop it in

on_client_connected/on_client_disconnected and around the VA lifecycle.

  1. 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.

  1. Timezone resets to UTC at runtime. The homeassistant time platform syncs the epoch

but not the zone, so set timezone: 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 via getenv("TZ") into a global, then re-apply it every second with setenv("TZ", g.c_str(), 1); tzset();. Note RealTimeClock has set_timezone() but no `get_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.

  1. 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 a safe_mode: block and call safe_mode.mark_successful once the device is clearly up (this repo does it when the boot splash ends), and/or lower boot_is_good_after.

  1. `${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 ${...}.

  1. 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) or esp32_ble_tracker (beacons / BLE sensors), optionally esp32_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 with micro_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_proxy raises 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.yaml is the always-on core (clock, control tiles, settings,

LED ring, voice). Optional screens are separate packages under base/screens/*.yaml, pulled via a remote packages: block in the thin user config (guition-va.yaml). A screen self-registers into the carousel (g_present[id]) and declares its own settings (the g_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_mode drives page_mode; gestures are classified in

touchscreen.on_release (swipe/tap/hold) and routed by a snapshot g_mode0.

  • A base carousel g_base (clock / player / timer / games / weather / thermostat /

sensors / demo, in screen_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 by ring_update with 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.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
168
Repository forks
19
7-day trend score
25

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/michalzaniewicz/esphome-guition-jc3636k718c-va/guition-jc3636k718c/badge)](https://verified-skill.com/skills/michalzaniewicz/esphome-guition-jc3636k718c-va/guition-jc3636k718c)

Something wrong with this skill or its page? Tell us.