Lubeca Tech · WorkDone+Smiley · Dev Deck 2026

Location,
Integrity &
Presence

Cómo convertir la tablet Smiley en un endpoint que sabe dónde está, detecta si la movieron y observa su entorno radio. Con Kotlin de ejemplo.

Kotlin · Android 15

Galaxy Tab A11 LTE

Privacy by design

01 · Concepto

No hace falta saber dónde está. Hace falta saber si sigue donde lo instalaron.

El Smiley es un equipo fijo, instalado por un técnico en un lugar conocido. Eso cambia indoor positioning por detección de cambio contra un baseline propio.

AEP / T-A / Arribos / Cinta 4 — confidence 94%

01 · Concepto

Tres ejes, un solo endpoint

01 · POSITION

¿Sigo donde debo?

Zona probable por sensor fusion, comparada contra el baseline de instalación.

GNSS
Wi-Fi BSSID / RSSI
BLE RSSI
Cell

02 · INTEGRITY

¿Me movieron o me golpearon?

Manipulación, golpe, inclinación, corte de energía y silencio.

Movement / Impact
Tilt
Power
Heartbeat

03 · PRESENCE

¿Hay alguien cerca?

Cliente cercano por BLE, sin identificar a nadie. Y el Smiley anunciándose.

Cliente cercano por RSSI
Contexto del voto
Advertising opt-in

Regla base: MOVIMIENTO ≠ REUBICACIÓN. Limpiar, inclinar o tocar el equipo no dispara una alarma de traslado.

02 · Plataforma

Qué trae la Galaxy Tab A11 LTE

Ubicación

GNSS

GPS, GLONASS, BeiDou, Galileo y QZSS. Sólo en el modelo LTE.

Radio local

Wi-Fi 5

a/b/g/n/ac en 2.4 y 5 GHz. Ve los APs sin conectarse.

Radio local

Bluetooth 5.3

LE scan disponible. El advertiser se verifica en runtime.

Uplink

LTE

Siempre conectada: la inferencia puede vivir en el backend.

Sensor

Acelerómetro

Movimiento, golpe y vector de gravedad para tilt.

Sensor

Brújula

Orientación completa y firma magnética del soporte.

No está

Sin giroscopio

No hace falta: el equipo está fijo y alcanza con gravedad.

No está

Sin NFC

La tablet no lee tags. El NFC queda del lado del operario.

Helio G99 · Android 15 con hasta 7 upgrades mayores · 5100 mAh. A confirmar con el probe: sensor de luz, BLE advertiser, scan y advertise a la vez.

02 · Plataforma

Tecnologías de ubicación: qué sirve para un Smiley

TecnologíaIndoorInfra nuevaFitComentario
Wi-Fi fingerprintAlto, por zonaNo5 / 5BSSID + RSSI, sin conectarse a la red
BLE beacons propiosAlto, por zonaLigera5 / 5Sólo donde la radio ambiental sea ambigua
BLE ambientalMedio a altoNo4 / 5Complementa Wi-Fi; emisores persistentes
GNSSBajo a medioNo3 / 5Muy bueno outdoor; indoor puede no haber fix
Cell fingerprintBajoNo3 / 5Sanity check de macro-zona
Wi-Fi RTT1 a 2 mAP 802.11mc3 / 5Sólo si tablet y AP lo soportan; a verificar
UWBCentimétricoAnclas + HW2 / 5No es la vía natural para la Tab A11
RFID / NFCPuntualLectores1 / 5Checkpoint; la A11 no trae NFC

Las dos primeras filas son el camino recomendado: fingerprint ambiental primero, beacons propios sólo si una zona no se distingue.

02 · Plataforma

La posición es una inferencia con confidence

GNSS

lat/lon · accuracy · age

Wi-Fi

top BSSID · RSSI

BLE

emisores fijos · RSSI

Cell

serving · vecinas

BACKEND

Location engine

normalización
match contra baseline
freshness
estabilidad
confidence

Cinta 4

94%

Cinta 5

18%

Partidas

6%

Periodic

Fingerprint y fix cada hora como baseline.

Triggered

El movimiento dispara un burst de GNSS, Wi-Fi y BLE.

Event-driven

Alerta sólo si cambia la evidencia. El heartbeat va aparte.

02 · Plataforma

La tablet detecta, el backend concluye

TABLET · FOREGROUND SERVICE

SensorManager

acelerómetro · brújula

FusedLocation

fix · accuracy · age

WifiManager

BSSID · RSSI

