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