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.DIOJNI;
008import edu.wpi.first.hal.HAL;
009import edu.wpi.first.hal.SimDevice;
010import edu.wpi.first.util.sendable.Sendable;
011import edu.wpi.first.util.sendable.SendableBuilder;
012import edu.wpi.first.util.sendable.SendableRegistry;
013
014/**
015 * Class to write digital outputs. This class will write digital outputs. Other devices that are
016 * implemented elsewhere will automatically allocate digital inputs and outputs as required.
017 */
018public class DigitalOutput implements AutoCloseable, Sendable {
019  private static final int invalidPwmGenerator = 0;
020  private int m_pwmGenerator = invalidPwmGenerator;
021
022  private final int m_channel;
023  private int m_handle;
024
025  /**
026   * Create an instance of a digital output. Create an instance of a digital output given a channel.
027   *
028   * @param channel the DIO channel to use for the digital output. 0-9 are on-board, 10-25 are on
029   *     the MXP
030   */
031  @SuppressWarnings("this-escape")
032  public DigitalOutput(int channel) {
033    SensorUtil.checkDigitalChannel(channel);
034    m_channel = channel;
035
036    m_handle = DIOJNI.initializeDIOPort(channel, false);
037
038    HAL.reportUsage("IO", channel, "DigitalOutput");
039    SendableRegistry.add(this, "DigitalOutput", channel);
040  }
041
042  @Override
043  public void close() {
044    SendableRegistry.remove(this);
045    // Disable the pwm only if we have allocated it
046    if (m_pwmGenerator != invalidPwmGenerator) {
047      disablePWM();
048    }
049    DIOJNI.freeDIOPort(m_handle);
050    m_handle = 0;
051  }
052
053  /**
054   * Set the value of a digital output.
055   *
056   * @param value true is on, off is false
057   */
058  public void set(boolean value) {
059    DIOJNI.setDIO(m_handle, value);
060  }
061
062  /**
063   * Gets the value being output from the Digital Output.
064   *
065   * @return the state of the digital output.
066   */
067  public boolean get() {
068    return DIOJNI.getDIO(m_handle);
069  }
070
071  /**
072   * Get the GPIO channel number that this object represents.
073   *
074   * @return The GPIO channel number.
075   */
076  public int getChannel() {
077    return m_channel;
078  }
079
080  /**
081   * Output a single pulse on the digital output line.
082   *
083   * <p>Send a single pulse on the digital output line where the pulse duration is specified in
084   * seconds. Maximum of 65535 microseconds.
085   *
086   * @param pulseLength The pulse length in seconds
087   */
088  public void pulse(final double pulseLength) {
089    DIOJNI.pulse(m_handle, pulseLength);
090  }
091
092  /**
093   * Determine if the pulse is still going. Determine if a previously started pulse is still going.
094   *
095   * @return true if pulsing
096   */
097  public boolean isPulsing() {
098    return DIOJNI.isPulsing(m_handle);
099  }
100
101  /**
102   * Change the PWM frequency of the PWM output on a Digital Output line.
103   *
104   * <p>The valid range is from 0.6 Hz to 19 kHz. The frequency resolution is logarithmic.
105   *
106   * <p>There is only one PWM frequency for all channels.
107   *
108   * @param rate The frequency to output all digital output PWM signals.
109   */
110  public void setPWMRate(double rate) {
111    DIOJNI.setDigitalPWMRate(rate);
112  }
113
114  /**
115   * Enable a PWM PPS (Pulse Per Second) Output on this line.
116   *
117   * <p>Allocate one of the 6 DO PWM generator resources.
118   *
119   * <p>Supply the duty-cycle to output.
120   *
121   * <p>The resolution of the duty cycle is 8-bit.
122   *
123   * @param dutyCycle The duty-cycle to start generating. [0..1]
124   */
125  public void enablePPS(double dutyCycle) {
126    if (m_pwmGenerator != invalidPwmGenerator) {
127      return;
128    }
129    m_pwmGenerator = DIOJNI.allocateDigitalPWM();
130    DIOJNI.setDigitalPWMPPS(m_pwmGenerator, dutyCycle);
131    DIOJNI.setDigitalPWMOutputChannel(m_pwmGenerator, m_channel);
132  }
133
134  /**
135   * Enable a PWM Output on this line.
136   *
137   * <p>Allocate one of the 6 DO PWM generator resources.
138   *
139   * <p>Supply the initial duty-cycle to output in order to avoid a glitch when first starting.
140   *
141   * <p>The resolution of the duty cycle is 8-bit for low frequencies (1kHz or less) but is reduced
142   * the higher the frequency of the PWM signal is.
143   *
144   * @param initialDutyCycle The duty-cycle to start generating. [0..1]
145   */
146  public void enablePWM(double initialDutyCycle) {
147    if (m_pwmGenerator != invalidPwmGenerator) {
148      return;
149    }
150    m_pwmGenerator = DIOJNI.allocateDigitalPWM();
151    DIOJNI.setDigitalPWMDutyCycle(m_pwmGenerator, initialDutyCycle);
152    DIOJNI.setDigitalPWMOutputChannel(m_pwmGenerator, m_channel);
153  }
154
155  /**
156   * Change this line from a PWM output back to a static Digital Output line.
157   *
158   * <p>Free up one of the 6 DO PWM generator resources that were in use.
159   */
160  public void disablePWM() {
161    if (m_pwmGenerator == invalidPwmGenerator) {
162      return;
163    }
164    // Disable the output by routing to a dead bit.
165    DIOJNI.setDigitalPWMOutputChannel(m_pwmGenerator, SensorUtil.kDigitalChannels);
166    DIOJNI.freeDigitalPWM(m_pwmGenerator);
167    m_pwmGenerator = invalidPwmGenerator;
168  }
169
170  /**
171   * Change the duty-cycle that is being generated on the line.
172   *
173   * <p>The resolution of the duty cycle is 8-bit for low frequencies (1kHz or less) but is reduced
174   * the higher the frequency of the PWM signal is.
175   *
176   * @param dutyCycle The duty-cycle to change to. [0..1]
177   */
178  public void updateDutyCycle(double dutyCycle) {
179    if (m_pwmGenerator == invalidPwmGenerator) {
180      return;
181    }
182    DIOJNI.setDigitalPWMDutyCycle(m_pwmGenerator, dutyCycle);
183  }
184
185  /**
186   * Indicates this input is used by a simulated device.
187   *
188   * @param device simulated device handle
189   */
190  public void setSimDevice(SimDevice device) {
191    DIOJNI.setDIOSimDevice(m_handle, device.getNativeHandle());
192  }
193
194  @Override
195  public void initSendable(SendableBuilder builder) {
196    builder.setSmartDashboardType("Digital Output");
197    builder.addBooleanProperty("Value", this::get, this::set);
198  }
199}