├── debian ├── compat ├── source │ └── format ├── ot-recorder.docs ├── ot-recorder.dirs ├── ot-recorder.postinst ├── changelog ├── ot-recorder.tmpfile ├── rules ├── ot-recorder.service └── control ├── docroot ├── static │ ├── .gitignore │ ├── leaflet │ │ ├── version-1.3.4 │ │ └── images │ │ │ ├── layers.png │ │ │ ├── layers-2x.png │ │ │ ├── marker-icon.png │ │ │ ├── marker-shadow.png │ │ │ └── marker-icon-2x.png │ └── recorder.png ├── last │ ├── .gitignore │ ├── recorder-logo-192.png │ ├── config.js │ ├── config.js.sample │ ├── manifest.json │ ├── last.html │ ├── websock.js │ ├── map_leaflet.js │ └── index.html ├── test.txt ├── views │ ├── .gitignore │ └── jane.json.sample ├── favicon.ico ├── table │ ├── flags │ │ ├── AD.png │ │ ├── AE.png │ │ ├── AF.png │ │ ├── AG.png │ │ ├── AI.png │ │ ├── AL.png │ │ ├── AM.png │ │ ├── AN.png │ │ ├── AO.png │ │ ├── AQ.png │ │ ├── AR.png │ │ ├── AS.png │ │ ├── AT.png │ │ ├── AU.png │ │ ├── AW.png │ │ ├── AX.png │ │ ├── AZ.png │ │ ├── BA.png │ │ ├── BB.png │ │ ├── BD.png │ │ ├── BE.png │ │ ├── BF.png │ │ ├── BG.png │ │ ├── BH.png │ │ ├── BI.png │ │ ├── BJ.png │ │ ├── BL.png │ │ ├── BM.png │ │ ├── BN.png │ │ ├── BO.png │ │ ├── BR.png │ │ ├── BS.png │ │ ├── BT.png │ │ ├── BW.png │ │ ├── BY.png │ │ ├── BZ.png │ │ ├── CA.png │ │ ├── CC.png │ │ ├── CD.png │ │ ├── CF.png │ │ ├── CG.png │ │ ├── CH.png │ │ ├── CI.png │ │ ├── CK.png │ │ ├── CL.png │ │ ├── CM.png │ │ ├── CN.png │ │ ├── CO.png │ │ ├── CR.png │ │ ├── CU.png │ │ ├── CV.png │ │ ├── CW.png │ │ ├── CX.png │ │ ├── CY.png │ │ ├── CZ.png │ │ ├── DE.png │ │ ├── DJ.png │ │ ├── DK.png │ │ ├── DM.png │ │ ├── DO.png │ │ ├── DZ.png │ │ ├── EC.png │ │ ├── EE.png │ │ ├── EG.png │ │ ├── EH.png │ │ ├── ER.png │ │ ├── ES.png │ │ ├── ET.png │ │ ├── EU.png │ │ ├── FI.png │ │ ├── FJ.png │ │ ├── FK.png │ │ ├── FM.png │ │ ├── FO.png │ │ ├── FR.png │ │ ├── GA.png │ │ ├── GB.png │ │ ├── GD.png │ │ ├── GE.png │ │ ├── GG.png │ │ ├── GH.png │ │ ├── GI.png │ │ ├── GL.png │ │ ├── GM.png │ │ ├── GN.png │ │ ├── GQ.png │ │ ├── GR.png │ │ ├── GS.png │ │ ├── GT.png │ │ ├── GU.png │ │ ├── GW.png │ │ ├── GY.png │ │ ├── HK.png │ │ ├── HN.png │ │ ├── HR.png │ │ ├── HT.png │ │ ├── HU.png │ │ ├── IC.png │ │ ├── ID.png │ │ ├── IE.png │ │ ├── IL.png │ │ ├── IM.png │ │ ├── IN.png │ │ ├── IQ.png │ │ ├── IR.png │ │ ├── IS.png │ │ ├── IT.png │ │ ├── JE.png │ │ ├── JM.png │ │ ├── JO.png │ │ ├── JP.png │ │ ├── KE.png │ │ ├── KG.png │ │ ├── KH.png │ │ ├── KI.png │ │ ├── KM.png │ │ ├── KN.png │ │ ├── KP.png │ │ ├── KR.png │ │ ├── KW.png │ │ ├── KY.png │ │ ├── KZ.png │ │ ├── LA.png │ │ ├── LB.png │ │ ├── LC.png │ │ ├── LI.png │ │ ├── LK.png │ │ ├── LR.png │ │ ├── LS.png │ │ ├── LT.png │ │ ├── LU.png │ │ ├── LV.png │ │ ├── LY.png │ │ ├── MA.png │ │ ├── MC.png │ │ ├── MD.png │ │ ├── ME.png │ │ ├── MF.png │ │ ├── MG.png │ │ ├── MH.png │ │ ├── MK.png │ │ ├── ML.png │ │ ├── MM.png │ │ ├── MN.png │ │ ├── MO.png │ │ ├── MP.png │ │ ├── MQ.png │ │ ├── MR.png │ │ ├── MS.png │ │ ├── MT.png │ │ ├── MU.png │ │ ├── MV.png │ │ ├── MW.png │ │ ├── MX.png │ │ ├── MY.png │ │ ├── MZ.png │ │ ├── NA.png │ │ ├── NC.png │ │ ├── NE.png │ │ ├── NF.png │ │ ├── NG.png │ │ ├── NI.png │ │ ├── NL.png │ │ ├── NO.png │ │ ├── NP.png │ │ ├── NR.png │ │ ├── NU.png │ │ ├── NZ.png │ │ ├── OM.png │ │ ├── PA.png │ │ ├── PE.png │ │ ├── PF.png │ │ ├── PG.png │ │ ├── PH.png │ │ ├── PK.png │ │ ├── PL.png │ │ ├── PN.png │ │ ├── PR.png │ │ ├── PS.png │ │ ├── PT.png │ │ ├── PW.png │ │ ├── PY.png │ │ ├── QA.png │ │ ├── RO.png │ │ ├── RS.png │ │ ├── RU.png │ │ ├── RW.png │ │ ├── SA.png │ │ ├── SB.png │ │ ├── SC.png │ │ ├── SD.png │ │ ├── SE.png │ │ ├── SG.png │ │ ├── SH.png │ │ ├── SI.png │ │ ├── SK.png │ │ ├── SL.png │ │ ├── SM.png │ │ ├── SN.png │ │ ├── SO.png │ │ ├── SR.png │ │ ├── SS.png │ │ ├── ST.png │ │ ├── SV.png │ │ ├── SY.png │ │ ├── SZ.png │ │ ├── TC.png │ │ ├── TD.png │ │ ├── TF.png │ │ ├── TG.png │ │ ├── TH.png │ │ ├── TJ.png │ │ ├── TK.png │ │ ├── TL.png │ │ ├── TM.png │ │ ├── TN.png │ │ ├── TO.png │ │ ├── TR.png │ │ ├── TT.png │ │ ├── TV.png │ │ ├── TW.png │ │ ├── TZ.png │ │ ├── UA.png │ │ ├── UG.png │ │ ├── US.png │ │ ├── UY.png │ │ ├── UZ.png │ │ ├── VA.png │ │ ├── VC.png │ │ ├── VE.png │ │ ├── VG.png │ │ ├── VI.png │ │ ├── VN.png │ │ ├── VU.png │ │ ├── WF.png │ │ ├── WS.png │ │ ├── YE.png │ │ ├── YT.png │ │ ├── ZA.png │ │ ├── ZM.png │ │ ├── ZW.png │ │ ├── __.png │ │ ├── LICENSE.txt │ │ └── Hello.txt │ ├── images │ │ ├── sort_asc.png │ │ ├── sort_both.png │ │ └── sort_desc.png │ ├── config.js │ ├── otable.css │ └── defaultface.js ├── owntracks.xsl ├── test-ws.html ├── map │ ├── index.html │ ├── map_google.js │ └── map_leaflet.js └── index.html ├── version.h ├── .github └── FUNDING.yml ├── contrib ├── faces │ ├── .gitignore │ ├── image2card.sh │ ├── gravatar2card.sh │ └── github2card.py ├── freebsd │ ├── readme.txt │ ├── otrecorder.rc │ └── ot-recorder.rc ├── lua │ └── http-to-mqtt-repub.lua ├── opencage.lua ├── get-missing │ └── get-missing.py ├── gpx-to-rec │ ├── README.md │ └── gpx_to_rec.py ├── new-view-auth.py ├── node-red.md └── ot-ping.py ├── assets ├── geohash-2.png ├── geohash-5.png ├── geohash-7.png ├── view-map.png ├── demo-table.png ├── ot-recorder.png ├── view-track.png ├── demo-live-map.png ├── demo-geojson-points.png ├── recorder-logo-192.png └── demo-geojson-linestring.png ├── etc ├── rhel │ ├── README │ ├── ot-recorder.defaults │ ├── ot-recorder.init │ └── ot-recorder.spec ├── ot-recorder.service ├── demo-iphone.readme ├── raspbian │ ├── config.mk.in │ ├── postinst │ └── fpm-make.sh ├── debian │ ├── config.mk.in │ ├── postinst │ └── fpm-make.sh ├── centos │ ├── config.mk.in │ ├── postinst │ └── fpm-make.sh ├── example.lua ├── repo.owntracks.org.gpg.key ├── ot-recorder.default └── demo-iphone.rec ├── listsort.h ├── geo.h ├── base64.h ├── recorder.h ├── .gitignore ├── http.h ├── misc.h ├── fences.h ├── hooks.h ├── gcache.h ├── .travis.yml ├── util.h ├── doc ├── OPENCAGE.md ├── DESIGN.md ├── FENCES.md └── STORE.md ├── storage.h ├── udata.h ├── mkpath.c ├── base64.c ├── geohash.h ├── fences.c ├── config.mk.in ├── API.md ├── Makefile ├── json.h └── listsort.c /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /docroot/static/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /docroot/last/.gitignore: -------------------------------------------------------------------------------- 1 | config.js 2 | -------------------------------------------------------------------------------- /docroot/static/leaflet/version-1.3.4: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docroot/test.txt: -------------------------------------------------------------------------------- 1 | Hello OwnTracks! 2 | -------------------------------------------------------------------------------- /version.h: -------------------------------------------------------------------------------- 1 | #define VERSION "0.8.8" 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | liberapay: owntracks.org 2 | -------------------------------------------------------------------------------- /docroot/views/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | plain.txt 3 | -------------------------------------------------------------------------------- /contrib/faces/.gitignore: -------------------------------------------------------------------------------- 1 | *.jpg 2 | *.png 3 | *.json 4 | -------------------------------------------------------------------------------- /debian/ot-recorder.docs: -------------------------------------------------------------------------------- 1 | LICENSE 2 | Changelog 3 | README.md 4 | -------------------------------------------------------------------------------- /debian/ot-recorder.dirs: -------------------------------------------------------------------------------- 1 | /var/spool/owntracks 2 | /var/spool/owntracks/recorder/store 3 | -------------------------------------------------------------------------------- /assets/geohash-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/geohash-2.png -------------------------------------------------------------------------------- /assets/geohash-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/geohash-5.png -------------------------------------------------------------------------------- /assets/geohash-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/geohash-7.png -------------------------------------------------------------------------------- /assets/view-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/view-map.png -------------------------------------------------------------------------------- /docroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/favicon.ico -------------------------------------------------------------------------------- /assets/demo-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/demo-table.png -------------------------------------------------------------------------------- /assets/ot-recorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/ot-recorder.png -------------------------------------------------------------------------------- /assets/view-track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/view-track.png -------------------------------------------------------------------------------- /assets/demo-live-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/demo-live-map.png -------------------------------------------------------------------------------- /debian/ot-recorder.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | adduser --system --home /var/spool/owntracks owntracks 4 | -------------------------------------------------------------------------------- /docroot/static/recorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/static/recorder.png -------------------------------------------------------------------------------- /docroot/table/flags/AD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AD.png -------------------------------------------------------------------------------- /docroot/table/flags/AE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AE.png -------------------------------------------------------------------------------- /docroot/table/flags/AF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AF.png -------------------------------------------------------------------------------- /docroot/table/flags/AG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AG.png -------------------------------------------------------------------------------- /docroot/table/flags/AI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AI.png -------------------------------------------------------------------------------- /docroot/table/flags/AL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AL.png -------------------------------------------------------------------------------- /docroot/table/flags/AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AM.png -------------------------------------------------------------------------------- /docroot/table/flags/AN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AN.png -------------------------------------------------------------------------------- /docroot/table/flags/AO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AO.png -------------------------------------------------------------------------------- /docroot/table/flags/AQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AQ.png -------------------------------------------------------------------------------- /docroot/table/flags/AR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AR.png -------------------------------------------------------------------------------- /docroot/table/flags/AS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AS.png -------------------------------------------------------------------------------- /docroot/table/flags/AT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AT.png -------------------------------------------------------------------------------- /docroot/table/flags/AU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AU.png -------------------------------------------------------------------------------- /docroot/table/flags/AW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AW.png -------------------------------------------------------------------------------- /docroot/table/flags/AX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AX.png -------------------------------------------------------------------------------- /docroot/table/flags/AZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/AZ.png -------------------------------------------------------------------------------- /docroot/table/flags/BA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BA.png -------------------------------------------------------------------------------- /docroot/table/flags/BB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BB.png -------------------------------------------------------------------------------- /docroot/table/flags/BD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BD.png -------------------------------------------------------------------------------- /docroot/table/flags/BE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BE.png -------------------------------------------------------------------------------- /docroot/table/flags/BF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BF.png -------------------------------------------------------------------------------- /docroot/table/flags/BG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BG.png -------------------------------------------------------------------------------- /docroot/table/flags/BH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BH.png -------------------------------------------------------------------------------- /docroot/table/flags/BI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BI.png -------------------------------------------------------------------------------- /docroot/table/flags/BJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BJ.png -------------------------------------------------------------------------------- /docroot/table/flags/BL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BL.png -------------------------------------------------------------------------------- /docroot/table/flags/BM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BM.png -------------------------------------------------------------------------------- /docroot/table/flags/BN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BN.png -------------------------------------------------------------------------------- /docroot/table/flags/BO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BO.png -------------------------------------------------------------------------------- /docroot/table/flags/BR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BR.png -------------------------------------------------------------------------------- /docroot/table/flags/BS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BS.png -------------------------------------------------------------------------------- /docroot/table/flags/BT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BT.png -------------------------------------------------------------------------------- /docroot/table/flags/BW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BW.png -------------------------------------------------------------------------------- /docroot/table/flags/BY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BY.png -------------------------------------------------------------------------------- /docroot/table/flags/BZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/BZ.png -------------------------------------------------------------------------------- /docroot/table/flags/CA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CA.png -------------------------------------------------------------------------------- /docroot/table/flags/CC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CC.png -------------------------------------------------------------------------------- /docroot/table/flags/CD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CD.png -------------------------------------------------------------------------------- /docroot/table/flags/CF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CF.png -------------------------------------------------------------------------------- /docroot/table/flags/CG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CG.png -------------------------------------------------------------------------------- /docroot/table/flags/CH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CH.png -------------------------------------------------------------------------------- /docroot/table/flags/CI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CI.png -------------------------------------------------------------------------------- /docroot/table/flags/CK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CK.png -------------------------------------------------------------------------------- /docroot/table/flags/CL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CL.png -------------------------------------------------------------------------------- /docroot/table/flags/CM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CM.png -------------------------------------------------------------------------------- /docroot/table/flags/CN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CN.png -------------------------------------------------------------------------------- /docroot/table/flags/CO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CO.png -------------------------------------------------------------------------------- /docroot/table/flags/CR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CR.png -------------------------------------------------------------------------------- /docroot/table/flags/CU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CU.png -------------------------------------------------------------------------------- /docroot/table/flags/CV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CV.png -------------------------------------------------------------------------------- /docroot/table/flags/CW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CW.png -------------------------------------------------------------------------------- /docroot/table/flags/CX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CX.png -------------------------------------------------------------------------------- /docroot/table/flags/CY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CY.png -------------------------------------------------------------------------------- /docroot/table/flags/CZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/CZ.png -------------------------------------------------------------------------------- /docroot/table/flags/DE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/DE.png -------------------------------------------------------------------------------- /docroot/table/flags/DJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/DJ.png -------------------------------------------------------------------------------- /docroot/table/flags/DK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/DK.png -------------------------------------------------------------------------------- /docroot/table/flags/DM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/DM.png -------------------------------------------------------------------------------- /docroot/table/flags/DO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/DO.png -------------------------------------------------------------------------------- /docroot/table/flags/DZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/DZ.png -------------------------------------------------------------------------------- /docroot/table/flags/EC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/EC.png -------------------------------------------------------------------------------- /docroot/table/flags/EE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/EE.png -------------------------------------------------------------------------------- /docroot/table/flags/EG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/EG.png -------------------------------------------------------------------------------- /docroot/table/flags/EH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/EH.png -------------------------------------------------------------------------------- /docroot/table/flags/ER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ER.png -------------------------------------------------------------------------------- /docroot/table/flags/ES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ES.png -------------------------------------------------------------------------------- /docroot/table/flags/ET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ET.png -------------------------------------------------------------------------------- /docroot/table/flags/EU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/EU.png -------------------------------------------------------------------------------- /docroot/table/flags/FI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/FI.png -------------------------------------------------------------------------------- /docroot/table/flags/FJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/FJ.png -------------------------------------------------------------------------------- /docroot/table/flags/FK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/FK.png -------------------------------------------------------------------------------- /docroot/table/flags/FM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/FM.png -------------------------------------------------------------------------------- /docroot/table/flags/FO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/FO.png -------------------------------------------------------------------------------- /docroot/table/flags/FR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/FR.png -------------------------------------------------------------------------------- /docroot/table/flags/GA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GA.png -------------------------------------------------------------------------------- /docroot/table/flags/GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GB.png -------------------------------------------------------------------------------- /docroot/table/flags/GD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GD.png -------------------------------------------------------------------------------- /docroot/table/flags/GE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GE.png -------------------------------------------------------------------------------- /docroot/table/flags/GG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GG.png -------------------------------------------------------------------------------- /docroot/table/flags/GH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GH.png -------------------------------------------------------------------------------- /docroot/table/flags/GI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GI.png -------------------------------------------------------------------------------- /docroot/table/flags/GL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GL.png -------------------------------------------------------------------------------- /docroot/table/flags/GM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GM.png -------------------------------------------------------------------------------- /docroot/table/flags/GN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GN.png -------------------------------------------------------------------------------- /docroot/table/flags/GQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GQ.png -------------------------------------------------------------------------------- /docroot/table/flags/GR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GR.png -------------------------------------------------------------------------------- /docroot/table/flags/GS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GS.png -------------------------------------------------------------------------------- /docroot/table/flags/GT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GT.png -------------------------------------------------------------------------------- /docroot/table/flags/GU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GU.png -------------------------------------------------------------------------------- /docroot/table/flags/GW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GW.png -------------------------------------------------------------------------------- /docroot/table/flags/GY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/GY.png -------------------------------------------------------------------------------- /docroot/table/flags/HK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/HK.png -------------------------------------------------------------------------------- /docroot/table/flags/HN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/HN.png -------------------------------------------------------------------------------- /docroot/table/flags/HR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/HR.png -------------------------------------------------------------------------------- /docroot/table/flags/HT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/HT.png -------------------------------------------------------------------------------- /docroot/table/flags/HU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/HU.png -------------------------------------------------------------------------------- /docroot/table/flags/IC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/IC.png -------------------------------------------------------------------------------- /docroot/table/flags/ID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ID.png -------------------------------------------------------------------------------- /docroot/table/flags/IE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/IE.png -------------------------------------------------------------------------------- /docroot/table/flags/IL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/IL.png -------------------------------------------------------------------------------- /docroot/table/flags/IM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/IM.png -------------------------------------------------------------------------------- /docroot/table/flags/IN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/IN.png -------------------------------------------------------------------------------- /docroot/table/flags/IQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/IQ.png -------------------------------------------------------------------------------- /docroot/table/flags/IR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/IR.png -------------------------------------------------------------------------------- /docroot/table/flags/IS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/IS.png -------------------------------------------------------------------------------- /docroot/table/flags/IT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/IT.png -------------------------------------------------------------------------------- /docroot/table/flags/JE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/JE.png -------------------------------------------------------------------------------- /docroot/table/flags/JM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/JM.png -------------------------------------------------------------------------------- /docroot/table/flags/JO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/JO.png -------------------------------------------------------------------------------- /docroot/table/flags/JP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/JP.png -------------------------------------------------------------------------------- /docroot/table/flags/KE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KE.png -------------------------------------------------------------------------------- /docroot/table/flags/KG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KG.png -------------------------------------------------------------------------------- /docroot/table/flags/KH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KH.png -------------------------------------------------------------------------------- /docroot/table/flags/KI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KI.png -------------------------------------------------------------------------------- /docroot/table/flags/KM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KM.png -------------------------------------------------------------------------------- /docroot/table/flags/KN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KN.png -------------------------------------------------------------------------------- /docroot/table/flags/KP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KP.png -------------------------------------------------------------------------------- /docroot/table/flags/KR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KR.png -------------------------------------------------------------------------------- /docroot/table/flags/KW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KW.png -------------------------------------------------------------------------------- /docroot/table/flags/KY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KY.png -------------------------------------------------------------------------------- /docroot/table/flags/KZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/KZ.png -------------------------------------------------------------------------------- /docroot/table/flags/LA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LA.png -------------------------------------------------------------------------------- /docroot/table/flags/LB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LB.png -------------------------------------------------------------------------------- /docroot/table/flags/LC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LC.png -------------------------------------------------------------------------------- /docroot/table/flags/LI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LI.png -------------------------------------------------------------------------------- /docroot/table/flags/LK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LK.png -------------------------------------------------------------------------------- /docroot/table/flags/LR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LR.png -------------------------------------------------------------------------------- /docroot/table/flags/LS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LS.png -------------------------------------------------------------------------------- /docroot/table/flags/LT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LT.png -------------------------------------------------------------------------------- /docroot/table/flags/LU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LU.png -------------------------------------------------------------------------------- /docroot/table/flags/LV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LV.png -------------------------------------------------------------------------------- /docroot/table/flags/LY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/LY.png -------------------------------------------------------------------------------- /docroot/table/flags/MA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MA.png -------------------------------------------------------------------------------- /docroot/table/flags/MC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MC.png -------------------------------------------------------------------------------- /docroot/table/flags/MD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MD.png -------------------------------------------------------------------------------- /docroot/table/flags/ME.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ME.png -------------------------------------------------------------------------------- /docroot/table/flags/MF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MF.png -------------------------------------------------------------------------------- /docroot/table/flags/MG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MG.png -------------------------------------------------------------------------------- /docroot/table/flags/MH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MH.png -------------------------------------------------------------------------------- /docroot/table/flags/MK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MK.png -------------------------------------------------------------------------------- /docroot/table/flags/ML.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ML.png -------------------------------------------------------------------------------- /docroot/table/flags/MM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MM.png -------------------------------------------------------------------------------- /docroot/table/flags/MN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MN.png -------------------------------------------------------------------------------- /docroot/table/flags/MO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MO.png -------------------------------------------------------------------------------- /docroot/table/flags/MP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MP.png -------------------------------------------------------------------------------- /docroot/table/flags/MQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MQ.png -------------------------------------------------------------------------------- /docroot/table/flags/MR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MR.png -------------------------------------------------------------------------------- /docroot/table/flags/MS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MS.png -------------------------------------------------------------------------------- /docroot/table/flags/MT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MT.png -------------------------------------------------------------------------------- /docroot/table/flags/MU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MU.png -------------------------------------------------------------------------------- /docroot/table/flags/MV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MV.png -------------------------------------------------------------------------------- /docroot/table/flags/MW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MW.png -------------------------------------------------------------------------------- /docroot/table/flags/MX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MX.png -------------------------------------------------------------------------------- /docroot/table/flags/MY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MY.png -------------------------------------------------------------------------------- /docroot/table/flags/MZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/MZ.png -------------------------------------------------------------------------------- /docroot/table/flags/NA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NA.png -------------------------------------------------------------------------------- /docroot/table/flags/NC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NC.png -------------------------------------------------------------------------------- /docroot/table/flags/NE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NE.png -------------------------------------------------------------------------------- /docroot/table/flags/NF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NF.png -------------------------------------------------------------------------------- /docroot/table/flags/NG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NG.png -------------------------------------------------------------------------------- /docroot/table/flags/NI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NI.png -------------------------------------------------------------------------------- /docroot/table/flags/NL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NL.png -------------------------------------------------------------------------------- /docroot/table/flags/NO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NO.png -------------------------------------------------------------------------------- /docroot/table/flags/NP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NP.png -------------------------------------------------------------------------------- /docroot/table/flags/NR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NR.png -------------------------------------------------------------------------------- /docroot/table/flags/NU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NU.png -------------------------------------------------------------------------------- /docroot/table/flags/NZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/NZ.png -------------------------------------------------------------------------------- /docroot/table/flags/OM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/OM.png -------------------------------------------------------------------------------- /docroot/table/flags/PA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PA.png -------------------------------------------------------------------------------- /docroot/table/flags/PE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PE.png -------------------------------------------------------------------------------- /docroot/table/flags/PF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PF.png -------------------------------------------------------------------------------- /docroot/table/flags/PG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PG.png -------------------------------------------------------------------------------- /docroot/table/flags/PH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PH.png -------------------------------------------------------------------------------- /docroot/table/flags/PK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PK.png -------------------------------------------------------------------------------- /docroot/table/flags/PL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PL.png -------------------------------------------------------------------------------- /docroot/table/flags/PN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PN.png -------------------------------------------------------------------------------- /docroot/table/flags/PR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PR.png -------------------------------------------------------------------------------- /docroot/table/flags/PS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PS.png -------------------------------------------------------------------------------- /docroot/table/flags/PT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PT.png -------------------------------------------------------------------------------- /docroot/table/flags/PW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PW.png -------------------------------------------------------------------------------- /docroot/table/flags/PY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/PY.png -------------------------------------------------------------------------------- /docroot/table/flags/QA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/QA.png -------------------------------------------------------------------------------- /docroot/table/flags/RO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/RO.png -------------------------------------------------------------------------------- /docroot/table/flags/RS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/RS.png -------------------------------------------------------------------------------- /docroot/table/flags/RU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/RU.png -------------------------------------------------------------------------------- /docroot/table/flags/RW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/RW.png -------------------------------------------------------------------------------- /docroot/table/flags/SA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SA.png -------------------------------------------------------------------------------- /docroot/table/flags/SB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SB.png -------------------------------------------------------------------------------- /docroot/table/flags/SC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SC.png -------------------------------------------------------------------------------- /docroot/table/flags/SD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SD.png -------------------------------------------------------------------------------- /docroot/table/flags/SE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SE.png -------------------------------------------------------------------------------- /docroot/table/flags/SG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SG.png -------------------------------------------------------------------------------- /docroot/table/flags/SH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SH.png -------------------------------------------------------------------------------- /docroot/table/flags/SI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SI.png -------------------------------------------------------------------------------- /docroot/table/flags/SK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SK.png -------------------------------------------------------------------------------- /docroot/table/flags/SL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SL.png -------------------------------------------------------------------------------- /docroot/table/flags/SM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SM.png -------------------------------------------------------------------------------- /docroot/table/flags/SN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SN.png -------------------------------------------------------------------------------- /docroot/table/flags/SO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SO.png -------------------------------------------------------------------------------- /docroot/table/flags/SR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SR.png -------------------------------------------------------------------------------- /docroot/table/flags/SS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SS.png -------------------------------------------------------------------------------- /docroot/table/flags/ST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ST.png -------------------------------------------------------------------------------- /docroot/table/flags/SV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SV.png -------------------------------------------------------------------------------- /docroot/table/flags/SY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SY.png -------------------------------------------------------------------------------- /docroot/table/flags/SZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/SZ.png -------------------------------------------------------------------------------- /docroot/table/flags/TC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TC.png -------------------------------------------------------------------------------- /docroot/table/flags/TD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TD.png -------------------------------------------------------------------------------- /docroot/table/flags/TF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TF.png -------------------------------------------------------------------------------- /docroot/table/flags/TG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TG.png -------------------------------------------------------------------------------- /docroot/table/flags/TH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TH.png -------------------------------------------------------------------------------- /docroot/table/flags/TJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TJ.png -------------------------------------------------------------------------------- /docroot/table/flags/TK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TK.png -------------------------------------------------------------------------------- /docroot/table/flags/TL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TL.png -------------------------------------------------------------------------------- /docroot/table/flags/TM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TM.png -------------------------------------------------------------------------------- /docroot/table/flags/TN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TN.png -------------------------------------------------------------------------------- /docroot/table/flags/TO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TO.png -------------------------------------------------------------------------------- /docroot/table/flags/TR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TR.png -------------------------------------------------------------------------------- /docroot/table/flags/TT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TT.png -------------------------------------------------------------------------------- /docroot/table/flags/TV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TV.png -------------------------------------------------------------------------------- /docroot/table/flags/TW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TW.png -------------------------------------------------------------------------------- /docroot/table/flags/TZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/TZ.png -------------------------------------------------------------------------------- /docroot/table/flags/UA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/UA.png -------------------------------------------------------------------------------- /docroot/table/flags/UG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/UG.png -------------------------------------------------------------------------------- /docroot/table/flags/US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/US.png -------------------------------------------------------------------------------- /docroot/table/flags/UY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/UY.png -------------------------------------------------------------------------------- /docroot/table/flags/UZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/UZ.png -------------------------------------------------------------------------------- /docroot/table/flags/VA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/VA.png -------------------------------------------------------------------------------- /docroot/table/flags/VC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/VC.png -------------------------------------------------------------------------------- /docroot/table/flags/VE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/VE.png -------------------------------------------------------------------------------- /docroot/table/flags/VG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/VG.png -------------------------------------------------------------------------------- /docroot/table/flags/VI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/VI.png -------------------------------------------------------------------------------- /docroot/table/flags/VN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/VN.png -------------------------------------------------------------------------------- /docroot/table/flags/VU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/VU.png -------------------------------------------------------------------------------- /docroot/table/flags/WF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/WF.png -------------------------------------------------------------------------------- /docroot/table/flags/WS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/WS.png -------------------------------------------------------------------------------- /docroot/table/flags/YE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/YE.png -------------------------------------------------------------------------------- /docroot/table/flags/YT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/YT.png -------------------------------------------------------------------------------- /docroot/table/flags/ZA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ZA.png -------------------------------------------------------------------------------- /docroot/table/flags/ZM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ZM.png -------------------------------------------------------------------------------- /docroot/table/flags/ZW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/ZW.png -------------------------------------------------------------------------------- /docroot/table/flags/__.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/flags/__.png -------------------------------------------------------------------------------- /assets/demo-geojson-points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/demo-geojson-points.png -------------------------------------------------------------------------------- /assets/recorder-logo-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/recorder-logo-192.png -------------------------------------------------------------------------------- /assets/demo-geojson-linestring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/assets/demo-geojson-linestring.png -------------------------------------------------------------------------------- /docroot/last/recorder-logo-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/last/recorder-logo-192.png -------------------------------------------------------------------------------- /docroot/table/images/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/images/sort_asc.png -------------------------------------------------------------------------------- /docroot/table/images/sort_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/images/sort_both.png -------------------------------------------------------------------------------- /docroot/table/images/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/table/images/sort_desc.png -------------------------------------------------------------------------------- /docroot/static/leaflet/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/static/leaflet/images/layers.png -------------------------------------------------------------------------------- /docroot/static/leaflet/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/static/leaflet/images/layers-2x.png -------------------------------------------------------------------------------- /docroot/static/leaflet/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/static/leaflet/images/marker-icon.png -------------------------------------------------------------------------------- /docroot/static/leaflet/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/static/leaflet/images/marker-shadow.png -------------------------------------------------------------------------------- /docroot/static/leaflet/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/recorder/master/docroot/static/leaflet/images/marker-icon-2x.png -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | ot-recorder (0.6.2) unstable; urgency=medium 2 | 3 | * Initial release. 4 | 5 | -- Thomas Weißschuh Wed, 30 Mar 2016 16:46:45 +0000 6 | -------------------------------------------------------------------------------- /etc/rhel/README: -------------------------------------------------------------------------------- 1 | $ cat ~/.rpmmacros 2 | %_topdir %(echo $HOME)/rpmbuild 3 | 4 | $ mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} 5 | 6 | $ rpmbuild -bb ot-recorder.spec 7 | -------------------------------------------------------------------------------- /listsort.h: -------------------------------------------------------------------------------- 1 | #ifndef _LISTSORT_INCL_ 2 | # define _LISTSORT_INCL_ 3 | 4 | #include "json.h" 5 | 6 | JsonNode *listsort(JsonNode *list, int is_circular, int is_double); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /geo.h: -------------------------------------------------------------------------------- 1 | #include "json.h" 2 | #include "udata.h" 3 | 4 | JsonNode *revgeo(struct udata *ud, double lat, double lon, UT_string *addr, UT_string *cc); 5 | void revgeo_init(); 6 | void revgeo_free(); 7 | -------------------------------------------------------------------------------- /debian/ot-recorder.tmpfile: -------------------------------------------------------------------------------- 1 | d /var/spool/owntracks - owntracks owntracks 2 | d /var/spool/owntracks/recorder - owntracks owntracks 3 | d /var/spool/owntracks/recorder/store - owntracks owntracks 4 | -------------------------------------------------------------------------------- /docroot/last/config.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Optionally rename the topic in the location popup on the map 4 | */ 5 | 6 | var renames = { 7 | 'jjolie/phone' : 'Jane Jolie', 8 | 'john/android' : "John Smith", 9 | 10 | }; 11 | -------------------------------------------------------------------------------- /docroot/last/config.js.sample: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Optionally rename the topic in the location popup on the map 4 | */ 5 | 6 | var renames = { 7 | 'jjolie/phone' : 'Jane Jolie', 8 | 'john/android' : "John Smith", 9 | }; 10 | -------------------------------------------------------------------------------- /base64.h: -------------------------------------------------------------------------------- 1 | #ifndef BASE64_H_GUARD 2 | #define BASE64_H_GUARD 3 | 4 | #include 5 | 6 | char* base64_encode(const void* buf, size_t size); 7 | void* base64_decode(const char* s, size_t *data_len); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DPKG_EXPORT_BUILDFLAGS=1 4 | include /usr/share/dpkg/default.mk 5 | 6 | %: 7 | dh $@ 8 | 9 | override_dh_auto_clean: 10 | cp etc/debian/config.mk.in config.mk 11 | dh_auto_clean 12 | -------------------------------------------------------------------------------- /debian/ot-recorder.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=OwnTracks Recorder 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | User=owntracks 8 | ExecStart=/usr/sbin/ot-recorder 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /recorder.h: -------------------------------------------------------------------------------- 1 | #ifndef _RECORDER_H_INCL_ 2 | # define _RECORDER_H_INCL_ 3 | 4 | void handle_message(void *userdata, char *topic, char *payload, size_t payloadlen, int retain, int httpmode, int was_encrypted); 5 | 6 | #endif /* _RECORDER_H_INCL_ */ 7 | -------------------------------------------------------------------------------- /docroot/views/jane.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "page" : "xmap.html", 3 | "user" : "jjolie", 4 | "device" : "phone", 5 | "from" : "2015-04-26", 6 | "to" : "2015-09-19", 7 | 8 | "label" : "Jane Jolie", 9 | "zoom" : 7, 10 | 11 | "auth" : "08d7ae5f231e86893fcda52546e150d6" 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | ot-recorder 3 | ocat 4 | *.dSYM 5 | .DS_Store 6 | store/ 7 | jpm-test/ 8 | config.mk 9 | obsolete/ 10 | codesign.sh 11 | sundry/ 12 | kick*.sh 13 | /debian/*\.debhelper 14 | /debian/*\.debhelper.log 15 | /debian/debhelper.log 16 | /debian/*\.substvars 17 | /debian/files 18 | -------------------------------------------------------------------------------- /docroot/last/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Recorder map", 3 | "icons" : [ 4 | { 5 | "src" : "recorder-logo-192.png", 6 | "sizes" : "192x192", 7 | "type" : "image/png", 8 | "density" : "4.0" 9 | } 10 | ], 11 | "start_url" : "index.html", 12 | "display" : "standalone", 13 | "orientation" : "portrait" 14 | } 15 | -------------------------------------------------------------------------------- /docroot/table/config.js: -------------------------------------------------------------------------------- 1 | config = { 2 | 3 | /* Titles of columns in the table. May be "". */ 4 | column_titles: { 5 | face: "", 6 | topic: "Topic", 7 | fulldate: "Date/Time", 8 | name: "Name", 9 | user: "User", 10 | batt: "Battery", 11 | tid: "TID", 12 | cc: "CC", 13 | addr: "Address", 14 | vel: "Vel", 15 | cog: "CoG" 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /contrib/freebsd/readme.txt: -------------------------------------------------------------------------------- 1 | - must use gmake 2 | - place script in /usr/local/etc/rc.d/ot-recorder 3 | - creates pid file out of $! to make restart and stop possible 4 | - note regarding line: # REQUIRE: LOGIN mosquitto hass 5 | remove mosquitto and hass if not setup on this box, but let LOGIN there 6 | if mosquitto runs on same box REQUIRE is a must! hass is Home Assistant 7 | -------------------------------------------------------------------------------- /etc/ot-recorder.service: -------------------------------------------------------------------------------- 1 | # /etc/systemd/system/ot-recorder.service 2 | 3 | [Unit] 4 | Description=OwnTracks Recorder 5 | Wants=network-online.target 6 | After=network-online.target 7 | 8 | [Service] 9 | Type=simple 10 | User=owntracks 11 | WorkingDirectory=/ 12 | ExecStartPre=/bin/sleep 15 13 | ExecStart=/usr/sbin/ot-recorder 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /etc/rhel/ot-recorder.defaults: -------------------------------------------------------------------------------- 1 | # Options for OwnTracks Recorder 2 | 3 | export OTR_HOST="localhost" 4 | export OTR_PORT="1883" 5 | # export OTR_USER="username" 6 | # export OTR_PASS="password" 7 | # export OTR_CAFILE="/path/to/tls/CA.crt" 8 | # export OTR_STORAGEDIR="/var/spool/owntracks/recorder/store" 9 | export OTR_TOPICS='owntracks/#' 10 | 11 | export OTR_OPTS="--http-host 127.0.0.1 --http-port 8083" 12 | -------------------------------------------------------------------------------- /etc/demo-iphone.readme: -------------------------------------------------------------------------------- 1 | This is demo data. After installing the recorder with 2 | `make install', create a directory within your storage 3 | called `rec', within that one named `demo' and again 4 | therein `iphone`, and then copy `demo-iphone.rec' into 5 | that so the result will be 6 | 7 | /rec/demo/iphone/demo-iphone.rec 8 | 9 | You can then use this data for demonstration purposes, 10 | taking into account that it's dated 2015-08-24. 11 | -------------------------------------------------------------------------------- /http.h: -------------------------------------------------------------------------------- 1 | #ifndef _HTTP_H_INCLUDED_ 2 | # define _HTTP_H_INCLUDED_ 3 | 4 | #include 5 | #include "mongoose.h" 6 | #include "storage.h" 7 | #include "json.h" 8 | 9 | #define API_PREFIX "/api/0/" 10 | #define MONITOR_URI "/api/0/monitor" 11 | 12 | typedef enum { 13 | PAGE = 0, 14 | GEODATA = 1, 15 | LASTPOS = 2, 16 | } viewtype; 17 | 18 | int ev_handler(struct mg_connection *conn, enum mg_event ev); 19 | void http_ws_push_json(struct mg_server *server, JsonNode *obj); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /misc.h: -------------------------------------------------------------------------------- 1 | #ifndef MISC_H_INCLUDED 2 | # define MISC_H_INCLUDED 3 | 4 | #include 5 | #include "udata.h" 6 | 7 | extern char STORAGEDIR[]; 8 | 9 | #ifndef TRUE 10 | # define TRUE (1) 11 | # define FALSE (0) 12 | #endif 13 | 14 | int mkpath(char *path); 15 | char *bindump(char *buf, long buflen); 16 | 17 | // void monitor_update(struct udata *ud, time_t now, char *topic); 18 | void monitorhook(struct udata *userdata, time_t now, char *topic); 19 | char *monitor_get(); 20 | void get_defaults(char *filename, struct udata *userdata); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: ot-recorder 2 | Priority: optional 3 | Section: net 4 | Maintainer: JP Mens 5 | Build-Depends: debhelper (>= 9), libcurl3-dev, libmosquitto-dev, liblua5.2-dev, libconfig8-dev, libsodium-dev, liblmdb-dev 6 | Standards-Version: 3.9.6 7 | Vcs-Git: https://github.com/owntracks/recorder 8 | Vcs-Browser: https://github.com/owntracks/recorder 9 | Homepage: https://github.com/owntracks/recorder 10 | 11 | Package: ot-recorder 12 | Architecture: any 13 | Description: Lightweight back-end for consuming OwnTracks data from an MQTT Broker 14 | Depends: ${shlibs:Depends}, ${misc:Depends} 15 | -------------------------------------------------------------------------------- /fences.h: -------------------------------------------------------------------------------- 1 | #ifndef FENCES_H_INCLUDED 2 | # define FENCES_H_INCLUDED 3 | 4 | #include 5 | 6 | typedef struct { 7 | double lat; 8 | double lon; 9 | long rad; 10 | char *desc; 11 | bool io; /* true: is IN region. false: is OUT of region */ 12 | char *user; 13 | char *device; 14 | enum { ENTER, LEAVE } event; 15 | struct udata *ud; 16 | char *topic; /* original publish topic */ 17 | JsonNode *json; /* full JSON payload of publish */ 18 | } wpoint; 19 | 20 | void check_fences(struct udata *ud, char *username, char *device, double lat, double lon, JsonNode *json, char *topic); 21 | 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /docroot/table/otable.css: -------------------------------------------------------------------------------- 1 | 2 | .img-circle { 3 | border-radius: 50%; 4 | border: 1px solid #CACACA; 5 | } 6 | 7 | tr { font-size: 90%; } 8 | thead { text-align: left; } 9 | td.fulldate { font-size: 80%; width: 10em; } 10 | td.cc { width: 30px; } 11 | td.topic, td.tid { font-size: 80%; font-family: courier; } 12 | td.addr { font-size: 90%; } 13 | td.user { font-size: 80%; } 14 | td.batt { font-size: 90%; } 15 | td.vel { font-size: 90%; } 16 | td.cog { font-size: 70%; } 17 | 18 | #togglers ul { 19 | list-style-type: none; 20 | margin-left: 0; 21 | padding-left: 0; 22 | } 23 | #togglers li { float: left; padding-right: 4px; } 24 | 25 | .warning { 26 | color: red; 27 | } 28 | -------------------------------------------------------------------------------- /etc/raspbian/config.mk.in: -------------------------------------------------------------------------------- 1 | #(@)config.mk for Debian 8 (x86_64) 2 | 3 | CC = arm-linux-gnueabihf-gcc 4 | INSTALLDIR = /usr/ 5 | CONFIGFILE = /etc/default/ot-recorder 6 | FREEBSD ?= no 7 | 8 | WITH_MQTT ?= yes 9 | WITH_HTTP ?= yes 10 | WITH_LUA ?= yes 11 | WITH_PING ?= yes 12 | WITH_KILL ?= yes 13 | WITH_ENCRYPT ?= yes 14 | WITH_GREENWICH ?= yes 15 | 16 | STORAGEDEFAULT = /var/spool/owntracks/recorder/store 17 | DOCROOT = /usr/share/owntracks/recorder/htdocs 18 | GHASHPREC = 7 19 | JSON_INDENT ?= no 20 | MOSQUITTO_INC = -I/usr/include 21 | MOSQUITTO_LIB = -L/usr/lib 22 | MORELIBS = 23 | 24 | LUA_CFLAGS = -I/usr/include/lua5.2 -llua5.2 25 | LUA_LIBS = -L /usr/lib/arm-linux-gnueabihf/ 26 | SODIUM_CFLAGS = 27 | SODIUM_LIBS = -lsodium 28 | 29 | GEOCODE_TIMEOUT = 4000 30 | -------------------------------------------------------------------------------- /etc/debian/config.mk.in: -------------------------------------------------------------------------------- 1 | #(@)config.mk for Debian 8 (x86_64) 2 | 3 | INSTALLDIR = /usr/ 4 | CONFIGFILE = /etc/default/ot-recorder 5 | FREEBSD ?= no 6 | 7 | WITH_MQTT ?= yes 8 | WITH_HTTP ?= yes 9 | WITH_LUA ?= yes 10 | WITH_PING ?= yes 11 | WITH_KILL ?= yes 12 | WITH_ENCRYPT ?= yes 13 | WITH_GREENWICH ?= yes 14 | 15 | STORAGEDEFAULT = /var/spool/owntracks/recorder/store 16 | DOCROOT = /usr/share/owntracks/recorder/htdocs 17 | GHASHPREC = 7 18 | JSON_INDENT ?= no 19 | MOSQUITTO_INC = -I/usr/include 20 | MOSQUITTO_LIB = -L/usr/lib 21 | MORELIBS = # -lssl 22 | LUA_CFLAGS = `pkg-config --cflags lua5.2` 23 | LUA_LIBS = `pkg-config --libs lua5.2` 24 | SODIUM_CFLAGS = `pkg-config --cflags libsodium` 25 | SODIUM_LIBS = `pkg-config --libs libsodium` 26 | 27 | GEOCODE_TIMEOUT = 4000 28 | -------------------------------------------------------------------------------- /docroot/owntracks.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
,
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /contrib/lua/http-to-mqtt-repub.lua: -------------------------------------------------------------------------------- 1 | -- requires: 2 | -- a) Compile recorder with -DLUA and configure 3 | -- b) OTR_LUASCRIPT = ".../repub.lua" 4 | -- also requires Lua JSON from http://regex.info/blog/lua/json 5 | 6 | JSON = (loadfile "/etc/ot-recorder/JSON.lua")() 7 | 8 | function otr_init() 9 | end 10 | 11 | function otr_exit() 12 | end 13 | 14 | function otr_hook(topic, _type, data) 15 | otr.log("DEBUG_PUB:" .. topic .. " " .. JSON:encode(data)) 16 | if(data['_http'] == true) then 17 | if(data['_repub'] == true) then 18 | return 19 | end 20 | data['_repub'] = true 21 | local payload = JSON:encode(data) 22 | otr.publish(topic, payload, 1, 1) 23 | end 24 | end 25 | 26 | function otr_putrec(u, d, s) 27 | j = JSON:decode(s) 28 | if (j['_repub'] == true) then 29 | return 1 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /hooks.h: -------------------------------------------------------------------------------- 1 | #ifndef HOOKS_H_INCLUDED 2 | # define HOOKS_H_INCLUDED 3 | 4 | #ifdef WITH_LUA 5 | # include 6 | 7 | 8 | struct luadata { 9 | char *script; /* Path to Lua script in --lua-script */ 10 | lua_State *L; /* The Lua machine */ 11 | }; 12 | 13 | struct luadata *hooks_init(struct udata *ud, char *luascript); 14 | void hooks_exit(struct luadata *, char *reason); 15 | void hooks_hook(struct udata *ud, char *topic, JsonNode *obj); 16 | int hooks_norec(struct udata *ud, char *user, char *device, char *payload); 17 | JsonNode *hooks_http(struct udata *ud, char *user, char *device, char *payload); 18 | void hooks_transition(struct udata *ud, char *user, char *device, int event, char *desc, double wplat, double wplon, double lat, double lon, char *topic, JsonNode *json, long meters); 19 | JsonNode *hook_revgeo(struct udata *ud, char *luafunc, char *topic, char *user, char *device, double lat, double lon); 20 | 21 | #endif /* WITH_LUA */ 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /contrib/freebsd/otrecorder.rc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # $FreeBSD$ 3 | # 4 | # PROVIDE: otrecorder 5 | # REQUIRE: LOGIN mosquitto hass 6 | # KEYWORD: shutdown 7 | # 8 | # 9 | # Add the following lines to /etc/rc.conf to configure ot-recorder: 10 | # otrecorder_enable (bool): Set it to "YES" to enable ot-recorder. 11 | # Default is "NO". 12 | # otrecorder_args (str): Arguments passed to ot-recorder on startup. 13 | # Default is "". 14 | 15 | . /etc/rc.subr 16 | 17 | name=otrecorder 18 | rcvar=${name}_enable 19 | pidfile="/var/run/otrecorder/${name}.pid" 20 | otrecorder_command="stdbuf -o0 %%PREFIX%%/sbin/ot-recorder" 21 | command="/usr/sbin/daemon" 22 | 23 | load_rc_config $name 24 | : ${otrecorder_enable:=NO} 25 | : ${otrecorder_args:=""} 26 | : ${otrecorder_user:="ot-recorder"} 27 | 28 | command_args="-P ${pidfile} -r -f -o /var/log/ot-recorder/ot-recorder.log ${otrecorder_command} ${otrecorder_args}" 29 | 30 | run_rc_command "$1" 31 | -------------------------------------------------------------------------------- /contrib/opencage.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/nmdguerreiro/lua-opencage-geocoder 2 | -- luarocks install lua-opencage-geocoder 3 | local geocoder = require "opencage.geocoder" 4 | local gc = geocoder.new({ 5 | key = os.getenv("OPENCAGE_APIKEY") 6 | }) 7 | 8 | function otr_init() 9 | end 10 | 11 | function otr_exit() 12 | end 13 | 14 | function otr_hook(topic, _type, data) 15 | end 16 | 17 | function otr_revgeo(topic, user, device, lat, lon) 18 | print("OpenCAge lookup for " .. lat .. ", " .. lon) 19 | 20 | local d = {} 21 | 22 | params = { 23 | abbrv = 1, 24 | no_record = 1 25 | } 26 | local res, status, err = gc:reverse_geocode(lat, lon, params) 27 | if status == 200 then 28 | d['cc'] = string.upper(res.results[1].components.country_code) 29 | d['locality'] = res.results[1].components.city 30 | d['addr'] = res.results[1].formatted 31 | else 32 | print("OpenCage lookup returned status=" .. status) 33 | end 34 | 35 | d['_rec'] = true -- store in .REC file 36 | return d 37 | end 38 | -------------------------------------------------------------------------------- /gcache.h: -------------------------------------------------------------------------------- 1 | #ifndef _GCACHE_H_INCLUDED_ 2 | # define _GCACHE_H_INCLUDED_ 3 | 4 | #include "json.h" 5 | #include "lmdb.h" 6 | 7 | #define LMDB_DB_SIZE ((size_t)100000 * (size_t)(1024 * 1024)) 8 | 9 | struct gcache { 10 | MDB_env *env; 11 | MDB_dbi dbi; 12 | }; 13 | 14 | struct gcache *gcache_open(char *path, char *dbname, int rdonly); 15 | void gcache_close(struct gcache *); 16 | int gcache_put(struct gcache *, char *ghash, char *payload); 17 | int gcache_json_put(struct gcache *, char *ghash, JsonNode *geo); 18 | long gcache_get(struct gcache *, char *key, char *buf, long buflen); 19 | JsonNode *gcache_json_get(struct gcache *, char *key); 20 | void gcache_dump(char *path, char *lmdbname); 21 | void gcache_load(char *path, char *lmdbname); 22 | int gcache_del(struct gcache *gc, char *keystr); 23 | bool gcache_enum(char *user, char *device, struct gcache *gc, char *key_part, int (*func)(char *key, wpoint *wp, double lat, double lon), double lat, double lon, struct udata *ud, char *topic, JsonNode *json); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /etc/centos/config.mk.in: -------------------------------------------------------------------------------- 1 | #(@)config.mk for Centos 7 (x86_64) 2 | 3 | INSTALLDIR = /usr/ 4 | CONFIGFILE = /etc/default/ot-recorder 5 | FREEBSD ?= no 6 | 7 | WITH_MQTT ?= yes 8 | WITH_HTTP ?= yes 9 | WITH_LUA ?= yes 10 | WITH_PING ?= yes 11 | WITH_KILL ?= yes 12 | WITH_ENCRYPT ?= yes 13 | WITH_GREENWICH ?= yes 14 | 15 | STORAGEDEFAULT = /var/spool/owntracks/recorder/store 16 | DOCROOT = /var/spool/owntracks/recorder/htdocs 17 | GHASHPREC = 7 18 | JSON_INDENT ?= no 19 | MOSQUITTO_INC = -I/usr/include 20 | MOSQUITTO_LIB = -L/usr/lib 21 | MORELIBS = # -lssl 22 | LUA_CFLAGS = `pkg-config --cflags lua` 23 | LUA_LIBS = `pkg-config --libs lua` 24 | 25 | GEOCODE_TIMEOUT = 4000 26 | 27 | # Requires statically built libsodium: 28 | # 29 | # wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.8.tar.gz 30 | # tar xvzf libsodium-1.0.8.tar.gz 31 | # cd libsodium* 32 | # ./configure --prefix=/usr/local --enable-minimal --enable-static --disable-shared 33 | # make install 34 | 35 | SODIUM_CFLAGS = -I/usr/local/include 36 | SODIUM_LIBS = -L /usr/local/lib -lsodium 37 | -------------------------------------------------------------------------------- /docroot/table/flags/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Go Squared Ltd. http://www.gosquared.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /contrib/get-missing/get-missing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import geocoder 4 | import json 5 | import sys 6 | import fileinput 7 | import time 8 | 9 | def reverse(lat, lon): 10 | ''' return JSON payload for ghash database from lat, lon ''' 11 | 12 | try: 13 | g = geocoder.google([lat, lon], method='reverse') 14 | 15 | # addr = '%s %s, %s %s, %s' % (g.street_long, g.housenumber, g.postal, g.city, g.country_long) 16 | addr = g.address 17 | 18 | data = { 19 | 'tst' : int(time.time()), 20 | 'cc' : g.country, 21 | 'addr' : addr, 22 | 'locality' : g.city_long 23 | } 24 | 25 | return json.dumps(data) 26 | except Exception, e: 27 | print >> sys.stderr, "lookup failed:", str(e) 28 | return None 29 | 30 | for line in fileinput.input(): 31 | line = line.rstrip() 32 | ghash, lat, lon = line.split() 33 | 34 | f = open("missing.again", "w") 35 | 36 | payload = reverse(lat, lon) 37 | if payload is None: 38 | f.write("%s\n" % line) 39 | continue 40 | 41 | time.sleep(1) 42 | print ghash, payload 43 | -------------------------------------------------------------------------------- /etc/example.lua: -------------------------------------------------------------------------------- 1 | local file 2 | 3 | function otr_init() 4 | otr.log("example.lua starting; writing to /tmp/lua.out") 5 | file = io.open("/tmp/lua.out", "a") 6 | file:write("written by OwnTracks Recorder version " .. otr.version .. "\n") 7 | end 8 | 9 | function otr_hook(topic, _type, data) 10 | local timestr = otr.strftime("It is %T in the year %Y", 0) 11 | print("L: " .. topic .. " -> " .. _type) 12 | addr = data['addr'] 13 | if addr == nil then 14 | addr = 'unknown' 15 | end 16 | file:write(timestr .. " " .. topic .. " lat=" .. data['lat'] .. addr .. "\n") 17 | end 18 | 19 | function otr_exit() 20 | end 21 | 22 | -- invoked when a publish over httpmode is received 23 | -- data is a Lua table with the OwnTracks payload in it, 24 | -- keys are those known from our JSON 25 | 26 | function otr_httpobject(user, device, _type, data) 27 | 28 | resp = {} 29 | 30 | if _type == 'transition' and data['desc'] == 'Fireplace' then 31 | resp["_type"] = "cmd" 32 | resp["tid"] = data['tid'] 33 | resp["tst"] = os.time() 34 | resp["action"] = "action" 35 | resp["comment"] = "by Lua in otr_httpobject()" 36 | 37 | if data['event'] == 'enter' then 38 | resp["url"] = "http://www.lua.org" 39 | resp["extern"] = false 40 | else 41 | resp["url"] = nil 42 | end 43 | end 44 | 45 | return resp 46 | end 47 | -------------------------------------------------------------------------------- /contrib/freebsd/ot-recorder.rc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # $FreeBSD$ 3 | # 4 | # PROVIDE: otrecorder 5 | # REQUIRE: LOGIN mosquitto hass 6 | # KEYWORD: shutdown 7 | # 8 | . /etc/rc.subr 9 | 10 | name=otrecorder 11 | rcvar=${name}_enable 12 | 13 | start_cmd=${name}_start 14 | stop_cmd=${name}_stop 15 | 16 | load_rc_config $name 17 | : ${otrecorder_enable:=NO} 18 | : ${otrecorder_pidfile:=/var/run/otrecorder.pid} 19 | 20 | command=/usr/local/sbin/ot-recorder 21 | #command_args="--debug --norevgeo" 22 | command_args="--debug --label MyMap" 23 | 24 | otrecorder_start() 25 | { 26 | if [ -f $otrecorder_pidfile ] && kill -0 $(cat $otrecorder_pidfile) >/dev/null 2>&1 ; then 27 | echo 'Service already running' 28 | return 1 29 | fi 30 | echo 'Starting service after 10 secs so mosquitto is up' 31 | sleep 10; 32 | $command $command_args & 33 | echo $! > ${otrecorder_pidfile} 34 | echo 'Service started' 35 | } 36 | 37 | otrecorder_stop() 38 | { 39 | if [ ! -f $otrecorder_pidfile ] || ! kill -0 $(cat $otrecorder_pidfile) >/dev/null 2>&1 ; then 40 | echo 'Service not running' 41 | return 1 42 | fi 43 | echo 'Stopping service' 44 | kill $(cat $otrecorder_pidfile) 45 | while ps -p $(cat $otrecorder_pidfile) >/dev/null 2>&1 ; do sleep 1;done 46 | echo 'Service stopped' 47 | } 48 | 49 | run_rc_command "$1" 50 | -------------------------------------------------------------------------------- /etc/centos/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinstall for Centos7 3 | # 4 | # Create owntracks user & group 5 | # binaries are setuid to owntracks 6 | # storage belongs to owntracks:owntracks 7 | 8 | set -e 9 | 10 | case "$1" in 11 | 1|2) 12 | getent group owntracks > /dev/null || /usr/sbin/groupadd --system owntracks 13 | getent passwd owntracks > /dev/null || adduser --system -g owntracks --no-create-home owntracks 14 | mkdir -p /var/spool/owntracks/recorder/store/last 15 | mkdir -p /var/spool/owntracks/recorder/store/ghash 16 | chown -R owntracks:owntracks /var/spool/owntracks/recorder/store 17 | chown owntracks:owntracks /etc/default/ot-recorder 18 | chown -R owntracks:owntracks /var/spool/owntracks/ 19 | chown owntracks:owntracks /usr/bin/ocat /usr/sbin/ot-recorder 20 | chmod 4111 /usr/bin/ocat /usr/sbin/ot-recorder 21 | /usr/sbin/ot-recorder --initialize 22 | 23 | if [ -x /bin/systemctl ]; then 24 | install -m644 /usr/share/doc/ot-recorder/ot-recorder.service /usr/lib/systemd/system/ot-recorder.service 25 | systemctl --system daemon-reload 26 | fi 27 | ;; 28 | 29 | abort-upgrade|abort-remove|abort-deconfigure) 30 | ;; 31 | 32 | *) 33 | echo "postinst called with unknown argument \`$1'" >&2 34 | exit 1 35 | ;; 36 | esac 37 | 38 | exit 0 39 | -------------------------------------------------------------------------------- /etc/centos/fpm-make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | tempdir=$(mktemp -d /tmp/ot-XXX) 6 | 7 | make install DESTDIR=$tempdir 8 | 9 | mkdir -p $tempdir/usr/share/doc/ot-recorder 10 | install -D README.md $tempdir/usr/share/doc/ot-recorder/README.md 11 | install -D etc/ot-recorder.service $tempdir/usr/share/doc/ot-recorder/ot-recorder.service 12 | 13 | 14 | name="ot-recorder" 15 | version=$(awk 'NR==1 {print $NF;}' version.h | sed -e 's/"//g' ) 16 | arch=$(uname -m) 17 | rpmfile="${name}_${version}_${arch}.rpm" 18 | 19 | rm -f "${rpmfile}" 20 | 21 | fpm -s dir \ 22 | -t rpm \ 23 | -n ${name} \ 24 | -v ${version} \ 25 | --vendor "OwnTracks.org" \ 26 | -a native \ 27 | --maintainer 'jpmens@gmail.com' \ 28 | --description "A lightweight back-end for consuming OwnTracks data from an MQTT broker" \ 29 | --license "https://github.com/owntracks/recorder/blob/master/LICENSE" \ 30 | --url "http://owntracks.org" \ 31 | -C $tempdir \ 32 | -p ${rpmfile} \ 33 | -d "libcurl" \ 34 | -d "libmosquitto1" \ 35 | -d "lua" \ 36 | -d "libconfig" \ 37 | -d "lmdb" \ 38 | --config-files etc/default/ot-recorder \ 39 | --post-install etc/centos/postinst \ 40 | usr var etc 41 | 42 | echo "${rpmfile}" > package.name 43 | rm -rf "${tempdir}" 44 | -------------------------------------------------------------------------------- /contrib/gpx-to-rec/README.md: -------------------------------------------------------------------------------- 1 | # gpx-to-rec.py 2 | This script attempts to convert a `gpx` file into `rec` files to be used with `recorder`. I've only tested this script with gpx files with only one track and one segment from `Phonetrack`. 3 | 4 | ## Installation 5 | `gpx-to-rec.py` depends on [`gpxpy`](https://github.com/tkrajina/gpxpy) which you can install with `pip`: 6 | ``` 7 | pip install gpxpy 8 | ``` 9 | ## Usage 10 | The script takes the `.gpx` file as an argument and creates `.rec` files in the same directory as the script. 11 | Example: 12 | ``` 13 | python3 gpx-to-rec.py my_track.gpx 14 | ``` 15 | 16 | You can then copy those `.rec` files to `recorder`'s storage location for the corresponding user and device. By default it's in `/var/spool/owntracks/recorder/store/rec//`. 17 | Make sure to change `` and `` to your usecase. 18 | After copying the files over, make sure that `recorder` has permissions to write to them: 19 | ``` 20 | sudo chown owntracks:owntracks /var/spool/owntracks/recorder/store/rec///* 21 | ``` 22 | Again, edit the command for your usecase. In my case, `recorder` uses a separate user named `owntracks`, which is why I needed to change the owner. 23 | 24 | And there you have it. Now every point from your `gpx` file should be displayed on, for example, [`frontend`](https://github.com/owntracks/frontend). 25 | -------------------------------------------------------------------------------- /contrib/gpx-to-rec/gpx_to_rec.py: -------------------------------------------------------------------------------- 1 | import gpxpy 2 | import json 3 | import time 4 | import datetime 5 | import sys 6 | 7 | if len(sys.argv) < 2: 8 | print("Please pass a .gpx file to parse") 9 | quit() 10 | 11 | gpx_file = open(sys.argv[1], 'r') 12 | 13 | print("Parsing ", sys.argv[1], "...", sep="") 14 | gpx = gpxpy.parse(gpx_file) 15 | 16 | for track in gpx.tracks: 17 | for segment in track.segments: 18 | # Creates a set of every month and year the points were recorded in 19 | months = set() 20 | for point in segment.points: 21 | months.add(point.time.strftime("%Y-%m")) 22 | 23 | # Generates the contents for every month and saves them to a .rec file (eg. 2022-03.rec) 24 | for month in months: 25 | output = "" 26 | for point in segment.points: 27 | if point.time.strftime("%Y-%m") == month: 28 | point_json = {'_type' : 'location', 'lat' : point.latitude, 'lon' : point.longitude, 'tst' : int(point.time.timestamp()), '_http' : True, 'alt' : point.elevation} 29 | output += point.time.strftime("%Y-%m-%dT%XZ") + "\t*" + (18 * " ") + "\t" + json.dumps(point_json) + "\n" 30 | rec_file = open(month + ".rec", "w") 31 | print("Saving file ", month, ".rec...", sep="") 32 | rec_file.write(output) 33 | rec_file.close() 34 | 35 | -------------------------------------------------------------------------------- /docroot/last/last.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | WebSocket Test 4 | 43 | 44 |