BluetoothLe

scanner · advertiser

Telephony

CellInfo

Local event engine

debounce
clasifica impact, movement y tilt
dispara el burst

Room queue

offline
seq IDs
retry

4G

BACKEND

Fusion

Location Profiles
similarity y confidence
secuencias
alertas
dead-man's switch

Crudo al backend. Thresholds y algoritmos cambian sin tocar el APK.

Todo con seq. Los huecos y duplicados se ven del otro lado.

Offline primero. Nada se pierde si se cae el 4G.

03 · Código en la tablet

Primero, el capability probe

SmileyCapabilities.kt

Kotlin · tablet

data class SmileyCapabilities(

val sdk: Int, val sensors: List<String>, val gyro: Boolean,

val bleAdvertiser: Boolean, val wifiRtt: Boolean, val gnss: Boolean,

)

 

fun probe(ctx: Context): SmileyCapabilities {

val sm = ctx.getSystemService(SensorManager::class.java)

val bt = ctx.getSystemService(BluetoothManager::class.java).adapter

val pm = ctx.packageManager

return SmileyCapabilities(

sdk = Build.VERSION.SDK_INT,

sensors = sm.getSensorList(Sensor.TYPE_ALL).map { it.name },

gyro = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null,

bleAdvertiser = bt.isMultipleAdvertisementSupported,

wifiRtt = pm.hasSystemFeature(PackageManager.FEATURE_WIFI_RTT),

gnss = pm.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS),

)

}

Nada se asume

Giroscopio, RTT y advertiser se le preguntan al hardware real.

Viaja al backend

El resultado sale con el primer heartbeat y queda como ficha del equipo.

Primera entrega

Una pantalla de diagnóstico, antes que cualquier lógica de inferencia.

03 · Código en la tablet

Enrollment: el baseline de cada equipo

Enrollment.kt

Kotlin · tablet

data class ApStat(val medianRssi: Int, val seenRatio: Double)

 

data class LocationProfile(

val zone: String, // "AEP/T1/ARR/CINTA_04"

val aps: Map<String, ApStat>, // BSSID -> estadísticos

val gravity: FloatArray, // orientación instalada

)

 

suspend fun enroll(zone: String, s: Sensors): LocationProfile {

val scans = List(8) { s.wifi().also { delay(30_000) } }

val aps = scans.flatMap { it.keys }.distinct().associateWith { id ->

val seen = scans.mapNotNull { it[id] }.sorted()

ApStat(seen[seen.size / 2], seen.size / scans.size.toDouble())

}

return LocationProfile(zone, aps, s.gravity())

}

Estadísticos, no una lectura

Mediana de RSSI y frecuencia de aparición por BSSID.

El throttle manda

Android permite 4 scans cada 2 minutos. Ocho muestras llevan 4 minutos.

Gravedad incluida

El vector del equipo ya amurado es la referencia para tilt.

03 · Código en la tablet

Wi-Fi fingerprint sin conectarse a la red

WifiFingerprint.kt

Kotlin · tablet

@SuppressLint("MissingPermission")

suspend fun WifiManager.scanOnce(ctx: Context): Map<String, Int> =

suspendCancellableCoroutine { cont ->

fun snapshot() = scanResults.associate { it.BSSID to it.level }

val rx = object : BroadcastReceiver() {

override fun onReceive(c: Context, i: Intent) {

ctx.unregisterReceiver(this)

cont.resume(snapshot())

}

}

val action = WifiManager.SCAN_RESULTS_AVAILABLE_ACTION

ctx.registerReceiver(rx, IntentFilter(action))

cont.invokeOnCancellation { ctx.unregisterReceiver(rx) }

if (!startScan()) { // throttled: uso el último resultado

ctx.unregisterReceiver(rx)

cont.resume(snapshot())

}

}

Sin asociarse

El scan devuelve BSSID y RSSI de todo lo visible, aunque la tablet use 4G.

4 scans cada 2 min

En foreground alcanza para el baseline horario y el burst por movimiento.

Location encendida

Sin el permiso de ubicación y el servicio activo, la lista viene vacía.

03 · Código en la tablet

¿Sigo donde me instalaron?

Similarity.kt

Kotlin · tablet o backend

/** 0.0 = otro lugar · 1.0 = idéntico al baseline */

