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 hereWith 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 state | Behaviour |
|---|---|
| Moving | Reports frequently — movement itself triggers a report, and the position is refreshed along with it |
| Stationary | Backs off to a long interval. On the standard configuration this is typically every 2–8 hours |
Reporting cadence is configurable per deviceThe 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:
- 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. - 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.
- 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
valueis the estimated accuracy of the fix, in metres. - The
contextholds the coordinates, under thelatandlngkeys.
{
"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 |
|---|---|
100 | The best seen in dense coverage — this is the floor |
261 | Good urban coverage |
456 | Typical; the median across a fleet sits around 300 m |
2921 | Sparse coverage — a very wide circle, so don't infer a street or an address from it |
Thelatitudeandlongitudevariables are roundedDevices also expose
latitudeandlongitudevariables 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. Thepositionvariable 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.contextgives59.89694675, 10.80005704while the bare variables give59.9, 10.8.
Other variables don't carry coordinatesPosition lives on the
positionvariable, not on every reading. Atemperaturedot comes back with an emptycontext— 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.
| Mode | When to use |
|---|---|
| auto | A moving asset. Uses the first variable whose label is position, gps, or location |
| specified | A moving asset whose location variable you want to name explicitly |
| manual | A 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 modeDon't give a device two or more variables labelled
position,gps, orlocation— which one wins is not defined. Usespecifiedmode 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 }
}
}'Updated 7 days ago