#include "ButtonLogic.h"
#include <sh_core.h>
// Сенсорные модули обычно дают HIGH при касании.
// Если у тебя наоборот — поставь false.
static const bool TOUCH_ACTIVE_HIGH = true;
// Антиспам
static const uint32_t PRESS_ANTISPAM_MS = 20;
// Debounce
static const uint32_t DEBOUNCE_MS = 30;
// Тайминги индикаций
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";
// NeoPixel создаём в setup (чтобы кол-во LED было CHANNEL_NUM)
static Adafruit_NeoPixel *pixels = nullptr;
// debounce/state
static uint8_t last_raw[BUTTON_MAX_CHANNELS];
static uint8_t stable_state[BUTTON_MAX_CHANNELS];
static uint32_t last_change_at[BUTTON_MAX_CHANNELS];
// антиспам
static uint32_t last_press_sent_at[BUTTON_MAX_CHANNELS];
// временные статусы
static bool temp_active[BUTTON_MAX_CHANNELS];
static IndicatorState temp_state[BUTTON_MAX_CHANNELS];
static uint32_t temp_until_ms[BUTTON_MAX_CHANNELS];
// базовые индикаторы (что задаёт сервер)
IndicatorState channel_indicator[BUTTON_MAX_CHANNELS] = {
IND_DISABLED, IND_DISABLED, IND_DISABLED, IND_DISABLED,
IND_DISABLED, IND_DISABLED, IND_DISABLED, IND_DISABLED
};
static uint32_t now_ms() { return millis(); }
static inline uint8_t chan_count() {
uint8_t n = CHANNEL_NUM;
if (n > BUTTON_MAX_CHANNELS) n = BUTTON_MAX_CHANNELS;
if (n < 1) n = 1;
return n;
}
static uint32_t color_rgb(uint8_t r, uint8_t g, uint8_t b) {
if (!pixels) return 0;
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 >= chan_count()) return;
channel_indicator[ch] = st;
}
IndicatorState get_channel_indicator(uint8_t ch) {
if (ch >= chan_count()) return IND_DISABLED;
return channel_indicator[ch];
}
void set_channel_waiting(uint8_t ch) {
if (ch >= chan_count()) 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 >= chan_count()) 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 >= chan_count()) return;
temp_active[ch] = false;
}
static IndicatorState get_effective_indicator(uint8_t ch) {
if (ch >= chan_count()) 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() {
if (!pixels) return;
uint32_t t = now_ms();
uint8_t n = chan_count();
// глобальные режимы перекрывают канальные
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 < n; 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 < n; i++) pixels->setPixelColor(i, c);
pixels->show();
return;
}
// обычный режим: по каналам
for (uint8_t ch = 0; ch < n; ch++) {
uint8_t pos = sh_channel_indicator(ch);
if (pos >= n) pos = ch;
IndicatorState st = get_effective_indicator(pos);
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 >= chan_count()) 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 >= chan_count()) return;
// если канал в mute — игнорируем
if (get_channel_indicator(ch) == IND_MUTE) return;
// антиспам
if (!antispam_allow_press(ch)) return;
set_channel_waiting(ch);
render_pixels();
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() {
uint8_t n = chan_count();
// кнопки
for (uint8_t ch = 0; ch < n; ch++) {
uint8_t pin = sh_channel_pin(ch);
if (pin == SH_PIN_UNUSED) continue;
// сенсорные модули: INPUT_PULLUP (как у тебя было)
pinMode(pin, INPUT_PULLUP);
digitalWrite(pin, HIGH);
uint8_t r = digitalRead(pin) ? 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;
}
// лента
if (!pixels) {
pixels = new Adafruit_NeoPixel(n, SIGNAL_LED, NEO_GRB + NEO_KHZ800);
}
pixels->begin();
pixels->setBrightness(30);
pixels->clear();
pixels->show();
}
void button_logic_loop() {
uint32_t t = now_ms();
uint8_t n = chan_count();
for (uint8_t ch = 0; ch < n; ch++) {
uint8_t pin = sh_channel_pin(ch);
if (pin == SH_PIN_UNUSED) continue;
uint8_t r = digitalRead(pin) ? 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);
}