170 lines
4.2 KiB
C++
170 lines
4.2 KiB
C++
#include "HalTiltSensor.h"
|
|
|
|
#include <Logging.h>
|
|
|
|
HalTiltSensor halTiltSensor; // Singleton instance
|
|
|
|
bool HalTiltSensor::readGyro(float& gx, float& gy, float& gz) const {
|
|
Imu::Sample sample;
|
|
if (!_sdkImu.read(sample)) return false;
|
|
gx = sample.gx;
|
|
gy = sample.gy;
|
|
gz = sample.gz;
|
|
return true;
|
|
}
|
|
|
|
void HalTiltSensor::begin() {
|
|
_available = _sdkImu.begin();
|
|
if (_available) {
|
|
_initMs = millis();
|
|
_lastPollMs = millis();
|
|
// begin() leaves the sensors sampling; stand them by until tilt page turn
|
|
// actually wakes them, so a disabled IMU doesn't drain the battery.
|
|
if (!_sdkImu.sleep()) {
|
|
LOG_ERR("GYR", "IMU standby failed");
|
|
}
|
|
LOG_INF("GYR", "SDK IMU initialized");
|
|
return;
|
|
}
|
|
LOG_ERR("GYR", "SDK IMU not found");
|
|
}
|
|
|
|
bool HalTiltSensor::wake() {
|
|
if (!_available) {
|
|
return false;
|
|
}
|
|
|
|
if (!_sdkImu.wake()) {
|
|
LOG_ERR("GYR", "IMU wake failed");
|
|
return false;
|
|
}
|
|
|
|
_lastPollMs = millis();
|
|
_lastTiltMs = millis();
|
|
_wakeMs = millis();
|
|
_isAwake = true;
|
|
return true;
|
|
}
|
|
|
|
bool HalTiltSensor::deepSleep() {
|
|
if (!_available) {
|
|
return false;
|
|
}
|
|
|
|
if (!_sdkImu.sleep()) {
|
|
LOG_ERR("GYR", "IMU sleep failed");
|
|
return false;
|
|
}
|
|
|
|
clearPendingEvents();
|
|
_inTilt = false;
|
|
_isAwake = false;
|
|
return true;
|
|
}
|
|
|
|
void HalTiltSensor::update(const uint8_t mode, const uint8_t orientation, const bool inReader) {
|
|
if (!_available) {
|
|
return;
|
|
}
|
|
|
|
// State machine: wake up or sleep based on the enabled flag
|
|
if ((mode != CrossPointTiltPageTurn::TILT_OFF) && !_isAwake) {
|
|
_isAwake = wake();
|
|
return;
|
|
} else if ((mode == CrossPointTiltPageTurn::TILT_OFF) && _isAwake) {
|
|
_isAwake = !deepSleep();
|
|
return;
|
|
}
|
|
|
|
// If disabled, skip the rest of the polling logic and avoid unnecessary I2C traffic in non-reader activities
|
|
if ((mode == CrossPointTiltPageTurn::TILT_OFF) || !inReader) {
|
|
return;
|
|
}
|
|
|
|
const unsigned long now = millis();
|
|
// Stabilization: discard readings during gyro startup transient
|
|
if ((now - _wakeMs) < WAKE_STABILIZE_MS) {
|
|
return;
|
|
}
|
|
|
|
if ((now - _lastPollMs) < POLL_INTERVAL_MS) {
|
|
return;
|
|
}
|
|
_lastPollMs = now;
|
|
|
|
float gx, gy, gz;
|
|
if (!readGyro(gx, gy, gz)) {
|
|
return;
|
|
}
|
|
|
|
// Map the gyro axis to left/right tilt based on reader orientation.
|
|
// On the X3 PCB: X axis = left/right in portrait, Y axis = left/right in landscape.
|
|
float tiltAxis;
|
|
switch (orientation) {
|
|
case CrossPointOrientation::PORTRAIT:
|
|
tiltAxis = mode == CrossPointTiltPageTurn::TILT_INVERTED ? -gx : gx;
|
|
break;
|
|
case CrossPointOrientation::INVERTED:
|
|
tiltAxis = mode == CrossPointTiltPageTurn::TILT_INVERTED ? gx : -gx;
|
|
break;
|
|
case CrossPointOrientation::LANDSCAPE_CW:
|
|
tiltAxis = mode == CrossPointTiltPageTurn::TILT_INVERTED ? gy : -gy;
|
|
break;
|
|
case CrossPointOrientation::LANDSCAPE_CCW:
|
|
tiltAxis = mode == CrossPointTiltPageTurn::TILT_INVERTED ? -gy : gy;
|
|
break;
|
|
default:
|
|
tiltAxis = gx;
|
|
break;
|
|
}
|
|
|
|
if (_inTilt) {
|
|
// Wait for device to return to neutral before allowing next trigger
|
|
if (fabsf(tiltAxis) < NEUTRAL_RATE_DPS) {
|
|
_inTilt = false;
|
|
}
|
|
} else {
|
|
// Check for new tilt gesture (with cooldown)
|
|
if ((now - _lastTiltMs) >= COOLDOWN_MS) {
|
|
if (tiltAxis > RATE_THRESHOLD_DPS) {
|
|
_tiltForwardEvent = true;
|
|
_hadActivity = true;
|
|
_inTilt = true;
|
|
_lastTiltMs = now;
|
|
LOG_INF("GYR", "Forward Trigger=(%.1f) dps", tiltAxis);
|
|
} else if (tiltAxis < -RATE_THRESHOLD_DPS) {
|
|
_tiltBackEvent = true;
|
|
_hadActivity = true;
|
|
_inTilt = true;
|
|
_lastTiltMs = now;
|
|
LOG_INF("GYR", "Backward Trigger=(%.1f) dps", tiltAxis);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HalTiltSensor::wasTiltedForward() {
|
|
const bool val = _tiltForwardEvent;
|
|
_tiltForwardEvent = false;
|
|
return val;
|
|
}
|
|
|
|
bool HalTiltSensor::wasTiltedBack() {
|
|
const bool val = _tiltBackEvent;
|
|
_tiltBackEvent = false;
|
|
return val;
|
|
}
|
|
|
|
bool HalTiltSensor::hadActivity() {
|
|
const bool val = _hadActivity;
|
|
_hadActivity = false;
|
|
return val;
|
|
}
|
|
|
|
void HalTiltSensor::clearPendingEvents() {
|
|
_tiltForwardEvent = false;
|
|
_tiltBackEvent = false;
|
|
_hadActivity = false;
|
|
// Intentionally preserve _inTilt so a held tilt doesn't retrigger on next poll
|
|
}
|