DevLab_ICM20948 1.0.0
Driver para sensor ICM-20948
Loading...
Searching...
No Matches
admin_pwm.ino
Go to the documentation of this file.
1// ═══════════════════════════════════════════════════════════
2// ADMIN SIMPLE - Solo SCAN y TOGGLE RELAY
3// Version minimalista para control basico I2C
4// ═══════════════════════════════════════════════════════════
5
6#include <Wire.h>
7#include <DevLab_ICM20948.h>
8// Configuracion I2C - Wire1 con GPIO2 (SDA) y GPIO3 (SCL)
9#define WIRE Wire
10#define I2C_SDA 6
11#define I2C_SCL 7
12#define I2C_FREQ 100000 // 100 kHz - SEGURO para inicialización (luego se puede subir a 400kHz)
13#define ICM_ADDR 0x69
14// ═══════════════════════════════════════════════════════════
15// COMANDOS CRÍTICOS DE SEGURIDAD (0xA0-0xAF)
16// ═══════════════════════════════════════════════════════════
17#define CMD_RELAY_OFF 0xA0 // Apagar relé PA2 y PB5
18#define CMD_RELAY_ON 0xA1 // Encender relé PA2 y PB5
19#define CMD_RELAY_TOGGLE 0xA6 // Pulso/Disparo relé PA2 y PB5
20
21// ═══════════════════════════════════════════════════════════
22// COMANDOS NEOPIXEL
23// ═══════════════════════════════════════════════════════════
24#define CMD_RED 0x02 // NeoPixels rojos
25#define CMD_GREEN 0x03 // NeoPixels verdes
26#define CMD_BLUE 0x04 // NeoPixels azules
27#define CMD_OFF 0x05 // NeoPixels apagados + PWM OFF
28#define CMD_WHITE 0x08 // NeoPixels blancos
29
30// ═══════════════════════════════════════════════════════════
31// COMANDOS PWM / BUZZER
32// ═══════════════════════════════════════════════════════════
33#define CMD_PWM_OFF 0x20 // PWM OFF - Silencio
34#define CMD_PWM_25 0x21 // PWM 25% - Tono grave 200Hz
35#define CMD_PWM_50 0x22 // PWM 50% - Tono medio 500Hz
36#define CMD_PWM_75 0x23 // PWM 75% - Tono agudo 1000Hz
37#define CMD_PWM_100 0x24 // PWM 100% - Tono muy agudo 2000Hz
38
39// Comandos deshabilitados - Causan problemas de estabilidad I2C
40/*
41// Notas musicales
42#define CMD_TONE_DO 0x25 // Tono DO (261Hz)
43#define CMD_TONE_RE 0x26 // Tono RE (294Hz)
44#define CMD_TONE_MI 0x27 // Tono MI (330Hz)
45#define CMD_TONE_FA 0x28 // Tono FA (349Hz)
46#define CMD_TONE_SOL 0x29 // Tono SOL (392Hz)
47#define CMD_TONE_LA 0x2A // Tono LA (440Hz)
48#define CMD_TONE_SI 0x2B // Tono SI (494Hz)
49
50// Alertas del sistema
51#define CMD_SUCCESS 0x2C // Success - Tono positivo 800Hz
52#define CMD_OK 0x2D // OK - Confirmación 1000Hz
53#define CMD_WARNING 0x2E // Warning - Advertencia 1200Hz
54#define CMD_ALERT 0x2F // Alert - Alerta crítica 1500Hz
55*/
56
57// ═══════════════════════════════════════════════════════════
58// COMANDOS LECTURA DIGITAL
59// ═══════════════════════════════════════════════════════════
60#define CMD_PA4_DIGITAL 0x07 // Leer PA0 como entrada digital
61#define RESP_PA4_DIGITAL 0x09 // Respuesta PA4 digital
62
63// ═══════════════════════════════════════════════════════════
64// COMANDOS ADC 12-bit (PA1 interno del slave)
65// ═══════════════════════════════════════════════════════════
66#define CMD_ADC_PA1_HSB 0xD8 // Leer ADC - bits 11-8 (nibble alto)
67#define CMD_ADC_PA1_LSB 0xD9 // Leer ADC - bits 7-0
68
69// ═══════════════════════════════════════════════════════════
70// COMANDOS I2C ADDRESS MANAGEMENT
71// ════════════════════════════════════════════════════════════
72#define CMD_SET_I2C_ADDR 0x3D // Establecer nueva dirección I2C (se guarda en Flash)
73#define CMD_RESET_FACTORY 0x3E // Reset factory (usar UID por defecto)
74#define CMD_GET_I2C_STATUS 0x3F // Obtener estado I2C (Flash vs UID)
75#define CMD_SAVE_COLOR 0x46 // Guardar color por slot (1..3 en admin, 0..2 en esclavo)
76
77#define RESP_I2C_ADDR_SET 0x0D // Nueva dirección I2C establecida
78#define RESP_FACTORY_RESET 0x0E // Reset factory ejecutado (usar UID)
79#define RESP_I2C_FROM_FLASH 0x0F // I2C usando dirección desde Flash
80#define RESP_I2C_FROM_UID 0x0A // I2C usando dirección desde UID
81#define RESP_COLOR_STAGE_READY 0x15 // Etapa de save_color lista
82#define RESP_COLOR_SAVED 0x16 // Color guardado en Flash
83
84// Variables globales
85uint8_t found_devices[128];
86uint8_t device_count = 0;
87bool scan_loop_active = false; // Bandera para scan continuo
88uint32_t last_scan_time = 0; // Timestamp del último scan
89uint32_t scan_interval = 1000; // Intervalo entre scans (1 segundo)
90bool scan_in_progress = false; // Bandera para evitar scans simultáneos
91#define DEVICE_DELAY 30 // Delay óptimo entre dispositivos (ms)
92struct DeviceError {
93 uint8_t address;
94 uint32_t error_count;
95 uint32_t success_count;
96 uint32_t last_error_time;
97};
98DeviceError device_errors[128];
99int device_error_count = 0;
100
101// Test mode
102bool test_mode_active = false;
103uint32_t test_interval = 100; // Intervalo de test (100ms por defecto)
104uint32_t test_start_time = 0;
105uint32_t test_duration = 0;
106
107// PA0 Digital reading stats
108struct DigitalReadStats {
109 uint8_t address;
110 uint32_t read_count;
111 uint32_t fail_count;
112 uint8_t last_state; // 0=LOW, 1=HIGH
113 uint32_t last_read_time;
114};
115DigitalReadStats digital_stats[128];
116int digital_stats_count = 0;
117
118// Test mode para lecturas digitales
119bool read_test_active = false;
120uint32_t read_test_interval = 100;
121uint32_t read_test_start_time = 0;
122uint32_t read_test_duration = 0;
123
124// Flag para reimprimir menú cuando se conecta Serial
125bool menu_shown = false;
126uint32_t last_serial_check = 0;
127bool i2c_safe_mode = false; // Modo seguro si I2C está bloqueado
128
129DevLab_ICM20948 imu;
130
131void setup() {
132 // ═══════════════════════════════════════════════════════════
133 // PASO 1: INICIALIZAR USB/CDC PRIMERO (CRÍTICO EN RP2040/RP2350)
134 // ═══════════════════════════════════════════════════════════
135 Serial.begin(115200);
136
137 #if defined(ARDUINO_ARCH_RP2040)
138 // RP2040/RP2350: Esperar a que USB enumere ANTES de tocar I2C
139 // Esto evita que I2C bloquee IRQs y mate el CDC
140 uint32_t t0 = millis();
141 while (!Serial && millis() - t0 < 2000) {
142 delay(10);
143 }
144 delay(200); // Margen extra de seguridad
145
146 Serial.println("\n[RP2040/RP2350] USB CDC listo");
147 #else
148 delay(500);
149 #endif
150
151 Serial.println("\n\n");
152 Serial.println("╔═══════════════════════════════════════╗");
153 Serial.println("║ ADMIN PWM - I2C Control ║");
154 Serial.println("║ Scan + Relay + PWM/Buzzer ║");
155 Serial.println("╚═══════════════════════════════════════╝");
156
157 // ═══════════════════════════════════════════════════════════
158 // PASO 2: AHORA SÍ INICIALIZAR I2C (USB ya está vivo)
159 // ═══════════════════════════════════════════════════════════
160 Serial.println("\n[INFO] Inicializando I2C...");
161
162 // Verificar que SDA/SCL no estén bloqueados ANTES de WIRE.begin()
163 Serial.println("[CHECK] Verificando bus I2C...");
164 pinMode(I2C_SDA, INPUT_PULLUP);
165 pinMode(I2C_SCL, INPUT_PULLUP);
166 delay(10);
167
168 bool sda_ok = digitalRead(I2C_SDA);
169 bool scl_ok = digitalRead(I2C_SCL);
170
171 Serial.printf(" SDA (GPIO%d): %s\n", I2C_SDA, sda_ok ? "HIGH ✓" : "LOW ✗ BLOQUEADO");
172 Serial.printf(" SCL (GPIO%d): %s\n", I2C_SCL, scl_ok ? "HIGH ✓" : "LOW ✗ BLOQUEADO");
173
174 if (!sda_ok || !scl_ok) {
175 Serial.println("\n[ERROR] Bus I2C bloqueado!");
176 Serial.println("CAUSA: Dispositivo esclavo colgado o sin pull-ups");
177 Serial.println("SOLUCION:");
178 Serial.println(" 1. Desconecta todos los dispositivos I2C");
179 Serial.println(" 2. Verifica resistencias pull-up (4.7k ohm)");
180 Serial.println(" 3. Resetea el RP2350");
181 Serial.println("\n[MODO SEGURO] I2C NO inicializado");
182 i2c_safe_mode = true;
183 // NO return - continuar para procesar comandos seriales
184 } else {
185 Serial.println("[OK] Bus I2C libre");
186 i2c_safe_mode = false;
187 }
188
189 // Solo inicializar I2C si el bus está libre
190 if (!i2c_safe_mode) {
191 // Inicializar I2C con Wire1
192 // RP2040 / RP2350: configurar pines ANTES de begin()
193 // WIRE.setSDA(I2C_SDA);
194 // WIRE.setSCL(I2C_SCL);
195 WIRE.begin(I2C_SDA,I2C_SCL);
196 WIRE.setClock(I2C_FREQ);
197
198 if(!imu.beginI2C(ICM_ADDR,WIRE,I2C_FREQ)){
199 Serial.println(F("ERROR: beginI2C() failed"));
200 while (1) delay(200);
201 }
202 if(!imu.auxMasterEnable(0x07)){
203 Serial.println(F("ERROR: enableAuxMaster() failed"));
204 while (1) delay(200);
205 }
206 // Deshabilitado temporalmente: bloqueaba el arranque (while(1)) si el
207 // esclavo en 0x2B no respondia al registro 0x31 (valores eran para el
208 // magnetometro AK09916, no para el esclavo de relay/PWM).
209 /*
210 if(!imu.auxWriteByte(0x2B, 0x31, 0x08)){
211 Serial.println(F("ERROR: auxwritebyte()) failed"));
212 while (1) delay(200);
213 }
214 */
215 Serial.println("[OK] I2C inicializado sin bloquear USB");
216 Serial.printf(" Wire1 - SDA:GPIO%d SCL:GPIO%d @ %d kHz\n", I2C_SDA, I2C_SCL, I2C_FREQ/1000);
217
218 } // Fin de if (!i2c_safe_mode)
219 bool mag_ok = imu.initMag();
220 Serial.printf("[TEST] initMag() = %s\n", mag_ok ? "OK" : "FAIL");
221
222 Serial.println("\nComandos disponibles:");
223 Serial.println(" s - Scan dispositivos I2C");
224 Serial.println(" loop - Scan continuo (cada 1 seg)");
225 Serial.println(" stop - Detener scan continuo");
226 Serial.println(" t XX - Pulso relay (XX = addr hex)");
227 Serial.println(" on XX - Encender relay");
228 Serial.println(" off XX - Apagar relay");
229 Serial.println(" help - Mostrar ayuda");
230 Serial.println("\nEjemplos:");
231 Serial.println(" s (scan de dispositivos)");
232 Serial.println(" loop (scan continuo)");
233 Serial.println(" stop (detener scan)");
234 Serial.println(" t 32 (toggle relay en 0x20)");
235 Serial.println(" off 32 (apagar relay en 0x20)");
236 Serial.println("\n");
237}
238
239void loop() {
240 // Reimprimir menú si se reconecta el Serial Monitor
241 #if defined(ARDUINO_ARCH_RP2040)
242 if (Serial && !menu_shown && millis() - last_serial_check > 1000) {
243 showMenu();
244 menu_shown = true;
245 last_serial_check = millis();
246 }
247 if (!Serial) {
248 menu_shown = false;
249 last_serial_check = millis();
250 }
251 #endif
252
253 // Procesar comandos desde Serial
254 if (Serial.available()) {
255 String cmd = Serial.readStringUntil('\n');
256 cmd.trim();
257 cmd.toLowerCase();
258
259 if (cmd.length() > 0) {
260 processCommand(cmd);
261 }
262 }
263
264 // Ejecutar scan continuo si está activo
265 if (scan_loop_active && !scan_in_progress) {
266 if (millis() - last_scan_time >= scan_interval) {
267 scanDevices();
268 last_scan_time = millis();
269 }
270 }
271
272 // Ejecutar test asíncrono si está activo
273 if (test_mode_active) {
274 if (millis() - last_scan_time >= test_interval) {
275 testDevices();
276 last_scan_time = millis();
277 }
278 // Verificar si el test debe terminar
279 if (test_duration > 0 && (millis() - test_start_time >= test_duration)) {
280 stopTest();
281 showTestResults();
282 }
283 }
284
285 // Ejecutar test de lectura digital si está activo
286 if (read_test_active) {
287 if (millis() - last_scan_time >= read_test_interval) {
288 testReadDigital();
289 last_scan_time = millis();
290 }
291 // Verificar si el test debe terminar
292 if (read_test_duration > 0 && (millis() - read_test_start_time >= read_test_duration)) {
293 stopReadTest();
294 }
295 }
296}
297
298void processCommand(String cmd) {
299 if (cmd == "s" || cmd == "scan") {
300 scanDevices();
301 } else if (cmd == "loop") {
302 startScanLoop();
303 } else if (cmd == "stop") {
304 stopScanLoop();
305 } else if (cmd == "menu" || cmd == "m") {
306 showMenu();
307 } else if (cmd.startsWith("test ")) {
308 startTest(cmd);
309 } else if (cmd == "stoptest") {
310 stopTest();
311 showTestResults();
312 } else if (cmd == "errors") {
313 showTestResults();
314 } else if (cmd.startsWith("adc ")) {
315 readADC(cmd);
316 } else if (cmd.startsWith("read ")) {
317 readDigital(cmd);
318 } else if (cmd == "dstats") {
319 showDigitalStats();
320 } else if (cmd.startsWith("readtest ")) {
321 startReadTest(cmd);
322 } else if (cmd == "stopread") {
323 stopReadTest();
324 } else if (cmd.startsWith("t ")) {
325 toggleRelay(cmd);
326 } else if (cmd.startsWith("on ")) {
327 relayOn(cmd);
328 } else if (cmd.startsWith("off ")) {
329 relayOff(cmd);
330 } else if (cmd.startsWith("pwm ")) {
331 pwmCommand(cmd);
332 /*
333 // Comandos deshabilitados
334 } else if (cmd.startsWith("note ")) {
335 playNote(cmd);
336 } else if (cmd.startsWith("alert ")) {
337 playAlert(cmd);
338 */
339 } else if (cmd == "silence" || cmd == "pwmoff") {
340 pwmOff();
341 } else if (cmd.startsWith("neo ")) {
342 neoCommand(cmd);
343 } else if (cmd.startsWith("red ")) {
344 neoRed(cmd);
345 } else if (cmd.startsWith("green ")) {
346 neoGreen(cmd);
347 } else if (cmd.startsWith("blue ")) {
348 neoBlue(cmd);
349 } else if (cmd.startsWith("white ")) {
350 neoWhite(cmd);
351 } else if (cmd == "neooff") {
352 neoOff();
353 } else if (cmd.startsWith("ch ")) {
354 changeI2CAddress(cmd);
355 } else if (cmd.startsWith("save_color ")) {
356 saveColorPreset(cmd);
357 }else if (cmd == "help" || cmd == "h") {
358 showHelp();
359 } else {
360 Serial.println("ERROR: Comando desconocido. Usa 'help' o 'menu' para ver comandos.");
361 }
362}
363
364void showMenu() {
365 Serial.println("\n\n");
366 Serial.println("╔═══════════════════════════════════════╗");
367 Serial.println("║ ADMIN PWM - I2C Control ║");
368 Serial.println("║ Scan + Relay + PWM/Buzzer ║");
369 Serial.println("╚═══════════════════════════════════════╝");
370 Serial.println("[OK] I2C inicializado sin bloquear USB");
371 Serial.printf(" SDA:GPIO%d SCL:GPIO%d @ %d kHz\n", I2C_SDA, I2C_SCL, I2C_FREQ/1000);
372 Serial.println("\nComandos disponibles:");
373 Serial.println(" s / menu - Scan dispositivos / mostrar menu");
374 Serial.println(" loop - Scan continuo (cada 1 seg)");
375 Serial.println(" stop - Detener scan continuo");
376 Serial.println(" t XX - Pulso relay (XX = addr hex)");
377 Serial.println(" on XX - Encender relay");
378 Serial.println(" off XX - Apagar relay");
379 Serial.println(" pwm XX NIVEL - PWM (25, 50, 75, 100, off)");
380 Serial.println(" silence - Apagar PWM en todos");
381 Serial.println(" neo XX COLOR - NeoPixel (red/green/blue/white/off o 1/2/3/4/0)");
382 Serial.println(" ch XX YY - Cambiar dir I2C (XX=old, YY=new)");
383 Serial.println(" save_color A P R G B - Guardar color por slot (1..3)");
384 Serial.println(" help - Mostrar ayuda completa");
385 Serial.println("\nEjemplos:");
386 Serial.println(" s (scan de dispositivos)");
387 Serial.println(" menu (mostrar este menu)");
388 Serial.println(" pwm 20 50 (PWM 50% en 0x20)");
389 Serial.println(" silence (apagar PWM en todos)");
390 Serial.println(" neo 20 1 (NeoPixel rojo en 0x20)");
391 Serial.println(" save_color 20 1 0x00 0xff 0x60");
392 Serial.println("");
393}
394
395void scanDevices() {
396 // Verificar si estamos en modo seguro
397 if (i2c_safe_mode) {
398 Serial.println("[ERROR] I2C en MODO SEGURO - comando no disponible");
399 Serial.println("Desconecta dispositivos I2C y resetea el dispositivo");
400 return;
401 }
402
403 // Evitar scans simultáneos
404 if (scan_in_progress) {
405 Serial.println("⚠ Scan ya en progreso, esperando...");
406 return;
407 }
408
409 scan_in_progress = true;
410 Serial.println("\n━━━ SCAN I2C + TOGGLE ━━━");
411
412 device_count = 0;
413 memset(found_devices, 0, sizeof(found_devices));
414
415 WIRE.setTimeout(50); // Timeout corto para velocidad
416
417 for (uint8_t addr = 0x08; addr <= 0x77; addr++) {
418 /*
419 WIRE.beginTransmission(addr);
420 uint8_t error = WIRE.endTransmission();
421
422 if (error == 0) {
423 found_devices[device_count++] = addr;
424 Serial.printf(" [%02d] 0x%02X (%3d) → ", device_count, addr, addr);
425
426 // Enviar toggle automático SIN esperar respuesta
427 if (sendCommandFast(addr, CMD_RELAY_TOGGLE)) {
428 Serial.println("TOGGLE ✓");
429 } else {
430 Serial.println("TOGGLE ✗");
431 }
432
433 delay(DEVICE_DELAY); // Delay optimizado: 30ms
434 }
435 */
436 uint8_t dummy;
437 // auxReadByte retorna true si el dispositivo hace ACK
438 if (imu.auxReadByte(addr, 0x00, dummy)) {
439 found_devices[device_count++] = addr;
440 Serial.printf(" [%02d] 0x%02X (%3d) → ", device_count, addr, addr);
441
442 if (imu.auxWriteCommand(addr, CMD_RELAY_TOGGLE)) {
443 Serial.println("TOGGLE ✓");
444 } else {
445 Serial.println("TOGGLE ✗");
446 }
447 delay(DEVICE_DELAY);
448 }
449 }
450
451 Serial.println("━━━━━━━━━━━━━━━━");
452 Serial.printf("Total: %d dispositivos\n\n", device_count);
453
454 if (device_count == 0) {
455 Serial.println("⚠ No se encontraron dispositivos I2C");
456 Serial.println(" Verifica conexiones y pull-ups\n");
457 }
458
459 scan_in_progress = false;
460}
461
462
463
464void toggleRelay(String cmd) {
465 // Verificar si estamos en modo seguro
466 if (i2c_safe_mode) {
467 Serial.println("[ERROR] I2C en MODO SEGURO - comando no disponible");
468 return;
469 }
470
471 String addr_str = cmd.substring(2);
472 addr_str.trim();
473
474 uint8_t address = parseHex(addr_str);
475 if (address == 0) {
476 Serial.println("ERROR: Direccion invalida");
477 return;
478 }
479
480 if (sendCommand(address, CMD_RELAY_TOGGLE)) {
481 Serial.printf("[OK] Pulso relay en 0x%02X (10ms)\n", address);
482 } else {
483 Serial.printf("[FAIL] No se pudo enviar pulso a 0x%02X\n", address);
484 }
485}
486
487void relayOn(String cmd) {
488 String addr_str = cmd.substring(3);
489 addr_str.trim();
490
491 uint8_t address = parseHex(addr_str);
492 if (address == 0) {
493 Serial.println("ERROR: Direccion invalida");
494 return;
495 }
496
497 if (sendCommand(address, CMD_RELAY_ON)) {
498 Serial.printf("[OK] Relay ON en 0x%02X\n", address);
499 } else {
500 Serial.printf("[FAIL] No se pudo encender relay en 0x%02X\n", address);
501 }
502}
503
504void relayOff(String cmd) {
505 String addr_str = cmd.substring(4);
506 addr_str.trim();
507
508 uint8_t address = parseHex(addr_str);
509 if (address == 0) {
510 Serial.println("ERROR: Direccion invalida");
511 return;
512 }
513
514 if (sendCommand(address, CMD_RELAY_OFF)) {
515 Serial.printf("[OK] Relay OFF en 0x%02X\n", address);
516 } else {
517 Serial.printf("[FAIL] No se pudo apagar relay en 0x%02X\n", address);
518 }
519}
520
521// ═══════════════════════════════════════════════════════════
522// LECTURA DIGITAL PA0
523// ═══════════════════════════════════════════════════════════
524
525void readADC(String cmd) {
526 String addr_str = cmd.substring(4);
527 addr_str.trim();
528
529 uint8_t address = parseHex(addr_str);
530 if (address == 0) {
531 Serial.println("ERROR: Direccion invalida");
532 return;
533 }
534
535 uint16_t raw = 0;
536 if (readADC12(address, raw)) {
537 float voltage = raw * (3.3f / 4095.0f);
538 Serial.printf("[OK] 0x%02X - ADC: %u (%.3f V)\n", address, raw, voltage);
539 } else {
540 Serial.printf("[FAIL] No se pudo leer ADC en 0x%02X\n", address);
541 }
542}
543
544void readDigital(String cmd) {
545 String addr_str = cmd.substring(5);
546 addr_str.trim();
547
548 uint8_t address = parseHex(addr_str);
549 if (address == 0) {
550 Serial.println("ERROR: Direccion invalida");
551 return;
552 }
553
554 uint8_t state = 0;
555 bool success = readPA0Digital(address, &state);
556
557 if (success) {
558 Serial.printf("[OK] 0x%02X - PA0 Digital: %s\n", address, state ? "HIGH" : "LOW");
559 } else {
560 Serial.printf("[FAIL] No se pudo leer PA0 digital en 0x%02X\n", address);
561 }
562}
563
564bool readPA0Digital(uint8_t address, uint8_t* state) {
565 // Enviar comando de lectura PA0 digital
566 WIRE.beginTransmission(address);
567 WIRE.write(CMD_PA4_DIGITAL);
568 uint8_t error = WIRE.endTransmission();
569
570 if (error != 0) {
571 updateDigitalStats(address, false, 0);
572 return false;
573 }
574
575 // Esperar procesamiento
576 delay(5);
577
578 // Leer respuesta
579 WIRE.setTimeout(50);
580 uint8_t bytesRead = WIRE.requestFrom(address, (uint8_t)1);
581
582 if (bytesRead == 1) {
583 uint8_t response = WIRE.read();
584
585 // El estado digital está en el nibble alto (bit 7-4)
586 // 0xF0 = HIGH, 0x00 = LOW
587 *state = (response & 0xF0) ? 1 : 0;
588
589 updateDigitalStats(address, true, *state);
590 return true;
591 }
592
593 updateDigitalStats(address, false, 0);
594 return false;
595}
596
597bool readADC12(uint8_t address, uint16_t &raw) {
598 uint8_t hi = 0, lo = 0;
599
600 // El slave necesita transacciones SEPARADAS: write (comando) → STOP,
601 // slave procesa + convierte ADC (~20ms), luego read independiente.
602 // auxReadByte hace write+RSTART+read combinado → el slave no alcanza
603 // a llamar HAL_I2C_Slave_Transmit → NACK.
604 // auxReadResponse hace solo [START, addr+R, 1byte, STOP].
605
606 if (!imu.auxWriteCommand(address, CMD_ADC_PA1_HSB)) return false;
607 delay(25); // slave: main loop + conversión ADC (HAL_ADC_PollForConversion, max 20ms)
608 if (!imu.auxReadResponse(address, hi)) return false;
609
610 // LSB: el slave usa valor cacheado (adc_last_read < 50ms → mismo sample que HSB)
611 if (!imu.auxWriteCommand(address, CMD_ADC_PA1_LSB)) return false;
612 delay(5); // sin conversión nueva, solo tiempo de main loop
613 if (!imu.auxReadResponse(address, lo)) return false;
614
615 raw = ((uint16_t)(hi & 0x0F) << 8) | lo; // 0–4095
616 return true;
617}
618
619void updateDigitalStats(uint8_t address, bool success, uint8_t state) {
620 // Buscar dispositivo en el array
621 int index = -1;
622 for (int i = 0; i < digital_stats_count; i++) {
623 if (digital_stats[i].address == address) {
624 index = i;
625 break;
626 }
627 }
628
629 // Si no existe, agregarlo
630 if (index == -1 && digital_stats_count < 128) {
631 index = digital_stats_count++;
632 digital_stats[index].address = address;
633 digital_stats[index].read_count = 0;
634 digital_stats[index].fail_count = 0;
635 digital_stats[index].last_state = 0;
636 digital_stats[index].last_read_time = 0;
637 }
638
639 if (index >= 0) {
640 digital_stats[index].read_count++;
641 if (!success) {
642 digital_stats[index].fail_count++;
643 } else {
644 digital_stats[index].last_state = state;
645 }
646 digital_stats[index].last_read_time = millis();
647 }
648}
649
650void showDigitalStats() {
651 if (digital_stats_count == 0) {
652 Serial.println("No hay estadisticas de lectura digital");
653 return;
654 }
655
656 Serial.println("\n╔════════════════════════════════════════════════════════╗");
657 Serial.println("║ ESTADISTICAS DE LECTURA DIGITAL PA0 ║");
658 Serial.println("╠════════════════════════════════════════════════════════╣");
659 Serial.println("║ Addr │ Lecturas │ Fallos │ Tasa OK │ Estado │ Tiempo ║");
660 Serial.println("╠════════════════════════════════════════════════════════╣");
661
662 for (int i = 0; i < digital_stats_count; i++) {
663 DigitalReadStats* stats = &digital_stats[i];
664
665 float success_rate = 0.0;
666 if (stats->read_count > 0) {
667 success_rate = ((float)(stats->read_count - stats->fail_count) / stats->read_count) * 100.0;
668 }
669
670 uint32_t time_since = (millis() - stats->last_read_time) / 1000; // segundos
671
672 Serial.printf("║ 0x%02X │ %8lu │ %6lu │ %6.1f%% │ %4s │ %4lus ║\n",
673 stats->address,
674 stats->read_count,
675 stats->fail_count,
676 success_rate,
677 stats->last_state ? "HIGH" : "LOW",
678 time_since);
679 }
680
681 Serial.println("╚════════════════════════════════════════════════════════╝");
682}
683
684// ═══════════════════════════════════════════════════════════
685// TEST DE LECTURA DIGITAL CONTINUA
686// ═══════════════════════════════════════════════════════════
687
688void startReadTest(String cmd) {
689 // Formato: "readtest <intervalo_ms> <duracion_ms>"
690 // Ejemplo: "readtest 100 10000" = leer cada 100ms durante 10 segundos
691 cmd.trim();
692 int space1 = cmd.indexOf(' ', 9);
693
694 if (space1 == -1) {
695 Serial.println("ERROR: Formato incorrecto");
696 Serial.println("Uso: readtest <intervalo_ms> <duracion_ms>");
697 Serial.println("Ejemplo: readtest 100 10000 (leer cada 100ms por 10 seg)");
698 return;
699 }
700
701 String interval_str = cmd.substring(9, space1);
702 String duration_str = cmd.substring(space1 + 1);
703
704 read_test_interval = interval_str.toInt();
705 read_test_duration = duration_str.toInt();
706
707 // Validar rango
708 if (read_test_interval < 30) read_test_interval = 30;
709 if (read_test_interval > 1000) read_test_interval = 1000;
710 if (read_test_duration < 1000) read_test_duration = 1000;
711
712 // Reset contadores de lectura digital
713 digital_stats_count = 0;
714 memset(digital_stats, 0, sizeof(digital_stats));
715
716 read_test_active = true;
717 read_test_start_time = millis();
718 last_scan_time = millis();
719
720 Serial.println("\n[READ TEST MODE ACTIVADO]");
721 Serial.printf("Intervalo: %lu ms\n", read_test_interval);
722 Serial.printf("Duración: %lu ms (%.1f seg)\n", read_test_duration, read_test_duration / 1000.0);
723 Serial.println("Dispositivos a leer: ");
724 for (int i = 0; i < device_count; i++) {
725 Serial.printf(" 0x%02X\n", found_devices[i]);
726 }
727 Serial.println("Usa 'stopread' para detener antes\n");
728}
729
730void stopReadTest() {
731 read_test_active = false;
732 uint32_t elapsed = millis() - read_test_start_time;
733 Serial.println("\n[READ TEST MODE DETENIDO]");
734 Serial.printf("Tiempo transcurrido: %.2f segundos\n", elapsed / 1000.0);
735 showDigitalStats();
736}
737
738void testReadDigital() {
739 WIRE.setTimeout(50);
740
741 for (int i = 0; i < device_count; i++) {
742 uint8_t addr = found_devices[i];
743 uint8_t state = 0;
744 readPA0Digital(addr, &state);
745 delay(read_test_interval / device_count); // Distribuir tiempo entre dispositivos
746 }
747}
748
749bool sendCommand(uint8_t address, uint8_t command) {
750 /*WIRE.beginTransmission(address);
751 WIRE.write(command);
752 uint8_t error = WIRE.endTransmission();
753
754 if (error != 0) return false;
755
756 // Esperar respuesta con timeout más generoso
757 delay(20); // Dar tiempo al esclavo para procesar
758 WIRE.setTimeout(100);
759 uint8_t bytesRead = WIRE.requestFrom(address, (uint8_t)1);
760 if (bytesRead == 1) {
761 WIRE.read(); // Leer y descartar respuesta
762 return true;
763 }
764
765 return false;*/
766 return imu.auxWriteCommand(address, command);
767}
768
769// Función para leer respuesta del dispositivo
770uint8_t readResponse(uint8_t address) {
771 delay(10); // Pequeño delay para que el dispositivo prepare respuesta
772 WIRE.setTimeout(100);
773 uint8_t bytesRead = WIRE.requestFrom(address, (uint8_t)1);
774 if (bytesRead == 1) {
775 return WIRE.read();
776 }
777 return 0xFF; // Error: no se recibió respuesta
778}
779
780// Versión rápida: enviar comando SIN esperar respuesta
781bool sendCommandFast(uint8_t address, uint8_t command) {
782 WIRE.beginTransmission(address);
783 WIRE.write(command);
784 uint8_t error = WIRE.endTransmission();
785
786 // Actualizar contador de errores
787 updateDeviceError(address, error == 0);
788
789 return (error == 0); // Solo verificar que se envió
790}
791
792void updateDeviceError(uint8_t address, bool success) {
793 // Buscar dispositivo en el array
794 int index = -1;
795 for (int i = 0; i < device_error_count; i++) {
796 if (device_errors[i].address == address) {
797 index = i;
798 break;
799 }
800 }
801
802 // Si no existe, agregarlo
803 if (index == -1 && device_error_count < 128) {
804 index = device_error_count++;
805 device_errors[index].address = address;
806 device_errors[index].error_count = 0;
807 device_errors[index].success_count = 0;
808 device_errors[index].last_error_time = 0;
809 }
810
811 // Actualizar contadores
812 if (index != -1) {
813 if (success) {
814 device_errors[index].success_count++;
815 } else {
816 device_errors[index].error_count++;
817 device_errors[index].last_error_time = millis();
818 }
819 }
820}
821
822void startTest(String cmd) {
823 // Formato: "test <intervalo_ms> <duracion_ms>"
824 // Ejemplo: "test 50 5000" = test cada 50ms durante 5 segundos
825 cmd.trim();
826 int space1 = cmd.indexOf(' ', 5);
827
828 if (space1 == -1) {
829 Serial.println("ERROR: Formato incorrecto");
830 Serial.println("Uso: test <intervalo_ms> <duracion_ms>");
831 Serial.println("Ejemplo: test 50 5000 (test cada 50ms por 5 seg)");
832 return;
833 }
834
835 String interval_str = cmd.substring(5, space1);
836 String duration_str = cmd.substring(space1 + 1);
837
838 test_interval = interval_str.toInt();
839 test_duration = duration_str.toInt();
840
841 // Validar rango
842 if (test_interval < 30) test_interval = 30;
843 if (test_interval > 1000) test_interval = 1000;
844 if (test_duration < 1000) test_duration = 1000;
845
846 // Reset contadores
847 device_error_count = 0;
848 memset(device_errors, 0, sizeof(device_errors));
849
850 test_mode_active = true;
851 test_start_time = millis();
852 last_scan_time = millis();
853
854 Serial.println("\n[TEST MODE ACTIVADO]");
855 Serial.printf("Intervalo: %lu ms\n", test_interval);
856 Serial.printf("Duración: %lu ms (%.1f seg)\n", test_duration, test_duration / 1000.0);
857 Serial.println("Usa 'stoptest' para detener antes\n");
858}
859
860void stopTest() {
861 test_mode_active = false;
862 uint32_t elapsed = millis() - test_start_time;
863 Serial.println("\n[TEST MODE DETENIDO]");
864 Serial.printf("Tiempo transcurrido: %.2f segundos\n\n", elapsed / 1000.0);
865}
866
867void testDevices() {
868 // Verificar si estamos en modo seguro
869 if (i2c_safe_mode) {
870 Serial.println("[ERROR] I2C en MODO SEGURO - comando no disponible");
871 return;
872 }
873
874 WIRE.setTimeout(50);
875
876 for (int i = 0; i < device_count; i++) {
877 uint8_t addr = found_devices[i];
878 sendCommandFast(addr, CMD_RELAY_TOGGLE);
879 delay(test_interval / device_count); // Distribuir tiempo entre dispositivos
880 }
881}
882
883void showTestResults() {
884 Serial.println("\n╔═══════════════════════════════════════════════════╗");
885 Serial.println("║ RESULTADOS DE TEST I2C ║");
886 Serial.println("╚═══════════════════════════════════════════════════╝\n");
887
888 if (device_error_count == 0) {
889 Serial.println("No hay datos de test disponibles\n");
890 return;
891 }
892
893 uint32_t total_success = 0;
894 uint32_t total_errors = 0;
895
896 Serial.println("Addr | Success | Errors | Tasa Éxito | Último Error");
897 Serial.println("------|---------|--------|------------|-------------");
898
899 for (int i = 0; i < device_error_count; i++) {
900 uint8_t addr = device_errors[i].address;
901 uint32_t success = device_errors[i].success_count;
902 uint32_t errors = device_errors[i].error_count;
903 uint32_t total = success + errors;
904 float rate = total > 0 ? (success * 100.0 / total) : 0;
905
906 total_success += success;
907 total_errors += errors;
908
909 Serial.printf("0x%02X | %7lu | %6lu | %6.2f%% | ",
910 addr, success, errors, rate);
911
912 if (errors > 0) {
913 uint32_t since_error = (millis() - device_errors[i].last_error_time) / 1000;
914 Serial.printf("%lu seg\n", since_error);
915 } else {
916 Serial.println("Nunca");
917 }
918 }
919
920 Serial.println("------|---------|--------|------------|-------------");
921 uint32_t grand_total = total_success + total_errors;
922 float overall_rate = grand_total > 0 ? (total_success * 100.0 / grand_total) : 0;
923 Serial.printf("TOTAL | %7lu | %6lu | %6.2f%% |\n\n",
924 total_success, total_errors, overall_rate);
925}
926
927uint8_t parseHex(String hex_str) {
928 hex_str.trim();
929
930 // Convertir de hexadecimal
931 char* endptr;
932 long addr = strtol(hex_str.c_str(), &endptr, 16);
933
934 if (*endptr != '\0' || addr < 0x08 || addr > 0x77) {
935 return 0;
936 }
937
938 return (uint8_t)addr;
939}
940
941bool parseByteAuto(String value_str, uint8_t* out) {
942 value_str.trim();
943 char* endptr;
944 long value = strtol(value_str.c_str(), &endptr, 0);
945
946 if (*endptr != '\0' || value < 0 || value > 0xFF) {
947 return false;
948 }
949
950 *out = (uint8_t)value;
951 return true;
952}
953
954void startScanLoop() {
955 scan_loop_active = true;
956 last_scan_time = millis();
957 Serial.println("\n[OK] Scan continuo ACTIVADO");
958 Serial.printf(" Intervalo: %lu ms\n", scan_interval);
959 Serial.println(" Usa 'stop' para detener\n");
960}
961
962void stopScanLoop() {
963 scan_loop_active = false;
964 Serial.println("\n[OK] Scan continuo DETENIDO\n");
965}
966
967// ═══════════════════════════════════════════════════════════
968// FUNCIONES PWM / BUZZER
969// ═══════════════════════════════════════════════════════════
970
971void pwmOff() {
972 Serial.println("\n━━━ APAGAR PWM (TODOS) ━━━");
973 int count = 0;
974 for (int i = 0; i < device_count; i++) {
975 uint8_t addr = found_devices[i];
976 if (sendCommand(addr, CMD_PWM_OFF)) {
977 Serial.printf(" [OK] 0x%02X - PWM apagado\n", addr);
978 count++;
979 } else {
980 Serial.printf(" [FAIL] 0x%02X\n", addr);
981 }
982 delay(10);
983 }
984 Serial.printf("\nTotal: %d/%d dispositivos\n", count, device_count);
985}
986
987void pwmCommand(String cmd) {
988 // Formato: "pwm XX NIVEL"
989 // XX = dirección hex, NIVEL = 25, 50, 75, 100, off
990 cmd.trim();
991 int space = cmd.indexOf(' ', 4);
992
993 if (space == -1) {
994 Serial.println("ERROR: Formato incorrecto");
995 Serial.println("Uso: pwm XX NIVEL");
996 Serial.println("Ejemplo: pwm 20 50 (PWM 50% en 0x20)");
997 Serial.println("Niveles: 25, 50, 75, 100, off");
998 return;
999 }
1000
1001 String addr_str = cmd.substring(4, space);
1002 String level_str = cmd.substring(space + 1);
1003 addr_str.trim();
1004 level_str.trim();
1005
1006 uint8_t address = parseHex(addr_str);
1007 if (address == 0) {
1008 Serial.println("ERROR: Direccion invalida");
1009 return;
1010 }
1011
1012 uint8_t pwm_cmd = CMD_PWM_OFF;
1013 String level_name = "OFF";
1014
1015 if (level_str == "off" || level_str == "0") {
1016 pwm_cmd = CMD_PWM_OFF;
1017 level_name = "OFF";
1018 } else if (level_str == "25") {
1019 pwm_cmd = CMD_PWM_25;
1020 level_name = "25% (200Hz)";
1021 } else if (level_str == "50") {
1022 pwm_cmd = CMD_PWM_50;
1023 level_name = "50% (500Hz)";
1024 } else if (level_str == "75") {
1025 pwm_cmd = CMD_PWM_75;
1026 level_name = "75% (1000Hz)";
1027 } else if (level_str == "100") {
1028 pwm_cmd = CMD_PWM_100;
1029 level_name = "100% (2000Hz)";
1030 } else {
1031 Serial.println("ERROR: Nivel invalido. Usa: 25, 50, 75, 100, off");
1032 return;
1033 }
1034
1035 if (sendCommand(address, pwm_cmd)) {
1036 Serial.printf("[OK] PWM %s en 0x%02X\n", level_name.c_str(), address);
1037 } else {
1038 Serial.printf("[FAIL] No se pudo configurar PWM en 0x%02X\n", address);
1039 }
1040}
1041
1042// ═══════════════════════════════════════════════════════════
1043// FUNCIONES NEOPIXEL
1044// ═══════════════════════════════════════════════════════════
1045
1046void neoCommand(String cmd) {
1047 // Formato: "neo XX COLOR"
1048 // XX = dirección hex, COLOR = red/green/blue/white/off o 1/2/3/4/0
1049 cmd.trim();
1050 int space = cmd.indexOf(' ', 4);
1051
1052 if (space == -1) {
1053 Serial.println("ERROR: Formato incorrecto");
1054 Serial.println("Uso: neo XX COLOR");
1055 Serial.println("Ejemplo: neo 20 1 (NeoPixel rojo en 0x20)");
1056 Serial.println("Colores: red, green, blue, white, off");
1057 Serial.println("Numeros: 1=red, 2=green, 3=blue, 4=white, 0=off");
1058 return;
1059 }
1060
1061 String addr_str = cmd.substring(4, space);
1062 String color_str = cmd.substring(space + 1);
1063 addr_str.trim();
1064 color_str.trim();
1065 color_str.toLowerCase();
1066
1067 uint8_t address = parseHex(addr_str);
1068 if (address == 0) {
1069 Serial.println("ERROR: Direccion invalida");
1070 return;
1071 }
1072
1073 uint8_t neo_cmd = 0;
1074 String color_name = "";
1075
1076 if (color_str == "red") {
1077 neo_cmd = CMD_RED;
1078 color_name = "ROJO";
1079 } else if (color_str == "1") {
1080 neo_cmd = CMD_RED;
1081 color_name = "ROJO";
1082 } else if (color_str == "green") {
1083 neo_cmd = CMD_GREEN;
1084 color_name = "VERDE";
1085 } else if (color_str == "2") {
1086 neo_cmd = CMD_GREEN;
1087 color_name = "VERDE";
1088 } else if (color_str == "blue") {
1089 neo_cmd = CMD_BLUE;
1090 color_name = "AZUL";
1091 } else if (color_str == "3") {
1092 neo_cmd = CMD_BLUE;
1093 color_name = "AZUL";
1094 } else if (color_str == "white") {
1095 neo_cmd = CMD_WHITE;
1096 color_name = "BLANCO";
1097 } else if (color_str == "4") {
1098 neo_cmd = CMD_WHITE;
1099 color_name = "BLANCO";
1100 } else if (color_str == "off") {
1101 neo_cmd = CMD_OFF;
1102 color_name = "APAGADO";
1103 } else if (color_str == "0") {
1104 neo_cmd = CMD_OFF;
1105 color_name = "APAGADO";
1106 } else {
1107 Serial.println("ERROR: Color invalido. Usa: red, green, blue, white, off");
1108 Serial.println(" O numeros: 1=red, 2=green, 3=blue, 4=white, 0=off");
1109 return;
1110 }
1111
1112 if (sendCommand(address, neo_cmd)) {
1113 Serial.printf("[OK] NeoPixel %s en 0x%02X\n", color_name.c_str(), address);
1114 } else {
1115 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1116 }
1117}
1118
1119void neoRed(String cmd) {
1120 String addr_str = cmd.substring(4);
1121 addr_str.trim();
1122
1123 uint8_t address = parseHex(addr_str);
1124 if (address == 0) {
1125 Serial.println("ERROR: Direccion invalida");
1126 return;
1127 }
1128
1129 if (sendCommand(address, CMD_RED)) {
1130 Serial.printf("[OK] NeoPixel ROJO en 0x%02X\n", address);
1131 } else {
1132 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1133 }
1134}
1135
1136void neoGreen(String cmd) {
1137 String addr_str = cmd.substring(6);
1138 addr_str.trim();
1139
1140 uint8_t address = parseHex(addr_str);
1141 if (address == 0) {
1142 Serial.println("ERROR: Direccion invalida");
1143 return;
1144 }
1145
1146 if (sendCommand(address, CMD_GREEN)) {
1147 Serial.printf("[OK] NeoPixel VERDE en 0x%02X\n", address);
1148 } else {
1149 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1150 }
1151}
1152
1153void neoBlue(String cmd) {
1154 String addr_str = cmd.substring(5);
1155 addr_str.trim();
1156
1157 uint8_t address = parseHex(addr_str);
1158 if (address == 0) {
1159 Serial.println("ERROR: Direccion invalida");
1160 return;
1161 }
1162
1163 if (sendCommand(address, CMD_BLUE)) {
1164 Serial.printf("[OK] NeoPixel AZUL en 0x%02X\n", address);
1165 } else {
1166 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1167 }
1168}
1169
1170void neoWhite(String cmd) {
1171 String addr_str = cmd.substring(6);
1172 addr_str.trim();
1173
1174 uint8_t address = parseHex(addr_str);
1175 if (address == 0) {
1176 Serial.println("ERROR: Direccion invalida");
1177 return;
1178 }
1179
1180 if (sendCommand(address, CMD_WHITE)) {
1181 Serial.printf("[OK] NeoPixel BLANCO en 0x%02X\n", address);
1182 } else {
1183 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1184 }
1185}
1186
1187void neoOff() {
1188 Serial.println("\n━━━ APAGAR NEOPIXEL (TODOS) ━━━");
1189 int count = 0;
1190 for (int i = 0; i < device_count; i++) {
1191 uint8_t addr = found_devices[i];
1192 if (sendCommand(addr, CMD_OFF)) {
1193 Serial.printf(" [OK] 0x%02X - NeoPixel apagado\n", addr);
1194 count++;
1195 } else {
1196 Serial.printf(" [FAIL] 0x%02X\n", addr);
1197 }
1198 delay(10);
1199 }
1200 Serial.printf("\nTotal: %d/%d dispositivos\n", count, device_count);
1201}
1202
1203// ═══════════════════════════════════════════════════════════
1204// FUNCIÓN: CAMBIAR DIRECCIÓN I2C
1205// ═══════════════════════════════════════════════════════════
1206void changeI2CAddress(String cmd) {
1207 // Formato: "ch XX YY" donde XX=dirección actual, YY=nueva dirección
1208 cmd.trim();
1209 int firstSpace = cmd.indexOf(' ');
1210 int secondSpace = cmd.indexOf(' ', firstSpace + 1);
1211
1212 if (firstSpace == -1 || secondSpace == -1) {
1213 Serial.println("[ERROR] Formato: ch XX YY");
1214 Serial.println(" XX = dirección actual (hex)");
1215 Serial.println(" YY = nueva dirección (hex)");
1216 return;
1217 }
1218
1219 String oldAddrStr = cmd.substring(firstSpace + 1, secondSpace);
1220 String newAddrStr = cmd.substring(secondSpace + 1);
1221
1222 oldAddrStr.trim();
1223 newAddrStr.trim();
1224
1225 // Convertir direcciones de hexadecimal
1226 uint8_t oldAddr = (uint8_t)strtol(oldAddrStr.c_str(), NULL, 16);
1227 uint8_t newAddr = (uint8_t)strtol(newAddrStr.c_str(), NULL, 16);
1228
1229 // Validar rangos
1230 if (oldAddr < 0x08 || oldAddr > 0x77) {
1231 Serial.printf("[ERROR] Dirección actual 0x%02X fuera de rango (0x08-0x77)\n", oldAddr);
1232 return;
1233 }
1234
1235 if (newAddr < 0x08 || newAddr > 0x77) {
1236 Serial.printf("[ERROR] Nueva dirección 0x%02X fuera de rango (0x08-0x77)\n", newAddr);
1237 return;
1238 }
1239
1240 Serial.println("\n━━━ CAMBIAR DIRECCIÓN I2C ━━━");
1241 Serial.printf("Dispositivo: 0x%02X → 0x%02X\n", oldAddr, newAddr);
1242 Serial.println("⚠️ ADVERTENCIA: Esta operación es PERMANENTE (guarda en Flash)");
1243 Serial.println("");
1244
1245 // PROTOCOLO CORRECTO según firmware i2c_slave/main.c:
1246 // 1. Enviar 0x3D (CMD_SET_I2C_ADDR) - activa modo "esperar nueva dirección"
1247 // 2. El firmware responde 0x0D y espera el siguiente byte como nueva dirección
1248 // 3. Enviar la nueva dirección como siguiente comando
1249 // 4. Firmware guarda en Flash y responde 0x0D
1250 // 5. Requiere reset para aplicar
1251
1252 Serial.print("Paso 1: Activando modo cambio dirección... ");
1253
1254 WIRE.beginTransmission(oldAddr);
1255 WIRE.write(CMD_SET_I2C_ADDR); // 0x3D
1256 uint8_t error = WIRE.endTransmission();
1257
1258 if (error != 0) {
1259 Serial.printf("[FAIL] Error I2C: %d\n", error);
1260 Serial.println(" El dispositivo no respondió. Verifica:");
1261 Serial.println(" - Dirección correcta (usa 'scan' para confirmar)");
1262 Serial.println(" - Dispositivo conectado y funcionando");
1263 return;
1264 }
1265
1266 delay(50); // Delay importante: firmware necesita procesar comando
1267
1268 // Leer respuesta
1269 uint8_t bytesRead = WIRE.requestFrom(oldAddr, (uint8_t)1);
1270 uint8_t response = 0xFF;
1271 if (bytesRead == 1) {
1272 response = WIRE.read();
1273 }
1274
1275 if (response == 0x0D) { // RESP_I2C_ADDR_SET
1276 Serial.println("[OK] Modo activado");
1277 } else {
1278 Serial.printf("[WARN] Respuesta: 0x%02X (esperaba 0x0D)\n", response);
1279 // Continuamos, puede funcionar igual
1280 }
1281
1282 delay(50); // Delay adicional antes del siguiente comando
1283
1284 // Paso 2: Enviar nueva dirección
1285 Serial.printf("Paso 2: Enviando nueva dirección 0x%02X... ", newAddr);
1286
1287 WIRE.beginTransmission(oldAddr);
1288 WIRE.write(newAddr); // Nueva dirección como dato
1289 error = WIRE.endTransmission();
1290
1291 if (error != 0) {
1292 Serial.printf("[FAIL] Error I2C: %d\n", error);
1293 Serial.println(" No se pudo completar el cambio");
1294 return;
1295 }
1296
1297 delay(100); // Delay largo: escritura Flash puede tomar tiempo
1298
1299 // Leer confirmación
1300 bytesRead = WIRE.requestFrom(oldAddr, (uint8_t)1);
1301 response = 0xFF;
1302 if (bytesRead == 1) {
1303 response = WIRE.read();
1304 }
1305
1306 if (response == 0x0D) { // RESP_I2C_ADDR_SET
1307 Serial.println("[OK] Guardado en Flash");
1308 } else {
1309 Serial.printf("[WARN] Respuesta: 0x%02X\n", response);
1310 }
1311
1312 Serial.println("");
1313 Serial.println("╔════════════════════════════════════════════╗");
1314 Serial.println("║ CAMBIO DE DIRECCIÓN COMPLETADO ║");
1315 Serial.println("╚════════════════════════════════════════════╝");
1316 Serial.println("");
1317 Serial.println(" IMPORTANTE - PRÓXIMOS PASOS:");
1318 Serial.println(" 1. La nueva dirección está GUARDADA EN FLASH");
1319 Serial.println(" 2. DEBES RESETEAR el dispositivo para aplicar");
1320 Serial.println(" 3. Opciones de reset:");
1321 Serial.println(" • Desconecta y reconecta el dispositivo");
1322 Serial.println(" • Usa el botón de reset físico");
1323 Serial.println(" • Cicla la alimentación");
1324 Serial.printf(" 4. Después del reset: dispositivo en 0x%02X\n", newAddr);
1325 Serial.println("");
1326 Serial.println("Verificación:");
1327 Serial.println(" 1. Resetea el dispositivo");
1328 Serial.println(" 2. Ejecuta: scan");
1329 Serial.printf(" 3. Busca la dirección 0x%02X en la lista\n", newAddr);
1330 Serial.println("");
1331}
1332
1333void saveColorPreset(String cmd) {
1334 // Formato: save_color <addr> <pos> <r> <g> <b>
1335 cmd.trim();
1336
1337 String tokens[6];
1338 int token_count = 0;
1339 int i = 0;
1340 while (i < cmd.length() && token_count < 6) {
1341 while (i < cmd.length() && cmd.charAt(i) == ' ') {
1342 i++;
1343 }
1344 if (i >= cmd.length()) {
1345 break;
1346 }
1347
1348 int start = i;
1349 while (i < cmd.length() && cmd.charAt(i) != ' ') {
1350 i++;
1351 }
1352 tokens[token_count++] = cmd.substring(start, i);
1353 }
1354
1355 if (token_count < 6 || tokens[0] != "save_color") {
1356 Serial.println("[ERROR] Formato: save_color <addr> <pos> <r> <g> <b>");
1357 Serial.println(" pos: 1=slot rojo, 2=slot verde, 3=slot azul");
1358 Serial.println(" r/g/b: 0..255 (decimal) o 0x00..0xFF");
1359 return;
1360 }
1361
1362 String addr_str = tokens[1];
1363 String pos_str = tokens[2];
1364 String r_str = tokens[3];
1365 String g_str = tokens[4];
1366 String b_str = tokens[5];
1367
1368 addr_str.trim();
1369 pos_str.trim();
1370 r_str.trim();
1371 g_str.trim();
1372 b_str.trim();
1373
1374 uint8_t addr = parseHex(addr_str);
1375 if (addr == 0) {
1376 Serial.println("[ERROR] Direccion invalida (0x08-0x77)");
1377 return;
1378 }
1379
1380 uint8_t pos_user = 0;
1381 uint8_t pos = 0;
1382 uint8_t r = 0;
1383 uint8_t g = 0;
1384 uint8_t b = 0;
1385
1386 if (!parseByteAuto(pos_str, &pos_user) || pos_user < 1 || pos_user > 3) {
1387 Serial.println("[ERROR] Posicion invalida. Usa 1, 2 o 3");
1388 return;
1389 }
1390 pos = (uint8_t)(pos_user - 1);
1391 if (!parseByteAuto(r_str, &r) || !parseByteAuto(g_str, &g) || !parseByteAuto(b_str, &b)) {
1392 Serial.println("[ERROR] RGB invalido. Usa 0..255 o 0x00..0xFF");
1393 return;
1394 }
1395
1396 Serial.println("\n━━━ SAVE COLOR PRESET ━━━");
1397 Serial.printf("Dispositivo 0x%02X, slot %u, RGB=(%u,%u,%u)\n", addr, pos_user, r, g, b);
1398
1399 if (!sendCommand(addr, CMD_SAVE_COLOR)) {
1400 Serial.println("[FAIL] No se pudo iniciar save_color");
1401 return;
1402 }
1403
1404 delay(5);
1405 if (!sendCommand(addr, pos)) {
1406 Serial.println("[FAIL] No se pudo enviar posicion");
1407 return;
1408 }
1409
1410 delay(5);
1411 if (!sendCommand(addr, r)) {
1412 Serial.println("[FAIL] No se pudo enviar canal R");
1413 return;
1414 }
1415
1416 delay(5);
1417 if (!sendCommand(addr, g)) {
1418 Serial.println("[FAIL] No se pudo enviar canal G");
1419 return;
1420 }
1421
1422 delay(5);
1423 if (!sendCommand(addr, b)) {
1424 Serial.println("[FAIL] No se pudo enviar canal B");
1425 return;
1426 }
1427
1428 Serial.println("[OK] Color guardado en Flash");
1429 Serial.println("Usa comandos red/green/blue para probar los 3 slots.");
1430}
1431
1432void showHelp() {
1433 Serial.println("\n╔═══════════════════════════════════════╗");
1434 Serial.println("║ COMANDOS DISPONIBLES ║");
1435 Serial.println("╚═══════════════════════════════════════╝");
1436 Serial.println("");
1437 Serial.println("SCAN:");
1438 Serial.println(" s - Escanear dispositivos I2C");
1439 Serial.println(" loop - Scan continuo (1 seg)");
1440 Serial.println(" stop - Detener scan continuo");
1441 Serial.println("");
1442 Serial.println("TEST I2C:");
1443 Serial.println(" test INT DUR - Test asíncrono");
1444 Serial.println(" INT = intervalo (30-1000ms)");
1445 Serial.println(" DUR = duración (ms)");
1446 Serial.println(" stoptest - Detener test y mostrar resultados");
1447 Serial.println(" errors - Mostrar estadísticas de errores");
1448 Serial.println("");
1449 Serial.println("LECTURA DIGITAL PA0:");
1450 Serial.println(" read XX - Leer estado digital PA0");
1451 Serial.println(" dstats - Estadísticas de lecturas PA0");
1452 Serial.println(" readtest I D - Test continuo de lectura PA0");
1453 Serial.println(" I = intervalo (30-1000ms)");
1454 Serial.println(" D = duración (ms)");
1455 Serial.println(" stopread - Detener test de lectura");
1456 Serial.println("");
1457 Serial.println("RELAY:");
1458 Serial.println(" t XX - Pulso relay PB5 (10ms)");
1459 Serial.println(" on XX - Encender relay PB5");
1460 Serial.println(" off XX - Apagar relay PB5");
1461 Serial.println("");
1462 Serial.println("PWM / BUZZER:");
1463 Serial.println(" pwm XX NIVEL - PWM en PA2 (25, 50, 75, 100, off)");
1464 Serial.println(" silence - Apagar PWM en todos los dispositivos");
1465 Serial.println("");
1466 Serial.println("NEOPIXEL:");
1467 Serial.println(" neo XX COLOR - Control NeoPixel (red/green/blue/white/off o 1/2/3/4/0)");
1468 Serial.println(" red XX - NeoPixel rojo");
1469 Serial.println(" green XX - NeoPixel verde");
1470 Serial.println(" blue XX - NeoPixel azul");
1471 Serial.println(" white XX - NeoPixel blanco");
1472 Serial.println(" neooff - Apagar NeoPixel en todos");
1473 Serial.println("");
1474 Serial.println("I2C ADDRESS:");
1475 Serial.println(" ch XX YY - Cambiar dirección I2C");
1476 Serial.println(" XX = dirección actual (hex)");
1477 Serial.println(" YY = nueva dirección (hex)");
1478 Serial.println(" (se guarda en Flash, requiere reset)");
1479 Serial.println(" save_color A P R G B - Guardar color por slot en Flash");
1480 Serial.println(" P: 1=red slot, 2=green slot, 3=blue slot");
1481 Serial.println(" R/G/B: decimal o 0x00..0xFF");
1482 Serial.println("");
1483 Serial.println("FORMATO:");
1484 Serial.println(" XX = Direccion I2C en hexadecimal");
1485 Serial.println("");
1486 Serial.println("EJEMPLOS:");
1487 Serial.println(" s → Scan completo");
1488 Serial.println(" loop → Scan automatico cada 1s");
1489 Serial.println(" stop → Detener scan automatico");
1490 Serial.println(" test 50 10000 → Test cada 50ms por 10 seg");
1491 Serial.println(" stoptest → Detener test y ver errores");
1492 Serial.println(" errors → Ver estadísticas");
1493 Serial.println(" read 20 → Leer PA0 digital en 0x20");
1494 Serial.println(" readtest 50 10000 → Test lectura cada 50ms por 10s");
1495 Serial.println(" stopread → Detener test de lectura");
1496 Serial.println(" dstats → Ver estadísticas PA0");
1497 Serial.println(" pwm 20 50 → PWM 50% (500Hz) en 0x20");
1498 Serial.println(" silence → Apagar PWM en todos");
1499 Serial.println(" neo 20 1 → NeoPixel rojo en 0x20");
1500 Serial.println(" red 20 → NeoPixel rojo en 0x20");
1501 Serial.println(" neooff → Apagar NeoPixel en todos");
1502 Serial.println(" t 20 → Pulso en 0x20");
1503 Serial.println(" on 32 → Encender en 0x32");
1504 Serial.println(" off 20 → Apagar relay en 0x20");
1505 Serial.println(" ch 19 25 → Cambiar 0x19 a 0x25");
1506 Serial.println(" save_color 20 1 0x00 0xff 0x60 → Slot 1 personalizado");
1507 Serial.println("");
1508}