feat(components): make KY-040 rotary encoder discoverable + add example (#104)

The KY-040 rotary encoder was already fully simulated (wokwi-ky-040 element + PartSimulationRegistry 'ky-040' driving CLK/DT quadrature and the SW button) and present in the catalog, but unfindable: named 'KY040', in the 'other' category, with no rotary/encoder search tags and a placeholder thumbnail. A user searching 'rotary encoder' got nothing (issue #104).

- generate-component-metadata.ts: let component-overrides.json patch category, description and tags on scanned wokwi parts (previously only name/thumbnail) -- the fields the picker category tab and ComponentRegistry.search() actually use. - component-overrides.json: ky-040 override -> name 'KY-040 Rotary Encoder', category 'input', rotary/encoder/knob tags, description, real encoder thumbnail SVG. - examples.ts: KY-040 + Arduino Uno example (quadrature read + SW reset). Regenerated components-metadata.json; searching rotary/encoder/knob now returns the KY-040. tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Montero Crespo 2026-05-31 12:39:19 -03:00
parent 6f0aa232db
commit 310271bdb2
4 changed files with 127 additions and 39 deletions

View File

@ -1624,6 +1624,52 @@
"8"
]
},
{
"id": "ky-040",
"tagName": "wokwi-ky-040",
"name": "KY-040 Rotary Encoder",
"category": "input",
"description": "KY-040 incremental rotary encoder with push-button. Turning the knob outputs quadrature pulses on CLK and DT (direction = which leads); the shaft press drives an active-low SW. Reads from any board's GPIO.",
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#1a2332\" rx=\"4\"/>\n <rect x=\"12\" y=\"38\" width=\"40\" height=\"18\" rx=\"2\" fill=\"#1f6f43\" stroke=\"#2d8f5a\" stroke-width=\"0.6\"/>\n <rect x=\"17\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <rect x=\"24\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <rect x=\"31\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <rect x=\"38\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <rect x=\"45\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <circle cx=\"32\" cy=\"24\" r=\"15\" fill=\"#9aa3ad\" stroke=\"#6b7280\" stroke-width=\"1\"/>\n <circle cx=\"32\" cy=\"24\" r=\"10\" fill=\"#c7cdd4\"/>\n <rect x=\"31\" y=\"11\" width=\"2\" height=\"8\" rx=\"1\" fill=\"#475569\"/>\n <path d=\"M 45 14 A 16 16 0 0 1 49 27\" fill=\"none\" stroke=\"#4ade80\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <path d=\"M 49 27 l -4 -1 l 2 -4 z\" fill=\"#4ade80\"/></svg>",
"properties": [
{
"name": "angle",
"type": "string",
"defaultValue": 0,
"control": "text"
},
{
"name": "stepSize",
"type": "string",
"defaultValue": 18,
"control": "text"
},
{
"name": "pressed",
"type": "string",
"defaultValue": false,
"control": "text"
}
],
"defaultValues": {
"angle": 0,
"stepSize": 18,
"pressed": false
},
"pinCount": 0,
"tags": [
"rotary",
"encoder",
"rotary encoder",
"rotary-encoder",
"ky-040",
"ky040",
"knob",
"quadrature",
"dial",
"input"
]
},
{
"id": "membrane-keypad",
"tagName": "wokwi-membrane-keypad",
@ -2742,45 +2788,6 @@
"dc5"
]
},
{
"id": "ky-040",
"tagName": "wokwi-ky-040",
"name": "KY040",
"category": "other",
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" fill=\"#e0e0e0\" rx=\"4\"/>\n <text x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" font-size=\"10\" fill=\"#666\">\n KY-040\n </text>\n </svg>",
"properties": [
{
"name": "angle",
"type": "string",
"defaultValue": 0,
"control": "text"
},
{
"name": "stepSize",
"type": "string",
"defaultValue": 18,
"control": "text"
},
{
"name": "pressed",
"type": "string",
"defaultValue": false,
"control": "text"
}
],
"defaultValues": {
"angle": 0,
"stepSize": 18,
"pressed": false
},
"pinCount": 0,
"tags": [
"ky-040",
"ky040",
"ky",
"040"
]
},
{
"id": "led-ring",
"tagName": "wokwi-led-ring",

View File

@ -84,6 +84,65 @@ export interface ExampleProject {
}
const legacyExamples: ExampleProject[] = [
{
id: 'ky-040-rotary-encoder',
title: 'KY-040 Rotary Encoder',
description:
'Read a KY-040 rotary encoder with an Arduino Uno. Turn the knob to move a counter (CLK/DT quadrature) and press the shaft (SW) to reset it. Open the Serial Monitor to watch the position.',
category: 'basics',
difficulty: 'beginner',
boardType: 'arduino-uno',
boardFilter: 'arduino-uno',
code: `// KY-040 rotary encoder — turn the knob to move the counter,
// press the shaft (SW) to reset. The encoder drives CLK/DT in quadrature;
// we sample DT on each CLK rising edge to get the direction.
const int PIN_CLK = 2; // CLK (A)
const int PIN_DT = 3; // DT (B)
const int PIN_SW = 4; // SW (push button, active LOW)
int counter = 0;
int lastClk = HIGH;
void setup() {
pinMode(PIN_CLK, INPUT_PULLUP);
pinMode(PIN_DT, INPUT_PULLUP);
pinMode(PIN_SW, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("KY-040 rotary encoder ready");
lastClk = digitalRead(PIN_CLK);
}
void loop() {
int clk = digitalRead(PIN_CLK);
if (clk != lastClk && clk == HIGH) { // CLK rising edge
if (digitalRead(PIN_DT) == LOW) {
counter++; // DT LOW -> clockwise
} else {
counter--; // DT HIGH -> counter-clockwise
}
Serial.print("position: ");
Serial.println(counter);
}
lastClk = clk;
if (digitalRead(PIN_SW) == LOW) {
counter = 0;
Serial.println("button pressed -> reset");
delay(200); // simple debounce
}
}`,
components: [
{ type: 'wokwi-ky-040', id: 'enc1', x: 360, y: 90, properties: {} },
],
wires: [
{ id: 'w-enc-clk', start: { componentId: 'enc1', pinName: 'CLK' }, end: { componentId: 'arduino-uno', pinName: '2' }, color: '#f59e0b' },
{ id: 'w-enc-dt', start: { componentId: 'enc1', pinName: 'DT' }, end: { componentId: 'arduino-uno', pinName: '3' }, color: '#10b981' },
{ id: 'w-enc-sw', start: { componentId: 'enc1', pinName: 'SW' }, end: { componentId: 'arduino-uno', pinName: '4' }, color: '#3b82f6' },
{ id: 'w-enc-vcc', start: { componentId: 'enc1', pinName: 'VCC' }, end: { componentId: 'arduino-uno', pinName: '5V' }, color: '#ef4444' },
{ id: 'w-enc-gnd', start: { componentId: 'enc1', pinName: 'GND' }, end: { componentId: 'arduino-uno', pinName: 'GND' }, color: '#1f2937' },
],
tags: ['rotary', 'encoder', 'ky-040', 'input', 'knob', 'arduino'],
},
{
id: 'stm32-bluepill-blink',
title: 'STM32 Blue Pill Blink',

View File

@ -2164,5 +2164,12 @@
"inductor": {
"name": "Inductor (custom)",
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#1a2332\" rx=\"4\"/>\n <rect x=\"6\" y=\"29\" width=\"52\" height=\"3\" fill=\"#aaa\"/>\n <path d=\"M 12 30 Q 16 18 20 30 Q 24 18 28 30 Q 32 18 36 30 Q 40 18 44 30 Q 48 18 52 30\"\n fill=\"none\" stroke=\"#B23820\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <text x=\"32\" y=\"56\" text-anchor=\"middle\" font-size=\"10\" font-family=\"sans-serif\" fill=\"#e5e7eb\" font-weight=\"bold\">?mH</text></svg>"
},
"ky-040": {
"name": "KY-040 Rotary Encoder",
"category": "input",
"description": "KY-040 incremental rotary encoder with push-button. Turning the knob outputs quadrature pulses on CLK and DT (direction = which leads); the shaft press drives an active-low SW. Reads from any board's GPIO.",
"tags": ["rotary", "encoder", "rotary encoder", "rotary-encoder", "ky-040", "ky040", "knob", "quadrature", "dial", "input"],
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#1a2332\" rx=\"4\"/>\n <rect x=\"12\" y=\"38\" width=\"40\" height=\"18\" rx=\"2\" fill=\"#1f6f43\" stroke=\"#2d8f5a\" stroke-width=\"0.6\"/>\n <rect x=\"17\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <rect x=\"24\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <rect x=\"31\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <rect x=\"38\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <rect x=\"45\" y=\"54\" width=\"2.5\" height=\"6\" fill=\"#d8b24a\"/>\n <circle cx=\"32\" cy=\"24\" r=\"15\" fill=\"#9aa3ad\" stroke=\"#6b7280\" stroke-width=\"1\"/>\n <circle cx=\"32\" cy=\"24\" r=\"10\" fill=\"#c7cdd4\"/>\n <rect x=\"31\" y=\"11\" width=\"2\" height=\"8\" rx=\"1\" fill=\"#475569\"/>\n <path d=\"M 45 14 A 16 16 0 0 1 49 27\" fill=\"none\" stroke=\"#4ade80\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <path d=\"M 49 27 l -4 -1 l 2 -4 z\" fill=\"#4ade80\"/></svg>"
}
}

View File

@ -252,6 +252,21 @@ class MetadataGenerator {
if (typeof ov.thumbnail === 'string') {
comp.thumbnail = ov.thumbnail;
}
// Discoverability fixes for scanned wokwi parts. Auto-generated tags and
// the default 'other' category use the original class name, so a part
// like "KY040" stays unfindable when a user searches "rotary"/"encoder"
// and sits in the wrong picker tab. Let an override patch the fields that
// ComponentRegistry.search() (name/id/description/tags) and the category
// tab actually use.
if (typeof ov.category === 'string') {
comp.category = ov.category;
}
if (typeof ov.description === 'string') {
comp.description = ov.description;
}
if (Array.isArray(ov.tags)) {
comp.tags = ov.tags;
}
applied++;
console.log(` 🔧 Applied overrides for ${comp.id}`);