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.wpilibj.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 @SuppressWarnings("serial") 035 public static class InvalidValueException extends RuntimeException { 036 /** 037 * Create a new exception with the given message. 038 * 039 * @param message the message to pass with the exception 040 */ 041 public InvalidValueException(String message) { 042 super(message); 043 } 044 } 045 046 /** The state to drive a Relay to. */ 047 public enum Value { 048 kOff("Off"), 049 kOn("On"), 050 kForward("Forward"), 051 kReverse("Reverse"); 052 053 private final String m_prettyValue; 054 055 Value(String prettyValue) { 056 m_prettyValue = prettyValue; 057 } 058 059 public String getPrettyValue() { 060 return m_prettyValue; 061 } 062 063 public static Optional<Value> getValueOf(String value) { 064 return Arrays.stream(Value.values()).filter(v -> v.m_prettyValue.equals(value)).findFirst(); 065 } 066 } 067 068 /** The Direction(s) that a relay is configured to operate in. */ 069 public enum Direction { 070 /** direction: both directions are valid. */ 071 kBoth, 072 /** direction: Only forward is valid. */ 073 kForward, 074 /** direction: only reverse is valid. */ 075 kReverse 076 } 077 078 private final int m_channel; 079 080 private int m_forwardHandle; 081 private int m_reverseHandle; 082 083 private Direction m_direction; 084 085 /** 086 * Common relay initialization method. This code is common to all Relay constructors and 087 * initializes the relay and reserves all resources that need to be locked. Initially the relay is 088 * set to both lines at 0v. 089 */ 090 private void initRelay() { 091 SensorUtil.checkRelayChannel(m_channel); 092 093 int portHandle = HAL.getPort((byte) m_channel); 094 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 095 m_forwardHandle = RelayJNI.initializeRelayPort(portHandle, true); 096 HAL.report(tResourceType.kResourceType_Relay, m_channel + 1); 097 } 098 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 099 m_reverseHandle = RelayJNI.initializeRelayPort(portHandle, false); 100 HAL.report(tResourceType.kResourceType_Relay, m_channel + 128); 101 } 102 103 setSafetyEnabled(false); 104 105 SendableRegistry.addLW(this, "Relay", m_channel); 106 } 107 108 /** 109 * Relay constructor given a channel. 110 * 111 * @param channel The channel number for this relay (0 - 3). 112 * @param direction The direction that the Relay object will control. 113 */ 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 public void setDirection(Direction direction) { 284 requireNonNullParam(direction, "direction", "setDirection"); 285 if (m_direction == direction) { 286 return; 287 } 288 289 freeRelay(); 290 m_direction = direction; 291 initRelay(); 292 } 293 294 @Override 295 public void initSendable(SendableBuilder builder) { 296 builder.setSmartDashboardType("Relay"); 297 builder.setActuator(true); 298 builder.setSafeState(() -> set(Value.kOff)); 299 builder.addStringProperty( 300 "Value", 301 () -> get().getPrettyValue(), 302 value -> set(Value.getValueOf(value).orElse(Value.kOff))); 303 } 304}