fun similarity(

base: Map<String, ApStat>,

now: Map<String, Int>,

): Double {

val stable = base.filterValues { it.seenRatio >= 0.8 }

if (stable.isEmpty()) return 0.0

val score = stable.entries.sumOf { (bssid, stat) ->

val rssi = now[bssid] ?: return@sumOf 0.0

val delta = abs(rssi - stat.medianRssi)

(1.0 - delta / 30.0).coerceIn(0.0, 1.0)

}

return score / stable.size

}

Mismo lugar

0.92

Cayó un AP

0.58

Otro lugar

0.12

Valores de ejemplo. El umbral real sale de dejar una tablet quieta 72 horas y ver cuánto varía sola.

Sólo APs estables

Los que aparecen en menos del 80% de las muestras no cuentan.

03 · Código en la tablet

Movimiento, golpe y tilt con un acelerómetro

IntegrityDetector.kt

Kotlin · tablet

class IntegrityDetector(

private val cfg: Thresholds, // remotos, por gabinete

private val baseG: FloatArray, // gravedad del enrollment

private val emit: (IntegrityEvent) -> Unit,

) : SensorEventListener {

private val g = baseG.copyOf()

override fun onSensorChanged(e: SensorEvent) {

for (i in 0..2) g[i] = 0.9f * g[i] + 0.1f * e.values[i]

val lin = FloatArray(3) { e.values[it] - g[it] }.norm()

val tilt = angleDeg(g, baseG)

when {

lin > cfg.impactMs2 -> emit(Impact(peak = lin))

lin > cfg.moveMs2 -> emit(Movement(energy = lin))

tilt > cfg.tiltDeg -> emit(Tilt(deg = tilt))

}

}

override fun onAccuracyChanged(s: Sensor, accuracy: Int) = Unit

}

Gravedad por low-pass

Al restarla queda la aceleración lineal. No hace falta giroscopio.

Tilt contra el enrollment

Equipo amurado, vector constante. Un desvío sostenido es la mejor señal de retiro.

Thresholds remotos

No existe "2 g = golpe". Se calibran sobre el gabinete real y llegan del backend.

03 · Código en la tablet

Golpes a la pantalla: lo que el acelerómetro no ve

01

Detecta, no mide

Un golpe en vidrio dura milisegundos. Con 200 Hz de muestreo y un rango de pocos g, el pico llega recortado. Se sabe que hubo golpe, no cuánto.

02

Votar también es golpear

Un dedo apurado en la carita roja mueve el acelerómetro. Sin cruzarlo con el touch, cada voto enojado es un falso golpe.

03

El touch dice con qué pegaron

Área de contacto grande: palma o puño. Impacto frontal sin touch: objeto duro. Impacto lateral: fue al gabinete.

04

El daño se ve después

Toques fantasma, una zona que deja de recibir votos, o gente cerca por BLE y cero votos. Así avisa una pantalla rota.

Al probe: sensor.maximumRange, sensor.minDelay y si touchMajor da valores útiles en la A11.

03 · Código en la tablet

Cruzar el acelerómetro con el touch

HitClassifier.kt

Kotlin · tablet

enum class Hit { STRONG_TAP, PALM, FRONT_OBJECT, SIDE }

class HitClassifier(private val cfg: Thresholds) {

private var lastDown = 0L to 0f // uptimeMillis, touchMajor

fun onTouch(e: MotionEvent) {

if (e.actionMasked != MotionEvent.ACTION_DOWN) return

lastDown = e.eventTime to e.touchMajor

}

fun onImpact(lin: FloatArray, atMs: Long): Hit {

val touched = abs(atMs - lastDown.first) < 150

val frontal = abs(lin[2]) > maxOf(abs(lin[0]), abs(lin[1]))

return when {

touched && lastDown.second > cfg.palmPx -> Hit.PALM

touched -> Hit.STRONG_TAP

frontal -> Hit.FRONT_OBJECT

else -> Hit.SIDE

}

}

}

Un solo reloj

El touch trae uptimeMillis. El impacto se sella con el mismo reloj al recibirlo.

Eje Z = frontal

Si pegan en el vidrio, domina el impulso perpendicular a la pantalla.

Votos bajo sospecha

Tras PALM o FRONT_OBJECT, los votos de los segundos siguientes van marcados.

03 · Código en la tablet

Del evento al sensor burst

BurstTrigger.kt

Kotlin · tablet

