Files
wadamesh/test/test_pager_haptics.cpp
Kaj SchittecatandClaude Opus 5.5 67ed48f4fb test: the Pager's charger, gauge, vibration and IMU logic on fake chips
Host tests that compile the production variant files (TLoraPagerBoard.cpp,
PagerHaptics.cpp, PagerImu.cpp from #748-#751) against register-level fakes of
the BQ25896, BQ27220, DRV2605 and BHI260AP (test/pager_hw_fakes/):

- test_pager_power: REG0B decoding (USB host, adapter, power-good alone, OTG not
  counted as USB, the 1 s cache), the temperature guard with its 3 C band, user
  limits and implausible readings, battery care 80/75 %, both holds through the
  one charger switch, charge limits rounded to the chip's 64 mA / 16 mV steps,
  the gauge capacity written once per target (a sealed gauge is left alone on
  later boots), and ship mode refused on USB, read afresh, BATFET_DLY cleared
  and BATFET_DIS set on battery.
- test_pager_haptics: the waveform slots of every pattern and strength, all
  eight slots rewritten, standby only after GO clears, no bus traffic without
  the chip.
- test_pager_imu: one firmware upload per boot at 1 MHz with the bus clock put
  back, scaling to g, stale samples refused, the 3 s idle stop and restart, no
  retry storm after a failed upload.

Each was checked against a mutated copy of its subject (hysteresis removed,
care resume moved, ship mode on USB, OTG as USB, stale waveform tail, standby
mid-pattern, capacity written every boot): all eight mutants fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:04:59 +01:00

106 lines
4.1 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// The T-LoRa Pager's vibration patterns (PagerHaptics.cpp) on a fake DRV2605 register
// file: the waveform slots each pattern and strength writes, every slot rewritten so
// a shorter pattern does not inherit the tail of a longer one, the chip woken for a
// pattern and parked in standby once GO clears, and nothing at all without the chip.
//
// Build: g++ -std=c++17 -I test/pager_hw_fakes -I src test/test_pager_haptics.cpp
#include <cassert>
#include <cstdio>
#include <vector>
#include "pager_hw_fakes/Arduino.h"
#include "pager_hw_fakes/Wire.h"
#include "pager_hw_fakes/SensorDRV2605.hpp"
uint32_t testMillis = 1000;
TestSerial Serial;
TwoWire Wire;
FakeDrvComm g_drv;
bool g_drv_present = true;
#include "../variants/lilygo_tlora_pager/PagerHaptics.cpp"
static std::vector<uint8_t> slots() {
std::vector<uint8_t> v;
for (int i = 0; i < 8; ++i) v.push_back(g_drv.reg[0x04 + i]);
return v;
}
static uint8_t W(int ms10) { return (uint8_t)(0x80 | ms10); }
int main() {
pagerHapticsBegin();
assert(pagerHapticsPresent());
assert(g_drv.reg[0x03] == 1); // ERM library A
assert(g_drv.reg[0x01] == 0x40); // parked in standby after setup
// Strength picks the buzz effect: 49 (60 %), 48 (80 %), 14 (100 %).
const uint8_t buzz[3] = { 49, 48, 14 };
for (uint8_t s = 0; s < 3; ++s) {
const uint8_t b = buzz[s];
pagerHapticsPattern(PAGER_VIB_ONCE, s);
assert((slots() == std::vector<uint8_t>{ b, 0, 0, 0, 0, 0, 0, 0 }));
pagerHapticsPattern(PAGER_VIB_TWICE, s);
assert((slots() == std::vector<uint8_t>{ b, W(12), b, 0, 0, 0, 0, 0 }));
pagerHapticsPattern(PAGER_VIB_THRICE, s);
assert((slots() == std::vector<uint8_t>{ b, W(8), b, W(8), b, 0, 0, 0 }));
pagerHapticsPattern(PAGER_VIB_LONG, s);
assert((slots() == std::vector<uint8_t>{ b, b, b, b, 0, 0, 0, 0 }));
}
pagerHapticsPattern(PAGER_VIB_PULSE, 0);
assert((slots() == std::vector<uint8_t>{ 2, W(10), 2, W(40), 2, W(10), 2, 0 }));
pagerHapticsPattern(PAGER_VIB_PULSE, 2);
assert((slots() == std::vector<uint8_t>{ 1, W(10), 1, W(40), 1, W(10), 1, 0 }));
// Strength past the end is clamped, not an out-of-bounds read.
pagerHapticsPattern(PAGER_VIB_ONCE, 7);
assert(slots()[0] == 14);
// A short pattern after a long one: the long one's tail is gone.
pagerHapticsPattern(PAGER_VIB_PULSE, 2);
pagerHapticsPattern(PAGER_VIB_ONCE, 2);
assert((slots() == std::vector<uint8_t>{ 14, 0, 0, 0, 0, 0, 0, 0 }));
assert(g_drv.reg[0x01] == 0x00 && g_drv.reg[0x0C] == 1); // awake, internal trigger, GO
// PAGER_VIB_OFF writes nothing.
const int w0 = g_drv.writes;
pagerHapticsPattern(PAGER_VIB_OFF, 2);
pagerHapticsClick(0);
assert(g_drv.writes == w0);
// Key click: 100 % (effect 1) strong, 60 % (effect 2) light.
pagerHapticsClick(2); assert(slots()[0] == 1 && slots()[1] == 0);
pagerHapticsClick(1); assert(slots()[0] == 2);
// Standby: not before the first 250 ms look, not while GO is still set, then once.
pagerHapticsIdleTick();
assert(g_drv.reg[0x01] == 0x00);
testMillis += 300;
pagerHapticsIdleTick(); // GO still 1: still playing
assert(g_drv.reg[0x01] == 0x00);
g_drv.reg[0x0C] = 0; // the chip finished the sequence
testMillis += 50;
pagerHapticsIdleTick(); // re-checks only every 100 ms
assert(g_drv.reg[0x01] == 0x00);
testMillis += 60;
pagerHapticsIdleTick();
assert(g_drv.reg[0x01] == 0x40);
const int w1 = g_drv.writes;
testMillis += 1000;
pagerHapticsIdleTick(); // parked: no more bus traffic
assert(g_drv.writes == w1);
// The legacy kinds still map to their sequences.
pagerHapticsPlay(PagerHaptic::Mention);
assert((slots() == std::vector<uint8_t>{ 1, W(8), 1, W(8), 14, 0, 0, 0 }));
// No chip: every call is a no-op (and none touches a null comm).
g_drv = FakeDrvComm{};
g_drv_present = false;
pagerHapticsBegin();
assert(!pagerHapticsPresent());
pagerHapticsPattern(PAGER_VIB_THRICE, 2);
pagerHapticsClick(2);
pagerHapticsPlay(PagerHaptic::Direct);
testMillis += 1000;
pagerHapticsIdleTick();
assert(g_drv.writes == 0);
std::printf("test_pager_haptics: OK\n");
return 0;
}