Keep talking and nobody explodes Docs

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.

Foto van de servo puzzel

    #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);
    }
    
Tinkercad schema voor de binaire puzzel