From 4cab1ca4382ea0bdf05cffc4d6c6d07f740ad94d Mon Sep 17 00:00:00 2001 From: Mystica Venatus Date: Sun, 13 Sep 2026 22:11:38 -0400 Subject: [PATCH] Battery remianing and HRM implemented --- nine-hazes-face/package.json | 2 +- nine-hazes-face/src/c/nine-hazes-face.c | 262 ++++++++++++++++++------ nine-hazes-face/src/c/widgets.h | 7 +- nine-hazes-face/src/pkjs/index.js | 67 +++++- 4 files changed, 266 insertions(+), 72 deletions(-) diff --git a/nine-hazes-face/package.json b/nine-hazes-face/package.json index 6d9188b..dc4aa0a 100644 --- a/nine-hazes-face/package.json +++ b/nine-hazes-face/package.json @@ -17,7 +17,7 @@ }, "messageKeys": ["taskName", "syncTime", "clockOff", "boundSec", "used", "taskCount", "curType", "curRem", "status", "abortCode", - "pendItems", "pendXp", "dictV", "serverDown"], + "pendItems", "pendXp", "dictV", "serverDown", "requestSync"], "resources": { "media": [] } diff --git a/nine-hazes-face/src/c/nine-hazes-face.c b/nine-hazes-face/src/c/nine-hazes-face.c index 2e7867b..a88c82c 100644 --- a/nine-hazes-face/src/c/nine-hazes-face.c +++ b/nine-hazes-face/src/c/nine-hazes-face.c @@ -1,6 +1,7 @@ // =========================================================================== -// Nine Hazes — watchface (v0.2, Idle Task Protocol) -// Replace the WHOLE file with this. Do not splice. +// Nine Hazes — watchface (Idle Task Protocol v1.0, GameSnapshot layer) +// Compatible with: snapshot.h (GameSnapshot), widgets.h (10 types, LAYOUT_VERSION 1) +// package.json messageKeys incl. requestSync // =========================================================================== #include #include @@ -12,9 +13,12 @@ static Window *s_window; static Layer *s_canvas; static LayoutState s_layout; + static char s_clock[8], s_date[16], s_strip[64]; +static char s_steps[16], s_dist[16], s_sleep[16], s_hrm[12]; +static bool s_health_ok = false; static int s_bat_pct = -1; -static bool s_server_down = false; // volatile: set on failed check, cleared by absence +static bool s_server_down = false; // set by serverDown tuple, cleared on good sync // ---- battery estimator ------------------------------------------------------ #define BAT_SAMPLES_MAX 16 @@ -41,7 +45,9 @@ static void bat_sample(uint8_t pct) { persist_write_data(BAT_KEY, &s_batlog, sizeof(s_batlog)); } -static int bat_days_remaining(void) { +// Estimated hours until the 5% power-save floor. Returns -1 if insufficient +// data. Note: usable capacity = pct - 5 (watch enters extreme power-save at 5%). +static double bat_hours_remaining(void) { if (s_batlog.count < 3) return -1; int n = s_batlog.count; double sx = 0, sy = 0, sxy = 0, sxx = 0; @@ -52,9 +58,11 @@ static int bat_days_remaining(void) { } double denom = n * sxx - sx * sx; if (denom <= 0) return -1; - double slope = (n * sxy - sx * sy) / denom; + double slope = (n * sxy - sx * sy) / denom; // %/day if (slope >= -0.05) return -1; - return (int)((double)s_batlog.pct[n - 1] / -slope + 0.5); + double usable = (double)s_batlog.pct[n - 1] - 5.0; + if (usable < 0) usable = 0; + return usable / -slope * 24.0; // hours } // ---- palette & fonts --------------------------------------------------------- @@ -79,6 +87,39 @@ static void fonts_load(void) { s_fonts[4] = fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD); } +static void health_refresh(void) { + HealthServiceAccessibilityMask acc = + health_service_metric_accessible(HealthMetricStepCount, + time_start_of_today(), time(NULL)); + s_health_ok = (acc == HealthServiceAccessibilityMaskAvailable); + if (!s_health_ok) { + snprintf(s_steps, sizeof(s_steps), "--"); + snprintf(s_dist, sizeof(s_dist), "--"); + snprintf(s_sleep, sizeof(s_sleep), "--"); + snprintf(s_hrm, sizeof(s_hrm), "--"); + return; + } + + int steps = (int)health_service_sum_today(HealthMetricStepCount); + snprintf(s_steps, sizeof(s_steps), "%d", steps); + + int meters = (int)health_service_sum_today(HealthMetricWalkedDistanceMeters); + if (meters >= 1000) snprintf(s_dist, sizeof(s_dist), "%d.%01dkm", + meters / 1000, (meters % 1000) / 100); + else snprintf(s_dist, sizeof(s_dist), "%dm", meters); + + int sleep_s = (int)health_service_sum_today(HealthMetricSleepSeconds); + snprintf(s_sleep, sizeof(s_sleep), "%dh%02dm", sleep_s / 3600, + (sleep_s % 3600) / 60); + + // Heart rate: 1-minute average; 0 = no current sensor reading + int bpm = (int)health_service_sum_averaged(HealthMetricHeartRateBPM, + time(NULL) - 60, time(NULL), + HealthServiceTimeScopeOnce); + if (bpm > 0) snprintf(s_hrm, sizeof(s_hrm), "%d", bpm); + else snprintf(s_hrm, sizeof(s_hrm), "--"); +} + // ---- values -------------------------------------------------------------------- static void update_values(void) { time_t now = time(NULL); @@ -101,7 +142,8 @@ static void update_values(void) { snprintf(s_strip, sizeof(s_strip), "done %u/36 seg\n%s (collect in game)", s->used, taskname_get()); } - } else if (s->status == 2) snprintf(s_strip, sizeof(s_strip), "FROZEN — in play"); + } + else if (s->status == 2) snprintf(s_strip, sizeof(s_strip), "FROZEN - in play"); else if (s->status == 3) snprintf(s_strip, sizeof(s_strip), "ABORTED (%d)", s->abort_code); else if (s->status == 4) snprintf(s_strip, sizeof(s_strip), "LOCKED"); else snprintf(s_strip, sizeof(s_strip), "No hero yet"); @@ -121,38 +163,83 @@ static void canvas_update(Layer *layer, GContext *ctx) { const char *text; char buf[64]; + int16_t tx_off = 0; // text x-offset: heart icon leads its number switch ((WidgetType)w->type) { case W_TIME: text = s_clock; break; case W_DATE: text = s_date; break; case W_BATTERY: { - int days = bat_days_remaining(); - if (days < 0) snprintf(buf, sizeof(buf), "%d%%", s_bat_pct); - else snprintf(buf, sizeof(buf), "%d%% (~%dd)", s_bat_pct, days); - text = buf; break; + double hrs = bat_hours_remaining(); + if (hrs < 0) { + snprintf(buf, sizeof(buf), "%d%%", s_bat_pct); + } else if (hrs >= 24) { + snprintf(buf, sizeof(buf), "%d%% (~%dd)", s_bat_pct, + (int)(hrs / 24.0 + 0.5)); + } else { + snprintf(buf, sizeof(buf), "%d%% (~%dh)", s_bat_pct, + (int)(hrs + 0.5)); + } + text = buf; + graphics_context_set_text_color(ctx, pal_color(PAL_ACCENT)); + graphics_draw_text(ctx, "WB", s_fonts[1], + GRect(w->x, w->y - 4, 24, 26), + GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); + tx_off = 26; + break; } - case W_PHONE_BATTERY: text = "phone?"; break; - case W_STEPS: text = "steps?"; break; - case W_DISTANCE: text = "dist?"; break; - case W_WEATHER: text = "--/--"; break; - case W_OMEN: text = ""; break; - case W_SLEEP: text = "sleep?"; break; - case W_TASK_STRIP: text = s_strip; break; + case W_PHONE_BATTERY: text = "phone?"; break; + case W_STEPS: { + graphics_context_set_text_color(ctx, pal_color(PAL_POSITIVE)); + graphics_draw_text(ctx, "S", s_fonts[1], + GRect(w->x, w->y - 4, 14, 26), + GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); + text = s_steps; + tx_off = 16; + break; + } + case W_DISTANCE: { + graphics_context_set_text_color(ctx, pal_color(PAL_POSITIVE)); + graphics_draw_text(ctx, "D", s_fonts[1], + GRect(w->x, w->y - 4, 14, 26), + GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); + text = s_dist; + tx_off = 16; + break; + } + case W_SLEEP: text = s_sleep; break; + case W_WEATHER: text = "--/--"; break; + case W_OMEN: text = ""; break; + case W_HEART: { + // state-indicating marker: moss = reading, oxblood = no signal + int reading = (strcmp(s_hrm, "--") != 0); + graphics_context_set_text_color(ctx, + reading ? pal_color(PAL_POSITIVE) : pal_color(PAL_WARNING)); + graphics_draw_text(ctx, "H", s_fonts[1], + GRect(w->x, w->y - 4, 14, 26), + GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); + snprintf(buf, sizeof(buf), "%s bpm", s_hrm); + text = buf; + tx_off = 16; + break; + } + case W_TASK_STRIP: text = s_strip; break; default: continue; } GFont f = (w->font_idx < 5) ? s_fonts[w->font_idx] : s_fonts[1]; graphics_context_set_text_color(ctx, pal_color((PalIndex)w->color_idx)); int16_t h = (w->type == W_TASK_STRIP) ? 56 : 40; // two-line strip - GRect box = GRect(w->x, w->y, b.size.w - w->x, h); + GRect box = GRect(w->x + tx_off, w->y, b.size.w - w->x - tx_off, h); graphics_draw_text(ctx, text, f, box, GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL); - // offline flag: oxblood "!" at strip's top-right + // offline flag: oxblood "!" at strip's top-right, part of the game + // widget render pass — hidden when the task strip is disabled/invisible. + // ASCII placeholder; becomes a PDC sprite later. if (s_server_down && w->type == W_TASK_STRIP) { graphics_context_set_text_color(ctx, pal_color(PAL_WARNING)); graphics_draw_text(ctx, "!", s_fonts[3], - GRect(b.size.w - 20, w->y - 4, 20, 28), - GTextOverflowModeWordWrap, GTextAlignmentRight, NULL); + GRect(b.size.w - 22, w->y - 8, 22, 36), + GTextOverflowModeTrailingEllipsis, GTextAlignmentRight, NULL); } } } @@ -168,15 +255,16 @@ static void layout_default(void) { s_layout.count = WIDGET_TYPE_COUNT; const WidgetCfg defs[WIDGET_TYPE_COUNT] = { {W_TIME, 1, 0, 10, 3, PAL_TEXT, 0}, - {W_DATE, 1, 4, 70, 0, PAL_ACCENT, 0}, - {W_BATTERY, 1, 4, 90, 0, PAL_TEXT, BAT_BOTH}, - {W_STEPS, 1, 4, 110, 0, PAL_TEXT, 0}, - {W_TASK_STRIP, 1, 0, 200, 1, PAL_ACCENT, 0}, - {W_PHONE_BATTERY, 0, 4, 130, 0, PAL_TEXT, 0}, - {W_DISTANCE, 0, 4, 150, 0, PAL_TEXT, 0}, - {W_WEATHER, 0, 4, 130, 2, PAL_TEXT, 0}, - {W_OMEN, 0, 4, 150, 0, PAL_POSITIVE, 0}, - {W_SLEEP, 0, 4, 170, 1, PAL_WATER, 0}, + {W_DATE, 1, 4, 70, 0, PAL_ACCENT, 0}, + {W_BATTERY, 1, 4, 90, 0, PAL_TEXT, BAT_BOTH}, + {W_PHONE_BATTERY, 0, 4, 130, 0, PAL_TEXT, 0}, + {W_STEPS, 1, 4, 115, 0, PAL_TEXT, 0}, + {W_DISTANCE, 1, 64,115, 0, PAL_TEXT, 0}, + {W_HEART, 1, 122, 115, 0, PAL_TEXT, 0}, + {W_WEATHER, 0, 4, 130, 2, PAL_TEXT, 0}, + {W_OMEN, 0, 4, 150, 0, PAL_POSITIVE, 0}, + {W_SLEEP, 0, 4, 170, 1, PAL_WATER, 0}, + {W_TASK_STRIP, 1, 0, 172, 1, PAL_ACCENT, 0}, }; memcpy(s_layout.widgets, defs, sizeof(defs)); persist_write_data(LAYOUT_KEY, &s_layout, sizeof(s_layout)); @@ -195,6 +283,7 @@ static void layout_init(void) { // ---- services -------------------------------------------------------------------- static void tick_handler(struct tm *t, TimeUnits u) { + (void)t; (void)u; request_redraw(); } @@ -204,31 +293,42 @@ static void battery_handler(BatteryChargeState state) { request_redraw(); } +static void health_handler(HealthEventType event, void *context) { + (void)context; + if (event == HealthEventMovementUpdate || + event == HealthEventSleepUpdate || + event == HealthEventHeartRateUpdate) { + health_refresh(); + request_redraw(); + } +} + // ---- AppMessage -------------------------------------------------------------------- static void inbox_received(DictionaryIterator *iter, void *ctx) { + (void)ctx; APP_LOG(APP_LOG_LEVEL_DEBUG, "[MSG] inbox received"); Tuple *t; - // serverDown: presence means down; absence on a successful sync clears it if ((t = dict_find(iter, MESSAGE_KEY_serverDown))) { - s_server_down = t->value->uint8 != 0; + s_server_down = (t->value->uint8 != 0); + APP_LOG(APP_LOG_LEVEL_DEBUG, "[MSG] serverDown=%d", (int)t->value->uint8); request_redraw(); return; } GameSnapshot s = *snapshot_get(); - if ((t = dict_find(iter, MESSAGE_KEY_syncTime))) s.sync_time = t->value->uint32; - if ((t = dict_find(iter, MESSAGE_KEY_clockOff))) s.clock_off = (int32_t)t->value->int32; - if ((t = dict_find(iter, MESSAGE_KEY_boundSec))) s.bound_sec = t->value->uint32; - if ((t = dict_find(iter, MESSAGE_KEY_used))) s.used = t->value->uint8; - if ((t = dict_find(iter, MESSAGE_KEY_taskCount))) s.task_count = t->value->uint8; - if ((t = dict_find(iter, MESSAGE_KEY_curType))) s.cur_type = t->value->uint8; - if ((t = dict_find(iter, MESSAGE_KEY_curRem))) s.cur_rem = t->value->uint8; - if ((t = dict_find(iter, MESSAGE_KEY_status))) s.status = t->value->uint8; + if ((t = dict_find(iter, MESSAGE_KEY_syncTime))) s.sync_time = t->value->uint32; + if ((t = dict_find(iter, MESSAGE_KEY_clockOff))) s.clock_off = (int32_t)t->value->int32; + if ((t = dict_find(iter, MESSAGE_KEY_boundSec))) s.bound_sec = t->value->uint32; + if ((t = dict_find(iter, MESSAGE_KEY_used))) s.used = t->value->uint8; + if ((t = dict_find(iter, MESSAGE_KEY_taskCount))) s.task_count = t->value->uint8; + if ((t = dict_find(iter, MESSAGE_KEY_curType))) s.cur_type = t->value->uint8; + if ((t = dict_find(iter, MESSAGE_KEY_curRem))) s.cur_rem = t->value->uint8; + if ((t = dict_find(iter, MESSAGE_KEY_status))) s.status = t->value->uint8; if ((t = dict_find(iter, MESSAGE_KEY_abortCode))) s.abort_code = t->value->uint8; - if ((t = dict_find(iter, MESSAGE_KEY_pendItems))) s.pend_items = t->value->uint16; - if ((t = dict_find(iter, MESSAGE_KEY_pendXp))) s.pend_xp = t->value->uint16; - if ((t = dict_find(iter, MESSAGE_KEY_dictV))) s.dict_v = t->value->uint8; + if ((t = dict_find(iter, MESSAGE_KEY_pendItems))) s.pend_items = t->value->uint16; + if ((t = dict_find(iter, MESSAGE_KEY_pendXp))) s.pend_xp = t->value->uint16; + if ((t = dict_find(iter, MESSAGE_KEY_dictV))) s.dict_v = t->value->uint8; snapshot_store(&s); if ((t = dict_find(iter, MESSAGE_KEY_taskName))) { @@ -238,10 +338,36 @@ static void inbox_received(DictionaryIterator *iter, void *ctx) { taskname_store(name); } - s_server_down = false; + s_server_down = false; // a good sync clears the flag + APP_LOG(APP_LOG_LEVEL_DEBUG, "[MSG] snapshot ok: st=%u used=%u rem=%u", + s.status, s.used, s.cur_rem); request_redraw(); } +static void inbox_dropped(AppMessageResult reason, void *ctx) { + (void)ctx; + APP_LOG(APP_LOG_LEVEL_DEBUG, "[MSG] inbox DROPPED: %d", (int)reason); +} + +static void outbox_failed(DictionaryIterator *iter, AppMessageResult reason, void *ctx) { + (void)iter; (void)ctx; + APP_LOG(APP_LOG_LEVEL_DEBUG, "[MSG] outbox FAILED: %d", (int)reason); +} + +// Deferred send: outbox isn't reliably usable the instant after app_message_open +static void send_sync_request(void *data) { + (void)data; + DictionaryIterator *out; + AppMessageResult r = app_message_outbox_begin(&out); + if (r != APP_MSG_OK) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "[INIT] outbox_begin failed: %d", (int)r); + return; + } + dict_write_uint8(out, MESSAGE_KEY_requestSync, 1); + r = app_message_outbox_send(); + APP_LOG(APP_LOG_LEVEL_DEBUG, "[INIT] requestSync sent: %d", (int)r); +} + // ---- window lifecycle ---------------------------------------------------------------- static void window_load(Window *w) { Layer *root = window_get_root_layer(w); @@ -254,41 +380,59 @@ static void window_load(Window *w) { } static void window_unload(Window *w) { + (void)w; layer_destroy(s_canvas); s_canvas = NULL; } // ---- app lifecycle ---------------------------------------------------------------------- static void init(void) { + persist_delete(LAYOUT_KEY); // widget layout → forced back to defaults Delete + // synthetic drain: ~6%/day over 4 days, oldest sample first + memset(&s_batlog, 0, sizeof(s_batlog)); + s_batlog.version = 1; + uint32_t now_min = (uint32_t)(time(NULL) / 60); + for (int i = 0; i < 5; i++) { + s_batlog.ts[i] = now_min - (uint32_t)(4 - i) * 1440; // 1 day apart + s_batlog.pct[i] = 100 - i * 6; // 100,94,88,82,76 + } + s_batlog.count = 5; + persist_write_data(BAT_KEY, &s_batlog, sizeof(s_batlog)); fonts_load(); layout_init(); snapshot_init(); - if (persist_exists(BAT_KEY) && + if (!(persist_exists(BAT_KEY) && persist_read_data(BAT_KEY, &s_batlog, sizeof(s_batlog)) == (int)sizeof(s_batlog) && - s_batlog.version == 1) { - // restored - } else { - memset(&s_batlog, 0, sizeof(s_batlog)); - s_batlog.version = 1; + s_batlog.version == 1)) { + memset(&s_batlog, 0, sizeof(s_batlog)); + s_batlog.version = 1; } s_window = window_create(); - window_set_window_handlers(s_window, (WindowHandlers){ - .load = window_load, .unload = window_unload }); - window_stack_push(s_window, true); + window_set_window_handlers(s_window, (WindowHandlers){ + .load = window_load, .unload = window_unload }); + window_stack_push(s_window, true); - tick_timer_service_subscribe(MINUTE_UNIT, tick_handler); - battery_state_service_subscribe(battery_handler); - battery_handler(battery_state_service_peek()); + tick_timer_service_subscribe(MINUTE_UNIT, tick_handler); + battery_state_service_subscribe(battery_handler); + battery_handler(battery_state_service_peek()); - app_message_register_inbox_received(inbox_received); - app_message_open(256, 64); + health_refresh(); + health_service_events_subscribe(health_handler, NULL); + + app_message_register_inbox_received(inbox_received); + app_message_register_inbox_dropped(inbox_dropped); + app_message_register_outbox_failed(outbox_failed); + app_message_open(256, 64); + + app_timer_register(300, send_sync_request, NULL); } static void deinit(void) { tick_timer_service_unsubscribe(); battery_state_service_unsubscribe(); + health_service_events_unsubscribe(); window_destroy(s_window); } diff --git a/nine-hazes-face/src/c/widgets.h b/nine-hazes-face/src/c/widgets.h index 2ef83f0..463af32 100644 --- a/nine-hazes-face/src/c/widgets.h +++ b/nine-hazes-face/src/c/widgets.h @@ -1,7 +1,7 @@ #pragma once #include -// ---- Widget types (10, locked in DESIGN.md §5) --------------------------- +// ---- Widget types (DESIGN.md §5 + heart) ------------------------------- typedef enum { W_TIME = 0, W_DATE, @@ -9,6 +9,7 @@ typedef enum { W_PHONE_BATTERY, W_STEPS, W_DISTANCE, + W_HEART, // heart-rate BPM, drawn icon + number W_WEATHER, // composite: current temp + hi/lo (sub-elements later) W_OMEN, // weather advisory flavor strip W_SLEEP, @@ -42,9 +43,9 @@ typedef struct __attribute__((packed)) { uint8_t aux; // per-type: BatMode for battery, sub-flags later } WidgetCfg; -#define LAYOUT_VERSION 1 +#define LAYOUT_VERSION 3 #define LAYOUT_KEY 100 -#define WIDGET_SLOTS 12 // 10 types + 2 spare, matching "12" scaffold note +#define WIDGET_SLOTS 12 // 11 types + 1 spare typedef struct __attribute__((packed)) { uint8_t version; diff --git a/nine-hazes-face/src/pkjs/index.js b/nine-hazes-face/src/pkjs/index.js index 9d1d7b6..fc38ef8 100644 --- a/nine-hazes-face/src/pkjs/index.js +++ b/nine-hazes-face/src/pkjs/index.js @@ -10,11 +10,19 @@ var CONFIG = { function get(path, cb) { var xhr = new XMLHttpRequest(); - var abortTimer = setTimeout(function () { xhr.abort(); }, 5000); + var done = false; + var abortTimer = setTimeout(function () { + if (done) return; + done = true; + cb('timeout'); + try { xhr.abort(); } catch (e) {} + }, 5000); xhr.open('GET', CONFIG.baseUrl + path, true); xhr.setRequestHeader('Authorization', 'Bearer ' + CONFIG.token); xhr.onload = function () { + if (done) return; + done = true; clearTimeout(abortTimer); if (xhr.status === 204) { return cb(null, null); } if (xhr.status !== 200) { return cb('HTTP ' + xhr.status); } @@ -22,6 +30,8 @@ function get(path, cb) { catch (e) { cb('malformed'); } }; xhr.onerror = function () { + if (done) return; + done = true; clearTimeout(abortTimer); cb('network'); }; @@ -60,21 +70,39 @@ Pebble.addEventListener('ready', function () { sync(); }); -Pebble.addEventListener('appmessage', function (e) { - console.log('[PKJS] appmessage from watch: ' + JSON.stringify(e.payload)); +// In your pkjs index.js, add: +Pebble.addEventListener('appmessage', function(e) { + console.log('[PKJS] Received appmessage from watch: ' + JSON.stringify(e.payload)); + if (e.payload.requestSync === 1) { + console.log('[PKJS] Watch requested sync, triggering sync()'); + sync(); + } }); Pebble.addEventListener('showConfiguration', function () {}); Pebble.addEventListener('webviewclosed', function () {}); function sync() { + console.log('[PKJS] Sync Function Entered'); + get('/v1/queue', function (err, q) { if (err || !q) { console.log('[PKJS] fetch failed (' + err + ') — flagging serverDown'); - Pebble.sendAppMessage({ serverDown: 1 }, function () {}, function () {}); + + // Explicit success/failure callbacks for sendAppMessage + Pebble.sendAppMessage( + { serverDown: 1 }, + function() { + console.log('[PKJS] serverDown flag delivered successfully'); + }, + function(e) { + console.log('[PKJS] FAILED to send serverDown: ' + JSON.stringify(e)); + } + ); return; } + // ... rest of your existing sync logic with enhanced logging var msg = { syncTime: Math.floor(Date.now() / 1000), clockOff: q.t - Date.now(), @@ -90,26 +118,45 @@ function sync() { dictV: q.dict, }; + console.log('[PKJS] Get /v1/queue - building message with ' + msg.taskCount + ' tasks'); + var cachedV = parseInt(localStorage.getItem('dict_v') || '-1', 10); var finish = function (names) { if (msg.curType !== 255 && names && names[msg.curType]) { var nameStr = String(names[msg.curType]).slice(0, 24); msg.taskName = nameStr; + console.log('[PKJS] Task name resolved: ' + nameStr); } - Pebble.sendAppMessage(msg, - function () { console.log('[PKJS] snapshot delivered'); }, - function (e) { console.log('[PKJS] send failed: ' + JSON.stringify(e)); }); + + Pebble.sendAppMessage( + msg, + function () { + console.log('[PKJS] snapshot delivered successfully'); + // Clear serverDown flag on successful sync + // Note: This is implicit - absence of serverDown key means "up" + }, + function (e) { + console.log('[PKJS] send failed: ' + JSON.stringify(e)); + // Optionally flag server down on send failure too + Pebble.sendAppMessage({ serverDown: 1 }, function(){}, function(){}); + } + ); }; if (cachedV === q.dict) { try { - finish(JSON.parse(localStorage.getItem('dict_names') || '{}')); + var cachedNames = JSON.parse(localStorage.getItem('dict_names') || '{}'); + console.log('[PKJS] Using cached dictionary v' + cachedV); + finish(cachedNames); return; - } catch (e) {} + } catch (e) { + console.log('[PKJS] Cache parse failed, fetching fresh'); + } } get('/v1/dictionary?v=' + cachedV, function (derr, dict) { if (derr || !dict) { + console.log('[PKJS] Dictionary fetch failed, using cache or null'); try { finish(JSON.parse(localStorage.getItem('dict_names') || '{}')); } catch (e2) { @@ -117,6 +164,8 @@ function sync() { } return; } + + console.log('[PKJS] Get /v1/dictionary?v=' + dict.v); var names = {}; for (var i = 0; i < dict.tasks.length; i++) { var t = dict.tasks[i];