001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) FIRST 2008-2012. 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 java.nio.ByteBuffer; 011import java.nio.ByteOrder; 012 013import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tResourceType; 014import edu.wpi.first.wpilibj.communication.UsageReporting; 015import edu.wpi.first.wpilibj.hal.DIOJNI; 016import edu.wpi.first.wpilibj.hal.HALUtil; 017import edu.wpi.first.wpilibj.hal.RelayJNI; 018import edu.wpi.first.wpilibj.livewindow.LiveWindow; 019import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable; 020import edu.wpi.first.wpilibj.tables.ITable; 021import edu.wpi.first.wpilibj.tables.ITableListener; 022import edu.wpi.first.wpilibj.util.AllocationException; 023import edu.wpi.first.wpilibj.util.CheckedAllocationException; 024 025/** 026 * Class for VEX Robotics Spike style relay outputs. Relays are intended to be 027 * connected to Spikes or similar relays. The relay channels controls a pair of 028 * pins that are either both off, one on, the other on, or both on. This 029 * translates into two Spike outputs at 0v, one at 12v and one at 0v, one at 0v 030 * and the other at 12v, or two Spike outputs at 12V. This allows off, full 031 * forward, or full reverse control of motors without variable speed. It also 032 * allows the two channels (forward and reverse) to be used independently for 033 * something that does not care about voltage polarity (like a solenoid). 034 */ 035public class Relay extends SensorBase implements LiveWindowSendable { 036 037 /** 038 * This class represents errors in trying to set relay values contradictory 039 * to the direction to which the relay is set. 040 */ 041 public class InvalidValueException extends RuntimeException { 042 043 /** 044 * Create a new exception with the given message 045 * 046 * @param message 047 * the message to pass with the exception 048 */ 049 public InvalidValueException(String message) { 050 super(message); 051 } 052 } 053 054 /** 055 * The state to drive a Relay to. 056 */ 057 public static enum Value { 058 /** 059 * value: off 060 */ 061 kOff(0), 062 /** 063 * value: on for relays with defined direction 064 */ 065 kOn(1), 066 /** 067 * value: forward 068 */ 069 kForward(2), 070 /** 071 * value: reverse 072 */ 073 kReverse(3); 074 075 /** 076 * The integer value representing this enumeration 077 */ 078 public final int value; 079 080 private Value(int value) { 081 this.value = value; 082 } 083 } 084 085 /** 086 * The Direction(s) that a relay is configured to operate in. 087 */ 088 public static enum Direction { 089 /** 090 * direction: both directions are valid 091 */ 092 093 kBoth(0), 094 /** 095 * direction: Only forward is valid 096 */ 097 kForward(1), 098 /** 099 * direction: only reverse is valid 100 */ 101 kReverse(2); 102 103 /** 104 * The integer value representing this enumeration 105 */ 106 public final int value; 107 108 private Direction(int value) { 109 this.value = value; 110 } 111 112 } 113 114 private final int m_channel; 115 private ByteBuffer m_port; 116 117 private Direction m_direction; 118 private static Resource relayChannels = new Resource(kRelayChannels * 2); 119 120 /** 121 * Common relay initialization method. This code is common to all Relay 122 * constructors and initializes the relay and reserves all resources that 123 * need to be locked. Initially the relay is set to both lines at 0v. 124 */ 125 private void initRelay() { 126 SensorBase.checkRelayChannel(m_channel); 127 try { 128 if (m_direction == Direction.kBoth 129 || m_direction == Direction.kForward) { 130 relayChannels.allocate(m_channel * 2); 131 UsageReporting.report(tResourceType.kResourceType_Relay, m_channel); 132 } 133 if (m_direction == Direction.kBoth 134 || m_direction == Direction.kReverse) { 135 relayChannels.allocate(m_channel * 2 + 1); 136 UsageReporting.report(tResourceType.kResourceType_Relay, m_channel + 128); 137 } 138 } catch (CheckedAllocationException e) { 139 throw new AllocationException("Relay channel " + m_channel + " is already allocated"); 140 } 141 142 ByteBuffer status = ByteBuffer.allocateDirect(4); 143 status.order(ByteOrder.LITTLE_ENDIAN); 144 145 m_port = DIOJNI.initializeDigitalPort(DIOJNI.getPort((byte) m_channel), status.asIntBuffer()); 146 HALUtil.checkStatus(status.asIntBuffer()); 147 148 LiveWindow.addActuator("Relay", m_channel, this); 149 } 150 151 /** 152 * Relay constructor given a channel. 153 * 154 * @param channel 155 * The channel number for this relay (0 - 3). 156 * @param direction 157 * The direction that the Relay object will control. 158 */ 159 public Relay(final int channel, Direction direction) { 160 if (direction == null) 161 throw new NullPointerException("Null Direction was given"); 162 m_channel = channel; 163 m_direction = direction; 164 initRelay(); 165 set(Value.kOff); 166 } 167 168 /** 169 * Relay constructor given a channel, allowing both directions. 170 * 171 * @param channel 172 * The channel number for this relay (0 - 3). 173 */ 174 public Relay(final int channel) { 175 this(channel, Direction.kBoth); 176 } 177 178 @Override 179 public void free() { 180 if (m_direction == Direction.kBoth || m_direction == Direction.kForward) { 181 relayChannels.free(m_channel*2); 182 } 183 if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) { 184 relayChannels.free(m_channel*2 + 1); 185 } 186 187 ByteBuffer status = ByteBuffer.allocateDirect(4); 188 status.order(ByteOrder.LITTLE_ENDIAN); 189 190 RelayJNI.setRelayForward(m_port, (byte) 0, status.asIntBuffer()); 191 HALUtil.checkStatus(status.asIntBuffer()); 192 RelayJNI.setRelayReverse(m_port, (byte) 0, status.asIntBuffer()); 193 HALUtil.checkStatus(status.asIntBuffer()); 194 195 DIOJNI.freeDIO(m_port, status.asIntBuffer()); 196 HALUtil.checkStatus(status.asIntBuffer()); 197 } 198 199 /** 200 * Set the relay state. 201 * 202 * Valid values depend on which directions of the relay are controlled by 203 * the object. 204 * 205 * When set to kBothDirections, the relay can be set to any of the four 206 * states: 0v-0v, 12v-0v, 0v-12v, 12v-12v 207 * 208 * When set to kForwardOnly or kReverseOnly, you can specify the constant 209 * for the direction or you can simply specify kOff_val and kOn_val. Using 210 * only kOff_val and kOn_val is recommended. 211 * 212 * @param value 213 * The state to set the relay. 214 */ 215 public void set(Value value) { 216 ByteBuffer status = ByteBuffer.allocateDirect(4); 217 status.order(ByteOrder.LITTLE_ENDIAN); 218 219 switch (value) { 220 case kOff: 221 if (m_direction == Direction.kBoth 222 || m_direction == Direction.kForward) { 223 RelayJNI.setRelayForward(m_port, (byte) 0, status.asIntBuffer()); 224 } 225 if (m_direction == Direction.kBoth 226 || m_direction == Direction.kReverse) { 227 RelayJNI.setRelayReverse(m_port, (byte) 0, status.asIntBuffer()); 228 } 229 break; 230 case kOn: 231 if (m_direction == Direction.kBoth 232 || m_direction == Direction.kForward) { 233 RelayJNI.setRelayForward(m_port, (byte) 1, status.asIntBuffer()); 234 } 235 if (m_direction == Direction.kBoth 236 || m_direction == Direction.kReverse) { 237 RelayJNI.setRelayReverse(m_port, (byte) 1, status.asIntBuffer()); 238 } 239 break; 240 case kForward: 241 if (m_direction == Direction.kReverse) 242 throw new InvalidValueException( 243 "A relay configured for reverse cannot be set to forward"); 244 if (m_direction == Direction.kBoth 245 || m_direction == Direction.kForward) { 246 RelayJNI.setRelayForward(m_port, (byte) 1, status.asIntBuffer()); 247 } 248 if (m_direction == Direction.kBoth) { 249 RelayJNI.setRelayReverse(m_port, (byte) 0, status.asIntBuffer()); 250 } 251 break; 252 case kReverse: 253 if (m_direction == Direction.kForward) 254 throw new InvalidValueException( 255 "A relay configured for forward cannot be set to reverse"); 256 if (m_direction == Direction.kBoth) { 257 RelayJNI.setRelayForward(m_port, (byte) 0, status.asIntBuffer()); 258 } 259 if (m_direction == Direction.kBoth 260 || m_direction == Direction.kReverse) { 261 RelayJNI.setRelayReverse(m_port, (byte) 1, status.asIntBuffer()); 262 } 263 break; 264 default: 265 // Cannot hit this, limited by Value enum 266 } 267 268 HALUtil.checkStatus(status.asIntBuffer()); 269 } 270 271 /** 272 * Get the Relay State 273 * 274 * Gets the current state of the relay. 275 * 276 * When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff 277 * not kForward/kReverse (per the recommendation in Set) 278 * 279 * @return The current state of the relay as a Relay::Value 280 */ 281 public Value get() { 282 ByteBuffer status = ByteBuffer.allocateDirect(4); 283 status.order(ByteOrder.LITTLE_ENDIAN); 284 285 if (RelayJNI.getRelayForward(m_port, status.asIntBuffer()) != 0) { 286 if (RelayJNI.getRelayReverse(m_port, status.asIntBuffer()) != 0) { 287 return Value.kOn; 288 } else { 289 if (m_direction == Direction.kForward) { 290 return Value.kOn; 291 } else { 292 return Value.kForward; 293 } 294 } 295 } else { 296 if (RelayJNI.getRelayReverse(m_port, status.asIntBuffer()) != 0) { 297 if (m_direction == Direction.kReverse) { 298 return Value.kOn; 299 } else { 300 return Value.kReverse; 301 } 302 } else { 303 return Value.kOff; 304 } 305 } 306 } 307 308 /** 309 * Set the Relay Direction 310 * 311 * Changes which values the relay can be set to depending on which direction 312 * is used 313 * 314 * Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly 315 * 316 * @param direction 317 * The direction for the relay to operate in 318 */ 319 public void setDirection(Direction direction) { 320 if (direction == null) 321 throw new NullPointerException("Null Direction was given"); 322 if (m_direction == direction) { 323 return; 324 } 325 326 free(); 327 328 m_direction = direction; 329 330 initRelay(); 331 } 332 333 /* 334 * Live Window code, only does anything if live window is activated. 335 */ 336 @Override 337 public String getSmartDashboardType() { 338 return "Relay"; 339 } 340 341 private ITable m_table; 342 private ITableListener m_table_listener; 343 344 /** 345 * {@inheritDoc} 346 */ 347 @Override 348 public void initTable(ITable subtable) { 349 m_table = subtable; 350 updateTable(); 351 } 352 353 /** 354 * {@inheritDoc} 355 */ 356 @Override 357 public ITable getTable() { 358 return m_table; 359 } 360 361 /** 362 * {@inheritDoc} 363 */ 364 @Override 365 public void updateTable() { 366 if (m_table != null) { 367 if (get() == Value.kOn) { 368 m_table.putString("Value", "On"); 369 } else if (get() == Value.kForward) { 370 m_table.putString("Value", "Forward"); 371 } else if (get() == Value.kReverse) { 372 m_table.putString("Value", "Reverse"); 373 } else { 374 m_table.putString("Value", "Off"); 375 } 376 } 377 } 378 379 /** 380 * {@inheritDoc} 381 */ 382 @Override 383 public void startLiveWindowMode() { 384 m_table_listener = new ITableListener() { 385 @Override 386 public void valueChanged(ITable itable, String key, Object value, 387 boolean bln) { 388 String val = ((String) value); 389 if (val.equals("Off")) { 390 set(Value.kOff); 391 } else if (val.equals("On")) { 392 set(Value.kOn); 393 } else if (val.equals("Forward")) { 394 set(Value.kForward); 395 } else if (val.equals("Reverse")) { 396 set(Value.kReverse); 397 } 398 } 399 }; 400 m_table.addTableListener("Value", m_table_listener, true); 401 } 402 403 /** 404 * {@inheritDoc} 405 */ 406 @Override 407 public void stopLiveWindowMode() { 408 // TODO: Broken, should only remove the listener from "Value" only. 409 m_table.removeTableListener(m_table_listener); 410 } 411}