universal_beacon_sdk 1.0.2 copy "universal_beacon_sdk: ^1.0.2" to clipboard
universal_beacon_sdk: ^1.0.2 copied to clipboard

SDK genérico y robusto para detección de beacons BLE. Ideal para control de acceso, sistemas de asistencia, marketing de proximidad, automatización IoT, tracking de assets y aplicaciones empresariales.

Universal Beacon SDK #

pub package License: MIT

SDK genérico y robusto para detección de beacons BLE con detección ultra-rápida (100-300ms) y optimizaciones para sistemas empresariales. Ideal para control de acceso, asistencia, marketing de proximidad, IoT, tracking de assets y aplicaciones de salud.

⚡ Destacados #

  • Detección Ultra-Rápida: De 100ms a 5s según tu caso de uso
  • 4 Configuraciones Optimizadas: ultraFast, balanced, enterprise, batterySaver
  • Escalabilidad: Maneja 100+ beacons con cache inteligente
  • Configuración Total: Control completo de todos los parámetros
  • Producción Lista: Usado en sistemas empresariales reales

🚀 Características Principales #

  • Detección Optimizada: 100-300ms en modo ultraFast, configurable hasta 5s
  • 🎯 4 Presets Listos: ultraFast, accessControl, enterprise, batterySaver
  • 🏢 Escalable: Cache de parsing, priorización, throttling para 100+ beacons
  • 🔧 100% Configurable: Control total de scanMode, intervalos, filtros, optimizaciones
  • 📡 Detección Universal: iBeacons, Eddystone, formatos personalizados
  • 🔋 Monitoreo de Batería: Lectura automática para beacons HOLYIOT (Normal e iBeacon)
  • 🎨 Gestión de Registros: Sistema completo para beacons conocidos
  • 📊 Eventos en Tiempo Real: Streams reactivos para entrada/salida de rango
  • 📏 Cálculo de Distancia: Algoritmos precisos basados en RSSI
  • 🔍 Logging Avanzado: Sistema configurable para debugging
  • 💾 Persistencia: Almacenamiento local con SharedPreferences
  • 📱 Multiplataforma: Android e iOS
  • Eficiencia Energética: Modo batterySaver con consumo mínimo

📦 Instalación #

Añade el SDK a tu pubspec.yaml:

dependencies:
  universal_beacon_sdk: ^1.0.0

Luego ejecuta:

flutter pub get

⚡ Inicio Rápido #

import 'package:universal_beacon_sdk/universal_beacon_sdk.dart';

// 1. Crear instancia del scanner
final scanner = UniversalBeaconScanner();

// 2. Inicializar con configuración ultra-rápida
await scanner.initialize(
  config: BeaconConfig.ultraFast(), // 100-300ms detección
  registeredBeacons: [
    BeaconRegistry(
      id: 'beacon_001',
      uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
      displayName: 'Puerta Principal',
    ),
  ],
);

// 3. Escuchar eventos
scanner.beaconEvents.listen((event) {
  if (event.type == BeaconEventType.detected) {
    print('¡Beacon detectado en ${event.timestamp}ms!');
    print('RSSI: ${event.device.rssi} dBm');
    print('Distancia: ${event.device.distance}m');
  }
});

// 4. Iniciar escaneo
await scanner.startScanning();

🎯 Configuraciones Optimizadas #

⚡ Ultra Rápido (100-300ms) #

Ideal para control de acceso, puertas, torniquetes

await scanner.initialize(
  config: BeaconConfig.ultraFast(),
);

⚖️ Balanceado (500ms) #

Para asistencia, uso general

await scanner.initialize(
  config: BeaconConfig.accessControl(),
);

🏢 Empresarial (100+ beacons) #

Hospitales, almacenes, campus grandes

await scanner.initialize(
  config: BeaconConfig.enterprise(),
);

� Ahorro de Batería #

Apps de larga duración, tracking pasivo

await scanner.initialize(
  config: BeaconConfig.batterySaver(),
);

🔧 Configuración Personalizada #

Control total de todos los parámetros

