private void checkRailPosition(
      List<RailState> railStates, SystemState state, List<Integer> nodes) {
    for (RailState railState : railStates) {
      Rail rail = railState.getRail();
      double setpoint = railState.getRailWidget().getSetpoint();
      double position = railState.getRailPosition();
      double home = rail.getHomeDistance();
      double tol = rail.getTolPlus();

      switch (railState.getRailFlag()) {
        case NOT_HOMED:
          yellowFlag = true;
          railState.setColor(GRAY);
          break;
        case HOMING:
          if (position > home - tol && position < home + tol)
            railState.setRailFlag(Equip.RailFlag.HOMED);
          yellowFlag = true;
          railState.setColor(WHITE);
          break;
        case HOMED:
          yellowFlag = true;
          railState.setColor(WHITE);
          break;
        case SEEKING:
          if (position > setpoint - tol && position < setpoint + tol)
            railState.setRailFlag(Equip.RailFlag.ACTIVE);
          yellowFlag = true;
          railState.setColor(WHITE);
          break;
        case ACTIVE:
          railState.setColor(GREEN);
          if (position < setpoint - tol || position > setpoint + tol) {
            yellowFlag = true;
            if (railState.getColor().equals(GREEN)) {
              railState.setColor(YELLOW);
              if (state.equals(SystemState.SYSTEM_STATE_RUN)) {
                CanComm.sendSystemState(
                    SystemState.SYSTEM_STATE_RUN_PAUSE, Can.OpId.OPER_SET.ordinal(), nodes);
                CanComm.setLightTower(Equip.LightTowerState.WARNING.getTower());
                log.error("Rail out of tolerance (warning) band...");
              }
            }
          }
          break;
        default:
          break;
      }
    }
  }
  private void checkBeltSpeed(List<BeltState> beltStates, SystemState state, List<Integer> nodes) {
    for (BeltState beltState : beltStates) {
      // determine upper and lower thresholds for alarms and warnings
      double setpoint = beltState.getBeltUnit().getSetpoint();
      double processValue = beltState.getBeltSpeed();
      double upperLimit = setpoint + beltState.getBeltUnit().getHiWarn();
      double lowerLimit = setpoint - beltState.getBeltUnit().getLoWarn();

      if (setpoint == 0.0) {
        yellowFlag = true;
        beltState.setColor(WHITE);
        continue;
      }

      switch (beltState.getBeltFlag()) {
        case OFF:
          yellowFlag = true;
          beltState.setColor(GRAY);
          break;
        case SET:
          yellowFlag = true;
          beltState.setColor(WHITE);
          break;
        case SEEKING:
          yellowFlag = true;
          if (processValue > lowerLimit && processValue < upperLimit)
            beltState.setBeltFlag(GenFlag.ACTIVE);
          beltState.setColor(WHITE);
          break;
        case ACTIVE:
          beltState.setColor(GREEN);
          if (processValue < lowerLimit || processValue > upperLimit) {
            yellowFlag = true;
            if (beltState.getColor().equals(GREEN)) {
              beltState.setColor(YELLOW);
              if (state.equals(SystemState.SYSTEM_STATE_RUN)) {
                CanComm.sendSystemState(
                    SystemState.SYSTEM_STATE_RUN_PAUSE, Can.OpId.OPER_SET.ordinal(), nodes);
                CanComm.setLightTower(Equip.LightTowerState.WARNING.getTower());
                log.error("beltSpeed out of warning band: " + processValue);
              }
            }
          }
          break;
        default:
          break;
      }
    }
  }
  @Override
  public String call() {

    log.info("OvenCheckThread started...");
    List<Integer> nodes = OvenState.getInstance().getNodes();
    ParamManager paramManager = new ParamManager();
    Config config = OvenState.getInstance().getConfig();

    // begin main thread loop to check oven com.heller.oven variables
    while (OvenState.getInstance().getOvenBoolVar("utility")) {

      // General Oven parameters needed in thread
      IoModule ioModule = OvenState.getInstance().getIoModule();
      double heaterOutputLimit = ioModule.getGeneralTwo().getHeaterOutputLimit();
      double cooldownTemp = ioModule.getGeneralTwo().getCooldown();

      // pause thread for specified amount
      try {
        Thread.sleep(OVEN_UPDATE_DELAY);
      } catch (InterruptedException e) {
        log.error("InterruptedException " + e.getMessage());
      }

      // get references from singleton that might change upon iteration
      List<ZoneState> zones = OvenState.getInstance().getZones();
      List<BeltState> beltStates = OvenState.getInstance().getBeltStates();
      List<RailState> railStates = OvenState.getInstance().getRailStates();
      SystemState state = OvenState.getInstance().getOvenSystemState();
      RecipeFlag recipeFlag = OvenState.getInstance().getRecipeFlag();

      // if recipe never loaded, then skip while loop
      if (OvenState.getInstance().getRecipeFlag() == RecipeFlag.NEVER_LOADED) continue;

      // If system is in COOLDOWN_DELAYED, test for boards in oven
      // If/when no boards in oven - start COOLDOWN and change System State
      if (state == SystemState.SYSTEM_STATE_RUN_COOLDOWN_DELAYED) {
        // if no boards in oven, change state to RUN_COOLDOWN
        if (!OvenManager.getInstance().isBoardsInOven()) {
          CanComm.sendSystemState(
              SystemState.SYSTEM_STATE_RUN_COOLDOWN, Can.OpId.OPER_SET.ordinal(), nodes);
        }

      } else if (state == SystemState.SYSTEM_STATE_RUN_COOLDOWN) {

        startCooldown(
            ioModule, paramManager, zones, beltStates, railStates, cooldownTemp, config, nodes);

      } else if (state.ordinal() >= LOWEST_RUN_STATE && state.ordinal() <= HIGHEST_RUN_STATE) {

        if (recipeFlag == RecipeFlag.RECIPE_UNLOADABLE) continue;
        if (recipeFlag == RecipeFlag.RECIPE_LOADING) {
          // white out heat zones on GUI
          for (ZoneState zone : zones) {
            zone.setTopColor(WHITE);
            if (zone.isHeatZone()) zone.setBottomColor(WHITE);
          }
          // white out belts on GUI
          for (BeltState beltState : beltStates) {
            beltState.setColor(WHITE);
          }
          // gray out rails on GUI
          for (RailState railState : railStates) {
            railState.setColor(WHITE);
          }
          // skip rest of loop until recipe is loaded.
          continue;
        }

        yellowFlag = false;
        // check rail position, set sysColor yellow if not ACTIVE within warning band
        checkRailPosition(railStates, state, nodes);
        // check belt speed, set sysColor yellow if belt speed out of warning band
        checkBeltSpeed(beltStates, state, nodes);
        // iterate through heaters and determine heater colors according to alarm/warning bands
        checkHeaterTemps(zones, heaterOutputLimit, state, nodes);
        // if any zone is red, Cooldown should have been called - skip rest of while loop
        if (redZone) {
          continue;
        }

        if (!yellowFlag) {
          // set light tower green and change to SYSTEM STATE RUN if in RUN PAUSE (with no warnings)
          // or RUN START
          if (state.equals(SystemState.SYSTEM_STATE_RUN_STARTUP)) {
            CanComm.sendSystemState(
                SystemState.SYSTEM_STATE_RUN, Can.OpId.OPER_SET.ordinal(), nodes);
            OvenState.getInstance().setRecipeFlag(RecipeFlag.RECIPE_RUNNING);
            CanComm.setLightTower(Equip.LightTowerState.EMPTY_OVEN.getTower());
          } else if (state.equals(SystemState.SYSTEM_STATE_RUN_PAUSE)) {
            if (OvenManager.getInstance().isAlarms() == -1
                || OvenManager.getInstance().isAlarms() == 2) {
              OvenManager.getInstance().clearAlarmState(true);
              CanComm.sendSystemState(
                  SystemState.SYSTEM_STATE_RUN, Can.OpId.OPER_SET.ordinal(), nodes);
              OvenState.getInstance().setRecipeFlag(RecipeFlag.RECIPE_RUNNING);
              CanComm.setLightTower(LightTowerState.PRODUCTS_IN_OVEN.getTower());
            }
          }
        }

        // pause thread for specified amount
        try {
          Thread.sleep(OVEN_UPDATE_DELAY);
        } catch (InterruptedException e) {
          log.error("InterruptedException " + e.getMessage());
        }
      }
    }
    log.info("OvenCheckThread closed...");
    return "thread_closed";
  }
  private void checkHeaterTemps(
      List<ZoneState> zones, double heaterOutputLimit, SystemState state, List<Integer> nodes) {
    for (ZoneState zone : zones) {
      Unit unit = zone.getTopUnit();
      double setpoint = unit.getSetpoint();
      double lowerLimit = setpoint - unit.getLoWarn();
      double upperLimit = setpoint + unit.getHiWarn();
      double upperAlarm = setpoint + unit.getHiAlarm();
      double lowerAlarm = setpoint - unit.getLoAlarm();
      // test action with heaterFlag
      switch (zone.getTopHeaterFlag()) {
        case OFF:
          zone.setTopColor(GRAY);
          yellowFlag = true;
          break;
        case SET:
          zone.setTopColor(WHITE);
          yellowFlag = true;
          break;
        case POWER:
          zone.setTopColor(WHITE);
          yellowFlag = true;
          if (zone.getTopPercentOP() < heaterOutputLimit) zone.setTopHeaterFlag(HeaterFlag.HEATING);
          break;
        case HEATING:
          zone.setTopColor(WHITE);
          yellowFlag = true;
          if (zone.getTopTemp() > lowerLimit && zone.getTopTemp() < upperLimit) {
            zone.setTopHeaterFlag(HeaterFlag.ACTIVE);
          }
          break;
        case ACTIVE:
          if (zone.getTopTemp() < lowerAlarm || zone.getTopTemp() > upperAlarm) {
            redZone = true;
            if (!zone.getTopColor().equals(RED)) {
              zone.setTopColor(RED);
              if (state.equals(SystemState.SYSTEM_STATE_RUN_STARTUP)
                  || state.equals(SystemState.SYSTEM_STATE_RUN)) {
                OvenManager.getInstance().startCooldown(LightTowerState.ALARM_COOLDOWN.getTower());
                log.error(
                    "Zone: "
                        + zone.getId()
                        + " top heater out of alarm band: "
                        + zone.getTopTemp());
              }
            }
          } else if (zone.getTopTemp() < lowerLimit || zone.getTopTemp() > upperLimit) {
            yellowFlag = true;
            if (zone.getTopColor().equals(GREEN)) {
              zone.setTopColor(YELLOW);
              if (state.equals(SystemState.SYSTEM_STATE_RUN)) {
                CanComm.sendSystemState(
                    SystemState.SYSTEM_STATE_RUN_PAUSE, Can.OpId.OPER_SET.ordinal(), nodes);
                CanComm.setLightTower(Equip.LightTowerState.WARNING.getTower());
                log.error(
                    "Zone: "
                        + zone.getId()
                        + " top heater out of warning band: "
                        + zone.getTopTemp());
              }
            }
          } else {
            zone.setTopColor(GREEN);
          }
          break;
        default:
          break;
      }

      if (zone.isHeatZone()) {
        unit = zone.getBottomUnit();
        setpoint = unit.getSetpoint();
        upperLimit = setpoint + unit.getHiWarn();
        lowerLimit = setpoint - unit.getLoWarn();
        upperAlarm = setpoint + unit.getHiAlarm();
        lowerAlarm = setpoint - unit.getLoAlarm();
        // test action with heaterFlag
        switch (zone.getBottomHeaterFlag()) {
          case OFF:
            zone.setBottomColor(GRAY);
            yellowFlag = true;
            break;
          case SET:
            zone.setBottomColor(WHITE);
            yellowFlag = true;
            break;
          case POWER:
            zone.setBottomColor(WHITE);
            yellowFlag = true;
            if (zone.getBottomPercentOP() < heaterOutputLimit)
              zone.setBottomHeaterFlag(HeaterFlag.HEATING);
            break;
          case HEATING:
            zone.setBottomColor(WHITE);
            yellowFlag = true;
            if (zone.getBottomTemp() > lowerLimit && zone.getBottomTemp() < upperLimit) {
              zone.setBottomHeaterFlag(HeaterFlag.ACTIVE);
            }
            break;
          case ACTIVE:
            if (zone.getBottomTemp() < lowerAlarm || zone.getBottomTemp() > upperAlarm) {
              redZone = true;
              if (!zone.getBottomColor().equals(RED)) {
                zone.setBottomColor(RED);
                if (state.equals(SystemState.SYSTEM_STATE_RUN_STARTUP)
                    || state.equals(SystemState.SYSTEM_STATE_RUN)) {
                  OvenManager.getInstance()
                      .startCooldown(LightTowerState.ALARM_COOLDOWN.getTower());
                  log.error(
                      "Zone: "
                          + zone.getId()
                          + " bottom heater out of alarm band: "
                          + zone.getBottomTemp());
                }
              }
            } else if (zone.getBottomTemp() < lowerLimit || zone.getBottomTemp() > upperLimit) {
              yellowFlag = true;
              if (zone.getBottomColor().equals(GREEN)) {
                zone.setBottomColor(YELLOW);
                if (state.equals(SystemState.SYSTEM_STATE_RUN)) {
                  CanComm.sendSystemState(
                      SystemState.SYSTEM_STATE_RUN_PAUSE, Can.OpId.OPER_SET.ordinal(), nodes);
                  CanComm.setLightTower(Equip.LightTowerState.WARNING.getTower());
                  log.error(
                      "Zone: "
                          + zone.getId()
                          + " bottom heater out of warning band: "
                          + zone.getBottomTemp());
                }
              }
            } else {
              zone.setBottomColor(GREEN);
            }
            break;
          default:
            break;
        }
      }
    }
  }
  private void startCooldown(
      IoModule ioModule,
      ParamManager paramManager,
      List<ZoneState> zones,
      List<BeltState> beltStates,
      List<RailState> railStates,
      double cooldownTemp,
      Config config,
      List<Integer> nodes) {
    if (ioModule.getOutput(Can.OutputIO.IOM_HEAT_CONTACTOR) == 1) {
      // Turn off heat contactor and disable alarms
      paramManager.setAlarms(ALARM_DISABLED);
      CanComm.sendCmdSetpoint(
          Can.BoardType.BOARD_TYPE_IOM.getStartNum(),
          0,
          Can.CmdSetpoint.SETPOINT_HEAT_CONTACTOR,
          0);
      log.info("Delayed Cooldown -- Heat Contactor Off...");
    }

    // gray out heat zones on GUI
    for (ZoneState zone : zones) {
      zone.setTopHeaterFlag(Equip.HeaterFlag.OFF);
      paramManager.zeroHeaterSetpoint(true, zone);
      zone.setTopColor(GRAY);
      if (zone.isHeatZone()) {
        zone.setBottomHeaterFlag(Equip.HeaterFlag.OFF);
        paramManager.zeroHeaterSetpoint(false, zone);
        zone.setBottomColor(GRAY);
      }
    }
    // gray out belts on GUI
    for (BeltState beltState : beltStates) {
      beltState.setColor(GRAY);
    }
    // gray out rails on GUI
    for (RailState railState : railStates) {
      railState.setColor(GRAY);
    }

    // test if zone heaters are above cooldown temperature
    boolean belowTemp = true;
    for (ZoneState zone : zones) {
      if (zone.getTopTemp() > cooldownTemp) {
        belowTemp = false;
        break;
      }
      if (zone.getBottomTemp() > cooldownTemp && zone.isHeatZone()) {
        belowTemp = false;
        break;
      }
    }
    if (belowTemp) {
      // complete cooldown after all zones less than cooldown temp
      if (OvenState.getInstance().getLightTower() == LightTowerState.COOLDOWN.getTower()) {
        CanComm.setLightTower(Equip.LightTowerState.TOWER_OFF.getTower());
      }
      // set all belts to speed of zero
      for (int i = 0; i < config.getNumBelts(); i++) {
        BeltState beltState = beltStates.get(i);
        paramManager.zeroBeltSetpoint(beltState);
      }
      // turn off fan contactor
      CanComm.sendCmdSetpoint(
          Can.BoardType.BOARD_TYPE_IOM.getStartNum(), 0, Can.CmdSetpoint.SETPOINT_FAN_CONTACTOR, 0);
      CanComm.sendSystemState(
          SystemState.SYSTEM_STATE_OVEN_OFF, Can.OpId.OPER_SET.ordinal(), nodes);
      log.info("Cooldown complete - Fan Contactor Off...");
    }
  }