class BurstTrigger(

private val scope: CoroutineScope, private val s: Sensors,

private val queue: EventQueue,

) {

private var last = 0L

fun onIntegrity(ev: IntegrityEvent) {

queue.put(ev)

val now = SystemClock.elapsedRealtime()

if (now - last < 30_000) return // debounce

last = now

scope.launch {

val gnss = async { s.location() }

val wifi = async { s.wifi() }

val ble = s.ble(windowMs = 4_000)

queue.put(Burst(ev.id, gnss.await(), wifi.await(), ble))

}

}

}

Una secuencia de retiro

14:31:02

IMPACT pico corto

14:31:05

MOVEMENT sostenido

14:31:06

BURST GNSS, Wi-Fi, BLE

14:31:18

similarity 0.21 sale del baseline

14:31:44

GNSS fix mejora la accuracy

14:32:10

REMOVAL lo concluye el backend

03 · Código en la tablet

Todo evento sobrevive offline

EventQueue.kt

Kotlin · Room

@Entity(tableName = "events")

data class EventRow(

@PrimaryKey(autoGenerate = true) val seq: Long = 0,

val type: String,

val ts: Long,

val payload: String, // JSON

val sent: Boolean = false,

)

 

@Dao

interface EventDao {

@Insert suspend fun put(e: EventRow): Long

@Query("SELECT * FROM events WHERE sent = 0 ORDER BY seq LIMIT :n")

suspend fun pending(n: Int = 50): List<EventRow>

@Query("UPDATE events SET sent = 1 WHERE seq <= :upTo")

suspend fun ack(upTo: Long)

}

seq monotónico

El backend detecta huecos y descarta duplicados.

ack por rango

El POST devuelve el último seq persistido y la tablet marca hasta ahí.

Crudo primero

Mientras se calibra, el payload lleva las muestras crudas. El volumen es chico.

04 · Presencia BLE

Saber si hay un cliente cerca

Tasa de respuesta

Votos sobre presencias cercanas. Pasa de contar votos a saber qué proporción de la gente vota.

Attract mode

La pantalla se despierta o cambia el mensaje cuando alguien se acerca.

Contexto del voto

Cuántos emisores había y qué tan cerca, en la ventana de cada voto.

Salud del equipo

Gente cerca y cero votos durante horas: pantalla rota, apagada o tapada.

Es una muestra, no un censo

No todos los teléfonos anuncian por BLE. Sirve la tendencia, no el número exacto.

El RSSI es aproximado

Bolsillo, cuerpo y mano lo mueven varios dB. "Cerca" se calibra contra conteo manual.

La dirección rota

Alcanza para ventanas de segundos. No sirve para reconocer a alguien mañana, y está bien.

04 · Presencia BLE

Monitor de proximidad

ProximityMonitor.kt

Kotlin · tablet

data class BleContext(val seen: Int, val near: Int, val strongest: Int?)

 

class ProximityMonitor(private val nearDbm: Int = -55) {

private val seen = ConcurrentHashMap<String, Pair<Int, Long>>()

private fun now() = SystemClock.elapsedRealtime()

val callback = object : ScanCallback() {

override fun onScanResult(type: Int, r: ScanResult) {

seen[r.device.address] = r.rssi to now()

}

}

 

// Sólo RAM: las direcciones nunca van a disco ni al backend

fun snapshot(windowMs: Long = 10_000): BleContext {

seen.values.removeIf { it.second < now() - windowMs }

val v = seen.values.map { it.first }

return BleContext(v.size, v.count { it > nearDbm }, v.maxOrNull())

}

}

-55 dBm para empezar

Ronda el metro. Se ajusta por gabinete y viaja como config remota.

Pantalla prendida

Android pausa los scans sin filtro con la pantalla apagada. En kiosco no pasa.

Reiniciar el scan

Uno muy largo se degrada a oportunista. Reiniciarlo cada 20 minutos y validarlo en el probe.

04 · Presencia BLE

Cliente cercano y contexto del voto

PresenceSensor.kt

Kotlin · tablet

@SuppressLint("MissingPermission")

class PresenceSensor(private val scanner: BluetoothLeScanner) {

private val monitor = ProximityMonitor()

private val settings = ScanSettings.Builder()

.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)

.build()

 

fun start() = scanner.startScan(null, settings, monitor.callback)

fun stop() = scanner.stopScan(monitor.callback)

 

/** Ventana corta alrededor del voto */

fun onVote(vote: Vote): VoteEvent =

VoteEvent(vote, ble = monitor.snapshot(windowMs = 10_000))

 

/** ¿Hay alguien enfrente? Attract mode y tasa de respuesta */

fun someoneNear(): Boolean = monitor.snapshot(3_000).near > 0

}

