diff --git a/package.json b/package.json index 9cc1a91..90f3dd5 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,17 @@ "location" ], "watchapp": { - "watchface": false + "watchface": false, + "hiddenApp": false, + "onlyShownOnCommunication": false }, "messageKeys": [ + "READY", "SEARCH", "SUCCESS", "DISTANCE", "TIME", - "STEPS", - "STEPS_DISTANCES" + "INSTRUCTIONS[20]" ], "resources": { "media": [ @@ -77,6 +79,24 @@ "name": "ICON_WALK_WHITE", "targetPlatforms": null, "type": "bitmap" + }, + { + "file": "images/error_network.png", + "name": "ICON_ERROR_NETWORK", + "targetPlatforms": null, + "type": "bitmap" + }, + { + "file": "images/error_api.png", + "name": "ICON_ERROR_API", + "targetPlatforms": null, + "type": "bitmap" + }, + { + "file": "images/error_other.png", + "name": "ICON_ERROR_OTHER", + "targetPlatforms": null, + "type": "bitmap" } ] } diff --git a/resources/images/error_api.png b/resources/images/error_api.png new file mode 100644 index 0000000..e74681e Binary files /dev/null and b/resources/images/error_api.png differ diff --git a/resources/images/error_network.png b/resources/images/error_network.png new file mode 100644 index 0000000..f4012ee Binary files /dev/null and b/resources/images/error_network.png differ diff --git a/resources/images/error_other.png b/resources/images/error_other.png new file mode 100644 index 0000000..29d054e Binary files /dev/null and b/resources/images/error_other.png differ diff --git a/src/colors.h b/src/colors.h index d42ca98..fbd0204 100644 --- a/src/colors.h +++ b/src/colors.h @@ -4,3 +4,5 @@ #define COLOR_BIKE GColorTiffanyBlue #define COLOR_TRAIN GColorSunsetOrange #define COLOR_WALK GColorChromeYellow + +#define COLOR_ERROR GColorLightGray diff --git a/src/directions_window.c b/src/directions_window.c index de30092..eafa55e 100644 --- a/src/directions_window.c +++ b/src/directions_window.c @@ -1,11 +1,11 @@ -#include -#include "colors.h" -#include "directions_window.h" - #include #include "colors.h" #include "select_window.h" #include "directions_window.h" +#include "error_window.h" + +#define MAX_STEP_COUNT 20 +#define MAX_STEP_CHARS 128 // The main window static Window *window; @@ -17,6 +17,18 @@ static GColor selected_type_color; static DictationSession *dictation_session; static char *address; +// TODO: implement a timer, that kills the proccess if the message is never send (needed?) +static bool AppMessageIsReady; +static bool AppMessageSendOnCallback; +static int RouteDataDistance; +static int RouteDataTime; +static char RouteDataSteps[MAX_STEP_COUNT][MAX_STEP_CHARS]; +static int RouteDataStepsCount; + +// Function declarations +static void app_message_send_search_data(); +void window_display_error(enum ErrorType err); + // ****************************** // * DIRECTON LIST (MENU LAYER) * @@ -51,6 +63,7 @@ static void draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuIndex } } +// FIXME: Will probably not be necessary later! static void selection_will_change_callback(struct MenuLayer *menu_layer, MenuIndex *new_index, MenuIndex old_index, void *context) { // Change the highlight color #ifdef PBL_COLOR @@ -62,7 +75,6 @@ static void selection_will_change_callback(struct MenuLayer *menu_layer, MenuInd // All other cells default: menu_layer_set_highlight_colors(directions_list, GColorWhite, selected_type_color); - break; } #endif } @@ -77,22 +89,150 @@ static void dictation_session_callback(DictationSession *session, DictationSessi if (status == DictationSessionStatusSuccess) { // Store the address and load a route address = transcript; - // TODO load route data + // Send the search data + app_message_send_search_data(); } else { - // TODO display proper error window + // Dictation failed, remove this window window_stack_remove(window, true); } } -// ****************** -// * APP SYNC STUFF * -// ****************** +// ********************* +// * APP MESSAGE STUFF * +// ********************* + +// Accept data from the watch +static void app_message_inbox_recived_callback(DictionaryIterator *iter, void *context) { + // Test all possible message types + Tuple *message; + + // Test if the recived message is for key READY + message = dict_find(iter, MESSAGE_KEY_READY); + if (message) { + // Set status to ready + AppMessageIsReady = (bool)message->value->int16; + // Send pending search data + if (AppMessageSendOnCallback) { + app_message_send_search_data(); + } + } + + // Test if the recived message is for key SUCCESS + message = dict_find(iter, MESSAGE_KEY_SUCCESS); + if (message) { + // Respond with the correct UI + switch ((int)message->value->int32) { + // Success + case 0: + // TODO: update the ui in some way + window_display_error(Other); + break; + // Route not found / api error + case 1: + window_display_error(Api); + break; + // Too many steps (== route not found error) + case 2: + window_display_error(Api); + break; + default: + window_display_error(Other); + } + } + + // Test if the recived message is for key DISTANCE + message = dict_find(iter, MESSAGE_KEY_DISTANCE); + if (message) { + RouteDataDistance = (int)message->value->int32; + } + + // Test if the recived message is for key TIME + message = dict_find(iter, MESSAGE_KEY_TIME); + if (message) { + RouteDataTime = (int)message->value->int32; + } + + // Test if the recived message is for key INSTRUCTIONS + for (int i = 0; i < MAX_STEP_COUNT; i++) { + message = dict_find(iter, MESSAGE_KEY_INSTRUCTIONS + i); + if (message) { + // Copy the string into the string array + static char *empty; + strncat(empty, message->value->cstring, MAX_STEP_CHARS); + strcpy(RouteDataSteps[i], empty); + // Store the new length of the RouteDataSteps + RouteDataStepsCount = i + 1; + } + } +} + +// Respond with error, if any data is lost +static void app_message_inbox_dropped_callback(AppMessageResult reason, void *context) { + // Display the network error + window_display_error(Network); +} + +// Respond with error, if data can not be send to watch +static void app_message_outbox_failed_callback(DictionaryIterator *iter, AppMessageResult reason, void *context) { + // Display the network error + window_display_error(Network); +} + +// Send the search data to the phone +static void app_message_send_search_data() { + // Send the data to the phone if conn is ready + if (AppMessageIsReady) { + // Create a string with the correct length + char message[sizeof(address) + 1]; + // Add the type as the first char + message[0] = '0' + selected_type_enum; + // Add the address + strcat(message, address); + // Make sure the string is terminated correctely, just in case + message[sizeof(message) - 1] = '\n'; + + // Write string to bluetooth storage + DictionaryIterator *iter; + if (app_message_outbox_begin(&iter) == APP_MSG_OK) { + dict_write_cstring(iter, MESSAGE_KEY_SEARCH, message); + // Send the outbox + app_message_outbox_send(); + } else { + // Display network error + window_display_error(Network); + } + } else { + AppMessageSendOnCallback = true; + } +} + +// Set up the whole app message thing (once the address is worked out) +static void app_message_start() { + // Set initial values + AppMessageIsReady = true; + AppMessageSendOnCallback = false; + // Register all callbacks + app_message_register_inbox_received(app_message_inbox_recived_callback); + app_message_register_inbox_dropped(app_message_inbox_dropped_callback); + app_message_register_outbox_failed(app_message_outbox_failed_callback); + // Open the app-message + app_message_open(APP_MESSAGE_INBOX_SIZE_MINIMUM, APP_MESSAGE_OUTBOX_SIZE_MINIMUM); +} + // ******************** // * WINDOW LIFECYCLE * // ******************** +// Network error callback +void window_display_error(enum ErrorType err) { + // Show the error window + error_window_push(err); + // Remove this window from the window stack + window_stack_remove(window, false); +} + // Window unload handler static void window_unload() { // Destroy the menu layer @@ -101,6 +241,9 @@ static void window_unload() { // Destroy the dictation session dictation_session_destroy(dictation_session); + // Remove all app message callbacks + app_message_deregister_callbacks(); + // Destroy the window window_destroy(window); window = NULL; @@ -175,4 +318,9 @@ void directions_window_push() { // Start the dictation session TODO: Change this, once app sync stuff is working // dictation_session_start(dictation_session); address = "Meerbusch Brockhofweg 9"; // WIP address string + + // Open the connection to the phone + app_message_start(); + // TODO: Remove this for final version; this is a playeholder implementation (skipping the dictation) + app_message_send_search_data(); } diff --git a/src/error_window.c b/src/error_window.c new file mode 100644 index 0000000..6fd1ca7 --- /dev/null +++ b/src/error_window.c @@ -0,0 +1,112 @@ +#include +#include "colors.h" +#include "error_window.h" + +// The main window +static Window *window; +static BitmapLayer *error_icon_layer; +static TextLayer *error_text_layer; + +// The error to be displayed +int display_error_type; + +// Error icon / text +static GBitmap *icon_error; +static char *text_error; + + +static void window_unload() { + // Destroy the text layer & the error icon layer + bitmap_layer_destroy(error_icon_layer); + text_layer_destroy(error_text_layer); + + // Destroy the image resource + gbitmap_destroy(icon_error); + + // Destroy the window + window_destroy(window); + window = NULL; +} + +static void window_load() { + // Get the root layer + Layer *window_layer = window_get_root_layer(window); + GRect bounds = layer_get_bounds(window_layer); + + switch(display_error_type) { + // Network + case 0: + icon_error = gbitmap_create_with_resource(RESOURCE_ID_ICON_ERROR_NETWORK); + text_error = "Could not reach phone"; + break; + // Api + case 1: + icon_error = gbitmap_create_with_resource(RESOURCE_ID_ICON_ERROR_API); + text_error = "No route could be found"; + break; + // Other / Undefined + case 2: + icon_error = gbitmap_create_with_resource(RESOURCE_ID_ICON_ERROR_OTHER); + text_error = "Something went wrong"; + break; + } + + // Set up the error image layer GRect(x, y, w, h) + #ifdef PBL_ROUND + // Round spacing / Align + error_icon_layer = bitmap_layer_create(GRect(0, 0, bounds.size.w, 110)); + bitmap_layer_set_alignment(error_icon_layer, GAlignBottom); + #else + // Square spacing / Align + error_icon_layer = bitmap_layer_create(GRect(0, 0, bounds.size.w, 100)); + bitmap_layer_set_alignment(error_icon_layer, GAlignCenter); + #endif + bitmap_layer_set_bitmap(error_icon_layer, icon_error); + bitmap_layer_set_compositing_mode(error_icon_layer, GCompOpSet); + #ifdef PBL_COLOR + // Color only + bitmap_layer_set_background_color(error_icon_layer, COLOR_ERROR); + #endif + + layer_add_child(window_layer, (Layer *)error_icon_layer); + + // Set up the error text + #ifdef PBL_ROUND + // Round spacing + error_text_layer = text_layer_create(GRect(0, 110, bounds.size.w, bounds.size.h - 110)); + #else + // Square spacing + error_text_layer = text_layer_create(GRect(0, 100, bounds.size.w, bounds.size.h - 100)); + #endif + text_layer_set_text(error_text_layer, text_error); + text_layer_set_font(error_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); + text_layer_set_text_alignment(error_text_layer, GTextAlignmentCenter); + text_layer_set_text_color(error_text_layer, GColorBlack); + #ifdef PBL_COLOR + // Color only + text_layer_set_background_color(error_text_layer, COLOR_ERROR); + #endif + + layer_add_child(window_layer, (Layer *)error_text_layer); + #ifdef PBL_ROUND + // Round text insets + text_layer_enable_screen_text_flow_and_paging(error_text_layer, 0); + #endif +} + +void error_window_push(enum ErrorType error_type) { + display_error_type = error_type; + if (!window) { + // Create the window + window = window_create(); + + // Set up the window handlers + window_set_window_handlers(window, (WindowHandlers) { + .load = window_load, + .unload = window_unload, + }); + } + + // Display the window + window_stack_push(window, true); +} diff --git a/src/error_window.h b/src/error_window.h new file mode 100644 index 0000000..2cd1c43 --- /dev/null +++ b/src/error_window.h @@ -0,0 +1,13 @@ +#pragma once +#include + +enum ErrorType { + // Problem connection to phone + Network = 0, + // Error response from api + Api = 1, + // Something simply went wrong + Other = 2 +}; + +void error_window_push(enum ErrorType); diff --git a/src/js/app.js b/src/js/app.js index 51a2531..54556c2 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -1,33 +1,123 @@ +/** +* SETUP OVERVIEW +* +* About the send messages +* ––– +* (1) Overview data is send (distance, time needed) +* (2) Step data array is send (each step string, max 20 entrys) -> THE LAST SEND INDEX IS THE LENGTH! +* (3) Success value is send (terminates the transmittion; it's true/false +* value determines whether the transmition was successfull or not (E.g. +* if false is send as the success first, no route was found) +* +* About the success codes: +* ––– +* 0 = Success; 1 = Route not found; 2 = Too many steps; ... +* +* About the recived message: +* ––– +* Field: SEARCH +* Contents: {selected_type}{address} (e.g. 0Brockhofweg 9) +* The first character is always the type selected, the rest +* is the written address. +* {selected_type} vals: 0 = Car; 1 = Bike; 2 = Train; 3 = Walk; +*/ + + // Data keys var keys = require('message_keys'); +var maxStepCount = 20; -// Common functions -function createData(success, distance, time, steps, stepsDistances) { + +// App Message functions +function sendSuccess(code) { + // Build message + var key = keys.SUCCESS; var dict = {}; - dict[keys.SUCCESS] = success; - dict[keys.DISTANCE] = distance; - dict[keys.TIME] = time; - dict[keys.STEPS] = steps; - dict[keys.STEPS_DISTANCES] = stepsDistances; - return dict; + dict[key] = +code; + + // Send message to pebble + Pebble.sendAppMessage(dict, function() { + // Success! + console.log('Transmission completed'); + }, function() { + // Error + console.log('Transmission failed at [SUCCESS MESSAGE]'); + }); } -// Some dummy data (let's make the c app work first ;) +function sendStepItem(stepList, index) { + // Build message + var key = keys.INSTRUCTIONS + index; + var dict = {}; + dict[key] = stepList[index]; + + // Send message to pebble + Pebble.sendAppMessage(dict, function() { + // Success, send next item + index ++; + if (index < stepList.length && index < maxStepCount) { + // Recursive callbacks, hell yeah! + sendStepItem(stepList, index); + } else { + // We are finished + sendSuccess(0); + } + }, function() { + // Error + console.log('Transmission failed at index '.concat(index)); + }); +} + +function sendRoute(success, distance, time, stepList) { + // Send message to pebble if a route was found + if (success) { + // Build message + var keyDistance = keys.DISTANCE; + var keyTime = keys.TIME; + dict[keyDistance] = +distance; + dict[keyTime] = +time; + + // Transmit + Pebble.sendAppMessage(dict, function() { + // Success! + sendStepItem(stepList, 0); + }, function() { + // Error + console.log('Transmission failed at [OVERVIEW]'); + }); + } else { + // Send error message (route not found) + sendSuccess(1); + } +} + +// Api data functions FIXME: Acutally use api +function fetchAndSendRoute(routeType, destination) { + // TODO: Add api data here (+ use geolocation etc) + console.log(routeType); + console.log(destination); + /* dummy data: */ + sendRoute(true, 560, 16, ['This is the first step', 'This is the second step', 'This is the third step', 'This is the final step']); +} + +// Accept data from the pebble watch Pebble.addEventListener('appmessage', function(e) { // Get the dictionary from the message var dict = e.payload; - console.log(dict.SEARCH); - var message = createData( - true, - 140, - 13, - ["Do sometoing", "Step 3", "the very last step"], - [11, 0, 5321] - ); - Pebble.sendAppMessage(message, function() { - console.log('Message sent successfully: ' + JSON.stringify(message)); - }, function(e) { - console.log('Message failed: ' + JSON.stringify(message)); - }); + // Does the SEARCH field exist? + if (dict['SEARCH']) { + fetchAndSendRoute(dict['SEARCH'].substr(0, 1), dict['SEARCH'].substr(1)); + } +}); + +// Ready event +Pebble.addEventListener('ready', function() { + // Tell the watch that the js part is ready + var key = keys.READY; + var dict = {}; + dict[key] = true; + Pebble.sendAppMessage(dict); + + console.log('Js part is ready!'); }); diff --git a/src/select_window.c b/src/select_window.c index 2270fa5..0b211b5 100644 --- a/src/select_window.c +++ b/src/select_window.c @@ -27,7 +27,7 @@ static uint16_t get_num_rows_callback(struct MenuLayer *menu_layer, uint16_t sec static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { // This part is for round watches only - #if defined(PBL_ROUND) + #ifdef PBL_ROUND // Round bool selected = menu_layer_is_index_selected(menu_layer, cell_index); if (selected) {