await scanner.initialize(
  config: BeaconConfig(
    // Velocidad BLE
    scanMode: ScanMode.lowLatency,  // opportunistic, lowPower, balanced, lowLatency
    
    // Intervalos (cualquier valor en ms)
    scanInterval: 250,              // ← Puedes poner 100, 250, 500, 1000, etc.
    minUpdateInterval: 50,          // ← Control por dispositivo
    streamThrottle: 200,            // ← Actualización de UI
    
    // Filtros
    minRssi: -85,                   // De -100 a 0
    maxDistance: 15.0,              // En metros
    deviceLostTimeout: 10,          // En segundos
    
    // Optimizaciones
    prioritizeRegisteredBeacons: true,   // Beacons conocidos primero
    immediateEventEmission: true,        // Eventos instantáneos
    enableParsingCache: true,            // Cache para reducir CPU 70%
    iBeaconOnly: false,                  // Solo iBeacons o todos
    
    // Debug
    logLevel: 2,                    // 0=errors, 1=warnings, 2=info, 3=verbose
  ),
);

📊 Tabla Comparativa de Presets #

Elige el preset correcto según tus necesidades:

Preset Detección Batería Beacons Uso Ideal Trade-offs
ultraFast 100-300ms Alta 🔴 <50 Control de acceso, puertas, torniquetes donde el usuario espera respuesta inmediata Consume más batería, no recomendado para apps que corren todo el día
accessControl ⚖️ 500ms Media 🟡 <100 Asistencia escolar/laboral, check-in/check-out, monitoreo de presencia Balance óptimo para mayoría de casos
enterprise 🏢 1-1.5s Media 🟡 100+ Hospitales, almacenes, campus con muchos beacons simultáneos Detección más lenta pero maneja gran escala
batterySaver 🔋 3-7s Mínima 🟢 Cualquiera Tracking de assets a largo plazo, monitoreo pasivo, apps que corren días Detección muy lenta, no para interacción

🎯 ¿Cuál Preset Usar? #

Usa ultraFast si:

  • ✅ Necesitas respuesta inmediata (<500ms)
  • ✅ Usuario interactúa activamente (ej: abrir puerta)
  • ✅ La app solo escanea cuando se necesita
  • ❌ NO si la app corre todo el día

Usa accessControl si:

  • ✅ Necesitas buen balance velocidad/batería
  • ✅ Detección de 500ms es aceptable
  • ✅ Caso de uso típico (asistencia, check-in)
  • ✅ No estás seguro cuál elegir → Empieza aquí

Usa enterprise si:

  • ✅ Tienes 50+ beacons en el área
  • ✅ Rendimiento es crítico con muchos dispositivos
  • ✅ Necesitas cache para reducir CPU
  • ❌ NO si tienes pocos beacons (usa accessControl)

Usa batterySaver si:

  • ✅ La app debe correr días sin recargar
  • ✅ Detección no es time-critical
  • ✅ Solo necesitas ubicación periódica
  • ❌ NO si necesitas interacción en tiempo real

🎯 Casos de Uso #

Control de Acceso #

import 'package:universal_beacon_sdk/universal_beacon_sdk.dart';

final scanner = UniversalBeaconScanner();

// Configuración ultra-rápida para puertas
await scanner.initialize(
  config: BeaconConfig.ultraFast(), // 100-300ms
  registeredBeacons: [
    BeaconRegistry(
      id: 'employee_001',
      uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
      displayName: 'Juan Pérez',
      metadata: {'department': 'IT', 'level': 'admin'},
    ),
  ],
);

// Escuchar eventos de acceso
scanner.beaconEvents.listen((event) {
  if (event.type == BeaconEventType.detected) {
    print('✅ Acceso autorizado: ${event.device.displayName}');
    openDoor(); // Puerta abre en ~200ms
  }
});

await scanner.startScanning();

Sistema de Asistencia #

// Configuración balanceada
await scanner.initialize(
  config: BeaconConfig.accessControl(),
  registeredBeacons: students,
);

scanner.beaconEvents.listen((event) {
  if (event.type == BeaconEventType.detected) {
    registerAttendance(event.device);
  }
  if (event.type == BeaconEventType.lost) {
    markExit(event.device);
  }
});
    // Marcar asistencia
  }
});

Marketing de Proximidad #

// Configuración para marketing
await scanner.initialize(
  config: ScannerConfig.proximityMarketing,
);

// Registrar productos
await scanner.registerBeacon(BeaconRegistry(
  id: 'product_001',
  uuid: 'F7826DA6-4FA2-4E98-8024-BC5B71E0893E',
  displayName: 'Promoción Especial',
  category: 'marketing',
  metadata: {'campaign': 'summer_sale', 'discount': '20%'},
  registeredAt: DateTime.now(),
));

