Write documentation for classes and functions; some cleanup

master
Travis Evans 2025-04-27 19:01:54 -05:00
parent 7a8c56023f
commit bca633dcec
3 changed files with 202 additions and 24 deletions

View File

@ -1,3 +1,6 @@
// Derived from `sched.js` from the `sched` app, with modifications
// for features unique to the `tevtimer` app.
// Chances are boot0.js got run already and scheduled *another* // Chances are boot0.js got run already and scheduled *another*
// 'load(sched.js)' - so let's remove it first! // 'load(sched.js)' - so let's remove it first!
if (Bangle.SCHED) { if (Bangle.SCHED) {
@ -8,6 +11,8 @@ if (Bangle.SCHED) {
const tt = require('tevtimer'); const tt = require('tevtimer');
function showAlarm(alarm) { function showAlarm(alarm) {
// Alert the user of the alarm and handle the response
const settings = require("sched").getSettings(); const settings = require("sched").getSettings();
const timer = tt.TIMERS[tt.find_timer_by_id(alarm.id)]; const timer = tt.TIMERS[tt.find_timer_by_id(alarm.id)];
if (timer === undefined) { if (timer === undefined) {
@ -52,8 +57,8 @@ function showAlarm(alarm) {
// Alarm options for chained timer are OK (dismiss) and Halt (dismiss // Alarm options for chained timer are OK (dismiss) and Halt (dismiss
// and pause the triggering timer). // and pause the triggering timer).
let promptButtons = isChainedTimer let promptButtons = isChainedTimer
? { "Halt": 'halt', "OK": 'ok' } ? { 'Halt': 'halt', 'OK': 'ok' }
: { "Snooze": 'snooze', "OK": 'ok' }; : { 'Snooze': 'snooze', 'OK': 'ok' };
E.showPrompt(message, { E.showPrompt(message, {
title: 'tev timer', title: 'tev timer',
buttons: promptButtons, buttons: promptButtons,
@ -108,6 +113,8 @@ function showAlarm(alarm) {
}); });
function buzz() { function buzz() {
// Handle buzzing and screen unlocking
if (settings.unlockAtBuzz) { if (settings.unlockAtBuzz) {
Bangle.setLocked(false); Bangle.setLocked(false);
} }
@ -127,6 +134,9 @@ function showAlarm(alarm) {
} }
function setNextRepeatDate(alarm) { function setNextRepeatDate(alarm) {
// Handle repeating alarms
// This is not used in tevtimer
let date = new Date(alarm.date); let date = new Date(alarm.date);
let rp = alarm.rp; let rp = alarm.rp;
if (rp===true) { // fallback in case rp is set wrong if (rp===true) { // fallback in case rp is set wrong

View File

@ -14,10 +14,13 @@ const MOVE_TO_TOP_TIMEOUT = 5000;
// Min number of pixels of movement to recognize a touchscreen drag/swipe // Min number of pixels of movement to recognize a touchscreen drag/swipe
const DRAG_THRESHOLD = 50; const DRAG_THRESHOLD = 50;
// Physical left/right button size in UI
const ARROW_BTN_SIZE = 15; const ARROW_BTN_SIZE = 15;
// IDs of main screen labels
const ROW_IDS = ['row1', 'row2', 'row3']; const ROW_IDS = ['row1', 'row2', 'row3'];
// Fonts to use for each screen label and display format
const FONT = { const FONT = {
'row1': { 'row1': {
'start hh:mm:ss': '12x20', 'start hh:mm:ss': '12x20',
@ -30,7 +33,7 @@ const FONT = {
'name': '12x20', 'name': '12x20',
'mode': '12x20', 'format-menu': '12x20',
}, },
'row2': { 'row2': {
@ -44,7 +47,7 @@ const FONT = {
'name': 'Vector:24x42', 'name': 'Vector:24x42',
'mode': 'Vector:26x42', 'format-menu': 'Vector:26x42',
}, },
'row3': { 'row3': {
@ -58,10 +61,12 @@ const FONT = {
'name': 'Vector:24x56', 'name': 'Vector:24x56',
'mode': 'Vector:26x56', 'format-menu': 'Vector:26x56',
} }
}; };
// List of format IDs available in the format menu
// (in the order they are displayed in the menu)
const FORMAT_MENU = [ const FORMAT_MENU = [
'start hh:mm:ss', 'start hh:mm:ss',
'start hh:mm', 'start hh:mm',
@ -72,6 +77,8 @@ const FORMAT_MENU = [
'name', 'name',
]; ];
// Mapping of format IDs to their human-friendly names displayed in the
// format menu
const FORMAT_DISPLAY = { const FORMAT_DISPLAY = {
'start hh:mm:ss': 'Start HMS', 'start hh:mm:ss': 'Start HMS',
'start hh:mm': 'Start HM', 'start hh:mm': 'Start HM',
@ -83,19 +90,28 @@ const FORMAT_DISPLAY = {
}; };
function row_font(row_name, mode_name) { function row_font(row_name, format) {
let font = FONT[row_name][mode_name]; // Convenience function to retrieve the font ID for the given display
// field and format mode
let font = FONT[row_name][format];
if (font === undefined) { if (font === undefined) {
console.error('Unknown font for row_font("' + row_name + '", "' + mode_name + '")'); console.error('Unknown font for row_font("' + row_name + '", "' + format + '")');
return '12x20'; return '12x20';
} }
return font; return font;
} }
function next_time_update(interval, curr_time, direction) {
// Determine time in milliseconds until next display update for a timer // Determine time in milliseconds until next display update for a timer
// that should be updated every `interval` milliseconds. // that should be updated every `interval` milliseconds.
function next_time_update(interval, curr_time, direction) { //
// `curr_time` is the current time in milliseconds, and `direction`
// is either 1 (forward) or -1 (backward). The function returns the
// time in milliseconds until the next update, or Infinity if there
// is no update needed (e.g. if interval is zero or negative).
if (interval <= 0) { if (interval <= 0) {
// Don't update if interval is zero or negative // Don't update if interval is zero or negative
return Infinity; return Infinity;
@ -119,6 +135,10 @@ function next_time_update(interval, curr_time, direction) {
class TimerView { class TimerView {
// Primary UI for displaying and operating a timer. The
// PrimitiveTimer object is passed to the constructor as a
// parameter.
constructor(timer) { constructor(timer) {
this.timer = timer; this.timer = timer;
@ -128,6 +148,8 @@ class TimerView {
} }
start() { start() {
// Initialize, display, and activate the UI
this._initLayout(); this._initLayout();
this.layout.update(); this.layout.update();
this.layout.clear(); this.layout.clear();
@ -175,6 +197,8 @@ class TimerView {
} }
stop() { stop() {
// Shut down the UI and clean up listeners and handlers
if (this.listeners.timer_render_timeout !== null) { if (this.listeners.timer_render_timeout !== null) {
clearTimeout(this.listeners.timer_render_timeout); clearTimeout(this.listeners.timer_render_timeout);
this.listeners.timer_render_timeout = null; this.listeners.timer_render_timeout = null;
@ -232,6 +256,10 @@ class TimerView {
} }
render(item) { render(item) {
// Draw the timer display and update the status and buttons. The
// `item` parameter specifies which part of the display to update.
// If `item` is not specified, the entire display is updated.
console.debug('render called: ' + item); console.debug('render called: ' + item);
if (!item) { if (!item) {
@ -325,6 +353,8 @@ class TimerView {
} }
start_stop_timer() { start_stop_timer() {
// Start or pause the timer
if (this.timer.is_running()) { if (this.timer.is_running()) {
this.timer.pause(); this.timer.pause();
} else { } else {
@ -338,6 +368,10 @@ class TimerView {
class TimerFormatView { class TimerFormatView {
// UI for selecting the display format of a timer. The
// PrimitiveTimer object is passed to the constructor as a
// parameter.
constructor(timer) { constructor(timer) {
this.timer = timer; this.timer = timer;
@ -357,6 +391,8 @@ class TimerFormatView {
} }
start() { start() {
// Initialize, display, and activate the UI
this._initLayout(); this._initLayout();
this.layout.update(); this.layout.update();
this.layout.clear(); this.layout.clear();
@ -430,6 +466,8 @@ class TimerFormatView {
} }
stop() { stop() {
// Shut down the UI and clean up listeners and handlers
Bangle.removeListener('drag', this.listeners.drag); Bangle.removeListener('drag', this.listeners.drag);
Bangle.removeListener('touch', this.listeners.touch); Bangle.removeListener('touch', this.listeners.touch);
clearWatch(this.listeners.button); clearWatch(this.listeners.button);
@ -465,7 +503,7 @@ class TimerFormatView {
type: 'txt', type: 'txt',
id: 'row1', id: 'row1',
label: FORMAT_DISPLAY[FORMAT_MENU[this.format_idx.row1]], label: FORMAT_DISPLAY[FORMAT_MENU[this.format_idx.row1]],
font: row_font('row1', 'mode'), font: row_font('row1', 'format-menu'),
fillx: 1, fillx: 1,
}, },
{ {
@ -491,7 +529,7 @@ class TimerFormatView {
type: 'txt', type: 'txt',
id: 'row2', id: 'row2',
label: FORMAT_DISPLAY[FORMAT_MENU[this.format_idx.row2]], label: FORMAT_DISPLAY[FORMAT_MENU[this.format_idx.row2]],
font: row_font('row2', 'mode'), font: row_font('row2', 'format-menu'),
fillx: 1, fillx: 1,
}, },
{ {
@ -517,7 +555,7 @@ class TimerFormatView {
type: 'txt', type: 'txt',
id: 'row3', id: 'row3',
label: FORMAT_DISPLAY[FORMAT_MENU[this.format_idx.row3]], label: FORMAT_DISPLAY[FORMAT_MENU[this.format_idx.row3]],
font: row_font('row3', 'mode'), font: row_font('row3', 'format-menu'),
fillx: 1, fillx: 1,
}, },
{ {
@ -548,10 +586,15 @@ class TimerFormatView {
} }
render() { render() {
// Draw the format selection UI.
this.layout.render(); this.layout.render();
} }
update_row(row_id) { update_row(row_id) {
// Render the display format for the given row ID. The row ID
// should be one of 'row1', 'row2', or 'row3'.
const elem = this.layout[row_id]; const elem = this.layout[row_id];
elem.label = FORMAT_DISPLAY[FORMAT_MENU[this.format_idx[row_id]]]; elem.label = FORMAT_DISPLAY[FORMAT_MENU[this.format_idx[row_id]]];
this.layout.clear(elem); this.layout.clear(elem);
@ -559,6 +602,9 @@ class TimerFormatView {
} }
incr_format_idx(row_id) { incr_format_idx(row_id) {
// Increment the selected format for the given row ID. The row ID
// should be one of 'row1', 'row2', or 'row3'.
this.format_idx[row_id] += 1; this.format_idx[row_id] += 1;
if (this.format_idx[row_id] >= FORMAT_MENU.length) { if (this.format_idx[row_id] >= FORMAT_MENU.length) {
this.format_idx[row_id] = 0; this.format_idx[row_id] = 0;
@ -567,6 +613,9 @@ class TimerFormatView {
} }
decr_format_idx(row_id) { decr_format_idx(row_id) {
// Decrement the selected format for the given row ID. The row ID
// should be one of 'row1', 'row2', or 'row3'.
this.format_idx[row_id] -= 1; this.format_idx[row_id] -= 1;
if (this.format_idx[row_id] < 0) { if (this.format_idx[row_id] < 0) {
this.format_idx[row_id] = FORMAT_MENU.length - 1; this.format_idx[row_id] = FORMAT_MENU.length - 1;
@ -574,8 +623,8 @@ class TimerFormatView {
this.update_row(row_id); this.update_row(row_id);
} }
// Save new format settings and return to TimerView
ok() { ok() {
// Save new format settings and return to TimerView
for (var row_id of ROW_IDS) { for (var row_id of ROW_IDS) {
tt.SETTINGS.format[row_id] = FORMAT_MENU[this.format_idx[row_id]]; tt.SETTINGS.format[row_id] = FORMAT_MENU[this.format_idx[row_id]];
} }
@ -583,31 +632,43 @@ class TimerFormatView {
switch_UI(new TimerView(this.timer)); switch_UI(new TimerView(this.timer));
} }
// Return to TimerViewMenu without saving changes
cancel() { cancel() {
// Return to TimerViewMenu without saving changes
switch_UI(new TimerViewMenu(this.timer)); switch_UI(new TimerViewMenu(this.timer));
} }
} }
class TimerViewMenu { class TimerViewMenu {
// UI for displaying the timer menu. The PrimitiveTimer object is
// passed to the constructor as a parameter.
constructor(timer) { constructor(timer) {
this.timer = timer; this.timer = timer;
} }
start() { start() {
// Display and activate the top menu of the timer view menu.
this.top_menu(); this.top_menu();
} }
stop() { stop() {
// Shut down the UI and clean up listeners and handlers
E.showMenu(); E.showMenu();
} }
back() { back() {
// Return to the timer view
// (i.e. the timer that was previously displayed)
switch_UI(new TimerView(this.timer)); switch_UI(new TimerView(this.timer));
} }
top_menu() { top_menu() {
// Display the top-level menu for the timer
const top_menu = { const top_menu = {
'': { '': {
title: this.timer.display_name(), title: this.timer.display_name(),
@ -663,6 +724,9 @@ class TimerViewMenu {
} }
edit_menu() { edit_menu() {
// Display the edit menu for the timer. This can be called in
// place of `start` to jump directly to the edit menu.
let keyboard = null; let keyboard = null;
try { keyboard = require("textinput"); } catch (e) {} try { keyboard = require("textinput"); } catch (e) {}
@ -724,6 +788,9 @@ class TimerViewMenu {
} }
edit_start() { edit_start() {
// Display the edit > start menu for the timer
// (i.e. the timer's starting value)
let origin_hms = { let origin_hms = {
h: Math.floor(this.timer.origin / 3600), h: Math.floor(this.timer.origin / 3600),
m: Math.floor(this.timer.origin / 60) % 60, m: Math.floor(this.timer.origin / 60) % 60,
@ -764,24 +831,37 @@ class TimerViewMenu {
class TimerMenu { class TimerMenu {
// UI for displaying the list of timers. The list of
// PrimitiveTimer objects is passed to the constructor as a
// parameter. The currently focused timer is passed as the
// second parameter.
constructor(timers, focused_timer) { constructor(timers, focused_timer) {
this.timers = timers; this.timers = timers;
this.focused_timer = focused_timer; this.focused_timer = focused_timer;
} }
start() { start() {
// Display the top timer menu
this.top_menu(); this.top_menu();
} }
stop() { stop() {
// Shut down the UI and clean up listeners and handlers
E.showMenu(); E.showMenu();
} }
back() { back() {
// Return to the timer's menu
switch_UI(new TimerViewMenu(this.focused_timer)); switch_UI(new TimerViewMenu(this.focused_timer));
} }
top_menu() { top_menu() {
// Display the top-level menu for the timer list
let menu = { let menu = {
'': { '': {
title: "Timers", title: "Timers",
@ -798,6 +878,10 @@ class TimerMenu {
function switch_UI(new_UI) { function switch_UI(new_UI) {
// Switch from one UI to another. The new UI instance is passed as a
// parameter. The old UI is stopped and cleaned up, and the new
// UI is started.
if (CURRENT_UI) { if (CURRENT_UI) {
CURRENT_UI.stop(); CURRENT_UI.stop();
} }

View File

@ -3,7 +3,7 @@ const Sched = require('sched');
const Time_utils = require('time_utils'); const Time_utils = require('time_utils');
// Convenience // // Convenience functions //
function mod(n, m) { function mod(n, m) {
// Modulus function that works like Python's % operator // Modulus function that works like Python's % operator
@ -20,7 +20,20 @@ function ceil(value) {
// Data models // // Data models //
class PrimitiveTimer { class PrimitiveTimer {
// A simple timer object that can be used as a countdown or countup
// timer. It can be paused and resumed, and it can be reset to its
// original value. It can also be saved to and loaded from a
// persistent storage.
constructor(origin, is_running, rate, name, id) { constructor(origin, is_running, rate, name, id) {
// origin: initial value of the timer
// is_running: true if the timer should begin running immediately,
// false if it should be paused
// rate: rate of the timer, in units per second. Positive for
// countup, negative for countdown
// name: name of the timer (can be empty)
// id: ID of the timer
this.origin = origin || 0; this.origin = origin || 0;
// default rate +1 unit per 1000 ms, countup // default rate +1 unit per 1000 ms, countup
this.rate = rate || 0.001; this.rate = rate || 0.001;
@ -36,18 +49,26 @@ class PrimitiveTimer {
} }
display_name() { display_name() {
// Return a string to display as the timer name
// If the name is empty, return a generated name
return this.name ? this.name : this.provisional_name(); return this.name ? this.name : this.provisional_name();
} }
provisional_name() { provisional_name() {
// Return a generated name for the timer based on the timer's
// origin and current value
return ( return (
Time_utils.formatDuration(this.origin / Math.abs(this.rate)) Time_utils.formatDuration(this.to_msec(this.origin))
+ ' / ' + ' / '
+ Time_utils.formatDuration(Math.abs(this.get() / Math.abs(this.rate))) + Time_utils.formatDuration(this.to_msec())
); );
} }
display_status() { display_status() {
// Return a string representing the timer's status
// (e.g. running, paused, expired)
let status = ''; let status = '';
// Indicate timer expired if its current value is <= 0 and it's // Indicate timer expired if its current value is <= 0 and it's
@ -64,10 +85,14 @@ class PrimitiveTimer {
} }
is_running() { is_running() {
// Return true if the timer is running, false if it is paused
return !this._pause_time; return !this._pause_time;
} }
start() { start() {
// Start the timer if it is paused
if (!this.is_running()) { if (!this.is_running()) {
this._start_time += Date.now() - this._pause_time; this._start_time += Date.now() - this._pause_time;
this._pause_time = null; this._pause_time = null;
@ -75,6 +100,8 @@ class PrimitiveTimer {
} }
pause() { pause() {
// Pause the timer if it is running
if (this.is_running()) { if (this.is_running()) {
this._pause_time = Date.now(); this._pause_time = Date.now();
} }
@ -85,6 +112,8 @@ class PrimitiveTimer {
} }
get() { get() {
// Return the current value of the timer, in rate units
const now = Date.now(); const now = Date.now();
const elapsed = const elapsed =
(now - this._start_time) (now - this._start_time)
@ -93,6 +122,8 @@ class PrimitiveTimer {
} }
set(new_value) { set(new_value) {
// Set the timer to a new value, in rate units
const now = Date.now(); const now = Date.now();
this._start_time = (now - new_value / this.rate) this._start_time = (now - new_value / this.rate)
+ (this.origin / this.rate); + (this.origin / this.rate);
@ -101,9 +132,9 @@ class PrimitiveTimer {
} }
} }
to_msec(value) {
// Convert given timer value to milliseconds using this.rate // Convert given timer value to milliseconds using this.rate
// Uses the current value of the timer if no value is provided // Uses the current value of the timer if no value is provided
to_msec(value) {
if (value === undefined) { if (value === undefined) {
value = this.get(); value = this.get();
} }
@ -111,6 +142,8 @@ class PrimitiveTimer {
} }
dump() { dump() {
// Serialize the timer object to a JSON-compatible object
return { return {
cls: 'PrimitiveTimer', cls: 'PrimitiveTimer',
version: 0, version: 0,
@ -127,6 +160,9 @@ class PrimitiveTimer {
} }
static load(data) { static load(data) {
// Deserialize a JSON-compatible object to a PrimitiveTimer
// object
if (!(data.cls == 'PrimitiveTimer' && data.version == 0)) { if (!(data.cls == 'PrimitiveTimer' && data.version == 0)) {
console.error('Incompatible data type for loading PrimitiveTimer state'); console.error('Incompatible data type for loading PrimitiveTimer state');
} }
@ -143,6 +179,9 @@ class PrimitiveTimer {
function format_duration(msec, have_seconds) { function format_duration(msec, have_seconds) {
// Format a duration in milliseconds as a string in HH:MM format
// (have_seconds is false) or HH:MM:SS format (have_seconds is true)
if (msec < 0) { if (msec < 0) {
return '-' + format_duration(-msec, have_seconds); return '-' + format_duration(-msec, have_seconds);
} }
@ -189,10 +228,13 @@ function find_timer_by_id(id) {
} }
function load_timers() { function load_timers() {
// Load timers from persistent storage
// If no timers are found, create and return a default timer
console.log('loading timers'); console.log('loading timers');
let timers = Storage.readJSON(TIMERS_FILENAME, true) || []; let timers = Storage.readJSON(TIMERS_FILENAME, true) || [];
if (timers.length) { if (timers.length) {
// Deserealize timer objects // Deserialize timer objects
timers = timers.map(t => PrimitiveTimer.load(t)); timers = timers.map(t => PrimitiveTimer.load(t));
} else { } else {
timers = [new PrimitiveTimer(600, false, -0.001, '', 1)]; timers = [new PrimitiveTimer(600, false, -0.001, '', 1)];
@ -202,6 +244,8 @@ function load_timers() {
} }
function save_timers() { function save_timers() {
// Save TIMERS to persistent storage
console.log('saving timers'); console.log('saving timers');
const dumped_timers = TIMERS.map(t => t.dump()); const dumped_timers = TIMERS.map(t => t.dump());
if (!Storage.writeJSON(TIMERS_FILENAME, dumped_timers)) { if (!Storage.writeJSON(TIMERS_FILENAME, dumped_timers)) {
@ -210,6 +254,11 @@ function save_timers() {
} }
function schedule_save_timers() { function schedule_save_timers() {
// Schedule a save of the timers to persistent storage
// after a timeout. This is used to reduce the number of
// writes to the flash storage when several changes are
// made in a short time.
if (SAVE_TIMERS_TIMEOUT === null) { if (SAVE_TIMERS_TIMEOUT === null) {
console.log('scheduling timer save'); console.log('scheduling timer save');
SAVE_TIMERS_TIMEOUT = setTimeout(() => { SAVE_TIMERS_TIMEOUT = setTimeout(() => {
@ -222,6 +271,8 @@ function schedule_save_timers() {
} }
function save_settings() { function save_settings() {
// Save SETTINGS to persistent storage
console.log('saving settings'); console.log('saving settings');
if (!Storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { if (!Storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) {
E.showAlert('Trouble saving settings'); E.showAlert('Trouble saving settings');
@ -229,6 +280,11 @@ function save_settings() {
} }
function schedule_save_settings() { function schedule_save_settings() {
// Schedule a save of the settings to persistent storage
// after a timeout. This is used to reduce the number of
// writes to the flash storage when several changes are
// made in a short time.
if (SAVE_SETTINGS_TIMEOUT === null) { if (SAVE_SETTINGS_TIMEOUT === null) {
console.log('scheduling settings save'); console.log('scheduling settings save');
SAVE_SETTINGS_TIMEOUT = setTimeout(() => { SAVE_SETTINGS_TIMEOUT = setTimeout(() => {
@ -255,6 +311,10 @@ var TIMERS = load_timers();
// Persistent data convenience functions // Persistent data convenience functions
function delete_timer(timers, timer) { function delete_timer(timers, timer) {
// Find `timer` in array `timers` and remove it.
// Return the next timer in the list, or the last one if `timer`
// was the last one in the list.
const idx = timers.indexOf(timer); const idx = timers.indexOf(timer);
if (idx !== -1) { if (idx !== -1) {
timers.splice(idx, 1); timers.splice(idx, 1);
@ -267,6 +327,11 @@ function delete_timer(timers, timer) {
} }
function add_timer(timers, timer) { function add_timer(timers, timer) {
// Create a independent timer object duplicating `timer`, assign it a
// new unique ID, and add it to the top of the array `timers`.
// Return the new timer object.
// This is used to create a new timer from an existing one.
// Create a copy of current timer object // Create a copy of current timer object
const new_timer = PrimitiveTimer.load(timer.dump()); const new_timer = PrimitiveTimer.load(timer.dump());
// Assign a new ID to the timer // Assign a new ID to the timer
@ -277,6 +342,9 @@ function add_timer(timers, timer) {
} }
function set_last_viewed_timer(timer) { function set_last_viewed_timer(timer) {
// Move `timer` to the top of the list of timers, so it will be
// displayed first when the timer list is shown.
const idx = TIMERS.indexOf(timer); const idx = TIMERS.indexOf(timer);
if (idx == -1) { if (idx == -1) {
console.warn('set_last_viewed_timer: Bug? Called with a timer not found in list'); console.warn('set_last_viewed_timer: Bug? Called with a timer not found in list');
@ -291,11 +359,17 @@ function set_last_viewed_timer(timer) {
} }
function set_timers_dirty() { function set_timers_dirty() {
// Mark the timers as modified and schedule a write to
// persistent storage.
setTimeout(update_system_alarms, 500); setTimeout(update_system_alarms, 500);
schedule_save_timers(); schedule_save_timers();
} }
function set_settings_dirty() { function set_settings_dirty() {
// Mark the settings as modified and schedule a write to
// persistent storage.
schedule_save_settings(); schedule_save_settings();
} }
@ -303,6 +377,9 @@ function set_settings_dirty() {
// Alarm handling // // Alarm handling //
function delete_system_alarms() { function delete_system_alarms() {
// Delete system alarms associated with the tevtimer app (except those
// that are snoozed, so that they will trigger later)
var alarms = Sched.getAlarms().filter(a => a.appid == 'tevtimer'); var alarms = Sched.getAlarms().filter(a => a.appid == 'tevtimer');
for (let alarm of alarms) { for (let alarm of alarms) {
if (alarm.ot === undefined) { if (alarm.ot === undefined) {
@ -317,6 +394,9 @@ function delete_system_alarms() {
} }
function set_system_alarms() { function set_system_alarms() {
// Set system alarms (via `sched` app) for running countdown timers
// that will expire in the future.
for (let idx = 0; idx < TIMERS.length; idx++) { for (let idx = 0; idx < TIMERS.length; idx++) {
let timer = TIMERS[idx]; let timer = TIMERS[idx];
let time_to_next_alarm = timer.to_msec(); let time_to_next_alarm = timer.to_msec();
@ -335,11 +415,15 @@ function set_system_alarms() {
} }
function update_system_alarms() { function update_system_alarms() {
// Refresh system alarms (`sched` app) to reflect changes to timers
delete_system_alarms(); delete_system_alarms();
set_system_alarms(); set_system_alarms();
} }
// Make sure we save timers and settings when switching to another app
// or rebooting
E.on('kill', () => { save_timers(); }); E.on('kill', () => { save_timers(); });
E.on('kill', () => { save_settings(); }); E.on('kill', () => { save_settings(); });