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 edu.wpi.first.hal.util.AllocationException; 008import edu.wpi.first.util.sendable.Sendable; 009import edu.wpi.first.util.sendable.SendableBuilder; 010import edu.wpi.first.util.sendable.SendableRegistry; 011 012/** 013 * DoubleSolenoid class for running 2 channels of high voltage Digital Output on the pneumatics 014 * module. 015 * 016 * <p>The DoubleSolenoid class is typically used for pneumatics solenoids that have two positions 017 * controlled by two separate channels. 018 */ 019public class DoubleSolenoid implements Sendable, AutoCloseable { 020 /** Possible values for a DoubleSolenoid. */ 021 public enum Value { 022 /** Off position. */ 023 kOff, 024 /** Forward position. */ 025 kForward, 026 /** Reverse position. */ 027 kReverse 028 } 029 030 private final int m_forwardMask; // The mask for the forward channel. 031 private final int m_reverseMask; // The mask for the reverse channel. 032 private final int m_mask; // The channel mask 033 private PneumaticsBase m_module; 034 private final int m_forwardChannel; 035 private final int m_reverseChannel; 036 037 /** 038 * Constructs a double solenoid for a default module of a specific module type. 039 * 040 * @param busId The bus ID 041 * @param moduleType The module type to use. 042 * @param forwardChannel The forward channel on the module to control. 043 * @param reverseChannel The reverse channel on the module to control. 044 */ 045 public DoubleSolenoid( 046 final int busId, 047 final PneumaticsModuleType moduleType, 048 final int forwardChannel, 049 final int reverseChannel) { 050 this( 051 busId, 052 PneumaticsBase.getDefaultForType(moduleType), 053 moduleType, 054 forwardChannel, 055 reverseChannel); 056 } 057 058 /** 059 * Constructs a double solenoid for a specified module of a specific module type. 060 * 061 * @param busId The bus ID 062 * @param module The module of the solenoid module to use. 063 * @param moduleType The module type to use. 064 * @param forwardChannel The forward channel on the module to control. 065 * @param reverseChannel The reverse channel on the module to control. 066 */ 067 @SuppressWarnings({"PMD.UseTryWithResources", "this-escape"}) 068 public DoubleSolenoid( 069 final int busId, 070 final int module, 071 final PneumaticsModuleType moduleType, 072 final int forwardChannel, 073 final int reverseChannel) { 074 m_module = PneumaticsBase.getForType(busId, module, moduleType); 075 boolean allocatedSolenoids = false; 076 boolean successfulCompletion = false; 077 078 m_forwardChannel = forwardChannel; 079 m_reverseChannel = reverseChannel; 080 081 m_forwardMask = 1 << forwardChannel; 082 m_reverseMask = 1 << reverseChannel; 083 m_mask = m_forwardMask | m_reverseMask; 084 085 try { 086 if (!m_module.checkSolenoidChannel(forwardChannel)) { 087 throw new IllegalArgumentException("Channel " + forwardChannel + " out of range"); 088 } 089 090 if (!m_module.checkSolenoidChannel(reverseChannel)) { 091 throw new IllegalArgumentException("Channel " + reverseChannel + " out of range"); 092 } 093 094 int allocMask = m_module.checkAndReserveSolenoids(m_mask); 095 if (allocMask != 0) { 096 if (allocMask == m_mask) { 097 throw new AllocationException( 098 "Channels " + forwardChannel + " and " + reverseChannel + " already allocated"); 099 } else if (allocMask == m_forwardMask) { 100 throw new AllocationException("Channel " + forwardChannel + " already allocated"); 101 } else { 102 throw new AllocationException("Channel " + reverseChannel + " already allocated"); 103 } 104 } 105 allocatedSolenoids = true; 106 107 m_module.reportUsage( 108 "Solenoid[" + forwardChannel + "," + reverseChannel + "]", "DoubleSolenoid"); 109 SendableRegistry.add(this, "DoubleSolenoid", m_module.getModuleNumber(), forwardChannel); 110 successfulCompletion = true; 111 } finally { 112 if (!successfulCompletion) { 113 if (allocatedSolenoids) { 114 m_module.unreserveSolenoids(m_mask); 115 } 116 m_module.close(); 117 } 118 } 119 } 120 121 @Override 122 public synchronized void close() { 123 SendableRegistry.remove(this); 124 m_module.unreserveSolenoids(m_mask); 125 m_module.close(); 126 m_module = null; 127 } 128 129 /** 130 * Set the value of a solenoid. 131 * 132 * @param value The value to set (Off, Forward, Reverse) 133 */ 134 public void set(final Value value) { 135 int setValue = 136 switch (value) { 137 case kOff -> 0; 138 case kForward -> m_forwardMask; 139 case kReverse -> m_reverseMask; 140 }; 141 142 m_module.setSolenoids(m_mask, setValue); 143 } 144 145 /** 146 * Read the current value of the solenoid. 147 * 148 * @return The current value of the solenoid. 149 */ 150 public Value get() { 151 int values = m_module.getSolenoids(); 152 153 if ((values & m_forwardMask) != 0) { 154 return Value.kForward; 155 } else if ((values & m_reverseMask) != 0) { 156 return Value.kReverse; 157 } else { 158 return Value.kOff; 159 } 160 } 161 162 /** 163 * Toggle the value of the solenoid. 164 * 165 * <p>If the solenoid is set to forward, it'll be set to reverse. If the solenoid is set to 166 * reverse, it'll be set to forward. If the solenoid is set to off, nothing happens. 167 */ 168 public void toggle() { 169 Value value = get(); 170 171 if (value == Value.kForward) { 172 set(Value.kReverse); 173 } else if (value == Value.kReverse) { 174 set(Value.kForward); 175 } 176 } 177 178 /** 179 * Get the forward channel. 180 * 181 * @return the forward channel. 182 */ 183 public int getFwdChannel() { 184 return m_forwardChannel; 185 } 186 187 /** 188 * Get the reverse channel. 189 * 190 * @return the reverse channel. 191 */ 192 public int getRevChannel() { 193 return m_reverseChannel; 194 } 195 196 /** 197 * Check if the forward solenoid is Disabled. If a solenoid is shorted, it is added to the 198 * DisabledList and disabled until power cycle, or until faults are cleared. 199 * 200 * @return If solenoid is disabled due to short. 201 */ 202 public boolean isFwdSolenoidDisabled() { 203 return (m_module.getSolenoidDisabledList() & m_forwardMask) != 0; 204 } 205 206 /** 207 * Check if the reverse solenoid is Disabled. If a solenoid is shorted, it is added to the 208 * DisabledList and disabled until power cycle, or until faults are cleared. 209 * 210 * @return If solenoid is disabled due to short. 211 */ 212 public boolean isRevSolenoidDisabled() { 213 return (m_module.getSolenoidDisabledList() & m_reverseMask) != 0; 214 } 215 216 @Override 217 public void initSendable(SendableBuilder builder) { 218 builder.setSmartDashboardType("Double Solenoid"); 219 builder.setActuator(true); 220 builder.addStringProperty( 221 "Value", 222 () -> get().name().substring(1), 223 value -> { 224 if ("Forward".equals(value)) { 225 set(Value.kForward); 226 } else if ("Reverse".equals(value)) { 227 set(Value.kReverse); 228 } else { 229 set(Value.kOff); 230 } 231 }); 232 } 233}