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 public Relay(final int channel, Direction direction) { 114 m_channel = channel; 115 m_direction = requireNonNullParam(direction, "direction", "Relay"); 116 initRelay(); 117 set(Value.kOff); 118 } 119 120 /** 121 * Relay constructor given a channel, allowing both directions. 122 * 123 * @param channel The channel number for this relay (0 - 3). 124 */ 125 public Relay(final int channel) { 126 this(channel, Direction.kBoth); 127 } 128 129 @Override 130 public void close() { 131 SendableRegistry.remove(this); 132 freeRelay(); 133 } 134 135 private void freeRelay() { 136 try { 137 RelayJNI.setRelay(m_forwardHandle, false); 138 } catch (UncleanStatusException | HalHandleException ignored) { 139 // do nothing. Ignore 140 } 141 try { 142 RelayJNI.setRelay(m_reverseHandle, false); 143 } catch (UncleanStatusException | HalHandleException ignored) { 144 // do nothing. Ignore 145 } 146 147 RelayJNI.freeRelayPort(m_forwardHandle); 148 RelayJNI.freeRelayPort(m_reverseHandle); 149 150 m_forwardHandle = 0; 151 m_reverseHandle = 0; 152 } 153 154 /** 155 * Set the relay state. 156 * 157 * <p>Valid values depend on which directions of the relay are controlled by the object. 158 * 159 * <p>When set to kBothDirections, the relay can be set to any of the four states: 0v-0v, 12v-0v, 160 * 0v-12v, 12v-12v 161 * 162 * <p>When set to kForwardOnly or kReverseOnly, you can specify the constant for the direction, or 163 * you can simply specify kOff and kOn. Using only kOff and kOn is recommended. 164 * 165 * @param value The state to set the relay. 166 */ 167 public void set(Value value) { 168 switch (value) { 169 case kOff: 170 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 171 RelayJNI.setRelay(m_forwardHandle, false); 172 } 173 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 174 RelayJNI.setRelay(m_reverseHandle, false); 175 } 176 break; 177 case kOn: 178 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 179 RelayJNI.setRelay(m_forwardHandle, true); 180 } 181 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 182 RelayJNI.setRelay(m_reverseHandle, true); 183 } 184 break; 185 case kForward: 186 if (m_direction == Direction.kReverse) { 187 throw new InvalidValueException( 188 "A relay configured for reverse cannot be set to " + "forward"); 189 } 190 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 191 RelayJNI.setRelay(m_forwardHandle, true); 192 } 193 if (m_direction == Direction.kBoth) { 194 RelayJNI.setRelay(m_reverseHandle, false); 195 } 196 break; 197 case kReverse: 198 if (m_direction == Direction.kForward) { 199 throw new InvalidValueException( 200 "A relay configured for forward cannot be set to " + "reverse"); 201 } 202 if (m_direction == Direction.kBoth) { 203 RelayJNI.setRelay(m_forwardHandle, false); 204 } 205 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 206 RelayJNI.setRelay(m_reverseHandle, true); 207 } 208 break; 209 default: 210 // Cannot hit this, limited by Value enum 211 } 212 } 213 214 /** 215 * Get the Relay State. 216 * 217 * <p>Gets the current state of the relay. 218 * 219 * <p>When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff not 220 * kForward/kReverse (per the recommendation in Set) 221 * 222 * @return The current state of the relay as a Relay::Value 223 */ 224 public Value get() { 225 if (m_direction == Direction.kForward) { 226 if (RelayJNI.getRelay(m_forwardHandle)) { 227 return Value.kOn; 228 } else { 229 return Value.kOff; 230 } 231 } else if (m_direction == Direction.kReverse) { 232 if (RelayJNI.getRelay(m_reverseHandle)) { 233 return Value.kOn; 234 } else { 235 return Value.kOff; 236 } 237 } else { 238 if (RelayJNI.getRelay(m_forwardHandle)) { 239 if (RelayJNI.getRelay(m_reverseHandle)) { 240 return Value.kOn; 241 } else { 242 return Value.kForward; 243 } 244 } else { 245 if (RelayJNI.getRelay(m_reverseHandle)) { 246 return Value.kReverse; 247 } else { 248 return Value.kOff; 249 } 250 } 251 } 252 } 253 254 /** 255 * Get the channel number. 256 * 257 * @return The channel number. 258 */ 259 public int getChannel() { 260 return m_channel; 261 } 262 263 @Override 264 public void stopMotor() { 265 set(Value.kOff); 266 } 267 268 @Override 269 public String getDescription() { 270 return "Relay ID " + getChannel(); 271 } 272 273 /** 274 * Set the Relay Direction. 275 * 276 * <p>Changes which values the relay can be set to depending on which direction is used 277 * 278 * <p>Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly 279 * 280 * @param direction The direction for the relay to operate in 281 */ 282 public void setDirection(Direction direction) { 283 requireNonNullParam(direction, "direction", "setDirection"); 284 if (m_direction == direction) { 285 return; 286 } 287 288 freeRelay(); 289 m_direction = direction; 290 initRelay(); 291 } 292 293 @Override 294 public void initSendable(SendableBuilder builder) { 295 builder.setSmartDashboardType("Relay"); 296 builder.setActuator(true); 297 builder.setSafeState(() -> set(Value.kOff)); 298 builder.addStringProperty( 299 "Value", 300 () -> get().getPrettyValue(), 301 value -> set(Value.getValueOf(value).orElse(Value.kOff))); 302 } 303}