Описание:
В данном проекте мы сделаем автополив с записью показаний датчика влажности почвы, датчика освещенности, с сенсорным дисплеем (для управления устройством и построением графиков датчиков) и управлением насосом через силовой ключ. Наше устройство будет собирать данные и включать насос по необходимости в поливе в зависимости от показаний датчика влажности и устоновок пользователя.
Видео:
Нам понадобится:
- 1x Piranha ULTRA
- 1x Trema-модуль Емкостной датчик влажности почвы
- 1x Trema-модуль Датчик освещенности
- 1x Trema-модуль Адаптер карт microSD
- 1x Trema-модуль Цветной графический дисплей 2.8 TFT 320x240, Сенсорный
- 1х Trema-модуль Силовой ключ
- 1x Мембранный насос
Для реализации проекта необходимо установить библиотеки:
- Библиотека iardiono_RTC
- Библиотека UTFT
- Библиотека TouchScreen
Подключение:
Для удобства подключения мы воспользуемся Trema Shield для Arduino. Так же нам понадобится: Trema-модуль Емкостной датчик влажности, Trema-модуль датчик освещённости, trema-модуль Адаптер карт microSD, Цветной графический дисплей 2.8 TFT 320x240, Сенсорный и Trema-модуль Силовой ключ.
Для начала подкючим Trema Shield к Piranha ULTRA:

Далее подключим адаптер microSD карт и силовой ключ по следующим таблицам:
| Адаптер карт microSD | Piranha ULTRA |
|---|---|
| SS(CS) | D10 |
| MOSI | D11 |
| MISO | D12 |
| SCK | D13 |
| VCC | VCC |
| GND | GND |
| Силовой ключ | Piranha ULTRA |
|---|---|
| G | GND |
| V | VCC |
| D | D8 |

Следующим шагом подключим датчик влажности, датчик освещённости и часы реального времени:
| Датчик влажности | Piranha ULTRA |
|---|---|
| A | A5 |
| V | VCC |
| G | GND |
| Датчик освещённости | Piranha ULTRA |
|---|---|
| S | A4 |
| V | VCC |
| G | GND |
| Часы реального времени | Piranha ULTRA |
|---|---|
| SCL | SCL |
| SDA | SDA |
| Vcc | Vcc |
| GND | Gnd |

Теперь подключим дисплей с touchscreen:
| Дисплей | Piranha ULTRA |
|---|---|
| VCC | VCC |
| GND | GND |
| D/C | D2 |
| RST | D3 |
| CS | D4 |
| SCK | D5 |
| MOSI | D6 |
| LED | D7 |
| YU | A0 |
| XR | A1 |
| YD | A2 |
| XL | A3 |

И финальным шагом подключим насос к внешнему питанию через уже подключенный к Piranha силовой ключ:

В результате у нас полючится вот такая система, в которую остаётся только загрузить скетч и вставить SD карту, отформатированную в файловой системе FAT16 или FAT32:
Внимание: Вствляем microSD карту только после успешной загрузки скетча

