Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,19 @@
{
"appKeys": {},
"capabilities": [
""
],
"companyName": "Pebble Technology",
"longName": "Accelerometer Peek Test",
"projectType": "native",
"resources": {
"media": []
},
"sdkVersion": "3",
"shortName": "Accelerometer",
"uuid": "5fe852be-99cd-4bd0-953d-31c767630dde",
"versionLabel": "1.0",
"watchapp": {
"watchface": false
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
#include <inttypes.h>
#define ACCEL_RAW_DATA 0
#define TIMEOUT_MS 1000
Window *window;
TextLayer *text_layer;
AppTimer* s_timer;
static AccelData s_last_accel_data;
static uint32_t prv_compute_delta_pos(AccelData *cur_pos, AccelData *last_pos) {
return (abs(last_pos->x - cur_pos->x) + abs(last_pos->y - cur_pos->y) +
abs(last_pos->z - cur_pos->z));
}
static void prv_timer_cb(void *data) {
s_timer = app_timer_register(TIMEOUT_MS, prv_timer_cb, NULL);
AccelData accel_data;
int error;
if ((error = accel_service_peek(&accel_data)) != 0) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Accelerometer error %i", error);
return;
}
static char accel_text[20];
#if !ACCEL_RAW_DATA
int32_t delta = prv_compute_delta_pos(&accel_data, &s_last_accel_data);
s_last_accel_data = accel_data;
snprintf(accel_text, sizeof(accel_text), "Accel delta: %"PRIu32, delta);
APP_LOG(APP_LOG_LEVEL_INFO, accel_text);
#else
snprintf(accel_text, sizeof(accel_text), "x:%"PRId16 ", y:%"PRId16 ", z:%"PRId16,
accel_data.x, accel_data.y, accel_data.z);
APP_LOG(APP_LOG_LEVEL_INFO, accel_text);
#endif
text_layer_set_text(text_layer, accel_text);
}
void handle_init(void) {
// Create a window and text layer
window = window_create();
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
uint32_t text_width = bounds.size.w;
uint32_t text_height = 28;
text_layer = text_layer_create(GRect(0, bounds.size.h/2 - text_height/2,
text_width, text_height));
// Set the text, font, and text alignment
text_layer_set_text(text_layer, "No Accelerometer");
text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
// Add the text layer to the window
layer_add_child(window_get_root_layer(window), text_layer_get_layer(text_layer));
// Push the window
window_stack_push(window, true);
// App Logging!
APP_LOG(APP_LOG_LEVEL_DEBUG, "Just pushed a window!");
// Subscribe Accelerometer and Register Timer
accel_data_service_subscribe(0, NULL);
s_timer = app_timer_register(TIMEOUT_MS, prv_timer_cb, NULL);
}
void handle_deinit(void) {
// Destroy Timer and Unsubscribe Accelerometer
app_timer_cancel(s_timer);
accel_data_service_unsubscribe();
// Destroy the text layer
text_layer_destroy(text_layer);
// Destroy the window
window_destroy(window);
}
int main(void) {
handle_init();
app_event_loop();
handle_deinit();
}

View file

@ -0,0 +1,61 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
try:
from sh import CommandNotFound, jshint, cat, ErrorReturnCode_2
hint = jshint
except (ImportError, CommandNotFound):
hint = None
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
if False and hint is not None:
try:
hint([node.abspath() for node in ctx.path.ant_glob("src/**/*.js")], _tty_out=False) # no tty because there are none in the cloudpebble sandbox.
except ErrorReturnCode_2 as e:
ctx.fatal("\nJavaScript linting failed (you can disable this in Project Settings):\n" + e.stdout)
# Concatenate all our JS files (but not recursively), and only if any JS exists in the first place.
ctx.path.make_node('src/js/').mkdir()
js_paths = ctx.path.ant_glob(['src/*.js', 'src/**/*.js'])
if js_paths:
ctx(rule='cat ${SRC} > ${TGT}', source=js_paths, target='pebble-js-app.js')
has_js = True
else:
has_js = False
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(p)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(p)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js='pebble-js-app.js' if has_js else [])

View file

@ -0,0 +1,17 @@
{
"uuid": "5900750a-b7e4-439c-890d-a7b2d2d29fc2",
"shortName": "AppHeap Demo",
"longName": "AppHeap Demo",
"companyName": "Pebble Technology",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
#define ALLOC_SIZE 2048
static Window *window;
static TextLayer *text_heap_info;
static unsigned s_alloc_total = 0;
static char s_text_buf[80];
static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
char *buf = malloc(ALLOC_SIZE);
if (buf == NULL) {
snprintf(s_text_buf, 80, "Heap full at %dB", s_alloc_total);
text_layer_set_text(text_heap_info, s_text_buf);
return;
}
s_alloc_total += ALLOC_SIZE;
snprintf(s_text_buf, 80, "%dB allocated", s_alloc_total);
text_layer_set_text(text_heap_info, s_text_buf);
}
static void config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
}
static void init() {
window = window_create();
window_set_click_config_provider(window, config_provider);
window_stack_push(window, true /* Animated */);
Layer *window_layer = window_get_root_layer(window);
text_heap_info = text_layer_create(layer_get_frame(window_layer));
text_layer_set_text_color(text_heap_info, GColorWhite);
text_layer_set_background_color(text_heap_info, GColorBlack);
text_layer_set_font(text_heap_info, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
snprintf(s_text_buf, 80, "Press [SELECT] to allocate %dB", ALLOC_SIZE);
text_layer_set_text(text_heap_info, s_text_buf);
layer_add_child(window_layer, text_layer_get_layer(text_heap_info));
}
static void deinit(void) {
// Don't free anything
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,25 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target='pebble-app.elf')
ctx.pbl_bundle(elf='pebble-app.elf',
js=ctx.path.ant_glob('js/**/*.js'))

View file

@ -0,0 +1,23 @@
{
"uuid": "2a3352d5-2d44-437f-9652-98464640aeab",
"shortName": "AppMsgTest",
"longName": "AppMsgTest",
"companyName": "Pebble",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"test0": 0
},
"resources": {
"media": []
},
"targetPlatforms": [
"aplite",
"basalt",
"chalk"
],
"sdkVersion": "3"
}

View file

@ -0,0 +1,131 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
static Window *s_window;
static TextLayer *s_text_layer;
enum {
DICT_KEY_TEST_0 = 0x0,
};
static int send_app_msg(void) {
Tuplet value = TupletInteger(DICT_KEY_TEST_0, 1);
DictionaryIterator *iter;
app_message_outbox_begin(&iter);
if (iter == NULL) {
return -1;
}
dict_write_tuplet(iter, &value);
dict_write_end(iter);
int result = app_message_outbox_send();
return result;
}
static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
int result;
APP_LOG(APP_LOG_LEVEL_DEBUG, "Sending messages");
for (int i=0; i<10; i++) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "app sending outbox");
do {
result = send_app_msg();
} while (result == -1);
APP_LOG(APP_LOG_LEVEL_DEBUG, "outbox result code: %d", result);
}
}
static void up_click_handler(ClickRecognizerRef recognizer, void *context) {
text_layer_set_text(s_text_layer, "Up");
for (int i=0; i<10; i++) {
APP_LOG(APP_LOG_LEVEL_INFO, "sending BT log message");
}
}
static void down_click_handler(ClickRecognizerRef recognizer, void *context) {
text_layer_set_text(s_text_layer, "Down");
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
}
static void in_received_handler(DictionaryIterator *iter, void *context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Received message");
}
static void in_dropped_handler(AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "App Message Dropped!");
}
static void out_failed_handler(DictionaryIterator *failed, AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "App Message Failed to Send!");
}
static void app_message_init(void) {
// Register message handlers
app_message_register_inbox_received(in_received_handler);
app_message_register_inbox_dropped(in_dropped_handler);
app_message_register_outbox_failed(out_failed_handler);
// Init buffers
app_message_open(64, 64);
}
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
s_text_layer = text_layer_create((GRect) { .origin = { 0, 72 }, .size = { bounds.size.w, 20 } });
text_layer_set_text(s_text_layer, "Press a button");
text_layer_set_text_alignment(s_text_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(s_text_layer));
}
static void window_unload(Window *window) {
}
static void init(void) {
s_window = window_create();
app_message_init();
window_set_click_config_provider(s_window, click_config_provider);
window_set_window_handlers(s_window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
const bool animated = true;
window_stack_push(s_window, animated);
}
static void deinit(void) {
window_destroy(s_window);
text_layer_destroy(s_text_layer);
}
int main(void) {
init();
APP_LOG(APP_LOG_LEVEL_DEBUG, "Done initializing, pushed window: %p", s_window);
app_event_loop();
deinit();
}

View file

@ -0,0 +1,30 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Set callback for the app ready event
Pebble.addEventListener("ready",
function(e) {
console.log("connected!" + e.ready);
console.log(e.type);
});
// Set callback for appmessage events
Pebble.addEventListener("appmessage",
function(e) {
console.log("sending reply");
Pebble.sendAppMessage({"test0": "42"});
});

View file

@ -0,0 +1,48 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# 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 = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), target=app_elf)
if build_worker:
worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob(['src/js/**/*.js', 'src/js/**/*.json']), js_entry_file='src/js/app.js')

View file

@ -0,0 +1,23 @@
{
"uuid": "02b1f111-ef11-4e89-af3d-25f4784214fd",
"shortName": "BG App Demo",
"longName": "BG App Demo",
"companyName": "Pebble",
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["basalt"],
"watchapp": {
"watchface": false
},
"appKeys": {},
"resources": {
"media": [
{
"characterRegex": "[:0-9]",
"type": "font",
"name": "FONT_ROBOTO_BOLD_SUBSET_49",
"file": "fonts/Roboto-Bold.ttf"
}
]
}
}

View file

