feat(face): complete widget set (phone batt, sleep, omens)
Phone battery via navigator.getBattery(), honest "--" when unavailable. Sleep widget enabled with M marker. Omens derived from weather condition: fog/rain/snow/thunder produce advisory text; clear shows empty. All 11 widget types rendered; layout enables all by default. Refs: DESIGN.md §5 widget set complete.
This commit is contained in:
@@ -18,7 +18,8 @@
|
|||||||
"messageKeys": ["taskName", "syncTime", "clockOff", "boundSec", "used",
|
"messageKeys": ["taskName", "syncTime", "clockOff", "boundSec", "used",
|
||||||
"taskCount", "curType", "curRem", "status", "abortCode",
|
"taskCount", "curType", "curRem", "status", "abortCode",
|
||||||
"pendItems", "pendXp", "dictV", "serverDown", "requestSync",
|
"pendItems", "pendXp", "dictV", "serverDown", "requestSync",
|
||||||
"weatherTemp", "weatherHi", "weatherLo", "weatherTs", "weatherCond"],
|
"weatherTemp", "weatherHi", "weatherLo", "weatherTs",
|
||||||
|
"weatherCond", "phoneBatt"],
|
||||||
"resources": {
|
"resources": {
|
||||||
"media": []
|
"media": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ 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 char s_steps[16], s_dist[16], s_sleep[16], s_hrm[12];
|
||||||
static bool s_health_ok = false;
|
static bool s_health_ok = false;
|
||||||
static int s_bat_pct = -1;
|
static int s_bat_pct = -1;
|
||||||
|
static int8_t s_phone_batt = INT8_MIN;
|
||||||
static bool s_server_down = false; // set by serverDown tuple, cleared on good sync
|
static bool s_server_down = false; // set by serverDown tuple, cleared on good sync
|
||||||
|
|
||||||
static char s_weather[16];
|
static char s_weather[24];
|
||||||
|
static char s_omen[32];
|
||||||
static int16_t s_wx_temp = INT16_MIN, s_wx_hi = INT16_MIN, s_wx_lo = INT16_MIN;
|
static int16_t s_wx_temp = INT16_MIN, s_wx_hi = INT16_MIN, s_wx_lo = INT16_MIN;
|
||||||
static uint8_t s_wx_cond = 0;
|
static uint8_t s_wx_cond = 0;
|
||||||
|
|
||||||
@@ -151,12 +153,24 @@ static void update_values(void) {
|
|||||||
else if (s->status == 3) snprintf(s_strip, sizeof(s_strip), "ABORTED (%d)", s->abort_code);
|
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 if (s->status == 4) snprintf(s_strip, sizeof(s_strip), "LOCKED");
|
||||||
else snprintf(s_strip, sizeof(s_strip), "No hero yet");
|
else snprintf(s_strip, sizeof(s_strip), "No hero yet");
|
||||||
|
|
||||||
|
// Current Weather (rain Snow Sunnt ect..)
|
||||||
if (s_wx_temp != INT16_MIN) {
|
if (s_wx_temp != INT16_MIN) {
|
||||||
static const char wx_icon[8] = { '?', 'o', '~', 'C', 'F', 'R', '*', 'T' };
|
static const char wx_icon[8] = { '?', 'o', '~', 'C', 'F', 'R', '*', 'T' };
|
||||||
snprintf(s_weather, sizeof(s_weather), "%c %d° %d/%d",
|
snprintf(s_weather, sizeof(s_weather), "%c %d° %d/%d",
|
||||||
wx_icon[s_wx_cond & 7], s_wx_temp, s_wx_hi, s_wx_lo);
|
wx_icon[s_wx_cond & 7], s_wx_temp, s_wx_hi, s_wx_lo);
|
||||||
|
|
||||||
|
// Omen derivation from weather condition
|
||||||
|
switch (s_wx_cond & 7) {
|
||||||
|
case 4: snprintf(s_omen, sizeof(s_omen), "Travel impaired"); break; // fog
|
||||||
|
case 5: snprintf(s_omen, sizeof(s_omen), "Rain delays"); break; // rain
|
||||||
|
case 6: snprintf(s_omen, sizeof(s_omen), "Cold footing"); break; // snow
|
||||||
|
case 7: snprintf(s_omen, sizeof(s_omen), "Dangerous air"); break; // thunder
|
||||||
|
default: s_omen[0] = '\0'; break;; // clear/other = no omen
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
snprintf(s_weather, sizeof(s_weather), "--/--");
|
snprintf(s_weather, sizeof(s_weather), "--/--");
|
||||||
|
s_omen[0] = '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +211,20 @@ static void canvas_update(Layer *layer, GContext *ctx) {
|
|||||||
tx_off = 26;
|
tx_off = 26;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case W_PHONE_BATTERY: text = "phone?"; break;
|
case W_PHONE_BATTERY: {
|
||||||
|
graphics_context_set_text_color(ctx, pal_color(PAL_ACCENT));
|
||||||
|
graphics_draw_text(ctx, "PB", s_fonts[1],
|
||||||
|
GRect(w->x, w->y - 4, 24, 26),
|
||||||
|
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
|
||||||
|
if (s_phone_batt >= 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "%d%%", s_phone_batt);
|
||||||
|
text = buf;
|
||||||
|
} else {
|
||||||
|
text = "--"; // no reading, honest empty
|
||||||
|
}
|
||||||
|
tx_off = 26;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case W_STEPS: {
|
case W_STEPS: {
|
||||||
graphics_context_set_text_color(ctx, pal_color(PAL_POSITIVE));
|
graphics_context_set_text_color(ctx, pal_color(PAL_POSITIVE));
|
||||||
graphics_draw_text(ctx, "S", s_fonts[1],
|
graphics_draw_text(ctx, "S", s_fonts[1],
|
||||||
@@ -216,9 +243,19 @@ static void canvas_update(Layer *layer, GContext *ctx) {
|
|||||||
tx_off = 16;
|
tx_off = 16;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case W_SLEEP: text = s_sleep; break;
|
case W_SLEEP: {
|
||||||
|
graphics_context_set_text_color(ctx, pal_color(PAL_WATER));
|
||||||
|
graphics_draw_text(ctx, "Z", s_fonts[1],
|
||||||
|
GRect(w->x, w->y - 4, 14, 26),
|
||||||
|
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
|
||||||
|
text = s_sleep;
|
||||||
|
tx_off = 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case W_WEATHER: text = s_weather; break;
|
case W_WEATHER: text = s_weather; break;
|
||||||
case W_OMEN: text = ""; break;
|
case W_OMEN:
|
||||||
|
text = s_omen[0] ? s_omen : "";
|
||||||
|
break;
|
||||||
case W_HEART: {
|
case W_HEART: {
|
||||||
// state-indicating marker: moss = reading, oxblood = no signal
|
// state-indicating marker: moss = reading, oxblood = no signal
|
||||||
int reading = (strcmp(s_hrm, "--") != 0);
|
int reading = (strcmp(s_hrm, "--") != 0);
|
||||||
@@ -265,17 +302,17 @@ static void layout_default(void) {
|
|||||||
s_layout.version = LAYOUT_VERSION;
|
s_layout.version = LAYOUT_VERSION;
|
||||||
s_layout.count = WIDGET_TYPE_COUNT;
|
s_layout.count = WIDGET_TYPE_COUNT;
|
||||||
const WidgetCfg defs[WIDGET_TYPE_COUNT] = {
|
const WidgetCfg defs[WIDGET_TYPE_COUNT] = {
|
||||||
{W_TIME, 1, 0, 10, 3, PAL_TEXT, 0},
|
{W_TIME, 1, 0, 10, 3, PAL_TEXT, 0},
|
||||||
{W_DATE, 1, 4, 70, 0, PAL_ACCENT, 0},
|
{W_DATE, 1, 4, 70, 0, PAL_ACCENT, 0},
|
||||||
{W_BATTERY, 1, 4, 90, 0, PAL_TEXT, BAT_BOTH},
|
{W_BATTERY, 1, 4, 90, 0, PAL_TEXT, BAT_BOTH},
|
||||||
{W_PHONE_BATTERY, 0, 4, 130, 0, PAL_TEXT, 0},
|
{W_PHONE_BATTERY, 1, 100, 90, 0, PAL_TEXT, 0},
|
||||||
{W_STEPS, 1, 4, 115, 0, PAL_TEXT, 0},
|
{W_STEPS, 1, 4, 115, 0, PAL_TEXT, 0},
|
||||||
{W_DISTANCE, 1, 64,115, 0, PAL_TEXT, 0},
|
{W_DISTANCE, 1, 64, 115, 0, PAL_TEXT, 0},
|
||||||
{W_HEART, 1, 122, 115, 0, PAL_TEXT, 0},
|
{W_HEART, 1, 122, 115, 0, PAL_TEXT, 0},
|
||||||
{W_WEATHER, 1, 4, 130, 2, PAL_TEXT, 0},
|
{W_SLEEP, 1, 4, 135, 1, PAL_WATER, 0},
|
||||||
{W_OMEN, 0, 4, 150, 0, PAL_POSITIVE, 0},
|
{W_WEATHER, 1, 4, 170, 2, PAL_TEXT, 0},
|
||||||
{W_SLEEP, 0, 4, 170, 1, PAL_WATER, 0},
|
{W_OMEN, 1, 4, 155, 0, PAL_POSITIVE, 0},
|
||||||
{W_TASK_STRIP, 1, 0, 172, 1, PAL_ACCENT, 0},
|
{W_TASK_STRIP, 1, 0, 200, 1, PAL_ACCENT, 0},
|
||||||
};
|
};
|
||||||
memcpy(s_layout.widgets, defs, sizeof(defs));
|
memcpy(s_layout.widgets, defs, sizeof(defs));
|
||||||
persist_write_data(LAYOUT_KEY, &s_layout, sizeof(s_layout));
|
persist_write_data(LAYOUT_KEY, &s_layout, sizeof(s_layout));
|
||||||
@@ -353,6 +390,8 @@ static void inbox_received(DictionaryIterator *iter, void *ctx) {
|
|||||||
if ((t = dict_find(iter, MESSAGE_KEY_weatherHi))) s_wx_hi = t->value->int16;
|
if ((t = dict_find(iter, MESSAGE_KEY_weatherHi))) s_wx_hi = t->value->int16;
|
||||||
if ((t = dict_find(iter, MESSAGE_KEY_weatherLo))) s_wx_lo = t->value->int16;
|
if ((t = dict_find(iter, MESSAGE_KEY_weatherLo))) s_wx_lo = t->value->int16;
|
||||||
|
|
||||||
|
if ((t = dict_find(iter, MESSAGE_KEY_phoneBatt))) s_phone_batt = (int8_t)t->value->uint8;
|
||||||
|
|
||||||
s_server_down = false; // a good sync clears the flag
|
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",
|
APP_LOG(APP_LOG_LEVEL_DEBUG, "[MSG] snapshot ok: st=%u used=%u rem=%u",
|
||||||
s.status, s.used, s.cur_rem);
|
s.status, s.used, s.cur_rem);
|
||||||
|
|||||||
@@ -92,6 +92,27 @@ function fetchWeather(cb) {
|
|||||||
}, { timeout: 8000, maximumAge: 600000 });
|
}, { timeout: 8000, maximumAge: 600000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- phone battery (percent only, honest-empty if unavailable) -------------
|
||||||
|
function fetchPhoneBatt(cb) {
|
||||||
|
if (typeof navigator === 'undefined' || !navigator.getBattery) {
|
||||||
|
return cb(null); // no API / emulator
|
||||||
|
}
|
||||||
|
navigator.getBattery().then(function (b) {
|
||||||
|
var pct = Math.round(b.level * 100);
|
||||||
|
cb(pct);
|
||||||
|
}).catch(function () { cb(null); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPhoneBatt() {
|
||||||
|
fetchPhoneBatt(function (pb) {
|
||||||
|
if (pb !== null) {
|
||||||
|
Pebble.sendAppMessage({ phoneBatt: pb },
|
||||||
|
function () { console.log('[PKJS] phone batt delivered'); },
|
||||||
|
function (e) { console.log('[PKJS] phone batt send failed'); });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function wxLookup(lat, lon, cb) {
|
function wxLookup(lat, lon, cb) {
|
||||||
// rough US bbox (CONUS + AK + HI + PR): NWS has data, else Open-Meteo
|
// rough US bbox (CONUS + AK + HI + PR): NWS has data, else Open-Meteo
|
||||||
var inUS = (lat >= 17 && lat <= 72 && lon >= -179 && lon <= -66);
|
var inUS = (lat >= 17 && lat <= 72 && lon >= -179 && lon <= -66);
|
||||||
@@ -274,7 +295,8 @@ function sync() {
|
|||||||
Pebble.sendAppMessage({ serverDown: 1 }, function(){}, function(){});
|
Pebble.sendAppMessage({ serverDown: 1 }, function(){}, function(){});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
sendWeather(); // fire-and-forget; separate message
|
sendWeather();
|
||||||
|
sendPhoneBatt();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cachedV === q.dict) {
|
if (cachedV === q.dict) {
|
||||||
|
|||||||
Reference in New Issue
Block a user