Anthony PierceCameron Mixon-tinsleyJason Zou
Published © GPL3+

Sky Watcher

A simple device that can remotely tell the user at any time if the night sky is clear and suitable for viewing.

BeginnerFull instructions provided1 hour202
Sky Watcher

Things used in this project

Hardware components

Photon 2
Particle Photon 2
×3
Adafruit TSL2591 High Dynamic Range Digital Light Sensor
×3
Adafruit AGS02MA I2C TVOC Gas Sensor
×3
Adafruit Tilt Ball Switch
×3
Breadboard (generic)
Breadboard (generic)
×3
Resistor 4.7k
×6

Software apps and online services

Particle Build Web IDE
Particle Build Web IDE
ThingSpeak API
ThingSpeak API

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

SkyWatcher Breadboard Diagram

A simple breadboard diagram of the circuit. Note that because there was no Fritzing part available for the AGS02MA gas sensor, a generic temperature and humidity sensor was used in its place as a representation. From left to right as seen below the pins on the actual gas sensor are VDD, SDA, GND, and SCL.

Code

SkyWatcher

C/C++
This is the main code that runs the board. It gathers and manipulates all the data from the sensors, publishes that data at appropriate intervals and with appropriate conditions, and subscribes to data from the other devices in the group. That data includes highly adjustable luminosity readings suitable for a wide variety of uses including nighttime use, as well as TVOC data and tilt sensing for shutoff features. Note that this code is for the device 1 which publishes lux dataset 1 and subscribes to the other two. For the other two devices this is tweaked respectively.
// This #include statement was automatically added by the Particle IDE.
#include "Adafruit_I2CDevice.h"
#include "Adafruit_TSL2591.h"
#include "Adafruit_Sensor.h"
#include "Wire.h"

// I2C address of the TVOC sensor
#define TVOC_SENSOR_ADDR 0x1A

// Register addresses for gas resistance and ppb data
#define GAS_RESISTANCE_REGISTER 0x20
#define PPB_DATA_REGISTER 0x00

// Variables to store sensor data plus other variable initialization
uint16_t gasResistance = 0;
uint16_t ppbData = 0;
int digitalVal = 0;
uint16_t x = 0;

//LED pin variable for visually indicating tilt
const int ledPin = D7;

// Time interval between readings (in milliseconds) and time interval between publishing webhooks
const unsigned long readingInterval = 1500;
const unsigned long webhookInterval = 45000;

//Device and program setup
Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591); // pass in a number for the sensor identifier (for your use later)

// Full options for gain and integration time, selectable by uncommenting
void configureTSL() {
  //tsl.setGain(TSL2591_GAIN_LOW);    // 1x gain (bright light)
    tsl.setGain(TSL2591_GAIN_MED);      // 25x gain
  //tsl.setGain(TSL2591_GAIN_HIGH);   // 428x gain
  
  // Changing the integration time gives you a longer time over which to sense light
  // longer timelines are slower, but are good in very low light situtations!
  //tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);  // shortest integration time (bright light)
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS);
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS);
    tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS);
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS);  // longest integration time (dim light)
  
}

// Variable to store the last time a reading or webhook was done
unsigned long lastReadingTime = 0;
unsigned long lastWebhookTime = 0;

// Initialize subscribe values (part of inter-device communication)
float lux2 = 0;
float lux3 = 0;
float luxavg = 0;

// Defining subscription handler function to compare the event name being subscribed to with the two possible events (in this case all but device 1, because we're using device 1) so that it can update the correct variable.
void myHandler(const char *event, const char *data) {

    if (strcmp(event, "Luminosity (device 2), lux") == 0)
    {
        lux2 == atof(data);
    }
    else if (strcmp(event, "Luminosity (device 3), lux") == 0)
    {
        lux3 == atof(data);
    }
}

void setup() {
  // Set up led indicator
  pinMode(ledPin, OUTPUT);
  // Set up tilt sensor pin with internal pull-up resistor
  pinMode(D2, INPUT_PULLUP);
  tsl.begin();
  configureTSL();
  Wire.begin();
  
   //A pair of subscribe functions which gather data from each of the two       other devices (incoming communication of most recent lux measurement)
   Particle.subscribe("Luminosity (device 2), lux", myHandler);
   Particle.subscribe("Luminosity (device 3), lux", myHandler);
}

void loop() {
  // Get the current time
  unsigned long currentTime = millis();

  // Check if the specified interval has passed since the last reading
  if (currentTime - lastReadingTime >= readingInterval) {
    // Update the last reading time
    lastReadingTime = currentTime;
    
    // Set tilt sensor variable according to current reading
    digitalVal = digitalRead(D2);
    // Publish value to Particle cloud
    Particle.publish("TiltVal", String(digitalVal), PRIVATE);
    // Conditional to only run data acquisition when tilt sensor is not tilted
    if (digitalVal == 0) {
        // Turn tilt indicator LED off if it's on
        digitalWrite(ledPin, LOW);
        
        // Read raw light sensor data
        uint32_t lum = tsl.getFullLuminosity();
        uint16_t ch1, ch0;
         ch1 = lum >> 16;
         ch0 = lum & 0xFFFF;
         
         
        // Calculate lux
        float lux1 = tsl.calculateLux(ch0, ch1);
        
        // Publish luminosity data to Particle cloud for monitoring and for             sharing with other devices.
        Particle.publish("Luminosity (device 1), lux", String(lux1), PRIVATE);
        
        // Conditional for whether or not it's been long enough for another             webhook
        if (currentTime - lastWebhookTime >= webhookInterval) {
            // update last webhook time 
            lastWebhookTime = currentTime;
            String data = String(lux1);
            
            // Publish luminosity data to webhook for visualization 
            Particle.publish("lux1", data, PRIVATE);
            // Check that each individual lux value is nonzero for this cycle                and, if so, average them, using a webhook to visualize this                  along all the individual values
            if (lux1 != 0 && lux2 != 0 && lux3 != 0) {
                luxavg = (lux1 + lux2 + lux3)/3;
                Particle.publish ("lux(average)", String(luxavg), PRIVATE);
            }
        }
      
        // Request gas resistance data
        Wire.beginTransmission(TVOC_SENSOR_ADDR);
        Wire.write(GAS_RESISTANCE_REGISTER);
        Wire.endTransmission();
        
        // Request 2 bytes of gas resistance data
        Wire.requestFrom(TVOC_SENSOR_ADDR, 2);
        if (Wire.available() >= 2) {
          gasResistance = Wire.read() << 8 | Wire.read();
          
          // Gas resistance conversion to kilo-ohms (stated unit measured)
          float gasResistanceKiloOhms = gasResistance * 0.1;
    
          // Publish gas resistance data to Particle Cloud
          Particle.publish("Gas Resistance, KiloOhms", String(gasResistanceKiloOhms), PRIVATE);
        }
    
        // Request TVOC ppb data
        Wire.beginTransmission(TVOC_SENSOR_ADDR);
        Wire.write(PPB_DATA_REGISTER);
        Wire.endTransmission();
    
        // Request 2 bytes of ppb data
        Wire.requestFrom(TVOC_SENSOR_ADDR, 2);
        if (Wire.available() >= 2) {
          ppbData = Wire.read() << 8 | Wire.read();
          
          // Publish ppb data to Particle Cloud
          Particle.publish("TVOC ppb", String(ppbData), PRIVATE);
        }
    } else {
        digitalWrite(ledPin, HIGH); //Do nothing except turn on the LED for                                        this cycle when the tilt switch is on
    }
  }
}

