Skip to content

Instantly share code, notes, and snippets.

@Onefabis
Created April 21, 2025 23:31
Show Gist options
  • Save Onefabis/d35c678a5eacc5fdf1e8a44626c3cf9f to your computer and use it in GitHub Desktop.
Save Onefabis/d35c678a5eacc5fdf1e8a44626c3cf9f to your computer and use it in GitHub Desktop.
Позволяет эмулировать нажатие энкодеров и кнопок для головного устройства (T10, TS10 и аналогов). Для расширения функций, увеличения комбинаторики, возможности подключения нескольких энкодеров параллельно и для добавления программного антидребезга. Энкодер теперь может быть любым, поддерживаемым библиотекой Alex Gyver (EncButton) #гу #HU #HEADUN…
#include <WiFi.h>
#include <AsyncTCP.h> // v3.3.7
#include <ESPAsyncWebServer.h> // v3.7.3
#include <ElegantOTA.h> // v3.1.7
#include <EncButton.h> // v3.7.1
#define ENC1_A 3 // Пин 1 виртуального энкодера ENC1 для магнитолы
#define ENC1_B 4 // Пин 2 виртуального энкодера ENC1 для магнитолы
#define ENC2_A 1 // Пин 1 виртуального энкодера ENC2 для магнитолы
#define ENC2_B 2 // Пин 2 виртуального энкодера ENC2 для магнитолы
#define KEY1 20 // Виртуальная кнопка 1 на KEY2 провод магнитолы
#define KEY2 21 // Виртуальная кнопка 2 на KEY2 провод магнитолы
#define STEP_DELAY 20 // Задержка между изменениями состояний (мс)
EncButton eb1(7, 6, 5); // Настройка физического энкодера 1 с кнопкой
EncButton eb2(10, 9, 8); // Настройка физического энкодера 2 с кнопкой
// Текущие состояния пинов
bool direction = true; // true = вправо, false = влево
// Установка глобальных переменных
bool assign_mode = false;
bool ota_enabled = false;
const char* ssid = "ESP32_AP"; // Название точки доступа ESP32 C3
const char* password = "1212ESPAP2121"; // !!! ОБЯЗАТЕЛЬНО ИЗМЕНИТЬ НА СВОЙ !!! Пароль точки доступа ESP32 C3
unsigned long ota_progress_millis = 0;
AsyncWebServer server(80);
void isr1() { // Вызов обработки физического энкодера 1 по прерыванию
eb1.tickISR();
}
void isr2() { // Вызов обработки физического энкодера 2 по прерыванию
eb2.tickISR();
}
void setup() {
Serial.begin(115200);
attachInterrupt(6, isr1, CHANGE); // Настройка прерывания энкодера 1 пина 1
attachInterrupt(7, isr1, CHANGE); // Настройка прерывания энкодера 1 пина 2
attachInterrupt(9, isr2, CHANGE); // Настройка прерывания энкодера 2 пина 1
attachInterrupt(10, isr2, CHANGE); // Настройка прерывания энкодера 2 пина 2
eb1.setEncISR(true);
eb2.setEncISR(true);
// Устанока всех пинов на режим выхода сигнала (OUTPUT)
pinMode(ENC1_A, OUTPUT);
pinMode(ENC1_B, OUTPUT);
pinMode(ENC2_A, OUTPUT);
pinMode(ENC2_B, OUTPUT);
pinMode(KEY1, OUTPUT);
pinMode(KEY2, OUTPUT);
// Установка всех уровней на высокий. Провода от магнитолы замыкаются через резисторы на землю
// для срабатывания, то есть, когда уровень становится LOW
digitalWrite(ENC1_A, HIGH);
digitalWrite(ENC1_B, HIGH);
digitalWrite(ENC2_A, HIGH);
digitalWrite(ENC2_B, HIGH);
digitalWrite(KEY1, HIGH);
digitalWrite(KEY2, HIGH);
}
// Эмуляция одного щелчка энкодера по часовой или против часовой стрелки
// Направление определяет переменная direction
void encoderClick(int repeat, int PIN_A, int PIN_B) {
if (direction) {
// Вращение вправо: A↓, B↓ → A↑, B↓ → A↑, B↑
for ( int i=0; i<repeat; i++){
digitalWrite(PIN_A, LOW);
digitalWrite(PIN_B, LOW);
delay(STEP_DELAY);
digitalWrite(PIN_A, HIGH);
delay(STEP_DELAY);
digitalWrite(PIN_B, HIGH);
delay(STEP_DELAY);
}
} else {
// Вращение влево: A↑, B↑ → A↑, B↓ → A↓, B↓
digitalWrite(PIN_A, HIGH);
digitalWrite(PIN_B, HIGH);
delay(STEP_DELAY);
digitalWrite(PIN_B, LOW);
delay(STEP_DELAY);
digitalWrite(PIN_A, LOW);
delay(STEP_DELAY);
}
}
// Эмуляция нажатия на кнопку, сигнал идёт на провод KEY2 магнитолы
void buttonClick(int KEY){
int delay_time = STEP_DELAY; // если выключен assign_mode, зажатие кнопки 20 милисекунд, в обычном режиме
if (assign_mode) delay_time = 3000; // если включен assign_mode, зажатие кнопки 3 секунды, чтобы успеть обучит магнитолу
digitalWrite(KEY, LOW);
delay(delay_time);
digitalWrite(KEY, HIGH);
}
// Запуск точки дотупа и включение OTA
void startAPandOTA() {
if (ota_enabled) return;
WiFi.softAP(ssid, password);
// Точка доступа, запись IP адреса точки доступа
Serial.println(WiFi.softAPIP());
// Стартовая страница с ссылкой на страницу обновления прошивки
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
String ip = WiFi.softAPIP().toString();
String html = "<h1>ESP OTA. Add /update</h1>";
html += "<p>Connect to the OTA page at: <a href='http://" + ip + "/update'>http://" + ip + "/update</a></p>";
request->send(200, "text/html", html);
});
ElegantOTA.begin(&server);
server.begin();
ota_enabled = true;
Serial.println("OTA via AP started");
}
// Отключение OTA для экономии расхода по току
void stopAPandOTA() {
if (!ota_enabled) return;
WiFi.softAPdisconnect(true);
ota_enabled = false;
Serial.println("OTA via AP stopped");
}
void loop() {
if (ota_enabled) { // Цикл для работы OTA, только если включено OTA режим
ElegantOTA.loop();
}
eb1.tick();
eb2.tick();
if (eb1.turn() && eb1.pressing()) { // Обработка поворота энкодера 1 с зажатой кнопкой
if (eb1.leftH()){ // Если поворот влево с зажатой кнопкой, меняем громкость на +/- 1 деление для обратной связи
direction = false;
encoderClick(1, ENC1_A, ENC1_B); // Громкость уменьшается на -1 для обозначения, что включен особый режим
delay(200);
direction = true;
encoderClick(1, ENC1_A, ENC1_B); // Громкость увеличивается на +1 для обозначения, что включен особый режим
assign_mode = false; // Отключается режим обучения кнопок руля, время нажатия кнопок обычное, 20 милисекунд
Serial.println("Assign mode stop");
}
if (eb1.rightH()){
direction = false;
encoderClick(1, ENC1_A, ENC1_B); // Громкость уменьшается на -1 для обозначения, что включен особый режим
delay(200);
direction = true;
encoderClick(1, ENC1_A, ENC1_B); // Громкость увеличивается на +1 для обозначения, что включен особый режим
assign_mode = true; // Включается режим обучения кнопок руля, время нажатия кнопок увеличенное, чтобы обучить магнитолу
Serial.println("Assign mode start");
}
} else if (eb1.turn() && eb1.pressing()==false){ // Обычное вращение энкодера 1
if (eb1.left()) direction = false;
if (eb1.right()) direction = true;
int repeat_steps = 1;
if (eb1.fast()) repeat_steps = 2; // Быстрое вращение громкости меняет уровень на +/- 2 деления
encoderClick(repeat_steps, ENC1_A, ENC1_B); // Смена уровня громкости
}
if (eb2.turn() && eb2.pressing()) { // Обработка поворота энкодера 2 с зажатой кнопкой
if (eb2.leftH()){
direction = false;
encoderClick(1, ENC1_A, ENC1_B); // Громкость уменьшается на -1 для обозначения, что включен особый режим
delay(200);
direction = true;
encoderClick(1, ENC1_A, ENC1_B); // Громкость увеличивается на +1 для обозначения, что включен особый режим
startAPandOTA(); // Включение точки доступа и OTA режима
Serial.println("Start OTA");
}
if (eb2.rightH()){
direction = false;
encoderClick(1, ENC1_A, ENC1_B); // Громкость уменьшается на -1 для обозначения, что включен особый режим
delay(200);
direction = true;
encoderClick(1, ENC1_A, ENC1_B); // Громкость увеличивается на +1 для обозначения, что включен особый режим
stopAPandOTA(); // Выключение точки доступа и OTA режима для экономии расхода по току
Serial.println("Stop OTA");
}
} else if (eb2.turn() && eb2.pressing()==false){ // Обычное вращение энкодера 2
if (eb2.left()) direction = false;
if (eb2.right()) direction = true;
int repeat_steps = 1;
if (eb1.fast()) repeat_steps = 3; // Быстрое вращение энкодера переключает треки на +/- 3 трека
encoderClick(repeat_steps, ENC2_A, ENC2_B); // Смена аудиотрека
}
if (eb1.click()) {
buttonClick(KEY1); // Эмуляция нажатия кнопки 1 через резистор на KEY2 магнитолы
Serial.println("key 1 is pressed");
}
if (eb2.click()) {
buttonClick(KEY2); // Эмуляция нажатия кнопки 2 через резистор на KEY2 магнитолы
Serial.println("key 2 is pressed");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment