Skip to content

Commit 222bc34

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

File tree

1 file changed

+234
-6
lines changed
  • source/tutorials/watchface-tutorial

1 file changed

+234
-6
lines changed

source/tutorials/watchface-tutorial/part4.md

Lines changed: 234 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ glance. This is typically implemented as the classic 'battery icon' that fills
3030
up according to the current charge level, but some watchfaces favor the more
3131
minimal approach, which will be implemented here.
3232

33-
This section continues from
34-
[*Part 3*](/tutorials/watchface-tutorial/part3/), so be sure to re-use
35-
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
The state of the battery is obtained using the ``BatteryStateService``. This
3838
service offers two modes of usage - 'peeking' at the current level, or
@@ -47,7 +47,8 @@ static int s_battery_level;
4747
As with all the Event Services, to receive an event when new battery information
4848
is available, a callback must be registered. Create this callback using the
4949
signature of ``BatteryStateHandler``, and use the provided
50-
``BatteryChargeState`` parameter to store the current charge percentage:
50+
``BatteryChargeState`` parameter to store the current charge percentage. Place
51+
it before `init()`, such as after `tick_handler()`:
5152

5253
```c
5354
static void battery_callback(BatteryChargeState state) {
@@ -136,7 +137,8 @@ called manually in `init()` to display an inital value:
136137
battery_callback(battery_state_service_peek());
137138
```
138139

139-
Don't forget to free the memory used by the new battery meter:
140+
Don't forget to free the memory used by the new battery meter in
141+
`main_window_unload()` as with the other ``Layer`` objects:
140142

