188 lines
4.4 KiB
Dart
188 lines
4.4 KiB
Dart
|
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')
|
||
|
..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 = [];
|
||
|
|
||
|
int n = 1;
|
||
|
|
||
|
for (final record in recordsList) {
|
||
|
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'];
|
||
|
|
||
|
int? batt = recordProps['battery'] != null
|
||
|
? int.tryParse(recordProps['battery'])
|
||
|
: null;
|
||
|
|
||
|
if (batt != null && batt <= 1) {
|
||
|
batt = batt * 100;
|
||
|
}
|
||
|
|
||
|
payload['batt'] = batt;
|
||
|
|
||
|
payload['bs'] = recordProps['battery_status'];
|
||
|
|
||
|
payload['inregions'] = recordProps['in_regions'];
|
||
|
|
||
|
payload['SSID'] = recordProps['ssid'];
|
||
|
|
||
|
payload['conn'] = recordProps['connection'];
|
||
|
|
||
|
if (verbose) {
|
||
|
print(' ');
|
||
|
print('Record $n of ${recordsList.length}');
|
||
|
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(' ');
|
||
|
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);
|
||
|
}
|
||
|
}
|