Sale sólo el agregado

seen, near y strongest. Ni direcciones ni un hash por voto.

Tasa de respuesta

Cada minuto sale un PRESENCE con el pico de near. El backend divide votos por presencias.

Votos repetidos

Se compara en RAM contra el voto anterior y sale un booleano. La dirección no se guarda.

04 · Presencia BLE · Experimental

El Smiley como beacon silencioso

SmileyBeacon.kt

Kotlin · tablet

val WD_UUID: ParcelUuid =

ParcelUuid.fromString("7d2f0a41-5c1e-4b8a-9a6f-3e1c9b2d4f10")

@SuppressLint("MissingPermission")

fun BluetoothAdapter.announce(rotatingId: ByteArray): Boolean {

if (!isMultipleAdvertisementSupported) return false

val settings = AdvertiseSettings.Builder()

.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER)

.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_LOW)

.setConnectable(false)

.build()

val data = AdvertiseData.Builder()

.setIncludeDeviceName(false)

.addServiceData(WD_UUID, rotatingId) // 6 bytes, sin PII

.build()

val cb = object : AdvertiseCallback() {}

bluetoothLeAdvertiser.startAdvertising(settings, data, cb)

return true

}

ID rotativo

HMAC de la clave del equipo y la ventana de 15 minutos, truncado a 6 bytes.

Uso más cercano

La app WorkDone lo escucha y prueba que el operario estuvo ahí, junto al NFC.

Apagable por sitio

Es config remota. Nunca intenta provocar pop-ups en teléfonos ajenos.

05 · Backend

El silencio también es un evento

PowerReceiver.kt

Kotlin · tablet

class PowerReceiver : BroadcastReceiver() {

override fun onReceive(ctx: Context, i: Intent) {

val cut = i.action == Intent.ACTION_POWER_DISCONNECTED

EventQueue.get(ctx).putNow(Power(onBattery = cut))

}

}

dead_man_switch.sql

T-SQL · backend

SELECT device_id, last_seen, on_battery

FROM device_status

WHERE last_seen < DATEADD(MINUTE, -15, SYSUTCDATETIME())

AND alerted = 0;

Corte de energía

La señal de retiro más barata. Sale al instante, sin esperar el batch.

Dead-man's switch

Quien se lleva el equipo lo apaga. Un equipo apagado no avisa nada.

Heartbeat cada 5 min

Tres perdidos seguidos y el backend levanta DEVICE_SILENT.

05 · Backend

Un evento compacto, trazable y explicable

POST /events

JSON

{

"deviceId": "WD-AEP-042",

"seq": 18233,

"event": "POSITION",

"ts": "2026-10-04T21:15:20-03:00",

"cause": "PERIODIC",

"location": {

"profile": "AEP/T1/ARR/CINTA_04",

"similarity": 0.94,

"gnssAccuracyM": 63.0

},

"radio": { "wifi": "wf_91ab", "ble": "bf_7c2e" },

"presence": { "seen": 7, "near": 1, "strongest": -37 },

"power": { "onBattery": false, "level": 100 }

}

Los manda la tablet

POSITION

MOVEMENT

IMPACT

TILT

POWER

PRESENCE

VOTE

HEARTBEAT

Los concluye el backend

RELOCATION

REMOVAL

DEVICE_SILENT

SCREEN_SUSPECT

Los sensores reportan hechos. Las conclusiones no viajan desde la tablet.

05 · Backend

Ningún sensor decide solo

Classifier.kt

Kotlin · backend

enum class Verdict {

QUIET, IMPACT, MANIPULATED, RELOCATION, REMOVAL, DEVICE_SILENT

}

 

data class Window(

val moved: Boolean, val tilted: Boolean, val impact: Boolean,

val similarity: Double, val gnssOutdoor: Boolean,

val silentFor: Duration,

)

 

fun classify(w: Window): Verdict = when {

w.silentFor > 15.minutes -> Verdict.DEVICE_SILENT

w.moved && w.similarity < 0.35 && w.gnssOutdoor -> Verdict.REMOVAL

w.moved && w.similarity < 0.35 -> Verdict.RELOCATION

w.moved || w.tilted -> Verdict.MANIPULATED

w.impact -> Verdict.IMPACT

else -> Verdict.QUIET

}

impacto solo

IMPACT

movimiento, misma radio

MANIPULATED

movimiento + cambio de radio

RELOCATION

lo anterior + GNSS outdoor

REMOVAL

sin heartbeat

DEVICE_SILENT

