Real time fleet management in San Francisco

A small transportation company in San Francisco operates a fleet of delivery trucks. They need a simple way to track vehicle locations. They choose Auron SMS Server to handle this. It processes incoming SMS messages from GPS trackers. This setup keeps their operations efficient and cost-effective.

Advantages for the Transportation Company

This system provides live visibility into fleet movements. Managers spot delays quickly. They reroute trucks to avoid traffic. Fuel costs drop due to optimized paths. Safety improves as speeding or off-route driving shows up. The setup is low-cost. It uses SMS instead of expensive data plans. No need for complex apps or subscriptions beyond Google Maps API.

The company saves time. Drivers focus on deliveries. Office staff monitor from one dashboard. Scalability is easy. Add more trucks without major changes.

Overview of the System

The company equips each truck with a Teltonika FMC920 GPS tracker. This device has an integrated GSM module. It sends location data via SMS at regular intervals. The SMS goes to a central phone number. This number connects to a Teltonika TRB142 GSM module. The module plugs into a Windows server running Auron SMS Server.

Auron SMS Server receives the SMS messages. A custom trigger script parses the data. It updates a JSON file called locations.json. This file stores the latest positions for all trucks.

The same server runs IIS. IIS hosts a static webpage. The page loads Google Maps. It reads locations.json. It places markers on the map for each truck. Staff access this dashboard from any browser. They see real-time fleet positions.

Locations.json

This is the file format that holds all current truck positions:

{
  "TRUCK1": {
    "lat": 37.7749,
    "lng": -122.4194,
    "timestamp": "2026-03-04T10:15:23Z",
    "speed": 60,
    "direction": 180
  },
  "TRUCK2": {
    "lat": 37.7840,
    "lng": -122.4090,
    "timestamp": "2026-03-04T10:15:30Z",
    "speed": 55,
    "direction": 90
  }
}

FleetGPS trigger

The heart of the trigger is where incoming locations are parsed and consolidated in the locations.js file.

// ========================================================================
// Function: ProcessMessageEx
// ------------------------------------------------------------------------
// ProcessMessageEx trigger function to process incoming GPS SMS messages
// from fleet trucks and update the shared locations.json file.
// ========================================================================
function ProcessMessageEx(objMessageIn, objMessageDB, dctContext)
{
    Log(">> ProcessMessageEx")

    ParseAndUpdateLocation(objMessageIn, objMessageDB)

    Log("<< ProcessMessageEx")
}

