001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.wpilibj;
006
007import edu.wpi.first.hal.ControlWord;
008import edu.wpi.first.hal.DriverStationJNI;
009import edu.wpi.first.util.WPIUtilJNI;
010import java.util.LinkedHashSet;
011import java.util.Set;
012
013/**
014 * The Motor Safety feature acts as a watchdog timer for an individual motor. It operates by
015 * maintaining a timer that tracks how long it has been since the feed() method has been called for
016 * that actuator. Code in the Driver Station class initiates a comparison of these timers to the
017 * timeout values for any actuator with safety enabled every 5 received packets (100ms nominal).
018 *
019 * <p>The subclass should call feed() whenever the motor value is updated.
020 */
021public abstract class MotorSafety {
022  private static final double kDefaultSafetyExpiration = 0.1;
023
024  private double m_expiration = kDefaultSafetyExpiration;
025  private boolean m_enabled;
026  private double m_stopTime = Timer.getFPGATimestamp();
027  private final Object m_thisMutex = new Object();
028  private static final Set<MotorSafety> m_instanceList = new LinkedHashSet<>();
029  private static final Object m_listMutex = new Object();
030  private static Thread m_safetyThread;
031
032  @SuppressWarnings("PMD.AssignmentInOperand")
033  private static void threadMain() {
034    int event = WPIUtilJNI.createEvent(false, false);
035    DriverStationJNI.provideNewDataEventHandle(event);
036    ControlWord controlWord = new ControlWord();
037
038    int safetyCounter = 0;
039    while (true) {
040      boolean timedOut;
041      try {
042        timedOut = WPIUtilJNI.waitForObjectTimeout(event, 0.1);
043      } catch (InterruptedException e) {
044        DriverStationJNI.removeNewDataEventHandle(event);
045        WPIUtilJNI.destroyEvent(event);
046        Thread.currentThread().interrupt();
047        return;
048      }
049      if (!timedOut) {
050        DriverStationJNI.getControlWord(controlWord);
051        if (!(controlWord.getEnabled() && controlWord.getDSAttached())) {
052          safetyCounter = 0;
053        }
054        if (++safetyCounter >= 4) {
055          checkMotors();
056          safetyCounter = 0;
057        }
058      } else {
059        safetyCounter = 0;
060      }
061    }
062  }
063
064  /** MotorSafety constructor. */
065  public MotorSafety() {
066    synchronized (m_listMutex) {
067      m_instanceList.add(this);
068      if (m_safetyThread == null) {
069        m_safetyThread = new Thread(() -> threadMain(), "MotorSafety Thread");
070        m_safetyThread.setDaemon(true);
071        m_safetyThread.start();
072      }
073    }
074  }
075
076  /**
077   * Feed the motor safety object.
078   *
079   * <p>Resets the timer on this object that is used to do the timeouts.
080   */
081  public void feed() {
082    synchronized (m_thisMutex) {
083      m_stopTime = Timer.getFPGATimestamp() + m_expiration;
084    }
085  }
086
087  /**
088   * Set the expiration time for the corresponding motor safety object.
089   *
090   * @param expirationTime The timeout value in seconds.
091   */
092  public void setExpiration(double expirationTime) {
093    synchronized (m_thisMutex) {
094      m_expiration = expirationTime;
095    }
096  }
097
098  /**
099   * Retrieve the timeout value for the corresponding motor safety object.
100   *
101   * @return the timeout value in seconds.
102   */
103  public double getExpiration() {
104    synchronized (m_thisMutex) {
105      return m_expiration;
106    }
107  }
108
109  /**
110   * Determine of the motor is still operating or has timed out.
111   *
112   * @return a true value if the motor is still operating normally and hasn't timed out.
113   */
114  public boolean isAlive() {
115    synchronized (m_thisMutex) {
116      return !m_enabled || m_stopTime > Timer.getFPGATimestamp();
117    }
118  }
119
120  /**
121   * Check if this motor has exceeded its timeout. This method is called periodically to determine
122   * if this motor has exceeded its timeout value. If it has, the stop method is called, and the
123   * motor is shut down until its value is updated again.
124   */
125  public void check() {
126    boolean enabled;
127    double stopTime;
128
129    synchronized (m_thisMutex) {
130      enabled = m_enabled;
131      stopTime = m_stopTime;
132    }
133
134    if (!enabled || RobotState.isDisabled() || RobotState.isTest()) {
135      return;
136    }
137
138    if (stopTime < Timer.getFPGATimestamp()) {
139      DriverStation.reportError(
140          getDescription()
141              + "... Output not updated often enough. See https://docs.wpilib.org/motorsafety for more information.",
142          false);
143
144      stopMotor();
145    }
146  }
147
148  /**
149   * Enable/disable motor safety for this device.
150   *
151   * <p>Turn on and off the motor safety option for this PWM object.
152   *
153   * @param enabled True if motor safety is enforced for this object
154   */
155  public void setSafetyEnabled(boolean enabled) {
156    synchronized (m_thisMutex) {
157      m_enabled = enabled;
158    }
159  }
160
161  /**
162   * Return the state of the motor safety enabled flag.
163   *
164   * <p>Return if the motor safety is currently enabled for this device.
165   *
166   * @return True if motor safety is enforced for this device
167   */
168  public boolean isSafetyEnabled() {
169    synchronized (m_thisMutex) {
170      return m_enabled;
171    }
172  }
173
174  /**
175   * Check the motors to see if any have timed out.
176   *
177   * <p>This static method is called periodically to poll all the motors and stop any that have
178   * timed out.
179   */
180  public static void checkMotors() {
181    synchronized (m_listMutex) {
182      for (MotorSafety elem : m_instanceList) {
183        elem.check();
184      }
185    }
186  }
187
188  public abstract void stopMotor();
189
190  public abstract String getDescription();
191}