001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) FIRST 2008-2017. 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.livewindow.LiveWindow; 014import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable; 015import edu.wpi.first.wpilibj.tables.ITable; 016import edu.wpi.first.wpilibj.tables.ITableListener; 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 SensorBase implements MotorSafety, LiveWindowSendable { 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 /** 039 * Create a new exception with the given message. 040 * 041 * @param message the message to pass with the exception 042 */ 043 public InvalidValueException(String message) { 044 super(message); 045 } 046 } 047 048 /** 049 * The state to drive a Relay to. 050 */ 051 public enum Value { 052 kOff, kOn, kForward, kReverse 053 } 054 055 /** 056 * The Direction(s) that a relay is configured to operate in. 057 */ 058 public enum Direction { 059 /** 060 * direction: both directions are valid. 061 */ 062 063 kBoth, 064 /** 065 * direction: Only forward is valid. 066 */ 067 kForward, 068 /** 069 * direction: only reverse is valid. 070 */ 071 kReverse 072 } 073 074 private final int m_channel; 075 076 private int m_forwardHandle = 0; 077 private int m_reverseHandle = 0; 078 079 private Direction m_direction; 080 081 /** 082 * Common relay initialization method. This code is common to all Relay constructors and 083 * initializes the relay and reserves all resources that need to be locked. Initially the relay is 084 * set to both lines at 0v. 085 */ 086 private void initRelay() { 087 SensorBase.checkRelayChannel(m_channel); 088 089 int portHandle = RelayJNI.getPort((byte)m_channel); 090 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 091 m_forwardHandle = RelayJNI.initializeRelayPort(portHandle, true); 092 HAL.report(tResourceType.kResourceType_Relay, m_channel); 093 } 094 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 095 m_reverseHandle = RelayJNI.initializeRelayPort(portHandle, false); 096 HAL.report(tResourceType.kResourceType_Relay, m_channel + 128); 097 } 098 099 m_safetyHelper = new MotorSafetyHelper(this); 100 m_safetyHelper.setSafetyEnabled(false); 101 102 LiveWindow.addActuator("Relay", m_channel, this); 103 } 104 105 /** 106 * Relay constructor given a channel. 107 * 108 * @param channel The channel number for this relay (0 - 3). 109 * @param direction The direction that the Relay object will control. 110 */ 111 public Relay(final int channel, Direction direction) { 112 m_channel = channel; 113 m_direction = requireNonNull( direction, "Null Direction was given"); 114 initRelay(); 115 set(Value.kOff); 116 } 117 118 /** 119 * Relay constructor given a channel, allowing both directions. 120 * 121 * @param channel The channel number for this relay (0 - 3). 122 */ 123 public Relay(final int channel) { 124 this(channel, Direction.kBoth); 125 } 126 127 @Override 128 public void free() { 129 try { 130 RelayJNI.setRelay(m_forwardHandle, false); 131 } catch (RuntimeException ex) { 132 // do nothing. Ignore 133 } 134 try { 135 RelayJNI.setRelay(m_reverseHandle, false); 136 } catch (RuntimeException ex) { 137 // do nothing. Ignore 138 } 139 140 RelayJNI.freeRelayPort(m_forwardHandle); 141 RelayJNI.freeRelayPort(m_reverseHandle); 142 143 m_forwardHandle = 0; 144 m_reverseHandle = 0; 145 } 146 147 /** 148 * Set the relay state. 149 * 150 * <p>Valid values depend on which directions of the relay are controlled by the object. 151 * 152 * <p>When set to kBothDirections, the relay can be set to any of the four states: 0v-0v, 12v-0v, 153 * 0v-12v, 12v-12v 154 * 155 * <p>When set to kForwardOnly or kReverseOnly, you can specify the constant for the direction or 156 * you can simply specify kOff_val and kOn_val. Using only kOff_val and kOn_val is recommended. 157 * 158 * @param value The state to set the relay. 159 */ 160 public void set(Value value) { 161 switch (value) { 162 case kOff: 163 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 164 RelayJNI.setRelay(m_forwardHandle, false); 165 } 166 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 167 RelayJNI.setRelay(m_reverseHandle, false); 168 } 169 break; 170 case kOn: 171 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 172 RelayJNI.setRelay(m_forwardHandle, true); 173 } 174 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 175 RelayJNI.setRelay(m_reverseHandle, true); 176 } 177 break; 178 case kForward: 179 if (m_direction == Direction.kReverse) { 180 throw new InvalidValueException("A relay configured for reverse cannot be set to " 181 + "forward"); 182 } 183 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 184 RelayJNI.setRelay(m_forwardHandle, true); 185 } 186 if (m_direction == Direction.kBoth) { 187 RelayJNI.setRelay(m_reverseHandle, false); 188 } 189 break; 190 case kReverse: 191 if (m_direction == Direction.kForward) { 192 throw new InvalidValueException("A relay configured for forward cannot be set to " 193 + "reverse"); 194 } 195 if (m_direction == Direction.kBoth) { 196 RelayJNI.setRelay(m_forwardHandle, false); 197 } 198 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 199 RelayJNI.setRelay(m_reverseHandle, true); 200 } 201 break; 202 default: 203 // Cannot hit this, limited by Value enum 204 } 205 } 206 207 /** 208 * Get the Relay State. 209 * 210 * <p>Gets the current state of the relay. 211 * 212 * <p>When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff not 213 * kForward/kReverse (per the recommendation in Set) 214 * 215 * @return The current state of the relay as a Relay::Value 216 */ 217 public Value get() { 218 if (RelayJNI.getRelay(m_forwardHandle)) { 219 if (RelayJNI.getRelay(m_reverseHandle)) { 220 return Value.kOn; 221 } else { 222 if (m_direction == Direction.kForward) { 223 return Value.kOn; 224 } else { 225 return Value.kForward; 226 } 227 } 228 } else { 229 if (RelayJNI.getRelay(m_reverseHandle)) { 230 if (m_direction == Direction.kReverse) { 231 return Value.kOn; 232 } else { 233 return Value.kReverse; 234 } 235 } else { 236 return Value.kOff; 237 } 238 } 239 } 240 241 /** 242 * Get the channel number. 243 * 244 * @return The channel number. 245 */ 246 public int getChannel() { 247 return m_channel; 248 } 249 250 @Override 251 public void setExpiration(double timeout) { 252 m_safetyHelper.setExpiration(timeout); 253 } 254 255 @Override 256 public double getExpiration() { 257 return m_safetyHelper.getExpiration(); 258 } 259 260 @Override 261 public boolean isAlive() { 262 return m_safetyHelper.isAlive(); 263 } 264 265 @Override 266 public void stopMotor() { 267 set(Value.kOff); 268 } 269 270 @Override 271 public boolean isSafetyEnabled() { 272 return m_safetyHelper.isSafetyEnabled(); 273 } 274 275 @Override 276 public void setSafetyEnabled(boolean enabled) { 277 m_safetyHelper.setSafetyEnabled(enabled); 278 } 279 280 @Override 281 public String getDescription() { 282 return "Relay ID " + getChannel(); 283 } 284 285 /** 286 * Set the Relay Direction. 287 * 288 * <p>Changes which values the relay can be set to depending on which direction is used 289 * 290 * <p>Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly 291 * 292 * @param direction The direction for the relay to operate in 293 */ 294 public void setDirection(Direction direction) { 295 if (direction == null) { 296 throw new NullPointerException("Null Direction was given"); 297 } 298 if (m_direction == direction) { 299 return; 300 } 301 302 free(); 303 304 m_direction = direction; 305 306 initRelay(); 307 } 308 309 /* 310 * Live Window code, only does anything if live window is activated. 311 */ 312 @Override 313 public String getSmartDashboardType() { 314 return "Relay"; 315 } 316 317 private ITable m_table; 318 private ITableListener m_tableListener; 319 320 @Override 321 public void initTable(ITable subtable) { 322 m_table = subtable; 323 updateTable(); 324 } 325 326 @Override 327 public ITable getTable() { 328 return m_table; 329 } 330 331 @Override 332 public void updateTable() { 333 if (m_table != null) { 334 if (get() == Value.kOn) { 335 m_table.putString("Value", "On"); 336 } else if (get() == Value.kForward) { 337 m_table.putString("Value", "Forward"); 338 } else if (get() == Value.kReverse) { 339 m_table.putString("Value", "Reverse"); 340 } else { 341 m_table.putString("Value", "Off"); 342 } 343 } 344 } 345 346 @Override 347 public void startLiveWindowMode() { 348 m_tableListener = new ITableListener() { 349 @Override 350 public void valueChanged(ITable itable, String key, Object value, boolean bln) { 351 String val = ((String) value); 352 if (val.equals("Off")) { 353 set(Value.kOff); 354 } else if (val.equals("On")) { 355 set(Value.kOn); 356 } else if (val.equals("Forward")) { 357 set(Value.kForward); 358 } else if (val.equals("Reverse")) { 359 set(Value.kReverse); 360 } 361 } 362 }; 363 m_table.addTableListener("Value", m_tableListener, true); 364 } 365 366 @Override 367 public void stopLiveWindowMode() { 368 // TODO: Broken, should only remove the listener from "Value" only. 369 m_table.removeTableListener(m_tableListener); 370 } 371}