An Android application for reading measurements from a Trotec SL400 sound level meter over USB OTG / USB serial, displaying live sound values in a custom mobile UI, capturing incident audio, and optionally forwarding alerts and remote-control commands through Matrix.
This project now combines five main areas:
- USB serial communication with the SL400
- Reverse-engineered protocol decoding for measurement frames
- Acoustic metrics and alert evaluation
- Incident audio buffering and clip upload
- Matrix integration for messaging, alerting, clip delivery, and remote control
The goal of this app is to turn an Android device into a mobile field companion for the Trotec SL400.
The app can:
- detect compatible USB serial devices
- connect to the SL400 over USB OTG
- continuously read and decode measurements
- show the latest sound level in dB
- compute rolling acoustic metrics such as LAeq 1 min / 5 min / 15 min and max 1 min
- evaluate configurable alert rules
- capture buffered audio clips around incidents
- store and manage incident history locally
- send alert messages and audio clips to Matrix rooms
- receive remote commands from a Matrix room to update alert behavior, query incidents, and fetch clips
The UI is optimized around a simple live measurement screen and a separate settings area for configuration and diagnostics.
- USB OTG based device connection
- detection of compatible USB serial devices using
usb-serial-for-android - explicit USB permission handling through
UsbManager - automatic device refresh on attach / detach
- connect / disconnect support
- foreground service while measurement is active
- marker-based protocol parsing
- live decoding of SL400 frames
- extraction of measurement values and auxiliary bytes
- recent measurement history in the UI
- rolling acoustic metrics over multiple time windows
- current live value
- LAeq 1 minute
- LAeq 5 minutes
- LAeq 15 minutes
- max 1 minute
- time-above-threshold tracking for the last minute
- window coverage tracking so alerts do not trigger before enough data exists
- configurable threshold in dB
- hysteresis support to avoid noisy re-triggering
- threshold crossing or periodic alert modes
- selectable trigger metric
- target room override for alert delivery
- persisted alert configuration with DataStore
- continuous audio ring buffer for incident capture
- configurable pre-roll and post-roll clip creation in code
- local WAV clip generation
- automatic audio hint analysis with confidence, source hints, and quality flags (sub/bass/mid/high ratios, low-end pulse detection, clipping/DC/low-signal checks)
- local JSONL-based incident store
- retention cleanup for old incidents and clips
- reuse of already uploaded Matrix media URLs when possible
- send Matrix test messages
- send alert messages to a Matrix room
- sync a Matrix command room
- ignore the app's own Matrix user during sync
- persist Matrix sync token across sessions
- modular Matrix command handling
- remote alert configuration via Matrix chat commands
- incident list queries by time window
- summary queries
- JSON export of incident data
- on-demand graphs (PNG)
- daily report scheduler (summary + optional JSON + optional graph)
- audio buffer remote control
- clip upload on demand
- audio hint follow-up messages with confidence and source hints
- custom themed live measurement screen
- settings screen with separate Matrix and alert sections
- USB audio test screen
- alarm audio buffer screen
- custom background and device imagery
- recent sample preview list with a bounded history
This project is based on a partially reverse-engineered protocol.
What is already working well:
- USB serial connection
- measurement frame parsing
- live value display
- rolling acoustic metrics
- alarm evaluation logic
- Matrix alert sending
- Matrix command sync and parsing
- incident storage
- incident clip generation and upload
- persistent settings with DataStore
What is still evolving:
- protocol documentation for all tags
- UI polish and information architecture
- clip metadata enrichment such as duration and waveform previews
- more advanced graphs and history views
- more robust background behavior on all Android / USB combinations
- broader hardware testing for USB microphones and hubs
At a high level, the app works like this:
Sl400Repositoryreads raw serial data from the device.Sl400Decoderconverts tagged byte frames intoSl400Sampleobjects.AcousticMetricsEnginebuilds rolling metrics from the live sample stream.AlarmEvaluatordecides whether an alert should be fired based on the selected metric mode and alert settings.IncidentRepositorystores the incident, whileAlarmAudioCaptureCoordinatorcreates a buffered WAV clip.HttpMatrixPublishersends alert messages and optional audio clips to Matrix.MatrixSyncClientlistens for commands from the configured Matrix command room.MatrixCommandProcessorand its command handlers interpret remote commands and apply changes or queries.
The orchestration happens in Sl400ViewModel.
The app uses Android's USB host APIs together with the usb-serial-for-android library.
Serial parameters currently used:
- Baud rate:
9600 - Data bits:
8 - Parity:
NONE - Stop bits:
1 - Flow control:
NONE - DTR:
false - RTS:
false
Device discovery happens via UsbSerialProber.getDefaultProber().findAllDrivers(...).
When a user selects a device:
- the app checks USB permission
- Android permission is requested if needed
- the first available serial port is opened
- serial parameters are applied
- a read loop starts in a coroutine on
Dispatchers.IO
Main class:
sl400/Sl400Repository.kt
The protocol is currently interpreted as a tagged byte stream with 0xA5 as a marker byte.
The decoder works as a small state machine:
SEEK_MARKERREAD_TAGREAD_PAYLOAD
Known tags currently handled:
0x0D→ measurement payload0x06→ auxiliary 3-byte payload0x00→ sample end / sample emit trigger- additional observed tags with fixed lengths are recognized but not yet semantically decoded
Measurement decoding currently uses a packed BCD-like interpretation:
- hundreds from low nibble of byte 1
- tens / ones from byte 2
- final value is represented as tenths of dB
Example:
- raw tenths
478→47.8 dB
Main decoder:
sl400/Sl400Decoder.kt
Each complete sample is represented as:
data class Sl400Sample(
val timestampMs: Long,
val db: Double,
val rawTenths: Int,
val aux06Hex: String?,
val tags: List<Int>
)This gives the app both:
- a user-friendly value (
db) - a raw machine-friendly value (
rawTenths) - optional auxiliary payload data
- the seen tag list for debugging / analysis
The most recent samples are exposed through a SharedFlow in the repository and collected in the Sl400ViewModel.
Rolling metrics are computed by:
sl400/AcousticMetrics.ktsl400/AcousticMetricsEngine.kt
The engine keeps a sliding in-memory sample window and computes:
currentDblaEq1MinlaEq5MinlaEq15MinmaxDb1MintimeAboveThresholdMs1Mincoverage1MinMscoverage5MinMscoverage15MinMs
LAeq-based alerts should not trigger before enough data exists.
For example:
LAEQ_1_MINrequires at least 60 seconds of valid coverageLAEQ_5_MINrequires at least 5 minutes of valid coverageLAEQ_15_MINrequires at least 15 minutes of valid coverage
This logic prevents false early triggers when the app has only recently started collecting measurements.
enum class MetricMode {
LIVE,
LAEQ_1_MIN,
LAEQ_5_MIN,
LAEQ_15_MIN,
MAX_1_MIN
}Alert behavior is handled by:
alert/AlertConfig.ktalert/AlarmEvaluator.kt
enabledthresholdDbhysteresisDbminSendIntervalMssendModemetricModeallowedSenderscommandRoomIdtargetRoomIdalertHintFollowupEnableddailyReportEnableddailyReportHourdailyReportMinutedailyReportRoomIddailyReportJsonEnableddailyReportGraphEnabled
Sends an alert only when the selected metric crosses the threshold from below.
Sends an alert when crossing the threshold and then continues sending periodically while the selected metric remains above the threshold and the minimum interval has elapsed.
Hysteresis prevents repeated re-triggering around the threshold.
Example:
- threshold =
70.0 - hysteresis =
2.0
The system triggers above 70.0 and only resets once the selected metric falls to 68.0 or below.
When incident audio capture is enabled, alerts are delayed briefly so the audio hint can be included.
If the hint is not ready within a short timeout, the alert is sent without it.
Optionally, a follow-up hint message can be sent depending on alertHintFollowupEnabled.
Example audio hint message:
SL400 audio hint (id=...): bass-heavy music (0.79), likely stage / PA dominated (clipped)
Incident persistence is handled by:
incident/IncidentRecord.ktincident/IncidentRepository.kt
Incidents are stored locally in:
filesDir/incidents/incidents.jsonl
Each incident stores:
- incident ID
- timestamp
- room ID
- metric mode and metric value
- threshold at trigger time
- rolling acoustic metrics
- time above threshold for the last minute
- local clip path
- upload state
- Matrix content URI (
mxcUrl) if already uploaded - optional audio hint field
The repository can automatically:
- remove incidents older than a configured retention window
- limit the total number of incident records
- limit the total number of stored clips
- delete unreferenced clip files after cleanup
Current cleanup call in the view model keeps:
- up to 14 days of incident history
- up to 2000 records
- up to 200 clips
Incident audio buffering is handled by:
audio/AlarmAudioCaptureCoordinator.ktaudio/AudioRingBuffer.ktaudio/WavWriter.kt
The app can keep an in-memory ring buffer of microphone audio so that when an alert fires, it captures a clip containing audio from before and after the incident.
Current defaults in code:
- ring buffer:
30 seconds - pre-roll:
10 seconds - post-roll:
20 seconds
The app tries to pick a preferred input device in this order:
- USB device
- USB headset
- first available input
- local start/stop of the alarm audio buffer in the UI
- remote Matrix commands for
audio start,audio stop, andaudio status - local USB microphone test recording via
AudioTestRecorder
Audio hints are computed from incident clips and include label, confidence, source hint, and quality flags (clipped input, DC offset, low signal). Hints can be computed from WAV files or directly from PCM:
AudioHintAnalyzer.analyzeWav(file)
AudioHintAnalyzer.analyzePcm16(pcmData, sampleRate, channels)Incident clips are written as WAV / PCM 16-bit files.
Example file location:
filesDir/audio_incidents/incident_<id>_<timestamp>.wav
The audio coordinator uses an isCapturing flag so multiple incident captures do not overlap.
Matrix support is implemented as a lightweight client integration.
Main files:
matrix/MatrixConfig.ktmatrix/MatrixSettingsRepository.ktmatrix/HttpMatrixPublisher.ktmatrix/MatrixSyncClient.ktalert/MatrixCommandProcessor.ktalert/MatrixConfigCommandHandler.ktalert/MatrixAudioCommandHandler.ktalert/MatrixQueryCommandHandler.ktalert/MatrixClipCommandHandler.ktalert/MatrixExportCommandHandler.ktalert/MatrixReportCommandHandler.ktalert/MatrixGraphCommandHandler.ktalert/MatrixHelpCommandHandler.kt
The app can send:
- plain Matrix test messages
- alert messages
- text responses to Matrix commands
- audio clips as
m.audio - files (JSON) as
m.file - graphs (PNG) as
m.image - already-uploaded audio again using a stored
mxc://URL without re-uploading
The app can sync a command room using /sync and:
- persist the
next_batchtoken - ignore events coming from its own Matrix account
- parse chat commands
- update alert settings from received commands
- answer queries with text summaries
- upload or resend clips
The app listens for commands in a configured commandRoomId and uses allowedSenders to restrict which Matrix users are allowed to control the device.
Daily reports are scheduled via WorkManager and can send:
- a summary message
- an optional JSON export
- an optional incident graph (PNG)
When the app successfully connects to the SL400, it starts a foreground service:
Sl400ForegroundService.kt
This service exists to support background measurement operation and keep the app alive while the USB connection is active.
It creates a low-importance notification channel and runs with:
- foreground notification
FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
This is important for newer Android versions.
The app currently has two main UI modes plus additional settings cards.
A simplified live measurement screen with:
- large current dB value
- USB connection status
- background artwork
- device illustration
- quick actions for auto-connect and opening settings
Structured into cards for:
- USB / connection handling
- Matrix configuration
- alert configuration
- USB microphone test
- alarm audio buffer
- recent samples
The Matrix integration lets you:
- configure alerts remotely
- start / stop audio buffering remotely
- request incident lists
- request summaries
- request clips
UI is written with Jetpack Compose and themed via:
ui/theme/Color.ktui/theme/Theme.ktui/theme/Type.kt
The app currently supports the following Matrix chat commands in the configured command room:
!sl400 help
!sl400 status
!sl400 config
!sl400 set enable true|false
!sl400 set threshold 72.5
!sl400 set hysteresis 2.0
!sl400 set interval 30000
!sl400 set mode crossing|periodic
!sl400 set metric live|laeq1|laeq5|laeq15|max1
!sl400 set commandroom !abc123:server.tld
!sl400 set targetroom !xyz456:server.tld
!sl400 allow @user:server.tld
!sl400 deny @user:server.tld
!sl400 set dailyreport true|false
!sl400 set reporttime 08:30
!sl400 set reportroom !report:server.tld
!sl400 set reportjson true|false
!sl400 set reportgraph true|false
!sl400 set alerthint true|false
!sl400 audio start
!sl400 audio stop
!sl400 audio status
!sl400 incidents since 30m
!sl400 incidents between 2026-03-27T18:00 2026-03-27T23:00
!sl400 incidents today
!sl400 incidents yesterday
!sl400 clips since 2h
!sl400 clip last
!sl400 clip incident <incidentId>
!sl400 summary since 1h
!sl400 summary today
!sl400 summary yesterday
!sl400 json today
!sl400 json yesterday
!sl400 json since 24h
!sl400 json since 24h cliponly
!sl400 json since 24h metric=laeq5
!sl400 report now
!sl400 report today
!sl400 report yesterday
!sl400 report since 24h
!sl400 graph today
!sl400 graph yesterday
!sl400 graph since 6h
statusreturns the current alert configurationset ...updates alert and report configurationcommandroomchanges the command room IDtargetroomchanges the destination room for alertsallowadds a Matrix user to the allowed sender listdenyremoves a Matrix user from the allowed sender listaudio ...controls or queries the audio ring bufferincidents ...returns incident lists for a requested time rangeclips ...returns available clip informationclip ...uploads the latest or a specific incident clipsummary ...returns a compact aggregated incident summaryjson ...returns a JSON export of incidents as a file uploadreport ...returns a summary and (optionally) JSON/graph based on report settingsgraph ...returns a PNG graph of incidents
The following settings are stored using DataStore:
homeserverBaseUrlaccessTokenroomIdenabledsyncToken
These are managed in:
matrix/MatrixSettingsRepository.kt
roomIdshould be the Matrix room ID used for test messages or as a default target room- HTTPS should be used for the homeserver URL
- access tokens should be treated as sensitive credentials
The following settings are persisted using DataStore:
- alert enabled / disabled
- threshold
- hysteresis
- minimum interval
- send mode
- metric mode
- command room ID
- target room ID
- alert hint follow-up enabled
- daily report enabled
- daily report time
- daily report room override
- daily report JSON enabled
- daily report graph enabled
- allowed senders CSV
These are managed in:
alert/AlertSettingsRepository.kt
Allowed Senders is a comma-separated list of full Matrix user IDs that are allowed to send configuration commands.
Example:
@alice:example.org,@bob:example.org
If the list is empty, any sender in the configured command room can issue commands.
Recommended production setup:
- use a dedicated command room
- allow only specific user IDs
- keep the command room separate from the alert room
- use a dedicated Matrix bot account or app account for SL400 control
app/src/main/java/de/drremote/trotecsl400/
├── MainActivity.kt
├── Sl400ForegroundService.kt
├── UsbDeviceExtensions.kt
├── alert/
│ ├── AlarmEvaluator.kt
│ ├── AlertConfig.kt
│ ├── AlertSettingsRepository.kt
│ ├── JsonExporter.kt
│ ├── DailyReportScheduler.kt
│ ├── DailyReportWorker.kt
│ ├── IncidentGraphRenderer.kt
│ ├── MatrixAudioCommandHandler.kt
│ ├── MatrixClipCommandHandler.kt
│ ├── MatrixCommandHandler.kt
│ ├── MatrixCommandParsing.kt
│ ├── MatrixCommandProcessor.kt
│ ├── MatrixCommandTypes.kt
│ ├── MatrixConfigCommandHandler.kt
│ ├── MatrixExportCommandHandler.kt
│ ├── MatrixGraphCommandHandler.kt
│ ├── MatrixHelpCommandHandler.kt
│ ├── MatrixQueryCommandHandler.kt
│ ├── MatrixReportCommandHandler.kt
│ └── SummaryFormatter.kt
├── audio/
│ ├── AlarmAudioCaptureCoordinator.kt
│ ├── AudioHintAnalyzer.kt
│ ├── AudioInputDeviceSelector.kt
│ ├── AudioRingBuffer.kt
│ ├── AudioTestRecorder.kt
│ └── WavWriter.kt
├── incident/
│ ├── IncidentRecord.kt
│ └── IncidentRepository.kt
├── matrix/
│ ├── HttpMatrixPublisher.kt
│ ├── MatrixConfig.kt
│ ├── MatrixPublisher.kt
│ ├── MatrixSettingsRepository.kt
│ ├── MatrixSyncClient.kt
│ └── MxcUploadResult.kt
├── sl400/
│ ├── AcousticMetrics.kt
│ ├── AcousticMetricsEngine.kt
│ ├── Sl400Decoder.kt
│ ├── Sl400Repository.kt
│ ├── Sl400Sample.kt
│ └── Sl400ViewModel.kt
└── ui/
├── cards/
│ ├── AlarmAudioCard.kt
│ ├── AlarmCard.kt
│ ├── AudioTestCard.kt
│ ├── CardShape.kt
│ ├── ConnectionCard.kt
│ ├── MatrixCard.kt
│ ├── RecentSamplesCard.kt
│ └── StatusCard.kt
├── screens/
│ ├── HomeScreen.kt
│ └── SettingsScreen.kt
└── theme/
├── Color.kt
├── Theme.kt
└── Type.kt
Additional UI image resources are expected under res/drawable, for example:
sl400_bgsl400_metersl400_mock
- Android phone or tablet with USB OTG support
- Trotec SL400
- appropriate USB OTG adapter / cable
- optional USB microphone for audio capture
- Android Studio
- Kotlin
- Gradle
- Android SDK compatible with Jetpack Compose
- Internet access for Matrix features
- reachable Matrix homeserver over HTTPS
At minimum, you should verify the following Android configuration.
Common required permissions include:
android.permission.INTERNETandroid.permission.RECORD_AUDIOandroid.permission.POST_NOTIFICATIONS(Android 13+ if notifications are used)android.permission.FOREGROUND_SERVICEandroid.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE
USB host support should be declared:
<uses-feature android:name="android.hardware.usb.host" />The foreground service should be declared in the manifest and configured for connected devices.
git clone <your-repository-url>Import the project and allow Gradle sync to complete.
Before running the app, make sure:
- drawable resources are present
- USB host feature is declared
- foreground service is correctly configured
- required permissions are present
- attach the device through USB OTG
- open the app
- use auto-connect or scan devices manually
- confirm USB permission
Open settings and enter:
- homeserver base URL
- access token
- room ID
Then:
- save settings
- enable Matrix sending
- send a test message
Set:
- threshold
- hysteresis
- interval
- mode
- metric
- command room
- target room
- allowed senders
Save the alert configuration.
- grant microphone permission
- start the alarm audio buffer from the UI or via Matrix command
- verify the selected microphone input and sample rate
Likely causes include:
- foreground service misconfiguration in the Android manifest
- missing foreground service permission
- Android version specific service restrictions
- unsupported USB device / driver mismatch
Possible reasons:
- missing USB permission
- incompatible USB serial driver
- device not exposing the expected serial port
- cable or OTG issue
Check the following:
- homeserver URL uses
https:// - access token is valid
- room ID is correct
- Matrix account is joined to the target room
- internet permission is present in the manifest
Verify:
commandRoomIdis correct- sender is listed in
allowedSenders, or the list is empty - Matrix sync is enabled through valid Matrix settings
- the app's Matrix user is able to sync the command room
Verify:
- alerts are enabled
- threshold is reachable
- the selected metric has enough window coverage
- hysteresis is not preventing re-triggering
- target room is valid
- Matrix sending is enabled
Verify:
- microphone permission is granted
- the alarm audio buffer is running
- a usable input device is available
- the clip file path exists after trigger
Check:
- access token validity
- Matrix media API availability on the homeserver
- network connectivity
- room permissions for sending audio messages
- the SL400 protocol is not fully documented
- some tags are still unknown and only stored for debugging
- no chart view or long-term measurement history visualization yet
- recent samples are intentionally limited in the UI
- alert state is local and intentionally simple
- Matrix integration is lightweight and not a full SDK-based client
- audio clips currently use basic metadata only
- not all USB microphones and Android devices have been tested
Potential future improvements:
- chart view for recent measurements
- min / max / average statistics in the UI
- JSON export of samples and incidents
- better onboarding and connection diagnostics
- clearer Matrix diagnostics screen
- dedicated bot account setup guide
- local Android notifications for alerts
- richer alarm payloads and state reporting
- support for additional SL400 protocol fields
- optional automatic reconnect behavior
- clip duration / waveform / upload status details
- downloadable recordings directly from chat workflows
This is an unofficial project based on reverse engineering and experimentation.
It is not an official Trotec application and is not affiliated with Trotec.
Protocol understanding may still evolve as more tags and behaviors are analyzed.
No license has been defined yet.
If you plan to publish this repository publicly, add an explicit license file such as:
- MIT
- Apache-2.0
- GPL-3.0
Depending on how you want the project to be used.