141143
```c
142144
layer_destroy(s_battery_layer);
@@ -149,9 +151,235 @@ existing design style.
149151
![battery-level >{pebble-screenshot,pebble-screenshot--steel-black}](/images/tutorials/intermediate/battery-level.png)
150152
151153
154+
## Conclusion
155+
156+
Now our watchface shows the watch's remaining battery level! It's discreet,
157+
but very useful.
158+
159+
As usual, you can compare your code to the example code provided below.
160+
161+
> The JS code file remains unchanged from the last part of the tutorial.
162+
163+
<details>
164+
<summary>View C code</summary>
165+
{% markdown %}
166+
```c
167+
#include <pebble.h>
168+
169+
static Window *s_main_window;
170+
static TextLayer *s_time_layer;
171+
static BitmapLayer *s_background_layer;
172+
static TextLayer *s_weather_layer;
173+
static Layer *s_battery_layer;
174+
175+
static GFont s_time_font;
176+
static GFont s_weather_font;
177+
static GBitmap *s_background_bitmap;
178+
179+
static int s_battery_level;
180+
181+
static void update_time() {
182+
// Get a tm structure
183+
time_t temp = time(NULL);
184+
struct tm *tick_time = localtime(&temp);
185+
186+
// Write the current hours and minutes into a buffer
187+
static char s_buffer[8];
188+
strftime(s_buffer, sizeof(s_buffer), clock_is_24h_style() ?
189+
"%H:%M" : "%I:%M", tick_time);
190+
191+
// Display this time on the TextLayer
192+
text_layer_set_text(s_time_layer, s_buffer);
193+
}
194+
195+
static void battery_update_proc(Layer *layer, GContext *ctx) {
196+
GRect bounds = layer_get_bounds(layer);
197+
198+
// Find the width of the bar (total width = 114px)
199+
int width = (s_battery_level * 114) / 100;
200+
201+
// Draw the background
202+
graphics_context_set_fill_color(ctx, GColorBlack);
203+
graphics_fill_rect(ctx, bounds, 0, GCornerNone);
204+
205+
// Draw the bar
206+
graphics_context_set_fill_color(ctx, GColorWhite);
207+
graphics_fill_rect(ctx, GRect(0, 0, width, bounds.size.h), 0, GCornerNone);
208+
}
209+
210+
static void main_window_load(Window *window) {
211+
// Get information about the Window
212+
Layer *window_layer = window_get_root_layer(window);
213+
GRect bounds = layer_get_bounds(window_layer);
214+
215+
// Create GBitmap
216+
s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BACKGROUND);
217+
218+
// Create BitmapLayer to display the GBitmap
219+
s_background_layer = bitmap_layer_create(bounds);
220+
bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
221+
layer_add_child(window_layer, bitmap_layer_get_layer(s_background_layer));
222+
223+
// Create GFont
224+
s_time_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_48));
225+
226+
// Create the TextLayer with specific bounds
227+
s_time_layer = text_layer_create(
228+
GRect(0, PBL_IF_ROUND_ELSE(58, 52), bounds.size.w, 50));
229+
text_layer_set_background_color(s_time_layer, GColorClear);
230+
text_layer_set_text_color(s_time_layer, GColorBlack);
231+
text_layer_set_text(s_time_layer, "00:00");
232+
text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
233+
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
234+
text_layer_set_font(s_time_layer, s_time_font);
235+
layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
236+
237+
// Create weather Layer
238+
s_weather_layer = text_layer_create(
239+
GRect(0, PBL_IF_ROUND_ELSE(125, 120), bounds.size.w, 25));
240+
text_layer_set_background_color(s_weather_layer, GColorClear);
241+
text_layer_set_text_color(s_weather_layer, GColorWhite);
242+
text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter);
243+
text_layer_set_text(s_weather_layer, "Loading...");
244+
245+
// Create second custom font, apply it and add to Window
246+
s_weather_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_20));
247+
text_layer_set_font(s_weather_layer, s_weather_font);
248+
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_weather_layer));
249+
250+
// Create battery meter Layer
251+
s_battery_layer = layer_create(GRect(14, 53, 115, 2));
252+
layer_set_update_proc(s_battery_layer, battery_update_proc);
253+
254+
// Add to Window
255+
layer_add_child(window_get_root_layer(window), s_battery_layer);
256+
}
257+
258+
static void main_window_unload(Window *window) {
259+
// Destroy TextLayer
260+
text_layer_destroy(s_time_layer);
261+
262+
// Unload GFont
263+
fonts_unload_custom_font(s_time_font);
264+
265+
// Destroy BitmapLayer
266+
bitmap_layer_destroy(s_background_layer);
267+
268+
// Destroy GBitmap
269+
gbitmap_destroy(s_background_bitmap);
270+
271+
// Destroy weather elements
272+
text_layer_destroy(s_weather_layer);
273+
fonts_unload_custom_font(s_weather_font);
274+
275+
layer_destroy(s_battery_layer);
276+
}
277+
278+
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
279+
update_time();
280+
281+
// Get weather update every 30 minutes
282+
if (tick_time->tm_min % 30 == 0) {
283+
// Begin dictionary
284+
DictionaryIterator *iter;
285+
app_message_outbox_begin(&iter);
286+
287+
// Add a key-value pair
288+
dict_write_uint8(iter, 0, 0);
289+
290+
// Send the message!
291+
app_message_outbox_send();
292+
}
293+
}
294+
295+
static void battery_callback(BatteryChargeState state) {
296+
// Record the new battery level
297+
s_battery_level = state.charge_percent;
298+
299+
// Update meter
300+
layer_mark_dirty(s_battery_layer);
301+
}
302+
303+
static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
304+
// Store incoming information
305+
static char temperature_buffer[8];
306+
static char conditions_buffer[32];
307+
static char weather_layer_buffer[32];
308+
309+
// Read tuples for data
310+
Tuple *temp_tuple = dict_find(iterator, MESSAGE_KEY_TEMPERATURE);
311+
Tuple *conditions_tuple = dict_find(iterator, MESSAGE_KEY_CONDITIONS);
312+
313+
// If all data is available, use it
314+
if (temp_tuple && conditions_tuple) {
315+
snprintf(temperature_buffer, sizeof(temperature_buffer), "%dC", (int)temp_tuple->value->int32);
316+
snprintf(conditions_buffer, sizeof(conditions_buffer), "%s", conditions_tuple->value->cstring);
317+
}
318+
319+
// Assemble full string and display
320+
snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%s, %s", temperature_buffer, conditions_buffer);
321+
text_layer_set_text(s_weather_layer, weather_layer_buffer);
322+
}
323+
324+
static void inbox_dropped_callback(AppMessageResult reason, void *context) {
325+
APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!");
326+
}
327+
328+
static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) {
329+
APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed!");
330+
}
331+
332+
static void outbox_sent_callback(DictionaryIterator *iterator, void *context) {
333+
APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!");
334+
}
335+
336+
static void init() {
337+
s_main_window = window_create();
338+
window_set_background_color(s_main_window, GColorBlack);
339+
window_set_window_handlers(s_main_window, (WindowHandlers) {
340+
.load = main_window_load,
341+
.unload = main_window_unload
342+
});
343+
window_stack_push(s_main_window, true);
344+
345+
tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
346+
update_time();
347+
348+
// Register callbacks
349+
app_message_register_inbox_received(inbox_received_callback);
350+
app_message_register_inbox_dropped(inbox_dropped_callback);
351+
app_message_register_outbox_failed(outbox_failed_callback);
352+
app_message_register_outbox_sent(outbox_sent_callback);
353+
354+
// Open AppMessage
355+
const int inbox_size = 256;
356+
const int outbox_size = 128;
357+
app_message_open(inbox_size, outbox_size);
358+
359+
// Register for battery level updates
360+
battery_state_service_subscribe(battery_callback);
361+
362+
// Ensure battery level is displayed from the start
363+
battery_callback(battery_state_service_peek());
364+
}
365+
366+
static void deinit() {
367+
window_destroy(s_main_window);
368+
}
369+
370+
int main(void) {
371+
init();
372+
app_event_loop();
373+
deinit();
374+
}
375+
```
376+
{% endmarkdown %}
377+
</details>
378+
379+
152380
## What's Next?
153381

154-
In the next, and final, section of this tutorial, we'll use the Connection Service
382+
In the next and final section of this tutorial, we'll use the Connection Service
155383
to notify the user when their Pebble smartwatch disconnects from their phone.
156384

157385
[Go to Part 5 &rarr; >{wide,bg-dark-red,fg-white}](/tutorials/watchface-tutorial/part5/)

0 commit comments

Comments
 (0)