From 3007dd62bd9b1c1a2ac69232860756f0a09102e9 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 16 May 2026 21:21:30 +0800 Subject: [PATCH 1/8] feat: implement true sleep timer and status icon --- ats-mini/Common.h | 3 +- ats-mini/Draw.cpp | 22 ++++++++++++++ ats-mini/Draw.h | 3 ++ ats-mini/Layout-Default.cpp | 3 ++ ats-mini/Layout-SMeter.cpp | 3 ++ ats-mini/Menu.cpp | 59 +++++++++++++++++++++++++++++++++---- ats-mini/Menu.h | 12 ++++---- ats-mini/Storage.cpp | 2 ++ ats-mini/ats-mini.ino | 12 ++++++++ 9 files changed, 107 insertions(+), 12 deletions(-) diff --git a/ats-mini/Common.h b/ats-mini/Common.h index b17f8c1c..be32f4cc 100644 --- a/ats-mini/Common.h +++ b/ats-mini/Common.h @@ -169,7 +169,8 @@ extern int16_t currentBFO; extern uint8_t currentMode; extern uint16_t currentCmd; extern uint16_t currentBrt; -extern uint16_t currentSleep; +extern uint16_t currentSleepTimer; +extern uint32_t sleepTimerStart; extern uint8_t sleepModeIdx; extern bool zoomMenu; extern int8_t scrollDirection; diff --git a/ats-mini/Draw.cpp b/ats-mini/Draw.cpp index b86e4061..61f71c2e 100644 --- a/ats-mini/Draw.cpp +++ b/ats-mini/Draw.cpp @@ -46,6 +46,28 @@ void drawBleIndicator(int x, int y) } } +// +// Draw Sleep Timer indicator +// +void drawSleepTimerIndicator(int x, int y) +{ + if(currentSleepTimer > 0) + { + uint16_t color = TH.text_muted; + + // Draw Zzz + // Big Z + spr.drawLine(x, y+2, x+6, y+2, color); + spr.drawLine(x+6, y+2, x, y+8, color); + spr.drawLine(x, y+8, x+6, y+8, color); + + // Small z + spr.drawLine(x+8, y+7, x+12, y+7, color); + spr.drawLine(x+12, y+7, x+8, y+11, color); + spr.drawLine(x+8, y+11, x+12, y+11, color); + } +} + // // Draw WiFi indicator // diff --git a/ats-mini/Draw.h b/ats-mini/Draw.h index c5e76f61..e119a1ec 100644 --- a/ats-mini/Draw.h +++ b/ats-mini/Draw.h @@ -31,6 +31,8 @@ #define WIFI_OFFSET_Y 0 // WiFi y offset #define BLE_OFFSET_X 104 // BLE x offset #define BLE_OFFSET_Y 0 // BLE y offset +#define SLEEP_OFFSET_X 120 // Sleep Timer x offset +#define SLEEP_OFFSET_Y 0 // Sleep Timer y offset void drawMessage(const char *msg); void drawZoomedMenu(const char *text, bool force = false); @@ -40,6 +42,7 @@ void drawScreen(const char *statusLine1 = 0, const char *statusLine2 = 0); void drawWiFiIndicator(int x, int y); void drawSaveIndicator(int x, int y); void drawBleIndicator(int x, int y); +void drawSleepTimerIndicator(int x, int y); void drawBandAndMode(const char *band, const char *mode, int x, int y); void drawFrequency(uint32_t freq, int x, int y, int ux, int uy, uint8_t hl); void drawLongStationName(const char *name, int x, int y); diff --git a/ats-mini/Layout-Default.cpp b/ats-mini/Layout-Default.cpp index 7056a4ec..ca9d4bb7 100644 --- a/ats-mini/Layout-Default.cpp +++ b/ats-mini/Layout-Default.cpp @@ -12,6 +12,9 @@ void drawLayoutDefault(const char *statusLine1, const char *statusLine2) // Draw BLE icon drawBleIndicator(BLE_OFFSET_X, BLE_OFFSET_Y); + // Draw Sleep Timer icon + drawSleepTimerIndicator(SLEEP_OFFSET_X, SLEEP_OFFSET_Y); + // Draw battery indicator & voltage bool has_voltage = drawBattery(BATT_OFFSET_X, BATT_OFFSET_Y); diff --git a/ats-mini/Layout-SMeter.cpp b/ats-mini/Layout-SMeter.cpp index 203cd938..4b2fca24 100644 --- a/ats-mini/Layout-SMeter.cpp +++ b/ats-mini/Layout-SMeter.cpp @@ -158,6 +158,9 @@ void drawLayoutSmeter(const char *statusLine1, const char *statusLine2) // Draw BLE icon drawBleIndicator(BLE_OFFSET_X, BLE_OFFSET_Y); + // Draw Sleep Timer icon + drawSleepTimerIndicator(SLEEP_OFFSET_X, SLEEP_OFFSET_Y); + // Draw battery indicator & voltage bool has_voltage = drawBattery(BATT_OFFSET_X, BATT_OFFSET_Y); diff --git a/ats-mini/Menu.cpp b/ats-mini/Menu.cpp index d2cd5329..8487793f 100644 --- a/ats-mini/Menu.cpp +++ b/ats-mini/Menu.cpp @@ -121,11 +121,12 @@ static const char *menu[] = #define MENU_SCROLL 8 #define MENU_SLEEP 9 #define MENU_SLEEPMODE 10 -#define MENU_LOADEIBI 11 -#define MENU_USBMODE 12 -#define MENU_BLEMODE 13 -#define MENU_WIFIMODE 14 -#define MENU_ABOUT 15 +#define MENU_SLEEPTIMER 11 +#define MENU_LOADEIBI 12 +#define MENU_USBMODE 13 +#define MENU_BLEMODE 14 +#define MENU_WIFIMODE 15 +#define MENU_ABOUT 16 int8_t settingsIdx = MENU_BRIGHTNESS; @@ -143,6 +144,7 @@ static const char *settings[] = "Scroll Dir.", "Sleep", "Sleep Mode", + "Sleep Timer", "Load EiBi", "USB Port", "Bluetooth", @@ -627,6 +629,29 @@ static void doSleepMode(int16_t enc) sleepModeIdx = wrap_range(sleepModeIdx, enc, 0, LAST_ITEM(sleepModeDesc)); } +void doSleepTimer(int16_t enc) +{ + int16_t val = currentSleepTimer; + if (enc > 0) { + for(int i=0; i 120) val = 120; + + if (currentSleepTimer != (uint16_t)val) { + currentSleepTimer = val; + sleepTimerStart = millis(); + } +} + static void doUSBMode(int16_t enc) { usbModeIdx = wrap_range(usbModeIdx, enc, 0, LAST_ITEM(usbModeDesc)); @@ -924,6 +949,7 @@ static void clickSettings(int cmd, bool shortPress) case MENU_SCROLL: currentCmd = CMD_SCROLL; break; case MENU_SLEEP: currentCmd = CMD_SLEEP; break; case MENU_SLEEPMODE: currentCmd = CMD_SLEEPMODE; break; + case MENU_SLEEPTIMER: currentCmd = CMD_SLEEPTIMER; break; case MENU_UTCOFFSET: currentCmd = CMD_UTCOFFSET; break; case MENU_USBMODE: currentCmd = CMD_USBMODE; break; case MENU_BLEMODE: currentCmd = CMD_BLEMODE; break; @@ -967,6 +993,7 @@ bool doSideBar(uint16_t cmd, int16_t enc, int16_t enca) case CMD_MEMORY: doMemory(scrollDirection * enca);break; case CMD_SLEEP: doSleep(enca);break; case CMD_SLEEPMODE: doSleepMode(scrollDirection * enc);break; + case CMD_SLEEPTIMER: doSleepTimer(enc);break; case CMD_USBMODE: doUSBMode(scrollDirection * enc);break; case CMD_BLEMODE: doBleMode(scrollDirection * enc);break; case CMD_WIFIMODE: doWiFiMode(scrollDirection * enc);break; @@ -1257,6 +1284,25 @@ static void drawSleepMode(int x, int y, int sx) } } +static void drawSleepTimer(int x, int y, int sx) +{ + drawCommon(settings[MENU_SLEEPTIMER], x, y, sx); + drawZoomedMenu(settings[MENU_SLEEPTIMER]); + spr.setTextDatum(MC_DATUM); + + spr.setTextColor(TH.menu_param); + if(currentSleepTimer) + { + char text[16]; + sprintf(text, "%d m", currentSleepTimer); + spr.drawString(text, 40+x+(sx/2), 60+y, 4); + } + else + { + spr.drawString("Off", 40+x+(sx/2), 60+y, 4); + } +} + static void drawUSBMode(int x, int y, int sx) { drawCommon(settings[MENU_USBMODE], x, y, sx, true); @@ -1725,7 +1771,8 @@ void drawSideBar(uint16_t cmd, int x, int y, int sx) case CMD_MEMORY: drawMemory(x, y, sx); break; case CMD_SLEEP: drawSleep(x, y, sx); break; case CMD_SLEEPMODE: drawSleepMode(x, y, sx); break; - case CMD_USBMODE: drawUSBMode(x, y, sx); break; + case CMD_SLEEPTIMER: drawSleepTimer(x, y, sx); break; + case CMD_LOADEIBI: drawLoadEiBi(x, y, sx); break; case CMD_BLEMODE: drawBleMode(x, y, sx); break; case CMD_WIFIMODE: drawWiFiMode(x, y, sx); break; case CMD_ZOOM: drawZoom(x, y, sx); break; diff --git a/ats-mini/Menu.h b/ats-mini/Menu.h index cc17c484..30f9c5b7 100644 --- a/ats-mini/Menu.h +++ b/ats-mini/Menu.h @@ -40,11 +40,12 @@ #define CMD_SCROLL 0x2900 // | #define CMD_SLEEP 0x2A00 // | #define CMD_SLEEPMODE 0x2B00 // | -#define CMD_LOADEIBI 0x2C00 // | -#define CMD_USBMODE 0x2D00 // | -#define CMD_BLEMODE 0x2E00 // | -#define CMD_WIFIMODE 0x2F00 // | -#define CMD_ABOUT 0x3000 //-+ +#define CMD_SLEEPTIMER 0x2C00 // | +#define CMD_LOADEIBI 0x2D00 // | +#define CMD_USBMODE 0x2E00 // | +#define CMD_BLEMODE 0x2F00 // | +#define CMD_WIFIMODE 0x3000 // | +#define CMD_ABOUT 0x3100 //-+ // UI Layouts #define UI_DEFAULT 0 @@ -130,6 +131,7 @@ void doVolume(int16_t enc); void doBrt(int16_t enc); void doCal(int16_t enc); void doStep(int16_t enc); +void doSleepTimer(int16_t enc); void doMode(int16_t enc); void doBand(int16_t enc); diff --git a/ats-mini/Storage.cpp b/ats-mini/Storage.cpp index 3aaf48fb..de02b9d7 100644 --- a/ats-mini/Storage.cpp +++ b/ats-mini/Storage.cpp @@ -183,6 +183,7 @@ void prefsSave(uint32_t items) prefs.putUChar("AmSoftMute", AmSoftMuteIdx); // AM soft mute prefs.putUChar("SsbSoftMute", SsbSoftMuteIdx); // SSB soft mute prefs.putUShort("Sleep", currentSleep); // Sleep delay + prefs.putUShort("SleepTmr", currentSleepTimer); // Sleep timer prefs.putUChar("Theme", themeIdx); // Color theme prefs.putUChar("RDSMode", rdsModeIdx); // RDS mode prefs.putUChar("SleepMode", sleepModeIdx); // Sleep mode @@ -260,6 +261,7 @@ bool prefsLoad(uint32_t items) AmSoftMuteIdx = prefs.getUChar("AmSoftMute", AmSoftMuteIdx); // AM soft mute SsbSoftMuteIdx = prefs.getUChar("SsbSoftMute", SsbSoftMuteIdx); // SSB soft mute currentSleep = prefs.getUShort("Sleep", currentSleep); // Sleep delay + currentSleepTimer = prefs.getUShort("SleepTmr", 0); // Sleep timer themeIdx = prefs.getUChar("Theme", themeIdx); // Color theme rdsModeIdx = prefs.getUChar("RDSMode", rdsModeIdx); // RDS mode sleepModeIdx = prefs.getUChar("SleepMode", sleepModeIdx); // Sleep mode diff --git a/ats-mini/ats-mini.ino b/ats-mini/ats-mini.ino index 6405ebe0..19ec6137 100644 --- a/ats-mini/ats-mini.ino +++ b/ats-mini/ats-mini.ino @@ -72,6 +72,8 @@ uint8_t FmRegionIdx = 0; // FM Region uint16_t currentBrt = 130; // Display brightness, range = 10 to 255 in steps of 5 uint16_t currentSleep = DEFAULT_SLEEP; // Display sleep timeout, range = 0 to 255 in steps of 5 +uint16_t currentSleepTimer = 0; // Audio sleep timer, minutes +uint32_t sleepTimerStart = millis(); // Audio sleep timer start time long elapsedSleep = millis(); // Display sleep timer bool zoomMenu = false; // Display zoomed menu item int8_t scrollDirection = 1; // Menu scroll direction @@ -949,6 +951,16 @@ void loop() elapsedSleep = elapsedCommand = currentTime = millis(); } + // Audio sleep timer + if(currentSleepTimer && (currentTime - sleepTimerStart) > (currentSleepTimer * 60000UL)) + { + currentSleepTimer = 0; + prefsRequestSave(SAVE_SETTINGS); + muteOn(MUTE_MAIN, true); + if(!sleepOn()) sleepOn(1); + elapsedSleep = elapsedCommand = currentTime = millis(); + } + if((currentTime - elapsedRSSI) > MIN_ELAPSED_RSSI_TIME) { needRedraw |= processRssiSnr(); From f33292d08be872b99ad0e27f2d60f7a0a4ddff87 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 16 May 2026 22:37:38 +0800 Subject: [PATCH 2/8] fix: restore missing variables and menu logic after compilation --- .gitignore | 1 + ats-mini/Common.h | 1 + ats-mini/Menu.cpp | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 286ccec3..58a0ed00 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ __pycache__/ # Project build/ +ats-mini/build/ diff --git a/ats-mini/Common.h b/ats-mini/Common.h index be32f4cc..66032b1e 100644 --- a/ats-mini/Common.h +++ b/ats-mini/Common.h @@ -169,6 +169,7 @@ extern int16_t currentBFO; extern uint8_t currentMode; extern uint16_t currentCmd; extern uint16_t currentBrt; +extern uint16_t currentSleep; extern uint16_t currentSleepTimer; extern uint32_t sleepTimerStart; extern uint8_t sleepModeIdx; diff --git a/ats-mini/Menu.cpp b/ats-mini/Menu.cpp index 8487793f..dc14aa53 100644 --- a/ats-mini/Menu.cpp +++ b/ats-mini/Menu.cpp @@ -1772,7 +1772,7 @@ void drawSideBar(uint16_t cmd, int x, int y, int sx) case CMD_SLEEP: drawSleep(x, y, sx); break; case CMD_SLEEPMODE: drawSleepMode(x, y, sx); break; case CMD_SLEEPTIMER: drawSleepTimer(x, y, sx); break; - case CMD_LOADEIBI: drawLoadEiBi(x, y, sx); break; + case CMD_USBMODE: drawUSBMode(x, y, sx); break; case CMD_BLEMODE: drawBleMode(x, y, sx); break; case CMD_WIFIMODE: drawWiFiMode(x, y, sx); break; case CMD_ZOOM: drawZoom(x, y, sx); break; From 6edb06e975447365a4fbf704a71d1bdbdd675514 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 17 May 2026 12:14:05 +0800 Subject: [PATCH 3/8] feat: optimize sleep timer with wrap-around logic --- ats-mini/Menu.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ats-mini/Menu.cpp b/ats-mini/Menu.cpp index dc14aa53..1f112769 100644 --- a/ats-mini/Menu.cpp +++ b/ats-mini/Menu.cpp @@ -639,12 +639,11 @@ void doSleepTimer(int16_t enc) } } else if (enc < 0) { for(int i=0; i<-enc; i++) { - if (val <= 20) val--; + if (val == 0) val = 120; + else if (val <= 20) val--; else val -= 5; } } - if (val < 0) val = 0; - if (val > 120) val = 120; if (currentSleepTimer != (uint16_t)val) { currentSleepTimer = val; From 9e82813a7e9e7f3d9363845ecc221a8ac60b4bd7 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 17 May 2026 12:28:40 +0800 Subject: [PATCH 4/8] fix: restore volume upon soft power-on after sleep timer shutdown. --- ats-mini/Common.h | 1 + ats-mini/Utils.cpp | 7 +++++++ ats-mini/ats-mini.ino | 2 ++ 3 files changed, 10 insertions(+) diff --git a/ats-mini/Common.h b/ats-mini/Common.h index 66032b1e..6b569abf 100644 --- a/ats-mini/Common.h +++ b/ats-mini/Common.h @@ -172,6 +172,7 @@ extern uint16_t currentBrt; extern uint16_t currentSleep; extern uint16_t currentSleepTimer; extern uint32_t sleepTimerStart; +extern bool sleepTimerMuted; extern uint8_t sleepModeIdx; extern bool zoomMenu; extern int8_t scrollDirection; diff --git a/ats-mini/Utils.cpp b/ats-mini/Utils.cpp index 57fe1b6c..71d04dbe 100644 --- a/ats-mini/Utils.cpp +++ b/ats-mini/Utils.cpp @@ -249,6 +249,13 @@ bool sleepOn(int x) else if((x==0) && sleep_on) { sleep_on = false; + + // Restore volume if muted by Sleep Timer + if (sleepTimerMuted) { + muteOn(MUTE_MAIN, false); + sleepTimerMuted = false; + } + tft.writecommand(ST7789_SLPOUT); delay(120); tft.writecommand(ST7789_DISPON); diff --git a/ats-mini/ats-mini.ino b/ats-mini/ats-mini.ino index 19ec6137..dd2bce5e 100644 --- a/ats-mini/ats-mini.ino +++ b/ats-mini/ats-mini.ino @@ -74,6 +74,7 @@ uint16_t currentBrt = 130; // Display brightness, range = 10 to 255 uint16_t currentSleep = DEFAULT_SLEEP; // Display sleep timeout, range = 0 to 255 in steps of 5 uint16_t currentSleepTimer = 0; // Audio sleep timer, minutes uint32_t sleepTimerStart = millis(); // Audio sleep timer start time +bool sleepTimerMuted = false; // Flag to restore volume on soft power-on long elapsedSleep = millis(); // Display sleep timer bool zoomMenu = false; // Display zoomed menu item int8_t scrollDirection = 1; // Menu scroll direction @@ -957,6 +958,7 @@ void loop() currentSleepTimer = 0; prefsRequestSave(SAVE_SETTINGS); muteOn(MUTE_MAIN, true); + sleepTimerMuted = true; if(!sleepOn()) sleepOn(1); elapsedSleep = elapsedCommand = currentTime = millis(); } From 7eca6602cade9f7483f644bb3c1d5d844c0a1fe0 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 23 May 2026 20:10:37 +0800 Subject: [PATCH 5/8] feat: swap default encoder VFO tuning with Seek and rename Seek menu to Tune --- .gitignore | 3 +++ ats-mini/Menu.cpp | 11 +++-------- ats-mini/ats-mini.ino | 11 +++++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 58a0ed00..8651c215 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ __pycache__/ # Project build/ ats-mini/build/ + +.gemini/ +gha-creds-*.json diff --git a/ats-mini/Menu.cpp b/ats-mini/Menu.cpp index 1f112769..195db510 100644 --- a/ats-mini/Menu.cpp +++ b/ats-mini/Menu.cpp @@ -95,7 +95,7 @@ static const char *menu[] = "Band", "Volume", "Step", - "Seek", + "Tune", "Scan", "Memory", "Squelch", @@ -546,7 +546,7 @@ static void clickSquelch(bool shortPress) static void clickSeek(bool shortPress) { - if(shortPress) seekMode(true); else currentCmd = CMD_NONE; + currentCmd = CMD_NONE; } static void clickScan(bool shortPress) @@ -1196,12 +1196,7 @@ static void drawSeek(int x, int y, int sx) spr.drawSmoothArc(40+x+(sx/2), 66+y, 30, 27, 225, 360, TH.menu_param, TH.menu_bg); spr.fillTriangle(40+x+(sx/2)+5, 66+y+32, 40+x+(sx/2)-5, 66+y+27, 40+x+(sx/2)+5, 66+y+22, TH.menu_param); - if(seekMode()==SEEK_SCHEDULE) - { - spr.drawCircle(40+x+(sx/2), 66+y, 10, TH.menu_param); - spr.drawLine(40+x+(sx/2), 66+y, 40+x+(sx/2), 66+y-7, TH.menu_param); - spr.drawLine(40+x+(sx/2), 66+y, 40+x+(sx/2)+4, 66+y+4, TH.menu_param); - } + // No special icon for Tune mode } static void drawScan(int x, int y, int sx) diff --git a/ats-mini/ats-mini.ino b/ats-mini/ats-mini.ino index dd2bce5e..e943c575 100644 --- a/ats-mini/ats-mini.ino +++ b/ats-mini/ats-mini.ino @@ -836,6 +836,11 @@ void loop() switch(currentCmd) { case CMD_NONE: + // Default to seek mode + needRedraw |= doSeek(encCount, encCountAccel); + // Current frequency may have changed + prefsRequestSave(SAVE_CUR_BAND); + break; case CMD_SCAN: // Tuning needRedraw |= doTune(encCountAccel); @@ -849,10 +854,8 @@ void loop() prefsRequestSave(SAVE_CUR_BAND); break; case CMD_SEEK: - // Seek mode - needRedraw |= doSeek(encCount, encCountAccel); - // Seek can take long time, renew the timestamp - currentTime = millis(); + // VFO tuning mode (accessible via menu) + needRedraw |= doTune(encCountAccel); // Current frequency may have changed prefsRequestSave(SAVE_CUR_BAND); break; From 2e904844e28af7bb4fdf019b8ba69155642db9dd Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 24 May 2026 14:43:01 +0800 Subject: [PATCH 6/8] feat: adjust volume via encoder rotation in sleep mode when unlocked --- ats-mini/ats-mini.ino | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ats-mini/ats-mini.ino b/ats-mini/ats-mini.ino index e943c575..0cd43a22 100644 --- a/ats-mini/ats-mini.ino +++ b/ats-mini/ats-mini.ino @@ -788,6 +788,17 @@ void loop() // Block encoder rotation when in the locked sleep mode if(encCount && sleepOn() && sleepModeIdx==SLEEP_LOCKED) encCount = encCountAccel = 0; + // In sleep mode, if the encoder is rotated (and not locked), adjust the volume instead of tuning + if(encCount && sleepOn()) + { + doVolume(encCount); + prefsRequestSave(SAVE_SETTINGS); + elapsedSleep = elapsedCommand = currentTime; // Reset inactivity timers to keep active + encCount = encCountAccel = 0; + needRedraw = true; + } + + // Activate push and rotate mode (can span multiple loop iterations until the button is released) if (encCount && pb1st.isPressed) pushAndRotate = true; From dcb8d10cd6ae6aa71c3c286a442cb37d5bc814a4 Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 24 May 2026 20:01:49 +0800 Subject: [PATCH 7/8] feat: implement local-time based Smart Sleep sub-menu and optimize button click detection --- ats-mini/Common.h | 4 ++ ats-mini/Menu.cpp | 153 ++++++++++++++++++++++++++++++++++++++++-- ats-mini/Menu.h | 6 +- ats-mini/Storage.cpp | 6 ++ ats-mini/Utils.cpp | 2 +- ats-mini/ats-mini.ino | 56 ++++++++++++++-- 6 files changed, 213 insertions(+), 14 deletions(-) diff --git a/ats-mini/Common.h b/ats-mini/Common.h index 6b569abf..452cfb2d 100644 --- a/ats-mini/Common.h +++ b/ats-mini/Common.h @@ -174,6 +174,9 @@ extern uint16_t currentSleepTimer; extern uint32_t sleepTimerStart; extern bool sleepTimerMuted; extern uint8_t sleepModeIdx; +extern bool sleepSmart; +extern uint8_t sleepDayStart; +extern uint8_t sleepNightStart; extern bool zoomMenu; extern int8_t scrollDirection; extern uint8_t utcOffsetIdx; @@ -238,6 +241,7 @@ bool ntpSyncTime(); void netRequestConnect(); void netTickTime(); +bool isSleepSmartActive(int &timeoutSeconds); // Remote.c #define REMOTE_CHANGED 1 diff --git a/ats-mini/Menu.cpp b/ats-mini/Menu.cpp index 195db510..3298f90b 100644 --- a/ats-mini/Menu.cpp +++ b/ats-mini/Menu.cpp @@ -6,6 +6,8 @@ #include "EIBI.h" #include "BleMode.h" #include "Menu.h" +#include "Storage.h" + // // Bands Menu @@ -120,13 +122,14 @@ static const char *menu[] = #define MENU_ZOOM 7 #define MENU_SCROLL 8 #define MENU_SLEEP 9 -#define MENU_SLEEPMODE 10 -#define MENU_SLEEPTIMER 11 -#define MENU_LOADEIBI 12 -#define MENU_USBMODE 13 -#define MENU_BLEMODE 14 -#define MENU_WIFIMODE 15 -#define MENU_ABOUT 16 +#define MENU_SMARTSLEEP 10 +#define MENU_SLEEPMODE 11 +#define MENU_SLEEPTIMER 12 +#define MENU_LOADEIBI 13 +#define MENU_USBMODE 14 +#define MENU_BLEMODE 15 +#define MENU_WIFIMODE 16 +#define MENU_ABOUT 17 int8_t settingsIdx = MENU_BRIGHTNESS; @@ -143,6 +146,7 @@ static const char *settings[] = "Zoom Menu", "Scroll Dir.", "Sleep", + "Smart Sleep", "Sleep Mode", "Sleep Timer", "Load EiBi", @@ -624,6 +628,62 @@ static void doSleep(int16_t enc) currentSleep = clamp_range(currentSleep, 5*enc, 0, 255); } +#define SMARTSLEEP_STATE 0 +#define SMARTSLEEP_DAY 1 +#define SMARTSLEEP_NIGHT 2 +#define SMARTSLEEP_BACK 3 + +static const char *smartSleepMenu[] = +{ + "Smart Sleep", + "Day Start", + "Night Start", + "Back" +}; +int8_t smartSleepIdx = 0; + +static void doSmartSleepMenu(int16_t enc) +{ + smartSleepIdx = wrap_range(smartSleepIdx, enc, 0, LAST_ITEM(smartSleepMenu)); +} + +static void clickSmartSleepMenu(bool shortPress) +{ + if (shortPress) + { + switch(smartSleepIdx) + { + case SMARTSLEEP_STATE: + sleepSmart = !sleepSmart; + prefsRequestSave(SAVE_SETTINGS); + break; + case SMARTSLEEP_DAY: + currentCmd = CMD_DAYSTART; + break; + case SMARTSLEEP_NIGHT: + currentCmd = CMD_NIGHTSTART; + break; + case SMARTSLEEP_BACK: + currentCmd = CMD_SETTINGS; + break; + } + } + else + { + currentCmd = CMD_SETTINGS; + } +} + +static void doDayStart(int16_t enc) +{ + sleepDayStart = wrap_range(sleepDayStart, enc, 0, 23); +} + +static void doNightStart(int16_t enc) +{ + sleepNightStart = wrap_range(sleepNightStart, enc, 0, 23); +} + static void doSleepMode(int16_t enc) { sleepModeIdx = wrap_range(sleepModeIdx, enc, 0, LAST_ITEM(sleepModeDesc)); @@ -947,6 +1007,7 @@ static void clickSettings(int cmd, bool shortPress) case MENU_ZOOM: currentCmd = CMD_ZOOM; break; case MENU_SCROLL: currentCmd = CMD_SCROLL; break; case MENU_SLEEP: currentCmd = CMD_SLEEP; break; + case MENU_SMARTSLEEP: currentCmd = CMD_SMARTSLEEP; break; case MENU_SLEEPMODE: currentCmd = CMD_SLEEPMODE; break; case MENU_SLEEPTIMER: currentCmd = CMD_SLEEPTIMER; break; case MENU_UTCOFFSET: currentCmd = CMD_UTCOFFSET; break; @@ -991,6 +1052,9 @@ bool doSideBar(uint16_t cmd, int16_t enc, int16_t enca) case CMD_RDS: doRDSMode(scrollDirection * enc);break; case CMD_MEMORY: doMemory(scrollDirection * enca);break; case CMD_SLEEP: doSleep(enca);break; + case CMD_SMARTSLEEP: doSmartSleepMenu(scrollDirection * enc);break; + case CMD_DAYSTART: doDayStart(scrollDirection * enc);break; + case CMD_NIGHTSTART: doNightStart(scrollDirection * enc);break; case CMD_SLEEPMODE: doSleepMode(scrollDirection * enc);break; case CMD_SLEEPTIMER: doSleepTimer(enc);break; case CMD_USBMODE: doUSBMode(scrollDirection * enc);break; @@ -1014,6 +1078,12 @@ bool clickHandler(uint16_t cmd, bool shortPress) { case CMD_MENU: clickMenu(menuIdx, shortPress);break; case CMD_SETTINGS: clickSettings(settingsIdx, shortPress);break; + case CMD_SMARTSLEEP: clickSmartSleepMenu(shortPress);break; + case CMD_DAYSTART: + case CMD_NIGHTSTART: + prefsRequestSave(SAVE_SETTINGS); + currentCmd = CMD_SMARTSLEEP; + break; case CMD_MEMORY: clickMemory(memoryIdx, shortPress);break; case CMD_BLEMODE: clickBleMode(bleModeIdx, shortPress);break; case CMD_WIFIMODE: clickWiFiMode(wifiModeIdx, shortPress);break; @@ -1641,6 +1711,72 @@ static void drawSleep(int x, int y, int sx) spr.drawNumber(currentSleep, 40+x+(sx/2), 60+y, 4); } +static void drawSmartSleepMenu(int x, int y, int sx) +{ + spr.setTextDatum(MC_DATUM); + + spr.fillSmoothRoundRect(1+x, 1+y, 76+sx, 110, 4, TH.menu_border); + spr.fillSmoothRoundRect(2+x, 2+y, 74+sx, 108, 4, TH.menu_bg); + spr.setTextColor(TH.menu_hdr); + spr.drawString("Smart Sleep", 40+x+(sx/2), 12+y, 2); + spr.drawLine(1+x, 23+y, 76+sx, 23+y, TH.menu_border); + + spr.setTextFont(0); + spr.setTextColor(TH.menu_item); + spr.fillRoundRect(6+x, 24+y+(2*16), 66+sx, 16, 2, TH.menu_hl_bg); + + int count = ITEM_COUNT(smartSleepMenu); + for(int i=-2 ; i<3 ; i++) + { + int idx = abs((smartSleepIdx+count+i)%count); + if(i==0) { + drawZoomedMenu(smartSleepMenu[idx]); + spr.setTextColor(TH.menu_hl_text, TH.menu_hl_bg); + } else { + spr.setTextColor(TH.menu_item); + } + + spr.setTextDatum(MC_DATUM); + + char text[32]; + if (idx == SMARTSLEEP_STATE) { + sprintf(text, "State: %s", sleepSmart ? "On" : "Off"); + } else if (idx == SMARTSLEEP_DAY) { + sprintf(text, "Day: %02d:00", sleepDayStart); + } else if (idx == SMARTSLEEP_NIGHT) { + sprintf(text, "Night: %02d:00", sleepNightStart); + } else { + strcpy(text, smartSleepMenu[idx]); + } + + spr.drawString(text, 40+x+(sx/2), 64+y+(i*16), 2); + } +} + +static void drawDayStart(int x, int y, int sx) +{ + drawCommon(smartSleepMenu[SMARTSLEEP_DAY], x, y, sx); + drawZoomedMenu(smartSleepMenu[SMARTSLEEP_DAY]); + spr.setTextDatum(MC_DATUM); + + spr.setTextColor(TH.menu_param); + char text[10]; + sprintf(text, "%02d:00", sleepDayStart); + spr.drawString(text, 40+x+(sx/2), 60+y, 4); +} + +static void drawNightStart(int x, int y, int sx) +{ + drawCommon(smartSleepMenu[SMARTSLEEP_NIGHT], x, y, sx); + drawZoomedMenu(smartSleepMenu[SMARTSLEEP_NIGHT]); + spr.setTextDatum(MC_DATUM); + + spr.setTextColor(TH.menu_param); + char text[10]; + sprintf(text, "%02d:00", sleepNightStart); + spr.drawString(text, 40+x+(sx/2), 60+y, 4); +} + static void drawZoom(int x, int y, int sx) { drawCommon(settings[MENU_ZOOM], x, y, sx); @@ -1764,6 +1900,9 @@ void drawSideBar(uint16_t cmd, int x, int y, int sx) case CMD_RDS: drawRDSMode(x, y, sx); break; case CMD_MEMORY: drawMemory(x, y, sx); break; case CMD_SLEEP: drawSleep(x, y, sx); break; + case CMD_SMARTSLEEP: drawSmartSleepMenu(x, y, sx); break; + case CMD_DAYSTART: drawDayStart(x, y, sx); break; + case CMD_NIGHTSTART: drawNightStart(x, y, sx); break; case CMD_SLEEPMODE: drawSleepMode(x, y, sx); break; case CMD_SLEEPTIMER: drawSleepTimer(x, y, sx); break; case CMD_USBMODE: drawUSBMode(x, y, sx); break; diff --git a/ats-mini/Menu.h b/ats-mini/Menu.h index 30f9c5b7..b0b354ec 100644 --- a/ats-mini/Menu.h +++ b/ats-mini/Menu.h @@ -45,7 +45,11 @@ #define CMD_USBMODE 0x2E00 // | #define CMD_BLEMODE 0x2F00 // | #define CMD_WIFIMODE 0x3000 // | -#define CMD_ABOUT 0x3100 //-+ +#define CMD_ABOUT 0x3100 +#define CMD_SMARTSLEEP 0x3200 // | +#define CMD_DAYSTART 0x3300 // | +#define CMD_NIGHTSTART 0x3400 //-+ + // UI Layouts #define UI_DEFAULT 0 diff --git a/ats-mini/Storage.cpp b/ats-mini/Storage.cpp index de02b9d7..e32a5233 100644 --- a/ats-mini/Storage.cpp +++ b/ats-mini/Storage.cpp @@ -184,6 +184,9 @@ void prefsSave(uint32_t items) prefs.putUChar("SsbSoftMute", SsbSoftMuteIdx); // SSB soft mute prefs.putUShort("Sleep", currentSleep); // Sleep delay prefs.putUShort("SleepTmr", currentSleepTimer); // Sleep timer + prefs.putBool("SleepSmart", sleepSmart); // Smart sleep mode + prefs.putUChar("SleepDaySt", sleepDayStart); // Smart sleep day start hour + prefs.putUChar("SleepNightSt",sleepNightStart); // Smart sleep night start hour prefs.putUChar("Theme", themeIdx); // Color theme prefs.putUChar("RDSMode", rdsModeIdx); // RDS mode prefs.putUChar("SleepMode", sleepModeIdx); // Sleep mode @@ -262,6 +265,9 @@ bool prefsLoad(uint32_t items) SsbSoftMuteIdx = prefs.getUChar("SsbSoftMute", SsbSoftMuteIdx); // SSB soft mute currentSleep = prefs.getUShort("Sleep", currentSleep); // Sleep delay currentSleepTimer = prefs.getUShort("SleepTmr", 0); // Sleep timer + sleepSmart = prefs.getBool("SleepSmart", sleepSmart); + sleepDayStart = prefs.getUChar("SleepDaySt", sleepDayStart); + sleepNightStart = prefs.getUChar("SleepNightSt", sleepNightStart); themeIdx = prefs.getUChar("Theme", themeIdx); // Color theme rdsModeIdx = prefs.getUChar("RDSMode", rdsModeIdx); // RDS mode sleepModeIdx = prefs.getUChar("SleepMode", sleepModeIdx); // Sleep mode diff --git a/ats-mini/Utils.cpp b/ats-mini/Utils.cpp index 71d04dbe..1931f53e 100644 --- a/ats-mini/Utils.cpp +++ b/ats-mini/Utils.cpp @@ -219,7 +219,7 @@ bool sleepOn(int x) esp_light_sleep_start(); // Waking up here - if(currentSleep) break; // Short click is enough to exit from sleep if timeout is enabled + if(currentSleep || sleepSmart) break; // Short click is enough to exit from sleep if timeout is enabled // Wait for a long press, otherwise enter the sleep again pb1.reset(); // Reset the button state (its timers could be stale due to CPU sleep) diff --git a/ats-mini/ats-mini.ino b/ats-mini/ats-mini.ino index 0cd43a22..57b04578 100644 --- a/ats-mini/ats-mini.ino +++ b/ats-mini/ats-mini.ino @@ -72,6 +72,9 @@ uint8_t FmRegionIdx = 0; // FM Region uint16_t currentBrt = 130; // Display brightness, range = 10 to 255 in steps of 5 uint16_t currentSleep = DEFAULT_SLEEP; // Display sleep timeout, range = 0 to 255 in steps of 5 +bool sleepSmart = true; +uint8_t sleepDayStart = 6; +uint8_t sleepNightStart = 22; uint16_t currentSleepTimer = 0; // Audio sleep timer, minutes uint32_t sleepTimerStart = millis(); // Audio sleep timer start time bool sleepTimerMuted = false; // Flag to restore volume on soft power-on @@ -900,7 +903,7 @@ void loop() { // If sleep timeout is enabled, exit it via button press of any duration // (users don't need to figure out that a long press is required to wake up the device) - if(currentSleep) + if(currentSleep || sleepSmart) { sleepOn(false); needRedraw = true; @@ -908,15 +911,15 @@ void loop() else if(sleepModeIdx == SLEEP_UNLOCKED) { // Allow to adjust the volume in sleep mode - if(pb1st.wasShortPressed && currentCmd==CMD_NONE) + if((pb1st.wasClicked || pb1st.wasShortPressed) && currentCmd==CMD_NONE) currentCmd = CMD_VOLUME; else if(currentCmd==CMD_VOLUME) - clickHandler(currentCmd, pb1st.wasShortPressed); + clickHandler(currentCmd, pb1st.wasClicked || pb1st.wasShortPressed); needRedraw = true; } } - else if(clickHandler(currentCmd, pb1st.wasShortPressed)) + else if(clickHandler(currentCmd, pb1st.wasClicked || pb1st.wasShortPressed)) { // Command handled, redraw screen needRedraw = true; @@ -959,7 +962,8 @@ void loop() } // Display sleep timeout - if(currentSleep && !sleepOn() && ((currentTime - elapsedSleep) > currentSleep * 1000)) + int sleepTimeout = 0; + if(isSleepSmartActive(sleepTimeout) && !sleepOn() && ((currentTime - elapsedSleep) > (uint32_t)sleepTimeout * 1000)) { sleepOn(true); // CPU sleep can take long time, renew the timestamps @@ -1029,3 +1033,45 @@ void loop() // Add a small default delay in the main loop delay(5); } + +bool isSleepSmartActive(int &timeoutSeconds) +{ + if (!sleepSmart || !clockAvailable()) + { + timeoutSeconds = currentSleep; + return (currentSleep > 0); + } + + uint8_t utcHours, utcMinutes; + if (clockGetHM(&utcHours, &utcMinutes)) + { + int localMinutes = (int)utcHours * 60 + utcMinutes + getCurrentUTCOffset() * 15; + localMinutes = (localMinutes < 0) ? localMinutes + 24*60 : (localMinutes % (24*60)); + int localHour = localMinutes / 60; + + bool isDaytime; + if (sleepDayStart < sleepNightStart) + { + isDaytime = (localHour >= sleepDayStart && localHour < sleepNightStart); + } + else + { + isDaytime = (localHour >= sleepDayStart || localHour < sleepNightStart); + } + + if (isDaytime) + { + timeoutSeconds = 0; + return false; + } + else + { + timeoutSeconds = (currentSleep > 0) ? currentSleep : 30; + return true; + } + } + + timeoutSeconds = currentSleep; + return (currentSleep > 0); +} + From aaad0f859db9edb9e60ec55a3d9725bdaa65af1a Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 31 May 2026 17:14:57 +0800 Subject: [PATCH 8/8] feat: force CPU light sleep on long press soft power-off and sleep timer --- ats-mini/Utils.cpp | 4 ++-- ats-mini/Utils.h | 2 +- ats-mini/ats-mini.ino | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ats-mini/Utils.cpp b/ats-mini/Utils.cpp index 1931f53e..a3171319 100644 --- a/ats-mini/Utils.cpp +++ b/ats-mini/Utils.cpp @@ -188,7 +188,7 @@ bool muteOn(uint8_t mode, int x) // // Turn sleep on (1) or off (0), or get current status (2) // -bool sleepOn(int x) +bool sleepOn(int x, bool forceCpuSleep) { if((x==1) && !sleep_on) { @@ -203,7 +203,7 @@ bool sleepOn(int x) while(pb1.update(digitalRead(ENCODER_PUSH_BUTTON) == LOW).isPressed) delay(100); - if(sleepModeIdx == SLEEP_LIGHT) + if(sleepModeIdx == SLEEP_LIGHT || forceCpuSleep) { // Disable WiFi netStop(); diff --git a/ats-mini/Utils.h b/ats-mini/Utils.h index 5cc6cc71..0025e0ce 100644 --- a/ats-mini/Utils.h +++ b/ats-mini/Utils.h @@ -22,7 +22,7 @@ const char *getMACAddress(); int getStrength(int rssi); // Set, reset, toggle, or query switches -bool sleepOn(int x = 2); +bool sleepOn(int x = 2, bool forceCpuSleep = false); bool muteOn(uint8_t mode, int x = 2); // Wall clock functions diff --git a/ats-mini/ats-mini.ino b/ats-mini/ats-mini.ino index 57b04578..4f450487 100644 --- a/ats-mini/ats-mini.ino +++ b/ats-mini/ats-mini.ino @@ -886,11 +886,22 @@ void loop() } else if(pb1st.isLongPressed) { - // Encoder is being LONG PRESSED: TOGGLE DISPLAY - sleepOn(!sleepOn()); + // Encoder is being LONG PRESSED: TOGGLE SOFT POWER STATE + bool nextState = !sleepOn(); + if (nextState) + { + // 进入深度软关机:关闭功放,设置音量恢复标志,强制 CPU Light Sleep + muteOn(MUTE_MAIN, true); + sleepTimerMuted = true; + sleepOn(1, true); + } + else + { + // 唤醒设备 + sleepOn(0); + } // CPU sleep can take long time, renew the timestamps elapsedSleep = elapsedCommand = currentTime = millis(); - } else if(pb1st.wasClicked || pb1st.wasShortPressed) { @@ -977,7 +988,7 @@ void loop() prefsRequestSave(SAVE_SETTINGS); muteOn(MUTE_MAIN, true); sleepTimerMuted = true; - if(!sleepOn()) sleepOn(1); + if(!sleepOn()) sleepOn(1, true); // 强制进入省电 CPU 休眠 elapsedSleep = elapsedCommand = currentTime = millis(); }