@ -0,0 +1,193 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pebble.h"
#include <inttypes.h>
Window *window;
TextLayer *text_layer;
Layer *line_layer;
static char g_text[100];
// Return current time in ms
static uint64_t prv_ms(void) {
time_t cur_sec;
uint16_t cur_ms = time_ms(&cur_sec, NULL);
return ((uint64_t)cur_sec * 1000) + cur_ms;
}
static void steps_event_handler(uint16_t type, AppWorkerMessage *data) {
//APP_LOG(APP_LOG_LEVEL_DEBUG, "Received new worker event. type: %d, data: %d, %d, %d", (int)type, (int)data->data0,
// (int)data->data1, (int)data->data2);
if (type == 0) {
snprintf(g_text, sizeof(g_text), "%5d %5d %5d", (int)data->data0, (int)data->data1, (int)data->data2);
text_layer_set_text(text_layer, g_text);
} else if (type == 1) {
snprintf(g_text, sizeof(g_text), "BAT: %d, %d, %d", (int)data->data0, (int)data->data1, (int)data->data2);
text_layer_set_text(text_layer, g_text);
}
}
static void up_click_handler(ClickRecognizerRef recognizer, void *context) {
AppWorkerResult result = app_worker_launch();
APP_LOG(APP_LOG_LEVEL_INFO, "launch result: %d", result);
}
static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
AppWorkerMessage m;
app_worker_send_message('x', &m);
APP_LOG(APP_LOG_LEVEL_INFO, "crashing worker");
}
static void down_click_handler(ClickRecognizerRef recognizer, void *context) {
AppWorkerResult result = app_worker_kill();
APP_LOG(APP_LOG_LEVEL_INFO, "kill result: %d", result);
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
}
static uint32_t s_seconds_count;
void handle_second_tick(struct tm *tick_time, TimeUnits units_changed) {
bool running = app_worker_is_running();
if (false) {
const char* status = "not";
if (running) {
status = "is";
}
APP_LOG(APP_LOG_LEVEL_INFO, "worker %s running", status);
}
s_seconds_count++;
if ((s_seconds_count % 5) == 0) {
int value = persist_read_int(42);
// APP_LOG(APP_LOG_LEVEL_INFO, "Updating persist value from %d to %d", value, value + 1);
persist_write_int(42, value + 1);
}
}
static void health_event_handler(HealthEventType event, void *context) {
APP_LOG(APP_LOG_LEVEL_INFO, "app: Got health event update. event_id: %"PRIu32"",
(uint32_t) event);
if (event == HealthEventMovementUpdate) {
HealthValue steps = health_service_sum_today(HealthMetricStepCount);
APP_LOG(APP_LOG_LEVEL_INFO, "app: movement event, steps: %"PRIu32"",
(uint32_t)steps);
// Test getting historical steps
time_t day_start = time_start_of_today();
for (int i = 0; i < 7; i++) {
steps = health_service_sum(HealthMetricStepCount, day_start, day_start + SECONDS_PER_DAY);
APP_LOG(APP_LOG_LEVEL_INFO, "%d days ago steps: %d", i, (int)steps);
day_start -= SECONDS_PER_DAY;
}
// Test getting steps for part of a day
day_start = time_start_of_today();
time_t seconds_today_so_far = time(NULL) - day_start;
steps = health_service_sum(HealthMetricStepCount, day_start,
day_start + (seconds_today_so_far / 2));
APP_LOG(APP_LOG_LEVEL_INFO, "steps 1st half of today: %d", (int)steps);
steps = health_service_sum(HealthMetricStepCount, day_start - (SECONDS_PER_DAY / 2), day_start);
APP_LOG(APP_LOG_LEVEL_INFO, "steps 2nd half of yesterday: %d", (int)steps);
// Test the get_minute_history call
const int minute_data_len = 10;
HealthMinuteData minute_data[minute_data_len];
uint32_t num_records = minute_data_len;
time_t utc_start = time(NULL) - 60 * 60 * 24; // All records since 1 day ago
time_t utc_end = time(NULL);
uint64_t start_ms = prv_ms();
health_service_get_minute_history(minute_data, num_records, &utc_start, &utc_end);
uint64_t elapsed_ms = prv_ms() - start_ms;
int num_records_returned = (utc_end - utc_start) / SECONDS_PER_MINUTE;
APP_LOG(APP_LOG_LEVEL_INFO, "app: Retrieved %d minute records in %"PRIu32" ms:",
num_records_returned, (uint32_t)elapsed_ms);
for (int i = 0; i < num_records_returned; i++) {
APP_LOG(APP_LOG_LEVEL_INFO, " steps: %"PRIu8", orient: 0x%"PRIx8", vmc: %"PRIu16", "
"light: %d, valid: %d", minute_data[i].steps, minute_data[i].orientation,
minute_data[i].vmc, (int)minute_data[i].light, (int)(!minute_data[i].is_invalid));
}
} else if (event == HealthEventSleepUpdate) {
HealthValue total_sleep = health_service_sum_today(HealthMetricSleepSeconds);
HealthValue restful_sleep = health_service_sum_today(HealthMetricSleepRestfulSeconds);
APP_LOG(APP_LOG_LEVEL_INFO, "app: New sleep event: total: %"PRIu32", restful: %"PRIu32" ",
total_sleep / SECONDS_PER_MINUTE, restful_sleep / SECONDS_PER_MINUTE);
}
}
void handle_deinit(void) {
tick_timer_service_unsubscribe();
health_service_events_unsubscribe();
}
void handle_init(void) {
window = window_create();
window_set_click_config_provider(window, click_config_provider);
window_stack_push(window, true /* Animated */);
window_set_background_color(window, GColorBlack);
Layer *window_layer = window_get_root_layer(window);
text_layer = text_layer_create(GRect(7, 40, 144-7, 168-40));
text_layer_set_text_color(text_layer, GColorWhite);
text_layer_set_background_color(text_layer, GColorClear);
text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24));
layer_add_child(window_layer, text_layer_get_layer(text_layer));
text_layer_set_text(text_layer, "? ? ?");
// Subscribe to mesages published by the worker
app_worker_message_subscribe(steps_event_handler);
// Subscribe to second ticks
tick_timer_service_subscribe(SECOND_UNIT, handle_second_tick);
// Launch the worker
AppWorkerResult result = app_worker_launch();
APP_LOG(APP_LOG_LEVEL_INFO, "launch result: %d", result);
// Subscribe to health service
health_service_events_subscribe(health_event_handler, NULL);
}
int main(void) {
handle_init();
app_event_loop();
handle_deinit();
}

View file

