mirror of
https://github.com/pelgraine/Meck.git
synced 2026-07-29 12:52:40 +02:00
t-watch - initial step counter implementation
This commit is contained in:
@@ -1787,7 +1787,7 @@ static void lastHeardToggleContact() {
|
||||
if (row == 2 && col == 0) { ui_task.gotoTraceScreen(); return 0; }
|
||||
// row 2 col 1 = Maps -- TODO subscreen not yet built; no-op for now.
|
||||
if (row == 3 && col == 0) { ui_task.gotoNotesScreen(); return 0; }
|
||||
// row 3 col 1 = Games -- placeholder, no target yet; no-op for now.
|
||||
if (row == 3 && col == 1) { ui_task.gotoStepsScreen(); return 0; }
|
||||
}
|
||||
return 0; // consume long-press on the tile page
|
||||
}
|
||||
|
||||
@@ -81,6 +81,12 @@ static const uint8_t icon_gamepad[] PROGMEM = {
|
||||
0x40,0x20, 0x30,0xC0, 0x10,0x80, 0x1F,0x80, 0x00,0x00, 0x00,0x00,
|
||||
};
|
||||
|
||||
// Star -- 12x12 home tile icon (Steps). MSB-first, 2 bytes per row.
|
||||
static const uint8_t icon_star[] PROGMEM = {
|
||||
0x06,0x00, 0x06,0x00, 0x0F,0x00, 0xFF,0xF0, 0x7F,0xE0, 0x3F,0xC0,
|
||||
0x1F,0x80, 0x3F,0xC0, 0x39,0xC0, 0x70,0xE0, 0x60,0x60, 0x00,0x00,
|
||||
};
|
||||
|
||||
// 🔔 Bell -- 7x8 status bar indicator (alarm enabled)
|
||||
// MSB-first, 1 byte per row
|
||||
#define BELL_ICON_W 7
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef AUTO_OFF_MILLIS
|
||||
#define AUTO_OFF_MILLIS 45000 // 45 seconds
|
||||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
||||
#endif
|
||||
// Right-aligned values on the dense home pages compensate for the e-ink X
|
||||
// origin offset so they are not clipped at the right edge. Default 0 for
|
||||
@@ -686,7 +686,7 @@ public:
|
||||
{ {icon_envelope, "Messages", 0x0CB1}, {icon_people, "Contacts", 0xCAA0} },
|
||||
{ {icon_gear, "Settings", 0x0560}, {icon_search, "Discover", 0xF81F} },
|
||||
{ {icon_trace, "Trace", 0xF800}, {icon_map, "Maps", 0x231D} },
|
||||
{ {icon_notepad, "Notes", 0x07FF}, {icon_gamepad, "Games", 0xFFE0} },
|
||||
{ {icon_notepad, "Notes", 0x07FF}, {icon_star, "Steps", 0xFFE0} },
|
||||
};
|
||||
const uint16_t TILE_FILL = 0x18C5; // dark navy
|
||||
|
||||
@@ -1485,6 +1485,27 @@ public:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Steps screen: big daily step count in the clock font. Opened by long-pressing
|
||||
// the Steps home tile; any tap dismisses back to the tiles.
|
||||
class StepsScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
public:
|
||||
StepsScreen(UITask* task) : _task(task) { }
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%u", (unsigned)_task->getTodaySteps());
|
||||
((LGFXDisplay*)&display)->printClockFont(display.width() / 2, display.height() / 2 - 18, buf);
|
||||
return 1000;
|
||||
}
|
||||
|
||||
bool handleInput(char c) override {
|
||||
_task->gotoHomeScreen();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
|
||||
@@ -1658,6 +1679,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
#endif
|
||||
#if defined(LILYGO_TWATCH_S3_PLUS)
|
||||
lock_screen = new ClockScreen(this, &rtc_clock, node_prefs);
|
||||
steps_screen = new StepsScreen(this);
|
||||
#endif
|
||||
#ifdef TWATCH_COMPOSE_ENABLED
|
||||
tw_picker = new TWatchChannelPicker(_display);
|
||||
@@ -2223,20 +2245,6 @@ void UITask::loop() {
|
||||
} else {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
}
|
||||
#elif defined(LILYGO_TWATCH_S3_PLUS)
|
||||
// Watch: on the contacts screen a button click opens the path editor for
|
||||
// the selected contact; elsewhere it wakes the display / emits KEY_NEXT.
|
||||
if (isOnContactsScreen()) {
|
||||
int idx = ((ContactsScreen*)contacts_screen)->getSelectedContactIdx();
|
||||
if (idx >= 0) {
|
||||
gotoPathEditor(idx);
|
||||
c = 0;
|
||||
} else {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
}
|
||||
} else {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
}
|
||||
#else
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
#endif
|
||||
@@ -2635,6 +2643,17 @@ if (curr) curr->poll();
|
||||
setCurrScreen(lock_screen);
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
}
|
||||
|
||||
// Roll the daily step baseline over at local midnight (12am-to-12am total).
|
||||
// No hardware reset -- the displayed count is raw_count - baseline.
|
||||
{
|
||||
uint32_t now = rtc_clock.getCurrentTime();
|
||||
if (now > 1700000000) { // valid timestamp
|
||||
int32_t localDay = ((int32_t)now + ((int32_t)the_mesh.getNodePrefs()->utc_offset_hours * 3600)) / 86400;
|
||||
if (_lastStepDay == 0) _lastStepDay = localDay; // seed on first valid read
|
||||
else if (localDay != _lastStepDay) { _stepBaseline = board.getStepCount(); _lastStepDay = localDay; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Auto-lock idle timer — runs regardless of display on/off state
|
||||
@@ -3617,6 +3636,22 @@ void UITask::gotoTraceScreen() {
|
||||
_next_refresh = 100;
|
||||
}
|
||||
|
||||
#if defined(LILYGO_TWATCH_S3_PLUS)
|
||||
uint32_t UITask::getTodaySteps() {
|
||||
uint32_t raw = board.getStepCount();
|
||||
return (raw >= _stepBaseline) ? (raw - _stepBaseline) : raw;
|
||||
}
|
||||
|
||||
void UITask::gotoStepsScreen() {
|
||||
setCurrScreen(steps_screen);
|
||||
if (_display != NULL && !_display->isOn()) {
|
||||
_display->turnOn();
|
||||
}
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
_next_refresh = 100;
|
||||
}
|
||||
#endif
|
||||
|
||||
void UITask::gotoGamesMenu() {
|
||||
GamesMenuScreen* gm = (GamesMenuScreen*)games_menu_screen;
|
||||
gm->enter();
|
||||
|
||||
@@ -119,6 +119,11 @@ class UITask : public AbstractUITask {
|
||||
int _tileGridVY = 44; // Virtual Y of tile grid top (updated each render)
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LILYGO_TWATCH_S3_PLUS)
|
||||
UIScreen* lock_screen; // Lock screen (big clock + battery + unread)
|
||||
#if defined(LILYGO_TWATCH_S3_PLUS)
|
||||
UIScreen* steps_screen; // Steps screen (big daily step count)
|
||||
int32_t _lastStepDay = 0; // local day-of-epoch, for the midnight step reset
|
||||
uint32_t _stepBaseline = 0; // raw step count at the start of the local day
|
||||
#endif
|
||||
UIScreen* _screenBeforeLock = nullptr;
|
||||
bool _locked = false;
|
||||
unsigned long _lastInputMillis = 0; // Auto-lock idle tracking
|
||||
@@ -220,6 +225,10 @@ public:
|
||||
void gotoGamesMenu(); // Navigate to games launcher menu
|
||||
void gotoSnakeScreen(); // Navigate to snake game
|
||||
void gotoMinesweeperScreen(); // Navigate to minesweeper game
|
||||
#if defined(LILYGO_TWATCH_S3_PLUS)
|
||||
void gotoStepsScreen(); // Navigate to the step counter screen
|
||||
uint32_t getTodaySteps(); // Daily step count (raw - baseline)
|
||||
#endif
|
||||
#if HAS_GPS
|
||||
void gotoMapScreen(); // Navigate to map tile screen
|
||||
#endif
|
||||
@@ -281,6 +290,9 @@ public:
|
||||
bool isOnTraceScreen() const { return curr == trace_screen; }
|
||||
bool isOnGamesMenu() const { return curr == games_menu_screen; }
|
||||
bool isOnSnakeScreen() const { return curr == snake_screen; }
|
||||
#if defined(LILYGO_TWATCH_S3_PLUS)
|
||||
bool isOnStepsScreen() const { return curr == steps_screen; }
|
||||
#endif
|
||||
bool isOnMinesweeperScreen() const { return curr == minesweeper_screen; }
|
||||
bool isOnMapScreen() const { return curr == map_screen; }
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro) || defined(LILYGO_TWATCH_S3_PLUS)
|
||||
@@ -370,6 +382,9 @@ public:
|
||||
UIScreen* getTraceScreen() const { return trace_screen; }
|
||||
UIScreen* getGamesMenuScreen() const { return games_menu_screen; }
|
||||
UIScreen* getSnakeScreen() const { return snake_screen; }
|
||||
#if defined(LILYGO_TWATCH_S3_PLUS)
|
||||
UIScreen* getStepsScreen() const { return steps_screen; }
|
||||
#endif
|
||||
UIScreen* getMinesweeperScreen() const { return minesweeper_screen; }
|
||||
UIScreen* getMapScreen() const { return map_screen; }
|
||||
#ifdef MECK_WEB_READER
|
||||
|
||||
@@ -8,6 +8,56 @@ volatile uint32_t TWatchS3PlusBoard::_tilt_isr_count = 0; // TEMP diagnostic
|
||||
|
||||
void IRAM_ATTR TWatchS3PlusBoard::onTiltISR() { _tilt_flag = true; _tilt_isr_count++; }
|
||||
|
||||
// ---- Wrapper-free BMA423 step counter (raw I2C) ----------------------------
|
||||
// SensorLib's SensorBMA423 step-counter methods do not compile in this build,
|
||||
// so the step counter is driven directly over I2C. Register/offset/mask values
|
||||
// are from the Bosch BMA423 driver.
|
||||
#define BMA423_REG_STEP_CNT_OUT 0x1E // 4-byte little-endian step count output
|
||||
#define BMA423_REG_FEATURE_CONFIG 0x5E // 64-byte feature config stream
|
||||
#define BMA423_FEATURE_LEN 64
|
||||
#define BMA423_STEP_EN_BYTE 0x37 // BMA423_STEP_CNTR_OFFSET(0x36) + 1
|
||||
#define BMA423_STEP_EN_BIT 0x10 // BMA423_STEP_CNTR_EN_MSK
|
||||
|
||||
#define BMA423_REG_POWER_CONF 0x7C // BMA4_POWER_CONF_ADDR
|
||||
#define BMA423_ADV_PWR_SAVE_BIT 0x01 // BMA4_ADVANCE_POWER_SAVE_MSK
|
||||
|
||||
static bool bma423ReadRegs(uint8_t reg, uint8_t* buf, uint8_t len) {
|
||||
Wire.beginTransmission(I2C_ADDR_ACCEL);
|
||||
Wire.write(reg);
|
||||
if (Wire.endTransmission(false) != 0) return false;
|
||||
if (Wire.requestFrom((int)I2C_ADDR_ACCEL, (int)len) != len) return false;
|
||||
for (uint8_t i = 0; i < len; i++) buf[i] = Wire.read();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool bma423WriteRegs(uint8_t reg, const uint8_t* buf, uint8_t len) {
|
||||
Wire.beginTransmission(I2C_ADDR_ACCEL);
|
||||
Wire.write(reg);
|
||||
for (uint8_t i = 0; i < len; i++) Wire.write(buf[i]);
|
||||
return Wire.endTransmission() == 0;
|
||||
}
|
||||
|
||||
// Enable the step counter by setting its enable bit in the feature config,
|
||||
// preserving every other byte (tilt lives at a different offset, 0x3A, so it is
|
||||
// untouched). The feature config can only be written with advanced-power-save
|
||||
// disabled, so we bracket the write and restore the prior power state after.
|
||||
static void bma423EnableStepCounter() {
|
||||
uint8_t pc;
|
||||
if (!bma423ReadRegs(BMA423_REG_POWER_CONF, &pc, 1)) return; // save power state
|
||||
uint8_t off = pc & ~BMA423_ADV_PWR_SAVE_BIT; // disable adv power save
|
||||
bma423WriteRegs(BMA423_REG_POWER_CONF, &off, 1);
|
||||
delay(2); // wake from low-power (>=450us)
|
||||
|
||||
uint8_t cfg[BMA423_FEATURE_LEN];
|
||||
if (bma423ReadRegs(BMA423_REG_FEATURE_CONFIG, cfg, BMA423_FEATURE_LEN)) {
|
||||
cfg[BMA423_STEP_EN_BYTE] |= BMA423_STEP_EN_BIT;
|
||||
bma423WriteRegs(BMA423_REG_FEATURE_CONFIG, cfg, BMA423_FEATURE_LEN);
|
||||
delay(1); // write settle
|
||||
}
|
||||
|
||||
bma423WriteRegs(BMA423_REG_POWER_CONF, &pc, 1); // restore power state
|
||||
}
|
||||
|
||||
void TWatchS3PlusBoard::begin() {
|
||||
ESP32Board::begin();
|
||||
power_init();
|
||||
@@ -29,6 +79,10 @@ void TWatchS3PlusBoard::begin() {
|
||||
// on a self-clearing line otherwise locks tilt-wake out permanently).
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_ACCEL_IRQ), onTiltISR, RISING);
|
||||
_accel->enableTiltDetector(true, true);
|
||||
// Enable the hardware step counter via raw I2C (SensorLib's wrapper method
|
||||
// does not compile in this build). It then counts in the BMA423 feature
|
||||
// engine with no CPU cost, even while the display is off.
|
||||
bma423EnableStepCounter();
|
||||
}
|
||||
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
@@ -140,4 +194,11 @@ bool TWatchS3PlusBoard::tiltFired() {
|
||||
digitalRead(PIN_ACCEL_IRQ), (unsigned)_tilt_isr_count);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t TWatchS3PlusBoard::getStepCount() {
|
||||
uint8_t d[4];
|
||||
if (!bma423ReadRegs(BMA423_REG_STEP_CNT_OUT, d, 4)) return 0;
|
||||
return (uint32_t)d[0] | ((uint32_t)d[1] << 8) |
|
||||
((uint32_t)d[2] << 16) | ((uint32_t)d[3] << 24);
|
||||
}
|
||||
@@ -57,6 +57,9 @@ public:
|
||||
return PMU ? PMU->getBatteryPercent() : 0;
|
||||
}
|
||||
|
||||
// Wrapper-free BMA423 step count (raw I2C; defined in the .cpp).
|
||||
uint32_t getStepCount();
|
||||
|
||||
// GPS power is the AXP2101 BLDO1 rail. Off at boot; toggled at runtime via
|
||||
// the gps_enabled pref (boot) and the "gps on/off" CLI command (live).
|
||||
void gpsPowerOn();
|
||||
|
||||
Reference in New Issue
Block a user