traccar / traccar-client-android

Traccar Client for Android
https://www.traccar.org/client
Apache License 2.0
661 stars 731 forks source link

Help with Adding fields #389

Open pointDude opened 5 years ago

pointDude commented 5 years ago

Good Morning,

I've already submitted this in the Forums, but I think this may be a more appropriate place to get some help. I'm attempting to modify the Traccar App code to report in the Fransen GPS Gate Tracker One format. I would like to do this so that I have one app for my clients who use both Traccar and GPS Gate. To accomplish this I have made several changes which have had mixed results in effectiveness. I am not familiar with the SQLight for android that the app uses and think that I have made a mistake, but every change I made is listed below.

The Changes I have made to the code are focused on three java files, the Position.java, the DatabaseHelper.java and the ProtocolFormatter.java.

Position.java got some extra dummy variables in order to get the proper date time format and the proper degree minute format.

 public Position(String deviceId, Location location, double battery) {
        this.deviceId = deviceId;
        // Changes to make the Tracker one Format Time
        Date date = new Date(location.getTime());
        time = date;
        DateFormat dateFormat = new SimpleDateFormat("ddMMYY,HHmmss.SS");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        String preGPSgate = dateFormat.format(date);
        GPSgateTime = preGPSgate;

        //Latitude and Longitude
        //change to get DDMM.mmm format
        double getsLatitude = (location.getLatitude());
        latitude = getsLatitude;
        String preLatitude = location.convert(getsLatitude, location.FORMAT_MINUTES);
        String preLatitude2 = preLatitude.replace(":","");
        double preLatitude3 = Double.parseDouble(preLatitude2);
        GPSgateLatitude = abs(preLatitude3);
        //Repeat change for Longitude
        double getsLongitude = (location.getLongitude());
        longitude = getsLongitude;
        String preLongitude = location.convert(getsLongitude, location.FORMAT_MINUTES);
        String preLongitude2 = preLongitude.replace(":","");
        double preLongitude3 = Double.parseDouble(preLongitude2);
        GPSgateLongitude = abs(preLongitude3);
        //end of changes to traccar variables
        altitude = location.getAltitude();
        speed = location.getSpeed() * 1.943844; // speed in knots
        course = location.getBearing();
        if (location.getProvider() != null && !location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
            accuracy = location.getAccuracy();
        }
        this.battery = battery;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            this.mock = location.isFromMockProvider();
        }
        //Start of additions to the traccar code.
        //get the North South Hemisphere.
        if (getsLatitude < 0) {
            this.NsHem = "S";
        }else {
            this.NsHem = "N";
        }
        //doing the same with East West
        if (getsLongitude < 0){
            this.EwHem = "W";
        }else {
            this.EwHem = "E";
        }
    }

I then added the declaration, set and get methods for the new additions

    //North and south Hemisphere
    private String NsHem;
    public String getNsHem() {
        return NsHem;
    }
    public void setNsHem(String NsHem) {
        this.NsHem = NsHem;
    }
    //East and West Hemisphere
    private String EwHem;
    public String getEwHem() {
        return EwHem;
    }
    public void setEwHem(String EwHem){
        this.EwHem = EwHem;
    }
    private String GPSgateTime;
    public String getGPSgateTime(){
        return GPSgateTime;
    }
    public void setGPSgateTime(String GPSgateTime){
        this.GPSgateTime =GPSgateTime;
    }
    private double GPSgateLatitude;
    public double getGPSgateLatitude() {
        return GPSgateLatitude;
    }
    public void setGPSgateLatitude(double GPSgateLatitude) {
        this.GPSgateLatitude = GPSgateLatitude;
    }
    private double GPSgateLongitude;
    public double getGPSgateLongitude(){
        return GPSgateLongitude;
    }
    public void setGPSgateLongitude(double GPSgateLongitude) {
        this.GPSgateLongitude = GPSgateLongitude;
    }

Next came the Database Helper, Here is where I think I made my mistakes, but I can't find them

