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