/* * StatsStreamer.java - Copyright(c) 2013 Joe Pasqua * Provided under the MIT License. See the LICENSE file for details. * Created: Nov 26, 2013 */ package org.noroomattheinn.visibletesla.data; import javafx.beans.property.BooleanProperty; import static org.noroomattheinn.tesla.Tesla.logger; import org.noroomattheinn.tesla.Vehicle; import org.noroomattheinn.utils.Utils; import org.noroomattheinn.utils.Utils.Predicate; import org.noroomattheinn.utils.ThreadManager; import org.noroomattheinn.visibletesla.vehicle.VTVehicle; import static org.noroomattheinn.utils.Utils.timeSince; import org.noroomattheinn.utils.TrackedObject; /** * StatsStreamer: Generate a stream of statistics * * @author Joe Pasqua <joe at NoRoomAtTheInn dot org> */ class StatsStreamer implements Runnable { /*------------------------------------------------------------------------------ * * Constants and Enums * *----------------------------------------------------------------------------*/ private static final long AllowSleepInterval = 30 * 60 * 1000; // 30 Minutes private static final long DefaultInterval = 4 * 60 * 1000; // 4 Minutes private enum CarState {Moving, Charging, Idle, Asleep}; /*------------------------------------------------------------------------------ * * Internal State * *----------------------------------------------------------------------------*/ private final VTData vtData; private final VTVehicle vtVehicle; private final TrackedObject<CarState> carState; private final BooleanProperty streamWhenPossible; private Predicate wakeEarly = Utils.alwaysFalse; private Predicate passiveCollection = Utils.alwaysFalse; /*============================================================================== * ------- ------- * ------- Public Interface To This Class ------- * ------- ------- *============================================================================*/ StatsStreamer(VTData data, VTVehicle vehicle, BooleanProperty streamWhenPossible) { this.vtData = data; this.vtVehicle = vehicle; this.streamWhenPossible = streamWhenPossible; this.carState = new TrackedObject<>(CarState.Idle); // The following two changeListeners look similar, but the order of // the if statements is different and significant. Always check the // one that changed, then test the other (older) value. vtVehicle.chargeState.addTracker(new Runnable() { @Override public void run() { if (isCharging()) carState.update(CarState.Charging); else if (isInMotion()) carState.update(CarState.Moving); else carState.update(CarState.Idle); // Can't be asleep - we just got data from it } }); vtVehicle.streamState.addTracker(new Runnable() { @Override public void run() { if (isInMotion()) carState.update(CarState.Moving); else if (isCharging()) carState.update(CarState.Charging); else carState.update(CarState.Idle); // Can't be asleep - we just got data from it } }); carState.addTracker(new Runnable() { @Override public void run() { logger.finest("Car State changed to: " + carState.get()); } }); ThreadManager.get().launch((Runnable)this, "CollectStats"); } void setWakeEarly(Predicate wakeEarly) { this.wakeEarly = wakeEarly; } void setPassiveCollection(Predicate pc) { this.passiveCollection = pc; } /*------------------------------------------------------------------------------ * * PRIVATE - The main body of the production thread * *----------------------------------------------------------------------------*/ @Override public void run() { try { while (!ThreadManager.get().shuttingDown()) { // String theState = String.format( // "App State: %s, App Mode: %s, Car State: %s", // App.get().state, App.get().mode.get().name(), carState.get()); // logger.finer(theState); boolean produce = true; if (passiveCollection.eval()) { if (carState.get() == CarState.Idle) { if (timeSince(carState.lastSet()) < AllowSleepInterval) { produce = false; if (carIsAsleep()) { carState.set(CarState.Asleep); } } else { carState.set(CarState.Idle); } // Reset idle start time } else if (carState.get() == CarState.Asleep) { if (carIsAwake()) carState.set(CarState.Idle); else produce = false; } } if (produce) produce(); Utils.sleep(DefaultInterval, wakeEarly); } } catch (Exception e) { logger.severe("Uncaught exception in StatsStreamer: " + e.getMessage()); } } private void produce() { if (carState.get() == CarState.Asleep) { if (!wakeupVehicle()) { // If we're unable to wake up the car, don't bother trying to // produce more data. TO DO: Should we do something so that this // is retried sooner rather than later? logger.warning("Unable to wakeup the car after many attempts"); return; } } vtData.produceStream(streamWhenPossible.get()); vtData.produceState(Vehicle.StateType.Charge, null); } /*------------------------------------------------------------------------------ * * PRIVATE - Methods & classes that determine the state of the vehicle and app * *----------------------------------------------------------------------------*/ private boolean isCharging() { return vtVehicle.chargeState.get().isCharging(); } private boolean isInMotion() { return vtVehicle.streamState.get().isInMotion(); } private boolean carIsAsleep() { return !carIsAwake(); } private boolean carIsAwake() { return vtVehicle.getVehicle().isAwake(); } private boolean carIsInactive() { return !carIsActive(); } private boolean carIsActive() { return (carState.get() == CarState.Moving || carState.get() == CarState.Charging); } private boolean wakeupVehicle() { if (vtVehicle.forceWakeup()) { carState.set(CarState.Idle); return true; } return false; } }