bfabiszewski / ulogger-server

μlogger • web viewer for tracks uploaded with μlogger mobile client
GNU General Public License v3.0
538 stars 85 forks source link

API documentation is missing #92

Closed ser closed 4 years ago

ser commented 5 years ago

I would like to hook up my own logger, but unfortunately API documentation is missing. How should look like a HTTP post of the location to the server to be properly logged?

bfabiszewski commented 5 years ago

Sorry, I never got to the point of making any external API for the project. Hence no documentation. Anyway, communication is pretty simple. You should get an idea by looking at the server part.

ser commented 5 years ago

OK, I'm developing an Arduino low-power board and I'm using your server. Because the board does not support https, I need to proxy the traffic, so I wrote a tiny ulogger-proxy service:

# sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 5000
import json
import requests
import sqlite3
import sys
from datetime import datetime
from datetime import timezone
from dateutil.tz import gettz
from flask import Flask, g

app = Flask(__name__)
DB = 'ulogger.db'
# specify your ulogger server without trailing slash!
your_ulogger_server = 'https://mapping.example.com'
your_ulogger_username = ''
your_ulogger_password = ''
your_timezone = "Europe/Warsaw"

# you should not modify this without a clear purpose
report_url = your_ulogger_server + '/client/index.php'

# SQlite part stolen from https://flask.palletsprojects.com/en/master/patterns/sqlite3/
def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = sqlite3.connect(DB)
    db.row_factory = sqlite3.Row
    return db

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

def query_db(query, args=(), one=False):
    cur = get_db().execute(query, args)
    rv = cur.fetchall()
    cur.close()
    return (rv[0] if rv else None) if one else rv

def init_db():
    with app.app_context():
        db = get_db()
        with app.open_resource('ulogger-schema.sql', mode='r') as f:
             db.cursor().executescript(f.read())
        db.commit()

# track creation on demand if does not exist
def check_track():
    '''we are checking or adding a track with today's date in specified timezone'''
    nowis = datetime.now(gettz(your_timezone))
    trackname = str(nowis.year) + '-' + str(nowis.month) + '-' + str(nowis.day)
    # open database with track IDs and check if the track already exists, if not - create
    trackidb = query_db('select id from tracks where name=?',
                        [trackname], one=True)
    if trackidb is None:
        payload = {
            'action': 'addtrack',
            'track': trackname
        }
        r = session_requests.post(report_url, data=payload)
        response = json.loads(r.text)
        trackid = int(response['trackid'])
        cur = get_db().execute('insert into tracks(id, name) values (?, ?)',
                               [trackid, trackname])
        cur.close()
        db = getattr(g, '_database', None)
        db.commit()
        print('Created track %d with name %s' % (trackid, trackname))
    else:
        trackid = trackidb['id']
        print('Reusing track %d with name %s' % (trackid, trackname))
    return trackid

def ulogin():
    '''Login into an account which you already have created on your ulogger-server'''
    payload = {
            'action': 'auth',
            'user': your_ulogger_username,
            'pass': your_ulogger_password
    }
    result = session_requests.post(
            report_url,
            data=payload,
    )
    if result.ok is True:
        print("Successfully logged into Ulogger-Server instance.")
    else:
        print("I am unable to login into Ulogger-Server instance.")
        print(result.json())
        sys.exit(1)

# flask routing
@app.route('/')
def hello_world():
    return 'Hello World!'

@app.route('/gps/<float:lat>/<float:lon>/<float:altitude>/<float:speed>/<float:bearing>/<float:accuracy>')
def gps(lat:float, lon:float, altitude:float, speed:float, bearing:float, accuracy:float):
    '''Report data received from arduino to ulogger-server'''
    # check track
    trackid = check_track()
    payload = {
        'action': 'addpos',
        'lat': lat,
        'lon': lon,
        'time': datetime.utcnow().replace(tzinfo=timezone.utc).timestamp(),
        'altitude': altitude,
        'speed': speed,
        'bearing': bearing,
        'accuracy': accuracy,
        'privider': 'arduino-ulogger-proxy',
        'comment': '',
        'imageid': '',
        'trackid': trackid,
    }
    result = session_requests.post(
            report_url,
            data=payload,
    )
    if result.ok is True:
        print("I have added location lat=%f lon=%f." % (lat, lon))
        return 'OK'
    else:
        print("I was unable to add location!")
        print(result.json())
        print("Trying to log again...")
        ulogin()
        return 'NOK'
    # report short confirmation that we received data from arduino back to ulogger-server

# main app
if __name__ == '__main__':
    # uncomment for debugging only
    app.debug = True
    # we use this requests session through all activity
    session_requests = requests.session()
    # we are logging to ulogger-server once when programm is running
    ulogin()
    # finally: start the flask app!
    app.run(host='1.2.3.4')

Above software could be much simpler if you could expose in the API ability to seek for a track name, then I would not have to store it locally on proxy server and get rid of the database.

bfabiszewski commented 5 years ago

Interesting project. I also planned to make similar simple device, but haven't found enough time yet.

I don't get why you need to seek for a track name. Track names are not unique. When you register new track you receive a track id. You have to store it somewhere if you want to upload positions to this track.

ser commented 5 years ago

So maybe just a list of all user tracks via API in json format? Track ID + Track name

I assume in my proxy that tracks are unique, I do not want to complicate it. If I get a list, I can find my track easily and get rid of a local database.

I will share the code of course when I finish, the board is very nice and tiny, all-in-one:

https://www.elecrow.com/wiki/index.php?title=Elecrow_SIMduino_UNO%2BSIM808_GPRS/GSM_Board

It's possible to order it on aliexpress, 30USD including delivery. Board is powered from USB, I use an ordinary power bank.

bfabiszewski commented 5 years ago

I may implement such feature when I introduce multiple track support to Android client. But it will probably not be soon. Now I am planning to release new feature, which allows to add waypoints manually.

It seems that my client is more simple than yours. I don't care for track names. User submits track name, which is sent to server. Later track name is not needed any more. Only track id is important.

bfabiszewski commented 5 years ago

The board is very nice. I also thought about this chipset for GPS/GSM functions

ser commented 5 years ago

Yes, but you store it locally, and I want to avoid it. If I make a Pull Request for track list, would you accept it?

bfabiszewski commented 5 years ago

I store it locally only for displaying the name to user and for exporting GPX files, when I want to use track name in exported file. It is not needed for logger functions. You shouldn't need it at all unless you want to store multiple tracks in your application. I don't exactly understand your problem, because you still need to save track id somewhere. I should not be much overhead to also store track name.

I am at the point of major rewrite of the server app, so I would prefer not to merge it now. Especially that it does not give any new functionality to my system.

obed13 commented 5 years ago

How can I make the mobile version connect to my localhost? is that I am new to this android studio

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.