Adafruit_I2CDevice.cpp

C/C++
Part of Adafruit's I2C library, containing various I2C functions.
#include "Adafruit_I2CDevice.h"

//#define DEBUG_SERIAL Serial

/*!
 *    @brief  Create an I2C device at a given address
 *    @param  addr The 7-bit I2C address for the device
 *    @param  theWire The I2C bus to use, defaults to &Wire
 */
Adafruit_I2CDevice::Adafruit_I2CDevice(uint8_t addr, TwoWire *theWire) {
  _addr = addr;
  _wire = theWire;
  _begun = false;
#ifdef ARDUINO_ARCH_SAMD
  _maxBufferSize = 250; // as defined in Wire.h's RingBuffer
#elif defined(ESP32)
  _maxBufferSize = I2C_BUFFER_LENGTH;
#else
  _maxBufferSize = 32;
#endif
}

/*!
 *    @brief  Initializes and does basic address detection
 *    @param  addr_detect Whether we should attempt to detect the I2C address
 * with a scan. 99% of sensors/devices don't mind, but once in a while they
 * don't respond well to a scan!
 *    @return True if I2C initialized and a device with the addr found
 */
bool Adafruit_I2CDevice::begin(bool addr_detect) {
  _wire->begin();
  _begun = true;

  if (addr_detect) {
    return detected();
  }
  return true;
}

/*!
 *    @brief  De-initialize device, turn off the Wire interface
 */
void Adafruit_I2CDevice::end(void) {
  // Not all port implement Wire::end(), such as
  // - ESP8266
  // - AVR core without WIRE_HAS_END
  // - ESP32: end() is implemented since 2.0.1 which is latest at the moment.
  // Temporarily disable for now to give time for user to update.
#if !(defined(ESP8266) ||                                                      \
      (defined(ARDUINO_ARCH_AVR) && !defined(WIRE_HAS_END)) ||                 \
      defined(ARDUINO_ARCH_ESP32))
  _wire->end();
  _begun = false;
#endif
}

/*!
 *    @brief  Scans I2C for the address - note will give a false-positive
 *    if there's no pullups on I2C
 *    @return True if I2C initialized and a device with the addr found
 */
bool Adafruit_I2CDevice::detected(void) {
  // Init I2C if not done yet
  if (!_begun && !begin()) {
    return false;
  }

  // A basic scanner, see if it ACK's
  _wire->beginTransmission(_addr);
#ifdef DEBUG_SERIAL
  DEBUG_SERIAL.print(F("Address 0x"));
  DEBUG_SERIAL.print(_addr);
#endif
  if (_wire->endTransmission() == 0) {
#ifdef DEBUG_SERIAL
    DEBUG_SERIAL.println(F(" Detected"));
#endif
    return true;
  }
#ifdef DEBUG_SERIAL
  DEBUG_SERIAL.println(F(" Not detected"));
#endif
  return false;
}

/*!
 *    @brief  Write a buffer or two to the I2C device. Cannot be more than
 * maxBufferSize() bytes.
 *    @param  buffer Pointer to buffer of data to write. This is const to
 *            ensure the content of this buffer doesn't change.
 *    @param  len Number of bytes from buffer to write
 *    @param  prefix_buffer Pointer to optional array of data to write before
 * buffer. Cannot be more than maxBufferSize() bytes. This is const to
 *            ensure the content of this buffer doesn't change.
 *    @param  prefix_len Number of bytes from prefix buffer to write
 *    @param  stop Whether to send an I2C STOP signal on write
 *    @return True if write was successful, otherwise false.
 */
bool Adafruit_I2CDevice::write(const uint8_t *buffer, size_t len, bool stop,
                               const uint8_t *prefix_buffer,
                               size_t prefix_len) {
  if ((len + prefix_len) > maxBufferSize()) {
    // currently not guaranteed to work if more than 32 bytes!
    // we will need to find out if some platforms have larger
    // I2C buffer sizes :/
#ifdef DEBUG_SERIAL
    DEBUG_SERIAL.println(F("\tI2CDevice could not write such a large buffer"));
#endif
    return false;
  }

  _wire->beginTransmission(_addr);

  // Write the prefix data (usually an address)
  if ((prefix_len != 0) && (prefix_buffer != nullptr)) {
    if (_wire->write(prefix_buffer, prefix_len) != prefix_len) {
#ifdef DEBUG_SERIAL
      DEBUG_SERIAL.println(F("\tI2CDevice failed to write"));
#endif
      return false;
    }
  }

  // Write the data itself
  if (_wire->write(buffer, len) != len) {
#ifdef DEBUG_SERIAL
    DEBUG_SERIAL.println(F("\tI2CDevice failed to write"));
#endif
    return false;
  }

#ifdef DEBUG_SERIAL

  DEBUG_SERIAL.print(F("\tI2CWRITE @ 0x"));
  DEBUG_SERIAL.print(_addr, HEX);
  DEBUG_SERIAL.print(F(" :: "));
  if ((prefix_len != 0) && (prefix_buffer != nullptr)) {
    for (uint16_t i = 0; i < prefix_len; i++) {
      DEBUG_SERIAL.print(F("0x"));
      DEBUG_SERIAL.print(prefix_buffer[i], HEX);
      DEBUG_SERIAL.print(F(", "));
    }
  }
  for (uint16_t i = 0; i < len; i++) {
    DEBUG_SERIAL.print(F("0x"));
    DEBUG_SERIAL.print(buffer[i], HEX);
    DEBUG_SERIAL.print(F(", "));
    if (i % 32 == 31) {
      DEBUG_SERIAL.println();
    }
  }

  if (stop) {
    DEBUG_SERIAL.print("\tSTOP");
  }
#endif

  if (_wire->endTransmission(stop) == 0) {
#ifdef DEBUG_SERIAL
    DEBUG_SERIAL.println();
    // DEBUG_SERIAL.println("Sent!");
#endif
    return true;
  } else {
#ifdef DEBUG_SERIAL
    DEBUG_SERIAL.println("\tFailed to send!");
#endif
    return false;
  }
}

