/******************************************************************************
Libelle.h
Library for interfacing with the Libelle shortwave pyranometer
Bobby Schulz @ Northern Widget LLC
7/5/2018
https://github.com/NorthernWidget-Skunkworks/Libelle_Library
Measures solar radiation across six spectral bands from UV-B through
short-wave infrared (~280-1700 nm), with tilt correction via onboard
accelerometer.
"The laws of nature are constructed in such a way as to make the universe
as interesting as possible."
-Freeman Dyson
Distributed as-is; no warranty is given.
******************************************************************************/
#ifndef Libelle_h
#define Libelle_h
#include <Arduino.h>
#include "Wire.h"
#include "math.h"
#include <NW_Core.h> // NW_Core: NW_Device (Schema 1 protocol), NW_Readings, NW_Report
#define LIBELLE_FW_MIN_PATCH 1
// Build identity: this library's version (held equal to library.properties by
// NW-Tests/version_check.py) and its build commit, set by the NW-Build wrapper from
// git and blank in an Arduino IDE build. Both go into a logger's status file.
#define LIBELLE_LIBRARY_VERSION "1.0.0"
#ifndef LIBELLE_LIBRARY_COMMIT
#define LIBELLE_LIBRARY_COMMIT ""
#endif
// Readings per updateMeasurements() are kept in static arrays of this
// capacity (one per chip group; no heap); set<Group>Readings(n) clamps to it.
// Override before the include to trade RAM for a longer batch. The default is
// 8, not the 16 of the other libraries: Libelle stores ten fields and a
// logger usually carries two units (UP and DOWN).
#ifndef LIBELLE_UV_CAPACITY
#define LIBELLE_UV_CAPACITY 8 // VEML6075: UVA, UVB
#endif
#ifndef LIBELLE_LIGHT_CAPACITY
#define LIBELLE_LIGHT_CAPACITY 8 // VEML6030: ALS, white, lux
#endif
#ifndef LIBELLE_IR_CAPACITY
#define LIBELLE_IR_CAPACITY 8 // ADS1115: IR short, IR mid, thermistor temperature
#endif
#ifndef LIBELLE_TILT_CAPACITY
#define LIBELLE_TILT_CAPACITY 8 // ADXL343: roll, pitch
#endif
enum Orientation { UP = 0, DOWN = 1 };
constexpr float LIBELLE_ERROR = NW_ERROR;
class Libelle : public NW_Sensor
{
public:
static constexpr uint8_t DEFAULT_ADDRESS_UP = 0x4C;
static constexpr uint8_t DEFAULT_ADDRESS_DOWN = 0x0C;
enum Component : uint8_t {
VEML6075 = 0x01,
VEML6030 = 0x02,
ADS1115 = 0x04,
ADXL343 = 0x08,
BRIDGE = 0x07,
ALL = 0x0F
};
Libelle(Orientation orientation = UP);
bool begin(uint8_t ADR_ = 0);
float getRoll(bool update = false);
float getPitch(bool update = false);
long getUVA(bool update = false);
long getUVB(bool update = false);
long getALS(bool update = false);
long getWhite(bool update = false);
float getLux(bool update = false);
float getIR_Short(bool update = false);
float getIR_Mid(bool update = false);
float getTemp(bool update = false);
String getHeader();
String getString();
bool updateMeasurements(uint8_t component = ALL);
bool updateUV();
bool updateLight();
bool updateIR();
bool updateTilt();
uint16_t setUVReadings(uint16_t n);
uint16_t setLightReadings(uint16_t n);
uint16_t setIRReadings(uint16_t n);
uint16_t setTiltReadings(uint16_t n);
void setUVStats(bool enable);
void setLightStats(bool enable);
void setIRStats(bool enable);
void setTiltStats(bool enable);
uint16_t getUVCount();
uint16_t getLightCount();
uint16_t getIRCount();
uint16_t getTiltCount();
// --- Statistics getters ---
// Computed two-pass in 32-bit float over the readings stored by the last
// updateMeasurements() (NW_Readings); LIBELLE_ERROR when none. Lux,
// temperature, roll and pitch are stored per reading in their physical
// units, since none is linear in the register values.
float getUVAMean(); float getUVAStd(); float getUVASterr(); float getUVAMedian();
float getUVBMean(); float getUVBStd(); float getUVBSterr(); float getUVBMedian();
float getALSMean(); float getALSStd(); float getALSSterr(); float getALSMedian();
float getWhiteMean(); float getWhiteStd(); float getWhiteSterr(); float getWhiteMedian();
float getLuxMean(); float getLuxStd(); float getLuxSterr(); float getLuxMedian();
float getIR_ShortMean(); float getIR_ShortStd(); float getIR_ShortSterr(); float getIR_ShortMedian();
float getIR_MidMean(); float getIR_MidStd(); float getIR_MidSterr(); float getIR_MidMedian();
float getTempMean(); float getTempStd(); float getTempSterr(); float getTempMedian();
float getRollMean(); float getRollStd(); float getRollSterr(); float getRollMedian();
float getPitchMean(); float getPitchStd(); float getPitchSterr(); float getPitchMedian();
// --- Reading interface (NW standard) ---
size_t printHeader(Print& out);
size_t printReading(Print& out);
size_t logReading(Print& out);
void beginReadings(uint8_t component = ALL, uint16_t n = 0);
void endReadings();
// --- Faults (status byte, live; Report register, latched) ---
bool faulted(uint8_t chip);
bool anyFault();
uint8_t reportChip();
uint8_t reportKind();
size_t printReport(Print& out);
String reportNote();
size_t printStatus(Print& out, bool boot = false) override;
// --- NW_Sensor: the logger's view (Margay::watch) ---
const char* name() const override { return "Libelle"; }
bool reportIsFault() override;
uint8_t bootReportKind() override;
void clearBootReport() override;
String beginFailure();
uint8_t getHardwareMajor();
uint8_t getHardwareMinor();
uint8_t getFirmwareVersion();
private:
const float LuxRes = 0.0036;
const float A = 0.003354016;
const float B = 0.0003074038;
const float C = 1.019153E-05;
const float D = 9.093712E-07;
NW_Device _dev;
uint8_t Accel_ADR = 0x1D;
Orientation _orientation = UP;
bool _bridgeOk = false; // the bridge passed begin()'s gates
bool _accelOk = false; // the accelerometer acknowledged at begin()
bool _accelFault = false; // the last accelerometer reading failed (not answering or all axes equal)
// Means of the last updateMeasurements() (or the last logReading()); LIBELLE_ERROR when none.
float _roll = LIBELLE_ERROR, _pitch = LIBELLE_ERROR; // [deg]
long _uva = (long)LIBELLE_ERROR, _uvb = (long)LIBELLE_ERROR; // compensated counts
long _als = (long)LIBELLE_ERROR, _white = (long)LIBELLE_ERROR; // raw counts
float _lux = LIBELLE_ERROR; // [lx]
float _irShort = LIBELLE_ERROR, _irMid = LIBELLE_ERROR; // [V]
float _temp = LIBELLE_ERROR; // [C]
// Readings as the bridge serves them where the scaling is linear (raw
// counts), else in physical units, one array per field.
NW_Readings<int32_t, LIBELLE_UV_CAPACITY> _uvaReadings;
NW_Readings<int32_t, LIBELLE_UV_CAPACITY> _uvbReadings;
NW_Readings<uint16_t, LIBELLE_LIGHT_CAPACITY> _alsReadings;
NW_Readings<uint16_t, LIBELLE_LIGHT_CAPACITY> _whiteReadings;
NW_Readings<float, LIBELLE_LIGHT_CAPACITY> _luxReadings; // [lx]
NW_Readings<uint16_t, LIBELLE_IR_CAPACITY> _irShortReadings;
NW_Readings<uint16_t, LIBELLE_IR_CAPACITY> _irMidReadings;
NW_Readings<float, LIBELLE_IR_CAPACITY> _tempReadings; // [C]
NW_Readings<float, LIBELLE_TILT_CAPACITY> _rollReadings; // [deg]
NW_Readings<float, LIBELLE_TILT_CAPACITY> _pitchReadings; // [deg]
NW_ReadingsConfig _uvCfg; // Readings per updateMeasurements() and stats columns, VEML6075 group
NW_ReadingsConfig _lightCfg; // VEML6030 group
NW_ReadingsConfig _irCfg; // ADS1115 group
NW_ReadingsConfig _tiltCfg; // ADXL343 group
uint8_t _component = ALL; // Selection of the current beginReadings() run
bool initAccel();
float getG(uint8_t Axis);
bool readUV(uint8_t* d); // Append one served VEML6075 reading (8 bytes from 0x50) unless faulted
bool readLight(uint8_t* d); // Append one served VEML6030 reading (6 bytes from 0x48) unless faulted
bool readIR(uint8_t* d); // Append one served ADS1115 reading (6 bytes from 0x58) unless faulted
bool readData(); // One 22-byte read of the three bridge chips, appended
void resetReadings(uint8_t component);
void summarise(uint8_t component); // Means into the single-value fields, LIBELLE_ERROR when no reading
NW_Report report(); // The bridge's report, or the library's own for the accelerometer
String column(const char* name, const char* unit, bool stats); // "R_u [deg]," plus std and sterr columns
float TempConvert(float V, float Vcc, float R, float A, float B, float C, float D, float R25);
void PrintAllRegs();
bool WriteByte(uint8_t Adr, uint8_t Pos, uint8_t Val);
};
#endif