OwnTracks Recorder WebSocket Test

45 | 46 |
47 | 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | compiler: gcc 3 | before_install: 4 | ## Fix apt preferences for libconfig 5 | #- "echo 'Package: libconfig*' | sudo tee -a /etc/apt/preferences" 6 | #- "echo 'Pin: release n=quantal*' | sudo tee -a /etc/apt/preferences" 7 | #- "echo 'Pin-Priority: 990' | sudo tee -a /etc/apt/preferences" 8 | #- sudo DEBIAN_FRONTEND=noninteractive apt-get -qq -y --fix-missing -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew dist-upgrade 9 | - sudo add-apt-repository ppa:mosquitto-dev/mosquitto-ppa -y 10 | - sudo DEBCONF_NONINTERACTIVE_SEEN=true DEBIAN_FRONTEND=noninteractive apt-get update -qq 11 | - apt-cache search mosquitto 12 | - sudo apt-get install -qq libmosquitto-dev libconfig8-dev liblmdb-dev 13 | script: 14 | - cp config.mk.in config.mk 15 | - make 16 | - ./ocat -v 17 | notifications: 18 | slack: 19 | secure: AWI43ztcN3D/TW3U2CQwuVKLrHqQk7xShuMm8d2JUfTuHtamRvhQ3jAYs3NdfA2brlJKiOHl6HLbbGRaT6ITdcel1UuAmKzNbuKw+jM9ug2ziGddmAeyoQ3DrjECCnydZ3YAEKtAvRFAl3Q+AmXDUkiFOV988lLQ3PkG2ODcwI/4KS3IM/H0SrDy6/ejAG8unICZfXs49XJRGI0kEj3Xzy8OUE1cdB9BGzTaQi/kOsHVgu/CUMwY/ofeRBRaYPqnj3ewgGJmRyLh2A1z6OSPRMkoSgS1u8a5OsiWgAL1j0rZZDws9MwkI3Lkkvw3rO2Gn3uRzMvzXx3/h+B9qBEH3L7NulDQNu8MY5iH6Bx/3m9eom79JJgN31+RDjjCrtNVlBj7bXjtrJJqNtL4EOXFMvjq3MgnMwREtBtIqj+xufRdJGQGfyvF4YA2MT96CTGtr1udTnD3E6S0n2XjTI9cBVty9FYG5juDGh5BwNk9HDttLusjQQ+8CbNnQjh3ILAiNRNhQNRL1M6MK8GJbtMfiWRYG5HZgfY4ZmKWAuYS67fJw4TSkNPtUMTiwRSdDckNaqfdLU9XNzBnHnswcr8ZRAdsWkWr4XmysLL3/XnRN3awLDGoibs65Yd7fg1TqV/9HWjpDmcwUOs5o06MIfFPakhYj1D+ebm+DwS9KoxawvE= 20 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef _UTIL_H_INCL_ 2 | # define _UTIL_H_INCL_ 3 | 4 | #ifndef TRUE 5 | # define TRUE (1) 6 | # define FALSE (0) 7 | #endif 8 | 9 | #include 10 | #include 11 | #include 12 | #include "json.h" 13 | #include "udata.h" 14 | #include "utstring.h" 15 | 16 | #define UB(x) utstring_body(x) 17 | 18 | int mkpath(char *path); 19 | int is_directory(char *path); 20 | const char *isotime(time_t t); 21 | const char *disptime(time_t t); 22 | char *slurp_file(char *filename, int fold_newlines); 23 | int json_copy_to_object(JsonNode *obj, JsonNode * object_or_array, int clobber); 24 | int json_copy_element_to_object(JsonNode *obj, char *key, JsonNode *node); 25 | int json_copy_from_file(JsonNode * obj, char *filename); 26 | int splitter(char *s, char *sep, char **parts); 27 | void splitterfree(char **parts); 28 | JsonNode *json_splitter(char *s, char *sep); 29 | int syslog_facility_code(char *facility); 30 | const char *yyyymm(time_t t); 31 | int tac(char *filename, long lines, int (*func)(char *, void *), void *param); 32 | int cat(char *filename, int (*func)(char *, void *), void *param); 33 | FILE *pathn(char *mode, char *prefix, UT_string *user, UT_string *device, char *suffix, time_t epoch); 34 | int safewrite(char *filename, char *buf); 35 | void olog(int level, char *fmt, ...); 36 | void geohash_setprec(int precision); 37 | int geohash_prec(void); 38 | void lowercase(char *s); 39 | double haversine_dist(double th1, double ph1, double th2, double ph2); 40 | void debug(struct udata *, char *fmt, ...); 41 | void chomp(char *s); 42 | double number(JsonNode *j, char *element); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /etc/debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinstall for DEB8 3 | # 4 | # Create owntracks user & group 5 | # binaries are setuid to owntracks 6 | # storage belongs to owntracks:owntracks 7 | 8 | set -e 9 | 10 | case "$1" in 11 | configure) 12 | getent group owntracks > /dev/null || /usr/sbin/groupadd --system owntracks 13 | getent passwd owntracks > /dev/null || adduser --system --disabled-password --disabled-login --ingroup owntracks --no-create-home owntracks 14 | mkdir -p /var/spool/owntracks/recorder/store/last 15 | mkdir -p /var/spool/owntracks/recorder/store/ghash 16 | chown -R owntracks:owntracks /var/spool/owntracks/recorder/store 17 | chown owntracks:owntracks /etc/default/ot-recorder 18 | chown -R owntracks:owntracks /var/spool/owntracks/ 19 | chown owntracks:owntracks /usr/bin/ocat /usr/sbin/ot-recorder 20 | 21 | chmod a+rX -R /usr/share/owntracks/ 22 | 23 | chmod 4111 /usr/bin/ocat /usr/sbin/ot-recorder 24 | /usr/sbin/ot-recorder --initialize 25 | 26 | 27 | if [ -x /bin/systemctl ]; then 28 | install -m644 /usr/share/doc/ot-recorder/ot-recorder.service /etc/systemd/system/ot-recorder.service 29 | systemctl --system daemon-reload || exit 0 # catch failure in docker setup 30 | fi 31 | ;; 32 | 33 | abort-upgrade|abort-remove|abort-deconfigure) 34 | ;; 35 | 36 | *) 37 | echo "postinst called with unknown argument \`$1'" >&2 38 | exit 1 39 | ;; 40 | esac 41 | 42 | # dh_installdeb will replace this with shell code automatically 43 | # generated by other debhelper scripts. 44 | 45 | #DEBHELPER# 46 | 47 | exit 0 48 | -------------------------------------------------------------------------------- /etc/raspbian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinstall for Raspbian Buster 10 3 | # 4 | # Create owntracks user & group 5 | # binaries are setuid to owntracks 6 | # storage belongs to owntracks:owntracks 7 | 8 | set -e 9 | 10 | case "$1" in 11 | configure) 12 | getent group owntracks > /dev/null || /usr/sbin/groupadd --system owntracks 13 | getent passwd owntracks > /dev/null || adduser --system --disabled-password --disabled-login --ingroup owntracks --no-create-home owntracks 14 | mkdir -p /var/spool/owntracks/recorder/store/last 15 | mkdir -p /var/spool/owntracks/recorder/store/ghash 16 | chown -R owntracks:owntracks /var/spool/owntracks/recorder/store 17 | chown owntracks:owntracks /etc/default/ot-recorder 18 | chown -R owntracks:owntracks /var/spool/owntracks/ 19 | chown owntracks:owntracks /usr/bin/ocat /usr/sbin/ot-recorder 20 | 21 | chmod a+rX -R /usr/share/owntracks/ 22 | 23 | chmod 4111 /usr/bin/ocat /usr/sbin/ot-recorder 24 | /usr/sbin/ot-recorder --initialize 25 | 26 | 27 | if [ -x /bin/systemctl ]; then 28 | install -m644 /usr/share/doc/ot-recorder/ot-recorder.service /etc/systemd/system/ot-recorder.service 29 | systemctl --system daemon-reload || exit 0 # catch failure in docker setup 30 | fi 31 | ;; 32 | 33 | abort-upgrade|abort-remove|abort-deconfigure) 34 | ;; 35 | 36 | *) 37 | echo "postinst called with unknown argument \`$1'" >&2 38 | exit 1 39 | ;; 40 | esac 41 | 42 | # dh_installdeb will replace this with shell code automatically 43 | # generated by other debhelper scripts. 44 | 45 | #DEBHELPER# 46 | 47 | exit 0 48 | -------------------------------------------------------------------------------- /contrib/new-view-auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # OwnTracks Recorder 4 | # Copyright (C) 2015-2016 Jan-Piet Mens 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | 20 | import getpass 21 | import sys 22 | try: 23 | from hashlib import md5 24 | except ImportError: 25 | from md5 import md5 26 | 27 | def digest_password(realm, username, password): 28 | """ construct a hash for HTTP digest authentication """ 29 | return md5("%s:%s:%s" % (username, realm, password)).hexdigest() 30 | 31 | if __name__ == '__main__': 32 | if len(sys.argv) != 2: 33 | print "Usage: %s username" % sys.argv[0] 34 | sys.exit(1) 35 | 36 | username = sys.argv[1] 37 | realm = 'owntracks-recorder' 38 | 39 | pw1 = getpass.getpass("Enter password for user %s: " % username) 40 | pw2 = getpass.getpass("Re-enter password: ") 41 | 42 | if pw1 != pw2: 43 | print "Passwords don't match" 44 | sys.exit(2) 45 | 46 | print '"auth" : [ "%s" ]' % (digest_password(realm, username, pw1)) 47 | -------------------------------------------------------------------------------- /etc/debian/fpm-make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | tempdir=$(mktemp -d /tmp/ot-XXX) 6 | 7 | make install DESTDIR=$tempdir 8 | 9 | install -D README.md $tempdir/usr/share/doc/ot-recorder/README.md 10 | install -D etc/ot-recorder.service $tempdir/usr/share/doc/ot-recorder/ot-recorder.service 11 | 12 | name="ot-recorder" 13 | # add -0 to indicate "not in Debian" as per Roger's suggestion 14 | version="$(awk 'NR==1 {print $NF;}' version.h | sed -e 's/"//g' )-0-deb$(cat /etc/debian_version)" 15 | arch=$(uname -m) 16 | 17 | case $arch in 18 | armv7l) arch=armhf;; 19 | esac 20 | 21 | debfile="${name}_${version}_${arch}.deb" 22 | 23 | rm -f "${debfile}" 24 | 25 | libcurl='libcurl3' 26 | libsodium='libsodium13' 27 | case $(cat /etc/debian_version) in 28 | 8.8) ;; 29 | 9.*) libsodium="libsodium18" ;; 30 | 10.*) 31 | libsodium="libsodium23" 32 | libcurl="libcurl3-gnutls" 33 | ;; 34 | esac 35 | 36 | fpm -s dir \ 37 | -t deb \ 38 | -n ${name} \ 39 | -v ${version} \ 40 | --vendor "OwnTracks.org" \ 41 | -a "${arch}" \ 42 | --maintainer 'jpmens@gmail.com' \ 43 | --description "A lightweight back-end for consuming OwnTracks data from an MQTT broker" \ 44 | --license "https://github.com/owntracks/recorder/blob/master/LICENSE" \ 45 | --url "http://owntracks.org" \ 46 | -C $tempdir \ 47 | -p ${debfile} \ 48 | -d "${libcurl}" \ 49 | -d "libmosquitto1" \ 50 | -d "liblua5.2-0" \ 51 | -d "libconfig9" \ 52 | -d "${libsodium}" \ 53 | -d "liblmdb0" \ 54 | --config-files etc/default/ot-recorder \ 55 | --post-install etc/debian/postinst \ 56 | usr var etc 57 | 58 | echo "${debfile}" > package.name 59 | rm -rf "${tempdir}" 60 | -------------------------------------------------------------------------------- /docroot/table/defaultface.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Base64-encoded 40x40px image to use as a default face 3 | */ 4 | var defaultface = '/9j/4AAQSkZJRgABAQAAAQABAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyB' + 5 | 'JSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gOTAK/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBA' + 6 | 'UKBwcGCAwKDAwLCgsLDQ4SEA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFB' + 7 | 'AUJBQUJFA0LDRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU' + 8 | 'FBQUFBQU/8AAEQgAKAAoAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQY' + 9 | 'HCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCsc' + 10 | 'EVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpa' + 11 | 'nN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS' + 12 | '09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgM' + 13 | 'EBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCka' + 14 | 'GxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZ' + 15 | 'mdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXG' + 16 | 'x8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A+t6KPxr0D4V' + 17 | 'eF7fVJrjUbtFmjgYJFGwyu/GSSPYY/OgDhvsFz5PnfZ5vJ6+ZsO386gr6awMY7V5N8VvC9v' + 18 | 'pklvqVoiwpO5jlRRhd+MggdsgHP0oA8+oo/GigA/CvR/hJ4hgtHudLndYmmcSQljgM2MFfr' + 19 | 'wMfjXnKIZHVEBZmOAo5JNez+DPh7aaHbR3N7EtxqLDcd4ysXsB6+/5UAdnz6V5Z8XPEMF19' + 20 | 'n0qB1laF/NmKnIVsEBfryc/hXqXHvXG+NPh7aa5bSXNlEtvqKgsNgwsvsR6+/wCdAHi/4UU' + 21 | 'roY3ZHBVlOCp4IPpRQB1Xwy05dR8WW5cBlt1afHuOB+RIP4V7hz60UUAHPrRz60UUAeH/AB' + 22 | 'N05dO8WXBQBUuEWfHucg/qCfxooooA/9k='; 23 | -------------------------------------------------------------------------------- /etc/raspbian/fpm-make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | tempdir=$(mktemp -d /tmp/ot-XXX) 6 | 7 | make install DESTDIR=$tempdir 8 | 9 | install -D README.md $tempdir/usr/share/doc/ot-recorder/README.md 10 | install -D etc/ot-recorder.service $tempdir/usr/share/doc/ot-recorder/ot-recorder.service 11 | 12 | name="ot-recorder" 13 | # add -0 to indicate "not in Debian" as per Roger's suggestion 14 | version="$(awk 'NR==1 {print $NF;}' version.h | sed -e 's/"//g' )-0-deb$(cat /etc/debian_version)" 15 | 16 | # arch=$(uname -m) 17 | arch=armhf 18 | 19 | case $arch in 20 | armv7l) arch=armhf;; 21 | esac 22 | 23 | debfile="${name}_${version}_${arch}.deb" 24 | 25 | rm -f "${debfile}" 26 | 27 | libcurl='libcurl3' 28 | libsodium='libsodium13' 29 | case $(cat /etc/debian_version) in 30 | 8.8) ;; 31 | 9.*) libsodium="libsodium18" ;; 32 | 10.*) 33 | libsodium="libsodium23" 34 | libcurl="libcurl3-gnutls" 35 | ;; 36 | esac 37 | 38 | fpm -s dir \ 39 | -t deb \ 40 | -n ${name} \ 41 | -v ${version} \ 42 | --vendor "OwnTracks.org" \ 43 | -a "${arch}" \ 44 | --maintainer 'jpmens@gmail.com' \ 45 | --description "A lightweight back-end for consuming OwnTracks data from an MQTT broker" \ 46 | --license "https://github.com/owntracks/recorder/blob/master/LICENSE" \ 47 | --url "http://owntracks.org" \ 48 | -C $tempdir \ 49 | -p ${debfile} \ 50 | -d "${libcurl}" \ 51 | -d "libmosquitto1" \ 52 | -d "liblua5.2-0" \ 53 | -d "libconfig9" \ 54 | -d "${libsodium}" \ 55 | -d "liblmdb0" \ 56 | --config-files etc/default/ot-recorder \ 57 | --post-install etc/debian/postinst \ 58 | usr var etc 59 | 60 | echo "${debfile}" > package.name 61 | rm -rf "${tempdir}" 62 | -------------------------------------------------------------------------------- /etc/repo.owntracks.org.gpg.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v2.0.22 (GNU/Linux) 3 | 4 | mQINBFap9qABEADHD51G5Falcv1nEABzDOHRJ484kOIe8W1fzy84jqKxbm7VRh1s 5 | Ji8taAbOHNg73cpaukLZtfXKPS4JIs1UH2wmiuQVV/QYm7QDsC0dFsc5EDYLllpv 6 | vqN8X6BPgoHKb3sjcJ0cac56ACDX2hPDbsvNhLumgMqMxLZG8YejWdh4GhyCX5Xw 7 | 8fD7HT3gANoyOPKeqv9vlb/XG6a8cGG5I4HgB2kztf+6FkAgr97vUs5KeXTxQxdz 8 | RSfufPXk0L7JVPyOlv/2rAlgAICbZQZcc8uXmBq9v6ezUyEh0C1olJUcWTWLxAQ3 9 | O3C0PIbwZfHixE2fU7mMyS39Nf0lQLTpKmYWV7mwcUHp/JyQa87qaglSehcbguR8 10 | Ny0lb4CvXeEkWFiuriPyhFAPIYz5Iqt277LlFrqoruuabtQC+dqf5mTmsLReIOpR 11 | xsYeIwKdfbYlMJl9ucwvivEBol0trhBbyuTJ8U6DBob81fGIEusVKPZ25Fn8bMhT 12 | /kRrIQEpJMjB4p03BgoGMoLjn5iZGYfM9mzECDnQ2mjS1PFeTBbRNRthN2pd6nrV 13 | ln8zg40GAHP7xaaFHGv2glqGaqxLgp4ncBdJOD9QsMmMNRIe40c3XQAiNYfo+3ql 14 | DRvSvCb3iU4Pupfeg1W106s+OGVzA7WrbNslrcchOgn/PGHy/HdW6aDIrQARAQAB 15 | tDxPd25UcmFja3MgcGFja2FnZXMgKHBhY2thZ2Ugc2lnbmluZykgPHN1cHBvcnRA 16 | b3dudHJhY2tzLm9yZz6JAjkEEwECACMFAlap9qACGwMHCwkIBwMCAQYVCAIJCgsE 17 | FgIDAQIeAQIXgAAKCRC+FnUVPgpRFhQTD/48dgQewJHzVs+rcKNdSEygEHf84zR0 18 | a5ggpVtBqbp2kKzj3A+jvnfyIc8gNGMLQR+tR/xLISixbx7zZPJErBUsJU9BPeev 19 | /m0GxFoZXdPN48viRrkpWFapQyXxEzkxEHcBqRWi0puV36mBsbl7vgSmodilbhCH 20 | Xyn70vZ3tC3nXhUt1E2gpAgck+fpmz2vbhJlETyICG5ymN34bcjLCCNem3QXNXb4 21 | aN4KWAvSxDAbcmzLs3JIPiT7YOQFueTMs++PQmNC4joT08kO+nc6MMOaMeGCdTea 22 | S8A53ulbMgQEO0HsWZpn1VE0dtbKi/LH+MMH0Cpftd7XPHLWBZ4OwjND8ETz6WhC 23 | Ln14/w4h6QVT3632k0b80llWR3G0v1VhnAMVvJSYQHidxdubjMFA07e3KzXDuncx 24 | 0l6eF1QqO2O5LwzsDSuscUUCetLBrvO5odjDbr8Y4ghSWQOJjwdzuva5YWsUDrGy 25 | PtQztyw7RD44ymR4xrg9iPdHbUTh5NRgUVG6m2BD9cLmhEmgrBX23Yht+3mMiYR8 26 | ge0VHj565ZqUP8Vb5F+aB7H1fnsg+wsMASiExiOwFEZQHfNldg+6pxFs7HKOf0hR 27 | vmkjG76XXo6PR82HAOAFtosUa67swJjQsaDAE4CxOXOiQNdIegqdiYGwDPtjvc23 28 | HZce4lqbNNbz1A== 29 | =PVJv 30 | -----END PGP PUBLIC KEY BLOCK----- 31 | -------------------------------------------------------------------------------- /doc/OPENCAGE.md: -------------------------------------------------------------------------------- 1 | # OpenCage 2 | 3 | We now (since May 2018) recommend using [OpenCage](https://opencagedata.com) as reverse geo-coding provider: their [pricing](https://opencagedata.com/pricing) is attractive and they currently offer a free tier which allows up to 2,500 requests per day. 4 | 5 | Use the OpenCage API in Recorder simply by setting the `--geokey` option to the string `"opencage:"` with your API key concatenated to it. (Without the substring `opencage:` the Recorder falls back to using Google in order to maintain backwards-compatibility.) 6 | 7 | ``` 8 | --geokey "opencage:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 9 | ``` 10 | 11 | Be aware that the Recorder uses the following settings: `no_record=1&limit=1`. OpenCage documents the first as meaning it will not log the request, and that protects your privacy. 12 | 13 | ## With Lua 14 | 15 | There was experimental Lua support for OpenCage, but this is no longer necessary; we keep the following for historic purposes only. 16 | 17 | In order to use OpenCage with the Recorder using Lua, proceed as follows: 18 | 19 | 1. Make sure you've built the Recorder with support for Lua 20 | 1. Install the required Lua modules: 21 | ```bash 22 | luarocks install lua-opencage-geocoder 23 | ``` 24 | 2. Obtain an [OpenCage API key](https://opencagedata.com/pricing), and make careful note of that. 25 | 3. Before launching the Recorder, export the API key to the Recorder's environment 26 | ```bash 27 | export OPENCAGE_APIKEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 28 | ``` 29 | 4. Add a Lua script for the Recorder, which looks like [our example OpenCage Lua script](/contrib/opencage.lua) 30 | 5. Launch the Recorder using something along these lines: 31 | ```bash 32 | ot-recorder --lua-script opencage.lua 'owntracks/#' 33 | ``` 34 | 6. Note that you'll currently still require a Google Maps browser API key for the actual maps. 35 | -------------------------------------------------------------------------------- /docroot/test-ws.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | WebSocket Test 4 | 55 | 56 |

