/*
* HVACController.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.util.concurrent.Callable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import org.noroomattheinn.tesla.HVACState;
import org.noroomattheinn.tesla.Options;
import org.noroomattheinn.tesla.Result;
import org.noroomattheinn.tesla.Vehicle;
import org.noroomattheinn.utils.Utils;
public class HVACController extends BaseController {
/*------------------------------------------------------------------------------
*
* Constants and Enums
*
*----------------------------------------------------------------------------*/
/*------------------------------------------------------------------------------
*
* Internal State
*
*----------------------------------------------------------------------------*/
private boolean useDegreesF = false;
private Vehicle v;
/*------------------------------------------------------------------------------
*
* UI Elements
*
*----------------------------------------------------------------------------*/
@FXML private ImageView bodyImg;
// Temperature Readouts
@FXML private Label insideTmpLabel, outsideTempLabel;
@FXML private Label targetTempLabel;
// Cold / Medium / Hot Images
@FXML private ImageView climateColdImg;
@FXML private ImageView climateHotImg;
// Fan Speed Images
@FXML private ImageView fan0, fan1, fan2, fan3, fan4;
// Defroster On/Off Images
@FXML private ImageView frontDefOffImg;
@FXML private ImageView frontDefOnImg;
@FXML private ImageView rearDefOffImg;
@FXML private ImageView rearDefOnImg;
// Controls
@FXML private ToggleButton hvacOffButton, hvacOnButton;
@FXML private Slider tempSlider;
/*------------------------------------------------------------------------------
*
* UI Action Handlers
*
*----------------------------------------------------------------------------*/
@FXML void hvacOnOffHandler(ActionEvent event) {
issueCommand(new Callable<Result>() {
@Override public Result call() {
Result r = v.setAC(hvacOnButton.isSelected());
updateStateLater(Vehicle.StateType.HVAC, 3 * 1000);
return r;
} }, "HVAC " + (hvacOnButton.isSelected() ? "On" : "Off"));
}
// Only called on MOUSE_RELEASED
@FXML void tempChangeHandler(MouseEvent event) {
double temp = tempSlider.valueProperty().doubleValue();
if (useDegreesF) temp = Math.round(temp);
else temp = nearestHalf(temp);
tempSlider.setValue(temp);
setTemp(temp);
}
private void setTemp(final double temp) {
final boolean setF = useDegreesF; // Must be final, so copy it...
issueCommand(new Callable<Result>() {
@Override public Result call() {
Result r = (setF) ? v.setTempF(temp, temp)
: v.setTempC(temp, temp);
updateStateLater(Vehicle.StateType.HVAC, 3 * 1000);
return r;
} }, "Set Temp");
}
private double nearestHalf(double val) { return Math.floor(val*2.0)/2.0; }
/*------------------------------------------------------------------------------
*
* Methods overridden from BaseController
*
*----------------------------------------------------------------------------*/
@Override protected void initializeState() {
v = vtVehicle.getVehicle();
vtVehicle.hvacState.addTracker(new Runnable() {
@Override public void run() {
if (active()) { reflectNewState(); }
}
});
getAppropriateBody(); // Get the body image that matches the wheels
prefs.overrides.wheels.addListener(new ChangeListener<String>() {
@Override public void changed(
ObservableValue<? extends String> ov, String t, String t1) {
getAppropriateBody(); // Get the body image that matches the wheels
}
});
prefs.overrides.doWheels.addListener(new ChangeListener<Boolean>() {
@Override public void changed(
ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
getAppropriateBody(); // Get the body image that matches the wheels
}
});
useDegreesF = vtVehicle.useDegreesF();
}
@Override protected void activateTab() {
useDegreesF = vtVehicle.useDegreesF();
if (useDegreesF) {
tempSlider.setMin(62);
tempSlider.setMax(90);
tempSlider.setMajorTickUnit(2);
tempSlider.setMinorTickCount(1);
tempSlider.setValue(70); // Until the real value is retrieved
} else {
tempSlider.setMin(17.0);
tempSlider.setMax(32.0);
tempSlider.setMajorTickUnit(1);
tempSlider.setMinorTickCount(1);
tempSlider.setValue(19.5); // Until the real value is retrieved
}
}
@Override protected void refresh() { updateState(Vehicle.StateType.HVAC); }
// Controller-specific initialization
@Override protected void fxInitialize() {
tempSlider.valueProperty().addListener(new ChangeListener<Number>() {
@Override public void changed(ObservableValue<? extends Number> ov,
Number old, Number cur) {
double temp = adjustedTemp(cur.doubleValue());
targetTempLabel.setText(String.format(useDegreesF ? "%.0f ºF" : "%.1f ºC", temp));
}
});
}
/*------------------------------------------------------------------------------
*
* Methods to Reflect the State of the HVAC System
*
*----------------------------------------------------------------------------*/
private void reflectNewState() {
reflectHVACOnState();
reflectActualTemps();
reflectDefrosterState();
}
private void reflectHVACOnState() {
// Determining whether the HVAC is on is a little tricky. You'd think
// you could just look at hvac.state.autoConditioning, but that only tells
// you whether the AC is running - not the heat. Until I determine a better
// way, I'm using the fan speed to indicate whether the HVAC is on and using
// the temp vs. temp set point to determine whether it is heating or cooling.
HVACState hvac = vtVehicle.hvacState.get();
boolean hvacOn = (hvac.fanStatus > 0);
hvacOnButton.setSelected(hvacOn);
hvacOffButton.setSelected(!hvacOn);
reflectFanStatus();
updateCoolHotImages();
double temp = hvac.driverTemp;
if (useDegreesF) temp = Math.round(Utils.cToF(temp));
else temp = nearestHalf(temp);
tempSlider.setValue(temp);
}
private void reflectFanStatus() {
// Ensure all the fan images are off
fan0.setVisible(false); fan1.setVisible(false);
fan2.setVisible(false); fan3.setVisible(false); fan4.setVisible(false);
// Now turn on the right one...
int fanSpeed = vtVehicle.hvacState.get().fanStatus; // Range of 0-7
if (fanSpeed >= 6) fan4.setVisible(true);
else if (fanSpeed >= 4) fan3.setVisible(true);
else if (fanSpeed >= 2) fan2.setVisible(true);
else if (fanSpeed == 1) fan1.setVisible(true);
else fan0.setVisible(true);
}
private void reflectDefrosterState() {
HVACState hvac = vtVehicle.hvacState.get();
setOptionState(hvac.isFrontDefrosterOn != 0, frontDefOnImg, frontDefOffImg);
setOptionState(hvac.isRearDefrosterOn, rearDefOnImg, rearDefOffImg);
}
private void reflectActualTemps() {
HVACState hvac = vtVehicle.hvacState.get();
setTempLabel(insideTmpLabel, hvac.insideTemp);
setTempLabel(outsideTempLabel, hvac.outsideTemp);
}
private void updateCoolHotImages() {
HVACState hvac = vtVehicle.hvacState.get();
climateColdImg.setVisible(false);
climateHotImg.setVisible(false);
if (hvac.fanStatus > 0) {
double insideTemp = hvac.insideTemp;
if (insideTemp > hvac.driverTemp)
climateColdImg.setVisible(true);
else if (insideTemp < hvac.driverTemp) {
climateHotImg.setVisible(true);
}
}
}
private double adjustedTemp(double temp) {
return (useDegreesF) ? Math.round(temp): nearestHalf(temp);
}
private void setTempLabel(Label label, double tempC) {
if (Double.isNaN(tempC)) { // No value is available
label.setText("...");
return;
}
double temp = adjustedTemp((useDegreesF) ? Utils.cToF(tempC) : tempC);
label.setText(String.format(useDegreesF ? "%.0f ºF" : "%.1f ºC", temp));
}
private static final String ImagePrefix = "org/noroomattheinn/TeslaResources/";
private void getAppropriateBody() {
ClassLoader cl = getClass().getClassLoader();
Options.WheelType wt = vtVehicle.wheelType();
String image;
switch (wt) {
case WT19: // Silver 19" Wheels
case WT1P:
case WTX1:
image = "05_climate_car_19@2x.png";
break;
case WTAE: // Aero Wheels
case WTAP:
image = "05_climate_car_aero@2x.png";
break;
case WTTB: // Turbine 19" Silver
case WTTP:
image = "05_climate_car_turbine@2x.png";
break;
case WTTG: // Turbine 19" Charcoal
case WTGP:
image = "05_climate_car_turbine_dark@2x.png";
break;
case WTSP: // Charcoal 21"
case WTSE:
case WTSG:
image = "05_climate_car_21_dark@2x.png";
break;
case WT21: // Silver 21"
case WT2E:
case WTSS:
default:
image = "05_climate_car_21@2x.png";
break;
}
bodyImg.setImage(new Image(cl.getResourceAsStream(ImagePrefix+image)));
}
}