@@ -30,9 +30,9 @@ glance. This is typically implemented as the classic 'battery icon' that fills
3030up according to the current charge level, but some watchfaces favor the more
3131minimal 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
3737The state of the battery is obtained using the `` BatteryStateService `` . This
3838service offers two modes of usage - 'peeking' at the current level, or
@@ -47,7 +47,8 @@ static int s_battery_level;
4747As with all the Event Services, to receive an event when new battery information
4848is available, a callback must be registered. Create this callback using the
4949signature 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
5354static void battery_callback (BatteryChargeState state) {
@@ -136,7 +137,8 @@ called manually in `init()` to display an inital value:
136137battery_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
142144layer_destroy (s_battery_layer);
@@ -149,9 +151,235 @@ existing design style.
149151
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
155383to 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