Reset projektu, jde se na to ručně. Zatím jen základ s konstantami a SerialMonitor

This commit is contained in:
Matěj Kubíček
2026-06-08 09:47:15 +02:00
parent e7b02e999d
commit 18db6b24da
11 changed files with 144 additions and 662 deletions
-62
View File
@@ -1,62 +0,0 @@
#pragma once
#include <Arduino.h>
class InterruptButton {
public:
explicit InterruptButton(byte pin)
: _pin(pin), _lastPressMs(0) {
}
void begin() {
pinMode(_pin, INPUT_PULLUP);
_instance = this;
attachInterrupt(digitalPinToInterrupt(_pin), InterruptButton::isrRouter, FALLING);
}
bool consumePress() {
bool pending = false;
noInterrupts();
if (_pendingInterrupt) {
pending = true;
_pendingInterrupt = false;
}
interrupts();
if (!pending) {
return false;
}
const uint32_t now = millis();
if (now - _lastPressMs < BUTTON_DEBOUNCE_MS) {
return false;
}
_lastPressMs = now;
return true;
}
void clearPending() {
noInterrupts();
_pendingInterrupt = false;
interrupts();
}
bool isPressedRaw() const {
return digitalRead(_pin) == LOW;
}
private:
static constexpr uint32_t BUTTON_DEBOUNCE_MS = 170;
static void isrRouter() {
if (_instance != nullptr) {
_pendingInterrupt = true;
}
}
inline static InterruptButton *_instance = nullptr;
inline static volatile bool _pendingInterrupt = false;
byte _pin;
uint32_t _lastPressMs;
};
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include <EEPROM.h>
#include <array>
#include <cstdint>
#include "VaultConfig.h"
inline bool loadStoredCode(std::array<uint8_t, CODE_LENGTH> &code) {
constexpr int STORAGE_MAGIC_ADDR = 0;
constexpr int STORAGE_CODE_ADDR = 1;
constexpr uint8_t STORAGE_MAGIC = 0x5A;
if (EEPROM.read(STORAGE_MAGIC_ADDR) != STORAGE_MAGIC) {
return false;
}
for (std::size_t i = 0; i < CODE_LENGTH; ++i) {
const uint8_t digit = EEPROM.read(STORAGE_CODE_ADDR + static_cast<int>(i));
if (digit > DIGIT_MAX) {
return false;
}
code[i] = digit;
}
return true;
}
inline void saveStoredCode(const std::array<uint8_t, CODE_LENGTH> &code) {
constexpr int STORAGE_MAGIC_ADDR = 0;
constexpr int STORAGE_CODE_ADDR = 1;
constexpr uint8_t STORAGE_MAGIC = 0x5A;
EEPROM.update(STORAGE_MAGIC_ADDR, STORAGE_MAGIC);
for (std::size_t i = 0; i < CODE_LENGTH; ++i) {
EEPROM.update(STORAGE_CODE_ADDR + static_cast<int>(i), code[i]);
}
}
-73
View File
@@ -1,73 +0,0 @@
#pragma once
#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <cstdio>
#include "VaultConfig.h"
class DisplayController {
public:
DisplayController()
: _lcd(LCD_ADDR, LCD_COLS, LCD_ROWS) {
}
void begin() {
_lcd.init();
_lcd.backlight();
showMessage("Elektronicky", "trezor ready");
}
void showEntry(uint8_t selectedDigit, std::size_t enteredLength) {
char line1[17];
char line2[17];
snprintf(line1, sizeof(line1), "Kod: %c%c%c%c",
enteredLength > 0 ? '*' : '_',
enteredLength > 1 ? '*' : '_',
enteredLength > 2 ? '*' : '_',
enteredLength > 3 ? '*' : '_');
snprintf(line2, sizeof(line2), "Cislice: %u", selectedDigit);
writePadded(0, line1);
writePadded(1, line2);
}
void showSetup(uint8_t selectedDigit, std::size_t enteredLength) {
char line1[17];
char line2[17];
snprintf(line1, sizeof(line1), "Novy kod: %c%c%c%c",
enteredLength > 0 ? '*' : '_',
enteredLength > 1 ? '*' : '_',
enteredLength > 2 ? '*' : '_',
enteredLength > 3 ? '*' : '_');
snprintf(line2, sizeof(line2), "Vyber: %u", selectedDigit);
writePadded(0, line1);
writePadded(1, line2);
}
void showLockout(uint32_t remainingSeconds) {
char line2[17];
writePadded(0, "LOCKOUT");
snprintf(line2, sizeof(line2), "Cekej %lus", static_cast<unsigned long>(remainingSeconds));
writePadded(1, line2);
}
void showMessage(const char *line1, const char *line2) {
writePadded(0, line1);
writePadded(1, line2);
}
private:
void writePadded(uint8_t row, const char *text) {
_lcd.setCursor(0, row);
_lcd.print(" ");
_lcd.setCursor(0, row);
_lcd.print(text);
}
LiquidCrystal_I2C _lcd;
};
-35
View File
@@ -1,35 +0,0 @@
#pragma once
#include <Arduino.h>
class RotaryEncoder {
public:
RotaryEncoder(byte clkPin, byte dtPin)
: _clkPin(clkPin), _dtPin(dtPin), _lastClk(HIGH) {
}
void begin() {
pinMode(_clkPin, INPUT_PULLUP);
pinMode(_dtPin, INPUT_PULLUP);
_lastClk = digitalRead(_clkPin);
}
// -1 = vlevo, +1 = vpravo, 0 = bez změny
int8_t readStep() {
const int clkState = digitalRead(_clkPin);
int8_t step = 0;
if (clkState != _lastClk && clkState == LOW) {
const int dtState = digitalRead(_dtPin);
step = (dtState != clkState) ? 1 : -1;
}
_lastClk = clkState;
return step;
}
private:
byte _clkPin;
byte _dtPin;
int _lastClk;
};
-84
View File
@@ -1,84 +0,0 @@
#pragma once
#include <Arduino.h>
#include <Servo.h>
class FeedbackController {
public:
FeedbackController(byte servoPin, byte redPin, byte greenPin, byte bluePin, byte buzzerPin)
: _servoPin(servoPin), _redPin(redPin), _greenPin(greenPin), _bluePin(bluePin), _buzzerPin(buzzerPin) {
}
void begin(uint8_t lockedAngle) {
pinMode(_redPin, OUTPUT);
pinMode(_greenPin, OUTPUT);
pinMode(_bluePin, OUTPUT);
pinMode(_buzzerPin, OUTPUT);
_servo.attach(_servoPin);
lock(lockedAngle);
ledOff();
}
void lock(uint8_t angle) {
_servo.write(angle);
}
void unlock(uint8_t angle) {
_servo.write(angle);
}
void ledOff() {
setRgb(0, 0, 0);
}
void onCorrectCode(uint8_t unlockAngle) {
unlock(unlockAngle);
setRgb(0, 255, 0);
playSuccessMelody();
}
void onWrongCode() {
for (uint8_t i = 0; i < 3; ++i) {
setRgb(255, 0, 0);
delay(140);
ledOff();
delay(110);
}
playAlarm();
}
private:
void setRgb(uint8_t red, uint8_t green, uint8_t blue) {
analogWrite(_redPin, red);
analogWrite(_greenPin, green);
analogWrite(_bluePin, blue);
}
void playSuccessMelody() {
tone(_buzzerPin, 784, 100);
delay(130);
tone(_buzzerPin, 988, 100);
delay(130);
tone(_buzzerPin, 1319, 170);
delay(190);
noTone(_buzzerPin);
}
void playAlarm() {
for (uint8_t i = 0; i < 2; ++i) {
tone(_buzzerPin, 220);
delay(250);
tone(_buzzerPin, 180);
delay(250);
}
noTone(_buzzerPin);
}
byte _servoPin;
byte _redPin;
byte _greenPin;
byte _bluePin;
byte _buzzerPin;
Servo _servo;
};
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include <Arduino.h>
constexpr byte BTN_PIN = 6; // tlačítko, druhý pin na GND
constexpr byte ENCODER_CLK_PIN = 8;
constexpr byte ENCODER_DT_PIN = 9;
constexpr byte ENCODER_SW_PIN = 12; // volitelný SW na enkodéru
// RGB LED common cathode
constexpr byte RGB_G_PIN = 3;
constexpr byte RGB_R_PIN = 5;
constexpr byte RGB_B_PIN = 11;
constexpr byte SERVO_PWM_PIN = 10;
constexpr byte BUZZER_PIN = 7;
-37
View File
@@ -1,37 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
-21
View File
@@ -1,21 +0,0 @@
#pragma once
#include <array>
#include <cstdint>
constexpr std::size_t CODE_LENGTH = 4;
constexpr uint8_t MAX_FAILED_ATTEMPTS = 3;
constexpr uint32_t LOCKOUT_MS = 30'000;
constexpr uint8_t DIGIT_MIN = 0;
constexpr uint8_t DIGIT_MAX = 9;
constexpr uint8_t SERVO_LOCKED_ANGLE = 5;
constexpr uint8_t SERVO_UNLOCKED_ANGLE = 95;
constexpr uint32_t UNLOCK_VISIBLE_MS = 5000;
constexpr uint8_t LCD_ADDR = 0x27;
constexpr uint8_t LCD_COLS = 16;
constexpr uint8_t LCD_ROWS = 2;
constexpr std::array<uint8_t, CODE_LENGTH> DEFAULT_CODE = {1, 2, 3, 4};
-96
View File
@@ -1,96 +0,0 @@
#pragma once
#include <array>
#include <cstdint>
#include "VaultConfig.h"
enum class SubmitResult : uint8_t {
DigitAccepted,
CorrectCode,
WrongCode,
LockedOut,
InvalidDigit
};
class VaultState {
public:
VaultState() : _code(DEFAULT_CODE), _entry{}, _entryLength(0), _failedAttempts(0), _lockoutUntilMs(0) {
}
void setCode(const std::array<uint8_t, CODE_LENGTH> &code) {
_code = code;
}
const std::array<uint8_t, CODE_LENGTH> &code() const {
return _code;
}
SubmitResult pushDigit(uint8_t digit, uint32_t nowMs) {
if (isLockedOut(nowMs)) {
return SubmitResult::LockedOut;
}
if (digit < DIGIT_MIN || digit > DIGIT_MAX) {
return SubmitResult::InvalidDigit;
}
if (_entryLength < CODE_LENGTH) {
_entry[_entryLength++] = digit;
}
if (_entryLength < CODE_LENGTH) {
return SubmitResult::DigitAccepted;
}
const bool ok = (_entry == _code);
clearEntry();
if (ok) {
_failedAttempts = 0;
return SubmitResult::CorrectCode;
}
++_failedAttempts;
if (_failedAttempts >= MAX_FAILED_ATTEMPTS) {
_failedAttempts = 0;
_lockoutUntilMs = nowMs + LOCKOUT_MS;
}
return SubmitResult::WrongCode;
}
bool isLockedOut(uint32_t nowMs) const {
return nowMs < _lockoutUntilMs;
}
uint32_t lockoutRemainingMs(uint32_t nowMs) const {
if (!isLockedOut(nowMs)) {
return 0;
}
return _lockoutUntilMs - nowMs;
}
void clearEntry() {
_entry.fill(0);
_entryLength = 0;
}
std::size_t enteredLength() const {
return _entryLength;
}
uint8_t failedAttempts() const {
return _failedAttempts;
}
void resetLockout() {
_lockoutUntilMs = 0;
_failedAttempts = 0;
clearEntry();
}
private:
std::array<uint8_t, CODE_LENGTH> _code;
std::array<uint8_t, CODE_LENGTH> _entry;
std::size_t _entryLength;
uint8_t _failedAttempts;
uint32_t _lockoutUntilMs;
};
+144
View File
@@ -0,0 +1,144 @@
#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Servo.h>
#include <EEPROM.h>
// piny
constexpr byte ENCODER_CLK_PIN = 11; // pwm pin, ale nepoužívá se
constexpr byte ENCODER_DT_PIN = 12;
constexpr byte BUTTON_PIN = 13;
constexpr byte RGB_RED_PIN = 3; // pwm
constexpr byte RGB_GREEN_PIN = 5; // pwm
constexpr byte RGB_BLUE_PIN = 6; // pwm
constexpr byte SERVO_PIN = 9;
constexpr byte BUZZER_PIN = 8;
// Servo
constexpr int CLOSED_POS = 150;
constexpr int OPEN_POS = 15;
Servo servo;
// LCD
LiquidCrystal_I2C lcd(0x27, 16, 2);
// proměnné
// příklad je zadávané heslo, stavy, apod.
String password = "";
String savedPassword = "";
// chování zámku
void OpenLock() {
servo.write(OPEN_POS);
}
void CloseLock() {
servo.write(CLOSED_POS);
}
// chování bzučáku
void OpenSound() {
// TODO: melodie při otevření
}
void CloseSound() {
// TODO: ověřit tento zvuk
digitalWrite(BUZZER_PIN, HIGH);
delay(100);
digitalWrite(BUZZER_PIN, LOW);
}
// chování RGB LED
void OpenLED() {
analogWrite(RGB_GREEN_PIN, 120);
analogWrite(RGB_BLUE_PIN, 0);
analogWrite(RGB_RED_PIN, 0);
}
void CloseLED() {
analogWrite(RGB_GREEN_PIN, 0);
analogWrite(RGB_BLUE_PIN, 0);
analogWrite(RGB_RED_PIN, 120);
}
// když není uložené heslo, ukazuje se modrá
void NeutralLED() {
analogWrite(RGB_GREEN_PIN, 0);
analogWrite(RGB_BLUE_PIN, 120);
analogWrite(RGB_RED_PIN, 0);
}
void ButtonPressed() {
if (savedPassword == "") {
savedPassword = password;
}
else {
if (password == savedPassword) {
OpenLock();
OpenSound();
OpenLED();
password = "";
}
else {
CloseLock();
CloseSound();
CloseLED();
password = "";
}
}
}
void setup() {
Serial.begin(9600);
Serial.println("Nastavování displeje.");
lcd.init();
lcd.backlight();
Serial.println("Displej nastaven.");
lcd.setCursor(0, 0);
lcd.printstr("Program startuje");
lcd.setCursor(0, 1);
lcd.printstr("NastavujeSeMotor");
delay(100);
Serial.println("Nastavování motoru.");
servo.attach(SERVO_PIN);
servo.write(CLOSED_POS); // ve výchozím stavu je zamčeno
Serial.println("Motor nastaven.");
lcd.setCursor(0, 1);
lcd.printstr("Motor nastaven. ");
delay(100);
lcd.setCursor(0, 1);
lcd.printstr("NastavujiSePiny.");
Serial.println("Nastavování pinů");
// nastavení pinu na tlačítko
pinMode(BUTTON_PIN, INPUT_PULLDOWN); // nezmáčknuto = 0
Serial.println("Piny: 1/8");
// nastavení pinů pro RGB LED (bude přes pwm, není to nutné přes pinmode, ale lepší to udělat)
pinMode(RGB_RED_PIN, OUTPUT); Serial.println("Piny: 2/8");
pinMode(RGB_GREEN_PIN, OUTPUT); Serial.println("Piny: 3/8");
pinMode(RGB_BLUE_PIN, OUTPUT); Serial.println("Piny: 4/8");
// nastavení pinů pro enkodér
pinMode(ENCODER_CLK_PIN, INPUT); Serial.println("Piny: 5/8");
pinMode(ENCODER_DT_PIN, INPUT); Serial.println("Piny: 6/8");
// nastavení pinu pro servo
pinMode(SERVO_PIN, OUTPUT); Serial.println("Piny: 7/8");
// nastavení pinu pro bzučák
pinMode(BUZZER_PIN, OUTPUT); Serial.println("Piny: 8/8");
Serial.println("Piny nastaveny.");
lcd.setCursor(0, 1);
lcd.printstr("Piny nastaveny. ");
delay(1000);
}
void loop() {
}
-200
View File
@@ -1,200 +0,0 @@
#include <Arduino.h>
#include <array>
#include "ButtonInterrupt.h"
#include "CodeStorage.h"
#include "Display.h"
#include "Encoder.h"
#include "Feedback.h"
#include "Pins.h"
#include "VaultConfig.h"
#include "VaultState.h"
enum class Mode : uint8_t {
EnterCode,
SetupCode,
Unlocked,
Lockout
};
VaultState vault;
RotaryEncoder encoder(ENCODER_CLK_PIN, ENCODER_DT_PIN);
InterruptButton button(BTN_PIN);
DisplayController display;
FeedbackController feedback(SERVO_PWM_PIN, RGB_R_PIN, RGB_G_PIN, RGB_B_PIN, BUZZER_PIN);
Mode mode = Mode::EnterCode;
uint8_t selectedDigit = 0;
std::array<uint8_t, CODE_LENGTH> setupCode = {0, 0, 0, 0};
std::size_t setupDigitsEntered = 0;
uint32_t unlockedUntil = 0;
void handleSerialCommands() {
if (!Serial.available()) {
return;
}
String cmd = Serial.readStringUntil('\n');
cmd.trim();
cmd.toUpperCase();
if (cmd == "RESET") {
vault.resetLockout();
mode = Mode::EnterCode;
display.showMessage("Reset lockout", "Zadej kod");
delay(550);
}
}
void moveSelectedDigit(int8_t step) {
if (step == 0) {
return;
}
int16_t next = static_cast<int16_t>(selectedDigit) + step;
if (next > DIGIT_MAX) {
next = DIGIT_MIN;
} else if (next < DIGIT_MIN) {
next = DIGIT_MAX;
}
selectedDigit = static_cast<uint8_t>(next);
}
void setupModeTick() {
if (button.consumePress()) {
setupCode[setupDigitsEntered++] = selectedDigit;
}
if (setupDigitsEntered >= CODE_LENGTH) {
saveStoredCode(setupCode);
vault.setCode(setupCode);
vault.resetLockout();
feedback.lock(SERVO_LOCKED_ANGLE);
feedback.ledOff();
display.showMessage("Kod ulozen", "Zadej novy kod");
delay(1000);
setupDigitsEntered = 0;
selectedDigit = 0;
mode = Mode::EnterCode;
return;
}
display.showSetup(selectedDigit, setupDigitsEntered);
}
void enterCodeModeTick(uint32_t nowMs) {
if (vault.isLockedOut(nowMs)) {
mode = Mode::Lockout;
return;
}
if (button.consumePress()) {
const SubmitResult result = vault.pushDigit(selectedDigit, nowMs);
if (result == SubmitResult::CorrectCode) {
feedback.onCorrectCode(SERVO_UNLOCKED_ANGLE);
display.showMessage("Kod spravny", "Trezor odemcen");
unlockedUntil = nowMs + UNLOCK_VISIBLE_MS;
mode = Mode::Unlocked;
return;
}
if (result == SubmitResult::WrongCode) {
feedback.onWrongCode();
if (vault.isLockedOut(nowMs)) {
mode = Mode::Lockout;
return;
}
display.showMessage("Spatny kod", "Zkus to znovu");
delay(800);
}
}
display.showEntry(selectedDigit, vault.enteredLength());
}
void unlockedModeTick(uint32_t nowMs) {
if (nowMs >= unlockedUntil) {
feedback.lock(SERVO_LOCKED_ANGLE);
feedback.ledOff();
mode = Mode::EnterCode;
return;
}
display.showMessage("Trezor odemcen", "Pristup povolen");
}
void lockoutModeTick(uint32_t nowMs) {
if (!vault.isLockedOut(nowMs)) {
feedback.lock(SERVO_LOCKED_ANGLE);
feedback.ledOff();
mode = Mode::EnterCode;
return;
}
const uint32_t remainingMs = vault.lockoutRemainingMs(nowMs);
const uint32_t remainingSec = (remainingMs + 999) / 1000;
display.showLockout(remainingSec);
}
void setup() {
Serial.begin(115200);
pinMode(BTN_PIN, INPUT_PULLUP);
const bool setupRequestedAtBoot = (digitalRead(BTN_PIN) == LOW);
encoder.begin();
button.begin();
button.clearPending();
feedback.begin(SERVO_LOCKED_ANGLE);
display.begin();
std::array<uint8_t, CODE_LENGTH> loadedCode = {0, 0, 0, 0};
if (loadStoredCode(loadedCode)) {
vault.setCode(loadedCode);
} else {
vault.setCode(DEFAULT_CODE);
saveStoredCode(DEFAULT_CODE);
}
if (setupRequestedAtBoot) {
mode = Mode::SetupCode;
display.showMessage("SETUP rezim", "Nastav novy kod");
delay(900);
} else {
mode = Mode::EnterCode;
display.showMessage("Zadej kod", "Otoc + tlacitko");
delay(800);
}
Serial.println("Prikaz pro reset lockoutu: RESET");
}
void loop() {
handleSerialCommands();
moveSelectedDigit(encoder.readStep());
const uint32_t nowMs = millis();
switch (mode) {
case Mode::SetupCode:
setupModeTick();
break;
case Mode::EnterCode:
enterCodeModeTick(nowMs);
break;
case Mode::Unlocked:
unlockedModeTick(nowMs);
break;
case Mode::Lockout:
lockoutModeTick(nowMs);
break;
}
delay(20);
}