// ========================================================================
// Function: ParseAndUpdateLocation
// ------------------------------------------------------------------------
// Checks whether the incoming SMS contains GPS data in the expected format:
//   #<DeviceID>,<TruckID>,<Timestamp>,<Lat>,<Lng>,<Speed>,<Direction>
//
// If valid, reads the current locations.json file, updates or inserts the
// truck entry, and writes the file back to disk.
// ========================================================================
function ParseAndUpdateLocation(objMessageIn, objMessageDB)
{
    Log(">> ParseAndUpdateLocation")

    var JSON_FILE_PATH = "C:\\inetpub\\wwwroot\\fleetdashboard\\locations.json"

    var sBody = objMessageIn.Body
    Log("Incoming SMS body: " + sBody)

    // ------------------------------------------------------------------
    // Step 1: Validate that the message matches the expected GPS format.
    // Expected: #<DeviceID>,<TruckID>,<YYYY-MM-DD HH:MM:SS>,<Lat>,<Lng>,<Speed>,<Dir>
    // ------------------------------------------------------------------
    if (!sBody || sBody.charAt(0) !== "#")
    {
        Log("Message does not start with '#' - not a GPS message. Skipping.")
        Log("<< ParseAndUpdateLocation")
        return
    }

    var aParts = sBody.split(",")

    if (aParts.length < 7)
    {
        Log("Message has fewer than 7 comma-separated fields - not a valid GPS message. Skipping.")
        Log("<< ParseAndUpdateLocation")
        return
    }

    // ------------------------------------------------------------------
    // Step 2: Parse each field from the message.
    // ------------------------------------------------------------------
    var sDeviceID  = aParts[0].substring(1)   // Strip leading '#'
    var sTruckID   = Trim(aParts[1])
    var sTimestamp = Trim(aParts[2])          // "YYYY-MM-DD HH:MM:SS"
    var fLat       = parseFloat(aParts[3])
    var fLng       = parseFloat(aParts[4])
    var nSpeed     = parseInt(aParts[5], 10)
    var nDirection = parseInt(aParts[6], 10)

    // Validate numeric fields
    if (isNaN(fLat) || isNaN(fLng) || isNaN(nSpeed) || isNaN(nDirection))
    {
        Log("One or more numeric fields (Lat/Lng/Speed/Direction) could not be parsed. Skipping.")
        Log("<< ParseAndUpdateLocation")
        return
    }

    // Convert timestamp from "YYYY-MM-DD HH:MM:SS" to "YYYY-MM-DDTHH:MM:SSZ"
    var sISOTimestamp = sTimestamp.replace(" ", "T") + "Z"

    Log("Parsed - DeviceID: " + sDeviceID +
        " | TruckID: "        + sTruckID +
        " | Timestamp: "      + sISOTimestamp +
        " | Lat: "            + fLat +
        " | Lng: "            + fLng +
        " | Speed: "          + nSpeed +
        " | Direction: "      + nDirection)

    // ------------------------------------------------------------------
    // Step 3: Read the existing locations.json file from disk.
    //         If the file does not exist or is empty, start with {}.
    // ------------------------------------------------------------------
    var oFSO  = new ActiveXObject("Scripting.FileSystemObject")
    var sJSON = "{}"

    if (oFSO.FileExists(JSON_FILE_PATH))
    {
        var oFile = oFSO.OpenTextFile(JSON_FILE_PATH, 1, false)  // 1 = ForReading
        if (!oFile.AtEndOfStream)
        {
            sJSON = oFile.ReadAll()
        }
        oFile.Close()
        Log("Read existing locations.json (" + sJSON.length + " bytes)")
    }
    else
    {
        Log("locations.json not found - will create a new file.")
    }

    // ------------------------------------------------------------------
    // Step 4: Parse JSON, update the truck entry, serialize back to JSON.
    //         Uses a lightweight regex-based approach compatible with the
    //         JScript engine used by Auron SMS Server (no JSON.parse).
    // ------------------------------------------------------------------
    sJSON = Trim(sJSON)

    // Remove the outer braces to work with the inner content
    if (sJSON.charAt(0) === "{")
    {
        sJSON = sJSON.substring(1)
    }
    if (sJSON.charAt(sJSON.length - 1) === "}")
    {
        sJSON = sJSON.substring(0, sJSON.length - 1)
    }

    // Build the replacement entry for this truck
    var sEntry =
        """ + sTruckID + "": {" +
        "\r\n    "lat": "         + fLat            + "," +
        "\r\n    "lng": "         + fLng            + "," +
        "\r\n    "timestamp": "" + sISOTimestamp   + ""," +
        "\r\n    "speed": "       + nSpeed          + "," +
        "\r\n    "direction": "   + nDirection      +
        "\r\n  }"

    // Check whether this truck already has an entry and replace it,
    // or append a new entry if it does not exist yet.
    var rPattern  = new RegExp(
        """ + sTruckID + ""\\s*:\\s*\\{[^}]*\\}",
        "i"
    )

    if (rPattern.test(sJSON))
    {
        // Replace the existing truck entry in-place
        sJSON = sJSON.replace(rPattern, sEntry)
        Log("Updated existing entry for truck: " + sTruckID)
    }
    else
    {
        // Append a new entry; add a comma separator if content already exists
        var sTrimmed = Trim(sJSON)
        if (sTrimmed.length > 0)
        {
            sJSON = sTrimmed + ",\r\n  " + sEntry
        }
        else
        {
            sJSON = "\r\n  " + sEntry + "\r\n"
        }
        Log("Added new entry for truck: " + sTruckID)
    }

    // Re-wrap with outer braces
    var sFinalJSON = "{\r\n  " + Trim(sJSON) + "\r\n}"

    // ------------------------------------------------------------------
    // Step 5: Write the updated JSON back to disk, overwriting the file.
    // ------------------------------------------------------------------
    var oOutFile = oFSO.OpenTextFile(JSON_FILE_PATH, 2, true)  // 2 = ForWriting, true = Create
    oOutFile.Write(sFinalJSON)
    oOutFile.Close()

    Log("Successfully wrote updated locations.json for truck: " + sTruckID)
    Log("<< ParseAndUpdateLocation")
}

