#include #include "basic.h" #include "constants.h" #include "options.h" #include "weather.h" #include "messaging.h" #include "digital_time.h" #include #define DEBUG_DATE "Sat 02 Jul" #define DEBUG_DAY "Sunday" #define SCREEN_SHOT_HR 20 #define SCREEN_SHOT_MIN 38 Options s_options; //stores weather retry counts (incremented in second tick handler). //Stop retry count at DEFAULT_MAX_WEATHER_RETRY_COUNT int g_weather_retry_count = 0; static TextLayer *s_hr_layer, *s_min_layer; static GFont s_time_font; static GFont s_time_thin_font; static GFont s_time_bold_font; static BitmapLayer *s_background_layer; static GBitmap *s_background_bitmap; static BluetoothLayer *s_bluetooth_layer; //static TextLayer *s_bluetooth_battery_text_layer; //static BatteryBarLayer *s_battery_layer; static GFont s_date_font; TextLayer *s_date_layer; TextLayer *s_steps_layer; AppTimer *temp_display_timer; void set_hr_layer_color(GColor textColor) { text_layer_set_text_color(s_hr_layer, textColor); } void set_min_layer_color(GColor textColor) { text_layer_set_text_color(s_min_layer, textColor); } void set_wsd_layers_color(GColor textColor) { text_layer_set_text_color(weather_layer1, textColor); text_layer_set_text_color(weather_layer2, textColor); text_layer_set_text_color(weather_layer_center, textColor); text_layer_set_text_color(s_steps_layer, textColor); text_layer_set_text_color(s_date_layer, textColor); } static void init_window_background_color(){ window_set_background_color(s_main_window, GColorBlack); text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorBlack); #ifdef PBL_COLOR // If on basalt window_set_background_color(s_main_window, GColorFromHEX(s_options.background_color)); text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorFromHEX(s_options.background_color)); battery_bar_set_colors(GColorGreen, GColorYellow, GColorRed, GColorWhite); #else if (s_options.background_color == 0xFFFFFF) { window_set_background_color(s_main_window, GColorWhite);//white text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorWhite);//white battery_bar_set_colors(GColorBlack, GColorBlack, GColorBlack, GColorBlack); } else if (s_options.background_color == 0x000000) {//black window_set_background_color(s_main_window, GColorBlack); text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorBlack); battery_bar_set_colors(GColorWhite, GColorWhite, GColorWhite, GColorWhite); } else { //didn't select white or black, default to white window_set_background_color(s_main_window, GColorBlack); text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorBlack); battery_bar_set_colors(GColorWhite, GColorWhite, GColorWhite, GColorWhite); } #endif layer_mark_dirty(text_layer_get_layer(s_bluetooth_battery_text_layer)); } void init_static_row(TextLayer *label, GFont font) { text_layer_set_background_color(label, GColorClear); text_layer_set_text_color(label, GColorWhite); text_layer_set_text_color(label, GColorFromHEX(s_options.wsd_color)); if (font) { text_layer_set_font(label, font); } } static void bt_handler(bool isConnected) { int current_bluetooth_state = 0; //default bluetooth disconnected if (isConnected) { current_bluetooth_state = 1; //bluetooth connected } //if bluetooth was previously disconnected and is now connected if (s_options.bluetooth_state == 0 && current_bluetooth_state == 1) { //clear weather conditions and reset weather retry count to force weather update memset(s_options.conditions, 0,sizeof(s_options.conditions)); s_options.condition_code = DEFAULT_CONDITION_CODE; g_weather_retry_count = 0; } //store bluetooth state s_options.bluetooth_state = current_bluetooth_state; } void on_animation_stopped(Animation *anim, bool finished, void *context) { //Free the memory used by the Animation property_animation_destroy((PropertyAnimation*) anim); } void animate_grect_layer(Layer *layer, GRect *start, GRect *finish, int duration, int delay) { //Declare animation PropertyAnimation *anim = property_animation_create_layer_frame(layer, start, finish); //Set characteristics animation_set_duration((Animation*) anim, duration); animation_set_delay((Animation*) anim, delay); //Set stopped handler to free memory AnimationHandlers handlers = { //The reference to the stopped handler is the only one in the array .stopped = (AnimationStoppedHandler) on_animation_stopped }; animation_set_handlers((Animation*) anim, handlers, NULL); //Start animation! animation_schedule((Animation*) anim); } static void animate_text_layers_round() { // Steps moves in from bottom GRect sins = GRect(20, 180, 60, 24); GRect sinf = GRect(20, 122, 60, 24); animate_grect_layer(text_layer_get_layer(s_steps_layer), &sins, &sinf, 1000, 0); GRect souts = GRect(20, 122, 60, 24); GRect soutf = GRect(20, 180, 60, 24); animate_grect_layer(text_layer_get_layer(s_steps_layer), &souts, &soutf, 1000, 5000); // Date moves in from bottom GRect dins = GRect(100, 180, 60, 24); GRect dinf = GRect(100, 122, 60, 24); animate_grect_layer(text_layer_get_layer(s_date_layer), &dins, &dinf, 1000, 0); GRect douts = GRect(100, 122, 60, 24); GRect doutf = GRect(100, 180, 60, 24); animate_grect_layer(text_layer_get_layer(s_date_layer), &douts, &doutf, 1000, 5000); // Bluetooth & battery layers revealed at top GRect bbins = GRect(0, 5, 200, 47); GRect bbinf = GRect(0, -47, 200, 47); animate_grect_layer(text_layer_get_layer(s_bluetooth_battery_text_layer), &bbins, &bbinf, 1000, 0); GRect bbouts = GRect(0, -47, 200, 47); GRect bboutf = GRect(0, 5, 200, 47); animate_grect_layer(text_layer_get_layer(s_bluetooth_battery_text_layer), &bbouts, &bboutf, 1000, 5000); } static void animate_text_layers() { // Steps moves in from bottom GRect sins = GRect(2, 180, 80, 24); GRect sinf = GRect(2, 151, 80, 24); animate_grect_layer(text_layer_get_layer(s_steps_layer), &sins, &sinf, 1000, 0); GRect souts = GRect(2, 151, 80, 24); GRect soutf = GRect(2, 180, 80, 24); animate_grect_layer(text_layer_get_layer(s_steps_layer), &souts, &soutf, 1000, 5000); // Date moves in from bottom GRect dins = GRect(82, 180, 60, 24); GRect dinf = GRect(82, 151, 60, 24); animate_grect_layer(text_layer_get_layer(s_date_layer), &dins, &dinf, 1000, 0); GRect douts = GRect(82, 151, 60, 24); GRect doutf = GRect(82, 180, 60, 24); animate_grect_layer(text_layer_get_layer(s_date_layer), &douts, &doutf, 1000, 5000); // Bluetooth & battery layers revealed at top GRect bbins = GRect(0, 0, 200, 35); GRect bbinf = GRect(0, -35, 200, 35); animate_grect_layer(text_layer_get_layer(s_bluetooth_battery_text_layer), &bbins, &bbinf, 1000, 0); GRect bbouts = GRect(0, -35, 200, 35); GRect bboutf = GRect(0, 0, 200, 35); animate_grect_layer(text_layer_get_layer(s_bluetooth_battery_text_layer), &bbouts, &bboutf, 1000, 5000); } static void update_time(struct tm *tick_time, TimeUnits units_changed) { static char date_buffer[16]; const char *current_date_layer; // Create a long-lived buffer static char hr_buffer[] = "00"; static char min_buffer[] = "00"; // Write the current hours and minutes into the buffer if(clock_is_24h_style() == true) { //Use 2h hour format strftime(hr_buffer, sizeof("00"), "%H", tick_time); } else { //Use 12 hour format strftime(hr_buffer, sizeof("00"), "%I", tick_time); } strftime(min_buffer, sizeof("00"), "%M", tick_time); // Display this time on the TextLayer text_layer_set_text(s_hr_layer, hr_buffer); // Display the seconds on the TextLayer text_layer_set_text(s_min_layer, min_buffer); //add date layer current_date_layer = text_layer_get_text(s_date_layer); if (current_date_layer != date_buffer) { //get current date strftime(date_buffer, sizeof(date_buffer), "%a %d %b", tick_time); #if DEBUG strftime(date_buffer, sizeof(date_buffer), DEBUG_DATE, tick_time); //for screen shots #endif text_layer_set_text(s_date_layer, date_buffer); } } //every five seconds static void handle_5second_tick(struct tm *tick_time, TimeUnits units_changed) { //if conditions blank (no current weather info) if(strlen(s_options.conditions) == 0 && g_weather_retry_count < DEFAULT_MAX_WEATHER_RETRY_COUNT) { //add one to weather retry count - stop trying this app session at DEFAULT_MAX_WEATHER_RETRY_COUNT ++g_weather_retry_count; //reset time since last forecast timer s_options.min_since_last_forecast = 0; //get updated weather send(KEY_GET_WEATHER, 1, KEY_WEATHER_USE_GPS, s_options.weather_use_GPS, KEY_WEATHER_LOCATION, s_options.weather_location); //update weather layer update_weather_layer(); } //APP_LOG(APP_LOG_LEVEL_DEBUG, "heap_bytes_free:heap_bytes_used %zu: %zu", heap_bytes_free(), heap_bytes_used()); } static void update_steps() { HealthMetric metric = HealthMetricStepCount; time_t start = time_start_of_today(); time_t end = time(NULL); static char steps_buffer[32]; // Check the metric has data available for today HealthServiceAccessibilityMask mask = health_service_metric_accessible(metric, start, end); if(mask & HealthServiceAccessibilityMaskAvailable) { // Data is available! snprintf(steps_buffer, sizeof(steps_buffer), "s: %d", (int)health_service_sum_today(metric)); #if DEBUG snprintf(steps_buffer, sizeof(steps_buffer), "s: 1%d", (int)health_service_sum_today(metric)); #endif APP_LOG(APP_LOG_LEVEL_INFO, "Steps today: %d", (int)health_service_sum_today(metric)); // Display to user in correct units text_layer_set_text(s_steps_layer, steps_buffer); } else { // No data recorded yet today APP_LOG(APP_LOG_LEVEL_ERROR, "Data unavailable!"); } } static void timer_callback(void *context) { APP_LOG(APP_LOG_LEVEL_INFO, "Timer elapsed!"); if (s_options.shake_for_lohi == 1) { //Display Lo Hi temp info; display_lohi_weather_info(); } } static void tap_handler(AccelAxisType axis, int32_t direction) { update_steps(); #if defined(PBL_ROUND) animate_text_layers_round(); #else animate_text_layers(); #endif // Schedule the timer app_timer_register(2500, timer_callback, NULL); } static void main_window_load(Window *window) { //set-retrieve watchface options init_options(); //add weather and date layers GRect layer_frame = layer_get_frame(window_get_root_layer(s_main_window)); const int16_t width = layer_frame.size.w; const int16_t height = layer_frame.size.h; //Create GFont s_time_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_ROBOTO_REGULAR_48)); s_time_thin_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_ROBOTO_LIGHT_48)); s_time_bold_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_ROBOTO_BOLD_48)); // Create time TextLayer #if defined(PBL_ROUND) s_hr_layer = text_layer_create(GRect(35, 57, 57, 48)); #else s_hr_layer = text_layer_create(GRect(15, 50, 57, 48)); #endif text_layer_set_background_color(s_hr_layer, GColorClear); text_layer_set_text_color(s_hr_layer, GColorFromHEX(s_options.hr_color)); text_layer_set_text_alignment(s_hr_layer, GTextAlignmentRight); text_layer_set_text(s_hr_layer, "00"); if (s_options.hourFont == 2) { text_layer_set_font(s_hr_layer, s_time_bold_font); } else if(s_options.hourFont == 0) { text_layer_set_font(s_hr_layer, s_time_thin_font); } else { text_layer_set_font(s_hr_layer, s_time_font); } // Add it as a child layer to the Window's root layer layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_hr_layer)); #if defined(PBL_ROUND) s_min_layer = text_layer_create(GRect(95, 57, 57, 48)); #else s_min_layer = text_layer_create(GRect(75, 50, 57, 48)); #endif text_layer_set_background_color(s_min_layer, GColorClear); text_layer_set_text_alignment(s_min_layer, GTextAlignmentLeft); text_layer_set_text_color(s_min_layer, GColorFromHEX(s_options.min_color)); if (s_options.minutesFont == 2) { text_layer_set_font(s_min_layer, s_time_bold_font); } else if(s_options.minutesFont == 0) { text_layer_set_font(s_min_layer, s_time_thin_font); } else { text_layer_set_font(s_min_layer, s_time_font); } // Add it as a child layer to the Window's root layer layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_min_layer)); //add Bluetooth layer s_bluetooth_layer = bluetooth_layer_create(); #if defined(PBL_ROUND) bluetooth_set_position(GPoint(42, 31)); #else bluetooth_set_position(GPoint(2, 2)); #endif layer_add_child(window_get_root_layer(window), s_bluetooth_layer); //add battery layer s_battery_layer = battery_bar_layer_create(); battery_bar_set_percent_hidden(true); battery_bar_set_icon_hidden(false); #ifdef PBL_COLOR // If on basalt battery_bar_set_colors(GColorGreen, GColorYellow, GColorRed, GColorWhite); #endif #if defined(PBL_ROUND) battery_bar_set_position(GPoint(102, 32)); #else battery_bar_set_position(GPoint(102, 3)); #endif layer_add_child(window_get_root_layer(window), s_battery_layer); //add weather and digital layers add_weather_layers(window_get_root_layer(s_main_window), width, height); add_digital_time_layer(window_get_root_layer(s_main_window), width, height); #if defined(PBL_ROUND) s_bluetooth_battery_text_layer = text_layer_create(GRect(0, 5, 200, 45)); #else s_bluetooth_battery_text_layer = text_layer_create(GRect(0, 0, 200, 35)); #endif #if defined(PBL_ROUND) s_bluetooth_battery_text_layer = text_layer_create(GRect(0, 5, 200, 45)); #else s_bluetooth_battery_text_layer = text_layer_create(GRect(0, 0, 200, 35)); #endif init_window_background_color(); layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_bluetooth_battery_text_layer)); /* //text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorClear); #ifdef PBL_COLOR // If on basalt text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorFromHEX(s_options.background_color)); #else if (s_options.background_color == 0xFFFFFF) { text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorWhite);//white } else if (s_options.background_color == 0x000000) {//black text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorBlack); } else { //didn't select white or black, default to white text_layer_set_background_color(s_bluetooth_battery_text_layer, GColorBlack); } #endif */ //date layer #if defined(PBL_ROUND) s_date_layer = text_layer_create(GRect(100, 180, 60, 24)); #else s_date_layer = text_layer_create(GRect(82, 180, 60, 24)); #endif text_layer_set_background_color(s_date_layer, GColorClear); text_layer_set_text_color(s_date_layer, GColorFromHEX(s_options.wsd_color)); text_layer_set_text_alignment(s_date_layer, GTextAlignmentRight); s_date_font = fonts_get_system_font(FONT_KEY_GOTHIC_14); //s_date_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_DIGITAL_SEVEN_14)); //Apply to TextLayer text_layer_set_font(s_date_layer, s_date_font); layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_date_layer)); //steps layer #if defined(PBL_ROUND) s_steps_layer = text_layer_create(GRect(20, 180, 60, 24)); #else s_steps_layer = text_layer_create(GRect(2, 180, 80, 24)); #endif text_layer_set_background_color(s_steps_layer, GColorClear); text_layer_set_text_color(s_steps_layer, GColorFromHEX(s_options.wsd_color)); text_layer_set_text_alignment(s_steps_layer, GTextAlignmentLeft); s_date_font = fonts_get_system_font(FONT_KEY_GOTHIC_14); //s_steps_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_DIGITAL_SEVEN_14)); //Apply to TextLayer text_layer_set_font(s_steps_layer, s_date_font); layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_steps_layer)); } static void main_window_unload(Window *window) { //Destroy Bluetooth layer bluetooth_layer_destroy(s_bluetooth_layer); //Destroy battery layer battery_bar_layer_destroy(s_battery_layer); //Destroy GBitmap gbitmap_destroy(s_background_bitmap); //Destroy BitmapLayer bitmap_layer_destroy(s_background_layer); // Destroy TextLayers text_layer_destroy(s_hr_layer); text_layer_destroy(s_min_layer); text_layer_destroy(s_bluetooth_battery_text_layer); text_layer_destroy(weather_layer1); text_layer_destroy(weather_layer_center); text_layer_destroy(weather_layer2); text_layer_destroy(digital_time_layer); } static void health_handler(HealthEventType event, void *context) { // Which type of event occurred? switch(event) { case HealthEventSignificantUpdate: //update_steps(); APP_LOG(APP_LOG_LEVEL_INFO, "New HealthService HealthEventSignificantUpdate event"); break; case HealthEventMovementUpdate: //update_steps(); APP_LOG(APP_LOG_LEVEL_INFO, "New HealthService HealthEventMovementUpdate event"); break; case HealthEventMetricAlert: APP_LOG(APP_LOG_LEVEL_INFO, "New HealthService HealthEventMetricAlert event"); break; case HealthEventHeartRateUpdate: APP_LOG(APP_LOG_LEVEL_INFO, "New HealthService HealthEventHeartRateUpdate event"); break; case HealthEventSleepUpdate: APP_LOG(APP_LOG_LEVEL_INFO, "New HealthService HealthEventSleepUpdate event"); break; } } static void handle_minute_tick(struct tm *tick_time, TimeUnits units_changed) { update_time(tick_time, units_changed); //get minutes since last weather update time_t t = time(NULL); int min_since_last_update = (difftime(t, s_options.last_update) / 60) + 1; APP_LOG(APP_LOG_LEVEL_DEBUG, " (Last update %s) min_since_last_update:weather_frequency - %d:%d", s_options.last_weather_update24hr, min_since_last_update, s_options.weather_frequency); //APP_LOG(APP_LOG_LEVEL_DEBUG, "handle_minute_tick strlen(s_options.conditions): %zu", strlen(s_options.conditions)); //if min_since_last_forecast greater than or equal to weatherUpdateFrequency if(min_since_last_update >= s_options.weather_frequency || strlen(s_options.conditions) == 0) { s_options.min_since_last_forecast = 0; //get updated weather send(KEY_GET_WEATHER, 1, KEY_WEATHER_USE_GPS, s_options.weather_use_GPS, KEY_WEATHER_LOCATION, s_options.weather_location); //update weather layer update_weather_layer(); } } void handle_tick(struct tm *tick_time, TimeUnits units_changed) { if (units_changed & SECOND_UNIT) { if (tick_time->tm_sec % 5 == 0) { handle_5second_tick(tick_time, units_changed); } } if (units_changed & MINUTE_UNIT) { handle_minute_tick(tick_time, units_changed); } } static void init() { // Create main Window element and assign to pointer s_main_window = window_create(); // Set handlers to manage the elements inside the Window window_set_window_handlers(s_main_window, (WindowHandlers) { .load = main_window_load, .unload = main_window_unload }); // Show the Window on the watch, with animated=true window_stack_push(s_main_window, true); app_message_register_inbox_received(inbox_received_callback); app_message_register_inbox_dropped(message_dropped); app_message_register_outbox_sent(message_out_success); app_message_register_outbox_failed(message_out_failed); #if defined(PBL_BW) app_message_open(175, 250); #else app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum()); #endif update_digital_time_layer(); //update digital_time_layer update_weather_layer(); //update weather layer time_t t = time(NULL); struct tm *time_now = localtime(&t); #if DEBUG time_now->tm_hour = SCREEN_SHOT_HR; //for screen shots time_now->tm_min = SCREEN_SHOT_MIN; #endif handle_tick(time_now, MINUTE_UNIT); // Subscribe to Bluetooth updates connection_service_subscribe((ConnectionHandlers) { .pebble_app_connection_handler = bt_handler }); // Check current bluetooth connection state bt_handler(connection_service_peek_pebble_app_connection()); #if defined(PBL_HEALTH) // Attempt to subscribe if(!health_service_events_subscribe(health_handler, NULL)) { APP_LOG(APP_LOG_LEVEL_ERROR, "Health not available!"); } #else APP_LOG(APP_LOG_LEVEL_ERROR, "Health not available!"); #endif accel_tap_service_subscribe(tap_handler); // Register with TickTimerService tick_timer_service_subscribe(MINUTE_UNIT | SECOND_UNIT, handle_tick); } static void deinit() { //save watchface options persist_write_data(KEY_OPTIONS, &s_options, sizeof(s_options)); // Destroy Window window_destroy(s_main_window); } int main(void) { init(); app_event_loop(); deinit(); }