package lu.fisch.canze.activities;
import android.os.Bundle;
import android.view.Menu;
import android.widget.TextView;
import lu.fisch.canze.R;
import lu.fisch.canze.actors.Battery;
import lu.fisch.canze.actors.Field;
import lu.fisch.canze.interfaces.DebugListener;
import lu.fisch.canze.interfaces.FieldListener;
public class PredictionActivity extends CanzeActivity implements FieldListener, DebugListener {
public static final String SID_AvChargingPower = "427.40";
public static final String SID_UserSoC = "42e.0"; // user SOC, not raw
public static final String SID_AverageBatteryTemperature = "7bb.6104.600"; // (LBC)
public static final String SID_RangeEstimate = "654.42";
public static final String SID_ChargingStatusDisplay = "65b.41";
private Battery battery;
private double car_soc = 5;
private double car_bat_temp = 10;
private double car_charger_ac_power = 22;
private int car_status = 0;
private int charging_status = 0;
private int seconds_per_tick = 288; // time 100 iterations = 8 hours
private double car_range_est = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_prediction);
// initialize the battery model
battery = new Battery();
// more adjustments are needed her for other car types than ZOE Q210
if (MainActivity.car == MainActivity.CAR_ZOE_R240 || MainActivity.car == MainActivity.CAR_ZOE_R90) {
battery.setDcPowerLowerLimit(1.0);
battery.setDcPowerUpperLimit(20.0);
}
if (MainActivity.car == MainActivity.CAR_ZOE_Q90 || MainActivity.car == MainActivity.CAR_ZOE_R90) {
battery.setRawCapacity(41.0);
}
}
protected void initListeners() {
MainActivity.getInstance().setDebugListener(this);
addField(SID_RangeEstimate, 10000);
addField(SID_AvChargingPower, 10000);
addField(SID_UserSoC, 10000);
addField(SID_ChargingStatusDisplay, 10000);
addField(SID_AverageBatteryTemperature, 10000);
}
// This is the event fired as soon as this the registered fields are
// getting updated by the corresponding reader class.
@Override
public void onFieldUpdateEvent(final Field field) {
String fieldId = field.getSID();
Double fieldVal = field.getValue();
if (fieldVal.isNaN()) return;
// get the text field
switch (fieldId) {
case SID_AvChargingPower:
car_charger_ac_power = fieldVal;
car_status |= 0x01;
break;
case SID_UserSoC:
car_soc = fieldVal;
car_status |= 0x02;
break;
case SID_AverageBatteryTemperature:
car_bat_temp = fieldVal;
car_status |= 0x04;
break;
case SID_RangeEstimate:
car_range_est = fieldVal;
car_status |= 0x08;
break;
case SID_ChargingStatusDisplay:
charging_status = (fieldVal == 3) ? 1 : 0;
car_status |= 0x10;
}
// display the debug values
if (car_status == 0x1f) {
runPrediction();
car_status = 0;
}
}
private void runPrediction() {
// set the battery object to an initial state equal to the real battery (
battery.setTimeRunning(0);
// set the internal battery temperature
updatePrediction("texttemp", "" + (int) car_bat_temp + "°C");
battery.setTemperature(car_bat_temp);
// set the internal state of charge
updatePrediction("textsoc", (int) car_soc + "%");
battery.setStateOfChargePerc(car_soc);
if (charging_status == 0) {
updatePrediction("textacpwr", "Not charging");
for (int t = 10; t <= 100; t = t + 10) {
updatePrediction("textTIM" + t, "00:00");
updatePrediction("textSOC" + t, "-");
updatePrediction("textRAN" + t, "-");
updatePrediction("textPWR" + t, "-");
}
return;
}
// set the external maximum charger capacity
updatePrediction("textacpwr", ((int) (car_charger_ac_power * 10)) / 10 + " kW");
battery.setChargerPower(car_charger_ac_power);
// now start iterating over time
int iter_at_99 = 100; // tick when the battery is full
for (int t = 1; t <= 100; t++) { // 100 ticks
battery.iterateCharging(seconds_per_tick);
double soc = battery.getStateOfChargePerc();
// save the earliest tick when the battery is full
if (soc >= 99 && t < iter_at_99) iter_at_99 = t;
// optimization
if ((t % 10) == 0) {
updatePrediction("textTIM" + t, "" + formatTime(battery.getTimeRunning()));
updatePrediction("textSOC" + t, "" + ((int) soc));
if (car_soc > 0.0) updatePrediction("textRAN" + t, "" + ((int) (car_range_est * soc / car_soc)));
updatePrediction("textPWR" + t, "" + ((int) battery.getDcPower()));
}
}
// adjust the tick time if neccesary. Note that this is
// effective on th next iteration
if (iter_at_99 == 100 && seconds_per_tick < 288) {
// if we were unable to go to 99% and below 8 hours, double tick step
seconds_per_tick *= 2;
} else if (iter_at_99 > 50) {
// if we were full after half the table size
// do nothing
seconds_per_tick *= 1;
} else if (iter_at_99 > 25 && seconds_per_tick > 18) {
// if we were full after a quarter of the table size
// and over half an hour, half the tick step
seconds_per_tick /= 2;
} else if (seconds_per_tick > 18) {
// if we were full before or equal a quarter of the table size
// and over half an hour, quarter the tick step
seconds_per_tick /= 4;
}
}
public void updatePrediction(final String id, final String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView tv;
tv = (TextView) findViewById(getResources().getIdentifier(id, "id", getPackageName()));
if (tv != null) {
tv.setText(msg);
}
}
});
}
private String formatTime(int t) {
// t is in seconds
t /= 60;
// t is in minutes
return "" + format2Digit(t / 60) + ":" + format2Digit(t % 60);
}
private String format2Digit(int t) {
return ("00" + t).substring(t > 9 ? 2 : 1);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_empty, menu);
return true;
}
}