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