I added my fields to the SQLite database creator

    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE position (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "deviceId TEXT," +
                "time INTEGER," +
                "GPSgateTime TEXT," +
                "latitude REAL," +
                "GPSgateLatitude, REAL" +
                "NsHem TEXT," +
                "longitude REAL," +
                "GPSgateLongitude, REAL" +
                "EwHem TEXT," +
                "altitude REAL," +
                "speed REAL," +
                "course REAL," +
                "accuracy REAL," +
                "battery REAL," +
                "mock INTEGER)");
    }

Then added the insert

    public void insertPosition(Position position) {
        ContentValues values = new ContentValues();
        values.put("deviceId", position.getDeviceId());
        values.put("time", position.getTime().getTime());
        values.put("GPSgateTime", position.getGPSgateTime());
        values.put("latitude", position.getLatitude());
        values.put("GPSgateLatitude", position.getGPSgateLatitude());
        values.put("NsHem", position.getNsHem());
        values.put("longitude", position.getLongitude());
        values.put("GPSgateLongitude", position.getGPSgateLongitude());
        values.put("EwHem", position.getEwHem());
        values.put("altitude", position.getAltitude());
        values.put("speed", position.getSpeed());
        values.put("course", position.getCourse());
        values.put("accuracy", position.getAccuracy());
        values.put("battery", position.getBattery());
        values.put("mock", position.getMock() ? 1 : 0);

        db.insertOrThrow("position", null, values);
    }

and then added them to the query

   public Position selectPosition() {
        Position position = new Position();

        Cursor cursor = db.rawQuery("SELECT * FROM position ORDER BY id LIMIT 1", null);
        try {
            if (cursor.getCount() > 0) {
// Added GPSgateTime, GPSgateLatitude, NsHem, GPSgateLongitude, EwHem
                cursor.moveToFirst();

                position.setId(cursor.getLong(cursor.getColumnIndex("id")));
                position.setDeviceId(cursor.getString(cursor.getColumnIndex("deviceId")));
                position.setTime(new Date(cursor.getLong(cursor.getColumnIndex("time"))));
                position.setGPSgateTime(cursor.getString(cursor.getColumnIndex("GPSgateTime")));
                position.setLatitude(cursor.getDouble(cursor.getColumnIndex("latitude")));
                position.setGPSgateLatitude(cursor.getDouble(cursor.getColumnIndex("GPSgateLatitude")));
                position.setNsHem(cursor.getString(cursor.getColumnIndex("NsHem")));
                position.setLongitude(cursor.getDouble(cursor.getColumnIndex("longitude")));
                position.setGPSgateLongitude(cursor.getDouble(cursor.getColumnIndex("GPSgateLongitude")));
                position.setEwHem(cursor.getString(cursor.getColumnIndex("EwHem")));
                position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude")));
                position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed")));
                position.setCourse(cursor.getDouble(cursor.getColumnIndex("course")));
                position.setAccuracy(cursor.getDouble(cursor.getColumnIndex("accuracy")));
                position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery")));
                position.setMock(cursor.getInt(cursor.getColumnIndex("mock")) > 0);

            } else {
                return null;
            }
        } finally {
            cursor.close();
        }

        return position;
    }