/*!
 *    @brief  Read from I2C into a buffer from the I2C device.
 *    Cannot be more than maxBufferSize() bytes.
 *    @param  buffer Pointer to buffer of data to read into
 *    @param  len Number of bytes from buffer to read.
 *    @param  stop Whether to send an I2C STOP signal on read
 *    @return True if read was successful, otherwise false.
 */
bool Adafruit_I2CDevice::read(uint8_t *buffer, size_t len, bool stop) {
  size_t pos = 0;
  while (pos < len) {
    size_t read_len =
        ((len - pos) > maxBufferSize()) ? maxBufferSize() : (len - pos);
    bool read_stop = (pos < (len - read_len)) ? false : stop;
    if (!_read(buffer + pos, read_len, read_stop))
      return false;
    pos += read_len;
  }
  return true;
}

bool Adafruit_I2CDevice::_read(uint8_t *buffer, size_t len, bool stop) {
#if defined(TinyWireM_h)
  size_t recv = _wire->requestFrom((uint8_t)_addr, (uint8_t)len);
#elif defined(ARDUINO_ARCH_MEGAAVR)
  size_t recv = _wire->requestFrom(_addr, len, stop);
#else
  size_t recv = _wire->requestFrom((uint8_t)_addr, (uint8_t)len, (uint8_t)stop);
#endif

  if (recv != len) {
    // Not enough data available to fulfill our obligation!
#ifdef DEBUG_SERIAL
    DEBUG_SERIAL.print(F("\tI2CDevice did not receive enough data: "));
    DEBUG_SERIAL.println(recv);
#endif
    return false;
  }

  for (uint16_t i = 0; i < len; i++) {
    buffer[i] = _wire->read();
  }

#ifdef DEBUG_SERIAL
  DEBUG_SERIAL.print(F("\tI2CREAD  @ 0x"));
  DEBUG_SERIAL.print(_addr, HEX);
  DEBUG_SERIAL.print(F(" :: "));
  for (uint16_t i = 0; i < len; i++) {
    DEBUG_SERIAL.print(F("0x"));
    DEBUG_SERIAL.print(buffer[i], HEX);
    DEBUG_SERIAL.print(F(", "));
    if (len % 32 == 31) {
      DEBUG_SERIAL.println();
    }
  }
  DEBUG_SERIAL.println();
#endif

  return true;
}

/*!
 *    @brief  Write some data, then read some data from I2C into another buffer.
 *    Cannot be more than maxBufferSize() bytes. The buffers can point to
 *    same/overlapping locations.
 *    @param  write_buffer Pointer to buffer of data to write from
 *    @param  write_len Number of bytes from buffer to write.
 *    @param  read_buffer Pointer to buffer of data to read into.
 *    @param  read_len Number of bytes from buffer to read.
 *    @param  stop Whether to send an I2C STOP signal between the write and read
 *    @return True if write & read was successful, otherwise false.
 */
bool Adafruit_I2CDevice::write_then_read(const uint8_t *write_buffer,
                                         size_t write_len, uint8_t *read_buffer,
                                         size_t read_len, bool stop) {
  if (!write(write_buffer, write_len, stop)) {
    return false;
  }

  return read(read_buffer, read_len);
}

/*!
 *    @brief  Returns the 7-bit address of this device
 *    @return The 7-bit address of this device
 */
uint8_t Adafruit_I2CDevice::address(void) { return _addr; }

/*!
 *    @brief  Change the I2C clock speed to desired (relies on
 *    underlying Wire support!
 *    @param desiredclk The desired I2C SCL frequency
 *    @return True if this platform supports changing I2C speed.
 *    Not necessarily that the speed was achieved!
 */
bool Adafruit_I2CDevice::setSpeed(uint32_t desiredclk) {
#if defined(__AVR_ATmega328__) ||                                              \
    defined(__AVR_ATmega328P__) // fix arduino core set clock
  // calculate TWBR correctly

  if ((F_CPU / 18) < desiredclk) {
#ifdef DEBUG_SERIAL
    Serial.println(F("I2C.setSpeed too high."));
#endif
    return false;
  }
  uint32_t atwbr = ((F_CPU / desiredclk) - 16) / 2;
  if (atwbr > 16320) {
#ifdef DEBUG_SERIAL
    Serial.println(F("I2C.setSpeed too low."));
#endif
    return false;
  }

  if (atwbr <= 255) {
    atwbr /= 1;
    TWSR = 0x0;
  } else if (atwbr <= 1020) {
    atwbr /= 4;
    TWSR = 0x1;
  } else if (atwbr <= 4080) {
    atwbr /= 16;
    TWSR = 0x2;
  } else { //  if (atwbr <= 16320)
    atwbr /= 64;
    TWSR = 0x3;
  }
  TWBR = atwbr;

#ifdef DEBUG_SERIAL
  Serial.print(F("TWSR prescaler = "));
  Serial.println(pow(4, TWSR));
  Serial.print(F("TWBR = "));
  Serial.println(atwbr);
#endif
  return true;
#elif (ARDUINO >= 157) && !defined(ARDUINO_STM32_FEATHER) &&                   \
    !defined(TinyWireM_h)
  _wire->setClock(desiredclk);
  return true;

#else
  (void)desiredclk;
  return false;
#endif
}

Adafruit_I2CDevice.h

C Header File
Header file for the Adafruit I2C library. All credit goes to Adafruit.
#ifndef Adafruit_I2CDevice_h
#define Adafruit_I2CDevice_h

#include <Arduino.h>
#include <Wire.h>

///< The class which defines how we will talk to this device over I2C
class Adafruit_I2CDevice {
public:
  Adafruit_I2CDevice(uint8_t addr, TwoWire *theWire = &Wire);
  uint8_t address(void);
  bool begin(bool addr_detect = true);
  void end(void);
  bool detected(void);

  bool read(uint8_t *buffer, size_t len, bool stop = true);
  bool write(const uint8_t *buffer, size_t len, bool stop = true,
             const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0);
  bool write_then_read(const uint8_t *write_buffer, size_t write_len,
                       uint8_t *read_buffer, size_t read_len,
                       bool stop = false);
  bool setSpeed(uint32_t desiredclk);

  /*!   @brief  How many bytes we can read in a transaction
   *    @return The size of the Wire receive/transmit buffer */
  size_t maxBufferSize() { return _maxBufferSize; }

private:
  uint8_t _addr;
  TwoWire *_wire;
  bool _begun;
  size_t _maxBufferSize;
  bool _read(uint8_t *buffer, size_t len, bool stop);
};

#endif // Adafruit_I2CDevice_h

Adafruit_Sensor.h

C Header File
A header file containing useful constants, structures, classes, etc. related to various common sensor types. All credit goes to Adafruit and K. Townsend.
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software< /span>
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* Update by K. Townsend (Adafruit Industries) for lighter typedefs, and
 * extended sensor support to include color, voltage and current */

