Druk
Published © LGPL

KeyCraft M5

KeyCraft M5: Design key cuts for Kwikset, Schlage, Yale on M5! Easy buttons & display for beginners to learn lockpicking.

BeginnerFull instructions provided183
KeyCraft M5

Things used in this project

Hardware components

M5StickC ESP32-PICO Mini IoT Development Board
M5Stack M5StickC ESP32-PICO Mini IoT Development Board
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

schematics_MF6ED2YNCU.png

Code

KeyCraft M5

C/C++
#include <M5Unified.h>

#define MAX_PINS 7
int cutDepths[MAX_PINS] = {0};
int currentCut = 0;
int keyFormat = 0; // 0 = Kwikset KW1, 1 = Schlage SC1, 2 = Yale Y2

// Key format data (pin spacing in mm, scaled to pixels; 1mm ≈ 4 pixels)
struct KeyFormat {
  const char* name;
  int pinCount;
  int maxDepth; // Max bitting depth (0 to maxDepth)
  float pinSpacingMm; // Pin spacing in mm
};
KeyFormat keyFormats[] = {
  {"Kwikset KW1", 7, 6, 3.9}, // 3.9mm pin spacing
  {"Schlage SC1", 7, 6, 3.968}, // 3.968mm pin spacing
  {"Yale Y2", 7, 6, 3.9} // 3.9mm pin spacing
};
int numFormats = 3;

// Colors
#define WHITE 0xFFFF
#define RED 0xF800
#define BLUE 0x001F
#define GREEN 0x07E0
#define SILVER 0xC618
#define DARK_SILVER 0x7BEF
#define BRASS 0xFD20
#define SHADOW 0x4208

void drawGradientRect(int x, int y, int w, int h, uint16_t startColor, uint16_t endColor) {
  for (int i = 0; i < h; i++) {
    uint16_t color = M5.Lcd.color565(
      map(i, 0, h, (startColor >> 11) * 8, (endColor >> 11) * 8),
      map(i, 0, h, ((startColor >> 5) & 0x3F) * 4, ((endColor >> 5) & 0x3F) * 4),
      map(i, 0, h, (startColor & 0x1F) * 8, (endColor & 0x1F) * 8)
    );
    M5.Lcd.drawFastHLine(x, y + i, w, color);
  }
}

