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