/*
Скетч к проекту "Домашняя метеостанция"
Страница проекта (схемы, описания): https://alexgyver.ru/meteoclock/
Исходники на GitHub: https://github.com/AlexGyver/MeteoClock
Нравится, как написан и закомментирован код? Поддержи автора! https://alexgyver.ru/support_alex/
Автор: AlexGyver Technologies, 2018
http://AlexGyver.ru/
*/
/*
Время и дата устанавливаются атвоматически при загрузке прошивки (такие как на компьютере)
График всех величин за час и за сутки (усреднённые за каждый час)
В модуле реального времени стоит батарейка, которая продолжает отсчёт времени после выключения/сброса питания
Как настроить время на часах. У нас есть возможность автоматически установить время на время загрузки прошивки, поэтому:
- Ставим настройку RESET_CLOCK на 1
- Прошиваемся
- Сразу ставим RESET_CLOCK 0
- И прошиваемся ещё раз
- Всё
*/
/* Версия 1.3
- Исправлен прогноз погоды
*/
// ------------------------- НАСТРОЙКИ --------------------
#define DISPLAY_TYPE 1 // тип дисплея: 1 - 2004 (большой), 0 - 1602 (маленький)
#define DISPLAY_ADDR 0x27 // адрес платы дисплея: 0x27 или 0x3f. Если дисплей не работает - смени адрес! На самом дисплее адрес не указан
#define WEEK_LANG 0
// пределы отображения для графиков
// #define TEMP_MIN 15
// #define TEMP_MAX 35
// #define HUM_MIN 0
// #define HUM_MAX 100
// #define PRESS_MIN -100
// #define PRESS_MAX 100
// #define CO2_MIN 300
// #define CO2_MAX 2000
#define EE_DATA_ADDR 1 // Адрес хранения настроек
#define EE_KEY_ADDR 0 // Адрес Хранения ключа ЕЕПРОМ
#define EE_KEY 0xBE // Ключ
// пины энкодеров энкодер МЕНЮ
#define CLK1 3
#define DT1 4
#define SW1 2
//На плате разведено
// #define CLK1 10
// #define DT1 11
// #define SW1 12
// пины энкодеров энкодер Громкости
#define CLK2 6
#define DT2 7
#define SW2 5
//На плате разведено
// #define CLK2 2
// #define DT2 3
// #define SW2 4
// библиотеки
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "GyverEncoder.h"
#include <avr/pgmspace.h>
#include <EEPROM.h>
#if (DISPLAY_TYPE == 1)
LiquidCrystal_I2C lcd(DISPLAY_ADDR, 20, 4);
#else
LiquidCrystal_I2C lcd(DISPLAY_ADDR, 16, 2);
#endif
Encoder enc(CLK1, DT1, SW1); // "Энкодер меню и приемника"
Encoder vol(CLK2, DT2, SW2); // "Энкодер громкости"
#include "RTClib.h"
RTC_DS3231 rtc;
DateTime now;
// Структура с переменными - параметрами звука
struct {
byte volume = 0;
byte treble = 0;
byte bass = 0;
bool input = false;
bool mute = false;
uint8_t ind_st = 5; // Индекс радиостанции
} data;
#include <GyverTimer.h>
//GTimer_ms sensorsTimer(SENS_TIME);
//GTimer_ms drawSensorsTimer(SENS_TIME);
GTimer_ms clockTimer(500);
bool backlight = false; // флаг подсветки дисплея
bool selected = false; // Флаг для меню
byte menu_pointer = 0; // Указатель меню
bool save_requested = false; // Флаг запроса ЕЕПРОМ
uint32_t request_time = 0; // Время последнего запроса
#define INPUT1_NAME "AUX INPUT" // Названия для селектора
#define INPUT2_NAME "BLUETOOTH"
#define INPUT3_NAME "TUNER"
// Структура настройки радиостанций
struct RadioStation {
const int freq;
const char StationName[30];
};
//char NameStan = "abc";
const RadioStation Radio[19] PROGMEM =
{
{877,"Lux FM"},// частоту умножить на 100
{889,"Radio City"},
{896,"Business FM"},
{913,"Radio DACHA"},
{917,"Eldorado"},
{1002,"Dala FM"},
{1010,"Kaz Radio"},
{1014,"Ghuldyz FM"},
{1018,"Tvoy Duman"},
{1022,"Energy FM"},
{1028,"Radio Classic"},
{1035,"Love Radio"},
{1040,"Monte Carlo"},
{1047,"Russian Asia"},
{1054,"AvtoRadio"},
{1060,"Radio NS"},
{1065,"Radio Shalkar"},
{1070,"Europa +"},
{1075,"Retro FM"}
};
String stName ;
int freq ;
int8_t hrs, mins, secs;
byte mode = 0;
/*
0 часы и данные
1 график температуры за час
2 график температуры за сутки
3 график влажности за час
4 график влажности за сутки
5 график давления за час
6 график давления за сутки
7 график углекислого за час
8 график углекислого за сутки
*/
// переменные для вывода
// float dispTemp;
// byte dispHum;
// int dispPres;
// int dispCO2;
// int dispRain;
// // массивы графиков
// int tempHour[15], tempDay[15];
// int humHour[15], humDay[15];
// int pressHour[15], pressDay[15];
// int co2Hour[15], co2Day[15];
// int delta;
// uint32_t pressure_array[6];
// uint32_t sumX, sumY, sumX2, sumXY;
// float a, b;
// byte time_array[6];
// символы
// график
/*
byte row8[8] = {0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte row7[8] = {0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte row6[8] = {0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte row5[8] = {0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte row4[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111};
byte row3[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111};
byte row2[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111};
byte row1[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111};
*/
// цифры
uint8_t LT[8] = {0b00111, 0b01111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
uint8_t UB[8] = {0b11111, 0b11111, 0b11111, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000};
uint8_t RT[8] = {0b11100, 0b11110, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
uint8_t LL[8] = {0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b01111, 0b00111};
uint8_t LB[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111};
uint8_t LR[8] = {0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11110, 0b11100};
uint8_t UMB[8] = {0b11111, 0b11111, 0b11111, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111};
uint8_t LMB[8] = {0b11111, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111};
void drawDig(byte dig, byte x, byte y) {
switch (dig) {
case 0:
lcd.setCursor(x, y); // set cursor to column 0, line 0 (first row)
lcd.write(0); // call each segment to create
lcd.write(1); // top half of the number
lcd.write(2);
lcd.setCursor(x, y + 1); // set cursor to colum 0, line 1 (second row)
lcd.write(3); // call each segment to create
lcd.write(4); // bottom half of the number
lcd.write(5);
break;
case 1:
lcd.setCursor(x + 2, y);
// lcd.write(1);
lcd.write(0);
lcd.setCursor(x + 2, y + 1);
lcd.write(255);
break;
case 2:
lcd.setCursor(x, y);
lcd.write(6);
lcd.write(6);
lcd.write(2);
lcd.setCursor(x, y + 1);
lcd.write(3);
lcd.write(7);
lcd.write(7);
break;
case 3:
lcd.setCursor(x, y);
lcd.write(6);
lcd.write(6);
lcd.write(2);
lcd.setCursor(x, y + 1);
lcd.write(7);
lcd.write(7);
lcd.write(5);
break;
case 4:
lcd.setCursor(x, y);
lcd.write(3);
lcd.write(4);
lcd.write(2);
lcd.setCursor(x + 2, y + 1);
lcd.write(5);
break;
case 5:
lcd.setCursor(x, y);
lcd.write(0);
lcd.write(6);
lcd.write(6);
lcd.setCursor(x, y + 1);
lcd.write(7);
lcd.write(7);
lcd.write(5);
break;
case 6:
lcd.setCursor(x, y);
lcd.write(0);
lcd.write(6);
lcd.write(6);
lcd.setCursor(x, y + 1);
lcd.write(3);
lcd.write(7);
lcd.write(5);
break;
case 7:
lcd.setCursor(x, y);
lcd.write(1);
lcd.write(1);
lcd.write(2);
lcd.setCursor(x + 1, y + 1);
lcd.write(0);
break;
case 8:
lcd.setCursor(x, y);
lcd.write(0);
lcd.write(6);
lcd.write(2);
lcd.setCursor(x, y + 1);
lcd.write(3);
lcd.write(7);
lcd.write(5);
break;
case 9:
lcd.setCursor(x, y);
lcd.write(0);
lcd.write(6);
lcd.write(2);
lcd.setCursor(x + 1, y + 1);
lcd.write(4);
lcd.write(5);
break;
case 10:
lcd.setCursor(x, y);
lcd.write(32);
lcd.write(32);
lcd.write(32);
lcd.setCursor(x, y + 1);
lcd.write(32);
lcd.write(32);
lcd.write(32);
break;
}
}
void drawdots(byte x, byte y, boolean state) {
byte code;
if (state) code = 46;
else code = 32;
// lcd.setCursor(x, y);
// lcd.write(code);
lcd.setCursor(x, y + 1);
lcd.write(code);
}
void drawRadio(uint16_t freq, char *st_name, byte x, byte y, boolean fm_mode) {
lcd.setCursor (0,0);
lcd.print(" ");
lcd.setCursor (0,0);
lcd.print(st_name);
// чисти чисти!
lcd.setCursor(x+1, y);
lcd.print(" ");
lcd.setCursor(x+1, y + 1);
lcd.print(" ");
int freq_1000 = freq/1000 ;
int freq_100 = (freq % 1000)/100;
int freq_10 = (freq % 100)/10;
int freq_1 = (freq % 10);
//if (hours > 23 || minutes > 59) return;
if (freq / 1000 == 0) drawDig(10, x, y);
else drawDig(freq/1000, x, y);
drawDig((freq % 1000)/100 , x + 4, y);
// тут должны быть точки. Отдельной функцией
drawdots(x+11, y, 1);
drawDig((freq % 100)/10, x + 8, y);
drawDig(freq % 10, x + 12, y);
if (fm_mode) {
lcd.setCursor(x+16, y);
lcd.print("St");
}
else{
lcd.setCursor(x+16,y);
lcd.print(" ");
};
lcd.setCursor(x+16, y+1);
lcd.print("MHz");
}
#if (WEEK_LANG == 0)
static const char *dayNames[] = {
"Sund",
"Mond",
"Tues",
"Wedn",
"Thur",
"Frid",
"Satu",
};
#else
static const char *dayNames[] = {
"BOCK",
"POND",
"BTOP",
"CPED",
"4ETB",
"5YAT",
"CYBB",
};
#endif
void loadDigit() {
lcd.createChar(0, LT);
lcd.createChar(1, UB);
lcd.createChar(2, RT);
lcd.createChar(3, LL);
lcd.createChar(4, LB);
lcd.createChar(5, LR);
lcd.createChar(6, UMB);
lcd.createChar(7, LMB);
}
void printValue(uint8_t data) { // Вывод числа с атрибутами min/max
lcd.setCursor(17, 3);
if (data > 99) lcd.print(F("MAX"));
else if (data < 1) lcd.print(F("MIN"));
else { if (data < 10 ) lcd.print(" ");
lcd.print(data);
lcd.print(F("%"));
}
}
void printBar (uint8_t data) {
lcd.setCursor(4,3);
lcd.print(" ");
int vol1 = map(data, 0, 100, 4, 16);
for(int i=4;i<vol1;i++) {
lcd.setCursor(i,3);
lcd.write(4);
}
}
void volHandler_left(void) { // Поворот влево
uint8_t inc = (vol.isFastL() ? 10 : 5); // Если поворот быстрый - меняем значение сильнее
data.volume = constrain(data.volume - inc, 0, 100);
/* if (selected) {
switch (menu_pointer) {
case 0: data.volume = constrain(data.volume - inc, 0, 100); break; // Крутим выбраный параметр
case 1: data.treble = constrain(data.treble - inc, 0, 100); break;
case 2: data.bass = constrain(data.bass - inc, 0, 100); break;
case 3: data.input = !data.input; break;
}
update(menu_pointer); // Выгружаем новые данные
} else {
menu_pointer = constrain(menu_pointer - 1, 0, 3); // Или двигаем указатель меню
}
*/
printValue(data.volume); // Обновляем картинку
printBar(data.volume);
}
void volHandler_right(void) { // см. поворот влево
uint8_t inc = (vol.isFastR() ? 10 : 5);
data.volume = constrain(data.volume + inc, 0, 100);
/*
if (selected) {
switch (menu_pointer) {
case 0: data.volume = constrain(data.volume + inc, 0, 100); break;
case 1: data.treble = constrain(data.treble + inc, 0, 100); break;
case 2: data.bass = constrain(data.bass + inc, 0, 100); break;
case 3: data.input = !data.input; break;
}
update(menu_pointer);
} else {
menu_pointer = constrain(menu_pointer + 1, 0, 3);
}
*/
printValue(data.volume); // Обновляем картинку
printBar(data.volume);
}
// Печать частоты индекса и имени радиостанции
void printName(uint8_t i) {
//for (int i=0; i<=18 ; i++){
//NameStan = Radio[i].StationName;
char buf[strlen_P(Radio[i].StationName)];
strcpy_P( buf, Radio[i].StationName);
stName = buf;
freq = pgm_read_word(&(Radio[i].freq));
Serial.print (i);
Serial.print("\t");
Serial.print (freq);
Serial.print("\t");
//Serial.print (freq1/10);
Serial.print("\t");
Serial.print(stName);
Serial.println (buf);
drawRadio(freq, buf, 0,1, true) ;
//return buf ;
//delay(2000);
}
void encHandler_left(void) {
// Поворот влево второй энкодер
// uint8_t inc = (vol.isFastL() ? 2 : 1); // Если поворот быстрый - меняем значение сильнее
uint8_t inc=1;
data.ind_st = constrain(data.ind_st - inc, 0, 18);
printName(data.ind_st);
Serial.println(" func. encHandler_left");
// return;
}
void encHandler_right(void) {
// Поворот вправо второй энкодер
// return;
// uint8_t inc = (vol.isFastR() ? 2 : 1); // Если поворот быстрый - меняем значение сильнее
uint8_t inc=1;
data.ind_st = constrain(data.ind_st + inc, 0, 18);
printName(data.ind_st);
Serial.println("func encHandler_right");
}
void encHandler_clicks(void) {
// Клик второй энкодер
Serial.println("func. encHandler_clicks");
return;
}
void encHandler_hold(void) {
// Удержание второй энкодер
return;
}
void volHandler_clicks(void) { // клик переключает свойство меню
if(selected) lcd.noBacklight();
else lcd.backlight();
selected = !selected;
// drawMenu();
}
void volHandler_hold(void) { // удержание переключает mute
data.mute = !data.mute;
// drawMenu();
// update(3);
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
lcd.init(); // initialize the lcd
// Print a message to the LCD.
lcd.backlight();
if (EEPROM.read(EE_KEY_ADDR) != EE_KEY) { // Если ключ в еепром не совпадает
EEPROM.put(EE_DATA_ADDR, data); // Это - первый запуск, записываем дефолт
EEPROM.write(EE_KEY_ADDR, EE_KEY); // Пишем ключ
}
EEPROM.get(EE_DATA_ADDR, data);
printName(data.ind_st);
int8_t ind = data.ind_st;
Serial.print(ind);
//int freq = ;
int vol =data.volume ;
//const char *st_name = "Energy FM" ;
//const char *s = "argument";
//Прототип фунции экрана РАДИО
//lcd.print("Radio FM");
lcd.setCursor(15, 0);
lcd.print("13:35");
loadDigit();
// drawRadio(freq, st_name , 0,1, true) ;
lcd.setCursor(0, 3);
lcd.print("Vol:");
// lcd.setCursor(4,3);
// lcd.write(95);
printValue(vol);
printBar(vol);
int vol1 = map(vol, 0, 100, 4, 16);
//896,"Business FM"
/*
int freq_1000 = freq/1000 ;
int freq_100 = (freq % 1000)/100;
int freq_10 = (freq % 100)/10;
int freq_1 = (freq % 10);
Serial.print(freq);
Serial.print("\t");
Serial.print(freq_1000);
Serial.print("\t");
Serial.print(freq_100);
Serial.print("\t");
Serial.print(freq_10);
Serial.print("\t");
Serial.print(freq_1);
*/
Serial.print("\t");
Serial.println(vol1);
Serial.print(stName);
Serial.print("\t");
Serial.print(freq);
}
void loop() {
// put your main code here, to run repeatedly:
vol.tick(); // Тикер энкодера vol
enc.tick(); // Тикер енкодера enc
if (save_requested and millis() - request_time > 5000) { // Если запись запрошена давно (>5 сек)
save_requested = false; // Сбрасываем флаг
EEPROM.put(EE_DATA_ADDR, data); // Пишем настройки
}
if (enc.isRight()) {
encHandler_right();
} // если был поворот
if (enc.isLeft()) {
encHandler_left();
}
if (enc.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc.isLeftH()) Serial.println("Left holded");
//if (enc1.isPress()) Serial.println("Press"); // нажатие на кнопку (+ дебаунс)
//if (enc1.isRelease()) Serial.println("Release"); // то же самое, что isClick
if (enc.isClick()) encHandler_clicks(); // одиночный клик
if (enc.isSingle()) Serial.println("Single"); // одиночный клик (с таймаутом для двойного)
if (enc.isDouble()) Serial.println("Double"); // двойной клик
if (enc.isHolded()) Serial.println("Holded"); // если была удержана и энк не поворачивался
//if (enc1.isHold()) Serial.println("Hold"); // возвращает состояние кнопки
if (vol.isRight()) {
Serial.println("Right");
volHandler_right();
} // если был поворот
if (vol.isLeft()) {
Serial.println("Left");
volHandler_left();
}
if (vol.isClick()) {
Serial.println("Click");
volHandler_clicks();
}
}