src/NW_Device.cpp
Source code
#include "NW_Device.h"
bool NW_Device::begin(uint8_t address, const char* name, uint8_t minPatch, unsigned long bootTimeoutMs) {
_adr = address;
Wire.begin();
// Check ACK, retrying for the device's boot time if asked to.
unsigned long t0 = millis();
while (true) {
Wire.beginTransmission(_adr);
if (Wire.endTransmission() == 0) break;
if (millis() - t0 >= bootTimeoutMs) { _beginFailure = 1; return false; }
delay(1);
}
// Page 0 Blocks 0-1 (0x00-0x0F): schema, name, versions. Read in one
// transaction; the firmware serves Page 0 from EEPROM so it is valid before
// the first reading.
uint8_t p0[16];
_hwMajor = _hwMinor = _fwPatch = 0;
if (!readBytes(NW_REG_SCHEMA, p0, 16)) { _beginFailure = 2; return false; }
_hwMajor = p0[NW_REG_HW_MAJOR];
_hwMinor = p0[NW_REG_HW_MINOR];
_fwPatch = p0[NW_REG_FW_PATCH];
if (p0[NW_REG_SCHEMA] != 0x01) { _beginFailure = 3; return false; } // not Schema 1 (0x00 legacy, 0xFF unprovisioned, other)
bool ended = false; // 7-byte name field, null-padded
for (uint8_t i = 0; i < 7; i++) {
char expected = ended ? 0 : name[i];
if (expected == 0) ended = true;
if (p0[NW_REG_NAME + i] != (uint8_t)expected) { _beginFailure = 4; return false; }
}
if (_fwPatch < minPatch) { _beginFailure = 5; return false; } // register map older than this library
// Block 0 before any write: the boot reports (unit reset 0xE6, Page 0 check 0xE3)
// would be cleared by the first trigger, which is a Control write.
uint8_t b0[8] = {0, 0, 0, 0, 0, 0, 0, 0};
if (!readBytes(NW_REG_STATUS, b0, 8)) { _beginFailure = 2; return false; }
_report.status = b0[0];
_report.code = b0[7];
_bootReport = _report; // kept for the logger's status file until it clears it
_beginFailure = 0;
return true;
}
String NW_Device::beginFailure() const {
switch (_beginFailure) {
case 1: return String(F("NotAnswering"));
case 2: return String(F("ReadFailed"));
case 3: return String(F("NotSchema1"));
case 4: return String(F("WrongName"));
case 5: return String(F("OldFirmware"));
default: return String(F("None"));
}
}
bool NW_Device::readBytes(uint8_t reg, uint8_t* buf, uint8_t n) {
// One transaction per chunk: pointer write, then requestFrom. Schema 1 firmware
// serves up to 32 bytes with auto-increment, and the AVR Wire buffer is 32.
while (n > 0) {
uint8_t k = (n > NW_WIRE_CHUNK) ? NW_WIRE_CHUNK : n;
Wire.beginTransmission(_adr);
Wire.write(reg);
if (Wire.endTransmission() != 0) return false;
if (Wire.requestFrom(_adr, k) != k) return false;
for (uint8_t i = 0; i < k; i++) buf[i] = Wire.read();
buf += k; reg += k; n -= k;
}
return true;
}
bool NW_Device::readData(uint8_t reg, uint8_t* buf, uint8_t n) {
// Seqlock over the bus: the counter after the read must equal the one captured
// before it. A device that committed in between gets captured again and re-read.
_dataMoved = false;
for (uint8_t attempt = 0; ; attempt++) {
if (!readBytes(reg, buf, n)) return false;
if (readCounter() == _lastCounter) return true;
if (attempt >= NW_DATA_RETRIES) { _dataMoved = true; return false; }
if (!captureReading()) return false;
}
}
bool NW_Device::writeByte(uint8_t reg, uint8_t value) {
Wire.beginTransmission(_adr);
Wire.write(reg);
Wire.write(value);
return Wire.endTransmission() == 0;
}
bool NW_Device::setI2CAddress(uint8_t newAddress) { return writeByte(NW_REG_I2C_ADDR, newAddress); }
size_t NW_Device::printSnapshot(Print& out, const char* const* chipNames, uint8_t nChips, bool boot, const char* lib, const char* libCommit) {
const NW_Report& r = boot ? _bootReport : _report;
uint8_t page[32];
size_t n = 0;
if (!readBytes(0x00, page, 32)) return out.print(F("NotAnswering"));
for (uint8_t i = 1; i <= 7 && page[i]; i++) n += out.print((char)page[i]); // name
n += out.print(',');
for (uint8_t i = 0; i < 4; i++) { if (i) n += out.print('-'); n += nwPrintHex(out, page + 0x10 + 2 * i, 2); } // serial, Block 2
n += out.print(','); n += out.print(page[NW_REG_HW_MAJOR]); n += out.print('.'); n += out.print(page[NW_REG_HW_MINOR]); // HW version
n += out.print(','); n += out.print(page[NW_REG_FW_PATCH]); // FW patch
n += out.print(','); n += nwPrintCommit(out, page + 0x18); // FW build commit, Block 3
n += out.print(','); n += out.print(lib); n += out.print(','); n += out.print(libCommit); // the reading library
n += out.print(F(",0x")); n += nwPrintHex(out, &r.code, 1);
n += out.print(','); n += out.print(r.note(chipNames, nChips));
n += out.print(','); n += nwPrintPage(out, page); // Page 0
for (uint8_t p = 0x20; p <= 0x40; p += 0x20) { // Page 1 (calibration), Page 2 (data)
n += out.print(',');
if (readBytes(p, page, 32)) n += nwPrintPage(out, page); else n += out.print(F("NotAnswering"));
}
return n;
}
uint8_t NW_Device::readConfig() {
uint8_t v = 0xFF;
readBytes(NW_REG_CONFIG, &v, 1);
return v;
}
bool NW_Device::ready() {
uint8_t status = 0;
return readBytes(NW_REG_STATUS, &status, 1) && (status & NW_BIT_READY);
}
uint16_t NW_Device::readCounter() {
uint8_t d[2] = {0xFF, 0xFF};
readBytes(NW_REG_COUNTER, d, 2);
return (uint16_t)((d[1] << 8) | d[0]);
}
bool NW_Device::newReading() { return readCounter() != _lastCounter; }
bool NW_Device::requestReading(uint8_t chips) {
_counterBefore = readCounter();
_chips = chips;
return writeByte(NW_REG_CTRL, (uint8_t)(NW_CTRL_TRIGGER | (chips << 1)));
}
bool NW_Device::waitReading() {
// Without a request, wait for the counter to move past the last capture
// (a free-running device); with one, past the value seen at the request.
uint16_t before = (_counterBefore != 0xFFFF) ? _counterBefore : _lastCounter;
unsigned long start = millis();
while (millis() - start < _timeout) {
uint16_t now = readCounter();
if (now != before) { _lastCounter = now; _counterBefore = 0xFFFF; return true; }
delay(1);
}
return false;
}
bool NW_Device::captureReading() {
// Block 0 of the new reading: status (0x40) and report (0x47), one 8-byte read.
uint8_t b0[8] = {0, 0, 0, 0, 0, 0, 0, 0};
if (!readBytes(NW_REG_STATUS, b0, 8)) return false;
_report.status = b0[0];
_report.code = b0[7];
_lastCounter = (uint16_t)((b0[3] << 8) | b0[2]); // the counter of the reading captured
// A selected chip that reports "no acknowledge" or "not initialised" is not
// coming for the rest of this batch (NW-Device-Specification, Readings requested).
uint8_t chip = _report.chip();
if (chip < 6 && (_chips & (1 << chip)) && _report.chipFaulted(chip) && _report.chipAbsent()) _absentChips |= (1 << chip);
return true;
}
bool NW_Device::takeReading(uint8_t chips) {
if (!requestReading(chips)) return false;
if (!waitReading()) return false;
return captureReading();
}
bool NW_Device::writeBatch(uint16_t n) {
_absentChips = 0;
return writeByte(NW_REG_REQUEST, n & 0xFF) && writeByte(NW_REG_REQUEST + 1, n >> 8);
}
Updated on 2026-09-29 at 05:58:59 +0000