Калибровка датчика:
Для калибровки датчика можно воспользоваться следующим скетчем. Необходимо экспериментальным путём определить минимальные и максимальные показания датчика и затем внести их в определения MY_SENSOR_MAX и MY_SENSOR_MIN в основном проекте. Так же калибровка необходима при изменении длинны провода датчика.
MY_SENSOR_MAX - датчик сухой, в воздухе
MY_SENSOR_MIN - датчик по линию погружения в воде.
#define MOIST_SENSOR A5
void setup() {
Serial.begin(9600);
pinMode(MOIST_SENSOR, INPUT);
}
void loop() {
int Sensor = analogRead(MOIST_SENSOR);
Serial.println(Sensor);
delay(500);
}
Скетч проекта:
// Подключаем библиотеки:
#include <EEPROM.h> // Подключаем библиотеку для работы с ПЗУ ардуино
#include "UTFT.h" // Подключаем библиотеку для работы с дисплеем
#include "TouchScreen.h" // Подключаем библиотеку для работы с TouchScreen
#include <SPI.h> // Подключаем библиотеку для работы с SPI
#include <SD.h> // Подключаем библиотеку для работы с SD картой
#include <iarduino_RTC.h> // Подключаем библиотеку для работы с часами реального времени
#define myPumpHysteresis 1 // часы, определяем гистерисис насоса (если насос сработал, он не будет работать независимо от показаний датчиков в течении этого времени)
// Определяем выводы датчиков и ключа насоса
#define MOIST_SENSOR A5 // Ескостной датчик влажности
#define LDR A4 // Датчик освещенности
#define PUMP 8 // Вывод управления насосом
// определяем колибровочные значения датчика
#define MY_SENSOR_MAX 629 // максимальное "сырое" значение (датчик сухой)
#define MY_SENSOR_MIN 326 // минимальное "сырое" значение (датчик влажный)
// определяем подмену значений для меню
#define my_backlight_timer 30 // секунды, определяем время отключения подсветки
#define mainMenu 0 // Определяем номер главного меню
#define durationMenu 1 // Опредеояем номер меню установки насоса
#define moistureMenu 2 // Опредеояем номер меню установки влажности
#define timeMenu 3 // Опредеояем номер меню установки времени
#define dateMenu 4 // Опредеояем номер меню установки даты
#define plotMenu 5 // Опредеояем номер меню графика
#define standBy 6 // Опредеояем номер меню
#define SS 10 // Опредеояем номер меню
#define draw true // Определяем значение draw
#define erase false // Определяем значение erase
#define plot_moisture_color VGA_AQUA // Определяем цвет кривой влажности
#define plot_ldr_color VGA_YELLOW // Определяем цвет кривой освещенности
#define background_color VGA_SILVER // Определяем цвет фона
#define font_color VGA_BLACK // Определяем цвет шрифта
#define button_color VGA_NAVY // Определяем цвет кнопки
#define pump_color VGA_RED // Определяем цвет графика мотора
#define stopButtonColor VGA_RED // Определяем цвет цвет кнопки "стоп"
//определяем кнопки
#define up1 1 //
#define down1 2 //
#define up2 3 //
#define down2 4 //
#define up3 5 //
#define down3 6 //
#define up4 7 //
#define down4 8 //
#define back 9 //
#define date 10 //
#define moist_up 11 //
#define moist_down 12 //
#define mainDuration 13 //
#define mainMoisture 14 //
#define mainTime 15 //
#define mainPlot 16 //
#define plotNext 17 //
#define plotPrev 18 //
// Определяем значения
#define logInterval 276 // Определяем интервал пробы датчиков (13 раз в час)
#define height 240 // Опрелеояем высоту дисплея
#define width 320 // Определяем ширину дисплея
#define latest 0 // Самый новый созданный файл для функции парсирования директории и меню графика
// Определяем ключевые слова для функции парсирования директории
#define prev -1 // Предыдущий
#define next 1 // Следующий
// Определяем ключевые слова для функции меню графика
#define oldest 2 // самый старый файл
#define between 1 // между самым новым и самым старым
//
bool mutex = false; // определяем взаимоисключение для SD.open()
uint16_t currentFile = 0; // итератор "указатель" на текущий файл
extern uint8_t SmallFont[]; // подключаем шрифт имитирующий семисегментный индикатор
extern uint8_t BigFont[]; // подключаем шрифт имитирующий семисегментный индикатор
extern uint8_t SevenSegNumFont[]; // подключаем шрифт имитирующий семисегментный индикатор
// Определяем выводы используемые для управления дисплеем 2.8" TFT 320x240 UNO:
const uint8_t RS = 6; //
const uint8_t WR = 5; //
const uint8_t CS = 4; //
const uint8_t RST = 3; //
const uint8_t SER = 2; //
const uint8_t LED = 7; //
// Определяем выводы используемые для чтения данных с TouchScreen:
const uint8_t YP = A3; // Вывод Y+ должен быть подключен к аналоговому входу
const uint8_t XM = A2; // Вывод X- должен быть подключен к аналоговому входу
const uint8_t YM = A1; // Вывод Y-
const uint8_t XP = A0; // Вывод X+
// Определяем экстремумы для значений считываемых с аналоговых входов при определении точек нажатия на TouchScreen:
const int tsMinX = 100; // соответствующий точке начала координат по оси X
const int tsMinY = 120; // соответствующий точке начала координат по оси Y
const int tsMaxX = 950; // соответствующий максимальной точке координат по оси X
const int tsMaxY = 915; // соответствующий максимальной точке координат по оси Y
const int minPress = 10; // соответствующий минимальной степени нажатия на TouchScreen
const int maxPress = 1000; // соответствующий максимальной степени нажатия на TouchScreen
// переменные для хранения времени, датчика, длительности работы насоса
const int duration_max = 25; // максимальное время работы насоса
const int duration_min = 3; // минимальное время работы насоса
const int sensor_moist_max= MY_SENSOR_MAX; // максимальное значение для датчика
const int sensor_moist_min= MY_SENSOR_MIN; // минимальное значение для датчика
const int set_moist_max = 99; // максимальное значение установки влажности
const int set_moist_min = 1; // минимальное значение установки влажности
// массивы для вывода в график
uint8_t plot_moist[314]; // массив для вывода лога датчика влажности
uint16_t plot_LDR[314]; // массив для вывода лога датчика освещенности
bool pumpPlotFlag[312]; // массив для вывода флага срабатывания насоса
// переменные насоса
bool pump_stop = false; // флаг нажатия на кнопку стоп
bool halt_pump = false; // флаг экстренной остановки
bool pump_stopped = false; // флаг остановленного насоса
bool pump_flag = false; // флаг сработавшего насоса
bool pumpLogFlag = false; // флаг записи в лог-файл
uint32_t pumpStartMillis; // миллисекунды старта насоса
uint32_t pumpHysteresis = myPumpHysteresis*60*60; // гистерисис насоса
// структура для хранения имён файлов логов
struct files { //
String Name = ""; // переменная для хранения названия файла
uint32_t ints = 0; // переменная для хранения даты файла
} *sFiles; //
// таймеры и настройки
String realtime = ""; // переменная для хранения текущего времени
int moist = 99; // переменная для хранения текущей влажности почвы
uint16_t set_moist = 78; // переменная для хранения влажности срабатывания насоса
uint8_t duration = 3; // переменная для хранения времени работы насоса
short menuState = 0; // состояние меню
bool wakeFlag = true; // флаг подсветки дисплея
bool waitFlag = false; // флаг выхода из режима ожидания
uint8_t plotMenuDay = latest; // переменная состояния меню графика
// таймеры:
unsigned long backlightMillis; // таймер подсветки
unsigned long minuteMillis; // таймер обновления минут в главном меню
unsigned long moistureMillis; // таймер обновления влажности в главном меню
unsigned long logMillis; // таймер записи лога
unsigned long wakeFlagMillis; // таймер выхода из режима ожидания
unsigned long backlight_timer = my_backlight_timer * 1000; // таймер подсветки
unsigned long sec; // таймер
// UI
const int plotStartX = 3; // точка X началa графика
const int plotStartY = 114; // точка Y началa графика
const uint16_t moist_x = 170; // положение данных датчика влажности по оси Х
// Создаём объекты библиотек:
iarduino_RTC time(RTC_DS1307); // Создаём объект часов реального времени
UTFT myGLCD(TFT01_24SP, RS, WR, CS, RST, SER); // Создаём объект для работы с дисплеем
TouchScreen ts= TouchScreen(XP, YP, XM, YM); // Создаём объект для работы с TouchScreen
TSPoint p; // Создаём переменную для хранения координат и силы нажатия
void setup() { //
pinMode(LED, OUTPUT); // Определяем вывод управления подсветкой в режим выход
analogWrite(LED, 196); // Определяем уровень ШИМ для подсаетки
Serial.begin(9600); // Инициируем передачу данных в монитор последовательного порта на скорости 9600 бит/сек
while (!Serial); // Ждём серийный протокол, если нужно
if(!SD.begin(SS)){ // Если SD карта не инициировалась
Serial.println("SD failed"); // Выводим текст в последовательный порт
myGLCD.setFont(BigFont); // Устанавливаем шрифт
myGLCD.setColor(VGA_WHITE); // Устанавливаем цвет шрифта
myGLCD.setBackColor(VGA_BLACK); // Устанавливаем цвет фона
myGLCD.print(("Isert SD card and restart") , 50, 120); // Выводим текст нв дисплей
while(true); // Скетч дальше не выполняется
} //
time.begin(); // Инициируем работу с часами реального времени
moist = readMoisture(); // Обновляем переменную влажности почвы
realtime = time.gettime("H:i"); // Обновляем часы реального времени
myGLCD.InitLCD(); // Инициируем работу с TFT дисплеем
myGLCD.clrScr(); // Отчищаем экран дисплея
myGLCD.setBackColor(background_color); // Устанавливаем цвет фона
if (EEPROM.read(0) == 0xFF) saveSettings; // если первый байт ипрома не инициализирован, сохраняем настройки
else readSettings(); // если нет - читаем настройки
delay(100); //
myGLCD.setColor(background_color); // Устанавливаем цвет фона
myGLCD.fillRect(0, 0, width, height); // заливаем экран
readLog(plot_moist, plot_LDR, pumpPlotFlag, parseRoot(latest));// читаем файл лога
delay(100);
drawMainMenu(draw); // рисуем главное меню
}
void loop() {
//Выводим время на главный экран -------------------------------
// если прошло 60 секунд
if(millis() - minuteMillis > 60000 && menuState == mainMenu){
realtime = time.gettime("H:i"); // записываем текущее время в переменную
myGLCD.print(realtime , 10, 35); // выводим переменную в главное меню
if (time.Hours == 0 && time.minutes == 0){ // если полночь
drawMainMenu(erase); // стираем главное меню (обновляем график и дату)
zero_out(); // обнуляем массивы графика
drawMainMenu(draw); // рисуем главное меню и пустой график
}
minuteMillis = millis(); // сбрасываем таймер минут
}
if (millis() - backlightMillis > backlight_timer && wakeFlag){ // если пришло время выключить подсветку и она включена
lightOff(); // выключаем подсветку
backlightMillis = millis(); // обновляем таймер
}
//Обновляем влажность в главном меню каждую секунду;
if(millis() - moistureMillis > 1000 && menuState == mainMenu){ //
drawMoisture(moist = readMoisture()); // выводим текущую влажность
moistureMillis = millis(); // обновляем таймер
}
//Насос здесь---------------------------------------------------
//если влажность ниже установленной и насос ещё не сработал и насос не остановлен вручную
if (moist < set_moist && !pump_flag && !halt_pump){
pump_stopped = false; // флаг остановки насоса
drawPumpButton(erase); // стираем кнопку установок насоса
drawStopButton(draw); // рисуем кнопку остановки насоса
pump_flag = true; // флаг работающего насоса
pumpLogFlag = true; // флаг для записи в файл лога
pumpStartMillis = millis(); // обновляем таймер старта насоса
pinMode(PUMP, OUTPUT); // переводим вывод управления насосом в режим выход
digitalWrite(PUMP, HIGH); // устанавливаем вывод насоса в режим логической единицы
}
// если время работы насоса равно уставленному и насос работает и не остановлен или нажата кнопка стоп
if(((millis() - pumpStartMillis > duration*1000) && pump_flag && !pump_stopped) || pump_stop){
pinMode(PUMP, INPUT); // переводим вывод управления насосом в режим вход
drawStopButton(erase); // стираем кнопку стоп
drawPumpButton(draw); // рисуем кнопку установок насоса
pump_stopped = true; // насос остановлен
if(pump_stop){ // если нажата кнопка стоп
pump_flag = false; // сбрасываем флаг насоса
pump_stop = false; // сбрасываем флаг ручной остановки
halt_pump = true; // насос в режиме ручной остановки
}
}
// ручная остановка сбрасывается, если влажность почвы стала выше установленной
if (moist > set_moist) halt_pump = false;
// сбрасываем флаг насоса, если прошло время гистерисиса
if(pump_flag && !pumpHysteresis){ //
pump_flag = false; // Сбрасывем флаг насоса
pumpHysteresis = myPumpHysteresis*3600; // Устанавливаем значение гистерисиса
}
//* Конец насоса -----------------------------------------------
// Логгинг здесь -----------------------------------------------
if((millis() - logMillis) > 1000){ // Если прошла секунда
time.gettime(); // Вызываем часы реального времени
sec = time.Hours*3600 + time.minutes*60 + time.seconds; // Рассчитываем секунды от полуночи
logMillis = millis(); // обновляем таймер
if (pump_flag) pumpHysteresis--; // Если нассос сработал, декрементируем таймер гистерисиса
}
if (!(sec % logInterval) && !mutex) { // если подошёл заданный интервал времени
String file = time.gettime("d-m-y"); // создаём строку "день-месяц-год"
file += ".csv"; // добавляем к ней расширение .csv
saveLog(file); // сохраняем в файл с названием из переменной
if (menuState == mainMenu) updatePlot(); // если в главном меню, обновляем график
}
//* Конец логгинга----------------------------------------------
// Пользлвательский интерфейс здесь-----------------------------
p = ts.getPoint(); // Считываем координаты и интенсивность нажатия на TouchScreen в структуру p
// Возвращаем режим работы выводов:
pinMode(XM, OUTPUT); // Возвращаем режим работы вывода X- в значение «выход» для работы с дисплеем
pinMode(YP, OUTPUT); // Возвращаем режим работы вывода Y+ в значение «выход» для работы с дисплеем
if(millis() - wakeFlagMillis > 100 && wakeFlag && waitFlag){ // Ожидаем 100 миллисекунд после включения подсветки
waitFlag = false; // Сбрасывем флаг ожидания
}
if (p.z > minPress && p.z < maxPress && !wakeFlag){ // Если степень нажатия достаточна для фиксации координат TouchScreen и подсветка выключена
waitFlag = true; // Устанавливаем флаг ожидания
wakeFlagMillis = millis(); // Обновляем таймер ожидания
backlightMillis = millis(); // Обновляем таймер подсветки
lightOn(); // Включаем подсветку
}
if(p.z > minPress && p.z < maxPress && !waitFlag && wakeFlag){ // Если степень нажатия достаточна для фиксации координат TouchScreen, подсветка включена и мы не ждем после её включения
backlightMillis = millis(); // Обновляем таймер подсветки
p.x = map(p.x, tsMinX, tsMaxX, 0, 320); // Преобразуем значение p.x от диапазона tsMinX...tsMaxX, к диапазону 0...320
p.y = map(p.y, tsMinY, tsMaxY, 0, 240); // Преобразуем значение p.y от диапазона tsMinY...tsMaxY, к диапазону 0...240
switch(menuState){ // проверяем состояние меню
// ------------ГЛАВНОЕ МЕНЮ------------ //
case mainMenu: mainMenuFunc(); break;
// -------МЕНЮ УСТАНОВОК ВРЕМЕНИ------- //
case timeMenu: timeMenuFunc(); break;
// -------МЕНЮ УСТАНОВОК ДАТЫ---------- //
case dateMenu: dateMenuFunc(); break;
// ------МЕНЮ УСТАНОВОК ВЛАЖНОСТИ------ //
case moistureMenu: moistureMenuFunc(); break;
// ------МЕНЮ УСТАНОВОК ДЛИТЕЛЬНОСТИ----//
case durationMenu: durationMenuFunc(); break;
// -------------МЕНЮ ГРАФИКА-----------//
case plotMenu: plotMenuFunc(); break;
default: break;
}
}
// Конец пользовательского интерфейса
}
//Функция прорисовки главного меню
void drawMainMenu(bool draw_erase){ // принимаемые значения: true - рисуем, false - стираем
myGLCD.setFont(SmallFont); // устанавливаем шрифт
// рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
myGLCD.print("Time, date:" , 10, 10); // выводим текст на экран
myGLCD.print("Moisture:" , 125, 10); // выводим текст на экран
myGLCD.print("Duration:" , 225, 10); // выводим текст на экран
myGLCD.setFont(BigFont); // устанавливаем шрифт
myGLCD.print(realtime , 10, 35); // выводим время на экран
String m = String(time.month); // записываем текущий месяц в переменную
if (m.toInt() < 10) m = "0" + m; // добавляем ноль, если меньше 10
String d = String(time.day); // записываем текущий день в переменную
if (d.toInt() < 10) d = "0" + d; // добавляем ноль, если меньше 10
myGLCD.print(d + "/" + m , 10, 60); // выводим дату на экран
myGLCD.setFont(SmallFont); // устанавливаем шрифт
myGLCD.print("Cur:", moist_x-32, 39); // выводим текст на экран
myGLCD.print("Set:", moist_x-32, 64); // выводим текст на экран
myGLCD.setFont(BigFont); // устанавливаем шрифт
drawMoisture(readMoisture()); // выводим показания датчика влажности на экран
myGLCD.print(String(set_moist) , moist_x, 60); // выводим установку влажности на экран
drawMenuButton(2,4, draw_erase); // рисуем/стираем кнопку установок времени
drawMenuButton(107,4, draw_erase); // рисуем/стираем кнопку установок
drawPumpButton(draw_erase); // рисуем/стираем кнопку установок
plot(draw_erase); // рисуем/стираем график
}
//Функция прорисовки графика
void plot(bool draw_erase){
// рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
myGLCD.setFont(SmallFont); // устанавливаем шрифт
for (int i = 0; i < 24; i+=2){ //
myGLCD.print(String(i) , plotStartX+5+i*13, 100); // выводим часы графика от 0 до 22
}
myGLCD.print("Plot:" , plotStartX+2, 100-13); // выводим текст на экран
myGLCD.print("Moisture" , plotStartX+7*8, 100-13); // выводим текст на экран
myGLCD.print("Sunlight" , plotStartX+7*8+10*8, 100-13); // выводим текст на экран
myGLCD.print("Pump" , plotStartX+7*8+10*8+10*8, 100-13); // выводим текст на экран
// рисуем? да: цвет графика = цвет графика влажности, нет: цвет графика = цвет фона
draw_erase ? myGLCD.setColor(plot_moisture_color) : myGLCD.setColor(background_color);
myGLCD.print("-" , plotStartX+6*8, 100-13); // выводим текст на экран
// рисуем? да: цвет = цвет графика освещенности, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(plot_ldr_color) : myGLCD.setColor(background_color);
myGLCD.print("-" , plotStartX+6*8+10*8, 100-13); // выводим текст на экран
// рисуем? да: цвет = цвет графика насоса, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(pump_color) : myGLCD.setColor(background_color);
myGLCD.print("*" , plotStartX+6*8+10*8+10*8, 100-13); // выводим текст на экран
// рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(button_color) : myGLCD.setColor(background_color);
for (int i = 12; i < 120; i += 12){ //
myGLCD.drawLine(plotStartX, 235 - i, 314, 235 - i); // рисуем сетку Y
}
for (int i = plotStartX; i < 312; i += 13){ //
myGLCD.drawLine(i, plotStartY, i, 235); // рисуем сетку X
}
for (int x = plotStartX; x < 314; x++) {
int newY = map(plot_LDR[x-plotStartX], 0, 1024, 0, 115); // определяем точку Y датчика освещённости
int newY2 = map(plot_LDR[x-(plotStartX-1)],0,1024,0,115); // определяем точку Y2 датчика освещённости
// рисуем? да: цвет = цвет графика освещенности, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(plot_ldr_color) : myGLCD.setColor(background_color);
if(/*abs(newY - newY2) > 3 && */newY > 0 && newY2 > 0) // если показания датчика не ноль
myGLCD.drawLine(x, 235 - newY, x+1, 235 - newY2); // рисуем линию от Y до Y2
//else myGLCD.drawPixel(x, 235 - newY);
newY = map(plot_moist[x-plotStartX], 0, 100, 0, 115); // определяем точку Y датчика влажности
newY2 = map(plot_moist[x-(plotStartX-1)], 0, 100, 0, 115); // определяем точку Y2 датчика влажности
// рисуем? да: цвет = цвет графика влажности, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(plot_moisture_color) : myGLCD.setColor(background_color);
if(/*abs(newY - newY2) > 3 && */newY > 0 && newY2 > 0) // если показания датчика не ноль
myGLCD.drawLine(x, 235 - newY, x+1, 235 - newY2); // рисуем линию от Y до Y2
//myGLCD.drawPixel(x, 235 - newY); // выводим точку на экран
// рисуем? да: цвет графика = цвет графика насоса, нет: цвет графика = цвет фона
draw_erase ? myGLCD.setColor(pump_color) : myGLCD.setColor(background_color);
if(pumpPlotFlag[x-plotStartX])myGLCD.fillCircle(x,235-newY,2); //рисуем круг, если сработал насос
}
// рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(button_color) : myGLCD.setColor(background_color);
myGLCD.drawRoundRect(plotStartX,plotStartY,314,235); // рисуем рамку графика
myGLCD.drawRoundRect(plotStartX-1,plotStartY-1,315,236); // рисуем рамку графика
myGLCD.setColor(font_color); // устанавливаем цвет обратно для шрифта
myGLCD.setFont(BigFont); // устнанавливаем шрифт
}
//функция прорисовки меню установок длительности работы насоса
void drawDurationMenu(bool draw_erase){ // рисуем меню установок времени работы насоса
uint8_t offset = 20; // общий сдвег элементов меню
// рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
myGLCD.setFont(SmallFont); // устанавливаем шрифт
myGLCD.print("Pump duration menu", 110, 20); // выводим текст на экран
myGLCD.setFont(BigFont); // устанавливаем шрифт
myGLCD.print("Sec", 200, 144); // выводим текст на экран
drawSmallButton(5,5,"<-back"); // вызываем функцию прорисовки кнопки
drawUpButton(offset + 110, 50); // рисуем кнопку вверх
drawDownButton(offset + 110, 150); // рисуем кнопку вниз
drawDigits(String(duration), offset + 110, 97);
}
//функция прорисовки меню установок влажности срабатывания насоса
void drawMoistureMenu(bool draw_erase){
uint8_t offset = 20;
// рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
drawSmallButton(5,5,"<-back"); // вызываем функцию прорисовки кнопки "назад"
myGLCD.setFont(SmallFont); // устанавливаем шрифт
myGLCD.print("Set moisture pump", 110, 5); // выводим текст на экран
myGLCD.print("activation menu",110, 17); // выводим текст на экран
drawUpButton(offset + 110, 50); // рисуем кнопку вверх
drawDownButton(offset + 110, 150); // рисуем кнопку вниз
drawDigits(String(set_moist), offset + 110, 97); // выводим текст на дисплей
}
//функция прорисовки меню установок времени -------------------------------------------------------------------
void drawTimeMenu(bool draw_erase){
int offsetY = 80; // общий сдвег элементов меню
// рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
drawSmallButton(5,5,"<-back"); // вызываем функцию прорисовки кнопки
drawSmallButton(312-95,5,"date->"); // вызываем функцию прорисовки кнопки
drawUpButton(80, offsetY); // рисуем кнопку вверх
drawDownButton(80, offsetY+100); // рисуем кнопку вниз
drawUpButton(160, offsetY); // рисуем кнопку вверх
drawDownButton(160, offsetY+100); // рисуем кнопку вниз
myGLCD.fillCircle(73+75, offsetY+62, 4); // двоеточие
myGLCD.fillCircle(73+75, offsetY+82, 4); // двоеточие
drawDigits(String(time.Hours), 80, offsetY+47); // минуты
drawDigits(String(time.minutes), 80 + 80, offsetY+47); // число
}
//рисуем или стираем меню установок даты --------------------------------------------------------------------
void drawDateMenu(bool draw_erase){
int offsetY = 80; // сдвиг элементов меню по оси Y
// рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
drawSmallButton(5,5,"<-back"); // вызываем функцию прорисовки малой кнопки
drawUpButton(5, offsetY); // рисуем кнопку вверх
drawDownButton(5, offsetY+100); // рисуем кнопку вниз
drawUpButton(90, offsetY); // рисуем кнопку вверх
drawDownButton(90, offsetY+100); // рисуем кнопку вниз
drawUpButton(240, offsetY); // рисуем кнопку вверх
drawDownButton(240, offsetY+100); // рисуем кнопку вниз
drawDigits(String(time.day), 5, offsetY+47); // выводим день
drawDigits(String(time.month), 90, offsetY+47); // выводим месяц
drawDigits(String(20), 180, offsetY+47); // выводим текст на дисплей
myGLCD.drawLine(71, offsetY+47+45, 84, offsetY+50); // рисуем косую черту
myGLCD.drawLine(72, offsetY+47+45, 85, offsetY+50); // рисуем косую черту
myGLCD.drawLine(73, offsetY+47+45, 86, offsetY+50); // рисуем косую черту
drawDigits(String(time.year), 240, offsetY+47); // выводим год
}
// функция прорисовки меню графика
void drawPlotMenu(bool draw_erase){
// рисуем? да: цвет шрифта = цвет шрифта, нет: цвет шрифта = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
myGLCD.setFont(SmallFont); // устнанавливаем шрифт
myGLCD.print(sFiles[currentFile].Name, 120, 50); // Выводим название файла на дисплей
drawSmallButton(108, 5, "home"); // рисуем кнопку "домой"
// если самый старый файл - стираем кнопку
if (plotMenuDay == oldest) myGLCD.setColor(background_color);
drawSmallButton(5, 5, "<-prev"); // рисуем кнопку
// рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
// если самый новый файл - стираем кнопку
if (plotMenuDay == latest) myGLCD.setColor(background_color);
drawSmallButton(width-100, 5, "next->"); // рисуем кнопку
plot(draw_erase); // рисуем график датчиков
}
// функция включения подсветки
void lightOn(){
analogWrite(LED, 196); // устанавливаем значение ШИМ на выводе подсветки
wakeFlag = true; // устанавливаем флаг подсветки
}
// функция выключения подсветки
void lightOff(){
digitalWrite(LED, LOW); // устанавливаем логический ноль на выводе подсветки
wakeFlag = false; // сбрасываем флаг подсветки
}
// функция вывода текущей влажности
void drawMoisture(int moist){
if (moist < 10){ // если число до 10
myGLCD.print(" " + String(moist) , moist_x, 35); // выводим пробел
} else myGLCD.print(String(moist) , moist_x, 35); // выводим показания датчика
}
// функция прорисовки цифр
void drawDigits(String digits, int x, int y){
myGLCD.setFont(SevenSegNumFont); // устанавливаем шрифт
if (digits.toInt() < 10){ // если одна цифра
myGLCD.print("0" + digits, x, y); // добавляем ноль и выводим на дисплей
}
else myGLCD.print(digits, x, y); // выводим цифры на дисплей
}
// функция прорисовки кнопки главного меню
void drawMenuButton(uint16_t x, uint16_t y, bool draw_erase){
// рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(button_color) : myGLCD.setColor(background_color);
myGLCD.drawRoundRect(x,y,x+101,y+82); // выводим прямоугольник
myGLCD.drawRoundRect(x+1,y+1,x+100,y+81); // выводим прямоугольник
// рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(VGA_WHITE) : myGLCD.setColor(background_color);
myGLCD.drawLine(x+1,y+3,x+1,y+80); // выводим линию
myGLCD.drawLine(x+3,y+1,x+98,y+1); // выводим линию
// рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
}
// функция прорисовки малой кнопки
void drawSmallButton(int x, int y, String bName){
myGLCD.drawRoundRect(x, y, x+95, y+40); // выводим прямоугольник
myGLCD.drawRoundRect(x+1, y+1, x+94, y+39); // выводим прямоугольник
myGLCD.setFont(SmallFont); // устанавливаем шрифт
myGLCD.print(bName, x+20, y+15); // выводим название кнопки
}
// функция прорисовки кнопки вверх
void drawUpButton(int x, int y) {
myGLCD.drawRoundRect(x, y, x+65, y+45); // выводим прямоугольник
myGLCD.drawRoundRect(x-1, y-1, x+66, y+46); // выводим прямоугольник
myGLCD.setFont(BigFont); // устанавливаем шрифт
myGLCD.print("UP", x+20, y+15); // выводим название кнопки
}
// функция прорисовки кнопки вниз
void drawDownButton(int x, int y){
myGLCD.drawRoundRect(x, y, x+65, y+45); // выводим прямоугольник
myGLCD.drawRoundRect(x-1, y-1, x+66, y+46); // выводим прямоугольник
myGLCD.print("DOWN", x+1, y+15); // выводим название кнопки
}
// функция чтения файла лога
void readLog(uint8_t* mos_val, uint16_t* ldr_val, bool* pump_log_flag, String logFile) {
zero_out(); // обнуляем массивы графика
mutex = true; // взаимоисключение для SD.open
File myFile = SD.open(logFile); // открываем файл с названием logFile
String value = ""; // создаем пустую переменную строки
byte Byte; // создаем переменную для чтения из байта из файла
uint8_t time_flag = 0; // создаем переменную флагов(часы - флаг=0, минуты - флаг=1 секунды - флаг=2)
uint8_t log_hour, log_minute, log_second; // создаем переменные для хранения часов, минут, секунд
uint32_t log_time; // создаем переменную для вычисления времени взятия пробы в секундах
while(myFile.available()){ // Пока файл не закончился
Byte = myFile.read(); // Читаем байты в переменную
if(Byte == '\n') { // Если символ новой строки
myFile.seek(myFile.position() - 23); // Отматываем на 23 байта назад
while((Byte = myFile.read())!=','){ // Пока не символ запятой
if (Byte != ':'){ // Если не двоеточие
value +=char(Byte); // Накапливаем в строку байты
} else { // Если двоеточие
switch(time_flag){ // проверяем переменную флагов времени
//если флаг равен нулю - записываем час, устанавливаем флаг на минуты
case 0: log_hour = value.toInt(); time_flag = 1; value = ""; break;
//если флаг равен единице - записываем минуты, устанавливаем флаг на секунды
case 1: log_minute = value.toInt(); time_flag = 2; value = ""; break;
default: value = ""; break;
}
}
}
log_second = value.toInt(); // заисывем секунды
//вичисляем время взятия пробы в секундах от 00:00
log_time = (uint32_t)log_hour*3600 + log_minute*60 + log_second;
int elem = map(log_time, 0, 86399, 0, 311); // преобразуем значения секунд к элементам массива
value = ""; // обнуляем строку
time_flag = 0; // обнуляем флаг
while((Byte = myFile.read())!=','){ // пока не символ запятой
value += char(Byte); // накапливаем в строку
} //
mos_val[elem] = value.toInt(); // записываем показания влажности в элемент массива
value = ""; // обнуляем строку
while((Byte = myFile.read())!=','){ // пока не запятая
value += char(Byte); // накапливаем в строку
}
ldr_val[elem] = value.toInt(); // записываем показания освещенности в элемент массива
value = ""; // обнуляем строку
myFile.read(); // считываем байт
Byte = myFile.read(); // записываем байт
char c = char(Byte); // записываем байт как символ
int i = c - '0'; // приводим символ к значению целого числа
pump_log_flag[elem] = bool(i); // записываем значение в массив
while((Byte = myFile.read())!='\n'); // ждём конца строки
}
}
myFile.close(); // закрываем файл
mutex = false; // сбрасывем взаимоисключение для SD.open
}
// функция чтения датчика влажности
int readMoisture() {
pinMode(MOIST_SENSOR, INPUT); // Устанавливаем вывод датчика в режим входа
uint16_t fluct = 0; // Создаём переменную для суммы 10 считываений с датчика
for (int i = 0; i < 10; i++){ // Считываем с датчика 10 раз
fluct += analogRead(MOIST_SENSOR); // при жтом суммируя показания в переменную
} //
fluct /= 10; // Делим переменную на 10 для вычисления среднего арифметического значения
// приводим показания датчки к диапазону от 0 до 100
uint8_t m = map(fluct, sensor_moist_max, sensor_moist_min, 0, 100);
if (m >= 100) m = 99; // ограничиваем верхнее показание датчика
return m; // возвращаем значение
}
// функция чтения настроек
void readSettings(){ //
duration = EEPROM.read(0); // читаем значение настроек из ПЗУ
set_moist = EEPROM.read(sizeof(duration)); // читаем значение настроек из ПЗУ
}
// функция сохранения настроек
void saveSettings(){ //
EEPROM.update(0, duration); // записываем значение настроек в ПЗУ
EEPROM.update(sizeof(duration), set_moist); // записываем значение настроек в ПЗУ
}
// функция сохранения файла лога
void saveLog(String fileName){
mutex = true; // устанавливаем взаимоисключение для SD.open()
String REAL_TIME = time.gettime("d-m-Y, H:i:s"); // записываем значение, считанное с часов реального времени, в переменную REAL_TIME
int ldr = analogRead(LDR); // записываем показания датчика освещённости
String ldr_str = String(ldr); // конвертируем в строку
if (ldr < 10) ldr_str = "0" + ldr_str; // добавляем ноль
if (ldr < 100) ldr_str = "0" + ldr_str; // добавляем ноль
if (ldr < 1000) ldr_str = "0" + ldr_str; // добавляем ноль
File myFile = SD.open(fileName, FILE_WRITE); // создаём или открываем файл "d-m-y.csv" для записи значений
myFile.print(REAL_TIME); // записываем переменную в файл
myFile.print(", "); // записываем разделительный символ
myFile.print(moist); // записываем переменную в файл
myFile.print(", "); // записываем разделительный символ
myFile.print(ldr_str); // записываем переменную в файл
myFile.print(", "); // записываем разделительный символ
myFile.println(pumpLogFlag); // записываем переменную в файл
myFile.flush(); // записываем буфер в файл
myFile.close(); // закрываем файл
pumpLogFlag = false; // сбрасывем флаг насоса
mutex = false; // сбрасываем взщаимоисключение для SD.open()
}
// функция нажатия кнопок
bool buttonPress(int button) {
uint8_t offset = 20;
switch (button) {
case back: if (p.x < 100 && p.y < 45) return true; else return false;
case date: if (p.x > 217 && p.y < 45) return true; else return false;
case moist_up: if (p.x > 110 + offset && p.x < 205 + offset && p.y > 50 && p.y < 95) return true; else return false;
case moist_down: if (p.x > 110 + offset && p.x < 205 + offset && p.y > 150 && p.y < 200) return true; else return false;
case up1: if (p.x > 5 && p.x < 50 && p.y > 80 && p.y < 125) return true; else return false;
case up2: if (p.x > 80 && p.x < 80+65 && p.y > 80 && p.y < 125) return true; else return false;
case up3: if (p.x > 160 && p.x < 160+65 && p.y > 80 && p.y < 125) return true; else return false;
case up4: if (p.x > 240 && p.x < 240+65 && p.y > 80 && p.y < 125) return true; else return false;
case down1: if (p.x > 5 && p.x < 50 && p.y > 180 && p.y < 225) return true; else return false;
case down2: if (p.x > 80 && p.x < 80+65 && p.y > 180 && p.y < 225) return true; else return false;
case down3: if (p.x > 160 && p.x < 160+65 && p.y > 180 && p.y < 225) return true; else return false;
case down4: if (p.x > 240 && p.x < 240+65 && p.y > 180 && p.y < 225) return true; else return false;
case mainDuration: if (p.x > 210 && p.y < 90) return true; else return false;
case mainMoisture: if (p.x > 120 && p.x < 210 && p.y < 90) return true; else return false;
case mainTime: if (p.x > 5 && p.x < 120 && p.y < 90) return true; else return false;
case mainPlot: if (p.x > 5 && p.y > 120) return true; else return false;
case plotPrev: if (p.x < 100 && p.y < 45) return true; else return false;
case plotNext: if (p.x > width-45-95 && p.y < 45) return true; else return false;
default: return false;
}
}
// функция меню даты
void dateMenuFunc(){
int8_t d = time.day; // записываем текущий день в переменную
int8_t m = time.month; // записываем текущий месяц в переменную
int8_t y = time.year; // записываем текущий год в переменную
if (buttonPress(back)){ // если нажата кнопка назад
drawDateMenu(erase); // стираем меню настроек
drawTimeMenu(draw); // рисуем главное меню
menuState = timeMenu; // меняем переменную состояния меню
} else if (buttonPress(up1)) { // если нажата кнопка день вверх
d++; // приращиваем день
switch (m){ // проверяем кол-во дней в месяце
case 1: //
case 3: //
case 5: //
case 7: //
case 8: //
case 10: //
case 12: if(d == 32) d = 1; break; // если январь, март, май, июль, август... ограничиваем до 31
case 4: //
case 6: //
case 9: //
case 11: if(d == 31) d = 1; break; // если февраль, апрель, июнь... ограничиваем до 30
case 2: if(d == 28) d = 1; break; // TODO: здесь можно добавить високосный год (year%4 ==0 && (year % 100 != 0 || year % 400 == 0))
default: break;
}
time.settime(-1, -1, -1, d); //
drawDigits(String(time.day), 5, 80+47); //
} else if (buttonPress(down1)) { // если нажата кнопка день вниз
d--; //
switch (m){ // проверяем кол-во дней в месяце
case 1: //
case 3: //
case 5: //
case 7: //
case 8: //
case 10: //
case 12: if(d == 0) d = 31; break; // если январь, март, май, июль, август... ограничиваем до 31
case 4: //
case 6: //
case 9: //
case 11: if(d == 0) d = 30; break; // если февраль, апрель, июнь... ограничиваем до 30
case 2: if(d == 0) d = 28; break; // TODO: здесь можно добавить високосный год (year%4 ==0 && (year % 100 != 0 || year % 400 == 0))
default: break;
}
time.settime(-1, -1, -1, d);
drawDigits(String(time.day), 5, 80+47);
} else if (buttonPress(up2)) { // если кнопка месяц вверх
m++; // приращиваем месяц
if (m == 13) m = 1; // ограничиваем месяцы до 12
time.settime(-1, -1, -1, -1, m); // устанавливаем месяц в часы реального времени
drawDigits(String(time.month), 90, 80+47); // выводим текст на экран
} else if (buttonPress(down2)){ // кнопка месяц вниз
m--; // уменьшаем месяц
if (m == 0) m = 12; // ограничиваем месяцы до 1
time.settime(-1, -1, -1, -1, m); // устанавливаем месяц в часы реального времени
drawDigits(String(time.month), 90, 80+47); // выводим текст на экран
} else if (buttonPress(up4)){ // если кнопка год вверх
y++; // приращиваем год
if (y == 100) y = 0; // ограничиваем год до 2099
time.settime(-1, -1, -1, -1, -1, y); // устанавливаем год в часы реального времени
drawDigits(String(time.year), 240, 80+47); // выводим текст на экран
} else if (buttonPress(down4)){ // кнопка год вниз
y--; // уменьшаем год
if (m == -1) m = 99; // оганичиваем до 2000
time.settime(-1, -1, -1, -1, -1, y); // устанавливаем год в часы реального времени
drawDigits(String(time.year), 240, 80+47); // выводим текст на экран
}
}
// функция меню длительности работы насоса
void durationMenuFunc() {
if (buttonPress(back)) { // если нажата кнопка назад
drawDurationMenu(erase); // стираем текущее меню
drawMainMenu(draw); // рисуем главное меню
menuState = mainMenu; // обновляем переменную состояний меню
saveSettings(); // сохраняем настройки
} else if (buttonPress(moist_up)) { // если кнопка вверх
if (duration < duration_max) duration++; // приращиваем переменную
drawDigits(String(duration), 110+20, 97); // обновляем её на жкране
} else if (buttonPress(moist_down)) { // если кнопка вних
if (duration > duration_min) duration--; // уменьшаем перменную
drawDigits(String(duration), 110+20, 97); // обновляем её на экране
}
}
// функция меню установок влажности
void moistureMenuFunc() {
if (buttonPress(back)) { // если нажата кнопка назад
drawMoistureMenu(erase); // стираем текущее меню
drawMainMenu(draw); // рисуем главное меню
menuState = mainMenu; // обновляем переменную состояний меню
saveSettings(); // сохраняем настройки
} else if (buttonPress(moist_up)) { // если кнопка вверх
if (set_moist < set_moist_max) set_moist++; // приращиваем переменную
drawDigits(String(set_moist), 110+20, 97); // обновляем её на жкране
} else if (buttonPress(moist_down)){ // если кнопка вних
if (set_moist > set_moist_min) set_moist--; // уменьшаем перменную
drawDigits(String(set_moist), 110+20, 97); // обновляем её на экране
}
}
// функция меню установок часов реального времени
void timeMenuFunc(){
int8_t H = time.Hours; // создаём переменную и записываем текущие часы
int8_t i = time.minutes; // создаём переменную и записываем текущие минуты
if (buttonPress(back)){ // если нажата кнопка назад
realtime = time.gettime("H:i"); // обновляем переменную часов реального времени
drawTimeMenu(erase); // стираем текущее меню
drawMainMenu(draw); // рисуем главное меню
menuState = mainMenu; // обновляем переменную состояний меню
} else if (buttonPress(date)){ // если нажата кнопка даты
time.settime(-1, i, H); // записываем время
drawTimeMenu(erase); // стираем текущее меню
drawDateMenu(draw); // рисуем главное меню
menuState = dateMenu; // обновляем переменную состояний меню
} else if(buttonPress(up2)){ // если нажата кнопка часы вверх
H++; // прибавляем часы
if (H == 24) H = 0; // ограничиваем часы до 23
time.settime(-1, -1, H); // устанавливаем новое время
drawDigits(String(time.Hours), 80, 80+47); // выводим на экран
} else if(buttonPress(down2)){ // если нажата кнопка часы вниз
H--; // убавляем часы
if (H == -1) H = 23; // ограничиваем часы до 00
time.settime(-1, -1, H); // устанавливаем новое время
drawDigits(String(time.Hours), 80, 80+47); // выводим на экран
} else if(buttonPress(up3)){ // если нажата кнопка минуты вверх
i++; // прибавляем
if (i == 60) i = 0; // ограничиваем часы до 23
time.settime(-1, i, -1); // устанавливаем новое время
drawDigits(String(time.minutes), 160, 80+47); // выводим на экран
} else if(buttonPress(down3)){ // если нажата кнопка минуты вниз
i--; // убавляем
if (i == -1) i = 59; // ограничиваем часы до 00
time.settime(-1, i, -1); // устанавливаем новое время
drawDigits(String(time.minutes), 160, 80+47); // выводим на экран
}
}
// функция главного меню
void mainMenuFunc(){
if (buttonPress(mainDuration) && !pump_flag){ // если нажата кнопка установок насса
drawMainMenu(erase); // стираем главное меню
drawDurationMenu(draw); // рисуем меню установок работы насоса
menuState = durationMenu; // меняем перменную состояний меню
} else if(buttonPress(mainDuration) && pump_flag){ // если нажата кнопка установок длительности работы насоса и насос работает
pump_stop = true; // экстренная остановка насоса
} else if (buttonPress(mainMoisture)) { // если нажата кнопка установки влажности срабатывания насоса
drawMainMenu(erase); // стираем главное меню
drawMoistureMenu(draw); // рисуем меню установок влажности
menuState = moistureMenu; // меняем переменную состояний меню
} else if(buttonPress(mainTime)) { // если нажата кнопка установок времени
drawMainMenu(erase); // стираем главное меню
drawTimeMenu(draw); // рисуем меню установок времени
menuState = timeMenu; // обновляем переменную состояний меню
} else if (buttonPress(mainPlot)){ // если нажата кнопка меню просмотра графика
drawMainMenu(erase); // стираем главное меню
drawPlotMenu(draw); // рисуем меню графика
menuState = plotMenu; // обновляем переменную состояний меню
}
}
// функция меню графика
void plotMenuFunc(){
if (buttonPress(mainMoisture)){ // если нажата кнопка "домой"
drawPlotMenu(erase); // стираем график
readLog(plot_moist,plot_LDR,pumpPlotFlag,parseRoot(latest));// читаем последний файл лога
drawMainMenu(draw); // рисуем главное меню
menuState = mainMenu; // меняем состояние меню
} else if (buttonPress(plotNext) && plotMenuDay != latest){ // если нажата кнопка следующего файла и мы просматриваем не самый последний файл
drawPlotMenu(erase); // стираем текущий график
readLog(plot_moist,plot_LDR,pumpPlotFlag,parseRoot(next)); // читаем следующий файл
drawPlotMenu(draw); // рисуем новый график
} else if (buttonPress(plotPrev) && plotMenuDay != oldest){ // если нажата кнопка предыдущего файла и мы просматриваем не самый старый файл
drawPlotMenu(erase); // стираем текущий график
readLog(plot_moist,plot_LDR,pumpPlotFlag,parseRoot(prev)); // читаем предыдущий файл
drawPlotMenu(draw); // рисуем новый график
}
}
// функция парсирования корневой директории
String parseRoot(int8_t action) {
File dir = SD.open("/"); // открываем корневой каталог карты
File entry; // создаем объект entry
byte buf[12]; // создаём буффер для названия файла (8+3)
int nFiles = 0; // переменная для хранения кол-ва файлов
int j = 0; // переменная-итератор
uint32_t Day, Month, Year; // перемнные для хранения даты
while(true) { // Начинаем цикл
entry = dir.openNextFile(); // Открываем следующий файл,
if (!entry) { // если файлов не осталось
dir.rewindDirectory(); // указатель на начало дериктории
break; // выходим из цикла.
} else if (!(entry.isDirectory())) { // Если файл не является директорией
nFiles++; // записываем кол-во файлов
} //
entry.close(); // закрываем файл
} //
sFiles = new files[nFiles]; // выделяем память для структуры sFiles
int i = 0; // создаём переменную-итератор
while(true) { // входим в цикл
entry = dir.openNextFile(); // открываем файл
String check_name = entry.name(); // записываем название файла в строку
if (!entry){ // если файлов не осталось
dir.rewindDirectory(); // "перематываем" директорию
break; // выходим из цикла
} else if(!(entry.isDirectory()) && check_name.toInt()!=0){ // если файл не директория и имя файла содержит цифры
sFiles[i].Name = entry.name(); // записываем названия файлов в переменные
sFiles[i].Name.getBytes(buf, 12); // берем 12 байт названия в буфер
uint8_t date_flag = 0; // флаг для вычисления даты
String s = ""; // переменная для сбора чисел без символа дефиса
int k, j; // итераторы
for (k = 0, j = 0; k < 8 || j < 6; k++){ // запускаем цикл
if(buf[k] != '-') { // если не дефис:
s += char(buf[k]); // накапливаем в строку всё, кроме дефиса
j++; // приращиваем j
} else { // если дефис:
switch(date_flag){ // проверяем флаг даты
// если флаг 0, записываем день, устанавливаем флаг равным 1
case 0: Day = s.toInt(); date_flag = 1; s = ""; break;
// если флаг 1, записываем месяц, устанавливаем флаг равным 2
case 1: Month = s.toInt(); date_flag = 2; s = ""; break;
default: s = ""; break; // обнуляем строку
}
}
}
Year = s.toInt(); // записываем год
s = ""; // обнуляем строку
date_flag = 0; // обнуляем флаг даты
sFiles[i].ints = Year*10000 + Month*100 + Day; // записываем в структуру;
j = 0; // сбрасываем итератор j
i++; // приращиваем итератор i
}
entry.close(); // закрываем файл
} //
dir.close(); // закрываем корневую директорию
mysort(sFiles, 0, nFiles-1); // сортируем файлы по дате
switch(action){ // проверяем переданную переменную
case latest: currentFile = nFiles-1; break; // последний файл
case next: if(currentFile < nFiles-1) currentFile++; break; // следующий файл
case prev: if(currentFile > 0) currentFile--; break; // предыдущий файл
default: break; //
} //
if (currentFile == 0) plotMenuDay = oldest; // если текущий файл - это начало массива, значит мы на самом старом файле
// если текущий файл больше нуля и меньше размера массива - мы не на самом новом файле и не на самом старом
else if (currentFile > 0 && currentFile < nFiles-1) plotMenuDay = between;
else if (currentFile == nFiles-1) plotMenuDay = latest; // если текущий файл - равен размеру массива - мы на самом новом файле
return sFiles[currentFile].Name; // возвращаем текущий файл
}
// функция перестановки элементов для mysort, в нашем случае для структуры files
void myswap(files f[], int i, int j) { //
files temp; //
temp = f[i]; //
f[i] = f[j]; //
f[j] = temp; //
} //
// функция быстрой сортировки //
void mysort(files f[], int left, int right){ //
int i, last; // объявляем итераторы
if (left >= right) // если мы перешли через точку сортировки -
return; // возвращаемся из рекурсии
myswap(f, left, (left+right)/2); // переставляем элеметы, точка сортировки - середина массива
last = left; // записываем номер элемента
for (i = left+1; i<= right; i++) // обходим массив слева направо
if (f[i].ints < f[left].ints) // если текущий элемент меньше левого
myswap(f, ++last, i); // переставляем местами и приащиваем last
myswap(f, left, last); // переставляем местами левый и последний
mysort(f, left, last-1); // функция вызввает сама себя для уменьшенного массива слева
mysort(f, last+1, right); // функция вызввает сама себя для уменьшенного массива справа
} //
// функция обнуления массивов //
void zero_out(){ //
mutex = true; //
memset(plot_moist, 0, sizeof(plot_moist)); // обнуляем массив датчика вляжности
memset(plot_LDR, 0, sizeof(plot_LDR)); // обнуляем массив датчика освещённости
memset(pumpPlotFlag, false, sizeof(pumpPlotFlag)); // обнуляем массив срабатывания насоса
mutex = false; //
}
// функция прорисовки кнопки насоса
void drawPumpButton(bool draw_erase){
// рисуем? да: цвет = цвет шрифта, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(font_color) : myGLCD.setColor(background_color);
myGLCD.print(String(duration) + "s" , 260, 35); // выводим текст на экран
drawMenuButton(213,4, draw_erase); // рисуем кноку
}
// функция прорисовки кнопки стоп
void drawStopButton(bool draw_erase){
// рисуем? да: цвет = цвет кнопки, нет: цвет = цвет фона
draw_erase ? myGLCD.setColor(stopButtonColor) : myGLCD.setColor(background_color);
myGLCD.print("Stop!" , 240, 35); // выводим текст на экран
int x = 213; // координата х для кнопки
int y = 4; // координата у для кнопки
myGLCD.drawRoundRect(x,y,x+101,y+82); // рисуем квадрат
myGLCD.drawRoundRect(x+1,y+1,x+100,y+81); // рисуем квадрат
myGLCD.setColor(font_color); // устанавливаем цвет
}
// функция обновления текущего графика
void updatePlot(){
// вычисляем кол-во секунд от 00:00
uint32_t now = (uint32_t)time.Hours*3600+time.minutes*60+time.seconds;
int i = map(now, 0, 86399, 0, 311); // приводим значения секунд к номеру элемента массива
plot(erase); // стираем график
plot_LDR[i] = analogRead(LDR); // считываем показания с датчика
plot_moist[i] = readMoisture(); // считываем показания с датчика
pumpPlotFlag[i] = pumpLogFlag; // записываем флаг насоса в массив
plot(draw); // рисуем график
}
Описание функций:
Функции draw....Menu()
- Назначение: прорисовка или стирание пикселей меню;
- Синтаксис: draw....Menu(true/false);
- Параметры: true - рисуем, false - стираем
- Возвращаемые значения: нет;
- Примечание: ....= Main, Duration, Moisture, etc.
Функция plot()
- Назначение: прорисовка или стирание пикселей графиков;
- Синтаксис: plot(true/false);
- Параметры: true - рисуем, false - стираем
- Возвращаемые значения: нет;
- Примечание: функция берет данные из массивов pumpPlotFlag, plot_LDR и plot_moist.
Функции draw....Button()
- Назначение: прорисовка или стирание пикселей кнопок;
- Синтаксис: draw....Button(int x, int y, [bool draw_erase, String bName]);
- Параметры: x - координаты х, y - координаты y (в некоторых функциях bName - название кнопки, draw_erase - рисуеи/стираем)
- Возвращаемые значения: нет;
- Примечание: ....= Small, Up, Down, etc.
Функция saveLog()
- Назначение: сохранеие текущих показаний датчиков;
- Синтаксис: saveLog(String fileName);
- Параметры: fileName - название файла для записи в него данных;
- Возвращаемые значения: нет;
- Примечание: функция записывает строку формата "ДД-ММ-ГГГГ, ЧЧ:ММ:СС, ВВ, ОООО, Ф" с последующим символом новой строки и возврата каретки.
- ДД - день месяца;
- ММ - месяц;
- ГГГГ - год;
- ЧЧ - часы от 0 до 23;
- ММ - минуты;
- СС - секунды;
- ВВ - двухзначные показания датчика влажности;
- ОООО - четырёхзначные показания датчика освещённости;
- Ф - флаг сработавшего насоса: 1, 0;
Функция buttonPress()
- Назначение: поиск файлов в корневом каталоге ;
- Синтаксис: if (buttonPress(int botton)){//};
- Параметры: значение кнопки.
- Возвращаемые значения: bool - кнопка нажата/не нажата (true/false);
- Примечание: возможные значения - back, datem moist_up, moist_down, up1(2,3,4), down1(2,3,4), mainDuration, mainMoisture, mainTime, mainPlot, plotPrev, plotNext.
Функция ....MenuFunc()
- Назначение: логика конкретного меню;
- Синтаксис: ....MenuFunc();
- Параметры: нет;
- Возвращаемые значения: нет;
- Примечание: ....= main, plotm, time, etc.
Функция zero_out()
- Назначение: функция обнуления массивов графика;
- Синтаксис: zeroOut();
- Параметры: нет;
- Возвращаемые значения: нет;
- Примечание: обнуляет массивы plot_moist, plot_LDR, pumpPlotFlag
Функция updatePlot()
- Назначение: обновляет текущий график текущими значениями;
- Синтаксис: updatePlot();
- Параметры: нет;
- Возвращаемые значения: нет;
Функция parseRoot()
- Назначение: поиск файлов в корневом каталоге ;
- Синтаксис: filename = parseRoot(latest);
- Параметры: int8_t action: 0 - самый новый файл, -1 - предыдущий файл, 1 - следующий файл.
- Возвращаемые значения: String - название файла;
- Примечание:
- Функция ищет файлы формата ЧЧ-ММ-ГГ.csv, где дата создания файла является именем файла;
- Файл, в имени которого отсутствуют числа или числа равны нулю будут проигнорированны функцией;
- Функция вызывает функцию mySort(), для сортировки структуры с названиями файлов;
Функция readLog()
- Назначение: Функция чтения и данных из файла;
- Синтаксис: readLog(uint8_t* mos_val, uint16_t* ldr_val, bool* pump_log_flag, String logFile);
- Параметры: mos_val - указатель на массив со значениямаи датчика влажности, ldr_val - указатель на массив со значениями датчика освещенности, pump_log_flag - указатель на массив флага срабатывания насоса, logFile - названия файла для парсирования;
- Возвращаемые значения: нет;
- Примечание:
- Функция записывает значения в массив преобразуя секунды от 0 (00:00) до 86399 (23:59) в итераторы массива от 0 до 311;
- Функция берёт время взятия пробы из файла;
Функция mySort()
- Назначение: функция сортировки, в данном случае структуры files;
- Синтаксис: mySort(files f[], int i, int j);
- Параметры: f[] - структура для сортировки, i - начало массива (или под-массива), j - конец массива (или под-массива);
- Примечание:
- Функция делит массив пополам и ищет слева элемент по значению больший чем средний, а справа меньше чем средний, затем меняет их местами. Затем рекурсивно вызывает себя для под-массивов слева и справа от среднего элемента;
- Функция вызывает функцию mySwap, для перестановки элементов местами;
- Функция является упращённой версией qsort() из книги Б. Кернигган, Д. Ритчи "Язык программирования C, второе издание", стр. 100;

Обсуждение