diff --git a/frontend/public/components-metadata.json b/frontend/public/components-metadata.json
index b58b52d5..56fae136 100644
--- a/frontend/public/components-metadata.json
+++ b/frontend/public/components-metadata.json
@@ -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": "",
+ "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": "",
- "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",
diff --git a/frontend/src/data/examples.ts b/frontend/src/data/examples.ts
index 9ac4014d..6742ff1e 100644
--- a/frontend/src/data/examples.ts
+++ b/frontend/src/data/examples.ts
@@ -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',
diff --git a/scripts/component-overrides.json b/scripts/component-overrides.json
index 5dfc5466..da7ca3c3 100644
--- a/scripts/component-overrides.json
+++ b/scripts/component-overrides.json
@@ -2164,5 +2164,12 @@
"inductor": {
"name": "Inductor (custom)",
"thumbnail": ""
+ },
+ "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": ""
}
}
diff --git a/scripts/generate-component-metadata.ts b/scripts/generate-component-metadata.ts
index 4c6b2c6a..4a858f33 100644
--- a/scripts/generate-component-metadata.ts
+++ b/scripts/generate-component-metadata.ts
@@ -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}`);