#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "GyverEncoder.h"
#include <EEPROM.h>
#include "I2Cdev.h"//не забути про розгон!!!
#include "MPU6050_6Axis_MotionApps20.h"
#include <BleGamepad.h> // при загрузці нa реально Arduino даний фрагмент розкоментувати a пуcті класи закоментувати
#include <FastLED.h> //FastLED Daniel Garcia
// 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_return_non_standard 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
//
// Gf_rjdc()
#define get_counter 0
#define counter_minus 1
#define r_d_c 2 // counter auto reset restart
//
// Gf_rjd()
#define rjd_get_data 3
//
//Md_moveButtonsAnimation();
#define print_batton 20
#define reset_animation 21
//
//af_displayPrintLain()
#define L(x) (x) // line
#define N(x) (x) // number
//
//Gf_combinedGyroRightJoystick()Gf_da()
#define g_x 0
#define g_y 1
#define s_x 2
#define s_y 3
#define joystick_full_priority 1
#define joystick_disable_gyro 2
#define gyro_joystick_combination 3
#define priority_by_magnitude 4
#define gyro_full_priority 5
#define only_gyro 6
#define g_o_m 7
//
//Gf_combinedGyroRightJoystick()
#define g_x_h 1//
#define g_y_v 2
#define j_x_h 0
#define j_y_v 1
#define c_x 0
#define c_y 1
//
//Gf_g()
#define g_x 0
#define g_y 1 //g_z = axis y horizon
#define g_z 2 //g_z = axis z vertical
#define g_g_d 20 //g_g_d = get gyro data
#define r_g_d 21 //g_g_d = get gyro data
#define g_e 22
#define g_d 23
//
//Gf_leftJoystick() Gf_rj() Gf_rjd()
#define x_l 0 // x_l = X left
#define x_r 1 // x_r = X right
#define y_u 2 // y_u = Y up
#define y_d 3 // y_d = Y down
#define x_a 0 // x_a = X axis h
#define y_a 1 // y_a = Y axis v
#define r_a_d 20
#define g_a_d 21 // g_a_d = get analog data
#define r_j_e 22
#define r_j_d 23
#define g_d_z 24 // g_d_z = get dead zones
#define g_m_m 25 // gmm = get minimum maximum
#define s_d_z 26 // s_d_z = set dead zones
#define s_m_m 27 // s_m_m = set minimum maximum
#define c_c_j 28 // = check calibrate joystick
#define s_c_w 28 // s_c_w = stop check Work
//
//Md_disableButton()
#define check_for_pressed_buttons 20
#define deactivated_buttons_get 21
#define deactivation_buttons_start 22
#define reset_deactivation_buttons 23
//
//Md_saveSelectedProfile( )
#define ssp_get_setting 20
#define ssp_change_setting 21
//
//af_triggerLight()
#define color_light_setting 20
#define color_switching 21
#define clssc 22 //clssc = color light standard settings check
#define color_light_reset 23
#define color_one 24
#define color_two 25
#define get_color1 26
#define get_color2 27
#define get_light1 28
#define get_light2 29
#define set_color1 30
#define set_color2 31
#define set_light1 32
#define set_light2 33
#define color_light_default 34
//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
//
//Effects
//af_vibration();
//#define p_vibration_motor_18 18
#define vibration_start 21
#define vibration_stop 22
#define vt_get_permission 23
#define vt_pin_activate 24
#define vt_change 25
//
// af_shotgunBolt()
#define trigger_light_change 20
#define trigger_get_light 21
#define change_color 22
#define trigger_color_red 23
#define trigger_color_dreen 24
#define trigger_color_start 25
//
// af_shotgunBolt()
//#define p_bolt_rifle_12 12
#define bolt_start 21
#define bolt_stop 22
#define bolt_get_permission 23
#define bolt_pin_activate 24
#define bolt_change 25
//
//
//bgpr()
#define set_press_permit 20
#define get_press_permit 21
#define set_reset_permit 22
#define get_disconnected_number 23
//
// af_passwordEntry()
#define typing_a_password 20
#define password_reset 21
#define password_not_entered 22
#define password_entered 23
//
//Gf_gamepadButtonsDuplicators()
#define dbrs_change_mode 20
#define dbrs_get_mode 21
#define dbrs_reset 22
//
// 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_movingButtons()
#define choice_bat_waiting 20
#define button_selection_complete 21
#define return_default 22
#define select_button 23
#define select_button_completed 34 //34 тому що щоб не було співпадінь з кнопками
//
//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
//
// Md_duplicateButtons()
#define d_b_d_m 0 // duplicate Buttons default mode
#define select_button_r_s 20
#define select_button_r_m 21
#define duplicate_button_reset 22
#define show_duplicate_buttons 23
#define r_s_button_reset 24
#define r_m_button_reset 25
#define select_regime_r_s 26
//
// Button gamepad class
#define touch_dead_zone 50
//getCalibrationData()
#define d_z 0 // Dead zone
#define m_v 1 // maximum value
// ButGamepad(byte gBT, byte gBN, byte pRT, byte n)
#define button_gamepad 20
#define trigger_gamepad 21
#define tactile_read 22
#define touch_read 23
#define analog_read 24
// byte (*gamepadPressPtr)();
#define read_button_state 20
#define gamepad_button_pressed 22
#define gamepad_button_release 23
#define button_get_name 24
#define r_b_double_press 25
#define read_analog_state 26
#define get_analog_state 27
#define i_p_p_g_b 28 //input pullup pin gamepad button
//byte. bgc_buttonGamepadPressRelease();
#define press_button 1
#define release_button 2
#define trigger_motion_detected 3
//
// Profiles class
#define t_sp 1// не міняти число!
#define t_mp 2// не міняти число!
#define r_off 0 // = off
#define r_on 12 // = on
//Md_changeNameProfile()
#define cl 24
#define dl 25
#define tb 26
#define sb 27
#define g_s_n_i_n_l_a_r_s 28 // (give standard name if no letters and remove spaces ) дати стандартну назву, якщо немає букв
#define get_name 29
#define get_name_a_f 30
#define add_letter 31
#define delete_letter 32
// *mps()
#define gapn 22 //get active profile number-name
#define activate_p_sp 23
#define activate_p_mp 24
#define eeprom_initialization 25
#define activate_p 26
#define copy_profile 27
#define not_save 30
#define save_changes 31
#define active_copy_compare 32
#define active_copy_same 33 // зафіксовані зміни
#define reset_gyro 34
#define reset_all_mp 35
//listButton[19]//даний масив має в собі номер-назву кнопки потрібно для порядкового опитування
#define button_0 0 //неіснуюча кнопка щоб виконувати деактивації
#define button_1 1 // список програмних кнопок які натискаються різними фізичними методами
#define button_2 2
#define button_3 3
#define button_4 4
#define button_5 5
#define button_6 6
#define button_7 7
#define button_8 8
#define button_select 9
#define button_start 10
#define button_home 11
#define dpad_up 12
#define dpad_down 13
#define dpad_left 14
#define dpad_right 15
#define trigger_r 16
#define trigger_l 17
//
// ButGamepad butGamepad[] порядковий номер-назва екземплярів класу
#define rifle_stock 17//це немає відношення до listButton[-]
#define rifle_magazine 18
//
uint16_t tmrProf = 0;
#define enter_default_location 20
#define check_location 21
#define standard_location 22
#define non_standard_location 23
//
// 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
//Gf_functionsGamepad( ){
#define functions_gamepad_stop 20
#define functions_gamepad_start 21
//
//
// c-v-a
// Function prototypes
byte Gf_r_j_d(int cmd = 0, int* cds = nullptr)
void af_controlsCheck();
bool af_controlsCheckExecution(bool mode = 0);
void Gf_controlElementsStope(void);
bool af_checkLeftTrigger(bool mode = 0);
bool af_checkRightTrigger(bool mode = 0);
void printgetCalibrationData();
int* Md_dzcdrj(bool getArr = false, bool arrClearFill = false);
int* Md_dzcdlj(bool getArr = false, bool arrClearFill = false);
byte Md_joysticksCalibrationDeadZones();
int* Md_mvcdrj(bool getArr = false, bool arrClearFill = false);
int* Md_mvcdlj(bool getArr = false, bool arrClearFill = false);
void Md_joysticksCalibrationMaximumValues();
int16_t* Gf_g(int cmd = 0);
int* Gf_rj(int cmd = 0, int* newData = nullptr);
int Gf_da(int cmd = 0, int data = 0);
byte Gf_combinedGyroRightJoystick(int cmd = 0);
int* Gf_leftJoystick(int cmd = 0, int* newData = nullptr);
template<typename T>
bool mdf_magnifier(T &numeric, int minNam = 0, int maxNam = 0, int Speed1 = 0, int Speed2 = 0);
int af_triggerLight(byte acceptedCommand = 0, int acceptedData = 0);
void Gf_functionsGamepad(byte acceptedCommand = 0);
void Gf_sendReport(byte acceptedCommand = 0);
void Md_moveButtonsAnimation(byte cmd = 0, byte buttonOne = 0, byte buttonTwo = 0);
void Md_gyroCalibrationExecution(byte acceptedCommand = 0);
byte af_shotgunBolt(byte acceptedCommand = 0);
byte af_vibration(byte acceptedCommand = 0);
int af_saveToEEPROM(int acceptedCommand = 0, void* address = nullptr, size_t dataSize = 0);
byte Gf_gamepadButtonsDuplicators(byte acceptedCommand = 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);
void Md_p_duplicateButtons(byte acceptedCommand = 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);
void af_triggerShootingEffects(byte startEffects = 0);
// Fp
// Ae
// Class instances from external libraries
Adafruit_SSD1306 display(128, 64, &Wire, -1);
BleGamepadConfiguration bleGamepadConfig;//потрібно для настройок геймпада
BleGamepad bleGamepad("FS G36", "UA", 100);
Encoder enc(p_clk_33, p_dt_32, p_sw_25);
MPU6050 mpu;
CRGB leds[1];
//
// 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
// Profiles class + supporting functions
class Profiles {
public:
Profiles(int deZoH, byte senHor, int deZoV, byte senVer, const char* nameProf, byte tpProf, byte nabProf)
:deadZoneHor(deZoH),
sensitivityHor(senHor),
deadZoneVer(deZoV),
sensitivityVer(senVer),
nps(nameProf),
tape(tpProf),
namber(nabProf),
status(r_off)
{
malb(enter_default_location);//(listButton) даємо стандартні імена-номера кнопкам + дублікатори натискають кнопку нуль
}
Profiles() {}// структор без параметрів потрібен для сейф профайл
//
//гіроскоп настройка
void gyroReset(){
sensitivityHor = sensitivityVer = 150;//це означає що чутливість мінімальна а максимальна 50
deadZoneHor = deadZoneVer = 0;
}
bool gssc(){//gssc = gyroStandardSettingsCheck якщо в нас стандартні настройки повернуться правда щоб не виконувати дії в подальшому використовується
return ((!deadZoneHor && !deadZoneVer) && (sensitivityVer == 150 && sensitivityHor == 150));
}
void setDeZoHor(int newDeZoH) {// set
if (deadZoneHor != newDeZoH) {//якщо встановлюємо нову мертву зону чутливість потрібно скинути чутливість настроюється після мертвої зони
sensitivityHor = 150;//ставим мінімальну чутливість в даному випадку це 150
}
deadZoneHor = newDeZoH;
}
void setSenHor(byte senHor) {
sensitivityHor = senHor;
}
void setDeZoVer(int newDeZoV) {
if (deadZoneVer != newDeZoV) {
sensitivityVer = 150;
}
deadZoneVer = newDeZoV;
}
void setSenVer(byte senVer) {
sensitivityVer = senVer;
}
//
// робота з іменами профілів String nps;
String nameProfiles(byte acceptedCommand, char Letter = ' '){// функція працює з іменем профілю
static Timer tmr(1000);
switch (acceptedCommand) {
case get_name://повертаємо ім'я
return nps;
break;
case add_letter://додаємо букву якщо є місце
if (nps.length() < 20) {nps += Letter;}
break;
case delete_letter://якщо є букви То видаляємо
if (nps.length()) {nps.remove(nps.length()-1, 1);}//nps.remove(кількість букв які потрібно оставити-1, кількість звуків які потрібно Видалити В кінці);
break;
case get_name_a_f://iм'я активного профілю в списку буде мигати
if (status == r_on) {//test можливі баги чомусь не мигало
return tmr.ta() ? nps:"";
}else{
return nps;
}
break;
case g_s_n_i_n_l_a_r_s://give standard name if no letters and remove spaces
checkForLetters();//стандартне ім'я якщо пустота
trimTrailingSpaces();//видаляємо пробіли в кінці
break;
}
return "";
}
void checkForLetters() {//check for letters перевірити наявність букв якщо немає букв То дати стандартне ім'я
bool allSpaces = true; // Припускаємо, що всі символи — пробіли
// Перевіряємо кожен символ у рядку
for (int i = 0; i < nps.length(); i++) {
if (nps[i] != ' ') { // Якщо хоча б один символ не пробіл
allSpaces = false;
break; // Можна припинити перевірку, якщо знайдено символ, відмінний від пробілу
}
}
// Якщо рядок порожній або складається лише з пробілів
if (nps.length() == 0 || allSpaces) {
resetName();
}
}
void trimTrailingSpaces() { // trim Trailing Spaces видаляю пробіли в кінці речення
int i = nps.length() - 1;
while (i >= 0 && nps.charAt(i) == ' ') {
i--; // Знаходимо позицію останнього непустого символу
}
nps.remove(i + 1); // Видаляємо всі символи після останнього значущого
}
byte lettersTakeNumber(){//повертає кількість букв в реченні
return nps.length();
}
void resetName(){
nps = " My profile " + String(namber) + String(" "); // Присвоюємо стандартне ім'я
}
//
byte malb(byte acceptedCommand) {//malb = management array list buttons //функція працює з порядковими номерами кнопок геймпада
if (acceptedCommand == enter_default_location) { // Скеровування до активного профілю
for (byte i = 0; i <= 16; i++) {//стандартний порядок кнопок два останніх елемента використовуються для дублювання
listButton[i] = i + 1; //вказуємо кнопкам номер-ім'я (i + 1 не чіпати Все правильно)
}
listButton[17] = listButton[18] = 0;//iціалізуємо дублікатори нулями тому що там сміттєві значення там потрібні нулі тому що кнопка нічого не дублює
}
else if (acceptedCommand == check_location) {
for (byte i = 0; i <= 16; i++) {//якщо розташування кнопок помінялось місцями функція видасть брехня(стандарт від 1-15)
if (listButton[i] != i+1) {
return non_standard_location;
}
}
return standard_location;
}
return 0;
}
byte setStatus(byte st) {
status = st;
return 0;
}
void profileResetAll(){//функція скидає всі настройки профілю
gyroReset();
resetName();
malb(enter_default_location);//даємо стандартні імена-номера кнопкам
}
void eepromProfile(byte acceptedCommand){
if (acceptedCommand == get_eeprom) {
char buffer[21];
af_saveToEEPROM(get_eeprom, (void*)buffer, 15676);
nps = String(buffer);
af_saveToEEPROM(get_eeprom, (void*)listButton, sizeof(listButton));
af_saveToEEPROM(get_eeprom, &deadZoneHor, sizeof(deadZoneHor));
af_saveToEEPROM(get_eeprom, &sensitivityHor, sizeof(sensitivityHor));
af_saveToEEPROM(get_eeprom, &deadZoneVer, sizeof(deadZoneVer));
af_saveToEEPROM(get_eeprom, &sensitivityVer, sizeof(sensitivityVer));
af_saveToEEPROM(get_eeprom, &tape, sizeof(tape));
af_saveToEEPROM(get_eeprom, &namber, sizeof(namber));
af_saveToEEPROM(get_eeprom, &status, sizeof(status));
//af_saveToEEPROM(get_eeprom, &lblm, sizeof(lblm));
af_saveToEEPROM(get_eeprom, &tape, sizeof(tape));
af_saveToEEPROM(get_eeprom, &rrsps, sizeof(rrsps));
}
else if (acceptedCommand == set_eeprom) {
af_saveToEEPROM(set_eeprom, (void*)nps.c_str(), 21);
af_saveToEEPROM(set_eeprom, (void*)listButton, sizeof(listButton));
af_saveToEEPROM(set_eeprom, &deadZoneHor, sizeof(deadZoneHor));
af_saveToEEPROM(set_eeprom, &sensitivityHor, sizeof(sensitivityHor));
af_saveToEEPROM(set_eeprom, &deadZoneVer, sizeof(deadZoneVer));
af_saveToEEPROM(set_eeprom, &sensitivityVer, sizeof(sensitivityVer));
af_saveToEEPROM(set_eeprom, &tape, sizeof(tape));
af_saveToEEPROM(set_eeprom, &namber, sizeof(namber));
af_saveToEEPROM(set_eeprom, &status, sizeof(status));
//af_saveToEEPROM(set_eeprom, &lblm, sizeof(lblm));
af_saveToEEPROM(set_eeprom, &tape, sizeof(tape));
af_saveToEEPROM(set_eeprom, &rrsps, sizeof(rrsps));
}
}
bool operator==(const Profiles& other) const {//використовується для порівняння профілів
return (deadZoneHor == other.deadZoneHor) &&
(sensitivityHor == other.sensitivityHor) &&
(deadZoneVer == other.deadZoneVer) &&
(sensitivityVer == other.sensitivityVer) &&
(tape == other.tape) &&
(namber == other.namber) &&
(status == other.status) &&
(memcmp(listButton, other.listButton, sizeof(listButton)) == 0) &&
(nps == other.nps) &&
(rrsps == other.rrsps);
}
String nps;// Динамічний масив для імені профілю//nps = name profile string
byte listButton[19];//зберігаю номер програмної кнопки
int deadZoneHor;// (primary початкова) початок руху
byte sensitivityHor;
int deadZoneVer;
byte sensitivityVer;
byte tape;
byte namber;
byte status;
//byte lblm;//list button located monitoring
byte rrsps = 1; //rrsps = regime rifle stock profile save
private:
};
// Profile designers конструктори класу Profiles
Profiles profil[] = {// test
Profiles (0, 150, 0, 150, "PROFILE NOT SELECTED", 0, 0),// використовується якщо профіль не вибрано
Profiles (2000, 100, 2556, 92, "CodMW", t_sp, 1),
Profiles (166, 120, 1965, 112, "Batl3", t_sp, 2),
Profiles (2000, 100, 2556, 92, "SP 33", t_sp, 3),
Profiles (166, 120, 1965, 112, "SP 44", t_sp, 4),
Profiles (2000, 100, 2556, 92, "SP 55", t_sp, 5),
Profiles (166, 120, 1965, 112, "SP 66", t_sp, 6),
Profiles (2000, 100, 2556, 92, "SP 77", t_sp, 7),
Profiles (2000, 100, 2556, 92, "05461545454546441264", t_mp, 1),
Profiles (166, 120, 1965, 112, "Xmen 74 ", t_mp, 2),
Profiles (2000, 100, 2556, 92, "Mp 3 ", t_mp, 3),
Profiles (166, 120, 1965, 112, "Mp 4 ", t_mp, 4),
Profiles (2000, 100, 2556, 92, "Mp 5 ", t_mp, 5),
Profiles (166, 120, 1965, 112, "Mp 6 ", t_mp, 6),
Profiles (2000, 100, 2556, 92, "Mp 7 ", t_mp, 7)
};
// Pd
byte mps(byte acceptedCommand, byte number = 0 , byte acceptedData = 0) {// mps= management profile setting
static Profiles saveProfile;//використовується для збереження настройок щоб можна Їх було повернути
static byte active = 0;
if (acceptedCommand == gapn) { // Скеровування до активного профілю
return active;
}
else if (acceptedCommand == activate_p) {// вибраний профіль активується
for (int i = 1; i < 15; i++) {
profil[i].setStatus(r_off);
}
active = number;
profil[active].setStatus(r_on);
}
else if (acceptedCommand == copy_profile) { //виконується одноразово при наборі адресу при заході в настройки див. адрес 2
saveProfile = profil[active];
}
else if (acceptedCommand == active_copy_compare) {
return (saveProfile == profil[active]) ? active_copy_same : 0;
}
else if (acceptedCommand == not_save) {
profil[active] = saveProfile;
}
//скидання настроек гіроскопа
else if (acceptedCommand == reset_gyro) {
if (profil[mps(gapn)].gssc()) {//gssc = gyroStandardSettingsCheck якщо є стандартні настройки і хочемо скинути то відображаємо повідомлення що не потрібно
if (mdf_confirm(print_not_need) == tf_function_completed) {
return tf_function_completed;
}
}
else if (mdf_confirm(offer_a_choice) == tf_data_ready) {//якщо настройки нестандартні перевіряємо чи є готові дані-вибір
if (mdf_confirm(tf_get_data) == choice_yes) {profil[mps(gapn)].gyroReset();}//якщо настройки нестандартні перевіряємо чи є готові дані вибір
return tf_function_completed;//в любому випадку так або ні повертається команда tf_function_completed
}
}
//
else if (acceptedCommand == tf_reset_settings) {
active = 8;
}
else if (acceptedCommand == get_eeprom) {
af_saveToEEPROM(get_eeprom, &active, sizeof(active));
profil[active].setStatus(r_on);
}
else if (acceptedCommand == set_eeprom) {
if (Md_saveSelectedProfile(ssp_get_setting)) {//якщо є дозвіл тоді зберігаємо якщо ні то при включенні потрібно буде вибрати профіль
af_saveToEEPROM(set_eeprom, &active, sizeof(active));//!!!
}
}
return 0;
}
// Pc
// Button debounce class
class DebButton {// нагадування Не міняй сука нічого тут все добре
private:
const byte pin;
Timer tmr(false, true);; //test
byte debounceTime;
byte buttonState = 0;
public:
// Конструктор класу, ініціалізація піна, номера кнопки геймпада та часу захисту від випадкових спрацьовувань
DebButton(int p, int dT = 10) : pin(p) {
debounceTime = dT;
pinMode(pin, INPUT_PULLUP);
}
// Метод для обробки натискання кнопки
bool press() {
if (!digitalRead(pin) && !buttonState && tmr.ts()) {// після натську кнопки буде повернення true
tmr.st(debounceTime, false);
buttonState = true;
}
else if (buttonState && digitalRead(pin) && tmr.ts()) {// пісня відпущення кнопки буде повернення брехня
tmr.st(debounceTime, false);
buttonState = false;// тепер переміна буде брехня
}
return buttonState;// постійно повертаємо стан кнопки
}
};
// Bdc
// Button gamepad class + supporting functions
class ButGamepad {
private:
byte gamepadButtonName;
byte pinReadType;
byte numbPin;
int flagPress = 0; //можемо зчитати через метод Get стан кнопки
uint32_t tmr = 0;
int cd[2] = {2048+300, 4095-100};//calibration data //потрібно зробити получення даних з енергонезалежної пам'яті
int ars; //analog read seve
int ar; //analog read
//вказівник вказує на одну з вибраних функцій які ми вибираємо конструкторi
typedef byte (ButGamepad::*GamepadPressMethod)(byte);
GamepadPressMethod gamepadPressPtr;
public:
// функції використовуються всередині класу обробка натиску кнопки
int tactilePinButtonRead(byte acceptedCommand = 0) {//кожна функція має мати параметр тому що має бути схожість
if (acceptedCommand == read_button_state) {//після натиску відбувається одноразове повернення правда або брехня
bool dr = digitalRead(numbPin);
if (!dr && !flagPress && (tmr < millis())) {//
tmr = millis() + 50;
flagPress = true;
return press_button;
}
else if (flagPress && dr && (tmr < millis())) {//
tmr = millis() + 50;
flagPress = false;// тепер переміна буде брехня
return release_button;
}
}
else if (acceptedCommand == i_p_p_g_b && pinReadType == tactile_read){ //i_p_p_g_b=input pullup pin gamepad button)
pinMode(numbPin, INPUT_PULLUP);
}
return 0;// постійно повертаємо стан кнопки
}
int touchPinButtonRead(byte acceptedCommand = 0) {
bool tr = touch_dead_zone < touchRead(numbPin);
if (tr && !flagPress && (tmr < millis())) {// після натську кнопки буде повернення true
tmr = millis() + 50;
flagPress = true;
return press_button;
}
else if (!tr && flagPress && (tmr < millis())) {// пісня відпущення кнопки буде повернення брехня
tmr = millis() + 50;
flagPress = false;
return release_button;
}
return 0;// постійно повертаємо стан кнопки
}
int analogPinButtonRead(byte acceptedCommand = 0) {
if(acceptedCommand == read_button_state){
ar = analogRead(numbPin);
bool dze = cd[d_z] < ar;// Dead zone Exit
if (gamepadButtonName == trigger_r || gamepadButtonName == trigger_l) {
switch (flagPress) {
case false://
if (dze) {
flagPress = true;// міняємо шлях виконання
return trigger_motion_detected; //повертаємо дану команду щоб виконати return press_button;
}
break;
case true:
if (dze) {
if (ar != ars) {// якщо є вихід з мертвої зони і є зміни даних повертаємо повідомлення щоб відправити ці дані
ars = ar;
return trigger_motion_detected;
}
}
else if (!dze) {// пісня відпущення курка буде повернення брехня одноразово значить курок в мертвій зоні
ars = flagPress = false;//скидаємо щоб очікувати вихід з мертвої зони
return release_button;
}
break;
}
else{
if (dze != flagPress) {
flagPress = dze;
if (flagPress) {return press_button;}
else {return release_button;}
}
}
}
}
else if (acceptedCommand == get_analog_state){
int temp = constrain(ar, cd[d_z], cd[m_v]);
return map(temp, cd[d_z], cd[m_v], 0, 32767); // test потрібно буде підібрати плюс або мінус
}
return 0;
}
//
// Конструктор проводимо настройку при першому запуску
ButGamepad(byte gBN, byte pRT, byte nP) {
gamepadButtonName = gBN;
pinReadType = pRT;
numbPin = nP;
// вибір яким способом-методом буде натиснути кнопки геймпада 1-16. варіанти tactile touch analog
if (pinReadType == tactile_read) {
gamepadPressPtr = &ButGamepad::tactilePress;
}
else if (pinReadType == touch_read) {
gamepadPressPtr = &ButGamepad::touchPress;
}
else if (pinReadType == analog_read) {
gamepadPressPtr = &ButGamepad::analogReadPress;
}
//
}
//
// Bluetooth button (методи які фізично зчитують стан піна і натискають програм кноп)
byte tactilePress(byte acceptedCommand) {
if (acceptedCommand == read_button_state) {
switch (tactilePinButtonRead(read_button_state)) {
case press_button:
bgc_buttonGamepadPressRelease(press_button, gamepadButtonName);
return press_button;
case release_button: //після того як кнопка відпущена повертається правда
bgc_buttonGamepadPressRelease(release_button, gamepadButtonName);
return release_button;
}
}
else if (acceptedCommand == r_b_double_press) {//використовується для кнопки приціл коли не можна змінити режим включення прицілу
switch (tactilePinButtonRead(r_b_double_press)) { ///test
case true:
bgc_buttonGamepadPressRelease(press_button, gamepadButtonName);
break;
case false:
bgc_buttonGamepadPressRelease(release_button, gamepadButtonName);
break;
}
}
return 0;
}
byte touchPress(byte acceptedCommand) {
if (acceptedCommand == read_button_state) {
switch (touchPinButtonRead()) {
case press_button:
bgc_buttonGamepadPressRelease(press_button, gamepadButtonName);
return press_button;
case release_button:
bgc_buttonGamepadPressRelease(release_button, gamepadButtonName);
return release_button;
}
}
return 0;
}
byte analogReadPress(byte acceptedCommand) {
if (acceptedCommand == read_button_state) {
switch (analogPinButtonRead(read_button_state)) {
case false:
break;
case press_button: // якщо віртуальну кнопку натискаємо за допомогою аналогового піна
bgc_buttonGamepadPressRelease(press_button, gamepadButtonName);
return press_button;
case release_button: // виконуємо відпущення кнопки якщо попали в dz використовується в двох випадках press_button trigger_motion_detected
bgc_buttonGamepadPressRelease(release_button, gamepadButtonName);
return release_button;
case trigger_motion_detected: // якщо в віртуальну кнопку курок натискаємо за допомогою аналога виконується даний кейс
bgc_buttonGamepadPressRelease(press_button, gamepadButtonName, analogPinButtonRead(get_analog_state));
return press_button; // тепер коли курок міняє положення постоянно буде повертатись Дана команда щоб виконати сет Report
}
}
return 0;
}
//
// Різне
int getAnalogData() {
return analogRead(numbPin);
}
bool calibrationTrigger() {//
art = analogRead(numbPin); // art analog Read temp
if (art < 2048-300 || 2048+300 < art) {//1he спрацювання виводимо повідомлення 2ge спрацювання виконуємо відключення
gamepadButtonName = 0;
return true;//повертаємо правда виконуємо дії залежності від етапу перезапуск або відключення
}
if (!cd[0]) {//це означає перший запуск в енергонезалежній пам'яті не було даних
cd[0] = art;
}
else if (cd[0] < art) {//записуємо максимальну величину потрібно підібрати полярність//
cd[0] = art;
}
return false;
}
byte getName(){
return gamepadButtonName;
}
void buttonReinstall(byte newButton){//
gamepadButtonName = newButton;//
}
byte gamepadBatton(byte acceptedCommand) { //функція повертає вказівник який вказує на функцію яка була вибрана через конструктор
return (this->*gamepadPressPtr)(acceptedCommand);
}
int* getCalibrationData(){//калібровочні дані для курків
return cd;
}
void setCalibrationData(int newData[]){//калібровочні дані для курків
memcpy(cd, newData, sizeof(cd));
}
bool getStatus(){//получимо стан флажка-кнопки
return flagPress;//стан флажка міняється в допоміжних функціях які обробляють сигнал дивитись вверху
}
//
};// Button gamepad
//створюємо екземпляри класу настроюємо вказівник на необхідну функцію
//ButGamepad(1(reinstall!), 2, 3) 1-вибираємо курок чи кнопка 2-вибираємо чим будемо натискати сенсорна-звичайна кнопка або аналог 3-номер піна
ButGamepad butGamepad[] = {// test конструктор класів вказуємо номера пінів і способи зчитування які відповідають за кнопки
ButGamepad(profil[mps(gapn)].listButton[0], tactile_read, 5), // стандартно listButton[0] = gamepad button n1
ButGamepad(profil[mps(gapn)].listButton[1], tactile_read, 23),
ButGamepad(profil[mps(gapn)].listButton[2], tactile_read, 16),
ButGamepad(profil[mps(gapn)].listButton[3], tactile_read, 16),
ButGamepad(profil[mps(gapn)].listButton[4], tactile_read, 16),
ButGamepad(profil[mps(gapn)].listButton[5], tactile_read, 16),
ButGamepad(profil[mps(gapn)].listButton[6], tactile_read, 16),
ButGamepad(profil[mps(gapn)].listButton[7], tactile_read, 16),
ButGamepad(profil[mps(gapn)].listButton[8], tactile_read, 16), // button_select = 9
ButGamepad(profil[mps(gapn)].listButton[9], tactile_read, 16), // button_start = 10
ButGamepad(profil[mps(gapn)].listButton[10], tactile_read, 16), // button_home = 11
ButGamepad(profil[mps(gapn)].listButton[11], tactile_read, 16), // dpad_up = 12
ButGamepad(profil[mps(gapn)].listButton[12], tactile_read, 17), // dpad_down = 13
ButGamepad(profil[mps(gapn)].listButton[13], tactile_read, 16), // dpad_left = 14
ButGamepad(profil[mps(gapn)].listButton[14], tactile_read, 17), // dpad_right = 15
ButGamepad(profil[mps(gapn)].listButton[15], analog_read, p_triger_l_34), // trigger_r = 16
ButGamepad(profil[mps(gapn)].listButton[16], analog_read, p_triger_r_35), // trigger_l = 17
ButGamepad(profil[mps(gapn)].listButton[17], tactile_read, 16), // два останніх для дублювання кнопок
ButGamepad(profil[mps(gapn)].listButton[18], tactile_read, 16)
};
void bgc_buttonGamepadPressRelease(byte command, byte buttonName, int analogData) {//функція натискає кнопки 1-8 Select Start і d-пад викликається в кожному екземплярі класу через метод gamepadBatton analogData = 32767
static byte x = 1;
static byte y = 1;
const byte dPad[3][3] = {
{HAT_UP_LEFT, HAT_UP, HAT_UP_RIGHT},
{HAT_LEFT, HAT_CENTERED, HAT_RIGHT},
{HAT_DOWN_LEFT, HAT_DOWN, HAT_DOWN_RIGHT}
};
if (command == press_button) {
switch (buttonName) {
case button_0://використовується щоб відключати кнопки кнопки нуль не існує
case button_1:
case button_2:
case button_3:
case button_4:
case button_5:
case button_6:
case button_7:
case button_8:
bleGamepad.press(buttonName);
break;
case button_select:// 9
bleGamepad.pressSelect();
break;
case button_start:// 10
bleGamepad.pressStart();
break;
case button_home://11
bleGamepad.pressHome();
break;
case dpad_up:// 12-13
case dpad_down:
x = map(buttonName, dpad_up, dpad_down, 0, 2);
bleGamepad.setHat1(dPad[x][y]);
break;//не прибирати брейк
case dpad_left:// 14-15
case dpad_right:
y = map(buttonName, dpad_left, dpad_right, 0, 2);
bleGamepad.setHat1(dPad[x][y]);
break;
case trigger_l://16
bleGamepad.setLeftTrigger(analogData);//якщо звичайна кнопка то встановиться максимум 32767 якщо аналог дані будуть іншими в залежності від сили натиску
break;
case trigger_r://17
bleGamepad.setRightTrigger(analogData);
break;
}
}
else if (command == release_button) {
switch (buttonName) {
case button_0:
case button_1:
case button_2:
case button_3:
case button_4:
case button_5:
case button_6:
case button_7:
case button_8:
bleGamepad.release(buttonName);
break;
case button_select://9
bleGamepad.releaseSelect();
break;
case button_start://10
bleGamepad.releaseStart();
break;
case button_home://11
bleGamepad.releaseHome();
break;
case dpad_up://12-13
case dpad_down:
x = 1;
bleGamepad.setHat1(dPad[x][y]);
break;
case dpad_left://14-15
case dpad_right:
y = 1;
bleGamepad.setHat1(dPad[x][y]);
break;
case trigger_l://16
bleGamepad.setLeftTrigger(0);
break;
case trigger_r://17
bleGamepad.setRightTrigger(0);
break;
}
}
}
void bgc_buttonGamepadReinstallAll() {
for (int i = 0; i <= 16; i++) {
butGamepad[i].buttonReinstall(profil[mps(gapn)].listButton[i]); //аналог перезапуску всіх(17ти) конструкторів екземплярів класу
}
}
void bgc_buttonConnectionResistor(){
for (int i = 0; i <= 14; i++) {//
butGamepad[i].tactilePinButtonRead(i_p_p_g_b);
}
delay(10);
}
void bgc_shutdownButtonsSticking(){
Gf_gamepadButtons();//кнопки які залипли натисниться
for (int i = 1; i <= 8; i++) {//І дальше в обхід відбудеться примусове відключення
bleGamepad.release(i);
}
// Відпустити кнопки Start та Select
bleGamepad.releaseStart();
bleGamepad.releaseSelect();
// Якщо використовуються L2/R2 як осі Z і RZ (осьові тригери)
bleGamepad.setZ(0); // L2
bleGamepad.setRZ(0); // R2
// Якщо ти використовуєш AutoReport = 0
bleGamepad.sendReport();
}
bool bgc_checkingAllButtons() {
static byte mode;
if (!mode) {
for (byte i = 0; i <= 14; i++) {
if (butGamepad[i].gamepadBatton(read_button_state) == press_button) {
mode = true;
return true;
}
}
if (butGamepad[rifle_stock].gamepadBatton(read_button_state) == press_button || butGamepad[rifle_magazine].gamepadBatton(read_button_state) == press_button) {
mode = true;
return true;
}
}
else {
for (byte i = 0; i <= 14; i++) {
if (butGamepad[i].gamepadBatton(read_button_state) == press_button) {
butGamepad[i].buttonReinstall(0);
}
}
if (butGamepad[rifle_stock].gamepadBatton(read_button_state) == press_button) {butGamepad[rifle_stock].buttonReinstall(0);}
if (butGamepad[rifle_magazine].gamepadBatton(read_button_state) == press_button) {butGamepad[rifle_magazine].buttonReinstall(0);}
}
return false;
}
// Bgc
// Additional functions Function svmbols
//Additional functions
//void tick(){}
void af_linesCenterY(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
static byte flagPress;
if (!flagPress && butGamepad[16].getStatus()) { //працює лише коли підключений Bluetooth
flagPress = true;
af_triggerLight(color_two);
af_shotgunBolt(bolt_start);
af_vibration(vibration_start);
}
else if (flagPress && !butGamepad[16].getStatus()) {// пісня відпущення кнопки буде повернення брехня
flagPress = false;
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;
}
#define START_TIMER() \
static unsigned long prevMicros = 0; \
unsigned long startMicros = micros();
#define END_TIMER() \
{ \
unsigned long endMicros = micros(); \
unsigned long elapsed = endMicros - startMicros; \
if (elapsed > prevMicros) { \
Serial.print("Час виконання: "); \
Serial.print(elapsed); \
Serial.print(" мкс | "); \
Serial.print(elapsed / 1000.0, 3); \
Serial.println(" мс"); \
prevMicros = elapsed; \
} \
}
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);
}
mps(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);
}
mps(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);//якщо перевірка успішна повернемо Нулик завершимо або варіант af_pleaseDoNotPress
break;
case stage_2:
stage = af_pleaseDoNotPress();//просто не чіпати і вибрати Next після вибору Next переходимо до stage_3
break;
case stage_3:
stage = af_deviceCheck(stage_3);// Тут тепер перевіряємо критичні відхилення і відключаємо елементи керування
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()) {//якщо на першій провірці є відхилення переключимося на екран прохання
tmr.st(4000, false);
af_drawCountdown(false);
return stage_2;
}
if (tmr.ts()) {
return stage_4;//просто завершуємо
}
}
else if (stage == stage_3) {
af_controlsCheckСalibration();//якщо тут будуть відхилення то відбудеться відключення попередження не буде
if (tmr.ts()) {
if (af_controlsCheckExecution(false)) {//тест завершення з помилкою
return stage_5;//
}
else{
return stage_4;
}
}
}
af_p_deviceCheck();
return stage;
}
void af_p_deviceCheck(){
const byte l = 1, p = 0, s = 48/2;
byte y[l];
af_linesCenterY(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(51)) {
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_linesCenterY(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 y, byte selection) {
display.fillRect(18, 4 + y, 23, 2, WHITE);
display.setCursor(86, 2 + y);
display.print("Next");
// Рамки навколо тексту
if (selection == 0) {
display.drawRect(16, 0 + y, 27, 11, WHITE); // -
} else {
display.drawRect(84, 0 + y, 27, 11, WHITE); // Next
}
}
byte af_selectNextRestart(byte y) {//y = 48+3
static Timer tmr(5000, false);
static byte selection = 0; // 0 = Restart, 1 = Next
af_p_selectNextRestart(y, 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() {
if (/
Gf_leftJoystick(c_c_j) ||
Gf_RightJoystick(c_c_j) ||
butGamepad[15].calibrationTrigger() ||
butGamepad[16].calibrationTrigger() ||
bgc_checkingAllButtons() ||
)
{//якщо получим правда перший раз виведемо прохання не чіпати якщо викл дану функцію другий раз виконується відключення якщо є критичні відхилення
return true;//перший раз реагуємо на правда другий раз Просто виконуємо дії
}
return false;
}
//
//
// For display (різне для дисплею)
void fd_drawCenteredFilledRect(int centerX, int centerY, int width, int height, uint16_t color = WHITE) {//прямокутник білий або 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 = WHITE) {//прямокутник рамка
// Обчислюємо початкові координати для верхнього лівого кута, щоб прямокутник був по центру
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_letterDeletionsvYmbol(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--------------------------------------------------------------
// Info display
void Md_infoDisplay() {// головна фунція виклик решта функцій дисплея
switch (mdf_addres(check_emb, 0)) {
case address_not_entered:
Md_p_infoDisplay();//виводимо перший пункт меню який буде викликати решту пунктів
break;
case 1:
Md_mainDisplay();
break;
}
}
void Md_p_infoDisplay() {
static Timer tmr(1000);
const byte Lines[] = {255};//щоб стрілка курсора не відображалася ставимо невідому координату
mdf_cursor(cursor_read_encoder, 1, Lines);
mdf_bluetoothPrint();
mdf_typeNumberPrint();
//mdf_batteryPrint();
display.drawRect(0, 14, 127, 19, WHITE);// РАМКА НАВКОЛО НАЗВИ
if (!mps(gapn)){ //якщо профіль не вибрано виводимо SELECT PROFILE
if (60000 < millis() || tmr.ta()){
af_centerTextX(X(64), Y(20), profil[mps(gapn)].nameProfiles(get_name));
}
}
else{
af_centerTextX(X(64), Y(20), profil[mps(gapn)].nameProfiles(get_name));//назва діючого профілю
}
display.setCursor(44,44);//щоб стрілка відображалася там де треба вручну виводимо значок
display.write(26);
display.drawRect(50, 42, 27, 11, WHITE);//рамка навколо слога
display.setCursor(52,44);
display.print("Menu");
}
//
//
// 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(!mps(gapn)){//якщо профіль не вибрано переходимо в пункт selectProfile
Md_selectProfile();
}else{
profil[mps(gapn)].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(!mps(gapn)){//якщо профіль не вибрано то попадаємо в пункт для вибору
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(profil[mps(gapn)].namber);
af_centerTextX(X(64), Y(15), profil[mps(gapn)].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(profil[mps(gapn)].namber);
af_centerTextX(X(64), Y(15), profil[mps(gapn)].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_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);
}
//
//
// add3-----------------------------------------------------------------------------------------------------------
// Profile
// Profile settings Sp (Buttons, Gyroscope settings) // залежить від типу профіля викликається Sp або Mp
void Md_profileSettingsSp() {
switch (mdf_addres(check_emb, 3)) {
case address_not_entered:
Md_p_profileSettingsSp();
break;
case 1:
Md_Buttons();
break;// Buttons
case 2:// back //якщо були зміни то повернення відбудеться через функцію prof mdf_profileStorage
mdf_cursor(set_pos, mdf_addres(get_add, 2)); //повернення
mdf_addres(back_add, 2);
break;
case address_entered:
if (mdf_addres(get_add, 3) == 1){mdf_cursor(set_pos, 1);}
break;
}
}
void Md_p_profileSettingsSp() {
const byte LinesSp[] = {16, 24};
mdf_cursor(cursor_read_encoder, sizeof(LinesSp), LinesSp);
display.setCursor(0, 16);
display.println(" " );
display.println(" Back" );
}
//
// Profile settings Mp (Buttons, Gyroscope settings, changeNameProfile)
void Md_profileSettingsMp(){
switch (mdf_addres(check_emb, 3)) {
case address_not_entered:
Md_p_profileSettingsMp();// відображення пунктів настройки
break;
case 1:// Gyroscope
Md_gyroscopeSettings();
break;
case 2:// Buttons
Md_Buttons();
break;
case 3:// Name
Md_changeNameProfile();
break;
case 4:// back
mdf_cursor(set_pos, mdf_addres(get_add, 2));
mdf_addres(back_add, 2);
break;
case address_entered:
if (mdf_addres(get_add, 3) != 4 ){ mdf_cursor(set_pos, 1);}
break;
}
}
void Md_p_profileSettingsMp() {
const byte LinesMp[] = {16, 24, 32, 50};
mdf_cursor(cursor_read_encoder, sizeof(LinesMp), LinesMp);
display.drawRoundRect(9, 14, 59, 28, 0, WHITE);
display.setCursor(0, 16);
display.println(" Gyroscope" );
display.println(" Buttons" );
display.println(" Name" );
display.setCursor(0, 50);
display.println(" Back" );
}
// changeNameProfile
//change name profile additional elements
int8_t cnpaX, cnpaY; // Змінні для координат X та Y
char letterBig[6][10] = { // Масив символів для великих букв
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'},
{'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'},
{'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', '_'},
{'\0', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '.','\0'},
{',', ':', '&', '!', ' ', ' ', '(', ')', '?','\0'}
};
char letterSmall[5][10] = {// Масив символів для малих букв
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'},
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '_'},
{'\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '.','\0'},
{',', ':', '&', '!', ' ', ' ', '(', ')', '?','\0'}
};
char (*ptrLetter)[10] = letterSmall;// // Вказівник на поточний масив символів (великі або малі букви)
int8_t getActionSelection() {//якщо навелись то функція поверне координату яка відповідає символу
if (cnpaY == 3 && cnpaX == 0) return cl;
if (cnpaY == 3 && cnpaX == 9) return dl;
if (cnpaY == 4 && cnpaX == 9) return tb;
if ((cnpaY == 4) && (cnpaX == 4 || cnpaX == 5)) return sb;
return 0; // Значення за замовчуванням
}
//
void Md_changeNameProfile(){
static int8_t magnitude = 1;// костилі використовується для вибору пробілу і виходу
static int8_t fl;// Перемикач між великими та малими літерами костилі вказівник не працює належним чином
if (af_checkRightHolded()){cnpaY--;cnpaY = af_clamp(cnpaY, 0, 4);}
else if (af_checkLeftHolded()){ cnpaY++;cnpaY = af_clamp(cnpaY, 0, 4);}
else{
if(mdf_magnifier(cnpaX, -1, 10, 1, 1)) {//якщо X вийшло за межі допустимих чисел виконуємо пробірку і збільшення або зменшення Y
if (cnpaX == 10) {cnpaY += 1;}//зменшуємо або збільшуємо y для того щоб перемістити курсор вниз після досягнення максимуму по X
else if (cnpaX == -1) {cnpaY -= 1;}
cnpaY = af_clamp(cnpaY, 0, 4);//додатково перевіряємо y щоб не Вийти за межі
}
cnpaX = af_clamp(cnpaX, 0, 9);//скидання на мінімальне і наоборот якщо перевищили значення
}
if (af_checkClick()) {//Клік виконує зміну бук видалення пробіл додавання букви вихід
if (getActionSelection() == cl) {//міняємо величину бук
ptrLetter = (ptrLetter == letterSmall) ? letterBig : letterSmall;
}
else if (getActionSelection() == dl) {//видаляємо букву
profil[mps(gapn)].nameProfiles(delete_letter);
if (!profil[mps(gapn)].lettersTakeNumber()) {ptrLetter = letterBig;}//якщо видаляємо останню букву робимо всі букви великими одноразово
}
else if (getActionSelection() == tb) {//вихід плюс скидання і перевірка набраного тексту
profil[mps(gapn)].nameProfiles(g_s_n_i_n_l_a_r_s); //(give standard name if no letters and remove spaces ) TEST якщо Видалити всі букви то заміниться ім'я на стандартне
cnpaX = cnpaY = 0;
ptrLetter = letterSmall;
mdf_cursor(set_pos, mdf_addres(get_add, 3));
mdf_addres(back_add, 3);
}
else{ //доплюсовуємо до речення вибрано букву
profil[mps(gapn)].nameProfiles(add_letter, ptrLetter[cnpaY][cnpaX]);
if (profil[mps(gapn)].lettersTakeNumber() == 1) {ptrLetter = letterSmall;}//речення починається з великої букви і автоматично скидається на Малі
}
}
Md_p_changeNameProfile();// віалізуємо програму
}
byte Md_p_changeNameProfile(){
static Timer tmr(500);
display.setCursor(2, 1);// виводи ім'я профіля
display.print(profil[mps(gapn)].nameProfiles(get_name));
if(tmr.ta()){// вертикальна риска біля букви
display.drawRect(2+6*profil[mps(gapn)].lettersTakeNumber(), 0, 1, 9, WHITE);
}
if(!getActionSelection()){//відображаємо рамку навколо букви якщо навелись на стандартний символ
display.drawRect(4+cnpaX*12, 12+cnpaY*10, 9, 11, WHITE);// рамка курса 10 вверх вниз, вліво вправо 12
}
for (int i = 0; i <= 4; i++) {// виводим масив букв на дисплей
for (int j = 0; j < 10; j++) {
display.setCursor(6+j*12, 14+i*10);
display.print(ptrLetter[i][j]);
}
}
if(getActionSelection() != cl){//якщо координата вказує на спеціальний символ починаємо ним мигати
ptrLetter == letterSmall ? fd_arrowLowercaseLetters(4, 42) : fd_arrowCapitalLetters(4, 42);
}
else if(getActionSelection() == cl && !tmr.ta()){
ptrLetter == letterSmall ? fd_arrowLowercaseLetters(4, 42) : fd_arrowCapitalLetters(4, 42);
}
if(getActionSelection() != dl){
fd_letterDeletionsvYmbol(110, 44);
}
else if(getActionSelection() == dl && !tmr.ta()){
fd_letterDeletionsvYmbol(110, 44);
}
if(getActionSelection() != sb){
display.fillRect(50, 55, 25, 6, WHITE);
}
else if(getActionSelection() == sb && !tmr.ta()){
display.fillRect(50, 55, 25, 6, WHITE);
}
if(getActionSelection() != tb){
fd_arrowReturn(110, 54);
}
else if(getActionSelection() == tb && !tmr.ta()){
fd_arrowReturn(110, 54);
}
return 0;
}
// cNP
//
// Select profile
void Md_selectProfile() {
switch (mdf_addres(check_emb, 3)) {
case address_not_entered:
Md_p_selectProfile();
break;
case 1:
Md_standardProfiles();
break;
case 2:
Md_myProfiles();
break;
case 3: //back
if(!mps(gapn)){//якщо профіль все одно не вибрана то повертаємося в головний пункт
mdf_cursor(set_pos, mdf_addres(back_add, 1));
}else{//якщо хотіли змінити профіль і передумали повертаємося в попередній пункт і ставимо курсор в залежності від типу профіля
mdf_addres(back_add, 2);
profil[mps(gapn)].tape == t_sp ? mdf_cursor(set_pos, 3) : mdf_cursor(set_pos, 2);//вказуємо вручну попередній пункт може мінятися Залежно від типу
}
break;
case address_entered:
mdf_cursor(set_pos, 1);
break;
}
}
void Md_p_selectProfile() {
const byte Lines[] = {16, 24, 32};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
display.setCursor(0, 16);
display.println(" Standard profiles" );
display.println(" My profiles");
display.println(" Back" );
}
// Sp
//
// General settings
// Various (кінець гілки)
void Md_various(){
switch (mdf_addres(check_emb, 3)) {
case 1:
//Volume
break;
case 2:
Md_saveSelectedProfile(ssp_change_setting);
break;
case 3:
Md_autoPowerOff(apo_change_time);
break;
case 4:
Md_displayTimeout(dt_change_time);
break;
case address_entered:
if (mdf_addres(get_add, 3) == 5){//back
mdf_cursor(set_pos, mdf_addres(back_add, 2));
}
else {
mdf_addres(mode_click_back);
}
break;
}
Md_p_Various();
}
void Md_p_Various(){
const byte l = 5, p = 2, s = 64/2;
byte y[l];
af_linesCenterY(l, p, s, y);
Md_p_volume(y[0]);
Md_p_saveSelectedProfile(y[1], L(2));//L(?) також передаємо номер строки щоб відбулося прокручування тексту
Md_p_autoPowerOff(y[2]);
Md_p_displayTimeout(y[3]);//потрібно зробити окремий пункт і додати настройку яскравості
af_displayPrintLain("Back", X(6), y[4]);
mdf_cursor(cursor_read_encoder, sizeof(y), y);
}
// Various(виклик)
// Volume?
void Md_p_volume(byte y){ //test
af_displayPrintLain("Volume", X(6), y);
}
//
// Save selected profile
byte Md_saveSelectedProfile(byte acceptedCommand){
static byte ssps;//ssps = save Selected Profile switch
if (acceptedCommand == ssp_change_setting){
mdf_magnifier(ssps, 0, 1, 1, 1);
}
else if (acceptedCommand == ssp_get_setting){
return ssps;
}
else if (acceptedCommand == tf_reset_settings) {
ssps = 0;
}
else if (acceptedCommand == get_eeprom) {
af_saveToEEPROM(get_eeprom, &ssps, sizeof(ssps));
}
else if (acceptedCommand == set_eeprom) { //не забути добавити
af_saveToEEPROM(set_eeprom, &ssps, sizeof(ssps));
}
return 0;
}
void Md_p_saveSelectedProfile(byte y, byte l){
af_displayPrintLain("Save selected profile", X(6), y, l, N(13));
if (Md_saveSelectedProfile(ssp_get_setting)){
af_displayPrintLain("on", X(128-12), y);
} else{
af_displayPrintLain("off", X(128-18), y);
}
}
//
// Auto power off
byte Md_autoPowerOff(byte acceptedCommand){
static byte tmrRestartMinutes = 10;
static uint32_t tmrRestartMillis = 3*60000;// = 3 хвилини
static Timer tmr(tmrRestartMillis, false);//вказуємо тут час і правда повернеться не одразу а по завершенню часу tmr. gp()
if (acceptedCommand) {
if (acceptedCommand == apo_restart_time) {
tmr.st(tmrRestartMillis, false);
}
else if (acceptedCommand == apo_change_time) {
if (mdf_magnifier(tmrRestartMinutes, 1, 10, 1, 2)) {
tmrRestartMillis = tmrRestartMinutes*60000;
}
}
else if (acceptedCommand == apo_get_time) {
return tmrRestartMinutes;
}
else if (acceptedCommand == get_eeprom) { //
af_saveToEEPROM(get_eeprom, &tmrRestartMinutes, sizeof(tmrRestartMinutes));
tmr.st(tmrRestartMillis, false);//якщо беремо дані з енергонезалежної пам'яті потрібно перезапуск
}
else if (acceptedCommand == set_eeprom) {
af_saveToEEPROM(set_eeprom, &tmrRestartMinutes, sizeof(tmrRestartMinutes));
}
else if (acceptedCommand == tf_reset_settings) {
tmrRestartMillis = 3*60000;
tmrRestartMinutes = 3;
}
}
else if (tmr.ts()){
Serial.println("p off");
af_powerOffExecution();
}
return 0;
}
void Md_p_autoPowerOff(byte y){
af_displayPrintLain("Auto power off m.", X(6), y);
af_centerTextX(X(107), y, Md_autoPowerOff(apo_get_time));
}
//
// Display timeout
byte Md_displayTimeout(byte acceptedCommand){
static byte status = tf_function_not_completed;
const unsigned int shutdownTime[] = {0, 5000, 15000, 30000, 60000, 120000, 300000, 600000};
static int8_t oti = 7; //oti = off time indicator вказівник на час відключення
static Timer tmr(shutdownTime[oti], false);
static byte bss; //bss = Bluetooth status saving
if (acceptedCommand) {
if (oti && acceptedCommand == timeout_restart) { //в основному якщо модуль encorder працює то виключення дисплею перезапускається
tmr.st(shutdownTime[oti],false);
}
else if (acceptedCommand == dt_change_time) {
mdf_magnifier(oti, 0, 7, 1, 1);
if (!oti) {tmr.st(false, false);} //захист від миттєвого згасання якщо перейдемо з off на вказаний час (5s.-10m.)
}
else if (acceptedCommand == dt_get_tim_minutes) {
if (4 <= oti) {
return shutdownTime[oti] / 60000;
}else{
return 0;
}
}
else if (acceptedCommand == dt_get_tim_seconds) {
if (oti <= 3 && oti) {
return shutdownTime[oti] / 1000;
}else{
return 0;
}
}
else if (acceptedCommand == tf_reset_settings) {
oti = 1;
}
else if (acceptedCommand == get_eeprom) {
af_saveToEEPROM(get_eeprom, &oti, sizeof(oti));
Md_displayTimeout(timeout_restart);//потрібно перезапустити з новим часом взятим з енергонезалежною пам'яті
}
else if (acceptedCommand == set_eeprom) {
af_saveToEEPROM(set_eeprom, &oti, sizeof(oti));
}
/*
else if (acceptedCommand == timeout_reset) {//використовується коли скидаємо всі настройки
tmr.st(shutdownTime[oti]);
}
*/
}
else if (oti && tmr.ts()){
return true;
}
return 0;
}
void Md_p_displayTimeout(byte y){
if (Md_displayTimeout(dt_get_tim_minutes)){
af_centerTextX(X(107), y, Md_displayTimeout(dt_get_tim_minutes));
af_displayPrintLain("Display timeout m.", X(6), y);
}
else if (Md_displayTimeout(dt_get_tim_seconds)){
af_centerTextX(X(107), y, Md_displayTimeout(dt_get_tim_seconds));
af_displayPrintLain("Display timeout s.", X(6), y);
}
else {
af_displayPrintLain("Display timeout off", X(6), y);
}
}
//
//
//
// Controls
void Md_controls(){
switch (mdf_addres(check_emb, 3)) {
case 0:
Md_p_controls();
break;
case 1:
Md_aimMode();
break;
case 2:
Md_calibration();
break;
case address_entered:
if (mdf_addres(get_add, 3) == 3){//back
mdf_cursor(set_pos, mdf_addres(back_add, 2));
}
else{mdf_cursor(set_pos, 1);}
break;
}
}
void Md_p_controls(){
const byte l = 3, p = 4, s = 64/2;
byte y[l];
af_linesCenterY(l, p, s, y);
af_displayPrintLain("Aim mode", X(6), y[0]);
af_displayPrintLain("Calibration", X(6), y[1]);
af_displayPrintLain("Back", X(6), y[2]);
mdf_cursor(cursor_read_encoder, sizeof(y), y);
}
//
// Effects
void Md_effects(){
switch (mdf_addres(check_emb, 3)) {
case 1:
af_triggerLightSettings();//тут перехід решта кінцеві
break;
case 2:
af_shotgunBolt(bolt_change);
break;
case 3:
af_vibration(vt_change);
break;
case address_entered:
if (mdf_addres(get_add, 3) == 2 || mdf_addres(get_add, 3) == 3){
mdf_addres(mode_click_back);
}
else if (mdf_addres(get_add, 3) == 4){//back
mdf_cursor(set_pos, mdf_addres(back_add, 2));
}
break;
}
if (mdf_addres(get_add, 3) != 1){//
Md_p_Effects();
}
}
void Md_p_Effects(){
const byte Lines[] = {16, 24, 32, 40};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
af_displayPrintLain(" Trigger light " + af_animatePointer(), 0, 16);
Md_p_shotgunBoltSettings();
Md_p_vibration();
af_displayPrintLain(" Back ", 0, 40);
}
//
// Expert settings
void Md_expertSettings(){
switch (mdf_addres(check_emb, 3)) {
case 0:
Md_p_expertSettings();
break;
case 1:
Md_gyroCalibration();
break;
case 2:
Md_disableButton();
break;
case 3:
Md_resetAll();
break;
case address_entered:
if (mdf_addres(get_add, 3) == 4) { //back
af_passwordEntry(password_reset);
mdf_cursor(set_pos, mdf_addres(back_add, 2));
}else{
mdf_cursor(set_pos, 1);
}
break;
}
}
void Md_p_expertSettings(){
af_displayPrintLain("Gyro calibration", X(6), Y(16));
af_displayPrintLain("Stuck buttons", X(6), Y(25));
af_displayPrintLain("Reset all", X(6), Y(34));
af_displayPrintLain("Back", X(6), Y(43));
const byte Lines[] = {16, 25, 34, 43};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
}
//
//
//
// add4-----------------------------------------------------------------------------------------------------------
// Profile
// Profile settings Sp або Mp
// Gyroscope settings Sp Mp
void Md_gyroscopeSettings(){
Md_p_gyroscopeSettings();//відображаємо пункти меню постійно
switch (mdf_addres(check_emb, 4)) {
case address_not_entered:
break;
case 1:
mdf_magnifier(profil[mps(gapn)].deadZoneHor, 0, 9999, 1, 100); //міняємо чутливість в профілю
break;
case 2:
mdf_magnifier(profil[mps(gapn)].sensitivityHor, 50, 150, 1, 5);//якщо функція поверне -1 значить передане число Не збільшувалось і не зменшувалось і тому не потрібно виконувати перезапис
break;
case 3:
mdf_magnifier(profil[mps(gapn)].deadZoneVer, 0, 9999, 1, 100);
break;
case 4:
mdf_magnifier(profil[mps(gapn)].sensitivityVer, 50, 150, 1 , 5);
break;
case 5:
if (mps(reset_gyro) == tf_function_completed) {mdf_addres(back_add, 4);}//скидаємо настройки гіроскопа після вибору так
break;
case 6: //back
mdf_cursor(set_pos, mdf_addres(get_add, 3));
mdf_addres(back_add, 3);
break;
case address_entered:
if (mdf_addres(get_add, 4) <= 4) {
mdf_addres(mode_click_back);
}
else if (mdf_addres(get_add, 4) == 5 ) {//якщо вибрали даний пункт значить робимо скидання до стандартних настройок
mdf_addres(mode_function_returns);//
mdf_confirm(output_by_coordinates, X(39), Y(46));//
}
/*
if (mdf_addres(check_emb, 4) == 1 || mdf_addres(check_emb, 4) == 3) {// test зупиняємо виконання функції Gf_gyro для того щоб настроїти мертву зону
timerAlarmDisable(timer);
}
*/
break;
}
}
void Md_p_gyroscopeSettings(){
static Timer tmr(500);
const byte Lines[] = {2, 11, 26, 35, 46, 55};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
display.setCursor(8, 2);
display.println("Dead zone ");
display.setCursor(8, 11);
display.println("Sensitivity");
display.setCursor(8, 26);
display.println("Dead zone ");
display.setCursor(8, 35);
display.println("Sensitivity");
display.setCursor(8, 46);
display.println("Reset");
display.setCursor(8, 55);
display.println("Back");
display.setCursor(84, 6);
display.println("H");
display.setCursor(84, 6+24);
display.println("V");
af_centerTextX(X(113), Y(2), profil[mps(gapn)].deadZoneHor);//виводимо настройки гіроскопа центруємо значення в рамці
int temp = map(profil[mps(gapn)].sensitivityHor, 50, 150, 100, 0);//відображаємо в процентному співвідношенні а не в одиницях
af_centerTextX(X(113), Y(11), temp);
af_centerTextX(X(113), Y(26), profil[mps(gapn)].deadZoneVer);
temp = map(profil[mps(gapn)].sensitivityVer, 50, 150, 100, 0);
af_centerTextX(X(113), Y(35), temp);
display.drawRoundRect(6, 0, 69, 20, 3, WHITE); // РАМКА НАВКОЛО Dead zone Sensitivity H
display.drawRoundRect(6, 0+24, 69, 20, 3, WHITE);// РАМКА НАВКОЛО Dead zone Sensitivity V
display.drawRect(128-30, 0, 29, 20, WHITE);//dzh значення показники
display.drawRect(128-30, 0+24, 29, 20, WHITE);//dzv значення показники
if(2 <= mdf_cursor(get_pos) || tmr.ta()){// РАМКА H
display.drawRoundRect(79, 2, 15, 15, 0, WHITE);
}
if((3 != mdf_cursor(get_pos) && 4 != mdf_cursor(get_pos)) || tmr.ta()){// РАМКА V
display.drawRoundRect(79, 2+24, 15, 15, 0, WHITE);
}
}
// Gs
// Buttons Sp Mp
void Md_Buttons(){
switch (mdf_addres(check_emb, 4)) {
case address_not_entered: // address_not_entered == 0
Md_p_Buttons();
break;
case 1:
Md_moveButtons();
break;
case 2:
Md_duplicateButtons();
break;
case 3:// back
mdf_cursor(set_pos, mdf_addres(get_add, 3));
mdf_addres(back_add, 3);
break;
case address_entered: // address_not_entered == 21
mdf_cursor(set_pos, 1);
break;
}
}
void Md_p_Buttons(){
static Timer tmr(500);
const byte LinesSp[] = {17, 28, 40};
mdf_cursor(cursor_read_encoder, sizeof(LinesSp), LinesSp);
af_displayPrintLain("Move button", X(6), Y(17));
af_displayPrintLain("Duplicate buttons", X(6), Y(28));
af_displayPrintLain("Back", X(6), Y(40));
display.drawRect(77, 15, 45, 11, WHITE); //рамка навколо слів
if (profil[mps(gapn)].malb(check_location) == non_standard_location) {//якщо кнопки мінялися місцями виводимо повідомлення про це
if (tmr.ta()) {af_displayPrintLain("Changed", X(79), Y(17));}
}
else{
af_displayPrintLain("Default", X(79), Y(17));
}
}
// B
//
// Select profile
// Standard profiles
void Md_standardProfiles(){
switch (mdf_addres(check_emb, 4)) {
case address_not_entered:
Md_p_standardProfiles();// відображення списку профілів вибір
break;
case 8: //back повертаємося в пункт де можна вибрати стандартний або власний профіль
mdf_cursor(set_pos, mdf_addres(get_add, 3));
mdf_addres(back_add, 3);
break;
case address_entered:
if (mdf_addres(get_add, 4) != 8){
if(!mps(gapn)){// після того як перший раз вибрали профіль повертаємося в основне меню
mps(activate_p, mdf_addres(get_add, 4));
mdf_cursor(set_pos, mdf_addres(back_add, 0));
}
else{//
mps(activate_p, mdf_addres(get_add, 4));
mdf_cursor(set_pos, 3);//в даному випадку вказуємо позицію три тому що є три пункти
mdf_addres(back_add, 2);//повертаємося через один пункт
}
bgc_buttonGamepadReinstallAll();//коли вибираємо або міняємо профіль розташування кнопок потрібно перевстановити керуючись настройками профілю
}
break;
}
}
void Md_p_standardProfiles(){
const byte Lines[] = {0, 8, 16, 24, 32, 40, 48, 56};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
for (int i = 0; i < 7; i++) {// виводим імена профілів діючий профіль мигає
display.setCursor(6, i*8);
display.print(profil[i+1].nameProfiles(get_name_a_f));
}
display.setCursor(0, 56);
display.println(" Back" );
}
// Sp
// My profiles
void Md_myProfiles(){
switch (mdf_addres(check_emb, 4)){// третій параметр забороняє вибирати діючий профіль
case address_not_entered:
Md_p_MyProfiles();
break;
case 8: //back повертаємося в пункт де можна вибрати стандартний або власний профіль
mdf_cursor(set_pos, mdf_addres(get_add, 3));
mdf_addres(back_add, 3);
break;
case address_entered:
if (mdf_addres(get_add, 4) != 8){
if(!mps(gapn)){//
mps(activate_p, mdf_addres(get_add, 4)+7);
mdf_cursor(set_pos, mdf_addres(back_add, 0));
}
else{//
mps(activate_p, mdf_addres(get_add, 4)+7);
mdf_cursor(set_pos, 2);
mdf_addres(back_add, 2);// не міняти двоєчку будуть глюки
}
bgc_buttonGamepadReinstallAll();//коли вибираємо або міняємо профіль розташування кнопок потрібно перевстановити керуючись настройками профілю
}
break;
}
}
void Md_p_MyProfiles() {
const byte Lines[] = {0, 8, 16, 24, 32, 40, 48, 56};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
for (int i = 0; i < 7; i++) {
display.setCursor(6, i*8);
display.print(profil[i+8].nameProfiles(get_name_a_f));
}
display.setCursor(6, 56);
display.println("Back" );
}
// Mp
//
//
// General settings
// Controls
// Aim mode(кінець гілки)
void Md_aimMode(){
if (mdf_addres(check_emb, 4)){//коли наберем адрес получимо address_entered = -1
int temp = mdf_addres(get_add, 4);
if (temp == 8){//back
mdf_cursor(set_pos, mdf_addres(back_add, 3));
}
else if (temp == 7){//reset
Gf_combinedGyroRightJoystick(joystick_full_priority);
mdf_cursor(set_pos, 1);//стрілочка вкаже на стандартний режим щоб хоч трохи було зрозуміло що відбулось скидання
mdf_addres(back_add, 4);
}
else {
Gf_combinedGyroRightJoystick(temp);//temp 1-6? //номер набраного адресу означає вибраний режим прицілювання
mdf_cursor(set_pos, mdf_addres(back_add, 4));//робимо повернення щоб не було зависання
}
}
Md_p_aimMode();
}
// print aim mode
void Md_p_aimMode(){
const byte l = 8, p = 0, s = 64/2;
byte y[l];
af_linesCenterY(l, p, s, y);
af_displayPrintLain("Joy priority", X(6), y[0]);
af_displayPrintLain("Joy disable gyro", X(6), y[1]);
af_displayPrintLain("Combined mode", X(6), y[2]);
af_displayPrintLain("Input dominance", X(6), y[3]);
af_displayPrintLain("Gyro priority", X(6), y[4]);
af_displayPrintLain("Gyro (Joy off)", X(6), y[5]);
af_displayPrintLain("Reset", X(6), y[6]);
af_displayPrintLain("Back", X(6), y[7]);
mdf_cursor(cursor_read_encoder, sizeof(y), y);
Md_p_aimModeActiveBlinking(y);//діючий режим буде мигати
}
void Md_p_aimModeActiveBlinking(const byte* coordinate){
static Timer tmr(1000);
static byte y;
if (tmr.ta()) {
y = coordinate[Gf_combinedGyroRightJoystick(g_o_m)-1];//функція повертає мінімум 1 а потрібно 0
display.fillRect(X(6), y, 128-6, 8, BLACK);//симулюємо мигання пункту раз на секунду закриваємо чорний прямокутником
}
}
//
//
// Calibration
void Md_calibration(){
switch (mdf_addres(check_emb, 4)) {
case 0:
Md_p_calibration();
break;
case 1:
Md_joysticksCalibration();
break;
case 2:
//Md_triggersCalibration();
break;
case 3:
//Md_gyroscopeCalibration();
break;
case 4://back
mdf_cursor(set_pos, mdf_addres(back_add, 3));
break;
}
}
void Md_p_calibration(){
const byte l = 4, p = 4, s = 64/2;
byte y[l];
af_linesCenterY(l, p, s, y);//af_linesCenterY(кількість рядків , відстань між рядками, центр розміщення рядків, масив який потрібно заповнити координатами),
af_displayPrintLain("Joysticks", X(6), y[0]);
af_displayPrintLain("Triggers", X(6), y[1]);
af_displayPrintLain("Gyroscope", X(6), y[2]);
af_displayPrintLain("Back", X(6), y[3]);
mdf_cursor(cursor_read_encoder, sizeof(y), y);
}
//
//
// Effects
// Trigger light
void af_triggerLightSettings(){
int seveDataTemp;
switch (mdf_addres(check_emb, 4)) {
case 1:
seveDataTemp = af_triggerLight(get_light1);
if (mdf_magnifier(seveDataTemp, 0, 100, 1, 10)) {af_triggerLight(set_light1, seveDataTemp);}
break;
case 2:
seveDataTemp = af_triggerLight(get_light2);
if (mdf_magnifier(seveDataTemp, 0, 100, 1, 10)) {af_triggerLight(set_light2, seveDataTemp);}
break;
case 3:
seveDataTemp = af_triggerLight(get_color1);
if (mdf_magnifier(seveDataTemp, 0, 1000, 1, 50)) {
af_triggerLight(set_color1, seveDataTemp);
if (!af_triggerLight(get_light1)) {af_triggerLight(set_light1, 100);}
}
break;
case 4:
seveDataTemp = af_triggerLight(get_color2);
if (mdf_magnifier(seveDataTemp, 0, 1000, 1, 50)) {
af_triggerLight(set_color2, seveDataTemp);
if (!af_triggerLight(get_light2)) {af_triggerLight(set_light2, 100);}
}
break;
case 5:
if (af_triggerLight(clssc) == color_light_default) { //clssc = color light standard settings check
if (mdf_confirm(print_not_need) == tf_function_completed) {
mdf_addres(back_add, 4);
}
}
else if (mdf_confirm(offer_a_choice) == tf_data_ready) {
if (mdf_confirm(tf_get_data) == choice_yes) {
af_triggerLight(color_light_reset);
}
mdf_addres(back_add, 4);
}
break;
case address_entered:
if (mdf_addres(get_add, 4) <= 4){
if (mdf_addres(get_add, 4) == 1 || mdf_addres(get_add, 4) == 3){
af_triggerLight(color_one);
}
else if (mdf_addres(get_add, 4) == 2 || mdf_addres(get_add, 4) == 4){
af_triggerLight(color_two);
}
af_triggerLight(color_light_setting);//забороняємо переключати колір перед тим включили колір який потрібно настроїти
mdf_addres(mode_click_back);
}
else if (mdf_addres(get_add, 4) == 5){
mdf_addres(mode_function_returns);
mdf_confirm(output_by_coordinates, X(47), Y(47));
}
else {
mdf_cursor(set_pos, mdf_addres(back_add, 3));
}
break;
case address_deleted: //якщо повертаємося назад включаємо стандартний режим роботи
af_triggerLight(color_switching);
af_triggerLight(color_one); //включаємо на всяк випадок перший колір невідомо в якому стані гачок
break;
}
Md_p_triggerLightSettings();
}
void Md_p_triggerLightSettings(){
byte tc = 0;
int temp;
af_displayPrintLain(" Light 1", 0, 0+tc);
af_displayPrintLain(" Light 2", 0, 8+tc);
af_displayPrintLain(" Color 1", 0, 24+tc);
af_displayPrintLain(" Color 2", 0, 32+tc);
af_displayPrintLain(" Reset ", 0, 48+tc);
af_displayPrintLain(" Back ", 0, 56+tc);
if (af_triggerLight(get_light1)){//перемо яскравість і відображаємо її якщо нуль показуємо off
temp = af_triggerLight(get_light1);
af_centerTextX(X(65), Y(0), temp);
af_displayPrintLain(" %", 0, 0);
}else{
af_displayPrintLain("off", 55, 0+tc);
}
if (af_triggerLight(get_light2)){
temp = af_triggerLight(get_light2);
af_centerTextX(X(65), Y(8), temp);
af_displayPrintLain(" %", 0, 8);
}else{
af_displayPrintLain("off", 55, 8+tc);
}
temp = af_triggerLight(get_color1); //беремо колір і відображаємо його відтінок
af_centerTextX(X(65), Y(24), temp);
temp = af_triggerLight(get_color2);
af_centerTextX(X(65), Y(32), temp);
const byte Lines[] = {0, 8, 24, 32, 48, 56};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
}
//
void Md_p_shotgunBoltSettings(){
if (af_shotgunBolt(bolt_get_permission)){
af_displayPrintLain(" Shotgun bolt on", 0, 24);
}else{
af_displayPrintLain(" Shotgun bolt off", 0, 24);
}
}
void Md_p_vibration(){
if (af_vibration(vt_get_permission)){
af_displayPrintLain(" Vibration on", 0, 32);
}else{
af_displayPrintLain(" Vibration off", 0, 32);
}
}
//
// Expert settings
// Gyroscope calibration
void Md_gyroCalibration(){
Md_p_gyroCalibration();
switch (mdf_addres(check_emb, 4)) {
case 0:
Md_gyroDataPrint();
break;
case 1:
Md_gyroCalibrationExecution();
break;
case 2:
mdf_cursor(set_pos, mdf_addres(back_add, 3));
break;
case address_entered:
if (mdf_addres(get_add, 4) == 1) {
mdf_addres(mode_click_back);
}
break;
case address_deleted:
Md_gyroCalibrationExecution(tf_reset);
break;
}
}
void Md_p_gyroCalibration(){
const byte Lines[] = {36, 46};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
display.drawRect(0, 0, 128, 33, WHITE);//великий прямокутник вверху
af_displayPrintLain("Calibrate", X(6), Y(36));
af_displayPrintLain("Back", X(6), Y(46));
}
void Md_gyroDataPrint(){
int temp; //test
if (1 < temp) {
display.setCursor(20, 13);
display.write(27); // Код символу "←"
}else if (temp < -1){
display.setCursor(56, 13);
display.write(26); // Код символу "→"
}
if (1 < abs(temp)) {
af_centerTextX(X(38), Y(13), abs(temp)-1);
}else{
af_centerTextX(X(38), Y(13), 0);
}
if (1 < temp) {
display.setCursor(91, 3);
display.write(24); // Код символу "↑"
}else if (temp < -1){
display.setCursor(91, 22);
display.write(25); // Код символу "↓"
}
if (1 < abs(temp)) {
af_centerTextX(X(93), Y(13), abs(temp)-1);
}else{
af_centerTextX(X(95), Y(13), 0);
}
}
// Gyro calibration execution
void Md_gyroCalibrationExecution(byte acceptedCommand) {
static int countdown = 10;
static Timer tmr(500);
if (acceptedCommand) {
if (acceptedCommand == tf_reset) {
countdown = 10;
tmr.st(500);//перед виходом перезапускаємо таймер правда при першому виклику
mdf_addres(back_add, 4);
}
else if (acceptedCommand == set_eeprom) {
int avgX, avgY, avgZ;
avgX = mpu.getXGyroOffset();
avgY = mpu.getYGyroOffset();
avgZ = mpu.getZGyroOffset();
af_saveToEEPROM(set_eeprom, &avgX, sizeof(avgX));
af_saveToEEPROM(set_eeprom, &avgY, sizeof(avgY));
af_saveToEEPROM(set_eeprom, &avgZ, sizeof(avgZ));
}
else if (acceptedCommand == get_eeprom) {
int avgX, avgY, avgZ;
af_saveToEEPROM(get_eeprom, &avgX, sizeof(avgX));
af_saveToEEPROM(get_eeprom, &avgY, sizeof(avgY));
af_saveToEEPROM(get_eeprom, &avgZ, sizeof(avgZ));
// Встановлення корекції для осі X
mpu.setXGyroOffset(avgY); // Встановлення корекції для осі Y
mpu.setYGyroOffset(avgZ); // Встановлення корекції для осі Z
mpu.setZGyroOffset(avgZ);
}
}
else if (tmr.gp()) {
countdown--;
if (countdown == -1) {
mpu.CalibrateGyro(5);//TEST
}
else if (countdown <= -4) {
countdown = 10;
tmr.st(500);
mdf_addres(back_add, 4);
}
}
Md_p_gyroCalibrationExecution(countdown);
}
void Md_p_gyroCalibrationExecution(int acceptedData) {
if (0 < acceptedData) {
af_centerTextX(X(64), Y((33/2)-8), "Don't move");
af_centerTextX(X(64), Y(33/2), "Beginning in-"+String(acceptedData));
}
else if (acceptedData == 0){
af_centerTextX(X(64), Y((33/2)-8), "Don't move");
af_centerTextX(X(64), Y(33/2), "Wait for completion");
}
else if (acceptedData <= -1){
af_centerTextX(X(64), Y((33/2)-4), "Calibration completed");
}
}
//
//
// Disabling buttons
void Md_disableButton(){
Md_p_disableButton();
switch (mdf_addres(check_emb, 4)) {
case 1:
if (mdf_confirm(offer_a_choice) == tf_data_ready) {// після вибору робимо дію відключення кнопок або нічого
if (mdf_confirm(tf_get_data) == choice_yes) {
Md_tactileButtonsDeactivation(deactivation_buttons_start);
}
mdf_addres(back_add, 4);
}
break;
case 2:
Md_tactileButtonsDeactivation(reset_deactivation_buttons);//скидаємо всі відключені кнопки
mdf_addres(back_add, 4);
break;
case address_entered:
if (mdf_addres(get_add, 4) == 1) {//перед виключенням Перевіряємо чи є зажаті кнопки
mdf_addres(mode_function_returns);
if (Md_tactileButtonsDeactivation(check_for_pressed_buttons)) {
mdf_confirm(output_by_coordinates, X(60), Y(30));
}else {
mdf_addres(back_add, 4);
}
}
else if (mdf_addres(get_add, 4) == 2) {mdf_addres(mode_function_returns);}//не чіпати інакше не працює
else {mdf_cursor(set_pos, mdf_addres(back_add, 3));}//back
break;
}
}
void Md_p_disableButton(){
display.drawRect(0, 0, 127, 19, WHITE);
af_centerTextX(X(127/2), Y(19/2-4), "Turned off("+ String(Md_tactileButtonsDeactivation(deactivated_buttons_get))+")");
af_displayPrintLain("Deactivation start", X(6), Y(22));
af_displayPrintLain("Reset", X(6), Y(32));
af_displayPrintLain("Back", X(6), Y(42));
const byte Lines[] = {22, 32, 42};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
}
byte Md_tactileButtonsDeactivation(byte acceptedCommand){
static byte maxFive;
static byte pinDeactivat[14];
const uint8_t pinList[14] = {//TEST
23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 5
};
if (acceptedCommand == deactivated_buttons_get) {
return maxFive;
}
else if (acceptedCommand == deactivation_buttons_start) {
for (int i = 0; i <= 13; i++) {
if (maxFive != 5 && digitalRead(pinList[i]) == LOW) {
// Якщо пін у низькому стані — міняємо його на вихід і ставимо HIGH
pinDeactivat[i] = true;
maxFive++;
pinMode(pinList[i], OUTPUT);
digitalWrite(pinList[i], HIGH);
}
}
}
else if (acceptedCommand == check_for_pressed_buttons) {
for (int i = 0; i <= 13; i++) {
if (digitalRead(pinList[i]) == LOW) {
return true;
}
}
}
else if (acceptedCommand == reset_deactivation_buttons) {
maxFive = 0;
}
return 0;
}
//
// Reset all
void Md_resetAll(){
switch (mdf_addres(check_emb, 4)) {
case 1:
if (mdf_confirm(offer_a_choice) == tf_data_ready) {// після вибору робимо дію відключення кнопок або нічого
mdf_addres(back_add, 4);
if ( mdf_confirm(tf_get_data) == choice_yes) {
Md_saveSelectedProfile(tf_reset_settings);
af_vibration(tf_reset_settings);
af_shotgunBolt(tf_reset_settings);
af_triggerLight(tf_reset_settings);
Md_displayTimeout(tf_reset_settings);
Md_autoPowerOff(tf_reset_settings);
mps(tf_reset_settings);
for (int i = 8; i < 14; i++) {
profil[i].profileResetAll();
}
}
}
break;
case 2:
mdf_cursor(set_pos, mdf_addres(back_add, 3));
break;
case address_entered:
if (mdf_addres(get_add, 4) == 1) {
mdf_confirm(output_by_coordinates, X(50), Y(40));
mdf_addres(mode_function_returns);
}
break;
}
Md_p_resetAll();
}
void Md_p_resetAll(){
display.drawRect(0, 0, 128, 37, WHITE);
af_displayPrintLain("Reset all my profiles", X(2), Y(5));
af_displayPrintLain("and", X(56), Y(14));
af_displayPrintLain("general settings", X(17), Y(23));
af_displayPrintLain("Reset", X(6), Y(42));
af_displayPrintLain("Back", X(6), Y(52));
const byte Lines[] = {42, 52};
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
}
//
//
//
//
// add5-----------------------------------------------------------------------------------------------------------
// Profile
// Profile settings // Buttons Sp Mp
// Moving buttons
void Md_moveButtons(){
Md_p_moveButtons();
switch (mdf_addres(check_emb, 5)) {
case address_not_entered:
break;
case 1:
if (Md_moveButtonsExecute(select_button) == tf_function_completed){//якщо функція завершена повертаємось назад дозволяємо дії геймпада
mdf_addres(back_add, 5);
Gf_functionsGamepad(functions_gamepad_start);
}
break;
case 2:
Md_moveButtonsExecute(return_default) == tf_function_completed ? mdf_addres(back_add, 5) : [](){return 0;}();
break;
case 3: //back
mdf_cursor(set_pos, mdf_addres(get_add, 4));
mdf_addres(back_add, 4);
break;
case address_entered:
mdf_addres(mode_function_returns);
if (mdf_addres(get_add, 5) == 1){Gf_functionsGamepad(functions_gamepad_stop);}//якщо міняємо кнопки місцями забороняємо дії геймпада
mdf_addres(get_add, 5) == 2 ? mdf_confirm(output_by_coordinates, X(37), Y(46)) : [](){return 0;}();
break;
}
}
void Md_p_moveButtons(){
const byte Lines[] = {40, 48, 56};// передаємо координати переміщення функції
mdf_cursor(cursor_read_encoder, sizeof(Lines), Lines);
display.setCursor(0, 40);// понятно
display.println(" Select-move");
display.println(" Reset");
display.println(" Back");
}
int Md_moveButtonsExecute(int acceptedCommand){// test
static byte sd1; // class gamepad batton number
static byte sd2;
static byte executionStage;
static Timer tmr(1000, false);//
switch (acceptedCommand) {
case select_button:
Md_moveButtonsAnimation(executionStage, butGamepad[sd1].getName(), butGamepad[sd2].getName());//відображаємо процес виконання
if (af_checkClick()){//виконання можна прервати і вийти якщо натиснути кнопку
tmr.st(1000, false);
executionStage = false;
return tf_function_completed;
}
else {
switch (executionStage) {
case 0://очікуємо натиску двох кнопок для заміни місцями
case 1:
for (int i = 0; i <= 16; i++) {
if (butGamepad[i].gamepadBatton(read_button_state) == release_button) { //очікуємо release_button не міняємо ти код
if (executionStage == 0) {
sd1 = i;
} else {
sd2 = i;
}
executionStage++;
break;
}
}
break;
case 2://після натискання двох кнопок ще секунду відображаємо натиснуті кнопки
if (tmr.ts()) {//по завершенню секунди міняємо їх місцями
profil[mps(gapn)].listButton[sd1] = butGamepad[sd1].getName();//зберігаємо кнопки які натиснули
profil[mps(gapn)].listButton[sd2] = butGamepad[sd2].getName();
butGamepad[sd1].buttonReinstall(profil[mps(gapn)].listButton[sd2]);//вказуємо екземпляром класу нові кнопки для натискання
butGamepad[sd2].buttonReinstall(profil[mps(gapn)].listButton[sd1]);
executionStage++;
tmr.st(2000, false);//вказуємо новий час для відображення вже поміняних кнопок місцями
}
break;
case 3://відображаємо ще дві секунди кнопки які поміняли місцями
if (tmr.ts()) {//по завершенню часу виходимо
tmr.st(1000, false);
executionStage = 0;
return tf_function_completed;
}
break;
}
}
break;
case return_default://виконується reset або якщо нема потреби відображення надпису not_need
if (profil[mps(gapn)].malb(check_location) == standard_location) {
if (mdf_confirm(print_not_need) == tf_function_completed) {
return tf_function_completed;
}
}
else{
if (mdf_confirm(offer_a_choice) == tf_data_ready) {//пропонуємо вибір якщо вибір зроблено получимо tf_data_ready для подальшої перевірки
if (mdf_confirm(tf_get_data) == choice_yes) {//Перевіряємо чи вибір так Якщо так тоді скидаємо
profil[mps(gapn)].malb(enter_default_location); //повертаємо стандартну номер-назву
bgc_buttonGamepadReinstallAll();//перенастройка всіх екземплярів класу тому що значення в listButton[] помінялись
}
return tf_function_completed;
}
}
break;
}
return 0;
}
void Md_moveButtonsAnimation(byte cmd, byte buttonOne, byte buttonTwo) {
static Timer tmr(500); // таймер для виконання анімації //і накопичення ТІК
const char** buttonNames = fd_getButtonNames();
static bsd ra(7);// ra = Running Arrow
display.drawRect(6, 5, 31, 31, WHITE); // квадрат L
display.drawRect(90, 5, 31, 31, WHITE);// квадрат R
display.drawCircle(21, 20, 8, WHITE);// кружки над кнопками
display.drawCircle(105, 20, 8, WHITE);
if (!cmd ){//1//Очікуємо на натиск кнопки 1
if (tmr.ta()){
af_centerTextX(X(22), Y(17), '?');
}
}
else if (cmd == 1){//2//очікуємо натиску кнопки 2
af_centerTextX(X(22), Y(17), buttonNames[buttonOne]);
if (tmr.ta()){
ra.p(1);
af_centerTextX(X(106), Y(17), '?');
}
for (int i = 0; i < 8; ++i) {// відображаємо кутові скобки
display.setCursor(40+i*6, 13);
display.write(62);
display.setCursor(40+i*6, 23);
display.write(60);
}
display.setCursor(40+ra.g()*6, 13);// трикутник переміщається по углових скобках анімація
display.write(16);
display.setCursor(83-ra.g()*6, 23);// трикутник переміщається по углових скобках анімація
display.write(17);
}
else{
if (ra.g()){
ra.r();
tmr.st(500,true);
}
af_centerTextX(X(22), Y(17), buttonNames[buttonOne]);
af_centerTextX(X(106), Y(17), buttonNames[buttonTwo]);
}
}
//
// Duplicate buttons
void Md_duplicateButtons(){//вибираємо Яку кнопку будемо дублювати або яку кнопку потрібно відключити від дублюючої кнопки
byte regime[] = {d_b_d_m, select_button_r_s, r_s_button_reset, select_button_r_m, r_m_button_reset};
Md_p_duplicateButtons(regime[mdf_addres(get_add, 5)]);//змінюємо анімацію в залежності від набраного адресу
switch (mdf_addres(check_emb, 5)) {
case 1:
if (Md_duplicateButtonsExecute(select_button_r_s) == tf_function_completed){
Gf_functionsGamepad(functions_gamepad_start);
mdf_addres(back_add, 5);
}
break;
case 2:
if (Md_duplicateButtonsExecute(r_s_button_reset) == tf_function_completed){mdf_addres(back_add, 5);}
break;
case 3:
if (Md_duplicateButtonsExecute(select_button_r_m) == tf_function_completed){
mdf_addres(back_add, 5);
Gf_functionsGamepad(functions_gamepad_start);
}
break;
case 4:
if (Md_duplicateButtonsExecute(r_m_button_reset) == tf_function_completed){mdf_addres(back_add, 5);}
break;
case 5: //back
mdf_cursor(set_pos, mdf_addres(back_add, 4));
break;
case address_entered:
Md_p_duplicateButtons();
if (mdf_addres(get_add, 5) == 1 || mdf_addres(get_add, 5) == 3){
Gf_functionsGamepad(functions_gamepad_stop);
mdf_addres(mode_click_back);
}
else if (mdf_addres(get_add, 5) == 2 || mdf_addres(get_add, 4) == 5){
mdf_addres(mode_function_returns);
}
break;
}
}
void Md_p_duplicateButtons(byte acceptedCommand){
static Timer tmr(300);
const char** buttonNames = fd_getButtonNames();
af_displayPrintLain("Rifle stock", X(10), Y(4));
af_displayPrintLain("Reset", X(6), Y(14));
af_displayPrintLain("Rifle magazine", X(10), Y(35), L(3), N(9));
af_displayPrintLain("Reset", X(6), Y(45));
af_displayPrintLain("Back", X(6), Y(56));
for (int y = 0; y <= 31; y += 31) {//відображаємо прямокутники і Квадрати і символи
display.drawRect(6, y+2, 72, 11, WHITE); // прямокутник
display.drawRect(103, y, 15, 15, WHITE);//квадрат ? - ім'я кнопки
display.setCursor(88, y+4);
display.println("=");
}
const byte LinesSp[] = {4, 14, 35, 45, 56};
mdf_cursor(cursor_read_encoder, sizeof(LinesSp), LinesSp);
if (acceptedCommand == select_button_r_m || acceptedCommand == select_button_r_s) {
if (acceptedCommand == select_button_r_s ) { //вибираємо кнопку яку будемо дублювати
if (tmr.ta()) { //виконуємо мигання за вказаною вище періодичністю
display.setCursor(108, 4);
display.println('?');//відображаємо знак запитання з таблиці символів
}
}
else if (acceptedCommand == select_button_r_m) {//майже та сама схема що і вище
if (tmr.ta()) {
display.setCursor(108, 35);
display.println('?');
}
}
if (acceptedCommand == select_button_r_m ){
af_centerTextX(X(111), Y(4), buttonNames[profil[mps(gapn)].listButton[17]]);
}
else if (acceptedCommand == select_button_r_s ){
af_centerTextX(X(111), Y(35), buttonNames[profil[mps(gapn)].listButton[18]]);
}
}
else {
af_centerTextX(X(111), Y(4), buttonNames[profil[mps(gapn)].listButton[17]]);
af_centerTextX(X(111), Y(35), buttonNames[profil[mps(gapn)].listButton[18]]);
}
}
byte Md_duplicateButtonsExecute(byte acceptedCommand){
if (acceptedCommand == select_button_r_s || acceptedCommand == select_button_r_m) {
for (byte i = 0; i <= 16; i++) {
if (butGamepad[i].gamepadBatton(read_button_state) == release_button) {//очікуємо натиску і відпущення кнопки яку будемо дублювати
if (acceptedCommand == select_button_r_s) { // в залежності від вибраної кнопки для дублювання виконуємо копіювання
profil[mps(gapn)].listButton[17] = butGamepad[i].getName();
butGamepad[17].buttonReinstall(profil[mps(gapn)].listButton[17]); // butGamepad[17]нагадування Це є butGamepad[17] назва профіля немає відношення до кнопок
}
else if (acceptedCommand == select_button_r_m) {
profil[mps(gapn)].listButton[18] = butGamepad[i].getName();
butGamepad[18].buttonReinstall(profil[mps(gapn)].listButton[18]);
}
return tf_function_completed;
}
}
}
else if (acceptedCommand == r_s_button_reset){//в залежності від вибору вибираємо кнопку яку будемо скидати
profil[mps(gapn)].listButton[17] = 0;
butGamepad[17].buttonReinstall(profil[mps(gapn)].listButton[17]);
return tf_function_completed;
}
else if (acceptedCommand == r_m_button_reset) {//логіка Та ж сама що і описано зверху
profil[mps(gapn)].listButton[18] = 0;
butGamepad[18].buttonReinstall(profil[mps(gapn)].listButton[18]);
return tf_function_completed;
}
return 0;
}
//
//
//
// General settings
// Controls // Calibration
void Md_joysticksCalibration(){
static byte executionStage;
switch (executionStage) {
case 0: //просимо не чіпати джойстики і натиснути Next
if (Md_jC_exitNext()){
Md_dzcdrj(false, true);//зберігаємо положення джойстиків в даний момент щоб було від чого відштовхуватися
Md_dzcdlj(false, true);
executionStage++;
}
break;
case 1: //виконується калібрування мертвої зони
if (Md_joysticksCalibrationDeadZones()){
Md_mvcdrj(false, true);//
Md_mvcdlj(false, true);
executionStage++;
}
break;
case 2: //просимо обертати джойстик впродовж 10 секунд і натиснути далі
Md_joysticksCalibrationMaximumValues();//виконуємо калібрування
if (Md_jC_exitNext()){executionStage++;}
break;
case 3: //просимо натиснути далі Щоб завершити і зберегти
if (Md_jC_exitNext()){
Gf_rj(s_d_z, Md_dzcdrj(true, false));//записуємо нові мертві зони для джойстиків
Gf_leftJoystick(s_d_z, Md_dzcdlj(true, false));
Gf_rj(s_m_m, Md_mvcdrj(true, false));//записуємо нові максимальні мінімальні значення для джойстиків
Gf_leftJoystick(s_m_m, Md_mvcdlj(true, false));
//Gf_rj(c_c_d);//Перевіряємо чи калібровочні дані відповідають мінімальним параметрам якщо ні ставимо стандартні
//Gf_leftJoystick(c_c_d);
printgetCalibrationData();//test просто для тесту виводить значення в серіал порт
mdf_cursor(set_pos, mdf_addres(back_add, 4));
executionStage = 0;
}
break;
}
Md_p_joysticksCalibration(executionStage);//повідомлення будуть змінюватися залежно від етапу (executionStage)
}
void Md_p_joysticksCalibration(byte executionStage){
display.drawRect(0, 0, 128, 64-14, WHITE);
switch (executionStage) {
case 0:
af_centerTextX(64, 10, "Please don't touch");
af_centerTextX(64, 22, "the joysticks");
af_centerTextX(64, 34, "and select NEXT");
break;
case 1:
af_centerTextX(64, 15, "Dead zone");
af_centerTextX(64, 27, "calibration");
break;
case 2:
af_centerTextX(64, 3, "For about 10 seconds,");
af_centerTextX(64, 15, "rotate the joysticks");
af_centerTextX(64, 27, "in full circles,");
af_centerTextX(64, 39, "then select NEXT");
break;
case 3:
af_centerTextX(64, 10, "Calibration completed");
af_centerTextX(64, 22, "to save and exit");
af_centerTextX(64, 34, "select NEXT");
break;
}
}
// Joystick calibration functions
// Joystick calibration dead zones
int* Md_dzcdrj(bool getArr, bool arrClearFill){ //dzcd = Dead zone calibration data right joystick
static int jd[4];
if (getArr) {//false, true беремо масив в якій попередньо записували дані
return jd;
}
else if (arrClearFill) {//false, true беремо нові дані по осям
int rjd[2];
Gf_rjar(rjd);
jd[x_l] = jd[x_r] = rjd[x_a];
jd[y_u] = jd[y_d] = rjd[y_a];
}
else {//видаляємо дані по осям
memset(jd, 0, sizeof(jd));
}
return nullptr;
}
int* Md_dzcdlj(bool getArr, bool arrClearFill){ //
static int jd[4];
if (getArr) {
return jd;
}
else if (arrClearFill) {
int ljd[2];
Gf_ljar(ljd);
jd[x_l] = jd[x_r] = ljd[x_a];
jd[y_u] = jd[y_d] = ljd[y_a];
}
else {
memset(jd, 0, sizeof(jd));
}
return nullptr;
}
byte Md_joysticksCalibrationDeadZones(){
int rjd[2]; //right joystick data
int ljd[2];
int* dzrj = Md_dzcdrj(true, false);//Беремо зчитані збережені на старті дані
int* dzlj = Md_dzcdlj(true, false);
for (int i = 0; i < 100; i++) {
Gf_rjar(rjd); //получаємо дані з джойстиків
Gf_ljar(ljd);
//rj
if (rjd[x_a] < dzrj[x_l]) {//
dzrj[x_l] = rjd[x_a];
}
if (dzrj[x_r] < rjd[x_a]) {//
dzrj[x_r] = rjd[x_a];
}
if (rjd[y_a] < dzrj[y_u]) {//
dzrj[y_u] = rjd[y_a];
}
if (dzrj[y_d] < rjd[y_a]) {//
dzrj[y_d] = rjd[y_a];
}
//lj
if (ljd[x_a] < dzlj[x_l]) {//
dzlj[x_l] = ljd[x_a];
}
if (dzlj[x_r] < ljd[x_a]) {//
dzlj[x_r] = ljd[x_a];
}
if (ljd[y_a] < dzlj[y_u]) {//
dzlj[y_u] = ljd[y_a];
}
if (dzlj[y_d] < ljd[y_a]) {//
dzlj[y_d] = ljd[y_a];
}
delay(30); // Затримка перед наступним зчитуванням
}
// правий джойстик
dzrj[x_l] -= 20;
dzrj[x_r] += 20;
dzrj[y_u] -= 20;
dzrj[y_d] += 20;
// лівий джойстик
dzlj[x_l] -= 20;
dzlj[x_r] += 20;
dzlj[y_u] -= 20;
dzlj[y_d] += 20;
return true;
}
//
// Joystick calibration maximum values
int* Md_mvcdrj(bool getArr, bool arrClearFill){ //mvcdrj = Maximum Values calibration data right joystick
static int jd[4];
if (getArr) {//якщо перший периметр правда повертаємо масив
return jd;
}
else if (arrClearFill) {//Якщо другий параметр правда на старті зберігаємо дані
int rjd[2];
Gf_rjar(rjd);
jd[x_l] = jd[x_r] = rjd[x_a];
jd[y_u] = jd[y_d] = rjd[y_a];
}
else {//видаляємо збережені попередньо дані
memset(jd, 0, sizeof(jd));
}
return nullptr;
}
int* Md_mvcdlj(bool getArr, bool arrClearFill){ //
static int jd[4];
if (getArr) {
return jd;
}
else if (arrClearFill) {
int ljd[2];
Gf_ljar(ljd);
jd[x_l] = jd[x_r] = ljd[x_a];
jd[y_u] = jd[y_d] = ljd[y_a];
}
else {
memset(jd, 0, sizeof(jd));
}
return nullptr;
}
void Md_joysticksCalibrationMaximumValues(){
static int rjd[2];//right joystick data
static int ljd[2];
Gf_rjar(rjd);
Gf_ljar(ljd);
int* mvrj = Md_mvcdrj(true, false);//беремо попередньо збережені дані
int* mvlj = Md_mvcdlj(true, false);////Беремо зчитані збережені на старті дані
//r
if (rjd[x_a] < mvrj[x_l]) {//шукаємо максимальні мінімальні значення джойстика
mvrj[x_l] = rjd[x_a];//записуємо нові дані
}
if (mvrj[x_r] < rjd[x_a]) {//
mvrj[x_r] = rjd[x_a];
}
if (rjd[y_a] < mvrj[y_u]) {//
mvrj[y_u] = rjd[y_a];
}
if (mvrj[y_d] < rjd[y_a]) {//
mvrj[y_d] = rjd[y_a];
}
//l
if (ljd[x_a] < mvlj[x_l]) {//
mvlj[x_l] = ljd[x_a];
}
if (mvlj[x_r] < ljd[x_a]) {//
mvlj[x_r] = ljd[x_a];
}
if (ljd[y_a] < mvlj[y_u]) {//
mvlj[y_u] = ljd[y_a];
}
if (mvlj[y_d] < ljd[y_a]) {//
mvlj[y_d] = ljd[y_a];
}
}
//
void printgetCalibrationData() {
const char* names[] = {
"Dead zone calib right joystick",
"Dead zone calib left joystick",
"Max values calib right joystick",
"Max values calib left joystick"
};
// Масив вказівників на функції, щоб не писати 4 рази одне й те саме
int* (*funcs[])(bool, bool) = {
Md_dzcdrj,
Md_dzcdlj,
Md_mvcdrj,
Md_mvcdlj
};
for (int i = 0; i < 4; i++) {
int* arr = funcs[i](true, false); // Отримуємо масив
Serial.print(names[i]);
Serial.print(": ");
for (int j = 0; j < 4; j++) {
Serial.print(arr[j]);
if (j < 3) Serial.print(", ");
}
Serial.println();
}
}
//
// Exit next
byte Md_jC_exitNext(){
static byte choice;
mdf_magnifier(choice, 0, 1, 1, 1);
Md_jC__p_exitNext(choice);
if (af_checkClick()) {
if (choice) {//повертаємо правда якщо вибрали Next
choice = false;
return true;
}
mdf_cursor(set_pos, mdf_addres(back_add, 4));//виходимо якщо вибрали Exit
}
return 0;
}
void Md_jC__p_exitNext(byte position){
static byte frame[] = {23, 77};
display.drawRect(frame[position], 52, 27, 11, WHITE);
af_centerTextX(X(64), Y(64-10), "EXIT NEXT");
}
//
//
//
//
// Main display functions
void mdf_displayOutput() {
/*
void applyContrast(byte val) {//потрібно щоб змінювати яскравість
display.ssd1306_command(SSD1306_SETCONTRAST);
display.ssd1306_command(val);//0-255
}
*/
static byte switchOnOff = e_u_d; //переключатель включено виключено
if (switchOnOff == e_u_d){
display.clearDisplay();
Md_infoDisplay(); //перший пункт меню який викликає решту пунктів
display.display();
if (Md_displayTimeout()){//відключаємо дисплей якщо не було ні дій n-кодера
switchOnOff = d_u_d;
display.clearDisplay();
display.display();
}
} //Md_displayTimeout(timeout_restart);
else if (af_checkFastRight() || af_checkFastLeft() || af_checkRight() || af_checkLeft() || af_checkClick()) {//включаємо дисплей якщо зафіксували роботу енкодера
switchOnOff = e_u_d;
}
}
// 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;
}
//
// Saving Settings Display
byte mdf_savingSettingsDisplay(){ //коротко копіювання mps(copy profile) порівняння mps(active_copy_compa re) якщо == active_copy_same просто виходимо якщо != active_copy_sameрізні викликаються Дана функція
mdf_confirm(output_by_coordinates, X(36), Y(40));
mdf_p_savingSettingsDisplay();// візуальна частина коду
if (mdf_confirm(offer_a_choice) == tf_data_ready) {
if (mdf_confirm(tf_get_data) == choice_no) {//якщо вибір ні повертаємо настройки
mps(not_save);
}
return tf_function_completed;//незалежно від вибору функція поверне tf_function_completed
}
return 0;
}
void mdf_p_savingSettingsDisplay(){
display.drawRoundRect(20, 8, 87, 22, 0, WHITE); // рамка навколо слова зверху
display.setCursor(25, 15);
display.println("Save settings");
}
//
void mdf_typeNumberPrint(){//функція відображає номер і тип профілю в інфодисплей і першому пункті меню
if (mps(gapn)) {//номер тип відображається лише коли вибрано профіль
char name[3][3] = {" ", "Sp", "Mp"};
display.setCursor(49, 0);
display.print("( )");
display.setCursor(55, 0);
display.print(name[profil[mps(gapn)].tape]);//tape може бути t_sp = 1 t_mp = 2
display.print(profil[mps(gapn)].namber);
}
}
// 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();
}
}
/*
byte mdf_batteryCheck(byte acceptedCommand) {
static float batLevels[] = {3.05, 3.10, 3.20, 3.29, 3.39, 3.48, 3.58, 3.67, 3.77, 3.86, 3.96, 4.06, 4.14, 4.18}; //1-14 0-13
static Timer tmr;
static byte li = []()->byte {
if (ina219.getCurrent_mA() > 50){
batLevels[12] = 4.18;
return 1;
}
return 12;
}(); //li = level Indicator;
static byte csf = []()->byte {return ina219.getCurrent_mA() > 50 ? b_c_p : b_c_m;}();//на старті Перевіряємо чи зарядка підключена
if (tmr.gp()) {
float voltageNewData = ina219.getBusVoltage_V();
float currentMa = ina219.getCurrent_mA();
if (currentMa <= 0){
if (csf == b_c_m && voltageNewData < batLevels[li]){
li--;
}
else if (li <= 4) {
Md_displayTimeout(timeout_reset);
if (li == 1) {af_powerOffExecution();}
}
else if (csf == b_c_p) {
Md_displayTimeout(timeout_reset);
tmr.ft(2000);
csf = b_c_m;
}
}
else if (50 < currentMa){
if (csf == b_c_p && batLevels[li+1] < voltageNewData && li !=12){
li++;
if (batLevels[12] < voltageNewData){batLevels[12] = 4.14;}
}
else if (csf == b_c_m){
batLevels[12] = 4.18;
Md_displayTimeout(timeout_reset);
tmr.ft(2000);
csf = b_c_p;
li++;
}
tmr.st(2000);
}
if (acceptedCommand == g_b_s) {
return csf == b_c_p ? b_c_p : li;
}
return 0;
}
void mdf_batteryPrint() {
static byte sevedChargeScale; //sevedChargeScale = збереження шкали заряду
static Timer tmr(300);
static bsd ca(12, mdf_batteryCheck(g_b_l)); //ca = charging animation
display.drawRect(111, 2, 1, 4, WHITE);// картинка батареї
display.drawRect(112, 0, 16, 8, WHITE);
if (mdf_batteryCheck(b_c_p)) {
display.fillRect(126-ca.tnar(), 2, ca.tnar(), 4, WHITE);
}
else if ((3 <= ca.g()) || tmr.ta()) {
ca.s(mdf_batteryCheck(g_b_l));
display.fillRect(126-ca.g(), 2, ca.g(), 4, WHITE);// шкала заряду батареї
}
}
*/
// Mdf
// Md
// Gamepad function
// Right joystick + Gyroscope (Z(V) RZ(H))
// Gyroscope
int16_t* Gf_g(int cmd){//перетворюємо дані з гіроскопа і керуємо правим аналогом джойстика
static bool ed; //enabled disabled
static bool fs; //flag return
static int16_t pd[3]; // previous data
static int16_t ud[3]; // unconverted data неперетворені
static int16_t gd[3]; // Get Data
static uint8_t fifoBuffer[42];
static Timer tmr(10);//таймер щоб опитувати кожні 10 мілісекунд
static int dzhZ;
static byte shZ;
static int dzvY;
static byte svY;
dzhZ = profil[mps(gapn)].deadZoneHor;// Мертва зона
shZ = profil[mps(gapn)].sensitivityHor;// чутливість
dzvY = profil[mps(gapn)].deadZoneVer;
svY = profil[mps(gapn)].sensitivityVer;
if (cmd == r_g_d) {
switch (ed) {
case tf_enabled:
if (tmr.gp() && mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) {// коли берем дані одразу зчитуємо нові якщо є
mpu.dmpGetGyro(ud, fifoBuffer);
// X-axis
if (map(abs(ud[g_z]), 0, 150, 1, 30) < abs(ud[g_y])) {// получ знач мають бути більше одинички // h=x // Перевіряємо чи є вихід динамічної мертвої зони
fs = true;
ud[g_y] = constrain(ud[g_y], -svY, svY);
if (0 < ud[g_y]) {
gd[x_a] = map(ud[g_y], 1, svY, -dzvY, -32767);
}
else if(ud[g_y] < 0){
gd[x_a] = map(ud[g_y], -1, -svY, dzvY, 32767);
}
}
else if(gd[x_a]){
fs = true;
gd[x_a] = 0;
}
//
// Y-axis
if (map(abs(ud[g_y]), 0, 150, 1, 30) < abs(ud[g_z])) {//v=y V якщо є вихід за динамічну мертву зону тоді можна виконати перетворення даних
fs = true;
ud[g_z] = constrain(ud[g_z], -shZ, shZ);
if (0 < ud[g_z]) {
gd[y_a] = map(ud[g_z], 1, shZ, -dzhZ, -32767);
}
else if(ud[g_z] < 0){
gd[y_a] = map(ud[g_z], -1, -shZ, dzhZ, 32767);
}
}
else if(gd[y_a]){
fs = true;
gd[y_a] = 0;
}
//
if (fs) {//останній раз ще поверн правда і можна буде становити нуль
fs = false;
return gd;
}
}
break;
case tf_disabled:
if (gd[x_a] || gd[y_a]){
gd[x_a] = gd[y_a] = 0;
return gd;
}
break;
}
}
else if (cmd == g_e) {ed = true;}
else if (cmd == g_d) {ed = false;}//звертаємося до цього масиву pd за даними щоб користуватися звичайними константами x_a y_a
else if (cmd == g_g_d) {return gd;}
return nullptr;
}
//
// Right joystick
byte Gf_r_j_d(byte cmd, int cds[]){ // rjd = right joystick deactivation
static byte executionPath = path_0;
static byte ed;
static Timer tmr(false, false);
static byte pc = 5; // press counter
switch (executionPath){//test
case path_0:// очікуємо коли лічильник назбирає п'ять спрацювань скидаємо його якщо джойстик мертвій зоні дві секунди
switch (cmd){
case rjd_get_data://перевіряємо стан лічильника
if (pc != 5){//якщо цілісність лічильника нарушена
if (tmr.ts()){//якщо джойстик мертвій зоні дві секунди тоді скидаємо лічильник
pc = 5;
}
else if (cds[x_a] || cds[y_a]) {// перезапускаємо таймер очікуємо відпущення джойстика
tmr.st(2000, false);
}
}
break;
case tf_disabled://Дана команда враховується якщо ми в активній зоні
if (cds[x_a] || cds[y_a]) {//якщо джойстик в активній зоні
tmr.st(2000, false); //вказуємо час таймеру
pc--;//зменшуємо лічильник
if (!pc){//означає що натиснули кнопку рукоятки п'ять разів з відхиленим джойстиком
ed = tf_disabled;//вставимо режим відключено щоб повернути його і перейти в режим g_return_non_standard;
executionPath = path_1; // міняємо шлях виконання функцій
}
}
break;
}
break;
case path_1:// лічильник спрацював потрібно виконати умови щоб повернутися попередній шлях виконання
switch (cmd){
case tf_disabled: // Тепер одразу відключаємо джойстик якщо натиснути рукоятку
ed = tf_disabled;
break;
case tf_enabled: // включаємо якщо відпустити рукоятку
ed = tf_enabled;
break;
case g_return_non_standard: // получаем дану команду після того як повернули tf_disabled щоб більше не повертати tf_enabled tf_disabled
ed = g_return_non_standard;
break;
case rjd_get_data: //моніторинг потрібно бути в мертвій зоні щоб відновити роботу
if (cds[x_a] || cds[y_a]) {//
tmr.st(2000, false);
}
else if (tmr.ts()){
ed = tf_enabled;
executionPath = path_0;
pc = 5;
}
break;
}
break;
}
return ed;
}
void Gf_rjar(int dest[2]) { //rjadf = right joystick analog readings
dest[x_a] = analogRead(p_s_t_x_39);//0-4095=l-r
dest[y_a] = analogRead(p_s_t_y_36);//0-4095=u=d
}
int* Gf_rj(int cmd, int newData[]){ //rjadf = right joystick analog data filtered
static Timer tmr(false, false);
static byte executionPath = path_0;// execution path шлях виконання
static int dz[4] = {4095, 0, 4095, 0}; // дані параметри потрібні для калібрування /// !!!В майбутньому потрібно щоб була ініціалізація 0 потрібна для eeprom
static int mm[4] = {0 + 100, 4095 - 100, 0 + 100, 4095 - 100}; // minimum maximum x y
static int dasu[2]; //data analog seve unfiltered
static int cds[2]; //converted data saved
static int da[2]; //data analog дані аналог
static byte fr; //
static bool permit = true;
if (cmd == r_a_d) {
switch (executionPath) {// один раз викликається в коді
case path_0:
Gf_rjar(da); //передаємо масив получаємо дані з аналогових пінів
// X-axis
if (da[x_a] < dz[x_l]) {//Перевіряємо чи є вихід за мертву зону
if (da[x_a] != dasu[x_a]) {//щоб лишній раз не робити розрахунки Перевіряємо чи вони відрізняються від старих
dasu[x_a] = da[x_a];
da[x_a] = constrain(da[x_a], mm[x_l], mm[x_r]);
cds[x_a] = map(da[x_a], dz[x_l], mm[x_l], 0, -32767);
fr = true;
}
else {fr = true;}//якщо дані не змінилися відправляємо перетворені старі дані
}
else if (dz[x_r] < da[x_a]) {
if (da[x_a] != dasu[x_a]) {
dasu[x_a] = da[x_a];
da[x_a] = constrain(da[x_a], mm[x_l], mm[x_r]);
cds[x_a] = map(da[x_a], dz[x_r], mm[x_r], 0, 32767);
fr = true;
}
else {fr = true;}
}
else if (cds[x_a]) {
fr = true;
cds[x_a] = 0;
}
//
// Y-axis
if (da[y_a] < dz[y_u]) {//Перевіряємо чи є вихід за мертву зону
if (da[y_a] != dasu[y_a]) {
dasu[y_a] = da[y_a];
da[y_a] = constrain(da[y_a], mm[y_u], mm[y_d]);
cds[y_a] = map(da[y_a], dz[y_u], mm[y_u], 0, -32767);
fr = true;
}
else {fr = true;}
}
else if (dz[y_d] < da[y_a]) {
if (da[y_a] != dasu[y_a]) {
dasu[y_a] = da[y_a];
da[y_a] = constrain(da[y_a], mm[y_u], mm[y_d]);
cds[y_a] = map(da[y_a], dz[y_d], mm[y_d], 0, 32767);
fr = true;
}
else {fr = true;}
}
else if (cds[y_a]) {
fr = true;
cds[y_a] = 0;
}
//
switch (Gf_rjd(rjd_get_data, cds)) {// в залежності від стану рукоятки або лічильника повертаємо дані відправляємо масив щоб моніторити позицію джойстика
case tf_enabled:// якщо відпустили рукоятку або після відключення потримали 2 секунди в мертвій зоні джойстик автоматично получим tf_enabled
if (fr) {// якщо правда значить дані потрібно відправити
fr = false;
return cds;// відправлення масиву рахується як правда
}
break;
case tf_disabled://
Gf_rjd(g_return_non_standard);//відправляємо дану команду щоб жоден з варіантів не повертався тому що tf_disabled потрібно получити лише один раз
cds[x_a] = cds[y_a] = false; // ставим нулі по осям
return cds;// одноразова повертаємо ці нулики
break;
}
break;
case path_1:
Gf_rjar(da);
if (da[x_a] < dz[x_l] || da[x_a] > dz[x_r] || da[y_a] < dz[y_u] || da[y_a] > dz[y_d]) { //при виході з dz відбувається постійно скидання і вказання нового часу
tmr.st(2000, false); // тут відбувається тільки запис в зміну часу, нема ариxметики
}
else if (tmr.ts()) {executionPath = path_0;} // тут можливa правда лише коли таймер перестане перезапускатися
break;
case path_2:
return nullptr;
break;
}
}
else if (cmd == g_a_d) {return cds;}
else if (cmd == r_j_e) {Gf_rjd(tf_enabled);}//рукоятка відпущена робота джойстика дозволена якщо спрацьовував захист
else if (cmd == r_j_d){Gf_rjd(tf_disabled, cds);}//відправляємо команду відключити і масив щоб дізнатись дані про джойстик
//беремо зчитані збережені дані
else if (cmd == g_d_z) {return dz;}
else if (cmd == g_m_m) {return mm;}
else if (cmd == s_m_m) {memcpy(mm, newData, sizeof(mm));} // Вказуємо мінімальне !! максимальне значення мінімальне значення не може бути нулем буде конфлікт
else if (cmd == s_d_z) {memcpy(dz, newData, sizeof(dz));} // Вказуємо мертві зони !!! нагадування Мертва зона може бути в певних межах зробити перевірку
else if (cmd == s_c_w) {permit = false;}//на старті можна заборонити роботу
else if (cmd == c_c_j) {// check calibration data
Gf_rjar(da);
if (abs(da[x_a] - 2048) > 300 || abs(da[y_a] - 2048) > 300) {//1he спрацювання виводимо повідомлення 2ge спрацювання виконуємо відключення
executionPath = path_2;// ставим режим відключено
int initialValues[] = {4095, 0, 4095, 0};//скидаємо записані дані камбрування
memcpy(dz, initialValues, sizeof(dz));
return da;//повертаємо правда виконуємо дії залежності від етапу перезапуск або відключення
}
else{
executionPath = path_1; // вказуємо режим перевірка мертвої зони
if (da[x_a] < dz[x_l]) {//калібруємо мертву зону //при першому виклику умова завжди буде правильно тому що сказали необхідні значення при створенні масиву дз
dz[x_l] = da[x_a];
}
if (da[x_a] > dz[x_r]) {
dz[x_r] = da[x_a];
}
if (da[y_a] < dz[y_u]) {
dz[y_u] = da[y_a];
}
if (da[y_a] > dz[y_d]) {
dz[y_d] = da[y_a];
}
}
}
return nullptr;
}
//
// Combined gyro right joystick
byte Gf_handleButton(){ // handle Button test
static int TDZ = []() { int s=0; for(int i=0;i<10;i++){s+=touchRead(p_h_b_14); delay(20);} Serial.print("touchRead(p_h_b_14) ");
Serial.println(touchRead(p_h_b_14));return s/10 - 30;}();//test
static bool buttonState; //
buttonState = TDZ < touchRead(p_h_b_14);//постійно моніторимо сенсор
static bool previousValue = !buttonState; // //previousValue = !buttonState; перший запуск потрібно щоб умова if (buttonState != previousValue){ була правдою при першому виклику функції
if (buttonState != previousValue){//перший виклик завжди буде правда
previousValue = buttonState;//повернем правда або брехня
return previousValue;//повертаємо натиснуто або відпущено далі будуть виконуватися дії дивитись код
}
return g_no_action;
}
int Gf_da(int cmd, int data){// data axis функція зберігає відправляє і повертає попередні дані по осям
static int pd[2];//
switch (cmd) {
case g_x:
return pd[x_a];
break;
case g_y:
return pd[y_a];
break;
case s_x:
pd[x_a] = data;
bleGamepad.setRZ(data);
Gf_sendReport(send_report);
break;
case s_y:
pd[y_a] = data;
bleGamepad.setZ(data);
Gf_sendReport(send_report);
break;
}
return 0;
}
byte Gf_combinedGyroRightJoystick(int cmd){
static byte om = joystick_full_priority; //operating mode;
switch (Gf_handleButton()){//test сенсор рукоятки
case true:
Gf_g(g_e);// якщо натиснути включаємо гіроскоп
Gf_rj(r_j_d);// якщо натиснуто рахуємо кількість(5) натисків якщо джойстик в робочий зоні
break;
case false:
Gf_g(g_d);//якщо відпустили забороняємо роботу гіроскопa
Gf_rj(r_j_e);//якщо відпустили в даному випадку дозволяємо роботу якщо було заборонено
break;
}
bool gyroActive = Gf_g(r_g_d); // виконується зчитування даних і повертається правда ящо дані міняються або в кінці получаємо нуль
bool joyActive = Gf_rj(r_a_d); // виконуємо перевірки і зчитуємо Дані
if (!cmd) {
switch (om){
case false: // = 0 пустота для того щоб був Jump table
break;
case joystick_full_priority: // = 1
if (joyActive) {
if (Gf_da(g_x) != Gf_rj(g_a_d)[x_a]) {
Gf_da(s_x, Gf_rj(g_a_d)[x_a]);
}
if (Gf_da(g_y) != Gf_rj(g_a_d)[y_a]) {
Gf_da(s_y, Gf_rj(g_a_d)[y_a]);
}
}
else if (gyroActive) {
if (Gf_da(g_x) != Gf_g(g_g_d)[x_a]) {
Gf_da(s_x, Gf_g(g_g_d)[x_a]);
}
if (Gf_da(g_y) != Gf_g(g_g_d)[y_a]) {
Gf_da(s_y, Gf_g(g_g_d)[y_a]);
}
}
break;
case joystick_disable_gyro:
if (joyActive) {
if (Gf_da(g_x) != Gf_rj(g_a_d)[x_a]) {
Gf_da(s_x, Gf_rj(g_a_d)[x_a]);
}
if (Gf_da(g_y) != Gf_rj(g_a_d)[y_a]) {
Gf_da(s_y, Gf_rj(g_a_d)[y_a]);
}
if(!Gf_rjdc(get_counter)){//якщо захист джойстика спрацював то наоборот включимо гіроскоп якщо він був виключений
Gf_g(g_e);
}
else if (gyroActive) {//якщо гіроскоп має дані під час роботи джойстика відключаємо його
Gf_g(g_d);
}
}
else if (gyroActive) {
if (Gf_da(g_x) != Gf_g(g_g_d)[x_a]) {
Gf_da(s_x, Gf_g(g_g_d)[x_a]);
}
if (Gf_da(g_y) != Gf_g(g_g_d)[y_a]) {
Gf_da(s_y, Gf_g(g_g_d)[y_a]);
}
}
break;
case gyro_joystick_combination:
if (gyroActive || joyActive) {
int temp = constrain((long)Gf_g(g_g_d)[x_a] + (long)Gf_rj(g_a_d)[x_a], -32768, 32767);
if (Gf_da(g_x) != temp) {
Gf_da(s_x, temp);
}
temp = constrain((long)Gf_g(g_g_d)[y_a] + (long)Gf_rj(g_a_d)[y_a], -32768, 32767);
if (Gf_da(g_y) != temp) {
Gf_da(s_y, temp);
}
}
break;
case priority_by_magnitude: //Пріоритет за величиною
if (joyActive || gyroActive) {
if (Gf_g(g_g_d)[x_a] < Gf_rj(g_a_d)[x_a]) {
if (Gf_da(g_x) != Gf_rj(g_a_d)[x_a]) {
Gf_da(s_x, Gf_rj(g_a_d)[x_a]);
}
}
else if (Gf_rj(g_a_d)[x_a] < Gf_g(g_g_d)[x_a]) {
if (Gf_da(g_x) != Gf_g(g_g_d)[x_a]) {
Gf_da(s_x, Gf_g(g_g_d)[x_a]);
}
}
else if (Gf_da(g_x)) {
Gf_da(s_x, 0);
}
if (Gf_g(g_g_d)[y_a] < Gf_rj(g_a_d)[y_a]) {
if (Gf_da(g_y) != Gf_rj(g_a_d)[y_a]) {
Gf_da(s_y, Gf_rj(g_a_d)[y_a]);
}
}
else if (Gf_rj(g_a_d)[y_a] < Gf_g(g_g_d)[y_a]) {
if (Gf_da(g_y) != Gf_g(g_g_d)[y_a]) {
Gf_da(s_y, Gf_g(g_g_d)[y_a]);
}
}
else if (Gf_da(g_y)) {
Gf_da(s_y, 0);
}
}
break;
case gyro_full_priority:
if (gyroActive) {
if (Gf_da(g_x) != Gf_g(g_g_d)[x_a]) {
Gf_da(s_x, Gf_g(g_g_d)[x_a]);
}
if (Gf_da(g_y) != Gf_g(g_g_d)[y_a]) {
Gf_da(s_y, Gf_g(g_g_d)[y_a]);
}
}
else if (joyActive) {
if (Gf_da(g_x) != Gf_rj(g_a_d)[x_a]) {
Gf_da(s_x, Gf_rj(g_a_d)[x_a]);
}
if (Gf_da(g_y) != Gf_rj(g_a_d)[y_a]) {
Gf_da(s_y, Gf_rj(g_a_d)[y_a]);
}
}
break;
case only_gyro://при переході на даний режим не можна нахиляти джойстик
if (gyroActive) {
if (Gf_da(g_x) != Gf_g(g_g_d)[x_a]) {
Gf_da(s_x, Gf_g(g_g_d)[x_a]);
}
if (Gf_da(g_y) != Gf_g(g_g_d)[y_a]) {
Gf_da(s_y, Gf_g(g_g_d)[y_a]);
}
}
break;
}
}
else{
switch (cmd) { // тому що получаємо значення від 1
case false: // = 0 пустота для того щоб був Jump table
break;
case joystick_full_priority: // = 1
om = joystick_full_priority;
break;
case joystick_disable_gyro: // = 2
om = joystick_disable_gyro;
break;
case gyro_joystick_combination: // = 3
om = gyro_joystick_combination;
break;
case priority_by_magnitude: // = 4
om = priority_by_magnitude;
break;
case gyro_full_priority: // = 5
om = gyro_full_priority;
break;
case only_gyro: // = 6
om = only_gyro;
break;
case g_o_m: // get operating mode = 7
return om;
break;
}
}
return 0;
}
//
//
// left joystick
void Gf_ljar(int dest[2]) { //ljadf = left joystick analog readings
dest[x_a] = analogRead(p_s_t_x_39);//test
dest[y_a] = analogRead(p_s_t_y_36);
}
int* Gf_leftJoystick(int cmd, int newData[]){//test
static Timer tmr(false, false);
static byte executionPath = path_0;// execution path шлях виконання
static int cds[2]; // cds = converted data saved (x y) (-32767 0 32767)
static int dz[4] = {4095, 0, 4095, 0}; // дані параметри потрібні для калібрування
static int mm[4] = {0+100, 4095-100, 0+100, 4095-100}; // minimum maximum x y
static int da[2]; // data analog
static int dasu[2] = {2048, 2048, 2048, 2048}; //data analog seve unfiltered // вказуємо 2048 тому що може бути звернення навіть якщо є заборона
if (!cmd) {//коли нема команди виконується основний код i коли пройшли перевірку checkLeftJoystick
switch (executionPath) {
case path_0: // зчитуємо перетворюємо відправляємо
Gf_ljar(da);
// X-axis
if (da[x_a] < dz[x_l]) { // незалежна як підключити кабель тут буде реакція на зменшення опору
if (dasu[x_a] != da[x_a]) {
da[x_a] = constrain(da[x_a], mm[x_l], dz[x_l]);// тому помилки не буде
cds[x_a] = map(da[x_a], mm[x_l], dz[x_l], -32767, 0);//можна переставити місцями 0 i -32767 якщо буде інверсія
dasu[x_a] = da[x_a];
bleGamepad.setX(cds[x_a]);
Gf_sendReport(send_report);
}
}
else if (da[x_a] > dz[x_r]) {
if (dasu[x_a] != da[x_a]) {
da[x_a] = constrain(da[x_a], dz[x_r], mm[x_r]);
cds[x_a] = map(da[x_a], dz[x_r], mm[x_r], 0, 32767);
dasu[x_a] = da[x_a];
bleGamepad.setX(cds[x_a]);
Gf_sendReport(send_report);
}
}
else if (cds[x_a]){
cds[x_a] = 0;
bleGamepad.setX(cds[x_a]);
Gf_sendReport(send_report);
}
//
// Y-axis
if (da[y_a] < dz[y_u]) {
if (dasu[y_a] != da[y_a]) {
da[y_a] = constrain(da[y_a], mm[y_u], dz[y_u]);
cds[y_a] = map(da[y_a], mm[y_u], dz[y_u], -32767, 0);
dasu[y_a] = da[y_a];
bleGamepad.setY(cds[y_a]);
Gf_sendReport(send_report);
}
}
else if (da[y_a] > dz[y_d]) {
if (dasu[y_a] != da[y_a]) {
da[y_a] = constrain(da[y_a], dz[y_d], mm[y_d]);
cds[y_a] = map(da[y_a], dz[y_d], mm[y_d], 0, 32767);
dasu[y_a] = da[y_a];
bleGamepad.setY(cds[y_a]);
Gf_sendReport(send_report);
}
}
else if (cds[y_a]) {
cds[y_a] = 0;
bleGamepad.setY(cd[y_a]);
Gf_sendReport(send_report);
}
//
break;
case path_1:// додаткова перевірка мертвої зони (логіка вихід, захід на 2 секунди, перевірка пройдена)
Gf_ljar(da);
if (da[x_a] < dz[x_l] || da[x_a] > dz[x_r] || da[y_a] < dz[y_u] || da[y_a] > dz[y_d]) { //при виході з dz відбувається постійно скидання і вказання нового часу
tmr.st(2000, false);//тут відбувається тільки запис в зміну часу, нема арифметики
}
else if (tmr.ts()) {executionPath = path_0;} // тут можливa правда лише коли таймер перестане перезапускатися
break;
case path_2:// робота заборонена перевірка не пройдена
return nullptr;
break;
}
} else{
if (cmd == g_a_d) {return cds;}
else if (cmd == g_d_z) {return dz;}
else if (cmd == g_m_m) {return mm;}
else if (cmd == s_d_z) {memcpy(dz, newData, sizeof(dz));}
else if (cmd == s_m_m) {memcpy(mm, newData, sizeof(mm));}
else if (cmd == c_c_j) {// check calibrate joystick
Gf_ljar(da);
if (abs(da[x_a] - 2048) > 300 || abs(da[y_a] - 2048) > 300) {//1he спрацювання виводимо повідомлення 2ge спрацювання виконуємо відключення
executionPath = path_2;//ставим режим відключено
int initialValues[] = {4095, 0, 4095, 0};//скидаємо записані дані камбрування
memcpy(dz, initialValues, sizeof(dz));
return da;//повертаємо правда виконуємо дії залежності від етапу перезапуск або відключення
}
else{
executionPath = path_1;//вказуємо режим перевірка мертвої зони
if (da[x_a] < dz[x_l]) {//калібруємо мертву зону //при першому виклику умова завжди буде правильно тому що сказали необхідні значення при створенні масиву дз
dz[x_l] = da[x_a];
}
if (da[x_a] > dz[x_r]) {
dz[x_r] = da[x_a];
}
if (da[y_a] < dz[y_u]) {
dz[y_u] = da[y_a];
}
if (da[y_a] > dz[y_d]) {
dz[y_d] = da[y_a];
}
}
}
}
return nullptr;
}
//
void Gf_gamepadButtons(){
static byte flagSendReport;
for (byte i = 0; i <= 16; i++) {//Перевіряємо чи натиснуті кнопки
if (butGamepad[i].gamepadBatton(read_button_state)){//в любому випадку якщо відпустити або натиснути получим правда
flagSendReport = send_report;//записуємо змінu щоб постійно не викликати функцію
}
}
if (flagSendReport){
Gf_sendReport(flagSendReport);
flagSendReport = false;
}
}
/*
byte Gf_gamepadButtonsDuplicators(byte acceptedCommand){
if (profil[mps(gapn)].rrsps){//беремо режим роботи
butGamepad[16].gamepadBatton(read_button_state);
}
else{
butGamepad[16].gamepadBatton(r_b_double_press);//зазвичай кнопка дублює кнопку приціл Тому потрібно деколи спеціальний режим подвійного натиску якщо немає можливості змінити в настройках
}
butGamepad[17].gamepadBatton(read_button_state);
if (acceptedCommand){
if (acceptedCommand == dbrs_change_mode){
profil[mps(gapn)].rrsps = !profil[mps(gapn)].rrsps;//режим роботи зберігається в профілі
}
else if (acceptedCommand == dbrs_get_mode){
return profil[mps(gapn)].rrsps;
}
else if (acceptedCommand == dbrs_reset){
profil[mps(gapn)].rrsps = 1;
}
}
return 0;
}
*/
void Gf_functionsGamepad(byte acceptedCommand){
//START_TIMER();
static byte fep = true; // fep = flag execution permission
if (acceptedCommand) {// TEST Для зупинки потрібно використати Gf_sendReport(stop); ому що гіроскоп може перестати працювати
if (acceptedCommand == functions_gamepad_stop) {Gf_controlElementsStope(); fep = false;}
else if (acceptedCommand == functions_gamepad_start) {fep = true;}
}
else if (bleGamepad.isConnected() && fep) {
Gf_leftJoystick();//!!!
Gf_combinedGyroRightJoystick();
Gf_gamepadButtons();
Gf_sendReport(send_report_execute);
}
//END_TIMER();
}
void Gf_sendReport(byte acceptedCommand){
static byte gamepadSendReport = waiting_command;
switch (acceptedCommand) {
case send_report_execute:
if (gamepadSendReport == send_report) {
Md_autoPowerOff(apo_restart_time);
gamepadSendReport = waiting_command;
bleGamepad.sendReport();
}
break;
case send_report:
gamepadSendReport = send_report;
break;
}
}
void Gf_controlElementsStope(){
for (int i = 1; i <= 8; i++) {// перед тим як заборонити відправку потрібно відмінити все що відбувалось перед тим
bleGamepad.release(i);
}
// Відпустити кнопки Start та Select
bleGamepad.releaseStart();
bleGamepad.releaseSelect();
//курки
bleGamepad.setRightTrigger(0);
bleGamepad.setLeftTrigger(0);
//правий джойстик
bleGamepad.setRZ(0);
bleGamepad.setZ(0);
//лівий джойстик
bleGamepad.setX(0);
bleGamepad.setY(0);
// Якщо ти використовуєш AutoReport = 0
bleGamepad.sendReport();
}
// Gf
void setup() {
Wire.begin();
Serial.begin(115200);
delay(1000);
Serial.println("Serial.println("");");
//виконуємо настройки геймпада
//добавити кнопку home можливо буде потрібно при підключенні до приставок
bleGamepadConfig.setAutoReport(0);
bleGamepadConfig.setButtonCount(8);
bleGamepadConfig.setIncludeStart(true);
bleGamepadConfig.setIncludeSelect(true);
bleGamepadConfig.setIncludeHome(true);
bleGamepadConfig.setHatSwitchCount(1);
bleGamepadConfig.setAxesMin(-32767);
bleGamepadConfig.setAxesMax(32767);
bleGamepadConfig.setWhichAxes(1, 1, 1, 1, 1, 1, 0, 0);
bleGamepad.begin(&bleGamepadConfig);
//
// Encoder setting
enc.setFastTimeout(2000); // Таймаут для швидкого повороту енкодера (в мс)
enc.setType(TYPE2);
//
// ----------Display--------------
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextSize(1);
display.setTextColor(WHITE);
display.clearDisplay();
display.display();
//
// запуск гороскопа
mpu.initialize();
if (!mpu.dmpInitialize()) {
mpu.setFullScaleGyroRange(MPU6050_GYRO_FS_250);//встановлюємо максимальну чутливість
mpu.CalibrateGyro(5); // калібрування гіроскопа test
Serial.println("Enabling DMP...");
mpu.setDMPEnabled(true); // вмикаємо DMP
}
//
af_startAnimation();// анімація при включенні. до робити в кінці!
//робота з енергонезалежною пам'яттю
EEPROM.begin(512);
delay(50);
EEPROM.put(0, 1);EEPROM.commit();//test
//af_saveToEEPROM(eeprom_initialization); //беремо збереження настройки з енергонезалежної пам'яті
//
//настройка rgb стрічки test
FastLED.addLeds<WS2812, p_led_pin_13, GRB>(leds,1);
FastLED.setBrightness(255); // глобальна яскравість (0–255)
//
// геймпад додатково
bgc_buttonConnectionResistor();//виконує підключення до резисторів якщо кнопка тактильна
af_controlsCheck();
//
//настрою необхідні параметри для роботи ефектів
af_triggerLight(color_one); // встановлюємо початковий колір
af_shotgunBolt(bolt_pin_activate); // настроюємо піни
af_vibration(vt_pin_activate);
//
}
void loop() {
//START_TIMER();
// tick
enc.tick();
Md_autoPowerOff();//якщо є дозвіл автоматичне відключення по завершенню часу викликається перезапуск в Gf_sendReportExecute encoderModule
af_triggerShootingEffects();//постійно моніторимо функцію функція запускає ефекти після того як дається команда (дивитись вище)
Gf_functionsGamepad();
mdf_displayOutput();
// display.clearDisplay();
// Md_disableButton();
//display.display();
//END_TIMER();
}// void loop()