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}