// Activar ofertas por proximidad
scanner.events.listen((event) {
  if (event.type == BeaconEventType.registeredEntered) {
    final discount = event.registry?.metadata['discount'];
    print('¡Oferta especial: $discount de descuento!');
    // Mostrar oferta
  }
});

🔧 API Principal #

Inicialización #

final scanner = UniversalBeaconScanner();

// Inicialización básica
await scanner.initialize();

// Inicialización con configuración
await scanner.initialize(
  config: BeaconConfig.balanced(),
  registeredBeacons: [/* lista de beacons */],
);

// Configuraciones predefinidas
await scanner.initialize(config: ScannerConfig.accessControl);
await scanner.initialize(config: ScannerConfig.attendance);
await scanner.initialize(config: ScannerConfig.proximityMarketing);

Configuración Personalizada #

final customConfig = BeaconConfig(
  scanInterval: 1000,
  minRssi: -80,
  maxDistance: 25.0,
  deviceLostTimeout: 10,
  iBeaconOnly: false,
  enableLogging: true,
  logLevel: 2,
  allowedUuids: ['FDA50693-A4E2-4FB1-AFCF-C6EB07647825'],
);

await scanner.initialize(config: customConfig);

🔋 Monitoreo de Batería #

El SDK incluye detección automática de nivel de batería para beacons HOLYIOT NRF52810. Funciona en ambos modos (Normal e iBeacon) sin configuración adicional.

Modos Soportados #

1. Modo Normal/Generic BLE

Beacons transmiten serviceData con UUID personalizado 0x5242:

Datos: 41 63 ce 07 42 82 e8 59 03 02 06 00 00
       ↑  ↑
       |  byte[1] = 0x63 = 99% batería

2. Modo iBeacon

Beacons transmiten manufacturerData con datos extendidos:

Datos: 06 00 01 09 20 ... [byte 25] [byte 26] [byte 27]
                          ↑        ↑        ↑
                          Posiciones de batería (prioridad: 25→26→27)

Propiedades Disponibles #

scanner.detectedDevices.listen((devices) {
  for (var device in devices) {
    // Verificar si tiene información de batería
    if (device.hasBatteryInfo) {
      // Nivel de batería (0-100%)
      print('Batería: ${device.batteryLevel}%');
      
      // Icono visual
      print('Estado: ${device.batteryIcon}'); // 🔋 o 🪫
      
      // Color para UI ('green', 'orange', 'red')
      print('Color: ${device.batteryColor}');
      
      // Timestamp de última lectura
      print('Leído: ${device.batteryLastRead}');
      
      // Alertas automáticas
      if (device.isBatteryCritical) {
        print('⚠️ CRÍTICO: < 10% - Reemplazar inmediatamente');
      } else if (device.isBatteryLow) {
        print('⚡ BAJO: < 20% - Programar reemplazo');
      }
    }
  }
});

Ejemplo Completo de Monitoreo #

// Sistema de alertas de batería
void monitorBatteryStatus() {
  scanner.detectedDevices.listen((devices) {
    for (var device in devices.where((d) => d.hasBatteryInfo)) {
      final batteryInfo = {
        'name': device.displayName,
        'level': device.batteryLevel,
        'icon': device.batteryIcon,
        'timestamp': device.batteryLastRead,
      };
      
      // Log de estado
      print('${batteryInfo['icon']} ${batteryInfo['name']}: ${batteryInfo['level']}%');
      
      // Alertas críticas
      if (device.isBatteryCritical) {
        sendCriticalAlert(device);
        scheduleImmediateReplacement(device);
      }
      
      // Alertas preventivas
      else if (device.isBatteryLow) {
        sendLowBatteryWarning(device);
        addToMaintenanceQueue(device);
      }
    }
  });
}

UI de Batería #

// Widget de batería con colores
Widget buildBatteryIndicator(BeaconDevice device) {
  if (!device.hasBatteryInfo) return SizedBox.shrink();
  
  Color getBatteryColor() {
    switch (device.batteryColor) {
      case 'green': return Colors.green;
      case 'orange': return Colors.orange;
      case 'red': return Colors.red;
      default: return Colors.grey;
    }
  }
  
  return Container(
    padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
    decoration: BoxDecoration(
      color: getBatteryColor().withOpacity(0.2),
      borderRadius: BorderRadius.circular(12),
      border: Border.all(color: getBatteryColor(), width: 1.5),
    ),
    child: Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(device.batteryIcon, style: TextStyle(fontSize: 12)),
        SizedBox(width: 4),
        Text(
          '${device.batteryLevel}%',
          style: TextStyle(
            color: getBatteryColor(),
            fontWeight: FontWeight.bold,
            fontSize: 12,
          ),
        ),
      ],
    ),
  );
}

