Functions

  Name
void setAccel(int16_t x, int16_t y, int16_t z)
void installBus(uint8_t bridgeAddress)
void put16(uint8_t * r, uint8_t at, uint16_t v)
void put32(uint8_t * r, uint8_t at, int32_t v)
void loadImage(int32_t uva, int32_t uvb, uint16_t als, uint16_t white, uint16_t luxMul, uint16_t irMid, uint16_t irShort, uint16_t therm, uint8_t fwPatch =1, uint8_t schema =0x01)
void loadStandard()
void report(const char * name, Libelle & s)
int main()

Attributes

  Name
TwoWire Wire
uint8_t adxl
uint8_t adxlAddress
bool adxlPresent

Functions Documentation

function setAccel

static void setAccel(
    int16_t x,
    int16_t y,
    int16_t z
)

function installBus

static void installBus(
    uint8_t bridgeAddress
)

function put16

static void put16(
    uint8_t * r,
    uint8_t at,
    uint16_t v
)

function put32

static void put32(
    uint8_t * r,
    uint8_t at,
    int32_t v
)

function loadImage

static void loadImage(
    int32_t uva,
    int32_t uvb,
    uint16_t als,
    uint16_t white,
    uint16_t luxMul,
    uint16_t irMid,
    uint16_t irShort,
    uint16_t therm,
    uint8_t fwPatch =1,
    uint8_t schema =0x01
)

function loadStandard

static void loadStandard()

function report

static void report(
    const char * name,
    Libelle & s
)

function main

int main()

Attributes Documentation

variable Wire

TwoWire Wire;

variable adxl

static uint8_t adxl;

variable adxlAddress

static uint8_t adxlAddress = 0x1D;

variable adxlPresent

static bool adxlPresent = true;

Source code

// Output-regression test for Libelle_Library: compiles src/Libelle.cpp against
// the NW_Core stubs and prints getHeader()/getString()/getters for fixed
// register images. Two chips share the bus: the pyranometer bridge (Schema 1
// register map) and the ADXL343 accelerometer, which the library reads
// directly (hardware v1). run.sh diffs the result against baseline.txt.
#include "Arduino.h"
#include "Wire.h"
TwoWire Wire;
#include "../../src/Libelle.cpp"
#include "NW_TestSupport.h"

// The ADXL343: a register file of its own at 0x1D (UP) or 0x53 (DOWN), served by the hooks.
static uint8_t adxl[64];
static uint8_t adxlAddress = 0x1D;
static bool adxlPresent = true;
static void setAccel(int16_t x, int16_t y, int16_t z) {   // 0.0039 g per LSB: 256 = 1 g
  adxl[0x32] = x & 0xFF; adxl[0x33] = (x >> 8) & 0xFF;
  adxl[0x34] = y & 0xFF; adxl[0x35] = (y >> 8) & 0xFF;
  adxl[0x36] = z & 0xFF; adxl[0x37] = (z >> 8) & 0xFF;
}
static void installBus(uint8_t bridgeAddress) {
  Wire.deviceAddress = bridgeAddress;
  Wire.isPresent = [](uint8_t a) { return Wire.present && (a == Wire.deviceAddress || (adxlPresent && a == adxlAddress)); };
  Wire.hookOnly  = [](uint8_t a) { return a == adxlAddress; };
  Wire.onRequest = [](TwoWire& w, uint8_t n, std::deque<uint8_t>& out) {
    if (w.address() != adxlAddress) return false;                       // the bridge: the image
    for (uint8_t i = 0; i < n; i++) out.push_back(adxl[(w.pointer() + i) & 0x3F]);
    return true;
  };
  installFirmwareEmulation();
}

