org.noroomattheinn.visibletesla.OverviewController.java Source code

Java tutorial

Introduction

Here is the source code for org.noroomattheinn.visibletesla.OverviewController.java

Source

/*
 * OverviewController.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.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Dialogs;
import javafx.scene.control.Dialogs.DialogOptions;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import org.apache.commons.lang3.StringUtils;
import org.noroomattheinn.tesla.ChargeState;
import org.noroomattheinn.tesla.Options;
import org.noroomattheinn.tesla.Result;
import org.noroomattheinn.tesla.StreamState;
import org.noroomattheinn.tesla.Vehicle;
import org.noroomattheinn.tesla.Vehicle.PanoCommand;
import org.noroomattheinn.tesla.VehicleState;
import org.noroomattheinn.utils.Utils;

public class OverviewController extends BaseController {
    /*------------------------------------------------------------------------------
     *
     * Constants and Enums
     * 
     *----------------------------------------------------------------------------*/

    private static final String ToggleChoiceKey = "DISP_VIN";

    private enum ToggleDisplayChoice {
        VIN, SW, FW
    };

    private static final int nToggleChoices = ToggleDisplayChoice.values().length;

    /*------------------------------------------------------------------------------
     *
     * Internal State
     * 
     *----------------------------------------------------------------------------*/

    private ToggleDisplayChoice toggleChoice;

    /*------------------------------------------------------------------------------
     *
     * UI Elements
     * 
     *----------------------------------------------------------------------------*/

    // Lock Status Images
    @FXML
    private ImageView lockedImg;
    @FXML
    private ImageView unlockedImg;

    //
    // Car Images (and Labels)
    //
    @FXML
    private ImageView bodyImg;

    // Wheels
    @FXML
    private ImageView silver21Front, silver21Rear;
    @FXML
    private ImageView darkRimFront, darkRimRear;
    @FXML
    private ImageView nineteenRimFront, nineteenRimRear;
    @FXML
    private ImageView aeroFront, aeroRear;
    @FXML
    private ImageView cycloneFront, cycloneRear;
    @FXML
    private ImageView darkCycloneFront, darkCycloneRear;
    private Map<Options.WheelType, Options.WheelType> wheelEquivs = new HashMap<>();
    private Map<Options.WheelType, ImageView[]> wheelImages = new HashMap<>();

    @FXML
    private ImageView seatsTanImg, seatsGrayImg;

    // Driver Side
    @FXML
    private ImageView dfOpenImg, dfClosedImg;
    @FXML
    private ImageView drOpenImg, drClosedImg;

    // Passenger Side
    @FXML
    private ImageView pfOpenImg;
    @FXML
    private ImageView prOpenImg;

    // Trunk/Frunk
    @FXML
    private ImageView ftClosedImg, ftOpenImg;
    @FXML
    private ImageView rtOpenImg, rtClosedImg;
    @FXML
    private ImageView spoilerClosedImg, spoilerOpenImg;

    // Roof Images (Solipath+Pano: Open, Closepath+Vent)
    @FXML
    private ImageView blackRoofImg, solidRoofImg;
    @FXML
    private ImageView panoClosedImg, panoVentImg, panoOpenImg;
    @FXML
    private Label panoPercent;

    // Charging related images
    @FXML
    private ImageView chargeCableImg, portClosedImg, portOpenImg, greenGlowImage;

    // Other Labels
    @FXML
    private Label shiftStateLabel;
    @FXML
    private Label rangeLabel;
    @FXML
    private Label odometerLabel;
    @FXML
    private Button vinButton;

    // Emblem Images
    @FXML
    private ImageView s60Img, s85Img, p85Img, p85pImg, p85dImg, s85dImg, s70dImg;

    //
    // Controls
    //
    @FXML
    private Button lockButton;
    @FXML
    private Button closePanoButton, ventPanoButton, openPanoButton;

    /*------------------------------------------------------------------------------
     *
     *  UI Action Handlers
     * 
     *----------------------------------------------------------------------------*/

    @FXML
    void lockButtonHandler(ActionEvent event) {
        final Button source = (Button) event.getSource();
        issueCommand(new Callable<Result>() {
            @Override
            public Result call() {
                Result r = vtVehicle.getVehicle().setLockState(source == lockButton);
                updateStateLater(Vehicle.StateType.Vehicle, 3 * 1000);
                return r;
            }
        }, (source == lockButton) ? "Lock" : "Unlock");
    }

    @FXML
    void panoButtonHandler(ActionEvent event) {
        Button source = (Button) event.getSource();
        final PanoCommand cmd = source == ventPanoButton ? PanoCommand.vent
                : ((source == openPanoButton) ? PanoCommand.open : PanoCommand.close);
        issueCommand(new Callable<Result>() {
            @Override
            public Result call() {
                Result r = vtVehicle.getVehicle().setPano(cmd);
                updateStateLater(Vehicle.StateType.Vehicle, 5 * 1000);
                return r;
            }
        }, "Move Pano");
    }

    @FXML
    void detailsButtonHandler(ActionEvent event) {
        AnchorPane pane = new AnchorPane();
        VehicleState car = vtVehicle.vehicleState.get();
        String info = vtVehicle.getVehicle().toString() + "\nFirmware Version: " + car.version
                + "\nRemote Start Enabled: " + vtVehicle.getVehicle().remoteStartEnabled() + "\nCalendar Enabled: "
                + vtVehicle.getVehicle().calendarEnabled() + "\nNotifications Enabled: "
                + vtVehicle.getVehicle().notificationsEnabled() + "\n--------------------------------------------"
                + "\nLow level information: " + vtVehicle.getVehicle().getUnderlyingValues() + "\nVehicle UUID: "
                + vtVehicle.getVehicle().getUUID() + "\nApp UUID: " + app.getAppID() + "\n";

        TextArea t = new TextArea(info);
        pane.getChildren().add(t);
        Dialogs.showCustomDialog(app.stage, pane, "Detailed Vehicle Description", "Details", DialogOptions.OK,
                null);
    }

    /*------------------------------------------------------------------------------
     *
     * Methods overridden from BaseController
     * 
     *----------------------------------------------------------------------------*/

    @Override
    protected void fxInitialize() {
        odometerLabel.setVisible(true);

        wheelImages.put(Options.WheelType.WT19, new ImageView[] { nineteenRimFront, nineteenRimRear });
        wheelEquivs.put(Options.WheelType.WT1P, Options.WheelType.WT19);
        wheelEquivs.put(Options.WheelType.WTX1, Options.WheelType.WT19);

        wheelImages.put(Options.WheelType.WTAE, new ImageView[] { aeroFront, aeroRear });
        wheelEquivs.put(Options.WheelType.WTAP, Options.WheelType.WTAE);

        wheelImages.put(Options.WheelType.WTTB, new ImageView[] { cycloneFront, cycloneRear });
        wheelEquivs.put(Options.WheelType.WTTP, Options.WheelType.WTTB);

        wheelImages.put(Options.WheelType.WTTG, new ImageView[] { darkCycloneFront, darkCycloneRear });
        wheelEquivs.put(Options.WheelType.WTGP, Options.WheelType.WTTG);

        wheelImages.put(Options.WheelType.WT21, new ImageView[] { silver21Front, silver21Rear });
        wheelEquivs.put(Options.WheelType.WT2E, Options.WheelType.WT21);
        wheelEquivs.put(Options.WheelType.WTSS, Options.WheelType.WT21);

        wheelImages.put(Options.WheelType.WTSP, new ImageView[] { darkRimFront, darkRimRear });
        wheelEquivs.put(Options.WheelType.WTSE, Options.WheelType.WTSP);
        wheelEquivs.put(Options.WheelType.WTSG, Options.WheelType.WTSP);
    }

    /**
     * Refresh the state either because the user requested it or because the 
     * auto-refresh interval has passed. We always update the car and
     * charge. Getting the odometer reading can be more burdensome because
     * it has to be done through the streaming API. We only do that every 3rd
     * time refresh is invoked, or if the user pressed the refresh button.
     * This keeps down our request rate to the tesla servers.
     * 
     */
    @Override
    protected void refresh() {
        updateState(Vehicle.StateType.Vehicle);
        updateState(Vehicle.StateType.Charge);
    }

    @Override
    protected void initializeState() {
        final Vehicle v = vtVehicle.getVehicle();
        getAppropriateImages(v);
        toggleChoice = this.storedToggleChoice();
        prefs.overrides.color.addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> ov, String t, String t1) {
                getAppropriateImages(v);
            }
        });
        prefs.overrides.doColor.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
                getAppropriateImages(v);
            }
        });

        vtVehicle.vehicleState.addTracker(new Runnable() {
            @Override
            public void run() {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        updateVehicleState();
                    }
                });
            }
        });
        vtVehicle.chargeState.addTracker(new Runnable() {
            @Override
            public void run() {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        updateChargePort();
                        updateRange();
                    }
                });
            }
        });
        vtVehicle.streamState.addTracker(new Runnable() {
            @Override
            public void run() {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        updateOdometer();
                        updateShiftState();
                    }
                });
            }
        });

        updateOdometer(); // Show at least an old reading to start
        vtData.produceStream(false); // Update it at some point

        updateWheelView(); // Make sure we display the right wheels from the get-go
        updateRoofView(); // Make sure we display the right roof from the get-go
        reflectVINOrFirmware();
        vinButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                cycleToggleChoice();
                reflectVINOrFirmware();
            }
        });
    }

    @Override
    protected void activateTab() {
    }

    /*------------------------------------------------------------------------------
     *
     * Methods to Reflect the overall state of the vehicle
     * 
     *----------------------------------------------------------------------------*/

    private void updateVehicleState() {
        updateWheelView();
        updateRoofView();
        updateDoorView();
        updateOdometer();
        updateEmblem();
        updateSeats();
        updateRange();
        updateShiftState();
    }

    private void updateRange() {
        ChargeState cs = vtVehicle.chargeState.get();
        double range = 0;
        String rangeType = prefs.overviewRange.get();
        switch (rangeType) {
        case "Estimated":
            range = cs.estimatedRange;
            break;
        case "Ideal":
            range = cs.idealRange;
            break;
        case "Rated":
            range = cs.range;
            break;
        }
        range = vtVehicle.inProperUnits(range);
        String units = vtVehicle.unitType() == Utils.UnitType.Imperial ? "mi" : "km";
        rangeLabel.setText(String.format("%s Range: %3.1f %s", rangeType, range, units));
    }

    private void updateShiftState() {
        StreamState snapshot = vtVehicle.streamState.get();
        if (snapshot == null)
            return;
        shiftStateLabel.setText(snapshot.shiftState());
    }

    private void updateDoorView() {
        VehicleState car = vtVehicle.vehicleState.get();
        boolean rtOpen = car.isRTOpen;

        // Show the open/closed state of the doors and trunks
        setOptionState(car.isFTOpen, ftOpenImg, ftClosedImg);
        setOptionState(rtOpen, rtOpenImg, rtClosedImg);
        setOptionState(car.isDFOpen, dfOpenImg, dfClosedImg);
        setOptionState(car.isPFOpen, pfOpenImg, null);
        setOptionState(car.isDROpen, drOpenImg, drClosedImg);
        setOptionState(car.isPROpen, prOpenImg, null);
        setOptionState(car.locked, lockedImg, unlockedImg);

        spoilerOpenImg.setVisible(false);
        spoilerClosedImg.setVisible(false);
        if (vtVehicle.vehicleState.get().hasSpoiler) {
            setOptionState(rtOpen, spoilerOpenImg, spoilerClosedImg);
        }
    }

    private void updateRoofView() {
        Options.RoofType type = vtVehicle.roofType();

        boolean hasPano = (type == Options.RoofType.RFPO);

        // Start with all images set to invisible, then turn on the one right one
        panoOpenImg.setVisible(false);
        panoClosedImg.setVisible(false);
        panoVentImg.setVisible(false);
        solidRoofImg.setVisible(false);
        blackRoofImg.setVisible(false);
        // Only show the pano controls and percent if we have a pano roof
        closePanoButton.setVisible(hasPano);
        ventPanoButton.setVisible(hasPano);
        openPanoButton.setVisible(hasPano);
        panoPercent.setVisible(hasPano);

        if (hasPano)
            updatePanoView();
        else
            setOptionState(type == Options.RoofType.RFBC, solidRoofImg, blackRoofImg);
    }

    private void updatePanoView() {
        VehicleState car = vtVehicle.vehicleState.get();
        int pct = car.panoPercent;

        if (pct == 0)
            panoClosedImg.setVisible(true);
        else if (pct > 0 && pct < 90)
            panoVentImg.setVisible(true);
        else
            panoOpenImg.setVisible(true);
        panoPercent.setText(String.valueOf(pct) + " %");
    }

    private void updateWheelView() {
        updateImages(vtVehicle.wheelType(), wheelImages, wheelEquivs);
    }

    private void updateChargePort() {
        ChargeState charge = vtVehicle.chargeState.get();
        boolean connected = charge.connectedToCharger();

        boolean chargePortDoorOpen = (charge.chargePortOpen || connected);
        setOptionState(chargePortDoorOpen, portOpenImg, portClosedImg);
        chargeCableImg.setVisible(connected);
        greenGlowImage.setVisible(charge.chargingState == ChargeState.Status.Charging);
    }

    private void updateSeats() {
        seatsGrayImg.setVisible(false);
        seatsTanImg.setVisible(false);
        switch (vtVehicle.getVehicle().getOptions().seatType().getColor()) {
        case Gray:
        case White:
            seatsGrayImg.setVisible(true);
            break;
        case Tan:
            seatsTanImg.setVisible(true);
            break;
        case Black: // Do nothing, the base image is black
        default:
            break;
        }
    }

    private void updateEmblem() {
        s60Img.setVisible(false);
        s85Img.setVisible(false);
        p85Img.setVisible(false);
        p85pImg.setVisible(false);
        s70dImg.setVisible(false);
        s85dImg.setVisible(false);
        p85dImg.setVisible(false);
        switch (vtVehicle.model()) {
        // RWD Standard Models
        case S60:
            s60Img.setVisible(true);
            break;
        case S85:
            s85Img.setVisible(true);
            break;
        // RWD Performance Models
        case P85:
            p85Img.setVisible(true);
            break;
        case P85Plus:
            p85pImg.setVisible(true);
            break;
        // AWD Standard & Performance Models
        case S70D:
            s70dImg.setVisible(true);
            break;
        case S85D:
            s85dImg.setVisible(true);
            break;
        case P85D:
            p85dImg.setVisible(true);
            break;

        default:
            s85Img.setVisible(true);
            break;
        }
    }

    private void updateOdometer() {
        double odometerReading;
        if (vtVehicle.streamState.get().valid) {
            odometerReading = vtVehicle.streamState.get().odometer;
            prefs.storage().putDouble(vinKey("odometer"), odometerReading);
        } else {
            odometerReading = prefs.storage().getDouble(vinKey("odometer"), 0);
        }

        boolean useMiles = vtVehicle.unitType() == Utils.UnitType.Imperial;
        String units = useMiles ? "mi" : "km";
        odometerReading *= useMiles ? 1.0 : Utils.KilometersPerMile;
        odometerLabel.setText(String.format("Odometer: %.1f %s", odometerReading, units));
    }

    private void reflectVINOrFirmware() {
        VehicleState car = vtVehicle.vehicleState.get();
        String text;
        switch (toggleChoice) {
        case SW:
            text = "v" + Firmware.getSoftwareVersion(car.version);
            break;
        case FW:
            text = "FW: " + car.version;
            break;
        case VIN:
        default:
            text = "VIN " + StringUtils.right(vtVehicle.getVehicle().getVIN(), 6);
            break;
        }
        vinButton.setText(text);
    }

    private ToggleDisplayChoice storedToggleChoice() {
        String currentAsString = prefs.storage().get("DISP_VIN", ToggleDisplayChoice.VIN.name());
        return ToggleDisplayChoice.valueOf(currentAsString);
    }

    private void cycleToggleChoice() {
        int nextIndex = (toggleChoice.ordinal() + 1) % nToggleChoices;
        toggleChoice = ToggleDisplayChoice.values()[nextIndex];
        prefs.storage().put(ToggleChoiceKey, toggleChoice.name());
    }

    /*------------------------------------------------------------------------------
     *
     * State and Methods for locating the right images based on vehicle parameters
     * 
     *----------------------------------------------------------------------------*/

    // This Map maps from a PaintColor to a directory name which holds the
    // images for that color. As new colors are added by Tesla, the map
    // must be udated (as must the PaintColor enum).
    private static final Map<Options.PaintColor, String> colorToDirectory = new HashMap<>();
    static {
        colorToDirectory.put(Options.PaintColor.PBCW, "COLOR_white/");
        colorToDirectory.put(Options.PaintColor.PBSB, "COLOR_black/");
        colorToDirectory.put(Options.PaintColor.PMAB, "COLOR_brown/");
        colorToDirectory.put(Options.PaintColor.PMMB, "COLOR_blue/");
        colorToDirectory.put(Options.PaintColor.PMSG, "COLOR_green/");
        colorToDirectory.put(Options.PaintColor.PMSS, "COLOR_silver/");
        colorToDirectory.put(Options.PaintColor.PMTG, "COLOR_gray/");
        colorToDirectory.put(Options.PaintColor.PPMR, "COLOR_newred/");
        colorToDirectory.put(Options.PaintColor.PPSR, "COLOR_red/");
        colorToDirectory.put(Options.PaintColor.PPSW, "COLOR_pearl/");
        colorToDirectory.put(Options.PaintColor.PMNG, "COLOR_steelgrey/");
        colorToDirectory.put(Options.PaintColor.PPTI, "COLOR_titanium/");
        colorToDirectory.put(Options.PaintColor.PMBL, "COLOR_black/");
        colorToDirectory.put(Options.PaintColor.PPSB, "COLOR_oceanblue/");
        colorToDirectory.put(Options.PaintColor.Unknown, "COLOR_white/");
    }

    // Where the images are stored relative to the classpath
    private static final String ImagePrefix = "org/noroomattheinn/TeslaResources/";

    // Replace the images that were selected by default with images for the actual color
    private void getAppropriateImages(Vehicle v) {
        Options.PaintColor c = vtVehicle.paintColor();

        ClassLoader cl = getClass().getClassLoader();
        String colorDirectory = colorToDirectory.get(c);
        String path = ImagePrefix + colorDirectory;

        if (v.getOptions().driveSide() == Options.DriveSide.DRLH)
            bodyImg.setImage(new Image(cl.getResourceAsStream(path + "body@2x.png")));
        else
            bodyImg.setImage(new Image(cl.getResourceAsStream(path + "body_RHD@2x.png")));
        dfOpenImg.setImage(new Image(cl.getResourceAsStream(path + "left_front_open@2x.png")));
        dfClosedImg.setImage(new Image(cl.getResourceAsStream(path + "left_front_closed@2x.png")));
        drOpenImg.setImage(new Image(cl.getResourceAsStream(path + "left_rear_open@2x.png")));
        drClosedImg.setImage(new Image(cl.getResourceAsStream(path + "left_rear_closed@2x.png")));
        pfOpenImg.setImage(new Image(cl.getResourceAsStream(path + "right_front_open@2x.png")));
        prOpenImg.setImage(new Image(cl.getResourceAsStream(path + "right_rear_open@2x.png")));
        ftClosedImg.setImage(new Image(cl.getResourceAsStream(path + "frunk_closed@2x.png")));
        ftOpenImg.setImage(new Image(cl.getResourceAsStream(path + "frunk_open@2x.png")));
        rtOpenImg.setImage(new Image(cl.getResourceAsStream(path + "trunk_open@2x.png")));
        rtClosedImg.setImage(new Image(cl.getResourceAsStream(path + "trunk_closed@2x.png")));
        solidRoofImg.setImage(new Image(cl.getResourceAsStream(path + "roof@2x.png")));
    }

}