0.35 y 15 minutos son ejemplos. Salen de los experimentos.

06 · Plan

Permisos y límites de Android

PermisoPara qué
ACCESS_FINE_LOCATIONGNSS, resultados del Wi-Fi scan, CellInfo
CHANGE_WIFI_STATEPedir un scan con startScan()
BLUETOOTH_SCANCliente cercano y fingerprint BLE
BLUETOOTH_ADVERTISEEl Smiley como beacon
FOREGROUND_SERVICE_LOCATIONServicio permanente en Android 14+
RECEIVE_BOOT_COMPLETEDArrancar solo después de un reinicio

4 scans cada 2 min

Throttle de Wi-Fi en foreground. En background es 1 cada 30 minutos entre todas las apps.

Kiosco = foreground

Pantalla prendida y alimentación fija esquivan casi todas las restricciones de background.

Device Owner

Lock task y permisos por policy. Ubicación tiene reglas propias desde Android 12: validarlo en el probe.

06 · Plan · Privacy by design

WorkDone observa el ambiente y la integridad del punto de feedback. No identifica ni sigue personas.

Sí

Conteos agregados, proximidad aproximada, IDs rotativos con TTL corto, comparaciones en RAM.

No

MAC como identidad, historial entre sitios, direcciones en disco o en el backend, pop-ups de proximidad.

06 · Plan

Cuatro experimentos antes de fijar thresholds

00 · 72 horas

Estabilidad

Una A11 quieta. ¿Cuánto varía sola la similarity entre día y noche, con hotspots y APs que rotan?

Define el umbral de similarity.

01 · 6 a 10 zonas

Separabilidad

Mover una A11 por zonas reales con Wi-Fi, BLE, GNSS y CellInfo. ¿Se distinguen de forma repetible?

Dice si hacen falta beacons.

02 · Gabinete real

Impacto

Limpieza, voto fuerte, palma, objeto duro, levantamiento, traslado. Probar un imán en el soporte.

Calibra golpe, tilt y touch.

03 · BLE

Presencia

Conteo manual contra near. Después: advertiser, scan y advertise a la vez, consumo.

Calibra "cerca" en dBm.

Regla: ningún threshold queda fijo sin 10 a 20 minutos de telemetría cruda por caso.

06 · Plan

Roadmap: valor antes que complejidad

V1

Baseline

Probe y diagnóstico
Enrollment
Position, movement, impact, tilt
Power y heartbeat

V1.1

BLE pasivo

Cliente cercano
Tasa de respuesta
Contexto del voto
Fingerprint ambiental

V1.2

Secuencias

Relocation
Removal
Golpe cruzado con touch
Pantalla sospechosa

V2

Radio map

Crowd learning
Zona de destino
Beacons propios donde haga falta

EXP

Beacon

BLE advertising
ID rotativo
Presencia del operario

Cambios respecto del doc original: enrollment y tilt pasan a V1, se suman power, heartbeat y el cruce con touch, y el contexto del voto queda en agregados sin hash.

06 · Plan

Primera entrega: sprint 0

Módulos

SmileyCapabilities

SmileyDiagnostics

SensorTelemetry

WifiFingerprint

BleFingerprint

LocationSnapshot

MovementEvent

ImpactEvent

Orden

1

Probe y pantalla de diagnóstico en la A11 real.

2

Telemetría cruda subiendo por 4G con cola offline.

3

Experimento de estabilidad corriendo 72 horas.

4

Recién ahí, thresholds e inferencia.

Sensor fusion + confidence + event semantics + privacy boundaries.

Referencias

Fuentes

Samsung · Galaxy Tab A11 LTE

GNSS, Wi-Fi, Bluetooth 5.3 y sensores.

GSMArena · Galaxy Tab A11

Chipset, upgrades de Android, sin NFC.

Android · Wi-Fi scanning

Permisos, restricciones y throttling.

Android · Bluetooth permissions

BLUETOOTH_SCAN y BLUETOOTH_ADVERTISE.

Android · BluetoothLeAdvertiser

API de advertising y soporte por plataforma.

Google · FusedLocationProviderClient

Ubicación actual, accuracy y freshness.

Android · Wi-Fi RTT

Ranging de 1 a 2 m con infraestructura compatible.

Bluetooth SIG · GAP privacy

Direcciones privadas y resolvibles.

Basado en el documento "WorkDone+Smiley — SOTA Location, Integrity & Presence Intelligence". Los samples de código son puntos de partida sin probar en hardware.