Compatibilidad #

  • HOLYIOT NRF52810: Ambos modos (Normal e iBeacon)
  • Battery Service Estándar: UUID 0x180F (BLE estándar)
  • Backward Compatible: Beacons sin batería siguen funcionando
  • Automático: Sin configuración manual requerida
  • Performance: Parsing optimizado, sin impacto en velocidad

Gestión de Beacons #

// Registrar beacon
final beacon = BeaconRegistry(
  id: 'unique_id',
  uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
  displayName: 'Mi Beacon',
  description: 'Descripción del beacon',
  category: 'general',
  metadata: {'custom': 'data'},
  registeredAt: DateTime.now(),
);

await scanner.registerBeacon(beacon);

// Obtener beacons registrados
final registeredBeacons = scanner.registeredBeacons;

// Buscar beacons
final searchResults = scanner.registryManager.searchBeacons('Holy');

Escaneo y Eventos #

// Iniciar escaneo
await scanner.startScanning();

// Escuchar todos los eventos
scanner.events.listen((event) {
  switch (event.type) {
    case BeaconEventType.detected:
      print('Nuevo beacon: ${event.device.name}');
      break;
    case BeaconEventType.registeredEntered:
      print('Beacon registrado entró: ${event.device.name}');
      break;
    case BeaconEventType.lost:
      print('Beacon perdido: ${event.device.name}');
      break;
  }
});

// Escuchar lista de dispositivos
scanner.devices.listen((devices) {
  print('Dispositivos detectados: ${devices.length}');
  for (final device in devices) {
    print('- ${device.name}: ${device.estimatedDistance?.toStringAsFixed(1)}m');
  }
});

// Escuchar estado del scanner
scanner.status.listen((status) {
  print('Estado: ${status.displayName}');
});

// Detener escaneo
await scanner.stopScanning();

📡 Los 3 Streams Principales #

El SDK expone 3 streams que cubren todos los casos de uso:

1️⃣ beaconEvents - Eventos Individuales en Tiempo Real #

Cuándo usarlo: Necesitas reaccionar inmediatamente a cada beacon (ej: abrir puerta)

scanner.beaconEvents.listen((event) {
  // Cada evento contiene:
  // - event.type: BeaconEventType (detected, updated, lost)
  // - event.device: BeaconDevice con toda la info
  // - event.timestamp: DateTime del evento
  
  switch (event.type) {
    case BeaconEventType.detected:
      // Primera vez que se detecta el beacon
      print('🆕 Nuevo: ${event.device.displayName}');
      openDoor(); // Acción inmediata
      break;
      
    case BeaconEventType.updated:
      // Beacon ya conocido, datos actualizados (RSSI, distancia)
      print('🔄 Actualizado: ${event.device.rssi} dBm');
      break;
      
    case BeaconEventType.lost:
      // Beacon salió del rango o se perdió señal
      print('❌ Perdido: ${event.device.displayName}');
      closeDoor();
      break;
  }
});

✅ Casos de uso:

  • Control de acceso (abrir puerta cuando se detecta)
  • Notificaciones push individuales
  • Triggers de acciones específicas
  • Logging detallado de eventos

❌ NO usar para:

  • Actualizar UI de lista (usa detectedDevices)
  • Verificar estado general (usa status)

2️⃣ detectedDevices - Lista Completa de Beacons Activos #

Cuándo usarlo: Necesitas mostrar lista en UI o procesar múltiples beacons

scanner.detectedDevices.listen((devices) {
  // devices es List<BeaconDevice>
  // Se actualiza según streamThrottle (default: 500ms)
  
  setState(() {
    _beaconsList = devices;
    _totalBeacons = devices.length;
    _registeredCount = devices.where((d) => d.isRegistered).length;
  });
  
  // Filtrar por distancia
  final nearbyBeacons = devices.where((d) => 
    d.distance != null && d.distance! < 5.0
  ).toList();
  
  // Filtrar por RSSI
  final strongSignal = devices.where((d) => d.rssi > -70).toList();
  
  // Ordenar por distancia
  devices.sort((a, b) => 
    (a.distance ?? 999).compareTo(b.distance ?? 999)
  );
});

