Back to Developer Docs
Developer Docs

Trillboards Sensing SDK Real-Time Audience Intelligence

On-device sensing for digital signage and retail media. Face detection, attention scoring, audio classification, BLE / WiFi co-viewing and demographic estimates run on the screen, and every person the camera detects becomes a record.

Overview

The Sensing SDK ships the canonical Trillboards sensing surface as a single Maven artifact for Android. It runs the face, audio, speech, BLE, WiFi and discovery pipelines on the device and sends Trillboards a record for each detected person (estimated age range, gender, dominant emotion, dwell time and time spent looking at the screen), speech transcripts, the Bluetooth, WiFi and local-network identifiers nearby devices broadcast, the screen's GPS position, and camera frames selected for cloud analysis. On the full sensing profile it also computes a face-identity template, a vector of 512 numbers that Trillboards uses to recognise the same person across screens and days. No audio recording leaves the device. The agent transmits the device's resettable Google Advertising ID (GAID) when available, used for cross-device frequency capping and partner attribution; the agent honors LIMIT_AD_TRACKING.

What ships in production today

  • On-device demographics, FaceXFormer (10-year age bins, gender estimates)
  • Audio classification, MediaPipe AudioClassifier (YAMNet event taxonomy)
  • Speech recognition, Moonshine ASR via sherpa-onnx
  • Audio diarization, multimodal clip prompt schema
  • BLE co-viewing, beacon scan, GATT 0x180A enumeration, manufacturer parsers (iBeacon, Eddystone, Continuity, FastPair, Tile)
  • WiFi environment, RSSI variance, channel congestion, AP density
  • Multi-protocol device discovery, mDNS, SSDP, ARP, HTTP probes
  • Vertex multimodal embeddings (1408-dim per observation)

Quick Start (Android)

Android is the primary platform. The full sensing surface ships as a single Maven AAR, one Gradle dependency line, no transitive setup.

1. Register the Maven registry

Add the registry block to your root settings.gradle.kts so Gradle can resolve com.trillboards:agent-core. maven.trillboards.com serves anonymously, no auth tokens needed.

settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://maven.trillboards.com") }
    }
}

2. Add the dependency

app/build.gradle.kts
dependencies {
    implementation("com.trillboards:agent-core:1.22.0")
}

Source repo (Apache 2.0): https://github.com/trillboards/agent-core. maven.trillboards.com publishes each release, and this page resolves the version straight from the registry. 1.22.0 is the current release. Pin it exactly, or use 1.22.+ to float the minor line.

3. Initialize the SDK

MyApplication.kt
import com.trillboards.sdk.TrillboardsSensingSdk

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        TrillboardsSensingSdk.start(
            context = applicationContext,
            partnerApiKey = BuildConfig.TRILLBOARDS_PARTNER_API_KEY
        )
    }
}

4. Declare manifest permissions

As of SDK 1.12 the discovery permission set (BLE, WiFi, location, multicast, network) merges into your app automatically from the SDK's library manifest, you only declare the two sensors that must stay host-owned: CAMERA and RECORD_AUDIO. The agent only activates the sensors your venue actually exposes; every hardware feature is declared optional so an APK without cameras, BLE, WiFi, or GPS still installs cleanly and the SDK degrades per-source (reporting a machine-readable skip_reason instead of silently going dark).

AndroidManifest.xml (your app)
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

The full set the SDK's manifest contributes (for review / allowlisting, do not re-declare unless noted):

Merged from the SDK
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
    android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Location note: GPS reporting is on by default and is part of the data contract your venues sign up for, grant ACCESS_FINE_LOCATION at runtime (MDM-managed fleets pre-grant it). If another dependency in your app declares ACCESS_FINE_LOCATION with maxSdkVersion="30" (many BLE/location plugins do), the SDK's manifest already carries a tools:node="replace" guard; if the capped declaration lives in YOUR app manifest, add the same marker there or GPS silently stops at Android 12+.

Quick Start (Linux / Windows)

For non-Android partners, Linux digital-signage players, Windows kiosks, embedded edge boxes, install the Node-based @trillboards/edge-sdk. It exposes the same sensing event surface backed by ONNX runtimes and BlueZ.

Terminal
npm install @trillboards/edge-sdk
agent.ts
import { EdgeAgent } from '@trillboards/edge-sdk';

const agent = new EdgeAgent({
  platform: 'linux',
  deviceToken: 'YOUR_DEVICE_TOKEN',
  screenId: 'screen-001',
  camera: { enabled: true },
  audio: { enabled: true },
});

await agent.start();

See the Edge SDK reference for federated learning, kiosk management, and the MCP fleet-control surface.

Quick Start (Browser / CTV WebView)