The last and most messed up thing I did was change the format request entirely, This was some dirty quick programming to get it into the proper GPSgate protocol with commas being the only special character.

    public static String formatRequest(String url, Position position, String alarm) {
        Uri serverUrl = Uri.parse(url);
        Uri.Builder builder = serverUrl.buildUpon()
                .path("GpsGate/")
                .encodedQuery("cmd=$FRCMD"
                        +"," + position.getDeviceId()
                        +"," + "_SendMessage,"
                        +"," + String.valueOf(position.getGPSgateLatitude())
                        +"," + String.valueOf(position.getNsHem())
                        +","+ String.valueOf(position.getGPSgateLongitude())
                        +","+ String.valueOf(position.getEwHem())
                        +"," + String.valueOf(position.getAltitude())
                        +"," + String.valueOf(position.getSpeed())
                        +"," + String.valueOf(position.getCourse())
                        +"," + String.valueOf(position.getGPSgateTime())
                        +"," + "1"
                        +",BatteryLevel=" + String.valueOf(position.getBattery())

        );

It does work some of the time, but I don't know what I've broken. I tried to leave all of the original variables in place for the degree, distance and time triggers. Can I please get some input/correction on where to go from here.

reslin commented 5 years ago

There are some misplaced commas:

In onCreate(SQLiteDatabase db) it should be: "GPSgateLatitude REAL," + and "GPSgateLongitude REAL," +,

your SQL-table couldn't have been created at all with misplaced commas!

Then in formatRequest(String url, Position position, String alarm) it should be: +"," + "_SendMessage" with no extra comma at the end.

Why you're saying

It does work some of the time

I really can't explain, because it shouldn't work at all if the commas are set in wrong places. You should see SQL-errors in Logcat (Android Studio).

pointDude commented 5 years ago

Good Morning Reslin,

I just got back from military training so I'm sorry for the delay in response.

That you for the help, I didn't notice that I messed that one up.

Very Respectfully,

Michael Travis

pointDude commented 5 years ago

So I made the changes that you suggested and I'm no longer getting the errors that I had, The SQL Error in logcat that I'm seeind is

2019-02-25 10:43:47.651 7472-11057/org.traccar.client E/CursorWindow: Failed to read row 0, column -1 from a CursorWindow which has 1 rows, 16 columns.

As for the What I mean by "It works sometimes" I mean when I send a SOS, which I assume does not use the SQL database, I do get a proper send code, that does map on the server.

It seems like I am getting lat, lon and time data, but I'm a little worried with the id.

2019-02-25 10:30:11.415 7472-7472/org.traccar.client D/TrackingController: write (id:0 time:1551115812 lat:43.63154864 lon:-116.2744972)

I did not see any code which limited the id length, but I will try to limit it from the IMEI to a smaller simple id and see if that helps.

pointDude commented 5 years ago

Oh and before I forget the double comma is due to the GPSGate tracker one format see below.

HTTP GETSupported in GpsGate Server 2.0.6 and later. By default port 8008 is used. Only commands from the tracker to server can use HTTP. The respons body contans the server reply ($FRRET).Commands sent over HTTP do not need any checksum. Both commands with and without checksum isaccepted by the server.Syntax:http://hostname:8008/GpsGate/?cmd=$FRCMD...Example:http://online.gpsgate.com:8008/GpsGate/?cmd=$FRCMD,353857014816785,_SendMessage,,4748.00000,N,3530.00000,E,34.6,234,90,170109,120523.657,1

pointDude commented 5 years ago

Update As of 5/19/2019 It is working, I have posted it in my github, only three files have changed, here is what the changes are In position.java The first Position function

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import static java.lang.Math.abs;

public class Position {

    public Position() {
    }

    public Position(String deviceId, Location location, double battery) {
        this.deviceId = deviceId;

        time = new Date(location.getTime());
        latitude = location.getLatitude();
        longitude = location.getLongitude();
        altitude = location.getAltitude();
        speed = location.getSpeed() * 1.943844; // speed in knots
        course = location.getBearing();
        if (location.getProvider() != null && !location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
            accuracy = location.getAccuracy();
        }
        this.battery = battery;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            this.mock = location.isFromMockProvider();
        }

        if (latitude < 0) {
            this.NsHem = "S";
        }else {
            this.NsHem = "N";
        }
        //doing the same with East West
        if (longitude < 0){
            this.EwHem = "W";
        }else {
            this.EwHem = "E";
        }
        // Changes to make the Tracker one Format Time
        try{
            Date date = new Date(location.getTime());
            //DateFormat dateFormat = new SimpleDateFormat("ddMMYY,HHmmss.SS");
            SimpleDateFormat dateFormat = new SimpleDateFormat("ddMMyy,HHmmss.SS");
            dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
            String preGPSgate = dateFormat.format(date);
            GPSgateTime = preGPSgate;

        }catch (Exception e){
            GPSgateTime = "020304";
        }
        //Latitude and Longitude
        //change to get DDMM.mmm format
        double getsLatitude = (location.getLatitude());
        String preLatitude = location.convert(getsLatitude, location.FORMAT_MINUTES);
        String preLatitude2 = preLatitude.replace(":","");
        double preLatitude3 = Double.parseDouble(preLatitude2);
        GPSgateLatitude = abs(preLatitude3);
        //Repeat change for Longitude
        double getsLongitude = (location.getLongitude());
        String preLongitude = location.convert(getsLongitude, location.FORMAT_MINUTES);
        String preLongitude2 = preLongitude.replace(":","");
        double preLongitude3 = Double.parseDouble(preLongitude2);
        GPSgateLongitude = abs(preLongitude3);
    }
and on the bottom add
//Added Fields
    private String NsHem = "S";
    public String getNsHem() {
        return NsHem;
    }
    public void setNsHem(String NsHem) {
        this.NsHem = NsHem;
    }

    //East and West Hemisphere
    private String EwHem = "W";
    public String getEwHem() {
        return EwHem;
    }
    public void setEwHem(String EwHem){
        this.EwHem = EwHem;
    }

    private String GPSgateTime;
    public String getGPSgateTime(){
        return GPSgateTime;
    }
    public void setGPSgateTime(String GPSgateTime){
        this.GPSgateTime =GPSgateTime;
    }

    private double GPSgateLatitude;
    public double getGPSgateLatitude() {
        return GPSgateLatitude;
    }
    public void setGPSgateLatitude(double GPSgateLatitude) {
        this.GPSgateLatitude = GPSgateLatitude;
    }
    private double GPSgateLongitude;
    public double getGPSgateLongitude(){
        return GPSgateLongitude;
    }
    public void setGPSgateLongitude(double GPSgateLongitude) {
        this.GPSgateLongitude = GPSgateLongitude;
    }

In database helper for creating the SQL database

@Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE position (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "deviceId TEXT," +
                "gpsGateLatitude REAL," +
                "gpsGateLongitude REAL," +
                "NsHem TEXT," +
                "EwHEM TEXT," +
                "gpsGateTime TEXT," +
                "time INTEGER," +
                "latitude REAL," +
                "longitude REAL," +
                "altitude REAL," +
                "speed REAL," +
                "course REAL," +
                "accuracy REAL," +
                "battery REAL," +
                "mock INTEGER)");
    }