✅ Casos de uso:

  • ListView/GridView de beacons en UI
  • Dashboard con estadísticas
  • Mapa de beacons cercanos
  • Filtrado y ordenamiento de múltiples beacons

❌ NO usar para:

  • Reaccionar a beacon específico (usa beaconEvents)
  • Solo verificar si está escaneando (usa status)

3️⃣ status - Estado del Scanner #

Cuándo usarlo: Necesitas mostrar estado de conexión/escaneo en UI

scanner.status.listen((status) {
  // status es ScannerStatus enum
  
  switch (status) {
    case ScannerStatus.idle:
      // Scanner inicializado pero no escaneando
      print('⚪ Listo para escanear');
      _showStartButton();
      break;
      
    case ScannerStatus.scanning:
      // Escaneando activamente
      print('🔵 Escaneando...');
      _showStopButton();
      _showLoadingIndicator();
      break;
      
    case ScannerStatus.stopped:
      // Escaneo detenido manualmente
      print('🔴 Detenido');
      break;
      
    case ScannerStatus.error:
      // Error (permisos, bluetooth off, etc)
      print('❌ Error');
      _showPermissionDialog();
      break;
  }
});

✅ Casos de uso:

  • Mostrar botón Start/Stop
  • Indicador de loading
  • Mensaje de error de permisos/bluetooth
  • Color de estado en UI

❌ NO usar para:

  • Información de beacons (usa otros streams)

📋 Ejemplo Completo con los 3 Streams #

class BeaconScreen extends StatefulWidget {
  @override
  _BeaconScreenState createState() => _BeaconScreenState();
}

class _BeaconScreenState extends State<BeaconScreen> {
  final scanner = UniversalBeaconScanner();
  
  List<BeaconDevice> _devices = [];
  ScannerStatus _status = ScannerStatus.idle;
  String? _lastEvent;
  
  @override
  void initState() {
    super.initState();
    _initScanner();
  }
  
