diff --git a/src/c/constants.c b/src/c/constants.c new file mode 100644 index 0000000..a4cc36b --- /dev/null +++ b/src/c/constants.c @@ -0,0 +1,42 @@ +#include +#include "constants.h" + +const int DEFAULT_SHAKE_FOR_LOHI = 1; //True - shake to show Lo Hi Temps +const int DEFAULT_DISPLAY_LOHI_TIMER = 5000; //length time (milliseconds) to display Lo Hi Temps +const int DEFAULT_MIN_SINCE_WEATHER_UPDATE = 0; //weather just updated +const char DEFAULT_LAST_WEATHER_UPDATE[16] = "--:--"; //default last update "time" display +const char DEFAULT_ERROR_WEATHER_UPDATE[16] = "---"; //default text display upon weather update error +const int DEFAULT_CONDITION_CODE = -99; //Invalid condition code to force weather update +const int DEFAULT_DISPLAY_WEATHER = 1; //true +const int DEFAULT_WEATHER_FREQUENCY = 30; //minutes +const int DEFAULT_WEATHER_USE_GPS = 1; //true +const int DEFAULT_USE_CELSIUS = 0; //false +const int DEFAULT_DISPLAY_O_PREFIX = 0; //false +const int DEFAULT_WEATHERDATE_ALIGNMENT = 1; //center +const int DEFAULT_HOURMINUTES_ALIGNMENT = 0; //left +const int DEFAULT_CHALK_ALIGNMENT = 1; //center +const int DEFAULT_TIME_COLOR = 0xFFFFFF; //white +const int DEFAULT_HR_COLOR = 0xFFFFFF; //white +const int DEFAULT_MIN_COLOR = 0xFFFFFF; //white +const int DEFAULT_BACKGROUND_COLOR = 0x000000; //black +const int DEFAULT_WEATHERDATE_READABILITY = 0; //use original size +const int DEFAULT_MINUTES_READABILITY = 0; //use default size +const int DEFAULT_WEATHER_Y_OFFSET_READABILITY = 4; //Y-axis adjustment for increased readability font options +const int DEFAULT_DATE_Y_OFFSET_READABILITY = 5; //Y-axis adjustment for increased readability font options +const int DEFAULT_VIBRATE_BT_STATUS = 0; //false +const int DEFAULT_WD_COLOR = 0xFFFFFF; //white +const int DEFAULT_DISPLAY_DATE = 1; //true + +const int DEFAULT_MAX_WEATHER_RETRY_COUNT = 5; //max (seconds) weather retry counts +const uint MAX_CHALK_SINGLE_LINE_CONDITIONS_LEN = 6; //max length for weather conditions to display on single line. longer split to two lines +const int DEFAULT_CHALK_TIME_X_OFFSET = 20; //X-axis adjustment for time display on Chalk (Pebble Time Round) +const int DEFAULT_CHALK_WEATHER1_Y_OFFSET = 0; //Y-axis adjustment for weather display on Chalk (Pebble Time Round) +const int DEFAULT_CHALK_WEATHER2_Y_OFFSET = 15; //Y-axis adjustment for weather display on Chalk (Pebble Time Round) +const int DEFAULT_CHALK_WEATHER_CENTER_Y_OFFSET = 8; //Y-axis adjustment for weather display on Chalk (Pebble Time Round) +const int DEFAULT_CHALK_TIME_Y_OFFSET = 10; //Y-axis adjustment for time display on Chalk (Pebble Time Round) +const int DEFAULT_CHALK_DATE_Y_OFFSET = 5; //Y-axis adjustment for date display on Chalk (Pebble Time Round) + +//watchface time display lines +const int HOUR_ROW = 0; +const int MINUTE_ROW1 = 1; +const int MINUTE_ROW2 = 2; diff --git a/src/c/constants.h b/src/c/constants.h new file mode 100644 index 0000000..432f90f --- /dev/null +++ b/src/c/constants.h @@ -0,0 +1,73 @@ +#pragma once + + #define DEBUG 0 + + #define KEY_TEMPERATURE_IN_C 1 + #define KEY_CONDITIONS 2 + #define KEY_BACKGROUND_COLOR 3 + #define KEY_TIME_COLOR 4 + #define KEY_WEATHER_FREQUENCY 5 + #define KEY_USE_CELSIUS 6 + #define KEY_DISPLAY_O_PREFIX 7 + #define KEY_DISPLAY_WEATHER 8 + #define KEY_MIN_SINCE_WEATHER_UPDATE 9 + #define KEY_WEATHERDATE_ALIGNMENT 10 + #define KEY_HOURMINUTES_ALIGNMENT 11 + #define KEY_GET_WEATHER 12 + #define KEY_TEMPERATURE 13 + #define KEY_WEATHERDATE_READABILITY 14 + #define KEY_MINUTES_READABILITY 15 + #define KEY_TEMPERATURE_LO 16 + #define KEY_TEMPERATURE_HI 17 + #define KEY_TEMPERATURE_IN_C_LO 18 + #define KEY_TEMPERATURE_IN_C_HI 19 + #define KEY_CONDITION_CODE 20 + #define KEY_SHAKE_FOR_LOHI 21 + #define KEY_VIBBRATE_BT_STATUS 22 + #define KEY_WEATHER_USE_GPS 23 + #define KEY_WEATHER_LOCATION 24 + #define KEY_JS_READY 25 + #define KEY_WD_COLOR 26 + #define KEY_DISPLAY_DATE 27 + #define KEY_HR_COLOR 28 + #define KEY_MIN_COLOR 29 + #define KEY_OPTIONS 99 + + extern const int DEFAULT_SHAKE_FOR_LOHI; + extern const int DEFAULT_DISPLAY_LOHI_TIMER; + extern const int DEFAULT_MIN_SINCE_WEATHER_UPDATE; + extern const char DEFAULT_LAST_WEATHER_UPDATE[16]; + extern const char DEFAULT_ERROR_WEATHER_UPDATE[16]; + extern const int DEFAULT_CONDITION_CODE; + extern const int DEFAULT_DISPLAY_WEATHER; + extern const int DEFAULT_WEATHER_FREQUENCY; + extern const int DEFAULT_WEATHER_USE_GPS; + extern const int DEFAULT_USE_CELSIUS; + extern const int DEFAULT_DISPLAY_O_PREFIX; + extern const int DEFAULT_WEATHERDATE_ALIGNMENT; + extern const int DEFAULT_HOURMINUTES_ALIGNMENT; + extern const int DEFAULT_CHALK_ALIGNMENT; + extern const int DEFAULT_TIME_COLOR; + extern const int DEFAULT_HR_COLOR; + extern const int DEFAULT_MIN_COLOR; + extern const int DEFAULT_BACKGROUND_COLOR; + extern const int DEFAULT_WEATHERDATE_READABILITY; + extern const int DEFAULT_MINUTES_READABILITY; + extern const int DEFAULT_WEATHER_Y_OFFSET_READABILITY; + extern const int DEFAULT_DATE_Y_OFFSET_READABILITY; + extern const int DEFAULT_VIBRATE_BT_STATUS; + extern const int DEFAULT_WD_COLOR; + extern const int DEFAULT_DISPLAY_DATE; + + extern const int DEFAULT_MAX_WEATHER_RETRY_COUNT; + extern const uint MAX_CHALK_SINGLE_LINE_CONDITIONS_LEN; + extern const int DEFAULT_CHALK_TIME_X_OFFSET; + extern const int DEFAULT_CHALK_WEATHER1_Y_OFFSET; + extern const int DEFAULT_CHALK_WEATHER2_Y_OFFSET; + extern const int DEFAULT_CHALK_WEATHER_CENTER_Y_OFFSET; + extern const int DEFAULT_CHALK_TIME_Y_OFFSET; + extern const int DEFAULT_CHALK_DATE_Y_OFFSET; + + extern const int HOUR_ROW; + extern const int MINUTE_ROW1; + extern const int MINUTE_ROW2; diff --git a/src/c/messaging.c b/src/c/messaging.c new file mode 100644 index 0000000..aef3179 --- /dev/null +++ b/src/c/messaging.c @@ -0,0 +1,224 @@ +#include +#include "messaging.h" +#include "constants.h" +#include "weather_date.h" +#include "sliding_time_wd.h" + +extern Options s_options; +extern SlidingTextData *s_data; + +void send(int key, int value, int key2, int value2, int key3, char value3[64]){ + #if DEBUG + APP_LOG(APP_LOG_LEVEL_DEBUG, "send: s_options.pebble_js_ready: %d", s_options.pebble_js_ready); + #endif + if (s_options.pebble_js_ready) { + + DictionaryIterator *iterator; + app_message_outbox_begin(&iterator); + dict_write_int(iterator, key, &value, sizeof(int), true); + dict_write_int(iterator, key2, &value2, sizeof(int), true); + dict_write_cstring(iterator, key3, value3); + + app_message_outbox_send(); + } +} + +void inbox_received_callback(DictionaryIterator *iterator, void *context) { + SlidingTextData *data = s_data; + SlidingRow *row; + + // Read first item + Tuple *t = dict_read_first(iterator); + + #if DEBUG + int dic_size = (int)dict_size(iterator); + APP_LOG(APP_LOG_LEVEL_DEBUG, "***************************** inbox_received_callback dict_size: %d", dic_size); + #endif + + // For all items + while(t != NULL) { + // Which key was received? + switch(t->key) { + + case KEY_TEMPERATURE: + s_options.tempF = (int)t->value->int32; + break; + + case KEY_TEMPERATURE_LO: + s_options.tempFLo = (int)t->value->int32; + break; + + case KEY_TEMPERATURE_HI: + s_options.tempFHi = (int)t->value->int32; + break; + + case KEY_TEMPERATURE_IN_C: + s_options.tempC = (int)t->value->int32; + break; + + case KEY_TEMPERATURE_IN_C_LO: + s_options.tempCLo = (int)t->value->int32; + break; + + case KEY_TEMPERATURE_IN_C_HI: + s_options.tempCHi = (int)t->value->int32; + break; + + case KEY_CONDITIONS: + snprintf(s_options.conditions, sizeof(s_options.conditions), "%s", t->value->cstring); + + //check if successful weather retrieval + if (strlen(s_options.conditions) > 0) { + // Get a tm structure + time_t temp = time(NULL); + struct tm *tick_time = localtime(&temp); + //strftime(s_options.last_weather_update, sizeof("00:00"), "%H:%M", tick_time); + strftime(s_options.last_weather_update24hr, sizeof("00:00"), "%H:%M", tick_time); + strftime(s_options.last_weather_update12hr, sizeof("00:00 pm"), "%l:%M %P", tick_time); + s_options.last_update = temp; + } + break; + + case KEY_CONDITION_CODE: + s_options.condition_code = (int)t->value->int32; + break; + + case KEY_DISPLAY_O_PREFIX: + s_options.display_prefix = (int)t->value->int32; + break; + + case KEY_DISPLAY_DATE: + s_options.display_date = (int)t->value->int32; + break; + + case KEY_SHAKE_FOR_LOHI: + s_options.shake_for_lohi = (int)t->value->int32; + break; + + case KEY_USE_CELSIUS: + s_options.use_celsius = (int)t->value->int32; + break; + + case KEY_WEATHER_USE_GPS: + //force weather update after each config page Save + memset(s_options.conditions, 0,sizeof(s_options.conditions)); + s_options.condition_code = DEFAULT_CONDITION_CODE; + + s_options.weather_use_GPS = (int)t->value->int32; + break; + + case KEY_WEATHER_LOCATION: + snprintf(s_options.weather_location, sizeof(s_options.weather_location), "%s", t->value->cstring); + break; + + case KEY_WEATHER_FREQUENCY: + s_options.weather_frequency = (int)t->value->int32; + break; + + case KEY_BACKGROUND_COLOR: + s_options.background_color = (int)t->value->int32; + window_set_background_color(data->window, GColorFromHEX(s_options.background_color)); + break; + + case KEY_TIME_COLOR: + s_options.time_color = (int)t->value->int32; + set_time_layers_color(GColorFromHEX(s_options.time_color)); + break; + + case KEY_HR_COLOR: + s_options.hr_color = (int)t->value->int32; + set_hr_layer_color(GColorFromHEX(s_options.hr_color)); + break; + + case KEY_MIN_COLOR: + s_options.min_color = (int)t->value->int32; + set_min_layers_color(GColorFromHEX(s_options.min_color)); + break; + + case KEY_WD_COLOR: + s_options.wd_color = (int)t->value->int32; + set_wd_layers_color(GColorFromHEX(s_options.wd_color)); + break; + + case KEY_WEATHERDATE_ALIGNMENT: + s_options.weatherdate_alignment = (int)t->value->int32; + #if defined(PBL_RECT) + text_layer_set_text_alignment(weather_layer1, row_alignment(s_options.weatherdate_alignment)); + text_layer_set_text_alignment(date_layer, row_alignment(s_options.weatherdate_alignment)); + #elif defined(PBL_ROUND) + text_layer_set_text_alignment(weather_layer1, row_alignment(DEFAULT_CHALK_ALIGNMENT)); + text_layer_set_text_alignment(weather_layer2, row_alignment(DEFAULT_CHALK_ALIGNMENT)); + text_layer_set_text_alignment(weather_layer_center, row_alignment(DEFAULT_CHALK_ALIGNMENT)); + text_layer_set_text_alignment(date_layer, row_alignment(DEFAULT_CHALK_ALIGNMENT)); + #endif + break; + + case KEY_HOURMINUTES_ALIGNMENT: + row = &data->rows[HOUR_ROW]; + s_options.hourminute_alignment = (int)t->value->int32; + text_layer_set_text_alignment(row->label, row_alignment(s_options.hourminute_alignment)); + row = &data->rows[MINUTE_ROW1]; + text_layer_set_text_alignment(row->label, row_alignment(s_options.hourminute_alignment)); + row = &data->rows[MINUTE_ROW2]; + text_layer_set_text_alignment(row->label, row_alignment(s_options.hourminute_alignment)); + break; + + case KEY_WEATHERDATE_READABILITY: + s_options.weatherdate_readability = (int)t->value->int32; + break; + + case KEY_MINUTES_READABILITY: + s_options.minutes_readability = (int)t->value->int32; + break; + + case KEY_VIBBRATE_BT_STATUS: + s_options.vibrate_bt_status = (int)t->value->int32; + break; + + case KEY_JS_READY: + s_options.pebble_js_ready = (int)t->value->int32; + #if DEBUG + APP_LOG(APP_LOG_LEVEL_DEBUG, "inbox_received_callback: s_options.pebble_js_ready: %d", s_options.pebble_js_ready); + #endif + break; + } + // Look for next item + t = dict_read_next(iterator); + } + + //check update weather & date layers + update_date_layer(); + update_weather_layer(); +} + +char *translate_error(AppMessageResult result) { + switch (result) { + case APP_MSG_OK: return "APP_MSG_OK"; + case APP_MSG_SEND_TIMEOUT: return "APP_MSG_SEND_TIMEOUT"; + case APP_MSG_SEND_REJECTED: return "APP_MSG_SEND_REJECTED"; + case APP_MSG_NOT_CONNECTED: return "APP_MSG_NOT_CONNECTED"; + case APP_MSG_APP_NOT_RUNNING: return "APP_MSG_APP_NOT_RUNNING"; + case APP_MSG_INVALID_ARGS: return "APP_MSG_INVALID_ARGS"; + case APP_MSG_BUSY: return "APP_MSG_BUSY"; + case APP_MSG_BUFFER_OVERFLOW: return "APP_MSG_BUFFER_OVERFLOW"; + case APP_MSG_ALREADY_RELEASED: return "APP_MSG_ALREADY_RELEASED"; + case APP_MSG_CALLBACK_ALREADY_REGISTERED: return "APP_MSG_CALLBACK_ALREADY_REGISTERED"; + case APP_MSG_CALLBACK_NOT_REGISTERED: return "APP_MSG_CALLBACK_NOT_REGISTERED"; + case APP_MSG_OUT_OF_MEMORY: return "APP_MSG_OUT_OF_MEMORY"; + case APP_MSG_CLOSED: return "APP_MSG_CLOSED"; + case APP_MSG_INTERNAL_ERROR: return "APP_MSG_INTERNAL_ERROR"; + default: return "UNKNOWN ERROR"; + } +} + +void message_dropped(AppMessageResult reason, void *context) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Dropped message! Reason given: %s", translate_error(reason)); +} + +void message_out_success(DictionaryIterator *iter, void *context) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Message sent."); +} + +void message_out_failed(DictionaryIterator *iter, AppMessageResult reason, void *context) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Failed to send message. Reason = %s", translate_error(reason)); +} \ No newline at end of file diff --git a/src/c/messaging.h b/src/c/messaging.h new file mode 100644 index 0000000..3b4166a --- /dev/null +++ b/src/c/messaging.h @@ -0,0 +1,7 @@ +#pragma once + + void send(int key, int value, int key2, int value2, int key3, char value3[64]); + void inbox_received_callback(DictionaryIterator *iterator, void *context); + void message_dropped(AppMessageResult reason, void *context); + void message_out_success(DictionaryIterator *iter, void *context); + void message_out_failed(DictionaryIterator *iter, AppMessageResult reason, void *context); \ No newline at end of file diff --git a/src/c/num2words.c b/src/c/num2words.c new file mode 100644 index 0000000..a67c29b --- /dev/null +++ b/src/c/num2words.c @@ -0,0 +1,217 @@ +#include "num2words.h" + +#include +#include + +static const char* const ONES[] = { + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine" +}; + +static const char* const TEENS[] ={ + "", + "eleven", + "twelve", + "thirteen", + "fourteen", + "fifteen", + "sixteen", + "seventeen", + "eighteen", + "nineteen" +}; + +static const char* const TEENS_SPLIT[][2] = { + {"", ""}, + {"eleven",""}, + {"twelve",""}, + {"thirteen",""}, + {"four","teen"}, + {"fifteen",""}, + {"sixteen",""}, + {"seven","teen"}, + {"eight","teen"}, + {"nine","teen"} +}; + +static const char* const TENS[] = { + "", + "ten", + "twenty", + "thirty", + "forty", + "fifty", + "sixty", + "seventy", + "eighty", + "ninety" +}; + +static const char* STR_OH_TICK = "o'"; +static const char* STR_O_TICK = "o"; +static const char* STR_CLOCK = "clock"; +static const char* STR_NOON = "noon"; +static const char* STR_MIDNIGHT = "midnight"; +static const char* STR_QUARTER = "quarter"; +static const char* STR_TO = "to"; +static const char* STR_PAST = "past"; +static const char* STR_HALF = "half"; +static const char* STR_AFTER = "after"; + +static size_t append_number(char* words, int num) { + int tens_val = num / 10 % 10; + int ones_val = num % 10; + + size_t len = 0; + + if (tens_val > 0) { + if (tens_val == 1 && num != 10) { + strcat(words, TEENS[ones_val]); + return strlen(TEENS[ones_val]); + } + strcat(words, TENS[tens_val]); + len += strlen(TENS[tens_val]); + if (ones_val > 0) { + strcat(words, " "); + len += 1; + } + } + + if (ones_val > 0 || num == 0) { + strcat(words, ONES[ones_val]); + len += strlen(ONES[ones_val]); + } + return len; +} + +void fuzzy_time_to_words(int hours, int minutes, char* words) { + int fuzzy_hours = hours; + int fuzzy_minutes = ((minutes + 2) / 5) * 5; + + // Handle hour & minute roll-over. + if (fuzzy_minutes > 55) { + fuzzy_minutes = 0; + fuzzy_hours += 1; + if (fuzzy_hours > 23) { + fuzzy_hours = 0; + } + } + + time_to_common_words(fuzzy_hours, fuzzy_minutes, words); +} + +// returns number of lines written +void time_to_common_words(int hours, int minutes, char *words) { + // TODO: make an app-safe assert + // PBL_ASSERT(hours >= 0 && hours < 24, "Invalid number of hours"); + // PBL_ASSERT(minutes >= 0 && minutes < 60, "Invalid number of minutes"); + + size_t written = 0; + strcpy(words, ""); + + if (minutes != 0 && (minutes >= 10 || minutes == 5 || hours == 0 || hours == 12)) { + if (minutes == 15) { + written += sprintf(words, "%s %s ", STR_QUARTER, STR_AFTER); + } else if (minutes == 45) { + written += sprintf(words, "%s %s ", STR_QUARTER, STR_TO); + hours = (hours + 1) % 24; + } else if (minutes == 30) { + written += sprintf(words, "%s %s ", STR_HALF, STR_PAST); + } else if (minutes < 30) { + written += append_number(words, minutes); + written += sprintf(words + written, " %s ", STR_AFTER); + } else { + written += append_number(words, 60 - minutes); + written += sprintf(words + written, " %s ", STR_TO); + hours = (hours + 1) % 24; + } + } + + if (hours == 0) { + written += sprintf(words + written, "%s", STR_MIDNIGHT); + } else if (hours == 12) { + strcat(words, STR_NOON); + written += sprintf(words + written, "%s", STR_NOON); + } else { + written += append_number(words, hours % 12); + } + + if (minutes < 10 && minutes != 5 && !(hours == 0 || hours == 12)) { + if (minutes == 0) { + sprintf(words + written, " %s%s", STR_OH_TICK, STR_CLOCK); + } else { + sprintf(words + written, " %s%s", STR_OH_TICK, ONES[minutes]); + } + } +} + + + +// o'clock (0) and plain number words (10..) +void minute_to_formal_words(int minutes, int displayPrefix, char *first_word, char *second_word) { + // PBL_ASSERT(minutes >= 0 && minutes < 60, "Invalid number of minutes"); + + strcpy(first_word, ""); + strcpy(second_word, ""); + + + if (minutes == 0) { + strcat(first_word, STR_OH_TICK); + strcat(first_word, STR_CLOCK); + return; + } + if (minutes < 10) { + if (displayPrefix) { + strcat(first_word, STR_O_TICK); //add "o" + strcat(second_word, ONES[minutes%10]); //new + } + else { + strcat(first_word, ONES[minutes%10]); + } + return; + } + if (minutes > 10 && minutes < 20) { + strcat(first_word, TEENS_SPLIT[(minutes - 10) % 10][0]); + strcat(second_word, TEENS_SPLIT[(minutes - 10) % 10][1]); + return; + } + + strcat(first_word, TENS[minutes / 10 % 10]); + + int minute_ones = minutes % 10; + if (minute_ones) { + strcat(second_word, ONES[minute_ones]); + } +} + +void hour_to_12h_word(int hours, char *word) { + // PBL_ASSERT(hours >= 0 && hours < 24, "Invalid number of hours"); + hours = hours % 12; + + if (hours == 0) { + hours = 12; + } + + strcpy(word, ""); + + append_number(word, hours); +} + + +void hour_to_24h_word(int hours, char *words) { + // PBL_ASSERT(hours >= 0 && hours < 24, "Invalid number of hours"); + + hours = hours % 24; + + strcpy(words, ""); + + append_number(words, hours); +} \ No newline at end of file