// Schema 1 register image: Page 0 as NW-Provision writes it (with the firmware's
// patch at 0x0A), Page 1 (calibration) zero, Page 2 with a complete reading (Libelle
// appendix: VEML6030 ALS, white and lux multiplier uint16 at 0x48; VEML6075 UVA and
// UVB int32 at 0x50; ADS1115 IR short, IR mid and thermistor uint16 at 0x58), little-endian.
static void put16(uint8_t* r, uint8_t at, uint16_t v) { r[at] = v & 0xFF; r[at + 1] = v >> 8; }
static void put32(uint8_t* r, uint8_t at, int32_t v) { for (int i = 0; i < 4; i++) r[at + i] = (v >> (8 * i)) & 0xFF; }
static void loadImage(int32_t uva, int32_t uvb, uint16_t als, uint16_t white, uint16_t luxMul,
                      uint16_t irMid, uint16_t irShort, uint16_t therm, uint8_t fwPatch = 1, uint8_t schema = 0x01) {
  uint8_t* r = Wire.image;
  nwLoadPage0(r, "Libelle", Wire.deviceAddress, 1, fwPatch, schema);   // Page 0 and Block 0, HW 0.1
  put16(r, 0x48, als); put16(r, 0x4A, white); put16(r, 0x4C, luxMul);
  put32(r, 0x50, uva); put32(r, 0x54, uvb);
  put16(r, 0x58, irShort); put16(r, 0x5A, irMid); put16(r, 0x5C, therm);
}
static void loadStandard() { loadImage(12345, 678, 4000, 3000, 8, 20000, 10000, 13200); }

static void report(const char* name, Libelle& s) {
  printf("[%s]\n", name);
  printf("header: %s\n", s.getHeader().c_str());
  printf("string: %s\n", s.getString().c_str());
  printf("getters: uva=%ld uvb=%ld als=%ld white=%ld lux=%.4f irS=%.4f irM=%.4f temp=%.4f roll=%.4f pitch=%.4f\n",
         s.getUVA(), s.getUVB(), s.getALS(), s.getWhite(), s.getLux(), s.getIR_Short(), s.getIR_Mid(), s.getTemp(), s.getRoll(), s.getPitch());
}