@ -0,0 +1,144 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble_worker.h>
#include <inttypes.h>
#define BREAKPOINT __asm("bkpt")
#define ACCEL_BATCH_SIZE 10
#define PERSIST_WRITE_PERIOD_MS 1000
// -------------------------------------------------------------------------------------------------
static void prv_assert(bool condition, const char* msg) {
if (!condition) {
APP_LOG(APP_LOG_LEVEL_ERROR, msg);
// Force an exception
typedef void (*FuncPtr)(void);
FuncPtr bad_func = NULL;
bad_func();
}
}
// -----------------------------------------------------------------------------------------------
void handle_accel(AccelRawData *accel_data, uint32_t num_samples, uint64_t timestamp) {
// Display data
//for (uint32_t i=0; i<num_samples; i++) {
// APP_LOG(APP_LOG_LEVEL_INFO, "Got accel data: %d, %d, %d", accel_data[i].x, accel_data[i].y, accel_data[i].z);
//}
// Publish new steps count
AppWorkerMessage steps_data = {
.data0 = accel_data[0].x,
.data1 = accel_data[0].y,
.data2 = accel_data[0].z,
};
app_worker_send_message(0 /*type*/, &steps_data);
}
// -----------------------------------------------------------------------------------------------
static void update_persist_callback(void* context) {
int value = persist_read_int(42);
// APP_LOG(APP_LOG_LEVEL_INFO, "Updating persist value from %d to %d", value, value + 1);
persist_write_int(42, value + 1);
app_timer_register(PERSIST_WRITE_PERIOD_MS /*ms*/, update_persist_callback, NULL);
}
// -----------------------------------------------------------------------------------------------
static void battery_state_handler(BatteryChargeState charge) {
APP_LOG(APP_LOG_LEVEL_INFO, "got battery state service update");
APP_LOG(APP_LOG_LEVEL_INFO, "percent: %d, is_charging: %d, is_plugged: %d", charge.charge_percent,
charge.is_charging, charge.is_plugged);
AppWorkerMessage battery_data = {
.data0 = charge.charge_percent,
.data1 = charge.is_charging,
.data2 = charge.is_plugged,
};
app_worker_send_message(1 /*type*/, &battery_data);
}
// -----------------------------------------------------------------------------------------------
static void connection_handler(bool connected) {
APP_LOG(APP_LOG_LEVEL_INFO, "got phone connection update");
APP_LOG(APP_LOG_LEVEL_INFO, "connected: %d", connected);
}
// -----------------------------------------------------------------------------------------------
static void tick_timer_handler(struct tm *tick_time, TimeUnits units_changed) {
APP_LOG(APP_LOG_LEVEL_INFO, "got tick timer update");
}
// -----------------------------------------------------------------------------------------------
static void worker_message_handler(uint16_t type, AppWorkerMessage *data) {
if (type == 'x') {
prv_assert(0, "crashing");
}
}
// -----------------------------------------------------------------------------------------------
static void health_event_handler(HealthEventType event, void *context) {
APP_LOG(APP_LOG_LEVEL_INFO, "worker: Got health event update. event_id: %"PRIu32"",
(uint32_t) event);
if (event == HealthEventMovementUpdate) {
HealthValue steps = health_service_sum_today(HealthMetricStepCount);
APP_LOG(APP_LOG_LEVEL_INFO, "worker: movement event, steps: %"PRIu32"",
(uint32_t)steps);
} else if (event == HealthEventSleepUpdate) {
HealthValue total_sleep = health_service_sum_today(HealthMetricSleepSeconds);
HealthValue restful_sleep = health_service_sum_today(HealthMetricSleepRestfulSeconds);
APP_LOG(APP_LOG_LEVEL_INFO, "worker: New sleep event: total: %"PRIu32", restful: %"PRIu32" ",
total_sleep / SECONDS_PER_MINUTE, restful_sleep / SECONDS_PER_MINUTE);
}
}
// -----------------------------------------------------------------------------------------------
int main(void) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "initializing...");
accel_raw_data_service_subscribe(ACCEL_BATCH_SIZE, handle_accel);
accel_service_set_sampling_rate(ACCEL_SAMPLING_10HZ);
app_timer_register(PERSIST_WRITE_PERIOD_MS /*ms*/, update_persist_callback, NULL);
battery_state_service_subscribe(battery_state_handler);
ConnectionHandlers conn_handlers = {
.pebble_app_connection_handler = connection_handler
};
connection_service_subscribe(conn_handlers);
tick_timer_service_subscribe(MINUTE_UNIT, tick_timer_handler);
app_worker_message_subscribe(worker_message_handler);
// Subscribe to health service
// health_service_events_subscribe(health_event_handler, NULL);
worker_event_loop();
accel_data_service_unsubscribe();
health_service_events_unsubscribe();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,15 @@
{
"uuid": "645314e3-acfa-42d1-a1dc-c0deb89af104",
"shortName": "BLE Demo",
"longName": "BLE Demo",
"companyName": "Pebble Technology",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,187 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_demo_scan.h"
static Window *s_scan_window;
static void descriptor_write_handler(BLEDescriptor descriptor,
BLEGATTError error) {
char uuid_buffer[UUID_STRING_BUFFER_LENGTH];
Uuid descriptor_uuid = ble_descriptor_get_uuid(descriptor);
uuid_to_string(&descriptor_uuid, uuid_buffer);
APP_LOG(APP_LOG_LEVEL_INFO, "Write response for Descriptor %s (error=%u)", uuid_buffer, error);
}
static void descriptor_read_handler(BLEDescriptor descriptor,
const uint8_t *value,
size_t value_length,
uint16_t value_offset,
BLEGATTError error) {
char uuid_buffer[UUID_STRING_BUFFER_LENGTH];
Uuid descriptor_uuid = ble_descriptor_get_uuid(descriptor);
uuid_to_string(&descriptor_uuid, uuid_buffer);
APP_LOG(APP_LOG_LEVEL_INFO, "Read Descriptor %s, %u bytes, error: %u",
uuid_buffer, value_length, error);
for (size_t i = 0; i < value_length; ++i) {
APP_LOG(APP_LOG_LEVEL_INFO, "0x%02x", value[i]);
}
}
static void read_handler(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length,
uint16_t value_offset,
BLEGATTError error) {
char uuid_buffer[UUID_STRING_BUFFER_LENGTH];
Uuid characteristic_uuid = ble_characteristic_get_uuid(characteristic);
uuid_to_string(&characteristic_uuid, uuid_buffer);
APP_LOG(APP_LOG_LEVEL_INFO, "Read Characteristic %s, %u bytes, error: %u",
uuid_buffer, value_length, error);
for (size_t i = 0; i < value_length; ++i) {
APP_LOG(APP_LOG_LEVEL_INFO, "0x%02x", value[i]);
}
}
static void write_handler(BLECharacteristic characteristic,
BLEGATTError error) {
char uuid_buffer[UUID_STRING_BUFFER_LENGTH];
Uuid characteristic_uuid = ble_characteristic_get_uuid(characteristic);
uuid_to_string(&characteristic_uuid, uuid_buffer);
APP_LOG(APP_LOG_LEVEL_INFO, "Write response for Characteristic %s (error=%u)",
uuid_buffer, error);
}
static void subscribe_handler(BLECharacteristic characteristic,
BLESubscription subscription_type,
BLEGATTError error) {
char uuid_buffer[UUID_STRING_BUFFER_LENGTH];
Uuid characteristic_uuid = ble_characteristic_get_uuid(characteristic);
uuid_to_string(&characteristic_uuid, uuid_buffer);
APP_LOG(APP_LOG_LEVEL_INFO, "Subscription to Characteristic %s (subscription_type=%u, error=%u)",
uuid_buffer, subscription_type, error);
}
static void service_change_handler(BTDevice device,
const BLEService services[],
uint8_t num_services,
BTErrno status) {
const BTDeviceAddress address = bt_device_get_address(device);
char uuid_buffer[UUID_STRING_BUFFER_LENGTH];
for (unsigned int i = 0; i < num_services; ++i) {
Uuid service_uuid = ble_service_get_uuid(services[i]);
uuid_to_string(&service_uuid, uuid_buffer);
APP_LOG(APP_LOG_LEVEL_INFO,
"Discovered service %s (0x%08x) on " BT_DEVICE_ADDRESS_FMT,
uuid_buffer,
services[i],
BT_DEVICE_ADDRESS_XPLODE(address));
BLECharacteristic characteristics[8];
uint8_t num_characteristics =
ble_service_get_characteristics(services[i], characteristics, 8);
if (num_characteristics > 8) {
num_characteristics = 8;
}
for (unsigned int c = 0; c < num_characteristics; ++c) {
Uuid characteristic_uuid = ble_characteristic_get_uuid(characteristics[c]);
uuid_to_string(&characteristic_uuid, uuid_buffer);
APP_LOG(APP_LOG_LEVEL_INFO, "-- Characteristic: %s (0x%08x)",
uuid_buffer, characteristics[c]);
Uuid device_name_characteristic = bt_uuid_expand_16bit(0x2A00);
if (uuid_equal(&device_name_characteristic, &characteristic_uuid)) {
BTErrno err = ble_client_read(characteristics[c]);
APP_LOG(APP_LOG_LEVEL_INFO, "Reading... %u", err);
}
// If the characteristic is the Alert Control Point, try to write something to it
const Uuid alert_control_point = bt_uuid_expand_16bit(0x2A44);
if (uuid_equal(&alert_control_point, &characteristic_uuid)) {
const char value[] = "Hello World.";
ble_client_write(characteristics[c], (const uint8_t *) value, strlen(value) + 1);
}
const Uuid hrm_uuid = bt_uuid_expand_16bit(0x2A37);
if (uuid_equal(&hrm_uuid, &characteristic_uuid)) {
ble_client_subscribe(characteristics[c], BLESubscriptionNotifications);
}
// BLEDescriptor descriptors[8];
// uint8_t num_descriptors =
// ble_characteristic_get_descriptors(characteristics[c], descriptors, 8);
// for (unsigned int d = 0; d < num_descriptors; ++d) {
// const Uuid descriptor_uuid = ble_descriptor_get_uuid(descriptors[d]);
// uuid_to_string(&descriptor_uuid, uuid_buffer);
// APP_LOG(APP_LOG_LEVEL_INFO, "---- Descriptor: %s (0x%08x)", uuid_buffer, descriptors[d]);
//
// // If the characteristic is the Heart Rate Measurement,
// // attempt to subscribe to it by writing to the CCCD:
// const Uuid hrm_uuid = bt_uuid_expand_16bit(0x2A37);
// const Uuid cccd_uuid = bt_uuid_expand_16bit(0x2902);
// if (uuid_equal(&hrm_uuid, &characteristic_uuid) &&
// uuid_equal(&cccd_uuid, &descriptor_uuid)) {
// APP_LOG(APP_LOG_LEVEL_INFO, "---- Subscribing to Heart Rate Measurement notifications");
// const uint16_t enable_notifications = 1;
// ble_client_write_descriptor(descriptors[d],
// (const uint8_t *) &enable_notifications,
// sizeof(enable_notifications));
// }
//
// ble_client_read_descriptor(descriptors[d]);
// }
}
}
}
static void connection_handler(BTDevice device, BTErrno connection_status) {
const BTDeviceAddress address = bt_device_get_address(device);
const bool connected = (connection_status == BTErrnoConnected);
APP_LOG(APP_LOG_LEVEL_INFO, "%s " BT_DEVICE_ADDRESS_FMT " (status=%d)",
connected ? "Connected" : "Disconnected",
BT_DEVICE_ADDRESS_XPLODE(address), connection_status);
ble_client_discover_services_and_characteristics(device);
}
int main(void) {
ble_client_set_descriptor_write_handler(descriptor_write_handler);
ble_client_set_descriptor_read_handler(descriptor_read_handler);
ble_client_set_read_handler(read_handler);
ble_client_set_write_response_handler(write_handler);
ble_client_set_subscribe_handler(subscribe_handler);
ble_central_set_connection_handler(connection_handler);
ble_client_set_service_change_handler(service_change_handler);
s_scan_window = ble_demo_scan_window_create();
window_stack_push(s_scan_window, true /* Animated */);
app_event_loop();
window_destroy(s_scan_window);
}

View file

@ -0,0 +1,396 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_demo_scan.h"
static MenuLayer *s_menu_layer;
struct ScanResult;
typedef struct ScanResult {
struct ScanResult *next;
BTDevice device;
int8_t rssi;
int8_t tx_power_level;
char local_name[32];
bool has_services;
bool has_heart_rate_service;
Uuid first_service_uuid;
} ScanResult;
static bool s_is_scanning;
//------------------------------------------------------------------------------
// ScanResult List management
static ScanResult *s_head;
//! Gets the number of ScanResults in the list:
static uint8_t list_get_count(void) {
uint8_t count = 0;
ScanResult *result = s_head;
while (result) {
++count;
result = result->next;
}
return count;
}
static void list_free_last(void) {
ScanResult *prev = NULL;
ScanResult *result = s_head;
while (result) {
if (!result->next) {
// Found the last result, unlink and free it:
prev->next = NULL;
free(result);
return;
}
prev = result;
result = result->next;
}
}
//! Finds ScanResult based on BTDevice. If found, unlink and return ScanResult.
static ScanResult *list_unlink(const BTDevice *device) {
ScanResult *prev = NULL;
ScanResult *result = s_head;
while (result) {
if (bt_device_equal(&result->device, device)) {
// Match!
if (prev) {
// Unlink from previous node:
prev->next = result->next;
} else {
// Unlink from head:
s_head = result->next;
}
// Return found result:
return result;
}
// Iterate:
prev = result;
result = result->next;
}
// Not found:
return NULL;
}
//! Inserts the result into the list, keeping the list sorted by RSSI (strongest
//! first).
static void list_link_sorted_by_rssi(ScanResult *result) {
ScanResult *prev = NULL;
ScanResult *other = s_head;
while (other) {
if (other->rssi < result->rssi) {
if (!prev) {
// Need to insert as the head, this is handled at the end.
break;
}
// Insert before "other":
prev->next = result;
result->next = other;
return;
}
// Iterate:
prev = other;
other = other->next;
}
// Broke out of the loop, this can happen at the head or the tail, handle it:
if (s_head == other) {
// Insert as head of the list:
result->next = s_head;
s_head = result;
} else {
// Insert as tail:
prev->next = result;
result->next = NULL;
}
}
static ScanResult *list_get_by_index(uint8_t index) {
ScanResult *result = s_head;
while (index && result) {
result = result->next;
--index;
}
return result;
}
static void list_free_all(void) {
ScanResult *result = s_head;
while (result) {
ScanResult *next = result->next;
free(result);
result = next;
}
s_head = NULL;
}
//------------------------------------------------------------------------------
// BLE Scan API callback
static void ble_scan_handler(BTDevice device,
int8_t rssi,
const BLEAdData *ad_data) {
const BTDeviceAddress address = bt_device_get_address(device);
APP_LOG(APP_LOG_LEVEL_INFO, "Got Advertisement from: " BT_DEVICE_ADDRESS_FMT,
BT_DEVICE_ADDRESS_XPLODE(address));
// Find existing ScanResult with BTDevice:
ScanResult *result = list_unlink(&device);
// If no existing result, create one:
if (!result) {
// Bound the number of items:
if (list_get_count() >= 10) {
list_free_last();
}
// Create new ScanResult:
result = (ScanResult *) malloc(sizeof(ScanResult));
if (!result) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Out of memory!");
return;
}
// Zero out:
memset(result, 0, sizeof(ScanResult));
}
// Update all the fields into the result:
result->device = device;
result->rssi = rssi;
// Try getting TX Power Level:
int8_t tx_power_level;
if (ble_ad_get_tx_power_level(ad_data, &tx_power_level)) {
APP_LOG(APP_LOG_LEVEL_INFO, "TX Power: %d", tx_power_level);
result->tx_power_level = tx_power_level;
}
// Try getting Local Name:
if (ble_ad_copy_local_name(ad_data, result->local_name,
sizeof(result->local_name))) {
APP_LOG(APP_LOG_LEVEL_INFO, "Local Name: %s", result->local_name);
} else {
// Clear out the local name field:
result->local_name[0] = 0;
}
// Try to copy the first Service UUID, we'll display this in the list:
const uint8_t num_services = ble_ad_copy_service_uuids(ad_data,
&result->first_service_uuid, 1);
if (num_services) {
result->has_services = true;
// Look for Heart Rate Monitor service:
// See https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
Uuid hrm_uuid = bt_uuid_expand_16bit(0x180D);
result->has_heart_rate_service = ble_ad_includes_service(ad_data, &hrm_uuid);
} else {
result->has_services = false;
result->has_heart_rate_service = false;
}
// Insert into the list:
list_link_sorted_by_rssi(result);
// Tell the menu to update:
menu_layer_reload_data(s_menu_layer);
}
void toggle_scan(void) {
if (s_is_scanning) {
ble_scan_stop();
s_is_scanning = false;
} else {
ble_scan_start(ble_scan_handler);
s_is_scanning = true;
}
menu_layer_reload_data(s_menu_layer);
}
//------------------------------------------------------------------------------
// MenuLayer callbacks:
enum {
SectionControl = 0,
SectionData,
};
static uint16_t menu_get_num_sections_callback(struct MenuLayer *menu_layer,
void *callback_context) {
return 2;
}
static uint16_t menu_get_num_rows_callback(MenuLayer *menu_layer,
uint16_t section_index, void *data) {
switch (section_index) {
case SectionControl:
return 1;
case SectionData:
return list_get_count();
default:
return 0;
}
}
static int16_t menu_get_header_height_callback(MenuLayer *menu_layer,
uint16_t section_index,
void *data) {
return MENU_CELL_BASIC_HEADER_HEIGHT;
}
static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer,
uint16_t section_index, void *data) {
menu_cell_basic_header_draw(ctx, cell_layer,
(section_index == SectionData) ? "Results" : "Options");
}
static void draw_data_row(GContext* ctx, const Layer *cell_layer,
MenuIndex *cell_index, void *data) {
ScanResult *result = list_get_by_index(cell_index->row);
// Build the title string:
char title[32];
// Annotate with "HRM" if the device has a heart rate service:
char *hrm_str = result->has_heart_rate_service ? "HRM" : "";
// If there is a local name, show it, otherwise use the device address:
if (strlen(result->local_name)) {
snprintf(title, sizeof(title), "%s %s", result->local_name, hrm_str);
} else {
const BTDeviceAddress address = bt_device_get_address(result->device);
snprintf(title, sizeof(title), BT_DEVICE_ADDRESS_FMT " %s",
BT_DEVICE_ADDRESS_XPLODE(address), hrm_str);
}
// Build the subtitle string:
char subtitle[UUID_STRING_BUFFER_LENGTH];
if (result->has_services) {
// Make a displayable string of the first Service UUID:
uuid_to_string(&result->first_service_uuid, subtitle);
} else {
// If advertisement did not contain Service UUIDs:
strncpy(subtitle, "No Service UUIDs", sizeof(subtitle));
}
menu_cell_basic_draw(ctx, cell_layer, title, subtitle, NULL);
}
static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer,
MenuIndex *cell_index, void *data) {
switch (cell_index->section) {
case SectionControl:
menu_cell_basic_draw(ctx, cell_layer,
s_is_scanning ? "Disable Scan" : "Enable Scan",
NULL, NULL);
break;
case SectionData:
draw_data_row(ctx, cell_layer, cell_index, data);
break;
default:
break;
}
}
static void menu_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index,
void *data) {
if (cell_index->section == SectionControl) {
toggle_scan();
return;
}
// Connect
ScanResult *result = list_get_by_index(cell_index->row);
BTErrno e = ble_central_connect(result->device,
true /* auto_reconnect */,
false /* is_pairing_required */);
if (e) {
APP_LOG(APP_LOG_LEVEL_INFO, "ble_central_connect: %d", e);
}
}
static void menu_select_long_callback(MenuLayer *menu_layer, MenuIndex *cell_index,
void *data) {
if (cell_index->section == SectionControl) {
return;
}
// Disconnect
ScanResult *result = list_get_by_index(cell_index->row);
BTErrno e = ble_central_cancel_connect(result->device);
if (e) {
APP_LOG(APP_LOG_LEVEL_INFO, "ble_central_cancel_connect: %d", e);
}
}
//------------------------------------------------------------------------------
// Window callbacks:
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_frame(window_layer);
s_menu_layer = menu_layer_create(bounds);
window_set_user_data(window, s_menu_layer);
menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks){
.get_num_sections = menu_get_num_sections_callback,
.get_num_rows = menu_get_num_rows_callback,
.get_header_height = menu_get_header_height_callback,
.draw_header = menu_draw_header_callback,
.draw_row = menu_draw_row_callback,
.select_click = menu_select_callback,
.select_long_click = menu_select_long_callback,
});
menu_layer_set_click_config_onto_window(s_menu_layer, window);
layer_add_child(window_layer, menu_layer_get_layer(s_menu_layer));
// Start scanning. Advertisments will be delivered in the callback.
toggle_scan();
}
static void window_unload(Window *window) {
// After ble_scan_stop() returns, the scan handler will not get called again.
ble_scan_stop();
s_is_scanning = false;
menu_layer_destroy(s_menu_layer);
s_menu_layer = NULL;
list_free_all();
}
//------------------------------------------------------------------------------
Window * ble_demo_scan_window_create(void) {
Window * window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
return window;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "pebble.h"
Window * ble_demo_scan_window_create(void);

24
src/apps/ble_demo/wscript Normal file
View file

@ -0,0 +1,24 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target='pebble-app.elf')
ctx.pbl_bundle(elf='pebble-app.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,26 @@
{
"uuid": "f267008f-0206-4a52-a6cc-d8e2cf0c88d3",
"shortName": "ComplexAnimations",
"longName": "ComplexAnimations",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"targetPlatform": [
"aplite",
"basalt"
],
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
},
"targetPlatforms": [
"aplite",
"basalt"
],
"sdkVersion": "3"
}

