Implement support for OwnTracks REC files

This commit is contained in:
Eugene Burmakin 2024-10-15 22:17:51 +02:00
parent fb2468ab3d
commit a2f3aef608
11 changed files with 64 additions and 330 deletions

File diff suppressed because one or more lines are too long

View file

@ -30,7 +30,12 @@ class ImportsController < ApplicationController
file = File.read(file)
raw_data = params[:import][:source] == 'gpx' ? Hash.from_xml(file) : JSON.parse(file)
raw_data =
case params[:import][:source]
when 'gpx' then Hash.from_xml(file)
when 'owntracks' then file
else JSON.parse(file)
end
import.update(raw_data:)
import.id

View file

@ -1,16 +1,16 @@
# frozen_string_literal: true
class OwnTracks::ExportParser
attr_reader :import, :json, :user_id
attr_reader :import, :data, :user_id
def initialize(import, user_id)
@import = import
@json = import.raw_data
@data = import.raw_data
@user_id = user_id
end
def call
points_data = parse_json
points_data = parse_data
points_data.each do |point_data|
next if Point.exists?(
@ -31,15 +31,11 @@ class OwnTracks::ExportParser
private
def parse_json
points = []
json.each_key do |user|
json[user].each_key do |devise|
json[user][devise].each { |point| points << OwnTracks::Params.new(point).call }
end
def parse_data
data.split("\n").map do |line|
OwnTracks::Params.new(
JSON.parse(line.split("\t* \t")[1])
).call
end
points
end
end

View file

@ -19,7 +19,7 @@
<%= form.radio_button :source, :owntracks, class: "radio radio-primary" %>
<span class="label-text">Owntracks</span>
</label>
<p class="text-sm mt-2">A JSON file you exported by pressing Download button in top right corner of OwnTracks web interface</p>
<p class="text-sm mt-2">A .REC file you could find in your volumes/owntracks-recorder/store/rec/USER/TOPIC directory</p>
</div>
</div>
<div class="card bordered shadow-lg p-3 hover:shadow-blue-500/50">

View file

@ -4,7 +4,7 @@ FactoryBot.define do
factory :import do
user
name { 'APRIL_2013.json' }
source { 1 }
raw_data { JSON.parse(File.read('spec/fixtures/files/owntracks/export.json')) }
source { Import.sources[:owntracks] }
raw_data { File.read('spec/fixtures/files/owntracks/2024-03.rec') }
end
end

View file

@ -0,0 +1,10 @@
2024-03-01T09:03:09Z * {"bs":2,"p":100.266,"batt":94,"_type":"location","tid":"RO","topic":"owntracks/test/iPhone 12 Pro","alt":36,"lon":13.332,"vel":0,"t":"p","BSSID":"b0:f2:8:45:94:33","SSID":"Home Wifi","conn":"w","vac":4,"acc":10,"tst":1709283789,"lat":52.225,"m":1,"inrids":["5f1d1b"],"inregions":["home"],"_http":true}
2024-03-01T17:46:02Z * {"bs":1,"p":100.28,"batt":94,"_type":"location","tid":"RO","topic":"owntracks/test/iPhone 12 Pro","alt":36,"lon":13.333,"t":"p","vel":0,"BSSID":"b0:f2:8:45:94:33","conn":"w","SSID":"Home Wifi","vac":3,"cog":98,"acc":9,"tst":1709315162,"lat":52.226,"m":1,"inrids":["5f1d1b"],"inregions":["home"],"_http":true}
2024-03-01T18:26:55Z * {"lon":13.334,"acc":5,"wtst":1696359532,"event":"leave","rid":"5f1d1b","desc":"home","topic":"owntracks/test/iPhone 12 Pro/event","lat":52.227,"t":"c","tst":1709317615,"tid":"RO","_type":"transition","_http":true}
2024-03-01T18:26:55Z * {"cog":40,"batt":85,"lon":13.335,"acc":5,"bs":1,"p":100.279,"vel":3,"vac":3,"lat":52.228,"topic":"owntracks/test/iPhone 12 Pro","t":"c","conn":"m","m":1,"tst":1709317615,"alt":36,"_type":"location","tid":"RO","_http":true}
2024-03-01T18:28:30Z * {"cog":38,"batt":85,"lon":13.336,"acc":5,"bs":1,"p":100.349,"vel":3,"vac":3,"lat":52.229,"topic":"owntracks/test/iPhone 12 Pro","t":"v","conn":"m","m":1,"tst":1709317710,"alt":35,"_type":"location","tid":"RO","_http":true}
2024-03-01T18:33:03Z * {"cog":18,"batt":85,"lon":13.337,"acc":5,"bs":1,"p":100.347,"vel":4,"vac":3,"lat":52.230,"topic":"owntracks/test/iPhone 12 Pro","conn":"m","m":1,"tst":1709317983,"alt":36,"_type":"location","tid":"RO","_http":true}
2024-03-01T18:40:11Z * {"cog":43,"batt":85,"lon":13.338,"acc":5,"bs":1,"p":100.348,"vel":6,"vac":3,"lat":52.231,"topic":"owntracks/test/iPhone 12 Pro","conn":"m","m":1,"tst":1709318411,"alt":37,"_type":"location","tid":"RO","_http":true}
2024-03-01T18:42:57Z * {"cog":320,"batt":85,"lon":13.339,"acc":5,"bs":1,"p":100.353,"vel":3,"vac":3,"lat":52.232,"topic":"owntracks/test/iPhone 12 Pro","t":"v","conn":"m","m":1,"tst":1709318577,"alt":37,"_type":"location","tid":"RO","_http":true}
2024-03-01T18:40:11Z * {"cog":43,"batt":85,"lon":13.338,"acc":5,"bs":1,"p":100.348,"vel":6,"vac":3,"lat":52.231,"topic":"owntracks/test/iPhone 12 Pro","conn":"m","m":1,"tst":1709318411,"alt":37,"_type":"location","tid":"RO","_http":true}
2024-03-01T18:40:11Z * {"cog":43,"batt":85,"lon":13.341,"acc":5,"bs":1,"p":100.348,"created_at":1709318940,"vel":6,"vac":3,"lat":52.234,"topic":"owntracks/test/iPhone 12 Pro","conn":"m","m":1,"tst":1709318411,"alt":37,"_type":"location","tid":"RO","_http":true}

View file

@ -1,275 +0,0 @@
{
"test": {
"iphone-12-pro": [
{
"batt": 85,
"lon": -74.0060,
"acc": 8,
"bs": 2,
"inrids": [
"5f1d1b"
],
"BSSID": "b0:f2:8:45:94:33",
"SSID": "Home Wifi",
"vac": 3,
"inregions": [
"home"
],
"lat": 40.7128,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "p",
"conn": "w",
"m": 1,
"tst": 1706965203,
"alt": 41,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T13:00:03Z",
"isotst": "2024-02-03T13:00:03Z",
"disptst": "2024-02-03 13:00:03"
},
{
"batt": 89,
"lon": -0.1278,
"acc": 8,
"bs": 1,
"inrids": [
"5f1d1b"
],
"p": 101.233,
"BSSID": "b0:f2:8:45:94:33",
"SSID": "Home Wifi",
"vac": 15,
"inregions": [
"home"
],
"lat": 51.5074,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "p",
"conn": "w",
"m": 1,
"tst": 1706967037,
"alt": 36,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T13:30:37Z",
"isotst": "2024-02-03T13:30:37Z",
"disptst": "2024-02-03 13:30:37"
},
{
"cog": 11,
"batt": 94,
"lon": 139.6917,
"acc": 5,
"bs": 1,
"p": 101.318,
"vel": 3,
"vac": 3,
"lat": 35.6895,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "v",
"conn": "m",
"m": 1,
"tst": 1706971835,
"alt": 35,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d77d",
"isorcv": "2024-02-03T14:50:35Z",
"isotst": "2024-02-03T14:50:35Z",
"disptst": "2024-02-03 14:50:35"
},
{
"cog": 11,
"batt": 94,
"lon": 151.2093,
"acc": 5,
"bs": 1,
"p": 101.318,
"vel": 3,
"vac": 3,
"lat": -33.8688,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "c",
"conn": "m",
"m": 1,
"tst": 1706971835,
"alt": 35,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d77d",
"isorcv": "2024-02-03T14:50:35Z",
"isotst": "2024-02-03T14:50:35Z",
"disptst": "2024-02-03 14:50:35"
},
{
"cog": 47,
"batt": 94,
"lon": 2.3522,
"acc": 4,
"bs": 1,
"inrids": [
"5f1d1b"
],
"p": 101.326,
"vel": 4,
"vac": 4,
"inregions": [
"home"
],
"lat": 48.8566,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "v",
"conn": "m",
"m": 1,
"tst": 1706974182,
"alt": 42,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T15:29:42Z",
"isotst": "2024-02-03T15:29:42Z",
"disptst": "2024-02-03 15:29:42"
},
{
"batt": 94,
"lon": -43.1729,
"acc": 8,
"bs": 1,
"inrids": [
"5f1d1b"
],
"p": 101.253,
"BSSID": "b0:f2:8:45:94:33",
"SSID": "Home Wifi",
"vac": 11,
"inregions": [
"home"
],
"lat": -22.9068,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "p",
"conn": "w",
"m": 1,
"tst": 1706974302,
"alt": 37,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T15:31:42Z",
"isotst": "2024-02-03T15:31:42Z",
"disptst": "2024-02-03 15:31:42"
},
{
"batt": 94,
"lon": -43.1729,
"acc": 8,
"bs": 1,
"inrids": [
"5f1d1b"
],
"p": 101.256,
"BSSID": "b0:f2:8:45:94:33",
"SSID": "Home Wifi",
"vac": 17,
"inregions": [
"home"
],
"lat": -22.9068,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "p",
"conn": "w",
"m": 1,
"tst": 1706977057,
"alt": 36,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T16:17:37Z",
"isotst": "2024-02-03T16:17:37Z",
"disptst": "2024-02-03 16:17:37"
},
{
"cog": 43,
"batt": 89,
"lon": 37.6176,
"acc": 5,
"bs": 1,
"p": 101.349,
"vel": 5,
"vac": 3,
"lat": 55.7558,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "v",
"conn": "m",
"m": 1,
"tst": 1706981399,
"alt": 38,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d7ku",
"isorcv": "2024-02-03T17:29:59Z",
"isotst": "2024-02-03T17:29:59Z",
"disptst": "2024-02-03 17:29:59"
},
{
"cog": 53,
"batt": 89,
"lon": 37.6176,
"acc": 5,
"bs": 1,
"p": 101.353,
"vel": 5,
"vac": 3,
"lat": 55.7558,
"topic": "owntracks/test/iPhone 12 Pro",
"conn": "m",
"m": 1,
"tst": 1706981451,
"alt": 37,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d7kv",
"isorcv": "2024-02-03T17:30:51Z",
"isotst": "2024-02-03T17:30:51Z",
"disptst": "2024-02-03 17:30:51"
},
{
"cog": 53,
"batt": 89,
"lon": 37.6176,
"acc": 5,
"bs": 1,
"p": 101.359,
"created_at": 1706981777,
"vel": 5,
"vac": 3,
"lat": 55.7558,
"topic": "owntracks/test/iPhone 12 Pro",
"conn": "m",
"m": 1,
"tst": 1706981451,
"alt": 37,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d7kv",
"isorcv": "2024-02-03T17:30:51Z",
"isotst": "2024-02-03T17:30:51Z",
"disptst": "2024-02-03 17:30:51"
}
]
}
}

View file

@ -41,7 +41,7 @@ RSpec.describe 'Imports', type: :request do
before { sign_in user }
context 'when importing owntracks data' do
let(:file) { fixture_file_upload('owntracks/export.json', 'application/json') }
let(:file) { fixture_file_upload('owntracks/2024-03.rec', 'application/json') }
it 'queues import job' do
expect do

View file

@ -18,21 +18,21 @@ RSpec.describe OwnTracks::ExportParser do
parser
expect(Point.first.attributes).to include(
'latitude' => 40.7128,
'longitude' => -74.006,
'latitude' => 52.225,
'longitude' => 13.332,
'battery_status' => 'charging',
'battery' => 85,
'ping' => nil,
'altitude' => 41,
'accuracy' => 8,
'vertical_accuracy' => 3,
'velocity' => nil,
'battery' => 94,
'ping' => '100.266',
'altitude' => 36,
'accuracy' => 10,
'vertical_accuracy' => 4,
'velocity' => '0',
'connection' => 'wifi',
'ssid' => 'Home Wifi',
'bssid' => 'b0:f2:8:45:94:33',
'trigger' => 'background_event',
'tracker_id' => 'RO',
'timestamp' => 1_706_965_203,
'timestamp' => 1709283789,
'inrids' => ['5f1d1b'],
'in_regions' => ['home'],
'topic' => 'owntracks/test/iPhone 12 Pro',
@ -40,29 +40,27 @@ RSpec.describe OwnTracks::ExportParser do
'user_id' => user.id,
'country' => nil,
'raw_data' => {
'batt' => 85,
'lon' => -74.006,
'acc' => 8,
'bs' => 2,
'inrids' => ['5f1d1b'],
'BSSID' => 'b0:f2:8:45:94:33',
'SSID' => 'Home Wifi',
'vac' => 3,
'inregions' => ['home'],
'lat' => 40.7128,
'topic' => 'owntracks/test/iPhone 12 Pro',
't' => 'p',
'conn' => 'w',
'm' => 1,
'tst' => 1_706_965_203,
'alt' => 41,
'_type' => 'location',
'tid' => 'RO',
'_http' => true,
'ghash' => 'u33d773',
'isorcv' => '2024-02-03T13:00:03Z',
'isotst' => '2024-02-03T13:00:03Z',
'disptst' => '2024-02-03 13:00:03'
'm'=>1,
'p'=>100.266,
't'=>'p',
'bs'=>2,
'acc'=>10,
'alt'=>36,
'lat'=>52.225,
'lon'=>13.332,
'tid'=>'RO',
'tst'=>1709283789,
'vac'=>4,
'vel'=>0,
'SSID'=>'Home Wifi',
'batt'=>94,
'conn'=>'w',
'BSSID'=>'b0:f2:8:45:94:33',
'_http'=>true,
'_type'=>'location',
'topic'=>'owntracks/test/iPhone 12 Pro',
'inrids'=>['5f1d1b'],
'inregions'=>['home']
}
)
end

View file

@ -6,7 +6,7 @@ RSpec.describe OwnTracks::Params do
describe '#call' do
subject(:params) { described_class.new(raw_point_params).call }
let(:file_path) { 'spec/fixtures/files/owntracks/export.json' }
let(:file_path) { 'spec/fixtures/files/owntracks/2024-03.rec' }
let(:file) { File.open(file_path) }
let(:json) { JSON.parse(file.read) }
let(:user) { json.keys.first }

View file

@ -69,7 +69,7 @@ describe 'OwnTracks Points API', type: :request do
parameter name: :api_key, in: :query, type: :string, required: true, description: 'API Key'
response '200', 'Point created' do
let(:file_path) { 'spec/fixtures/files/owntracks/export.json' }
let(:file_path) { 'spec/fixtures/files/owntracks/2024-03.rec' }
let(:file) { File.open(file_path) }
let(:json) { JSON.parse(file.read) }
let(:point) { json['test']['iphone-12-pro'].first }
@ -79,7 +79,7 @@ describe 'OwnTracks Points API', type: :request do
end
response '401', 'Unauthorized' do
let(:file_path) { 'spec/fixtures/files/owntracks/export.json' }
let(:file_path) { 'spec/fixtures/files/owntracks/2024-03.rec' }
let(:file) { File.open(file_path) }
let(:json) { JSON.parse(file.read) }
let(:point) { json['test']['iphone-12-pro'].first }