void drawKey() {
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(1);

  // Draw instructions
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.print("Align key with blue contour");

  // Draw key bow
  int bowX = 10, bowY = 30, bowW = 50, bowH = 50;
  drawGradientRect(bowX, bowY, bowW, bowH, SILVER, DARK_SILVER);
  M5.Lcd.fillEllipse(bowX + bowW / 2, bowY + bowH / 2, 12, 12, BLACK);

  // Draw blade
  int bladeX = bowX + bowW, bladeY = bowY + 10, bladeWidth = 140, bladeHeight = 30;
  drawGradientRect(bladeX, bladeY, bladeWidth, bladeHeight, BRASS, SILVER);

  // Draw key tip
  M5.Lcd.fillTriangle(bladeX + bladeWidth, bladeY, bladeX + bladeWidth, bladeY + bladeHeight,
                      bladeX + bladeWidth + 15, bladeY + bladeHeight / 2, SILVER);

  // Draw key contour (for alignment)
  M5.Lcd.drawRect(bladeX - 2, bladeY - 2, bladeWidth + 17, bladeHeight + 4, BLUE);
  M5.Lcd.drawLine(bladeX + bowW - 10, bladeY + bladeHeight + 2, bladeX + bladeWidth + 15, bladeY + bladeHeight + 2, BLUE);

  // Draw lock cylinder outline
  M5.Lcd.drawRect(bladeX - 5, bladeY - 15, bladeWidth + 25, bladeHeight + 30, SHADOW);

  // Draw pins with realistic notches
  int numPins = keyFormats[keyFormat].pinCount;
  float pinSpacingMm = keyFormats[keyFormat].pinSpacingMm;
  int pinSpacing = pinSpacingMm * 4; // 1mm ≈ 4 pixels
  int pinWidth = 8;

  for (int i = 0; i < numPins; i++) {
    int cut = cutDepths[i];
    int notchDepth = map(cut, 0, keyFormats[keyFormat].maxDepth, 4, 24);
    int pinX = bladeX + pinSpacing * i + pinSpacing / 2 - pinWidth / 2;
    int pinY = bladeY;

    // Draw smooth notch (parabolic shape)
    for (int x = pinX; x < pinX + pinWidth; x++) {
      int yOffset = notchDepth * (1 - pow((x - pinX - pinWidth / 2.0) / (pinWidth / 2.0), 2));
      M5.Lcd.drawFastVLine(x, pinY, yOffset, RED);
      M5.Lcd.drawPixel(x, pinY + yOffset + 1, SHADOW); // Shadow for depth
    }

    // Draw ramps between notches
    if (i < numPins - 1) {
      int nextCut = cutDepths[i + 1];
      int nextNotchDepth = map(nextCut, 0, keyFormats[keyFormat].maxDepth, 4, 24);
      int nextPinX = bladeX + pinSpacing * (i + 1) + pinSpacing / 2 - pinWidth / 2;
      M5.Lcd.drawLine(pinX + pinWidth, pinY + notchDepth, nextPinX, pinY + nextNotchDepth, RED);
    }

    if (i == currentCut) {
      M5.Lcd.drawRect(pinX - 1, pinY - 1, pinWidth + 2, notchDepth + 2, GREEN);
    }
  }

  // Draw metadata
  M5.Lcd.setCursor(10, 90);
  M5.Lcd.printf("Format: %s", keyFormats[keyFormat].name);
  M5.Lcd.setCursor(10, 100);
  M5.Lcd.printf("Pins: %d", numPins);
  M5.Lcd.setCursor(10, 110);
  M5.Lcd.printf("Cut %d: %d", currentCut + 1, cutDepths[currentCut]);

  // Draw bitting code
  M5.Lcd.setCursor(10, 120);
  M5.Lcd.print("Bitting: ");
  for (int i = 0; i < numPins; i++) {
    M5.Lcd.print(cutDepths[i]);
  }
}

void resetCuts() {
  for (int i = 0; i < MAX_PINS; i++) {
    cutDepths[i] = 0;
  }
  currentCut = 0;
  drawKey();
  M5.Lcd.setCursor(100, 80);
  M5.Lcd.setTextColor(GREEN);
  M5.Lcd.print("Reset!");
  delay(500);
  drawKey();
}

void setup() {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.Lcd.setRotation(1);
  Serial.begin(115200); // For debugging button presses

  drawKey();
}

void loop() {
  M5.update();

  if (M5.BtnA.wasPressed()) {
    Serial.println("Button A pressed (cycle pin)");
    currentCut = (currentCut + 1) % keyFormats[keyFormat].pinCount;
    drawKey();
  }

  if (M5.BtnA.pressedFor(2000)) {
    Serial.println("Button A long press (cycle format)");
    keyFormat = (keyFormat + 1) % numFormats;
    currentCut = 0;
    drawKey();
    M5.Lcd.setCursor(100, 80);
    M5.Lcd.setTextColor(GREEN);
    M5.Lcd.print("Format Changed!");
    delay(500);
    drawKey();
  }

  if (M5.BtnB.wasPressed()) {
    Serial.println("Button B pressed (increment cut depth)");
    cutDepths[currentCut] = (cutDepths[currentCut] + 1) % (keyFormats[keyFormat].maxDepth + 1);
    drawKey();
  }

  if (M5.BtnB.pressedFor(2000)) {
    Serial.println("Button B long press (reset)");
    resetCuts();
  }
}

Credits

Druk
2 projects • 0 followers

Comments