# 04 - RouteTrack Pi — GPS Data Logging Service

#### **Date:** December 24th, 2025  
**Category:** Raspberry Pi / GPS / Logging / Linux Services  
**Backlink:** [RouteTrack Pi — gpsd Installation &amp; GPS Validation](https://docs.natenetworks.com/books/06-raspberry-pi-python-linux-tips/page/03-routetrack-pi-gpsd-installation-gps-validation)

---

## Project Goal

This page adds the next layer on top of the now-stable GPS subsystem:

- Continuously **collect TPV updates** from `gpsd` (localhost port **2947**)
- Write the data to a **local database** for later:
    
    
    - Route mapping (GeoJSON export)
    - Mileage calculation
    - Stop detection / time-on-site
    - Daily totals (hours, distance, etc.)

This is a **local-first** design so the system still works even when the truck is offline.

---

## Prerequisites

This page assumes:

- `gpsd-standalone.service` is enabled and running
- `gpsd` is listening on **127.0.0.1:2947**
- `gpspipe -w -n 25` shows TPV + SKY messages and `mode: 3` once a fix is obtained

---

## Data We Will Log (Minimum Viable Dataset)

From `TPV` messages, we will store:

- `time` (UTC timestamp from gpsd)
- `lat`, `lon`
- `alt` (optional)
- `speed`
- `track` (heading)
- `mode` (0/1/2/3 — we only trust **3** for real routes)
- `epx`, `epy`, `eps` (accuracy-ish fields if present)

This is enough to:

- Render a map route
- Compute distance
- Identify movement vs stops
- Generate daily summaries

---

Perfect — this is exactly the right moment to clean this up 👍  
Below is a **fully copy-pasteable replacement** for **both sections** you showed: **Folder Layout** and **Install Dependencies**, rewritten to match the **venv + dashboard** direction and your clean BookStack style.

You can drop this in **as-is** and delete what’s there now.

---

## Folder Layout

This project uses a structured directory layout under `/opt/routetrack` to keep scripts, data, logs, configuration, and the Python virtual environment clearly separated.

Create the directory structure:

```bash
sudo mkdir -p /opt/routetrack/{bin,data,logs,config}
sudo chown -R $USER:$USER /opt/routetrack
```

### Directory Purpose

- **bin/**  
    Application scripts and executables  
    (e.g. GPS logger, web dashboard entrypoints)
- **data/**  
    Persistent application data  
    (SQLite database, exports)
- **logs/**  
    Application logs  
    (GPS logging, web service logs)
- **config/**  
    Configuration files  
    (database schema, environment variables)
- **venv/**  
    Python virtual environment  
    (created in a later step)

### Reference Paths

These paths are used consistently throughout the RouteTrack project:

- **Logger script:**  
    `/opt/routetrack/bin/routetrack-logger.py`
- **SQLite database:**  
    `/opt/routetrack/data/routetrack.sqlite`
- **Application logs:**  
    `/opt/routetrack/logs/routetrack.log`
- **Database schema / config files:**  
    `/opt/routetrack/config/schema.sql`

---

## Install Dependencies

RouteTrack runs on **Raspberry Pi OS Lite 64 bit** and uses a Python virtual environment to avoid modifying the system Python installation.

Update package lists:

```bash
sudo apt update
```

Install required system packages:

```bash
sudo apt install -y python3-venv python3-pip sqlite3
```

### Package Purpose

- **python3-venv**  
    Creates an isolated Python environment for RouteTrack
- **python3-pip**  
    Installs Python packages inside the virtual environment
- **sqlite3**  
    Lightweight local database for GPS data and summaries

Using a virtual environment avoids conflicts with the OS-managed Python environment and allows RouteTrack to safely install web and dashboard dependencies.

Nice — and you’re right to call that out. Your BookStack section should explicitly include the **venv package install** step since that’s where you’re at now.

Here’s a **drop-in BookStack section** you can paste **immediately after “Install Dependencies”** (or as the last part of it). It documents exactly what you did, cleanly.

---

## Create the Python Virtual Environment (venv)

RouteTrack uses a dedicated Python virtual environment stored under `/opt/routetrack/venv`.  
This avoids installing packages into the OS-managed Python environment and keeps the project portable and stable.

Create the virtual environment:

```bash
python3 -m venv /opt/routetrack/venv
```

---

## Install Web Dashboard Dependencies (Flask + Gunicorn)

Upgrade `pip` inside the virtual environment:

```bash
/opt/routetrack/venv/bin/pip install --upgrade pip
```

Install the local dashboard requirements:

```bash
/opt/routetrack/venv/bin/pip install flask gunicorn
```

### Verify Flask Works

Run a quick import test:

```bash
/opt/routetrack/venv/bin/python -c "import flask; print('Flask OK')"
```

Expected output:

[![image.png](https://docs.natenetworks.com/uploads/images/gallery/2025-12/scaled-1680-/WeUK9eDpRGpFcH91-image.png)](https://docs.natenetworks.com/uploads/images/gallery/2025-12/WeUK9eDpRGpFcH91-image.png)

---

## Current Status

At this point:

- Folder layout exists under `/opt/routetrack`
- Python virtual environment is created at `/opt/routetrack/venv`
- Flask + Gunicorn are installed and verified successfully

The next phase will initialize the SQLite database schema and begin logging `gpsd` TPV points into the database for mapping and reporting.

---

## Creating the SQLite Database Schema

This project uses a **file-based SQL schema** to define the RouteTrack database structure.  
Storing the schema in a dedicated `.sql` file makes it easier to review, document, and extend later as new features (stops, daily summaries, exports) are added.

---

### Creating the Schema File

Create a schema file in the RouteTrack configuration directory:

```bash
sudo nano /opt/routetrack/config/schema.sql
```

Paste the following contents:

```sqlite
PRAGMA journal_mode=WAL;

CREATE TABLE IF NOT EXISTS gps_points (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  ts TEXT NOT NULL,
  lat REAL,
  lon REAL,
  speed REAL,
  track REAL,
  alt REAL,
  mode INTEGER,
  epx REAL,
  epy REAL,
  eps REAL
);

CREATE INDEX IF NOT EXISTS idx_gps_points_ts
  ON gps_points(ts);
```

Save and exit:

- `CTRL + O` → Enter or `CTRL + S`
- `CTRL + X`

---

### Applying the Schema to the Database

Run the schema file once to initialize the SQLite database:

```bash
sqlite3 /opt/routetrack/data/routetrack.sqlite \
  < /opt/routetrack/config/schema.sql
```

If the database file does not already exist, SQLite will create it automatically.

---

### Verify Database Creation

Confirm that the table was created successfully:

```bash
sqlite3 /opt/routetrack/data/routetrack.sqlite ".tables"
```

Expected output:

[![image.png](https://docs.natenetworks.com/uploads/images/gallery/2025-12/scaled-1680-/5MxBkYokolw8GH5s-image.png)](https://docs.natenetworks.com/uploads/images/gallery/2025-12/5MxBkYokolw8GH5s-image.png)

To inspect the table structure:

```bash
sqlite3 /opt/routetrack/data/routetrack.sqlite ".schema gps_points"
```

---

### Why This Approach

SQLite was chosen because it is:

- **Lightweight** and ideal for Raspberry Pi hardware
- **Reliable** for long-running, vehicle-mounted deployments
- **Easy to export** later to GeoJSON or CSV
- Well-suited for **daily summaries** and route analysis

Using a standalone schema file keeps database changes explicit and versionable, which aligns with the long-term goal of expanding RouteTrack into a full route-tracking and reporting system

---

## Create the RouteTrack Logger Script (SQLite + gpsd)

This logger is responsible for continuously collecting GPS position updates from `gpsd` (localhost port `2947`) and storing them in the local SQLite database.

### Design Notes (Why this works well on a vehicle Pi)

- Connects to `gpsd` over TCP (`127.0.0.1:2947`) using newline-delimited JSON
- Stores only `TPV` class updates (time/position/speed/heading)
- Commits inserts in small batches to reduce SD card write amplification
- Runs under `systemd` so it starts on boot and self-heals if `gpsd` restarts

---

### Create the Logger Script File

Create/edit the logger script:

```bash
sudo nano /opt/routetrack/bin/routetrack-logger.py
```

Paste the following script:

```python
<span class="token comment">#!/usr/bin/env python3</span>
<span class="token triple-quoted-string string">"""
RouteTrack GPS Logger
---------------------

Purpose:
  - Connect to gpsd (localhost:2947)
  - Subscribe to JSON streaming (WATCH)
  - Extract TPV messages (Time/Position/Velocity)
  - Insert points into SQLite (gps_points table)
  - Commit periodically for SD-card friendly writes
  - Print logs to stdout so systemd journald captures them

Key assumptions:
  - gpsd is already running as gpsd-standalone.service and listening on port 2947
  - SQLite DB exists at /opt/routetrack/data/routetrack.sqlite
  - Table gps_points exists (created by schema.sql)
"""</span>

<span class="token keyword">import</span> json
<span class="token keyword">import</span> socket
<span class="token keyword">import</span> sqlite3
<span class="token keyword">import</span> time
<span class="token keyword">from</span> datetime <span class="token keyword">import</span> datetime<span class="token punctuation">,</span> timezone


<span class="token comment"># gpsd host/port (your standalone service binds gpsd to localhost:2947)</span>
GPSD_HOST <span class="token operator">=</span> <span class="token string">"127.0.0.1"</span>
GPSD_PORT <span class="token operator">=</span> <span class="token number">2947</span>

<span class="token comment"># SQLite database path created earlier</span>
DB_PATH <span class="token operator">=</span> <span class="token string">"/opt/routetrack/data/routetrack.sqlite"</span>

<span class="token comment"># Commit every N points:</span>
<span class="token comment"># - Reduces disk writes vs committing each insert</span>
<span class="token comment"># - Helps SD card longevity in vehicle deployments</span>
COMMIT_EVERY <span class="token operator">=</span> <span class="token number">10</span>


<span class="token keyword">def</span> <span class="token function">utc_now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">str</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""Return a UTC timestamp string for logging."""</span>
    <span class="token keyword">return</span> datetime<span class="token punctuation">.</span>now<span class="token punctuation">(</span>timezone<span class="token punctuation">.</span>utc<span class="token punctuation">)</span><span class="token punctuation">.</span>isoformat<span class="token punctuation">(</span><span class="token punctuation">)</span>


<span class="token keyword">def</span> <span class="token function">db_connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> sqlite3<span class="token punctuation">.</span>Connection<span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Open SQLite connection and apply performance/safety pragmas.

    - WAL (Write-Ahead Logging) mode is already enabled via schema.sql,
      but repeating it here is harmless.
    - synchronous=NORMAL is a common setting for WAL mode:
      better performance with good durability.
    """</span>
    conn <span class="token operator">=</span> sqlite3<span class="token punctuation">.</span>connect<span class="token punctuation">(</span>DB_PATH<span class="token punctuation">,</span> timeout<span class="token operator">=</span><span class="token number">30</span><span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token string">"PRAGMA journal_mode=WAL;"</span><span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token string">"PRAGMA synchronous=NORMAL;"</span><span class="token punctuation">)</span>
    <span class="token keyword">return</span> conn


<span class="token keyword">def</span> <span class="token function">insert_point</span><span class="token punctuation">(</span>cur<span class="token punctuation">:</span> sqlite3<span class="token punctuation">.</span>Cursor<span class="token punctuation">,</span> tpv<span class="token punctuation">:</span> <span class="token builtin">dict</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Insert a TPV message into gps_points.

    We use tpv.get(...) so missing keys do not crash the logger.
    This keeps the service robust when gpsd emits partial data during fix acquisition.
    """</span>
    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span>
        <span class="token triple-quoted-string string">"""
        INSERT INTO gps_points (ts, lat, lon, speed, track, alt, mode, epx, epy, eps)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"time"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>   <span class="token comment"># gpsd's UTC timestamp (string)</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"lat"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token comment"># latitude (float)</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"lon"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token comment"># longitude (float)</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"speed"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token comment"># speed (typically meters/second)</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"track"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token comment"># heading/course (degrees)</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"alt"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token comment"># altitude (meters)</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"mode"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>   <span class="token comment"># 0/1/2/3 (3 = best fix quality)</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"epx"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token comment"># estimated longitude error (meters)</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"epy"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token comment"># estimated latitude error (meters)</span>
            tpv<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"eps"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token comment"># estimated speed error</span>
        <span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>


<span class="token keyword">def</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Main loop:
      - Connect to SQLite
      - Forever:
          - Connect to gpsd
          - Send WATCH request to enable JSON streaming
          - Read gpsd lines (newline-delimited JSON)
          - Store TPV messages to SQLite
      - On disconnect/errors:
          - commit anything pending
          - sleep briefly
          - reconnect
    """</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">{</span>utc_now<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token string"> RouteTrack logger starting…"</span></span><span class="token punctuation">,</span> flush<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span>

    <span class="token comment"># Create DB connection and cursor once.</span>
    <span class="token comment"># SQLite is local, fast, and lightweight for Pi deployments.</span>
    conn <span class="token operator">=</span> db_connect<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur <span class="token operator">=</span> conn<span class="token punctuation">.</span>cursor<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token comment"># Count uncommitted inserts so we can batch commits.</span>
    pending <span class="token operator">=</span> <span class="token number">0</span>

    <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span>
        <span class="token keyword">try</span><span class="token punctuation">:</span>
            <span class="token comment"># Establish TCP connection to gpsd service.</span>
            <span class="token keyword">with</span> socket<span class="token punctuation">.</span>create_connection<span class="token punctuation">(</span><span class="token punctuation">(</span>GPSD_HOST<span class="token punctuation">,</span> GPSD_PORT<span class="token punctuation">)</span><span class="token punctuation">,</span> timeout<span class="token operator">=</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">as</span> s<span class="token punctuation">:</span>

                <span class="token comment"># Enable JSON output streaming from gpsd.</span>
                <span class="token comment"># gpsd will emit multiple classes (TPV, SKY, etc.); we filter for TPV.</span>
                s<span class="token punctuation">.</span>sendall<span class="token punctuation">(</span><span class="token string">b'?WATCH={"enable":true,"json":true}\n'</span><span class="token punctuation">)</span>

                <span class="token comment"># gpsd responses arrive in chunks; accumulate until newline.</span>
                buf <span class="token operator">=</span> <span class="token string">b""</span>

                <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span>
                    chunk <span class="token operator">=</span> s<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token number">4096</span><span class="token punctuation">)</span>
                    <span class="token keyword">if</span> <span class="token keyword">not</span> chunk<span class="token punctuation">:</span>
                        <span class="token comment"># Socket closed; force reconnect</span>
                        <span class="token keyword">raise</span> RuntimeError<span class="token punctuation">(</span><span class="token string">"gpsd socket closed"</span><span class="token punctuation">)</span>

                    buf <span class="token operator">+=</span> chunk

                    <span class="token comment"># Process all complete lines currently buffered.</span>
                    <span class="token keyword">while</span> <span class="token string">b"\n"</span> <span class="token keyword">in</span> buf<span class="token punctuation">:</span>
                        line<span class="token punctuation">,</span> buf <span class="token operator">=</span> buf<span class="token punctuation">.</span>split<span class="token punctuation">(</span><span class="token string">b"\n"</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>

                        <span class="token keyword">if</span> <span class="token keyword">not</span> line<span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
                            <span class="token keyword">continue</span>

                        <span class="token comment"># Convert bytes -> string -> JSON dict</span>
                        <span class="token keyword">try</span><span class="token punctuation">:</span>
                            msg <span class="token operator">=</span> json<span class="token punctuation">.</span>loads<span class="token punctuation">(</span>line<span class="token punctuation">.</span>decode<span class="token punctuation">(</span><span class="token string">"utf-8"</span><span class="token punctuation">,</span> errors<span class="token operator">=</span><span class="token string">"replace"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                        <span class="token keyword">except</span> json<span class="token punctuation">.</span>JSONDecodeError<span class="token punctuation">:</span>
                            <span class="token comment"># Skip malformed lines without crashing</span>
                            <span class="token keyword">continue</span>

                        <span class="token comment"># Only store TPV messages (position/time/speed)</span>
                        <span class="token keyword">if</span> msg<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"class"</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token string">"TPV"</span><span class="token punctuation">:</span>
                            <span class="token keyword">continue</span>

                        <span class="token comment"># Skip TPV messages without time.</span>
                        <span class="token comment"># This can occur before a real fix is established.</span>
                        <span class="token keyword">if</span> <span class="token string">"time"</span> <span class="token keyword">not</span> <span class="token keyword">in</span> msg<span class="token punctuation">:</span>
                            <span class="token keyword">continue</span>

                        <span class="token comment"># Insert into SQLite</span>
                        insert_point<span class="token punctuation">(</span>cur<span class="token punctuation">,</span> msg<span class="token punctuation">)</span>
                        pending <span class="token operator">+=</span> <span class="token number">1</span>

                        <span class="token comment"># Commit every N points to reduce write load</span>
                        <span class="token keyword">if</span> pending <span class="token operator">>=</span> COMMIT_EVERY<span class="token punctuation">:</span>
                            conn<span class="token punctuation">.</span>commit<span class="token punctuation">(</span><span class="token punctuation">)</span>
                            pending <span class="token operator">=</span> <span class="token number">0</span>

        <span class="token keyword">except</span> Exception <span class="token keyword">as</span> e<span class="token punctuation">:</span>
            <span class="token comment"># If gpsd restarts, USB hiccups, or anything breaks, we reconnect.</span>
            <span class="token comment"># Commit any pending inserts first (best effort).</span>
            <span class="token keyword">try</span><span class="token punctuation">:</span>
                conn<span class="token punctuation">.</span>commit<span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token keyword">except</span> Exception<span class="token punctuation">:</span>
                <span class="token keyword">pass</span>

            <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">{</span>utc_now<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token string"> ERROR: </span><span class="token interpolation"><span class="token punctuation">{</span>e<span class="token punctuation">}</span></span><span class="token string"> (reconnecting in 3s)"</span></span><span class="token punctuation">,</span> flush<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span>
            time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>


<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">"__main__"</span><span class="token punctuation">:</span>
    main<span class="token punctuation">(</span><span class="token punctuation">)</span>

```

Make the script executable:

```bash
sudo chmod +x /opt/routetrack/bin/routetrack-logger.py
```

---

## Create the systemd Service (RouteTrack Logger)

This service ensures the logger starts at boot, stays running, and is tied to the GPS subsystem.

Create the unit file:

```bash
sudo nano /etc/systemd/system/routetrack-logger.service
```

Paste:

```ini
[Unit]
Description=RouteTrack GPS Logger
# Start after gpsd is online and networking is available
After=gpsd-standalone.service network.target
# Pull gpsd up if needed, and fail if gpsd is missing
Wants=gpsd-standalone.service
Requires=gpsd-standalone.service

[Service]
Type=simple

# Run from /opt/routetrack so relative paths (if added later) behave predictably
WorkingDirectory=/opt/routetrack

# IMPORTANT:
# Use the virtual environment Python so packages (Flask, etc.) remain isolated
ExecStart=/opt/routetrack/venv/bin/python /opt/routetrack/bin/routetrack-logger.py

# Always restart if the logger exits (gpsd restarts, USB dropouts, etc.)
Restart=always
RestartSec=3

# Send script output to journald
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
```

Reload systemd + enable and start the service:

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now routetrack-logger.service
```

Check service status:

```bash
sudo systemctl status routetrack-logger.service --no-pager -l
```

View logs live:

```bash
sudo journalctl -u routetrack-logger -f
```

---

## Verify GPS Data is Being Written to SQLite

Confirm row count is increasing:

```bash
sqlite3 /opt/routetrack/data/routetrack.sqlite "SELECT COUNT(*) FROM gps_points;"
```

View the latest 10 points:

```bash
sqlite3 /opt/routetrack/data/routetrack.sqlite \
"SELECT ts, lat, lon, speed, mode FROM gps_points ORDER BY id DESC LIMIT 10;"
```

Optional: verify you are receiving `mode: 3` fixes consistently:

```bash
sqlite3 /opt/routetrack/data/routetrack.sqlite \
"SELECT mode, COUNT(*) FROM gps_points GROUP BY mode ORDER BY mode;"
```

---

## Notes for Later Phases (Mileage + Stops)

- gpsd `speed` values are typically **meters/second**
    
    
    - mph conversion: `mph = mps * 2.23694`
- Some “movement” may appear when parked due to GPS drift.
    
    
    - Mileage calculations should apply filtering later:
        
        
        - count movement only when `mode = 3`
        - ignore points under a speed threshold (example: `>= 0.5 m/s`)

---

## Log Rotation (Prevent SD Card Bloat)

RouteTrack services write their runtime output to **systemd journald** (viewable via `journalctl`). journald handles rotation automatically and is capped by the retention settings configured in `/etc/systemd/journald.conf`.

In addition, a `logrotate` policy is created for any **file-based logs** that may be added later under `/opt/routetrack/logs/` (for example: helper scripts, exporters, or future components that write `.log` files).

### Create the logrotate Policy (File Logs)

Create a logrotate config for RouteTrack:

```bash
sudo nano /etc/logrotate.d/routetrack

```

Paste:

```conf
/opt/routetrack/logs/*.log {
  daily
  rotate 14
  compress
  delaycompress
  missingok
  notifempty
  copytruncate
}

```

### Fix: logrotate “Insecure Permissions” Error

logrotate may refuse to rotate logs if the parent directory is writable by non-root users (reported as “insecure permissions”). To resolve this securely, the RouteTrack logs directory is locked down to root ownership:

```bash
sudo chown root:root /opt/routetrack/logs
sudo chmod 755 /opt/routetrack/logs

```

Verify permissions:

```bash
ls -ld /opt/routetrack /opt/routetrack/logs

```

### Test logrotate

Force a rotation to validate the config:

```bash
sudo logrotate -f /etc/logrotate.d/routetrack

```

### RouteTrack Logging Note (Primary Logging)

The RouteTrack logger service outputs to **journald**, so you can view logs with:

```bash
sudo journalctl -u routetrack-logger -f

```

This is the primary logging method. File-based log rotation is included for future scripts or components that write to `/opt/routetrack/logs/*.log`.

---

### Current Status

- File-based RouteTrack logs are rotated daily
- Old logs are compressed and retained safely
- Disk usage remains controlled for long-running deployments

---

## Next Steps

Next phase will build the actual “route intelligence”:

- **Mileage calculation**
    
    
    - Haversine distance between points
    - Only count `mode: 3` fixes
    - Ignore drift when speed is near zero
- **Stop detection**
    
    
    - Define stop events (speed threshold + dwell time)
    - Write stop events to a second table
- **Daily summaries**
    
    
    - Total mileage
    - Total moving time
    - Total stopped time (time-on-site)
    - Start time / end time per shift