Initial commit

This commit is contained in:
collin 2026-06-21 21:33:28 +02:00
commit 17a11c894a
6 changed files with 535 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.cache/
build/
.lock-waf_linux_build

36
README.md Normal file
View file

@ -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 <ip> # 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: <https://developer.repebble.com>

58
compile_commands.json Normal file
View file

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

34
package.json Normal file
View file

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

350
src/c/tijd.c Normal file
View file

@ -0,0 +1,350 @@
#include <pebble.h>
#include <stdio.h>
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();
}

54
wscript Normal file
View file

@ -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')