v2 Phase 4: Session screen — today's tokens and equivalent cost
Daemon: compute_today_usage() sums today's Claude Code token usage across the local project transcripts (~/.claude/projects/**/*.jsonl), reading only files modified today so the scan stays cheap, and prices it with Anthropic list rates to show the equivalent API cost (the ccusage-style flex for subscription users). The compact fields tk (total tokens), tc (cost in cents) and tn (message count) piggyback onto the existing 60s BLE payload. Firmware: UsageData gains tokens_today (64-bit) / cost_cents_today / messages_today; parse_json reads tk/tc/tn; a new Session screen shows the cost as the hero with total tokens and message count below, refreshed from ui_update so it's current whenever opened. Wire SCREEN_SESSION to the real screen (was a stub). Verified end to end: daemon sends e.g. tk=105374109 tc=26838 tn=663. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+63
-1
@@ -129,6 +129,10 @@ static lv_obj_t* bt_container; // BLE connection info
|
||||
static lv_obj_t* bt_status_lbl;
|
||||
static lv_obj_t* bt_name_lbl;
|
||||
static lv_obj_t* bt_mac_lbl;
|
||||
static lv_obj_t* session_container; // today's tokens / cost
|
||||
static lv_obj_t* sess_cost_lbl;
|
||||
static lv_obj_t* sess_tokens_lbl;
|
||||
static lv_obj_t* sess_msgs_lbl;
|
||||
|
||||
// App registry — the launcher renders one tile per entry, so adding a screen is
|
||||
// one line here plus its builder. Order = tile order. "Animations" reuses the
|
||||
@@ -237,6 +241,13 @@ static void format_reset_time(int mins, char* buf, size_t len) {
|
||||
}
|
||||
}
|
||||
|
||||
// Format a token count compactly: 94972115 -> "95.0M", 960621 -> "960.6K".
|
||||
static void format_tokens(long long n, char* buf, size_t len) {
|
||||
if (n >= 1000000) snprintf(buf, len, "%.1fM", (double)n / 1000000.0);
|
||||
else if (n >= 1000) snprintf(buf, len, "%.1fK", (double)n / 1000.0);
|
||||
else snprintf(buf, len, "%lld", n);
|
||||
}
|
||||
|
||||
// Forward decls — callbacks defined near ui_show_screen below
|
||||
static void global_click_cb(lv_event_t* e);
|
||||
static void logo_click_cb(lv_event_t* e);
|
||||
@@ -676,6 +687,46 @@ static void init_soundpad_screen(lv_obj_t* scr) {
|
||||
lv_obj_add_flag(soundpad_container, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
// Session: today's Claude Code usage — equivalent API cost (hero), total tokens,
|
||||
// and message count. Labels are refreshed from ui_update() as data arrives.
|
||||
static void init_session_screen(lv_obj_t* scr) {
|
||||
session_container = lv_obj_create(scr);
|
||||
lv_obj_set_size(session_container, L.scr_w, L.scr_h);
|
||||
lv_obj_set_pos(session_container, 0, 0);
|
||||
lv_obj_set_style_bg_opa(session_container, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(session_container, 0, 0);
|
||||
lv_obj_set_style_pad_all(session_container, 0, 0);
|
||||
lv_obj_clear_flag(session_container, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
make_screen_title(session_container, "Session");
|
||||
|
||||
sess_cost_lbl = lv_label_create(session_container);
|
||||
lv_label_set_text(sess_cost_lbl, "$0.00");
|
||||
lv_obj_set_style_text_font(sess_cost_lbl, &font_tiempos_56, 0);
|
||||
lv_obj_set_style_text_color(sess_cost_lbl, COL_TEXT, 0);
|
||||
lv_obj_align(sess_cost_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 30);
|
||||
|
||||
lv_obj_t* sub = lv_label_create(session_container);
|
||||
lv_label_set_text(sub, "spent today");
|
||||
lv_obj_set_style_text_font(sub, &font_styrene_20, 0);
|
||||
lv_obj_set_style_text_color(sub, COL_DIM, 0);
|
||||
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, L.content_y + 110);
|
||||
|
||||
sess_tokens_lbl = lv_label_create(session_container);
|
||||
lv_label_set_text(sess_tokens_lbl, "\xE2\x80\x94 tokens");
|
||||
lv_obj_set_style_text_font(sess_tokens_lbl, &font_styrene_28, 0);
|
||||
lv_obj_set_style_text_color(sess_tokens_lbl, COL_ACCENT, 0);
|
||||
lv_obj_align(sess_tokens_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 165);
|
||||
|
||||
sess_msgs_lbl = lv_label_create(session_container);
|
||||
lv_label_set_text(sess_msgs_lbl, "\xE2\x80\x94 messages");
|
||||
lv_obj_set_style_text_font(sess_msgs_lbl, &font_styrene_20, 0);
|
||||
lv_obj_set_style_text_color(sess_msgs_lbl, COL_DIM, 0);
|
||||
lv_obj_align(sess_msgs_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 215);
|
||||
|
||||
lv_obj_add_flag(session_container, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
// ======== Public API ========
|
||||
|
||||
void ui_init(void) {
|
||||
@@ -700,6 +751,7 @@ void ui_init(void) {
|
||||
init_stub_screen(scr);
|
||||
init_bt_screen(scr);
|
||||
init_soundpad_screen(scr);
|
||||
init_session_screen(scr);
|
||||
|
||||
logo_img = lv_image_create(scr);
|
||||
lv_image_set_src(logo_img, &logo_dsc);
|
||||
@@ -746,6 +798,15 @@ void ui_update(const UsageData* data) {
|
||||
|
||||
format_reset_time(data->weekly_reset_mins, buf, sizeof(buf));
|
||||
lv_label_set_text(lbl_weekly_reset, buf);
|
||||
|
||||
// Session screen — refreshed here so it's current whenever it's opened.
|
||||
if (sess_cost_lbl) {
|
||||
lv_label_set_text_fmt(sess_cost_lbl, "$%d.%02d",
|
||||
data->cost_cents_today / 100, data->cost_cents_today % 100);
|
||||
format_tokens(data->tokens_today, buf, sizeof(buf));
|
||||
lv_label_set_text_fmt(sess_tokens_lbl, "%s tokens", buf);
|
||||
lv_label_set_text_fmt(sess_msgs_lbl, "%d messages", data->messages_today);
|
||||
}
|
||||
}
|
||||
|
||||
// Pick the usage-view sub-screen: pairing hint (BLE down), the idle "Zzz" screen
|
||||
@@ -854,6 +915,7 @@ void ui_show_screen(screen_t screen) {
|
||||
if (stub_container) lv_obj_add_flag(stub_container, LV_OBJ_FLAG_HIDDEN);
|
||||
if (bt_container) lv_obj_add_flag(bt_container, LV_OBJ_FLAG_HIDDEN);
|
||||
if (soundpad_container) lv_obj_add_flag(soundpad_container, LV_OBJ_FLAG_HIDDEN);
|
||||
if (session_container) lv_obj_add_flag(session_container, LV_OBJ_FLAG_HIDDEN);
|
||||
splash_hide();
|
||||
|
||||
switch (screen) {
|
||||
@@ -861,8 +923,8 @@ void ui_show_screen(screen_t screen) {
|
||||
case SCREEN_USAGE: lv_obj_clear_flag(usage_container, LV_OBJ_FLAG_HIDDEN); break;
|
||||
case SCREEN_MENU: lv_obj_clear_flag(menu_container, LV_OBJ_FLAG_HIDDEN); break;
|
||||
case SCREEN_SOUNDPAD: lv_obj_clear_flag(soundpad_container, LV_OBJ_FLAG_HIDDEN); break;
|
||||
case SCREEN_SESSION: lv_obj_clear_flag(session_container, LV_OBJ_FLAG_HIDDEN); break;
|
||||
case SCREEN_BLUETOOTH: bt_refresh(); lv_obj_clear_flag(bt_container, LV_OBJ_FLAG_HIDDEN); break;
|
||||
case SCREEN_SESSION:
|
||||
case SCREEN_NOWPLAYING:
|
||||
case SCREEN_HOMEASSIST:
|
||||
stub_show_for(screen);
|
||||
|
||||
Reference in New Issue
Block a user