Upload files to "src/c"

This commit is contained in:
2024-10-06 20:49:40 +00:00
parent 979eac8f6e
commit 3339bdb922
5 changed files with 735 additions and 0 deletions

569
src/c/analog_wd.c Normal file
View File

@@ -0,0 +1,569 @@
#include <pebble.h>
#include "analog_wd.h"
#include "constants.h"
#include "options.h"
#include "weather.h"
#include "date.h"
#include "messaging.h"
#include "digital_time.h"
#include "battery.h"
#define ANTIALIASING true
#define HAND_MARGIN 18
#define FINAL_RADIUS 80
#define REGULAR_HANDS_WIDTH 6
#define THIN_HANDS_WIDTH 5
#define ANIMATION_DURATION 500
#define ANIMATION_DELAY 600
#define HOUR_BAR_MARGIN 5
#define SCREEN_SHOT_HR 18
#define SCREEN_SHOT_MIN 22
#define HAND_LENGTH_SEC 75
#define SECONDS_HAND_DISPLAY_TIME_SECS 18
int g_display_seconds_hand_count = 0; //stores brief counter to display seconds hand
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;
typedef struct {
int hours;
int minutes;
int seconds;
} Time;
static GPoint s_center;
static Time s_last_time, s_anim_time;
static int s_radius = 0, s_color_channels[3];
static bool s_animating = false;
void set_text_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(date_layer1, textColor);
text_layer_set_text_color(date_layer2, textColor);
text_layer_set_text_color(battery_layer, textColor);
}
static void init_window_background_color(){
window_set_background_color(s_main_window, GColorBlack);
window_set_background_color(s_main_window, GColorFromHEX(s_options.background_color));
}
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.text_color));
if (font) {
text_layer_set_font(label, font);
}
}
/*************************** AnimationImplementation **************************/
static void animation_started(Animation *anim, void *context) {
s_animating = true;
}
static void animation_stopped(Animation *anim, bool stopped, void *context) {
s_animating = false;
}
static void animate(int duration, int delay, AnimationImplementation *implementation, bool handlers) {
Animation *anim = animation_create();
animation_set_duration(anim, duration);
animation_set_delay(anim, delay);
animation_set_curve(anim, AnimationCurveEaseInOut);
animation_set_implementation(anim, implementation);
if(handlers) {
animation_set_handlers(anim, (AnimationHandlers) {
.started = animation_started,
.stopped = animation_stopped
}, NULL);
}
animation_schedule(anim);
}
/************************************ UI **************************************/
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 connected and is now disconnected
if (s_options.bluetooth_state == 1 &&
current_bluetooth_state == 0) {
if (s_options.vibrate_bt_status) {
vibes_short_pulse(); //vibrate on bt disconnect, if option enabled
if(s_canvas_layer) {
layer_mark_dirty(s_canvas_layer);
}
}
}
//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;
}
static void tap_handler(AccelAxisType axis, int32_t direction) {
if (s_options.display_seconds_hand > 0) {
time_t t = time(NULL);
struct tm *time_now = localtime(&t);
s_last_time.seconds = time_now->tm_sec;
g_display_seconds_hand_count = SECONDS_HAND_DISPLAY_TIME_SECS;
}
if (s_options.shake_for_lohi == 1) {
//Display Lo Hi temp info;
display_lohi_weather_info();
}
}
//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());
}
//every second
static void handle_second_tick(struct tm *tick_time, TimeUnits units_changed) {
if((s_options.display_seconds_hand == 1 && g_display_seconds_hand_count > 0) ||
s_options.display_seconds_hand == 2) {
s_last_time.seconds = tick_time->tm_sec;
if(s_canvas_layer) {
layer_mark_dirty(s_canvas_layer);
}
}
}
//every minute
static void handle_minute_tick(struct tm *tick_time, TimeUnits units_changed) {
// Store time
s_last_time.hours = tick_time->tm_hour;
s_last_time.hours -= (s_last_time.hours > 12) ? 12 : 0;
s_last_time.minutes = tick_time->tm_min;
for(int i = 0; i < 3; i++) {
s_color_channels[i] = 0;
}
// Redraw
if(s_canvas_layer) {
layer_mark_dirty(s_canvas_layer);
}
if (units_changed & HOUR_UNIT) {
//vibes_double_pulse(); //hourly vibration
}
update_date_layer(); //check/update date layer
update_digital_time_layer(); //check/update digital_time_layer
update_battery_layer(); //check/update update_battery_layer
//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) {
handle_second_tick(tick_time, units_changed);
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 int hours_to_minutes(int hours_out_of_12) {
return (int)(float)(((float)hours_out_of_12 / 12.0F) * 60.0F);
}
static void update_proc(Layer *layer, GContext *ctx) {
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;
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_context_set_antialiased(ctx, ANTIALIASING);
/************************************ Minute lines **************************************/
for (int i = 0; i < 60; i++) {
// hour bar
if (i % 1 == 0 && i / 1 != -1) {
GPoint minute_line = (GPoint) {
.x = (int16_t)(sin_lookup(TRIG_MAX_ANGLE * i / 60) * (int32_t)(s_radius - HOUR_BAR_MARGIN) / TRIG_MAX_RATIO) + s_center.x,
.y = (int16_t)(-cos_lookup(TRIG_MAX_ANGLE * i / 60) * (int32_t)(s_radius - HOUR_BAR_MARGIN) / TRIG_MAX_RATIO) + s_center.y,
};
GPoint line_start = minute_line;
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
//emphasize/display hour indicators
if (i % 5 == 0) {
if (!s_options.display_hour_digits) {
// emphasis
graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.hour_markers_color));
line_start.x += (s_center.x - minute_line.x) * 0.12;
line_start.y += (s_center.y - minute_line.y) * 0.12;
graphics_context_set_stroke_width(ctx, 2);
graphics_draw_line(ctx, line_start, minute_line);
}
else {
graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.hour_markers_color));
line_start.x += (s_center.x - minute_line.x) * 0.02;
line_start.y += (s_center.y - minute_line.y) * 0.02;
graphics_context_set_stroke_width(ctx, 2);
graphics_draw_line(ctx, line_start, minute_line);
// show hour digits
line_start.x += (s_center.x - line_start.x) * 0.11;
line_start.y += (s_center.y - line_start.y) * 0.11;
line_start.x -= 14;
line_start.y -= 12;
GSize sz = (GSize) {
.w = 30,
.h = 20,
};
GRect rect = (GRect) {
.origin = line_start,
.size = sz,
};
char str[3];
snprintf(str, 3, "%d", i / 5 == 0 ? 12 : i / 5);
if (clock_is_24h_style() && time_now->tm_hour > 12) {
snprintf(str, 3, "%d", i / 5 == 0 ? 24 : i / 5 + 12);
}
graphics_context_set_text_color(ctx, GColorFromHEX(s_options.hour_markers_color));
graphics_draw_text(ctx, str, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD), rect, GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
}
else {
if (!s_options.display_hour_digits) {
graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.minor_markers_color));
line_start.x += (s_center.x - minute_line.x) * 0.07;
line_start.y += (s_center.y - minute_line.y) * 0.07;
graphics_context_set_stroke_width(ctx, 1);
graphics_draw_line(ctx, line_start, minute_line);
}
else {
graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.minor_markers_color));
graphics_context_set_fill_color(ctx, GColorFromHEX(s_options.minor_markers_color));
graphics_fill_circle(ctx, minute_line, 1);
}
}
}
}
/************************************ Watch hand width **************************************/
//set watch hand width
if (s_options.use_thin_hands) {
graphics_context_set_stroke_width(ctx, THIN_HANDS_WIDTH);
}
else {
graphics_context_set_stroke_width(ctx, REGULAR_HANDS_WIDTH);
}
/************************************ Twelve o'clock dot **************************************/
//if background color the same color as the hour marker color, dispaly a dot at twelve o'clock position
int chalk_offset = 10;
if (s_options.background_color == s_options.hour_markers_color) {
graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.hour_hand_color));
graphics_draw_circle(ctx, GPoint(width / 2, chalk_offset), 1); //twelve
}
/************************************ Hour and minute hands **************************************/
// Don't use current time while animating
Time mode_time = (s_animating) ? s_anim_time : s_last_time;
// Adjust for minutes through the hour
float minute_angle = TRIG_MAX_ANGLE * mode_time.minutes / 60;
float hour_angle;
if(s_animating) {
// Hours out of 60 for smoothness
hour_angle = TRIG_MAX_ANGLE * mode_time.hours / 60;
} else {
hour_angle = TRIG_MAX_ANGLE * mode_time.hours / 12;
}
hour_angle += (minute_angle / TRIG_MAX_ANGLE) * (TRIG_MAX_ANGLE / 12);
// Plot hands
GPoint minute_hand = (GPoint) {
.x = (int16_t)(sin_lookup(TRIG_MAX_ANGLE * mode_time.minutes / 60) * (int32_t)(s_radius - HAND_MARGIN) / TRIG_MAX_RATIO) + s_center.x,
.y = (int16_t)(-cos_lookup(TRIG_MAX_ANGLE * mode_time.minutes / 60) * (int32_t)(s_radius - HAND_MARGIN) / TRIG_MAX_RATIO) + s_center.y,
};
GPoint hour_hand = (GPoint) {
.x = (int16_t)(sin_lookup(hour_angle) * (int32_t)(s_radius - (1.75 * HAND_MARGIN)) / TRIG_MAX_RATIO) + s_center.x,
.y = (int16_t)(-cos_lookup(hour_angle) * (int32_t)(s_radius - (1.75 * HAND_MARGIN)) / TRIG_MAX_RATIO) + s_center.y,
};
// Draw hands with positive length only
if(s_radius > HAND_MARGIN) {
graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.minute_hand_color));
graphics_draw_line(ctx, s_center, minute_hand);
}
if(s_radius > 2 * HAND_MARGIN) {
graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.hour_hand_color));
if (s_options.vibrate_bt_status) {
if (s_options.bluetooth_state == 0) {
graphics_context_set_stroke_color(ctx, GColorBlue);
}
}
graphics_draw_line(ctx, s_center, hour_hand);
//center circle
graphics_draw_circle(ctx, GPoint(width / 2, height / 2), 1);
}
/************************************ Seconds hand line **************************************/
// Draw seconds hand
--g_display_seconds_hand_count;
if (g_display_seconds_hand_count < 0)
g_display_seconds_hand_count = 0;
if((s_options.display_seconds_hand == 1 && g_display_seconds_hand_count > 0) ||
(s_options.display_seconds_hand == 2)) {
// Plot seconds hand ends
GPoint second_hand_long = (GPoint) {
.x = (int16_t)(sin_lookup(TRIG_MAX_ANGLE * s_last_time.seconds / 60) * (int32_t)HAND_LENGTH_SEC / TRIG_MAX_RATIO) + s_center.x,
.y = (int16_t)(-cos_lookup(TRIG_MAX_ANGLE * s_last_time.seconds / 60) * (int32_t)HAND_LENGTH_SEC / TRIG_MAX_RATIO) + s_center.y,
};
graphics_context_set_stroke_width(ctx, 2);
graphics_context_set_stroke_color(ctx, GColorFromHEX(s_options.seconds_hand_color));
graphics_draw_line(ctx, GPoint(s_center.x, s_center.y), GPoint(second_hand_long.x, second_hand_long.y));
}
}
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect window_bounds = layer_get_bounds(window_layer);
s_center = grect_center_point(&window_bounds);
s_canvas_layer = layer_create(window_bounds);
layer_set_update_proc(s_canvas_layer, update_proc);
layer_add_child(window_layer, s_canvas_layer);
}
static void window_unload(Window *window) {
layer_destroy(s_canvas_layer);
}
/*********************************** App **************************************/
static int anim_percentage(AnimationProgress dist_normalized, int max) {
return (int)(float)(((float)dist_normalized / (float)ANIMATION_NORMALIZED_MAX) * (float)max);
}
static void radius_update(Animation *anim, AnimationProgress dist_normalized) {
int chalkRadiusAdjustment = 0;
#if defined(PBL_ROUND)
chalkRadiusAdjustment = 12;
#endif
s_radius = anim_percentage(dist_normalized, FINAL_RADIUS + chalkRadiusAdjustment);
layer_mark_dirty(s_canvas_layer);
}
static void hands_update(Animation *anim, AnimationProgress dist_normalized) {
s_anim_time.hours = anim_percentage(dist_normalized, hours_to_minutes(s_last_time.hours));
s_anim_time.minutes = anim_percentage(dist_normalized, s_last_time.minutes);
layer_mark_dirty(s_canvas_layer);
}
static void handle_deinit() {
//save watchface options
persist_write_data(KEY_OPTIONS, &s_options, sizeof(s_options));
text_layer_destroy(weather_layer1);
text_layer_destroy(weather_layer_center);
text_layer_destroy(weather_layer2);
text_layer_destroy(digital_time_layer);
text_layer_destroy(battery_layer);
text_layer_destroy(date_layer1);
text_layer_destroy(date_layer2);
connection_service_unsubscribe();
accel_tap_service_unsubscribe();
tick_timer_service_unsubscribe();
window_destroy(s_main_window);
}
static void handle_init() {
//light_enable(true); //background light for screen shots
srand(time(NULL));
s_main_window = window_create();
window_set_window_handlers(s_main_window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
//set-retrieve watchface options
init_options();
init_window_background_color();
// Prepare animations
AnimationImplementation radius_impl = {
.update = radius_update
};
animate(ANIMATION_DURATION, ANIMATION_DELAY, &radius_impl, false);
AnimationImplementation hands_impl = {
.update = hands_update
};
animate(2 * ANIMATION_DURATION, ANIMATION_DELAY, &hands_impl, true);
//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;
//add_weatherdate_layers(window_get_root_layer(s_main_window), width, height);
add_date_layers(window_get_root_layer(s_main_window), width, height);
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);
add_battery_layer(window_get_root_layer(s_main_window), width, height);
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);
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
update_date_layer(); //update date_layer
update_digital_time_layer(); //update digital_time_layer
update_battery_layer(); //update update_battery_layer
update_weather_layer(); //update weather layer
tick_timer_service_subscribe(MINUTE_UNIT | SECOND_UNIT, handle_tick);
accel_tap_service_subscribe(tap_handler);
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
if (s_options.display_seconds_hand > 0)
s_last_time.seconds = time_now->tm_sec;
handle_tick(time_now, MINUTE_UNIT);
//get minutes since last weather update
//int min_since_last_update = difftime(t, s_options.last_update) / 60;
//APP_LOG(APP_LOG_LEVEL_DEBUG, "******************* minutes since last weather update %d: weather_frequency %d", min_since_last_update, s_options.weather_frequency);
// 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());
const bool animated = true;
window_stack_push(s_main_window, animated);
}
int main() {
handle_init();
app_event_loop();
handle_deinit();
}