Creatives running inside a partner WebView consume sensing data through the bridge contract exposed by @trillboards/ctv-measurement. No camera permissions, no on-device models, the host agent fans out measurement snapshots and your WebView reads them.

Terminal
npm install @trillboards/ctv-measurement
creative.ts
// Inside a WebView creative running on a Trillboards-hosted screen
const bridge = (window as any).__TRILL_BRIDGE_V1__;

if (bridge?.getMeasurementSnapshot) {
  const snapshot = await bridge.getMeasurementSnapshot();
  console.log('attention', snapshot.attention);
  console.log('audience', snapshot.audience);
  console.log('discovery', snapshot.discovery);
}

See the CTV Measurement reference for the full bridge schema, GIVT-deduped impression ingestion, and QR attribution.

Configuration

Every knob below is partner-tunable at construction time or via server-pushed sensing-config updates. Defaults are tuned for retail tablets at typical mounting distances; only override when your venue calls for it.

OptionTypeDefaultDescription
partnerApiKeyStringNonePartner API key issued by Trillboards. Identifies the cohort all heartbeats land in.
apiBaseUrlStringhttps://api.trillboards.comOverride for staging or self-hosted ingestion. Most partners leave this default.
socketUrlStringhttps://chat.trillboards.comRealtime command channel. Used for server-pushed sensing-config updates and device commands.
heartbeatPathString/openrtb/v1/heartbeatPOST path for the heartbeat envelope. Override only when running behind a proxy that rewrites paths.
heartbeatIntervalMsLong30000Heartbeat cadence in milliseconds. 30s is the recommended default; lower values increase server cost without measurably improving freshness.
sharedPrefsNameStringNoneApp-scoped SharedPreferences name the agent uses for fingerprint persistence. Set per-app to avoid collisions in multi-tenant builds.
overlayRefreshActionStringNoneLocal-broadcast action the agent emits when the server pushes a creative refresh. Wire your overlay activity to receive this intent.
overlayBlackoutActionStringNoneLocal-broadcast action emitted on screen-blackout commands. Lets your kiosk shell react without polling.
SensingConfig.attention.dwellMsThresholdLong1500Minimum gaze dwell before an attention event is emitted. Raise to suppress glance-only signals; lower to capture lighter engagement.
SensingConfig.detection.personConfidenceThresholdFloat0.55Lower bound for person-detection confidence before downstream demographic / attention scoring runs.
SensingConfig.audio.classificationEnabledBooleantrueMaster switch for MediaPipe AudioClassifier. Disable on devices without microphone access.
SensingConfig.viewability.darkLuxThresholdFloatcalibratedAuto-calibrated by LuxCalibrator on first run. Frames below this lux value are excluded from viewability counts.
SensingConfig.capture.maxNearbyDevicesPerListInt50Hard cap on BLE / WiFi device-list length per heartbeat. Prevents unbounded payloads in dense venues.

Heartbeat Telemetry

From SDK 1.12 the heartbeat envelope (POST /openrtb/v1/heartbeat, ~65s effective cadence: a ~30s BLE scan window plus the 30s interval) carries the device-truth fields below alongside the audience and device-presence signals. You do not need to send any of this yourself; the SDK assembles it.

