#include <SPI.h> // Потрібна для роботи з MAX6675
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "GyverEncoder.h"
#include <EEPROM.h>
#include "I2Cdev.h"//не забути про розгон!!!
#include "max6675.h"
// Additional elements
// constants-variable-arrays
// СПІЛЬНІ КОНСТАНТИ ПІД ХОД ДЛЯ БАГ ФУНК
#define X(x) (x) // Макрос повертає прийняти значення створено для кращого розуміння коду
#define Y(y) (y)
#define W(w) (w) // ширина
#define H(h) (h) //висота
#define tf_passive 0
#define tf_active 1
#define tf_reset 0
#define tf_perform 1
#define tf_reset_settings 0
#define tf_function_not_completed 0
#define tf_function_completed 1
#define tf_disabled 0
#define tf_enabled 1
#define tf_on 0
#define tf_yes 1
#define tf_data_ready 1
#define tf_get_data 1
#define tf_start 1
#define g_no_action 2
#define g_waiting_command 2
#define g_non_bool 2
#define stage_0 0 // етапи виконання
#define stage_1 1
#define stage_2 2
#define stage_3 3
#define stage_4 4
#define stage_5 5
#define path_0 0 // шлях
#define path_1 1
#define path_2 2
#define path_3 3
#define path_4 4
#define path_5 5
//
//
// Піни карта
#define p_bolt_rifle_12 12
#define p_vibration_motor_18 18
#define p_h_b_14 14 //test
#define p_triger_r_35 35
#define p_triger_l_34 34
#define p_s_t_x_39 39//test
#define p_s_t_y_36 36
#define p_sw_25 25 // кнопка енко // RotaryEncoder encoder(27, 14, RotaryEncoder::LatchMode::TWO03);
#define p_dt_32 32
#define p_clk_33 33
#define p_on_off 26
#define p_led_pin_13 13
#define p_test_25 25 // test
//
//af_displayPrintLain()
#define L(x) (x) // line
#define N(x) (x) // number
//
//mdf_batteryCheck()
#define g_b_s 20 //get battery status
#define b_c_p 21 // battery charge plus
#define g_c_m 22 // battery charge minus
#define b_c_m 23 //// battery charge minus
//
//Md_gyroCalibrationExecution()
#define calibration_start 20
//
//af_saveToEEPROM()
#define get_eeprom 101
#define set_eeprom 102
#define eeprom_initialization 103
#define eeprom_seve 104
#define get_eeprom_last_read 105
#define address_indicator_reset 106
//
// display() {
#define d_u_d 23 //disable_updates_display
#define e_u_d 24 //enablet_updates_display
//
// Md_mainDisplayENG();
#define menu_off 21
#define menu_start 22
//
// byte mdf_addres()
#define back 20
#define get_add 23
#define check_emb 24
#define check_emb_ptp 25
#define mode_click_input 26
#define mode_click_back 27
#define back_add 28
#define mode_function_returns 29
#define set_add 30
#define address_not_entered 0
#define address_entered -1
#define address_deleted -2
//
// int mdf_cursor() // mdf_p_cursor()
#define get_pos 20
#define set_pos 21
#define cursor_read_encoder 24
#define cursor_type_one 0
#define cursor_type_two 1
//
// mdf_confirm() // mdf_p_confirm() // mdf_p_outputNotNeed()
//#define X(x) (x)
//#define Y(y) (y)
//#define tf_reset 0
//#define tf_get_data 1
//#define tf_function_completed 1
#define choice_no 20
#define choice_yes 21
#define accelerate_frame_blinking 22
#define print_not_need 23
#define offer_a_choice 24
#define output_by_coordinates 25
#define output_by_center_x 27
#define change_selection 28
//
// Md_autoPowerOffa()
// #define tf_reset 11
#define apo_change_time 20
#define apo_get_time 21
#define apo_restart_time 22
//
//Md_displayTimeout()
#define dt_get_tim_minutes 20
#define dt_get_tim_seconds 21
#define dt_change_time 22
#define timeout_restart 23
#define timeout_reset 24
//
// encoder module class
//turn()
#define get_press_end_turn 20
#define get_turn 21
#define rotated_r 22
#define rotated_l 23
#define rotated_r_fast 24
#define rotated_l_fast 25
#define reset_rotation 27
//bat()
#define click 28
#define press_b_e 29
#define hold_b_e 30
#define press_end_turn 31
#define get_button 32
//
#define send_report_execute 0
#define send_report 1
#define send_report_stop 2
#define send_report_start 3
#define waiting_command 4
//
// c-v-a
// Function prototypes
byte tc_temperatureControl(byte cmd = 0, byte data = 0);
byte mps(byte acceptedCommand = 0, byte number = 0, byte acceptedData = 0);
void fd_drawCenteredFilledRect(int centerX = 0, int centerY = 0, int width = 0, int height = 0, uint16_t color = WHITE);
void fd_drawCenteredHollowRect(int centerX = 0, int centerY = 0, int width = 0, int height = 0, uint16_t color = WHITE);
void af_controlsCheck();
bool af_controlsCheckСalibration(bool reset = 0);
void Gf_controlElementsStope(void);
bool af_checkLeftTrigger(bool mode = 0);
bool af_checkRightTrigger(bool mode = 0);
void printgetCalibrationData();
template<typename T>
bool mdf_magnifier(T &numeric, int minNam = 0, int maxNam = 0, int Speed1 = 0, int Speed2 = 0);
int af_saveToEEPROM(int acceptedCommand = 0, void* address = nullptr, size_t dataSize = 0);
byte Md_displayTimeout(byte acceptedCommand = 0);
uint32_t af_clamp(int value = 0, int minVal = 0, int maxVal = 0);
void af_displayPrintLain(String text = "", byte x = 0, byte y = 0, byte scrollFlag = 0, byte cropText = 20);
byte mdf_confirm(byte acceptedCommand = 0, byte x = 0, byte y = 0);
byte mdf_p_confirm(byte acceptedCommand = 0, byte x = 0, byte y = 0);
int mdf_addres(byte acceptedCommand = 0, byte addNumb = 0);
void mdf_displayOutput();
void displayRefresh(byte acceptedCommand = 0);
byte Md_autoPowerOff(byte acceptedCommand = 0);
void bgc_buttonGamepadPressRelease(byte command = 0, byte buttonName = 0, int analogData = 32767);
byte mdf_cursor(byte acceptedCommand = 0, byte data = 0, const byte* coordinate = nullptr);
// Fp
// Ae
// Class instances from external libraries екземпляри класу сторонніх бібліотек
Adafruit_SSD1306 display(128, 64, &Wire, -1);
Encoder enc(p_clk_33, p_dt_32, p_sw_25);
MAX6675 thermocouple(0, 0, 0);
//
// Byte saving data (bsd) використовується як унікальна зміна яка після досягнення потрібного нам значення повертає його і скидається на нуль
class bsd {
private:
const byte maxValue; // Максимальне значення
byte val = 0; // Накопичене значення
public:
// Конструктор
bsd(byte mV = 0, byte minV = 0) : maxValue(mV){
val = minV;
}
// Метод p() для додавання значення
void p(byte addValue) {
val += addValue;
val = af_clamp(val, 0, maxValue);// Скидання на нуль, якщо перевищено
}
// Метод m() для перевірки досягнення максимального значення
bool m() {
if (val >= maxValue) {
val = 0;
return true; // Повертаємо true, якщо досягнуто максимального значення
}
return false; // Максимальне значення не досягнуто
}
// Метод g() для повернення накопиченого значення
byte g() {
return val;
}
byte s(byte recordDatae) {
return val = recordDatae;
}
byte r() {//перед скиданням одноразового відправляємо получене число
byte temp = val; // Зберігаємо значення
val = 0; // Скидання на нуль
return temp; // Повертаємо збережене значення
}
byte tnar() { //tnar = take number automatic reset функція доплюсовує повертає і скидає самостійно
byte temp = val; // Зберігаємо значення
val = (val == maxValue) ? 0 : val + 1;
return temp;
}
byte enar(byte addValue) { //enar = enter number and return вписує нове значення і повертає його
val = addValue;
return val;
}
};
// Bsd
// Timer class (конструкція працює приблизно 49 днів після чого потрібен перезапуск мікроконтролера)
class Timer {
private:
uint32_t tmr = 0;
bool timerFlag = 0;
uint32_t restartTimer = 0;//час перезапуску
public:
//!!!!!таймер запускається коли викликається один з методів час не запускається одразу коли вказати його в конструкторі або методі st()
//конструктор вказуємо час і з чого починати правда брехня
Timer(uint32_t rT = 0, bool firstCall = 1) { //екземпляри таймерів можуть створюватися одразу з вказаним часом або вказувати його пізніше
restartTimer = rT;//якщо не вказати час тоді буде постійно повертатись значення яке є в timerFlag
timerFlag = firstCall;//якщо вказати час тоді стан буде мінятися Якщо ні тоді стан цієї змінної буде повертатись постійно допоки не вкажемо час через метод
}
//
// таймер одноразово поверне правда з вказаною частотою
bool gp() { // gt = get pulsation з вказаною частотою повертає правда
if (restartTimer) {
if (tmr < millis()) {
tmr = millis() + restartTimer;
if (timerFlag) {//одноразово повертаємо те що вказали в настройках
return true;
}
else{
timerFlag = true;//повертаємося до стандартного значення яке буде повертатись
}
}
}
else{//якщо змінна restartTimer брехня тоді будемо постійно повертати те що вказали в другому параметрі
return timerFlag;
}
return false;
}
//
// таймер одноразовий сигнал (брехня один раз правда по завершенню таймера, брехня до перезапуску)
bool ts() { // ts = timer single одноразова повернуть правда і буде чекати перезапуску
if (restartTimer) {
if (tmr < millis()){
if (!tmr){//це означає перший запуск або перший запуск після перезапуску
tmr = millis() + restartTimer;//
int temp = timerFlag;//одноразово повертаємо значення вказане в другому параметрі
timerFlag = true;//встановлюєm стандартне значення
return temp;
}
else{//це означає що таймер спрацював вдруге завершуємо роботу очікуємо перезапуск
timerFlag = restartTimer = false;//забороняємо виконання основної умови
return true;//одноразово повертаємо правда
}
}
}
else{//Дана умова виконується якщо ми не вказали перший параметр або функція завершила роботу
return timerFlag;//(0, false || true)=буде постійно повертати другий параметр//якщо завершила роботу буде постійно повертати брехня
}
return false;
}
//
// таймер (секунда правда секунда брехня)
bool ta() {//ta = timer auto постійно повертає правда брехня використовується там де не потрібна точність
if (restartTimer){
if (tmr < millis()){
tmr = millis() + restartTimer;
timerFlag = !timerFlag; //
}
return !timerFlag;//потрібно виконати ще раз інверсію щоб було правильнo підлаштовуємося під стандартний спосіб запуску-зупинки таймера
}
return timerFlag;
}
//
// повертає постійно по завершенню часу правда або брехня залежно від настройки
bool tm() { // tm = timer manual
if (restartTimer){
if (tmr < millis()){
if (!tmr){//спрацювання 1 запуск таймера або перший запуск після перезапуску
tmr = millis() + restartTimer;
int temp = timerFlag;//одноразово повертаємо значення вказане в другому параметрі
timerFlag = true;//встановлюєm стандартне значення
return temp;
}
else{//друге спрацювання завершуємо роботу
timerFlag = true;//ставимо значення правда щоб вона постійно поверталася
restartTimer = false;//забороняє виконання основної умови
return true;
}
}
}
else{//якщо Друга умова неправда То будемо постійно повертати те значення яке знаходиться в зміній
return timerFlag;
}
return false;
}
//
// відправляємо в новий час або зупиняємо таймер
void st(uint32_t rT = 0, bool firstCall = 1){ // вказуємо новий час спрацювання для tmr І що перше повертати правда брехня
tmr = false;
timerFlag = firstCall;//
restartTimer = rT;//вказуємо новий час або зупиняємо роботу таймера якщо вказати false
}
//
// берем стан таймера
int gmt() {//get millis timer
if (restartTimer && millis() < tmr) {
return tmr - millis();
}
return 0;
}
int gst() {//get seconds timer
if (restartTimer && millis() < tmr) {
return (tmr - millis()) / 1000;
}
return 0;
}
//
};
// Tc
// Button debounce class
class DebButton {
private:
const byte pin;
Timer tmr; // Оголошення без ініціалізації
byte debounceTime;
bool buttonState = false; // Змінено на bool
public:
// Конструктор класу, ініціалізація піна, номера кнопки геймпада та часу захисту від випадкових спрацьовувань
DebButton(int p, int dT = 10) : pin(p), tmr(false, true) { // Ініціалізація tmr у списку ініціалізації
debounceTime = dT;
pinMode(pin, INPUT_PULLUP);
}
// Метод для обробки натискання кнопки
bool press() {
if (!digitalRead(pin) && !buttonState && tmr.ts()) {
tmr.st(debounceTime, false);
buttonState = true;
}
else if (buttonState && digitalRead(pin) && tmr.ts()) {
tmr.st(debounceTime, false);
buttonState = false;
}
return buttonState;
}
};
// Bdc
// Additional functions Function svmbols
//Additional functions
//void tick(){}
void af_menuLinesCenterY(byte totalItems, byte spacing, byte centerY, byte yOut[]) {
const byte lineHeight = 8; // Висота одного рядка
int top;
if (totalItems % 2 == 1) {
// Непарна кількість рядків: центральний рядок точно на centerY
byte middleIndex = totalItems / 2;
top = centerY - middleIndex * (lineHeight + spacing) - lineHeight / 2;
} else {
// Парна кількість рядків: центр між двома середніми рядками
byte middleIndex = totalItems / 2 - 1;
top = centerY - (middleIndex * (lineHeight + spacing) + lineHeight + spacing / 2);
}
for (byte i = 0; i < totalItems; i++) {
yOut[i] = top + i * (lineHeight + spacing);
}
}
int clampMinMax(int value, int minVal, int maxVal) {
if (value < minVal) return minVal;
if (value > maxVal) return maxVal;
return value;
}
//Encoder module
bool af_checkClick() {
if (enc.isClick()) {
Md_autoPowerOff(apo_restart_time);
Md_displayTimeout(timeout_restart);
return true;
}
return false;
}
bool af_checkTurn() {
if (enc.isTurn()) {
Md_autoPowerOff(apo_restart_time);
Md_displayTimeout(timeout_restart);
return true;
}
return false;
}
bool af_checkRightHolded() {
if (enc.isRightH()) {
Md_autoPowerOff(apo_restart_time);
Md_displayTimeout(timeout_restart);
return true;
}
return false;
}
bool af_checkLeftHolded() {
if (enc.isLeftH()) {
Md_autoPowerOff(apo_restart_time);
Md_displayTimeout(timeout_restart);
return true;
}
return false;
}
bool af_checkFastRight() {
if (enc.isFastR()) {
Md_autoPowerOff(apo_restart_time);
Md_displayTimeout(timeout_restart);
return true;
}
return false;
}
bool af_checkFastLeft() {
if (enc.isFastL()) {
Md_autoPowerOff(apo_restart_time);
Md_displayTimeout(timeout_restart);
return true;
}
return false;
}
bool af_checkRight() {
if (enc.isRight()) {
Md_autoPowerOff(apo_restart_time);
Md_displayTimeout(timeout_restart);
return true;
}
return false;
}
bool af_checkLeft() {
if (enc.isLeft()) {
Md_autoPowerOff(apo_restart_time);
Md_displayTimeout(timeout_restart);
return true;
}
return false;
}
//
//Shooting effects
void af_triggerShootingEffects(byte acceptedCommand){//функція запускає ефекти викликається в analogReadTriggerRightPress
if (physicalButtons[t_r].triggerRightGetStatus()) { //працює лише коли підключений Bluetooth
af_triggerLight(color_two);
af_shotgunBolt(bolt_start);
af_vibration(vibration_start);
}
else if (!physicalButtons[t_r].triggerRightGetStatus()) {// пісня відпущення кнопки буде одноразове повернення брехня
af_triggerLight(color_one);
af_shotgunBolt(bolt_stop);
af_vibration(vibration_stop);
}
af_triggerLight();
af_shotgunBolt();
af_vibration();
}
byte af_shotgunBolt(byte acceptedCommand){
static byte boltPermission = 1;
if (acceptedCommand){
if (acceptedCommand == bolt_start && boltPermission){
digitalWrite(p_bolt_rifle_12, HIGH);
}
else if (acceptedCommand == bolt_stop && boltPermission){
digitalWrite(p_bolt_rifle_12, LOW);
}
else if (acceptedCommand == bolt_change){
if (mdf_magnifier(boltPermission, 0, 1, 1, 1)){
if (!boltPermission){digitalWrite(p_bolt_rifle_12, LOW);}
}
}
else if (acceptedCommand == bolt_get_permission){ //використовується для відображення включена чи виключено
return boltPermission;
}
else if (acceptedCommand == bolt_pin_activate){
pinMode(p_bolt_rifle_12, OUTPUT);
digitalWrite(p_bolt_rifle_12, LOW);
}
else if (acceptedCommand == acceptedCommand == tf_reset_settings) {
boltPermission = 1;
}
else if (acceptedCommand == get_eeprom) {
af_saveToEEPROM(get_eeprom, &boltPermission, sizeof(boltPermission));
}
else if (acceptedCommand == set_eeprom) {
af_saveToEEPROM(set_eeprom, &boltPermission, sizeof(boltPermission));
}
}
return 0;
}
byte af_vibration(byte acceptedCommand){
static byte vibrationPermission = 1;
if (acceptedCommand){
if (acceptedCommand == vibration_start && vibrationPermission){
digitalWrite(p_vibration_motor_18, HIGH);
}
else if (acceptedCommand == vibration_stop && vibrationPermission){
digitalWrite(p_vibration_motor_18, LOW);
}
else if (acceptedCommand == vt_change){
if (mdf_magnifier(vibrationPermission, 0, 1, 1, 1)){
if (vibrationPermission){
digitalWrite(p_vibration_motor_18, HIGH); //test можливо заребриє на секунду потрібно тестувати
digitalWrite(p_vibration_motor_18, LOW);
}
else {digitalWrite(p_vibration_motor_18, LOW);}
}
}
else if (acceptedCommand == vt_get_permission){
return vibrationPermission;
}
else if (acceptedCommand == vt_pin_activate){
pinMode(p_vibration_motor_18, OUTPUT);
digitalWrite(p_vibration_motor_18, LOW);
}
else if (acceptedCommand == tf_reset_settings) {
vibrationPermission = 1;
}
else if (acceptedCommand == get_eeprom) {
af_saveToEEPROM(get_eeprom, &vibrationPermission, sizeof(vibrationPermission));
}
else if (acceptedCommand == set_eeprom) {
af_saveToEEPROM(set_eeprom, &vibrationPermission, sizeof(vibrationPermission));
}
}
return 0;
}
int af_triggerLight(byte acceptedCommand, int acceptedData){
static byte light1 = 100;
static byte light2 = 100;
static int color1 = 260;
static int color2 = 0;
static byte lom = color_switching; // lom = light operating mode
if (acceptedCommand){
if (acceptedCommand == color_one){
if (lom == color_switching){
af_setColorByValue(color1, light1);
}
}
else if (acceptedCommand == color_two){
if (lom == color_switching){
af_setColorByValue(color2, light2);
}
}
else if (acceptedCommand == get_color1){
return color1;
}
else if (acceptedCommand == get_color2){
return color2;
}
else if (acceptedCommand == get_light1){
return light1;
}
else if (acceptedCommand == get_light2){
return light2;
}
else if (acceptedCommand == set_color1){
color1 = acceptedData;
af_setColorByValue(color1, light1);
}
else if (acceptedCommand == set_color2){
color2 = acceptedData;
af_setColorByValue(color2, light2);
}
else if (acceptedCommand == set_light1){
light1 = acceptedData;
af_setColorByValue(color1, light1);
}
else if (acceptedCommand == set_light2){
light2 = acceptedData;
af_setColorByValue(color2, light2);
}
else if (acceptedCommand == clssc){ //clssc = color light standard settings check
if (light1 == 100 && light2 == 100 && color1 == 260 && color2 == 0){
return color_light_default;
}
}
else if (acceptedCommand == color_light_reset){
light1 = 100;
light2 = 100;
color1 = 260;
color2 = 0;
af_setColorByValue(color1, light1);
}
else if (acceptedCommand == color_switching){//один з двох режимів переключення кольорів або настройка кольору
lom = color_switching;
}
else if (acceptedCommand == color_light_setting){
lom = color_light_setting;
}
}
return 0;
}
void af_setColorByValue(uint16_t colorIndex, uint8_t brightnessPercent) {
// Обмеження: не нижче 1, не вище 1000
colorIndex = constrain(colorIndex, 1, 1000);
brightnessPercent = constrain(brightnessPercent, 0, 100);
uint8_t hue;
uint8_t sat;
if (colorIndex <= 700) {
// Відтінки кольору (насиченість максимальна)
hue = map(colorIndex, 1, 700, 0, 255);
sat = 255;
} else {
// Перехід до білого — зменшуємо насиченість
hue = map(700, 1, 700, 0, 255); // Зберігаємо останній hue
sat = map(1000 - colorIndex, 0, 300, 0, 255);
}
// Яскравість
uint8_t val = map(brightnessPercent, 0, 100, 0, 255);
leds[0] = CHSV(hue, sat, val);
FastLED.show();
}
//
String af_animatePointer() {
static Timer anim(400);
static byte pos = 0;
if (anim.gp()) {
if (++pos > 2) pos = 0;
}
const char arrow = (char)16; // код символа зі шрифту OLED
String out = "";
for (byte i = 0; i < 3; i++) {
out += (i == pos) ? String(arrow) : ">";
}
return out;
}
// Функція для зупинки вимірювання та обробки статистики
unsigned long overallMinTime = 99999999;
unsigned long overallMaxTime = 0;
unsigned long avgTotalTime = 0;
unsigned int avgMeasurements = 0;
unsigned long startMicros = 0;
// Змінні для керування логікою "за секунду"
unsigned long lastSecondStartTime = 0;
bool isMeasuring = true;
bool isDataReady = false;
// Функція для старту вимірювання
void startPerfMeasurement() {
// Вимірювання відбувається лише якщо ми у стані "збору"
if (isMeasuring) {
startMicros = micros();
}
}
// Функція для зупинки вимірювання та обробки статистики
void endPerfMeasurement() {
// Обробляємо команду з Serial Port
if (Serial.available()) {
char command = Serial.read();
if (command == '1') {
// Якщо команда '1' отримана і дані готові
if (isDataReady) {
// Виводимо звіт
float avgTime = (float)avgTotalTime / avgMeasurements;
Serial.println("--- Статистика за останній замір ---");
Serial.print("Середній час: ");
Serial.print(avgTime / 1000.0, 3);
Serial.println(" мс");
Serial.print("Мінімальний час (за весь час): ");
Serial.print((unsigned long)overallMinTime / 1000.0, 3);
Serial.println(" мс");
Serial.print("Максимальний час (за весь час): ");
Serial.print((unsigned long)overallMaxTime / 1000.0, 3);
Serial.println(" мс");
Serial.print("Кількість вимірів: ");
Serial.println(avgMeasurements);
Serial.println("-------------------------------------");
// Перезапускаємо збір даних
isMeasuring = true;
isDataReady = false;
lastSecondStartTime = millis();
avgTotalTime = 0;
avgMeasurements = 0;
} else {
Serial.println("Збір даних ще не завершено.");
}
}
}
// Якщо ми у стані "збору", оновлюємо статистику
if (isMeasuring) {
unsigned long endMicros = micros();
unsigned long elapsed = endMicros - startMicros;
// Оновлюємо абсолютні мінімальні та максимальні значення
if (elapsed < overallMinTime) {
overallMinTime = elapsed;
}
if (elapsed > overallMaxTime) {
overallMaxTime = elapsed;
}
// Накопичуємо дані для середнього часу
avgTotalTime += elapsed;
avgMeasurements++;
// Перевіряємо, чи пройшла секунда
if (millis() - lastSecondStartTime >= 1000) {
isMeasuring = false;
isDataReady = true;
}
}
}
//
void af_powerOffExecution(){
//af_saveToEEPROM(eeprom_seve);
display.clearDisplay();
display.display();
leds[0] = CHSV(0, 0, 0);
FastLED.show();
btStop(); // Вимкнути Bluetooth, якщо був увімкнений
esp_deep_sleep_start();
}
// !!! нагадування можна просто викликати всі функції які мають мати дані з енергонезалежної пам'яті по черзі і проінтеризувати зміни всередині тих функцій через лямбда функцію
int af_saveToEEPROM(int acceptedCommand, void* address, size_t dataSize) {//TEST
static int addInd = 1;///перший байт використовується для флажка
uint8_t* bytePointer = (uint8_t*)address;
if (acceptedCommand == set_eeprom) {
bool dataChanged = false; // Флаг для перевірки змін
for (size_t i = 0; i < dataSize; i++) {
if (*(bytePointer + i) != EEPROM.read(addInd + i)) {//економія ресурсів перед записом Перевіряємо чи дані відрізняються якщо відрізняються відбудеться перезапис
dataChanged = true; // Дані змінилися
break; // Якщо знайшли різницю, не потрібно перевіряти далі
}
}
if (dataChanged) {//якщо стан важка змінився значить дані потрібно перезаписати
for (size_t i = 0; i < dataSize; i++) {
EEPROM.write(addInd + i, *(bytePointer + i)); // Побайтово записуємо в EEPROM
}
EEPROM.commit(); // Фіксуємо зміни
}
addInd += dataSize;
}
else if (acceptedCommand == get_eeprom || acceptedCommand == get_eeprom_last_read) {//по байтах зчитуємо збережені дані
for (size_t i = 0; i < dataSize; i++) {
*(bytePointer + i) = EEPROM.read(addInd+i);
}
if (acceptedCommand == get_eeprom) {
addInd += dataSize;// Переходимо до наступного індексу
} else{addInd = 1;}//якщо в нас get_eeprom_last_read то потрібно скинути щоб записувати знову з першого байта
}
else if (acceptedCommand == address_indicator_reset) {
addInd = 1;
}
else if (acceptedCommand == eeprom_initialization && !EEPROM.read(0)) {
for (int i = 8; i < 14; i++) {
//profil[i].eepromProfile(get_eeprom);
}
af_vibration(get_eeprom);
af_shotgunBolt(get_eeprom);
af_triggerLight(get_eeprom);
Md_displayTimeout(get_eeprom);
Md_autoPowerOff(get_eeprom);
Md_gyroCalibrationExecution(get_eeprom);
af_saveToEEPROM(address_indicator_reset);
}
else if (acceptedCommand == eeprom_seve) {
if (EEPROM.read(0)) {EEPROM.put(0, 0);EEPROM.commit();}
for (int i = 8; i < 14; i++) {
//profil[i].eepromProfile(set_eeprom);
}
af_vibration(set_eeprom);
af_shotgunBolt(set_eeprom);
af_triggerLight(set_eeprom);
Md_displayTimeout(set_eeprom);
Md_autoPowerOff(set_eeprom);
Md_gyroCalibrationExecution(set_eeprom);
}
return 0;
}
byte af_passwordEntry(byte acceptedCommand) {
static byte currentStage = 0; // Поточний етап (0 - клік, 1 - утримання, 2 - клік)
if (currentStage != 2 && acceptedCommand == typing_a_password){
switch (currentStage) {
case 0: // Етап 1: Очікування утримання
if (enc.isHolded()) {
currentStage = 1; // Переходимо до наступного етапу
}
break;
case 1: // Етап 2: Очікування кліку
if (af_checkClick()) {
currentStage = 2; // Переходимо до наступного етапу
return password_entered;
}
break;
}
return password_not_entered;
}
else if (acceptedCommand == password_reset){
currentStage = 0;
}
return 0;
}
void af_centerTextX(int x, int y, auto input, bool minusNo = false) {
// Перетворюємо вхідне значення у рядок
static String text;
static int adjustedLength;
static int textWidth;
// Якщо треба ігнорувати мінус, коригуємо довжину тексту
text = String(input);
adjustedLength = text.length();
if (minusNo && text[0] == '-') {
adjustedLength--; // Ігноруємо мінус
}
// Обчислюємо ширину тексту
textWidth = adjustedLength * 6; // 6 пікселів на символ
// Встановлюємо курсор
display.setCursor(x - (textWidth / 2), y);
// Виводимо текст
display.print(text);
}
void af_displayPrintLain(String text, byte x, byte y, byte linePosition, byte cropText) {// (ДИВИТИСЬ ПОЯСНЕННЯ)
// Статичні змінні для збереження стану між викликами
static Timer tmr(10); // Таймер для контролю автоматичної прокрутки також включаємо оновлення дисплея при кожному зміщенні тексту
static byte lps; // Змінна для збереження line Position seve
static int stx; // Позиція прокрутки по горизонталі
byte stxt; // Тимчасова змінна для прокрутки stxt =crolling text x temporary
static int tlip; // Довжина тексту в пікселях tlip = text Length In Pixels
if (mdf_cursor(get_pos) != lps) {//тут відбувається скидання прокрутки якщо курс змінив позицію
tlip = text.length() * 6; // Довжина тексту у пікселях (6 пікселів на символ)
stx = 0; // Початкова позиція прокрутки
lps = linePosition; // Оновлення
}
if (mdf_cursor(get_pos) == linePosition) {//якщо номер строки співпадає з позицією kурсора відбувається прокрутка тексту
// Автоматична прокрутка за таймером, якщо кнопка не натиснута
if (tmr.ta()) {// test здається має бути інший таймер
if (tlip > stx) {
stx += 1; // Збільшення позиції прокрутки
} else {
stx = 0; // Скидання прокрутки до початку тексту
}
}
stxt = stx; // Присвоєння тимчасової змінної значення прокрутки
}
// Встановлення курсора з урахуванням прокрутки та виведення тексту
//if (linePosition) {display.drawRect((x + cropText * 6)-12, y, 5, 8, 1);}
display.setCursor(x - stxt, y);
display.print(text);
//чорні прямокутники для закриття потрібні лише тоді коли даний параметр приймає любе значення окрім нуля
//якщо даний параметр брехня То це означає що текст поміщається виділену область
if (linePosition) {
display.fillRect(0, y, x, 8, 0); // Ліва область
display.fillRect(x + cropText * 6, y, 128, 8, 0); // Права область відповідно до обрізаного тексту
display.fillRect(0, y + 7, 128, 64, 0); // Очищення області під текстом
display.setCursor((x + cropText * 6)-3, y);
display.write(16); // це стрілочка ►
}
}
uint32_t af_clamp(int value, int minVal, int maxVal) {//якщо вийти за рамки то поверне в початок або кінець
if (value < minVal) {
return maxVal;
}
else if (value > maxVal) {
return minVal;
}
return value;
}
void af_startAnimation(){
display.clearDisplay();
af_centerTextX(X(64), Y(28), "FS G36" );
display.display();
delay(2000);
display.clearDisplay();
display.display();
}
// Controls check
void af_controlsCheck(){
byte stage = stage_1;
while (stage) {
enc.tick();
Md_autoPowerOff();
display.clearDisplay();
display.drawRect(0, 0, 128, 48, WHITE);
switch (stage) {
case stage_1:
stage = af_deviceCheck(stage_1);//якщо успішна тоді одразу кейс stage_4 якщо ні перейдемо до stage_2 виведемо повідомлення
break;
case stage_2:
stage = af_pleaseDoNotPress();//просmo не чіпати і вибрати Next після вибору Next або завершення часу відображення переходимо до stage_3
break;
case stage_3:
stage = af_deviceCheck(stage_3);// якщо виконуємо другий раз перевірку то можливі два варіанти завершення stage_4 означає що натиснуте було відпущене stage_5 означає що є проблеми з елементами керування виконалось відключення несправного або натиснутого
break;
case stage_4:
stage = af_endMessage(stage_4);//якщо перевірка успішна то виводимо Device OK
break;
case stage_5:
stage = af_endMessage(stage_5);// якщо була помилка то виводимо Controls ERROR
break;
}
display.display();
}
delay(1000);//секунда затримки щоб відобразити повідомлення успішне чи погане завершення
}
// dead zones calibration button check
byte af_deviceCheck(byte stage){
static Timer tmr(4000, false);
if (stage == stage_1) {
if (af_controlsCheckСalibration(false)) {//якщо на першій провірці є відхилення переключимося на екран прохання //виконуємо калібрування провірку
af_controlsCheckСalibration(true);//потрібно скинути щоб можна було повторно получити правда if (af_controlsCheckСalibration(false)) {
tmr.st(4000, false);
af_drawCountdown(false);//перезапускаємо анімацію таймера тому що є відхилення
return stage_2;
}
if (tmr.ts()) {// якщо таймер дойде до кінця значить провірка успішна
return stage_4;//просто завершуємо
}
}
else if (stage == stage_3) {
af_controlsCheckСalibration(false);//якщо тут будуть відхилення то відбудеться відключення попередження не буде
if (tmr.ts()) {//таймер доходить до кінця є два варіанти завершення
if (af_controlsCheckСalibration(true)) {//тест завершення з помилкою означає що відбулось відключення елементів керування
return stage_5;//
}
else{//означає що при другій спр Все пройшло добре елементи керування були відпущені
return stage_4;
}
}
}
af_p_deviceCheck();//відображаємо повідомлення Device check і анімацію таймера
return stage;
}
void af_p_deviceCheck(){
const byte l = 1, p = 0, s = 48/2;
byte y[l];
af_menuLinesCenterY(l, p, s, y);
af_centerTextX(X(65), y[0], "Device check");
af_drawCountdown(true);
}
void af_drawCountdown(bool start) {
static Timer tmr(4000, false);
static int inspectionLine = 114;//лінія перевірки
static bool digitJump; // цифра стрибок
static bool displayPermission = true; // дозвіл відображення
if (displayPermission){
fd_drawCenteredFilledRect(X(64), Y(56), W(inspectionLine), H(3));//Смужка яка зменшується в центр
inspectionLine = map(tmr.gmt(), 0, 4000, 14, 114);//зменшуємо розмір смужки до мінімума
if (inspectionLine % 2 != 0) {//робимо це рівномірно відрізаємо по одному Пікселю Зліва і справа
inspectionLine = inspectionLine - 1;
}
fd_drawCenteredHollowRect(X(64), Y(56), W(14), H(13));//прямокутник в центрі в якому відображаються цифри
fd_drawCenteredFilledRect(X(64), Y(56), W(12), H(11), BLACK);//робимо чорне полотно в центрі прямокутника накриваємо смужку
int temp = tmr.gst();
if (temp) {// якщо таймер не вийшов відображаємо секунди
af_centerTextX(X(64+digitJump), Y(53), temp);
digitJump = !digitJump;
}
else{fd_drawCenteredHollowRect(X(64), Y(56), W(6), H(7));}//якщо час вийшов відображаємо прямокутник замість Нолика так красивіше
if (tmr.ts()){displayPermission = false; delay(500);}//час вийшов завершуємо додатково трошки зупиняємося щоб було видно наш Нулик
}
if (!start){
tmr.st(4000, false);
inspectionLine = 114;
displayPermission = true;
}
}
//
// please Do Not Press
byte af_pleaseDoNotPress(){
if (af_selectNextRestart()) {
return stage_3;
}
af_p_pleaseDoNotPress();
return stage_2;
}
void af_p_pleaseDoNotPress(){
const byte l = 2, p = 1, s = 48/2;
byte y[l];
af_menuLinesCenterY(l, p, s, y);
af_centerTextX(X(65), y[0], "Please don't" );
af_centerTextX(X(65), y[1], "press anything");
}
// Select next restart
void af_p_selectNextRestart(byte selection) {
display.fillRect(19, 4 + 51, 21, 3, WHITE); //жирна риска -
display.setCursor(86, 2 + 51);
display.print("Next");
// Рамки навколо тексту
if (selection == 0) {
display.drawRect(16, 0 + 51, 27, 11, WHITE); //жирна риска -
} else {
display.drawRect(84, 0 + 51, 27, 11, WHITE); // Next
}
}
byte af_selectNextRestart() {//
static Timer tmr(5000, false);
static byte selection = 0; // 0 = Restart, 1 = Next
af_p_selectNextRestart(selection);
if (af_checkTurn()) {
selection = !selection;
}
if ((af_checkClick() && selection) || tmr.ts()) {
selection = 0;
return true;
}
return false; // поки нічого не обираємо
}
//
//
// end message
byte af_endMessage(byte stage){
af_p_endMessage(stage);
return stage_0; // одразу завершуємо функцію в кінці циклу затримка delay(1000); дозволить побачити виведене повідомлення
}
void af_p_endMessage(byte stage){
if (stage == stage_4) {
af_centerTextX(X(65), (48/2)-4, "Device OK");
}
else if (stage == stage_5) {
af_centerTextX(X(65), (48/2)-4, "Controls ERROR");
}
}
//
bool af_controlsCheckСalibration(bool reset) {
static byte st;
if (!reset) {//означає виконати перевірку
if (Gf_leftJoystick(c_c_j) || //якщо получим правда перший раз виведемо прохання не чіпати якщо викл дану функцію другий раз виконується відключення якщо є критичні відхилення
Gf_rj(c_c_j) ||
physicalButtons[t_l].calibrationTrigger() ||
physicalButtons[t_r].calibrationTrigger() ||
bgc_checkingAllButtons()
)
{
st = true;
}
}
else if (reset){//скидаємо і одночасно получаємо результат перевірки в першому виклику потрібно скинути в другому потрібен взяти результат
bool temp = st;
st = false;
return temp;//
}
return st;
}
//
//
// For display (різне для дисплею)
void fd_drawCenteredFilledRect(int centerX, int centerY, int width, int height, uint16_t color) {//прямокутник білий або BLACK
int startX = centerX - (width / 2);
int startY = centerY - (height / 2);
display.fillRect(startX, startY, width, height, color);//якщо непарна кількість зміщення вправо вниз
}
void fd_drawCenteredHollowRect(int centerX, int centerY, int width, int height, uint16_t color) {//прямокутник рамка
// Обчислюємо початкові координати для верхнього лівого кута, щоб прямокутник був по центру
int startX = centerX - (width / 2);
int startY = centerY - (height / 2);
// Викликаємо стандартний метод для малювання прямокутника з рамкою
display.drawRect(startX, startY, width, height, color);
}
const char** fd_getButtonNames() {//функція яка повертає масив з іменами кнопок
static const char* buttonNames[] = {
"A",// не міняти нічого не добавляти нічого
"B",
"X",
"Y",
"A1",
"A2",
"RB",
"LB",
"Se",
"St",
"H",
"dU",
"dD",
"dL",
"dR",
"RT",
"LT"
};
return buttonNames;
}
void fd_bluetoothSymbol(){
const byte bluArr[9] = {
0b00100,
0b00110,
0b10101,
0b01110,
0b00100,
0b01110,
0b10101,
0b00110,
0b00100
};
display.drawBitmap(1, 2, bluArr, 8, 9, WHITE);
}
void fd_curvedArrow(int x, int y) {//крива стрілка Вверх
display.setCursor(x, y);
display.println("_");
display.setCursor(x + 3, y); // Зсув на 3 пікселі для другого символу
display.write(24);
}
void fd_arrowLowercaseLetters(int xOffset, int yOffset) {//символ малі букви
// Масив, який описує символ "стрілка"
uint8_t pixelArray[10][9] = {
{0, 0, 0, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 1, 0, 0, 0},
{0, 0, 1, 0, 0, 0, 1, 0, 0},
{0, 1, 0, 0, 0, 0, 0, 1, 0},
{1, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 0, 0, 0, 1, 1, 1},
{0, 0, 1, 0, 0, 0, 1, 0, 0},
{0, 0, 1, 0, 0, 0, 1, 0, 0},
{0, 0, 1, 0, 0, 0, 1, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 0, 0}
};
for (int y = 0; y < 10; y++) { // 10 рядків у масиві
for (int x = 0; x < 9; x++) { // 9 стовпців у масиві
if (pixelArray[y][x] == 1) {
display.drawPixel(x + xOffset, y + yOffset, SSD1306_WHITE); // Малюємо білий піксель
}
}
}
}
void fd_arrowCapitalLetters(int xOffset, int yOffset) {//символ великі букви
// Масив, який описує заповнену стрілку
uint8_t pixelArray[10][9] = {
{0, 0, 0, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 0, 0},
{0, 1, 1, 1, 1, 1, 1, 1, 0},
{1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1},
{0, 0, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 0, 0}
};
for (int y = 0; y < 10; y++) { // 10 рядків у масиві
for (int x = 0; x < 9; x++) { // 9 стовпців у масиві
if (pixelArray[y][x] == 1) {
display.drawPixel(x + xOffset, y + yOffset, SSD1306_WHITE); // Малюємо білий піксель
}
}
}
}
void fd_letterDeletion(int xOffset, int yOffset) {//символи видалення
uint8_t pixelArray[7][9] = {
{0, 0, 0, 1, 1, 1, 1, 1, 1},
{0, 0, 1, 0, 0, 0, 0, 0, 1},
{0, 1, 0, 0, 1, 0, 1, 0, 1},
{1, 0, 0, 0, 0, 1, 0, 0, 1},
{0, 1, 0, 0, 1, 0, 1, 0, 1},
{0, 0, 1, 0, 0, 0, 0, 0, 1},
{0, 0, 0, 1, 1, 1, 1, 1, 1}
};
for (int y = 0; y < 7; y++) { // 7 рядків у масиві
for (int x = 0; x < 9; x++) { // 9 стовпців у масиві
if (pixelArray[y][x] == 1) {
display.drawPixel(x + xOffset, y + yOffset, SSD1306_WHITE); // Малюємо білий піксель
}
}
}
}
void fd_arrowReturn(int xOffset, int yOffset) {//символ повернення
// Масив, який описує символ "стрілка повернення"
uint8_t pixelArray[9][12] = {
{0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0},
{0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0},
{1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1},
{1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1},
{1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1},
{0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0},
{0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0}
};
for (int y = 0; y < 9; y++) { // 9 рядків у масиві
for (int x = 0; x < 12; x++) { // 12 стовпців у масиві
if (pixelArray[y][x] == 1) {
display.drawPixel(x + xOffset, y + yOffset, SSD1306_WHITE); // Малюємо білий піксель
}
}
}
}
void fd_square2x2(int xOffset, int yOffset) {
display.drawPixel(xOffset, yOffset, SSD1306_WHITE);
display.drawPixel(xOffset + 1, yOffset, SSD1306_WHITE);
display.drawPixel(xOffset, yOffset + 1, SSD1306_WHITE);
display.drawPixel(xOffset + 1, yOffset + 1, SSD1306_WHITE);
}
void fd_horizontal6(int xOffset, int yOffset) {
for (int x = 0; x < 6; x++) {
display.drawPixel(x + xOffset, yOffset, SSD1306_WHITE);
}
}
void fd_vertical6(int xOffset, int yOffset) {
for (int y = 0; y < 6; y++) {
display.drawPixel(xOffset, y + yOffset, SSD1306_WHITE);
}
}
void fd_miniTesterSmiley(int xOffset, int yOffset) {
}
//
// Af Fs
// Main display
// add0--------------------------------------------------------------
void Md_settings(){
switch (mdf_addres(check_emb, 0)) {
case 0:
tc_temperatureControl(forced_start_pumps);
break;
case 1:
int temp = tc_temperatureControl(get_min_t);
mdf_magnifier(temp, 0, 50, 1, 1);
tc_temperatureControl(set_min_t, temp);
break;
case 2:
int temp = tc_temperatureControl(get_max_t);
mdf_magnifier(temp, 70, 90, 1, 1);
tc_temperatureControl(set_max_t, temp);
break;
case 3:
int temp = a_alarms(get_time_restart);
mdf_magnifier(temp, 1, 99, 1, 1);
a_alarms(set_time_restart, temp);
break;
case 4:
break;
case address_entered:
mdf_addres(mode_click_back);
if (mdf_addres(get_add, 0) == 4){
a_alarms(alarms_test_start);
}
break;
case address_deleted:
a_alarms(alarms_test_stope);
break;
}
Md_p_outputMinMax();
}
void Md_p_outputMinMax(){
const byte l = 3, p = 2, s = 64/2;
byte y[l];
af_menuLinesCenterY(l, p, s, y);
af_displayPrintLain("Forced start", X(6), y[0]);
af_displayPrintLain("Min", X(6), y[1]);
af_displayPrintLain("Max", X(6), y[2]);
af_displayPrintLain("Alarm off ", X(6), y[3]);//вказуємо час на який вона має виключитись
af_displayPrintLain("Alarm test 10s", X(6), y[4]);//відображаємо зворотній відлік
mdf_cursor(cursor_read_encoder, sizeof(y), y);
}
//
// add1--------------------------------------------------------------
// Main display
void Md_mainDisplay() {// головна фунція виклик решта функцій дисплея
switch (mdf_addres(check_emb, 1)) {
case address_not_entered:
Md_p_mainDisplay();
mdf_bluetoothPrint();
mdf_typeNumberPrint();
//mdf_batteryPrint();
break;//
case 1:
if(!ap->tape){//якщо профіль не вибрано переходимо в пункт selectProfile
Md_selectProfile();
}else{
ap->tape == t_sp ? Md_ProfileSp() : Md_ProfileMp();//відображаємо меню залежності від типу профіля
}
break;
case 2:
Md_generalSettings();
break;
case 3:
// qr-код ссылка на інструкцію
break;
case 4: //back
af_saveToEEPROM(eeprom_seve);
mdf_cursor(set_pos, 1);
mdf_addres(back_add, 0);
break;
case address_entered:
mdf_cursor(set_pos, 1);
break;
}
}
void Md_p_mainDisplay(){
const byte Lines[] = {22, 32, 42, 52};
mdf_cursor(cursor_read_encoder, 4, Lines);
if(!ap->tape){//якщо профіль не вибрано то попадаємо в пункт для вибору
display.setCursor(0, 22);
display.println(" Select profile");
}else{
display.setCursor(0, 22);
display.println(" Profile");
}
display.setCursor(0, 32);
display.println(" Settings");
display.setCursor(0, 42);
display.println(" Instruction (!)");
display.setCursor(0, 52);
display.println(" Exit ");
}
//
//
// add2-----------------------------------------------------------------------------------------------------------
// Profile
// Profile Sp // залежить від типу профіля викликається Sp або Mp
void Md_ProfileSp() {
switch (mdf_addres(check_emb, 2)) {
case address_not_entered:
Md_p_ProfileSp();
break;
case 1:
Md_profileSettingsSp();
break;
case 2: // QR code
// Додайте код для обробки QR-коду тут, якщо потрібно
break;
case 3:
Md_selectProfile();
break;
case 4: //back
mdf_cursor(set_pos, mdf_addres(get_add, 1));
mdf_addres(back_add, 1);
break;
case address_entered:
mdf_cursor(set_pos, 1);
break;
}
}
void Md_p_ProfileSp() {
const byte LinesSp[] = {31, 39, 49, 57};
mdf_cursor(cursor_read_encoder, 4, LinesSp);
display.setCursor(3, 1);// стрілки по боках активний профіль
display.write(25);
display.setCursor(118, 1);
display.write(25);
display.drawRoundRect(0, 0, 127, 10, 2, WHITE);// РАМКА АКТИВ ПРОФ НАЗВА ГРИ
display.drawRect(0, 9, 127, 19, WHITE);
display.drawRect(10, 29, 51, 19, WHITE);// РАМКА НАСТРОЙКИ КЮАР КОД
display.setCursor(21, 1);
display.print("Active profile");
display.setCursor(98, 36); // РИСКА В лыво РАЗОМ ПОЛУЧАЭТСЯ КРИВА СТРЫЛКА
display.println("_" );
display.setCursor(93, 36);
display.println("_" );
display.setCursor(91, 36);// СТРЫЛКА ВВЕРХ РАЗОМ ПОЛУЧАЭТСЯ КРИВА СТРЫЛКА
display.write(24);
display.setCursor(91, 34);
display.write(30);
display.drawCircle(113, 42, 11, WHITE);// КРУГ НАВК Sp
display.setCursor(105, 39);
display.print("Sp");
display.println(ap->number);
af_centerTextX(X(64), Y(15), ap->nameProfiles(get_name));
display.setCursor(0, 32); // РИСКА В ПРАВО РАЗОМ ПОЛУЧАЭТСЯ КРИВА СТРЫЛКА
display.println(" _" );
display.setCursor(4, 32);
display.println(" _" );
display.setCursor(66, 32);// СТРЫЛКА ВВЕРХ РАЗОМ ПОЛУЧАЭТСЯ КРИВА СТРЫЛКА
display.write(24);
display.setCursor(66, 30);
display.write(30);
display.setCursor(0, 31);
display.println(" Settings " );
display.println(" QR code");
display.setCursor(0, 49);
display.println(" Select profile ");
display.println(" Back" );
}
//
// Profile Mp
void Md_ProfileMp() {
switch (mdf_addres(check_emb, 2)) {
case address_not_entered:
Md_p_ProfileMp();
break;
case 1:
Md_profileSettingsMp();
break;
case 2:
Md_selectProfile();
break;
case 3: //back
mdf_cursor(set_pos, mdf_addres(get_add, 1));
mdf_addres(back_add, 1);
break;
case address_entered:
mdf_cursor(set_pos, 1);
break;
}
}
void Md_p_ProfileMp() {
const byte LinesMp[] = {35, 49, 57};
mdf_cursor(cursor_read_encoder, 3, LinesMp);
display.setCursor(3, 1);// стрілки по боках активний профіль
display.write(25);
display.setCursor(118, 1);
display.write(25);
display.drawRoundRect(0, 0, 127, 10, 2, WHITE);// РАМКА АКТИВ ПРОФ НАЗВА ГРИ
display.drawRect(0, 9, 127, 19, WHITE);
display.drawRect(10, 29, 51, 19, WHITE);// РАМКА НАСТРОЙКИ КЮАР КОД
display.setCursor(21, 1);
display.print("Active profile");
display.setCursor(98, 36); // РИСКА В лыво РАЗОМ ПОЛУЧАЭТСЯ КРИВА СТРЫЛКА
display.println("_" );
display.setCursor(93, 36);
display.println("_" );
display.setCursor(91, 36);// СТРЫЛКА ВВЕРХ РАЗОМ ПОЛУЧАЭТСЯ КРИВА СТРЫЛКА
display.write(24);
display.setCursor(91, 34);
display.write(30);
display.drawCircle(113, 42, 11, WHITE);// КРУГ НАВК Sp
display.setCursor(105, 39);
display.print("Mp");
display.println(ap->number);
af_centerTextX(X(64), Y(15), ap->nameProfiles(get_name));
display.setCursor(0, 32); // РИСКА В ПРАВО РАЗОМ ПОЛУЧАЭТСЯ КРИВА СТРЫЛКА
display.println(" _" );
display.setCursor(4, 32);
display.println(" _" );
display.setCursor(66, 32);// СТРЫЛКА ВВЕРХ РАЗОМ ПОЛУЧАЭТСЯ КРИВА СТРЫЛКА
display.write(24);
display.setCursor(66, 30);
display.write(30);
display.setCursor(0, 35);
display.println(" Settings " );
display.setCursor(0, 49);
display.println(" Select profile ");
display.println(" Back" );
}
//
//
// General settings
void Md_generalSettings(){
switch (mdf_addres(check_emb, 2)) {
case 0:
Md_p_generalSettings();
break;
case 1:
Md_various();//1
break;
case 2:
Md_controls();
break;
case 3:
Md_effects();
break;
case 4:
Md_expertSettings();//потрібно добавити пароль
break;
case address_entered:
if (mdf_addres(get_add, 2) == 5){//back
mdf_cursor(set_pos, mdf_addres(back_add, 1));
}
else {
mdf_cursor(set_pos, 1);
}
break;
}
/*
if (mdf_addres(get_add, 2) == 4){ //набравши пароль попадам в експерт настройки
switch (af_passwordEntry(typing_a_password)) {
case password_not_entered:
Md_p_generalSettings();
if (mdf_cursor(get_pos) != 3){
af_passwordEntry(password_reset);
mdf_addres(back_add, 2);
}
break;
case password_entered:
mdf_cursor(set_pos, 1);
break;
default:
Md_expertSettings();
break;
}
}
*/
}
void Md_p_generalSettings(){
const byte l = 5, p = 2, s = 64/2;
byte y[l];
af_menuLinesCenterY(l, p, s, y);
af_displayPrintLain("Various", X(6), y[0]);
af_displayPrintLain("Controls", X(6), y[1]);
af_displayPrintLain("Effects", X(6), y[2]);
af_displayPrintLain("Expert settings", X(6), y[3]);
af_displayPrintLain("Back", X(6), y[4]);
mdf_cursor(cursor_read_encoder, sizeof(y), y);
}
//
//
// Main display functions
void mdf_displayOutput() {
static byte switchOnOff = e_u_d; //переключатель включено виключено
if (1){
display.clearDisplay();
Md_settings();
display.display();
/*
if (Md_displayTimeout()){//відключаємо дисплей якщо не було ні дій n-кодера
switchOnOff = d_u_d;
display.clearDisplay();
display.display();
}
*/
}
}
// Cursor
byte mdf_cursor(byte acceptedCommand, byte getY, const byte* coordinate) {
static int8_t y = 0;
static int8_t cursorVisualTypes = cursor_type_one;
switch (acceptedCommand) {
case cursor_read_encoder:
// переміщаємо курсор якщо тип стрілки є cursor_type_one
// і якщо кнопка нкодра не натиснута при натиску і обертання використовується для прокрутки тексту Тому потрібно заборона на переміщення
if(cursorVisualTypes == cursor_type_one){
mdf_magnifier(y, 0 , getY-1 , 1 , 1);
}
mdf_p_cursor(cursorVisualTypes, coordinate[y]);
break;
case cursor_type_one:
cursorVisualTypes = cursor_type_one;
break;
case cursor_type_two:
cursorVisualTypes = cursor_type_two;
break;
case get_pos:
return y+1;
break;
case set_pos: //вказуємо позицію курс при переходах вперед або назад по адресу
y = getY-1;
cursorVisualTypes = cursor_type_one;//зазвичай Після цього потрібно вказати візуальний тип один щоб лишній раз не викликати робимо це автоматично
break;
}
return 0;
}
byte mdf_p_cursor(int visualTypes, int coordinateY) {
const byte cursor[] = {26, 16}; //cursor_type_one == 0 cursor_type_two == 1
display.setCursor(0, coordinateY);
display.write(cursor[visualTypes]);//вибираємо один з двох видів курсора з таблиці символів
return 0;
}
//
int mdf_addres(byte acceptedCommand, byte addNumb) {//N
static byte add[10];//для коректної роботи можна використати тільки вісім адресів 1-8
static byte inputMode = mode_click_input;
switch (acceptedCommand) {
case back_add:
{
mdf_cursor(cursor_type_one);
inputMode = mode_click_input;
byte temp = add[addNumb];
for (int i=9; i >= addNumb; i--){// команда back виконує видалення всіх адресів до числа вказаного в addNumb
add[i] = address_not_entered;
}
return temp;//також повертаємо адрес після видалення щоб можна було скористатися mdf_cursor(set_pos, mdf_addres(back_add, 1));
}
break;
case mode_click_back:
mdf_cursor(cursor_type_two);
inputMode = mode_click_back;
break;
case mode_function_returns:
mdf_cursor(cursor_type_two);
inputMode = mode_function_returns;
break;
case get_add:
return add[addNumb];
break;
case check_emb:
if (inputMode == mode_click_input){
//printArrayEverySecond(add, sizeof( add));
if (add[addNumb] == address_not_entered && af_checkClick()){// набір адреса
add[addNumb] = mdf_cursor(get_pos);//позиція курсора означає номер набраного адресу
return address_entered;//одноразово повертаємо повідомлення що адрес набрано щоб виконати якісь дії перед тим коли переходимо по адресу
}
}
else if (inputMode == mode_click_back){
if (add[addNumb+1] == address_not_entered && af_checkClick()){//якщо попадемо на нуль повертаємося
mdf_addres(back_add, addNumb);
return address_deleted;
}
}
return add[addNumb];
break;
}
return 0;
}
// magnifier
template <typename T>//функція працює безпосередньо з змінною напряму
bool mdf_magnifier(T &numeric, int minNam, int maxNam, int Speed1, int Speed2) {
if (af_checkFastRight()) {
numeric += Speed2;
Serial.println(2);
} else if (af_checkFastLeft()) {
numeric -= Speed2;
Serial.println(-2);
} else if (af_checkRight()) {
numeric += Speed1;
Serial.println(1);
} else if (af_checkLeft()) {
numeric -= Speed1;
Serial.println(-1);
} else {
return false;
}
numeric = af_clamp(numeric, minNam, maxNam);
return true;
}
//
void mdf_typeNumberPrint(){//функція відображає номер і тип профілю в інфодисплей і першому пункті меню
if (ap->tape == t_sp) {//номер тип відображається лише коли вибрано профіль
display.setCursor(49, 0);
display.print("(Sp");
display.print(ap->number);
display.print(")");
}
else if (ap->tape == t_mp) {//номер тип відображається лише коли вибрано профіль
display.setCursor(49, 0);
display.print("(Mp");
display.print(ap->number);//
display.print(")");
}
}
// Confirm
byte mdf_confirm(byte acceptedCommand, byte x, byte y){//функція надає варіант для вибору та каву ні або виводить повідомлення що не потрібно якщо нема потреби
static bsd selection(1);//вибір так або ні
static bsd clicksCount(3);//зміна накопичує вказані ліки при досягненні виконається дія
byte variants[] = {choice_no, choice_yes};
if (acceptedCommand == output_by_coordinates) {// кожен раз перед основним викликом одноразово функція приймає координати
mdf_p_confirm(output_by_coordinates, x, y);// переправляє координати в mdf_p_confirm для збереження
}
else if (acceptedCommand == output_by_center_x) {
mdf_p_confirm(output_by_coordinates, 64-27, y);
}
else if (acceptedCommand == print_not_need) {//після перевірки можна подати команду вивести повідомлення якщо підтвердження не потрібне
if (mdf_p_confirm(print_not_need) == tf_function_completed || af_checkClick()) {
mdf_p_confirm(tf_reset);
return tf_function_completed;
}
}
else if (acceptedCommand == offer_a_choice) {//якщо підтвердження потрібне запропоновуємо вибір виводимо так або ні
if (af_checkTurn()) {//при обертах енкодера в залежності від накопичених значень відбудеться різні дії
selection.p(1);//якщо вже є одиничка і ми доплюсуємо один змінна скинеться на нуль
mdf_p_confirm(change_selection); // міняємо позицію відображення рамки так або ні
clicksCount.r();//якщо було накопичення ліків відбудеться скидання із-за того що позиція рамки змінилася
}
if (af_checkClick()) {//робимо кліки і накопичуємо їх
clicksCount.p(1);
if (clicksCount.m()) { // якщо максимум повідомляємо що дані готові
mdf_p_confirm(tf_reset);
return tf_data_ready;
}
else if (variants[selection.g()] == choice_no) { //
mdf_p_confirm(tf_reset);
return tf_data_ready;
}
mdf_p_confirm(accelerate_frame_blinking);
}
mdf_p_confirm(offer_a_choice);
}
else if (acceptedCommand == tf_get_data) {//якщо получили tf_data_ready беремо дані-вибір
return variants[selection.r()]; //метод .r() одноразово повертає накопичне число і скидає його
}
return 0;
}
byte mdf_p_confirm(byte acceptedCommand, byte x, byte y){
const byte frameSizeWidth[] = {15, 21};// розмір рамки міняємо в залежності yes no
const byte framePosition[] = {2, 33};//координати для позиції рамки так ні
const int frameBlink[] = {1000, 500, 100};//test частота мигання рамки якщо натискаємо кнопку
static byte frequency;//frequency ЧАСТОТА вказує на частоту мигання рамки
static byte flashFlag;
static Timer tmr(frameBlink[frequency]);
static byte selection;
static byte azixX;
static byte azixY;
switch (acceptedCommand) {
case output_by_coordinates://зберігаємо координати для виведення
azixX = x;
azixY = y;
break;
case change_selection://якщо ми міняємо позицію рамки то скидання
selection = !selection;
frequency = 0;
tmr.st(frameBlink[frequency], true);
break;
case accelerate_frame_blinking://при накопиченні кліків збільшується частота блимання рамки навколо вибору
frequency++;
tmr.st(frameBlink[frequency], true);
break;
case tf_reset://якщо накопичили необхідну кількість bліків відбудеться скидання до початкового стану
selection = frequency = 0;
tmr.st(frameBlink[frequency], true);//після того як скинули frequency вказуємо знову чистоту
break;
case print_not_need://якщо настройки не мінялись виводимо повідомлення що не потрібне скидання
//test Потрібно провести перевірку логіка така що при скиданні встановлювалась секунда а тут секунда буде правда потім стане брехня
if (!tmr.ta()) {//це означає що пів секунди будемо виводити повідомлення допоки тут не появиться True
tmr.st(frameBlink[frequency], true);
return tf_function_completed;
}
display.setCursor(azixX+4,azixY+3); //
display.print("not need");
display.drawRect(azixX, azixY, 56, 15, WHITE);// велика рамка навколо
break;
case offer_a_choice://якщо настройки мінялись пропонується вибір
display.setCursor(azixX+4, azixY+4);
display.println("NO");
display.setCursor(azixX+35, azixY+4);
display.println("YES");
if (tmr.ta()) {//рамка світилась одразу після запуску або змінні вибору
display.drawRect(azixX + framePosition[selection], azixY + 2, frameSizeWidth[selection], 11, WHITE);//рамка навколо так або ні
}
display.drawRect(azixX, azixY, 56, 15, WHITE);// велика рамка навколо так ні
break;
}
return 0;
}
//
void mdf_bluetoothPrint() {
static Timer tmr(600);
display.drawCircle(6, 6, 6, WHITE);// кружок
if (bleGamepad.isConnected() || tmr.ta()) {
fd_bluetoothSymbol();
}
}
// Mdf
// Md
// Temperature control
void Md_p_temperatureIndicator(){
int tempC = thermocouple.readCelsius();
String tempString = "T: " + String(tempC, 1) + " c";
af_centerTextX(X(64), Y(0), tempString);
}
byte tc_temperatureControl(byte cmd, byte data){
static byte testSEN = 51;//test імітація сенсор датчика температури
static Timer tmrTempMin(60000, true);
static byte forcedStartPumps;//test
//static byte tempMinLimitAchieved;
static byte tempMinLimit = 50;//test
static byte tempMaxLimit = 90;
if (tmrTempMin.gp()){//ТАЙМЕР НЕ ПІДХОДИТЬ раз на хвилину провіряє мінімальну межу температура може бути нестабільною тому раз на хвилину
if (testSEN < tempMinLimit){//очікуємо зменшення щоб виключити
if (pumpRelay(get_pumps_status) == start_pumps){//якщо поверне forced_start_pumps то виключення не відбудеться допоки не перейдемо в межу мінімум а потім назад
pumpRelay(stop_pumps);
a_alarms(short_beep);//короткий сигнал повідомляємо що відбулось відключення
}
}
else if(tempMinLimit <= testSEN){//очікуємо збільшення щоб включити
if (pumpRelay(get_pumps_status) != start_pumps){ //не міняти умову!
pumpRelay(start_pumps);// відбувається запуск тепер при зменшенні температури відбудеться відключення
}
else if (tempMaxLimit <= testSEN){
a_alarms(alarms_start);//два коротких сигнала через хвилину повний Старт
}
}
}
if (cmd){
if(cmd == set_forced_start_pumps){
if(pumpRelay(get_pumps_status) == stop_pumps){//виключити можна тільки тоді коли ми ще не досягнули мінімально встановленої межі
pumpRelay(forced_start_pumps);
}
}
else if(cmd == set_forced_stop_pumps){
if(testSEN < tempMinLimit){//виключити можна тільки тоді коли ми ще не досягнули мінімально встановленої межі
pumpRelay(stop_pumps);
}
}
else if(cmd == set_min_t){
tempMinLimit = data;
tmr.st(60000, true); //перезапускаємо таймер коли міняємо час щоб було спрацювання коли міняємо температуру
}
else if(cmd == set_max_t){
tempMaxLimit = data;
}
}
}
// T
// Alarms
byte a_alarms(byte cmd){
byte alarmMode;
if(alarmMode != silent_mode){
if(alarmMode == alarms_start){}
else if(alarmMode == short_stop){}
else if(alarmMode == short_beep){}
else if(alarmMode == alarm_temporary_shutdown){}
}
if(cmd){
alarmMode = cmd;
}
}
// A
// Relay module
//
void setup() {
Wire.begin();
Serial.begin(115200);
delay(1000);
Serial.println("Serial.println("");");
// Encoder setting
enc.setFastTimeout(2000); // Таймаут для швидкого повороту енкодера (в мс)
enc.setType(TYPE2);
//
// ----------Display--------------
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextSize(1);
display.setTextColor(WHITE);
display.clearDisplay();
display.display();
//
//робота з енергонезалежною пам'яттю
EEPROM.begin(512);
delay(50);
EEPROM.put(0, 1);EEPROM.commit();//test
//af_saveToEEPROM(eeprom_initialization); //беремо збереження настройки з енергонезалежної пам'яті
//
}
void loop() {
enc.tick();
mdf_displayOutput();
}// void loop()