package com.openvehicles.OVMS.entities;
import android.content.Context;
import android.util.Log;
import com.google.gson.Gson;
import com.openvehicles.OVMS.BaseApp;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;
/**
* Created by balzer on 06.04.15.
*/
public class GPSLogData {
private static final transient String TAG = "GPSLogData";
static public class Entry {
public Date timeStamp;
public float odometerMi;
public double latitude, longitude;
public int altitude, direction, speed;
public int gspFix, gpsStaleCnt, gsmSignal;
public int currentPower, powerUsedWh, powerRecdWh;
public int powerDistance, minPower, maxPower;
public int carStatus;
// Twizy status flags:
// bit 0 = 0x01: 1 = Footbrake
// bit 1 = 0x02: 1 = Forward mode "D"
// bit 2 = 0x04: 1 = Reverse mode "R"
// bit 3 = 0x08: 1 = "GO" = Motor ON (Ignition)
// bit 4 = 0x10: 1 = Car awake (key turned)
// bit 5 = 0x20: 1 = Charging
// bit 6 = 0x40: 1 = Switch-ON/-OFF phase / 0 = normal operation
// bit 7 = 0x80: 1 = CAN-Bus online (test flag to detect offline)
/**
* Get operative status
*
* @return -- -1=charging / 0=off / 1=on
*/
public int getOpStatus() {
if ((carStatus & 0x60) == 0x20)
return -1;
else if ((carStatus & 0x50) == 0x10)
return 1;
else
return 0;
}
/**
* Get key status
*
* @return -- 0x10=on / 0x00=off
*/
public int getKeyStatus() {
return carStatus & 0x10;
}
/**
* Check if this is a new data point to be displayed (= X axis entry):
* - time delta >= 60 seconds
*
* @param ref -- last data point displayed
* @return
*/
public boolean isNewTimePoint(Entry ref) {
return ((ref != null)
&& ((timeStamp.getTime() - ref.timeStamp.getTime()) / 1000f >= 60)
&& ((getOpStatus() != 0) || (ref.getOpStatus() != 0))
);
}
/**
* Check if this data point is the beginning of a new section (drive/charge).
*
* @param ref
* @return
*/
public boolean isSectionStart(Entry ref) {
return ((ref != null)
&& (getKeyStatus() > ref.getKeyStatus()
|| powerDistance < ref.powerDistance
|| powerUsedWh < ref.powerUsedWh
|| powerRecdWh < ref.powerRecdWh)
);
}
public float getTimeDiff(Entry ref) {
return (timeStamp.getTime() - ref.timeStamp.getTime()) / 1000f / 60f / 60f;
}
public float getOdometer(String unit) {
return unit.equals("M") ? odometerMi : (odometerMi * 1.609344f);
}
public float getOdoDiff(Entry ref, String unit) {
float odoDiff = (odometerMi - ref.odometerMi);
return unit.equals("M") ? odoDiff : (odoDiff * 1.609344f);
}
public float getSpeed(Entry ref, String unit) {
float calcSpeed = (odometerMi - ref.odometerMi) / getTimeDiff(ref);
return unit.equals("M") ? calcSpeed : (calcSpeed * 1.609344f);
}
public int getSegUsedWh(Entry ref) {
if (ref.powerUsedWh <= powerUsedWh)
return powerUsedWh - ref.powerUsedWh;
else
return powerUsedWh;
}
public int getSegRecdWh(Entry ref) {
if (ref.powerRecdWh <= powerRecdWh)
return -(powerRecdWh - ref.powerRecdWh);
else
return -powerRecdWh;
}
public float getSegAvgPwr(Entry ref) {
return (getSegUsedWh(ref) + getSegRecdWh(ref)) / getTimeDiff(ref);
}
public float getSegAvgEnergy(Entry ref, String unit) {
float odoDiff = getOdoDiff(ref, unit);
return (odoDiff > 0.050f)
? ((getSegUsedWh(ref) + getSegRecdWh(ref)) / odoDiff)
: 0f;
}
// minPower & maxPower are per GPS log entry instead of cumulative, thus need to be
// scanned for a segment spanning multiple entries:
public int getMinPower(ArrayList<Entry> entries, Entry ref) {
int min = 32767;
Entry entry;
for (int i = entries.indexOf(ref); i <= entries.indexOf(this); i++) {
entry = entries.get(i);
if (entry.minPower < min)
min = entry.minPower;
}
return min;
}
public int getMaxPower(ArrayList<Entry> entries, Entry ref) {
int max = -32768;
Entry entry;
for (int i = entries.indexOf(ref); i <= entries.indexOf(this); i++) {
entry = entries.get(i);
if (entry.maxPower > max)
max = entry.maxPower;
}
return max;
}
}
//
// System environment:
//
private transient static final Context context = BaseApp.getApp();
private transient static final Gson gson = new Gson();
//
// Storage data:
//
public String vehicleId;
public ArrayList<Entry> entries;
//
// Methods
//
public GPSLogData() {
vehicleId = "";
entries = new ArrayList<Entry>(24*60);
}
public static GPSLogData loadFile(String vehicleId) {
FileInputStream inputStream;
String filename = "gpslog-" + vehicleId + "-default.json";
Log.v(TAG, "loading from file: " + filename);
try {
inputStream = context.openFileInput(filename);
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
String json = sb.toString();
return gson.fromJson(json, GPSLogData.class);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public boolean saveFile(String vehicleId) {
FileOutputStream outputStream;
String filename = "gpslog-" + vehicleId + "-default.json";
Log.v(TAG, "saving to file: " + filename);
this.vehicleId = vehicleId;
String json = gson.toJson(this);
try {
outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(json.getBytes());
outputStream.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public void processCmdResults(CmdSeries cmdSeries) {
int recNr, recCnt;
String recType;
Date timeStamp;
SimpleDateFormat serverTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
serverTime.setTimeZone(TimeZone.getTimeZone("UTC"));
Entry entry;
for (int i=0; i < cmdSeries.size(); i++) {
CmdSeries.Cmd cmd = cmdSeries.get(i);
if (cmd.commandCode != 32)
continue;
// init:
if (cmd.command.equals("32,RT-GPS-Log")) {
entries.clear();
}
for (int resNr=0; resNr < cmd.results.size(); resNr++) {
String result[] = cmd.results.get(resNr);
if (result[2].equals("No historical data available"))
continue;
try {
recNr = Integer.parseInt(result[2]);
recCnt = Integer.parseInt(result[3]);
recType = result[4];
timeStamp = serverTime.parse(result[5]);
Log.v(TAG, "processing recType " + recType + " entryNr " + recNr + "/" + recCnt);
if (recType.equals("RT-GPS-Log")) {
try {
// create record:
entry = new Entry();
entry.timeStamp = timeStamp;
int j = 6;
entry.odometerMi = Integer.parseInt(result[j++]) / 10f;
entry.latitude = Double.parseDouble(result[j++]);
entry.longitude = Double.parseDouble(result[j++]);
entry.altitude = Integer.parseInt(result[j++]);
entry.direction = Integer.parseInt(result[j++]);
entry.speed = Integer.parseInt(result[j++]);
entry.gspFix = Integer.parseInt(result[j++]);
entry.gpsStaleCnt = Integer.parseInt(result[j++]);
entry.gsmSignal = Integer.parseInt(result[j++]);
entry.currentPower = Integer.parseInt(result[j++]);
entry.powerUsedWh = Integer.parseInt(result[j++]);
entry.powerRecdWh = Integer.parseInt(result[j++]);
entry.powerDistance = Integer.parseInt(result[j++]);
entry.minPower = Integer.parseInt(result[j++]);
entry.maxPower = Integer.parseInt(result[j++]);
entry.carStatus = Integer.parseInt(result[j++], 16);
// store record:
entries.add(entry);
} catch (Exception e) {
// invalid record: skip
Log.e(TAG, "GPS-Log skip: " + e.getMessage());
}
}
} catch (Exception e) {
// most probably parse error, skip row
e.printStackTrace();
}
}
// no more results: finish
if (cmd.command.equals("32,RT-GPS-Log")) {
try {
// ...?
} catch (Exception e) {
Log.e(TAG, "GPS-Log finish error: " + e.getMessage());
}
}
}
Log.v(TAG, "processCmdResults done");
}
}