Initial commit, adding original binaries, initial watch and companion apps which can talk

This commit is contained in:
ml
2026-06-01 15:53:11 +02:00
commit d7adb56197
63 changed files with 1787 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
# SchwertUndMagieOnPebbleFrontend
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>
@@ -0,0 +1,33 @@
{
"name": "sumop_frontend",
"author": "MakeAwesomeHappen",
"version": "1.0.0",
"keywords": ["pebble-app"],
"private": true,
"dependencies": {},
"pebble": {
"displayName": "SchwertUndMagieOnPebbleFrontend",
"uuid": "8039ba8c-f1e8-4620-838d-650fa35335b7",
"sdkVersion": "3",
"enableMultiJS": true,
"targetPlatforms": [
"aplite",
"basalt",
"chalk",
"diorite",
"emery",
"flint",
"gabbro"
],
"watchapp": {
"watchface": false
},
"messageKeys": [
"TIME",
"COMMAND"
],
"resources": {
"media": []
}
}
}
@@ -0,0 +1,99 @@
#include <pebble.h>
enum {
KEY_TIME = 0, // Inbound: current time string from companion app
KEY_COMMAND = 1 // Outbound: button name sent to companion app
};
static Window *s_main_window;
static TextLayer *s_time_layer;
static TextLayer *s_hint_layer;
static char s_time_buffer[32] = "Waiting...";
static void send_key_to_phone(const char *key_str) {
DictionaryIterator *iter;
app_message_outbox_begin(&iter);
if (iter == NULL) return;
dict_write_cstring(iter, KEY_COMMAND, key_str);
app_message_outbox_send();
}
static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "inbox_received_callback fired");
Tuple *time_tuple = dict_find(iterator, KEY_TIME);
if (time_tuple) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "KEY_TIME found, type=%d", (int)time_tuple->type);
} else {
APP_LOG(APP_LOG_LEVEL_WARNING, "KEY_TIME not found in message");
}
if (time_tuple && time_tuple->type == TUPLE_CSTRING) {
snprintf(s_time_buffer, sizeof(s_time_buffer), "%s", time_tuple->value->cstring);
APP_LOG(APP_LOG_LEVEL_DEBUG, "Setting time: %s", s_time_buffer);
text_layer_set_text(s_time_layer, s_time_buffer);
layer_mark_dirty(text_layer_get_layer(s_time_layer));
}
}
static void up_click_handler(ClickRecognizerRef recognizer, void *context) {
send_key_to_phone("UP");
}
static void down_click_handler(ClickRecognizerRef recognizer, void *context) {
send_key_to_phone("DOWN");
}
static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
send_key_to_phone("SELECT");
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
}
static void main_window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
s_time_layer = text_layer_create(GRect(0, 55, bounds.size.w, 50));
text_layer_set_text(s_time_layer, s_time_buffer);
text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_30_BLACK));
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
s_hint_layer = text_layer_create(GRect(0, 130, bounds.size.w, 30));
text_layer_set_text(s_hint_layer, "UP / DN / SEL");
text_layer_set_font(s_hint_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
text_layer_set_text_alignment(s_hint_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(s_hint_layer));
}
static void main_window_unload(Window *window) {
text_layer_destroy(s_time_layer);
text_layer_destroy(s_hint_layer);
}
static void init(void) {
s_main_window = window_create();
window_set_click_config_provider(s_main_window, click_config_provider);
window_set_window_handlers(s_main_window, (WindowHandlers) {
.load = main_window_load,
.unload = main_window_unload
});
window_stack_push(s_main_window, true);
app_message_register_inbox_received(inbox_received_callback);
app_message_open(128, 64);
}
static void deinit(void) {
window_destroy(s_main_window);
}
int main(void) {
init();
app_event_loop();
deinit();
}
@@ -0,0 +1,58 @@
var SERVER = 'http://127.0.0.1:8888';
// Numeric keys matching the C enum: KEY_TIME=0, KEY_COMMAND=1
var KEY_TIME = 0;
var KEY_COMMAND = 1;
function httpGet(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function() {
if (xhr.status !== 200) {
console.log('[SuM] HTTP ' + xhr.status + ' for ' + url);
}
};
xhr.onerror = function() {
console.log('[SuM] request failed: ' + url);
};
xhr.send();
}
function sendTimeToWatch() {
var xhr = new XMLHttpRequest();
xhr.open('GET', SERVER + '/time', true);
xhr.onload = function() {
console.log('[SuM] /time response: ' + xhr.status + ' ' + xhr.responseText);
if (xhr.status === 200) {
try {
var data = JSON.parse(xhr.responseText);
var msg = {};
msg[KEY_TIME] = data.time;
console.log('[SuM] sendAppMessage key=' + KEY_TIME + ' value=' + data.time);
Pebble.sendAppMessage(
msg,
function() { console.log('[SuM] sendAppMessage ok'); },
function(e) { console.log('[SuM] sendAppMessage failed: ' + JSON.stringify(e)); }
);
} catch (err) {
console.log('[SuM] JSON parse error: ' + err);
}
}
};
xhr.onerror = function() {
console.log('[SuM] GET /time failed - is the companion app running?');
};
xhr.send();
}
Pebble.addEventListener('ready', function() {
console.log('[SuM] PebbleKit JS ready');
setInterval(sendTimeToWatch, 1000);
});
Pebble.addEventListener('appmessage', function(e) {
var command = e.payload[KEY_COMMAND];
if (!command) { return; }
console.log('[SuM] key: ' + command);
httpGet(SERVER + '/key?cmd=' + encodeURIComponent(command));
});
+54
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')