From 17a11c894a1278241da7eac6700fe22181df24b6 Mon Sep 17 00:00:00 2001 From: collin Date: Sun, 21 Jun 2026 21:33:28 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 + README.md | 36 +++++ compile_commands.json | 58 +++++++ package.json | 34 ++++ src/c/tijd.c | 350 ++++++++++++++++++++++++++++++++++++++++++ wscript | 54 +++++++ 6 files changed, 535 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 compile_commands.json create mode 100644 package.json create mode 100644 src/c/tijd.c create mode 100644 wscript diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b12ea55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.cache/ +build/ +.lock-waf_linux_build diff --git a/README.md b/README.md new file mode 100644 index 0000000..e8c8095 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# tijd + +A Pebble watchapp/watchface written in C using the Pebble SDK. + +## Building & running + +```sh +pebble build # build for all targetPlatforms +pebble install --emulator emery # install on the emery emulator +pebble install --phone # install to a paired phone +``` + +## Target platforms + +`targetPlatforms` in `package.json` controls which watches you build for. The +modern Pebble hardware is **emery** (Pebble Time 2), **gabbro** (Pebble Round +2), and **flint** (Pebble 2 Duo); the original Pebble platforms (aplite, +basalt, chalk, diorite) are included by default for backwards compatibility. + +## Project layout + +``` +src/c/ C source for the watchapp +src/pkjs/ PebbleKit JS (phone-side) source, if any +worker_src/c/ Background worker source, if any +resources/ Images, fonts, and other bundled resources +package.json Project metadata (UUID, platforms, resources, message keys) +wscript Build rules — usually no need to edit +``` + +By default this project is configured as a watchapp. To make it a watchface, +set `pebble.watchapp.watchface` to `true` in `package.json`. + +## Documentation + +Full SDK docs, tutorials, and API reference: diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..6f81fe9 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,58 @@ +[ + { + "directory": "/home/collin/Documents/tijd", + "file": "/home/collin/Documents/tijd/src/c/tijd.c", + "arguments": [ + "/home/collin/.local/share/pebble-sdk/SDKs/current/toolchain/arm-none-eabi/bin/arm-none-eabi-gcc", + "-std=c99", + "-mcpu=cortex-m3", + "-mthumb", + "-ffunction-sections", + "-fdata-sections", + "-fcommon", + "-g", + "-fPIE", + "-Os", + "-D_TIME_H_", + "-Dtime_t=long", + "-Wall", + "-Wextra", + "-Werror", + "-Wno-unused-parameter", + "-Wno-error=unused-function", + "-Wno-error=unused-variable", + "-Wno-error=builtin-declaration-mismatch", + "-Wno-error=format-truncation", + "-Wno-error=expansion-to-defined", + "-Wno-error=zero-length-bounds", + "-Wno-error=cast-function-type", + "-Wno-error=unused-value", + "-DRELEASE", + "-DPBL_PLATFORM_EMERY", + "-DPBL_COLOR", + "-DPBL_RECT", + "-DPBL_MICROPHONE", + "-DPBL_SMARTSTRAP", + "-DPBL_HEALTH", + "-DPBL_SMARTSTRAP_POWER", + "-DPBL_COMPASS", + "-DPBL_TOUCH", + "-DPBL_RGB_BACKLIGHT", + "-DPBL_SPEAKER", + "-DPBL_DISPLAY_WIDTH=200", + "-DPBL_DISPLAY_HEIGHT=228", + "-DPBL_SDK_3", + "-I/home/collin/.local/share/pebble-sdk/SDKs/current/sdk-core/pebble/emery/include", + "-I/home/collin/Documents/tijd", + "-I/home/collin/Documents/tijd/src", + "-I/home/collin/Documents/tijd/src/c", + "-I/home/collin/Documents/tijd/build/include", + "-I/home/collin/Documents/tijd/build/emery", + "-I/home/collin/Documents/tijd/build/emery/src", + "-c", + "/home/collin/Documents/tijd/src/c/tijd.c", + "-o", + "build/emery/src/c/tijd.c.o" + ] + } +] diff --git a/package.json b/package.json new file mode 100644 index 0000000..658560a --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "tijd", + "author": "MakeAwesomeHappen", + "version": "1.0.0", + "keywords": [ + "pebble-app" + ], + "private": true, + "dependencies": {}, + "pebble": { + "displayName": "tijd", + "uuid": "09093348-4510-4240-aafb-31e3b54e5c13", + "sdkVersion": "3", + "enableMultiJS": true, + "targetPlatforms": [ + "aplite", + "basalt", + "chalk", + "diorite", + "emery", + "flint", + "gabbro" + ], + "watchapp": { + "watchface": true + }, + "messageKeys": [ + "dummy" + ], + "resources": { + "media": [] + } + } +} diff --git a/src/c/tijd.c b/src/c/tijd.c new file mode 100644 index 0000000..083994b --- /dev/null +++ b/src/c/tijd.c @@ -0,0 +1,350 @@ +#include +#include + +static Window *s_window; +static TextLayer *s_time_layer; + +static TextLayer *s_hour_layer; +static TextLayer *s_modifier_layer; +static TextLayer *s_addend_layer; +static TextLayer *s_operator_layer; +static Layer *s_operator_hand_layer; + +enum MinutesRounded { + MIN_ZERO, + MIN_FIVE, + MIN_TEN, + MIN_FIFTEEN, + MIN_TWENTY, + MIN_TWENTYFIVE, + MIN_THIRTY, + MIN_THIRTYFIVE, + MIN_FORTY, + MIN_FORTYFIVE, + MIN_FIFTY, + MIN_FIFTYFIVE, +}; + +enum Modifier { + MOD_NONE, + MOD_HOUR, + MOD_HALF, +}; + +enum Operator { + OPERATOR_NONE, + OPERATOR_TILL, + OPERATOR_PAST, +}; + +enum Addend { + ADDEND_ZERO, + ADDEND_FIVE, + ADDEND_TEN, + ADDEND_QUARTER, +}; + +typedef struct { + int hour; + enum Modifier mod; + enum Addend add; + enum Operator op; +} TimeComponents; + +static TimeComponents tc = { -1, MOD_NONE, ADDEND_ZERO, OPERATOR_NONE }; + +static bool time_components_eq(TimeComponents *tc1, TimeComponents *tc2) { + return tc1 != NULL && tc2 != NULL && tc1->hour == tc2->hour && + tc1->add == tc2->add && tc1->mod == tc2->mod && tc1->op == tc2->op; +} + +static enum MinutesRounded get_minutes_rounded(struct tm *tick_time) { + int min_unrounded = tick_time->tm_min; + int min_remainder = min_unrounded % 5; + int min_base = min_unrounded - min_remainder; + /** + * rounded to nearest 5 mins where 3,4,5 round to 5 and 0,1,2 round to 0 + */ + int min_rounded = min_base + (min_remainder >= 3 ? 5 : 0); + APP_LOG(APP_LOG_LEVEL_INFO, "Minutes rounded: %d", min_rounded); + switch (min_rounded) { + case 0: + return MIN_ZERO; + case 5: + return MIN_FIVE; + case 10: + return MIN_TEN; + case 15: + return MIN_FIFTEEN; + case 20: + return MIN_TWENTY; + case 25: + return MIN_TWENTYFIVE; + case 30: + return MIN_THIRTY; + case 35: + return MIN_THIRTYFIVE; + case 40: + return MIN_FORTY; + case 45: + return MIN_FORTYFIVE; + case 50: + return MIN_FIFTY; + case 55: + return MIN_FIFTY; + case 60: + default: + return MIN_ZERO; + } +} + +static int get_hour_component(struct tm *tick_time, enum MinutesRounded mr) { + int hour = ((tick_time->tm_hour) + (mr > 3 ? 1 : 0)) % 12; + return hour == 0 ? 12 : hour; +} + +static enum Modifier get_modifier_component(enum MinutesRounded mr) { + switch (mr) { + case MIN_ZERO: + return MOD_HOUR; + case MIN_FIVE: + return MOD_NONE; + case MIN_TEN: + return MOD_NONE; + case MIN_FIFTEEN: + return MOD_NONE; + case MIN_TWENTY: + return MOD_HALF; + case MIN_TWENTYFIVE: + return MOD_HALF; + case MIN_THIRTY: + return MOD_HALF; + case MIN_THIRTYFIVE: + return MOD_HALF; + case MIN_FORTY: + return MOD_HALF; + case MIN_FORTYFIVE: + return MOD_NONE; + case MIN_FIFTY: + return MOD_NONE; + case MIN_FIFTYFIVE: + return MOD_NONE; + default: + return MOD_NONE; + } +} + +static enum Addend get_addend_component(enum MinutesRounded mr) { + switch (mr) { + case MIN_ZERO: + case MIN_THIRTY: + return ADDEND_ZERO; + case MIN_FIVE: + case MIN_TWENTYFIVE: + case MIN_THIRTYFIVE: + case MIN_FIFTYFIVE: + return ADDEND_FIVE; + case MIN_TEN: + case MIN_TWENTY: + case MIN_FORTY: + case MIN_FIFTY: + return ADDEND_TEN; + case MIN_FIFTEEN: + case MIN_FORTYFIVE: + return ADDEND_QUARTER; + default: + return ADDEND_ZERO; + } +} + +static enum Operator get_operator_component(enum MinutesRounded mr) { + switch (mr) { + case MIN_ZERO: + return OPERATOR_NONE; + case MIN_FIVE: + return OPERATOR_PAST; + case MIN_TEN: + return OPERATOR_PAST; + case MIN_FIFTEEN: + return OPERATOR_PAST; + case MIN_TWENTY: + return OPERATOR_TILL; + case MIN_TWENTYFIVE: + return OPERATOR_TILL; + case MIN_THIRTY: + return OPERATOR_NONE; + case MIN_THIRTYFIVE: + return OPERATOR_PAST; + case MIN_FORTY: + return OPERATOR_PAST; + case MIN_FORTYFIVE: + return OPERATOR_TILL; + case MIN_FIFTY: + return OPERATOR_TILL; + case MIN_FIFTYFIVE: + return OPERATOR_TILL; + default: + return OPERATOR_NONE; + } +} + +static TimeComponents get_time_components(struct tm *tick_time) { + enum MinutesRounded mr = get_minutes_rounded(tick_time); + enum Modifier mod = get_modifier_component(mr); + int hour = get_hour_component(tick_time, mr); + enum Addend add = get_addend_component(mr); + enum Operator op = get_operator_component(mr); + + TimeComponents tc = {hour, mod, add, op}; + return tc; +} + +static char *render_hour_component(int hour) { + switch (hour % 12) { + case 0: + return "TWALF"; + case 1: + return "EEN"; + case 2: + return "TWEE"; + case 3: + return "DRIE"; + case 4: + return "VIER"; + case 5: + return "VIJF"; + case 6: + return "ZES"; + case 7: + return "ZEVEN"; + case 8: + return "ACHT"; + case 9: + return "NEGEN"; + case 10: + return "TIEN"; + case 11: + return "ELF"; + default: + return "???"; + } +} + +static char *render_modifier_component(enum Modifier mod) { + switch (mod) { + case MOD_NONE: + return ""; + case MOD_HALF: + return "HALF"; + case MOD_HOUR: + return "UUR"; + default: + return ""; + } +} + +static char *render_addend_component(enum Addend add) { + switch (add) { + case ADDEND_FIVE: + return "VIJF"; + case ADDEND_TEN: + return "TIEN"; + case ADDEND_QUARTER: + return "KWART"; + case ADDEND_ZERO: + return ""; + default: + return ""; + } +} + +static char *render_operator_component(enum Operator op) { + switch (op) { + case OPERATOR_NONE: + return ""; + case OPERATOR_PAST: + return "OVER"; + case OPERATOR_TILL: + return "VOOR"; + default: + return ""; + } +} + +static void update_time(struct tm *tick_time) { + TimeComponents new_tc = get_time_components(tick_time); + bool needs_redraw = !time_components_eq(&tc, &new_tc); + if (needs_redraw) { + tc = new_tc; + static char s_time_buffer[24]; + snprintf(s_time_buffer, sizeof(s_time_buffer), "\n%s\n%s\n%s\n%s", + render_addend_component(tc.add), render_operator_component(tc.op), + render_modifier_component(tc.mod), render_hour_component(tc.hour)); + layer_mark_dirty(s_operator_hand_layer); + text_layer_set_text(s_time_layer, s_time_buffer); + } +} + +static void tick_handler(struct tm *tick_time, TimeUnits units_changed) { + update_time(tick_time); +} + +static void operator_hand_layer_update_proc(Layer *layer, GContext *ctx) { + graphics_context_set_stroke_color(ctx, GColorArmyGreen); + graphics_context_set_stroke_width(ctx, 4); + graphics_draw_arc(ctx, layer_get_bounds(layer), GOvalScaleModeFitCircle, + DEG_TO_TRIGANGLE(0), DEG_TO_TRIGANGLE(360)); +} + +static void main_window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + window_set_background_color(window, GColorPurple); + + s_time_layer = text_layer_create(bounds); + text_layer_set_background_color(s_time_layer, GColorClear); + text_layer_set_text_color(s_time_layer, GColorWhite); + text_layer_set_font(s_time_layer, + fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD)); + text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter); + + s_hour_layer = text_layer_create(bounds); + text_layer_set_background_color(s_hour_layer, GColorClear); + text_layer_set_text_color(s_hour_layer, GColorWhite); + text_layer_set_font(s_hour_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD)); + text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter); + text_layer_set_text(s_time_layer, "TIJD"); + + layer_add_child(window_layer, text_layer_get_layer(s_time_layer)); + layer_add_child(window_layer, text_layer_get_layer(s_hour_layer)); + + GRect operator_hand_layer_bounds = GRect(bounds.origin.x + 20, bounds.origin.y + 20, bounds.size.w - 40, bounds.size.h - 40); + s_operator_hand_layer = layer_create(operator_hand_layer_bounds); + layer_set_update_proc(s_operator_hand_layer, operator_hand_layer_update_proc); + layer_add_child(window_layer, s_operator_hand_layer); +} + +static void main_window_unload(Window *window) { + text_layer_destroy(s_time_layer); +} + +static void init() { + s_window = window_create(); + window_set_background_color(s_window, GColorRed); + window_set_window_handlers( + s_window, + (WindowHandlers){.load = main_window_load, .unload = main_window_unload}); + + window_stack_push(s_window, true); + + tick_timer_service_subscribe(MINUTE_UNIT, tick_handler); +} + +static void deinit() { window_destroy(s_window); } + +int main(void) { + init(); + app_event_loop(); + deinit(); +} 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')