Import of the watch repository from Pebble

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

View file

@ -0,0 +1,18 @@
{
"uuid": "cf1e816a-9db0-4511-bbb8-f60c48ca8fac",
"shortName": "Golf",
"longName": "Golf",
"companyName": "MakeAwesomeHappen",
"versionCode": 1.0,
"versionLabel": "1.0",
"watchapp": {
"watchface": false,
"onlyShownOnCommunication": true
},
"appKeys": {
"dummy": 0
},
"resources": {
"media": []
}
}

395
stored_apps/golf/src/golf.c Normal file
View file

@ -0,0 +1,395 @@
/*
* 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.
*/
//! The app is driven by pebble protocol app_messages, used indirectly through app_sync.
#include <pebble.h>
#include "golf_resources.h"
//! TODO: Fixme once i18n support is available for 3rd party apps
#define i18n_get(a, b) a
#define i18n_free_all(data)
enum {
GOLF_FRONT_KEY = 0x0, // TUPLE_CSTRING
GOLF_MID_KEY = 0x1, // TUPLE_CSTRING
GOLF_BACK_KEY = 0x2, // TUPLE_CSTRING
GOLF_HOLE_KEY = 0x3, // TUPLE_CSTRING
GOLF_PAR_KEY = 0x4, // TUPLE_CSTRING
GOLF_CMD_KEY = 0x5, // TUPLE_INTEGER
};
enum {
CMD_PREV = 0x01,
CMD_NEXT = 0x02,
CMD_SELECT = 0x03,
};
typedef enum {
TextBack = 0,
TextMid,
TextFront,
TextParLabel,
TextPar,
TextHoleLabel,
TextHole,
NumTextIdx
} TextIdx;
const int KEY_TO_TEXT_IDX[] = {
[GOLF_FRONT_KEY] = TextFront,
[GOLF_MID_KEY] = TextMid,
[GOLF_BACK_KEY] = TextBack,
[GOLF_HOLE_KEY] = TextHole,
[GOLF_PAR_KEY] = TextPar
};
typedef struct {
Window *window;
ActionBarLayer *action_bar;
StatusBarLayer *status_layer;
GBitmap *up_bitmap;
GBitmap *down_bitmap;
GBitmap *click_bitmap;
Layer *background;
TextLayer *text_layers[NumTextIdx];
TextLayer *disconnected_text;
uint8_t sync_buffer[60];
AppSync sync;
} AppData;
static AppData s_data;
static void bluetooth_status_callback(bool connected) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Golf bluetooth connection status: %d", connected);
AppData *data = &s_data;
TextLayer **text = &data->text_layers[0];
#if PBL_ROUND
layer_set_hidden(data->background, !connected);
layer_set_hidden(action_bar_layer_get_layer(data->action_bar), !connected);
#endif
layer_set_hidden(text_layer_get_layer(data->disconnected_text), connected);
if (!connected) {
// blank out text if we have no up-to-date data
text_layer_set_text(text[TextBack], NULL);
text_layer_set_text(text[TextMid], NULL);
text_layer_set_text(text[TextFront], NULL);
text_layer_set_text(text[TextPar], "-");
text_layer_set_text(text[TextHole], "-");
} else {
// Return text to normal size. Display '...' while waiting for updated data.
text_layer_set_text(text[TextMid], "...");
}
}
static void sync_error_callback(DictionaryResult dict_error, AppMessageResult app_message_error,
void *context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Golf sync error! dict: %u, app msg: %u", dict_error,
app_message_error);
}
static void sync_tuple_changed_callback(const uint32_t key, const Tuple *new_tuple,
const Tuple *old_tuple, void *context) {
AppData *data = context;
TextLayer **text = &data->text_layers[0];
switch (key) {
case GOLF_BACK_KEY:
case GOLF_MID_KEY:
case GOLF_FRONT_KEY:
case GOLF_HOLE_KEY:
case GOLF_PAR_KEY:
text_layer_set_text(text[KEY_TO_TEXT_IDX[key]], new_tuple->value->cstring);
default:
// Unknown key
return;
}
}
static void send_golf_cmd(uint8_t cmd) {
Tuplet value = TupletInteger(GOLF_CMD_KEY, cmd);
DictionaryIterator *iter;
app_message_outbox_begin(&iter);
if (iter == NULL)
return;
dict_write_tuplet(iter, &value);
dict_write_end(iter);
app_message_outbox_send();
}
static void up_click_handler(ClickRecognizerRef recognizer, AppData *data) {
send_golf_cmd(CMD_PREV);
}
static void down_click_handler(ClickRecognizerRef recognizer, AppData *data) {
send_golf_cmd(CMD_NEXT);
}
static void select_click_handler(ClickRecognizerRef recognizer, AppData *data) {
send_golf_cmd(CMD_SELECT);
}
static void config_provider(AppData *data) {
window_single_click_subscribe(BUTTON_ID_UP, (ClickHandler) up_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, (ClickHandler) down_click_handler);
window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler) select_click_handler);
}
static void window_unload(Window *window) {
AppData *data = &s_data;
app_sync_deinit(&data->sync);
}
// Used to draw lines to contain 'hole' and 'par' sections.
static void draw_dotted_line(GContext *ctx, GPoint p0, uint16_t length, bool is_vertical) {
bool even = (p0.x + p0.y) % 2 == 0;
const GPoint delta = is_vertical ? GPoint(0, 1) : GPoint(1, 0);
while (length >= 1) {
if (even) {
graphics_draw_pixel(ctx, p0);
}
even = !even;
p0.x += delta.x;
p0.y += delta.y;
--length;
}
}
static void background_update_proc(Layer *layer, GContext *ctx) {
GRect bounds = layer_get_bounds(layer);
// Draw lines to contain 'hole' and 'par' sections.
// Magic numbers measured from design spec
const int16_t vertical_divider_height = PBL_IF_ROUND_ELSE(107, 50);
const int16_t horizontal_divider_width = PBL_IF_ROUND_ELSE(51, bounds.size.w - ACTION_BAR_WIDTH);
const int16_t vertical_divider_x_offset = PBL_IF_ROUND_ELSE(72, horizontal_divider_width / 2);
const int16_t horizontal_divider_x_offset = PBL_IF_ROUND_ELSE(vertical_divider_x_offset, 0);
const int16_t vertical_divider_y_offset = PBL_IF_ROUND_ELSE(
37, bounds.size.h - vertical_divider_height);
const uint16_t horizontal_divider_y_offset = PBL_IF_ROUND_ELSE(
vertical_divider_y_offset + (vertical_divider_height / 2), vertical_divider_y_offset);
graphics_context_set_stroke_color(ctx, GColorBlack);
draw_dotted_line(ctx, GPoint(horizontal_divider_x_offset, horizontal_divider_y_offset),
horizontal_divider_width, false /* is_vertical */);
draw_dotted_line(ctx, GPoint(vertical_divider_x_offset, vertical_divider_y_offset),
vertical_divider_height, true /* is_vertical */);
}
static void prv_setup_text_layer(TextLayer *text_layer, const char *font_key, const char *text,
GTextAlignment alignment) {
text_layer_set_font(text_layer, fonts_get_system_font(font_key));
text_layer_set_text(text_layer, text);
text_layer_set_text_alignment(text_layer, alignment);
text_layer_set_text_color(text_layer, GColorBlack);
text_layer_set_background_color(text_layer, GColorClear);
}
static void window_load(Window *window) {
AppData *data = &s_data;
// Action bar icon bitmaps.
data->up_bitmap = gbitmap_create_from_png_data(s_golf_api_up_icon_png_data,
sizeof(s_golf_api_up_icon_png_data));
data->down_bitmap = gbitmap_create_from_png_data(s_golf_api_down_icon_png_data,
sizeof(s_golf_api_down_icon_png_data));
data->click_bitmap = gbitmap_create_from_png_data(s_golf_api_click_icon_png_data,
sizeof(s_golf_api_click_icon_png_data));
// Set up UI here
Layer *window_layer = window_get_root_layer(window);
TextLayer **text = &data->text_layers[0];
const GRect window_bounds = layer_get_bounds(window_layer);
const int16_t background_width = window_bounds.size.w - PBL_IF_RECT_ELSE(ACTION_BAR_WIDTH, 0);
data->background = layer_create(window_bounds);
Layer *background = data->background;
layer_set_update_proc(background, &background_update_proc);
layer_add_child(window_layer, background);
// Set up the action bar.
data->action_bar = action_bar_layer_create();
action_bar_layer_set_context(data->action_bar, data);
action_bar_layer_set_icon(data->action_bar, BUTTON_ID_UP, data->up_bitmap);
action_bar_layer_set_icon(data->action_bar, BUTTON_ID_SELECT, data->click_bitmap);
action_bar_layer_set_icon(data->action_bar, BUTTON_ID_DOWN, data->down_bitmap);
action_bar_layer_set_click_config_provider(data->action_bar,
(ClickConfigProvider) config_provider);
action_bar_layer_set_icon_press_animation(data->action_bar,
BUTTON_ID_UP,
ActionBarLayerIconPressAnimationMoveUp);
action_bar_layer_set_icon_press_animation(data->action_bar,
BUTTON_ID_DOWN,
ActionBarLayerIconPressAnimationMoveDown);
action_bar_layer_add_to_window(data->action_bar, data->window);
data->status_layer = status_bar_layer_create();
// Change the status bar width to make space for the action bar
const GRect status_frame = GRect(0, 0, background_width, STATUS_BAR_LAYER_HEIGHT);
layer_set_frame(status_bar_layer_get_layer(data->status_layer), status_frame);
status_bar_layer_set_colors(data->status_layer, GColorClear, GColorBlack);
#if PBL_RECT
status_bar_layer_set_separator_mode(data->status_layer, StatusBarLayerSeparatorModeDotted);
#endif
layer_add_child(background, status_bar_layer_get_layer(data->status_layer));
// labels
const char * const font_key_label = FONT_KEY_GOTHIC_09;
// back, mid, front numbers
const char * const font_key_small_numbers = PBL_IF_ROUND_ELSE(FONT_KEY_LECO_20_BOLD_NUMBERS,
FONT_KEY_LECO_28_LIGHT_NUMBERS);
const char * const font_key_accent_numbers = PBL_IF_ROUND_ELSE(FONT_KEY_LECO_20_BOLD_NUMBERS,
FONT_KEY_LECO_38_BOLD_NUMBERS);
// hole, par numbers
const char * const font_key_large_numbers = PBL_IF_ROUND_ELSE(FONT_KEY_LECO_32_BOLD_NUMBERS,
FONT_KEY_LECO_38_BOLD_NUMBERS);
// "disconnected" text
const char * const font_key_disconnected = FONT_KEY_GOTHIC_24_BOLD;
static const GTextAlignment distance_text_alignment = PBL_IF_ROUND_ELSE(GTextAlignmentRight,
GTextAlignmentCenter);
// text heights only used for setting text box height, not for layout
const int16_t label_height = 10;
const int16_t small_numbers_height = 30;
const int16_t accent_numbers_height = 40;
const int16_t large_numbers_height = 40;
const int16_t disconnected_text_height = 24;
// magic numbers measured from design spec
const int16_t distance_column_x_offset = 0;
const int16_t distance_column_width = PBL_IF_ROUND_ELSE(63, background_width);
const int16_t back_value_y_offset = STATUS_BAR_LAYER_HEIGHT + PBL_IF_ROUND_ELSE(24, 0);
const int16_t mid_value_y_offset = back_value_y_offset + PBL_IF_ROUND_ELSE(30, 26);
const int16_t front_value_y_offset = mid_value_y_offset + PBL_IF_ROUND_ELSE(30, 40);
const int16_t disconnected_text_y_offset = mid_value_y_offset + PBL_IF_ROUND_ELSE(-5, 8);
const int16_t stroke_box_width = PBL_IF_ROUND_ELSE(54, background_width / 2);
const int16_t stroke_box_height = PBL_IF_ROUND_ELSE(53, 50);
const int16_t hole_box_x_offset = PBL_IF_ROUND_ELSE(73, 0);
const int16_t hole_label_y_offset = STATUS_BAR_LAYER_HEIGHT + PBL_IF_ROUND_ELSE(18, 104);
const int16_t hole_value_y_offset = hole_label_y_offset + PBL_IF_ROUND_ELSE(5, 2);
const int16_t par_box_x_offset = hole_box_x_offset + PBL_IF_ROUND_ELSE(0, stroke_box_width);
const int16_t par_label_y_offset = hole_label_y_offset + PBL_IF_ROUND_ELSE(stroke_box_height, 0);
const int16_t par_value_y_offset = hole_value_y_offset + PBL_IF_ROUND_ELSE(stroke_box_height, 0);
// Hole label.
text[TextHoleLabel] = text_layer_create(GRect(hole_box_x_offset, hole_label_y_offset,
stroke_box_width, label_height));
prv_setup_text_layer(text[TextHoleLabel], font_key_label,
i18n_get("HOLE", data), GTextAlignmentCenter);
layer_add_child(background, text_layer_get_layer(text[TextHoleLabel]));
// Hole value.
text[TextHole] = text_layer_create(GRect(hole_box_x_offset, hole_value_y_offset,
stroke_box_width, large_numbers_height));
prv_setup_text_layer(text[TextHole], font_key_large_numbers, NULL, GTextAlignmentCenter);
layer_add_child(background, text_layer_get_layer(text[TextHole]));
// Par label.
text[TextParLabel] = text_layer_create(GRect(par_box_x_offset, par_label_y_offset,
stroke_box_width, label_height));
prv_setup_text_layer(text[TextParLabel], font_key_label, i18n_get("PAR", data),
GTextAlignmentCenter);
layer_add_child(background, text_layer_get_layer(text[TextParLabel]));
// Par value.
text[TextPar] = text_layer_create(GRect(par_box_x_offset, par_value_y_offset,
stroke_box_width, large_numbers_height));
prv_setup_text_layer(text[TextPar], font_key_large_numbers, NULL, GTextAlignmentCenter);
layer_add_child(background, text_layer_get_layer(text[TextPar]));
// Back value.
text[TextBack] = text_layer_create(GRect(distance_column_x_offset, back_value_y_offset,
distance_column_width, small_numbers_height));
prv_setup_text_layer(text[TextBack], font_key_small_numbers, NULL, distance_text_alignment);
layer_add_child(background, text_layer_get_layer(text[TextBack]));
// Mid value.
text[TextMid] = text_layer_create(GRect(distance_column_x_offset, mid_value_y_offset,
distance_column_width, accent_numbers_height));
prv_setup_text_layer(text[TextMid], font_key_accent_numbers, NULL, distance_text_alignment);
layer_add_child(background, text_layer_get_layer(text[TextMid]));
// Front value.
text[TextFront] = text_layer_create(GRect(distance_column_x_offset, front_value_y_offset,
distance_column_width, small_numbers_height));
prv_setup_text_layer(text[TextFront], font_key_small_numbers, NULL, distance_text_alignment);
layer_add_child(background, text_layer_get_layer(text[TextFront]));
// Disconnected text.
data->disconnected_text = text_layer_create(GRect(0, disconnected_text_y_offset,
background_width, disconnected_text_height));
prv_setup_text_layer(data->disconnected_text, font_key_disconnected,
i18n_get("Disconnected", data), GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(data->disconnected_text));
layer_set_hidden(text_layer_get_layer(data->disconnected_text), true);
// Sync setup:
Tuplet initial_values[] = {
TupletCString(GOLF_PAR_KEY, "0"),
TupletCString(GOLF_HOLE_KEY, "0"),
TupletCString(GOLF_BACK_KEY, "000"),
TupletCString(GOLF_MID_KEY, "000"),
TupletCString(GOLF_FRONT_KEY, "000"),
};
app_sync_init(&data->sync, data->sync_buffer, sizeof(data->sync_buffer), initial_values,
ARRAY_LENGTH(initial_values), sync_tuple_changed_callback, sync_error_callback,
data);
}
static void push_window(AppData *data) {
data->window = window_create();
Window *window = data->window;
window_set_user_data(window, data);
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
window_set_click_config_provider_with_context(window, (ClickConfigProvider) config_provider,
data);
window_set_background_color(window, PBL_IF_COLOR_ELSE(GColorMintGreen, GColorWhite));
window_stack_push(window, true);
}
static void handle_init(void) {
app_message_open(64, 16);
push_window(&s_data);
// overall reduce the sniff-mode latency at the expense of some power...
app_comm_set_sniff_interval(SNIFF_INTERVAL_REDUCED);
ConnectionHandlers handlers = {
.pebble_app_connection_handler = NULL,
.pebblekit_connection_handler = bluetooth_status_callback
};
connection_service_subscribe(handlers);
}
////////////////////
// App boilerplate
int main(void) {
handle_init();
app_event_loop();
}

