#include "ButtonLogic.h"
#include <ESP8266WiFi.h>
#include <sh_core_esp8266.h>
// Сенсорные модули обычно дают HIGH при касании.
// Если у тебя наоборот — поставь false.
static const bool TOUCH_ACTIVE_HIGH = true;
// Антиспам
static const uint32_t PRESS_ANTISPAM_MS = 200;
// Debounce
static const uint32_t DEBOUNCE_MS = 50;
// Тайминги индикаций
static const uint32_t WARNING_BLINK_MS = 500;
static const uint32_t ERROR_BLINK_MS = 1500;
static const uint32_t GLOBAL_BLINK_MS = 1000;
// Таймаут ожидания ответа сервера
static const uint32_t WAIT_TIMEOUT_MS = 1000;
// endpoint события (на сервере)
static const char* EVENT_PATH = "/events/new";
// лента
static Adafruit_NeoPixel pixels(BUTTON_CHANNEL_NUM, SIGNAL_LED, NEO_GRB + NEO_KHZ800);
// debounce/state
static uint8_t last_raw[BUTTON_CHANNEL_NUM];
static uint8_t stable_state[BUTTON_CHANNEL_NUM];
static uint32_t last_change_at[BUTTON_CHANNEL_NUM];
// антиспам
static uint32_t last_press_sent_at[BUTTON_CHANNEL_NUM];
// временные статусы
static bool temp_active[BUTTON_CHANNEL_NUM];
static IndicatorState temp_state[BUTTON_CHANNEL_NUM];
static uint32_t temp_until_ms[BUTTON_CHANNEL_NUM];
// базовые индикаторы (что задаёт сервер)
IndicatorState channel_indicator[BUTTON_CHANNEL_NUM] = { IND_DISABLED, IND_DISABLED, IND_DISABLED, IND_DISABLED };
static const uint32_t IndicatorPosition[BUTTON_CHANNEL_NUM] = {0, 2, 3, 1};
static uint32_t now_ms() { return millis(); }
static uint32_t color_rgb(uint8_t r, uint8_t g, uint8_t b) {
return pixels.Color(r, g, b);
}
static bool is_global_setup() {
return (deviceMode == DEVICE_MODE_SETUP);
}
static bool is_global_nowifi() {
if (deviceMode == DEVICE_MODE_SETUP) return false;
return (WiFi.status() != WL_CONNECTED);
}
void set_channel_indicator(uint8_t ch, IndicatorState st) {
if (ch >= BUTTON_CHANNEL_NUM) return;
channel_indicator[ch] = st;
}
IndicatorState get_channel_indicator(uint8_t ch) {
if (ch >= BUTTON_CHANNEL_NUM) return IND_DISABLED;
return channel_indicator[ch];
}
void set_channel_waiting(uint8_t ch) {
if (ch >= BUTTON_CHANNEL_NUM) return;
temp_active[ch] = true;
temp_state[ch] = IND_WAITING;
temp_until_ms[ch] = now_ms() + WAIT_TIMEOUT_MS;
}
void set_channel_warning_temp(uint8_t ch, uint32_t ms) {
if (ch >= BUTTON_CHANNEL_NUM) return;
temp_active[ch] = true;
temp_state[ch] = IND_WARNING;
temp_until_ms[ch] = now_ms() + ms;
}
void clear_channel_temp(uint8_t ch) {
if (ch >= BUTTON_CHANNEL_NUM) return;
temp_active[ch] = false;
}
static IndicatorState get_effective_indicator(uint8_t ch) {
if (ch >= BUTTON_CHANNEL_NUM) return IND_DISABLED;
if (temp_active[ch]) {
if ((int32_t)(temp_until_ms[ch] - now_ms()) <= 0) {
temp_active[ch] = false;
} else {
return temp_state[ch];
}
}
return channel_indicator[ch];
}
static void render_pixels() {
uint32_t t = now_ms();
// глобальные режимы перекрывают канальные
if (is_global_setup()) {
bool on = ((t / GLOBAL_BLINK_MS) % 2) == 0;
uint32_t c = on ? color_rgb(255, 255, 255) : color_rgb(0, 0, 0);
for (uint8_t i = 0; i < BUTTON_CHANNEL_NUM; i++) pixels.setPixelColor(i, c);
pixels.show();
return;
}
if (is_global_nowifi()) {
bool on = ((t / GLOBAL_BLINK_MS) % 2) == 0;
uint32_t c = on ? color_rgb(0, 0, 255) : color_rgb(0, 0, 0);
for (uint8_t i = 0; i < BUTTON_CHANNEL_NUM; i++) pixels.setPixelColor(i, c);
pixels.show();
return;
}
// обычный режим: по каналам
for (uint8_t ch = 0; ch < BUTTON_CHANNEL_NUM; ch++) {
IndicatorState st = get_effective_indicator(IndicatorPosition[ch]);
uint32_t c = 0;
switch (st) {
case IND_ENABLED:
c = color_rgb(0, 255, 0);
break;
case IND_DISABLED:
c = color_rgb(255, 255, 255);
break;
case IND_MUTE:
c = color_rgb(0, 0, 0);
break;
case IND_WAITING:
c = color_rgb(255, 255, 0);
break;
case IND_WARNING: {
bool on = ((t / WARNING_BLINK_MS) % 2) == 0;
c = on ? color_rgb(255, 120, 0) : color_rgb(0, 0, 0);
break;
}
case IND_ERROR: {
bool on = ((t / ERROR_BLINK_MS) % 2) == 0;
c = on ? color_rgb(255, 0, 0) : color_rgb(0, 0, 0);
break;
}
}
pixels.setPixelColor(ch, c);
}
pixels.show();
}
static bool antispam_allow_press(uint8_t ch) {
if (ch >= BUTTON_CHANNEL_NUM) return false;
uint32_t t = now_ms();
if ((uint32_t)(t - last_press_sent_at[ch]) < PRESS_ANTISPAM_MS) return false;
last_press_sent_at[ch] = t;
return true;
}
void send_press_event(uint8_t ch) {
if (ch >= BUTTON_CHANNEL_NUM) return;
// если канал в mute — игнорируем
if (get_channel_indicator(ch) == IND_MUTE) return;
// антиспам
if (!antispam_allow_press(ch)) return;
set_channel_waiting(ch);
int http_code = -1;
String body = "{";
body += "\"event_name\":\"press\",";
body += "\"data\": {";
body += "\"channel\":" + String(ch) + "},";
body += "\"device_id\":\"" + getUniqueID() + "\"";
body += "}";
bool ok = core_post_json_to_server(EVENT_PATH, body, WAIT_TIMEOUT_MS, http_code);
if (!ok) {
set_channel_warning_temp(ch, 2000);
} else {
clear_channel_temp(ch);
}
}
void button_logic_setup() {
// сенсорные модули: INPUT
for (uint8_t ch = 0; ch < BUTTON_CHANNEL_NUM; ch++) {
pinMode(BUTTON_PINS[ch], INPUT_PULLUP);
digitalWrite(BUTTON_PINS[ch], HIGH);
// pinMode(BUTTON_PINS[ch], INPUT);
uint8_t r = digitalRead(BUTTON_PINS[ch]) ? 1 : 0;
last_raw[ch] = r;
stable_state[ch] = r;
last_change_at[ch] = now_ms();
temp_active[ch] = false;
temp_state[ch] = IND_DISABLED;
temp_until_ms[ch] = 0;
last_press_sent_at[ch] = 0;
}
pixels.begin();
pixels.setBrightness(30);
pixels.clear();
pixels.show();
}
void button_logic_loop() {
uint32_t t = now_ms();
for (uint8_t ch = 0; ch < BUTTON_CHANNEL_NUM; ch++) {
uint8_t r = digitalRead(BUTTON_PINS[ch]) ? 1 : 0;
if (r != last_raw[ch]) {
last_raw[ch] = r;
last_change_at[ch] = t;
continue;
}
if ((t - last_change_at[ch]) < DEBOUNCE_MS) continue;
if (stable_state[ch] != r) {
uint8_t prev = stable_state[ch];
stable_state[ch] = r;
if (TOUCH_ACTIVE_HIGH) {
if (prev == 0 && r == 1) send_press_event(ch);
} else {
if (prev == 1 && r == 0) send_press_event(ch);
}
}
}
render_pixels();
delay(0);
}