Shared foundation for Northern Widget sensor libraries. Unversioned and unregistered during the 2026 library overhaul; it becomes a Library Manager dependency of every NW sensor library once the overhaul is finished.
Three parts, one include:
#include <NW_Core.h>NW_Devicespeaks the NW-Device-Specification Schema 1 register map over I2C: Page 0 identity with the threebegin()gates (schema byte, name, minimum firmware patch) and a boot-time retry, withbeginFailure()naming the gate that refused; the Block 0 handshake in three steps,requestReading(chips),waitReading(),captureReading(), ortakeReading(chips)for all three; batches through the readings-requested word (beginBatch(n),batchFaulted(chips), andtakeReadings(chips, n, readOne), the N-readings loop every library runs); the Report register, read once inbegin()so boot reports survive;readData(), the data read checked against the counter with a bounded retry (dataMoved()when a device outruns it);printSnapshot(out, chipNames, n), one status line for a logger's status file (name, serial, versions, the last report, Pages 0 to 2 in hex); register reads split at the 32-byte Wire buffer.NW_Readings<T, CAPACITY>holds one measurement's readings in a fixed array, no heap: every acquisition appends;last()is the scalar;mean(),std(),sterr(),median()are computed from the array on demand, so a batch logged to a file has its statistics without a second acquisition.NW_Error.hdefinesNW_ERROR(-9999), the missing value on file, andnwScaled(v, divisor), which scales a statistic from register units and passes the sentinel through.NW_ReadingsConfigholds, per chip group, how many readingsupdateMeasurements()takes (set(n, capacity)clamps) and whether its std and sterr columns print (columns()).NW_Reportdecodes the status and Report bytes and prints the whole report (print(out, chipNames, n): "MS5803: not answering") or gives it as one word for a note column (note(chipNames, n): "MS5803NotAnswering"), in the spec's note words, the library passing its chip-name table;printKind()andkindWord()give the universal kind alone.NW_Sensoris the view a logger takes of any sensor library:name(),reportKind(),reportIsFault(),bootReportKind(),clearBootReport()andprintStatus(out, boot). A library inherits it so thatLogger.watch(sensor)can write the sensor's boot row and its report rows to the status file without knowing the library.NW_Pagesis the device side of the same map, for a logger that is itself a Schema 1 device (Margay):loadStored(base)copies Pages 0 and 1 from EEPROM,beginReading()/endReading(chipFaults)keep Block 0,latchFault()/latchNotice()/acknowledge()run the Report register with the spec's precedence, andprintSnapshot()prints the logger's own status row.- Build identity.
NW_CORE_LIBRARY_VERSIONandNW_CORE_LIBRARY_COMMITset the pattern every library follows: the version is held equal tolibrary.propertiesby NW-Tests, and the commit is defined by the NW-Build wrapper from git (blank in an IDE build). A status row carries the device's firmware build commit from Page 0 Block 3 and the reading library's version and commit:Time,Trigger,Device,Serial,HW,FW,FWCommit,Lib,LibCommit,Code,Note,Page0,Page1,Page2.
A sensor library holds one NW_Device and one NW_Readings per measurement and forwards to them, and inherits NW_Sensor for the logger's sake; nothing else inherits. The design, and the line between what lives here and what stays in each library, is in LIBRARY-DESIGN.md, section 11.
The shape every NW sensor library takes on Core (Walrus, two chip groups):
class Walrus {
NW_Device _dev;
NW_Readings<int32_t, WALRUS_PRESSURE_CAPACITY> _pressureReadings; // uBar, as served
NW_ReadingsConfig _pressureCfg; // how many readings; stats columns?
public:
enum Component : uint8_t { MS5803 = 0x01, MCP9808 = 0x02, ALL = 0x03 }; // the chip-select bits
bool begin(uint8_t address = DEFAULT_ADDRESS) { return _dev.begin(address, "Walrus", WALRUS_FW_MIN_PATCH); }
bool updatePressure() { // one reading of one chip group
uint8_t d[6];
if (!_dev.takeReading(MS5803) || !_dev.readData(PRES_REG, d, 6) || _dev.faulted(0)) return false;
_pressureReadings.append((int32_t)(d[0] | (d[1] << 8) | ((uint32_t)d[2] << 16) | ((uint32_t)d[3] << 24)));
return true;
}
bool updateMeasurements(uint8_t component = ALL) { // N readings, then the means
_pressureReadings.reset();
_dev.takeReadings(MS5803, _pressureCfg.n, [this] { return updatePressure(); });
_pressure = nwScaled(_pressureReadings.mean(), 1000.0); // mBar; NW_ERROR when none
return _pressureReadings.count() > 0;
}
uint16_t setPressureReadings(uint16_t n) { return _pressureCfg.set(n, WALRUS_PRESSURE_CAPACITY); }
float getPressureStd() { return nwScaled(_pressureReadings.std(), 1000.0); }
size_t printReport(Print& out) {
static const char* const chips[] = {"MS5803", "MCP9808"}; // the spec's chip table
return _dev.report().print(out, chips, 2);
}
};takeReadings() declares the batch to the device (beginBatch), calls the one-reading function n times, and stops once a selected chip reports absent, so a dead chip costs one reading. getHeader()/getString() print std and sterr columns when _pressureCfg.columns() is true. The full pattern, with the reading interface (beginReadings, printHeader, printReading, logReading, endReadings), is in Apis_Library, Walrus_Library and Haar_Library.
extras/test/run.sh compiles the library on a desktop against stub Arduino.h and Wire.h (an emulated Schema 1 device with firmware behaviour, boot delay, free running, faults, and the 32-byte Wire buffer) and checks that the output is byte-identical to extras/test/baseline.txt. The stubs are shared: every NW library's harness includes them from here (NW_CORE path, default the sibling checkout). --record rewrites the baseline when a change is intended.
Full API reference: https://docs.northernwidget.com/NW_Core/