001/*
002 * Copyright (c) 2019-2021 REV Robotics
003 *
004 * Redistribution and use in source and binary forms, with or without
005 * modification, are permitted provided that the following conditions are met:
006 *
007 * 1. Redistributions of source code must retain the above copyright notice,
008 *    this list of conditions and the following disclaimer.
009 * 2. Redistributions in binary form must reproduce the above copyright
010 *    notice, this list of conditions and the following disclaimer in the
011 *    documentation and/or other materials provided with the distribution.
012 * 3. Neither the name of REV Robotics nor the names of its
013 *    contributors may be used to endorse or promote products derived from
014 *    this software without specific prior written permission.
015 *
016 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
017 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
018 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
019 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
020 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
021 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
022 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
023 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
024 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
025 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
026 * POSSIBILITY OF SUCH DAMAGE.
027 */
028
029package com.revrobotics;
030
031import edu.wpi.first.hal.SimDevice;
032import edu.wpi.first.hal.SimDouble;
033import edu.wpi.first.wpilibj.DriverStation;
034import edu.wpi.first.wpilibj.I2C;
035import edu.wpi.first.wpilibj.util.Color;
036
037/** REV Robotics Color Sensor V3 */
038public class ColorSensorV3 {
039  private static final byte kAddress = 0x52;
040  private static final byte kPartID = (byte) 0xC2;
041
042  // This is a transformation matrix given by the chip
043  // manufacturer to transform the raw RGB to CIE XYZ
044  // private static final double Cmatrix[] = {
045  //    0.048112847, 0.289453437, -0.084950826,
046  //    0.030754752, 0.339680186, -0.071569905,
047  //    -0.093947499, 0.072838494,  0.34024948
048  // };
049
050  private I2C m_i2c;
051  private SimDevice m_simDevice;
052  private SimDouble m_simR, m_simG, m_simB, m_simIR, m_simProx;
053
054  public static class RawColor {
055    public RawColor(int r, int g, int b, int _ir) {
056      red = r;
057      green = g;
058      blue = b;
059      ir = _ir;
060    }
061
062    public int red;
063    public int green;
064    public int blue;
065    public int ir;
066  }
067
068  /**
069   * Constructs a ColorSensor.
070   *
071   * @param port The I2C port the color sensor is attached to
072   */
073  public ColorSensorV3(I2C.Port port) {
074    m_i2c = new I2C(port, kAddress);
075
076    m_simDevice = SimDevice.create("REV Color Sensor V3", port.value, kAddress);
077    if (m_simDevice != null) {
078      m_simR = m_simDevice.createDouble("Red", false, 0.0);
079      m_simG = m_simDevice.createDouble("Green", false, 0.0);
080      m_simB = m_simDevice.createDouble("Blue", false, 0.0);
081      m_simIR = m_simDevice.createDouble("IR", false, 0.0);
082      m_simProx = m_simDevice.createDouble("Proximity", false, 0.0);
083      return;
084    }
085
086    // Only report errors if this is not a simulation
087    if (!checkDeviceID(m_simDevice == null)) {
088      return;
089    }
090
091    initializeDevice();
092
093    // Clear the reset flag
094    hasReset();
095  }
096
097  public enum Register {
098    kMainCtrl(0x00),
099    kProximitySensorLED(0x01),
100    kProximitySensorPulses(0x02),
101    kProximitySensorRate(0x03),
102    kLightSensorMeasurementRate(0x04),
103    kLightSensorGain(0x05),
104    kPartID(0x06),
105    kMainStatus(0x07),
106    kProximityData(0x08),
107    kDataInfrared(0x0A),
108    kDataGreen(0x0D),
109    kDataBlue(0x10),
110    kDataRed(0x13);
111
112    public final byte bVal;
113
114    Register(int i) {
115      this.bVal = (byte) i;
116    }
117  }
118
119  public enum MainControl {
120    kRGBMode(0x04), /* If bit is set to 1, color channels are activated */
121    kLightSensorEnable(0x02), /* Enable light sensor */
122    kProximitySensorEnable(0x01), /* Proximity sensor active */
123    OFF(0x00); /* Nothing on */
124
125    public final byte bVal;
126
127    MainControl(int i) {
128      this.bVal = (byte) i;
129    }
130  }
131
132  public enum GainFactor {
133    kGain1x(0x00),
134    kGain3x(0x01),
135    kGain6x(0x02),
136    kGain9x(0x03),
137    kGain18x(0x04);
138
139    public final byte bVal;
140
141    GainFactor(int i) {
142      this.bVal = (byte) i;
143    }
144  }
145
146  public enum LEDCurrent {
147    kPulse2mA(0x00),
148    kPulse5mA(0x01),
149    kPulse10mA(0x02),
150    kPulse25mA(0x03),
151    kPulse50mA(0x04),
152    kPulse75mA(0x05),
153    kPulse100mA(0x06), /* default value */
154    kPulse125mA(0x07);
155
156    public final byte bVal;
157
158    LEDCurrent(int i) {
159      this.bVal = (byte) i;
160    }
161  }
162
163  public enum LEDPulseFrequency {
164    kFreq60kHz(0x18), /* default value */
165    kFreq70kHz(0x40),
166    kFreq80kHz(0x28),
167    kFreq90kHz(0x30),
168    kFreq100kHz(0x38);
169
170    public final byte bVal;
171
172    LEDPulseFrequency(int i) {
173      this.bVal = (byte) i;
174    }
175  }
176
177  public enum ProximitySensorResolution {
178    kProxRes8bit(0x00),
179    kProxRes9bit(0x08),
180    kProxRes10bit(0x10),
181    kProxRes11bit(0x18);
182
183    public final byte bVal;
184
185    ProximitySensorResolution(int i) {
186      this.bVal = (byte) i;
187    }
188  }
189
190  public enum ProximitySensorMeasurementRate {
191    kProxRate6ms(0x01),
192    kProxRate12ms(0x02),
193    kProxRate25ms(0x03),
194    kProxRate50ms(0x04),
195    kProxRate100ms(0x05), /* default value */
196    kProxRate200ms(0x06),
197    kProxRate400ms(0x07);
198
199    public final byte bVal;
200
201    ProximitySensorMeasurementRate(int i) {
202      this.bVal = (byte) i;
203    }
204  }
205
206  public enum ColorSensorResolution {
207    kColorSensorRes20bit(0x00),
208    kColorSensorRes19bit(0x10),
209    kColorSensorRes18bit(0x20),
210    kColorSensorRes17bit(0x30),
211    kColorSensorRes16bit(0x40),
212    kColorSensorRes13bit(0x50);
213
214    public final byte bVal;
215
216    ColorSensorResolution(int i) {
217      this.bVal = (byte) i;
218    }
219  }
220
221  public enum ColorSensorMeasurementRate {
222    kColorRate25ms(0),
223    kColorRate50ms(1),
224    kColorRate100ms(2),
225    kColorRate200ms(3),
226    kColorRate500ms(4),
227    kColorRate1000ms(5),
228    kColorRate2000ms(7);
229
230    public final byte bVal;
231
232    ColorSensorMeasurementRate(int i) {
233      this.bVal = (byte) i;
234    }
235  };
236
237  /**
238   * Configure the the IR LED used by the proximity sensor.
239   *
240   * <p>These settings are only needed for advanced users, the defaults will work fine for most
241   * teams. Consult the APDS-9151 for more information on these configuration settings and how they
242   * will affect proximity sensor measurements.
243   *
244   * @param freq The pulse modulation frequency for the proximity sensor LED
245   * @param curr The pulse current for the proximity sensor LED
246   * @param pulses The number of pulses per measurement of the proximity sensor LED (0-255)
247   */
248  public void configureProximitySensorLED(LEDPulseFrequency freq, LEDCurrent curr, int pulses) {
249    write8(Register.kProximitySensorLED, freq.bVal | curr.bVal);
250    write8(Register.kProximitySensorPulses, (byte) pulses);
251  }
252
253  /**
254   * Configure the proximity sensor.
255   *
256   * <p>These settings are only needed for advanced users, the defaults will work fine for most
257   * teams. Consult the APDS-9151 for more information on these configuration settings and how they
258   * will affect proximity sensor measurements.
259   *
260   * @param res Bit resolution output by the proximity sensor ADC.
261   * @param rate Measurement rate of the proximity sensor
262   */
263  public void configureProximitySensor(
264      ProximitySensorResolution res, ProximitySensorMeasurementRate rate) {
265    write8(Register.kProximitySensorRate, res.bVal | rate.bVal);
266  }
267
268  /**
269   * Configure the color sensor.
270   *
271   * <p>These settings are only needed for advanced users, the defaults will work fine for most
272   * teams. Consult the APDS-9151 for more information on these configuration settings and how they
273   * will affect color sensor measurements.
274   *
275   * @param res Bit resolution output by the respective light sensor ADCs
276   * @param rate Measurement rate of the light sensor
277   * @param gain Gain factor applied to light sensor (color) outputs
278   */
279  public void configureColorSensor(
280      ColorSensorResolution res, ColorSensorMeasurementRate rate, GainFactor gain) {
281    write8(Register.kLightSensorMeasurementRate, res.bVal | rate.bVal);
282    write8(Register.kLightSensorGain, gain.bVal);
283  }
284
285  /**
286   * Get the most likely color. Works best when within 2 inches and perpendicular to surface of
287   * interest.
288   *
289   * @return Color enum of the most likely color, including unknown if the minimum threshold is not
290   *     met
291   */
292  public Color getColor() {
293    double r = (double) getRed();
294    double g = (double) getGreen();
295    double b = (double) getBlue();
296    double mag = r + g + b;
297    return new Color(r / mag, g / mag, b / mag);
298  }
299
300  /**
301   * Get the raw proximity value from the sensor ADC (11 bit). This value is largest when an object
302   * is close to the sensor and smallest when far away.
303   *
304   * @return Proximity measurement value, ranging from 0 to 2047
305   */
306  public int getProximity() {
307    if (m_simDevice != null) {
308      return (int) m_simProx.get();
309    }
310    return read11BitRegister(Register.kProximityData);
311  }
312
313  /**
314   * Get the raw color values from their respective ADCs (20-bit).
315   *
316   * @return ColorValues struct containing red, green, blue and IR values
317   */
318  public RawColor getRawColor() {
319    return new RawColor(getRed(), getGreen(), getBlue(), getIR());
320  }
321
322  /**
323   * Get the raw color value from the red ADC
324   *
325   * @return Red ADC value
326   */
327  public int getRed() {
328    if (m_simDevice != null) {
329      return (int) m_simR.get();
330    }
331    return read20BitRegister(Register.kDataRed);
332  }
333
334  /**
335   * Get the raw color value from the green ADC
336   *
337   * @return Green ADC value
338   */
339  public int getGreen() {
340    if (m_simDevice != null) {
341      return (int) m_simG.get();
342    }
343    return read20BitRegister(Register.kDataGreen);
344  }
345
346  /**
347   * Get the raw color value from the blue ADC
348   *
349   * @return Blue ADC value
350   */
351  public int getBlue() {
352    if (m_simDevice != null) {
353      return (int) m_simB.get();
354    }
355    return read20BitRegister(Register.kDataBlue);
356  }
357
358  /**
359   * Get the raw color value from the IR ADC
360   *
361   * @return IR ADC value
362   */
363  public int getIR() {
364    if (m_simDevice != null) {
365      return (int) m_simIR.get();
366    }
367    return read20BitRegister(Register.kDataInfrared);
368  }
369
370  // This is a transformation matrix given by the chip
371  // manufacturer to transform the raw RGB to CIE XYZ
372  private final double Cmatrix[] = {
373    0.048112847,
374    0.289453437,
375    -0.084950826,
376    -0.030754752,
377    0.339680186,
378    -0.071569905,
379    -0.093947499,
380    0.072838494,
381    0.34024948
382  };
383
384  /**
385   * Get the color converted to CIE XYZ color space using factory calibrated constants.
386   *
387   * <p>https://en.wikipedia.org/wiki/CIE_1931_color_space
388   *
389   * @return CIEColor value from sensor
390   */
391  public CIEColor getCIEColor() {
392    RawColor raw = getRawColor();
393    return new CIEColor(
394        Cmatrix[0] * raw.red + Cmatrix[1] * raw.green + Cmatrix[2] * raw.blue,
395        Cmatrix[3] * raw.red + Cmatrix[4] * raw.green + Cmatrix[5] * raw.blue,
396        Cmatrix[6] * raw.red + Cmatrix[7] * raw.green + Cmatrix[8] * raw.blue);
397  }
398
399  /**
400   * Indicates if the device reset. Based on the power on status flag in the status register. Per
401   * the datasheet:
402   *
403   * <p>Part went through a power-up event, either because the part was turned on or because there
404   * was power supply voltage disturbance (default at first register read).
405   *
406   * <p>This flag is self clearing
407   *
408   * @return true if the device was reset
409   */
410  public boolean hasReset() {
411    if (m_simDevice != null) {
412      return false;
413    }
414
415    byte[] raw = new byte[1];
416
417    m_i2c.read(Register.kMainStatus.bVal, 1, raw);
418
419    return (raw[0] & 0x20) != 0;
420  }
421
422  /**
423   * Indicates if the device can currently be communicated with.
424   *
425   * @return true if the device is currently connected and responsive
426   */
427  public boolean isConnected() {
428    if (m_simDevice != null) {
429      return true;
430    }
431
432    return checkDeviceID(false);
433  }
434
435  private boolean checkDeviceID(boolean reportErrors) {
436    byte[] raw = new byte[1];
437    if (m_i2c.read(Register.kPartID.bVal, 1, raw)) {
438      if (reportErrors) {
439        DriverStation.reportError("Could not find REV color sensor", false);
440      }
441      return false;
442    }
443
444    if (kPartID != raw[0]) {
445      if (reportErrors) {
446        DriverStation.reportError(
447            "Unknown device found with same I2C address as REV color sensor", false);
448      }
449      return false;
450    }
451
452    return true;
453  }
454
455  private void initializeDevice() {
456    write8(
457        Register.kMainCtrl,
458        MainControl.kRGBMode.bVal
459            | MainControl.kLightSensorEnable.bVal
460            | MainControl.kProximitySensorEnable.bVal);
461
462    write8(
463        Register.kProximitySensorRate,
464        ProximitySensorResolution.kProxRes11bit.bVal
465            | ProximitySensorMeasurementRate.kProxRate100ms.bVal);
466
467    write8(Register.kProximitySensorPulses, (byte) 32);
468  }
469
470  private int read11BitRegister(Register reg) {
471    byte[] raw = new byte[2];
472
473    m_i2c.read(reg.bVal, 2, raw);
474
475    return (((int) raw[0] & 0xFF) | (((int) raw[1] & 0xFF) << 8)) & 0x7FF;
476  }
477
478  private int read20BitRegister(Register reg) {
479    byte[] raw = new byte[3];
480
481    m_i2c.read(reg.bVal, 3, raw);
482
483    return (((int) raw[0] & 0xFF) | (((int) raw[1] & 0xFF) << 8) | (((int) raw[2] & 0xFF) << 16))
484        & 0x03FFFF;
485  }
486
487  private void write8(Register reg, int data) {
488    m_i2c.write(reg.bVal, data);
489  }
490}