View file

@ -0,0 +1,54 @@
/*
* 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
static const uint8_t s_golf_api_click_icon_png_data[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0e, 0x02, 0x03, 0x00, 0x00, 0x00, 0x62, 0x26, 0xc5,
0x42, 0x00, 0x00, 0x00, 0x0c, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0x38, 0xc0, 0x3b, 0xa8, 0x00, 0x00, 0x00, 0x04, 0x74, 0x52, 0x4e,
0x53, 0x00, 0xaa, 0xff, 0x55, 0xef, 0x59, 0xe6, 0x25, 0x00, 0x00, 0x00, 0x34, 0x49, 0x44, 0x41,
0x54, 0x78, 0xda, 0x63, 0x60, 0x88, 0x0a, 0x60, 0x60, 0xe0, 0x5b, 0xb5, 0x9a, 0x81, 0xc1, 0x6a,
0xd5, 0xaa, 0x03, 0x0c, 0x5a, 0xab, 0x56, 0x35, 0x30, 0x64, 0xad, 0x5a, 0x35, 0x01, 0x42, 0xac,
0x5a, 0xb5, 0x6a, 0x01, 0x84, 0x40, 0x88, 0x81, 0x95, 0x80, 0x15, 0x83, 0xb5, 0x81, 0x0c, 0x00,
0x00, 0xb3, 0x7e, 0x1b, 0x27, 0x9c, 0x43, 0x2d, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e,
0x44, 0xae, 0x42, 0x60, 0x82,
};
static const uint8_t s_golf_api_up_icon_png_data[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x07, 0x02, 0x03, 0x00, 0x00, 0x00, 0x41, 0xdc, 0x44,
0xb7, 0x00, 0x00, 0x00, 0x0c, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0x38, 0xc0, 0x3b, 0xa8, 0x00, 0x00, 0x00, 0x04, 0x74, 0x52, 0x4e,
0x53, 0x00, 0xaa, 0x55, 0xff, 0x8d, 0xb2, 0xf8, 0xb0, 0x00, 0x00, 0x00, 0x25, 0x49, 0x44, 0x41,
0x54, 0x78, 0xda, 0x63, 0x60, 0x90, 0x60, 0x60, 0x60, 0xd8, 0xc3, 0xc0, 0xc0, 0xf4, 0x9f, 0x81,
0x81, 0xfb, 0xbf, 0x03, 0x03, 0xff, 0xff, 0x0b, 0x0c, 0xf2, 0xff, 0xbf, 0x30, 0xd4, 0xff, 0xff,
0x0b, 0x00, 0x58, 0x99, 0x09, 0x8b, 0xcd, 0x9c, 0x7f, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
};
static const uint8_t s_golf_api_down_icon_png_data[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x07, 0x02, 0x03, 0x00, 0x00, 0x00, 0x41, 0xdc, 0x44,
0xb7, 0x00, 0x00, 0x00, 0x0c, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
0x00, 0x00, 0xff, 0xff, 0xff, 0x05, 0x00, 0x64, 0xc2, 0x00, 0x00, 0x00, 0x04, 0x74, 0x52, 0x4e,
0x53, 0xaa, 0xff, 0x00, 0x55, 0x34, 0xdc, 0x0e, 0x6d, 0x00, 0x00, 0x00, 0x25, 0x49, 0x44, 0x41,
0x54, 0x78, 0xda, 0x63, 0x10, 0x0d, 0x0d, 0x61, 0x68, 0x0d, 0x0d, 0x62, 0x58, 0x18, 0xea, 0xc5,
0xb0, 0x22, 0x34, 0x8b, 0x61, 0x95, 0xe8, 0x2b, 0x86, 0x55, 0xed, 0xab, 0x18, 0x56, 0xad, 0x5b,
0x05, 0x00, 0x77, 0x5b, 0x0a, 0x18, 0x68, 0x2f, 0xcf, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
};

44
stored_apps/pebble_app.ld Normal file
View file

@ -0,0 +1,44 @@
ENTRY(main)
MEMORY
{
APP (rwx) : ORIGIN = 0, LENGTH = 24K
}
SECTIONS
{
.header :
{
KEEP(*(.pbl_header))
} > APP
.text :
{
*(.text)
*(.text.*)
*(.rodata)
*(.rodata*)
} > APP
.data :
{
KEEP(*(.data))
*(.data.*)
} > APP
.bss :
{
*(.bss)
*(.bss.*)
} > APP
DISCARD :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
*(.eh_frame)
}
}

135
stored_apps/wscript Normal file
View file

@ -0,0 +1,135 @@
import os
import time
import waflib
import waflib.Tools
import waftools.objcopy as objcopy
import waftools.pebble_sdk_gcc as pebble_sdk_gcc
from resources.types.resource_definition import ResourceDefinition
from resources.types.resource_object import ResourceObject
from pebble_sdk_platform import pebble_platforms, maybe_import_internal
from pebble_sdk_version import set_env_sdk_version
import generate_appinfo
#
# Make each of the apps within the stored_apps directory
#
# -----------------------------------------------------------------------------------
def configure(conf):
process_info = conf.path.parent.find_node('src/fw/process_management/pebble_process_info.h')
set_env_sdk_version(conf, process_info)
pebble_sdk_gcc.configure(conf)
conf.env.append_value('DEFINES', 'RELEASE')
# -----------------------------------------------------------------------------------
def build_app(bld, app_name):
sdk_folder = bld.path.parent.get_bld().make_node('sdk').make_node(bld.env.PLATFORM_NAME)
# -----------------------------------------------------------------------------------
# Generate the appinfo.auto.c file
appinfo_json_node = bld.path.get_src().find_node('%s/appinfo.json' % (app_name))
if appinfo_json_node is None:
bld.fatal('Could not find appinfo.json')
appinfo_c_node = bld.path.get_bld().make_node('%s/appinfo.auto.c' % (app_name))
resource_ids_auto_node = bld.path.get_bld().make_node(
'%s/src/resource_ids.auto.h' % (app_name))
bld(rule='echo "#define DEFAULT_MENU_ICON 0" >> ${TGT}', target=resource_ids_auto_node)
message_keys_auto_node = bld.path.get_bld().make_node(app_name).make_node("message_keys.auto.h")
bld(rule='touch ${TGT}', target=message_keys_auto_node)
def _generate_appinfo_c_file_rule(task):
generate_appinfo.generate_appinfo(task.inputs[0].abspath(), task.outputs[0].abspath())
bld(rule=_generate_appinfo_c_file_rule,
source=appinfo_json_node,
target=appinfo_c_node)
# -----------------------------------------------------------------------------------
# Generate the rule to compile and link the sources into an ELF
includes = [sdk_folder.make_node('include'), app_name, '%s/src' % (app_name)]
link_flags = ['-mcpu=cortex-m3','-mthumb','-fPIE', '-Wl,--emit-relocs']
source = bld.path.ant_glob('%s/src/**/*.c' % (app_name)) + [appinfo_c_node]
ld_script = bld.path.make_node('pebble_app.ld').path_from(bld.path)
app_elf_file = bld.path.get_bld().make_node('%s/pebble-app.elf' % (app_name))
stored_apps_env = bld.all_envs['stored_apps']
maybe_import_internal(bld.env)
# Fetch platform-specific defines and add them to CFLAGS
platform_name = stored_apps_env['PLATFORM_NAME']
platform_info = pebble_platforms.get(platform_name, None)
if platform_info is None:
bld.fatal("Unsupported platform: %s" % platform_name)
platform_defines = platform_info['DEFINES']
c_flags = ['-fPIE'] + ['-D%s' % define for define in platform_defines]
gen = bld.program(env=stored_apps_env,
source=source,
target=app_elf_file,
includes=includes,
cflags=c_flags,
ldscript=ld_script,
linkflags=link_flags,
stlibpath=[sdk_folder.make_node('lib').abspath()],
stlib=['pebble'])
# -----------------------------------------------------------------------------------
# Create the bin file and inject the metadata
app_raw_bin_file = bld.path.get_bld().make_node('%s/pebble-app.raw.bin' % (app_name))
bld(rule=objcopy.objcopy_bin, source=app_elf_file, target=app_raw_bin_file)
app_bin_file = bld.path.get_bld().make_node('%s/pebble-app.bin' % (app_name))
# Use a dummy timestamp for the metadata in stored_apps. This timestamp is only used
# to describe the resource version, and stored_apps have no resources. If we use the
# real timestamp that will cause the CRC of the app to change from build to build,
# which causes the pbpack's CRC to change from build to build, which means we have to
# keep using image_resources in development every time we rebuild, even though the
# content didn't change.
pebble_sdk_gcc.gen_inject_metadata_rule(bld, src_bin_file=app_raw_bin_file,
dst_bin_file=app_bin_file, elf_file=app_elf_file,
resource_file=None, timestamp=0, has_pkjs=False,
has_worker=False)
# -----------------------------------------------------------------------------------
# Copy into the resources directory as a .reso file
def _make_reso(task):
app_bin_data = task.inputs[0].read(flags='rb')
reso = ResourceObject(
ResourceDefinition('raw', 'STORED_APP_{}'.format(app_name.upper()), None),
app_bin_data)
reso.dump(task.outputs[0])
resources_bld_node = bld.bldnode.make_node('resources/')
app_resource_node = resources_bld_node.make_node('normal/base/raw/app_%s.bin.reso' % (app_name))
bld(rule=_make_reso, source=app_bin_file, target=app_resource_node, name='stored_app_reso')
bld.DYNAMIC_RESOURCES.append(app_resource_node)
# -----------------------------------------------------------------------------------
def build(bld):
# When you add a new stored app, you must also do the following to include it into the
# system resources and register it with the launcher:
# 1.) Add a raw resource entry with a name of "STORED_APP_<appname>" to
# resources/normal/resource_map.json.
# The "file" field should be set to normal/raw/app_<appname>.bin
# 2.) Add a new entry to the INIT_STORED_APPS array in system_app_registry.h
apps = bld.path.ant_glob('*', dir=True, src=False)
for app in apps:
build_app(bld, app.name)
# vim:filetype=python