/*
**************************************************************************
TODO LIST
????? 1. Режим "русской рулетки":
- наливает случайную рюмку + визуальные эффекты
2. Режим калибровки:
- установка объёма, позиций и времени налива (?) с записью в EEPROM
3. Режим "прокачки":
- Стартовый - при включении устройства:
При наличии в определённой позиции рюмки - осуществить прокачку,
до нажатия кнопки
- "смена бутылки" - в любой момент времени при определённом нажатии кнопки
(в режиме настроек?)
**************************************************************************
*/
//#define RLT_ENABLE // включить компиляцию рулетки
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Servo.h>
#include <Adafruit_NeoPixel.h>
// #include <EEPROM.h>
// Основные константы
#define MIN_VOLUME 10 // минимальный объём, мл
#define MAX_VOLUME 50 // максимальный объём, мл
#define STEP_VOLUME 5 // шаг изменения объёма, мл
#define BASE_VOLUME 30 // базовый объём
#define TOTAL_GLASSES 3 // количество рюмок
#define BASE_FILLING_TIME 3000 // время налива рюмки базового объёма, мс
#define SERVO_START_POSITION 0 // начальная позиция сервопривода
#define SETTING_TIME 2500 // время удержания кнопки энкодера для входа в настройки, мс
#define RELEASE_TIME 1000 // время удержания кноки "Старт" до сброса цикла, мс
#define MODE_TIME 2500 // время удержания кнопки "Старт" для смены режима работы (автоматический/ручной)
#define DOT_PRINTING_TIME 40 // время отображения 1 сегмента прогресс-бара, мс
#define ROULETTE_TIME 2000 // время цветомузыки в режиме рулетки
#define CLOCKWISE 1 // направление вращения энкодера (по часовой стрелке)
#define COUNTERCLOCKWISE -1 // направление вращения энкодера (против часовой стрелки)
#define NO_ROTATION 0 // энкодер не вращается
#define RED 1
#define YELLOW 2
#define GREEN 3
#define BLUE 4
// Распиновка (Arduino UNO)
#define ENC_CLK_PIN 2 // CLK энкодера
#define ENC_DT_PIN 3 // DT энкодера
#define ENC_BUTTON_PIN 4 // кнопка энкодера
#define SERVO_PIN 9 // сервопривод (кран)
#define START_BUTTON_PIN 10 // кнопка "Старт"
#define POMP_PIN 11 // помпа
#define LEDS_PIN A2 // индикация
const int glassPin[TOTAL_GLASSES] = {5, 6, 7}; // рюмки
// Параметры дисплея
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
// Константы и переменные
const int glassPosition[TOTAL_GLASSES] = {30, 90, 150}; // позиции рюмок
int currentVolume = BASE_VOLUME; // текущий объём, мл
int currentFillingTime = BASE_FILLING_TIME; // текущее время наполнения рюмки (текущего объёма), мс
int servoPosition = SERVO_START_POSITION; // текущая позиция сервопривода
int startButtonState = HIGH; // чтение состояния кнопки "Старт"
bool startButtonFlag = false; // состояние кнопки "Старт" (false - отпущена)
unsigned long startButtonTimer = 0; // таймер удержания кнопки "Старт"
int encoderButtonState = HIGH; // чтение кнопки энкодера
bool encoderButtonFlag = false; // состояние кнопки энкодера (false - отпущена)
unsigned long encoderButtonTimer = 0; // таймер удержания кнопки энкодера
int sensorGlass[TOTAL_GLASSES] = {HIGH, HIGH, HIGH}; // состояние датчика рюмки
bool isGlass = false; // признак наличия хотя бы одной рюмки
#ifdef RLT_ENABLE
unsigned long rouletteTimer = 0;
#endif
bool startFlag = true;
enum MODE {AUTO, MANUAL, SETTINGS, ROULETTE}; // режимы работы
MODE workMode;
enum STATES {NOGLASS, EMPTY, FILLING, FULL}; // состояния рюмок
STATES stateGlass[TOTAL_GLASSES];
STATES stateGlassPrev[TOTAL_GLASSES];
// Создание объектов
Servo srv;
Adafruit_NeoPixel leds(TOTAL_GLASSES, LEDS_PIN);
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Настройуки запуска устройства
void setup() {
Serial.begin(9600);
// Настройка пинов
for (int i = 0; i < TOTAL_GLASSES; i++){
pinMode(glassPin[i], INPUT_PULLUP);
}
pinMode(START_BUTTON_PIN, INPUT_PULLUP);
pinMode(POMP_PIN, OUTPUT);
pinMode(SERVO_PIN, OUTPUT);
pinMode(LEDS_PIN, OUTPUT);
pinMode(ENC_CLK_PIN, INPUT);
pinMode(ENC_DT_PIN, INPUT);
pinMode(ENC_BUTTON_PIN, INPUT_PULLUP);
// Режим работы
workMode = MANUAL;
// Иниципализация дисплея
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
delay(1000);
oled.setFont(&FreeSans9pt7b);
oled.setTextColor(SSD1306_WHITE);
oledMainDisplay();
// Установка сервопривода в стартовую позиция
goServo(SERVO_START_POSITION);
// Отключение реле помпы
digitalWrite(POMP_PIN, HIGH);
// Инициализация индикации
leds.begin();
// Статусы рюмок
for (int i = 0; i < TOTAL_GLASSES; i++){
stateGlass[i] = NOGLASS;
stateGlassPrev[i] = NOGLASS;
}
// Прокачка при включении?
//pumpSystem();
}
// Вращение серво
void goServo(int position){
srv.attach(SERVO_PIN);
srv.write(position);
delay(100);
srv.detach();
}
// Изменение статуса рюмки
void changeGlassState(int glass, STATES state){
stateGlassPrev[glass] = stateGlass[glass];
stateGlass[glass] = state;
ledsGlassControl();
}
// Изменение индикации
void setLedColor(int ledNumber, int color = 0){
int r, g, b;
switch (color){
case RED:
leds.setPixelColor(ledNumber, leds.Color(32, 0, 0));
break;
case YELLOW:
leds.setPixelColor(ledNumber, leds.Color(127, 127, 0));
break;
case GREEN:
leds.setPixelColor(ledNumber, leds.Color(0, 127, 0));
break;
case BLUE:
leds.setPixelColor(ledNumber, leds.Color(0, 0, 127));
break;
default:
leds.setPixelColor(ledNumber, leds.Color(0, 0, 0));
}
leds.show();
}
// Установка цвета в зависимости от статуса рюмки
void ledsGlassControl(){
int newColor = 0;
for (int i = 0; i < TOTAL_GLASSES; i++){
switch(stateGlass[i]){
case NOGLASS:
newColor = RED;
break;
case EMPTY:
newColor = YELLOW;
break;
case FILLING:
newColor = BLUE;
break;
case FULL:
newColor = GREEN;
break;
default:
newColor = 0;
}
setLedColor(i, newColor);
}
}
// Изменение заданного объёма (и времени налива)
void changeVol(int encDirection){
currentVolume += STEP_VOLUME*encDirection;
currentVolume = constrain(currentVolume, MIN_VOLUME, MAX_VOLUME);
currentFillingTime = currentVolume * BASE_FILLING_TIME / BASE_VOLUME;
}
// Отображение общего экрана
void oledMainDisplay(){
oled.clearDisplay();
oled.setCursor(1,11);
oled.println("Mode:");
oled.setCursor(55,11);
switch (workMode){
case AUTO:
oled.println("auto");
break;
case MANUAL:
oled.println("manual");
break;
case SETTINGS:
oled.println("settings");
break;
case ROULETTE:
oled.println("roulette");
}
oled.drawLine(0, 20, 127, 20, WHITE);
oled.setCursor(20, 40);
oled.println("Vol.: " + String(currentVolume) + " ml.");
oled.display();
}
// Отображение прогресс-бара во время налива рюмки
void showProgressBar (int prog){
oled.drawRoundRect(0, 47, 127, 13, 2, WHITE);
oled.fillRect(0, 48, prog-1, 11, WHITE);
oled.display();
}
// Режим настроек
void settings(){
int encClk;
int encClkPrev = HIGH;
int encDt;
int encDirection;
bool encFlag;
MODE workModePrev = workMode;
workMode = SETTINGS;
oledMainDisplay();
while (digitalRead(ENC_BUTTON_PIN) == HIGH){
encClk = digitalRead(ENC_CLK_PIN);
if (encClk != encClkPrev){
encFlag = !encFlag;
if (encFlag){
encDt = digitalRead(ENC_DT_PIN);
if (encClk == LOW && encDt == HIGH){
// Поворот по часовой стрелке
encDirection = COUNTERCLOCKWISE;
}
if (encClk == LOW && encDt == LOW){
// Поворот против часовой стрелки
encDirection = CLOCKWISE;
}
encClkPrev = encClk;
changeVol(encDirection);
encDirection = NO_ROTATION;
oledMainDisplay();
}
}
}
workMode = workModePrev;
oledMainDisplay();
}
#ifdef RLT_ENABLE
// Световые эффекты режима рулетки
void showEffects(){
rouletteTimer = millis();
while (millis() - rouletteTimer < ROULETTE_TIME){
for (int i = 0 ; i < TOTAL_GLASSES; i++){
setLedColor (i, random(2, 5) * (!sensorGlass[i]));
leds.show();
delay(250);
}
}
}
// Режим рулетки
void roulette(){
int lucky = 0;
int players = 0;
int start = 0;
checkGlasses();
// Определяем количество игроков
for (int i = 0; i < TOTAL_GLASSES; i++){
players += !sensorGlass[i];
}
if (players >= 2){
showEffects();
}
}
#endif
// прокачка системы
void pumpSystem(){
}
// Наливаем рюмку
void fillGlass(int glassNum){
unsigned long timeOfFilling = 0; // таймер времени налива
bool fillingFlag = true; // флаг прекращения налива
int progrDotPrintTime = currentFillingTime / (SCREEN_WIDTH - 2); // интервал увеличения прогресс-бара
int progrDots = currentFillingTime / DOT_PRINTING_TIME; // количество сегментов прогресс-бара
int progrDotCounter = 0; // счётчик прогресс-бара
changeGlassState(glassNum, FILLING);
delay(250);
digitalWrite(POMP_PIN, LOW); // включаем помпу
timeOfFilling = millis();
while (fillingFlag){
// Отображение прогресса налива
if ((millis() - timeOfFilling) > (progrDotPrintTime * progrDotCounter)){
showProgressBar(map(progrDotCounter, 0, progrDots, 0, SCREEN_WIDTH - 2));
progrDotCounter++;
}
// Контроль времени налива
if (millis() - timeOfFilling > currentFillingTime){
fillingFlag = false;
}
// Контроль убранной рюмки
checkGlasses();
if (stateGlass[glassNum] == NOGLASS){
fillingFlag = false;
}
}
digitalWrite(POMP_PIN, HIGH); // выключаем помпу
if (stateGlass[glassNum] == FILLING){
changeGlassState(glassNum, FULL);
}
oledMainDisplay();
}
// Основной цикл налива напитков (для ручного режима)
void pourDrink(){
for (int i = 0; i < TOTAL_GLASSES; i++){
if (stateGlass[i] == EMPTY){
goServo(glassPosition[i]);
fillGlass(i);
}
}
goServo(SERVO_START_POSITION);
}
// проверка наличия рюмки, возвращает true, если установлена хотя бы одна рюмка
bool checkGlasses(){
bool result = false;
for (int i = 0; i < TOTAL_GLASSES; i++){
sensorGlass[i] = digitalRead(glassPin[i]);
if (sensorGlass[i] == LOW){
if (stateGlassPrev[i] == NOGLASS){
changeGlassState(i, EMPTY);
result = true;
}
} else {
changeGlassState(i, NOGLASS);
}
}
return result;
}
// Изменяем режим работы
void changeMode(){
#ifdef RLT_ENABLE
workMode = (workMode == MANUAL) ? AUTO : ((workMode == AUTO) ? ROULETTE : MANUAL);
#else
workMode = (workMode == MANUAL) ? AUTO : MANUAL;
#endif
}
// Основной цикл работы
void loop() {
// Если есть рюмка - включим подсветку
checkGlasses();
// Проверка нажатия кнопки "Старт"
startButtonState = digitalRead(START_BUTTON_PIN);
// Определяем продолжительность нажатия кнопки "Старт"...
if (startButtonState == LOW){
startButtonTimer = millis();
startButtonFlag = true;
while (startButtonFlag){
if (digitalRead(START_BUTTON_PIN) == HIGH){
startButtonFlag = false;
startButtonTimer = millis() - startButtonTimer;
}
}
if (startButtonTimer > MODE_TIME){ // ...если больше MODE_TIME - меняем режим работы
changeMode();
oledMainDisplay();
} else if (startButtonTimer < RELEASE_TIME) { // ...если меньше RELEASE_TIME и в ручном режиме -наливаем, иниаче ничего не делаем
if (workMode == MANUAL){
pourDrink();
}
#ifdef RLT_ENABLE
if (workMode == ROULETTE){
roulette();
}
#endif
}
}
// Автоматический режим - ожидание установки рюмки
if (workMode == AUTO){
isGlass = checkGlasses();
if (isGlass){
pourDrink();
isGlass = false;
}
}
// Проверка нажатия кнопки энкодера
encoderButtonState = digitalRead(ENC_BUTTON_PIN);
// Определяем продолжительность нажатия кнопки "Старт"
if (encoderButtonState == LOW){
encoderButtonTimer = millis();
encoderButtonFlag = true;
while (encoderButtonFlag){
encoderButtonState = digitalRead(ENC_BUTTON_PIN);
if (encoderButtonState == HIGH){
encoderButtonTimer = millis() - encoderButtonTimer;
encoderButtonFlag = false;
}
}
if (encoderButtonTimer > SETTING_TIME){ // если больше SETTING_TIME - идём в настройки
settings();
}
}
}