int main() {
  // 1. UP unit, level: ALS 4000 x mult 8 x 0.0036 = 115.2 lx; IR 1.25 V and 2.5 V; thermistor at 25.00 C
  //    (1.65 V: Rt = R25, so the Steinhart-Hart log term is zero); 1 g on Z.
  installBus(0x4C); adxlAddress = 0x1D; setAccel(0, 0, 256);
  loadStandard();
  { Libelle s(UP); printf("begin=%d\n", s.begin()); report("UP, level", s); }

  // 2. Tilted: X 100, Y 50, Z 230 LSB.
  setAccel(100, 50, 230);
  { Libelle s(UP); s.begin(); report("UP, tilted", s); }

  // 3. DOWN unit: bridge at 0x0C, accelerometer at 0x53; a different set of values.
  installBus(0x0C); adxlAddress = 0x53; setAccel(-30, 10, 250);
  loadImage(1000000, 2000000, 65535, 1, 1, 65535, 0, 2640);   // 0.33 V on the thermistor
  { Libelle s(DOWN); printf("begin=%d\n", s.begin()); report("DOWN", s); }

  // 4. Nothing on the bus.
  installBus(0x4C); adxlAddress = 0x1D; setAccel(0, 0, 256); loadStandard(); Wire.present = false;
  { Libelle s(UP); bool ok = s.begin(); printf("begin with no sensor: %d failure=%s\n", ok, s.beginFailure().c_str()); report("absent", s); }
  Wire.present = true;

  // 5. begin() gates: wrong name, wrong schema, firmware too old, the versions it reports,
  //    an explicit address, and a bridge without its accelerometer.
  loadStandard(); Wire.image[0x01] = 'X';
  { Libelle s; bool ok = s.begin(); printf("[wrong name] begin=%d failure=%s\n", ok, s.beginFailure().c_str()); }
  loadImage(12345, 678, 4000, 3000, 8, 20000, 10000, 13200, 1, 0x00);
  { Libelle s; bool ok = s.begin(); printf("[schema 0x00] begin=%d failure=%s\n", ok, s.beginFailure().c_str()); }
  loadImage(12345, 678, 4000, 3000, 8, 20000, 10000, 13200, 0);
  { Libelle s; bool ok = s.begin(); printf("[fw patch 0 < min %d] begin=%d fw=%u failure=%s\n", LIBELLE_FW_MIN_PATCH, ok, s.getFirmwareVersion(), s.beginFailure().c_str()); }
  loadStandard();
  { Libelle s; bool ok = s.begin(); printf("[versions] begin=%d hw=%u.%u fw=%u failure=%s\n", ok, s.getHardwareMajor(), s.getHardwareMinor(), s.getFirmwareVersion(), s.beginFailure().c_str()); }
  { Libelle s(DOWN); bool ok = s.begin(0x4C); printf("[DOWN unit at an explicit 0x4C] begin=%d failure=%s\n", ok, s.beginFailure().c_str()); }
  adxlPresent = false;
  { Libelle s; bool ok = s.begin(); printf("[no accelerometer] begin=%d failure=%s\n", ok, s.beginFailure().c_str());
    String row = s.getString();   // evaluated before the fault getters: printf argument order is unspecified
    printf("[no accelerometer] string: %s faulted(3)=%d any=%d note='%s'\n", row.c_str(), s.faulted(3), s.anyFault(), s.reportNote().c_str()); }
  adxlPresent = true;

  // 6. Faults: the VEML6075 does not acknowledge (status bit 1, pan-fault, latched 0x01); the
  //    other chips survive. Then an ADS1115 timeout (0x22), then a unit reset with a clean
  //    status, then an accelerometer whose three axes read the same (the library's own fault).
  loadStandard();
  { Libelle s; s.begin(); char pb[48];
    onReading = [](TwoWire& w) { w.image[0x40] = 0x83; w.image[0x47] = 0x01; };
    bool ok = s.updateMeasurements(); BufferPrint bp(pb, sizeof pb); s.printReport(bp);
    printf("[VEML6075 no ACK] update=%d faulted(0)=%d faulted(1)=%d faulted(2)=%d any=%d chip=%u kind=%u text='%s' note='%s'\n",
           ok, s.faulted(0), s.faulted(1), s.faulted(2), s.anyFault(), s.reportChip(), s.reportKind(), pb, s.reportNote().c_str());
    printf("[VEML6075 no ACK] string: %s\n", s.getString().c_str());
    onReading = [](TwoWire& w) { w.image[0x40] = 0x89; w.image[0x47] = 0x42; };   // chip 2, kind 2
    String row = s.getString();   // evaluated before the note: printf argument order is unspecified
    printf("[ADS1115 timeout] string: %s note='%s'\n", row.c_str(), s.reportNote().c_str());
    onReading = [](TwoWire& w) { w.image[0x40] = 0x01; w.image[0x47] = 0xE6; };
    ok = s.updateMeasurements(); BufferPrint bp2(pb, sizeof pb); s.printReport(bp2);
    printf("[unit reset] update=%d any=%d chip=%u kind=%u text='%s' note='%s'\n", ok, s.anyFault(), s.reportChip(), s.reportKind(), pb, s.reportNote().c_str());
    onReading = nullptr; setAccel(0, 0, 0);
    ok = s.updateMeasurements(); BufferPrint bp3(pb, sizeof pb); s.printReport(bp3);
    printf("[accelerometer flat] update=%d faulted(3)=%d any=%d chip=%u kind=%u text='%s' note='%s' roll=%.2f tiltCount=%u\n", ok, s.faulted(3), s.anyFault(), s.reportChip(), s.reportKind(), pb, s.reportNote().c_str(), s.getRoll(), s.getTiltCount());
    setAccel(0, 0, 256); }

  // 7. N readings with statistics: ALS steps through five values (lux follows), UVA through
  //    three, the thermistor through two; the batch word reaches the bridge; the
  //    accelerometer takes four; getString() grows its columns.
  loadStandard();
  { Libelle s; s.begin(); int k = 0;
    onReading = [&](TwoWire& w) { k++;
      put16(w.image, 0x48, 4000 + 100 * (k % 5));
      put32(w.image, 0x50, 12345 + 10 * (k % 3));
      put16(w.image, 0x5C, 13200 + 40 * (k % 2)); };
    printf("[N] setLightReadings(5)=%u setUVReadings(3)=%u setIRReadings(2)=%u setTiltReadings(4)=%u setLightReadings(99)=%u\n",
           s.setLightReadings(5), s.setUVReadings(3), s.setIRReadings(2), s.setTiltReadings(4), s.setLightReadings(99));
    s.setLightReadings(5); s.setLightStats(true); s.setUVStats(true); s.setIRStats(true); s.setTiltStats(true);
    lastRequest = 0; unsigned t0 = Wire.transactions; bool ok = s.updateMeasurements();
    printf("[N=5,3,2,4] update=%d lightCount=%u uvCount=%u irCount=%u tiltCount=%u lastRequest=%u requestFrom=%u\n",
           ok, s.getLightCount(), s.getUVCount(), s.getIRCount(), s.getTiltCount(), lastRequest, Wire.transactions - t0);
    printf("[N=5,3,2,4] als mean=%.4f std=%.4f sterr=%.4f median=%.4f | lux mean=%.4f std=%.4f | uva mean=%.4f std=%.4f median=%.4f | temp mean=%.4f std=%.4f | roll mean=%.4f std=%.4f\n",
           s.getALSMean(), s.getALSStd(), s.getALSSterr(), s.getALSMedian(), s.getLuxMean(), s.getLuxStd(),
           s.getUVAMean(), s.getUVAStd(), s.getUVAMedian(), s.getTempMean(), s.getTempStd(), s.getRollMean(), s.getRollStd());
    printf("[N=5,3,2,4] header: %s\n", s.getHeader().c_str());
    printf("[N=5,3,2,4] string: %s\n", s.getString().c_str());
    ok = s.updateMeasurements(Libelle::VEML6030);
    printf("[VEML6030 only] update=%d lightCount=%u uvCount=%u uva=%ld lux=%.4f\n", ok, s.getLightCount(), s.getUVCount(), s.getUVA(), s.getLux());
    onReading = nullptr; }

  // 8. Reading interface: header, three logged readings of ALL, then the accelerometer
  //    alone (nothing written to the bridge), then the VEML6030 alone.
  loadStandard();
  { Libelle s; s.begin(); int k = 0; char pb[160];
    onReading = [&](TwoWire& w) { k++; put32(w.image, 0x50, 12300 + 5 * k); };
    lastRequest = 0; s.beginReadings(Libelle::ALL, 3);
    BufferPrint bh(pb, sizeof pb); s.printHeader(bh); printf("[run ALL] header: %s lastRequest=%u\n", pb, lastRequest);
    for (int i = 0; i < 3; i++) { BufferPrint bp(pb, sizeof pb); size_t n = s.logReading(bp); printf("[run ALL] row %d (%zu bytes): %s\n", i, n, pb); }
    s.endReadings();
    printf("[run ALL] uva count=%u mean=%.4f median=%.4f\n", s.getUVCount(), s.getUVAMean(), s.getUVAMedian());
    lastRequest = 0; unsigned t0 = Wire.transactions; s.beginReadings(Libelle::ADXL343, 2);
    BufferPrint bh2(pb, sizeof pb); s.printHeader(bh2); printf("[run ADXL343] header: %s lastRequest=%u\n", pb, lastRequest);
    BufferPrint bp2(pb, sizeof pb); s.logReading(bp2); s.endReadings(); printf("[run ADXL343] row: %s requestFrom=%u\n", pb, Wire.transactions - t0);
    s.beginReadings(Libelle::VEML6030);
    BufferPrint bh3(pb, sizeof pb); s.printHeader(bh3); printf("[run VEML6030] header: %s\n", pb);
    BufferPrint bp3(pb, sizeof pb); s.logReading(bp3); s.endReadings(); printf("[run VEML6030] row: %s\n", pb);
    onReading = nullptr; }

  // 9. A dead VEML6030 (not answering on the first reading) stops its batch of 8.
  loadStandard();
  { Libelle s; s.begin(); int k = 0;
    onReading = [&](TwoWire& w) { k++; w.image[0x40] = 0x85; w.image[0x47] = 0x21; };   // chip 1, kind 1
    s.setLightReadings(8); bool ok = s.updateMeasurements(Libelle::VEML6030);
    printf("[dead VEML6030] N=8: update=%d readings taken=%d lightCount=%u lux=%.2f note='%s'\n", ok, k, s.getLightCount(), s.getLux(), s.reportNote().c_str());
    onReading = nullptr; }

  // 10. Cost of one getString() with one reading of everything: bridge transactions plus three accelerometer axes.
  loadStandard();
  { Libelle s; s.begin(); unsigned t0 = Wire.transactions; s.getString(); printf("[cost] requestFrom calls for one getString(): %u\n", Wire.transactions - t0); }

  // 11. The status line for a logger's status file.
  loadStandard();
  { Libelle s; s.begin(); s.updateMeasurements(); char sb[320]; BufferPrint sp(sb, sizeof sb); size_t k = s.printStatus(sp); printf("[status] %zu bytes: %s\n", k, sb); }

  fprintf(stderr, "bus transactions total: %u\n", Wire.transactions);   // metric, not output
  return 0;
}

Updated on 2026-09-23 at 22:43:23 +0000