// sketch.ino - Testes de Unidade para Wokwi (Versão Final)
// Inclui as bibliotecas necessárias para os testes
#include <ArduinoUnit.h> // Framework de testes
#include <Keypad.h> // Para ter acesso a NO_KEY, KeyState (e talvez redefinir)
#include <math.h> // Para sqrt e fabs
// --- Mocks para Funções de Hardware e Tempo ---
// IMPORTANT: Estas definições devem vir ANTES de qualquer uso das funções originais
// que você deseja mockar no seu código, ou antes de incluir bibliotecas
// que poderiam definir esses símbolos de forma conflitante.
// Mock para millis()
unsigned long mockMillis = 0;
#define millis() mockMillis // Redefine millis() para usar nosso mock diretamente
// Mock para Keypad.getKey()
// Em vez de mockar o método de um objeto, vamos fazer com que as funções de estado
// recebam o char lido como parâmetro no ambiente de teste.
// No código de teste, chamaremos handleIdleState(keypad_getKey_mock_char()) etc.
char keypad_getKey_mock_char_val = NO_KEY;
char keypad_getKey_mock_char() {
char tempKey = keypad_getKey_mock_char_val;
keypad_getKey_mock_char_val = NO_KEY; // Reseta após a leitura
return tempKey;
}
// Mocks para MPU6050.update() e leituras
float mockAccX = 0.0, mockAccY = 0.0, mockAccZ = 0.0;
float mockGyroX = 0.0, mockGyroY = 0.0, mockGyroZ = 0.0;
// Mocks para as funções de leitura do MPU6050
// Redefinimos elas diretamente para usar nossas variáveis mock
#define mpu_update() (void)0 // Não faz nada, apenas para compilar
#define mpu_getAccX() mockAccX
#define mpu_getAccY() mockAccY
#define mpu_getAccZ() mockAccZ
#define mpu_getGyroX() mockGyroX
#define mpu_getGyroY() mockGyroY
#define mpu_getGyroZ() mockGyroZ
// Mock para Servo.write()
int lastServoAngle = 0; // Para verificar se o servo foi movido
#define myServo_write(angle) (lastServoAngle = angle) // Redefine myServo.write()
// --- Variáveis de Estado do Sistema (Copiadas para o ambiente de teste) ---
// Enumeração para os estados da Máquina de Estados Finitos (FSM)
enum SystemState {
SYSTEM_IDLE,
AUTH,
VERIFY,
MOTION_CHECK,
UNLOCK
};
// Variáveis globais do sistema (copiadas para o ambiente de teste)
SystemState currentState = SYSTEM_IDLE;
String passwordAttempt = "";
const String CORRECT_PASSWORD = "1234";
unsigned long stateStartTime = 0;
const unsigned long MOTION_CHECK_DURATION = 2000;
const unsigned long UNLOCK_DURATION = 5000;
const float GYRO_THRESHOLD = 0.5;
const float ACCEL_VARIATION_THRESHOLD = 0.1;
// --- Funções de Manipulação de Estados (Copiadas e ADAPTADAS para o ambiente de teste) ---
// Estas são as implementações das funções do seu sistema que serão testadas.
// Elas foram modificadas para usar os MOCKS que definimos.
void handleIdleState() {
// ATENÇÃO: No código original, chamava keypad.getKey(). Aqui, usaremos a nossa mock
// Mas como a função original Keypad.getKey() está encapsulada dentro da sua
// handleIdleState(), vamos passar o valor mockado através de uma variável global.
// Uma alternativa mais limpa (mas que exigiria refatoração mais profunda) seria
// alterar handleIdleState() para aceitar um `char key` como parâmetro.
char key = keypad_getKey_mock_char_val; // Assumimos que o teste vai definir isso
if (key == '*') {
// Serial.println("Entre com a senha de 4 digitos:");
passwordAttempt = "";
currentState = AUTH;
}
}
void handleAuthState() {
char key = keypad_getKey_mock_char_val; // Assumimos que o teste vai definir isso
if (key != NO_KEY) {
if (key >= '0' && key <= '9') {
passwordAttempt += key;
// Serial.print("*");
if (passwordAttempt.length() == 4) {
currentState = VERIFY;
}
} else {
// Serial.println("\nCaractere invalido. Tente novamente.");
passwordAttempt = "";
currentState = SYSTEM_IDLE;
}
}
}
void handleVerifyState() {
if (checkPassword(passwordAttempt)) {
// Serial.println("\nSenha correta. Verificando movimento...");
stateStartTime = millis(); // Usa o mock definido pelo #define
currentState = MOTION_CHECK;
} else {
// Serial.println("\nSenha incorreta. Acesso negado.");
currentState = SYSTEM_IDLE;
}
}
void handleMotionCheckState() {
if (millis() - stateStartTime < MOTION_CHECK_DURATION) {
mpu_update(); // Usa o mock definido pelo #define
float accelX = mpu_getAccX(); // Usa o mock definido pelo #define
float accelY = mpu_getAccY();
float accelZ = mpu_getAccZ();
float gyroX = mpu_getGyroX(); // Usa o mock definido pelo #define
float gyroY = mpu_getGyroY();
float gyroZ = mpu_getGyroZ();
if (checkMotion(accelX, accelY, accelZ, gyroX, gyroY, gyroZ)) {
// Serial.println("Movimento detectado durante a verificacao. Acesso negado.");
currentState = SYSTEM_IDLE;
return;
}
} else {
// Serial.println("Movimento minimo. Acesso liberado.");
stateStartTime = millis(); // Usa o mock definido pelo #define
currentState = UNLOCK;
}
}
void handleUnlockState() {
myServo_write(90); // Usa o mock definido pelo #define
// Serial.println("Porta aberta. Aguardando 5 segundos...");
if (millis() - stateStartTime >= UNLOCK_DURATION) { // Usa o mock definido pelo #define
myServo_write(0); // Usa o mock definido pelo #define
// Serial.println("Porta fechada. Sistema retornou ao Idle.");
currentState = SYSTEM_IDLE;
}
}
// --- Funções Auxiliares (Copiadas para o ambiente de teste) ---
// Estas funções não interagem diretamente com hardware, então são copiadas 1:1.
bool checkPassword(String password) {
return password == CORRECT_PASSWORD;
}
bool checkMotion(float accelX, float accelY, float accelZ, float gyroX, float gyroY, float gyroZ) {
float gyroMagnitude = sqrt(gyroX*gyroX + gyroY*gyroY + gyroZ*gyroZ);
if (gyroMagnitude > GYRO_THRESHOLD) {
return true;
}
float accelMagnitude = sqrt(accelX*accelX + accelY*accelY + accelZ*accelZ);
if (fabs(accelMagnitude - 1.0) > ACCEL_VARIATION_THRESHOLD) {
return true;
}
return false;
}
// --- Testes de Unidade ---
test(check_password_correct) {
assertEqual(checkPassword("1234"), true, "Senha '1234' deve ser aceita");
}
test(check_password_incorrect) {
assertEqual(checkPassword("4321"), false, "Senha '4321' deve ser rejeitada");
assertEqual(checkPassword("0000"), false, "Senha '0000' deve ser rejeitada");
}
test(check_password_wrong_length) {
assertEqual(checkPassword("123"), false, "Senha de 3 digitos deve ser rejeitada");
assertEqual(checkPassword("12345"), false, "Senha de 5 digitos deve ser rejeitada");
assertEqual(checkPassword(""), false, "Senha vazia deve ser rejeitada");
}
test(check_motion_no_movement) {
// Definir mocks para MPU6050 para simular "sem movimento"
mockAccX = 0.01; mockAccY = 0.01; mockAccZ = 1.01;
mockGyroX = 0.01; mockGyroY = 0.01; mockGyroZ = 0.01;
assertEqual(checkMotion(mockAccX, mockAccY, mockAccZ, mockGyroX, mockGyroY, mockGyroZ), false, "Nenhum movimento deve retornar false");
}
test(check_motion_with_gyro_movement) {
// Definir mocks para MPU6050 para simular "com movimento de giroscópio"
mockAccX = 0.0; mockAccY = 0.0; mockAccZ = 1.0;
mockGyroX = 1.0; mockGyroY = 0.0; mockGyroZ = 0.0;
assertEqual(checkMotion(mockAccX, mockAccY, mockAccZ, mockGyroX, mockGyroY, mockGyroZ), true, "Movimento no giroscopio X deve retornar true");
}
test(check_motion_with_accel_movement) {
// Definir mocks para MPU6050 para simular "com movimento de aceleração"
mockAccX = 2.0; mockAccY = 0.0; mockAccZ = 0.0;
mockGyroX = 0.0; mockGyroY = 0.0; mockGyroZ = 0.0;
assertEqual(checkMotion(mockAccX, mockAccY, mockAccZ, mockGyroX, mockGyroY, mockGyroZ), true, "Movimento de aceleracao (X) deve retornar true");
}
test(transition_idle_to_auth) {
currentState = SYSTEM_IDLE;
keypad_getKey_mock_char_val = '*'; // Define o valor que o mock vai retornar
handleIdleState();
assertEqual(currentState, AUTH, "Pressionar '*' deve transitar de SYSTEM_IDLE para AUTH");
assertEqual(passwordAttempt, "", "passwordAttempt deve ser limpa ao entrar em AUTH");
}
test(transition_auth_to_verify) {
currentState = AUTH;
passwordAttempt = "";
keypad_getKey_mock_char_val = '1'; handleAuthState();
keypad_getKey_mock_char_val = '2'; handleAuthState();
keypad_getKey_mock_char_val = '3'; handleAuthState();
keypad_getKey_mock_char_val = '4'; handleAuthState();
assertEqual(currentState, VERIFY, "Digitar 4 digitos deve transitar de AUTH para VERIFY");
assertEqual(passwordAttempt, "1234", "passwordAttempt deve ser '1234'");
}
test(transition_verify_correct_to_motion_check) {
currentState = VERIFY;
passwordAttempt = "1234";
mockMillis = 100; // Define um tempo inicial para o mock millis()
handleVerifyState();
assertEqual(currentState, MOTION_CHECK, "Senha correta deve transitar de VERIFY para MOTION_CHECK");
assertEqual(stateStartTime, 100, "stateStartTime deve ser atualizado");
}
test(transition_verify_incorrect_to_idle) {
currentState = VERIFY;
passwordAttempt = "4321";
handleVerifyState();
assertEqual(currentState, SYSTEM_IDLE, "Senha incorreta deve transitar de VERIFY para SYSTEM_IDLE");
}
test(transition_motion_check_no_movement_to_unlock) {
currentState = MOTION_CHECK;
mockMillis = 0;
stateStartTime = 0;
// Definir mocks para MPU6050 para simular "sem movimento" DURANTE A DURAÇÃO
mockAccX = 0.01; mockAccY = 0.01; mockAccZ = 1.01;
mockGyroX = 0.01; mockGyroY = 0.01; mockGyroZ = 0.01;
for (int i = 0; i < 20; i++) { // Roda 20 vezes para simular 2 segundos (20 * 100ms)
mockMillis += 100; // Avança o tempo
handleMotionCheckState();
if (currentState != MOTION_CHECK) break;
}
assertEqual(currentState, UNLOCK, "Sem movimento apos 2s deve transitar para UNLOCK");
assertEqual(stateStartTime, mockMillis, "stateStartTime deve ser atualizado para UNLOCK");
}
test(transition_motion_check_with_movement_to_idle) {
currentState = MOTION_CHECK;
mockMillis = 0;
stateStartTime = 0;
// Definir mocks para MPU6050 para simular "com movimento"
mockAccX = 2.0; mockAccY = 0.0; mockAccZ = 0.0;
mockGyroX = 0.0; mockGyroY = 0.0; mockGyroZ = 0.0;
mockMillis += 10;
handleMotionCheckState();
assertEqual(currentState, SYSTEM_IDLE, "Movimento detectado deve transitar para SYSTEM_IDLE");
}
test(transition_unlock_to_idle) {
currentState = UNLOCK;
mockMillis = 0;
stateStartTime = 0;
mockMillis += UNLOCK_DURATION;
handleUnlockState();
assertEqual(currentState, SYSTEM_IDLE, "Apos 5s em UNLOCK deve transitar para SYSTEM_IDLE");
assertEqual(lastServoAngle, 0, "Servo deve estar na posicao fechada (0 graus)");
}
// Configuração do ArduinoUnit
void setup() {
Serial.begin(9600);
}
void loop() {
Test::run();
while(true);
}