#ifndef _ADAFRUIT_SENSOR_H
#define _ADAFRUIT_SENSOR_H

#ifndef ARDUINO
#include <stdint.h>
#elif ARDUINO >= 100
#include "Arduino.h"
#include "Print.h"
#else
#include "WProgram.h"
#endif

/* Constants */
#define SENSORS_GRAVITY_EARTH (9.80665F) /**< Earth's gravity in m/s^2 */
#define SENSORS_GRAVITY_MOON (1.6F)      /**< The moon's gravity in m/s^2 */
#define SENSORS_GRAVITY_SUN (275.0F)     /**< The sun's gravity in m/s^2 */
#define SENSORS_GRAVITY_STANDARD (SENSORS_GRAVITY_EARTH)
#define SENSORS_MAGFIELD_EARTH_MAX                                             \
  (60.0F) /**< Maximum magnetic field on Earth's surface */
#define SENSORS_MAGFIELD_EARTH_MIN                                             \
  (30.0F) /**< Minimum magnetic field on Earth's surface */
#define SENSORS_PRESSURE_SEALEVELHPA                                           \
  (1013.25F) /**< Average sea level pressure is 1013.25 hPa */
#define SENSORS_DPS_TO_RADS                                                    \
  (0.017453293F) /**< Degrees/s to rad/s multiplier                            \
                  */
#define SENSORS_RADS_TO_DPS                                                    \
  (57.29577793F) /**< Rad/s to degrees/s  multiplier */
#define SENSORS_GAUSS_TO_MICROTESLA                                            \
  (100) /**< Gauss to micro-Tesla multiplier */

/** Sensor types */
typedef enum {
  SENSOR_TYPE_ACCELEROMETER = (1), /**< Gravity + linear acceleration */
  SENSOR_TYPE_MAGNETIC_FIELD = (2),
  SENSOR_TYPE_ORIENTATION = (3),
  SENSOR_TYPE_GYROSCOPE = (4),
  SENSOR_TYPE_LIGHT = (5),
  SENSOR_TYPE_PRESSURE = (6),
  SENSOR_TYPE_PROXIMITY = (8),
  SENSOR_TYPE_GRAVITY = (9),
  SENSOR_TYPE_LINEAR_ACCELERATION =
      (10), /**< Acceleration not including gravity */
  SENSOR_TYPE_ROTATION_VECTOR = (11),
  SENSOR_TYPE_RELATIVE_HUMIDITY = (12),
  SENSOR_TYPE_AMBIENT_TEMPERATURE = (13),
  SENSOR_TYPE_OBJECT_TEMPERATURE = (14),
  SENSOR_TYPE_VOLTAGE = (15),
  SENSOR_TYPE_CURRENT = (16),
  SENSOR_TYPE_COLOR = (17),
  SENSOR_TYPE_TVOC = (18),
  SENSOR_TYPE_VOC_INDEX = (19),
  SENSOR_TYPE_NOX_INDEX = (20),
  SENSOR_TYPE_CO2 = (21),
  SENSOR_TYPE_ECO2 = (22),
  SENSOR_TYPE_PM10_STD = (23),
  SENSOR_TYPE_PM25_STD = (24),
  SENSOR_TYPE_PM100_STD = (25),
  SENSOR_TYPE_PM10_ENV = (26),
  SENSOR_TYPE_PM25_ENV = (27),
  SENSOR_TYPE_PM100_ENV = (28),
  SENSOR_TYPE_GAS_RESISTANCE = (29),
  SENSOR_TYPE_UNITLESS_PERCENT = (30),
  SENSOR_TYPE_ALTITUDE = (31)
} sensors_type_t;

/** struct sensors_vec_s is used to return a vector in a common format. */
typedef struct {
  union {
    float v[3]; ///< 3D vector elements
    struct {
      float x; ///< X component of vector
      float y; ///< Y component of vector
      float z; ///< Z component of vector
    };         ///< Struct for holding XYZ component
    /* Orientation sensors */
    struct {
      float roll; /**< Rotation around the longitudinal axis (the plane body, 'X
                     axis'). Roll is positive and increasing when moving
                     downward. -90 degrees <= roll <= 90 degrees */
      float pitch;   /**< Rotation around the lateral axis (the wing span, 'Y
                        axis'). Pitch is positive and increasing when moving
                        upwards. -180 degrees <= pitch <= 180 degrees) */
      float heading; /**< Angle between the longitudinal axis (the plane body)
                        and magnetic north, measured clockwise when viewing from
                        the top of the device. 0-359 degrees */
    };               ///< Struct for holding roll/pitch/heading
  };                 ///< Union that can hold 3D vector array, XYZ components or
                     ///< roll/pitch/heading
  int8_t status;     ///< Status byte
  uint8_t reserved[3]; ///< Reserved
} sensors_vec_t;

/** struct sensors_color_s is used to return color data in a common format. */
typedef struct {
  union {
    float c[3]; ///< Raw 3-element data
    /* RGB color space */
    struct {
      float r;   /**< Red component */
      float g;   /**< Green component */
      float b;   /**< Blue component */
    };           ///< RGB data in floating point notation
  };             ///< Union of various ways to describe RGB colorspace
  uint32_t rgba; /**< 24-bit RGBA value */
} sensors_color_t;

/* Sensor event (36 bytes) */
/** struct sensor_event_s is used to provide a single sensor event in a common
 * format. */
typedef struct {
  int32_t version;   /**< must be sizeof(struct sensors_event_t) */
  int32_t sensor_id; /**< unique sensor identifier */
  int32_t type;      /**< sensor type */
  int32_t reserved0; /**< reserved */
  int32_t timestamp; /**< time is in milliseconds */
  union {
    float data[4];              ///< Raw data */
    sensors_vec_t acceleration; /**< acceleration values are in meter per second
                                   per second (m/s^2) */
    sensors_vec_t
        magnetic; /**< magnetic vector values are in micro-Tesla (uT) */
    sensors_vec_t orientation; /**< orientation values are in degrees */
    sensors_vec_t gyro;        /**< gyroscope values are in rad/s */
    float temperature; /**< temperature is in degrees centigrade (Celsius) */
    float distance;    /**< distance in centimeters */
    float light;       /**< light in SI lux units */
    float pressure;    /**< pressure in hectopascal (hPa) */
    float relative_humidity; /**< relative humidity in percent */
    float current;           /**< current in milliamps (mA) */
    float voltage;           /**< voltage in volts (V) */
    float tvoc;              /**< Total Volatile Organic Compounds, in ppb */
    float voc_index; /**< VOC (Volatile Organic Compound) index where 100 is
                          normal (unitless) */
    float nox_index; /**< NOx (Nitrogen Oxides) index where 100 is normal
                          (unitless) */
    float CO2;       /**< Measured CO2 in parts per million (ppm) */
    float eCO2;      /**< equivalent/estimated CO2 in parts per million (ppm
                        estimated from some other measurement) */
    float pm10_std;  /**< Standard Particulate Matter <=1.0 in parts per million
                        (ppm) */
    float pm25_std;  /**< Standard Particulate Matter <=2.5 in parts per million
                        (ppm) */
    float pm100_std; /**< Standard Particulate Matter <=10.0 in parts per
                        million (ppm) */
    float pm10_env;  /**< Environmental Particulate Matter <=1.0 in parts per
                        million (ppm) */
    float pm25_env;  /**< Environmental Particulate Matter <=2.5 in parts per
                        million (ppm) */
    float pm100_env; /**< Environmental Particulate Matter <=10.0 in parts per
                        million (ppm) */
    float gas_resistance;   /**< Proportional to the amount of VOC particles in
                               the air (Ohms) */
    float unitless_percent; /**<Percentage, unit-less (%) */
    sensors_color_t color;  /**< color in RGB component values */
    float altitude; /**< Distance between a reference datum and a point or
                       object, in meters. */
  };                ///< Union for the wide ranges of data we can carry
} sensors_event_t;

