Skip to content

Commit b7fb43e

Browse files
committed
Test and update C tutorial part 5
Signed-off-by: C-D-Lewis <[email protected]>
1 parent 222bc34 commit b7fb43e

File tree

1 file changed

+248
-15
lines changed
  • source/tutorials/watchface-tutorial

1 file changed

+248
-15
lines changed

source/tutorials/watchface-tutorial/part5.md

Lines changed: 248 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ when their watch connects or disconnects. This can be useful to know when the
3030
watch is out of range and notifications will not be received, or to let the user
3131
know that they might have walked off somewhere without their phone.
3232

33-
This section continues from
34-
[*Part 4*](/tutorials/watchface-tutorial/part4), so be sure to
35-
re-use your code or start with that finished project.
33+
To continue from the last part, you can either modify your existing Pebble
34+
project or create a new one, using the code from the end of the last tutorial
35+
as a starting point. Don't forget also to include changes to `package.json`.
3636

3737
In a similar manner to both the ``TickTimerService`` and
3838
``BatteryStateService``, the events associated with the Bluetooth connection are
@@ -60,16 +60,16 @@ hidden when reconnected. Save the image below for use in this project:
6060

6161
<img style="background-color: #CCCCCC;" src="/assets/images/tutorials/intermediate/bt-icon.png"</img>
6262

63-
Add this icon to your project by copying the above icon image to the `resources`
64-
project directory, and adding a new JSON object to the `media` array in
65-
`package.json` such as the following:
63+
Add this icon to your project by copying the above icon image to the
64+
`/resources/images` project directory, and adding a new JSON object to the
65+
`media` array in `package.json` such as the following:
6666

6767
```js
6868
{
6969
"type": "bitmap",
7070
"name": "IMAGE_BT_ICON",
71-
"file": "bt-icon.png"
72-
},
71+
"file": "images/bt-icon.png"
72+
}
7373
```
7474

