// *** Бесполезная коробка ***
// https://tsibrov.blogspot.com/2019/05/useless-box.html
#include <avr/wdt.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <Servo.h>
Servo servo_switch; // Сервопривод для тумблера
Servo servo_cover; // Сервопривод для крышки
#define pin_servo_switch 9 // пин управления сервоприводом для тумблера
//определим несколько положений:
#define servo_switch_home 40 // home
#define servo_switch_push 170 // переключить тумблер
#define servo_switch_oversee 100 // "выглягуть" из коробки
#define servo_switch_almost_push 145 // приблизиться к тумблеру, но не переключать
#define pin_servo_cover 10 // пин управления сервоприводом для крышки
//определим положения крышки:
#define servo_cover_home 10 // home
#define servo_cover_open 64 // открыть
#define servo_cover_preopen 30 // приоткрыть
#define pin_switch 2 // пин тумблера
#define pin_servo_power A5 // пин управления питанием сервоприводов
unsigned long tm; // Переменная для отслеживания интервалов времени
volatile byte user_temp = 0; // Переменная для отслеживания скорости игрока
bool NoEffects = 1;
void SwitchOff() {
// Переключаем тублер
wdt_enable(WDTO_1S); // На случай зависания в цикле установим WDT на 1с
servo_switch.write(servo_switch_push);
while (digitalRead(pin_switch) != 1) delay(50);
wdt_disable();
delay(10);
}
void setup() {
MCUSR = 0;
wdt_disable();
randomSeed(analogRead(0)); // Инициализация генератора псевдослучайных чисел
pinMode(pin_switch, INPUT_PULLUP); // Задействуем для тумблера подтягивающий резистор
attachInterrupt(digitalPinToInterrupt(pin_switch), Switch_ISR, LOW); // Разрешим прерывание от тумблера
pinMode(pin_servo_power, OUTPUT);
digitalWrite(pin_servo_power, 1);
// Подключаем сервоприводы
servo_switch.attach(pin_servo_switch);
servo_switch.write(servo_switch_home);
servo_cover.attach(pin_servo_cover);
servo_cover.write(servo_cover_home);
tm = millis() + 4000; // 4 секунды до ухода в сон
ADCSRA &= ~(1 << ADEN); // Отключаем АЦП (для снижения энергопотребления в спящем режиме)
}
void loop() {
if (digitalRead(pin_switch) == 0) {
if (user_temp == 5) { // Если игрок быстро переключает тумблер, то и мы будем быстро переключать, без эффектов
delay(random(800));
servo_cover.write(servo_cover_open);
SwitchOff(); // Переключаем тублер
servo_switch.write(servo_switch_oversee); // Отодвинем рычаг. Крышку оставим открытой
attachInterrupt(digitalPinToInterrupt(pin_switch), Switch_ISR, LOW);
tm = millis() + 1000;
return;
}
// Если же игрок не спешит, то и нам не надо:
delay(random(2000) + 200);
switch (NoEffects ? 12 : random(12)) { // Разнообразим поведение коробки эффектами:
case 0: { // Приоткрыть крышку и закрыть
servo_cover.write(servo_cover_preopen);
delay(random(2000));
servo_cover.write(servo_cover_home);
return;
}
case 1: { // Приоткрыть-закрыть несколько раз
for (int i = 1; i < 5; i++) {
servo_cover.write(servo_cover_preopen);
delay(50);
servo_cover.write(servo_cover_home);
delay(50);
}
return;
}
case 2: { // Медленно приоткрыть и закрыть крышку рычагом
for (int i = servo_switch_home; i < servo_switch_oversee; i++) {
servo_switch.write(i);
delay(20);
}
delay(random(2000));
for (int i = servo_switch_oversee; i > servo_switch_home; i--) {
servo_switch.write(i);
delay(20);
}
return;
}
case 3: { // Медленно приоткрыть рычагом и быстро закрыть
for (int i = servo_switch_home; i < servo_switch_oversee; i++) {
servo_switch.write(i);
delay(20);
}
delay(random(2000));
servo_switch.write(servo_switch_home);
return;
}
case 4: { // Открыть, приблизиться к тумблеру, но не переключать
servo_cover.write(servo_cover_open);
servo_switch.write(servo_switch_almost_push);
delay(random(2000));
for (int i = servo_switch_almost_push; i > servo_switch_home; i--) {
servo_switch.write(i);
delay(20);
}
delay(100);
servo_cover.write(servo_cover_home);
return;
}
case 5: { // Медленно открыть крышку, поднять рычаг и прихлопнуть его
for (int i = servo_cover_home; i < servo_cover_open; i++) {
servo_cover.write(i);
delay(20);
}
for (int i = servo_switch_home; i < servo_switch_oversee; i++) {
servo_switch.write(i);
delay(20);
}
servo_cover.write(servo_cover_home);
delay(100);
for (int i = 0; i < 5; i++) {
servo_switch.write(servo_switch_oversee + 10);
delay(50);
servo_switch.write(servo_switch_oversee - 10);
delay(50);
}
servo_switch.write(servo_switch_home);
return;
}
}
// case 6..11 - Переключить тумблер
SwitchOff();
servo_switch.write(servo_switch_home);
NoEffects = 0;
if (user_temp == 1)
tm = millis() + 4000;
else if (user_temp == 2)
tm = millis() + 3000;
else if (user_temp == 3)
tm = millis() + 2000;
else if (user_temp == 4)
tm = millis() + 1000;
attachInterrupt(digitalPinToInterrupt(pin_switch), Switch_ISR, LOW);
}
if (millis() > tm) { // Игрок бездействует
if (user_temp <= 1) { // Переходим в режим энергосбережения
NoEffects = 1; // При пробуждении переключаем тумблер без эфектов
delay(200);// Возможно сервоприводы еще в движении. Дадим им немного времени
digitalWrite(pin_servo_power, 0); // Выключаем питание сервоприводов
servo_cover.detach();
pinMode(pin_servo_cover, INPUT); //Переводим пины сервоприводов в режим INPUT
servo_switch.detach();
pinMode(pin_servo_switch, INPUT);
// Переводим МК в спящий режим
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
noInterrupts();
sleep_enable();
// Разрешаем обработку внешнего прерывания иначе МК не проснется
attachInterrupt(digitalPinToInterrupt(pin_switch), Switch_ISR, LOW);
MCUCR = bit (BODS) | bit (BODSE);
MCUCR = bit (BODS);
interrupts();
sleep_cpu();
sleep_disable();
// После сна подключаем сервоприводы и подаем на них питание
digitalWrite(pin_servo_power, 1);
servo_switch.attach(pin_servo_switch);
servo_switch.write(servo_switch_home);
servo_cover.attach(pin_servo_cover);
servo_cover.write(servo_cover_home);
}
else {
if (user_temp == 5) {
servo_switch.write(servo_switch_home);
servo_cover.write(servo_cover_home);
}
user_temp = 0;
tm = millis() + 4000;
}
}
}
void Switch_ISR() {
detachInterrupt(digitalPinToInterrupt(pin_switch)); // Выключаем обработку внешнего прерывания
if (user_temp < 5) user_temp++;
}