/* Sensor details (40 bytes) */
/** struct sensor_s is used to describe basic information about a specific
 * sensor. */
typedef struct {
  char name[12];     /**< sensor name */
  int32_t version;   /**< version of the hardware + driver */
  int32_t sensor_id; /**< unique sensor identifier */
  int32_t type;      /**< this sensor's type (ex. SENSOR_TYPE_LIGHT) */
  float max_value;   /**< maximum value of this sensor's value in SI units */
  float min_value;   /**< minimum value of this sensor's value in SI units */
  float resolution; /**< smallest difference between two values reported by this
                       sensor */
  int32_t min_delay; /**< min delay in microseconds between events. zero = not a
                        constant rate */
} sensor_t;

/** @brief Common sensor interface to unify various sensors.
 * Intentionally modeled after sensors.h in the Android API:
 * https://github.com/android/platform_hardware_libhardware/blob/master/include/hardware/sensors.h
 */
class Adafruit_Sensor {
public:
  // Constructor(s)
  Adafruit_Sensor() {}
  virtual ~Adafruit_Sensor() {}

  // These must be defined by the subclass

  /*! @brief Whether we should automatically change the range (if possible) for
     higher precision
      @param enabled True if we will try to autorange */
  virtual void enableAutoRange(bool enabled) {
    (void)enabled; /* suppress unused warning */
  };

  /*! @brief Get the latest sensor event
      @returns True if able to fetch an event */
  virtual bool getEvent(sensors_event_t *) = 0;
  /*! @brief Get info about the sensor itself */
  virtual void getSensor(sensor_t *) = 0;

  void printSensorDetails(void);
};

#endif

Adafruit_TSL2591.cpp

C/C++
Part of the Adafruit library for the TSL2591, the primary sensor which our project relies on. This file contains many useful functions for obtaining and manipulating luminosity data and configuring the sensor.
/**************************************************************************/
/*!
    @file     Adafruit_TSL2591.cpp
    @author   KT0WN (adafruit.com)

    This is a library for the Adafruit TSL2591 breakout board
    This library works with the Adafruit TSL2591 breakout
    ----> https://www.adafruit.com/products/1980

    Check out the links above for our tutorials and wiring diagrams
    These chips use I2C to communicate

    Adafruit invests time and resources providing this open source code,
    please support Adafruit and open-source hardware by purchasing
    products from Adafruit!

    @section LICENSE

    Software License Agreement (BSD License)

    Copyright (c) 2014 Adafruit Industries
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:
    1. Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
    3. Neither the name of the copyright holders nor the
    names of its contributors may be used to endorse or promote products
    derived from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**************************************************************************/

#include "Adafruit_TSL2591.h"
#include <stdlib.h>

/**************************************************************************/
/*!
    @brief  Instantiates a new Adafruit TSL2591 class
    @param  sensorID An optional ID # so you can track this sensor, it will tag
   sensorEvents you create.
*/
/**************************************************************************/
Adafruit_TSL2591::Adafruit_TSL2591(int32_t sensorID) {
  _initialized = false;
  _integration = TSL2591_INTEGRATIONTIME_100MS;
  _gain = TSL2591_GAIN_MED;
  _sensorID = sensorID;

  // we cant do wire initialization till later, because we havent loaded Wire
  // yet
}

Adafruit_TSL2591::~Adafruit_TSL2591() {
  if (i2c_dev)
    delete i2c_dev;
}

/**************************************************************************/
/*!
    @brief   Setups the I2C interface and hardware, identifies if chip is found
    @param   theWire a reference to TwoWire instance
    @param   addr The I2C adress of the sensor (Default 0x29)
    @returns True if a TSL2591 is found, false on any failure
*/
/**************************************************************************/
boolean Adafruit_TSL2591::begin(TwoWire *theWire, uint8_t addr) {
  if (i2c_dev)
    delete i2c_dev;
  i2c_dev = new Adafruit_I2CDevice(addr, theWire);
  if (!i2c_dev->begin())
    return false;

  /*
  for (uint8_t i=0; i<0x20; i++)
  {
    uint8_t id = read8(0x12);
    Serial.print("$"); Serial.print(i, HEX);
    Serial.print(" = 0x"); Serial.println(read8(i), HEX);
  }
  */

  uint8_t id = read8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_DEVICE_ID);
  if (id != 0x50) {
    return false;
  }
  // Serial.println("Found Adafruit_TSL2591");

  _initialized = true;

  // Set default integration time and gain
  setTiming(_integration);
  setGain(_gain);

  // Note: by default, the device is in power down mode on bootup
  disable();

  return true;
}
/**************************************************************************/
/*!
    @brief   Setups the I2C interface and hardware, identifies if chip is found
    @param   addr The I2C adress of the sensor (Default 0x29)
    @returns True if a TSL2591 is found, false on any failure
*/
/**************************************************************************/
boolean Adafruit_TSL2591::begin(uint8_t addr) { return begin(&Wire, addr); }

/**************************************************************************/
/*!
    @brief  Enables the chip, so it's ready to take readings
*/
/**************************************************************************/
void Adafruit_TSL2591::enable(void) {
  if (!_initialized) {
    if (!begin()) {
      return;
    }
  }

  // Enable the device by setting the control bit to 0x01
  write8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE,
         TSL2591_ENABLE_POWERON | TSL2591_ENABLE_AEN | TSL2591_ENABLE_AIEN |
             TSL2591_ENABLE_NPIEN);
}

