feat: add deep sleep batch caching, wake log, and log viewer tools

- Batch-fetch asset IDs in deep sleep mode with NVS caching to reduce
  network calls and improve battery life (configurable batch size 1-50)
- Add wake_log module: persistent CSV log of each photo cycle surviving
  reboots and deep sleep, with download/clear via web UI
- Add browser-based log viewer and log comparison tools (tools/)
- Update .gitignore to exclude .cursor/, chat-summaries/, wake_*.csv,
  and .DS_Store

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-31 12:59:03 -04:00
parent 150ca56542
commit 7fa74c03c1
15 changed files with 1465 additions and 33 deletions

View File

@@ -110,6 +110,13 @@ async function renderSlideshow(el) {
</select>
</div>
</div>
<div class="card">
<h2>Deep Sleep Batch Size</h2>
<p style="opacity:0.7;font-size:13px;margin-bottom:8px">Photos to pre-fetch per API call in deep sleep mode. Higher = fewer network calls, better battery life.</p>
<div class="field">
<input type="number" id="timerBatch" min="1" max="50" value="${settings.timer_batch_size || 10}" style="width:80px">
</div>
</div>
<div class="btn-group"><button class="btn btn-primary" id="saveSlideshow">Save</button></div>`;
el.querySelectorAll('[data-interval]').forEach(btn => {
btn.onclick = () => {
@@ -128,7 +135,8 @@ async function renderSlideshow(el) {
interval = parseInt(activeBtn?.dataset.interval || '5');
}
const mode = parseInt(document.getElementById('cycleMode').value);
await api('/api/settings', { method: 'POST', body: JSON.stringify({ interval_min: interval, cycle_mode: mode }) });
const batch = Math.min(50, Math.max(1, parseInt(document.getElementById('timerBatch').value) || 10));
await api('/api/settings', { method: 'POST', body: JSON.stringify({ interval_min: interval, cycle_mode: mode, timer_batch_size: batch }) });
toast('Slideshow settings saved');
};
}
@@ -241,6 +249,14 @@ async function renderDevice(el) {
<button class="btn btn-danger" onclick="if(confirm('Enter deep sleep?'))api('/api/action/sleep',{method:'POST'})">Deep Sleep</button>
<button class="btn btn-danger" onclick="if(confirm('Reboot device?'))fetch('/api/action/reboot',{method:'POST'})">Reboot</button>
</div>
</div>
<div class="card">
<h2>Wake Log</h2>
<p style="opacity:0.7;margin-bottom:8px">Persistent CSV log of each photo cycle (survives reboots/sleep).</p>
<div class="btn-group">
<button class="btn btn-primary" id="downloadLog">Download Log</button>
<button class="btn btn-danger" id="clearLog">Clear Log</button>
</div>
</div>`;
document.getElementById('saveImmich').onclick = async () => {
@@ -270,6 +286,19 @@ async function renderDevice(el) {
const result = await api('/api/action/sync-time', { method: 'POST' });
toast(result.msg || 'Time synced');
};
document.getElementById('downloadLog').onclick = () => {
const a = document.createElement('a');
a.href = '/api/log';
a.download = 'wake_log.csv';
a.click();
};
document.getElementById('clearLog').onclick = async () => {
if (!confirm('Clear the wake log?')) return;
const result = await api('/api/log/clear', { method: 'POST' });
toast(result.msg || 'Log cleared');
};
}
async function renderFirmware(el) {