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:
parent
6f0aa232db
commit
310271bdb2
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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>"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
Loading…
Reference in New Issue