/*
* ChargeController.java - Copyright(c) 2013 Joe Pasqua
* Provided under the MIT License. See the LICENSE file for details.
* Created: Jul 22, 2013
*/
package org.noroomattheinn.visibletesla;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.Callable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Stop;
import jfxtras.labs.scene.control.gauge.Battery;
import jfxtras.labs.scene.control.gauge.Lcd;
import org.noroomattheinn.tesla.ChargeState;
import org.noroomattheinn.tesla.Result;
import org.noroomattheinn.tesla.Vehicle;
import org.noroomattheinn.utils.Utils;
public class ChargeController extends BaseController {
/*------------------------------------------------------------------------------
*
* Constants and Enums
*
*----------------------------------------------------------------------------*/
private static final String MinVersionForChargePct = "1.33.38";
// This value is taken from the wiki here: http://tinyurl.com/mzwxbps
/*------------------------------------------------------------------------------
*
* Internal State
*
*----------------------------------------------------------------------------*/
private boolean useMiles;
private boolean showTimeComplete;
/*------------------------------------------------------------------------------
*
* UI Elements
*
*----------------------------------------------------------------------------*/
@FXML private Button startButton, stopButton;
@FXML private Slider chargeSlider;
@FXML private Label chargeSetting;
@FXML private Hyperlink stdLink, maxLink;
// Elements that display charge status
@FXML private Battery batteryGauge, usableGauge;
@FXML private Label batteryPercentLabel;
// Elements that display reminaing range
@FXML private Lcd estOdometer;
@FXML private Lcd idealOdometer;
@FXML private Lcd ratedOdometer;
// Charging Schedule
@FXML private Label chargeScheduledLabel, scheduledTimeLabel;
// The Charge Property TableView and its Columns
@FXML private TableView<GenericProperty> propertyTable;
@FXML private TableColumn<GenericProperty,String> propNameColumn;
@FXML private TableColumn<GenericProperty,String> propValColumn;
@FXML private TableColumn<GenericProperty,String> propUnitsColumn;
/*------------------------------------------------------------------------------
*
* The Elements of the Property Table
*
*----------------------------------------------------------------------------*/
// Each Property defines a row of the table...
private final GenericProperty pilotCurrent = new GenericProperty("Pilot Current", "0.0", "Amps");
private final GenericProperty voltage = new GenericProperty("Voltage", "0.0", "Volts");
private final GenericProperty batteryCurrent = new GenericProperty("Battery Current", "0.0", "Amps");
private final GenericProperty nRangeCharges = new GenericProperty("# Range Charges", "0.0", "Count");
private final GenericProperty fastCharger = new GenericProperty("Supercharger", "No", "");
private final GenericProperty chargeRate = new GenericProperty("Charge Rate", "0.0", "MPH");
private final GenericProperty remaining = new GenericProperty("Time Left", "00:00:00", "HH:MM:SS");
private final GenericProperty actualCurrent = new GenericProperty("Current", "0.0", "Amps");
private final GenericProperty chargerPower = new GenericProperty("Charger Power", "0.0", "kW");
private final GenericProperty chargingState = new GenericProperty("State", "Disconnected", "");
private final GenericProperty batteryLevel = new GenericProperty("Battery Level", "0", "%");
final ObservableList<GenericProperty> data = FXCollections.observableArrayList(
actualCurrent, voltage, chargeRate, remaining, chargingState,
pilotCurrent, batteryCurrent, fastCharger, chargerPower,
nRangeCharges, batteryLevel);
/*------------------------------------------------------------------------------
*
* UI Action Handlers
*
*----------------------------------------------------------------------------*/
@FXML private void sliderMoved(MouseEvent event) {
setChargePercent((int)chargeSlider.getValue());
}
@FXML void rangeLinkHandler(ActionEvent event) {
Hyperlink h = (Hyperlink)event.getSource();
ChargeState charge = vtVehicle.chargeState.get();
int percent = (h == stdLink) ? charge.chargeLimitSOCStd : charge.chargeLimitSOCMax;
setChargePercent(percent);
}
@FXML void chargeButtonHandler(ActionEvent event) {
final Button b = (Button) event.getSource();
issueCommand(new Callable<Result>() {
@Override public Result call() {
Result r = vtVehicle.getVehicle().setChargeState(b == startButton);
updateStateLater(Vehicle.StateType.Charge, 5 * 1000);
return r;
} }, "Start Charge");
}
private void setChargePercent(final int percent) {
chargeSlider.setValue(percent);
chargeSetting.setText(percent + " %");
issueCommand(new Callable<Result>() {
@Override public Result call() {
Result r = vtVehicle.getVehicle().setChargePercent(percent);
updateStateLater(Vehicle.StateType.Charge, 3 * 1000);
return r;
} }, "Set Charge %");
}
/*------------------------------------------------------------------------------
*
* Methods overridden from BaseController
*
*----------------------------------------------------------------------------*/
@Override protected void fxInitialize() {
// Prepare the property table
propNameColumn.setCellValueFactory( new PropertyValueFactory<GenericProperty,String>("name") );
propValColumn.setCellValueFactory( new PropertyValueFactory<GenericProperty,String>("value") );
propUnitsColumn.setCellValueFactory( new PropertyValueFactory<GenericProperty,String>("units") );
propertyTable.setItems(data);
updatePendingChargeLabels(false);
usableGauge.setVisible(false);
usableGauge.setLevelColors(new Stop[]{
new Stop(0.0, Color.web("#FFC1C1")),
new Stop(0.55, Color.web("#FFFFC1")),
new Stop(1.0, Color.web("#C1D6B8"))
});
// Until we know that we've got the right software version, disable the slider
chargeSlider.setDisable(true);
}
@Override protected void initializeState() {
chargeSlider.setDisable(Utils.compareVersions(
vtVehicle.vehicleState.get().version, MinVersionForChargePct) < 0);
setTimeType(prefs.chargeTimeType.get());
prefs.chargeTimeType.addListener(new ChangeListener<String>() {
@Override public void changed(ObservableValue<? extends String> ov, String t, String t1) {
setTimeType(t1);
}
});
reflectNewState();
vtVehicle.chargeState.addTracker(new Runnable() {
@Override public void run() { if (active()) { reflectNewState(); }
}
});
}
@Override protected void activateTab() {
useMiles = vtVehicle.unitType() == Utils.UnitType.Imperial;
String units = useMiles ? "Miles" : "Km";
estOdometer.setUnit(units);
idealOdometer.setUnit(units);
ratedOdometer.setUnit(units);
chargeRate.setUnits(useMiles ? "mph" : "km/h");
if (vtVehicle.chargeState.get() != null) { reflectNewState(); }
}
@Override protected void refresh() { updateState(Vehicle.StateType.Charge); }
/*------------------------------------------------------------------------------
*
* Methods to Reflect the Status of the Charge
*
*----------------------------------------------------------------------------*/
private void reflectNewState() {
reflectRange();
reflectBatteryStats();
reflectChargeStatus();
reflectProperties();
boolean connected = vtVehicle.chargeState.get().connectedToCharger();
startButton.setDisable(!connected);
stopButton.setDisable(!connected);
}
private void reflectProperties() {
ChargeState charge = vtVehicle.chargeState.get();
double conversionFactor = useMiles ? 1.0 : Utils.KilometersPerMile;
int pc = charge.chargerPilotCurrent;
if (pc == -1) pilotCurrent.setValue("Unknown");
else pilotCurrent.setValue(String.valueOf(pc));
voltage.setValue(String.valueOf(charge.chargerVoltage));
batteryCurrent.setValue(String.format("%.1f", charge.batteryCurrent));
nRangeCharges.setValue(String.valueOf(charge.maxRangeCharges));
fastCharger.setValue(charge.fastChargerPresent ? "Yes":"No");
chargeRate.setValue(String.format("%.1f", charge.chargeRate*conversionFactor));
if (showTimeComplete) {
long msToFull = (long)(charge.timeToFullCharge * (60*60*1000));
if (msToFull == 0 || !charge.isCharging()) {
remaining.setValue("00:00:00");
} else {
Calendar when = Calendar.getInstance();
when.setTimeInMillis(System.currentTimeMillis() + msToFull);
remaining.setValue(
String.format("%02d:%02d:%02d", when.get(Calendar.HOUR_OF_DAY),
when.get(Calendar.MINUTE),
when.get(Calendar.SECOND)));
}
} else {
remaining.setValue(charge.timeToFull());
}
actualCurrent.setValue(String.valueOf(charge.chargerActualCurrent));
chargerPower.setValue(String.valueOf(charge.chargerPower));
chargingState.setValue(charge.chargingState.name());
actualCurrent.setName(charge.chargerPhases == 3 ? "Current \u2462" : "Current");
if (charge.batteryPercent != charge.usableBatteryLevel) {
batteryLevel.setValue(String.format(
"%d/%d", charge.batteryPercent, charge.usableBatteryLevel));
} else {
batteryLevel.setValue(String.format("%d", charge.batteryPercent));
}
}
private void reflectChargeStatus() {
ChargeState charge = vtVehicle.chargeState.get();
int percent = charge.chargeLimitSOC;
chargeSlider.setMin((charge.chargeLimitSOCMin/10)*10);
chargeSlider.setMax(100);
chargeSlider.setMajorTickUnit(10);
chargeSlider.setMinorTickCount(4);
chargeSlider.setBlockIncrement(10);
chargeSlider.setValue(percent);
chargeSetting.setText(percent + " %");
stdLink.setVisited(percent == charge.chargeLimitSOCStd);
maxLink.setVisited(percent == charge.chargeLimitSOCMax);
// Set the labels that indicate a charge is pending
if (charge.scheduledChargePending) {
Date d = new Date(charge.scheduledStart*1000);
String time = new SimpleDateFormat("hh:mm a").format(d);
scheduledTimeLabel.setText("Charging will start at " + time);
updatePendingChargeLabels(true);
} else {
updatePendingChargeLabels(false);
}
}
private void reflectBatteryStats() {
ChargeState charge = vtVehicle.chargeState.get();
double range = charge.range;
int bl = charge.batteryPercent;
int ubl = charge.usableBatteryLevel;
batteryGauge.setChargingLevel(bl/100.0);
usableGauge.setChargingLevel(ubl/100.0);
switch (charge.chargingState) {
case Complete:
case Charging:
batteryGauge.setCharging(true);
usableGauge.setCharging(true);
break;
default:
batteryGauge.setCharging(false);
usableGauge.setCharging(false);
break;
}
if (ubl == 0) ubl = bl;
if (bl - ubl >= 2) {
usableGauge.setVisible(true);
batteryPercentLabel.setTextFill(Color.BLUE);
bl = ubl;
} else {
usableGauge.setVisible(false);
batteryPercentLabel.setTextFill(Color.BLACK);
}
batteryPercentLabel.setText(String.valueOf(bl));
}
private void reflectRange() {
ChargeState charge = vtVehicle.chargeState.get();
double conversionFactor = useMiles ? 1.0 : Utils.KilometersPerMile;
estOdometer.setValue(Utils.round(charge.estimatedRange * conversionFactor, 1));
idealOdometer.setValue(Utils.round(charge.idealRange * conversionFactor, 1));
ratedOdometer.setValue(Utils.round(charge.range * conversionFactor, 1));
}
private void updatePendingChargeLabels(boolean show) {
chargeScheduledLabel.setVisible(show);
scheduledTimeLabel.setVisible(show);
}
private void setTimeType(String timeType) {
switch (timeType) {
case "Complete At":
remaining.setName("Will Complete");
showTimeComplete = true;
break;
case "Remaining":
default:
remaining.setName("Time Left");
showTimeComplete = false;
break;
}
}
}