You don't need a hardware GPS tracker to put a phone on a Traccar map. Traccar understands the OsmAnd protocol, which is a plain HTTP request with the position in the query string. Any Flutter app can send it with a few lines of code.
This post shows how to send positions from Flutter, how to raise an SOS alarm that Traccar turns into a notification, and how to keep points that were recorded while the phone had no signal.
How the OsmAnd protocol works
Traccar listens for OsmAnd requests on port 5055 by default. One request carries one position:
http://your-traccar-server:5055/?id=123456&lat=13.0827&lon=80.2707×tamp=1793433600&speed=12.5&bearing=90&altitude=15&accuracy=8&batt=76
The parameters you'll use most:
| Parameter | Meaning |
|---|---|
id |
Device identifier. Must match the Identifier of a device in Traccar |
lat, lon |
Position in decimal degrees |
timestamp |
Unix time in seconds (when the fix was taken, not when it was sent) |
speed |
Speed in knots |
bearing |
Heading in degrees |
altitude |
Metres |
accuracy |
Horizontal accuracy in metres |
batt |
Battery level in percent |
alarm |
Alarm type, e.g. sos |
Traccar replies with 200 OK when it accepts the position. If the id doesn't match a device on the server, the position is rejected, so register the device first: Settings → Devices → +, and copy the identifier your app will send.
Use HTTPS in production. Port 5055 is plain HTTP. Put a reverse proxy (Nginx or Apache) with a certificate in front of it, for example
https://track.example.com/osmand→http://127.0.0.1:5055.
Getting the position in Flutter
Add the packages:
dependencies:
geolocator: ^13.0.0
http: ^1.2.0
battery_plus: ^6.0.0
Ask for permission and read one position:
import 'package:geolocator/geolocator.dart';
Future<Position> currentPosition() async {
if (!await Geolocator.isLocationServiceEnabled()) {
throw Exception('Location services are turned off');
}
var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
if (permission == LocationPermission.denied ||
permission == LocationPermission.deniedForever) {
throw Exception('Location permission denied');
}
return Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(accuracy: LocationAccuracy.high),
);
}
Remember to add the location permission entries to AndroidManifest.xml and Info.plist. The geolocator README lists them.
Sending a position to Traccar
import 'package:http/http.dart' as http;
import 'package:geolocator/geolocator.dart';
class TraccarClient {
TraccarClient({required this.serverUrl, required this.deviceId});
final String serverUrl; // e.g. https://track.example.com/osmand
final String deviceId; // must match the device identifier in Traccar
static const _msToKnots = 1.943844;
Uri buildUri(Position p, {int? battery, String? alarm}) {
return Uri.parse(serverUrl).replace(queryParameters: {
'id': deviceId,
'lat': p.latitude.toStringAsFixed(6),
'lon': p.longitude.toStringAsFixed(6),
'timestamp': (p.timestamp.millisecondsSinceEpoch ~/ 1000).toString(),
'speed': (p.speed < 0 ? 0 : p.speed * _msToKnots).toStringAsFixed(1),
'bearing': p.heading.toStringAsFixed(0),
'altitude': p.altitude.toStringAsFixed(0),
'accuracy': p.accuracy.toStringAsFixed(0),
if (battery != null) 'batt': battery.toString(),
if (alarm != null) 'alarm': alarm,
});
}
Future<bool> send(Uri uri) async {
try {
final res = await http.post(uri).timeout(const Duration(seconds: 15));
return res.statusCode == 200;
} catch (_) {
return false; // no network, timeout, DNS failure...
}
}
}
Two details catch people out:
- Speed is in knots. Geolocator gives metres per second, so multiply by 1.943844. If you skip this, every speed report in Traccar is about half the real value.
- Send the fix time, not the current time.
p.timestampis when the GPS fix was taken. That matters when you upload points that were stored while the phone was offline.
Raising an SOS alarm
An SOS is just a normal position with alarm=sos added:
import 'package:battery_plus/battery_plus.dart';
Future<bool> sendSos(TraccarClient client) async {
final position = await currentPosition();
final battery = await Battery().batteryLevel;
return client.send(client.buildUri(position, battery: battery, alarm: 'sos'));
}
Wire it to a big button. Use a long press or a confirmation step so it can't be triggered from a pocket.
On the server, Traccar saves the alarm attribute on the position and creates an Alarm event. To actually receive the alert:
- Go to Settings → Notifications → +.
- Choose type Alarm and select SOS in the alarms list.
- Pick the channels (web, mail, SMS, Firebase push) and link the notification to the device or a group.
Send a test SOS and check that the alert arrives before giving the app to real users.
Don't lose points when the signal drops
Phones lose signal in basements, lifts and rural roads. If the app just drops failed requests, the trip in Traccar has gaps. A small queue fixes this:
class PositionQueue {
PositionQueue(this.client);
final TraccarClient client;
final List<Uri> _pending = [];
Future<void> add(Uri uri) async {
_pending.add(uri);
await flush();
}
Future<void> flush() async {
while (_pending.isNotEmpty) {
final ok = await client.send(_pending.first);
if (!ok) return; // still offline, try again on the next point
_pending.removeAt(0);
}
}
}
Points are sent oldest first, so Traccar receives them in order. Because each one carries its own timestamp, the route is drawn correctly even when points arrive minutes late.
For a production app, store the queue on disk (for example with sqflite or hive) so it survives the app being killed, and cap its size so a phone that is offline for weeks doesn't fill its storage. SOS requests should skip the queue and be retried straight away.
Tracking in the background
The examples above send a position when the app asks for one. Continuous tracking with the screen off needs a foreground service on Android and background location mode on iOS. That's a topic of its own, and battery life is the hard part. The next post covers adaptive location sampling: changing how often you record a position based on whether the device is moving.
Summary
- Traccar accepts OsmAnd HTTP requests on port 5055. The
idmust match a device on the server. - Convert speed from m/s to knots, and send the GPS fix time.
- Add
alarm=sosto raise an SOS, and create an Alarm notification in Traccar to receive it. - Queue positions while offline and send them oldest first.
- Put HTTPS in front of port 5055 before going live.
