Battery remianing and HRM implemented

This commit is contained in:
Mystica Venatus
2026-09-13 22:11:38 -04:00
parent 55480b3fc6
commit 4cab1ca438
4 changed files with 266 additions and 72 deletions

View File

@@ -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": []
}

View File

@@ -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 <pebble.h>
#include <time.h>
@@ -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);
}

View File

@@ -1,7 +1,7 @@
#pragma once
#include <pebble.h>
// ---- 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;

View File

@@ -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];