SQLite Log Format
Ultralight Tags can store downloaded data in SQLite database files with the
.db3 extension. These files are produced by tag-dwnld and by host download
workflows that use the same SQLite writer.
Each database contains a small set of common tables plus the sensor tables for
the downloaded tag type. Start with the streams table when you want to find
the measurements in a file: it lists each available stream, the table and
column that store it, and the units for the value.
Tools For Inspecting Logs
You can inspect .db3 files with any SQLite-compatible tool.
- DB Browser for SQLite is a desktop application for opening tables, browsing rows, and running SQL queries without using the command line.
- The
sqlite3command-line tool is useful for quick checks and scripted analysis. - Python, R, MATLAB, and many notebook environments can read SQLite databases directly.
Quick Command-Line Checks
List the tables in a database:
sqlite3 my-log.db3 ".tables"
List the sensor streams described by the database:
sqlite3 my-log.db3 \
"select stream_id, table_name, time_column, value_column, units from streams order by stream_id;"
Show the tag type and UUID:
sqlite3 my-log.db3 \
"select fieldname, value from info where fieldname in ('tagtype', 'uuid');"
Common Tables
Every SQLite log has common metadata tables. The sensor tables vary by tag type.
| Table | Purpose |
|---|---|
schema_info |
Describes the database schema version and the software component that produced the file. |
info |
Stores tag metadata as field/value pairs, including tag type, UUID, tag info JSON, and configuration JSON. |
states |
Stores state-history snapshots reported by the tag around the collection and download. |
streams |
Maps measurements to sensor tables, time columns, value columns, display names, units, and quantities. |
schema_info
schema_info is a key/value table for database-level metadata.
| Column | Meaning |
|---|---|
key |
Metadata key. Current files include log_schema_version and producer. |
value |
Metadata value as text. |
info
info stores flexible field/value metadata. Common fields include:
| Field | Meaning |
|---|---|
tagtype |
Tag family name recorded by the downloader. |
uuid |
Tag UUID. |
info |
JSON copy of the tag information returned by the device. |
config |
JSON copy of the tag configuration used for the download. |
bittag_log |
BitTag log packing mode, present for classic BitTag logs. |
The JSON fields are useful when an analysis script needs configuration details that are not promoted into their own columns.
The JSON values in info, config, and calibration rows are generated from
the same Protobuf messages used by the host/tag protocol. Field names follow
the schema names from the repository's proto/ directory. Use
proto/tag.proto for tag configuration, state, and metadata messages. Use
proto/tagdata.proto for log payload messages and CalibrationConstants when
you need to interpret nested data or calibration JSON fields.
states
states records tag state-history entries. These rows are separate from sensor
samples.
| Column | Meaning |
|---|---|
Epoch |
UTC Unix timestamp in seconds. |
State |
Tag state name, such as a running or terminal state. |
EntryCode |
Reason reported for entering that state. |
Temperature |
Tag-reported internal temperature in degrees C. |
Voltage |
Tag supply voltage in V. |
InternalLogSize |
Count of internal log records or pages reported by the tag. |
ExternalLogSize |
Count of external log records or pages reported by the tag. |
streams
streams is the best table for discovering usable data. It tells viewers and
analysis scripts where each measurement lives.
| Column | Meaning |
|---|---|
stream_id |
Stable programmatic name for the measurement. |
group_id |
Optional group for related columns, such as the columns in a compass record. |
group_name |
Human-readable name for the group. |
table_name |
Table containing the data. |
time_column |
Column to use as the x-axis or sample time. |
value_column |
Column containing this stream's value. |
stream_kind |
Kind of stream. Current logs use scalar and record_column. |
display_name |
Human-readable measurement name. |
units |
Engineering units for the value, when applicable. |
quantity |
General physical quantity, such as pressure, voltage, or acceleration. |
comment |
Short description of the stream. |
scalar streams can be plotted directly as one value against one time column.
record_column streams are columns in a wider record table. For example,
CompassTag stores acceleration and magnetic-field columns in one Compass
table, and streams describes each column separately.
Time Columns
Most tag families use Epoch as their time column. Epoch is a UTC Unix
timestamp in seconds.
IMUTag high-rate tables use elapsed collection time instead:
| Column | Meaning |
|---|---|
ElapsedUs |
Corrected elapsed microseconds from the start of the retained collection. This is the ordinary time column for IMUTag sensor streams. |
RawElapsedUs |
Nominal elapsed microseconds from the start of the segment, before clock correction. It restarts at zero in every segment, so it is not a timeline for the whole log. |
SegmentId |
Timing segment containing the row. A new segment starts at collection start and after explicit resync events. |
SampleIndex |
Sample index within the segment. |
The IMUTag clock correction comes from ppm_clock_error in the info JSON
field. The downloader copies the value it actually used into
ImuSegment.CorrectionPpm for each timing segment. Older firmware may omit
ppm_clock_error; in that case the downloader uses 0 ppm.
Within a segment, RawElapsedUs is computed from sample count and configured
sample rate. ElapsedUs applies the segment's ppm correction:
ElapsedUs = segment_start_us
+ round(RawElapsedUs * (1 + CorrectionPpm / 1000000.0))
Use ElapsedUs for ordinary plotting and analysis. Use RawElapsedUs,
CorrectionPpm, and the segment tables when you need to audit or rebuild the
timing model.
To convert IMUTag elapsed time to seconds:
select ElapsedUs / 1000000.0 as seconds, ax, ay, az
from ImuAccel
order by ElapsedUs;
Shared Sensor Tables
Several tag families use the same simple table shapes. A table appears only when the tag type records that measurement.
| Table | Columns | Units | Meaning |
|---|---|---|---|
Voltage |
Epoch, Voltage |
V | Tag supply voltage. |
CoreTemperature |
Epoch, Temperature |
C | Internal tag temperature. |
Activity |
Epoch, Activity |
% | Activity value over the tag's sample bucket. |
Pressure |
Epoch, Pressure |
mbar | Absolute pressure from the pressure sensor. |
Temperature |
Epoch, Temperature |
C | Temperature reported by the pressure sensor. |
CoreTemperature and Temperature are different measurements. Use
CoreTemperature for the tag's internal temperature and Temperature for the
pressure sensor's temperature.
BitTag, BitTag LE, And BitTagNG
BitTag-family logs contain:
| Table | Contents |
|---|---|
Voltage |
One voltage row for each downloaded log entry or page. |
CoreTemperature |
One internal-temperature row for each downloaded log entry or page. |
Activity |
Activity rows unpacked from compact activity buckets. |
Classic BitTag logs use the bittag_log value in info to describe how
activity was packed. BitTagNG activity is stored as 15-second buckets. In both
cases, read the expanded Activity table for analysis rather than trying to
interpret the compact field yourself.
Example:
select Epoch, Activity
from Activity
order by Epoch;
PresTag
PresTag logs contain:
| Table | Contents |
|---|---|
Voltage |
Tag supply voltage. |
Pressure |
Absolute pressure samples in mbar. |
Temperature |
Pressure-sensor temperature samples in degrees C. |
Samples are written using the sample period from the tag configuration. Raw PresTag downloads are converted to engineering units before rows are written.
Example:
select Epoch, Pressure
from Pressure
order by Epoch;
BitPresTag
BitPresTag logs combine activity and pressure-family measurements.
| Table | Contents |
|---|---|
Voltage |
Tag supply voltage. |
Activity |
Activity rows unpacked from compact 15-second buckets. |
Pressure |
Absolute pressure samples in mbar. |
Temperature |
Pressure-sensor temperature samples in degrees C. |
Example:
select p.Epoch, p.Pressure, t.Temperature
from Pressure as p
join Temperature as t on t.Epoch = p.Epoch
order by p.Epoch;
UIUCTag
UIUCTag logs use the same tables and streams as BitPresTag, so the same queries work for both.
| Table | Contents |
|---|---|
Voltage |
One row per two-hour block, at the block's start time. |
Pressure |
One row per five-minute sample that has a pressure value, in mbar. |
Temperature |
One row per five-minute sample that has a temperature value, in degrees C. |
Activity |
Five rows per sample, one per one-minute bucket that follows it, as a percentage of the minute spent active. |
A sample or activity word that was never written, or a conversion that failed, produces no row; missing measurements are omitted rather than stored as a placeholder, so gaps stay visible in plots and out of aggregates. The newest sample in a log normally has no activity rows yet, because its activity is recorded one sample period later.
Block start times follow the run's first minute boundary, so they are generally not multiples of two hours.
CompassTag
CompassTag logs contain the BitTag-style summary streams plus a raw compass record table and calibration history.
| Table | Contents |
|---|---|
Voltage |
Tag supply voltage. |
CoreTemperature |
Internal tag temperature. |
Activity |
Activity samples. |
Compass |
Raw accelerometer and magnetometer record columns. |
Calibration |
JSON calibration constants with their timestamps. |
Compass has these columns:
| Column | Units | Meaning |
|---|---|---|
Epoch |
seconds | UTC Unix timestamp. |
ax, ay, az |
mg | Raw accelerometer axes used for orientation. |
mx, my, mz |
uT | Raw magnetometer axes used for orientation. |
SensorViz uses Compass and Calibration together to generate heading,
pitch, roll, dip, field-strength, and acceleration views.
Example:
select Epoch, mx, my, mz
from Compass
order by Epoch;
IMUTag
IMUTag logs use elapsed-time tables because the high-rate IMU samples are reconstructed from raw pages, timing anchors, configured sample rate, and clock correction metadata.
| Table | Contents |
|---|---|
ImuHeader |
One row for each downloaded IMUTag page header. Preserves the RTC timestamp anchor and raw header flags. |
ImuSegment |
Collection-start and resync timing segments used to reconstruct elapsed sample time. |
ImuEvent |
Resync and restart markers for viewers and analysis scripts. |
ImuAccel |
High-rate accelerometer samples in mg. |
ImuGyro |
High-rate gyroscope samples in dps. |
ImuMag |
Lower-rate magnetometer samples in uT. |
ImuPressure |
Lower-rate pressure samples in mbar. |
ImuTemperature |
Pressure-sensor temperature samples in degrees C. |
Calibration |
JSON calibration constants with their timestamps. |
The sample tables share timing columns:
| Column | Meaning |
|---|---|
SegmentId |
Timing segment containing the sample. |
SampleIndex |
Nominal sample index within the segment. For ImuMag, ImuPressure and ImuTemperature rows this is the first IMU sample index of the superframe the value belongs to. |
RawElapsedUs |
Uncorrected elapsed microseconds from the segment start: SampleIndex times the nominal sample period. |
ElapsedUs |
Corrected elapsed microseconds used by SensorViz. |
ImuAccel, ImuGyro, and ImuMag then add axis columns:
| Table | Value columns |
|---|---|
ImuAccel |
ax, ay, az |
ImuGyro |
gx, gy, gz |
ImuMag |
mx, my, mz |
ImuPressure adds Pressure, and ImuTemperature adds Temperature.
For most analysis, use ElapsedUs as the sample time. Keep ImuHeader,
ImuSegment, and ImuEvent when you need to inspect timing anchors, restarts,
or storage skips, or when a filter or fusion tool should model clock state and
timing uncertainty explicitly.
ImuHeader has one row per downloaded page header:
| Column | Meaning |
|---|---|
HeaderIndex |
Zero-based header counter in download order. |
SegmentId |
Timing segment active at this header. |
StartElapsedUs |
Corrected elapsed microseconds assigned to the page start. |
Epoch |
RTC epoch seconds from the page header. |
Millisecond |
Millisecond rounded from the 1/1024-second subsecond ticks. |
SubsecondTicks |
Raw subsecond tick field. |
SubsecondHz |
Tick frequency for SubsecondTicks (1024). |
Flags |
Raw header flags: 0x0400 RESYNC starts a new timing segment; 0x0800 RESYNC_STORAGE_SKIP means the previous segment ended with a storage skip; 0x1000 RESTART_RECOVERY means the segment follows monitor reset recovery. |
Temperature |
Pressure-sensor temperature in degrees C from the page header. |
ImuSegment has one row for collection start and one for each resync. It
repeats the anchoring header's HeaderIndex, StartElapsedUs, Epoch,
Millisecond, SubsecondTicks, SubsecondHz and Flags, and adds:
| Column | Meaning |
|---|---|
SegmentId |
Monotonically increasing segment id. |
Event |
Why the segment began (see below). |
FirstSampleIndex |
Sample index within the segment at the anchor. |
ConfiguredOdrHz |
IMU output data rate used for nominal sample spacing. |
CorrectionPpm |
Clock correction, in ppm, applied to the segment's samples. |
ImuEvent has StartElapsedUs (corrected elapsed time of the event),
HeaderIndex (the header that produced it) and Event (the decoded event
name).
IMUTag Timing Segments
An IMUTag segment is a continuous run of samples whose timing can be computed
from one anchor point, the configured IMU output data rate, the sample count,
and the clock correction. Ordinary page headers are still stored in
ImuHeader, but they are treated as diagnostic checkpoints. They do not
normally re-anchor high-rate samples because their rounded millisecond values
can introduce page-to-page jitter.
New segments begin at collection start and after explicit resync boundaries. Those boundaries preserve useful context when continuity may have been disrupted. Examples include recovery after a monitor/debug connection resets a running tag, or recovery after storage readback finds an erased, invalid, or unreadable page while restoring log cursors. A storage skip can also mark a resync when firmware had to skip external storage after a write failure.
ImuSegment.Event records why the segment began, such as COLLECTION_START,
RESYNC, RESYNC_STORAGE_SKIP, or RESTART_RECOVERY. ImuEvent stores the
same discontinuities as viewer markers: RESTART_RECOVERY is a resync caused by
monitor reset recovery, while RESYNC_STORAGE_SKIP indicates a storage
discontinuity. When a resync anchor would place the
new segment before samples already written for the previous segment, the
downloader keeps elapsed timestamps monotonic by starting the new segment no
earlier than the next expected sample boundary.
Example:
select ElapsedUs / 1000000.0 as seconds, gx, gy, gz
from ImuGyro
order by ElapsedUs;
Calibration Table
CompassTag and IMUTag logs include Calibration when calibration history is
available.
| Column | Meaning |
|---|---|
Epoch |
UTC Unix timestamp in seconds for the calibration entry. |
Constants |
JSON calibration constants generated from the CalibrationConstants Protobuf message in proto/tagdata.proto. |
The JSON has two objects. magnetometer holds the hard-iron offset v0-v2
(uT) and the soft-iron matrix a00-a22; a calibrated reading is
A (M - V). accelerometer holds the zero-g offset o0-o2 in mg; a
corrected reading is a - o. Zero-valued fields are omitted, so treat a
missing key as zero. Logs written before October 2026 have no
accelerometer object and carry a b field magnitude that nothing applies.
Use the newest calibration row when you need the same default behavior as SensorViz. Keep older rows if you need to audit how calibration changed over time.
External Analysis Notes
- Prefer
streamsover hard-coded table names when possible. - Use
unitsfromstreamsso scripts label exported data correctly. - Treat SensorViz preferences as display settings, not as database schema.
- Processed database copies may add derived tables and derived stream rows.
Re-read
streamsafter processing instead of assuming only raw streams are present.
Related Pages
- tag-dwnld explains how to create SQLite downloads from the command line.
- SensorViz explains how to load and inspect SQLite logs visually.
- dataprocessing explains how processed database copies add derived streams.