001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) 2008-2018 FIRST. All Rights Reserved. */ 003/* Open Source Software - may be modified and shared by FRC teams. The code */ 004/* must be accompanied by the FIRST BSD license file in the root directory of */ 005/* the project. */ 006/*----------------------------------------------------------------------------*/ 007 008package edu.wpi.first.wpilibj; 009 010import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType; 011import edu.wpi.first.wpilibj.hal.HAL; 012import edu.wpi.first.wpilibj.hal.RelayJNI; 013import edu.wpi.first.wpilibj.smartdashboard.SendableBuilder; 014 015import java.util.Arrays; 016import java.util.Optional; 017 018import static java.util.Objects.requireNonNull; 019 020/** 021 * Class for VEX Robotics Spike style relay outputs. Relays are intended to be connected to Spikes 022 * or similar relays. The relay channels controls a pair of channels that are either both off, one 023 * on, the other on, or both on. This translates into two Spike outputs at 0v, one at 12v and one 024 * at 0v, one at 0v and the other at 12v, or two Spike outputs at 12V. This allows off, full 025 * forward, or full reverse control of motors without variable speed. It also allows the two 026 * channels (forward and reverse) to be used independently for something that does not care about 027 * voltage polarity (like a solenoid). 028 */ 029public class Relay extends SendableBase implements MotorSafety, Sendable { 030 private MotorSafetyHelper m_safetyHelper; 031 032 /** 033 * This class represents errors in trying to set relay values contradictory to the direction to 034 * which the relay is set. 035 */ 036 public class InvalidValueException extends RuntimeException { 037 /** 038 * Create a new exception with the given message. 039 * 040 * @param message the message to pass with the exception 041 */ 042 public InvalidValueException(String message) { 043 super(message); 044 } 045 } 046 047 /** 048 * The state to drive a Relay to. 049 */ 050 public enum Value { 051 kOff("Off"), 052 kOn("On"), 053 kForward("Forward"), 054 kReverse("Reverse"); 055 056 private final String m_prettyValue; 057 058 Value(String prettyValue) { 059 m_prettyValue = prettyValue; 060 } 061 062 public String getPrettyValue() { 063 return m_prettyValue; 064 } 065 066 public static Optional<Value> getValueOf(String value) { 067 return Arrays.stream(Value.values()).filter(v -> v.m_prettyValue.equals(value)).findFirst(); 068 } 069 } 070 071 /** 072 * The Direction(s) that a relay is configured to operate in. 073 */ 074 public enum Direction { 075 /** 076 * direction: both directions are valid. 077 */ 078 079 kBoth, 080 /** 081 * direction: Only forward is valid. 082 */ 083 kForward, 084 /** 085 * direction: only reverse is valid. 086 */ 087 kReverse 088 } 089 090 private final int m_channel; 091 092 private int m_forwardHandle = 0; 093 private int m_reverseHandle = 0; 094 095 private Direction m_direction; 096 097 /** 098 * Common relay initialization method. This code is common to all Relay constructors and 099 * initializes the relay and reserves all resources that need to be locked. Initially the relay is 100 * set to both lines at 0v. 101 */ 102 private void initRelay() { 103 SensorBase.checkRelayChannel(m_channel); 104 105 int portHandle = RelayJNI.getPort((byte) m_channel); 106 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 107 m_forwardHandle = RelayJNI.initializeRelayPort(portHandle, true); 108 HAL.report(tResourceType.kResourceType_Relay, m_channel); 109 } 110 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 111 m_reverseHandle = RelayJNI.initializeRelayPort(portHandle, false); 112 HAL.report(tResourceType.kResourceType_Relay, m_channel + 128); 113 } 114 115 m_safetyHelper = new MotorSafetyHelper(this); 116 m_safetyHelper.setSafetyEnabled(false); 117 118 setName("Relay", m_channel); 119 } 120 121 /** 122 * Relay constructor given a channel. 123 * 124 * @param channel The channel number for this relay (0 - 3). 125 * @param direction The direction that the Relay object will control. 126 */ 127 public Relay(final int channel, Direction direction) { 128 m_channel = channel; 129 m_direction = requireNonNull(direction, "Null Direction was given"); 130 initRelay(); 131 set(Value.kOff); 132 } 133 134 /** 135 * Relay constructor given a channel, allowing both directions. 136 * 137 * @param channel The channel number for this relay (0 - 3). 138 */ 139 public Relay(final int channel) { 140 this(channel, Direction.kBoth); 141 } 142 143 @Override 144 public void free() { 145 super.free(); 146 freeRelay(); 147 } 148 149 private void freeRelay() { 150 try { 151 RelayJNI.setRelay(m_forwardHandle, false); 152 } catch (RuntimeException ex) { 153 // do nothing. Ignore 154 } 155 try { 156 RelayJNI.setRelay(m_reverseHandle, false); 157 } catch (RuntimeException ex) { 158 // do nothing. Ignore 159 } 160 161 RelayJNI.freeRelayPort(m_forwardHandle); 162 RelayJNI.freeRelayPort(m_reverseHandle); 163 164 m_forwardHandle = 0; 165 m_reverseHandle = 0; 166 } 167 168 /** 169 * Set the relay state. 170 * 171 * <p>Valid values depend on which directions of the relay are controlled by the object. 172 * 173 * <p>When set to kBothDirections, the relay can be set to any of the four states: 0v-0v, 12v-0v, 174 * 0v-12v, 12v-12v 175 * 176 * <p>When set to kForwardOnly or kReverseOnly, you can specify the constant for the direction or 177 * you can simply specify kOff and kOn. Using only kOff and kOn is recommended. 178 * 179 * @param value The state to set the relay. 180 */ 181 public void set(Value value) { 182 switch (value) { 183 case kOff: 184 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 185 RelayJNI.setRelay(m_forwardHandle, false); 186 } 187 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 188 RelayJNI.setRelay(m_reverseHandle, false); 189 } 190 break; 191 case kOn: 192 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 193 RelayJNI.setRelay(m_forwardHandle, true); 194 } 195 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 196 RelayJNI.setRelay(m_reverseHandle, true); 197 } 198 break; 199 case kForward: 200 if (m_direction == Direction.kReverse) { 201 throw new InvalidValueException("A relay configured for reverse cannot be set to " 202 + "forward"); 203 } 204 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 205 RelayJNI.setRelay(m_forwardHandle, true); 206 } 207 if (m_direction == Direction.kBoth) { 208 RelayJNI.setRelay(m_reverseHandle, false); 209 } 210 break; 211 case kReverse: 212 if (m_direction == Direction.kForward) { 213 throw new InvalidValueException("A relay configured for forward cannot be set to " 214 + "reverse"); 215 } 216 if (m_direction == Direction.kBoth) { 217 RelayJNI.setRelay(m_forwardHandle, false); 218 } 219 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 220 RelayJNI.setRelay(m_reverseHandle, true); 221 } 222 break; 223 default: 224 // Cannot hit this, limited by Value enum 225 } 226 } 227 228 /** 229 * Get the Relay State. 230 * 231 * <p>Gets the current state of the relay. 232 * 233 * <p>When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff not 234 * kForward/kReverse (per the recommendation in Set) 235 * 236 * @return The current state of the relay as a Relay::Value 237 */ 238 public Value get() { 239 if (m_direction == Direction.kForward) { 240 if (RelayJNI.getRelay(m_forwardHandle)) { 241 return Value.kOn; 242 } else { 243 return Value.kOff; 244 } 245 } else if (m_direction == Direction.kReverse) { 246 if (RelayJNI.getRelay(m_reverseHandle)) { 247 return Value.kOn; 248 } else { 249 return Value.kOff; 250 } 251 } else { 252 if (RelayJNI.getRelay(m_forwardHandle)) { 253 if (RelayJNI.getRelay(m_reverseHandle)) { 254 return Value.kOn; 255 } else { 256 return Value.kForward; 257 } 258 } else { 259 if (RelayJNI.getRelay(m_reverseHandle)) { 260 return Value.kReverse; 261 } else { 262 return Value.kOff; 263 } 264 } 265 } 266 } 267 268 /** 269 * Get the channel number. 270 * 271 * @return The channel number. 272 */ 273 public int getChannel() { 274 return m_channel; 275 } 276 277 @Override 278 public void setExpiration(double timeout) { 279 m_safetyHelper.setExpiration(timeout); 280 } 281 282 @Override 283 public double getExpiration() { 284 return m_safetyHelper.getExpiration(); 285 } 286 287 @Override 288 public boolean isAlive() { 289 return m_safetyHelper.isAlive(); 290 } 291 292 @Override 293 public void stopMotor() { 294 set(Value.kOff); 295 } 296 297 @Override 298 public boolean isSafetyEnabled() { 299 return m_safetyHelper.isSafetyEnabled(); 300 } 301 302 @Override 303 public void setSafetyEnabled(boolean enabled) { 304 m_safetyHelper.setSafetyEnabled(enabled); 305 } 306 307 @Override 308 public String getDescription() { 309 return "Relay ID " + getChannel(); 310 } 311 312 /** 313 * Set the Relay Direction. 314 * 315 * <p>Changes which values the relay can be set to depending on which direction is used 316 * 317 * <p>Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly 318 * 319 * @param direction The direction for the relay to operate in 320 */ 321 public void setDirection(Direction direction) { 322 requireNonNull(direction, "Null Direction was given"); 323 if (m_direction == direction) { 324 return; 325 } 326 327 freeRelay(); 328 m_direction = direction; 329 initRelay(); 330 } 331 332 @Override 333 public void initSendable(SendableBuilder builder) { 334 builder.setSmartDashboardType("Relay"); 335 builder.setSafeState(() -> set(Value.kOff)); 336 builder.addStringProperty("Value", () -> get().getPrettyValue(), 337 (value) -> set(Value.getValueOf(value).orElse(Value.kOff))); 338 } 339}