/**************************************************************************/
/*!
    @brief Disables the chip, so it's in power down mode
*/
/**************************************************************************/
void Adafruit_TSL2591::disable(void) {
  if (!_initialized) {
    if (!begin()) {
      return;
    }
  }

  // Disable the device by setting the control bit to 0x00
  write8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE,
         TSL2591_ENABLE_POWEROFF);
}

/************************************************************************/
/*!
    @brief  Setter for sensor light gain
    @param  gain {@link tsl2591Gain_t} gain value
*/
/**************************************************************************/
void Adafruit_TSL2591::setGain(tsl2591Gain_t gain) {
  if (!_initialized) {
    if (!begin()) {
      return;
    }
  }

  enable();
  _gain = gain;
  write8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL, _integration | _gain);
  disable();
}

/************************************************************************/
/*!
    @brief  Getter for sensor light gain
    @returns {@link tsl2591Gain_t} gain value
*/
/**************************************************************************/
tsl2591Gain_t Adafruit_TSL2591::getGain() { return _gain; }

/************************************************************************/
/*!
    @brief  Setter for sensor integration time setting
    @param integration {@link tsl2591IntegrationTime_t} integration time setting
*/
/**************************************************************************/
void Adafruit_TSL2591::setTiming(tsl2591IntegrationTime_t integration) {
  if (!_initialized) {
    if (!begin()) {
      return;
    }
  }

  enable();
  _integration = integration;
  write8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL, _integration | _gain);
  disable();
}

/************************************************************************/
/*!
    @brief  Getter for sensor integration time setting
    @returns {@link tsl2591IntegrationTime_t} integration time
*/
/**************************************************************************/
tsl2591IntegrationTime_t Adafruit_TSL2591::getTiming() { return _integration; }

/************************************************************************/
/*!
    @brief  Calculates the visible Lux based on the two light sensors
    @param  ch0 Data from channel 0 (IR+Visible)
    @param  ch1 Data from channel 1 (IR)
    @returns Lux, based on AMS coefficients (or < 0 if overflow)
*/
/**************************************************************************/
float Adafruit_TSL2591::calculateLux(uint16_t ch0, uint16_t ch1) {
  float atime, again;
  float cpl, lux;

  // Check for overflow conditions first
  if ((ch0 == 0xFFFF) | (ch1 == 0xFFFF)) {
    // Signal an overflow
    return -1;
  }

  // Note: This algorithm is based on preliminary coefficients
  // provided by AMS and may need to be updated in the future

  switch (_integration) {
  case TSL2591_INTEGRATIONTIME_100MS:
    atime = 100.0F;
    break;
  case TSL2591_INTEGRATIONTIME_200MS:
    atime = 200.0F;
    break;
  case TSL2591_INTEGRATIONTIME_300MS:
    atime = 300.0F;
    break;
  case TSL2591_INTEGRATIONTIME_400MS:
    atime = 400.0F;
    break;
  case TSL2591_INTEGRATIONTIME_500MS:
    atime = 500.0F;
    break;
  case TSL2591_INTEGRATIONTIME_600MS:
    atime = 600.0F;
    break;
  default: // 100ms
    atime = 100.0F;
    break;
  }

  switch (_gain) {
  case TSL2591_GAIN_LOW:
    again = 1.0F;
    break;
  case TSL2591_GAIN_MED:
    again = 25.0F;
    break;
  case TSL2591_GAIN_HIGH:
    again = 428.0F;
    break;
  case TSL2591_GAIN_MAX:
    again = 9876.0F;
    break;
  default:
    again = 1.0F;
    break;
  }

  // cpl = (ATIME * AGAIN) / DF
  cpl = (atime * again) / TSL2591_LUX_DF;

  // Original lux calculation (for reference sake)
  // float lux1 = ( (float)ch0 - (TSL2591_LUX_COEFB * (float)ch1) ) / cpl;
  // float lux2 = ( ( TSL2591_LUX_COEFC * (float)ch0 ) - ( TSL2591_LUX_COEFD *
  // (float)ch1 ) ) / cpl; lux = lux1 > lux2 ? lux1 : lux2;

  // Alternate lux calculation 1
  // See: https://github.com/adafruit/Adafruit_TSL2591_Library/issues/14
  lux = (((float)ch0 - (float)ch1)) * (1.0F - ((float)ch1 / (float)ch0)) / cpl;

  // Alternate lux calculation 2
  // lux = ( (float)ch0 - ( 1.7F * (float)ch1 ) ) / cpl;

  // Signal I2C had no errors
  return lux;
}

/************************************************************************/
/*!
    @brief  Reads the raw data from both light channels
    @returns 32-bit raw count where high word is IR, low word is IR+Visible
*/
/**************************************************************************/
uint32_t Adafruit_TSL2591::getFullLuminosity(void) {
  if (!_initialized) {
    if (!begin()) {
      return 0;
    }
  }

  // Enable the device
  enable();

  // Wait x ms for ADC to complete
  for (uint8_t d = 0; d <= _integration; d++) {
    delay(120);
  }

  // CHAN0 must be read before CHAN1
  // See: https://forums.adafruit.com/viewtopic.php?f=19&t=124176
  uint32_t x;
  uint16_t y;
  y = read16(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_LOW);
  x = read16(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_LOW);
  x <<= 16;
  x |= y;

  disable();

  return x;
}

/************************************************************************/
/*!
    @brief  Reads the raw data from the channel
    @param  channel Can be 0 (IR+Visible, 1 (IR) or 2 (Visible only)
    @returns 16-bit raw count, or 0 if channel is invalid
*/
/**************************************************************************/
uint16_t Adafruit_TSL2591::getLuminosity(uint8_t channel) {
  uint32_t x = getFullLuminosity();

  if (channel == TSL2591_FULLSPECTRUM) {
    // Reads two byte value from channel 0 (visible + infrared)
    return (x & 0xFFFF);
  } else if (channel == TSL2591_INFRARED) {
    // Reads two byte value from channel 1 (infrared)
    return (x >> 16);
  } else if (channel == TSL2591_VISIBLE) {
    // Reads all and subtracts out just the visible!
    return ((x & 0xFFFF) - (x >> 16));
  }

  // unknown channel!
  return 0;
}

/************************************************************************/
/*!
    @brief  Set up the interrupt to go off when light level is outside the
   lower/upper range.
    @param  lowerThreshold Raw light data reading level that is the lower value
   threshold for interrupt
    @param  upperThreshold Raw light data reading level that is the higher value
   threshold for interrupt
    @param  persist How many counts we must be outside range for interrupt to
   fire, default is any single value
*/
/**************************************************************************/
void Adafruit_TSL2591::registerInterrupt(
    uint16_t lowerThreshold, uint16_t upperThreshold,
    tsl2591Persist_t persist = TSL2591_PERSIST_ANY) {
  if (!_initialized) {
    if (!begin()) {
      return;
    }
  }

  enable();
  write8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_PERSIST_FILTER, persist);
  write8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_THRESHOLD_AILTL,
         lowerThreshold);
  write8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_THRESHOLD_AILTH,
         lowerThreshold >> 8);
  write8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_THRESHOLD_AIHTL,
         upperThreshold);
  write8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_THRESHOLD_AIHTH,
         upperThreshold >> 8);
  disable();
}

