package oculusPrime; import developer.Navigation; import developer.Ros; import oculusPrime.AutoDock.autodockmodes; import oculusPrime.State.values; import oculusPrime.commport.ArduinoPower; import oculusPrime.commport.ArduinoPrime; import oculusPrime.commport.PowerLogger; import java.util.Timer; import java.util.TimerTask; public class SystemWatchdog { private final Settings settings = Settings.getReference(); protected Application application = null; static final String AP = "oculusprime"; private static final long DELAY = 10000; // 10 sec public static final long AUTODOCKTIMEOUT= 360000; // 6 min private static final long ABANDONDEDLOGIN= 30*Util.ONE_MINUTE; public static final String NOFORWARD = "noforward"; // stale system reboot frequency private static final long STALE = Util.ONE_DAY * 2; private State state = State.getReference(); public String lastpowererrornotify = null; // this gets set to null on client login public boolean powererrorwarningonly = true; public boolean redocking = false; private boolean lowbattredock = false; private long lowbattredockstart = 0; private String ssid = null; SystemWatchdog(Application a){ application = a; new Timer().scheduleAtFixedRate(new Task(), DELAY, DELAY); } private class Task extends TimerTask { public void run() { // regular reboot if set if (System.currentTimeMillis() - state.getLong(values.linuxboot) > STALE && !state.exists(State.values.driver.toString()) && !state.exists(State.values.powererror.toString()) && state.get(State.values.dockstatus).equals(AutoDock.DOCKED) && state.getInteger(State.values.telnetusers) == 0 && state.getUpTime() > Util.TEN_MINUTES && // prevent runaway reboots (settings.getBoolean(GUISettings.reboot))){ // String boot = new Date(state.getLong(State.values.javastartup.name())).toString(); Util.log("regular reboot", this); application.driverCallServer(PlayerCommands.reboot, null); } // show AP mode enabled, if no driver if(state.equals(values.ssid, AP)){ if( ! state.getBoolean(State.values.autodocking) && ! state.exists(State.values.driver)) { application.driverCallServer(PlayerCommands.strobeflash, "on 10 10"); } } // notify clients of power errors if (state.exists(State.values.powererror.toString())) { if (lastpowererrornotify == null) notifyPowerError(); else if ( ! lastpowererrornotify.equals(state.get(State.values.powererror))) notifyPowerError(); } // safety: check for force_undock command from battery firmware if (state.getBoolean(State.values.forceundock) && state.equals(values.dockstatus, AutoDock.DOCKED)){ Util.log("System WatchDog, force undock", this); PowerLogger.append("System WatchDog, force undock", this); forceundock(); } // deal with abandoned logins, driver still connected if (state.exists(State.values.driver.toString()) && System.currentTimeMillis() - state.getLong(State.values.lastusercommand) > ABANDONDEDLOGIN ) { application.driverCallServer(PlayerCommands.disconnectotherconnections, null); application.driverCallServer(PlayerCommands.driverexit, null); if (state.get(State.values.dockstatus).equals(AutoDock.UNDOCKED) && settings.getBoolean(GUISettings.redock)) { Util.log("abandoned logins, driver still connected, attempt redock", this); PowerLogger.append("abandoned logins, driver still connected, attempt redock", this); redock(NOFORWARD); } } // deal with abandonded, undocked, low battery, not redocking, not already attempted redock if (state.get(values.batterylife).matches(".*\\d+.*")) { // make sure batterylife != 'TIMEOUT', throws error if (!state.exists(State.values.driver) && System.currentTimeMillis() - state.getLong(State.values.lastusercommand) > ABANDONDEDLOGIN && redocking == false && Integer.parseInt(state.get(State.values.batterylife).replaceAll("[^0-9]", "")) < ArduinoPower.LOWBATTPERCENTAGE && state.get(State.values.dockstatus).equals(AutoDock.UNDOCKED) && settings.getBoolean(GUISettings.redock) ) { if (!lowbattredock) { lowbattredock = true; lowbattredockstart = System.currentTimeMillis(); Util.log("abandonded, undocked, low battery, not redocking", this); PowerLogger.append("abandonded, undocked, low battery, not redocking", this); redock(NOFORWARD); } else { // power down if redock failed, helps with battery death by parasitics if (System.currentTimeMillis() - lowbattredockstart > AUTODOCKTIMEOUT) { Util.log("abandonded, undocked, low battery, redock failed", this); application.driverCallServer(PlayerCommands.powershutdown, null); } } } else if (state.get(values.dockstatus).equals(AutoDock.DOCKED)) lowbattredock = false; } // navigation running, route running, undocked, low battery, next waypoint != dock, no driver, drive to dock if (state.get(values.batterylife).matches(".*\\d+.*")) { // make sure batterylife != 'TIMEOUT', throws error if (!state.exists(State.values.driver) && Integer.parseInt(state.get(State.values.batterylife).replaceAll("[^0-9]", "")) < ArduinoPower.LOWBATTPERCENTAGE && state.get(State.values.dockstatus).equals(AutoDock.UNDOCKED) && settings.getBoolean(GUISettings.redock) && state.equals(State.values.navsystemstatus, Ros.navsystemstate.running) && state.exists(State.values.navigationroute) && !state.equals(State.values.roswaypoint, Navigation.DOCK) ) { Util.log("route running, undocked, low battery, no driver", this); PowerLogger.append("route running, undocked, low battery, no driver", this); application.driverCallServer(PlayerCommands.gotodock, null); } } // check cpu useage int cpuNow = Util.getCPU(); if(cpuNow > 60) Util.log("cpu: "+cpuNow, this); state.set(values.cpu, Util.getCPU()); // notify driver if any system messages if (state.exists(values.guinotify)) { if (state.exists(State.values.driver.toString())) { // String str="<span style='font-size: 12px'>("+Util.getDateStamp()+")</span><br><br>"; String str = state.get(values.guinotify); str += "<br><br><a href='javascript: guinotify("ok");'>"; str += "<span class='cancelbox'>✔</span> OK</a>     "; application.sendplayerfunction("guinotify", str ); } } } } private void notifyPowerError() { PowerLogger.append("notifyPowerError()", this); lastpowererrornotify = state.get(State.values.powererror); boolean warningonly = true; boolean resetrequired = false; String longerror = ""; boolean commlost = false; String code[] = lastpowererrornotify.split(","); for (int i=0; i < code.length; i++) { int c = Integer.parseInt(code[i]); if (c > ArduinoPower.WARNING_ONLY_BELOW ) { warningonly = false; longerror += "<span style='color: red'>"; } if (c != 0) longerror += ArduinoPower.pwrerr.get(c).replaceFirst("ERROR_", "") + "<br>"; if (!warningonly) longerror += "</span>"; if (c > ArduinoPower.RESET_REQUIRED_ABOVE && c != ArduinoPower.COMM_LOST) resetrequired = true; if (c == ArduinoPower.COMM_LOST) commlost = true; if (c > ArduinoPower.FORCE_UNDOCK_ABOVE) state.set(State.values.forceundock, true); } // cancel any navigation routes (TODO: and other autonomous functions ??) if (!warningonly) application.driverCallServer(PlayerCommands.cancelroute, null); if (state.exists(State.values.driver.toString())) { String msg = ""; if (warningonly && !commlost) msg += "POWER WARNING<br>History:<br><br>"; else msg += "POWER SYSTEM ERROR<br>History:<br><br>"; msg += longerror + "<br>"; if (warningonly && !commlost) msg += "OK to clear warnings?"; else if (warningonly && commlost) msg += "Try: restart application, reboot, check USB cable"; // commlost else msg += "Please UNPLUG BATTERY and consult technical support"; if (resetrequired) msg += "<br><br>Charging is limited or disabled"; if (warningonly && resetrequired) msg += "<br>until this message is cleared"; msg += "<br><br>"; msg += "<a href='javascript: acknowledgeerror("true");'>"; msg += "<span class='cancelbox'>✔</span> OK</a>     "; if (warningonly) { powererrorwarningonly = true; msg += "<a href='javascript: acknowledgeerror("cancel");'>"; msg += "<span class='cancelbox'><b>X</b></span> IGNORE</a><br>"; } else powererrorwarningonly = false; application.sendplayerfunction("acknowledgeerror", msg); } else if (!warningonly) callForHelp("Oculus Prime POWER ERROR","Please UNPLUG BATTERY and consult technical support"); else if (warningonly && commlost) callForHelp("Oculus Prime POWER ERROR","Power PCB Communication Lost"); } public void redock(String str) { if (redocking) return; if (str == null) str = ""; // TODO: force nav shutdown? if (settings.getBoolean(GUISettings.navigation) && !str.equals(NOFORWARD) ) { if ( !state.get(values.navsystemstatus).equals(Ros.navsystemstate.stopping.toString()) && !state.get(values.navsystemstatus).equals(Ros.navsystemstate.stopped.toString())) { Util.log("warning: redock skipped, navigation running", this); return; } if (state.exists(values.nextroutetime)) { if (state.getLong(values.nextroutetime.toString()) - System.currentTimeMillis() < Util.TWO_MINUTES) { Util.log("warning: redock skipped, route starting soon", this); return; } } } final String option = str; new Thread(new Runnable() { public void run() { redocking = true; long start; String subject = "Oculus Prime Unable to Dock"; String body = "Un-docked, battery draining"; // warn // application.driverCallServer(PlayerCommands.speech, "warning, moving, in, 5, seconds"); application.driverCallServer(PlayerCommands.strobeflash, "on 500 50"); Util.delay(1000); // including 500 delay application.driverCallServer(PlayerCommands.strobeflash, "on 500 50"); Util.delay(1000); // including 500 delay application.driverCallServer(PlayerCommands.strobeflash, "on 500 50"); Util.delay(6000); // allow reaction if (!redocking) return; // camera on application.driverCallServer(PlayerCommands.streamsettingsset, Application.camquality.high.toString()); application.driverCallServer(PlayerCommands.publish, Application.streamstate.camera.toString()); // go forward momentarily application.driverCallServer(PlayerCommands.speed, ArduinoPrime.speeds.med.toString()); state.set(State.values.motionenabled, true); // state.set(State.values.controlsinverted, false); ArduinoPrime.checkIfInverted(); if (!option.equals(NOFORWARD)) { application.driverCallServer(PlayerCommands.move, ArduinoPrime.direction.forward.toString()); Util.delay(800); application.driverCallServer(PlayerCommands.move, ArduinoPrime.direction.stop.toString()); } if (!redocking) return; // reverse tilt application.driverCallServer(PlayerCommands.cameracommand, ArduinoPrime.cameramove.reverse.toString()); // docklight on, spotlight off application.driverCallServer(PlayerCommands.floodlight, Integer.toString(AutoDock.FLLOW)); application.driverCallServer(PlayerCommands.spotlight, "0"); Util.delay(2000); // allow for motion stop and light to settle // rotate and dock detect int rot = 0; String res = ""; while (true) { if (!redocking) return; if (rot == 32) { // failure give up callForHelp(subject, body); application.driverCallServer(PlayerCommands.publish, Application.streamstate.stop.toString()); application.driverCallServer(PlayerCommands.floodlight, "0"); redocking = false; return; } if (rot == 16) { res = AutoDock.HIGHRES; application.driverCallServer(PlayerCommands.floodlight, Integer.toString(AutoDock.FLHIGH)); } waitForCpu(); application.driverCallServer(PlayerCommands.dockgrab, res); Util.delay(10); // thread safe start = System.currentTimeMillis(); while (!state.exists(State.values.dockfound.toString()) && System.currentTimeMillis() - start < 10000) { Util.delay(10); } // wait if (state.getBoolean(State.values.dockfound)) break; // great, onwards else { // rotate a bit application.driverCallServer(PlayerCommands.left, "25"); Util.delay(10); // thread safe start = System.currentTimeMillis(); while(!state.get(State.values.direction).equals(ArduinoPrime.direction.stop.toString()) && System.currentTimeMillis() - start < 5000) { Util.delay(10); } // wait Util.delay(ArduinoPrime.TURNING_STOP_DELAY); } rot ++; } if (!redocking) return; application.driverCallServer(PlayerCommands.autodock, autodockmodes.go.toString()); // attempt dock // wait while autodocking does its thing start = System.currentTimeMillis(); while (state.getBoolean(State.values.autodocking) && System.currentTimeMillis() - start < AUTODOCKTIMEOUT) Util.delay(100); if (!redocking) return; application.driverCallServer(PlayerCommands.publish, Application.streamstate.stop.toString()); application.driverCallServer(PlayerCommands.floodlight, "0"); if (!state.get(State.values.dockstatus).equals(AutoDock.DOCKED)) { if (!state.exists(State.values.driver.toString())) { if (state.exists(values.navigationroute)) application.driverCallServer(PlayerCommands.cancelroute, null); callForHelp(subject, body); } } redocking = false; } }).start(); } private void callForHelp(String subject, String body) { application.driverCallServer(PlayerCommands.messageclients, body); Util.log("callForHelp() " + subject + " " + body, this); PowerLogger.append("callForHelp() " + subject + " " + body, this); if (!settings.getBoolean(ManualSettings.alertsenabled)) return; body += "\nhttp://"+state.get(State.values.externaladdress)+":"+ settings.readRed5Setting("http.port")+"/oculusPrime/"; String emailto = settings.readSetting(GUISettings.email_to_address); if (!emailto.equals(Settings.DISABLED)) application.driverCallServer(PlayerCommands.email, emailto+" ["+subject+"] "+body); application.driverCallServer(PlayerCommands.rssadd, "[" + subject + "] " + body); } private void forceundock() { application.driverCallServer(PlayerCommands.messageclients, "Power ERROR, Forced Un-Dock"); // go forward momentarily application.driverCallServer(PlayerCommands.speed, ArduinoPrime.speeds.med.toString()); state.set(State.values.motionenabled, true); // state.set(State.values.controlsinverted, false); ArduinoPrime.checkIfInverted(); application.driverCallServer(PlayerCommands.move, ArduinoPrime.direction.forward.toString()); Util.delay(800); application.driverCallServer(PlayerCommands.move, ArduinoPrime.direction.stop.toString()); // String subject = "Oculus Prime Power ERROR, Forced Un-Dock"; // String body = "Oculus Prime Power ERROR, Forced Un-Dock"; // callForHelp(subject, body); } public void waitForCpuThread() { new Thread(new Runnable() { public void run() { waitForCpu(); } }).start(); } public static void waitForCpu() { waitForCpu(60, 20000); } public static void waitForCpu(long timeout) { waitForCpu(60, timeout); } public static void waitForCpu(int threshold, long timeout) { State state = State.getReference(); if (state.getBoolean(values.waitingforcpu)) return; state.set(values.waitingforcpu, true); long start = System.currentTimeMillis(); int cpu = 0; while (System.currentTimeMillis() - start < timeout) { cpu = Util.getCPU(); if (cpu < threshold) { // do it again to be sure cpu = Util.getCPU(); if (cpu <threshold) { Util.debug("SystemWatchdog.waitForCpu() cleared, cpu @ " + cpu + "% after " + (System.currentTimeMillis() - start) + "ms", null); state.set(values.waitingforcpu, false); return; } } Util.delay(1000); } state.set(values.waitingforcpu, false); Util.log("SystemWatchdog.waitForCpu() warning, timed out " + cpu + "% after " + timeout + "ms", null); } }