From b6dca806708b1f83787c3c571d0b42bf18ce3d3f Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Tue, 7 Apr 2026 00:51:07 -0400 Subject: [PATCH] Initial code commit --- .gitignore | 3 ++ clangd.zsh | 19 +++++++ package.json | 40 +++++++++++++++ src/c/main.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++ src/pkjs/index.js | 9 ++++ wscript | 54 ++++++++++++++++++++ 6 files changed, 249 insertions(+) create mode 100644 .gitignore create mode 100755 clangd.zsh create mode 100644 package.json create mode 100644 src/c/main.c create mode 100644 src/pkjs/index.js create mode 100644 wscript diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b41ca9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.clangd +/build +/.lock* diff --git a/clangd.zsh b/clangd.zsh new file mode 100755 index 0000000..933b8be --- /dev/null +++ b/clangd.zsh @@ -0,0 +1,19 @@ +#!/usr/bin/env zsh +echo "CompileFlags: + Add: + [ + -DPBL_DISPLAY_WIDTH=200, + -DPBL_DISPLAY_HEIGHT=228, + -xc, + -nostdinc, + -DPBL_COLOR, + -I${HOME}/.pebble-sdk/SDKs/current/sdk-core/pebble/emery/include, + -include$(pwd)/build/include/message_keys.auto.h, + -I$(pwd)/build/emery, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/arm-none-eabi/include/stdint.h, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/arm-none-eabi/include/stdlib.h, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/arm-none-eabi/include/time.h, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/arm-none-eabi/include/string.h, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/lib/gcc/arm-none-eabi/14.2.1/include/stddef.h, + -include${HOME}/.pebble-sdk/SDKs/current/toolchain/arm-none-eabi/lib/gcc/arm-none-eabi/14.2.1/include/stdbool.h, + ]" > ./.clangd diff --git a/package.json b/package.json new file mode 100644 index 0000000..f7a8fe6 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "author": "RandyTheSilly", + "dependencies": {}, + "keywords": [ + "jellyfin", + "plex", + "sleep" + ], + "capabilities": [ + "health" + ], + "name": "one-more-episode", + "pebble": { + "displayName": "One More Episode", + "enableMultiJS": true, + "messageKeys": [ + "APP_READY", + "SLEEP_TIME", + "API_FULL_URL" + ], + "projectType": "native", + "resources": { + "media": [] + }, + "sdkVersion": "3", + "targetPlatforms": [ + "basalt", + "chalk", + "diorite", + "flint", + "emery", + "gabbro" + ], + "uuid": "3097b48e-552c-4a78-92b2-566b9128640e", + "watchapp": { + "watchface": false + } + }, + "version": "1.0.0" +} diff --git a/src/c/main.c b/src/c/main.c new file mode 100644 index 0000000..7129bc1 --- /dev/null +++ b/src/c/main.c @@ -0,0 +1,124 @@ +#include +#include +#include + +// declare general statics +static Window *s_main_window; +static TextLayer *s_sleep_time_header_layer; +static TextLayer *s_sleep_time_layer; +static TextLayer *s_last_episode_header_layer; +static TextLayer *s_last_episode_layer; +static TextLayer *s_pin_notice_layer; + +static void set_sleep_start_time_text(time_t *start_time) { + static char buffer[8]; + struct tm *timeinfo = localtime(start_time); + strftime(buffer, sizeof(buffer), "%I:%M", timeinfo); + text_layer_set_text(s_sleep_time_layer, buffer); +} + +static bool cb_update_sleep_time(HealthActivity activity, time_t start_time, + time_t end_time, void *context) { + if (activity == HealthActivitySleep) { + set_sleep_start_time_text(&start_time); + return false; + } + return true; +} + +static void update_sleep_time() { + time_t end_time = time(NULL); // now + time_t start_time = end_time - (2 * (SECONDS_PER_DAY / 2)); // search up to the past 1.5 days + + health_service_activities_iterate(HealthActivitySleep, start_time, end_time, + HealthIterationDirectionPast, + cb_update_sleep_time, NULL); +} + +static void s_send_to_pkjs() { + DictionaryIterator *out; + AppMessageResult result = app_message_outbox_begin(&out); + if (result != APP_MSG_OK) { + text_layer_set_text(s_last_episode_layer, "Phone comm failure 1"); + return; + } + dict_write_cstring(out, MESSAGE_KEY_SLEEP_TIME, text_layer_get_text(s_sleep_time_layer)); + dict_write_cstring(out, MESSAGE_KEY_API_FULL_URL, "https://test.com/api/endpoint?apikey=42069"); + result = app_message_outbox_send(); + if (result != APP_MSG_OK) { + text_layer_set_text(s_last_episode_layer, "Phone comm failure 2"); + return; + } +} + +static void main_window_load(Window *window) { + // ensure sleep data is available + s_sleep_time_header_layer = text_layer_create(GRect(6, 0, PBL_DISPLAY_WIDTH, 30)); + text_layer_set_background_color(s_sleep_time_header_layer, GColorClear); + text_layer_set_font(s_sleep_time_header_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); + text_layer_set_text(s_sleep_time_header_layer, "Last Sleep"); + s_sleep_time_layer = text_layer_create(GRect(10, 24, PBL_DISPLAY_WIDTH, 30)); + text_layer_set_background_color(s_sleep_time_layer, GColorClear); + text_layer_set_font(s_sleep_time_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24)); + text_layer_set_text(s_sleep_time_layer, "N/A (rough night?)"); // default - will be updated before display + s_last_episode_header_layer = text_layer_create(GRect(6, 72, PBL_DISPLAY_WIDTH, 30)); + text_layer_set_background_color(s_last_episode_header_layer, GColorClear); + text_layer_set_font(s_last_episode_header_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); + text_layer_set_text(s_last_episode_header_layer, "Last Episode"); + s_last_episode_layer = text_layer_create(GRect(10, 96, PBL_DISPLAY_WIDTH, 30)); + text_layer_set_background_color(s_last_episode_layer, GColorClear); + text_layer_set_font(s_last_episode_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24)); + text_layer_set_text(s_last_episode_layer, "N/A"); // default - will be updated before display + s_pin_notice_layer = text_layer_create(GRect(0, PBL_DISPLAY_HEIGHT - 50, PBL_DISPLAY_WIDTH, 50)); + text_layer_set_background_color(s_pin_notice_layer, GColorClear); + text_layer_set_font(s_pin_notice_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14)); + text_layer_set_text_alignment(s_pin_notice_layer, GTextAlignmentCenter); + + // populate data text layers + time_t now = time(NULL); + if (health_service_any_activity_accessible(HealthActivitySleep, now - (2 * SECONDS_PER_DAY), now) == HealthServiceAccessibilityMaskAvailable) { + update_sleep_time(); + } + if (strcmp(text_layer_get_text(s_sleep_time_layer), "N/A (rough night?)") == 0) { // TODO change back to != 0 after debug + // get last episode using PKJS + psleep(100); // TODO Implement proper wait for PKJS to respond ready + s_send_to_pkjs(); + + if (strcmp(text_layer_get_text(s_last_episode_layer), "N/A") != 0) { + text_layer_set_text(s_pin_notice_layer, "press select\nto pin to timeline and exit"); + } + } + + // add layers as children to windows + Layer *window_layer = window_get_root_layer(window); + layer_add_child(window_layer, text_layer_get_layer(s_sleep_time_header_layer)); + layer_add_child(window_layer, text_layer_get_layer(s_sleep_time_layer)); + layer_add_child(window_layer, text_layer_get_layer(s_last_episode_header_layer)); + layer_add_child(window_layer, text_layer_get_layer(s_last_episode_layer)); + layer_add_child(window_layer, text_layer_get_layer(s_pin_notice_layer)); +} + +static void main_window_unload(Window *window) { + text_layer_destroy(s_sleep_time_header_layer); + text_layer_destroy(s_sleep_time_layer); + text_layer_destroy(s_last_episode_header_layer); + text_layer_destroy(s_last_episode_layer); + text_layer_destroy(s_pin_notice_layer); +} + +static void init() { + s_main_window = window_create(); + window_set_window_handlers( + s_main_window, + (WindowHandlers){.load = main_window_load, .unload = main_window_unload}); + app_message_open(4096, 4096); + window_stack_push(s_main_window, true); +} + +static void deinit() { window_destroy(s_main_window); } + +int main(void) { + init(); + app_event_loop(); + deinit(); +} diff --git a/src/pkjs/index.js b/src/pkjs/index.js new file mode 100644 index 0000000..c5f180a --- /dev/null +++ b/src/pkjs/index.js @@ -0,0 +1,9 @@ +Pebble.addEventListener("ready", function (e) { + Pebble.sendAppMessage({ APP_READY: true }); +}); + +Pebble.addEventListener("appmessage", function (dict) { + if (dict.payload["API_FULL_URL"]) { + console.log("Time to harass " + dict.payload["API_FULL_URL"] + " to find what you watched at " + dict.payload["SLEEP_TIME"]); + } +}); diff --git a/wscript b/wscript new file mode 100644 index 0000000..5238bc8 --- /dev/null +++ b/wscript @@ -0,0 +1,54 @@ +# +# This file is the default set of rules to compile a Pebble application. +# +# Feel free to customize this to your needs. +# +import os.path + +top = '.' +out = 'build' + + +def options(ctx): + ctx.load('pebble_sdk') + + +def configure(ctx): + """ + This method is used to configure your build. ctx.load(`pebble_sdk`) automatically configures + a build for each valid platform in `targetPlatforms`. Platform-specific configuration: add your + change after calling ctx.load('pebble_sdk') and make sure to set the correct environment first. + Universal configuration: add your change prior to calling ctx.load('pebble_sdk'). + """ + ctx.load('pebble_sdk') + + +def build(ctx): + ctx.load('pebble_sdk') + + build_worker = os.path.exists('worker_src') + binaries = [] + + cached_env = ctx.env + for platform in ctx.env.TARGET_PLATFORMS: + ctx.env = ctx.all_envs[platform] + ctx.set_group(ctx.env.PLATFORM_NAME) + app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) + ctx.pbl_build(source=ctx.path.ant_glob('src/c/**/*.c'), target=app_elf, bin_type='app') + + if build_worker: + worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) + binaries.append({'platform': platform, 'app_elf': app_elf, 'worker_elf': worker_elf}) + ctx.pbl_build(source=ctx.path.ant_glob('worker_src/c/**/*.c'), + target=worker_elf, + bin_type='worker') + else: + binaries.append({'platform': platform, 'app_elf': app_elf}) + ctx.env = cached_env + + ctx.set_group('bundle') + ctx.pbl_bundle(binaries=binaries, + js=ctx.path.ant_glob(['src/pkjs/**/*.js', + 'src/pkjs/**/*.json', + 'src/common/**/*.js']), + js_entry_file='src/pkjs/index.js')