/************************************************************************/
/*!
    @brief  Clear interrupt status
*/
/**************************************************************************/
void Adafruit_TSL2591::clearInterrupt() {
  if (!_initialized) {
    if (!begin()) {
      return;
    }
  }

  enable();
  write8(TSL2591_CLEAR_INT);
  disable();
}

/************************************************************************/
/*!
    @brief  Gets the most recent sensor event from the hardware status register.
    @return Sensor status as a byte. Bit 0 is ALS Valid. Bit 4 is ALS Interrupt.
   Bit 5 is No-persist Interrupt.
*/
/**************************************************************************/
uint8_t Adafruit_TSL2591::getStatus(void) {
  if (!_initialized) {
    if (!begin()) {
      return 0;
    }
  }

  // Enable the device
  enable();
  uint8_t x;
  x = read8(TSL2591_COMMAND_BIT | TSL2591_REGISTER_DEVICE_STATUS);
  disable();
  return x;
}

/************************************************************************/
/*!
    @brief  Gets the most recent sensor event
    @param  event Pointer to Adafruit_Sensor sensors_event_t object that will be
   filled with sensor data
    @return True on success, False on failure
*/
/**************************************************************************/
bool Adafruit_TSL2591::getEvent(sensors_event_t *event) {
  uint16_t ir, full;
  uint32_t lum = getFullLuminosity();
  /* Early silicon seems to have issues when there is a sudden jump in */
  /* light levels. :( To work around this for now sample the sensor 2x */
  lum = getFullLuminosity();
  ir = lum >> 16;
  full = lum & 0xFFFF;

  /* Clear the event */
  memset(event, 0, sizeof(sensors_event_t));

  event->version = sizeof(sensors_event_t);
  event->sensor_id = _sensorID;
  event->type = SENSOR_TYPE_LIGHT;
  event->timestamp = millis();

  /* Calculate the actual lux value */
  /* 0 = sensor overflow (too much light) */
  event->light = calculateLux(full, ir);

  return true;
}

/**************************************************************************/
/*!
    @brief  Gets the overall sensor_t data including the type, range and
   resulution
    @param  sensor Pointer to Adafruit_Sensor sensor_t object that will be
   filled with sensor type data
*/
/**************************************************************************/
void Adafruit_TSL2591::getSensor(sensor_t *sensor) {
  /* Clear the sensor_t object */
  memset(sensor, 0, sizeof(sensor_t));

  /* Insert the sensor name in the fixed length char array */
  strncpy(sensor->name, "TSL2591", sizeof(sensor->name) - 1);
  sensor->name[sizeof(sensor->name) - 1] = 0;
  sensor->version = 1;
  sensor->sensor_id = _sensorID;
  sensor->type = SENSOR_TYPE_LIGHT;
  sensor->min_delay = 0;
  sensor->max_value = 88000.0;
  sensor->min_value = 0.0;
  sensor->resolution = 0.001;
}
/*******************************************************/

uint8_t Adafruit_TSL2591::read8(uint8_t reg) {
  uint8_t buffer[1];
  buffer[0] = reg;
  i2c_dev->write_then_read(buffer, 1, buffer, 1);
  return buffer[0];
}

uint16_t Adafruit_TSL2591::read16(uint8_t reg) {
  uint8_t buffer[2];
  buffer[0] = reg;
  i2c_dev->write_then_read(buffer, 1, buffer, 2);
  return uint16_t(buffer[1]) << 8 | uint16_t(buffer[0]);
}

void Adafruit_TSL2591::write8(uint8_t reg, uint8_t value) {
  uint8_t buffer[2];
  buffer[0] = reg;
  buffer[1] = value;
  i2c_dev->write(buffer, 2);
}

void Adafruit_TSL2591::write8(uint8_t reg) {
  uint8_t buffer[1];
  buffer[0] = reg;
  i2c_dev->write(buffer, 1);
}

Adafruit_TSL2591.h

C Header File
The header file for the TSL2591 library. Contains definitions, structures, classes, and function forms for quick and easy use of the sensor, and access to its settings and properties. Credit once again goes to Adafruit and K. Townsend for both components of this library.
/**************************************************************************/
/*!
    @file     Adafruit_TSL2591.h
    @author   KT0WN (adafruit.com)

    This is a library for the Adafruit TSL2591 breakout board
    This library works with the Adafruit TSL2591 breakout
    ----> https://www.adafruit.com/products/1980

    Check out the links above for our tutorials and wiring diagrams
    These chips use I2C to communicate

    Adafruit invests time and resources providing this open source code,
    please support Adafruit and open-source hardware by purchasing
    products from Adafruit!
*/
/**************************************************************************/

#ifndef _TSL2591_H_
#define _TSL2591_H_

#include <Adafruit_I2CDevice.h>
#include <Adafruit_Sensor.h>
#include <Arduino.h>

#define TSL2591_VISIBLE (2)      ///< (channel 0) - (channel 1)
#define TSL2591_INFRARED (1)     ///< channel 1
#define TSL2591_FULLSPECTRUM (0) ///< channel 0

#define TSL2591_ADDR (0x29) ///< Default I2C address

#define TSL2591_COMMAND_BIT                                                    \
  (0xA0) ///< 1010 0000: bits 7 and 5 for 'command normal'

///! Special Function Command for "Clear ALS and no persist ALS interrupt"
#define TSL2591_CLEAR_INT (0xE7)
///! Special Function Command for "Interrupt set - forces an interrupt"
#define TSL2591_TEST_INT (0xE4)

#define TSL2591_WORD_BIT (0x20)  ///< 1 = read/write word (rather than byte)
#define TSL2591_BLOCK_BIT (0x10) ///< 1 = using block read/write

#define TSL2591_ENABLE_POWEROFF (0x00) ///< Flag for ENABLE register to disable
#define TSL2591_ENABLE_POWERON (0x01)  ///< Flag for ENABLE register to enable
#define TSL2591_ENABLE_AEN                                                     \
  (0x02) ///< ALS Enable. This field activates ALS function. Writing a one
         ///< activates the ALS. Writing a zero disables the ALS.
#define TSL2591_ENABLE_AIEN                                                    \
  (0x10) ///< ALS Interrupt Enable. When asserted permits ALS interrupts to be
         ///< generated, subject to the persist filter.
#define TSL2591_ENABLE_NPIEN                                                   \
  (0x80) ///< No Persist Interrupt Enable. When asserted NP Threshold conditions
         ///< will generate an interrupt, bypassing the persist filter

