2025-02-04 13:38:17 +02:00
|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
|
|
|
|
import 'package:args/args.dart';
|
|
|
|
|
|
|
|
const String version = '0.0.1';
|
|
|
|
|
|
|
|
ArgParser buildParser() {
|
|
|
|
return ArgParser()
|
|
|
|
..addOption('source', abbr: 's', help: 'Source GeoJSON file')
|
|
|
|
..addOption('destination',
|
|
|
|
abbr: 'd', help: 'OwnTracks Recorder server address')
|
|
|
|
..addOption('user', abbr: 'u', help: 'User for OwnTrack record')
|
2025-02-06 14:49:13 +02:00
|
|
|
..addOption('continue',
|
|
|
|
abbr: 'c',
|
|
|
|
help: 'The number of record to continue import from (Starting from 1)')
|
2025-02-04 13:38:17 +02:00
|
|
|
..addFlag(
|
|
|
|
'help',
|
|
|
|
abbr: 'h',
|
|
|
|
negatable: false,
|
|
|
|
help: 'Print this usage information.',
|
|
|
|
)
|
|
|
|
..addFlag(
|
|
|
|
'verbose',
|
|
|
|
abbr: 'v',
|
|
|
|
negatable: false,
|
|
|
|
help: 'Show additional command output.',
|
|
|
|
)
|
|
|
|
..addFlag(
|
|
|
|
'no-limits',
|
|
|
|
abbr: 'n',
|
|
|
|
defaultsTo: false,
|
|
|
|
help:
|
|
|
|
'Disable 2 seconds delay to not hit reverse geocoding API rate limits.',
|
|
|
|
)
|
|
|
|
..addFlag(
|
|
|
|
'version',
|
|
|
|
negatable: false,
|
|
|
|
help: 'Print the tool version.',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void printUsage(ArgParser argParser) {
|
|
|
|
print('Usage: dart owntracks_import.dart <options> <flags>');
|
|
|
|
print(argParser.usage);
|
|
|
|
}
|
|
|
|
|
|
|
|
void main(List<String> arguments) async {
|
|
|
|
final ArgParser argParser = buildParser();
|
|
|
|
try {
|
|
|
|
final ArgResults results = argParser.parse(arguments);
|
|
|
|
|
|
|
|
bool verbose = false;
|
|
|
|
|
|
|
|
if (results.wasParsed('help')) {
|
|
|
|
printUsage(argParser);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (results.wasParsed('version')) {
|
|
|
|
print('owntracks_import version: $version');
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (results.wasParsed('verbose')) {
|
|
|
|
verbose = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
final sourceFilePath = results.option('source');
|
|
|
|
|
|
|
|
if (sourceFilePath == null) {
|
|
|
|
throw FormatException('No source file specified');
|
|
|
|
}
|
|
|
|
|
|
|
|
final rawData = await File(sourceFilePath).readAsString();
|
|
|
|
|
|
|
|
final data = jsonDecode(rawData);
|
|
|
|
|
|
|
|
final recordsList = data['features'];
|
|
|
|
|
|
|
|
final List<dynamic> failedList = [];
|
|
|
|
|
2025-02-06 14:49:13 +02:00
|
|
|
final rawContinueFrom = results.option('continue');
|
|
|
|
|
|
|
|
final continueFrom =
|
|
|
|
rawContinueFrom == null ? 1 : int.parse(rawContinueFrom);
|
|
|
|
|
2025-02-04 13:38:17 +02:00
|
|
|
int n = 1;
|
|
|
|
|
2025-02-06 14:49:13 +02:00
|
|
|
if (continueFrom > recordsList.length) {
|
|
|
|
throw FormatException('There are only ${recordsList.length} in the list');
|
|
|
|
}
|
|
|
|
|
2025-02-04 13:38:17 +02:00
|
|
|
for (final record in recordsList) {
|
2025-02-06 14:49:13 +02:00
|
|
|
if (n < continueFrom) {
|
|
|
|
if (verbose) {
|
|
|
|
print(' ');
|
|
|
|
print('Record $n of ${recordsList.length} SKIPPED');
|
|
|
|
}
|
|
|
|
|
|
|
|
n++;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (verbose) {
|
|
|
|
print(' ');
|
|
|
|
print('Record $n of ${recordsList.length}');
|
|
|
|
}
|
|
|
|
|
2025-02-04 13:38:17 +02:00
|
|
|
final Map<String, dynamic> payload = {'_type': 'location', 't': 'u'};
|
|
|
|
|
|
|
|
final recordProps = record['properties'];
|
|
|
|
|
|
|
|
payload['tst'] = recordProps['timestamp'];
|
|
|
|
|
|
|
|
payload['lat'] = double.tryParse(recordProps['latitude']);
|
|
|
|
|
|
|
|
payload['lon'] = double.tryParse(recordProps['longitude']);
|
|
|
|
|
|
|
|
payload['acc'] = recordProps['accuracy'];
|
|
|
|
|
|
|
|
payload['alt'] = recordProps['altitude'];
|
|
|
|
|
|
|
|
payload['vac'] = recordProps['vertical_accuracy'];
|
|
|
|
|
|
|
|
payload['vel'] = recordProps['velocity'];
|
|
|
|
|
2025-02-06 14:35:35 +02:00
|
|
|
double? batt = recordProps['battery'] != null
|
|
|
|
? double.tryParse(recordProps['battery'].toString())
|
2025-02-04 13:38:17 +02:00
|
|
|
: null;
|
|
|
|
|
|
|
|
if (batt != null && batt <= 1) {
|
|
|
|
batt = batt * 100;
|
2025-02-06 14:52:22 +02:00
|
|
|
} else if (batt != null && batt > 100) {
|
|
|
|
batt = batt / 100;
|
2025-02-04 13:38:17 +02:00
|
|
|
}
|
|
|
|
|
2025-02-06 14:35:35 +02:00
|
|
|
payload['batt'] = batt?.toInt();
|
2025-02-04 13:38:17 +02:00
|
|
|
|
|
|
|
payload['bs'] = recordProps['battery_status'];
|
|
|
|
|
|
|
|
payload['inregions'] = recordProps['in_regions'];
|
|
|
|
|
|
|
|
payload['SSID'] = recordProps['ssid'];
|
|
|
|
|
|
|
|
payload['conn'] = recordProps['connection'];
|
|
|
|
|
|
|
|
if (verbose) {
|
|
|
|
print(payload);
|
|
|
|
}
|
|
|
|
|
|
|
|
String device = recordProps['tracker_id'] ?? 'phone';
|
|
|
|
|
|
|
|
if (device.startsWith('device_tracker.')) {
|
|
|
|
device = device.replaceAll('device_tracker.', '');
|
|
|
|
}
|
|
|
|
|
|
|
|
final user = results.option('user');
|
|
|
|
|
|
|
|
final url = '${results.option('destination')}/pub?u=$user&d=$device';
|
|
|
|
|
|
|
|
if (verbose) {
|
|
|
|
print('...Sending to: $url');
|
|
|
|
}
|
|
|
|
|
|
|
|
final response = await http.post(
|
|
|
|
Uri.parse('${results.option('destination')}/pub?u=$user&d=$device'),
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
body: jsonEncode(payload),
|
|
|
|
);
|
|
|
|
|
|
|
|
if (response.statusCode == 200) {
|
|
|
|
if (verbose) {
|
|
|
|
print('Success: ${response.body}');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
print('Error sending record. ${response.statusCode}: ${response.body}');
|
|
|
|
|
|
|
|
failedList.add(payload);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!results.flag('no-limits')) {
|
|
|
|
if (verbose) {
|
|
|
|
print('Waiting 2 seconds to not hit API rate limit');
|
|
|
|
}
|
|
|
|
|
|
|
|
await Future.delayed(Duration(seconds: 2));
|
|
|
|
}
|
|
|
|
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (verbose) {
|
|
|
|
print('Finished all records.');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (failedList.isNotEmpty) {
|
|
|
|
print('The list of failed records:');
|
|
|
|
print(failedList);
|
|
|
|
}
|
|
|
|
} on FormatException catch (e) {
|
|
|
|
// Print usage information if an invalid argument was provided.
|
|
|
|
print(e.message);
|
|
|
|
print('');
|
|
|
|
printUsage(argParser);
|
|
|
|
} on Exception catch (e) {
|
|
|
|
print(e);
|
|
|
|
}
|
|
|
|
}
|