For the insert position

 public void insertPosition(Position position) {
        ContentValues values = new ContentValues();
        values.put("deviceId", position.getDeviceId());
        values.put("gpsGateLatitude", position.getGPSgateLatitude());
        values.put("gpsGateLongitude", position.getGPSgateLongitude());
        values.put("NsHem", position.getNsHem());
        values.put("EwHEM", position.getEwHem());
        values.put("gpsGateTime", position.getGPSgateTime());

        values.put("time", position.getTime().getTime());
        values.put("latitude", position.getLatitude());
        values.put("longitude", position.getLongitude());
        values.put("altitude", position.getAltitude());
        values.put("speed", position.getSpeed());
        values.put("course", position.getCourse());
        values.put("accuracy", position.getAccuracy());
        values.put("battery", position.getBattery());
        values.put("mock", position.getMock() ? 1 : 0);

        db.insertOrThrow("position", null, values);
    }

and add into the select position function

    public Position selectPosition() {
        Position position = new Position();

        Cursor cursor = db.rawQuery("SELECT * FROM position ORDER BY id LIMIT 1", null);
        try {
            if (cursor.getCount() > 0) {

                cursor.moveToFirst();

                position.setId(cursor.getLong(cursor.getColumnIndex("id")));
                position.setDeviceId(cursor.getString(cursor.getColumnIndex("deviceId")));

                position.setGPSgateLatitude(cursor.getDouble(cursor.getColumnIndex("gpsGateLatitude")));
                position.setGPSgateLongitude(cursor.getDouble(cursor.getColumnIndex("gpsGateLongitude")));
                position.setNsHem(cursor.getString(cursor.getColumnIndex("NsHem")));
                position.setEwHem(cursor.getString(cursor.getColumnIndex("EwHEM")));
                position.setGPSgateTime(cursor.getString(cursor.getColumnIndex("gpsGateTime")));

                position.setTime(new Date(cursor.getLong(cursor.getColumnIndex("time"))));
                position.setLatitude(cursor.getDouble(cursor.getColumnIndex("latitude")));
                position.setLongitude(cursor.getDouble(cursor.getColumnIndex("longitude")));
                position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude")));
                position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed")));
                position.setCourse(cursor.getDouble(cursor.getColumnIndex("course")));
                position.setAccuracy(cursor.getDouble(cursor.getColumnIndex("accuracy")));
                position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery")));
                position.setMock(cursor.getInt(cursor.getColumnIndex("mock")) > 0);

Finally in the ProtocolFormatter.java

   public static String formatRequest(String url, Position position, String alarm) {
        Uri serverUrl = Uri.parse(url);

        Uri.Builder builder = serverUrl.buildUpon()
                .path("GpsGate/")
                .encodedQuery(
                        "cmd=$FRCMD"
                                + addParameter(position.getDeviceId())
                                + addParameter("_SendMessage,")
                                + addParameter(String.valueOf(position.getGPSgateLatitude()))
                                + addParameter(String.valueOf(position.getNsHem()))
                                + addParameter(String.valueOf(position.getGPSgateLongitude()))
                                + addParameter(String.valueOf(position.getEwHem()))
                                + addParameter(String.valueOf(position.getAltitude()))
                                + addParameter(String.valueOf(position.getSpeed()))
                                + addParameter(String.valueOf(position.getCourse()))
                                + addParameter(String.valueOf(position.getGPSgateTime()))
                                + addParameter("1")

                );

        return builder.build().toString();
    }

    private static String addParameter(String data){
        return "," + data;
    }
}

The results I'm getting are looking good image

Except where it runs out of coverage and then adds 106 to the value of the longitude

image

Now I only need help with finding out why the code does that one strange piece of behavior, and those of us running both gpsGate and Traccar can have a unified application that is open source.

reslin commented 5 years ago

I've only skimmed your source quickly, but why exactly do you use the abs() function in position.java on the members preLatitude3 and preLongitude3? It is perfectly "allowed" for geo-coordinates to have negative values.

Give it a try and get rid of the abs(...).

Also in position.java I see the lines if (location.getProvider() != null && !location.getProvider().equals(LocationManager.GPS_PROVIDER)) { accuracy = location.getAccuracy(); }

which means: if we have a Provider AND the Provider is NOT from GPS, then set the accuracy member. Is this your intention?

I would rather do this as follows: if(location != null && location.hasAccuracy() { accuracy = location.getAccuracy(); }

which means: if the location is existing AND has an accuracy value, then set the accuracy member.

pointDude commented 5 years ago

Will try that out and let you know how it goes.

Very Respectfully,

Michael Travis

pointDude commented 5 years ago

So I forgot that GPSgate does not deal with negative numbers and that's why I put it in there, but it didn't fix the issue, It just made all of my tracks go to China and still skip when I get out of coverage. It's a constant number of 106, perhaps I should just add it in?

reslin commented 5 years ago

Your problem eludes me... but I don't advise in guessing to add or subtract the seemingly arbitrary value of "106" (which surely must have an origin?!) and not really knowing where this error stems from.

If you get out of coverage it seems best to not collect locations at all, because they will very likely not be usable.

Also I (generally) experienced an odd behaviour on my devices (an old Samsung S2, a not-so-old LG Stylus): if being worked as a mobile WLAN-Hotspot, locations get incorrect / erratic / jumpy and accuracy is really bad (1500 m at best). The old S2 even tends to locate itself at coordinates Lat/Lon 0.0/0.0.

I'm sorry that I cannot help you any further.

oliv3 commented 5 years ago

Well, where is the code ?

reslin commented 5 years ago

Well, where is the code ?

Apparently there are no changes pushed to the fork https://github.com/pointDude/traccar-client-android, so all altered code is only here within this issue-thread.

(Sadly enough I don't have the time to explain Git, Github and Android programming to pointDude, but perhaps someone has.)

pointDude commented 5 years ago

Update, Found the Issue, Android FORMAT_DEGREES does not always return DD: MM:mmmm, if it counts down below the 10's digit it may report DD:M.mmmmm which leads to a situation where 11410.03675,W, can count down to 1149.96046,W. I'll be working on that in the meantime, perhaps doing a cut off after the : a count to the . and a if then formatting statement. I will post all fixs into my seperate fork after it's fully working. Besides for that all of the changes to the code is already posted here. Reslin do you happen to know a good way of addressing the formatting issue?