data = new ArrayList<>();
try {
Cursor c = database.rawQuery("SELECT * FROM data WHERE sid='" + sid + "' ORDER BY moment ASC", null);
c.moveToFirst();
while (!c.isAfterLast()) {
TimePoint b = new TimePoint(
c.getLong(c.getColumnIndex("moment")),
c.getDouble(c.getColumnIndex("value"))
);
data.add(b);
c.moveToNext();
}
// make sure to close the cursor
c.close();
} catch (Exception e) {
// do nothing
}
return data;
}
/*
* FieldListener implementation
*/
@Override
public void onFieldUpdateEvent(Field field) {
insert(field);
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/database/CanzeOpenHelper.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import lu.fisch.canze.classes.Crashlytics;
public class CanzeOpenHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "lu.fisch.canze.db";
CanzeOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
try {
// create the database
db.execSQL("CREATE TABLE data (sid TEXT NOT NULL, moment INTEGER NOT NULL, value REAL NOT NULL)");
// set an index on the "sid" field
db.execSQL("CREATE INDEX indexSid ON data (sid)");
//db.execSQL("CREATE TABLE data (id_data INTEGER PRIMARY KEY AUTOINCREMENT, sid TEXT NOT NULL, moment INTEGER NOT NULL, value REAL NOT NULL)");
} catch (Exception e) {
Crashlytics.logException(e);
// do nothing
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onCreate(db);
}
public void clear(SQLiteDatabase db) {
try {
db.execSQL("DROP TABLE IF EXISTS data");
} catch (Exception e) {
Crashlytics.logException(e);
// do nothing
}
}
public void reinit(SQLiteDatabase db) {
clear(db);
onCreate(db);
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/devices/CanSee.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.devices;
import java.io.IOException;
import java.util.Calendar;
import lu.fisch.canze.BuildConfig;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.actors.Frame;
import lu.fisch.canze.actors.Message;
import lu.fisch.canze.bluetooth.BluetoothManager;
/**
* Created by robertfisch on 07.09.2015.
*/
public class CanSee extends Device {
// *** needed by the "reader" part
//private String buffer = "";
//private final String separator = "\n";
// If there are more than WRONG_THRESHOLD empty answer strings (meaning either just
// EOM as answer or no answer in TIMEOUT milliseconds, Bluetooth will be restarted
private static final int WRONG_THRESHOLD = 20;
private int wrongCount = 0;
// define the timeout we may wait to get an answer
private static final int TIMEOUT_FREE = 250;
private static final int TIMEOUT_ISO = 1000;
// define End Of Message for this type of reader
private static final char EOM = '\n';
// the actual filter
//private int fieldIndex = 0;
// the thread that polls the data to the stack
public void join() throws InterruptedException {
pollerThread.join();
}
// send a command and wait for an answer
private String sendAndWaitForAnswer(String command, int waitMillis, int timeout) {
// empty incoming buffer. This is necessary to ensure things get not horribly out of sync
try {
while (BluetoothManager.getInstance().available() > 0) {
BluetoothManager.getInstance().read();
}
} catch (IOException e) {
// ignore
}
// send the command
if (command != null)
// prefix fir EOM to make sure the previous command is done!
//BluetoothManager.getInstance().write("\r\n"+command + "\r\n");
BluetoothManager.getInstance().write(command + "\n");
//MainActivity.debug("Send > "+command);
// wait if needed
if (waitMillis > 0)
try {
Thread.sleep(waitMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
// init the buffer
boolean stop = false;
//Do not use StringBuilder as it's not thread safe
//StringBuilder readBuffer = new StringBuilder();
StringBuilder readBuffer = new StringBuilder();
// wait for answer
long start = Calendar.getInstance().getTimeInMillis();
long runtime = 0;
while (!stop && runtime < timeout) {
//MainActivity.debug("Delta = "+(Calendar.getInstance().getTimeInMillis()-start));
try {
if (BluetoothManager.getInstance().available() > 0) { // read a byte
int data = BluetoothManager.getInstance().read();
//MainActivity.debug("Received byte:" + Integer.toHexString(data));
if (data != -1) { // if it is a real one
char ch = (char) data; // convert it to a character
if (ch == EOM) { // stop if we reached the end or if no more data is available
stop = true;
} else {
// add it to the readBuffer
readBuffer.append(ch);
}
}
//} else {
// //stop = true;
}
} catch (IOException e) {
e.printStackTrace();
}
runtime = Calendar.getInstance().getTimeInMillis() - start;
}
//MainActivity.debug("Recv < " + readBuffer + ", runtime:" + runtime);
return readBuffer.toString().replaceAll("\\s", ""); // all whitespace is removed
}
@Override
public void clearFields() {
super.clearFields();
}
@Override
public Message requestFreeFrame(Frame frame) {
// build the command string to send to the remote device
// CanSee maintains a table of all received free frames and immediately responds with
// the last known frame
String command = "g" + frame.getFromIdHex();
return responseToMessage(frame, command, TIMEOUT_FREE);
}
@Override
public Message requestIsoTpFrame(Frame frame) {
// build the command string to send to the remote device
// Note that all ISOTP handling is done by the CanSee device
String command = "i" + frame.getFromIdHex() + "," + frame.getRequestId() + "," + frame.getResponseId();
return responseToMessage(frame, command, TIMEOUT_ISO);
}
private Message responseToMessage(Frame frame, String command, int timeout) {
// convert CanSee output to a Message object
MainActivity.debug("CanSee.rtm.send [" + command + "]");
String text = sendAndWaitForAnswer(command, 0, timeout); // send and wait for an answer, no delay
MainActivity.debug("CanSee.rtm.receive [" + text + "]");
if (text.length() == 0) {
//wrongCount++;
if (wrongCount > WRONG_THRESHOLD) {
wrongCount = 0;
(new Thread(() -> {
MainActivity.getInstance().stopBluetooth(false);
MainActivity.getInstance().reloadBluetooth(false);
})).start();
}
return new Message(frame, "-E-CanSee.rtm.empty", true);
}
// split up the fields
String[] pieces = text.trim().split(",");
if (pieces.length < 2) {
MainActivity.debug("CanSee.rtm.nocomma [" + text + "]");
return new Message(frame, "-E-CanSee.rtm.nocomma:" + text, true);
}
int id;
try {
id = Integer.parseInt(pieces[0].trim(), 16);
} catch (NumberFormatException e) {
MainActivity.debug("CanSee.rtm.Nan [" + text + "]");
return new Message(frame, "-E-CanSee.rtm.Nan:" + text, true);
}
if (id != frame.getFromId()) {
MainActivity.debug("CanSee.rtm.diffid [" + text + "]");
return new Message(frame, "-E-CanSee.rtm.diffid:" + text, true);
}
wrongCount = 0;
return new Message(frame, pieces[1].trim().toLowerCase(), false);
}
@Override
public boolean initDevice(int toughness) {
return initDevice(toughness, 1);
}
@Override
protected boolean initDevice(int toughness, int retries) {
if (BuildConfig.BRANCH.equals("master") || !BuildConfig.DEBUG) {
sendAndWaitForAnswer("n110,0", 0, TIMEOUT_FREE); // disable all serial when on master branch
sendAndWaitForAnswer("n114,0", 0, TIMEOUT_FREE); // disable all debugging when on master branch
} else {
sendAndWaitForAnswer("n110,0", 0, TIMEOUT_FREE); // enable all serial
sendAndWaitForAnswer("n114,fe", 0, TIMEOUT_FREE); // enable all default debugging, except reception of free frames
}
lastInitProblem = "";
return true;
}
@Override
public void stopAndJoin() {
super.stopAndJoin();
BluetoothManager.getInstance().disconnect();
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/devices/Device.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.devices;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.actors.Field;
import lu.fisch.canze.actors.Fields;
import lu.fisch.canze.actors.Frame;
import lu.fisch.canze.actors.Message;
import lu.fisch.canze.actors.VirtualField;
import lu.fisch.canze.bluetooth.BluetoothManager;
import lu.fisch.canze.database.CanzeDataSource;
/**
* This class defines an abstract device. It has to manage the device related
* decoding of the incoming data as well as the data flow to the device or
* whatever is needed to "talk" to it.
*
* Created by robertfisch on 07.09.2015.
*/
public abstract class Device {
public static final int INTERVAL_ASAP = 0; // follows frame rate
public static final int INTERVAL_ASAPFAST = -1; // truly as fast as possible
public static final int INTERVAL_ONCE = -2; // one shot
protected static final int TOUGHNESS_HARD = 0; // hardest reset possible (ie atz)
protected static final int TOUGHNESS_MEDIUM = 1; // medium reset (i.e. atws)
protected static final int TOUGHNESS_SOFT = 2; // softest reset (i.e atd for ELM)
protected static final int TOUGHNESS_NONE = 100; // just clear error status
private final double minIntervalMultiplicator = 1.3;
private final double maxIntervalMultiplicator = 2.5;
double intervalMultiplicator = minIntervalMultiplicator;
private boolean deviceIsInitialized = false; // if true initConnection will only start a new pollerthread
/* ----------------------------------------------------------------
* Attributes
\ -------------------------------------------------------------- */
/**
* A device will "monitor" or "request" a given number of fields from
* the connected CAN-device, so this is the list of all fields that
* have to be read and updated.
*/
protected final ArrayList fields = new ArrayList<>();
/**
* Some fields will be custom, activity based
*/
private ArrayList activityFieldsScheduled = new ArrayList<>();
private ArrayList activityFieldsAsFastAsPossible = new ArrayList<>();
/**
* Some other fields will have to be queried anyway,
* such as e.g. the speed --> safe mode driving
*/
private ArrayList applicationFields = new ArrayList<>();
/**
* The index of the actual field to query.
* Loops over ther "fields" array
*/
//protected int fieldIndex = 0;
private int activityFieldIndex = 0;
private boolean pollerActive = false;
Thread pollerThread;
/**
* lastInitProblem should be filled with a descriptive problem description by the initDevice implementation. In normal operation we don't care
* because a device either initializes or not, but for testing a new device this can be very helpful.
*/
String lastInitProblem = "";
/* ----------------------------------------------------------------
* Abstract methods (to be implemented in each "real" device)
\ -------------------------------------------------------------- */
/**
* A device may need some initialisation before data can be requested.
*/
public void initConnection() {
MainActivity.debug("Device.initConnection: start");
if (BluetoothManager.getInstance().isConnected()) {
MainActivity.debug("Device.initConnection: BT is connected");
// make sure we only have one poller task
if (pollerThread == null) {
MainActivity.debug("Device.initConnection: starting new poller");
// post a task to the UI thread
setPollerActive(true);
Runnable r = new Runnable() {
@Override
public void run() {
// if the device has been initialised and we got an answer
// TOUGHNESS_NONE does basically nothing
if (initDevice(deviceIsInitialized ? TOUGHNESS_NONE : TOUGHNESS_HARD)) {
deviceIsInitialized = true;
while (isPollerActive()) {
// MainActivity.debug("Device: inside poller thread");
if (applicationFields.size() + activityFieldsScheduled.size() + activityFieldsAsFastAsPossible.size() == 0
|| !BluetoothManager.getInstance().isConnected()) {
// MainActivity.debug("Device.poller: no work");
try {
if (isPollerActive())
Thread.sleep(1000);
else return;
} catch (Exception e) {
// ignore a sleep exception
}
}
// query a field
else {
if (isPollerActive()) {
MainActivity.debug("Device.poller: Doing next query");
queryNextFilter();
} else return;
}
}
// dereference the poller thread (it i stopped now anyway!)
MainActivity.debug("Device.poller stopped");
pollerThread = null;
} else {
MainActivity.debug("Device.poller: initDevice failed");
deviceIsInitialized = false;
// first check if we have not yet been killed!
if (isPollerActive()) {
MainActivity.debug("Device.poller: restarting Bluetooth");
// drop the BT connexion and try again
(new Thread(new Runnable() {
@Override
public void run() {
// stop the BT but don't reset the device registered fields
MainActivity.getInstance().stopBluetooth(false);
// reload the BT with filter registration
MainActivity.getInstance().reloadBluetooth(false);
//BluetoothManager.getInstance().connect();
pollerThread = null; // but we are still quitting the poller thread
}
})).start();
}
}
}
};
pollerThread = new Thread(r);
// start the thread
pollerThread.start();
} // never mind, the BT is active, and the poller thread is running. Nothing to do
} else {
MainActivity.debug("Device.initConnection: BT is not connected");
if (pollerThread != null && pollerThread.isAlive()) {
MainActivity.debug("Device.initConnection: stopping poller");
setPollerActive(false);
try {
pollerThread.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public Message injectRequest(String sid) {
Field field = Fields.getInstance().getBySID(sid);
if (field == null) return null;
return injectRequest(field.getFrame());
}
// stop the poller and request a frame (new!)
public Message injectRequest(Frame frame) {
if (frame == null) return null;
// stop the poller and wait for it to become inactive
stopAndJoin();
Message message = requestFrame(frame);
// restart the poller
initConnection();
// return the captured message
return message;
}
// stop the poller and request multiple frames (new!)
public Message injectRequests(Frame[] frames) {
return injectRequests(frames, false, false);
}
// stop the poller and request multiple frames (new!)
// this variant will be very useful for ie LoadAllData
public Message injectRequests(Frame[] frames, boolean stopOnError, boolean callOnMessageComplete) {
// stop the poller and wait for it to become inactive
stopAndJoin();
Message message = null;
for (Frame frame : frames) {
if (frame != null) {
message = requestFrame(frame);
if (stopOnError && (message == null || message.isError())) break;
if (message != null && callOnMessageComplete) {
if (!message.isError()) {
message.onMessageCompleteEvent();
} else {
message.onMessageIncompleteEvent();
}
}
}
}
// restart the poller
initConnection();
// return the last captured message
return message;
}
// query the device for the next filter
private void queryNextFilter() {
if (applicationFields.size() + activityFieldsScheduled.size() + activityFieldsAsFastAsPossible.size() > 0) {
try {
Field field = getNextField();
if (field == null) {
// MainActivity.debug("Device: got no next field --> sleeping");
// no next field ---> sleep
try {
Thread.sleep(200);
} catch (Exception e) {
// ignore a sleep exception
}
} else {
// long start = Calendar.getInstance().getTimeInMillis();
// MainActivity.debug("Device: queryNextFilter: " + field.getSID());
MainActivity.getInstance().dropDebugMessage(field.getSID());
// get the data
Message message = requestFrame(field.getFrame());
// test if we got something
if (!message.isError()) {
MainActivity.getInstance().appendDebugMessage("ok");
// trigger the compete event of the message. It will update all fields linked to it's corresponding frame
message.onMessageCompleteEvent();
if (field.getInterval() == INTERVAL_ONCE) {
removeActivityField(field);
}
} else {
if (message.isError7f()) {
message.onMessageIncompleteEvent();
return;
}
// one plain retry
MainActivity.getInstance().appendDebugMessage("...");
message = requestFrame(field.getFrame());
if (!message.isError()) {
MainActivity.getInstance().appendDebugMessage("ok");
message.onMessageCompleteEvent();
if (field.getInterval() == INTERVAL_ONCE) {
removeActivityField(field);
}
} else {
if (message.isError7f()) {
message.onMessageIncompleteEvent();
return;
}
MainActivity.getInstance().appendDebugMessage("fail");
// failed after single retry. Mark underlying fields as updated to avoid
// queue clogging. The frame will have to get back to the end of the queue
message.onMessageIncompleteEvent();
// reset if something went wrong ...
// ... but only if we are not asked to stop!
if (BluetoothManager.getInstance().isConnected()) {
MainActivity.debug("Device.queryNextFilter: Re-initializing");
deviceIsInitialized = false; // force a true device init
initDevice(TOUGHNESS_MEDIUM, 2); // toughness = 1, retries = 2
}
}
}
}
}
// if any error occures, reset the fieldIndex
catch (Exception e) {
e.printStackTrace();
}
}
}
private Field getNextField() {
long referenceTime = Calendar.getInstance().getTimeInMillis();
synchronized (fields) {
if (applicationFields.size() > 0) {
// get the first field (the one with the smallest lastRequest time
Field field = Collections.min(applicationFields, new Comparator() {
@Override
public int compare(Field lhs, Field rhs) {
return (int) (lhs.getLastRequest() + lhs.getInterval() - (rhs.getLastRequest() + rhs.getInterval()));
}
});
// return it's index in the global registered field array
if (field.isDue(referenceTime)) {
//MainActivity.debug(Calendar.getInstance().getTimeInMillis()/1000.+" > Chosing: "+field.getSID());
MainActivity.debug("Device.getNextField (" + pollerThread.getId() + "): applicationFields, " + field.getSID());
return field;
}
}
// take the next custom field
if (activityFieldsScheduled.size() > 0) {
// get the first field (the one with the smallest lastRequest time
Field field = Collections.min(activityFieldsScheduled, new Comparator() {
@Override
public int compare(Field lhs, Field rhs) {
return (int) (lhs.getLastRequest() + lhs.getInterval() - (rhs.getLastRequest() + rhs.getInterval()));
}
});
// return it's index in the global registered field array
if (field.isDue(referenceTime)) {
//MainActivity.debug(Calendar.getInstance().getTimeInMillis()/1000.+" > Chosing: "+field.getSID());
MainActivity.debug("Device.getNextField (" + pollerThread.getId() + "): activityFieldsScheduled, " + field.getSID());
return field;
}
}
if (activityFieldsAsFastAsPossible.size() > 0) {
activityFieldIndex = (activityFieldIndex + 1) % activityFieldsAsFastAsPossible.size();
Field field = activityFieldsAsFastAsPossible.get(activityFieldIndex);
MainActivity.debug("Device.getNextField (" + pollerThread.getId() + "): activityFieldsAsFastAsPossible, " + field.getSID());
return field;
}
MainActivity.debug("Device.getNextField (" + pollerThread.getId() + "): empty:" + applicationFields.size() + " / " + activityFieldsScheduled.size() + " / " + activityFieldsAsFastAsPossible.size());
return null;
}
}
public void join() throws InterruptedException {
if (pollerThread != null)
pollerThread.join();
}
/* ----------------------------------------------------------------
* Methods (that will be inherited by any "real" device)
\ -------------------------------------------------------------- */
/**
* This method clears the list of monitored fields,
* but only the custom ones ...
*/
public void clearFields() {
MainActivity.debug("Device.clearFields: start");
synchronized (fields) {
activityFieldsScheduled.clear();
activityFieldsAsFastAsPossible.clear();
fields.clear();
fields.addAll(applicationFields);
}
}
/**
* A CAN message will trigger updates for all connected fields, meaning
* any field with the same ID and the same responseID will be updated.
* For this reason we don't need to query these fields multiple times
* in one turn.
*
* @param _field the field to be tested
* @return boolean true if field's frame is already monitored
*/
private boolean containsField(Field _field) {
for (int i = 0; i < fields.size(); i++) {
Field field = fields.get(i);
if (field.getId() == _field.getId() && field.getResponseId().equals(_field.getResponseId()))
return true;
}
return false;
}
private boolean containsApplicationField(Field _field) {
for (int i = 0; i < applicationFields.size(); i++) {
Field field = applicationFields.get(i);
if (field.getId() == _field.getId() && field.getResponseId().equals(_field.getResponseId()))
return true;
}
return false;
}
private boolean containsActivityFieldScheduled(Field _field) {
for (int i = 0; i < activityFieldsScheduled.size(); i++) {
Field field = activityFieldsScheduled.get(i);
if (field.getId() == _field.getId() && field.getResponseId().equals(_field.getResponseId()))
return true;
}
return false;
}
private boolean containsActivityFieldAsFastAsPossible(Field _field) {
for (int i = 0; i < activityFieldsAsFastAsPossible.size(); i++) {
Field field = activityFieldsAsFastAsPossible.get(i);
if (field.getId() == _field.getId() && field.getResponseId().equals(_field.getResponseId()))
return true;
}
return false;
}
/**
* Method to add a field to the list of monitored field.
* The field is also immediately registered onto the device.
*
* @param field the field to be added
*/
private void addActivityField(final Field field) {
// ass already present listeners are no being re-registered, do this always
// register it to be saved to the database
field.addListener(CanzeDataSource.getInstance());
if (!field.isVirtual()) {
synchronized (fields) {
if (!containsField(field)) {
// add it to the lists
fields.add(field);
activityFieldsAsFastAsPossible.add(field);
// if the scheduled list constains the same frame id,
// it can be removed there
if (containsActivityFieldScheduled(field))
activityFieldsScheduled.remove(field);
}
if (!containsActivityFieldAsFastAsPossible(field)) {
activityFieldsAsFastAsPossible.add(field);
// if the scheduled list constains the same frame id,
// it can be removed there
if (containsActivityFieldScheduled(field))
activityFieldsScheduled.remove(field);
}
}
}
// register real fields on which a virtual field may depend
else {
VirtualField virtualField = (VirtualField) field;
for (Field realField : virtualField.getFields()) {
addActivityField(realField);
}
}
}
/*
JM: I made addActivity without interval private, as to change the behavior with interval 0, which
is used throughout the code to mean "we don't care, as fast as possible"
*/
public void addActivityField(final Field field, int interval) {
/*
JM changed behavior
interval > 0 is interval
interval == INTERVAL_ASAP (0) is frame repeat interval (as given in the frame assets, especially relevant for freeframes)
interval == INTERVAL_ASAPFAST (-1) is ASAP (note: old behavior is ASAP for ANYTHING negative, but I am pretty sure only -1
was ever used). This should be used sparingly, if at all. The only reason I can think of is for a fast ISO-TP field
which has no known interval rate
interval == INTERVAL_ONCE) (-2) is add, but remove after one successful execution (in the poller thread). This is very
useful for i.e. tester present messages
*/
if (field.isSelfPropelled()) { //if self propelled, do not add to queue
return;
} else if (interval > INTERVAL_ASAP) { // interval is a ms value. Continue
// Nothing
} else if (interval == INTERVAL_ASAP) { // if INTERVAL_ASAP, get from the frame definition
interval = field.getFrame().getInterval();
} else if (interval == INTERVAL_ASAPFAST) { // if INTERVAL_ASAP, add to the ASAP queue
addActivityField(field);
return;
} else if (interval == INTERVAL_ONCE) { // if INTERVAL_ONCE. Continue
// Nothing. The INTERVAL_ONCE will be picked up by the poller
} else { // abort
return;
}
// ass already present listeners are no being re-registered, do this always
// register it to be saved to the database
field.addListener(CanzeDataSource.getInstance());
if (!field.isVirtual()) {
synchronized (fields) {
if (!containsField(field)) {
// add it to the lists
fields.add(field);
activityFieldsScheduled.add(field);
// set the fields query interval
field.setInterval(interval);
}
if (!containsActivityFieldScheduled(field)) {
// only add it this field id is not yet on the list of the
// request as fast as possible list.
if (!containsActivityFieldAsFastAsPossible(field))
activityFieldsScheduled.add(field);
// the fields interval will be ignored as the one from the
// applicationFields has priority
} else {
// the smallest interval is the one to take
if (interval < field.getInterval())
field.setInterval(interval);
}
}
}
// register real fields on which a virtual field may depend
else {
VirtualField virtualField = (VirtualField) field;
for (Field realField : virtualField.getFields()) {
// increase interval
addActivityField(realField, interval * virtualField.getFields().size());
}
}
}
public void removeActivityField(final Field field) {
synchronized (fields) {
// only remove from the custom fields
if (activityFieldsScheduled.remove(field)) {
//field.setInterval(Integer.MAX_VALUE);
//return; /*
// remove it from the database if it is not on the other list
if (!containsApplicationField(field) && !containsActivityFieldAsFastAsPossible(field)) {
fields.remove(field);
field.setInterval(Integer.MAX_VALUE);
// un-register it ...
field.removeListener(CanzeDataSource.getInstance());
}
}
}
}
public void addApplicationField(final Field field, int interval) {
// as already present listeners are not being re-registered, do this always
// register it to be saved to the database
field.addListener(CanzeDataSource.getInstance());
if (!field.isVirtual()) {
synchronized (fields) {
if (!containsField(field)) {
// set the fields query interval
field.setInterval(interval);
// add it to the two lists
fields.add(field);
applicationFields.add(field);
}
}
}
// register real fields on which a virtual field may depend
else {
VirtualField virtualField = (VirtualField) field;
for (Field realField : virtualField.getFields()) {
// increase interval
addApplicationField(realField, interval * virtualField.getFields().size());
}
}
}
public void removeApplicationField(final Field field) {
synchronized (fields) {
// only remove from the custom fields
if (applicationFields.remove(field)) {
// remove it from the database if it is not on the other list
if (!containsActivityFieldScheduled(field) && !containsActivityFieldAsFastAsPossible(field)) {
fields.remove(field);
field.setInterval(Integer.MAX_VALUE);
// un-register it ...
field.removeListener(CanzeDataSource.getInstance());
}
}
}
// remove depenand fields
// ATTENTION; remove the field, despite if it is used by some other VF or not!
//if(field.isVirtual())
//{
// may break something, so please do it manually if really needed!
//}
}
/* ----------------------------------------------------------------
* Methods (that will be inherited by any "real" device)
\ -------------------------------------------------------------- */
public void init(boolean reset) {
// init the connection
initConnection();
if (reset) {
// clean all filters (just to make sure)
clearFields();
MainActivity.debug("Device.init: done, reset");
} else
MainActivity.debug("Device.init: done, noreset");
}
/**
* Stop the poller thread and wait for it to be finished
*/
public void stopAndJoin() {
MainActivity.debug("Device.stopAndJoin: start");
setPollerActive(false);
try {
if (pollerThread != null && pollerThread.isAlive()) {
MainActivity.debug("Device.stopAndJoin: poller informed. Joining thread");
if (pollerThread != null)
pollerThread.join();
pollerThread = null;
} else MainActivity.debug("Device.stopAndJoin: pollerThread is null");
} catch (Exception e) {
e.printStackTrace();
}
MainActivity.debug("Device.stopAndJoin: poller stopped");
}
private boolean isPollerActive() {
return pollerActive;
}
void setPollerActive(boolean pollerActive) {
this.pollerActive = pollerActive;
}
/**
* Request a field from the device depending on the
* type of field.
*
* @param frame the field to be requested
* @return Message containing the response or an error
*/
public Message requestFrame(Frame frame) {
Message msg;
if (frame == null) return null;
if (frame.isIsoTp()) {
msg = requestIsoTpFrame(frame);
} else {
if (MainActivity.altFieldsMode) MainActivity.toast(MainActivity.TOAST_NONE, "Free frame in ISOTP mode:" + frame.getRID()); // MainActivity.debug("********* free frame in alt mode ********: " + frame.getRID());
msg = requestFreeFrame(frame);
}
if (msg.isError()) {
MainActivity.debug("Device.requestframe: " + frame.getRID() + " returned error " + msg.getError());
// when the answer is empty, the timeout is to low --> increase it!
if (intervalMultiplicator < maxIntervalMultiplicator) {
intervalMultiplicator += 0.1;
MainActivity.debug("Device.requestframe: intervalMultiplicator+ = " + intervalMultiplicator);
}
} else {
MainActivity.debug("Device.requestframe: request for " + frame.getRID() + " returned data " + msg.getData());
// theory: when the answer is good, we might recover slowly --> decrease it!
// jm: but never below 1 ----> 2015-12-14 changed 10 1.3
if (intervalMultiplicator > minIntervalMultiplicator) {
intervalMultiplicator -= 0.01;
MainActivity.debug("Device.requestframe: intervalMultiplicator- = " + intervalMultiplicator);
}
}
return msg;
}
/**
* Request a free-frame type field from the device
*
* @param frame The frame requested
* @return Message
*/
public abstract Message requestFreeFrame(Frame frame);
/**
* Request an ISO-TP frame type from the device
*
* @param frame The frame requested
* @return Message
*/
public abstract Message requestIsoTpFrame(Frame frame);
public abstract boolean initDevice(int toughness);
protected abstract boolean initDevice(int toughness, int retries);
public String getLastInitProblem() {
return lastInitProblem;
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/devices/ELM327.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.devices;
import java.io.IOException;
import java.util.Calendar;
import lu.fisch.canze.R;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.actors.Ecu;
import lu.fisch.canze.actors.Ecus;
import lu.fisch.canze.actors.Frame;
import lu.fisch.canze.actors.Message;
import lu.fisch.canze.bluetooth.BluetoothManager;
/**
* Created by robertfisch on 07.09.2015.
* Main loop fir ELM
*/
public class ELM327 extends Device {
// Implementation of requestFreeFrame and requestIsotpFrame for the eLM327 style KONNWEI and
// comparable drivers. Note that all ISOTP handling is done in this driver, as well as the
// low level serial command interface.
// Note that the output of the driver is an object of the type Message
// This is a fairly complex driver dealing with subtle intricacies and it's better not touched
// without extensive testing afterwards. Any assumpion may safely be considered wrong ;-)
// define the Timeout we may wait to get an answer
private static int DEFAULT_TIMEOUT = 500;
private static int MINIMUM_TIMEOUT = 100;
private int generalTimeout = 500;
// define End Of Message for this type of reader
private static final char EOM1 = '\r';
private static final char EOM2 = '>';
private static final char EOM3 = '?';
private boolean deviceIsInitialized = false;
private int lastId = 0; // used to skip sending unneeded commands
private boolean lastCommandWasFreeFrame = false; // ditto
protected boolean initDevice(int toughness, int retries) {
if (initDevice(toughness)) return true;
while (retries-- > 0) {
MainActivity.debug("ELM327: flushWithTimeout");
flushWithTimeout(500);
MainActivity.debug("ELM327: initDevice(" + toughness + "), " + retries + " retries left");
if (initDevice(toughness)) return true;
}
MainActivity.toast(MainActivity.TOAST_ELM, R.string.message_HardResetFailed);
MainActivity.debug("ELM327: Hard reset failed, restarting Bluetooth ...");
///----- WE ARE HERE INSIDE THE POLLER THREAD, SO
///----- JOINING CAN'T WORK!
// ... but we don't want the next request to happen,
// so we need to stop the poller here anyway, but
// DO NOT JOIN IT!
setPollerActive(false);
(new Thread(new Runnable() {
@Override
public void run() {
// -- give up and restart BT
// stop BT without resetting the registered fields
MainActivity.debug("ELM327: stopBluetooth (via MainActivity)");
MainActivity.getInstance().stopBluetooth(false);
// restart BT without reloading all settings
MainActivity.debug("ELM327: reloadBluetooth (via MainActivity)");
MainActivity.getInstance().reloadBluetooth(false);
}
})).start();
return false;
}
public boolean initDevice(int toughness) {
MainActivity.debug("ELM327: initDevice (" + toughness + ")");
String response;
int elmVersion = 0;
lastInitProblem = "";
// ensure the dongle header field is set again
lastId = 0;
// extremely soft, just clear the global error condition
if (toughness == TOUGHNESS_NONE) {
deviceIsInitialized = true;
return deviceIsInitialized;
}
killCurrentOperation();
if (toughness == TOUGHNESS_HARD || toughness == TOUGHNESS_MEDIUM) {
// the default 500mS should be enough to answer, however, the answer contains various 's, so we need to set untilEmpty to true
response = sendAndWaitForAnswer("atws", 500, true, -1, true);
} else { // TOUGHNESS_WEAK
response = sendAndWaitForAnswer("atd", 500, true, -1, true);
MainActivity.debug("ELM327: version = " + response);
}
MainActivity.debug("ELM327: version: [" + response + "]");
response = response.trim();
if (response.equals("")) {
lastInitProblem = "ELM is not responding (toughness = " + toughness + ")";
MainActivity.toast(MainActivity.TOAST_ELM, lastInitProblem);
return false;
}
// only do version control at a full reset
if (toughness <= TOUGHNESS_MEDIUM) {
if (response.toUpperCase().contains("V1.3")) {
elmVersion = 13;
} else if (response.toUpperCase().contains("V1.4")) {
elmVersion = 14;
} else if (response.toUpperCase().contains("V1.5")) {
elmVersion = 15;
} else if (response.toUpperCase().contains("V2.")) {
elmVersion = 20;
} else if (response.toUpperCase().contains("INNOCAR")) {
elmVersion = 8015;
} else {
lastInitProblem = "Unrecognized ELM version response [" + response.replace("\r", "").replace(" ", "") + "]";
MainActivity.toast(MainActivity.TOAST_ELM, lastInitProblem);
return false;
}
}
deviceIsInitialized = false;
// ***** CanZE version *******************************************************************
// ate0 (no echo. At this point, echo is still on (except when atd was issued), so
// we still need to absorb the echoed command. After this point, echo is
// finally off so we can safely check for OK messages. If the app starts
// responding with toasts showing the responses in brackets equal to the
// commands, somehow the echo was not executed. so maybe we need to check for
// that specific condition in the next command.)
// ats0 (no spaces)
// atsp6 (CAN 500K 11 bit)
// atat1 (auto timing)
// atcaf0 (no formatting)
// atfcsh77b (flow control response ID to 77b. This is needed to be able to set the flow
// control response. Any ID would be fine
// atfcsd300010 (the flow control response data to 300010 (flow control, clear to send,
// all frames, 16 ms wait between frames. Note that it is not possible to let
// the ELM request each frame as the Altered Flow Control only responds to a
// FRST, not a NEXT)
// atfcsm1 (flow control mode 1: ID and data suplied)
//String[] commands = "ate0;ats0;atsp6;atat1;atcaf0;atfcsh77b;atfcsd300010;atfcsm1".split(";");
// ***** DDT4ALL version *****************************************************************
// ate1 (echo on) ==> we don't do this
// ats0 (no spaces)
// ath0 (headers off = default)
// atl0 (Linefeeds off)
// atal (allow long messages)
// atcaf0 (no formatting)
// Here they stop initialization and do the remainder per ECU.
// We do ATSH and ATFCSH too per request (since we constantly switch ECU's,
// (we can optimize that, remember last ECU), however they also do flow control
// initialisation and bus speed, per ECU, which we prefer to do here.
// We also skip ATCRA (see comments at freeframe)
// atfcsh77b (flow control response ID to 77b. This is needed to be able to set the flow
// control response. Any ID would be fine
// atfcsd300000 (the flow control response data to 300010 (flow control, clear to send,
// all frames, no wait between frames. Note that it is not possible to let
// the ELM request each frame as the Altered Flow Control only responds to a
// FRST, not a NEXT)
// atsp6 (CAN 500K 11 bit) ==> might need to change that if we want to support
// pin re-assignment to ie the MM bus
String[] commands = "ate0;ats0;ath0;atl0;atal;atcaf0;atfcsh77b;atfcsd300000;atfcsm1;atsp6".split(";");
boolean first = true;
for (String command : commands) {
if (!initCommandExpectOk(command, first)) {
lastInitProblem = command + " command problem";
return deviceIsInitialized;
}
first = false;
}
if (toughness == TOUGHNESS_HARD) {
switch (elmVersion) {
case 13:
MainActivity.toast(MainActivity.TOAST_ELM, R.string.message_ELM13Ready);
break;
case 14:
MainActivity.toast(MainActivity.TOAST_ELM, R.string.message_ELM13Ready);
break;
case 15:
MainActivity.toast(MainActivity.TOAST_ELM, R.string.message_ELMReady);
break;
case 20:
lastInitProblem = MainActivity.getStringSingle(R.string.message_ELM20Ready);
MainActivity.toast(MainActivity.TOAST_ELM, lastInitProblem);
break;
case 8015:
MainActivity.toast(MainActivity.TOAST_ELM, R.string.message_ELM8015Ready);
break;
// default should never be reached!!
default:
lastInitProblem = MainActivity.getStringSingle(R.string.message_ELMUnknown);
MainActivity.toast(MainActivity.TOAST_ELM, lastInitProblem);
break;
}
}
deviceIsInitialized = true;
return deviceIsInitialized;
}
private void killCurrentOperation() {
// ensure any running operation is stopped
// sending a return might restart the last command. Bad plan.
sendNoWait("x");
// discard everything that still comes in
flushWithTimeoutCore(200, '\0');
// if a command was running, it is interrupted now and the ELM is waiting for a command.
// However, if there was no command running, the x in the buffer will screw up the next
// command. There are two possibilities: Sending a Backspace and hope for the best, or
// sending x and being sure the ELM will report an unknown command (prompt a ? mark),
// as it will be processing either x or xx . We choose the latter and discard
// the ? anser
sendNoWait("x\r");
if (!flushWithTimeoutCore(500, '\0')) {
MainActivity.debug("ELM327: KillCurrentOperation unable to flush after x");
}
}
private void flushWithTimeout(int timeout) {
flushWithTimeout(timeout, '\0');
}
private void flushWithTimeout(int timeout, char eom) {
if (flushWithTimeoutCore(timeout, eom)) return;
killCurrentOperation();
}
private boolean flushWithTimeoutCore(int timeout, char eom) {
// empty incoming buffer
// just make sure there is no previous response
// the ELM might be in a mode where it is spewing out data, and that might put this
// method in an endless loop. If there are more than 100 character flushed, return false
// this should normally be followed by a failure and thus device re-initialisation
int count = 100;
try {
// fast track, don't use expensive calendar.....
if (timeout == 0) {
while (BluetoothManager.getInstance().isConnected() && BluetoothManager.getInstance().available() > 0) {
BluetoothManager.getInstance().read();
if (count-- == 0) return false;
}
} else {
long end = Calendar.getInstance().getTimeInMillis() + timeout;
while (Calendar.getInstance().getTimeInMillis() < end) {
// read a byte
if (!BluetoothManager.getInstance().isConnected()) return false;
if (BluetoothManager.getInstance().available() > 0) {
// absorb the characters
while (BluetoothManager.getInstance().available() > 0) {
int c = BluetoothManager.getInstance().read();
if (c == (int) eom) return true;
if (count-- == 0) return false;
}
// restart the timer
end = Calendar.getInstance().getTimeInMillis() + timeout;
} else {
// let the system breath if there was no data
Thread.sleep(5);
}
}
}
} catch (IOException | InterruptedException e) {
// ignore
}
return true;
}
private boolean initCommandExpectOk(String command) {
return initCommandExpectOk(command, false, true);
}
private boolean initCommandExpectOk(String command, boolean untilEmpty) {
return initCommandExpectOk(command, untilEmpty, true);
}
private boolean initCommandExpectOk(String command, boolean untilEmpty, boolean addReturn) {
String response = "";
for (int i = 2; i > 0; i--) {
if (untilEmpty) {
response = sendAndWaitForAnswer(command, 40, true, -1, addReturn); // wait 40 ms for untilempty
} else {
response = sendAndWaitForAnswer(command, 0, false, -1, addReturn); // // just one line
}
if (response.toUpperCase().contains("OK")) return true; // we're done if we got an OK
if (MainActivity.altFieldsMode) untilEmpty = true; // crappy dongles answer with things like CR > LF [space]
}
// we've tried and tried and failed here
/* if (timeoutLogLevel >= 2 || (timeoutLogLevel >= 1 && !command.startsWith("atma") && command.startsWith("at"))) {
MainActivity.toast("Err " + command + " [" + response.replace("\r", "").replace(" ", "") + "]");
} */
MainActivity.toast(MainActivity.TOAST_ELM, "Error [" + command + "] [" + response.replace("\r", "").replace(" ", "") + "]");
MainActivity.debug("ELM327.initCommandExpectOk c:" + command + ", untilempty:" + untilEmpty + " res:" + response);
return false;
}
private void sendNoWait(String command) {
if (!BluetoothManager.getInstance().isConnected()) return;
if (command != null) {
BluetoothManager.getInstance().write(command);
}
}
private String sendAndWaitForAnswer(String command, int waitMillis) {
return sendAndWaitForAnswer(command, waitMillis, false, -1, true);
}
private String sendAndWaitForAnswer(String command, int waitMillis, int answerLinesCount) {
return sendAndWaitForAnswer(command, waitMillis, false, answerLinesCount, true);
}
private String sendAndWaitForAnswer(String command, int waitMillis, boolean untilEmpty) {
return sendAndWaitForAnswer(command, waitMillis, untilEmpty, -1, true);
}
private String sendAndWaitForAnswer(String command, int waitMillis, boolean untilEmpty, int answerLinesCount, boolean addReturn) {
int maxUntilEmptyCounter = 10;
int maxLengthCounter = 500; // char = nibble, so 2000 bits
if (!BluetoothManager.getInstance().isConnected()) return "";
if (command != null) {
flushWithTimeout(10, '>');
// send the command
BluetoothManager.getInstance().write(command + (addReturn ? "\r" : ""));
}
MainActivity.debug("Send > "+command);
// wait if needed (JM: tbh, I think waiting here is never needed. Any waiting should be handled in the wait for an answer timeout. But that's me.
/* if(waitMillis>0) {
try {
Thread.sleep(waitMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
} */
// init the buffer
boolean stop = false;
StringBuilder readBuffer = new StringBuilder();
// wait for answer
long end = Calendar.getInstance().getTimeInMillis() + generalTimeout;
boolean timedOut = false;
while (!stop && !timedOut) {
//MainActivity.debug("Delta = "+(Calendar.getInstance().getTimeInMillis()-start));
try {
// read a byte
if (BluetoothManager.getInstance().isConnected() && BluetoothManager.getInstance().available() > 0) {
//MainActivity.debug("Reading ...");
int data = BluetoothManager.getInstance().read();
//MainActivity.debug("... done");
// if it is a real one
if (data != -1) {
// we might be JUST approaching the generalTimeout, so give it a chance to get to the EOM,
// end = end + 2;
// convert it to a character
char ch = (char) data;
if (ch == '\n') ch = '\r';
// add it to the readBuffer
readBuffer.append(ch);
// if we reach the end of a line
if (ch == EOM1 || ch == EOM2 || ch == EOM3) {
//MainActivity.debug("ALC: "+answerLinesCount+")\n"+readBuffer);
// decrease awaiting answer lines
answerLinesCount--;
// if we not asked to keep on and we got enough lines, stop
if (!untilEmpty) {
if (answerLinesCount <= 0) { // the number of lines is in
//MainActivity.debug("ELM327: sendAndWaitForAnswer > stop on decimal char [" + data + "]");
stop = true; // so quit
} else // the number of lines is NOT in
{
end = Calendar.getInstance().getTimeInMillis() + generalTimeout; // so restart the timeout
}
} else { // if (untilEmpty) {
stop = (BluetoothManager.getInstance().available() == 0);
// a problem here is that we assume the next character is already available, which might not be the case, so adding.....
if (stop) {
// wait a fraction
try {
//Thread.sleep(50);
if (waitMillis > 50) {
Thread.sleep(400);
} else {
Thread.sleep(50);
}
end = Calendar.getInstance().getTimeInMillis() + generalTimeout;
} catch (InterruptedException e) {
// do nothing
}
stop = (BluetoothManager.getInstance().available() == 0);
} else {
if (--maxUntilEmptyCounter <= 0)
timedOut = true; // well, this is a timed"In", as in, too many lines
}
}
} else {
if (--maxLengthCounter <= 0)
timedOut = true; // well, this is a timed"In", as in, too many lines
}
}
} else {
// let the system breath if there was no data
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// do nothing
}
}
if (Calendar.getInstance().getTimeInMillis() > end) {
timedOut = true;
// MainActivity.toast("Sum Ting Wong on command " + command);
}
} catch (IOException e) {
// ignore: e.printStackTrace();
}
}
// set the flag that a timeout has occurred. someThingWrong can be inspected anywhere, but we reset the device after a full filter has been run
if (timedOut) {
/* if (timeoutLogLevel >= 2 || (timeoutLogLevel >= 1 && (command==null || (!command.startsWith("atma") && command.startsWith("at"))))) {
MainActivity.toast("Timeout on [" + command + "][" + readBuffer.replace("\r", "").replace(" ", "") + "]");
} */
MainActivity.toast(MainActivity.TOAST_ELM, "Timeout on [" + command + "] [" + readBuffer.toString().replace("\r", "").replace(" ", "") + "]");
MainActivity.debug("ELM327: sendAndWaitForAnswer > timed out on [" + command + "] [" + readBuffer.toString().replace("\r", "").replace(" ", "") + "]");
return ("");
}
//MainActivity.debug("ALC: "+answerLinesCount+" && Stop: "+stop+" && Delta: "+(Calendar.getInstance().getTimeInMillis()-start));
MainActivity.debug("Recv < "+readBuffer);
return readBuffer.toString();
}
@Override
public void clearFields() {
super.clearFields();
//fieldIndex=0;
}
@Override
public Message requestFreeFrame(Frame frame) {
if (!deviceIsInitialized) {
return new Message(frame, "-E-Re-initialisation needed", true);
}
String hexData;
// ensure the ATCRA filter is reset in the next NON free frame request
lastCommandWasFreeFrame = true;
// EML needs the filter to be 3 hex symbols and contains the from CAN id of the ECU.
// getFromIdHex returns 3 chars for 11 bit id, and 8 bits for a 29 bit id
String emlFilter = frame.getFromIdHex();
MainActivity.debug("ELM327: requestFreeFrame: atcra" + emlFilter);
if (!initCommandExpectOk("atcra" + emlFilter))
return new Message(frame, "-E-Problem sending atcra command", true);
//sendAndWaitForAnswer("atcra" + emlFilter, 400);
// atma (wait for one answer line)
generalTimeout = (int) (frame.getInterval() * intervalMultiplicator + 50);
if (generalTimeout < MINIMUM_TIMEOUT) generalTimeout = MINIMUM_TIMEOUT;
MainActivity.debug("ELM327: requestFreeFrame > TIMEOUT = " + generalTimeout);
// 10 ms plus repeat time timeout, do not wait until empty, do not count lines, add \r to command
hexData = sendAndWaitForAnswer("atma", frame.getInterval() + 10);
MainActivity.debug("ELM327: requestFreeFrame > hexData = [" + hexData + "]");
// the dongle starts babbling now. sendAndWaitForAnswer should stop at the first full line
// ensure any running operation is stopped
// sending a return might restart the last command. Bad plan.
sendNoWait("x");
// let it settle down, the ELM should indicate STOPPED then prompt >
flushWithTimeout(100, '>');
generalTimeout = DEFAULT_TIMEOUT;
// atar (clear filter)
// AM has suggested the atar might not be neccesary as it might only influence cra filters and they are always set
// however, make sure proper flushing is done
// if cra does influence ISO-TP requests, an small optimization might be to only sending an atar when switching from free
// frames to isotp frames.
// if (!initCommandExpectOk("atar")) someThingWrong |= true;
hexData = hexData.trim();
if (hexData.equals(""))
return new Message(frame, "-E-data empty", true);
else
return new Message(frame, hexData, false);
}
@Override
public Message requestIsoTpFrame(Frame frame) {
if (!deviceIsInitialized) {
return new Message(frame, "-E-Re-initialisation needed", true);
}
String hexData;
int len;
// PERFORMANCE ENHANCEMENT: only send ATAR if coming from a free frame
if (lastCommandWasFreeFrame) {
// atar (clear filter set by free frame capture method)
if (!initCommandExpectOk("atar")) {
return new Message(frame, "-E-Problem sending atar command", true);
}
lastCommandWasFreeFrame = false;
}
// PERFORMANCE ENHANCEMENT II: lastId contains the CAN id of the previous ISO-TP command. If
// the current ID is the same, no need to re-address that ECU. Even if different ECU but
// same id length, no change 11/29 bit mode needed either.
// (re)enabled after 1.54 release
// lastId = 0;
if (lastId != frame.getFromId()) {
// IIa: 11/29 bit mode change only if needed
if (frame.isExtended()) {
if (lastId < 0x1000) {
// switch to 29 bit
if (!initCommandExpectOk("atsp7",true))
return new Message(frame, "-E-Problem sending atsp7 command", true);
// set prio using AT CP
if (!initCommandExpectOk("atcp" + frame.getToIdHexMSB(), true))
return new Message(frame, "-E-Problem sending atcp command", true);
}
} else {
if (lastId >= 0x1000 || lastId == 0) { // 0 check if optimization II is disabled
// switch to 11 bit
if (!initCommandExpectOk("atsp6", true))
return new Message(frame, "-E-Problem sending atsp6 command", true);
}
}
// change ECU address
// Set header
if (!initCommandExpectOk("atsh" + frame.getToIdHexLSB()))
return new Message(frame, "-E-Problem sending atsh command", true);
// Set filter
if (!initCommandExpectOk("atcra" + frame.getFromIdHex()))
return new Message(frame, "-E-Problem sending atcra command", true);
// Set flow control response ID
if (!initCommandExpectOk("atfcsh" + frame.getToIdHex()))
return new Message(frame, "-E-Problem sending atfcsh command", true);
lastId = frame.getFromId();
}
// ISOTP outgoing starts here
int outgoingLength = frame.getRequestId().length();
String elmResponse = "";
if (outgoingLength <= 14) {
// SINGLE transfers up to 7 bytes. If we ever implement extended addressing (which is
// not the same as 29 bits mode) this driver considers this simply data
// 022104 ISO-TP single frame - length 2 - payload 2104, which means PID 21 (??), id 04 (see first tab).
String elmCommand = "0" + (outgoingLength / 2) + frame.getRequestId();
// send SING frame.
elmResponse = sendAndWaitForAnswer(elmCommand, 0, false).replace("\r", "");
} else {
int startIndex = 0;
int endIndex = 12;
// send FRST frame.
String elmCommand = String.format("1%03X", outgoingLength / 2) + frame.getRequestId().substring(startIndex, endIndex);
//flushWithTimeout(500, '>');
String elmFlowResponse = sendAndWaitForAnswer(elmCommand, 0, false).replace("\r", "");
startIndex = endIndex;
if (startIndex > outgoingLength) startIndex = outgoingLength;
endIndex += 14;
if (endIndex > outgoingLength) endIndex = outgoingLength;
int next = 1;
while (startIndex < outgoingLength) {
// prepare NEXT frame.
elmCommand = String.format("2%01X", next) + frame.getRequestId().substring(startIndex, endIndex);
// for the moment we ignore block size, just 1 or all. Also ignore delay
if (elmFlowResponse.startsWith("3000")) {
// The receiving ECU expects all data to be sent without further flow control,
// the ELM still answers with at least a \n after each sent frame.
// Since there are no further flow control frames, we just pretent the answer
// of each frame is the actual answer and won't change the FlowResponse
//flushWithTimeout(500, '>');
elmResponse = sendAndWaitForAnswer(elmCommand, 0, false).replace("\r", "");
} else if (elmFlowResponse.startsWith("30")) {
// The receiving ECU expects the next frame of data to be sent, and it will
// respond with the next flow control command, or the actual answer. We just
// pretent the answer of the frame is both the actual answer as wel as the next
// FlowResponse
//flushWithTimeout(500, '>');
elmFlowResponse = sendAndWaitForAnswer(elmCommand, 0, false).replace("\r", "");
elmResponse = elmFlowResponse;
} else {
return new Message(frame, "-E-ISOTP tx flow Error:" + elmFlowResponse, true);
}
startIndex = endIndex;
if (startIndex > outgoingLength) startIndex = outgoingLength;
endIndex += 14;
if (endIndex > outgoingLength) endIndex = outgoingLength;
if (next == 15) next = 0;
else next++;
}
}
// ISOTP receiver starts here
// clean-up if there is mess around
elmResponse = elmResponse.trim();
if (elmResponse.startsWith(">")) elmResponse = elmResponse.substring(1);
// quit on error conditions
if (elmResponse.compareTo("CAN ERROR") == 0) {
return new Message(frame, "-E-Can Error", true);
} else if (elmResponse.compareTo("?") == 0) {
return new Message(frame, "-E-Unknown command", true);
} else if (elmResponse.compareTo("") == 0) {
return new Message(frame, "-E-Empty result", true);
}
// get type (first nibble of first line)
switch (elmResponse.substring(0, 1)) {
case "0": // SINGLE frame
try {
len = Integer.parseInt(elmResponse.substring(1, 2), 16);
// remove 2 nibbles (type + length)
hexData = elmResponse.substring(2);
// and we're done
} catch (StringIndexOutOfBoundsException e) {
return new Message(frame, "-E-ISOTP rx unexpected length of SING frame:" + elmResponse, true);
} catch (NumberFormatException e) {
return new Message(frame, "-E-ISOTP rx uninterpretable length of SING frame:" + elmResponse, true);
}
break;
case "1": // FIRST frame
try {
len = Integer.parseInt(elmResponse.substring(1, 4), 16);
// remove 4 nibbles (type + length)
hexData = elmResponse.substring(4);
} catch (StringIndexOutOfBoundsException e) {
return new Message(frame, "-E-ISOTP rx unexpected length of FRST frame:" + elmResponse, true);
} catch (NumberFormatException e) {
return new Message(frame, "-E-ISOTP rx uninterpretable length of FRST frame:" + elmResponse, true);
}
// calculate the # of frames to come. 6 byte are in and each of the 0x2 frames has a payload of 7 bytes
int framesToReceive = len / 7; // read this as ((len - 6 [remaining characters]) + 6 [offset to / 7, so 0->0, 1-7->7, etc]) / 7
// get all remaining 0x2 (NEXT) frames
String lines0x1 = sendAndWaitForAnswer(null, 0, framesToReceive);
// split into lines with hex data
String[] hexDataLines = lines0x1.split("[\\r]+");
int next = 1;
for (String hexDataLine : hexDataLines) {
// ignore empty lines
hexDataLine = hexDataLine.trim();
if (hexDataLine.length() > 2) {
// check the proper sequence
if (hexDataLine.startsWith(String.format("2%01X", next))) {
// cut off the first byte (type + sequence) and add to the result
hexData += hexDataLine.substring(2);
} else {
return new Message(frame, "-E-ISOTP rx out of sequence:" + hexDataLine, true);
}
if (next == 15) next = 0;
else next++;
}
}
break;
default: // a NEXT, FLOWCONTROL should not be received. Neither should any other string (such as NO DATA)
flushWithTimeout(400, '>');
return new Message(frame, "-E-ISOTP rx unexpected 1st nibble of 1st frame:" + elmResponse, true);
}
// There was spurious error here, that immediately sending another command STOPPED the still not entirely finished ISO-TP command.
// It was probably still sending "OK>" or just ">". So, the next command files and if it was i.e. an atcra f a free frame capture,
// the following ATMA immediately overwhelmed the ELM as no filter was set.
// As a solution, added this wait for a > after an ISO-TP command.
flushWithTimeout(400, '>');
len *= 2;
// Having less data than specified in length is actually an error, but at least we do not need so substr it
// if there is more data than specified in length, that is OK (filler bytes in the last frame), so cut those away
hexData = (hexData.length() <= len) ? hexData.trim().toLowerCase() : hexData.substring(0, len).trim().toLowerCase();
if (hexData.equals(""))
return new Message(frame, "-E-ISOTP rx data empty", true);
else
return new Message(frame, hexData.toLowerCase(), false);
}
@Override
public void stopAndJoin() {
super.stopAndJoin();
BluetoothManager.getInstance().disconnect();
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/devices/Http.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.devices;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import lu.fisch.canze.BuildConfig;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.actors.Frame;
import lu.fisch.canze.actors.Message;
public class Http extends Device {
private static final int TIMEOUT_DUMMMY = 0;
private String urlLeader;
public void join() throws InterruptedException {
pollerThread.join();
}
private String sendAndWaitForAnswer(String command, int waitMillis, int timeout) {
String result;
try {
result = httpGet(urlLeader + command);
//MainActivity.debug("Http: jsonLineResult:" + jsonLine);
if (result.compareTo("") == 0) {
return "-E-result from httpGet empty";
}
if (result.compareTo("") == 0) {
MainActivity.debug("Http: getMessageResult is empty");
return "-E-result from json element R empty";
}
if (result.substring(0, 1).compareTo("-") == 0) {
MainActivity.debug("Http: getMessageResult is an error or warning");
return result;
}
} catch (Exception e) {
MainActivity.debug("Http: Exception");
return "-E-Exception";
}
return result;
}
private String httpGet(String urlString) {
try {
// MainActivity.debug("Http: httpGet url:" + urlString);
URL url = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
urlConnection.setConnectTimeout(10000);
// MainActivity.debug("Http: httpGet start connection and get result");
InputStream ips = urlConnection.getInputStream();
// MainActivity.debug("Http: httpGet ips opened");
BufferedInputStream in = new BufferedInputStream(ips);
// MainActivity.debug("Http: httpGet in opened");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String st;
StringBuilder stringBuilder = new StringBuilder(200);
while ((st = reader.readLine()) != null) {
// MainActivity.debug("Http: httpGet append " + st);
stringBuilder.append(st);
}
// MainActivity.debug("Http: httpGet return " + stringBuilder.toString());
return stringBuilder.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
urlConnection.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
@Override
public void clearFields() {
super.clearFields();
}
@Override
public Message requestFreeFrame(Frame frame) {
// build the command string to send to the remote device
// CanSee maintains a table of all received free frames and immediately responds with
// the last known frame
String command = "g" + frame.getFromIdHex();
return responseToMessage(frame, command, TIMEOUT_DUMMMY);
}
@Override
public Message requestIsoTpFrame(Frame frame) {
// build the command string to send to the remote device
// Note that all ISOTP handling is done by the CanSee device
String command = "i" + frame.getFromIdHex() + "," + frame.getRequestId() + "," + frame.getResponseId();
return responseToMessage(frame, command, TIMEOUT_DUMMMY);
}
private Message responseToMessage(Frame frame, String command, int timeout) {
// convert CanSee output to a Message object
MainActivity.debug("Http.rtm.send [" + command + "]");
String text = sendAndWaitForAnswer(command, 0, timeout); // send and wait for an answer, no delay
MainActivity.debug("Http.rtm.receive [" + text + "]");
if (text.length() == 0) {
return new Message(frame, "-E-Http.rtm.empty", true);
}
// split up the fields
String[] pieces = text.trim().split(",");
if (pieces.length < 2) {
MainActivity.debug("Http.rtm.nocomma [" + text + "]");
return new Message(frame, "-E-Http.rtm.nocomma:" + text, true);
}
int id;
try {
id = Integer.parseInt(pieces[0].trim(), 16);
} catch (NumberFormatException e) {
MainActivity.debug("Http.rtm.Nan [" + text + "]");
return new Message(frame, "-E-Http.rtm.Nan:" + text, true);
}
if (id != frame.getFromId()) {
MainActivity.debug("Http.rtm.diffid [" + text + "]");
return new Message(frame, "-E-Http.rtm.diffid:" + text, true);
}
return new Message(frame, pieces[1].trim().toLowerCase(), false);
}
@Override
public boolean initDevice(int toughness) {
return initDevice(toughness, 1);
}
@Override
protected boolean initDevice(int toughness, int retries) {
urlLeader = MainActivity.getBluetoothDeviceAddress() + "?command=";
if (BuildConfig.BRANCH.equals("master")) {
sendAndWaitForAnswer("n110,0", 0, TIMEOUT_DUMMMY); // disable all serial when on master branch
sendAndWaitForAnswer("n114,0", 0, TIMEOUT_DUMMMY); // disable all debugging when on master branch
} else {
sendAndWaitForAnswer("n110,1", 0, TIMEOUT_DUMMMY); // enable all serial
sendAndWaitForAnswer("n114,f6", 0, TIMEOUT_DUMMMY); // enable all default debugging
}
lastInitProblem = "";
return true;
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/exceptions/NoDecoderException.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.exceptions;
public class NoDecoderException extends Exception {
}
================================================
FILE: app/src/main/java/lu/fisch/canze/fragments/CustomFragment.java
================================================
package lu.fisch.canze.fragments;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import lu.fisch.canze.R;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.classes.Activity;
import lu.fisch.canze.classes.ActivityRegistry;
public class CustomFragment extends Fragment {
public CustomFragment() {
// Required empty public constructor
}
public static final int BUTTONCOUNT = 14;
private View view;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_custom, container, false);
this.view=view;
loadButtons();
return view;
}
@Override
public void onResume()
{
super.onResume();
loadButtons();
}
public void loadButtons()
{
ActivityRegistry registry = ActivityRegistry.getInstance();
for(int i=0; i activityClass) {
Button button = view.findViewById(buttonId);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!MainActivity.isSafe()) return;
if (MainActivity.device == null) {
MainActivity.toast(MainActivity.TOAST_NONE, R.string.toast_AdjustSettings);
return;
}
MainActivity.getInstance().leaveBluetoothOn = true;
Intent intent = new Intent(MainActivity.getInstance(), activityClass);
CustomFragment.this.startActivityForResult(intent, MainActivity.LEAVE_BLUETOOTH_ON);
}
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
MainActivity.getInstance().onActivityResult(requestCode, resultCode, data);
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/fragments/ExperimentalFragment.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.fragments;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import lu.fisch.canze.BuildConfig;
import lu.fisch.canze.R;
import lu.fisch.canze.activities.DashActivity;
import lu.fisch.canze.activities.FieldTestActivity;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.activities.ResearchActivity;
import lu.fisch.canze.activities.TwingoTestActivity;
import lu.fisch.canze.activities.TwizyTestActivity;
/**
* A simple {@link Fragment} subclass.
*/
public class ExperimentalFragment extends Fragment {
public ExperimentalFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_experimental, container, false);
activateButton(view, R.id.buttonDash, DashActivity.class);
activateButton(view, R.id.buttonResearch, ResearchActivity.class);
activateButton(view, R.id.buttonFieldTest, FieldTestActivity.class, true);
activateButton(view, R.id.buttonTwingoTest, TwingoTestActivity.class, false);
activateButton(view, R.id.buttonTwizyTest, TwizyTestActivity.class, false);
return view;
}
private void activateButton(View view, int buttonId, final Class> activityClass) {
Button button = view.findViewById(buttonId);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!MainActivity.isSafe()) return;
if (MainActivity.device == null) {
MainActivity.toast(MainActivity.TOAST_NONE, R.string.toast_AdjustSettings);
return;
}
MainActivity.getInstance().leaveBluetoothOn = true;
Intent intent = new Intent(MainActivity.getInstance(), activityClass);
ExperimentalFragment.this.startActivityForResult(intent, MainActivity.LEAVE_BLUETOOTH_ON);
}
});
}
private void activateButton(View view, int buttonId, final Class> activityClass, boolean onlyDebug) {
if (BuildConfig.BRANCH.equals("master") & onlyDebug) {
// if on master and onlyDebug is true, hide button
Button button = view.findViewById(buttonId);
button.setVisibility(View.INVISIBLE);
} else {
activateButton(view, buttonId, activityClass);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
MainActivity.getInstance().onActivityResult(requestCode, resultCode, data);
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/fragments/MainFragment.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.fragments;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import lu.fisch.canze.BuildConfig;
import lu.fisch.canze.R;
import lu.fisch.canze.activities.BatteryActivity;
import lu.fisch.canze.activities.BrakingActivity;
import lu.fisch.canze.activities.ChargingActivity;
import lu.fisch.canze.activities.ClimaTechActivity;
import lu.fisch.canze.activities.ConsumptionActivity;
import lu.fisch.canze.activities.DrivingActivity;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.activities.SpeedcontrolActivity;
public class MainFragment extends Fragment {
private static boolean firstRun = true;
private static String msg = "";
private static boolean isHtml = false;
public MainFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
// For a bundle, the app might have been installed by users of rooted devices without the
// proper "sub-APKs", which will make it crash on not found resources when inflating the
// screen. We try to catch that here without using any resource.
try {
View view = inflater.inflate(R.layout.fragment_main, container, false);
activateButton(view, R.id.buttonConsumption, ConsumptionActivity.class);
activateButton(view, R.id.buttonChargingActivity, ChargingActivity.class);
activateButton(view, R.id.buttonBattery, BatteryActivity.class);
activateButton(view, R.id.buttonDrivingActivity, DrivingActivity.class);
activateButton(view, R.id.buttonClimaTech, ClimaTechActivity.class);
activateButton(view, R.id.buttonBraking, BrakingActivity.class);
activateButton(view, R.id.buttonSpeed, SpeedcontrolActivity.class);
getNews(view);
return view;
} catch (Exception e) {
MainActivity.toast(MainActivity.TOAST_NONE, "Please install from the play store");
MainActivity.getInstance().finish();
System.exit(0);
}
return null;
}
private void activateButton(View view, int buttonId, final Class> activityClass) {
Button button = view.findViewById(buttonId);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!MainActivity.isSafe()) return;
if (MainActivity.device == null) {
MainActivity.toast(MainActivity.TOAST_NONE, R.string.toast_AdjustSettings);
return;
}
MainActivity.getInstance().leaveBluetoothOn = true;
Intent intent = new Intent(MainActivity.getInstance(), activityClass);
MainFragment.this.startActivityForResult(intent, MainActivity.LEAVE_BLUETOOTH_ON);
}
});
}
private void getNews(final View view) {
if (firstRun) {
(new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL("https://raw.githubusercontent.com/fesch/CanZE/Development/NEWS.json");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
if (urlConnection == null) return;
try {
urlConnection.setConnectTimeout(10000);
InputStream ips = urlConnection.getInputStream();
BufferedInputStream in = new BufferedInputStream(ips);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder stringBuilder = new StringBuilder(200);
while ((msg = reader.readLine()) != null) {
// MainActivity.debug("ELM327Http: httpGet append " + st);
stringBuilder.append(msg);
}
JsonElement jsonElement = JsonParser.parseString(stringBuilder.toString());
msg = "";
int version = jsonElement.getAsJsonObject().get("version").getAsInt();
msg = jsonElement.getAsJsonObject().get("news").getAsString();
isHtml = msg.contains("<");
if (version > BuildConfig.VERSION_CODE) {
if (isHtml) {
msg = "Please upgrade CanZE
" + msg;
} else {
msg = "Please upgrade CanZE\n\n" + msg;
}
}
} catch (Exception e) {
// ignore offline situation
} finally {
urlConnection.disconnect();
}
} catch (Exception e) {
// ignore offline situation
}
displayNews(view);
}
})).start();
firstRun = false;
} else {
displayNews(view);
}
}
private void displayNews(final View view) {
if (msg == null || "".equals(msg)) return;
FragmentActivity fa = getActivity();
if (fa == null) return;
fa.runOnUiThread(new Runnable() {
@Override
public void run() {
TextView tv = view.findViewById(R.id.textNews);
tv.setVisibility(View.VISIBLE);
if (isHtml) {
tv.setText(Html.fromHtml(msg));
tv.setMovementMethod(LinkMovementMethod.getInstance());
} else {
tv.setText(msg);
}
}
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
MainActivity.getInstance().onActivityResult(requestCode, resultCode, data);
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/fragments/TechnicalFragment.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.fragments;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import lu.fisch.canze.R;
import lu.fisch.canze.activities.AllDataActivity;
import lu.fisch.canze.activities.AuxBattTechActivity;
import lu.fisch.canze.activities.ChargingGraphActivity;
import lu.fisch.canze.activities.ChargingHistActivity;
import lu.fisch.canze.activities.ChargingTechActivity;
import lu.fisch.canze.activities.DtcActivity;
import lu.fisch.canze.activities.ElmTestActivity;
import lu.fisch.canze.activities.FirmwareActivity;
import lu.fisch.canze.activities.HeatmapBatcompActivity;
import lu.fisch.canze.activities.HeatmapCellvoltageActivity;
import lu.fisch.canze.activities.LeakCurrentsActivity;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.activities.PredictionActivity;
import lu.fisch.canze.activities.RangeActivity;
import lu.fisch.canze.activities.TiresActivity;
/**
* A simple {@link Fragment} subclass.
*/
public class TechnicalFragment extends Fragment {
public TechnicalFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_technical, container, false);
activateButton(view, R.id.buttonChargingTech, ChargingTechActivity.class);
activateButton(view, R.id.buttonDtc, DtcActivity.class);
activateButton(view, R.id.buttonChargingGraphs, ChargingGraphActivity.class);
activateButton(view, R.id.buttonFirmware, FirmwareActivity.class);
activateButton(view, R.id.buttonChargingPrediction, PredictionActivity.class);
activateButton(view, R.id.buttonElmTest, ElmTestActivity.class);
activateButton(view, R.id.buttonChargingHistory, ChargingHistActivity.class);
activateButton(view, R.id.buttonAuxBatt, AuxBattTechActivity.class);
activateButton(view, R.id.buttonLeakCurrents, LeakCurrentsActivity.class);
activateButton(view, R.id.buttonTires, TiresActivity.class);
activateButton(view, R.id.buttonHeatmapCellvoltage, HeatmapCellvoltageActivity.class);
activateButton(view, R.id.buttonHeatmapBatcomp, HeatmapBatcompActivity.class);
activateButton(view, R.id.buttonRange, RangeActivity.class);
activateButton(view, R.id.buttonAllData, AllDataActivity.class);
return view;
}
private void activateButton(View view, int buttonId, final Class> activityClass) {
Button button = view.findViewById(buttonId);
if (MainActivity.isPh2() && buttonId == R.id.buttonTires) {
button.setVisibility(View.INVISIBLE);
button.setEnabled(false);
} else {
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!MainActivity.isSafe()) return;
if (MainActivity.device == null) {
MainActivity.toast(MainActivity.TOAST_NONE, R.string.toast_AdjustSettings);
return;
}
MainActivity.getInstance().leaveBluetoothOn = true;
Intent intent = new Intent(MainActivity.getInstance(), activityClass);
TechnicalFragment.this.startActivityForResult(intent, MainActivity.LEAVE_BLUETOOTH_ON);
}
});
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
MainActivity.getInstance().onActivityResult(requestCode, resultCode, data);
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/interfaces/BluetoothEvent.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.interfaces;
import android.bluetooth.BluetoothSocket;
public interface BluetoothEvent {
void onBeforeConnect();
void onAfterConnect(BluetoothSocket bluetoothSocket);
void onBeforeDisconnect(BluetoothSocket bluetoothSocket);
void onAfterDisconnect();
}
================================================
FILE: app/src/main/java/lu/fisch/canze/interfaces/DebugListener.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
* This class represents a stack listener.
*/
package lu.fisch.canze.interfaces;
public interface DebugListener {
void dropDebugMessage (String msg);
void appendDebugMessage (String msg);
}
================================================
FILE: app/src/main/java/lu/fisch/canze/interfaces/DrawSurfaceInterface.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.interfaces;
public interface DrawSurfaceInterface {
void repaint();
}
================================================
FILE: app/src/main/java/lu/fisch/canze/interfaces/FieldListener.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
* This class represents a stack listener.
*/
package lu.fisch.canze.interfaces;
import lu.fisch.canze.actors.Field;
public interface FieldListener {
void onFieldUpdateEvent(Field field);
}
================================================
FILE: app/src/main/java/lu/fisch/canze/interfaces/VirtualFieldAction.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.interfaces;
import java.util.HashMap;
import lu.fisch.canze.actors.Field;
public interface VirtualFieldAction {
double updateValue(HashMap dependantFields);
}
================================================
FILE: app/src/main/java/lu/fisch/canze/ui/AppSectionsPagerAdapter.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.ui;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.ArrayList;
import lu.fisch.canze.fragments.CustomFragment;
import lu.fisch.canze.fragments.ExperimentalFragment;
import lu.fisch.canze.fragments.MainFragment;
import lu.fisch.canze.fragments.TechnicalFragment;
public class AppSectionsPagerAdapter extends FragmentPagerAdapter {
private final ArrayList fragments = new ArrayList<>();
public AppSectionsPagerAdapter(FragmentManager fm) {
super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
fragments.add(new MainFragment()); // 0
fragments.add(new TechnicalFragment()); // 1
fragments.add(new ExperimentalFragment()); // 2
fragments.add(new CustomFragment()); // 2
}
@NonNull
@Override
public Fragment getItem(int i) {
return fragments.get(i);
}
@Override
public int getCount() {
return fragments.size();
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/Bar.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.widgets;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
/**
*
* @author robertfisch
*/
public class Bar extends Drawable {
public Bar() {
super();
}
public Bar(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
// test
}
public Bar(DrawSurfaceInterface drawSurface, int x, int y, int width, int height) {
this.drawSurface=drawSurface;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void draw(Graphics g) {
// black border
g.setColor(Color.BLACK);
g.drawRect(x, y, width, height);
int value = this.value;
if (inverted) value=-value;
// calculate fill height
int fillHeight = (int) ((value-min)/(double)(max-min)*(height-1));
if(valuemax) fillHeight = (int) ((max-min)/(double)(max-min)*(height-1));
int barWidth = width-Math.max(g.stringWidth(min+""),g.stringWidth(max+""))-10-10-g.stringHeight(title)-4;
int startY = y+height-fillHeight;
// if min is below zero, we want to start at that position
if(min<0)
{
int zero = (int) ((0-min)/(double)(max-min)*(height-1));
startY = y+zero;
if(value>0)
{
fillHeight=zero-(height-fillHeight);
startY-=fillHeight;
}
else
{
fillHeight=zero-fillHeight;
}
}
// draw the filled part
g.drawRect(x + width - barWidth, y, barWidth, height);
g.setColor(Color.RED);
String sid = getSids().get(0);
if(getOptions().getOption(sid)!=null &&
getOptions().getOption(sid).contains("gradient")) {
int[] colors = colorRanges.getColors(sid);
float[] spacings = colorRanges.getSpacings(sid, min, max);
if(colors.length==spacings.length)
{
g.setGradient(0, height-1, 0, 0, colors, spacings);
}
}
g.fillRect(x + 1 + width - barWidth, startY, barWidth - 2, fillHeight);
g.clearGradient();
// draw the ticks
if(minorTicks>0 || majorTicks>0)
{
g.setColor(Color.GRAY_DARK);
int toTicks = minorTicks;
if(toTicks==0) toTicks=majorTicks;
double accel = (double)height/((max-min)/(double)toTicks);
double ax,ay,bx=0,by=0;
int actual = min;
int sum = 0;
for(double i=height; i>=0; i-=accel)
{
if(minorTicks>0)
{
ax = x+width-barWidth-5;
ay = y+i;
bx = x+width-barWidth;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw majorTicks
if(majorTicks!=0 && sum % majorTicks == 0) {
if(majorTicks>0)
{
ax = x+width-barWidth-10;
ay = y+i;
bx = x+width-barWidth;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
if(ay!=y+height && (int)i!=0)
{
g.setColor(Color.WHITE);
g.drawLine(x+1+width-barWidth, (int)ay, x+width-1, (int)by);
g.setColor(Color.GRAY_DARK);
}
}
// draw String
if(showLabels)
{
String text = (actual)+"";
double sw = g.stringWidth(text);
bx = x+width-barWidth-16-sw;
by = y+i;
g.drawString(text, (int)(bx), (int)(by+g.stringHeight(text)*(1-i/height)));
}
actual+=majorTicks;
}
sum+=minorTicks;
}
}
// draw the value
if(showValue)
{
g.setTextSize(Math.min(width/7,40));
String text = String.format("%." + String.valueOf(field.getDecimals()) + "f", field.getValue());
int tw = g.stringWidth(text);
int th = g.stringHeight(text);
int tx = x+width-barWidth/2-tw/2;
int ty = y+height/2+th/2;
g.setColor(Color.BLACK);
g.drawString(text, tx, ty);
}
// draw the title
if(title!=null && !title.equals(""))
{
g.setColor(getTitleColor());
g.setTextSize(16);
int tw = g.stringWidth(title);
int th = g.stringHeight(title);
int tx = x; //x+width-barWidth/2-tw/2;
int ty = y+height; //getY()+getHeight()-8;
g.rotate(-90, tx, ty);
g.drawString(title,tx+4,ty+th+2);
g.rotate(90,tx,ty);
}
}
/* --------------------------------
* Serialization
\ ------------------------------ */
@Override
public String dataToJson() {
return "";
}
@Override
public void dataFromJson(String json) {
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/BarGraph.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.widgets;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.actors.Field;
import lu.fisch.canze.actors.Fields;
import lu.fisch.canze.database.CanzeDataSource;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
/**
* Created by robertfisch on 26.10.2015.
*/
public class BarGraph extends Plotter {
public BarGraph() {
super();
}
public BarGraph(int x, int y, int width, int height) {
super(x, y, width, height);
}
public BarGraph(DrawSurfaceInterface drawSurface, int x, int y, int width, int height) {
super(drawSurface,x,y,width,height);
}
@Override
public void draw(Graphics g) {
// black border
g.setColor(Color.BLACK);
g.drawRect(x, y, width, height);
// calculate fill height
int fillHeight = (int) ((value-min)/(double)(max-min)*(height-1));
int barWidth = width-Math.max(g.stringWidth(min+""),g.stringWidth(max+""))-10-10;
// draw the ticks
if(minorTicks>0 || majorTicks>0)
{
int toTicks = minorTicks;
if(toTicks==0) toTicks=majorTicks;
double accel = (double)height/((max-min)/(double)toTicks);
double ax,ay,bx,by;
int actual = min;
int sum = 0;
for(double i=height; i>=0; i-=accel)
{
if(minorTicks>0)
{
g.setColor(Color.GRAY);
ax = x+width-barWidth-5;
ay = y+i;
bx = x+width-barWidth;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw majorTicks
if(majorTicks!=0 && sum % majorTicks == 0) {
if(majorTicks>0)
{
g.setColor(Color.GRAY_LIGHT);
ax = x+width-barWidth-10;
ay = y+i;
bx = x+width;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
g.setColor(Color.GRAY);
ax = x+width-barWidth-10;
ay = y+i;
bx = x+width-barWidth;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw String
if(showLabels)
{
g.setColor(Color.GRAY);
String text = (actual)+"";
double sw = g.stringWidth(text);
bx = x+width-barWidth-16-sw;
by = y+i;
g.drawString(text, (int)(bx), (int)(by+g.stringHeight(text)*(1-i/height)));
}
actual+=majorTicks;
}
sum+=minorTicks;
}
}
// draw the graph
g.drawRect(x+width-barWidth, y, barWidth, height);
if(values.size()>0)
{
double w = (double) barWidth/values.size();
double h = (double) getHeight()/(getMax()-getMin());
for(int i=0; i "+field.getValue());
int index = sids.indexOf(sid);
if (index == -1) {
sids.add(sid);
values.add(field.getValue());
//minValues.add(CanzeDataSource.getInstance().getMin(sid));
//maxValues.add(CanzeDataSource.getInstance().getMax(sid));
} else setValue(index, field.getValue());
// only repaint if the last field has been updated
//if(index==sids.size()-1)
super.onFieldUpdateEvent(field);
}
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/BatteryBar.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.widgets;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
/**
*
* @author robertfisch
*/
public class BatteryBar extends Drawable {
public BatteryBar() {
super();
}
public BatteryBar(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
// test
}
public BatteryBar(DrawSurfaceInterface drawSurface, int x, int y, int width, int height) {
this.drawSurface=drawSurface;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void draw(Graphics g) {
// black border
g.setColor(Color.BLACK);
g.drawRect(x, y, width, height);
// calculate fill height
int fillHeight = (int) ((value-min)/(double)(max-min)*(height-1));
int barWidth = width-Math.max(g.stringWidth(min+""),g.stringWidth(max+""))-10-10-g.stringHeight(title)-4;
// draw the filled part
g.drawRect(x + width - barWidth, y, barWidth, height);
if( value< -20 ) {g.setColor(Color.BLACK);}
else if ( value< 0 ) { g.setColor(Color.BLUE);}
else if ( value< 15 ) { g.setColor(Color.GRAY);}
else if ( value< 22 ) { g.setColor(Color.GREEN);}
else if ( value< 25 ) { g.setColor(Color.YELLOW);}
else { g.setColor(Color.RED);}
if(inverted)
g.fillRect(x+1+width-barWidth, y+1, barWidth-1, fillHeight);
else
g.fillRect(x+1+width-barWidth, y+height-fillHeight, barWidth-1, fillHeight);
// draw the ticks
if(minorTicks>0 || majorTicks>0)
{
g.setColor(Color.GRAY_DARK);
int toTicks = minorTicks;
if(toTicks==0) toTicks=majorTicks;
double accel = (double)height/((max-min)/(double)toTicks);
double ax,ay,bx=0,by=0;
int actual = min;
int sum = 0;
for(double i=height; i>=0; i-=accel)
{
if(minorTicks>0)
{
ax = x+width-barWidth-5;
ay = y+i;
bx = x+width-barWidth;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw majorTicks
if(majorTicks!=0 && sum % majorTicks == 0) {
if(majorTicks>0)
{
ax = x+width-barWidth-10;
ay = y+i;
bx = x+width-barWidth;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
if(ay!=y+height && (int)i!=0)
{
g.setColor(Color.WHITE);
g.drawLine(x+1+width-barWidth, (int)ay, x+width-1, (int)by);
g.setColor(Color.GRAY_DARK);
}
}
// draw String
if(showLabels)
{
String text = (actual)+"";
double sw = g.stringWidth(text);
bx = x+width-barWidth-16-sw;
by = y+i;
g.drawString(text, (int)(bx), (int)(by+g.stringHeight(text)*(1-i/height)));
}
actual+=majorTicks;
}
sum+=minorTicks;
}
}
// draw the value
if(showValue)
{
g.setTextSize(Math.min(width/7,40));
String text = String.format("%." + String.valueOf(field.getDecimals()) + "f", field.getValue());
int tw = g.stringWidth(text);
int th = g.stringHeight(text);
int tx = x+width-barWidth/2-tw/2;
int ty = y+height/2;
g.setColor(Color.GREEN_DARK);
g.drawString(text, tx, ty);
}
// draw the title
if(title!=null && !title.equals(""))
{
g.setColor(getTitleColor());
g.setTextSize(16);
int tw = g.stringWidth(title);
int th = g.stringHeight(title);
int tx = x; //x+width-barWidth/2-tw/2;
int ty = y+height; //getY()+getHeight()-8;
g.rotate(-90, tx, ty);
g.drawString(title,tx+4,ty+th-2);
g.rotate(90,tx,ty);
}
}
/* --------------------------------
* Serialization
\ ------------------------------ */
@Override
public String dataToJson() {
return "";
}
@Override
public void dataFromJson(String json) {
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/DrawThread.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.widgets;
import lu.fisch.awt.Graphics;
import lu.fisch.canze.widgets.Drawable;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.os.Handler;
import android.view.SurfaceHolder;
public class DrawThread extends Thread {
/** Handle to the surface manager object we interact with */
private SurfaceHolder mSurfaceHolder;
/** Handle to the application context, used to e.g. fetch Drawables. */
private Context mContext;
/** Message handler used by thread to interact with TextView */
private Handler mHandler;
// indicates weather we are running or not
private boolean running = false;
private Drawable drawable = null;
public DrawThread(SurfaceHolder surfaceHolder,
Context context,
Handler handler)
{
// get handles to some important objects
mSurfaceHolder = surfaceHolder;
mHandler = handler;
mContext = context;
}
public void setDrawable(Drawable item)
{
this.drawable =item;
}
private void draw(Canvas c)
{
if(running)
{
// enable anti-aliasing
c.setDrawFilter(new PaintFlagsDrawFilter(1, Paint.ANTI_ALIAS_FLAG));
// clean background
Paint paint = new Paint();
paint.setColor(Color.WHITE);
c.drawRect(0, 0, c.getWidth(), c.getHeight(), paint);
// do your paintings here ...
drawable.draw(new Graphics(c));
}
}
@Override
public void run()
{
Canvas c = null;
try
{
// get the surface
c = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder)
{
if(c!=null)
{
draw(c);
}
}
}
finally
{
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the surface in an
// inconsistent state
if (c != null)
{
mSurfaceHolder.unlockCanvasAndPost(c);
}
running=false;
}
}
@Override
public void start()
{
running=true;
super.start();
}
public boolean isRunning() {
return running;
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/Drawable.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
* Represents an elemtn which can be draw onto a canvas
*/
package lu.fisch.canze.widgets;
import android.graphics.Point;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.HashMap;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.awt.Rectangle;
import lu.fisch.canze.activities.CanzeActivity;
import lu.fisch.canze.actors.Field;
import lu.fisch.canze.actors.Fields;
import lu.fisch.canze.classes.ColorRanges;
import lu.fisch.canze.classes.Intervals;
import lu.fisch.canze.classes.Options;
import lu.fisch.canze.database.CanzeDataSource;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
import lu.fisch.canze.interfaces.FieldListener;
/**
*
* @author robertfisch
*/
public abstract class Drawable implements FieldListener {
protected int x, y, width, height;
protected int min = 0;
protected int max = 0;
protected int minAlt = 0;
protected int maxAlt = 0;
protected int majorTicks = 10;
protected int minorTicks = 2;
protected boolean showLabels = true;
protected boolean showValue = true;
protected boolean inverted = false;
protected int value = 0;
protected Field field = null;
protected String title = "";
protected int timeSale = 1;
// colors
protected Color foreground = Color.BLACK;
protected Color background = Color.WHITE;
protected Color intermediate = Color.GRAY_LIGHT;
protected Color titleColor = Color.BLUE;
protected DrawSurfaceInterface drawSurface = null;
protected ArrayList sids = new ArrayList<>();
protected ColorRanges colorRanges = new ColorRanges();
protected Intervals intervals = new Intervals();
protected Options options = new Options();
public Drawable()
{
}
public abstract void draw(Graphics g);
public boolean isInside(Point p)
{
Rectangle rect = new Rectangle(x, y, width, height);
return rect.contains(p);
}
protected double mkRad(double degree)
{
return degree/180.*Math.PI;
}
@Override
public void onFieldUpdateEvent(Field field) {
this.field = field;
setValue((int) field.getValue());
if(drawSurface!=null)
drawSurface.repaint();
}
public void onLayout(boolean landscape)
{
// empty
}
public void reset()
{
// empty
}
/* --------------------------------
* Serialization
\ ------------------------------ */
public abstract String dataToJson();
public abstract void dataFromJson(String json);
public void loadValuesFromDatabase()
{
// empty
}
/* --------------------------------
* Getters & setters
\ ------------------------------ */
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getMin() {
return min;
}
public void setMin(int min) {
this.min = min;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public int getMajorTicks() {
return majorTicks;
}
public void setMajorTicks(int majorTicks) {
this.majorTicks = majorTicks;
}
public int getMinorTicks() {
return minorTicks;
}
public void setMinorTicks(int minorticks) {
this.minorTicks = minorticks;
}
public boolean isShowLabels() {
return showLabels;
}
public void setShowLabels(boolean showLabels) {
this.showLabels = showLabels;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public DrawSurfaceInterface getDrawSurface() {
return drawSurface;
}
public void setDrawSurface(DrawSurfaceInterface drawSurface) {
this.drawSurface = drawSurface;
}
public boolean isShowValue() {
return showValue;
}
public void setShowValue(boolean showValue) {
this.showValue = showValue;
}
public boolean isInverted() {
return inverted;
}
public void setInverted(boolean inverted) {
this.inverted = inverted;
}
public Field getField() {
return field;
}
public void addField(String sid)
{
if(!sids.contains(sid)) {
sids.add(sid);
}
}
public ArrayList getSids() {
return sids;
}
public void setColorRanges(ColorRanges colorRanges) {
this.colorRanges = colorRanges;
}
public Color getForeground() {
return foreground;
}
public void setForeground(Color foreground) {
this.foreground = foreground;
}
public Color getBackground() {
return background;
}
public void setBackground(Color background) {
this.background = background;
}
public Color getIntermediate() {
return intermediate;
}
public void setIntermediate(Color intermediate) {
this.intermediate = intermediate;
}
public Color getTitleColor() {
return titleColor;
}
public void setTitleColor(Color titleColor) {
this.titleColor = titleColor;
}
public Intervals getIntervals() {
return intervals;
}
public void setIntervals(Intervals intervals) {
this.intervals = intervals;
}
public Options getOptions() {
return options;
}
public void setOptions(Options options) {
this.options = options;
}
public int getMaxAlt() {
return maxAlt;
}
public void setMaxAlt(int maxAlt) {
this.maxAlt = maxAlt;
}
public int getMinAlt() {
return minAlt;
}
public void setMinAlt(int minAlt) {
this.minAlt = minAlt;
}
public int getTimeScale() {
return timeSale;
}
public void setTimeScale(int timescale) {
this.timeSale = timescale;
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/Kompass.java
================================================
package lu.fisch.canze.widgets;
import android.graphics.Point;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.awt.Polygon;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
/**
*
* @author robertfisch
*/
public class Kompass extends Tacho {
public Kompass() {
super();
}
public Kompass(int x, int y, int width, int height) {
super(x, y, width, height);
}
public Kompass(DrawSurfaceInterface drawSurface, int x, int y, int width, int height) {
super(drawSurface, x, y, width, height);
}
@Override
public void setMax(int max) {
super.setMax(max);
super.setMin(-max);
}
@Override
public void setMin(int min) {
super.setMin(min);
super.setMax(-min);
}
@Override
public void draw(Graphics g)
{
// clean the surface
g.setColor(Color.WHITE);
g.fillRect(0,0,getWidth(),getHeight());
g.setTextSize(12);
double alpha = (360-angle)/2.;
// determine the rayon to be used
double rayon = 0;
if(angle>=180)
rayon = Math.min(width/2.,height/(1+Math.sin(mkRad(90-alpha))))-padding;
else
rayon = Math.min((width/2)/Math.cos(mkRad(90-angle/2)),height)-padding;
// determine center point
Point center = new Point(x+(width/2),y+(int)(rayon+padding));
g.setColor(Color.BLACK);
g.drawRect(x, y, width, height);
// draw the frame
g.setColor(Color.GRAY_DARK);
double ax,ay,bx=0,by=0;
// draw right arm
ax = center.x+rayon*Math.cos(mkRad(-90+alpha));
ay = center.y-rayon*Math.sin(mkRad(-90+alpha));
//g.drawLine(center.x,center.y,(int)ax,(int)ay);
// draw arc
for(double i=-90+alpha; i<=360-90-alpha; i+=2)
{
bx = center.x+rayon*Math.cos(mkRad(i));
by = center.y-rayon*Math.sin(mkRad(i));
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
ax=bx;
ay=by;
}
// draw left arm
ax = center.x+rayon*Math.cos(mkRad(-90-alpha));
ay = center.y-rayon*Math.sin(mkRad(-90-alpha));
//g.drawLine(center.x,center.y,(int)ax,(int)ay);
// draw last chunk of arc
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
// draw the labels
g.setColor(Color.BLACK);
double dist = 360-2*alpha;
int actual = 0;
// draw the minor ticks
if(minorTicks >0 || majorTicks>0)
{
g.setColor(Color.GRAY_DARK);
int toTicks = minorTicks;
if(toTicks==0) toTicks=majorTicks;
double accel = dist/((max-min)/toTicks);
int sum = 0;
// min --> 0
for(double i=-90+alpha+dist/2; i<=360-90-alpha; i+=accel)
{
if(minorTicks >0)
{
ax = center.x+(rayon)*Math.cos(mkRad(i));
ay = center.y-(rayon)*Math.sin(mkRad(i));
bx = center.x+(rayon-6)*Math.cos(mkRad(i));
by = center.y-(rayon-6)*Math.sin(mkRad(i));
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw majorTicks
if(majorTicks!=0 && sum % majorTicks == 0) {
if(majorTicks>0)
{
ax = center.x+(rayon)*Math.cos(mkRad(i));
ay = center.y-(rayon)*Math.sin(mkRad(i));
bx = center.x+(rayon-12)*Math.cos(mkRad(i));
by = center.y-(rayon-12)*Math.sin(mkRad(i));
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw String
if(showLabels)
{
bx = center.x+(rayon-24)*Math.cos(mkRad(i));
by = center.y-(rayon-24)*Math.sin(mkRad(i));
String text = (actual)+"";
double sw = g.stringWidth(text);
g.drawString(text, (int) (bx - (sw/2. + sw/2.*Math.cos(mkRad(i))) ), (int) (by + g.stringHeight(text) / 2));
}
actual-=majorTicks;
}
sum+= minorTicks;
}
// 0 --> max
actual = 0;
sum=0;
//for(double i=-90+alpha+dist/2; i<=360-90-alpha; i+=accel)
for(double i=-90+alpha+dist/2; i>=-90+alpha; i-=accel)
{
if(minorTicks >0)
{
ax = center.x+(rayon)*Math.cos(mkRad(i));
ay = center.y-(rayon)*Math.sin(mkRad(i));
bx = center.x+(rayon-6)*Math.cos(mkRad(i));
by = center.y-(rayon-6)*Math.sin(mkRad(i));
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw majorTicks
if(majorTicks!=0 && sum % majorTicks == 0) {
if(majorTicks>0)
{
ax = center.x+(rayon)*Math.cos(mkRad(i));
ay = center.y-(rayon)*Math.sin(mkRad(i));
bx = center.x+(rayon-12)*Math.cos(mkRad(i));
by = center.y-(rayon-12)*Math.sin(mkRad(i));
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw String
if(showLabels)
{
bx = center.x+(rayon-24)*Math.cos(mkRad(i));
by = center.y-(rayon-24)*Math.sin(mkRad(i));
String text = (actual)+"";
double sw = g.stringWidth(text);
g.drawString(text, (int) (bx - (sw/2. + sw/2.*Math.cos(mkRad(i))) ), (int) (by + g.stringHeight(text) / 2));
}
actual+=majorTicks;
}
sum+= minorTicks;
}
}
// draw the needle
g.setColor(Color.RED);
double rota = 90+alpha+dist*(value-min)/(max-min);
if (inverted) rota = 90+alpha+dist*(-value-min)/(max-min);
Polygon p = new Polygon();
double needleLength = rayon-45;
double pointerLength = rayon*0.1;
double angleDiff = 5;
p.addPoint(center.x,
center.y);
p.addPoint((int)(center.x+(needleLength-pointerLength)*Math.cos(mkRad(rota + angleDiff))),
(int)(center.y+(needleLength-pointerLength)*Math.sin(mkRad(rota + angleDiff))));
p.addPoint((int)(center.x+needleLength*Math.cos(mkRad(rota))),
(int)(center.y+needleLength*Math.sin(mkRad(rota))));
p.addPoint((int)(center.x+(needleLength-pointerLength)*Math.cos(mkRad(rota-angleDiff))),
(int)(center.y+(needleLength-pointerLength)*Math.sin(mkRad(rota - angleDiff))));
g.fillPolygon(p);
// draw the value
if(showValue) {
if(field !=null)
{
g.setTextSize(Math.min(width / 7, 40));
String text = String.format("%." + String.valueOf(field.getDecimals()) + "f", field.getValue());
int tw = g.stringWidth(text);
int th = g.stringHeight(text);
int tx = center.x-tw/2-3;
int ty = center.y+th;
g.setColor(Color.WHITE);
g.fillRect(tx - 1, ty - th, tw + 7, th + 5);
//g.setColor(Color.BLACK);
//g.drawRect(tx - 1, ty - th, tw + 7, th +5);
g.setColor(Color.GREEN_DARK);
g.drawString(text, tx, ty);
}
}
// draw the title
if(title!=null && !title.equals(""))
{
g.setColor(getTitleColor());
g.setTextSize(20);
int tw = g.stringWidth(title);
//int th = g.stringHeight(title);
int tx = getX()+getWidth()/2-tw/2;
int ty = getY()+getHeight()-8;
g.drawString(title,tx,ty);
}
}
/* --------------------------------
* Serialization
\ ------------------------------ */
@Override
public String dataToJson() {
return "";
}
@Override
public void dataFromJson(String json) {
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/Label.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.widgets;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
/**
* Created by robertfisch on 04.10.2015.
*/
public class Label extends Drawable {
private int textSize = -1;
public Label() {
super();
}
public Label(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
// test
}
public Label(DrawSurfaceInterface drawSurface, int x, int y, int width, int height) {
this.drawSurface=drawSurface;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void onLayout(boolean landscape)
{
textSize=-1;
}
@Override
public void reset()
{
textSize=-1;
}
@Override
public void draw(Graphics g) {
// black border
//g.setColor(Color.BLACK);
//g.drawRect(x, y, width, height);
// background
g.setColor(getBackground());
g.fillRect(x, y, width, height);
// black border
g.setColor(getForeground());
g.drawRect(x, y, width, height);
// draw the value
if(showValue) {
if(field !=null)
{
// get text
String text = String.format("%." + String.valueOf(field.getDecimals()) + "f", field.getValue()).trim();
int th, tw;
//if(textSize==-1) {
// init
textSize = 10;
// find out what the biggest text size could be
do {
g.setTextSize(textSize);
tw = g.stringWidth(text);
th = g.stringHeight(text);
textSize++;
} while (th < getHeight() * 0.9 && tw < getWidth() * 0.9);
textSize--;
//}
g.setTextSize(textSize);
tw = g.stringWidth(text);
th = g.stringHeight(text);
int tx = getX()+getWidth()/2-tw/2;
int ty = getY()+getHeight()/2+th/2;
//g.setColor(Color.GREEN_DARK);
g.setColor(getForeground());
g.drawString(text, tx, ty);
}
}
// draw the title
if(title!=null && !title.equals(""))
{
g.setColor(getTitleColor());
g.setTextSize(20);
int tx = getX()+8;
int ty = getY()+g.stringHeight(title)+8;
g.drawString(title,tx,ty);
}
}
/* --------------------------------
* Serialization
\ ------------------------------ */
@Override
public String dataToJson() {
return "";
}
@Override
public void dataFromJson(String json) {
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/Plotter.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.widgets;
import static lu.fisch.canze.activities.MainActivity.debug;
import android.content.res.Resources;
import android.util.TypedValue;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.actors.Field;
import lu.fisch.canze.actors.Fields;
import lu.fisch.canze.classes.TimePoint;
import lu.fisch.canze.classes.Crashlytics;
import lu.fisch.canze.database.CanzeDataSource;
import lu.fisch.canze.fragments.MainFragment;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
/**
*
* @author robertfisch
*/
public class Plotter extends Drawable {
protected ArrayList values = new ArrayList<>();
//protected ArrayList minValues = new ArrayList<>();
//protected ArrayList maxValues = new ArrayList<>();
protected ArrayList sids = new ArrayList<>();
public Plotter() {
super();
}
public Plotter(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
// test
}
public Plotter(DrawSurfaceInterface drawSurface, int x, int y, int width, int height) {
this.drawSurface=drawSurface;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void setValue(int index, double value)
{
// synchronized as loadValuesFromDatabase can be running
synchronized (values) {
try {
values.set(index, value);
} catch (IndexOutOfBoundsException e) {
Crashlytics.logException(e);
// Bail out. Based on Play Console Crash Report
}
//if(valuemaxValues.get(index)) maxValues.set(index,value);
}
}
@Override
public void setValue(int value) {
super.setValue(value);
//addValue(value);
}
@Override
public void draw(Graphics g) {
// black border
//g.setColor(Color.BLACK);
//g.drawRect(x, y, width, height);
// background
g.setColor(getBackground());
g.fillRect(x, y, width, height);
// black border
g.setColor(getForeground());
g.drawRect(x, y, width, height);
// calculate fill height
int fillHeight = (int) ((value-min)/(double)(max-min)*(height-1));
int barWidth = width-Math.max(g.stringWidth(min+""),g.stringWidth(max+""))-10-10;
int spaceAlt = Math.max(g.stringWidth(minAlt+""),g.stringWidth(maxAlt+""))+10+10;
// reduce with if second y-axe is used
if (minAlt==-1 && maxAlt==-1)
{
spaceAlt=0;
}
barWidth-=spaceAlt;
// what is the graph height
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
int graphHeight = height-g.stringHeight(sdf.format(Calendar.getInstance().getTime()))-5;
// draw the ticks
if(minorTicks>0 || majorTicks>0)
{
int toTicks = minorTicks;
if(toTicks==0) toTicks=majorTicks;
double accel = (double)height/((max-min)/(double)toTicks);
double ax,ay,bx,by;
int actual = min;
int sum = 0;
for(double i=height; i>=0; i-=accel)
{
if(minorTicks>0)
{
g.setColor(Color.GRAY);
ax = x+width-barWidth-5;
ay = y+i;
bx = x+width-barWidth;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw majorTicks
if(majorTicks!=0 && sum % majorTicks == 0) {
if(majorTicks>0)
{
g.setColor(Color.GRAY_LIGHT);
ax = x+width-barWidth-10;
ay = y+i;
bx = x+width;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
g.setColor(Color.GRAY);
ax = x+width-barWidth-10;
ay = y+i;
bx = x+width-barWidth;
by = y+i;
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
}
// draw String
if(showLabels)
{
g.setColor(Color.GRAY);
String text = (actual)+"";
double sw = g.stringWidth(text);
bx = x+width-barWidth-16-sw;
by = y+i;
g.drawString(text, (int)(bx), (int)(by+g.stringHeight(text)*(1-i/height)));
}
actual+=majorTicks;
}
sum+=minorTicks;
}
}
// draw the horizontal grid
/*
g.setColor(getIntermediate());
long start = Calendar.getInstance().getTimeInMillis()/1000;
int interval = 60/timeSale;
for(long x=width-(start%interval)-spaceAlt; x>=width-barWidth-spaceAlt; x-=interval)
{
g.drawLine(x, 1, x, graphHeight + 5);
}
*/
/*
MainActivity.debug("Values "+values.size());
MainActivity.debug("Values min "+minValues.size());
MainActivity.debug("Values max "+maxValues.size());/**/
// draw the graph
g.drawRect(x+width-barWidth, y, barWidth, height);
// min & max
/*
if(minValues.size()>0)
{
double w = (double) barWidth/minValues.size();
double h = (double) getHeight()/(getMax()-getMin()+1);
double lastX = Double.NaN;
double lastY = Double.NaN;
g.setColor(Color.GREEN_DARK);
for(int i=0; i0)
{
g.drawLine(getX()+getWidth()-barWidth+(int)lastX,
getY()+(int)lastY,
getX()+getWidth()-barWidth+(int)mx,
getY()+(int)my);
}
lastX=mx;
lastY=my;
}
}
if(maxValues.size()>0)
{
double w = (double) barWidth/maxValues.size();
double h = (double) getHeight()/(getMax()-getMin()+1);
double lastX = Double.NaN;
double lastY = Double.NaN;
g.setColor(Color.BLUE);
for(int i=0; i0)
{
g.drawLine(getX()+getWidth()-barWidth+(int)lastX,
getY()+(int)lastY,
getX()+getWidth()-barWidth+(int)mx,
getY()+(int)my);
}
lastX=mx;
lastY=my;
}
}
*/
// values
//MainActivity.debug("PLOTTER SIZE: "+values.size());
if(values.size()>0)
{
double w = (double) barWidth/values.size();
double h = (double) getHeight()/(getMax()-getMin());
double lastX = Double.NaN;
double lastY = Double.NaN;
double min = Double.NaN;
double max = Double.NaN;
if(values.size()>0)
min=values.get(0);
max=min;
g.setColor(Color.RED);
for(int i=0; imax)
max=values.get(i);
try {
//MainActivity.debug("Value "+i+": "+values.get(i));
//MainActivity.debug("Value "+i+": "+values.get(i)+" Max: "+getMax()+" Min: "+getMin()+" height: "+getHeight()+" h: "+h);
double mx = w / 2 + i * w;
double my = getHeight() - (values.get(i) - getMin()) * h;
int rayon = 2;
g.fillOval(getX() + getWidth() - barWidth + (int) mx - rayon, getY() + (int) my - rayon, 2 * rayon + 1, 2 * rayon + 1);
if (i > 0) {
g.drawLine(getX() + getWidth() - barWidth + (int) lastX,
getY() + (int) lastY,
getX() + getWidth() - barWidth + (int) mx,
getY() + (int) my);
}
lastX = mx;
lastY = my;
} catch (IndexOutOfBoundsException | NullPointerException e) {
/* simply ignore */
}
}
// draw min & max
if (showValue) {
g.setColor(getTitleColor());
g.setTextSize(20);
int tx = getX() + width - 8 - spaceAlt;
int ty = getY();
int dy = g.stringHeight("Ip") + 4; // full height and undersling
String text = "min: "+String.format("%.2f", min);
int tw = g.stringWidth(text);
ty += dy;
g.drawString(text, tx - tw, ty);
text = "max: "+String.format("%.2f", max);
tw = g.stringWidth(text);
ty += dy;
g.drawString(text, tx - tw, ty);
}
}
// draw the title
if(title!=null && !title.equals(""))
{
g.setColor(getTitleColor());
g.setTextSize(20);
int th = g.stringHeight(title);
int tx = getX()+width-barWidth+8;
int ty = getY()+th+4;
g.drawString(title,tx,ty);
}
}
@Override
public void onFieldUpdateEvent(Field field) {
// only take data fofr valid cars
//MainActivity.debug("Plotter: "+field.getSID()+" --> "+field.getValue());
//MainActivity.debug("Car = "+MainActivity.car+" / "+field.getCar()+" / "+field.isCar(MainActivity.car));
if(field.isCar(MainActivity.car)) {
String sid = field.getSID();
//MainActivity.debug("!! Plotter: "+sid+" --> "+field.getValue());
int index = sids.indexOf(sid);
if (index == -1) {
values.add(field.getValue());
sids.add(sid);
//minValues.add(CanzeDataSource.getInstance().getMin(sid));
//maxValues.add(CanzeDataSource.getInstance().getMax(sid));
} else setValue(index, field.getValue());
// only repaint if the last field has been updated
//if(index==sids.size()-1)
super.onFieldUpdateEvent(field);
}
}
/* --------------------------------
* Serialization
\ ------------------------------ */
@Override
public void loadValuesFromDatabase() {
super.loadValuesFromDatabase();
// since this code is called in a separate thread (see WidgetView.SurfaceCreated) we need
// to synchronize "values"
synchronized (values) {
values.clear();
//maxValues.clear();
//minValues.clear();
for (int s = 0; s < sids.size(); s++) {
String sid = sids.get(s);
values.add(CanzeDataSource.getInstance().getLast(sid));
//maxValues.add(CanzeDataSource.getInstance().getMax(sid));
//minValues.add(CanzeDataSource.getInstance().getMin(sid));
}
}
}
@Override
public String dataToJson() {
Gson gson = new Gson();
ArrayList> data = new ArrayList<>();
data.add((ArrayList) values.clone());
//data.add((ArrayList) minValues.clone());
//data.add((ArrayList) maxValues.clone());
return gson.toJson(data);
}
@Override
public void dataFromJson(String json) {
Gson gson = new Gson();
Type fooType = new TypeToken>>() {}.getType();
ArrayList> data = gson.fromJson(json, fooType);
values = data.get(0);
//minValues=data.get(1);
//maxValues=data.get(2);
}
//unused and faulty anyway as the size of sids and values may now divert
//public void setValues(ArrayList values)
//{
// this.values = values;
//}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/Tacho.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.widgets;
import android.content.res.Resources;
import android.graphics.Point;
import android.util.TypedValue;
import com.google.gson.Gson;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.awt.Polygon;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
/**
*
* @author robertfisch
*/
public class Tacho extends Drawable {
protected double angle = 240;
protected boolean autoRange = false;
protected int padding = 5;
public Tacho() {
super();
}
public Tacho(DrawSurfaceInterface drawSurface, int x, int y, int width, int height) {
this.drawSurface=drawSurface;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public Tacho(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void draw(Graphics g)
{
// clean the surface
//g.setColor(Color.WHITE);
//g.fillRect(0,0,getWidth(),getHeight());
// background
g.setColor(getBackground());
g.fillRect(x, y, width, height);
// black border
g.setColor(getForeground());
g.drawRect(x, y, width, height);
g.setTextSize(12);
double alpha = (360-angle)/2.;
// determine the rayon to be used
double rayon = 0;
if(angle>=180)
rayon = Math.min(width/2.,height/(1+Math.sin(mkRad(90-alpha))))-padding;
else
rayon = Math.min((width/2)/Math.cos(mkRad(90-angle/2)),height)-padding;
// determine center point
Point center = new Point(x+(width/2),y+(int)(rayon+padding));
//g.setColor(Color.BLACK);
g.setColor(getForeground());
g.drawRect(x, y, width, height);
// draw the frame
//g.setColor(Color.GRAY_DARK);
g.setColor(getIntermediate());
double ax,ay,bx=0,by=0;
// draw right arm
ax = center.x+rayon*Math.cos(mkRad(-90+alpha));
ay = center.y-rayon*Math.sin(mkRad(-90+alpha));
//g.drawLine(center.x,center.y,(int)ax,(int)ay);
// draw arc
for(double i=-90+alpha; i<=360-90-alpha; i+=2)
{
bx = center.x+rayon*Math.cos(mkRad(i));
by = center.y-rayon*Math.sin(mkRad(i));
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
ax=bx;
ay=by;
}
// draw left arm
ax = center.x+rayon*Math.cos(mkRad(-90-alpha));
ay = center.y-rayon*Math.sin(mkRad(-90-alpha));
//g.drawLine(center.x,center.y,(int)ax,(int)ay);
// draw last chunk of arc
g.drawLine((int)ax, (int)ay, (int)bx, (int)by);
// draw the labels
g.setColor(getForeground());
//g.setColor(Color.BLACK);
double dist = 360-2*alpha;
int actual = min;
// draw the minor ticks
if(minorTicks >0 || majorTicks>0) {
//g.setColor(Color.GRAY_DARK);
g.setColor(getIntermediate());
int toTicks = minorTicks;
if (toTicks == 0) toTicks = majorTicks;
double accel = dist / ((max - min) / toTicks);
int sum = 0;
for (double i = 360 - 90 - alpha; i > (int)( -90 + alpha)-accel; i -= accel) {
if (minorTicks > 0) {
ax = center.x + (rayon) * Math.cos(mkRad(i));
ay = center.y - (rayon) * Math.sin(mkRad(i));
bx = center.x + (rayon - 6) * Math.cos(mkRad(i));
by = center.y - (rayon - 6) * Math.sin(mkRad(i));
g.drawLine((int) ax, (int) ay, (int) bx, (int) by);
}
// draw majorTicks
if (majorTicks != 0 && sum % majorTicks == 0) {
if (majorTicks > 0) {
ax = center.x + (rayon) * Math.cos(mkRad(i));
ay = center.y - (rayon) * Math.sin(mkRad(i));
bx = center.x + (rayon - 12) * Math.cos(mkRad(i));
by = center.y - (rayon - 12) * Math.sin(mkRad(i));
g.drawLine((int) ax, (int) ay, (int) bx, (int) by);
}
// draw String
if (showLabels) {
bx = center.x + (rayon - 24) * Math.cos(mkRad(i));
by = center.y - (rayon - 24) * Math.sin(mkRad(i));
String text = (actual) + "";
double sw = g.stringWidth(text);
g.drawString(text, (int) (bx - (sw/2. + sw/2.*Math.cos(mkRad(i))) ), (int) (by + g.stringHeight(text) / 2));
}
actual += majorTicks;
}
sum += minorTicks;
}
}
// draw the needle
g.setColor(Color.RED);
double rota = 90+alpha+dist*(value-min)/(max-min);
Polygon p = new Polygon();
double needleLength = rayon-45;
double pointerLength = rayon*0.1;
double angleDiff = 5;
p.addPoint(center.x,
center.y);
p.addPoint((int)(center.x+(needleLength-pointerLength)*Math.cos(mkRad(rota + angleDiff))),
(int)(center.y+(needleLength-pointerLength)*Math.sin(mkRad(rota + angleDiff))));
p.addPoint((int)(center.x+needleLength*Math.cos(mkRad(rota))),
(int)(center.y+needleLength*Math.sin(mkRad(rota))));
p.addPoint((int)(center.x+(needleLength-pointerLength)*Math.cos(mkRad(rota-angleDiff))),
(int)(center.y+(needleLength-pointerLength)*Math.sin(mkRad(rota - angleDiff))));
g.fillPolygon(p);
// draw the value
if(showValue)
{
if(field !=null) {
//g.setTextSize(Math.min(width / 7, 40));
g.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 22, Resources.getSystem().getDisplayMetrics()));
String text = String.format("%." + String.valueOf(field.getDecimals()) + "f", field.getValue());
int tw = g.stringWidth(text);
int th = g.stringHeight(text);
int tx = center.x-tw/2-3;
int ty = center.y+th;
g.setColor(getBackground());
//g.setColor(Color.WHITE);
g.fillRect(tx - 1, ty - th, tw + 7, th + 5);
//g.setColor(Color.BLACK);
//g.drawRect(tx - 1, ty - th, tw + 7, th +5);
g.setColor(getForeground());
//g.setColor(Color.GREEN_DARK);
g.drawString(text, tx, ty);
}
}
// draw the title
if(title!=null && !title.equals(""))
{
//g.setColor(Color.BLUE);
g.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, Resources.getSystem().getDisplayMetrics()));
//g.setTextSize(20);
int tw = g.stringWidth(title);
//int th = g.stringHeight(title);
int tx = getX()+getWidth()/2-tw/2;
int ty = getY()+getHeight()-8;
g.drawString(title,tx,ty);
}
}
/* --------------------------------
* Getters & setters
\ ------------------------------ */
public void setAngle(double angle) {
this.angle = angle;
}
public void setValue(int value) {
if(autoRange)
{
if(valuemax) max=value;
}
else
{
if(valuemax) value=max;
}
this.value = value;
}
/* --------------------------------
* Serialization
\ ------------------------------ */
@Override
public String dataToJson() {
return "";
}
@Override
public void dataFromJson(String json) {
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/Timeplot.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.widgets;
import android.content.res.Resources;
import android.util.TypedValue;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.awt.Polygon;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.actors.Field;
import lu.fisch.canze.actors.Fields;
import lu.fisch.canze.classes.TimePoint;
import lu.fisch.canze.database.CanzeDataSource;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
/**
* @author robertfisch
*/
public class Timeplot extends Drawable {
protected HashMap> values = new HashMap<>();
private boolean backward = true;
public Timeplot() {
super();
}
public Timeplot(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
// test
}
public Timeplot(DrawSurfaceInterface drawSurface, int x, int y, int width, int height) {
this.drawSurface = drawSurface;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void addValue(String fieldSID, double value) {
long iTime = Calendar.getInstance().getTimeInMillis();
// with the new dongle, fields may come in too fast, so let's
// make sure we do not get an overflow >> very slow app reaction
// maximum each second a new value!
iTime = (iTime / 1000) * 1000;
//MainActivity.debug(values.size()+"");
if (!values.containsKey(fieldSID)) values.put(fieldSID, new ArrayList());
// don't add every point, but check if for the given second we already have this point
// remembering more than one point per second is kind of overkill
// values.get(fieldSID).add(new TimePoint(Calendar.getInstance().getTimeInMillis(), value));
// if empty, add
/* TODO given crashlitics, I think we should sync this block JM */
ArrayList val = values.get(fieldSID);
if (val == null) return;
if (val.size() == 0)
val.add(new TimePoint(iTime, value));
else {
TimePoint lastTP = val.get(val.size() - 1);
// if this is really a new point, add it
if (lastTP == null || lastTP.date != iTime)
val.add(new TimePoint(iTime, value));
// if not, replace the previous point
// ( database will store the max, but as the value of the last point is also being
// displayed on the screen, we should prefer having the real last point here )
else {
val.set(val.size() - 1, new TimePoint(iTime, value));
}
}
}
private Color getColor(int i) {
if (i == 0) return Color.RENAULT_RED;
else if (i == 1) return Color.BLUE;
else return Color.GREEN_DARK;
}
@Override
public void draw(Graphics g) {
// background
g.setColor(getBackground());
g.fillRect(x, y, width, height);
// black border
g.setColor(getForeground());
g.drawRect(x, y, width, height);
// calculate fill height
//int fillHeight = (int) ((value-min)/(double)(max-min)*(height-1));
int barWidth = width - Math.max(g.stringWidth(min + ""), g.stringWidth(max + "")) - 10 - 10;
int spaceAlt = Math.max(g.stringWidth(minAlt + ""), g.stringWidth(maxAlt + "")) + 10 + 10;
// reduce with if second y-axe is used
//MainActivity.debug("Alt: "+minAlt+" - "+maxAlt);
if (minAlt == 0 && maxAlt == 0) {
spaceAlt = 0;
}
barWidth -= spaceAlt;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
int graphHeight = height - g.stringHeight(sdf.format(Calendar.getInstance().getTime())) - 15;
// draw the ticks
double realMaxAlt = getMaxAlt();
if (minorTicks > 0 || majorTicks > 0) {
int toTicks = minorTicks;
if (toTicks == 0) toTicks = majorTicks;
double intervals = ((max - min) / (double) toTicks);
double accel = (double) graphHeight / intervals;
double ax, ay, bx, by;
int actual = min;
int actualAlt = minAlt;
int sum = 0;
for (double i = graphHeight; i >= 0; i -= accel) {
if (minorTicks > 0) {
g.setColor(getForeground());
ax = x + width - barWidth - spaceAlt - 5;
ay = y + i;
bx = x + width - barWidth - spaceAlt;
by = y + i;
g.drawLine((int) ax, (int) ay, (int) bx, (int) by);
if (spaceAlt != 0) {
ax = x + width - spaceAlt;
ay = y + i;
bx = ax + 5;
by = y + i;
g.drawLine((int) ax, (int) ay, (int) bx, (int) by);
}
}
// draw majorTicks
if (majorTicks != 0 && sum % majorTicks == 0) {
if (majorTicks > 0) {
g.setColor(getIntermediate());
ax = x + width - spaceAlt - barWidth - 10;
ay = y + i;
bx = x + width - spaceAlt + (spaceAlt != 0 ? 10 : 0);
by = y + i;
g.drawLine((int) ax, (int) ay, (int) bx, (int) by);
g.setColor(getForeground());
ax = x + width - barWidth - spaceAlt - 10;
ay = y + i;
bx = x + width - barWidth - spaceAlt;
by = y + i;
g.drawLine((int) ax, (int) ay, (int) bx, (int) by);
if (spaceAlt != 0) {
ax = x + width - spaceAlt;
ay = y + i;
bx = ax + 10;
by = y + i;
g.drawLine((int) ax, (int) ay, (int) bx, (int) by);
}
}
// draw String
if (showLabels) {
g.setColor(getForeground());
g.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, Resources.getSystem().getDisplayMetrics()));
String text = (actual) + "";
double sw = g.stringWidth(text);
bx = x + width - barWidth - 16 - sw - spaceAlt;
by = y + i;
g.drawString(text, (int) (bx), (int) (by + g.stringHeight(text) * (1 - i / graphHeight)));
// alternative labels
if (spaceAlt != 0) {
text = (actualAlt) + "";
//sw = g.stringWidth(text);
bx = x + width - spaceAlt + 16;
by = y + i;
g.drawString(text, (int) (bx), (int) (by + g.stringHeight(text) * (1 - i / graphHeight)));
}
}
actual += majorTicks;
actualAlt += Math.round((double) (maxAlt - minAlt) / (max - min) * majorTicks);
}
sum += minorTicks;
}
// calculate real max alt
double altTicks = Math.round((double) (maxAlt - minAlt) / (max - min) * majorTicks) / ((double) majorTicks / toTicks);
realMaxAlt = intervals * altTicks + getMinAlt();
}
// draw the vertical grid
g.setColor(getIntermediate());
long start = (Calendar.getInstance().getTimeInMillis() / 1000); // start in seconds
int interval = 60 / timeSale;
try {
if (backward) {
ArrayList list = this.values.get(sids.get(0));
start = list.get(list.size() - 1).date / 1000;
for (int s = 0; s < sids.size(); s++) {
list = this.values.get(sids.get(s));
long thisDate = list.get(list.size() - 1).date / 1000;
if (thisDate > start) start = thisDate;
}
long newStart = start;
for (long x = width - (newStart % interval) - spaceAlt; x >= width - barWidth - spaceAlt; x -= interval) {
g.drawLine(x, 1, x, graphHeight + 5);
}
} else {
ArrayList list = this.values.get(sids.get(0));
start = list.get(0).date / 1000;
for (int s = 0; s < sids.size(); s++) {
list = this.values.get(sids.get(s));
long thisDate = list.get(0).date / 1000;
if (thisDate < start) start = thisDate;
}
for (long x = width - barWidth - spaceAlt; x < width - spaceAlt; x += interval) {
g.drawLine(x, 1, x, graphHeight + 5);
}
}
} catch (Exception e) {
//MainActivity.debug("Exception: "+e.getMessage());
for (long x = width - (start % interval) - spaceAlt; x >= width - barWidth - spaceAlt; x -= interval) {
g.drawLine(x, 1, x, graphHeight + 5);
}
}
// draw the graph
for (int s = 0; s < sids.size(); s++) {
String sid = sids.get(s);
//ArrayList tmpValues = this.values.get(sid);
// make a shallow copy. this avoids array size changes and thus out of bounds while drawing
ArrayList tmpValues = new ArrayList<>(this.values.get(sid));
// setup an empty list if no list has been found
if (tmpValues == null) {
tmpValues = new ArrayList<>();
this.values.put(sid, tmpValues);
}
g.setColor(getForeground());
g.drawRect(x + width - barWidth - spaceAlt, y, barWidth, graphHeight);
if (tmpValues.size() > 0) {
double w = (double) barWidth / tmpValues.size();
double h = (double) graphHeight / (getMax() - getMin());
double hAlt = (double) graphHeight / (realMaxAlt - getMinAlt());
double lastX = Double.NaN;
double lastY = Double.NaN;
g.setColor(getColor(s));
if (isBackward()) {
long maxTime = start * 1000; //values.get(values.size() - 1).date;
for (int i = tmpValues.size() - 1; i >= 0; i--) {
TimePoint tp;
try {
tp = tmpValues.get(i);
} catch (IndexOutOfBoundsException e) {
tp = null;
}
if (tp != null && !Double.isNaN(tp.value) && tp.date != 0) {
g.setColor(colorRanges.getColor(sid, tp.value, getColor(s)));
double mx = barWidth - ((maxTime - tp.date) / timeSale / 1000.0);
if (mx < 0) {
tmpValues.remove(i);
} else {
// determine Y
double my;
// distinct "alt" vs "normal"
if (getOptions().getOption(sid) != null &&
getOptions().getOption(sid).contains("alt"))
my = graphHeight - (tp.value - minAlt) * hAlt;
else
my = graphHeight - (tp.value - min) * h;
// check if y should be fixed: colorline[value-of-y]
if ((getOptions().getOption(sid) != null &&
!getOptions().getOption(sid).isEmpty() &&
getOptions().getOption(sid).contains("colorline"))) {
// parse out position of line
String options = getOptions().getOption(sid);
int index = options.indexOf("colorline");
index += ("colorline").length() + 1;
String value = "";
while (index < options.length() && options.charAt(index) != ']') {
value += options.charAt(index);
index++;
}
if (getOptions().getOption(sid) != null &&
getOptions().getOption(sid).contains("alt"))
my = graphHeight - (Double.parseDouble(value) - minAlt) * hAlt;
else
my = graphHeight - (Double.parseDouble(value) - min) * h;
}
// now get ZY
double zy;
if (getOptions().getOption(sid) != null &&
getOptions().getOption(sid).contains("alt"))
zy = graphHeight - (- minAlt) * hAlt;
else
zy = graphHeight - (- min) * h;
int rayon = 2;
if (getOptions().getOption(sid) == null ||
(getOptions().getOption(sid) != null &&
(getOptions().getOption(sid).isEmpty() || getOptions().getOption(sid).contains("dot")))) {
g.fillOval(getX() + getWidth() - barWidth + (int) mx - rayon - spaceAlt,
getY() + (int) my - rayon,
2 * rayon + 1,
2 * rayon + 1);
}
if (i < tmpValues.size() - 1 && (mx != 0 || my != 0)) {
if (getOptions().getOption(sid) != null &&
getOptions().getOption(sid).contains("full")) {
if (!testErrorPoint(lastX, lastY, "last full") && !testErrorPoint(mx, my, "m full")) {
Polygon p = new Polygon();
p.addPoint(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
getY() + (int) lastY);
p.addPoint(getX() + getWidth() - barWidth + (int) mx - spaceAlt,
getY() + (int) my);
p.addPoint(getX() + getWidth() - barWidth + (int) mx - spaceAlt,
(int) (getY() + zy));
p.addPoint(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
(int) (getY() + zy));
g.fillPolygon(p);
}
} else if (getOptions().getOption(sid) != null &&
getOptions().getOption(sid).contains("gradient")) {
if (i < tmpValues.size() && tmpValues.get(i + 1) != null) {
if (!testErrorPoint(lastX, lastY, "last grad") && !testErrorPoint(mx, my, "m grad")) {
Polygon p = new Polygon();
p.addPoint(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
getY() + (int) lastY);
p.addPoint(getX() + getWidth() - barWidth + (int) mx - spaceAlt,
getY() + (int) my);
p.addPoint(getX() + getWidth() - barWidth + (int) mx - spaceAlt,
(int) (getY() + zy));
p.addPoint(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
(int) (getY() + zy));
int[] colors = colorRanges.getColors(sid);
float[] spacings = colorRanges.getSpacings(sid, min, max);
if (colors.length == spacings.length)
g.setGradient(0, graphHeight, 0, 0, colors, spacings);
g.fillPolygon(p);
g.clearGradient();
//else MainActivity.debug("size not equal: "+colors.length+"=="+spacings.length);
}
}
} else {
if (!testErrorPoint(mx, my, "m line")) {
g.drawLine(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
getY() + (int) lastY,
getX() + getWidth() - barWidth + (int) mx - spaceAlt,
getY() + (int) my);
}
}
}
lastX = mx;
lastY = my;
}
}
}
} else { // forward
long minTime = start * 1000; //values.get(0).date;
for (int i = 0; i < tmpValues.size(); i++) {
TimePoint tp = tmpValues.get(i);
if (tp != null && !Double.isNaN(tp.value) && tp.date != 0) {
g.setColor(colorRanges.getColor(sid, tp.value, getColor(s)));
double mx = ((tp.date - minTime) / timeSale / 1000.0);
if (mx <= barWidth) {
// ignore point that are out of scope but do not delete them
double my = graphHeight - (tp.value - min) * h;
// check if y should be fixed: colorline[value-of-y]
if ((getOptions().getOption(sid) != null &&
!getOptions().getOption(sid).isEmpty() &&
getOptions().getOption(sid).contains("colorline"))) {
// parse out position of line
String options = getOptions().getOption(sid);
int index = options.indexOf("colorline");
index += ("colorline").length() + 1;
String value = "";
while (index < options.length() && options.charAt(index) != ']') {
value += options.charAt(index);
index++;
}
my = graphHeight - (Double.parseDouble(value) - min) * h;
}
double zy = graphHeight - (- min) * h;
// draw on alternate scale if requested
if (getOptions().getOption(sid) != null &&
getOptions().getOption(sid).contains("alt")) {
my = graphHeight - (tp.value - minAlt) * hAlt;
zy = graphHeight - (- minAlt) * hAlt;
}
int rayon = 2;
//MainActivity.debug("HERE: "+sid+" / "+getOptions().getOption(sid));
if (getOptions().getOption(sid) == null ||
(getOptions().getOption(sid) != null &&
(getOptions().getOption(sid).isEmpty() || getOptions().getOption(sid).contains("dot")))) {
g.fillOval(getX() + getWidth() - barWidth + (int) mx - rayon - spaceAlt,
getY() + (int) my - rayon,
2 * rayon + 1,
2 * rayon + 1);
}
if (i > 0 && (mx != 0 || my != 0)) {
if (getOptions().getOption(sid) != null &&
getOptions().getOption(sid).contains("full")) {
if (!testErrorPoint(lastX, lastY, "last full") && !testErrorPoint(mx, my, "m full")) {
Polygon p = new Polygon();
p.addPoint(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
getY() + (int) lastY);
p.addPoint(getX() + getWidth() - barWidth + (int) mx - spaceAlt,
getY() + (int) my);
p.addPoint(getX() + getWidth() - barWidth + (int) mx - spaceAlt,
(int) (getY() + zy));
p.addPoint(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
(int) (getY() + zy));
g.fillPolygon(p);
}
} else if (getOptions().getOption(sid) != null &&
getOptions().getOption(sid).contains("gradient")) {
//if (i < values.size() && values.get(i - 1) != null) {
if (!testErrorPoint(lastX, lastY, "last grad") && !testErrorPoint(mx, my, "m grad")) {
Polygon p = new Polygon();
p.addPoint(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
getY() + (int) lastY);
p.addPoint(getX() + getWidth() - barWidth + (int) mx - spaceAlt,
getY() + (int) my);
p.addPoint(getX() + getWidth() - barWidth + (int) mx - spaceAlt,
(int) (getY() + zy));
p.addPoint(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
(int) (getY() + zy));
int[] colors = colorRanges.getColors(sid);
float[] spacings = colorRanges.getSpacings(sid, min, max);
if (colors.length == spacings.length)
g.setGradient(0, graphHeight, 0, 0, colors, spacings);
g.fillPolygon(p);
g.clearGradient();
//else MainActivity.debug("size not equal: "+colors.length+"=="+spacings.length);
}
//}
} else {
if (!testErrorPoint(mx, my, "m line")) {
g.drawLine(getX() + getWidth() - barWidth + (int) lastX - spaceAlt,
getY() + (int) lastY,
getX() + getWidth() - barWidth + (int) mx - spaceAlt,
getY() + (int) my);
}
}
}
lastX = mx;
lastY = my;
}
}
}
}
}
}
// clean bottom
g.setColor(getBackground());
g.fillRect(width - barWidth - 2, graphHeight + 1, barWidth + 1, height - graphHeight - 2);
// draw bottom axis
int c = 0;
int ts = (int) timeSale;
// draw the horizontal scale
sdf = new SimpleDateFormat("HH:mm", Locale.getDefault());
if (!this.values.isEmpty())
if (backward) {
long newStart = (Calendar.getInstance().getTimeInMillis()); // use now as starting
for (int s = 0; s < sids.size(); s++) { //check all sids in this graph
ArrayList list = this.values.get(sids.get(s)); // get the timepoints of this sid
if (list != null && !list.isEmpty()) {
TimePoint tp = list.get(list.size() - 1); // get the last one
if (tp != null) {
if (tp.date > newStart) newStart = tp.date; // find the max
}
}
}
//long newStart = start*1000;
//MainActivity.debug("Start 2: "+sdf.format(newStart));
for (long x = width - (start % interval) - spaceAlt; x >= width - barWidth - spaceAlt; x -= interval) {
if (c % (5 * ts) == 0) {
g.setColor(getForeground());
g.drawLine(x, graphHeight, x, graphHeight + 10);
String date = sdf.format((newStart - ((newStart % interval)) * timeSale - interval * c * timeSale * 1000));
g.drawString(date, x - g.stringWidth(date) - 4, height - 2);
} else {
g.setColor(getForeground());
g.drawLine(x, graphHeight, x, graphHeight + 3);
}
c++;
}
} else { // forward
ArrayList list; // = this.values.get(sids.get(0));
long newStart = (Calendar.getInstance().getTimeInMillis());
for (int s = 0; s < sids.size(); s++) { //check all sids in this graph
list = this.values.get(sids.get(s)); // get the timepoints of this sid
if (list != null && !list.isEmpty()) {
TimePoint tp = list.get(0); // get the first one
if (tp != null) {
if (tp.date < newStart) newStart = tp.date; // find the min
}
}
}
//MainActivity.debug("START: "+sdf.format(start));
//long newStart = start*1000;
for (long x = width - barWidth - spaceAlt; x < width - spaceAlt; x += interval) {
if (c % (5 * ts) == 0) {
g.setColor(getForeground());
g.drawLine(x, graphHeight, x, graphHeight + 10);
String date = sdf.format(newStart + (interval * c * timeSale * 1000));
g.drawString(date, x - g.stringWidth(date) - 4, height - 2);
} else {
g.setColor(getForeground());
g.drawLine(x, graphHeight, x, graphHeight + 3);
}
c++;
}
}
// draw the title
if (title != null && !title.equals("")) {
g.setColor(getTitleColor());
if (MainActivity.getInstance().isLandscape())
g.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, Resources.getSystem().getDisplayMetrics()));
else
g.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, Resources.getSystem().getDisplayMetrics()));
// draw multi-line title
title = title.replace(" / ", ", "); // let's be lazy
String[] parts = title.split(",");
int tx = getX() + width - barWidth + 8 - spaceAlt;
for (int s = 0; s < sids.size(); s++) {
String sid = sids.get(s);
//ArrayList tmpValues = this.values.get(sid);tmpValues =
ArrayList tmpValues = new ArrayList<>(this.values.get(sid));
if (tmpValues == null) continue;
int th = g.stringHeight(title);
int ty = getY() + th + 4;
if (tmpValues.isEmpty())
g.setColor(getColor(s));
else
g.setColor(colorRanges.getColor(sid, tmpValues.get(tmpValues.size() - 1).value, getColor(s)));
if (s < parts.length) {
g.drawString(parts[s].trim(), tx, ty);
tx += g.stringWidth(parts[s].trim());
}
// draw " / " if needed
if (s < sids.size() - 1) {
g.setColor(getTitleColor());
g.drawString(" / ", tx, ty);
tx += g.stringWidth(" /-"); // " / " will _not_ work!
}
}
/*
// draw single line title
int th = g.stringHeight(title);
int tx = getX()+width-barWidth+8-spaceAlt;
int ty = getY()+th+4;
g.drawString(title,tx,ty);
*/
}
// draw the value
if (showValue) {
//g.setTextSize(40);
if (MainActivity.getInstance().isLandscape())
g.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 22, Resources.getSystem().getDisplayMetrics()));
else
g.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 18, Resources.getSystem().getDisplayMetrics()));
int tx = getX() + width - 8 - spaceAlt;
int ty = getY();
int dy = g.stringHeight("Ip") + 4; // full height and undersling
for (int s = 0; s < sids.size(); s++) {
String sid = sids.get(s);
// ArrayList tmpValues = this.values.get(sid);
ArrayList tmpValues = new ArrayList<>(this.values.get(sid));
if (tmpValues == null) continue;
Field field = Fields.getInstance().getBySID(sid);
String text;
if (field != null) {
text = String.format("%." + field.getDecimals() + "f", field.getValue());
} else {
if (tmpValues.size() == 0) text = "N/A";
else text = String.valueOf(tmpValues.get(tmpValues.size() - 1).value);
}
if (tmpValues.isEmpty()) {
g.setColor(getColor(s));
} else {
g.setColor(colorRanges.getColor(sid, tmpValues.get(tmpValues.size() - 1).value, getColor(s)));
}
int tw = g.stringWidth(text);
ty += dy;
g.drawString(text, tx - tw, ty);
}
}
// black border
g.setColor(getForeground());
g.drawRect(x, y, width, height);
}
@Override
public void onFieldUpdateEvent(Field field) {
addValue(field.getSID(), field.getValue());
super.onFieldUpdateEvent(field);
}
@Override
public String dataToJson() {
Gson gson = new Gson();
return gson.toJson(values.clone());
}
@Override
public void dataFromJson(String json) {
Gson gson = new Gson();
Type fooType = new TypeToken>>() {
}.getType();
values = gson.fromJson(json, fooType);
}
@Override
public void loadValuesFromDatabase() {
super.loadValuesFromDatabase();
//values.clear(); // not needed as items will be replaced anyway!
for (int s = 0; s < sids.size(); s++) {
String sid = sids.get(s);
values.put(sid, CanzeDataSource.getInstance().getData(sid));
}
}
public void addField(String sid) {
super.addField(sid);
if (!values.containsKey(sid)) {
values.put(sid, new ArrayList());
}
}
public void setValues(HashMap> values) {
sids.clear();
sids.addAll(values.keySet());
this.values = values;
}
public boolean isBackward() {
return backward;
}
public void setBackward(boolean backward) {
this.backward = backward;
}
private boolean testErrorPoint(double x, double y, String er) {
double maxdelta = 2.0;
if (Double.isNaN(x) || Double.isNaN(y)) {
return true;
}
// test for origin (close to 0, 0 to find tringle problem)
// MainActivity.toast ("x:" + x + ", y:" + y + ", " + er);
return x >= -maxdelta && x <= maxdelta && y >= -maxdelta && y <= maxdelta;
}
}
================================================
FILE: app/src/main/java/lu/fisch/canze/widgets/WidgetView.java
================================================
/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package lu.fisch.canze.widgets;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Point;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import java.lang.reflect.Constructor;
import java.util.Locale;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.canze.activities.CanzeActivity;
import lu.fisch.canze.actors.Field;
import lu.fisch.canze.classes.ColorRanges;
import lu.fisch.canze.classes.Crashlytics;
import lu.fisch.canze.classes.Intervals;
import lu.fisch.canze.classes.Options;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.R;
import lu.fisch.canze.activities.WidgetActivity;
public class WidgetView extends SurfaceView implements DrawSurfaceInterface, SurfaceHolder.Callback, View.OnTouchListener {
// a reference to the drawing thread
private DrawThread drawThread = null;
// your application certainly needs some data model
private Drawable drawable = null;
private String fieldSID = "";
private boolean clickable = true;
protected boolean landscape = true;
private CanzeActivity canzeActivity = null;
// for data sharing
public static Drawable selectedDrawable = null;
public void setDrawable(Drawable drawable)
{
this.drawable=drawable;
//if(drawable.getDrawSurface()==null)
drawable.setDrawSurface(this);
repaint();
}
public Drawable getDrawable()
{
return drawable;
}
public WidgetView(Context context) {
super(context);
init(context, null);
setOnTouchListener(this);
}
public WidgetView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
setOnTouchListener(this);
}
public WidgetView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
setOnTouchListener(this);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
landscape = (right-left)>(bottom-top);
if(changed) drawable.onLayout(landscape);
}
public void reset()
{
drawable.reset();
repaint();
}
public String extractCarValue(String[] values)
{
// the first value is the default one
String carValue = values[0];
for(int i=1; i constructor = clazz.getConstructor(null);
Constructor> constructor = clazz.getConstructor();
drawable = (Drawable) constructor.newInstance();
drawable.setDrawSurface(WidgetView.this);
// apply attributes
drawable.setMin(Integer.valueOf(extractCarValue(attributes.getString(R.styleable.WidgetView_axisMinY).split(","))));
drawable.setMax(Integer.valueOf(extractCarValue(attributes.getString(R.styleable.WidgetView_axisMaxY).split(","))));
//drawable.setMin(attributes.getInt(R.styleable.WidgetView_min, 0));
//drawable.setMax(attributes.getInt(R.styleable.WidgetView_max, 0));
drawable.setMajorTicks(Integer.valueOf(extractCarValue(attributes.getString(R.styleable.WidgetView_majorTicks).split(","))));
drawable.setMinorTicks(Integer.valueOf(extractCarValue(attributes.getString(R.styleable.WidgetView_minorTicks).split(","))));
//drawable.setMajorTicks(attributes.getInt(R.styleable.WidgetView_majorTicks, 0));
//drawable.setMinorTicks(attributes.getInt(R.styleable.WidgetView_minorTicks, 0));
drawable.setTitle(attributes.getString(R.styleable.WidgetView_text));
drawable.setShowLabels(attributes.getBoolean(R.styleable.WidgetView_showLabels, true));
drawable.setShowValue(attributes.getBoolean(R.styleable.WidgetView_showValue, true));
drawable.setInverted(attributes.getBoolean(R.styleable.WidgetView_isInverted, false));
String colorRangesJson =attributes.getString(R.styleable.WidgetView_colorRanges);
if(colorRangesJson!=null && !colorRangesJson.trim().isEmpty())
drawable.setColorRanges(new ColorRanges(colorRangesJson.replace("'", "\"")));
String foreground =attributes.getString(R.styleable.WidgetView_foregroundColor);
if(foreground!=null && !foreground.isEmpty())
drawable.setForeground(Color.decode(foreground));
String background =attributes.getString(R.styleable.WidgetView_backgroundColor);
if(background!=null && !background.isEmpty())
drawable.setBackground(Color.decode(background));
String intermediate =attributes.getString(R.styleable.WidgetView_intermediateColor);
if(intermediate!=null && !intermediate.isEmpty())
drawable.setIntermediate(Color.decode(intermediate));
String titleColor =attributes.getString(R.styleable.WidgetView_titleColor);
if(titleColor!=null && !titleColor.isEmpty())
drawable.setTitleColor(Color.decode(titleColor));
String intervalJson =attributes.getString(R.styleable.WidgetView_intervals);
if(intervalJson!=null && !intervalJson.trim().isEmpty())
drawable.setIntervals(new Intervals(intervalJson.replace("'", "\"")));
String optionsJson =attributes.getString(R.styleable.WidgetView_options);
if(optionsJson!=null && !optionsJson.trim().isEmpty())
drawable.setOptions(new Options(optionsJson.replace("'", "\"")));
//drawable.setMinAlt(attributes.getInt(R.styleable.WidgetView_minAlt, -1));
//drawable.setMaxAlt(attributes.getInt(R.styleable.WidgetView_maxAlt, -1));
String minAlt = attributes.getString(R.styleable.WidgetView_axisMinYAlt);
if(minAlt!=null && !minAlt.trim().isEmpty())
drawable.setMinAlt(Integer.valueOf(extractCarValue(minAlt.split(","))));
String maxAlt = attributes.getString(R.styleable.WidgetView_axisMaxYAlt);
if(maxAlt!=null && !maxAlt.trim().isEmpty())
drawable.setMaxAlt(Integer.valueOf(extractCarValue(maxAlt.split(","))));
drawable.setTimeScale(attributes.getInt(R.styleable.WidgetView_timeScale,1));
fieldSID = attributes.getString(R.styleable.WidgetView_fieldSID);
if(fieldSID!=null && !fieldSID.equals("")) {
String[] sids = fieldSID.split(",");
for (int s = 0; s < sids.length; s++) {
Field field = MainActivity.fields.getBySID(sids[s]);
if (field == null) {
//MainActivity.debug("WidgetView: init: Field with SID <" + sids[s] + "> (index <" + s + "> in <" + R.styleable.WidgetView_text + "> not found!");
MainActivity.toast(MainActivity.TOAST_NONE, String.format(Locale.getDefault(), MainActivity.getStringSingle(R.string.format_NoSid), "Widget", sids[s]));
} else if (field.getResponseId().equals("999999")) {
//do nothing for temporary disabled fields
} else {
// add field to list of registered sids for this widget
drawable.addField(field.getSID());
// add listener
field.addListener(drawable);
// add filter to reader
int interval = drawable.getIntervals().getInterval(field.getSID());
if (interval == -1)
MainActivity.device.addActivityField(field, 0); // JM added 0, see that method for rationale
else
MainActivity.device.addActivityField(field, interval);
}
}
}
//MainActivity.debug("WidgetView: My SID is "+fieldSID);
if(MainActivity.milesMode) drawable.setTitle(drawable.getTitle().replace("km","mi"));
}
else
{
MainActivity.debug("WidgetView: init: WidgetIndex " + widgetIndex + " is wrong!? Not registered in ?");
}
}
catch(Exception e)
{
Crashlytics.logException(e);
}
}
// in case your application needs one or more timers,
// you have to put them here
/*Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
repaint();
}
}, 100, 100);
*/
}
private boolean motionDown = false;
private boolean motionMove = false;
private float downX, downY;
@Override
public boolean onTouch(View v, MotionEvent event)
{
// react on touch events
// get pointer index from the event object
int pointerIndex = event.getActionIndex();
// get pointer ID
int pointerId = event.getPointerId(pointerIndex);
// get masked (not specific to a pointer) action
int maskedAction = event.getActionMasked();
MainActivity.debug("WidgetView: maskedAction = " + maskedAction);
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:{
motionDown=true;
downX=event.getX();
downY=event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
if (Math.abs(downX - event.getX()) + Math.abs(downY - event.getY()) > 20) {
motionMove=true;
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
{
if(!motionMove && clickable && MainActivity.isSafe()) {
canzeActivity.setWidgetClicked(true);
Intent intent = new Intent(this.getContext(), WidgetActivity.class);
selectedDrawable = this.getDrawable();
this.getContext().startActivity(intent);
}
motionDown=false;
motionMove=false;
break;
}
case MotionEvent.ACTION_CANCEL: {
break;
}
}
invalidate();
return true;
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
@Override
public void surfaceCreated(SurfaceHolder arg0)
{
// load data from the database
(new Thread(new Runnable() {
@Override
public void run() {
drawable.loadValuesFromDatabase();
repaint();
}
})).start();
}
// DIRECT repaint method
public void repaint2() {
Canvas c = null;
try {
c = getHolder().lockCanvas();
if (c != null) {
// enable anti-aliasing
c.setDrawFilter(new PaintFlagsDrawFilter(1, Paint.ANTI_ALIAS_FLAG));
// clean background
Paint paint = new Paint();
paint.setColor(drawable.getBackground().getAndroidColor());
c.drawRect(0, 0, c.getWidth(), c.getHeight(), paint);
// set dimensions
drawable.setWidth(getWidth());
drawable.setHeight(getHeight());
// do the drawing
drawable.draw(new Graphics(c));
}
}
catch(Exception e)
{
Crashlytics.logException(e);
// ignore
}
finally
{
if (c != null) {
getHolder().unlockCanvasAndPost(c);
}
}
}
// INDIRECT repaint method (using a separate thread
public void repaint()
{
if(drawThread==null || !drawThread.isRunning())
{
// gargabe collect
System.gc();
// post a task to the UI thread
this.post(new Runnable() {
@Override
public void run() {
// create a new drawThread
drawThread = new DrawThread(getHolder(), getContext(), new Handler() {
@Override
public void handleMessage(Message m) {
}
});
// call the setter for the pointer to the model
if (drawable != null) {
drawable.setWidth(getWidth());
drawable.setHeight(getHeight());
// draw the widget
drawThread.setDrawable(drawable);
}
// start the thread
drawThread.start();
}
});
}
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0)
{
// stop the drawThread properly
boolean retry = true;
while (retry)
{
try
{
// wait for it to finish
if(drawThread!=null && drawThread.isRunning())
drawThread.join();
retry = false;
}
catch (InterruptedException e)
{
// ignore any error
e.printStackTrace();
}
}
// set it to null, so that a new one can be created in case of a resume
drawThread=null;
// set parent
//if(canzeActivity!=null)
// canzeActivity.setWidgetClicked(false);
}
/* *************************************
* Getter & Setter
* *************************************/
public String getFieldSID()
{
return fieldSID;
}
@Override
public boolean isClickable() {
return clickable;
}
@Override
public void setClickable(boolean clickable) {
this.clickable = clickable;
}
public void setFieldSID(String fieldSID) {
this.fieldSID = fieldSID;
}
public void setCanzeActivity(CanzeActivity canzeActivity) {
this.canzeActivity = canzeActivity;
}
}
================================================
FILE: app/src/main/res/drawable/animation_bluetooth.xml
================================================
================================================
FILE: app/src/main/res/drawable/background_splash.xml
================================================
-
================================================
FILE: app/src/main/res/drawable/border.xml
================================================
-
-
================================================
FILE: app/src/main/res/drawable/circular_progress_bar.xml
================================================
================================================
FILE: app/src/main/res/drawable/progressbar_canze.xml
================================================
-
-
================================================
FILE: app/src/main/res/drawable/progressbar_canze_accel.xml
================================================
-
-
================================================
FILE: app/src/main/res/drawable/progressbar_canze_decel.xml
================================================
-
-
================================================
FILE: app/src/main/res/drawable/progressbar_canze_decel_aim.xml
================================================
-
================================================
FILE: app/src/main/res/drawable/progressbar_canze_decel_aimright.xml
================================================
-
================================================
FILE: app/src/main/res/drawable/progressbar_canze_green.xml
================================================
-
-
================================================
FILE: app/src/main/res/drawable/progressbar_canze_red.xml
================================================
-
-
================================================
FILE: app/src/main/res/layout/activity_alldata.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_auxbatt.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_battery.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_braking.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_can_see.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_charging.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_charging_graph.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_charginghist.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_chargingtech.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_climatech.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_consumption.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_consumption_dash.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_consumption_mi.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_dash.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_driving.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_dtc.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_elm_test.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_fieldtest.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_firmware.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_heatmap_batcomp.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_heatmap_cellvoltage.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_leak_currents.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_logging.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_prediction.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_range.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_research.xml
================================================
-->
================================================
FILE: app/src/main/res/layout/activity_settings.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_settings_custom.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_speedcontrol.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_tires.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_widget.xml
================================================
================================================
FILE: app/src/main/res/layout/alert_dist_to_dest.xml
================================================
================================================
FILE: app/src/main/res/layout/animated_menu_item.xml
================================================
================================================
FILE: app/src/main/res/layout/fragment_custom.xml
================================================
================================================
FILE: app/src/main/res/layout/fragment_experimental.xml
================================================
================================================
FILE: app/src/main/res/layout/fragment_main.xml
================================================
================================================
FILE: app/src/main/res/layout/fragment_technical.xml
================================================
================================================
FILE: app/src/main/res/layout/logger_field.xml
================================================
================================================
FILE: app/src/main/res/layout/spinner_item.xml
================================================
================================================
FILE: app/src/main/res/menu/menu_driving.xml
================================================
================================================
FILE: app/src/main/res/menu/menu_empty.xml
================================================
================================================
FILE: app/src/main/res/menu/menu_main.xml
================================================
================================================
FILE: app/src/main/res/menu/menu_settings.xml
================================================
================================================
FILE: app/src/main/res/values/arrays.xml
================================================
- ELM327
- CanSee
- Http
- ELM327
- CanSee
- Http
- @string/label_ThemeFollowSystem
- @string/label_ThemeDark
- @string/label_ThemeLight
- @string/label_ToastLevelNone
- @string/label_ToastLevelElm
- @string/label_ToastLevelElmCar
================================================
FILE: app/src/main/res/values/attr.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#112273
#303F9F
#3d82dc
#000080
#ff00ff00
#ff009900
#ffff0000
#8B0000
#1E90FF
#9400D3
#ff30ff30
#ffff3030
#e0a000
#808080
================================================
FILE: app/src/main/res/values/dimens.xml
================================================
8dp
8dp
================================================
FILE: app/src/main/res/values/strings.xml
================================================
CanZE
Settings
Bluetooth
0
-
/
\u0020\u0020
Yes
No
OK
Not OK
Cancel
Reset
No view with id \'%s%s\'
yyyyMMddHHmmssSSS
yyyy-MM-dd HH:mm:ss
yyyy-MM-dd-HH-mm-ss
Nm
\u0020km
\u0020mi
\u0020km/h
\u0020mi/h
\u0020kWh/100km
\u0020mi/kWh
°C
Debug\u0020
#Debug
Initialising widgets
Bluetooth connection lost!
Formal Disclaimer
Yes, I got it!
No, I didn\'t understand a word…
CanZE (“the software”) is provided as is. Use the software at your own
risk. The authors make no warranties as to performance or fitness for a particular purpose,
or any other warranties whether expressed or implied. No oral or written communication
from or information provided by the authors shall create a warranty. Under no circumstances
shall the authors be liable for direct, indirect, special, incidental, or consequential
damages resulting from the use, misuse, or inability to use the software, even if the author
has been advised of the possibility of such damages. These exclusions and limitations may not
apply in all jurisdictions. You may have additional rights and some of these limitations may not
apply to you. This software is only intended for scientific usage.
By using this software you are interfering with your car and doing that with hardware and
software beyond your control, created by a loose team of interested amateurs in this field. Any
car is a possibly lethal piece of machinery and you might hurt or kill yourself or others using
it, or even paying attention to the displays instead of watching the road.