View file

@ -0,0 +1,231 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pebble.h"
#include <stdlib.h>
static Window *window;
static TextLayer *s_text_layer_a;
static TextLayer *s_text_layer_b;
static Animation *s_animation;
static int toggle;
#define DURATION 1000
static void animation_started(Animation *animation, void *data) {
text_layer_set_text(s_text_layer_a, "Started.");
}
static void animation_stopped(Animation *animation, bool finished, void *data) {
text_layer_set_text(s_text_layer_a, finished ? "Hi, I'm a TextLayer!" : "Just Stopped.");
}
// --------------------------------------------------------------------------------------
// setup handler
static void prv_setup_handler(Animation *animation) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Executing setup handler for %d", (int)animation);
}
// --------------------------------------------------------------------------------------
// teardown handler
static void prv_teardown_handler(Animation *animation) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Executing teardown handler for %d", (int)animation);
}
// --------------------------------------------------------------------------------------
// update handler
static void prv_update_handler(Animation *animation, const uint32_t distance) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Executing update handler for %d, distance: %d", (int)animation,
(int)distance);
}
// --------------------------------------------------------------------------------------
static const AnimationImplementation s_custom_implementation = {
.setup = prv_setup_handler,
.update = prv_update_handler,
.teardown = prv_teardown_handler
};
// --------------------------------------------------------------------------------------
static Animation *prv_create_custom_animation(void) {
// Create a custom animation with a custom update procedure
Animation *d = animation_create();
animation_set_implementation(d, &s_custom_implementation);
animation_set_duration(d, DURATION);
return d;
}
static void click_handler(ClickRecognizerRef recognizer, Window *window) {
// If the animation is still running, fast-foward to 300ms from the end
if (animation_is_scheduled(s_animation)) {
uint32_t duration = animation_get_duration(s_animation, true, true);
animation_set_elapsed(s_animation, duration - 300);
APP_LOG(APP_LOG_LEVEL_INFO, "Advancing to 300ms from the end of %d ms", (int)duration);
return;
}
Layer *layer = text_layer_get_layer(s_text_layer_a);
GRect from_rect_a = GRect(0, 0, 60, 60);
GRect to_rect_a = GRect(84, 92, 60, 60);
GRect from_rect_b = GRect(84, 0, 60, 60);
GRect to_rect_b = GRect(0, 92, 60, 60);
GRect tmp;
if (toggle) {
tmp = to_rect_b;
to_rect_b = from_rect_b;
from_rect_b = tmp;
}
toggle = !toggle;
animation_destroy(s_animation);
s_animation = NULL;
PropertyAnimation *a = property_animation_create_layer_frame(layer, &from_rect_a, &to_rect_a);
animation_set_duration((Animation*)a, DURATION);
animation_set_handlers((Animation*) a, (AnimationHandlers) {
.started = (AnimationStartedHandler) animation_started,
.stopped = (AnimationStoppedHandler) animation_stopped,
}, NULL /* callback data */);
PropertyAnimation *a_rev = property_animation_clone(a);
animation_set_handlers((Animation*) a_rev, (AnimationHandlers) {
.started = (AnimationStartedHandler) animation_started,
.stopped = (AnimationStoppedHandler) animation_stopped,
}, NULL /* callback data */);
animation_set_delay((Animation*)a_rev, 400);
animation_set_duration((Animation*)a_rev, DURATION);
animation_set_reverse((Animation *)a_rev, true);
GRect test_rect;
property_animation_get_to_grect(a, &test_rect);
APP_LOG(APP_LOG_LEVEL_DEBUG, "rect is %d, %d, %d, %d", test_rect.origin.x, test_rect.origin.y,
test_rect.size.w, test_rect.size.h);
switch (click_recognizer_get_button_id(recognizer)) {
case BUTTON_ID_UP:
animation_set_curve((Animation*) a, AnimationCurveEaseOut);
animation_set_curve((Animation*) a_rev, AnimationCurveEaseOut);
break;
case BUTTON_ID_DOWN:
animation_set_curve((Animation*) a, AnimationCurveEaseIn);
animation_set_curve((Animation*) a_rev, AnimationCurveEaseIn);
break;
default:
case BUTTON_ID_SELECT:
animation_set_curve((Animation*) a, AnimationCurveEaseInOut);
animation_set_curve((Animation*) a_rev, AnimationCurveEaseInOut);
break;
}
/*
// Exmple animation parameters:
// Duration defaults to 250 ms
animation_set_duration(&prop_animation->animation, 1000);
// Curve defaults to ease-in-out
animation_set_curve(&prop_animation->animation, AnimationCurveEaseOut);
// Delay defaults to 0 ms
animation_set_delay(&prop_animation->animation, 1000);
*/
Animation *seq = animation_sequence_create((Animation *)a, (Animation *)a_rev, NULL);
PropertyAnimation *c = property_animation_create_layer_frame(
text_layer_get_layer(s_text_layer_b), &from_rect_b, &to_rect_b);
animation_set_duration((Animation*)c, DURATION);
s_animation = animation_spawn_create(seq, (Animation *)c, prv_create_custom_animation());
animation_schedule(s_animation);
}
static void config_provider(Window *window) {
window_single_click_subscribe(BUTTON_ID_UP, (ClickHandler) click_handler);
window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler) click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, (ClickHandler) click_handler);
}
static void init(void) {
window = window_create();
window_set_click_config_provider(window, (ClickConfigProvider) config_provider);
window_stack_push(window, false);
GRect from_rect_a = GRect(0, 0, 60, 60);
GRect to_rect_a = GRect(84, 92, 60, 60);
GRect from_rect_b = GRect(84, 0, 60, 60);
GRect to_rect_b = GRect(0, 92, 60, 60);
s_text_layer_a = text_layer_create(from_rect_a);
text_layer_set_text(s_text_layer_a, "Started!");
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_text_layer_a));
s_text_layer_b = text_layer_create(from_rect_b);
text_layer_set_text(s_text_layer_b, "Spawned");
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_text_layer_b));
// Animate text layer a from top-left to bottom right and back
PropertyAnimation *a = property_animation_create_layer_frame(
text_layer_get_layer(s_text_layer_a), &from_rect_a, &to_rect_a);
animation_set_duration((Animation*)a, DURATION);
PropertyAnimation *a_rev = property_animation_clone(a);
animation_set_delay((Animation*) a_rev, 400);
animation_set_duration((Animation*)a_rev, DURATION);
animation_set_reverse((Animation*) a_rev, true);
Animation *seq = animation_sequence_create((Animation *)a, (Animation *)a_rev, NULL);
// Animate text layer b from top-right to bottom-left
PropertyAnimation *c = property_animation_create_layer_frame(
text_layer_get_layer(s_text_layer_b), &from_rect_b, &to_rect_b);
animation_set_duration((Animation*)c, DURATION);
toggle = !toggle;
s_animation = animation_spawn_create(seq, (Animation *)c, prv_create_custom_animation(), NULL);
animation_schedule(s_animation);
}
static void deinit(void) {
animation_destroy(s_animation);
window_stack_remove(window, false);
window_destroy(window);
text_layer_destroy(s_text_layer_a);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

3
src/apps/crash_demo/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,19 @@
{
"uuid": "2e0effb8-b141-449d-8c41-0f2bc3cb8e88",
"shortName": "Crash Test",
"longName": "Crash Test",
"companyName": "Pebble",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"resources": {
"media": []
},
"targetPlatforms": [
"aplite",
"basalt"
],
"sdkVersion": "3"
}

View file

@ -0,0 +1,169 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
typedef struct MainWindowData {
Window *window;
SimpleMenuLayer *menu_layer;
} MainWindowData;
static MainWindowData s_main_window_data;
static void execute_gibberish_menu_cb(int index, void *context) {
int32_t gibberish[] = { 0, 0, 0, 0 };
int8_t* gibberish_ptr = (int8_t*) gibberish;
((void (*)(void))gibberish_ptr + 1)();
}
static void write_to_null_menu_cb(int index, void *context) {
int* null_ptr = NULL;
*null_ptr = 0xdeadbeef;
}
static void write_to_kernel_menu_cb(int index, void *context) {
// The kernel ram is between 0x20000000 to 0x20018000
int* kernel_ptr = (int*) 0x20010000;
*kernel_ptr = 0xdeadbeef;
}
static void trigger_applib_assert_cb(int index, void *context) {
// A little fragile, I know we have an assert in this function but it may change in the future.
layer_set_update_proc(0, 0);
}
static void trigger_infinite_loop(int index, void *context) {
while (true) {
}
}
static void trigger_persist_loop(int index, void *context) {
int value = 1;
while (true) {
persist_write_int(42, value++);
}
}
static void trigger_loop_log_spam(int index, void *context) {
while (true) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Crash Demo Looping Log Spam! WarbleGarbleWarbleGarbleWarble");
}
}
static void accel_data_handler(AccelData *data, uint32_t num_samples) {
}
static void trigger_to_app_event_flood(int index, void *context) {
// Generate a crazy number of events and then busy wait.
accel_data_service_subscribe(1, accel_data_handler);
accel_service_set_sampling_rate(ACCEL_SAMPLING_100HZ);
while (true) {
}
}
static void trigger_double_free(int index, void *context) {
volatile int* storage = malloc(sizeof(int));
*storage = 1337;
free((void*) storage);
free((void*) storage);
}
static void trigger_stack_overflow(int index, void *context) {
volatile int counter = (int) context;
if (counter > 300) {
return;
}
++counter;
trigger_stack_overflow(index, (void*) counter);
}
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
static const SimpleMenuItem menu_items[] = {
{
.title = "Execute gibberish",
.callback = execute_gibberish_menu_cb
}, {
.title = "Write to NULL",
.callback = write_to_null_menu_cb
}, {
.title = "Write to kernel",
.callback = write_to_kernel_menu_cb
}, {
.title = "Trigger applib assert",
.callback = trigger_applib_assert_cb
}, {
.title = "Infinite loop",
.callback = trigger_infinite_loop
}, {
.title = "Loop Log Spam",
.callback = trigger_loop_log_spam
}, {
.title = "To App Event Flood",
.callback = trigger_to_app_event_flood
}, {
.title = "Double Free",
.callback = trigger_double_free
}, {
.title = "Stack Overflow",
.callback = trigger_stack_overflow
}, {
.title = "Persist loop",
.callback = trigger_persist_loop
}
};
static const SimpleMenuSection sections[] = {
{
.items = menu_items,
.num_items = ARRAY_LENGTH(menu_items)
}
};
s_main_window_data.menu_layer = simple_menu_layer_create(bounds, window, sections, ARRAY_LENGTH(sections), NULL);
layer_add_child(window_layer, simple_menu_layer_get_layer(s_main_window_data.menu_layer));
}
static void window_unload(Window *window) {
simple_menu_layer_destroy(s_main_window_data.menu_layer);
}
static void init(void) {
Window *window = s_main_window_data.window;
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
const bool animated = true;
window_stack_push(window, animated);
}
static void deinit(void) {
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,19 @@
{
"uuid": "7de4c63c-e674-4a46-862b-bd41da17467e",
"shortName": "crash_demo_watchface",
"longName": "crash_demo_watchface",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["aplite", "basalt"],
"watchapp": {
"watchface": true
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
static Window *window;
static TextLayer *text_layer;
static char text_buffer[64];
static int s_counter = 3;
static void prv_update_text(void) {
snprintf(text_buffer, sizeof(text_buffer), "Crashing in %d seconds", s_counter);
text_layer_set_text(text_layer, text_buffer);
}
static void prv_execute_gibberish(void) {
int32_t gibberish[] = { 0, 0, 0, 0 };
int8_t* gibberish_ptr = (int8_t*) gibberish;
((void (*)(void))gibberish_ptr + 1)();
}
static void prv_timer_callback(void *data) {
--s_counter;
if (s_counter == 0) {
prv_execute_gibberish();
}
prv_update_text();
app_timer_register(1000, prv_timer_callback, 0);
}
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
text_layer = text_layer_create((GRect) { .origin = { 0, 72 }, .size = { bounds.size.w, 20 } });
text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
prv_update_text();
layer_add_child(window_layer, text_layer_get_layer(text_layer));
}
static void window_unload(Window *window) {
text_layer_destroy(text_layer);
}
static void init(void) {
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
const bool animated = true;
window_stack_push(window, animated);
app_timer_register(1000, prv_timer_callback, 0);
}
static void deinit(void) {
window_destroy(window);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,39 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

3
src/apps/data_logging_spam/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,17 @@
{
"uuid": "4fcaae25-0cb3-4ba0-83b0-01f313fee892",
"shortName": "data_logging_spam",
"longName": "data_logging_spam",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
static void log_data(void *data) {
DataLoggingSessionRef *session = data_logging_create(0, DATA_LOGGING_BYTE_ARRAY, 4, true);
for (int i = 0; i < 32; ++i) {
uint32_t t = ((uint32_t) time(NULL)) + i;
data_logging_log(session, &t, 1);
}
data_logging_finish(session);
app_timer_register(100, log_data, 0);
}
int main(void) {
Window *window = window_create();
const bool animated = true;
window_stack_push(window, animated);
app_timer_register(100, log_data, 0);
app_event_loop();
}

View file

@ -0,0 +1,24 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target='pebble-app.elf')
ctx.pbl_bundle(elf='pebble-app.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,6 @@
# Ignore build generated files
build
# Ignore waf lock file
.lock-waf*

View file

@ -0,0 +1,19 @@
{
"uuid": "3bfcd75f-5781-4fd1-97ad-b0751f3f1007",
"shortName": "Delayed Worker Crash",
"longName": "Delayed Worker Crash",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["aplite", "basalt"],
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,90 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
typedef struct {
Window *window;
TextLayer *text_layer;
} DelayedWorkerCrashData;
static const char *WORKER_ALREADY_RUNNING = "Worker already running, crashing soon!";
static const char *WORKER_LAUNCHED = "Worker launched, will crash in 5 seconds!";
static const char *WORKER_LAUNCH_ERROR = "Error launching worker!";
static DelayedWorkerCrashData s_data;
static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
char *message = NULL;
// Check to see if the worker is currently active
const bool running = app_worker_is_running();
// Toggle running state
if (running) {
message = WORKER_ALREADY_RUNNING;
} else {
AppWorkerResult result = app_worker_launch();
if (result == APP_WORKER_RESULT_SUCCESS) {
message = WORKER_LAUNCHED;
} else {
message = WORKER_LAUNCH_ERROR;
}
}
text_layer_set_text(s_data.text_layer, message);
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
}
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
s_data.text_layer = text_layer_create(GRect(0, 72, bounds.size.w, 500));
text_layer_set_text(s_data.text_layer, "Click select to launch worker");
text_layer_set_text_alignment(s_data.text_layer, GTextAlignmentCenter);
text_layer_set_overflow_mode(s_data.text_layer, GTextOverflowModeWordWrap);
layer_add_child(window_layer, text_layer_get_layer(s_data.text_layer));
}
static void window_unload(Window *window) {
text_layer_destroy(s_data.text_layer);
}
static void init(void) {
s_data.window = window_create();
window_set_click_config_provider(s_data.window, click_config_provider);
window_set_window_handlers(s_data.window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
const bool animated = true;
window_stack_push(s_data.window, animated);
}
static void deinit(void) {
window_destroy(s_data.window);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,33 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble_worker.h>
#define WORKER_CRASH_DELAY_MS 5000
static void worker_timer_callback(void *data) {
// Free -1 to crash the worker
free((void *) -1);
}
static void worker_init(void) {
app_timer_register(WORKER_CRASH_DELAY_MS, worker_timer_callback, NULL);
}
int main(void) {
worker_init();
worker_event_loop();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

3
src/apps/dictation_demo/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,19 @@
{
"uuid": "6e637ec2-f78e-4a76-b797-307cbdfd66dc",
"shortName": "Dictation demo",
"longName": "Dictation demo",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["aplite", "basalt"],
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,130 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
typedef struct AppData {
Window *window;
TextLayer *result_text;
char *result;
DictationSession *session;
bool confirm;
} AppData;
static void prv_result_handler(DictationSession *session, DictationSessionStatus result,
char *transcription, void *context) {
AppData *app_data = context;
free(app_data->result);
if (result == DictationSessionStatusSuccess) {
size_t size = strlen(transcription) + 11;
app_data->result = malloc(size);
if (!app_data->result) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Failed to allocated memory for transcription");
result = 99;
} else {
snprintf(app_data->result, size, "You said:\n%s", transcription);
text_layer_set_text(app_data->result_text, app_data->result);
return;
}
}
app_data->result = malloc(100);
snprintf(app_data->result, 100, "Welp, that didn't work (Error: %u).\n Try again.", result);
text_layer_set_text(app_data->result_text, app_data->result);
}
static void prv_select_click_handler(ClickRecognizerRef recognizer, void *context) {
AppData *app_data = context;
dictation_session_start(app_data->session);
}
static void prv_down_click_handler(ClickRecognizerRef recognizer, void *context) {
AppData *app_data = context;
dictation_session_destroy(app_data->session);
app_data->session = NULL;
}
static void prv_up_click_handler(ClickRecognizerRef recognizer, void *context) {
AppData *app_data = context;
app_data->confirm = !app_data->confirm;
dictation_session_enable_confirmation(app_data->session, app_data->confirm);
}
static void prv_click_config_provider(void *context) {
window_set_click_context(BUTTON_ID_SELECT, context);
window_single_click_subscribe(BUTTON_ID_SELECT, prv_select_click_handler);
window_set_click_context(BUTTON_ID_DOWN, context);
window_single_click_subscribe(BUTTON_ID_DOWN, prv_down_click_handler);
window_set_click_context(BUTTON_ID_UP, context);
window_single_click_subscribe(BUTTON_ID_UP, prv_up_click_handler);
}
static void prv_window_load(Window *window) {
AppData *app_data = window_get_user_data(window);
Layer *window_layer = window_get_root_layer(app_data->window);
GRect bounds = layer_get_bounds(window_layer);
app_data->result_text = text_layer_create((GRect) {
.origin = { .x = 10, .y = 10},
.size = { .w = bounds.size.w - 20, .h = bounds.size.h - 20 }
});
text_layer_set_text(app_data->result_text, "Press SELECT to start");
text_layer_set_overflow_mode(app_data->result_text, GTextOverflowModeWordWrap);
text_layer_set_text_alignment(app_data->result_text, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(app_data->result_text));
}
static void prv_window_unload(Window *window) {
AppData *app_data = window_get_user_data(window);
text_layer_destroy(app_data->result_text);
}
static void init(AppData *app_data) {
app_data->session = dictation_session_create(1024, prv_result_handler, app_data);
if (!app_data->session) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Failed to create dictation session");
}
app_data->confirm = true;
app_data->window = window_create();
window_set_click_config_provider_with_context(app_data->window, prv_click_config_provider,
app_data);
window_set_window_handlers(app_data->window, (WindowHandlers) {
.load = prv_window_load,
.unload = prv_window_unload,
});
window_set_user_data(app_data->window, app_data);
const bool animated = true;
window_stack_push(app_data->window, animated);
APP_LOG(APP_LOG_LEVEL_DEBUG, "Done initializing, pushed window: %p", app_data->window);
}
static void deinit(AppData *app_data) {
dictation_session_destroy(app_data->session);
window_destroy(app_data->window);
}
int main(void) {
AppData *app_data = calloc(1, sizeof(AppData));
init(app_data);
app_event_loop();
deinit(app_data);
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,18 @@
{
"uuid": "21475b26-299f-4194-8013-eb7ce0bb8e29",
"shortName": "draw_lines",
"longName": "draw_lines",
"companyName": "MakeAwesomeHappen",
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["basalt", "chalk", "emery"],
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,92 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
// Access profiler functions.
extern void __profiler_init(void);
extern void __profiler_print_stats(void);
extern void __profiler_start(void);
extern void __profiler_stop(void);
static Window *window;
typedef enum {
LineDirection_Horizontal,
LineDirection_Vertical,
LineDirection_Diagonal,
} LineDirection;
static void prv_draw_lines(GContext *ctx, GRect bounds, uint32_t num_lines, LineDirection dir) {
GPoint start, end;
switch (dir) {
case LineDirection_Horizontal:
APP_LOG(APP_LOG_LEVEL_INFO, "Horizontal lines");
start = GPoint(bounds.origin.x, bounds.size.h / 2);
end = GPoint(bounds.size.w, bounds.size.h / 2);
break;
case LineDirection_Vertical:
APP_LOG(APP_LOG_LEVEL_INFO, "Vertical lines");
start = GPoint(bounds.size.w / 2, bounds.origin.y);
end = GPoint(bounds.size.w / 2, bounds.size.h);
break;
case LineDirection_Diagonal:
APP_LOG(APP_LOG_LEVEL_INFO, "Diagonal lines");
start = bounds.origin;
end = GPoint(bounds.size.w, bounds.size.h);
break;
}
__profiler_start();
for (uint32_t i = 0; i < num_lines; ++i) {
graphics_draw_line(ctx, start, end);
}
__profiler_stop();
__profiler_print_stats();
}
static void prv_update_proc(Layer *layer, GContext *ctx) {
static const uint32_t NUM_LINES_TO_DRAW = 10000;
GRect bounds = layer_get_bounds(layer);
prv_draw_lines(ctx, bounds, NUM_LINES_TO_DRAW, LineDirection_Vertical);
prv_draw_lines(ctx, bounds, NUM_LINES_TO_DRAW, LineDirection_Horizontal);
prv_draw_lines(ctx, bounds, NUM_LINES_TO_DRAW, LineDirection_Diagonal);
}
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
layer_set_update_proc(window_layer, prv_update_proc);
}
static void init(void) {
__profiler_init();
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
});
window_stack_push(window, true);
}
static void deinit(void) {
window_destroy(window);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

6
src/apps/draw_text/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# Ignore build generated files
build
# Ignore waf lock file
.lock-waf*

View file

@ -0,0 +1,18 @@
{
"uuid": "16e58bbb-cec7-436c-bbc1-c1cc701b4886",
"shortName": "draw_text",
"longName": "draw_text",
"companyName": "MakeAwesomeHappen",
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["basalt", "chalk", "emery"],
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
// Access profiler functions.
extern void __profiler_init(void);
extern void __profiler_print_stats(void);
extern void __profiler_start(void);
extern void __profiler_stop(void);
#define ITERATIONS 100
static Window *window;
static const char *TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing "\
"elit, sed do eiusmod tempor incididunt ut labore "\
"et dolore magna aliqua. Ut enim ad minim veniam, "\
"quis nostrud exercitation ullamco laboris nisi ut "\
"aliquip ex ea commodo consequat.";
static void prv_update_proc(Layer *layer, GContext *ctx) {
GRect bounds = layer_get_bounds(layer);
graphics_context_set_text_color(ctx, GColorBlack);
__profiler_start();
for (int i = 0; i < ITERATIONS; ++i) {
graphics_draw_text(ctx, TEXT, fonts_get_system_font(FONT_KEY_GOTHIC_14),
bounds, GTextOverflowModeWordWrap, GTextAlignmentLeft,
NULL);
}
__profiler_stop();
APP_LOG(APP_LOG_LEVEL_INFO, "Draw Text");
__profiler_print_stats();
}
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
layer_set_update_proc(window_layer, prv_update_proc);
}
static void init(void) {
__profiler_init();
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
});
window_stack_push(window, true);
}
static void deinit(void) {
window_destroy(window);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

6
src/apps/fill_radial/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# Ignore build generated files
build
# Ignore waf lock file
.lock-waf*

View file

@ -0,0 +1,18 @@
{
"uuid": "876bf5e0-3749-4632-acd6-c8093c4a51c1",
"shortName": "fill_radial",
"longName": "fill_radial",
"companyName": "MakeAwesomeHappen",
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["basalt", "chalk", "emery"],
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,100 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
// Access profiler functions.
extern void __profiler_init(void);
extern void __profiler_print_stats(void);
extern void __profiler_start(void);
extern void __profiler_stop(void);
#define ITERATIONS 1000
static Window *window;
static uint16_t min(uint16_t a, uint16_t b) {
return a < b ? a : b;
}
static void prv_update_proc(Layer *layer, GContext *ctx) {
GRect bounds = layer_get_bounds(layer);
graphics_context_set_fill_color(ctx, GColorRed);
uint16_t radius = min(bounds.size.w / 2, bounds.size.h / 2);
uint16_t inset_thickness = 1;
// 360 degrees, filled in
__profiler_start();
for (int i = 0; i < ITERATIONS; ++i) {
graphics_fill_radial(ctx, bounds, GOvalScaleModeFitCircle, radius, 0, TRIG_MAX_ANGLE);
}
__profiler_stop();
APP_LOG(APP_LOG_LEVEL_INFO, "360 filled");
__profiler_print_stats();
// 360 degrees, 1px inset
__profiler_start();
for (int i = 0; i < ITERATIONS; ++i) {
graphics_fill_radial(ctx, bounds, GOvalScaleModeFitCircle, inset_thickness, 0, TRIG_MAX_ANGLE);
}
__profiler_stop();
APP_LOG(APP_LOG_LEVEL_INFO, "360 insets");
__profiler_print_stats();
// 180 degrees, filled in
__profiler_start();
for (int i = 0; i < ITERATIONS; ++i) {
graphics_fill_radial(ctx, bounds, GOvalScaleModeFitCircle, radius, 0, TRIG_MAX_ANGLE / 2);
}
__profiler_stop();
APP_LOG(APP_LOG_LEVEL_INFO, "180 filled");
__profiler_print_stats();
// 180 degrees, 1px inset
__profiler_start();
for (int i = 0; i < ITERATIONS; ++i) {
graphics_fill_radial(ctx, bounds, GOvalScaleModeFitCircle, inset_thickness, 0,
TRIG_MAX_ANGLE / 2);
}
__profiler_stop();
APP_LOG(APP_LOG_LEVEL_INFO, "180 insets");
__profiler_print_stats();
}
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
layer_set_update_proc(window_layer, prv_update_proc);
}
static void init(void) {
__profiler_init();
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
});
window_stack_push(window, true);
}
static void deinit(void) {
window_destroy(window);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

3
src/apps/fps_test/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,17 @@
{
"uuid": "56c66530-e463-4442-9dfc-e94b90ede647",
"shortName": "FPS Test",
"longName": "FPS Test",
"companyName": "Pebble",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,263 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "test_bitmaps.h"
#include <pebble.h>
typedef struct AppData {
Window *window;
BitmapLayer *background_layer;
BitmapLayer *topleft_layer;
MenuLayer *action_list1;
MenuLayer *action_list2;
int64_t time_started;
uint32_t rendered_frames;
} AppData;
static int64_t prv_time_64(void) {
time_t s;
uint16_t ms;
time_ms(&s, &ms);
return (int64_t)s * 1000 + ms;
}
static void prv_redraw_timer_cb(void *cb_data) {
AppData *data = (AppData *)cb_data;
Layer *root_layer = window_get_root_layer(data->window);
layer_mark_dirty(root_layer);
app_timer_register(0, prv_redraw_timer_cb, data);
}
// The profiler functions are in the exported symbols table but not in the header
// because we aren't quite ready to expose them to 3rd party developers
extern void __profiler_init(void);
extern void __profiler_print_stats(void);
extern void __profiler_start(void);
extern void __profiler_stop(void);
/*****************************************************************************************
Stop our timer and display results.
A frame update consists of the following operations:
op_1) App renders to its own frame buffer
op_2) System copies the app frame buffer to the system frame buffer
op_3) System sends the system frame buffer to the display hardware (using DMA).
op_3 can happen in parallel with op_1, so the effective frame period is:
frame_period = MAX(op_1_time + op_2_time, op2_time + op_3_time)
This app measures op_1_time + op_2_time and does so by counting the number of times
the app window's update callback got called within a set amount of time. The window update
callback only does op1, but the app_render_handler() method in app.c insures that a window
update is not called again until op_2 has completed for the previous update. This throttling
if the app's window update also insures that:
(op_1_time + op_2_time) is always >= (op_2_time + op_3_time)
To measure op_1, we use a profiler timer node called "render". This timer
measures the amount of time we spend in the window_render() method.
To measure op_2, we use a profiler timer node called "framebuffer_prepare". This timer
measures the amount of time we spend copying the app's frame buffer to the system framebuffer
To measure op_3, we use a profiler timer node called "framebuffer_send". This profiler timer
measures the amount of time we spend waiting for a display DMA to complete.
op_1 can be computed from the app's update period - op_2_time
*/
static void prv_pop_all_windows_cb(void *cb_data) {
AppData *data = (AppData *)cb_data;
// Print profiler stats which include the time spent copying the app frame buffer to the
// system frame buffer and the time spent sending the system frame buffer to the display.
__profiler_stop();
__profiler_print_stats();
int64_t time_rendered = prv_time_64() - data->time_started;
APP_LOG(APP_LOG_LEVEL_INFO, "## %d frames rendered", (int)data->rendered_frames);
if (time_rendered) {
int frame_period = time_rendered/(int64_t)data->rendered_frames;
int fps = (int64_t)data->rendered_frames*1000/time_rendered;
APP_LOG(APP_LOG_LEVEL_INFO, "## at %d FPS (%d ms/frame)", fps, frame_period);
}
window_stack_pop_all(false);
}
static const char *prv_row_texts[] = {
"Row 1",
"Row 2",
"Row 3",
"Row 4",
"Row 5",
"Row 6",
};
static uint16_t prv_get_num_rows(struct MenuLayer *menu_layer, uint16_t section_index,
void *callback_context) {
return ARRAY_LENGTH(prv_row_texts);
}
static void prv_draw_row(GContext* ctx, const Layer *cell_layer, char const *title,
int16_t offset) {
// mostly copied from menu_cell_basic_draw_with_value
// (that unfortunately doesn't respect bounds.origin.x)
const int16_t title_height = 24;
GRect box = layer_get_bounds(cell_layer);
box.origin.x += offset;
box.origin.y = (box.size.h - title_height) / 2;
box.size.w -= offset;
box.size.h = title_height + 4;
const GFont title_font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
if (title) {
graphics_context_set_text_color(ctx, GColorWhite);
graphics_draw_text(ctx, title, title_font, box,
GTextOverflowModeFill, GTextAlignmentLeft, NULL);
}
}
void prv_draw_row_1(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index,
void *callback_context) {
const char *title = prv_row_texts[cell_index->row];
GRect frame = layer_get_frame(cell_layer);
prv_draw_row(ctx, cell_layer, title, -frame.origin.y/4);
}
static void prv_draw_row_2(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index,
void *callback_context) {
const char *title = prv_row_texts[cell_index->row];
GRect frame = layer_get_frame(cell_layer);
GRect bounds = layer_get_bounds(cell_layer);
prv_draw_row(ctx, cell_layer, title, -frame.origin.y/4 + bounds.size.w);
}
static int16_t prv_get_separator_height(struct MenuLayer *menu_layer, MenuIndex *cell_index,
void *callback_context) {
return 0;
}
static void prv_window_update_proc(struct Layer *layer, GContext *ctx) {
Window *window = layer_get_window(layer);
AppData *data = window_get_user_data(window);
if (data->rendered_frames == 0) {
data->time_started = prv_time_64();
__profiler_init();
__profiler_start();
}
data->rendered_frames++;
}
void prv_syncing_selection_changed(MenuLayer *menu_layer, MenuIndex old_index,
MenuIndex new_index, void *context) {
ScrollLayer *scroll_layer = (ScrollLayer *)menu_layer;
AppData *data = context;
GPoint offset = scroll_layer_get_content_offset(scroll_layer);
scroll_layer_set_content_offset((ScrollLayer *)data->action_list1, offset, false);
}
static void prv_window_load(Window *window) {
// creates a structure as outlined at
// https://pebbletechnology.atlassian.net/wiki/display/DEV/3.0+Notifications+UI+MVP
// it's one full screen background image .background_layer,
// one image at the top left .topleft_layer,
// and two menu layers .action_list1 and .action_list2 that overlay each other
// some hackery with the two menu layers goes on to keep their scroll offest in sync
// and to have the inverter layer rendered only once
const int16_t navbar_width = s_fps_topleft_bitmap.bounds.size.w;
AppData *data = window_get_user_data(window);
Layer *root_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(root_layer);
const GRect full_rect = bounds;
data->background_layer = bitmap_layer_create(full_rect);
bitmap_layer_set_background_color(data->background_layer, GColorBlack);
bitmap_layer_set_bitmap(data->background_layer, &s_fps_background_bitmap);
layer_add_child(root_layer, (Layer *)data->background_layer);
data->topleft_layer = bitmap_layer_create(GRect(0, 0, navbar_width, navbar_width));
bitmap_layer_set_background_color(data->topleft_layer, GColorWhite);
bitmap_layer_set_bitmap(data->topleft_layer, &s_fps_topleft_bitmap);
layer_add_child(root_layer, (Layer *)data->topleft_layer);
const GRect menu_layer_rect =
GRect(navbar_width, 0, full_rect.size.w - navbar_width, full_rect.size.h);
data->action_list1 = menu_layer_create(menu_layer_rect);
menu_layer_set_callbacks(data->action_list1, data, (MenuLayerCallbacks){
.get_num_rows = prv_get_num_rows,
.draw_row = prv_draw_row_1,
.get_separator_height = prv_get_separator_height,
});
// FIXME layer_set_hidden(&data->action_list1.inverter.layer, true);
scroll_layer_set_shadow_hidden((ScrollLayer *)data->action_list1, true);
layer_add_child(root_layer, menu_layer_get_layer(data->action_list1));
data->action_list2 = menu_layer_create(menu_layer_rect);
menu_layer_set_callbacks(data->action_list2, data, (MenuLayerCallbacks){
.get_num_rows = prv_get_num_rows,
.draw_row = prv_draw_row_2,
.get_separator_height = prv_get_separator_height,
.selection_changed = prv_syncing_selection_changed,
});
scroll_layer_set_shadow_hidden((ScrollLayer *)data->action_list2, true);
menu_layer_set_click_config_onto_window(data->action_list2, window);
layer_add_child(root_layer, menu_layer_get_layer(data->action_list2));
// start infinite update loop
prv_redraw_timer_cb(data);
// run application for a given time, than terminate
app_timer_register(5000, prv_pop_all_windows_cb, data);
}
void prv_deinit(AppData *data) {
menu_layer_destroy(data->action_list1);
menu_layer_destroy(data->action_list2);
bitmap_layer_destroy(data->background_layer);
bitmap_layer_destroy(data->topleft_layer);
window_destroy(data->window);
free(data);
}
int main(void) {
AppData *data = malloc(sizeof(AppData));
memset(data, 0, sizeof(AppData));
Window *window = window_create();
data->window = window;
window_set_user_data(window, data);
window_set_fullscreen(window, true);
Layer *root_layer = window_get_root_layer(window);
layer_set_update_proc(root_layer, prv_window_update_proc);
window_set_window_handlers(window, (WindowHandlers){
.load = prv_window_load,
});
window_stack_push(window, true);
__profiler_init();
__profiler_start();
app_event_loop();
prv_deinit(data);
}

File diff suppressed because it is too large Load diff

33
src/apps/fps_test/wscript Normal file
View file

@ -0,0 +1,33 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target='pebble-app.elf')
if os.path.exists('worker_src'):
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target='pebble-worker.elf')
ctx.pbl_bundle(elf='pebble-app.elf',
worker_elf='pebble-worker.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))
else:
ctx.pbl_bundle(elf='pebble-app.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,18 @@
{
"uuid": "9754c10e-72d8-481f-a89b-f9cb30601c21",
"shortName": "Health API",
"longName": "Health API",
"companyName": "MakeAwesomeHappen",
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["diorite"],
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,19 @@
{
"uuid": "02ba06f5-583f-4e70-b84d-6424efffe821",
"shortName": "Launch Reason",
"longName": "Launch Reason Demo",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["aplite", "basalt"],
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,97 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
static TextLayer *s_text_launch_reason;
static TextLayer *s_text_instructions;
static Window *window;
static void prv_wakeup_handler(WakeupId id, int32_t reason) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Woken up.");
}
static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
wakeup_service_subscribe(prv_wakeup_handler);
wakeup_schedule(time(NULL) + 5 , 1, false);
window_stack_pop(true);
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
}
static char *get_launch_reason_str(void) {
switch (launch_reason()) {
case APP_LAUNCH_SYSTEM:
return "SYSTEM";
case APP_LAUNCH_USER:
return "USER";
case APP_LAUNCH_PHONE:
return "PHONE";
case APP_LAUNCH_WAKEUP:
return "WAKEUP";
case APP_LAUNCH_WORKER:
return "WORKER";
case APP_LAUNCH_QUICK_LAUNCH:
return "QUICK LAUNCH";
case APP_LAUNCH_TIMELINE_ACTION:
return "TIMELINE ACTION";
}
return "ERROR";
}
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
s_text_launch_reason = text_layer_create(layer_get_frame(window_layer));
char *reason = get_launch_reason_str();
APP_LOG(APP_LOG_LEVEL_INFO, "Launch reason: %s", reason);
text_layer_set_text(s_text_launch_reason, reason);
layer_add_child(window_layer, text_layer_get_layer(s_text_launch_reason));
GRect frame = layer_get_frame(window_layer);
frame.origin = GPoint(0, 50);
s_text_instructions = text_layer_create(frame);
text_layer_set_text(s_text_instructions, "Press select to start 5s wakeup");
layer_add_child(window_layer, text_layer_get_layer(s_text_instructions));
}
static void window_unload(Window *window) {
text_layer_destroy(s_text_launch_reason);
text_layer_destroy(s_text_instructions);
}
static void init(void) {
window = window_create();
window_set_click_config_provider(window, click_config_provider);
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
const bool animated = true;
window_stack_push(window, animated);
}
static void deinit(void) {
window_destroy(window);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,22 @@
{
"uuid": "812b900e-53fb-463f-a7e5-691937328687",
"shortName": "gmtime_localtime_test",
"longName": "gmtime_localtime_test",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
},
"targetPlatforms": [
"aplite",
"basalt"
],
"sdkVersion": "3"
}

View file

@ -0,0 +1,94 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
static Window *window;
static TextLayer *time_layer;
static TextLayer *gmtime_layer;
static TextLayer *localtime_layer;
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
time_t the_time = time(NULL);
// Time layer
static char time_buf[32];
snprintf(time_buf, 32, "time: %u", (unsigned) the_time);
time_layer = text_layer_create(GRect(0, 0, 144, 168));
text_layer_set_text(time_layer, time_buf);
text_layer_set_font(time_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
layer_add_child(window_layer, text_layer_get_layer(time_layer));
// Time layer
struct tm *gm_time = gmtime(&the_time);
static char gmtime_buf[32];
snprintf(gmtime_buf, 32, "gmtime: %d:%02d, is_dst: %d",
gm_time->tm_hour, gm_time->tm_min, gm_time->tm_isdst);
gmtime_layer = text_layer_create(GRect(0, 40, 144, 168));
text_layer_set_text(gmtime_layer, gmtime_buf);
text_layer_set_font(gmtime_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
layer_add_child(window_layer, text_layer_get_layer(gmtime_layer));
char gmtime_strftime_buf[32];
strftime(gmtime_strftime_buf, 32, "%z %Z", gm_time);
APP_LOG(APP_LOG_LEVEL_DEBUG, "gmtime: %s", gmtime_strftime_buf);
// Time layer
struct tm *lt_time = localtime(&the_time);
static char localtime_buf[32];
snprintf(localtime_buf, 32, "localtime: %d:%02d, is_dst: %d",
lt_time->tm_hour, lt_time->tm_min, lt_time->tm_isdst);
localtime_layer = text_layer_create(GRect(0, 96, 144, 168));
text_layer_set_text(localtime_layer, localtime_buf);
text_layer_set_font(localtime_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
layer_add_child(window_layer, text_layer_get_layer(localtime_layer));
char localtime_strftime_buf[32];
strftime(localtime_strftime_buf, 32, "%z %Z", lt_time);
APP_LOG(APP_LOG_LEVEL_DEBUG, "localtime: %s", localtime_strftime_buf);
}
static void window_unload(Window *window) {
text_layer_destroy(time_layer);
text_layer_destroy(gmtime_layer);
text_layer_destroy(localtime_layer);
}
static void init(void) {
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
const bool animated = true;
window_stack_push(window, animated);
}
static void deinit(void) {
window_destroy(window);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,14 @@
{
"uuid": "2e0eabcd-b141-449d-8c41-0f2bc3cb8e88",
"shortName": "Startup Crash Test",
"longName": "Startup Crash Test",
"companyName": "Pebble",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
int main(void) {
void (*foobar)(void) = NULL;
foobar();
app_event_loop();
}

View file

@ -0,0 +1,24 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target='pebble-app.elf')
ctx.pbl_bundle(elf='pebble-app.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))

3
src/apps/strftime_test/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,22 @@
{
"uuid": "812b900e-53fb-463f-a7e5-691937328687",
"shortName": "strftime_test",
"longName": "strftime_test",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
},
"targetPlatforms": [
"aplite",
"basalt"
],
"sdkVersion": "3"
}

View file

@ -0,0 +1,326 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
static Window *window;
static TextLayer *result_layer;
static struct tm good_data = {
.tm_sec = 49,
.tm_min = 4,
.tm_hour = 11,
.tm_mday = 5,
.tm_mon = 4,
.tm_year = 115,
.tm_wday = 2,
.tm_yday = 124,
.tm_isdst = 1
};
static struct tm bad_data = {
.tm_sec = 49756567,
.tm_min = 49756567,
.tm_hour = 49756567,
.tm_mday = 49756567,
.tm_mon = 49756567,
.tm_year = 49756567,
.tm_wday = 49756567,
.tm_yday = 49756567,
.tm_isdst = 49756567
};
static void prv_test_valid_data(void) {
const int buf_size = 64;
char buf[buf_size];
int r;
// Make sure the valid struct works as expected
r = strftime(buf, buf_size, "%a", &good_data);
if (r == 0 || strncmp(buf, "Tue", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"a\": %s", buf);
}
r = strftime(buf, buf_size, "%A", &good_data);
if (r == 0 || strncmp(buf, "Tuesday", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"A\": %s", buf);
}
r = strftime(buf, buf_size, "%b", &good_data);
if (r == 0 || strncmp(buf, "May", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"b\": %s", buf);
}
r = strftime(buf, buf_size, "%B", &good_data);
if (r == 0 || strncmp(buf, "May", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"B\": %s", buf);
}
r = strftime(buf, buf_size, "%c", &good_data);
if (r == 0 || strncmp(buf, "Tue May 5 11:04:49 2015", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"c\": %s", buf);
}
r = strftime(buf, buf_size, "%d", &good_data);
if (r == 0 || strncmp(buf, "05", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"d\": %s", buf);
}
r = strftime(buf, buf_size, "%D", &good_data);
if (r == 0 || strncmp(buf, "05/05/15", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"D\": %s", buf);
}
r = strftime(buf, buf_size, "%e", &good_data);
if (r == 0 || strncmp(buf, " 5", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"e\": %s", buf);
}
r = strftime(buf, buf_size, "%F", &good_data);
if (r == 0 || strncmp(buf, "2015-05-05", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"F\": %s", buf);
}
r = strftime(buf, buf_size, "%g", &good_data);
if (r == 0 || strncmp(buf, "15", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"f\": %s", buf);
}
r = strftime(buf, buf_size, "%G", &good_data);
if (r == 0 || strncmp(buf, "2015", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"G\": %s", buf);
}
r = strftime(buf, buf_size, "%h", &good_data);
if (r == 0 || strncmp(buf, "May", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"h\": %s", buf);
}
r = strftime(buf, buf_size, "%H", &good_data);
if (r == 0 || strncmp(buf, "11", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"H\": %s", buf);
}
r = strftime(buf, buf_size, "%I", &good_data);
if (r == 0 || strncmp(buf, "11", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"I\": %s", buf);
}
r = strftime(buf, buf_size, "%j", &good_data);
if (r == 0 || strncmp(buf, "125", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"j\": %s", buf);
}
r = strftime(buf, buf_size, "%m", &good_data);
if (r == 0 || strncmp(buf, "05", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"m\": %s", buf);
}
r = strftime(buf, buf_size, "%M", &good_data);
if (r == 0 || strncmp(buf, "04", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"M\": %s", buf);
}
r = strftime(buf, buf_size, "%p", &good_data);
if (r == 0 || strncmp(buf, "AM", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"p\": %s", buf);
}
r = strftime(buf, buf_size, "%r", &good_data);
if (r == 0 || strncmp(buf, "11:04:49 AM", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"r\": %s", buf);
}
r = strftime(buf, buf_size, "%R", &good_data);
if (r == 0 || strncmp(buf, "11:04", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"R\": %s", buf);
}
r = strftime(buf, buf_size, "%S", &good_data);
if (r == 0 || strncmp(buf, "49", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"S\": %s", buf);
}
r = strftime(buf, buf_size, "%T", &good_data);
if (r == 0 || strncmp(buf, "11:04:49", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"T\": %s", buf);
}
r = strftime(buf, buf_size, "%u", &good_data);
if (r == 0 || strncmp(buf, "2", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"u\": %s", buf);
}
r = strftime(buf, buf_size, "%U", &good_data);
if (r == 0 || strncmp(buf, "18", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"U\": %s", buf);
}
r = strftime(buf, buf_size, "%V", &good_data);
if (r == 0 || strncmp(buf, "19", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"V\": %s", buf);
}
r = strftime(buf, buf_size, "%w", &good_data);
if (r == 0 || strncmp(buf, "2", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"w\": %s", buf);
}
r = strftime(buf, buf_size, "%W", &good_data);
if (r == 0 || strncmp(buf, "18", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"W\": %s", buf);
}
r = strftime(buf, buf_size, "%x", &good_data);
if (r == 0 || strncmp(buf, "05/05/15", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"x\": %s", buf);
}
r = strftime(buf, buf_size, "%X", &good_data);
if (r == 0 || strncmp(buf, "11:04:49", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"X\": %s", buf);
}
r = strftime(buf, buf_size, "%y", &good_data);
if (r == 0 || strncmp(buf, "15", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"y\": %s", buf);
}
r = strftime(buf, buf_size, "%Y", &good_data);
if (r == 0 || strncmp(buf, "2015", buf_size) != 0) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"Y\": %s", buf);
}
// r = strftime(buf, buf_size, "%z", &good_data);
// if (r == 0 || strncmp(buf, "", buf_size) != 0) {
// APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"z\": %s", buf);
// }
// r = strftime(buf, buf_size, "%Z", &good_data);
// if (r == 0 || strncmp(buf, "", buf_size) != 0) {
// APP_LOG(APP_LOG_LEVEL_DEBUG, "Error with \"Z\": %s", buf);
// }
}
static void prv_test_invalid_data(void) {
const int buf_size = 64;
char buf[buf_size];
// Make sure the invalid structs don't crash us
// These should all return 0, but many don't seem to be doing that
strftime(buf, buf_size, "%a", &bad_data);
strftime(buf, buf_size, "%A", &bad_data);
strftime(buf, buf_size, "%b", &bad_data);
strftime(buf, buf_size, "%B", &bad_data);
strftime(buf, buf_size, "%c", &bad_data);
strftime(buf, buf_size, "%d", &bad_data);
strftime(buf, buf_size, "%D", &bad_data);
strftime(buf, buf_size, "%e", &bad_data);
strftime(buf, buf_size, "%F", &bad_data);
strftime(buf, buf_size, "%g", &bad_data);
strftime(buf, buf_size, "%G", &bad_data);
strftime(buf, buf_size, "%h", &bad_data);
strftime(buf, buf_size, "%H", &bad_data);
strftime(buf, buf_size, "%I", &bad_data);
strftime(buf, buf_size, "%j", &bad_data);
strftime(buf, buf_size, "%m", &bad_data);
strftime(buf, buf_size, "%M", &bad_data);
strftime(buf, buf_size, "%p", &bad_data);
strftime(buf, buf_size, "%r", &bad_data);
strftime(buf, buf_size, "%R", &bad_data);
strftime(buf, buf_size, "%S", &bad_data);
strftime(buf, buf_size, "%T", &bad_data);
strftime(buf, buf_size, "%u", &bad_data);
strftime(buf, buf_size, "%U", &bad_data);
strftime(buf, buf_size, "%V", &bad_data);
strftime(buf, buf_size, "%w", &bad_data);
strftime(buf, buf_size, "%W", &bad_data);
strftime(buf, buf_size, "%x", &bad_data);
strftime(buf, buf_size, "%X", &bad_data);
strftime(buf, buf_size, "%y", &bad_data);
strftime(buf, buf_size, "%Y", &bad_data);
// strftime(buf, buf_size, "%z", &bad_data);
// strftime(buf, buf_size, "%Z", &bad_data);
}
static void window_load(Window *window) {
prv_test_valid_data();
prv_test_invalid_data();
Layer *window_layer = window_get_root_layer(window);
result_layer = text_layer_create(GRect(0, 0, 144, 168));
text_layer_set_text(result_layer, "strftime() test. Check the app logs for details");
text_layer_set_font(result_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
layer_add_child(window_layer, text_layer_get_layer(result_layer));
}
static void window_unload(Window *window) {
text_layer_destroy(result_layer);
}
static void init(void) {
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
const bool animated = true;
window_stack_push(window, animated);
}
static void deinit(void) {
window_destroy(window);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

3
src/apps/timer_starve/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,17 @@
{
"uuid": "560b6ed2-4669-4e77-8b6e-6e8a6f866204",
"shortName": "timer_starve",
"longName": "timer_starve",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
static const int FPS_NO_PROBLEM = 10;
static const int FPS_NO_RESPONSE = 20;
#define FPS 80
static Window *window;
static void timed_update(void *data) {
layer_mark_dirty(window_get_root_layer(window));
app_timer_register(1000 / FPS, timed_update, NULL);
}
static void init(void) {
window = window_create();
Layer *window_layer = window_get_root_layer(window);
GRect window_bounds = layer_get_bounds(window_layer);
TextLayer *text_layer = text_layer_create(window_bounds);
text_layer_set_text(text_layer, "Unplug and plug in the charger. You will see that the system cannot keep up with it.");
layer_add_child(window_layer, text_layer_get_layer(text_layer));
text_layer = text_layer_create((GRect) {{ 0, window_bounds.size.h / 2 }, window_bounds.size} );
static char buffer[80];
snprintf(buffer, sizeof(buffer), "FPS: %u", FPS);
text_layer_set_text(text_layer, buffer);
layer_add_child(window_layer, text_layer_get_layer(text_layer));
window_stack_push(window, true);
timed_update(NULL);
}
int main(void) {
init();
app_event_loop();
}

View file

@ -0,0 +1,24 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target='pebble-app.elf')
ctx.pbl_bundle(elf='pebble-app.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))

3
src/apps/watch_info_test/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,17 @@
{
"uuid": "812b900e-53fb-463f-a7e5-681937328687",
"shortName": "watch_info_test",
"longName": "watch_info_test",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
static Window *window;
static TextLayer *text_layer;
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = layer_get_bounds(window_layer);
text_layer = text_layer_create(bounds);
static char buffer[64];
WatchInfoColor color = watch_info_get_color();
WatchInfoModel model = watch_info_get_model();
WatchInfoVersion version = watch_info_get_firmware_version();
snprintf(buffer, 64, "Version: %d.%d.%d\nColor: %d\nModel: %d", version.major, version.minor, version.patch, color, model);
text_layer_set_text(text_layer, buffer);
text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(text_layer));
}
static void window_unload(Window *window) {
text_layer_destroy(text_layer);
}
static void init(void) {
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
const bool animated = true;
window_stack_push(window, animated);
}
static void deinit(void) {
window_destroy(window);
}
int main(void) {
init();
app_event_loop();
deinit();
}

View file

View file

@ -0,0 +1,25 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#metadata
# {
# "bb2": true,
# "sdk": true
# }
#/metadata
import harness.test_fixtures
class WatchInfoTestTest(harness.test_fixtures.PebbleAppTest):
''' test for watch_info_test app '''

View file

@ -0,0 +1,33 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target='pebble-app.elf')
if os.path.exists('worker_src'):
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target='pebble-worker.elf')
ctx.pbl_bundle(elf='pebble-app.elf',
worker_elf='pebble-worker.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))
else:
ctx.pbl_bundle(elf='pebble-app.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))

View file

@ -0,0 +1,3 @@
# Ignore build generated files
build

View file

@ -0,0 +1,17 @@
{
"uuid": "77e534af-d663-4f25-942e-c0e64c20f925",
"shortName": "Window Transitions",
"longName": "Window Transitions",
"companyName": "MakeAwesomeHappen",
"versionCode": 1,
"versionLabel": "1.0",
"watchapp": {
"watchface": false
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pebble.h>
static bool s_next_window_fullscreen;
static void unload_handler(Window *window) {
window_destroy(window);
}
static void push_window(void);
static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
push_window();
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
}
static void push_window(void) {
Window *window = window_create();
window_set_fullscreen(window, s_next_window_fullscreen);
window_set_window_handlers(window, (WindowHandlers) {
.unload = unload_handler,
});
window_set_click_config_provider(window, click_config_provider);
s_next_window_fullscreen = !s_next_window_fullscreen;
window_stack_push(window, true);
}
int main(void) {
push_window();
app_event_loop();
}

View file

@ -0,0 +1,24 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target='pebble-app.elf')
ctx.pbl_bundle(elf='pebble-app.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))