  Future<void> _initScanner() async {
    await scanner.initialize(
      config: BeaconConfig.accessControl(),
      registeredBeacons: [
        BeaconRegistry(
          id: 'door_1',
          uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
          displayName: 'Puerta Principal',
        ),
      ],
    );
    
    // Stream 1: Eventos individuales para acciones inmediatas
    scanner.beaconEvents.listen((event) {
      if (event.type == BeaconEventType.detected && event.device.isRegistered) {
        _openDoor(event.device);
      }
      setState(() {
        _lastEvent = '${event.type.name}: ${event.device.displayName}';
      });
    });
    
    // Stream 2: Lista completa para UI
    scanner.detectedDevices.listen((devices) {
      setState(() {
        _devices = devices;
      });
    });
    
    // Stream 3: Estado para botones e indicadores
    scanner.status.listen((status) {
      setState(() {
        _status = status;
      });
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Beacon Scanner'),
        actions: [
          // Indicador de estado (Stream 3)
          Icon(
            _status == ScannerStatus.scanning 
              ? Icons.bluetooth_searching 
              : Icons.bluetooth,
            color: _status == ScannerStatus.error ? Colors.red : Colors.green,
          ),
        ],
      ),
      body: Column(
        children: [
          // Último evento (Stream 1)
          if (_lastEvent != null)
            Card(
              child: Padding(
                padding: EdgeInsets.all(8),
                child: Text(_lastEvent!),
              ),
            ),
          
          // Lista de beacons (Stream 2)
          Expanded(
            child: ListView.builder(
              itemCount: _devices.length,
              itemBuilder: (context, index) {
                final device = _devices[index];
                return ListTile(
                  leading: Icon(
                    device.isRegistered ? Icons.verified : Icons.bluetooth,
                    color: device.isRegistered ? Colors.green : Colors.grey,
                  ),
                  title: Text(device.displayName ?? 'Beacon'),
                  subtitle: Text('${device.distance?.toStringAsFixed(1)}m'),
                  trailing: Text('${device.rssi} dBm'),
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        // Botón basado en estado (Stream 3)
        onPressed: _status == ScannerStatus.scanning 
          ? scanner.stopScanning 
          : scanner.startScanning,
        child: Icon(
          _status == ScannerStatus.scanning ? Icons.stop : Icons.play_arrow,
        ),
      ),
    );
  }
  
  void _openDoor(BeaconDevice device) {
    print('✅ Abriendo puerta para: ${device.displayName}');
    // Tu lógica aquí
  }
  
  @override
  void dispose() {
    scanner.dispose();
    super.dispose();
  }
}

🎯 BeaconRegistry: ¿Cuándo Usar y Cuándo NO? #

✅ USA BeaconRegistry CUANDO: #

1. Necesitas identificar beacons específicos

// ✅ Control de acceso - sabes exactamente qué badges esperar
registeredBeacons: [
  BeaconRegistry(id: 'employee_123', uuid: '...', displayName: 'Juan'),
  BeaconRegistry(id: 'employee_456', uuid: '...', displayName: 'María'),
]

2. Quieres metadata adicional

// ✅ Guardar info del empleado con el beacon
BeaconRegistry(
  id: 'emp_001',
  uuid: 'FDA50693-...',
  metadata: {
    'cargo': 'Desarrollador',
    'departamento': 'IT',
    'nivel_acceso': 'admin',
  },
)

3. Necesitas filtrar eventos de beacons conocidos

// ✅ Solo reaccionar a beacons registrados
scanner.beaconEvents.listen((event) {
  if (event.device.isRegistered) {
    openDoor(); // Solo para empleados registrados
  }
});

4. Quieres priorizar detección de beacons importantes

// ✅ Con prioritizeRegisteredBeacons: true
// Los beacons registrados se detectan más rápido
config: BeaconConfig(
  prioritizeRegisteredBeacons: true, // Empleados conocidos primero
)

❌ NO USES BeaconRegistry CUANDO: #

1. Solo quieres descubrir beacons disponibles

// ❌ NO necesitas registrar nada
// ✅ Simplemente escanea y ve qué hay
await scanner.initialize(config: BeaconConfig.accessControl());
scanner.detectedDevices.listen((devices) {
  print('Encontré ${devices.length} beacons');
});

2. No sabes qué beacons esperar

// ❌ Mala idea: registrar todos los UUIDs posibles
// ✅ Mejor: escanea y filtra después por proximidad/RSSI
scanner.detectedDevices.listen((devices) {
  final nearby = devices.where((d) => d.distance! < 5).toList();
});

3. Solo necesitas métricas generales

// ❌ No necesitas identificación individual
// ✅ Solo cuenta cuántos beacons hay cerca
final count = devices.length;
final avgDistance = devices.map((d) => d.distance).average;

4. Quieres máxima flexibilidad

// ❌ Registrar beacons limita detección si usas filtros
// ✅ Sin registrar, detectas TODOS los beacons
config: BeaconConfig(
  iBeaconOnly: false, // Detecta todos los formatos
  allowedUuids: null, // No filtrar por UUID
)

🎯 Regla General: #

Usa BeaconRegistry si: Tienes una lista conocida de beacons Y necesitas tratarlos de forma especial (metadata, priorización, filtrado)

No uses BeaconRegistry si: Solo quieres descubrir/monitorear beacons genéricamente

📈 Logging y Debugging #

final logger = BeaconLogger();

// Configurar nivel de logging
logger.setLevel(3); // 0: Error, 1: Warning, 2: Info, 3: Debug

// Obtener historial de logs
final history = logger.getHistory(filterLevel: LogLevel.error);

// Exportar logs
final exportedLogs = logger.exportLogs(since: DateTime.now().subtract(Duration(hours: 1)));

// Estadísticas
final stats = logger.getStatistics();

🎛️ Configuraciones Avanzadas #

Por Plataforma #

final config = BeaconConfig(
  scanInterval: 1000,
  platformConfig: PlatformSpecificConfig(
    android: AndroidConfig(
      scanMode: 2, // Balanced
      useLowPowerMode: false,
    ),
    ios: IOSConfig(
      allowBackgroundScanning: true,
      restoreState: true,
    ),
  ),
);

Configuración Específica por Beacon #

final beacon = BeaconRegistry(
  id: 'special_beacon',
  uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
  displayName: 'Beacon Especial',
  config: BeaconSpecificConfig(
    notificationCooldown: 5,
    maxDistance: 10.0,
    minRssi: -70,
    trackHistory: true,
    customConfig: {'special': true},
  ),
  registeredAt: DateTime.now(),
);

💾 Persistencia y Exportación #

// Los registros se guardan automáticamente
// Exportar registros
final exported = scanner.registryManager.exportToJson();

// Importar registros
await scanner.registryManager.importFromJson(exported);

// Estadísticas
final stats = scanner.registryManager.getStatistics();

🧪 Configuraciones de Testing #

// Configuración para desarrollo
await scanner.initialize(config: ScannerConfig.debug);

// Configuración para demo
await scanner.initialize(config: ScannerConfig.demo);

📱 Casos de Uso Específicos #

IoT y Automatización #

scanner.events.listen((event) {
  if (event.device.category == 'sensor' && 
      event.type == BeaconEventType.registeredEntered) {
    // Activar automatización
    triggerIoTAction(event.device.metadata);
  }
});

Aplicaciones de Salud #

await scanner.initialize(config: ScannerConfig.healthcare);

scanner.events.listen((event) {
  if (event.device.category == 'medical_device') {
    // Procesar datos médicos
    processMedicalData(event.device);
  }
});

🔧 Personalización Completa #

// Configuración empresarial personalizada
final enterpriseConfig = BeaconConfig(
  scanInterval: 500,
  minRssi: -75,
  maxDistance: 50.0,
  allowedUuids: [
    'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
    'E2C56DB5-DFFB-48D2-B060-D0F5A7100000',
  ],
  enablePersistence: true,
  logLevel: 2,
);

await scanner.initialize(config: enterpriseConfig);

🆘 Manejo de Errores #

try {
  await scanner.startScanning();
} catch (e) {
  if (e.toString().contains('permissions')) {
    // Manejar error de permisos
  } else if (e.toString().contains('bluetooth')) {
    // Manejar error de Bluetooth
  }
}

// Escuchar errores del scanner
scanner.status.listen((status) {
  if (status.isError) {
    handleScannerError(status);
  }
});

✅ Best Practices #

🚫 Errores Comunes y Cómo Evitarlos #

1. Memory Leaks - Olvidar dispose()

❌ MAL:

class MyWidget extends StatefulWidget {
  final scanner = UniversalBeaconScanner();
  
  @override
  Widget build(BuildContext context) {
    scanner.startScanning(); // ¡Nunca se detiene!
    return Container();
  }
}

✅ BIEN:

class MyWidget extends StatefulWidget {
  final scanner = UniversalBeaconScanner();
  
  @override
  void initState() {
    super.initState();
    _initScanner();
  }
  
  @override
  void dispose() {
    scanner.dispose(); // ← CRÍTICO: Libera recursos
    super.dispose();
  }
}

2. Permisos - No verificar antes de escanear

❌ MAL:

await scanner.startScanning(); // ¡Puede fallar sin permisos!

✅ BIEN:

// Opción 1: El SDK maneja permisos automáticamente
await scanner.initialize(config: config);
await scanner.startScanning(); // SDK verifica permisos internamente

// Opción 2: Verificación manual explícita
final permissionManager = PermissionManager();
if (!await permissionManager.hasRequiredPermissions()) {
  final granted = await permissionManager.checkAndRequestAllPermissions();
  if (!granted) {
    showPermissionDialog();
    return;
  }
}
await scanner.startScanning();

3. Configuración Incorrecta para Caso de Uso

❌ MAL:

// Usando batterySaver para control de acceso
config: BeaconConfig.batterySaver() // ¡Detección cada 5s es muy lento!

✅ BIEN:

// Usar el preset correcto según caso de uso
config: BeaconConfig.ultraFast() // Para control de acceso inmediato

4. No Escuchar el Stream Correcto

❌ MAL:

// Usando beaconEvents para actualizar ListView (demasiados updates)
scanner.beaconEvents.listen((event) {
  setState(() {
    _devices = [...]; // ¡Se actualiza 10+ veces por segundo!
  });
});

✅ BIEN:

// Usar detectedDevices para UI (throttled)
scanner.detectedDevices.listen((devices) {
  setState(() {
    _devices = devices; // Se actualiza cada 500ms (configurable)
  });
});

5. Registrar Beacons Innecesariamente

❌ MAL:

// Registrar todos los beacons del mundo "por si acaso"
registeredBeacons: [
  BeaconRegistry(id: 'beacon_1', uuid: '...'),
  BeaconRegistry(id: 'beacon_2', uuid: '...'),
  // ... 500 beacons más
] // ¡Impacto en rendimiento!

✅ BIEN:

// Solo registrar beacons que realmente necesitas identificar
registeredBeacons: [
  BeaconRegistry(id: 'main_door', uuid: '...'),
  BeaconRegistry(id: 'back_door', uuid: '...'),
] // Mantén la lista pequeña y relevante

6. No Manejar Estado de Bluetooth

❌ MAL:

await scanner.startScanning(); // ¿Qué pasa si BT está off?

✅ BIEN:

scanner.status.listen((status) {
  switch (status) {
    case ScannerStatus.error:
      showBluetoothDialog(); // Pedir al usuario activar BT
      break;
    case ScannerStatus.scanning:
      hideLoadingIndicator();
      break;
  }
});

7. Configuración Demasiado Agresiva

❌ MAL:

BeaconConfig(
  scanInterval: 50,           // ¡Muy rápido!
  minUpdateInterval: 10,      // ¡Demasiado frecuente!
  scanMode: ScanMode.lowLatency,
  // Batería se agota en 2 horas
)

✅ BIEN:

// Empieza con preset y ajusta solo si es necesario
BeaconConfig.accessControl() // 500ms es suficiente para mayoría de casos

💡 Recomendaciones Generales #

✅ DO (Hacer)

  1. Siempre llama dispose() cuando el widget se destruye
  2. Usa el preset correcto para tu caso de uso antes de personalizar
  3. Escucha status para mostrar estado de BT/permisos en UI
  4. Prueba en dispositivo real - el emulador no tiene Bluetooth
  5. Usa detectedDevices para UI, beaconEvents para acciones
  6. Mantén pequeña la lista de registeredBeacons (<50)
  7. Verifica permisos antes de funcionalidad crítica
  8. Maneja el caso de Bluetooth apagado

❌ DON'T (No Hacer)

  1. No olvides dispose() - causa memory leaks
  2. No uses batterySaver para apps interactivas
  3. No registres beacons si no necesitas identificarlos
  4. No uses beaconEvents para actualizar listas en UI
  5. No asumas permisos están concedidos sin verificar
  6. No uses ultraFast si la app corre todo el día
  7. No ignores el estado del scanner en UI
  8. No escanees continuamente si no es necesario

🎯 Checklist de Implementación #

Antes de ir a producción, verifica:

  • ❌ ✅ dispose() llamado en todos los lugares donde se crea scanner
  • ❌ ✅ Permisos verificados/solicitados antes de escanear
  • ❌ ✅ Preset correcto elegido según caso de uso
  • ❌ ✅ Streams correctos usados (events vs devices vs status)
  • ❌ ✅ Manejo de Bluetooth apagado implementado
  • ❌ ✅ Manejo de permisos denegados implementado
  • ❌ ✅ UI muestra estado del scanner claramente
  • ❌ ✅ Probado en dispositivo real Android/iOS
  • ❌ ✅ Probado con Bluetooth apagado/encendido
  • ❌ ✅ Probado sin permisos concedidos
  • ❌ ✅ No hay memory leaks (verificado con DevTools)
  • ❌ ✅ Batería consumida es aceptable para tu caso de uso

📱 Testing en Dispositivos Reales #

// Ejemplo de test completo
void testBeaconScanner() async {
  final scanner = UniversalBeaconScanner();
  
  print('✓ Test 1: Inicialización');
  await scanner.initialize(config: BeaconConfig.accessControl());
  
  print('✓ Test 2: Verificar permisos');
  assert(await PermissionManager().hasRequiredPermissions());
  
  print('✓ Test 3: Iniciar escaneo');
  await scanner.startScanning();
  
  print('✓ Test 4: Detectar beacons (esperar 10s)');
  await Future.delayed(Duration(seconds: 10));
  final devices = scanner.currentDevices;
  print('  Detectados: ${devices.length} beacons');
  
  print('✓ Test 5: Detener escaneo');
  await scanner.stopScanning();
  
  print('✓ Test 6: Cleanup');
  await scanner.dispose();
  
  print('✅ Todos los tests pasaron');
}

📚 Recursos Adicionales #

📄 Licencia #

MIT License - Ver LICENSE para más detalles.

2
likes
130
points
274
downloads

Publisher

unverified uploader

Weekly Downloads

SDK genérico y robusto para detección de beacons BLE. Ideal para control de acceso, sistemas de asistencia, marketing de proximidad, automatización IoT, tracking de assets y aplicaciones empresariales.

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_reactive_ble, permission_handler, shared_preferences, uuid

More

Packages that depend on universal_beacon_sdk