#define TSL2591_LUX_DF (408.0F)   ///< Lux cooefficient
#define TSL2591_LUX_COEFB (1.64F) ///< CH0 coefficient
#define TSL2591_LUX_COEFC (0.59F) ///< CH1 coefficient A
#define TSL2591_LUX_COEFD (0.86F) ///< CH2 coefficient B

/// TSL2591 Register map
enum {
  TSL2591_REGISTER_ENABLE = 0x00,          // Enable register
  TSL2591_REGISTER_CONTROL = 0x01,         // Control register
  TSL2591_REGISTER_THRESHOLD_AILTL = 0x04, // ALS low threshold lower byte
  TSL2591_REGISTER_THRESHOLD_AILTH = 0x05, // ALS low threshold upper byte
  TSL2591_REGISTER_THRESHOLD_AIHTL = 0x06, // ALS high threshold lower byte
  TSL2591_REGISTER_THRESHOLD_AIHTH = 0x07, // ALS high threshold upper byte
  TSL2591_REGISTER_THRESHOLD_NPAILTL =
      0x08, // No Persist ALS low threshold lower byte
  TSL2591_REGISTER_THRESHOLD_NPAILTH =
      0x09, // No Persist ALS low threshold higher byte
  TSL2591_REGISTER_THRESHOLD_NPAIHTL =
      0x0A, // No Persist ALS high threshold lower byte
  TSL2591_REGISTER_THRESHOLD_NPAIHTH =
      0x0B, // No Persist ALS high threshold higher byte
  TSL2591_REGISTER_PERSIST_FILTER = 0x0C, // Interrupt persistence filter
  TSL2591_REGISTER_PACKAGE_PID = 0x11,    // Package Identification
  TSL2591_REGISTER_DEVICE_ID = 0x12,      // Device Identification
  TSL2591_REGISTER_DEVICE_STATUS = 0x13,  // Internal Status
  TSL2591_REGISTER_CHAN0_LOW = 0x14,      // Channel 0 data, low byte
  TSL2591_REGISTER_CHAN0_HIGH = 0x15,     // Channel 0 data, high byte
  TSL2591_REGISTER_CHAN1_LOW = 0x16,      // Channel 1 data, low byte
  TSL2591_REGISTER_CHAN1_HIGH = 0x17,     // Channel 1 data, high byte
};

/// Enumeration for the sensor integration timing
typedef enum {
  TSL2591_INTEGRATIONTIME_100MS = 0x00, // 100 millis
  TSL2591_INTEGRATIONTIME_200MS = 0x01, // 200 millis
  TSL2591_INTEGRATIONTIME_300MS = 0x02, // 300 millis
  TSL2591_INTEGRATIONTIME_400MS = 0x03, // 400 millis
  TSL2591_INTEGRATIONTIME_500MS = 0x04, // 500 millis
  TSL2591_INTEGRATIONTIME_600MS = 0x05, // 600 millis
} tsl2591IntegrationTime_t;

/// Enumeration for the persistance filter (for interrupts)
typedef enum {
  //  bit 7:4: 0
  TSL2591_PERSIST_EVERY = 0x00, // Every ALS cycle generates an interrupt
  TSL2591_PERSIST_ANY = 0x01,   // Any value outside of threshold range
  TSL2591_PERSIST_2 = 0x02,     // 2 consecutive values out of range
  TSL2591_PERSIST_3 = 0x03,     // 3 consecutive values out of range
  TSL2591_PERSIST_5 = 0x04,     // 5 consecutive values out of range
  TSL2591_PERSIST_10 = 0x05,    // 10 consecutive values out of range
  TSL2591_PERSIST_15 = 0x06,    // 15 consecutive values out of range
  TSL2591_PERSIST_20 = 0x07,    // 20 consecutive values out of range
  TSL2591_PERSIST_25 = 0x08,    // 25 consecutive values out of range
  TSL2591_PERSIST_30 = 0x09,    // 30 consecutive values out of range
  TSL2591_PERSIST_35 = 0x0A,    // 35 consecutive values out of range
  TSL2591_PERSIST_40 = 0x0B,    // 40 consecutive values out of range
  TSL2591_PERSIST_45 = 0x0C,    // 45 consecutive values out of range
  TSL2591_PERSIST_50 = 0x0D,    // 50 consecutive values out of range
  TSL2591_PERSIST_55 = 0x0E,    // 55 consecutive values out of range
  TSL2591_PERSIST_60 = 0x0F,    // 60 consecutive values out of range
} tsl2591Persist_t;

/// Enumeration for the sensor gain
typedef enum {
  TSL2591_GAIN_LOW = 0x00,  /// low gain (1x)
  TSL2591_GAIN_MED = 0x10,  /// medium gain (25x)
  TSL2591_GAIN_HIGH = 0x20, /// medium gain (428x)
  TSL2591_GAIN_MAX = 0x30,  /// max gain (9876x)
} tsl2591Gain_t;

/**************************************************************************/
/*!
    @brief  Class that stores state and functions for interacting with TSL2591
   Light Sensor
*/
/**************************************************************************/
class Adafruit_TSL2591 : public Adafruit_Sensor {
public:
  Adafruit_TSL2591(int32_t sensorID = -1);
  ~Adafruit_TSL2591();

  boolean begin(TwoWire *theWire, uint8_t addr = TSL2591_ADDR);
  boolean begin(uint8_t addr = TSL2591_ADDR);
  void enable(void);
  void disable(void);

  float calculateLux(uint16_t ch0, uint16_t ch1);
  void setGain(tsl2591Gain_t gain);
  void setTiming(tsl2591IntegrationTime_t integration);
  uint16_t getLuminosity(uint8_t channel);
  uint32_t getFullLuminosity();

  tsl2591IntegrationTime_t getTiming();
  tsl2591Gain_t getGain();

  // Interrupt
  void clearInterrupt(void);
  void registerInterrupt(uint16_t lowerThreshold, uint16_t upperThreshold,
                         tsl2591Persist_t persist);
  uint8_t getStatus();

  /* Unified Sensor API Functions */
  bool getEvent(sensors_event_t *);
  void getSensor(sensor_t *);

private:
  Adafruit_I2CDevice *i2c_dev = NULL; ///< Pointer to I2C bus interface

  void write8(uint8_t r);
  void write8(uint8_t r, uint8_t v);
  uint16_t read16(uint8_t reg);
  uint8_t read8(uint8_t reg);

  tsl2591IntegrationTime_t _integration;
  tsl2591Gain_t _gain;
  int32_t _sensorID;
  uint8_t _addr;

  boolean _initialized;
};
#endif

Credits

Anthony Pierce

Anthony Pierce

1 project • 0 followers
Cameron Mixon-tinsley

Cameron Mixon-tinsley

0 projects • 0 followers
Jason Zou

Jason Zou

0 projects • 0 followers

Comments