Lukas — Servo puzzel
Deze puzzel, gemaakt door Lukas, is een mechanisch slot dat wordt bediend door een servo motor. Spelers moeten de juiste code van cijfers invoeren door een potentiometer te draaienDe servo zal de deur ontgrendelen wanneer de juiste code is ingevoerd.
#include <Servo.h>
const int POT_PIN = A0;
const int SERVO_PIN = 10;
const int LOCKED_ANGLE = 0;
const int UNLOCKED_ANGLE = 90;
const int DIGIT_COUNT = 4;
const int COMBINATION[DIGIT_COUNT] = {0, 0, 5, 9};
/*
#######
#######
####### code voor het slot hierboven
#######
#######
*/
const int DIAL_DIGITS = 10;
const int DIAL_RANGE_MAX = 900; // raw waarde die op cijfer 9 mapt
const int BAND_WIDTH = DIAL_RANGE_MAX / DIAL_DIGITS;
const int STILL_THRESHOLD = 20; // max jitter (counts) die nog "stil" telt
const int REVERSAL_BACKLASH = 10; // terugval van piek voor omkeer telt
const unsigned long HOLD_MS = 400; // hoelang stil voor commit
// reset = sweep van 0-kant naar 9-kant in een beweging
const int SWEEP_LOW = 50;
const int SWEEP_HIGH = 850;
const int SWEEP_BACKLASH = 30; // toegelaten terugval voor sweep cancelt
const unsigned long SWEEP_TIMEOUT_MS = 2500;
const float EMA_ALPHA = 0.20;
const unsigned long LOOP_MS = 10;
const bool DEBUG = true;
Servo lock;
int lastRaw = 0;
float potFiltered = 0;
bool filterReady = false;
int prevValue = 0;
bool prevValueReady = false;
int moveDir = 0; // -1, 0, +1
int peakValue = 0;
int stillRef = 0;
unsigned long stillSince = 0;
bool committedRest = false;
bool entryBlocked = false;
bool wrongLockout = false; // na fout cijfer: geen accept tot reset
int enteredDigits = 0;
bool isUnlocked = false;
bool sweepArmed = false;
int sweepMax = 0;
unsigned long sweepStart = 0;
int readPot() {
int raw = analogRead(POT_PIN);
lastRaw = raw;
if (!filterReady) {
potFiltered = raw;
filterReady = true;
} else {
potFiltered += EMA_ALPHA * (raw - potFiltered);
}
return (int)(potFiltered + 0.5);
}
int digitOf(int value) {
int d = value / BAND_WIDTH;
if (d >= DIAL_DIGITS) d = DIAL_DIGITS - 1;
return d;
}
void resetEntryState(int value) {
stillRef = value;
stillSince = millis();
committedRest = false;
moveDir = 0;
peakValue = value;
prevValue = value;
prevValueReady = true;
}
void clearProgress() {
enteredDigits = 0;
committedRest = false;
stillSince = 0;
moveDir = 0;
prevValueReady = false;
wrongLockout = false;
if (DEBUG) Serial.println("progress cleared");
}
void doLock() {
lock.write(LOCKED_ANGLE);
isUnlocked = false;
clearProgress();
if (DEBUG) Serial.println("LOCKED");
}
void doUnlock() {
lock.write(UNLOCKED_ANGLE);
isUnlocked = true;
if (DEBUG) Serial.println("
*** UNLOCKED ***
");
/*
#######
#######
####### unlock hier
#######
#######
*/
}
void commitDigit(int value) {
if (wrongLockout) return;
int d = digitOf(value);
if (d == COMBINATION[enteredDigits]) {
enteredDigits++;
// aangrenzende gelijke cijfers tellen mee op één selectie
while (enteredDigits < DIGIT_COUNT && COMBINATION[enteredDigits] == d) {
enteredDigits++;
}
if (DEBUG) {
Serial.print("accept ");
Serial.print(d);
Serial.print(" ");
Serial.print(enteredDigits);
Serial.print("/");
Serial.println(DIGIT_COUNT);
}
if (enteredDigits >= DIGIT_COUNT) doUnlock();
} else {
wrongLockout = true;
if (DEBUG) {
Serial.print("wrong ");
Serial.print(d);
Serial.print(" need ");
Serial.print(COMBINATION[enteredDigits]);
Serial.println(" -> reset nodig");
}
}
}
// houdt de echte piek (extreme) per draairichting bij en commit bij omkeer
void trackReversal(int value, int delta) {
if (delta == 0) return;
if (moveDir == 0) {
moveDir = (delta > 0) ? 1 : -1;
peakValue = value;
return;
}
// verder in zelfde richting: piek uitbreiden
if ((moveDir > 0 && value > peakValue) ||
(moveDir < 0 && value < peakValue)) {
peakValue = value;
return;
}
// terugval van piek; accumuleert over loops, richting nog niet geflipt
int backlash = (moveDir > 0) ? (peakValue - value) : (value - peakValue);
if (backlash <= REVERSAL_BACKLASH) return;
// bevestigde omkeer
if (!committedRest) commitDigit(peakValue);
moveDir = -moveDir;
peakValue = value;
stillRef = value;
stillSince = millis();
committedRest = false;
}
// commit wanneer de dial lang genoeg stil ligt
void trackHold(int value) {
if (abs(value - stillRef) > STILL_THRESHOLD) {
stillRef = value;
stillSince = millis();
committedRest = false;
return;
}
if (!committedRest && (millis() - stillSince >= HOLD_MS)) {
commitDigit(value);
committedRest = true;
peakValue = value;
moveDir = 0;
}
}
void handleEntry(int value) {
if (entryBlocked) {
if (digitOf(value) != DIAL_DIGITS - 1) {
entryBlocked = false;
resetEntryState(value);
} else {
prevValue = value;
prevValueReady = true;
}
return;
}
if (!prevValueReady) {
resetEntryState(value);
return;
}
int delta = value - prevValue;
trackReversal(value, delta);
trackHold(value);
prevValue = value;
prevValueReady = true;
}
void handleReset(int value) {
if (!sweepArmed) {
if (value <= SWEEP_LOW) {
sweepArmed = true;
sweepMax = value;
sweepStart = millis();
}
return;
}
if (millis() - sweepStart > SWEEP_TIMEOUT_MS) {
sweepArmed = false;
return;
}
if (value > sweepMax) sweepMax = value;
if (value < sweepMax - SWEEP_BACKLASH) { // geen vloeiende beweging
sweepArmed = false;
return;
}
if (value >= SWEEP_HIGH) {
sweepArmed = false;
entryBlocked = true; // niet meteen 9 inlezen
if (DEBUG) Serial.println("reset sweep");
if (isUnlocked) doLock();
else clearProgress();
}
}
void setup() {
Serial.begin(9600);
lock.attach(SERVO_PIN);
doLock();
}
void loop() {
int value = readPot();
if (DEBUG) {
Serial.print("raw:");
Serial.print(lastRaw);
Serial.print(",filtered:");
Serial.println(value);
}
handleReset(value);
if (!isUnlocked) handleEntry(value);
delay(LOOP_MS);
}