atelier · pagină internă · pentru tata
Tot codul firmware
Toate sketch-urile Arduino pentru VIA, gata de copy-paste în Arduino IDE.
Layout LED-uri: [0]=roșu pieton · [1]=verde pieton · [2]=verde maș · [3]=galben · [4]=roșu maș.
Cod final — gata de flashat
Selectează nod (N1/N2/N3) sau mașinuță (M1/M2/M3), apasă Copy, lipești în Arduino IDE și flashezi. Board pentru N1/N2/N3: ESP32 Dev Module. Pentru M1/M2/M3: ESP32C3 Dev Module.
// VIA — Nod N1 (firmware v2 — ciclu 4 stari + adaptive + ambulanta)
// LED + OLED mare + buton + microfon I2S + ESP-NOW mesh + sniff tag-uri WiFi (cu RSSI)
// Board: ESP32 Dev Module
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <driver/i2s.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#define NODE_ID "N1"
const int PIN_LED = 5;
const int NUM_LEDS = 5;
const int PIN_BUTON = 4;
#define I2S_PORT I2S_NUM_0
#define SCK_PIN 14
#define WS_PIN 15
#define SD_PIN 32
#define SAMPLE_RATE 16000
#define BUFFER_SIZE 512
#define PRAG_AMP 200 // gate scăzut — sirena la distanță ajunge la 300-500 amp
#define PRAG_TON 5.0f // sirena tonal-curată la distanță — vorbire <1, palme <2
#define PRAG_AMP_HUGE 3000 // override pentru sirena APROAPE și tare (orice tonalitate)
#define N_CONSEC 8 // 8 frame-uri (~256ms sustained) — RAMÂNE strict ca să filtreze palme
#define LONG_PRESS_MS 1500 // long-press >=1.5s pe buton = AMBULANTA manual; <1.5s = pieton
#define CASCADA_OFFSET_MS 400 // offset val intre noduri (N1=0, N2=400, N3=800ms)
#define WIFI_CANAL 1 // ESP-NOW + sniff pe ACELAȘI canal
#define RSSI_APROAPE -65 // dBm: peste asta, tag e "la mine" (mai permisiv decât -55)
#define TIMEOUT_TAG 4000 // ms
// Goertzel: target 750Hz si 600Hz (sirena europeana two-tone).
// k = N * f_target / fs fs=16000, N=512 → k=24 → 750Hz exact; k=19 → 593.75Hz
const int K_HI = 24;
const int K_LO = 19;
const float COEFF_HI = 2.0f * cosf(2.0f * 3.14159265f * K_HI / (float)BUFFER_SIZE);
const float COEFF_LO = 2.0f * cosf(2.0f * 3.14159265f * K_LO / (float)BUFFER_SIZE);
uint8_t BROADCAST_MAC[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
typedef struct {
char node_id[4];
uint8_t tip; // 0=ALIVE, 1=SIRENA, 2=CORIDOR_REQ (green wave masini)
uint32_t scor;
int8_t rssi_tags[3]; // RSSI per M1/M2/M3 (asa cum vede expeditorul); -127 = nevazut
uint8_t stare; // 0-3 (VERDE_MASINI, GALBEN_SPRE_R, ROSU_MASINI, GALBEN_SPRE_V)
uint16_t ramas_ms; // câte ms până la următoarea tranziție (clip la 65535)
uint8_t telefoane; // T = telefoane detectate
uint8_t pietoni; // P = pietoni count
uint8_t flags; // bit0=ambulanta_activa, bit1=wave_activ
} mesaj_t;
typedef struct {
int8_t rssi;
unsigned long ultim_vazut;
} tag_t;
typedef struct {
uint8_t mac[6];
char node_id[4];
unsigned long ultim_vazut;
int8_t rssi_tags[3]; // ultimul raport de RSSI per tag de la acest vecin
uint8_t stare;
uint16_t ramas_ms;
uint8_t telefoane;
uint8_t pietoni;
uint8_t flags;
} vecin_t;
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
// Layout LED fizic: [0]=roșu pieton, [1]=verde pieton, [2]=verde maș, [3]=galben, [4]=roșu maș
// State machine: 4 stari (galben INAINTE si DUPA verde mașini)
const int VERDE_MASINI = 0; // V maș + R pieton
const int GALBEN_SPRE_R = 1; // Galben + R pieton (transition V→R)
const int ROSU_MASINI = 2; // R maș + V pieton
const int GALBEN_SPRE_V = 3; // Galben + R pieton (transition R→V)
const int NUM_STARI = 4;
// NUME = culoarea PIETONULUI/UTILIZATORULUI dominant pe stare. SUBT = pe cine.
// Fix de la versiunea precedenta (era "ROSU/PIETONI" — incorrect, pietonii au VERDE cand masinile au ROSU).
const char* NUME[NUM_STARI] = { "VERDE", "GALBEN", "VERDE", "GALBEN" };
const char* SUBT[NUM_STARI] = { "MASINI", "STOP", "PIETONI", "PORNESC" };
int stare = VERDE_MASINI;
unsigned long timp_intrat = 0;
unsigned long durata_stare = 5000; // setata la fiecare intrare in stare
int pietoni = 0;
unsigned long ultima_apasare = 0;
const unsigned long ANTI_BOUNCE = 1500;
unsigned long apasari_recente[10] = {0}; // timestamp ultimele 10 apasari
int32_t buffer_i2s[BUFFER_SIZE];
bool sirena_locala = false;
bool sirena_de_la_vecin = false;
unsigned long timp_sirena = 0;
const unsigned long DURATA_SIRENA = 10000; // ambulanță 10s (era 6s)
int frames_tonal_seq = 0; // contor frame-uri consecutive cu tonal=true
// Cascadă val: programez intrarea în ambulanța la timpul X
unsigned long intra_ambulanta_la = 0;
char nod_sirena[4] = "?"; // cine a detectat (afișat pe OLED)
// Green wave: când local M>=2 mașinuțe → trimit CORIDOR_REQ → toate trec pe VERDE
unsigned long wave_pana_la = 0;
char nod_wave[4] = "?";
unsigned long ultim_wave_trimis = 0;
#define WAVE_DURATA 8000
#define WAVE_COOLDOWN 5000
// Direction tracking per tag (origine→destinație cu histeresis)
typedef struct {
char origine[4]; // unde a fost INAINTE (gol = necunoscut)
char destinatie[4]; // unde e ACUM (gol = nedetectat)
char candidat[4]; // candidat pentru schimbare (gol = niciun candidat)
unsigned long candidat_de_la;
unsigned long ultim_aproape;
} tag_dir_t;
tag_dir_t tag_dir[3] = {0};
#define RSSI_LA_INTERSECTIE -55 // peste asta consider tag-ul ca fiind la nodul respectiv
#define SUSTAINED_MS 2000 // 2s la rang ca să schimbe destinația
#define UITARE_DIR_MS 8000 // dacă tag-ul nu a fost aproape de nimeni 8s, șterg directie
// Telefoane detectate prin probe requests (alte device-uri WiFi în zonă)
#define MAX_TELEFOANE 16
#define TIMEOUT_TELEFON 10000 // 10s fără probe → uitat
typedef struct {
uint8_t mac[6];
unsigned long ultim_vazut;
int8_t rssi;
} telefon_t;
volatile telefon_t telefoane[MAX_TELEFOANE] = {0};
volatile int telefoane_n_global = 0;
// Long-press buton (pentru AMBULANȚĂ manual)
bool buton_apasat = false;
unsigned long buton_apasat_la = 0;
bool long_press_consumat = false;
volatile tag_t taguri[3] = { {0, 0}, {0, 0}, {0, 0} };
// Vecini ESP-NOW — array cu MAC complet, max 8 vecini
vecin_t vecini_arr[8] = {0};
const unsigned long TIMP_VECIN_VECHI = 10000; // 10s (era 6s — prea agresiv)
unsigned long ultim_broadcast = 0;
const unsigned long INTERVAL_ALIVE = 1000; // 1s (era 2s — prea rar)
unsigned long ultim_print = 0;
const unsigned long INTERVAL_PRINT = 500;
// Helper: cauta/adauga vecin in array
int idx_vecin(const uint8_t* mac) {
for (int i = 0; i < 8; i++) {
if (vecini_arr[i].ultim_vazut > 0 && memcmp(vecini_arr[i].mac, mac, 6) == 0) return i;
}
// slot liber sau cel mai vechi
int idx_liber = -1;
unsigned long cel_mai_vechi = 0xFFFFFFFF;
int idx_inlocuit = 0;
for (int i = 0; i < 8; i++) {
if (vecini_arr[i].ultim_vazut == 0) { idx_liber = i; break; }
if (vecini_arr[i].ultim_vazut < cel_mai_vechi) {
cel_mai_vechi = vecini_arr[i].ultim_vazut;
idx_inlocuit = i;
}
}
return (idx_liber >= 0) ? idx_liber : idx_inlocuit;
}
void on_recv(const esp_now_recv_info* info, const uint8_t* data, int len) {
if (len != sizeof(mesaj_t)) return;
mesaj_t* m = (mesaj_t*)data;
int idx = idx_vecin(info->src_addr);
memcpy(vecini_arr[idx].mac, info->src_addr, 6);
memcpy(vecini_arr[idx].node_id, m->node_id, 4);
vecini_arr[idx].ultim_vazut = millis();
// Salvez RSSI per tag raportat de acest vecin (pentru triangulare)
for (int t = 0; t < 3; t++) vecini_arr[idx].rssi_tags[t] = m->rssi_tags[t];
// Salvez extended state info (pentru retransmit Serial fără 3 USB)
vecini_arr[idx].stare = m->stare;
vecini_arr[idx].ramas_ms = m->ramas_ms;
vecini_arr[idx].telefoane = m->telefoane;
vecini_arr[idx].pietoni = m->pietoni;
vecini_arr[idx].flags = m->flags;
if (m->tip == 1) {
// Cascadă val: nodul transmitter intră primul, eu cu offset bazat pe distanță ID
int my_num = NODE_ID[1] - '0'; // N1→1, N2→2, N3→3
int sender_num = m->node_id[1] - '0';
int diff = abs(my_num - sender_num);
intra_ambulanta_la = millis() + (unsigned long)diff * CASCADA_OFFSET_MS;
memcpy(nod_sirena, m->node_id, 4);
nod_sirena[3] = 0;
Serial.printf("[ESP-NOW] SIRENA de la %s, intru val in %dms\n", m->node_id, (int)(diff * CASCADA_OFFSET_MS));
} else if (m->tip == 2) {
// CORIDOR_REQ: green wave declanșat de aglomerație mașinuțe la alt nod
wave_pana_la = millis() + WAVE_DURATA;
memcpy(nod_wave, m->node_id, 4);
nod_wave[3] = 0;
// Forțez countdown corect (la fel ca trigger local — fixat în loop secțiunea 5c)
Serial.printf("[WAVE] Coridor verde primit de la %s (durata %dms)\n", m->node_id, (int)WAVE_DURATA);
}
}
void IRAM_ATTR sniff_cb(void* buf, wifi_promiscuous_pkt_type_t type) {
if (type != WIFI_PKT_MGMT) return;
wifi_promiscuous_pkt_t* pkt = (wifi_promiscuous_pkt_t*)buf;
const uint8_t* payload = pkt->payload;
if ((payload[0] & 0xFC) != 0x40) return; // nu-i probe request
const uint8_t* src = payload + 10;
// 1. Tag-uri mașinuțe (OUI C0:49:EF:00:00:0X)
if (src[0] == 0xC0 && src[1] == 0x49 && src[2] == 0xEF
&& src[3] == 0x00 && src[4] == 0x00) {
uint8_t last = src[5];
if (last >= 1 && last <= 3) {
uint8_t idx = last - 1;
taguri[idx].rssi = pkt->rx_ctrl.rssi;
taguri[idx].ultim_vazut = millis();
}
return;
}
// 2. Alt probe request = telefon real / device WiFi în zonă
// Cache circular: caut MAC existent, sau slot liber, sau înlocuiesc cel mai vechi
unsigned long acum = millis();
int slot_existent = -1;
int slot_liber = -1;
int slot_vechi = 0;
unsigned long cel_mai_vechi = telefoane[0].ultim_vazut;
for (int i = 0; i < MAX_TELEFOANE; i++) {
if (telefoane[i].ultim_vazut > 0 && memcmp((const void*)telefoane[i].mac, src, 6) == 0) {
slot_existent = i; break;
}
if (telefoane[i].ultim_vazut == 0 && slot_liber < 0) slot_liber = i;
if (telefoane[i].ultim_vazut > 0 && telefoane[i].ultim_vazut < cel_mai_vechi) {
cel_mai_vechi = telefoane[i].ultim_vazut;
slot_vechi = i;
}
}
int slot = (slot_existent >= 0) ? slot_existent : (slot_liber >= 0 ? slot_liber : slot_vechi);
for (int b = 0; b < 6; b++) telefoane[slot].mac[b] = src[b];
telefoane[slot].ultim_vazut = acum;
telefoane[slot].rssi = pkt->rx_ctrl.rssi;
}
void setup_i2s() {
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = 0,
.dma_buf_count = 4,
.dma_buf_len = 512,
.use_apll = false,
};
i2s_pin_config_t pins = {
.mck_io_num = I2S_PIN_NO_CHANGE,
.bck_io_num = SCK_PIN,
.ws_io_num = WS_PIN,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = SD_PIN
};
i2s_driver_install(I2S_PORT, &cfg, 0, NULL);
i2s_set_pin(I2S_PORT, &pins);
}
// Cate masini aproape (RSSI > prag, văzute recent)
int conteaza_masini_aproape(unsigned long acum) {
int n = 0;
for (int i = 0; i < 3; i++) {
if (taguri[i].ultim_vazut > 0 && acum - taguri[i].ultim_vazut < TIMEOUT_TAG
&& taguri[i].rssi > RSSI_APROAPE) n++;
}
return n;
}
// Cate apasari pieton in ultimele 30s
int conteaza_pietoni_recenti(unsigned long acum) {
int n = 0;
for (int i = 0; i < 10; i++) {
if (apasari_recente[i] > 0 && acum - apasari_recente[i] < 30000) n++;
}
return n;
}
// Update direction tracking pentru un tag — origine→destinație cu histeresis
void update_directie(int t, unsigned long acum, int8_t rssi_glob, const char* best_node) {
// Reset complet dacă tag-ul a dispărut de mult
if (acum - tag_dir[t].ultim_aproape > UITARE_DIR_MS) {
tag_dir[t].origine[0] = 0;
tag_dir[t].destinatie[0] = 0;
tag_dir[t].candidat[0] = 0;
}
// Daca rssi prea slab, nu fac update activ (păstrez ultima destinație ca „inertia")
if (rssi_glob < RSSI_LA_INTERSECTIE) return;
// RSSI suficient → tag-ul e aproape de cineva, marchez timestamp
tag_dir[t].ultim_aproape = acum;
// Cazul: nicio destinație înca → o setez direct (fără histeresis pentru prima detection)
if (tag_dir[t].destinatie[0] == 0) {
strncpy(tag_dir[t].destinatie, best_node, 4);
tag_dir[t].destinatie[3] = 0;
tag_dir[t].candidat[0] = 0;
return;
}
// best_node == destinație curentă → nimic de schimbat, reset candidat
if (strncmp(tag_dir[t].destinatie, best_node, 4) == 0) {
tag_dir[t].candidat[0] = 0;
return;
}
// best_node diferit de destinație — verifică/start candidat
if (strncmp(tag_dir[t].candidat, best_node, 4) != 0) {
strncpy(tag_dir[t].candidat, best_node, 4);
tag_dir[t].candidat[3] = 0;
tag_dir[t].candidat_de_la = acum;
return;
}
// Candidatul s-a stabilizat 2s+ → promovează
if (acum - tag_dir[t].candidat_de_la >= SUSTAINED_MS) {
strncpy(tag_dir[t].origine, tag_dir[t].destinatie, 4);
tag_dir[t].origine[3] = 0;
strncpy(tag_dir[t].destinatie, best_node, 4);
tag_dir[t].destinatie[3] = 0;
tag_dir[t].candidat[0] = 0;
Serial.printf("[DIR] M%d: %.4s → %.4s\n", t + 1, tag_dir[t].origine, tag_dir[t].destinatie);
}
}
// Decide durata la INTRAREA in stare (adaptive, lent demonstrativ)
unsigned long durata_pentru(int s, unsigned long acum) {
if (s == VERDE_MASINI) {
int m = conteaza_masini_aproape(acum);
return (m >= 3) ? 12000 : 8000; // trafic mare (3+ mașini) → 12s, normal 8s
}
if (s == ROSU_MASINI) {
int p = conteaza_pietoni_recenti(acum);
// Pietoni virtuali (telefoane în zonă) cresc nevoia
int t = telefoane_n_global;
if (p >= 3 || t >= 5) return 9000; // multi pietoni reali SAU multe telefoane → 9s
if (t >= 2) return 7000; // câteva telefoane → 7s
return 6000; // normal
}
return 2500; // galben (ambele tranziții) → 2.5s
}
// Centreaza text pe OLED la y dat (size 1/2/3)
void print_centrat(const char* s, int y, int size) {
oled.setTextSize(size);
int16_t x1, y1; uint16_t w, h;
oled.getTextBounds(s, 0, 0, &x1, &y1, &w, &h);
int x = (128 - w) / 2;
oled.setCursor(x < 0 ? 0 : x, y);
oled.print(s);
}
void deseneaza_oled(unsigned long acum, bool sirena, bool wave_activ, int masini, int idx_aproape,
int8_t rssi_aproape, int vecini_n, int telefoane_n,
char dir_per_tag[3][4], int8_t rssi_global_tag[3]) {
oled.clearDisplay();
if (sirena) {
// Mod CORIDOR VERDE — afișeaza traseul + ambulanță animată
oled.setTextColor(SSD1306_WHITE);
// Header cu sursa
char src[16];
snprintf(src, sizeof(src), "AMBULANTA %s", nod_sirena);
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.print(src);
oled.drawFastHLine(0, 10, 128, SSD1306_WHITE);
// Text mare CORIDOR / VERDE
print_centrat("CORIDOR", 14, 2);
print_centrat("VERDE", 32, 2);
// Săgeată mare jos, animată spre dreapta (spre spital)
int sage_offset = (int)((acum / 80) % 16); // 0..15 cicluri 80ms
int ay = 54;
// 3 chevron-uri „» » »" care se mută la dreapta
for (int i = 0; i < 4; i++) {
int cx = 16 + i * 28 + sage_offset;
if (cx > 128) cx -= 96;
oled.drawLine(cx - 5, ay - 4, cx, ay, SSD1306_WHITE);
oled.drawLine(cx, ay, cx - 5, ay + 4, SSD1306_WHITE);
oled.drawLine(cx - 4, ay - 4, cx + 1, ay, SSD1306_WHITE);
oled.drawLine(cx + 1, ay, cx - 4, ay + 4, SSD1306_WHITE);
}
oled.display();
return;
}
// Header sus, mic (size 1) — include telefoane (T) ca pietoni virtuali + marker WAVE
char hdr[32];
if (wave_activ) {
snprintf(hdr, sizeof(hdr), "%s WAVE-%.2s M%d T%d", NODE_ID, nod_wave, masini, telefoane_n);
} else {
snprintf(hdr, sizeof(hdr), "%s M%d V%d T%d P%d", NODE_ID, masini, vecini_n, telefoane_n, pietoni);
}
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.print(hdr);
oled.drawFastHLine(0, 10, 128, wave_activ ? SSD1306_WHITE : SSD1306_WHITE);
if (wave_activ) {
// Marker subțire deasupra liniei pentru a evidenția stare specială
oled.drawFastHLine(0, 11, 128, SSD1306_WHITE);
}
// Stare mare (size 2) centrat
print_centrat(NUME[stare], 14, 2);
print_centrat(SUBT[stare], 32, 1);
// Countdown jos dreapta — secunde mari (size 2)
unsigned long ramas = (durata_stare > acum - timp_intrat) ? (durata_stare - (acum - timp_intrat)) : 0;
unsigned long sec = (ramas + 999) / 1000;
char buf[8];
snprintf(buf, sizeof(buf), "%lus", sec);
oled.setTextSize(2);
int16_t x1, y1; uint16_t w, h;
oled.getTextBounds(buf, 0, 0, &x1, &y1, &w, &h);
oled.setCursor(128 - w - 2, 44);
oled.print(buf);
// Progress bar countdown jos (umple invers — scade pe măsură ce timpul trece)
if (durata_stare > 0) {
int progBarY = 62;
int progLen = (int)((128 * ramas) / durata_stare);
if (progLen < 0) progLen = 0;
if (progLen > 128) progLen = 128;
oled.drawFastHLine(0, progBarY, 128, SSD1306_WHITE);
oled.drawFastHLine(0, progBarY + 1, progLen, SSD1306_WHITE);
}
// Direction tracking jos stânga: "M1:N1>N2 M2@N2" (origine→destinație cu histeresis)
oled.setTextSize(1);
oled.setCursor(0, 50);
int print_count = 0;
for (int t = 0; t < 3 && print_count < 2; t++) {
if (tag_dir[t].destinatie[0]) {
oled.print("M"); oled.print(t + 1);
// Doar 2 chars din NODE_ID ("N1", "N2", "N3")
char destbuf[3] = { tag_dir[t].destinatie[0], tag_dir[t].destinatie[1], 0 };
if (tag_dir[t].origine[0]) {
oled.print(":");
char origbuf[3] = { tag_dir[t].origine[0], tag_dir[t].origine[1], 0 };
oled.print(origbuf);
oled.print(">");
oled.print(destbuf);
} else {
oled.print("@");
oled.print(destbuf);
}
oled.print(" ");
print_count++;
}
}
oled.display();
}
void deseneaza_led(int s, unsigned long acum, bool sirena) {
leduri.clear();
if (sirena) {
// CORIDOR VERDE — verde mașini steady (drum liber pentru ambulanță) + LED4 roșu pulsing
// ca să distinge de ciclul normal (LED roșu mașini clipește alert)
leduri.setPixelColor(2, leduri.Color(0, 100, 0)); // verde mașini, intens
bool blink = ((acum / 200) % 2) == 0;
if (blink) leduri.setPixelColor(4, leduri.Color(120, 0, 0)); // alert roșu pulsing
// Pieton rămâne roșu (nu treci când e ambulanță)
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
} else if (s == VERDE_MASINI) {
leduri.setPixelColor(2, leduri.Color(0, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
} else if (s == GALBEN_SPRE_R || s == GALBEN_SPRE_V) {
leduri.setPixelColor(3, leduri.Color(120, 90, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
} else if (s == ROSU_MASINI) {
leduri.setPixelColor(4, leduri.Color(80, 0, 0));
leduri.setPixelColor(1, leduri.Color(0, 80, 0));
}
leduri.show();
}
void setup() {
Serial.begin(115200);
delay(200);
Serial.println();
Serial.print("=== VIA Nod ");
Serial.print(NODE_ID);
Serial.println(" v2 booting ===");
leduri.begin();
pinMode(PIN_BUTON, INPUT_PULLUP);
if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("EROARE: OLED nu raspunde la I2C 0x3C");
while (true) { delay(1000); }
}
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1);
oled.setCursor(20, 24);
oled.print("Pornesc ");
oled.print(NODE_ID);
oled.display();
Serial.println("OK: OLED");
setup_i2s();
Serial.println("OK: microfon I2S");
WiFi.mode(WIFI_STA);
esp_wifi_set_channel(WIFI_CANAL, WIFI_SECOND_CHAN_NONE);
if (esp_now_init() != ESP_OK) {
Serial.println("EROARE: ESP-NOW init"); while (true) delay(1000);
}
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, BROADCAST_MAC, 6);
peer.channel = WIFI_CANAL;
peer.encrypt = false;
esp_now_add_peer(&peer);
esp_now_register_recv_cb(on_recv);
Serial.print("OK: ESP-NOW pe canal ");
Serial.println(WIFI_CANAL);
// MAC propriu (util la debug)
uint8_t my_mac[6];
esp_wifi_get_mac(WIFI_IF_STA, my_mac);
Serial.printf("MAC nod: %02X:%02X:%02X:%02X:%02X:%02X\n",
my_mac[0], my_mac[1], my_mac[2], my_mac[3], my_mac[4], my_mac[5]);
esp_wifi_set_promiscuous_rx_cb(&sniff_cb);
wifi_promiscuous_filter_t filt = { .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT };
esp_wifi_set_promiscuous_filter(&filt);
esp_wifi_set_promiscuous(true);
Serial.println("OK: sniff probe requests activ (OUI C0:49:EF:00:00:XX)");
timp_intrat = millis();
durata_stare = durata_pentru(stare, timp_intrat);
}
void loop() {
unsigned long acum = millis();
// 1. Microfon — citește frame
size_t citit = 0;
i2s_read(I2S_PORT, buffer_i2s, sizeof(buffer_i2s), &citit, 0);
int32_t maxim = -2147483648, minim = 2147483647;
for (int i = 0; i < BUFFER_SIZE; i++) {
if (buffer_i2s[i] > maxim) maxim = buffer_i2s[i];
if (buffer_i2s[i] < minim) minim = buffer_i2s[i];
}
int32_t amplitudine = (maxim - minim) >> 16;
// 1b. Goertzel pentru 750Hz si 600Hz + energie totala
// Calculam doar daca amplitudine > prag minim (skip processing pe liniste)
float ratio = 0.0f;
float g_hi = 0.0f, g_lo = 0.0f, e_total = 0.0f;
if (amplitudine > PRAG_AMP) {
float q1_hi = 0, q2_hi = 0, q1_lo = 0, q2_lo = 0;
for (int i = 0; i < BUFFER_SIZE; i++) {
float s = (float)(buffer_i2s[i] >> 16); // scale la int16 range
e_total += s * s;
float q0_hi = COEFF_HI * q1_hi - q2_hi + s;
q2_hi = q1_hi; q1_hi = q0_hi;
float q0_lo = COEFF_LO * q1_lo - q2_lo + s;
q2_lo = q1_lo; q1_lo = q0_lo;
}
g_hi = q1_hi*q1_hi + q2_hi*q2_hi - q1_hi*q2_hi*COEFF_HI;
g_lo = q1_lo*q1_lo + q2_lo*q2_lo - q1_lo*q2_lo*COEFF_LO;
if (e_total > 1.0f) ratio = (g_hi + g_lo) / e_total;
}
// 2. Detect sirena: SAU (amp + ratio tonal) SAU (amp foarte mare orice tonalitate)
bool tonal = (amplitudine > PRAG_AMP) && (ratio > PRAG_TON);
bool huge = amplitudine > PRAG_AMP_HUGE;
if (tonal || huge) frames_tonal_seq++; else frames_tonal_seq = 0;
if (frames_tonal_seq >= N_CONSEC && !sirena_locala) {
sirena_locala = true;
timp_sirena = acum;
timp_intrat = acum; // reset countdown — afișează cât a rămas din DURATA_SIRENA
stare = VERDE_MASINI;
durata_stare = DURATA_SIRENA;
strncpy(nod_sirena, NODE_ID, 4); nod_sirena[3] = 0;
mesaj_t m;
strncpy(m.node_id, NODE_ID, 4);
m.tip = 1;
m.scor = (uint32_t)(ratio * 1000);
for (int t = 0; t < 3; t++) {
m.rssi_tags[t] = (taguri[t].ultim_vazut > 0 && acum - taguri[t].ultim_vazut < TIMEOUT_TAG)
? taguri[t].rssi : -127;
}
for (int i = 0; i < 5; i++) {
esp_now_send(BROADCAST_MAC, (uint8_t*)&m, sizeof(m));
delay(20);
}
Serial.printf("[MIC] SIRENA TONALA local @ amp=%ld ratio=%.2f\n", (long)amplitudine, ratio);
}
// 2b. Cascadă val — vecinii intră cu offset
if (intra_ambulanta_la > 0 && acum >= intra_ambulanta_la && !sirena_de_la_vecin) {
sirena_de_la_vecin = true;
timp_sirena = acum;
timp_intrat = acum; // reset countdown pentru afișare corectă
stare = VERDE_MASINI;
durata_stare = DURATA_SIRENA;
Serial.printf("[VAL] intru in AMBULANTA (dupa offset)\n");
}
bool sirena = sirena_locala || sirena_de_la_vecin;
if (sirena && (acum - timp_sirena > DURATA_SIRENA)) {
sirena_locala = false;
sirena_de_la_vecin = false;
intra_ambulanta_la = 0;
nod_sirena[0] = '?'; nod_sirena[1] = 0;
timp_intrat = acum;
stare = VERDE_MASINI;
durata_stare = durata_pentru(stare, acum);
}
// 3. ALIVE broadcast cu rssi_tags + extended state (pentru retransmit pe N1 Serial)
if (acum - ultim_broadcast >= INTERVAL_ALIVE) {
mesaj_t m;
strncpy(m.node_id, NODE_ID, 4);
m.tip = 0;
m.scor = (uint32_t)pietoni;
for (int t = 0; t < 3; t++) {
m.rssi_tags[t] = (taguri[t].ultim_vazut > 0 && acum - taguri[t].ultim_vazut < TIMEOUT_TAG)
? taguri[t].rssi : -127;
}
m.stare = (uint8_t)stare;
unsigned long ramas_calc = (durata_stare > acum - timp_intrat) ? (durata_stare - (acum - timp_intrat)) : 0;
m.ramas_ms = (ramas_calc > 65535) ? 65535 : (uint16_t)ramas_calc;
m.telefoane = (telefoane_n_global > 255) ? 255 : (uint8_t)telefoane_n_global;
m.pietoni = (pietoni > 255) ? 255 : (uint8_t)pietoni;
m.flags = 0;
if (sirena_locala || sirena_de_la_vecin) m.flags |= 0x01;
if (wave_pana_la > acum) m.flags |= 0x02;
esp_now_send(BROADCAST_MAC, (uint8_t*)&m, sizeof(m));
delay(15);
esp_now_send(BROADCAST_MAC, (uint8_t*)&m, sizeof(m));
ultim_broadcast = acum;
}
// 4. Vecini activi
int vecini_n = 0;
for (int i = 0; i < 8; i++) {
if (vecini_arr[i].ultim_vazut > 0 && acum - vecini_arr[i].ultim_vazut < TIMP_VECIN_VECHI) {
vecini_n++;
}
}
// 4b. Telefoane (probe requests externe) — pietoni virtuali în zonă
int telefoane_n = 0;
for (int i = 0; i < MAX_TELEFOANE; i++) {
if (telefoane[i].ultim_vazut > 0 && acum - telefoane[i].ultim_vazut < TIMEOUT_TELEFON) {
telefoane_n++;
}
}
telefoane_n_global = telefoane_n; // util în durata_pentru()
// 5. Tag-uri vazute local + triangulare (global)
int masini = 0;
int cel_mai_aproape = -1;
int8_t cel_mai_bun_rssi = -127;
for (int i = 0; i < 3; i++) {
if (taguri[i].ultim_vazut > 0 && acum - taguri[i].ultim_vazut < TIMEOUT_TAG) {
masini++;
if (taguri[i].rssi > cel_mai_bun_rssi) {
cel_mai_bun_rssi = taguri[i].rssi;
cel_mai_aproape = i + 1;
}
}
}
// Triangulare: pentru fiecare tag, găsește nodul (local sau vecin) cu RSSI maxim
char dir_per_tag[3][4] = { {0}, {0}, {0} }; // node_id al celui mai aproape (instant)
int8_t rssi_global_tag[3] = { -127, -127, -127 };
for (int t = 0; t < 3; t++) {
if (taguri[t].ultim_vazut > 0 && acum - taguri[t].ultim_vazut < TIMEOUT_TAG) {
rssi_global_tag[t] = taguri[t].rssi;
strncpy(dir_per_tag[t], NODE_ID, 4);
}
for (int v = 0; v < 8; v++) {
if (vecini_arr[v].ultim_vazut > 0 && acum - vecini_arr[v].ultim_vazut < TIMP_VECIN_VECHI) {
if (vecini_arr[v].rssi_tags[t] != -127 && vecini_arr[v].rssi_tags[t] > rssi_global_tag[t]) {
rssi_global_tag[t] = vecini_arr[v].rssi_tags[t];
strncpy(dir_per_tag[t], vecini_arr[v].node_id, 4);
}
}
}
// Update direction tracking cu histeresis
update_directie(t, acum, rssi_global_tag[t], dir_per_tag[t]);
}
// 5b. Green wave: dacă local văd 2+ mașinuțe aproape, trimit CORIDOR_REQ
int m_aproape = conteaza_masini_aproape(acum);
if (m_aproape >= 3 && acum - ultim_wave_trimis > WAVE_COOLDOWN && !sirena) {
mesaj_t m;
strncpy(m.node_id, NODE_ID, 4);
m.tip = 2;
m.scor = (uint32_t)m_aproape;
for (int t = 0; t < 3; t++) {
m.rssi_tags[t] = (taguri[t].ultim_vazut > 0 && acum - taguri[t].ultim_vazut < TIMEOUT_TAG)
? taguri[t].rssi : -127;
}
for (int i = 0; i < 3; i++) {
esp_now_send(BROADCAST_MAC, (uint8_t*)&m, sizeof(m));
delay(15);
}
wave_pana_la = acum + WAVE_DURATA;
strncpy(nod_wave, NODE_ID, 4); nod_wave[3] = 0;
ultim_wave_trimis = acum;
// Forțez countdown afișat la WAVE_DURATA și state VERDE
stare = VERDE_MASINI;
timp_intrat = acum;
durata_stare = WAVE_DURATA;
Serial.printf("[WAVE] Trimit CORIDOR (%d masini aproape local)\n", m_aproape);
}
// 5c. Wave activ → forțez VERDE_MASINI (NU în sirena — ambulanța are prioritate)
bool wave_activ = (wave_pana_la > acum) && !sirena;
if (wave_activ) {
unsigned long wave_remaining = wave_pana_la - acum;
// Forțez doar dacă stare != VERDE sau countdown nu acoperă wave-ul
bool need_force = (stare != VERDE_MASINI) ||
((durata_stare > (acum - timp_intrat)) ? (durata_stare - (acum - timp_intrat)) : 0) < wave_remaining;
if (need_force) {
stare = VERDE_MASINI;
timp_intrat = acum;
durata_stare = wave_remaining;
}
}
// 6. Tranzitie stare (NU in sirena, NU in wave)
if (!sirena && !wave_activ && acum - timp_intrat >= durata_stare) {
stare = (stare + 1) % NUM_STARI;
timp_intrat = acum;
durata_stare = durata_pentru(stare, acum);
Serial.printf("[STARE] -> %s/%s durata=%lums\n", NUME[stare], SUBT[stare], durata_stare);
}
// 7. Buton: long-press = AMBULANTA manual, short = pieton
bool buton_jos = (digitalRead(PIN_BUTON) == LOW);
if (buton_jos && !buton_apasat) {
buton_apasat = true;
buton_apasat_la = acum;
long_press_consumat = false;
}
if (buton_jos && buton_apasat && !long_press_consumat && acum - buton_apasat_la >= LONG_PRESS_MS) {
long_press_consumat = true;
if (!sirena_locala) {
sirena_locala = true;
timp_sirena = acum;
timp_intrat = acum; // reset countdown — durata afișată = DURATA_SIRENA
stare = VERDE_MASINI;
durata_stare = DURATA_SIRENA;
strncpy(nod_sirena, NODE_ID, 4); nod_sirena[3] = 0;
mesaj_t m;
strncpy(m.node_id, NODE_ID, 4);
m.tip = 1;
m.scor = 9999;
for (int t = 0; t < 3; t++) {
m.rssi_tags[t] = (taguri[t].ultim_vazut > 0 && acum - taguri[t].ultim_vazut < TIMEOUT_TAG)
? taguri[t].rssi : -127;
}
for (int i = 0; i < 5; i++) {
esp_now_send(BROADCAST_MAC, (uint8_t*)&m, sizeof(m));
delay(20);
}
Serial.println("[BUTON] LONG-PRESS → AMBULANTA manual");
}
}
if (!buton_jos && buton_apasat) {
if (!long_press_consumat && acum - ultima_apasare >= ANTI_BOUNCE) {
pietoni++;
ultima_apasare = acum;
int idx_min = 0;
for (int i = 1; i < 10; i++) if (apasari_recente[i] < apasari_recente[idx_min]) idx_min = i;
apasari_recente[idx_min] = acum;
Serial.println("[BUTON] pieton apasat (short)");
}
buton_apasat = false;
}
// 8. LED + OLED
deseneaza_led(stare, acum, sirena);
deseneaza_oled(acum, sirena, wave_activ, masini, cel_mai_aproape, cel_mai_bun_rssi, vecini_n, telefoane_n,
dir_per_tag, rssi_global_tag);
// 9. Serial debug la 500ms
if (acum - ultim_print >= INTERVAL_PRINT) {
unsigned long ramas_ms = (durata_stare > acum - timp_intrat) ? (durata_stare - (acum - timp_intrat)) : 0;
Serial.printf("[%s] amp=%ld r=%.2f %s/%s ramas=%lums M=%d V=%d T=%d P=%d",
NODE_ID, (long)amplitudine, ratio, NUME[stare], SUBT[stare], ramas_ms, masini, vecini_n, telefoane_n, pietoni);
if (sirena) Serial.printf(" !AMBULANTA-de-la-%s!", nod_sirena);
if (wave_activ) Serial.printf(" !WAVE-de-la-%s(%lums)!", nod_wave, wave_pana_la - acum);
for (int t = 0; t < 3; t++) {
if (tag_dir[t].destinatie[0]) {
if (tag_dir[t].origine[0]) {
Serial.printf(" M%d:%.4s>%.4s(%d)", t + 1, tag_dir[t].origine, tag_dir[t].destinatie, rssi_global_tag[t]);
} else {
Serial.printf(" M%d@%.4s(%d)", t + 1, tag_dir[t].destinatie, rssi_global_tag[t]);
}
}
}
for (int i = 0; i < 8; i++) {
if (vecini_arr[i].ultim_vazut > 0 && acum - vecini_arr[i].ultim_vazut < TIMP_VECIN_VECHI) {
Serial.printf(" vec=%.4s", vecini_arr[i].node_id);
}
}
Serial.println();
// RETRANSMIT: pentru fiecare vecin activ, printez o linie cu status-ul lui
// ca radar/debug să vadă toate 3 noduri fără să le conectăm pe toate la USB
for (int i = 0; i < 8; i++) {
vecin_t& v = vecini_arr[i];
if (v.ultim_vazut == 0 || acum - v.ultim_vazut >= TIMP_VECIN_VECHI) continue;
if (v.stare >= NUM_STARI) continue; // stare invalida = nu retransmit
Serial.printf("[%.4s_r] %s/%s ramas=%ums T=%u P=%u",
v.node_id, NUME[v.stare], SUBT[v.stare], v.ramas_ms, v.telefoane, v.pietoni);
if (v.flags & 0x01) Serial.printf(" !AMBULANTA-de-la-%.4s!", v.node_id);
if (v.flags & 0x02) Serial.printf(" !WAVE-de-la-%.4s!", v.node_id);
// tag-uri din perspectiva acestui vecin
for (int t = 0; t < 3; t++) {
if (v.rssi_tags[t] != -127) {
Serial.printf(" M%d_vecin=%d", t + 1, v.rssi_tags[t]);
}
}
Serial.println();
}
ultim_print = acum;
}
delay(20);
}
Toți N1/N2/N3 au cod IDENTIC, doar NODE_ID diferă. Toți M1/M2/M3 la fel, doar ultimul byte MAC. Asta înseamnă că poți edita o singură dată N1, copia, schimbi NODE_ID și salvezi sub alt nume pentru N2.
Schimbări v2 față de versiunea inițială:
- 4 stări în loc de 3 — galben între VERDE→ROȘU și galben între ROȘU→VERDE (cum e în realitate, nu doar într-o direcție).
- Durată adaptivă: VERDE_MASINI = 5s normal, dar 8s dacă văd 2+ mașini aproape (RSSI > -65 dBm); ROSU_MASINI = 4s normal, dar 7s dacă au fost 3+ apăsări pe buton în ultimele 30s.
- Mod AMBULANȚĂ: la sirenă, toate cele 5 LED-uri clipesc galben (mod ALERT, stop universal) iar OLED-ul devine alb invertat cu „AMBULANTA trece!" pulsing. Durează 6s, apoi ciclul reia de la VERDE_MASINI. Sirena se propagă pe ESP-NOW spre toate celelalte noduri (5x repeat).
- Tracking vecini cu MAC complet + node_id — array de 8 sloturi, fiecare cu MAC ID. Serial print arată
vec=N1 vec=N3(clar care vecini se văd, nu un număr abstract). - ESP-NOW fiabil: ALIVE la 1s (era 2s), trimis 2x consecutiv per cycle. Timeout vecin 10s (era 6s — prea agresiv pentru broadcast loss). Codul vechi pierdea cca 30% din ALIVE → vecini = 0 sau 1; v2 fixează asta.
- OLED redesign mare: header subțire sus (NODE_ID, mașini, vecini, pietoni), stare MARE centrată (size 2), subtitlu (size 1), countdown secunde mare jos-dreapta. La sirenă, totul invertat + pulse-bar.
- Tag scan la 800ms (era 2s) — nodurile văd mașinuțele mult mai fiabil în timpul demo-ului.
Protocol de test (înainte de concurs)
Ordine clară de testare. NU sări peste pași — fiecare confirmă o piesă din sistem. Bifează pe măsură ce merge.
- PAS 1 · Tuning PRAG_SIRENA pe N1 (15 min)
- Flashezi pe N1 sketch-ul tuning de mai jos.
- Deschizi Arduino IDE → Tools → Serial Plotter (115200).
- Cu liniște în cameră → vezi un grafic plat (zgomot ~ 100-500).
- Pornești sirena MP3 lângă microfon → grafic urcă brusc → notează valoarea de vârf.
- Pune
PRAG_SIRENAîn codul final la 60% din vârf (ex: vârf 8000 → PRAG = 4800).
- PAS 2 · Flashezi N1 cu codul final (5 min)
- Selectezi tab
N1sus, Copy, paste în Arduino IDE, schimbi PRAG_SIRENA dacă-i nevoie, Upload. - Deschizi Serial Monitor 115200 → ar trebui să vezi:
=== VIA Nod N1 v2 booting === OK: OLED OK: microfon I2S OK: ESP-NOW pe canal 1 MAC nod: XX:XX:XX:XX:XX:XX OK: sniff probe requests activ (OUI C0:49:EF:00:00:XX) [STARE] -> GALBEN durata=1500ms [N1] amp=152 VERDE/MASINI ramas=4750ms M=0 V=0 P=0 [N1] amp=148 VERDE/MASINI ramas=4250ms M=0 V=0 P=0 ...Pe OLED: header subțire sus „N1 M0 V0 P0", apoi „VERDE / MASINI" mare centrat, countdown „5s" jos dreapta. Ciclu 4 stări: VERDE(5s) → GALBEN(1.5s) → ROȘU(4s) → GALBEN(1.5s) → repeat.
- Selectezi tab
- PAS 3 · Verifici un tag mașinuță (M1) (5 min)
- Flashezi tag-ul M1 cu tab
M1(board ESP32C3 Dev Module). - Pornești switch-ul tag-ului → LED roșu pe TP4056 stins, ESP32-C3 activ.
- Apropii tag-ul de N1 (≤30 cm).
- Serial Monitor N1 → după 2-4 secunde apare
M1=-45(sau valoare similară) la sfârșit:
[N1] amp=130 VERDE/MASINI ramas=3200ms M=1 V=0 P=0 M1=-45 [N1] amp=128 GALBEN/ATENTIE ramas=1100ms M=1 V=0 P=0 M1=-47Pe OLED: header arată „M1" (1 mașinuță), iar jos stânga „M1:-45dB". Mută tag-ul la 2m → RSSI scade spre -70/-80. În cealaltă cameră → după 4s, „M0" (dispare).
Dacă NU apare M1=...: vezi troubleshoot tag-uri mai jos.
- Flashezi tag-ul M1 cu tab
- PAS 4 · 3 noduri + 3 tag-uri toate odată (10 min)
- Flashezi N2 și N3 (cu tab-urile corespunzătoare, doar NODE_ID diferă).
- Pornești toate 3 noduri și toate 3 tag-uri.
- Pe oricare nod, Serial Monitor → trebuie să vezi:
[N1] amp=120 VERDE/MASINI ramas=4800ms M=3 V=2 P=0 M1=-52 M2=-68 M3=-45 vec=N2 vec=N3V=2 = celelalte 2 noduri (cu
vec=N2 vec=N3la sfârșit ne spune EXACT cine se vede). M=3 = toate 3 tag-uri văzute.Dacă V=1 sau 0: ESP-NOW broadcast loss. Verifică pe Serial al fiecărui nod ce
vec=...apare. Dacă N2 vede N3 dar nu N1, e probabil interferență fizică (carcasă, distanță). Mută plăcile mai aproape, repetă. - PAS 5 · Test AMBULANȚĂ + propagare ESP-NOW (5 min)
- 3 noduri pornite, Serial Monitor pe N2.
- Pornești sirena MP3 lângă N1 (vezi mai jos link YouTube).
- Pe N1 Serial:
[MIC] SIRENA local @ amp=XXXX. Toate 5 LED-uri pe N1 clipesc galben (mod ALERT). OLED-ul lui N1 invertat alb cu „AMBULANTA trece!" pulsing + bar. - Pe N2 și N3 Serial:
[ESP-NOW] SIRENA primita de la N1(în maxim 200ms după N1). Aceleași LED-uri clipind galben + OLED invertat. - După 6s, toate 3 revin la VERDE_MASINI și încep ciclul normal.
De ce mod ALERT (galben pulsat) și nu „toate verzi"? În realitate când vine ambulanța intersecția trece în „atenție universală" (analog cu semafor în avarie) — ambulanța trece cu prioritate iar mașinile/pietonii se opresc. Vizual mai impresionant pentru pitch.
- Troubleshoot tag-uri (dacă PAS 3 eșuează):
Simptom Cauză Fix Niciun M1=delocTag nu emite (oprit/baterie) LED pe tag aprins? Switch ON? Niciun M1=, dar tag aprinsMAC nu s-a setat Pe tag în Serial Monitor → trebuie să apară „MAC tag M1: C0:49:EF:00:00:01" RSSI sare brusc 30 dBm Antenă acoperită sau interferență Bluetooth Departe de telefon/router. Țineți tag-ul în mână (nu pe metal). M1=-90 chiar lipit de nod ESP32-C3 SuperMini antenă slabă pe PCB Acceptabil — pragul de „aproape" e -55 dBm. Mută tag-ul mai aproape. Tag dispare după 4s deși-i aproape Tag scan-ul WiFi prea rar (2s/scan e marginal) Scade delay(2000)ladelay(800)pe sketch tag — emite probe mai des.
Sketch de tuning PRAG_SIRENA
Sketch minimal cu doar microfonul I2S. Output curat pentru Serial Plotter.
Rulează 30s în liniște (notează vârful zgomot), apoi 30s cu sirena MP3 (notează vârful sirenă).
Setezi PRAG_SIRENA la jumătatea distanței dintre cele 2.
Board: ESP32 Dev Module. Hardware: doar INMP441 (VDD→3.3V, GND→GND, L/R→GND, WS→GPIO 15, SCK→GPIO 14, SD→GPIO 32).
// Tuning PRAG_SIRENA — printează amplitudine pentru Serial Plotter
#include <driver/i2s.h>
#define I2S_PORT I2S_NUM_0
#define SCK_PIN 14
#define WS_PIN 15
#define SD_PIN 32
#define SAMPLE_RATE 16000
#define BUFFER_SIZE 512
int32_t buffer_i2s[BUFFER_SIZE];
void setup() {
Serial.begin(115200);
delay(200);
Serial.println("Amplitudine"); // header Serial Plotter
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = 0,
.dma_buf_count = 4,
.dma_buf_len = 512,
.use_apll = false,
};
i2s_pin_config_t pins = {
.mck_io_num = I2S_PIN_NO_CHANGE,
.bck_io_num = SCK_PIN,
.ws_io_num = WS_PIN,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = SD_PIN
};
i2s_driver_install(I2S_PORT, &cfg, 0, NULL);
i2s_set_pin(I2S_PORT, &pins);
}
void loop() {
size_t citit = 0;
i2s_read(I2S_PORT, buffer_i2s, sizeof(buffer_i2s), &citit, 0);
int32_t maxim = -2147483648, minim = 2147483647;
for (int i = 0; i < BUFFER_SIZE; i++) {
if (buffer_i2s[i] > maxim) maxim = buffer_i2s[i];
if (buffer_i2s[i] < minim) minim = buffer_i2s[i];
}
int32_t amplitudine = (maxim - minim) >> 16;
Serial.println(amplitudine);
delay(50);
} Cum interpretezi graficul:
- Liniște (cameră goală, fără vorbit) → 50-300
- Vorbit normal → 500-1500
- Strigăt/bătaie din palme → 2000-4000
- Sirenă MP3 lângă mic → 6000-15000 (depinde volum)
- Sirenă MP3 la 1m → 3000-6000
Regula: PRAG_SIRENA = jumătatea distanței dintre vârful „strigăt" și vârful „sirenă". Pentru sirenă la ~1m, prag bun e 2500-3500.
🔊 Sursă sirenă pentru test (YouTube)
Recomand un clip loop de minim 10 minute (ca să nu se oprească mid-test). Caută pe YouTube:
→ caută „ambulance siren 10 hours" pe YouTubeSirena europeană (two-tone) merge cel mai bine cu INMP441 — are vârfuri spectrale curate la ~600Hz și ~800Hz. Volumul telefonului pe ~70%, ține-l la ~30 cm de microfon pentru tuning, apoi mută-l la ~1m pentru testul de demo.
⚠ ÎNAINTE DE PRIMUL COMPILE — Librării de instalat
Dacă vezi fatal error: Adafruit_NeoPixel.h: No such file or directory înseamnă că librăria nu-i instalată.
Soluția: instalează cele 3 librării de mai jos. Durează 1-2 minute total. Apoi rămân salvate permanent pe disc.
Pași:
- Arduino IDE → Sketch → Include Library → Manage Libraries... (sau
Ctrl+Shift+I) - Caută pe rând și apasă Install pe fiecare:
| Caută | Autor | Pentru |
|---|---|---|
Adafruit NeoPixel | Adafruit | LED-uri WS2812 (Pas B+) |
Adafruit GFX Library | Adafruit | grafică OLED (Pas D+) |
Adafruit SSD1306 | Adafruit | driver OLED (Pas D+). La instalare va întreba „Install missing dependencies?" → Install all. |
După ce le vezi marcate „INSTALLED" verde, închide Library Manager. Re-compilează → ar trebui să meargă. Dacă tot dă eroare: Restart Arduino IDE (uneori cache-ul nu se reîncarcă automat).
Suport ESP32 (dacă nu-l ai deja):
- File → Preferences → Additional Boards Manager URLs:
https://espressif.github.io/arduino-esp32/package_esp32_index.json Apoi Tools → Board → Boards Manager → caută „esp32" → Install. Selectează ESP32 Dev Module pentru plăcile N1/N2/N3, sau ESP32C3 Dev Module pentru tag-uri.
Test 1 — Pas A · Blink intern
Board: ESP32 Dev Module. Hardware: doar placa + cablu USB.
void setup() {
pinMode(2, OUTPUT); // pin 2 = LED intern pe majoritatea ESP32 DevKit
}
void loop() {
digitalWrite(2, HIGH); // pune 3.3V pe pin 2 → LED aprins
delay(500);
digitalWrite(2, LOW); // pune 0V pe pin 2 → LED stins
delay(500);
} Test 1 — Pas B · Primul LED roșu pieton (LED[0])
Hardware: LED[0] cablat (VCC→5V, GND→GND, DI→GPIO 5).
#include <Adafruit_NeoPixel.h>
const int PIN_LED = 5;
const int NUM_LEDS = 1;
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
void setup() {
leduri.begin();
leduri.setPixelColor(0, leduri.Color(80, 0, 0)); // LED[0] roșu (verifică LED fizic = roșu pieton)
leduri.show();
}
void loop() {
// nimic
} Dacă LED-ul se aprinde verde sau albastru în loc de roșu → schimbă NEO_GRB cu NEO_RGB și reflashează.
Test 1 — Pas C · Ciclu semafor REAL
Hardware: toate 5 LED-uri în lanț. 3 stări de semafor, 2 LED-uri aprinse simultan per stare:
- VERDE_MASINI (5s): LED[2] verde + LED[0] roșu pieton
- GALBEN (1.5s): LED[3] galben + LED[0] roșu pieton
- ROSU_MASINI (4s): LED[4] roșu + LED[1] verde pieton
#include <Adafruit_NeoPixel.h>
const int PIN_LED = 5;
const int NUM_LEDS = 5;
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
// Layout fizic: [0]=roșu pieton, [1]=verde pieton, [2]=verde maș, [3]=galben, [4]=roșu maș
const int VERDE_MASINI = 0, GALBEN = 1, ROSU_MASINI = 2;
const unsigned long DURATA[] = { 5000, 1500, 4000 };
int stare = VERDE_MASINI;
unsigned long timp_intrat = 0;
void setup() {
leduri.begin();
timp_intrat = millis();
}
void loop() {
if (millis() - timp_intrat >= DURATA[stare]) {
stare = (stare + 1) % 3;
timp_intrat = millis();
}
leduri.clear();
if (stare == VERDE_MASINI) {
leduri.setPixelColor(2, leduri.Color(0, 80, 0)); // verde mașini
leduri.setPixelColor(0, leduri.Color(80, 0, 0)); // roșu pieton
}
if (stare == GALBEN) {
leduri.setPixelColor(3, leduri.Color(80, 80, 0)); // galben
leduri.setPixelColor(0, leduri.Color(80, 0, 0)); // roșu pieton (continuă)
}
if (stare == ROSU_MASINI) {
leduri.setPixelColor(4, leduri.Color(80, 0, 0)); // roșu mașini
leduri.setPixelColor(1, leduri.Color(0, 80, 0)); // verde pieton
}
leduri.show();
} Test 1 — Pas D · + OLED
Hardware: Pas C + OLED cablat (SDA→GPIO 21, SCL→GPIO 22, VCC→3.3V, GND→GND).
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int PIN_LED = 5;
const int NUM_LEDS = 5;
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
const int VERDE_MASINI = 0, GALBEN = 1, ROSU_MASINI = 2;
const unsigned long DURATA[] = { 5000, 1500, 4000 };
const char* NUME[] = { "VERDE MASINI", "GALBEN", "VERDE PIETON" };
int stare = VERDE_MASINI;
unsigned long timp_intrat = 0;
void setup() {
leduri.begin();
if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true) { delay(1000); } // OLED nu pornește — blocat aici
}
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1);
timp_intrat = millis();
}
void loop() {
if (millis() - timp_intrat >= DURATA[stare]) {
stare = (stare + 1) % 3;
timp_intrat = millis();
}
leduri.clear();
if (stare == VERDE_MASINI) {
leduri.setPixelColor(2, leduri.Color(0, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == GALBEN) {
leduri.setPixelColor(3, leduri.Color(80, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == ROSU_MASINI) {
leduri.setPixelColor(4, leduri.Color(80, 0, 0));
leduri.setPixelColor(1, leduri.Color(0, 80, 0));
}
leduri.show();
oled.clearDisplay();
oled.setCursor(0, 0);
oled.println("Nod N1");
oled.println();
oled.println(NUME[stare]);
oled.display();
} Test 1 — Pas E · + Buton pieton
Hardware: Pas D + Buton (S→GPIO 4, GND→GND, VCC neconectat).
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int PIN_LED = 5;
const int NUM_LEDS = 5;
const int PIN_BUTON = 4;
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
const int VERDE_MASINI = 0, GALBEN = 1, ROSU_MASINI = 2;
const unsigned long DURATA[] = { 5000, 1500, 4000 };
const char* NUME[] = { "VERDE MASINI", "GALBEN", "VERDE PIETON" };
int stare = VERDE_MASINI;
unsigned long timp_intrat = 0;
int pietoni = 0;
unsigned long ultima_apasare = 0;
const unsigned long ANTI_BOUNCE = 2000;
void setup() {
leduri.begin();
pinMode(PIN_BUTON, INPUT_PULLUP);
if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true) { delay(1000); }
}
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1);
timp_intrat = millis();
}
void loop() {
if (millis() - timp_intrat >= DURATA[stare]) {
stare = (stare + 1) % 3;
timp_intrat = millis();
}
// Citește buton
if (digitalRead(PIN_BUTON) == LOW) {
if (millis() - ultima_apasare >= ANTI_BOUNCE) {
pietoni++;
ultima_apasare = millis();
}
}
leduri.clear();
if (stare == VERDE_MASINI) {
leduri.setPixelColor(2, leduri.Color(0, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == GALBEN) {
leduri.setPixelColor(3, leduri.Color(80, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == ROSU_MASINI) {
leduri.setPixelColor(4, leduri.Color(80, 0, 0));
leduri.setPixelColor(1, leduri.Color(0, 80, 0));
}
leduri.show();
oled.clearDisplay();
oled.setCursor(0, 0);
oled.println("Nod N1");
oled.println();
oled.println(NUME[stare]);
oled.print("Pietoni: ");
oled.println(pietoni);
oled.display();
} Test 2 — Microfon I2S (neverificat pe placă)
Hardware: Pas E + INMP441 (VDD→3.3V, GND→GND, L/R→GND, WS→GPIO 15, SCK→GPIO 14, SD→GPIO 32).
Codul de mai jos înlocuiește complet Pas E. PRAG_SIRENA reglat prin observație pe Serial Monitor.
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <driver/i2s.h>
const int PIN_LED = 5;
const int NUM_LEDS = 5;
const int PIN_BUTON = 4;
#define I2S_PORT I2S_NUM_0
#define SCK_PIN 14
#define WS_PIN 15
#define SD_PIN 32
#define SAMPLE_RATE 16000
#define BUFFER_SIZE 512
#define PRAG_SIRENA 3500 // reglează prin observație pe Serial
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
const int VERDE_MASINI = 0, GALBEN = 1, ROSU_MASINI = 2;
const unsigned long DURATA[] = { 5000, 1500, 4000 };
const char* NUME[] = { "VERDE MASINI", "GALBEN", "VERDE PIETON" };
int stare = VERDE_MASINI;
unsigned long timp_intrat = 0;
int pietoni = 0;
unsigned long ultima_apasare = 0;
const unsigned long ANTI_BOUNCE = 2000;
int32_t buffer[BUFFER_SIZE];
bool sirena_detectata = false;
unsigned long timp_sirena = 0;
const unsigned long DURATA_ALERTA_SIRENA = 5000;
void setup_i2s() {
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = 0,
.dma_buf_count = 4,
.dma_buf_len = 512,
.use_apll = false,
};
i2s_pin_config_t pins = {
.bck_io_num = SCK_PIN,
.ws_io_num = WS_PIN,
.data_out_num = -1,
.data_in_num = SD_PIN,
};
i2s_driver_install(I2S_PORT, &cfg, 0, NULL);
i2s_set_pin(I2S_PORT, &pins);
}
void setup() {
Serial.begin(115200);
leduri.begin();
pinMode(PIN_BUTON, INPUT_PULLUP);
if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true) { delay(1000); }
}
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1);
setup_i2s();
Serial.println("OK: Microfon I2S");
timp_intrat = millis();
}
void loop() {
// 1. Citește microfon
size_t citit = 0;
i2s_read(I2S_PORT, buffer, sizeof(buffer), &citit, 0);
int32_t maxim = -2147483648, minim = 2147483647;
for (int i = 0; i < BUFFER_SIZE; i++) {
if (buffer[i] > maxim) maxim = buffer[i];
if (buffer[i] < minim) minim = buffer[i];
}
int32_t amplitudine = (maxim - minim) >> 16;
// 2. Detectează sirenă
if (amplitudine > PRAG_SIRENA) {
sirena_detectata = true;
timp_sirena = millis();
stare = VERDE_MASINI; // forțează trecerea instant la verde mașini
timp_intrat = millis();
}
if (sirena_detectata && (millis() - timp_sirena > DURATA_ALERTA_SIRENA)) {
sirena_detectata = false;
}
// 3. Tranziție normală stare (dacă nu-i sirenă)
if (!sirena_detectata && millis() - timp_intrat >= DURATA[stare]) {
stare = (stare + 1) % 3;
timp_intrat = millis();
}
// 4. Citește buton pieton
if (digitalRead(PIN_BUTON) == LOW) {
if (millis() - ultima_apasare >= ANTI_BOUNCE) {
pietoni++;
ultima_apasare = millis();
}
}
// 5. LED-uri
leduri.clear();
if (stare == VERDE_MASINI) {
leduri.setPixelColor(2, leduri.Color(0, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == GALBEN) {
leduri.setPixelColor(3, leduri.Color(80, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == ROSU_MASINI) {
leduri.setPixelColor(4, leduri.Color(80, 0, 0));
leduri.setPixelColor(1, leduri.Color(0, 80, 0));
}
leduri.show();
// 6. OLED
oled.clearDisplay();
oled.setCursor(0, 0);
oled.println("Nod N1");
if (sirena_detectata) {
oled.println("!! SIRENA !!");
} else {
oled.println(NUME[stare]);
}
oled.print("Pietoni: ");
oled.println(pietoni);
oled.print("Amp: ");
oled.println(amplitudine);
oled.display();
Serial.print("Amplitudine: ");
Serial.println(amplitudine);
delay(30);
} Test 3 — ESP-NOW mesh (neverificat pe placă)
Hardware: 2 plăci identice cu Test 2. Schimbă NODE_ID între cele 2 reflasheri.
⚠ Despre versiunea ESP32 Arduino core
Codul de mai jos folosește semnătura nouă pentru callback-ul ESP-NOW (ESP32 Arduino core v3.x):
void on_recv(const esp_now_recv_info* info, const uint8_t* data, int len) Dacă vezi eroarea invalid conversion from 'void (*)(const uint8_t*, ...)' to 'esp_now_recv_cb_t', înseamnă că ai core v3.x dar codul folosea signatura veche.
Pentru core v2.x (mai vechi), foloseste varianta:
void on_recv(const uint8_t* mac, const uint8_t* data, int len) Verifică versiunea în Tools → Board → Boards Manager → caută „esp32" → vezi versiunea curentă instalată.
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <driver/i2s.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#define NODE_ID "N1" // ← schimbă la "N2" pentru a doua placă
const int PIN_LED = 5;
const int NUM_LEDS = 5;
const int PIN_BUTON = 4;
#define I2S_PORT I2S_NUM_0
#define SCK_PIN 14
#define WS_PIN 15
#define SD_PIN 32
#define SAMPLE_RATE 16000
#define BUFFER_SIZE 512
#define PRAG_SIRENA 3500
uint8_t BROADCAST_MAC[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
typedef struct {
char node_id[4];
uint8_t tip; // 0=ALIVE, 1=SIRENA
uint32_t scor;
} mesaj_t;
mesaj_t outgoing = { NODE_ID, 0, 0 };
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
const int VERDE_MASINI = 0, GALBEN = 1, ROSU_MASINI = 2;
const unsigned long DURATA[] = { 5000, 1500, 4000 };
const char* NUME[] = { "VERDE MASINI", "GALBEN", "VERDE PIETON" };
int stare = VERDE_MASINI;
unsigned long timp_intrat = 0;
int pietoni = 0;
unsigned long ultima_apasare = 0;
const unsigned long ANTI_BOUNCE = 2000;
int32_t buffer[BUFFER_SIZE];
bool sirena_locala = false;
bool sirena_de_la_vecin = false;
unsigned long timp_sirena = 0;
const unsigned long DURATA_SIRENA = 5000;
int vecini = 0;
unsigned long ultim_alive[5] = {0};
const unsigned long TIMP_VECIN_VECHI = 6000;
unsigned long ultim_broadcast = 0;
const unsigned long INTERVAL_ALIVE = 2000;
// ESP32 Arduino core v3.x: primul parametru e esp_now_recv_info*, nu uint8_t* mac
// Pentru core v2.x: foloseste signatura veche (const uint8_t* mac, ...)
void on_recv(const esp_now_recv_info* info, const uint8_t* data, int len) {
if (len != sizeof(mesaj_t)) return;
mesaj_t* m = (mesaj_t*)data;
if (m->tip == 1) {
sirena_de_la_vecin = true;
timp_sirena = millis();
stare = VERDE_MASINI;
timp_intrat = millis();
}
// simplificat: numără cât timp am primit ALIVE de la cineva
ultim_alive[0] = millis();
// MAC-ul expeditorului e disponibil în info->src_addr dacă vrei să-l folosești
}
void setup_i2s() {
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = 0,
.dma_buf_count = 4,
.dma_buf_len = 512,
.use_apll = false,
};
i2s_pin_config_t pins = {
.mck_io_num = I2S_PIN_NO_CHANGE,
.bck_io_num = SCK_PIN,
.ws_io_num = WS_PIN,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = SD_PIN
};
i2s_driver_install(I2S_PORT, &cfg, 0, NULL);
i2s_set_pin(I2S_PORT, &pins);
}
void setup() {
Serial.begin(115200);
leduri.begin();
pinMode(PIN_BUTON, INPUT_PULLUP);
if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true) { delay(1000); }
}
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1);
setup_i2s();
WiFi.mode(WIFI_STA);
esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);
if (esp_now_init() != ESP_OK) {
Serial.println("EROARE: ESP-NOW init");
while (true) delay(1000);
}
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, BROADCAST_MAC, 6);
peer.channel = 1;
peer.encrypt = false;
esp_now_add_peer(&peer);
esp_now_register_recv_cb(on_recv);
Serial.print("OK ");
Serial.println(NODE_ID);
timp_intrat = millis();
}
void loop() {
// 1. Citește microfon
size_t citit = 0;
i2s_read(I2S_PORT, buffer, sizeof(buffer), &citit, 0);
int32_t maxim = -2147483648, minim = 2147483647;
for (int i = 0; i < BUFFER_SIZE; i++) {
if (buffer[i] > maxim) maxim = buffer[i];
if (buffer[i] < minim) minim = buffer[i];
}
int32_t amplitudine = (maxim - minim) >> 16;
// 2. Detectează sirenă local
if (amplitudine > PRAG_SIRENA && !sirena_locala) {
sirena_locala = true;
timp_sirena = millis();
stare = VERDE_MASINI;
timp_intrat = millis();
// trimit SIRENA prin ESP-NOW (de 3 ori pentru fiabilitate)
mesaj_t m = { NODE_ID, 1, 0 };
for (int i = 0; i < 3; i++) {
esp_now_send(BROADCAST_MAC, (uint8_t*)&m, sizeof(m));
delay(50);
}
}
// 3. Reset alertă sirenă
bool sirena = sirena_locala || sirena_de_la_vecin;
if (sirena && (millis() - timp_sirena > DURATA_SIRENA)) {
sirena_locala = false;
sirena_de_la_vecin = false;
}
// 4. ALIVE broadcast la fiecare 2s
if (millis() - ultim_broadcast >= INTERVAL_ALIVE) {
mesaj_t m = { NODE_ID, 0, (uint32_t)pietoni };
esp_now_send(BROADCAST_MAC, (uint8_t*)&m, sizeof(m));
ultim_broadcast = millis();
}
// 5. Numără vecini activi
vecini = 0;
for (int i = 0; i < 5; i++) {
if (ultim_alive[i] > 0 && millis() - ultim_alive[i] < TIMP_VECIN_VECHI) {
vecini++;
}
}
// 6. Tranziție stare (dacă nu sirenă)
if (!sirena && millis() - timp_intrat >= DURATA[stare]) {
stare = (stare + 1) % 3;
timp_intrat = millis();
}
// 7. Buton
if (digitalRead(PIN_BUTON) == LOW) {
if (millis() - ultima_apasare >= ANTI_BOUNCE) {
pietoni++;
ultima_apasare = millis();
}
}
// 8. LED-uri
leduri.clear();
if (stare == VERDE_MASINI) {
leduri.setPixelColor(2, leduri.Color(0, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == GALBEN) {
leduri.setPixelColor(3, leduri.Color(80, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == ROSU_MASINI) {
leduri.setPixelColor(4, leduri.Color(80, 0, 0));
leduri.setPixelColor(1, leduri.Color(0, 80, 0));
}
leduri.show();
// 9. OLED
oled.clearDisplay();
oled.setCursor(0, 0);
oled.print("Nod ");
oled.println(NODE_ID);
if (sirena) {
oled.println("!! SIRENA !!");
} else {
oled.println(NUME[stare]);
}
oled.print("Pietoni: ");
oled.println(pietoni);
oled.print("Vecini: ");
oled.println(vecini);
oled.display();
delay(30);
} Tag mașinuță — ESP32-C3 SuperMini + MAC unic
Board: ESP32C3 Dev Module. Hardware: LiPo + TP4056 + switch + ESP32-C3 SuperMini (vezi schema în lecția 08).
Schimbă ultimul byte din MAC_TAG între cele 3 tag-uri: 0x01 pentru tag 1, 0x02 pentru tag 2, 0x03 pentru tag 3.
#include <WiFi.h>
#include <esp_wifi.h>
// MAC unic per tag (schimbă ultimul byte între tag-uri)
uint8_t MAC_TAG[] = { 0xC0, 0x49, 0xEF, 0x00, 0x00, 0x01 };
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
esp_wifi_set_mac(WIFI_IF_STA, MAC_TAG);
WiFi.disconnect();
delay(100);
Serial.print("MAC tag: ");
Serial.println(WiFi.macAddress());
}
void loop() {
// Scanare WiFi pe canal 1 — emite probe requests
WiFi.scanNetworks(true, true, false, 100, 1);
delay(2000); // sleep 2 secunde
} Note importante:
- Codul Test 2 și Test 3 e SCRIS dar nu-i testat pe placă reală. La prima compilare pot apărea warnings sau errors mici de adaptat.
- Pentru ESP32-C3 SuperMini (tag), folosește board
ESP32C3 Dev Module, nuESP32 Dev Module. - Toate sketch-urile pornesc cu un Serial.begin(115200) — deschide Serial Monitor la 115200 ca să vezi mesajele.
- Dacă upload-ul eșuează cu „Connecting...........": apasă și ține apăsat
BOOTpe placă în timp ce dai Upload, eliberează după ce începe transferul.