OwnTracks Recorder WebSocket Test

57 | 58 |
59 | 60 | -------------------------------------------------------------------------------- /contrib/faces/image2card.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # image2card.sh 4 | # Copyright (C) 2015-2016 Jan-Piet Mens 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | 20 | # Usage: image2card imagefile "full name" 21 | # 22 | # Requires `convert' from ImageMagick. 23 | # Read image, convert to PNG, forcing a 40x40 size and encode 24 | # to BASE64. Create a JSON payload to be published to 25 | # owntracks/username/device/info 26 | # 27 | # You probably want to do this: 28 | # 29 | # image2card.sh filename.jpg "Jane Jolie" > card.json 30 | # mosquitto_pub -t owntracks/jane/phone/info -f card.json -r 31 | # 32 | # Note: the two commands cannot be piplelined (mosquitto_pub -l) 33 | # because of a bug in mosquitto_pub: https://bugs.eclipse.org/bugs/show_bug.cgi?id=478917 34 | # If you have a newish version it should work fine. 35 | 36 | 37 | [ $# -ne 2 ] && { echo "Usage: $0 image-file full-name" >&2; exit 2; } 38 | imagefile="$1" 39 | fullname="$2" 40 | 41 | base64switch="" 42 | base64check=$(echo "jj" | base64 -w 0 > /dev/null 2>&1) 43 | [ "$?" -eq "0" ] && base64switch="-w 0" 44 | imgdata=$(convert "${imagefile}" -resize '40x40!' - | base64 $base64switch) 45 | 46 | cat < 5 | #include "json.h" 6 | #include "udata.h" 7 | 8 | #define DEFAULT_HISTORY_HOURS 6 9 | 10 | /* Output types */ 11 | typedef enum { 12 | GEOJSON = 0, 13 | CSV, 14 | JSON, 15 | RAW, 16 | GPX, 17 | RAWPAYLOAD, 18 | LINESTRING, 19 | XML, 20 | } output_type; 21 | 22 | /* JSON payload types */ 23 | typedef enum { 24 | T_UNKNOWN = 0, 25 | T_BEACON, 26 | T_CARD, 27 | T_CMD, 28 | T_CONFIG, 29 | T_LOCATION, 30 | T_LWT, 31 | T_MSG, 32 | T_STEPS, 33 | T_TRANSITION, 34 | T_WAYPOINT, 35 | T_WAYPOINTS, 36 | #if WITH_ENCRYPT 37 | T_ENCRYPTED, 38 | #endif 39 | } payload_type; 40 | 41 | JsonNode *lister(char *username, char *device, time_t s_lo, time_t s_hi, int reverse); 42 | JsonNode *multilister(JsonNode *udpairs, time_t s_lo, time_t s_hi, int reverse); 43 | void locations(char *filename, JsonNode *obj, JsonNode *arr, time_t s_lo, time_t s_hi, output_type otype, int limit, JsonNode *fields, char *username, char *device); 44 | int make_times(char *time_from, time_t *s_lo, char *time_to, time_t *s_to, int hours); 45 | JsonNode *geo_json(JsonNode *json); 46 | JsonNode *geo_linestring(JsonNode *location_array); 47 | JsonNode *kill_datastore(char *username, char *device); 48 | JsonNode *last_users(char *user, char *device, JsonNode *fields); 49 | char *gpx_string(JsonNode *json); 50 | void storage_init(int revgeo); 51 | void storage_gcache_dump(char *lmdbname); 52 | void storage_gcache_load(char *lmdbname); 53 | void xml_output(JsonNode *json, output_type otype, JsonNode *fields, void (*func)(char *s, void *param), void *param); 54 | void csv_output(JsonNode *json, output_type otype, JsonNode *fields, void (*func)(char *s, void *param), void *param); 55 | char *storage_userphoto(char *username); 56 | void append_card_to_object(JsonNode *obj, char *user, char *device); 57 | void extra_http_json(JsonNode *array, char *user, char *device); 58 | void load_otrw_from_string(struct udata *ud, char *username, char *device, char *js); 59 | bool load_fences(struct udata *ud); 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /contrib/faces/gravatar2card.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # gravatar2card.sh 4 | # Copyright (C) 2015-2016 Jan-Piet Mens 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | 20 | # Usage: gravatar2card email "full name" 21 | # 22 | # Calculate email hash and obtain a 40x40 PNG from Gravatar 23 | # and convert image to BASE64. Create a JSON payload to be published 24 | # retained to owntracks/username/device/info 25 | # 26 | # You probably want to do this: 27 | # 28 | # gravatar2card.sh email-address "Jane Jolie" > card.json 29 | # mosquitto_pub -t owntracks/jane/phone/info -r -f card.json 30 | # 31 | # Note: the two commands cannot be piplelined (mosquitto_pub -l) 32 | # because of a bug in mosquitto_pub: https://bugs.eclipse.org/bugs/show_bug.cgi?id=478917 33 | # If you have a newer version it should work fine. 34 | 35 | 36 | [ $# -ne 2 ] && { echo "Usage: $0 email full-name" >&2; exit 2; } 37 | 38 | md5=md5 39 | if [ -x /usr/bin/md5sum ]; then 40 | md5=/usr/bin/md5sum 41 | fi 42 | 43 | email="$1" 44 | fullname="$2" 45 | tmp=$(mktemp /tmp/gravatar.XXXXXX) 46 | 47 | hash=$(echo -n "${email}" | tr '[:upper:]' '[:lower:]' | $md5) 48 | url="http://www.gravatar.com/avatar/${hash}?s=40" 49 | 50 | 51 | curl -s -o $tmp "${url}" 52 | 53 | imgdata=$(base64 -i $tmp) 54 | rm -f $tmp 55 | 56 | cat <PING... '); 37 | } else { 38 | // out('RESPONSE: ' + ev.data + ' '); 39 | try { 40 | var loc = JSON.parse(ev.data); 41 | 42 | if (loc['_label']) { 43 | document.getElementById('maplabel').textContent = loc['_label']; 44 | } 45 | 46 | if (loc['_type'] == 'location') { 47 | ws_callback(loc); 48 | } 49 | } catch (x) { 50 | ; 51 | } 52 | } 53 | }; 54 | 55 | ws.onerror = function(ev) { 56 | // out('ERROR: ' + ev.data); 57 | }; 58 | } 59 | 60 | function ws_go(callback) { 61 | // var url = 'ws://' + location.host + '/ws/last'; 62 | // console.log(JSON.stringify(location)); 63 | 64 | if (typeof callback != 'undefined') { 65 | ws_callback=callback; 66 | } 67 | 68 | var url = ("https:" == document.location.protocol ? "wss://" : "ws://") + location.host + "/"; 69 | var parts = location.pathname.split('/'); 70 | for (var i = 1; i < parts.length - 2; i++) { 71 | url = url + parts[i] + "/"; 72 | } 73 | url = url + "ws/last"; 74 | 75 | console.log("Websocket URI: " + url); 76 | 77 | 78 | ws_url = url; 79 | ws_connect(); 80 | 81 | }; 82 | -------------------------------------------------------------------------------- /etc/rhel/ot-recorder.init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #(@)ot-recorder 3 | # Start on runlevels 3, 4 and 5. Start late, stop early. 4 | # chkconfig: 345 95 05 5 | 6 | # absolute path to executable binary 7 | exec='/usr/sbin/ot-recorder' 8 | 9 | # Program name 10 | prog=$(basename $exec) 11 | 12 | # Source config 13 | if [ -f /etc/default/$prog ] ; then 14 | . /etc/default/$prog 15 | fi 16 | 17 | 18 | # PID file 19 | pidfile="/var/run/${prog}.pid" 20 | 21 | # Ensure full path to executable binary is found 22 | ! [ -x $exec ] && echo "$exec: executable not found" && exit 1 23 | 24 | eval_cmd() { 25 | local rc=$1 26 | if [ $rc -eq 0 ]; then 27 | echo '[ OK ]' 28 | else 29 | echo '[FAILED]' 30 | fi 31 | return $rc 32 | } 33 | 34 | start() { 35 | # see if running 36 | local pids=$(pgrep -f $exec) 37 | 38 | if [ -n "$pids" ]; then 39 | echo "$prog (pid $pids) is already running" 40 | return 0 41 | fi 42 | printf "%-50s%s" "Starting $prog: " '' 43 | umask 0002 44 | $exec $OTR_OPTS $OTR_TOPICS & 45 | 46 | # save pid to file if you want 47 | echo $! > $pidfile 48 | 49 | # check again if running 50 | pgrep -f $exec >/dev/null 2>&1 51 | eval_cmd $? 52 | } 53 | 54 | stop() { 55 | # see if running 56 | local pids=$(pgrep -f $exec) 57 | 58 | if [ -z "$pids" ]; then 59 | echo "$prog not running" 60 | return 0 61 | fi 62 | printf "%-50s%s" "Stopping $prog: " '' 63 | rm -f $pidfile 64 | kill -9 $pids 65 | eval_cmd $? 66 | } 67 | 68 | status() { 69 | # see if running 70 | local pids=$(pgrep -f $exec) 71 | 72 | if [ -n "$pids" ]; then 73 | echo "$prog (pid $pids) is running" 74 | else 75 | echo "$prog is stopped" 76 | fi 77 | } 78 | 79 | case $1 in 80 | start) 81 | start 82 | ;; 83 | stop) 84 | stop 85 | ;; 86 | status) 87 | status 88 | ;; 89 | restart) 90 | stop 91 | sleep 1 92 | start 93 | ;; 94 | *) 95 | echo "Usage: $0 {start|stop|status|restart}" 96 | exit 1 97 | esac 98 | 99 | exit $? 100 | -------------------------------------------------------------------------------- /doc/DESIGN.md: -------------------------------------------------------------------------------- 1 | # Design decisions 2 | 3 | We took a number of decisions when designing the Recorder and its utilities: 4 | 5 | * Flat files. The filesystem is the database. Period. That's were everything is stored. It makes incremental backups, purging old data, manipulation via the Unix toolset easy. (Admittedly, for fast geo-lookups we employ LMDB as a cache, but the final word is in the filesystem.) We considered all manner of databases and decided to keep this as simple and lightweight as possible. You can however have the Recorder send data to a database of your choosing, in addition to the file system it uses, by utilizing our embedded Lua hook. 6 | * We wanted to store received data in the format it's published in. As this format is JSON, we store this raw payload in the `.rec` files. If we add an attribute to the JSON published by our apps, you have it right there. There's one slight exception: the monthly logs (the `.rec` files) have a leading timestamp and a relative topic; see below. (In the particular case of the OwnTracks firmware for Greenwich devices which can publish in CSV mode, we convert the CSV into OwnTracks JSON for storage.) 7 | * File names are lower case. A user called `JaNe` with a device named `myPHONe` will be found in a file named `jane/myphone`. 8 | * All times are UTC (a.k.a. Zulu or GMT). We got sick and tired of converting stuff back and forth. It is up to the consumer of the data to convert to local time if need be. 9 | * The Recorder does not really provide authentication or authorization. Well, it does a bit. Think about this before making it available on a publicly-accessible IP address. Or rather: don't think about it; just don't do it. You can of course place a HTTP proxy in front of the Recorder to control access to it. Or use views (see below). 10 | * `ocat`, the `cat` program for the Recorder, uses the same back-end which is used by the API though it accesses it directly (i.e. without resorting to HTTP). 11 | * The Recorder supports 3-level MQTT topics only, in the typical OwnTracks format: `"owntracks//"`, optionally with a leading slash. (The first part of the topic need not be "owntracks".) Publishes via HTTP POST construct a fictitious topic internally using the provided user (`u`) and device (`d`) parameters. 12 | -------------------------------------------------------------------------------- /udata.h: -------------------------------------------------------------------------------- 1 | #ifndef UDATA_H_INCLUDED 2 | # define UDATA_H_INCLUDED 3 | 4 | #include "json.h" 5 | 6 | #ifdef WITH_HTTP 7 | # include 8 | # include "mongoose.h" 9 | #endif 10 | // #include "gcache.h" 11 | 12 | 13 | struct udata { 14 | JsonNode *topics; /* Array of topics to subscribe to */ 15 | int ignoreretained; /* True if retained messages should be ignored */ 16 | #if WITH_MQTT 17 | struct mosquitto *mosq; /* MQTT connection */ 18 | char *pubprefix; /* If not NULL (default), republish modified payload to /topic */ 19 | int qos; /* Subscribe QoS */ 20 | char *hostname; /* MQTT broker */ 21 | int port; /* MQTT port */ 22 | char *username; /* MQTT user */ 23 | char *password; /* MQTT password */ 24 | char *clientid; /* MQTT clientid */ 25 | char *cafile; /* path to CA PEM for MQTT */ 26 | char *capath; /* CA path */ 27 | char *certfile; /* certificate (client) */ 28 | char *keyfile; /* client key */ 29 | char *identity; /* PSK identity (hint) */ 30 | char *psk; /* PSK */ 31 | #endif 32 | int skipdemo; /* True if _demo users are to be skipped */ 33 | int revgeo; /* True (default) if we should do reverse Geo lookups */ 34 | int verbose; /* TRUE if print verbose messages to stdout */ 35 | int norec; /* If TRUE, no .REC files are written to */ 36 | struct gcache *gc; 37 | struct gcache *t2t; /* topic to tid */ 38 | #ifdef WITH_HTTP 39 | struct mg_server *mgserver; /* Mongoose */ 40 | char *http_host; /* address of http bind */ 41 | int http_port; /* port number for above */ 42 | char *http_logdir; /* full path to http access log */ 43 | char *browser_apikey; /* Google maps browser API key */ 44 | char *viewsdir; /* path to views directory */ 45 | #endif 46 | #ifdef WITH_LUA 47 | char *luascript; /* Path to Lua script */ 48 | struct luadata *luadata; /* Lua stuff */ 49 | struct gcache *luadb; /* lmdb named database 'luadb' */ 50 | #endif 51 | #ifdef WITH_ENCRYPT 52 | struct gcache *keydb; /* encryption keys */ 53 | #endif 54 | char *label; /* Server label */ 55 | char *geokey; /* reverse-geo API key */ 56 | int debug; /* enable for debugging */ 57 | struct gcache *httpfriends; /* lmdb named database 'friends' */ 58 | struct gcache *wpdb; /* lmdb named database 'wp' (waypoints) */ 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /docroot/table/flags/Hello.txt: -------------------------------------------------------------------------------- 1 | Flag Icons by GoSquared (http://www.gosquared.com/) 2 | 3 | Version: 2.6 4 | Last Updated: 8th March 2013 5 | 6 | Usage: 7 | You're free to use this set for both personal and commercial projects 8 | (MIT License, see LICENSE.txt), and a back-link is not required 9 | (but certainly appreciated). 10 | 11 | 12 | Inclusion of flags: 13 | This icon set includes all flags for recognized sovereign states, plus several other major 14 | territories. As a rule, disputed states ARE included without discrimination. 15 | 16 | 17 | Contacting Us: 18 | If you have any suggestions of corrections (incorrectly named, missing, or out-of-date flags etc.), 19 | then don't hesitate to get in touch at support@gosquared.com and we will do our best 20 | to rectify any mistakes or omissions. 21 | 22 | 23 | Changelog: 24 | 25 | * 2.6: 8th March 2013 26 | Added Saint Martin to bring total now to 260 27 | 28 | * 2.5: 3rd March 2013 29 | Couple more missing flags: 30 | Canary Islands 31 | Curaçao 32 | 33 | * 2.4: 2nd March 2013 34 | Added a couple more missing: 35 | Basque Country 36 | Martinique 37 | Added "unknown" 38 | Cleaned out some buggy files called ".png" that crept in somehow 39 | 40 | * 2.3: 26th January 2013 41 | Added more missing flags: 42 | French Polynesia 43 | Frensh Southern Territories 44 | Tokelau 45 | Wallis and Futuna 46 | Removed Kosovo's ISO code (it doesn't have one, KV was wrong) 47 | Corrected East Timor's ISO code (TP -> TL) 48 | 49 | * 2.2: 29th October 2012 50 | Added some missing flags: 51 | British Antarctic Territories 52 | Christmas Island 53 | Cocos (Keeling) Islands 54 | Cook Islands 55 | Gibraltar 56 | Mayotte 57 | New Caledonia 58 | Niue 59 | Added ico and icns formats 60 | Fixed problem where all flags in flats/flat had no extension (oops!) 61 | Fixed alignment of Nepal overlay at 48x48 62 | 63 | * 2.1: 19th October 2012 64 | Fixed incorrect ISO code for Montenegro (CS -> ME) 65 | 66 | * 2.0: 18th October 2012 67 | Fully revamped with new "shiny" style, updated with changed and new flags 68 | 69 | * 1.0: 16th September 2010 70 | First created. Icon set containing 2400 flag icons 71 | -------------------------------------------------------------------------------- /docroot/map/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GeoJSON track 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 60 | 61 | 62 |
63 | 64 | 65 | -------------------------------------------------------------------------------- /etc/ot-recorder.default: -------------------------------------------------------------------------------- 1 | #(@)ot-recorder.default 2 | # 3 | # Specify global configuration options for the OwnTracks Recorder 4 | # and its associated utilities to override compiled-in defaults. 5 | # Lines beginning with # are comments 6 | # *** In libconfig versions < 1.4 a trailing semicolon is mandatory 7 | 8 | # ----------------------------------------------------- 9 | # Storage directory 10 | # 11 | 12 | # OTR_STORAGEDIR="/var/spool/owntracks/recorder/store" 13 | 14 | # ----------------------------------------------------- 15 | # Address or hostname of the MQTT broker 16 | # 17 | 18 | # OTR_HOST="localhost" 19 | 20 | # ----------------------------------------------------- 21 | # Port number of the MQTT broker. Defaults to 1883. 22 | # MQTT can be disabled by setting this to 0. 23 | # 24 | 25 | # OTR_PORT=1883 26 | 27 | # ----------------------------------------------------- 28 | # Username for the MQTT connection 29 | # 30 | 31 | # OTR_USER="" 32 | 33 | # ----------------------------------------------------- 34 | # Password for the MQTT connection 35 | # 36 | 37 | # OTR_PASS="" 38 | 39 | # ----------------------------------------------------- 40 | # QoS for MQTT connection 41 | # 42 | 43 | # OTR_QOS=2 44 | 45 | # ----------------------------------------------------- 46 | # MQTT clientid (default is constant+hostname+pid) 47 | # 48 | 49 | # OTR_CLIENTID="" 50 | 51 | # ----------------------------------------------------- 52 | # Path to PEM-encoded CA certificate file for MQTT (no default) 53 | 54 | # OTR_CAFILE="" 55 | 56 | # ----------------------------------------------------- 57 | # Address for the HTTP module to bind to (default: localhost) 58 | # 59 | 60 | # OTR_HTTPHOST="localhost" 61 | 62 | # ----------------------------------------------------- 63 | # Port number for the HTTP module to bind to (default: 8083) 64 | # 65 | 66 | # OTR_HTTPPORT=8083 67 | 68 | # ----------------------------------------------------- 69 | # optional path to HTTP directory in which to store 70 | # access log. Default is to not log access 71 | 72 | # OTR_HTTPLOGDIR="" 73 | 74 | # ----------------------------------------------------- 75 | # API key for reverse-geo lookups 76 | # 77 | 78 | # OTR_GEOKEY="" 79 | 80 | # ----------------------------------------------------- 81 | # Reverse geo precision 82 | # 83 | 84 | # OTR_PRECISION=7 85 | 86 | # ----------------------------------------------------- 87 | # Browser API key for Google maps 88 | # 89 | # OTR_BROWSERAPIKEY="" 90 | 91 | # ----------------------------------------------------- 92 | # List of topics for MQTT to subscribe to, blank separated in a string 93 | # 94 | 95 | OTR_TOPICS="owntracks/#" 96 | 97 | -------------------------------------------------------------------------------- /mkpath.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: mkpath.c,v 1.2 2005/06/20 07:14:06 otto Exp $ */ 2 | /* 3 | * Copyright (c) 1983, 1992, 1993 4 | * The Regents of the University of California. All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the University nor the names of its contributors 15 | * may be used to endorse or promote products derived from this software 16 | * without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | /* Code taken directly from mkdir(1). 38 | 39 | * mkpath -- create directories. 40 | * path - path 41 | */ 42 | int 43 | mkpath(char *path) 44 | { 45 | struct stat sb; 46 | char *slash; 47 | int done = 0; 48 | 49 | slash = path; 50 | 51 | while (!done) { 52 | slash += strspn(slash, "/"); 53 | slash += strcspn(slash, "/"); 54 | 55 | done = (*slash == '\0'); 56 | *slash = '\0'; 57 | 58 | if (stat(path, &sb)) { 59 | if (errno != ENOENT || (mkdir(path, 0777) && 60 | errno != EEXIST)) { 61 | warn("%s", path); 62 | return (-1); 63 | } 64 | } else if (!S_ISDIR(sb.st_mode)) { 65 | warnx("%s: %s", path, strerror(ENOTDIR)); 66 | return (-1); 67 | } 68 | 69 | *slash = '/'; 70 | } 71 | 72 | return (0); 73 | } 74 | 75 | -------------------------------------------------------------------------------- /contrib/faces/github2card.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # github2card.py 4 | # Copyright (C) 2016 Jan-Piet Mens 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | 20 | # Usage: github2card username 21 | # 22 | # Read the Github profile of `username' and obtain user's 23 | # name and avatar_url to create a CARD JSON payload to 24 | # be published retained to owntracks/username/device/info 25 | # 26 | # You probably want to do this: 27 | # 28 | # github2card.py jjolie > card.json 29 | # mosquitto_pub -t owntracks/jane/phone/info -r -f card.json 30 | # 31 | # Note: the two commands cannot be piplelined (mosquitto_pub -l) 32 | # because of a bug in mosquitto_pub: https://bugs.eclipse.org/bugs/show_bug.cgi?id=478917 33 | # If you have a newer version it should work fine. 34 | 35 | import requests 36 | import base64 37 | import json 38 | import sys 39 | 40 | def user_profile(username): 41 | url = "https://api.github.com/users/" + username 42 | r = requests.get(url) 43 | if r.status_code != 200: 44 | print("User not found; response:", r) 45 | sys.exit(1) 46 | 47 | profile = json.loads(r.content) 48 | name = profile.get('name') 49 | avatar_url = profile.get('avatar_url', None) 50 | 51 | if name is None: 52 | print("User has no name; stopping") 53 | sys.exit(1) 54 | 55 | if avatar_url is None: 56 | print("No avatar for this user") 57 | sys.exit(1) 58 | 59 | # sizing an avatar works only for "real" avatars; not 60 | # for the github-generated thingies (identicons) 61 | avatar_url = avatar_url + '&size=80' 62 | 63 | r = requests.get(avatar_url) 64 | if r.status_code != 200: 65 | print("Cannot retrieve avatar for this user:", r) 66 | sys.exit(1) 67 | 68 | f = open(username + ".png", "wb") 69 | f.write(r.content) 70 | f.close() 71 | 72 | card = { 73 | '_type': 'card', 74 | 'name' : name, 75 | 'face' : base64.b64encode(r.content).decode('utf-8') 76 | } 77 | 78 | print(json.dumps(card)) 79 | 80 | 81 | if __name__ == '__main__': 82 | (username) = sys.argv[1] 83 | user_profile(username) 84 | -------------------------------------------------------------------------------- /docroot/last/map_leaflet.js: -------------------------------------------------------------------------------- 1 | 2 | function initialize_leaflet() { 3 | var markerLayer; 4 | var markers={}; 5 | var map = L.map('map-canvas').setView([0.0, 0.0], 1); 6 | 7 | L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 8 | attribution: '© OpenStreetMap contributors' 9 | }).addTo(map); 10 | 11 | function osm_map_marker(loc) 12 | { 13 | console.log("Updating marker"); 14 | var id = loc.topic.replace(/\//g, '-'); 15 | 16 | if (!markers.hasOwnProperty(id)) { 17 | markers[id] = L.marker([loc.lat, loc.lon]); 18 | markerLayer.addLayer(markers[id]); 19 | if (loc.face) { 20 | markers[id]._face=loc.face; 21 | } 22 | } 23 | 24 | markers[id].setLatLng({lat: loc.lat, lng: loc.lon}); 25 | loc.face=markers[id]._face; 26 | var description = formatPopup(loc); 27 | 28 | markers[id].bindPopup(description.htmldesc).openPopup() 29 | map.fitBounds(markerLayer.getBounds()); 30 | 31 | } 32 | 33 | 34 | 35 | markerLayer = new L.FeatureGroup(); 36 | map.addLayer(markerLayer); 37 | 38 | ws_go(osm_map_marker); // Connect to websocket and start listening 39 | 40 | } 41 | 42 | function formatPopup(loc) { 43 | var htmldesc=""; 44 | var shortdesc=""; 45 | var s = loc.topic.split('/'); 46 | var username = (s[0]) ? s[1] : s[2]; /* cater for leading / in topic */ 47 | var device = (s[0]) ? s[2] : s[3]; /* cater for leading / in topic */ 48 | 49 | var dt = moment.utc(loc.tst * 1000).local(); 50 | 51 | var userdev = username + "/" + device; 52 | userdev = renames[userdev] ? renames[userdev] : userdev; 53 | userdev = loc.name ? loc.name : userdev; 54 | 55 | var data = { 56 | userdev: userdev, 57 | ghash: loc.ghash ? loc.ghash : 'unknown', 58 | addr: loc.addr, 59 | lat: loc.lat, 60 | lon: loc.lon, 61 | fulldate: dt.format("DD MMM YYYY HH:mm:ss"), 62 | facedata: loc.face, 63 | vel: loc.vel, 64 | cog: loc.cog, 65 | }; 66 | 67 | 68 | if (loc.face) { 69 | htmldesc = htmldesc + "
"; 70 | } 71 | 72 | if (loc.addr) { 73 | htmldesc = htmldesc + "{{userdev}}
{{addr}}
{{ghash}} ({{lat}},{{lon}}) v={{vel}}, c={{cog}} {{fulldate}}"; 74 | shortdesc = "{{{userdev}}} {{addr}}"; 75 | } else { 76 | htmldesc = htmldesc + "{{userdev}}
{{lat}}, {{lon}}
{{ghash}} ({{lat}},{{lon}}) v={{vel}}, c={{cog}} {{fulldate}}"; 77 | shortdesc = shortdesc + "{{{userdev}}} {{lat}},{{lon}}"; 78 | } 79 | 80 | 81 | var result = {} 82 | result.short = Mustache.render(shortdesc, data); 83 | result.htmldesc = Mustache.render(htmldesc, data); 84 | return result; 85 | } 86 | -------------------------------------------------------------------------------- /base64.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This code is public domain software. 4 | 5 | */ 6 | 7 | #include "base64.h" 8 | 9 | #include 10 | #include 11 | 12 | // base64 encoding 13 | // 14 | // buf: binary input data 15 | // size: size of input (bytes) 16 | // return: base64-encoded string (null-terminated) 17 | // memory for output will be allocated here, free it later 18 | // 19 | char* base64_encode(const void* buf, size_t size) 20 | { 21 | static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 22 | 23 | char* str = (char*) malloc((size+3)*4/3 + 1); 24 | 25 | char* p = str; 26 | const unsigned char* q = (const unsigned char*) buf; 27 | size_t i = 0; 28 | 29 | while (i < size) { 30 | int c = q[i++]; 31 | c *= 256; 32 | if (i < size) 33 | c += q[i]; 34 | i++; 35 | 36 | c *= 256; 37 | if (i < size) 38 | c += q[i]; 39 | i++; 40 | 41 | *p++ = base64[(c & 0x00fc0000) >> 18]; 42 | *p++ = base64[(c & 0x0003f000) >> 12]; 43 | 44 | if (i > size + 1) 45 | *p++ = '='; 46 | else 47 | *p++ = base64[(c & 0x00000fc0) >> 6]; 48 | 49 | if (i > size) 50 | *p++ = '='; 51 | else 52 | *p++ = base64[c & 0x0000003f]; 53 | } 54 | 55 | *p = 0; 56 | 57 | return str; 58 | } 59 | 60 | 61 | // single base64 character conversion 62 | // 63 | static int POS(char c) 64 | { 65 | if (c>='A' && c<='Z') return c - 'A'; 66 | if (c>='a' && c<='z') return c - 'a' + 26; 67 | if (c>='0' && c<='9') return c - '0' + 52; 68 | if (c == '+') return 62; 69 | if (c == '/') return 63; 70 | if (c == '=') return -1; 71 | return -2; 72 | } 73 | 74 | // base64 decoding 75 | // 76 | // s: base64 string, must be null-terminated 77 | // data: output buffer for decoded data 78 | // data_len size of decoded data 79 | // return: allocated data buffer 80 | // 81 | void* base64_decode(const char* s, size_t *data_len) 82 | { 83 | const char *p; 84 | unsigned char *q, *data; 85 | int n[4]; 86 | 87 | size_t len = strlen(s); 88 | if (len % 4) 89 | return NULL; 90 | data = (unsigned char*) malloc(len/4*3); 91 | q = (unsigned char*) data; 92 | 93 | for (p = s; *p; ) { 94 | n[0] = POS(*p++); 95 | n[1] = POS(*p++); 96 | n[2] = POS(*p++); 97 | n[3] = POS(*p++); 98 | 99 | if (n[0] == -2 || n[1] == -2 || n[2] == -2 || n[3] == -2) 100 | return NULL; 101 | 102 | if (n[0] == -1 || n[1] == -1) 103 | return NULL; 104 | 105 | if (n[2] == -1 && n[3] != -1) 106 | return NULL; 107 | 108 | q[0] = (n[0] << 2) + (n[1] >> 4); 109 | if (n[2] != -1) 110 | q[1] = ((n[1] & 15) << 4) + (n[2] >> 2); 111 | if (n[3] != -1) 112 | q[2] = ((n[2] & 3) << 6) + n[3]; 113 | q += 3; 114 | } 115 | 116 | *data_len = q-data - (n[2]==-1) - (n[3]==-1); 117 | 118 | return data; 119 | } 120 | -------------------------------------------------------------------------------- /geohash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * geohash.h 3 | * libgeohash 4 | * 5 | * Created by Derek Smith on 10/6/09. 6 | * Copyright (c) 2010, SimpleGeo 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, this list 13 | * of conditions and the following disclaimer. Redistributions in binary form must 14 | * reproduce the above copyright notice, this list of conditions and the following 15 | * disclaimer in the documentation and/or other materials provided with the distribution. 16 | * Neither the name of the SimpleGeo nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without specific prior 18 | * written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 22 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 23 | * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 25 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 28 | * OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | // Metric in meters 32 | typedef struct GeoBoxDimensionStruct { 33 | 34 | double height; 35 | double width; 36 | 37 | } GeoBoxDimension; 38 | 39 | typedef struct GeoCoordStruct { 40 | 41 | double latitude; 42 | double longitude; 43 | 44 | double north; 45 | double east; 46 | double south; 47 | double west; 48 | 49 | GeoBoxDimension dimension; 50 | 51 | } GeoCoord; 52 | 53 | /* 54 | * Creates a the hash at the specified precision. If precision is set to 0. 55 | * or less than it defaults to 12. 56 | */ 57 | extern char* geohash_encode(double lat, double lng, int precision); 58 | 59 | /* 60 | * Returns the latitude and longitude used to create the hash along with 61 | * the bounding box for the encoded coordinate. 62 | */ 63 | extern GeoCoord geohash_decode(char* hash); 64 | 65 | /* 66 | * Return an array of geohashes that represent the neighbors of the passed 67 | * in value. The neighbors are indexed as followed: 68 | * 69 | * N, NE, E, SE, S, SW, W, NW 70 | * 0, 1, 2, 3, 4, 5, 6, 7 71 | */ 72 | extern char** geohash_neighbors(char* hash); 73 | 74 | /* 75 | * Returns the width and height of a precision value. 76 | */ 77 | extern GeoBoxDimension geohash_dimensions_for_precision(int precision); -------------------------------------------------------------------------------- /doc/FENCES.md: -------------------------------------------------------------------------------- 1 | ## Geo fences 2 | 3 | Since version > 0.6.9, Recorder has compiled-in support for Geofences irrespective of their use on the OwnTracks devices. (What we mean by this is that OwnTracks iOS/Android apps do native geofences, but if you have some device which just publishes its location in OwnTracks JSON format, you can now process geofences for it with the Recorder.) 4 | 5 | In particular, Recorder can read a list of fences from `.otrw` files, and it will monitor a user's position to determine whether the user is transitioning in to or out of a fence, in which case Recorder will invoke a Lua hook (called `otr_transition`) upon detecting such a transition event. This Lua hook function is one which you provide, and it could, say, publish a new message to an MQTT broker, invoke a REST API, etc. 6 | 7 | ### `.otrw` 8 | 9 | Recorder reads `.otrw` files from `/waypoints/user/device/user-device.otrw` (for all existing globs of user and device, so `/waypoints/*/*/*-*.otrw`) upon startup and loads these into an internal LMDB database. Each waypoint (geo fence) is keyed by `user-device-geohash(lat,lon)` in the LMDB sub table. In addition, when Recorder receives a waypoint dump (say, from an OwnTracks device), it will also inspect said dump and merge new waypoints for the user/device into this database. 10 | 11 | 12 | For example, the following otrw 13 | 14 | ```json 15 | { 16 | "_type": "waypoints", 17 | "waypoints": [ 18 | { 19 | "_type": "waypoint", 20 | "tst": 9999, 21 | "lat": 48.85833, 22 | "lon": 3.29513, 23 | "rad": 1000, 24 | "desc": "chez Madelaine" 25 | } 26 | ] 27 | } 28 | ``` 29 | 30 | is read in as 31 | 32 | ``` 33 | $ ocat -S jp --dump=wp 34 | uno-lua-u0dmfyrkqh {"lat":48.85833,"lon":3.29513,"rad":1000,"desc":"chez Madelaine","io":false} 35 | ``` 36 | 37 | Note how the `io` (in / out) element in the JSON indicates whether the last position reported was in or out of the fence. 38 | 39 | ### Transition hook 40 | 41 | When Recorder determines that the user's device has entered or left the geo fence, it invokes a user-provided Lua function called `otr_transition()`: 42 | 43 | ```lua 44 | function otr_init() 45 | end 46 | 47 | function otr_exit() 48 | end 49 | 50 | function otr_transition(topic, _type, data) 51 | print("IN TRANSITION " .. _type .. " " .. topic) 52 | otr.publish('special/topic', data['event'] .. " " .. data['desc']) 53 | end 54 | ``` 55 | 56 | `topic` is the topic on which the message was originally received, `_type` is `"transition"` and `data` is a Lua table with the full payload plus merged data for the event. 57 | 58 | In addition to the payload as described in the Booklet, recorder enhances the table passed to the Lua function with the following elements: 59 | 60 | - `dist` the distance in meters between the current position and the center of the waypoint 61 | - `wplat` / `wplon` are the latitude / longitude of the original waypoint definition 62 | 63 | The special user and device pair (`_`, `_`) is meant as a global store of waypoints; these are shared throughout all users. So any user with any device can trigger a geo fence contained in `_-_.otrw`. 64 | -------------------------------------------------------------------------------- /docroot/map/map_google.js: -------------------------------------------------------------------------------- 1 | // __author__ = 'Jan-Piet Mens ' 2 | // __copyright__ = 'Copyright 2015 Jan-Piet Mens' 3 | 4 | // https://developers.google.com/maps/documentation/javascript/datalayer 5 | 6 | var infowindow; 7 | 8 | function processPoints(geometry, callback, thisArg) { 9 | if (geometry instanceof google.maps.LatLng) { 10 | callback.call(thisArg, geometry); 11 | } else if (geometry instanceof google.maps.Data.Point) { 12 | callback.call(thisArg, geometry.get()); 13 | } else { 14 | geometry.getArray().forEach(function(g) { 15 | processPoints(g, callback, thisArg); 16 | }); 17 | } 18 | } 19 | 20 | function initialize_googlemaps() { 21 | 22 | var map; 23 | var center = new google.maps.LatLng( 46.993665, 10.399188); 24 | 25 | infowindow = new google.maps.InfoWindow(); 26 | 27 | mapOptions = { 28 | center: center, 29 | zoom: 5, 30 | mapTypeId: google.maps.MapTypeId.ROADMAP, 31 | scrollwheel: false, 32 | disableDefaultUI: false, 33 | panControl: false, 34 | scaleControl: false, 35 | streetViewControl: true, 36 | overviewMapControl: true, 37 | }; 38 | 39 | map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions); 40 | 41 | var dataURL = location.protocol + "//" + location.host; 42 | 43 | var parts = location.pathname.split('/'); 44 | for (var i = 1; i < parts.length - 2; i++) { 45 | dataURL = dataURL + "/" + parts[i]; 46 | } 47 | dataURL = dataURL + "/api/0/locations" + location.search; 48 | 49 | console.log("dataURL = " + dataURL); 50 | 51 | map.data.loadGeoJson(dataURL); 52 | 53 | var circle ={ 54 | path: google.maps.SymbolPath.CIRCLE, 55 | fillColor: '#ff0000', 56 | fillOpacity: .9, 57 | scale: 5.5, 58 | strokeColor: 'white', 59 | strokeWeight: 2 60 | }; 61 | var featureStyle = { 62 | strokeColor: 'red', 63 | strokeWeight: 4, 64 | icon: circle 65 | }; 66 | map.data.setStyle(featureStyle); 67 | 68 | // Zoom to show all the features 69 | var bounds = new google.maps.LatLngBounds(); 70 | map.data.addListener('addfeature', function (e) { 71 | processPoints(e.feature.getGeometry(), bounds.extend, bounds); 72 | map.fitBounds(bounds); 73 | }); 74 | 75 | // click dot on map to show info 76 | map.data.addListener('click', function(event) { 77 | var tst = event.feature.getProperty('tst'); 78 | var dt = moment.utc(tst * 1000).local(); 79 | var data = { 80 | lat: event.latLng.lat().toFixed(4), 81 | lon: event.latLng.lng().toFixed(4), 82 | tst: tst, 83 | addr: event.feature.getProperty('address'), 84 | fulldate: dt.format("DD MMM YYYY HH:mm:ss"), 85 | vel: event.feature.getProperty('vel'), 86 | }; 87 | 88 | var t = "{{ addr }}
({{ lat }},{{lon}}) vel={{vel}} {{ fulldate }}"; 89 | if (typeof(tst) === 'undefined') { 90 | t = "Position: {{lat}}, {{lon}}"; 91 | } 92 | 93 | infowindow.setContent(Mustache.render(t, data)); 94 | infowindow.setPosition(event.latLng); 95 | infowindow.open(map); 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /fences.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "json.h" 4 | #include "udata.h" 5 | #include "fences.h" 6 | #include "gcache.h" 7 | #include "util.h" 8 | #ifdef WITH_LUA 9 | # include "hooks.h" 10 | #endif 11 | 12 | /* 13 | * OwnTracks Recorder 14 | * Copyright (C) 2015-2020 Jan-Piet Mens 15 | * 16 | * This program is free software; you can redistribute it and/or 17 | * modify it under the terms of the GNU General Public License 18 | * as published by the Free Software Foundation; either version 2 19 | * of the License, or (at your option) any later version. 20 | * 21 | * This program is distributed in the hope that it will be useful, 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | * GNU General Public License for more details. 25 | * 26 | * You should have received a copy of the GNU General Public License 27 | * along with this program; if not, write to the Free Software 28 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 29 | */ 30 | 31 | /* 32 | * lat/lon are the position that has been reported by the device as it is at 33 | * now, and wp contains the point read from the list of waypoints. Calculate 34 | * distance betwee the two, and if that's less than waypoint radius, user is 35 | * now IN the geofence, else OUT of the geofence. Indicate transition by 36 | * changing IO in wp and telling caller to store. 37 | */ 38 | 39 | static int check_a_waypoint(char *key, wpoint *wp, double lat, double lon) 40 | { 41 | double dist; 42 | bool rewrite = false; 43 | 44 | dist = haversine_dist(wp->lat, wp->lon, lat, lon); 45 | // printf("WP= rad=%ld, io=%d, dist=%lf %s\n", wp->rad, wp->io, dist, wp->desc); 46 | 47 | if (dist < wp->rad) { 48 | // printf("YEAH: key(%s)\n", key); 49 | 50 | if (wp->io == false) { 51 | wp->event = ENTER; 52 | wp->io = true; 53 | rewrite = true; 54 | } 55 | } else if (wp->io == true) { 56 | wp->event = LEAVE; 57 | wp->io = false; 58 | rewrite = true; 59 | } 60 | 61 | if (rewrite) { 62 | // printf("%s - %s: EVENT == %s\n", wp->user, wp->device, wp->event == 0 ? "ENTER" : "LEAVE"); 63 | #ifdef WITH_LUA 64 | if (wp->ud->luadata) { 65 | hooks_transition(wp->ud, wp->user, wp->device, wp->event, wp->desc, wp->lat, wp->lon, lat, lon, wp->topic, wp->json, (long)dist); 66 | } 67 | #endif /* WITH_LUA */ 68 | } 69 | 70 | return (rewrite); 71 | } 72 | 73 | /* 74 | * Every time a position is obtained, calculate the distance to the center 75 | * of each geofence and check whether that distance is less than the radius 76 | * of the geofence. If the distance is <= to the radius, the obtained position 77 | * is considered to be inside the geofence. This position is calculated using 78 | * the Haversine formula. 79 | */ 80 | 81 | void check_fences(struct udata *ud, char *username, char *device, double lat, double lon, JsonNode *json, char *topic) 82 | { 83 | static UT_string *userdev; 84 | 85 | utstring_renew(userdev); 86 | utstring_printf(userdev, "%s-%s", username, device); 87 | 88 | /* 89 | * For each of this user's geofences (username-device-*) obtain lat/lon/rad 90 | * and do as described above. 91 | */ 92 | 93 | gcache_enum(username, device, ud->wpdb, UB(userdev), check_a_waypoint, lat, lon, ud, topic, json); 94 | } 95 | -------------------------------------------------------------------------------- /docroot/map/map_leaflet.js: -------------------------------------------------------------------------------- 1 | 2 | function initialize_leaflet() { 3 | var map = L.map('map-canvas').setView([0.0, 0.0], 1); 4 | 5 | L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 6 | attribution: '© OpenStreetMap contributors' 7 | }).addTo(map); 8 | 9 | 10 | var dataURL = location.protocol + "//" + location.host; 11 | 12 | var parts = location.pathname.split('/'); 13 | for (var i = 1; i < parts.length - 2; i++) { 14 | dataURL = dataURL + "/" + parts[i]; 15 | } 16 | dataURL = dataURL + "/api/0/locations" + location.search; 17 | 18 | console.log("dataURL = " + dataURL); 19 | 20 | var geojsonMarkerOptions = { 21 | radius: 5, 22 | fillColor: "#ff0000", 23 | color: "#ffffff", 24 | weight: 2, 25 | opacity: 1, 26 | fillOpacity: 0.9 27 | }; 28 | 29 | var empty_geojson = { 30 | type: "FeatureCollection", 31 | features: [] 32 | }; 33 | var lastLatLng = L.latLng(0.0, 0.0); 34 | 35 | var geojsonLayer = new L.GeoJSON(empty_geojson, { 36 | pointToLayer: function (feature, latlng) { 37 | return L.circleMarker(latlng, geojsonMarkerOptions); 38 | }, 39 | onEachFeature: function(feature, layer) { 40 | if (feature.geometry.type == 'Point') { 41 | var data ={}; 42 | data.address = feature.properties.address; 43 | data.lat = feature.geometry.coordinates[1].toFixed(5); 44 | data.lon = feature.geometry.coordinates[0].toFixed(5); 45 | data.vel = feature.properties.vel; 46 | data.acc = feature.properties.acc; 47 | data.tst = feature.properties.tst; 48 | var localtime = moment.utc(data.tst * 1000).local(); 49 | data.timestring = localtime.format('YYYY-MM-DD, ddd, HH:mm:ss Z'); 50 | 51 | var text = []; 52 | data.accstring = ""; 53 | if(data.timestring) {text.push('{{ timestring }}')} 54 | if(data.acc !== undefined) {data.accstring="acc: " + data.acc}; 55 | if(data.lat && data.lon) {text.push('{{ lat }},{{ lon }} {{ accstring }}')} 56 | if(data.address) {text.push('{{ address }}')} 57 | if(data.vel !== undefined) {text.push('{{ vel }} km/h')} 58 | layer.bindPopup(Mustache.render(text.join('
'), data)); 59 | } 60 | }, 61 | style : function(feature) { 62 | if (feature.geometry.type == 'Point') { 63 | return {} 64 | } else { 65 | return { 66 | color : "#ff0000", 67 | weight : 4, 68 | } 69 | } 70 | }, 71 | coordsToLatLng: function(coords) { 72 | var lat = coords[1]; 73 | var lon = coords[0]; 74 | var dist0 = Math.abs(lon - lastLatLng.lng); 75 | var dist1 = Math.abs(lon + 360.0 - lastLatLng.lng); 76 | var dist2 = Math.abs(lon - 360.0 - lastLatLng.lng); 77 | if (dist0 > dist1 || dist0 > dist2) { 78 | if (dist0 > dist1) { 79 | lon = lon + 360.0; 80 | } else { 81 | lon = lon - 360.0; 82 | } 83 | } 84 | var latLng = L.GeoJSON.coordsToLatLng([lon, lat]); 85 | lastLatLng = latLng; 86 | return latLng 87 | } 88 | }); 89 | 90 | map.addLayer(geojsonLayer); 91 | 92 | $.getJSON( dataURL, function( data ) { 93 | geojsonLayer.addData(data) 94 | map.fitBounds(geojsonLayer.getBounds()); 95 | }); 96 | 97 | } 98 | -------------------------------------------------------------------------------- /config.mk.in: -------------------------------------------------------------------------------- 1 | # Select features you want. Default is fine for most installations 2 | 3 | # Uncomment the following for FreeBSD; this assumes: 4 | # 5 | # The following ports are required for building recorder: 6 | # 7 | # databases/lmdb ftp/curl devel/libconfig net/mosquitto devel/gmake 8 | # 9 | # You can install them via the ports collection as: 10 | # 11 | # $ cd /usr/ports/databases/lmdb; make install 12 | # $ cd /usr/ports/ftp/curl; make install 13 | # $ cd /usr/ports/devel/libconfig; make install 14 | # $ cd /usr/ports/net/mosquitto; make install 15 | # $ cd /usr/ports/devel/gmake; make install 16 | # 17 | # Or as a package: 18 | # 19 | # $ sudo pkg install lmdb curl libconfig mosquitto gmake 20 | # 21 | # Uncomment the following lines and replace "no" with "yes": 22 | # 23 | # CC = clang 24 | # CFLAGS += -I/usr/local/include 25 | # MORELIBS += -L /usr/local/lib 26 | FREEBSD ?= no 27 | # 28 | # Now build recorder with the "gmake" command: 29 | # 30 | # $ gmake 31 | # 32 | # -- end FreeBSD 33 | 34 | INSTALLDIR = /usr/local 35 | 36 | # Do you want support for MQTT? 37 | WITH_MQTT ?= yes 38 | 39 | # Do you want recorder's built-in HTTP REST API? 40 | WITH_HTTP ?= yes 41 | 42 | # Do you have Lua libraries installed and want the Lua hook integration? 43 | WITH_LUA ?= no 44 | 45 | # Do you want support for the `pingping' monitoring feature? 46 | WITH_PING ?= yes 47 | 48 | # Do you want support for removing data via the API? (Dangerous) 49 | WITH_KILL ?= no 50 | 51 | # Do you want support for payload encryption with libsodium? 52 | # This requires WITH_LMDB to be configured. 53 | WITH_ENCRYPT ?= no 54 | 55 | # Do you require support for OwnTracks Greenwich firmware? 56 | WITH_GREENWICH ?= no 57 | 58 | # Where should the recorder store its data? This directory must 59 | # exist and be writeable by recorder (and readable by ocat) 60 | STORAGEDEFAULT = /var/spool/owntracks/recorder/store 61 | 62 | # Where should the recorder find its document root (HTTP)? 63 | DOCROOT = /var/spool/owntracks/recorder/htdocs 64 | 65 | # Define the precision for reverse-geo lookups. The higher 66 | # the number, the more granular reverse-geo will be: 67 | # 68 | # 1 => 5,009.4km x 4,992.6km 69 | # 2 => 1,252.3km x 624.1km 70 | # 3 => 156.5km x 156km 71 | # 4 => 39.1km x 19.5km 72 | # 5 => 4.9km x 4.9km 73 | # 6 => 1.2km x 609.4m 74 | # 7 => 152.9m x 152.4m 75 | # 8 => 38.2m x 19m 76 | # 9 => 4.8m x 4.8m 77 | # 10 => 1.2m x 59.5cm 78 | 79 | GHASHPREC = 7 80 | 81 | # Should the JSON emitted by recorder be indented? (i.e. beautified) 82 | # yes or no 83 | JSON_INDENT ?= no 84 | 85 | # Location of optional default configuration file 86 | CONFIGFILE = /etc/default/ot-recorder 87 | 88 | # Optionally specify the path to the Mosquitto libs, include here 89 | MOSQUITTO_INC = -I/usr/include 90 | MOSQUITTO_LIB = -L/usr/lib 91 | MORELIBS += # -lssl 92 | 93 | # Milliseconds (ms) timeout for reverse geocoding 94 | GEOCODE_TIMEOUT = 4000 95 | 96 | # If WITH_LUA is configured, specify compilation and linkage flags 97 | # for Lua either manually or using pkg-config. This may require tweaking, 98 | # and in particular could require you to add the lua+version (e.g lua-5.2) 99 | # to both pkg-config invocations 100 | 101 | LUA_CFLAGS = `pkg-config --cflags lua` 102 | LUA_LIBS = `pkg-config --libs lua` 103 | 104 | SODIUM_CFLAGS = `pkg-config --cflags libsodium` 105 | SODIUM_LIBS = `pkg-config --libs libsodium` 106 | -------------------------------------------------------------------------------- /docroot/last/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Recorder Map 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 81 | 82 | 83 | 84 |
85 |
86 |
87 |
88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | The Recorder's API provides most of the functions that are surfaced by _ocat_. GET and POST requests are supported, and if a username and device are needed, these can be passed in via `X-Limit-User` and `X-Limit-Device` headers alternatively to GET or POST parameters. (From and To dates may also be specified as `X-Limit-From` and `X-Limit-To` respectively.) 4 | 5 | The API endpoint is at `/api/0` and is followed by the verb. 6 | 7 | ## `monitor` 8 | 9 | Returns the content of the `monitor` file as plain text. 10 | 11 | ``` 12 | curl 'http://127.0.0.1:8083/api/0/monitor' 13 | 1441962082 owntracks/jjolie/phone 14 | ``` 15 | 16 | ## `last` 17 | 18 | Returns a list of last users' positions. (Can be limited by _user_, _device_, and _fields_, a comma-separated list of fields which should be returned instead of the default of all fields.) 19 | 20 | ``` 21 | curl http://127.0.0.1:8083/api/0/last [-d user=jjolie [-d device=phone]] 22 | ``` 23 | 24 | ``` 25 | curl 'http://127.0.0.1:8083/api/0/last?fields=tst,tid,addr,topic,isotst' 26 | ``` 27 | 28 | ## `list` 29 | 30 | List users. If _user_ is specified, lists that user's devices. If both _user_ and _device_ are specified, lists that device's `.rec` files. 31 | 32 | ## `locations` 33 | 34 | Here comes the actual data. This lists users' locations and requires both _user_ and _device_. Output format is JSON unless a different _format_ is given (`csv`, `json`, `geojson`, `xml`, and `linestring` are supported). 35 | 36 | In order to limit the number of records returned, use _limit_ which causes a reverse search through the `.rec` files; this can be used to find the last N positions. 37 | 38 | Date/time ranges may be specified as _from_ and _to_ with dates/times specified as described for _ocat_ above. 39 | 40 | ``` 41 | curl http://127.0.0.1:8083/api/0/locations -d user=jpm -d device=5s 42 | curl http://127.0.0.1:8083/api/0/locations -d user=jpm -d device=5s -d limit=1 43 | curl http://127.0.0.1:8083/api/0/locations -d user=jpm -d device=5s -d format=geojson 44 | curl http://127.0.0.1:8083/api/0/locations -d user=jpm -d device=5s -d from=2014-08-03 45 | curl 'http://127.0.0.1:8083/api/0/locations?from=2015-09-01&user=jpm&device=5s&fields=tst,tid,addr,isotst' 46 | ``` 47 | 48 | ## `q` 49 | 50 | Query the geo cache for a particular _lat_ and _lon_. 51 | 52 | ``` 53 | curl 'http://127.0.0.1:8083/api/0/q?lat=48.85833&lon=2.295' 54 | { 55 | "cc": "FR", 56 | "addr": "9 Avenue Anatole France, 75007 Paris, France", 57 | "tst": 1441984405 58 | } 59 | ``` 60 | 61 | The reported timestamp was the time at which this cache entry was made. Note that this interface queries only -- it does not populate the cache. 62 | 63 | ## `photo` 64 | 65 | Requires GET method and _user_, and will return the `image/png` 40x40px photograph of a user if available in `STORAGEDIR/photos/` or a transparent 40x40png with a black border otherwise. 66 | 67 | ## `kill` 68 | 69 | If support for this is compiled in, this API endpoint allows a client to remove data from _storage_. (Warning: *any* client can do this, as there is no authentication/authorization in the Recorder!) 70 | 71 | ``` 72 | curl 'http://127.0.0.1:8083/api/0/kill?user=ngin&device=ojo' 73 | 74 | { 75 | "path": "s0/rec/ngin/ojo", 76 | "status": "OK", 77 | "last": "s0/last/ngin/ojo/ngin-ojo.json", 78 | "killed": [ 79 | "2015-09.rec", 80 | ] 81 | } 82 | ``` 83 | The response contains a list of removed `.rec` files, and file system operations are logged to syslog. 84 | 85 | ## `version` 86 | 87 | Returns a JSON object which contains the Recorder's version string, such as 88 | 89 | ```json 90 | { "version": "0.4.7" } 91 | ``` 92 | 93 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | CFLAGS +=-Wall -Werror 4 | LIBS = $(MORELIBS) -lm 5 | LIBS += -lcurl -lconfig 6 | 7 | TARGETS= 8 | OTR_OBJS = json.o \ 9 | gcache.o \ 10 | geo.o \ 11 | geohash.o \ 12 | mkpath.o \ 13 | base64.o \ 14 | misc.o \ 15 | util.o \ 16 | storage.o \ 17 | fences.o \ 18 | listsort.o 19 | OTR_EXTRA_OBJS = 20 | 21 | CFLAGS += -DGHASHPREC=$(GHASHPREC) 22 | 23 | LIBS += -llmdb 24 | LIBS += -lpthread 25 | 26 | ifeq ($(WITH_MQTT),yes) 27 | CFLAGS += -DWITH_MQTT=1 28 | CFLAGS += $(MOSQUITTO_INC) 29 | LIBS += $(MOSQUITTO_LIB) -lmosquitto -lm 30 | endif 31 | 32 | ifeq ($(WITH_PING),yes) 33 | CFLAGS += -DWITH_PING=1 34 | endif 35 | 36 | ifeq ($(WITH_LUA),yes) 37 | CFLAGS += -DWITH_LUA=1 $(LUA_CFLAGS) 38 | LIBS += $(LUA_LIBS) 39 | OTR_OBJS += hooks.o 40 | endif 41 | 42 | ifeq ($(WITH_ENCRYPT),yes) 43 | CFLAGS += -DWITH_ENCRYPT=1 $(SODIUM_CFLAGS) 44 | LIBS += $(SODIUM_LIBS) 45 | endif 46 | 47 | ifeq ($(WITH_KILL),yes) 48 | CFLAGS += -DWITH_KILL=1 49 | endif 50 | 51 | ifeq ($(WITH_HTTP),yes) 52 | CFLAGS += -DWITH_HTTP=1 53 | OTR_EXTRA_OBJS += mongoose.o http.o 54 | endif 55 | 56 | ifeq ($(WITH_GREENWICH),yes) 57 | CFLAGS += -DWITH_GREENWICH=1 58 | endif 59 | 60 | ifeq ($(JSON_INDENT),yes) 61 | CFLAGS += -DJSON_INDENT="\" \"" 62 | else 63 | CFLAGS += -DJSON_INDENT=NULL 64 | endif 65 | 66 | CFLAGS += -DSTORAGEDEFAULT=\"$(STORAGEDEFAULT)\" -DDOCROOT=\"$(DOCROOT)\" 67 | CFLAGS += -DCONFIGFILE=\"$(CONFIGFILE)\" 68 | CFLAGS += -DGEOCODE_TIMEOUT=$(GEOCODE_TIMEOUT) 69 | 70 | TARGETS += ot-recorder ocat 71 | 72 | GIT_VERSION := $(shell git describe --long --abbrev=10 --dirty --tags 2>/dev/null || echo "tarball") 73 | CFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\" 74 | 75 | all: $(TARGETS) 76 | 77 | ot-recorder: recorder.o $(OTR_OBJS) $(OTR_EXTRA_OBJS) 78 | $(CC) $(CFLAGS) -o ot-recorder recorder.o $(OTR_OBJS) $(OTR_EXTRA_OBJS) $(LIBS) 79 | if test -r codesign.sh; then /bin/sh codesign.sh; fi 80 | 81 | ocat: ocat.o $(OTR_OBJS) 82 | $(CC) $(CFLAGS) -o ocat ocat.o $(OTR_OBJS) $(LIBS) 83 | 84 | $(OTR_OBJS): config.mk Makefile 85 | 86 | recorder.o: recorder.c storage.h util.h Makefile geo.h udata.h json.h http.h gcache.h config.mk hooks.h base64.h recorder.h version.h fences.h 87 | geo.o: geo.h geo.c udata.h 88 | geohash.o: geohash.h geohash.c udata.h 89 | base64.o: base64.h base64.c 90 | $(CC) $(CFLAGS) -Wno-unused-result -Wno-uninitialized -c base64.c 91 | gcache.o: gcache.c gcache.h json.h 92 | misc.o: misc.c misc.h udata.h 93 | http.o: http.c mongoose.h util.h http.h storage.h version.h hooks.h 94 | util.o: util.c util.h 95 | mongoose.o: mongoose.c mongoose.h 96 | ocat.o: ocat.c storage.h util.h version.h config.mk Makefile 97 | storage.o: storage.c storage.h util.h gcache.h listsort.h 98 | hooks.o: hooks.c udata.h hooks.h util.h version.h gcache.h 99 | listsort.o: listsort.c listsort.h 100 | fences.o: fences.c fences.h util.h json.h udata.h gcache.h hooks.h 101 | 102 | 103 | clean: 104 | rm -f *.o 105 | clobber: clean 106 | rm -f ot-recorder ocat 107 | 108 | install: ot-recorder ocat 109 | mkdir -p $(DESTDIR)$(INSTALLDIR)/bin 110 | mkdir -p $(DESTDIR)$(INSTALLDIR)/sbin 111 | mkdir -p $(DESTDIR)$(DOCROOT) 112 | mkdir -p $(DESTDIR)$(STORAGEDEFAULT)/last 113 | cp -R docroot/* $(DESTDIR)$(DOCROOT)/ 114 | install -m 0755 ot-recorder $(DESTDIR)$(INSTALLDIR)/sbin 115 | install -m 0755 ocat $(DESTDIR)$(INSTALLDIR)/bin 116 | mkdir -p `dirname $(DESTDIR)/$(CONFIGFILE)` 117 | test -r $(DESTDIR)/$(CONFIGFILE) || install -m 640 etc/ot-recorder.default $(DESTDIR)/$(CONFIGFILE) 118 | ifndef DESTDIR 119 | $(INSTALLDIR)/sbin/ot-recorder --initialize 120 | endif 121 | # mkdir -p $(DESTDIR)/etc/systemd/system/ 122 | # install --mode 0644 etc/ot-recorder.service $(DESTDIR)/etc/systemd/system/ot-recorder.service 123 | -------------------------------------------------------------------------------- /doc/STORE.md: -------------------------------------------------------------------------------- 1 | # Storage 2 | 3 | As mentioned earlier, data is stored in files, and these files are relative to `STORAGEDIR` (compiled into the programs or specified as an option). In particular, the following directory structure can exist, whereby directories are created as needed by the Recorder: 4 | 5 | * `cards/`, optional, may contains user cards. This card is then stored here and used with, e.g., `ocat --last` to show a user's name and optional avatar. User cards are typically stored in a subdirectory called `username`, and therein a JSON file `[username].json`. When reading cards, the Recorder will first attempt to open `[username]/[device]/[username].json` and then `[username]/[username].json`. 6 | * `config/`, optional, contains the JSON of a [device configuration](http://owntracks.org/booklet/features/remoteconfig/) (`.otrc`) which was requested remotely via a [dump command](http://owntracks.org/booklet/tech/json/#_typecmd). Note that this will contain sensitive data. You can use this `.otrc` file to restore the OwnTracks configuration on your device by copying to the device and opening it in OwnTracks. 7 | * `ghash/`, unless disabled, reverse Geo data (using a Google service) is collected into an LMDB database located in this directory. This LMDB database also contains named databases which are used by your optional Lua hooks, as well as a `topic2tid` database which can be used for TID re-mapping. 8 | * `last/` contains the last location published by devices. E.g. Jane's last publish from her iPhone would be in `last/jjolie/iphone/jjolie-iphone.json`. The JSON payload contained therein is enhanced with the fields `user`, `device`, `topic`, and `ghash`. If a device's `last/` directory contains a file called `extra.json` (i.e. matching the example, this would be `last/jjolie/iphone/extra.json`), the content of this file is merged into the existing JSON for this user and returned by the API. Note, that you cannot overwrite existing values. So, an `extra.json` containing `{ "tst" : 11 }` will do nothing because the `tst` element we obtain from location data overrules, but adding `{ "beverage" : "water" }` will do what you want. If Recorder is built with support for our Greenwich firmware, this directory might contain `batt.json`, `ext.json`, and/or `status.json` each of which hold an array of the last 100 reports for internal battery voltage, external voltage, and status respectively. These values are returned via the API in the LAST object. A file `http.json` which should contain either a single JSON object or an array of JSON objects is returned to clients in HTTP mode. 9 | * `monitor` a file which contains a timestamp and the last received topic (see Monitoring below). 10 | * `msg/` contains messages received by the Messaging system. 11 | * `photos/` optional; contains the binary photos from a card. 12 | * `rec/` the Recorder data proper. One subdirectory per user, one subdirectory therein per device. Data files are named `YYYY-MM.rec` (e.g. `2015-08.rec` for the data accumulated during the month of August 2015. The content is a time stamp obtained from `tst` (or _now_, i.e. `time(0)` if there is no `tst` in the payload) followed by record type and message payload. 13 | * `waypoints/` contains a directory per user and device. Therein are individual files named by a timestamp with the JSON payload of published (i.e. shared) waypoints. The file names are timestamps because the `tst` of a waypoint is its key. If a user publishes all waypoints from a device (Publish Waypoints), the payload is stored in this directory as `username-device.otrw`. (Note, that this is the JSON [waypoints import format](http://owntracks.org/booklet/tech/json/#_typewaypoints).) You can use this `.otrw` file to restore the waypoints on your device by copying to the device and opening it in OwnTracks. 14 | 15 | You should definitely **not** modify or touch these files: they remain under the control of the Recorder. You can of course, remove old `.rec` files if they consume too much space. 16 | -------------------------------------------------------------------------------- /contrib/node-red.md: -------------------------------------------------------------------------------- 1 | Glyn [writes](https://github.com/owntracks/recorder/issues/331): 2 | 3 | The integration was done in quick and hacky way using OVMS V3 server logging to MQTT then using NodeRED so subscribe to the OVMS GPS MQTT topics and then publish these to owntracks recorder MQTT topics in the correct format. I'm happy to share this NodeRED flow but it's probably not the most eligant solution. 4 | 5 | ```json 6 | [{"id":"89e9d395.f358","type":"mqtt in","z":"d605de2e.9f01e","name":"","topic":"ovms/emonpi//metric/v/p/latitude","qos":"0","broker":"42d1a439.248b0c","x":200,"y":60,"wires":[["a434b606.26e328"]]},{"id":"c9be458f.e2e928","type":"mqtt in","z":"d605de2e.9f01e","name":"","topic":"ovms/emonpi//metric/v/p/longitude","qos":"0","broker":"42d1a439.248b0c","x":200,"y":100,"wires":[["b2f98901.84ec48"]]},{"id":"dd774d3a.647f2","type":"function","z":"d605de2e.9f01e","name":"context.global.lat","func":"context.global.lat = msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":710,"y":80,"wires":[["9656d4f7.b65408"]]},{"id":"3087ba49.6ce996","type":"function","z":"d605de2e.9f01e","name":"context.global.lon","func":"context.global.lon = msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":710,"y":120,"wires":[[]]},{"id":"9656d4f7.b65408","type":"debug","z":"d605de2e.9f01e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":930,"y":80,"wires":[]},{"id":"a434b606.26e328","type":"function","z":"d605de2e.9f01e","name":"ignore 0","func":"var value=msg.payload;\n// if (value !==\"\" && value !==\"0\" && value !==0 );{\nif (value){\n return msg;\n}\n","outputs":1,"noerr":0,"x":480,"y":80,"wires":[["dd774d3a.647f2"]]},{"id":"b2f98901.84ec48","type":"function","z":"d605de2e.9f01e","name":"ignore 0","func":"var value=msg.payload;\n// if (value !==\"\" && value !==\"0\" && value !==0 );{\nif (value){\n return msg;\n}\n","outputs":1,"noerr":0,"x":480,"y":120,"wires":[["3087ba49.6ce996"]]},{"id":"f4ec6fc4.4dcaa","type":"inject","z":"d605de2e.9f01e","name":"","topic":"","payload":"","payloadType":"date","repeat":"60","crontab":"","once":false,"onceDelay":"60","x":90,"y":260,"wires":[["e9f44233.cddf6"]]},{"id":"a61cddc0.6c138","type":"function","z":"d605de2e.9f01e","name":"create object","func":"var owntracks={_type:\"location\",tst:msg.payload,lat:Number(context.global.lat),lon:Number(context.global.lon)};\nvar newMsg = { payload: JSON.stringify(owntracks) };\nreturn newMsg;","outputs":1,"noerr":0,"x":450,"y":260,"wires":[["4b6d9633.eead28","a5474c90.c79c4","55689f77.2efd3"]]},{"id":"4b6d9633.eead28","type":"debug","z":"d605de2e.9f01e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":930,"y":260,"wires":[]},{"id":"a5474c90.c79c4","type":"mqtt out","z":"d605de2e.9f01e","name":"","topic":"owntracks/my/car","qos":"","retain":"","broker":"834ac211.8eba8","x":730,"y":300,"wires":[]},{"id":"55689f77.2efd3","type":"file","z":"d605de2e.9f01e","name":"","filename":"/data/gps-json.csv","appendNewline":true,"createDir":false,"overwriteFile":"false","x":770,"y":200,"wires":[[]]},{"id":"e9f44233.cddf6","type":"function","z":"d605de2e.9f01e","name":"timestamp in s","func":"// msg.payload = msg.payload/1000;\nmsg.payload = Math.floor(Date.now() / 1000);\nreturn msg;","outputs":1,"noerr":0,"x":260,"y":260,"wires":[["a61cddc0.6c138"]]},{"id":"42d1a439.248b0c","type":"mqtt-broker","z":"","name":"emonpi","broker":"IP-ADDRESS","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"834ac211.8eba8","type":"mqtt-broker","z":"","name":"MQTT-SERVER","broker":"IP-ADDRESS","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}] 7 | ``` 8 | -------------------------------------------------------------------------------- /etc/rhel/ot-recorder.spec: -------------------------------------------------------------------------------- 1 | %define version 0.4.2 2 | 3 | Name: ot-recorder 4 | Version: %{version} 5 | Release: 0 6 | Summary: OwnTracks Recorder 7 | Group: Applications/Location 8 | License: MIT 9 | URL: http://owntracks.org/ 10 | Vendor: OwnTracks team@owntracks.org 11 | Source: https://github.com/owntracks/recorder/archive/%{version}.tar.gz 12 | Packager: jpmens 13 | Requires: libmosquitto1 14 | Requires: libcurl 15 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 16 | 17 | %if %{defined suse_version} 18 | Requires: libopenssl1_0_0, libcurl 19 | BuildRequires: libopenssl-devel, libcurl-devel, libmosquitto-devel 20 | %endif 21 | 22 | %if %{defined rhel_version} 23 | Requires: openssl 24 | BuildRequires: openssl-devel, libcurl-devel, libmosquitto-devel 25 | %endif 26 | 27 | %if %{defined centos_version} 28 | Requires: openssl 29 | BuildRequires: openssl-devel, libcurl-devel, libmosquitto-devel 30 | %endif 31 | 32 | %if %{defined fedora_version} 33 | Requires: openssl 34 | BuildRequires: openssl-devel, libcurl-devel, libmosquitto-devel 35 | %endif 36 | 37 | %description 38 | OwnTracks is a location-based service which runs over MQTT. The 39 | OwnTracks Recorder connects to an MQTT broker and stores location 40 | publishes from the OwnTracks apps (iOS, Android) into files which 41 | can be viewed through the supporting ocat utility and via a REST 42 | API provided by the Recorder itself. 43 | 44 | %prep 45 | %setup -n recorder-%{version} 46 | 47 | %build 48 | cp config.mk.in config.mk 49 | make 50 | 51 | %install 52 | ## make install 53 | mkdir -p %{buildroot}%{_bindir} 54 | mkdir -p %{buildroot}%{_sbindir} %{buildroot}%{_datadir}/doc/owntracks 55 | mkdir -p %{buildroot}%{_sbindir} %{buildroot}/var/spool/owntracks/recorder/htdocs 56 | cp -R docroot/* %{buildroot}/var/spool/owntracks/recorder/htdocs 57 | install --strip --mode 0755 ot-recorder %{buildroot}%{_sbindir} 58 | install --strip --mode 0755 ocat %{buildroot}%{_bindir} 59 | mkdir -p %{buildroot}%{_sysconfdir}/default 60 | mkdir -p %{buildroot}%{_sysconfdir}/init.d 61 | cp etc/rhel/ot-recorder.defaults %{buildroot}%{_sysconfdir}/default/ot-recorder 62 | install --mode 0755 etc/rhel/ot-recorder.init %{buildroot}%{_sysconfdir}/init.d/ot-recorder 63 | install --mode 0444 README.md %{buildroot}%{_datadir}/doc/owntracks 64 | mkdir -p %{buildroot}/var/spool/owntracks/recorder/store 65 | 66 | # Build a manifest of the RPM's directory hierarchy. 67 | echo "%%defattr(-, root, root)" >MANIFEST 68 | (cd %{buildroot}; find . -type f -or -type l | sed -e s/^.// -e /^$/d) >>MANIFEST 69 | 70 | %clean 71 | [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT MANIFEST 72 | 73 | # %defattr(-,root,owntracks) 74 | %files -f MANIFEST 75 | %config /var/spool/owntracks/recorder/store 76 | %config /etc/default/ot-recorder 77 | 78 | %changelog 79 | 80 | * Mon Sep 14 2015 Jan-Piet Mens 81 | - munge for OBS (thank you, Roger Light, for your help!) 82 | - relocate spool dir 83 | 84 | * Sun Sep 13 2015 Jan-Piet Mens 85 | - remove config.h.example 86 | - use plain config.mk 87 | 88 | * Thu Sep 09 2015 Jan-Piet Mens 89 | - Add owntracks group and chmod to SGID for ocat to be able to open LMDB env 90 | 91 | * Thu Sep 09 2015 Jan-Piet Mens 92 | - initial spec with tremendous thank yous to https://stereochro.me/ideas/rpm-for-the-unwilling 93 | 94 | %post 95 | getent group owntracks > /dev/null || /usr/sbin/groupadd -r owntracks 96 | mkdir -p /var/spool/owntracks/recorder/store/ghash 97 | chmod 775 /var/spool/owntracks/recorder/store/ghash 98 | chgrp owntracks /var/spool/owntracks/recorder/store 99 | chgrp owntracks /var/spool/owntracks/recorder/store/ghash 100 | chgrp owntracks /usr/bin/ocat /usr/sbin/ot-recorder 101 | chmod 3755 /usr/bin/ocat /usr/sbin/ot-recorder 102 | chkconfig --add ot-recorder 103 | -------------------------------------------------------------------------------- /json.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) 3 | All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | 24 | #ifndef CCAN_JSON_H 25 | #define CCAN_JSON_H 26 | 27 | #include 28 | #include 29 | 30 | typedef enum { 31 | JSON_NULL, 32 | JSON_BOOL, 33 | JSON_STRING, 34 | JSON_NUMBER, 35 | JSON_ARRAY, 36 | JSON_OBJECT, 37 | } JsonTag; 38 | 39 | typedef struct JsonNode JsonNode; 40 | 41 | struct JsonNode 42 | { 43 | /* only if parent is an object or array (NULL otherwise) */ 44 | JsonNode *parent; 45 | JsonNode *prev, *next; 46 | 47 | /* only if parent is an object (NULL otherwise) */ 48 | char *key; /* Must be valid UTF-8. */ 49 | 50 | JsonTag tag; 51 | union { 52 | /* JSON_BOOL */ 53 | bool bool_; 54 | 55 | /* JSON_STRING */ 56 | char *string_; /* Must be valid UTF-8. */ 57 | 58 | /* JSON_NUMBER */ 59 | double number_; 60 | 61 | /* JSON_ARRAY */ 62 | /* JSON_OBJECT */ 63 | struct { 64 | JsonNode *head, *tail; 65 | } children; 66 | }; 67 | }; 68 | 69 | /*** Encoding, decoding, and validation ***/ 70 | 71 | JsonNode *json_decode (const char *json); 72 | char *json_encode (const JsonNode *node); 73 | char *json_encode_string (const char *str); 74 | char *json_stringify (const JsonNode *node, const char *space); 75 | void json_delete (JsonNode *node); 76 | 77 | bool json_validate (const char *json); 78 | 79 | /*** Lookup and traversal ***/ 80 | 81 | JsonNode *json_find_element (JsonNode *array, int index); 82 | JsonNode *json_find_member (JsonNode *object, const char *key); 83 | 84 | JsonNode *json_first_child (const JsonNode *node); 85 | 86 | #define json_foreach(i, object_or_array) \ 87 | for ((i) = json_first_child(object_or_array); \ 88 | (i) != NULL; \ 89 | (i) = (i)->next) 90 | 91 | /*** Construction and manipulation ***/ 92 | 93 | JsonNode *json_mknull(void); 94 | JsonNode *json_mkbool(bool b); 95 | JsonNode *json_mkstring(const char *s); 96 | JsonNode *json_mknumber(double n); 97 | JsonNode *json_mkarray(void); 98 | JsonNode *json_mkobject(void); 99 | 100 | void json_append_element(JsonNode *array, JsonNode *element); 101 | void json_prepend_element(JsonNode *array, JsonNode *element); 102 | void json_append_member(JsonNode *object, const char *key, JsonNode *value); 103 | void json_prepend_member(JsonNode *object, const char *key, JsonNode *value); 104 | 105 | void json_remove_from_parent(JsonNode *node); 106 | 107 | /*** Debugging ***/ 108 | 109 | /* 110 | * Look for structure and encoding problems in a JsonNode or its descendents. 111 | * 112 | * If a problem is detected, return false, writing a description of the problem 113 | * to errmsg (unless errmsg is NULL). 114 | */ 115 | bool json_check(const JsonNode *node, char errmsg[256]); 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /contrib/ot-ping.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # OwnTracks Recorder 5 | # Copyright (C) 2015-2016 Jan-Piet Mens 6 | # 7 | # This program is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU General Public License 9 | # as published by the Free Software Foundation; either version 2 10 | # of the License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program; if not, write to the Free Software 19 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | 21 | # ot-ping.py 22 | # send a "pingping" to the OwnTracks Recorder via MQTT with "now" 23 | # in timestamp (tst). Then verify via Recorder's REST interface 24 | # whether the timestamp matches. Print a Nagios/Icinga-compatible 25 | # string to stdout and exit() appropriately. 26 | 27 | import paho.mqtt.publish as mqtt # pip install paho-mqtt 28 | import json 29 | import time 30 | import os 31 | import sys 32 | import requests 33 | 34 | __author__ = 'Jan-Piet Mens ' 35 | __copyright__ = 'Copyright 2015 Jan-Piet Mens' 36 | 37 | hostname = 'localhost' 38 | port = 1883 39 | username = None 40 | password = None 41 | tls = None 42 | qos = 0 43 | retain = 0 44 | 45 | UNKNOWN = -1 46 | OK = 0 47 | WARNING = 1 48 | CRITICAL = 2 49 | 50 | codes = [ 'OK', 'WARNING', 'CRITICAL' ] 51 | 52 | def pingping(tics): 53 | status = OK 54 | msg = "MQTT publish" 55 | 56 | params = { 57 | 'hostname' : hostname, 58 | 'port' : port, 59 | 'qos' : qos, 60 | 'retain' : retain, 61 | 'client_id' : "ot-recorder-ping-ping-%s" % os.getpid(), 62 | } 63 | auth = None 64 | 65 | if username is not None: 66 | auth = { 67 | 'username' : username, 68 | 'password' : password 69 | } 70 | 71 | topic = "owntracks/ping/ping" 72 | 73 | location = { 74 | "_type" : "location", 75 | "tid" : "pp", 76 | "lat" : 51.47879, 77 | "lon" : -0.010677, 78 | "tst" : tics, 79 | } 80 | 81 | topic = "owntracks/ping/ping" 82 | payload = json.dumps(location) 83 | 84 | try: 85 | mqtt.single(topic, payload, 86 | auth=auth, 87 | tls=tls, 88 | **params) 89 | except Exception, e: 90 | status = CRITICAL 91 | msg = msg + " " + str(e) 92 | 93 | return status, msg 94 | 95 | # Connect to URL (http://host:port) and obtain the last position 96 | # of the ping/ping user. Note: URL must not include the API endpoint 97 | # Verify, that the `tst' in the returned payload is close to `tics' 98 | 99 | def check_response(url, tics): 100 | status = OK 101 | msg = "ot-recorder pingping at %s: " % url 102 | data = None 103 | 104 | try: 105 | r = requests.post(url + "/api/0/last", params= { 'user' : 'ping', 'device' : 'ping' }) 106 | data = json.loads(r.text)[0] # Return is an array 107 | except Exception, e: 108 | return CRITICAL, str(e) 109 | 110 | tst = data['tst'] 111 | diff = tics - tst 112 | 113 | if diff > 10: 114 | status = WARNING 115 | if diff > 60: 116 | status = CRITICAL 117 | 118 | msg = msg + " %d seconds difference" % diff 119 | return status, msg 120 | 121 | if __name__ == '__main__': 122 | tics = int(time.time()) 123 | 124 | status, msg = pingping(tics) 125 | if status != OK: 126 | print "%s ot-recorder pingping failed: %s" % (codes[status], msg) 127 | sys.exit(status) 128 | 129 | status, msg = check_response('http://127.0.0.1:8083', tics) 130 | 131 | print "%s %s" % (codes[status], msg) 132 | sys.exit(status) 133 | 134 | -------------------------------------------------------------------------------- /docroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Recorder 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 41 | 42 | 109 | 110 | 111 | 117 | 118 |
119 | 120 | 121 | 122 | 123 | 124 | 125 |
User/DevicePointsPointsTrackTrackTrack
126 |
127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /etc/demo-iphone.rec: -------------------------------------------------------------------------------- 1 | 2015-08-24T05:55:07Z * {"tst":1440395361,"acc":3000,"_type":"location","alt":51,"lon":10.02785726730668,"vac":29,"vel":-1,"lat":52.37888580984668,"cog":-1,"tid":"NE","batt":96} 2 | 2015-08-24T05:55:07Z * {"tst":1440395704,"acc":5000,"_type":"location","alt":51,"lon":10.12589338283843,"vac":29,"vel":-1,"lat":52.35875498932977,"cog":-1,"tid":"NE","batt":95} 3 | 2015-08-24T05:55:14Z - {"tst":"1440363922","_type":"lwt"} 4 | 2015-08-24T06:16:05Z * {"tst":1440396651,"acc":2000,"_type":"location","alt":51,"lon":10.75530210620358,"vac":29,"vel":-1,"lat":52.30995001175547,"cog":-1,"tid":"NE","batt":95} 5 | 2015-08-24T06:16:05Z * {"tst":1440396961,"acc":5000,"_type":"location","alt":51,"lon":10.86265983610408,"vac":29,"vel":-1,"lat":52.28833075036061,"cog":-1,"tid":"NE","batt":94} 6 | 2015-08-24T06:16:11Z - {"tst":"1440363922","_type":"lwt"} 7 | 2015-08-24T06:44:53Z * {"tst":1440397999,"acc":3000,"_type":"location","alt":129,"lon":11.41177879499181,"vac":56,"vel":-1,"lat":52.18106927977037,"cog":-1,"tid":"NE","batt":93} 8 | 2015-08-24T06:44:53Z * {"tst":1440398690,"acc":3000,"_type":"location","alt":129,"lon":11.53796442908309,"vac":56,"vel":-1,"lat":52.13745195383985,"cog":-1,"tid":"NE","batt":93} 9 | 2015-08-24T06:51:20Z * {"tst":1440399078,"acc":2000,"_type":"location","alt":129,"lon":11.62219349201541,"vac":56,"vel":-1,"lat":52.04909826664092,"cog":-1,"tid":"NE","batt":93} 10 | 2015-08-24T06:59:28Z * {"tst":1440399566,"acc":2540,"_type":"location","alt":129,"lon":11.68744553418538,"vac":56,"vel":-1,"lat":51.90673010895662,"cog":-1,"tid":"NE","batt":93} 11 | 2015-08-24T07:10:32Z * {"tst":1440400230,"acc":1709,"_type":"location","alt":86,"lon":11.6713054715202,"vac":42,"vel":-1,"lat":51.74682642787724,"cog":-1,"tid":"NE","batt":92} 12 | 2015-08-24T07:19:08Z * {"tst":1440400747,"acc":4286,"_type":"location","alt":86,"lon":11.77831471084768,"vac":42,"vel":-1,"lat":51.67832228078593,"cog":-1,"tid":"NE","batt":92} 13 | 2015-08-24T07:25:24Z * {"tst":1440401122,"acc":10,"_type":"location","alt":111,"lon":11.81757730432955,"vac":24,"vel":0,"lat":51.65665419777078,"cog":-1,"tid":"NE","batt":88} 14 | 2015-08-24T07:32:53Z * {"tst":1440401570,"acc":4000,"_type":"location","alt":95,"lon":11.88829232787799,"vac":26,"vel":-1,"lat":51.60676807734803,"cog":-1,"tid":"NE","batt":88} 15 | 2015-08-24T07:39:39Z * {"tst":1440401977,"acc":4000,"_type":"location","alt":95,"lon":11.9619174891511,"vac":26,"vel":-1,"lat":51.54479803584139,"cog":-1,"tid":"NE","batt":88} 16 | 2015-08-24T07:48:53Z * {"tst":1440402530,"acc":3000,"_type":"location","alt":95,"lon":12.13955564850482,"vac":26,"vel":-1,"lat":51.45035255994988,"cog":-1,"tid":"NE","batt":87} 17 | 2015-08-24T07:58:38Z * {"tst":1440403115,"acc":2000,"_type":"location","alt":95,"lon":12.41517003914026,"vac":26,"vel":-1,"lat":51.38810805753496,"cog":-1,"tid":"NE","batt":87} 18 | 2015-08-24T08:05:00Z * {"tst":1440403497,"acc":4000,"_type":"location","alt":95,"lon":12.55283111863677,"vac":26,"vel":-1,"lat":51.31553072995163,"cog":-1,"tid":"NE","batt":87} 19 | 2015-08-24T08:09:59Z * {"tst":1440403798,"acc":5,"_type":"location","alt":166,"lon":12.71026164294473,"vac":6,"vel":35,"lat":51.26004282389884,"cog":118,"tid":"NE","batt":88} 20 | 2015-08-24T08:15:00Z * {"tst":1440404099,"acc":5,"_type":"location","alt":208,"lon":12.88225444034942,"vac":8,"vel":48,"lat":51.23548229695412,"cog":116,"tid":"NE","batt":91} 21 | 2015-08-24T08:20:00Z * {"tst":1440404399,"acc":5,"_type":"location","alt":249,"lon":13.0338817276182,"vac":4,"vel":39,"lat":51.17630911995766,"cog":138,"tid":"NE","batt":94} 22 | 2015-08-24T08:25:00Z * {"tst":1440404699,"acc":10,"_type":"location","alt":279,"lon":13.18166758866346,"vac":4,"vel":40,"lat":51.10900407196126,"cog":130,"tid":"NE","batt":96} 23 | 2015-08-24T08:30:01Z * {"tst":1440405000,"acc":10,"_type":"location","alt":272,"lon":13.33330241964509,"vac":4,"vel":50,"lat":51.06296642688202,"cog":140,"tid":"NE","batt":97} 24 | 2015-08-24T08:35:02Z * {"tst":1440405301,"acc":5,"_type":"location","alt":263,"lon":13.47772294656697,"vac":6,"vel":40,"lat":51.0592983803256,"cog":92,"tid":"NE","batt":98} 25 | 2015-08-24T08:40:02Z * {"tst":1440405601,"acc":10,"_type":"location","alt":262,"lon":13.60279820860699,"vac":6,"vel":18,"lat":51.06263391678321,"cog":82,"tid":"NE","batt":99} 26 | -------------------------------------------------------------------------------- /listsort.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "listsort.h" 4 | 5 | /* from http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.c */ 6 | 7 | /* 8 | * This file is copyright 2001 Simon Tatham. 9 | * 10 | * Permission is hereby granted, free of charge, to any person 11 | * obtaining a copy of this software and associated documentation 12 | * files (the "Software"), to deal in the Software without 13 | * restriction, including without limitation the rights to use, 14 | * copy, modify, merge, publish, distribute, sublicense, and/or 15 | * sell copies of the Software, and to permit persons to whom the 16 | * Software is furnished to do so, subject to the following 17 | * conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be 20 | * included in all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR 26 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 27 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 28 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | static double cmp(JsonNode * a, JsonNode * b) 33 | { 34 | JsonNode *ta = json_find_member(a, "tst"); 35 | JsonNode *tb = json_find_member(b, "tst"); 36 | //printf("a=%lf, b=%lf\n", ta->number_, tb->number_); 37 | 38 | /* user may have chosen fields without tst */ 39 | 40 | if (!ta || !tb) 41 | return (0.0); 42 | 43 | return ta->number_ - tb->number_; 44 | } 45 | 46 | /* 47 | * This is the actual sort function. Notice that it returns the new head of 48 | * the list. (It has to, because the head will not generally be the same 49 | * element after the sort.) So unlike sorting an array, where you can do 50 | * 51 | * sort(myarray); 52 | * 53 | * you now have to do 54 | * 55 | * list = listsort(mylist); 56 | */ 57 | JsonNode *listsort(JsonNode * list, int is_circular, int is_double) 58 | { 59 | JsonNode *p, *q, *e, *tail, *oldhead; 60 | int insize, nmerges, psize, qsize, i; 61 | 62 | /* 63 | * Silly special case: if `list' was passed in as NULL, return NULL 64 | * immediately. 65 | */ 66 | if (!list) 67 | return NULL; 68 | 69 | insize = 1; 70 | 71 | while (1) { 72 | p = list; 73 | oldhead = list; /* only used for circular linkage */ 74 | list = NULL; 75 | tail = NULL; 76 | 77 | nmerges = 0; /* count number of merges we do in this pass */ 78 | 79 | while (p) { 80 | nmerges++; /* there exists a merge to be done */ 81 | /* step `insize' places along from p */ 82 | q = p; 83 | psize = 0; 84 | for (i = 0; i < insize; i++) { 85 | psize++; 86 | if (is_circular) 87 | q = (q->next == oldhead ? NULL : q->next); 88 | else 89 | q = q->next; 90 | if (!q) 91 | break; 92 | } 93 | 94 | /* 95 | * if q hasn't fallen off end, we have two lists to 96 | * merge 97 | */ 98 | qsize = insize; 99 | 100 | /* now we have two lists; merge them */ 101 | while (psize > 0 || (qsize > 0 && q)) { 102 | 103 | /* 104 | * decide whether next element of merge comes 105 | * from p or q 106 | */ 107 | if (psize == 0) { 108 | /* p is empty; e must come from q. */ 109 | e = q; 110 | q = q->next; 111 | qsize--; 112 | if (is_circular && q == oldhead) 113 | q = NULL; 114 | } else if (qsize == 0 || !q) { 115 | /* q is empty; e must come from p. */ 116 | e = p; 117 | p = p->next; 118 | psize--; 119 | if (is_circular && p == oldhead) 120 | p = NULL; 121 | } else if (cmp(p, q) <= 0) { 122 | /* 123 | * First element of p is lower (or 124 | * same); e must come from p. 125 | */ 126 | e = p; 127 | p = p->next; 128 | psize--; 129 | if (is_circular && p == oldhead) 130 | p = NULL; 131 | } else { 132 | /* 133 | * First element of q is lower; e 134 | * must come from q. 135 | */ 136 | e = q; 137 | q = q->next; 138 | qsize--; 139 | if (is_circular && q == oldhead) 140 | q = NULL; 141 | } 142 | 143 | /* add the next element to the merged list */ 144 | if (tail) { 145 | tail->next = e; 146 | } else { 147 | list = e; 148 | } 149 | if (is_double) { 150 | /* 151 | * Maintain reverse pointers in a 152 | * doubly linked list. 153 | */ 154 | e->prev = tail; 155 | } 156 | tail = e; 157 | } 158 | 159 | /* 160 | * now p has stepped `insize' places along, and q has 161 | * too 162 | */ 163 | p = q; 164 | } 165 | if (is_circular) { 166 | tail->next = list; 167 | if (is_double) 168 | list->prev = tail; 169 | } else 170 | tail->next = NULL; 171 | 172 | /* If we have done only one merge, we're finished. */ 173 | if (nmerges <= 1) /* allow for nmerges==0, the empty 174 | * list case */ 175 | return list; 176 | 177 | /* Otherwise repeat, merging lists twice the size */ 178 | insize *= 2; 179 | } 180 | } 181 | 182 | #ifdef TESTING 183 | int main() 184 | { 185 | JsonNode *n, *sorted, *arr = json_mkarray(); 186 | JsonNode *o; 187 | 188 | o = json_mkobject(); 189 | json_append_member(o, "tst", json_mknumber(2)); 190 | json_append_member(o, "desc", json_mkstring("dos")); 191 | json_append_element(arr, o); 192 | 193 | o = json_mkobject(); 194 | json_append_member(o, "tst", json_mknumber(69)); 195 | json_append_member(o, "desc", json_mkstring("sixtynine")); 196 | json_append_element(arr, o); 197 | 198 | o = json_mkobject(); 199 | json_append_member(o, "tst", json_mknumber(14)); 200 | json_append_member(o, "desc", json_mkstring("fourteen")); 201 | json_append_element(arr, o); 202 | 203 | o = json_mkobject(); 204 | json_append_member(o, "tst", json_mknumber(7)); 205 | json_append_member(o, "desc", json_mkstring("seven")); 206 | json_append_element(arr, o); 207 | 208 | 209 | json_foreach(n, arr) { 210 | JsonNode *e = json_find_member(n, "tst"); 211 | JsonNode *d = json_find_member(n, "desc"); 212 | printf("%lf - %s\n", e->number_, d->string_); 213 | } 214 | 215 | 216 | listsort(NULL, 0, 0); 217 | sorted = listsort(json_first_child(arr), 0, 0); 218 | sorted = sorted->parent; 219 | printf("------- %s\n", (sorted == arr) ? "SAME" : "different"); 220 | 221 | json_foreach(n, sorted) { 222 | JsonNode *e = json_find_member(n, "tst"); 223 | JsonNode *d = json_find_member(n, "desc"); 224 | printf("%lf - %s\n", e->number_, d->string_); 225 | } 226 | 227 | json_delete(arr); 228 | 229 | return 0; 230 | } 231 | #endif 232 | --------------------------------------------------------------------------------