FleetGPS dashboard

The fleet GPS dashboard is a simple static HTML page that loads in a google map and places markers at the current truck locations.

Here’s part of the code that does this:


    // ====================================================================
    // loadLocations
    // --------------------------------------------------------------------
    // Fetches locations.json, then calls updateMarkers with the parsed data.
    // A cache-busting query string ensures the browser never serves a stale
    // cached copy of the JSON file.
    // ====================================================================
    function loadLocations() {
      var url = LOCATIONS_JSON + "?t=" + Date.now();

      fetch(url)
        .then(function(response) {
          if (!response.ok) {
            throw new Error("HTTP " + response.status + " fetching " + url);
          }
          return response.json();
        })
        .then(function(data) {
          updateMarkers(data);
          document.getElementById("last-refresh").textContent = new Date().toLocaleTimeString();
          document.getElementById("truck-count").textContent  = Object.keys(data).length;
        })
        .catch(function(err) {
          console.error("Failed to load locations.json:", err);
        });
    }

    // ====================================================================
    // updateMarkers
    // --------------------------------------------------------------------
    // Iterates over the trucks in the JSON data.
    // - Creates a new marker if the truck is new.
    // - Moves and updates the info window if the truck already has a marker.
    // - Removes markers for trucks no longer present in the data.
    // ====================================================================
    function updateMarkers(data) {
      var activeTruckIds = {};

      for (var truckId in data) {
        if (!data.hasOwnProperty(truckId)) continue;

        activeTruckIds[truckId] = true;
        var info = data[truckId];
        var position = { lat: info.lat, lng: info.lng };

        if (markers[truckId]) {
          // --- Update existing marker ---
          markers[truckId].marker.setPosition(position);
          markers[truckId].infoWindow.setContent(buildInfoWindowContent(truckId, info));
        } else {
          // --- Create a new marker ---
          var marker = new google.maps.Marker({
            position : position,
            map      : map,
            title    : truckId,
            icon     : buildTruckIcon()
          });

          var infoWindow = new google.maps.InfoWindow({
            content: buildInfoWindowContent(truckId, info)
          });

          // Open info window on marker click
          (function(m, iw) {
            m.addListener("click", function() {
              // Close any other open info windows before opening this one
              closeAllInfoWindows();
              iw.open(map, m);
            });
          })(marker, infoWindow);

          markers[truckId] = { marker: marker, infoWindow: infoWindow };
        }
      }

      // --- Remove markers for trucks no longer in the data ---
      for (var existingId in markers) {
        if (!markers.hasOwnProperty(existingId)) continue;
        if (!activeTruckIds[existingId]) {
          markers[existingId].infoWindow.close();
          markers[existingId].marker.setMap(null);
          delete markers[existingId];
        }
      }
    }

Try it for yourself?

This is a very inexpensive setup that any transportation or delivery company can setup with minimal effort.

If you want to try this for yourself please download the full function, 30 day trail version of the Auron SMS Server.

Please let us know if you need any help with setting this up.