FieldTypeDescription
metadataobjectDevice identity block: manufacturer, model, os, sdkInt, uptimeSeconds, screen geometry, sensing mode, process-memory stats. Populates the fleet dashboard so Trillboards can support each device without asking you.
capabilitiesobjectHardware + ML capability snapshot (camera/microphone availability, camera health state, chipset, RAM, NPU, OS API level).
batteryobject{pct, charging, plugged} from BatteryManager. Aggregate device state only.
storageobject{total_mb, free_mb} for the app data volume, storage-pressure early warning.
host_appobject{package, version, version_code, foreground}, which app build embeds the SDK and whether it is foregrounded.
permission_mapobjectRuntime-grant map (camera, record_audio, location_fine, location_coarse, bluetooth_scan, bluetooth_connect, nearby_wifi_devices → boolean). Lets fleet support see a missing grant instead of a silent data gap.
gps_fix_age_ms / latitude / longitude / accuracy_metersnumberLatest device GPS fix + its age. GPS is on by default (see Location note above); omitted when no grant / no fix.
clock_skew_msnumberDevice clock minus server clock (from the previous heartbeat's Date header, ±1s). Surfaces devices whose clocks drift.
signal_queue_depthnumberLocally-buffered signal count while the realtime socket is down, backlog gauge.
config_ackobject{profile_id, sensing_config_version}, echoes the applied server config so "did the device apply config vX?" is answerable.
subsystem_healthobjectPer-subsystem status registry (camera, microphone, ML pipeline, memory, player, socket).
skip_reason_countsobjectPer-source skip counters since the previous heartbeat (see the table below).

skip_reason semantics

When a collector cannot run, it reports a machine-readable reason instead of silently emitting zero rows, so a missing runtime grant looks different from an empty room. Counters accumulate per source between heartbeats and reset on send. Trillboards monitors fleet-level permission_denied and scanner_unavailable rates, which is how a mis-provisioned rollout gets caught quickly.

SourceReasonMeaning
blepermission_deniedBLUETOOTH_SCAN (or legacy FINE_LOCATION) runtime grant missing, the scan never ran.
blebluetooth_disabledBluetooth radio is off.
blescanner_unavailable / adapter_unavailableBLE stack present but the scanner could not start (adapter wedged, OEM restriction).
wifipermission_deniedNEARBY_WIFI_DEVICES (API 33+) or FINE_LOCATION (older) grant missing.
wifiwifi_disabledWiFi radio off AND "scanning always available" off, no scan possible.
wifiwifi_throttledThe OS rejected a fresh scan (4/2-min foreground throttle) and the cached results were stale, so the SDK refused to ship a prior location's networks (data-truth guard for vehicles).
mdns / ssdpmulticast_lock_failedAndroid denied the multicast lock, inbound discovery packets are being dropped.
mdns / ssdp / http_probeffi_failure / no_responseThe native discovery core failed or the probed host did not answer.

Data & Defaults

What the Sensing SDK sends, how long Trillboards keeps it and who receives it.

  • Per-person records, one for each person the camera detects: estimated age range, gender and dominant emotion, dwell time and time spent looking at the screen, with head orientation, facial measurements and upper-body points for each analysed frame. Trillboards keeps them for 400 days, and its encrypted archive keeps the records with no scheduled deletion.
  • Face-identity templates, on the full sensing profile: a vector of 512 numbers for each detected person, which Trillboards clusters into person identifiers that recognise the same person across screens and days and links to the device keys of the phones seen with them. Templates are kept for 400 days and are never sold or shared with a data partner.
  • Camera frames, selected frames go to Trillboards and Google Vertex AI, where a cloud model estimates attributes including ethnicity, age band, social composition, purchase intent, attire and lifestyle segments. Trillboards keeps the frames for 400 days, and the frames captured between 5 May and 21 June 2026 are kept as a model-evaluation set with no scheduled deletion. No video is recorded.
  • Speech, the agent transcribes speech on the device. Trillboards stores the transcript and a cloud model reads it for purchase intent, brands mentioned and sentiment. No audio recording is stored or transmitted.
  • Radio identifiers, the Bluetooth, WiFi and local-network identifiers nearby devices broadcast, stored as received and as device keys; Trillboards links a device key to the person seen with the device and to mobile advertising IDs.
  • Advertising ID, the agent transmits the device's Google Advertising ID (GAID) when available, used for cross-device frequency capping and partner attribution. The agent honors LIMIT_AD_TRACKING.
  • Defaults, sensing, face detection, audio classification, speech intelligence, demographics and advertising-ID collection are on by default, each with a SensingSdkConfig flag that turns it off. GPS capture runs whenever the app holds the location permission.
  • Data partners, Trillboards sells and shares audience measurement and location data with its data partners, including 375ai and Intuizi. The Data Processing Addendum at trillboards.com/legal/data-processing sets out every category, how long each is kept and who receives it.
  • Transparent disclosure, partners are responsible for venue-side signage. The SDK exposes a status surface so your kiosk shell can render the active sensing posture to viewers on request.

Comparison

Capabilities differ across the three platform SDKs because the underlying runtimes do. The table below is honest about per-platform gaps so you can pick the right surface for your hardware.

CapabilitySensing SDK (Android)Edge SDK (Linux/Windows)CTV Measurement (Browser)
Face detectionFaceXFormer (full)BlazeFace ONNXMediaPipe Web (limited)
Audio classificationMediaPipe AudioClassifierONNX YAMNetNot supported
Speech recognitionMoonshine ASRNot supportedNot supported
BLE co-viewingNative (BLUETOOTH_SCAN)BlueZ (Linux only)Web Bluetooth (user-gesture-gated)
WiFi environmentNative (RSSI / channel)NativeNot supported
Discovery (mDNS/SSDP/ARP)Full multi-protocolPartialNot supported
Vertex multimodal embeddingsYes (1408-dim)Not supportedNot supported

Need help?

Email engineering@trillboards.com for integration support. For native IMA SDK consumers, the partner-native-ima-sdk guide's Section 12 covers the Maven coordinate, GitHub Packages registry block, and minimal init snippet from a partner-ad-serving perspective.

Ship the first sensing-enabled fleet.

One Gradle line on Android. One npm install on Linux/Windows. One bridge call in the browser. Same canonical sensing surface across all three.