7575
This icon will be loaded into the app as a ``GBitmap`` for display in a
@@ -122,7 +122,7 @@ static void bluetooth_callback(bool connected) {
122122
123123
Upon initialization, the app will display the icon unless a re-connection event
124124
occurs, and the current state is evaluated. Manually call the handler in
125-
`main_window_load()` to display the correct initial state:
125+
`init()` to display the correct initial state:
126126
127127
```c
128128
// Show the correct state of the BT connection from the start
@@ -131,15 +131,248 @@ bluetooth_callback(connection_service_peek_pebble_app_connection());
131131

132132
With this last feature in place, running the app and disconnecting the Bluetooth
133133
connection will cause the new indicator to appear, and the watch to vibrate
134-
twice.
134+
twice. It may take a few seconds for the watch to register the disconnection.
135135

136136
![bt >{pebble-screenshot,pebble-screenshot--steel-black}](/images/tutorials/intermediate/bt.png)
137137

138-
You can see the finished project source code in
139-
[this GitHub Gist](https://gist.github.com/pebble-gists/ddd15cbe8b0986fda407).
140138

139+
## Conclusion
140+
141+
Now our watchface shows the watch's remaining battery level! It's discreet,
142+
but very useful.
143+
144+
As usual, you can compare your code to the example code provided below.
145+
146+
> The JS code file remains unchanged from the last part of the tutorial.
147+
148+
<details>
149+
<summary>View C code</summary>
150+
{% markdown %}
151+
```c
152+
#include <pebble.h>
153+
154+
static Window *s_main_window;
155+
static TextLayer *s_time_layer;
156+
static BitmapLayer *s_background_layer, *s_bt_icon_layer;
157+
static TextLayer *s_weather_layer;
158+
static Layer *s_battery_layer;
159+
160+
static GFont s_time_font;
161+
static GFont s_weather_font;
162+
static GBitmap *s_background_bitmap, *s_bt_icon_bitmap;
163+
164+
static int s_battery_level;
165+
166+
static void update_time() {
167+
// Get a tm structure
168+
time_t temp = time(NULL);
169+
struct tm *tick_time = localtime(&temp);
170+
171+
// Write the current hours and minutes into a buffer
172+
static char s_buffer[8];
173+
strftime(s_buffer, sizeof(s_buffer), clock_is_24h_style() ?
174+
"%H:%M" : "%I:%M", tick_time);
175+
176+
// Display this time on the TextLayer
177+
text_layer_set_text(s_time_layer, s_buffer);
178+
}
179+
180+
static void battery_update_proc(Layer *layer, GContext *ctx) {
181+
GRect bounds = layer_get_bounds(layer);
182+
183+
// Find the width of the bar (total width = 114px)
184+
int width = (s_battery_level * 114) / 100;
185+
186+
// Draw the background
187+
graphics_context_set_fill_color(ctx, GColorBlack);
188+
graphics_fill_rect(ctx, bounds, 0, GCornerNone);
189+
190+
// Draw the bar
191+
graphics_context_set_fill_color(ctx, GColorWhite);
192+
graphics_fill_rect(ctx, GRect(0, 0, width, bounds.size.h), 0, GCornerNone);
193+
}
141194

142-
## What's Next?
195+
static void main_window_load(Window *window) {
196+
// Get information about the Window
197+
Layer *window_layer = window_get_root_layer(window);
198+
GRect bounds = layer_get_bounds(window_layer);
143199

144-
Now that you've successfully built a feature rich watchface, it's time to
145-
[publish it](/guides/appstore-publishing/publishing-an-app/)!
200+
// Create GBitmap
201+
s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BACKGROUND);
202+
203+
// Create BitmapLayer to display the GBitmap
204+
s_background_layer = bitmap_layer_create(bounds);
205+
bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
206+
layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer));
207+
208+
// Create GFont
209+
s_time_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_48));
210+
211+
// Create the TextLayer with specific bounds
212+
s_time_layer = text_layer_create(
213+
GRect(0, PBL_IF_ROUND_ELSE(58, 52), bounds.size.w, 50));
214+
text_layer_set_background_color(s_time_layer, GColorClear);
215+
text_layer_set_text_color(s_time_layer, GColorBlack);
216+
text_layer_set_text(s_time_layer, "00:00");
217+
text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
218+
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
219+
text_layer_set_font(s_time_layer, s_time_font);
220+
layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
221+
222+
// Create weather Layer
223+
s_weather_layer = text_layer_create(
224+
GRect(0, PBL_IF_ROUND_ELSE(125, 120), bounds.size.w, 25));
225+
text_layer_set_background_color(s_weather_layer, GColorClear);
226+
text_layer_set_text_color(s_weather_layer, GColorWhite);
227+
text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter);
228+
text_layer_set_text(s_weather_layer, "Loading...");
229+
230+
// Create second custom font, apply it and add to Window
231+
s_weather_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_20));
232+
text_layer_set_font(s_weather_layer, s_weather_font);
233+
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_weather_layer));
234+
235+
// Create battery meter Layer
236+
s_battery_layer = layer_create(GRect(14, 53, 115, 2));
237+
layer_set_update_proc(s_battery_layer, battery_update_proc);
238+
layer_add_child(window_get_root_layer(window), s_battery_layer);
239+
240+
// Create the Bluetooth icon GBitmap
241+
s_bt_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BT_ICON);
242+
243+
// Create the BitmapLayer to display the GBitmap
244+
s_bt_icon_layer = bitmap_layer_create(GRect(59, 12, 30, 30));
245+
bitmap_layer_set_bitmap(s_bt_icon_layer, s_bt_icon_bitmap);
246+
layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(s_bt_icon_layer));
247+
}
248+
249+
static void main_window_unload(Window *window) {
250+
text_layer_destroy(s_time_layer);
251+
bitmap_layer_destroy(s_background_layer);
252+
bitmap_layer_destroy(s_bt_icon_layer);
253+
text_layer_destroy(s_weather_layer);
254+
layer_destroy(s_battery_layer);
255+
256+
fonts_unload_custom_font(s_weather_font);
257+
fonts_unload_custom_font(s_time_font);
258+
259+
gbitmap_destroy(s_background_bitmap);
260+
gbitmap_destroy(s_bt_icon_bitmap);
261+
}
262+
263+
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
264+
update_time();
265+
266+
// Get weather update every 30 minutes
267+
if (tick_time->tm_min % 30 == 0) {
268+
// Begin dictionary
269+
DictionaryIterator *iter;
270+
app_message_outbox_begin(&iter);
271+
272+
// Add a key-value pair
273+
dict_write_uint8(iter, 0, 0);
274+
275+
// Send the message!
276+
app_message_outbox_send();
277+
}
278+
}
279+
280+
static void battery_callback(BatteryChargeState state) {
281+
// Record the new battery level
282+
s_battery_level = state.charge_percent;
283+
284+
// Update meter
285+
layer_mark_dirty(s_battery_layer);
286+
}
287+
288+
static void bluetooth_callback(bool connected) {
289+
// Show icon if disconnected
290+
layer_set_hidden(bitmap_layer_get_layer(s_bt_icon_layer), connected);
291+
292+
if(!connected) {
293+
// Issue a vibrating alert
294+
vibes_double_pulse();
295+
}
296+
}
297+
298+
static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
299+
// Store incoming information
300+
static char temperature_buffer[8];
301+
static char conditions_buffer[32];
302+
static char weather_layer_buffer[32];
303+
304+
// Read tuples for data
305+
Tuple *temp_tuple = dict_find(iterator, MESSAGE_KEY_TEMPERATURE);
306+
Tuple *conditions_tuple = dict_find(iterator, MESSAGE_KEY_CONDITIONS);
307+
308+
// If all data is available, use it
309+
if (temp_tuple && conditions_tuple) {
310+
snprintf(temperature_buffer, sizeof(temperature_buffer), "%dC", (int)temp_tuple->value->int32);
311+
snprintf(conditions_buffer, sizeof(conditions_buffer), "%s", conditions_tuple->value->cstring);
312+
}
313+
314+
// Assemble full string and display
315+
snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%s, %s", temperature_buffer, conditions_buffer);
316+
text_layer_set_text(s_weather_layer, weather_layer_buffer);
317+
}
318+
319+
static void inbox_dropped_callback(AppMessageResult reason, void *context) {
320+
APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!");
321+
}
322+
323+
static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) {
324+
APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed!");
325+
}
326+
327+
static void outbox_sent_callback(DictionaryIterator *iterator, void *context) {
328+
APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!");
329+
}
330+
331+
static void init() {
332+
s_main_window = window_create();
333+
window_set_background_color(s_main_window, GColorBlack);
334+
window_set_window_handlers(s_main_window, (WindowHandlers) {
335+
.load = main_window_load,
336+
.unload = main_window_unload
337+
});
338+
window_stack_push(s_main_window, true);
339+
340+
tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
341+
update_time();
342+
343+
// Register callbacks
344+
app_message_register_inbox_received(inbox_received_callback);
345+
app_message_register_inbox_dropped(inbox_dropped_callback);
346+
app_message_register_outbox_failed(outbox_failed_callback);
347+
app_message_register_outbox_sent(outbox_sent_callback);
348+
349+
// Open AppMessage
350+
const int inbox_size = 256;
351+
const int outbox_size = 128;
352+
app_message_open(inbox_size, outbox_size);
353+
354+
// Register for battery level updates
355+
battery_state_service_subscribe(battery_callback);
356+
battery_callback(battery_state_service_peek());
357+
358+
// Register for Bluetooth connection updates
359+
connection_service_subscribe((ConnectionHandlers) {
360+
.pebble_app_connection_handler = bluetooth_callback
361+
});
362+
363+
// Show the correct state of the BT connection from the start
364+
bluetooth_callback(connection_service_peek_pebble_app_connection());
365+
}
366+
367+
static void deinit() {
368+
window_destroy(s_main_window);
369+
}
370+
371+
int main(void) {
372+
init();
373+
app_event_loop();
374+
deinit();
375+
}
376+
```
377+
{% endmarkdown %}
378+
</details>

0 commit comments

Comments
 (0)