Device Position

How the Emma tracker reports position: cell triangulation, coarse accuracy, and movement-driven updates.

Device position

Emma rides with the goods, so its position comes from cell triangulation rather than GPS. Two things follow from that, and both are worth knowing before you build against position data:

  • Position is coarse, and every reading tells you how coarse. Accuracy typically runs from roughly 100 m to 3 km, depending on how dense the cellular coverage is where the device happens to be, with a median around 300 m. Each position reading carries its own estimated accuracy in metres, so you never have to guess — treat a position as a circle, not a point.
  • Position updates are event-driven, not scheduled. Emma refreshes its position when it detects movement, and backs off when it is still. There is no fixed cadence to poll against.

Why the position is triangulated

Emma does have a GPS receiver. It just cannot use it where the device has to be.

There is no external antenna, and Emma sits inside cargo holds, pallets and boxes. Inside a moving reefer it can almost never get a fix. Cellular signals still penetrate, so triangulation is what actually works there.

This is a deliberate trade, not a gap: the device has to be with the goods to measure the goods. Anything that reliably gets a GPS fix is mounted where it can see sky — on the trailer roof, on the cooling unit — which is exactly where it cannot tell you the temperature of what is inside. The placement that costs us a GPS fix is the placement that makes the temperature record meaningful.

In practice, coarse position is enough to act on: knowing a shipment is at a depot, in transit, or arriving in the delivery city is what drives operational decisions. The extra precision also costs battery, which most fleets care about more.

📘

How triangulation works here

With each report the device includes the cell it is connected to plus a list of neighbouring base stations it can hear. Surfact computes the position from that server-side — the device does not calculate its own coordinates.

How often position updates

Emma's accelerometer monitors movement autonomously and wakes the device when it crosses a threshold. The reporting rate follows from that:

Device stateBehaviour
MovingReports frequently — movement itself triggers a report, and the position is refreshed along with it
StationaryBacks off to a long interval. On the standard configuration this is typically every 2–8 hours
❗️

Reporting cadence is configurable per device

The intervals above describe the standard configuration. Cadence is set per customer and per fleet, so the only correct answer to "how often does this device report?" is "which configuration is it on?". Do not hard-code an expected interval.

Two consequences for anything you build:

  • Don't poll on a fixed schedule expecting a new position each time. Read the latest value and use its timestamp.
  • Don't alert on position age alone. A large gap between position updates almost always means the device is parked, not that it is broken.

Positions can arrive late

Emma buffers readings while out of cellular coverage — in an aircraft hold, offshore, deep inside a warehouse — and uploads the backlog when it reconnects. That gap can be days, occasionally weeks.

So:

  1. Filter and sort on the dot's own timestamp, not on when you fetched it. A naive "last 7 days" query misses readings that have not arrived yet, and picks up readings that belong to last month.
  2. The same query over the same window can legitimately return more points when you re-run it later. That is buffered data arriving, not a bug.
  3. State an as-of time on anything you generate from position data.

How position is stored

Every Emma device has a variable labelled position — displayed as Position Accuracy, with the unit meters. It is the device's location variable (isLocationVariable: true), and it carries both halves of a position reading:

  • The value is the estimated accuracy of the fix, in metres.
  • The context holds the coordinates, under the lat and lng keys.
{
  "value": 261.0,
  "timestamp": 1788377537000,
  "context": {
    "lat": 59.89694675,
    "lng": 10.80005704
  },
  "created_at": 1788377585384
}

Read that as: the device was somewhere within about 261 m of 59.89695, 10.80006. All Surfact maps read lat and lng from the context to plot the asset — a single dot is enough, and there is no separate accuracy field to look up, because the accuracy is the value. See Data Structure Overview for how context works generally.

The number moves with coverage. Values seen across a working fleet:

Value (m)Reading it
100The best seen in dense coverage — this is the floor
261Good urban coverage
456Typical; the median across a fleet sits around 300 m
2921Sparse coverage — a very wide circle, so don't infer a street or an address from it
❗️

The latitude and longitude variables are rounded

Devices also expose latitude and longitude variables carrying the coordinates as bare numbers, but rounded to two decimal places — about 1 km of latitude, and roughly 600 m of longitude at Nordic latitudes. That is coarser than the position fix itself, so they are only useful for charting one axis roughly. The position variable is authoritative: its context holds the full-precision coordinates, and it is the only place where coordinates and accuracy arrive together on the same timestamp.

For the reading above, position.context gives 59.89694675, 10.80005704 while the bare variables give 59.9, 10.8.

❗️

Other variables don't carry coordinates

Position lives on the position variable, not on every reading. A temperature dot comes back with an empty context — don't expect to read a location off it.

Reading a device's position

Fetch the position variable to get the latest fix and its accuracy in one call:

curl -X GET 'https://api.surfact.com/api/v2.0/variables/<position_variable_id>/' \
  -H 'X-Auth-Token: your_token_here'

The lastValue object holds the reading:

{
  "label": "position",
  "name": "Position Accuracy",
  "unit": "meters",
  "description": "Accuracy of position measured with triangulation in the mobile network of the device",
  "properties": { "isLocationVariable": true },
  "lastValue": {
    "value": 261.0,
    "timestamp": 1788377537000,
    "context": { "lat": 59.89694675, "lng": 10.80005704 }
  }
}

To find the variable's id, list the device's variables and pick the one labelled position:

curl -X GET 'https://api.surfact.com/api/v2.0/devices/~reefer-trailer-21/variables/' \
  -H 'X-Auth-Token: your_token_here'

For a position history rather than the latest fix, read the variable's values — each point has the same value-plus-context shape:

curl -X GET 'https://api.surfact.com/api/v1.6/variables/<position_variable_id>/values/?page_size=10' \
  -H 'X-Auth-Token: your_token_here'

For time ranges, aggregation and CSV output, see Working with Device Data.

Choosing which variable holds the position

A device's properties._location_type decides where the platform looks for its coordinates.

ModeWhen to use
autoA moving asset. Uses the first variable whose label is position, gps, or location
specifiedA moving asset whose location variable you want to name explicitly
manualA fixed asset that never moves, such as a warehouse cold room

Naming the location variable explicitly

Point the device at any variable with _location_variable (the label, without the ~ prefix). That variable's isLocationVariable property is then set to true automatically.

curl -X PATCH 'https://api.surfact.com/api/v2.0/devices/~delivery-van-14/' \
  -H 'Content-Type: application/json' \
  -H 'X-Auth-Token: your_token_here' \
  -d '{
    "properties": {
      "_location_type": "specified",
      "_location_variable": "position"
    }
  }'
🚧

Avoid ambiguity in auto mode

Don't give a device two or more variables labelled position, gps, or location — which one wins is not defined. Use specified mode instead.

Pinning a fixed position

For a stationary asset, skip triangulation entirely and record the coordinates on the device:

curl -X PATCH 'https://api.surfact.com/api/v2.0/devices/~warehouse-fridge-oslo/' \
  -H 'Content-Type: application/json' \
  -H 'X-Auth-Token: your_token_here' \
  -d '{
    "properties": {
      "_location_type": "manual",
      "_location_fixed": { "lat": 59.9139, "lng": 10.7522 }
    }
  }'

Did this page help you?