mirror of
https://github.com/google/pebble.git
synced 2025-05-20 02:14:57 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
19
src/apps/accelerometer_peek_test/appinfo.json
Normal file
19
src/apps/accelerometer_peek_test/appinfo.json
Normal 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
|
||||
}
|
||||
}
|
103
src/apps/accelerometer_peek_test/src/accelerometer_peek_test.c
Normal file
103
src/apps/accelerometer_peek_test/src/accelerometer_peek_test.c
Normal 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();
|
||||
}
|
61
src/apps/accelerometer_peek_test/wscript
Normal file
61
src/apps/accelerometer_peek_test/wscript
Normal 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 [])
|
17
src/apps/app_heap_demo/appinfo.json
Normal file
17
src/apps/app_heap_demo/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
67
src/apps/app_heap_demo/src/app_heap_demo.c
Normal file
67
src/apps/app_heap_demo/src/app_heap_demo.c
Normal 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();
|
||||
}
|
25
src/apps/app_heap_demo/wscript
Normal file
25
src/apps/app_heap_demo/wscript
Normal 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'))
|
||||
|
23
src/apps/app_messages_test/appinfo.json
Normal file
23
src/apps/app_messages_test/appinfo.json
Normal 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"
|
||||
}
|
131
src/apps/app_messages_test/src/app_message_test.c
Normal file
131
src/apps/app_messages_test/src/app_message_test.c
Normal 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();
|
||||
}
|
30
src/apps/app_messages_test/src/js/pebble-js-app.js
Normal file
30
src/apps/app_messages_test/src/js/pebble-js-app.js
Normal 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"});
|
||||
});
|
||||
|
48
src/apps/app_messages_test/wscript
Normal file
48
src/apps/app_messages_test/wscript
Normal 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')
|
23
src/apps/bg_task_test/appinfo.json
Normal file
23
src/apps/bg_task_test/appinfo.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
193
src/apps/bg_task_test/src/fg.c
Normal file
193
src/apps/bg_task_test/src/fg.c
Normal 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();
|
||||
}
|
144
src/apps/bg_task_test/worker_src/bg.c
Normal file
144
src/apps/bg_task_test/worker_src/bg.c
Normal 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();
|
||||
}
|
||||
|
41
src/apps/bg_task_test/wscript
Normal file
41
src/apps/bg_task_test/wscript
Normal 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'))
|
15
src/apps/ble_demo/appinfo.json
Normal file
15
src/apps/ble_demo/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
187
src/apps/ble_demo/src/ble_demo.c
Normal file
187
src/apps/ble_demo/src/ble_demo.c
Normal 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);
|
||||
}
|
396
src/apps/ble_demo/src/ble_demo_scan.c
Normal file
396
src/apps/ble_demo/src/ble_demo_scan.c
Normal 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;
|
||||
}
|
21
src/apps/ble_demo/src/ble_demo_scan.h
Normal file
21
src/apps/ble_demo/src/ble_demo_scan.h
Normal 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
24
src/apps/ble_demo/wscript
Normal 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'))
|
26
src/apps/complex_animations/appinfo.json
Normal file
26
src/apps/complex_animations/appinfo.json
Normal 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"
|
||||
}
|
231
src/apps/complex_animations/src/complex_animations.c
Normal file
231
src/apps/complex_animations/src/complex_animations.c
Normal 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();
|
||||
}
|
41
src/apps/complex_animations/wscript
Normal file
41
src/apps/complex_animations/wscript
Normal 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
3
src/apps/crash_demo/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
19
src/apps/crash_demo/appinfo.json
Normal file
19
src/apps/crash_demo/appinfo.json
Normal 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"
|
||||
}
|
169
src/apps/crash_demo/src/crash_demo.c
Normal file
169
src/apps/crash_demo/src/crash_demo.c
Normal 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();
|
||||
}
|
41
src/apps/crash_demo/wscript
Normal file
41
src/apps/crash_demo/wscript
Normal 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_watchface/.gitignore
vendored
Normal file
3
src/apps/crash_demo_watchface/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
19
src/apps/crash_demo_watchface/appinfo.json
Normal file
19
src/apps/crash_demo_watchface/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
85
src/apps/crash_demo_watchface/src/crash_demo_watchface.c
Normal file
85
src/apps/crash_demo_watchface/src/crash_demo_watchface.c
Normal 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();
|
||||
}
|
39
src/apps/crash_demo_watchface/wscript
Normal file
39
src/apps/crash_demo_watchface/wscript
Normal 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
3
src/apps/data_logging_spam/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
17
src/apps/data_logging_spam/appinfo.json
Normal file
17
src/apps/data_logging_spam/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
42
src/apps/data_logging_spam/src/data_logging_spam.c
Normal file
42
src/apps/data_logging_spam/src/data_logging_spam.c
Normal 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();
|
||||
}
|
||||
|
24
src/apps/data_logging_spam/wscript
Normal file
24
src/apps/data_logging_spam/wscript
Normal 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'))
|
6
src/apps/delayed_worker_crash/.gitignore
vendored
Normal file
6
src/apps/delayed_worker_crash/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
||||
|
||||
# Ignore waf lock file
|
||||
.lock-waf*
|
19
src/apps/delayed_worker_crash/appinfo.json
Normal file
19
src/apps/delayed_worker_crash/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
90
src/apps/delayed_worker_crash/src/delayed_worker_crash.c
Normal file
90
src/apps/delayed_worker_crash/src/delayed_worker_crash.c
Normal 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();
|
||||
}
|
|
@ -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();
|
||||
}
|
41
src/apps/delayed_worker_crash/wscript
Normal file
41
src/apps/delayed_worker_crash/wscript
Normal 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
3
src/apps/dictation_demo/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
19
src/apps/dictation_demo/appinfo.json
Normal file
19
src/apps/dictation_demo/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
130
src/apps/dictation_demo/src/dictation_demo.c
Normal file
130
src/apps/dictation_demo/src/dictation_demo.c
Normal 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);
|
||||
}
|
41
src/apps/dictation_demo/wscript
Normal file
41
src/apps/dictation_demo/wscript
Normal 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'))
|
18
src/apps/draw_lines/appinfo.json
Normal file
18
src/apps/draw_lines/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
92
src/apps/draw_lines/src/draw_lines.c
Normal file
92
src/apps/draw_lines/src/draw_lines.c
Normal 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();
|
||||
}
|
41
src/apps/draw_lines/wscript
Normal file
41
src/apps/draw_lines/wscript
Normal 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
6
src/apps/draw_text/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
||||
|
||||
# Ignore waf lock file
|
||||
.lock-waf*
|
18
src/apps/draw_text/appinfo.json
Normal file
18
src/apps/draw_text/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
71
src/apps/draw_text/src/draw_text.c
Normal file
71
src/apps/draw_text/src/draw_text.c
Normal 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();
|
||||
}
|
41
src/apps/draw_text/wscript
Normal file
41
src/apps/draw_text/wscript
Normal 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
6
src/apps/fill_radial/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
||||
|
||||
# Ignore waf lock file
|
||||
.lock-waf*
|
18
src/apps/fill_radial/appinfo.json
Normal file
18
src/apps/fill_radial/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
100
src/apps/fill_radial/src/fill_radial.c
Normal file
100
src/apps/fill_radial/src/fill_radial.c
Normal 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();
|
||||
}
|
41
src/apps/fill_radial/wscript
Normal file
41
src/apps/fill_radial/wscript
Normal 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
3
src/apps/fps_test/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
17
src/apps/fps_test/appinfo.json
Normal file
17
src/apps/fps_test/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
263
src/apps/fps_test/src/fps_test.c
Normal file
263
src/apps/fps_test/src/fps_test.c
Normal 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);
|
||||
}
|
1904
src/apps/fps_test/src/test_bitmaps.h
Normal file
1904
src/apps/fps_test/src/test_bitmaps.h
Normal file
File diff suppressed because it is too large
Load diff
33
src/apps/fps_test/wscript
Normal file
33
src/apps/fps_test/wscript
Normal 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'))
|
18
src/apps/health_api_test/appinfo.json
Normal file
18
src/apps/health_api_test/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
1246
src/apps/health_api_test/src/health_api_test.c
Normal file
1246
src/apps/health_api_test/src/health_api_test.c
Normal file
File diff suppressed because it is too large
Load diff
41
src/apps/health_api_test/wscript
Normal file
41
src/apps/health_api_test/wscript
Normal 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'))
|
19
src/apps/launch_reason_demo/appinfo.json
Normal file
19
src/apps/launch_reason_demo/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
97
src/apps/launch_reason_demo/src/launch_reason_demo.c
Normal file
97
src/apps/launch_reason_demo/src/launch_reason_demo.c
Normal 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();
|
||||
}
|
41
src/apps/launch_reason_demo/wscript
Normal file
41
src/apps/launch_reason_demo/wscript
Normal 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/localtime_gmtime_test/.gitignore
vendored
Normal file
3
src/apps/localtime_gmtime_test/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
22
src/apps/localtime_gmtime_test/appinfo.json
Normal file
22
src/apps/localtime_gmtime_test/appinfo.json
Normal 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"
|
||||
}
|
94
src/apps/localtime_gmtime_test/src/localtime_gmtime_test.c
Normal file
94
src/apps/localtime_gmtime_test/src/localtime_gmtime_test.c
Normal 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();
|
||||
}
|
41
src/apps/localtime_gmtime_test/wscript
Normal file
41
src/apps/localtime_gmtime_test/wscript
Normal 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/startup_crash_demo/.gitignore
vendored
Normal file
3
src/apps/startup_crash_demo/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
14
src/apps/startup_crash_demo/appinfo.json
Normal file
14
src/apps/startup_crash_demo/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
25
src/apps/startup_crash_demo/src/crash_demo.c
Normal file
25
src/apps/startup_crash_demo/src/crash_demo.c
Normal 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();
|
||||
}
|
||||
|
24
src/apps/startup_crash_demo/wscript
Normal file
24
src/apps/startup_crash_demo/wscript
Normal 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
3
src/apps/strftime_test/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
22
src/apps/strftime_test/appinfo.json
Normal file
22
src/apps/strftime_test/appinfo.json
Normal 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"
|
||||
}
|
326
src/apps/strftime_test/src/strftime_test.c
Normal file
326
src/apps/strftime_test/src/strftime_test.c
Normal 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();
|
||||
}
|
41
src/apps/strftime_test/wscript
Normal file
41
src/apps/strftime_test/wscript
Normal 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
3
src/apps/timer_starve/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
17
src/apps/timer_starve/appinfo.json
Normal file
17
src/apps/timer_starve/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
57
src/apps/timer_starve/src/timer_starve.c
Normal file
57
src/apps/timer_starve/src/timer_starve.c
Normal 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();
|
||||
}
|
||||
|
24
src/apps/timer_starve/wscript
Normal file
24
src/apps/timer_starve/wscript
Normal 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
3
src/apps/watch_info_test/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
17
src/apps/watch_info_test/appinfo.json
Normal file
17
src/apps/watch_info_test/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
61
src/apps/watch_info_test/src/watch_info_test.c
Normal file
61
src/apps/watch_info_test/src/watch_info_test.c
Normal 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();
|
||||
}
|
0
src/apps/watch_info_test/tests/.tests
Normal file
0
src/apps/watch_info_test/tests/.tests
Normal file
25
src/apps/watch_info_test/tests/watch_info_test_test.py
Normal file
25
src/apps/watch_info_test/tests/watch_info_test_test.py
Normal 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 '''
|
33
src/apps/watch_info_test/wscript
Normal file
33
src/apps/watch_info_test/wscript
Normal 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'))
|
3
src/apps/window_transitions/.gitignore
vendored
Normal file
3
src/apps/window_transitions/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Ignore build generated files
|
||||
build
|
17
src/apps/window_transitions/appinfo.json
Normal file
17
src/apps/window_transitions/appinfo.json
Normal 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": []
|
||||
}
|
||||
}
|
52
src/apps/window_transitions/src/window_transitions.c
Normal file
52
src/apps/window_transitions/src/window_transitions.c
Normal 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();
|
||||
}
|
24
src/apps/window_transitions/wscript
Normal file
24
src/apps/window_transitions/wscript
Normal 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'))
|
Loading…
Add table
Add a link
Reference in a new issue