diff options
author | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2014-10-16 23:32:17 +0400 |
---|---|---|
committer | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2014-10-16 23:32:17 +0400 |
commit | 9e38f5606ec18eb1805c8d99639843525fab9316 (patch) | |
tree | 6fb1e2c7f45037832b7caacd1d9c6cd109faccec | |
parent | bfda6bc48548a9a77d59aa5190dd98934ef885aa (diff) |
WatchFace and plain text
-rw-r--r-- | README.md | 157 | ||||
-rw-r--r-- | appinfo.json | 7 | ||||
-rw-r--r-- | resources/configuration.html | 11 | ||||
-rw-r--r-- | src/js/pebble-js-app.src.js | 82 | ||||
-rw-r--r-- | src/pebble-my-data.c | 46 |
5 files changed, 85 insertions, 218 deletions
@@ -1,8 +1,8 @@ -# Pebble My Data App +# Pebble My Data App - WatchFace/TXT version Pebble watches application to show only your own data, prepared on your own server. This software is licensed under the terms of the MIT license. -Sources available on [github](https://github.com/bahbka/pebble-my-data). +Original sources available on [github](https://github.com/bahbka/pebble-my-data). Inspired by [Pebble Cards](http://keanulee.com/pebblecards). [My Data at Pebble App Store](https://apps.getpebble.com/applications/53b0607c94943f8e710001e2) @@ -11,160 +11,11 @@ Inspired by [Pebble Cards](http://keanulee.com/pebblecards). ## Features -* Fetch JSON from custom URL, specified in settings +* Fetch TXT from custom URL, specified in settings * No companion app required, using PebbleKit JS * Force update with buttons or shaking -* Append select=1/select=2 GET param on short/long select button update -* Ability to change up/down buttons behavior from JSON (scrolling or up=1|2,down=1|2 params) -* Append coordinates to URL (configurable) -* Append HTTP request header Pebble-Token (unique to device/app pair), can be used for server-side device identification -* Authentication (see documentation) -* Scrollable data area -* Custom update interval, specified in JSON -* Vibrate on update if specified in JSON -* Change text font from JSON -* Black/white theme switched from JSON -* Turn on light from JSON -* Blink content from JSON -* Define scroll offset as percentage after update from JSON +* Custom update interval * Vibrate on bluetooth connection loss (configurable) * Watches battery charge status * Digital clock (12h/24h support), seconds dots blinking (configurable) -## Changelog - -### 2.3.5 -- Reduced GPS cache lifetime (from 30 mins to 10 mins). - -### 2.3.4 -- Workaround for APP_MSG_INTERNAL_ERROR (request last response after 0.1s if occur) - -### 2.3.3 -- Extract fields from any level of JSON (useful with [KimonoLabs API](https://www.kimonolabs.com)); multiple content fields will be concatenated with '\n\n'; other fields will be converted to integer, first copy will be used -- Don't schedule update if another one already in progress -- Keeps update type on retries when update failed - -### 2.3.2 -- Update with shake function (append shake=1 GET param while update, configurable) -- Changed scroll param behavior, now used to define scroll offset as percentage -- Truncate content if too big - -### 2.2.0 -- Authentication (see documentation) - -### 2.1.2 -- Ability to change up/down buttons behavior from JSON (scrolling or up=1|2,down=1|2 params) -- Added HTTP request header Pebble-Token (unique to device/app pair), can be used for server-side device identification -- **WARNING:** Changed short=1/long=1 params to select=1/select=2 (sorry for this) - -### 2.0.3 - -- Append coordinates to URL (configurable) -- Digital clock font, AM/PM support -- Seconds dots blinking (configurable) -- Configurable vibration on bluetooth loss -- Turn on light (value in JSON) -- Blink content (value in JSON) -- Scroll-up content after update (value in JSON) -- Improved configuration page -- Some minor fixes - -### 1.1.0 - -- Append short=1 or long=1 GET param to URL on short/long select button update (changed to select=1/select=2 in 2.1.2) - -### 1.0.0 - -- Initial release - -## Screenshots -![pebble screenshot 1](https://raw.githubusercontent.com/bahbka/pebble-my-data/master/stuff/screenshots/pebble-screenshot_2014-07-06_18-18-15.png) -![pebble screenshot 2](https://raw.githubusercontent.com/bahbka/pebble-my-data/master/stuff/screenshots/pebble-screenshot_2014-07-06_18-19-33.png) -![pebble screenshot 3](https://raw.githubusercontent.com/bahbka/pebble-my-data/master/stuff/screenshots/pebble-screenshot_2014-07-06_18-23-00.png) -![pebble screenshot 4](https://raw.githubusercontent.com/bahbka/pebble-my-data/master/stuff/screenshots/pebble-screenshot_2014-07-06_18-26-22.png) -![pebble screenshot 5](https://raw.githubusercontent.com/bahbka/pebble-my-data/master/stuff/screenshots/pebble-screenshot_2014-07-06_18-27-09.png) -[![android screenshot 1](https://raw.githubusercontent.com/bahbka/pebble-my-data/master/stuff/screenshots/Screenshot_2014-07-06-18-31-03_small.png)](https://raw.githubusercontent.com/bahbka/pebble-my-data/master/stuff/screenshots/Screenshot_2014-07-06-18-31-03.png) - -## JSON - -JSON output example (some fields are optional): - - { - "content": "Hello\nWorld!", - "refresh": 300, - "vibrate": 0, - "font": 4, - "theme": 0, - "scroll": 33, - "light": 1, - "blink": 3, - "updown": 1, - "auth": "salt" - } - -GET param short=1 or long=1 added to URL on short or long select button update - -### content -Your content to display. Use "\n" as CR. - -### refresh -Next update delay in seconds. - -### vibrate - -- 0 - Don't vibrate -- 1 - Short vibrate -- 2 - Double vibrate -- 3 - Long vibrate - -### font - -- 1 - GOTHIC_14 -- 2 - GOTHIC_14_BOLD -- 3 - GOTHIC_18 -- 4 - GOTHIC_18_BOLD -- 5 - GOTHIC_24 -- 6 - GOTHIC_24_BOLD -- 7 - GOTHIC_28 -- 8 - GOTHIC_28_BOLD - -### theme - -- 0 - Black -- 1 - White - -### scroll -Scroll content to offset (as percentage 0..100). -If param not defined or >100 - position will be kept. - -### light - -- 0 - Do nothing -- 1 - Turn pebble light on for short time - -### blink - -- 1..10 - Blink content count (blinks with black/white for "count" times) - -### updown -- 0 use up/down buttons for scrolling -- 1 use up/down buttons for update, appending up=1|2/down=1|2 params (1=short/2=long) - -### auth -Salt for Pebble-Auth hash (see below) - -## Auth - -Authentication algorithm example (reinvent the wheel): - 1. -> Pebble makes HTTP request with Pebble-Token header (Pebble App Token by default, unique to device/app pair, can be changed at configuration page, clear to restore default) - 2. <- Server answers with JSON like { ..., "content": "logging in...", "refresh": 5, "auth": "randomsalt", ... } - 3. Pebble calculates MD5(MD5(password)+"randomsalt"), saves it as auth token and uses as Pebble-Auth HTTP request header in future requests. - 4. -> Pebble makes HTTP request after 5 seconds with Pebble-Token header and with Pebble-Auth header (calculated and stored in previous step) - 5. Server checks Pebble-Token and Pebble-Auth headers if data equal data in database (Pebble-Token <=> login, calculate MD5(password_md5_db+"randomsalt")) - 6. <- Server answers with private content (seems your need https for more security), or some error if auth failed; auth field in JSON not needed anymore, until you desire to regenerate auth token with new salt (paranoid mode) or to clear Pebble-Auth header - -To clear Pebble-Auth header, send { ..., "auth": "", ...} (eg logout). - -## Bugs - -Sometime after install JS app fails to start, issue related Pebble App. Force stop Pebble App and start it again. diff --git a/appinfo.json b/appinfo.json index aa6386f..70b40d5 100644 --- a/appinfo.json +++ b/appinfo.json @@ -1,12 +1,12 @@ { "uuid": "9be6e663-b542-49f3-82d9-9bc174ddc63a", "shortName": "My Data", - "longName": "My Data", + "longName": "My Data (watchface)", "companyName": "bahbka", "versionCode": 235, "versionLabel": "2.3.5", "watchapp": { - "watchface": false + "watchface": true }, "capabilities": [ "configurable", "location" ], "appKeys": { @@ -23,7 +23,8 @@ "config_location": 10, "config_vibrate": 11, "config_seconds": 12, - "config_shake": 13 + "config_shake": 13, + "config_interval": 14 }, "resources": { "media": [ diff --git a/resources/configuration.html b/resources/configuration.html index 59dac4b..bcd8833 100644 --- a/resources/configuration.html +++ b/resources/configuration.html @@ -79,7 +79,6 @@ vim: sw=2 ts=2 expandtab ai Password <input type="password" id="password" class="text" value=""> - </div> <hr size="1" /> @@ -92,6 +91,7 @@ vim: sw=2 ts=2 expandtab ai <input type="checkbox" id="config_location" class="checkbox"> </div> </div> + </div> <div class="param"> <div class="label"> @@ -121,6 +121,15 @@ vim: sw=2 ts=2 expandtab ai <input type="checkbox" id="config_shake" class="checkbox"> </div> </div> + + <div class="param"> + <div class="label"> + Update interval in seconds:<br> + </div> + <div class="checkbox"> + <input type="text" id="config_interval" class="text" style="width: 100px;"> + </div> + </div> <input type="submit" id="save" class="submit" value="save and apply"> </form> diff --git a/src/js/pebble-js-app.src.js b/src/js/pebble-js-app.src.js index 4bbed74..45d636d 100644 --- a/src/js/pebble-js-app.src.js +++ b/src/js/pebble-js-app.src.js @@ -23,7 +23,8 @@ var config = { "config_location": false, "config_vibrate": true, "config_seconds": true, - "config_shake": false + "config_shake": false, + "config_interval": 300 }; function extract_fields(raw) { @@ -40,7 +41,7 @@ function extract_fields(raw) { "light": INT_FIELD, "blink": INT_FIELD, "updown": INT_FIELD, - "auth": STR_FIELD + "auth": STR_FIELD, }; var result = {}; @@ -90,26 +91,17 @@ function http_request(url) { if (req.readyState == 4) { if(req.status == 200) { try { - var response = extract_fields(JSON.parse(req.responseText)); + //var response = extract_fields(JSON.parse(req.responseText)); //console.log("success: " + JSON.stringify(response)); + var response = req.responseText; - response["msg_type"] = MSG.JSON_RESPONSE; - - if (response["content"] && response["content"].length > CONTENT_MAX_LENGTH) { - response["content"] = response["content"].substring(0, CONTENT_MAX_LENGTH); - } - - window.localStorage.setItem('pebble-my-data-response', JSON.stringify(response)); - Pebble.sendAppMessage(response); - - if (response["auth"] != null) { - if (response["auth"] == "") { - window.localStorage.removeItem('pebble-my-data-auth'); - } else if (config["password"]) { - window.localStorage.setItem('pebble-my-data-auth', MD5(MD5(config["password"]) + response["auth"])); - } + if (response && response.length > CONTENT_MAX_LENGTH) { + response = response.substring(0, CONTENT_MAX_LENGTH); } + window.localStorage.setItem('pebble-my-data-response', response); + //Pebble.sendAppMessage(response); + Pebble.sendAppMessage({ "msg_type": MSG.JSON_RESPONSE, "content": response }); } catch(e) { console.log("json parse error " + e); Pebble.sendAppMessage({ "msg_type": MSG.ERROR }); @@ -127,35 +119,7 @@ function http_request(url) { } function fetch_data(url) { - if (config["config_location"]) { - if(navigator.geolocation) { - navigator.geolocation.getCurrentPosition( - function(position) { - var latitude = position.coords.latitude; - var longitude = position.coords.longitude; - - var s = (url.indexOf("?")===-1)?"?":"&"; - http_request(url + s + 'lat=' + latitude + '&lon=' + longitude); - }, - function(error) { - //error error.message - /* - TODO inform user about error - PERMISSION_DENIED (numeric value 1) - POSITION_UNAVAILABLE (numeric value 2) - TIMEOUT (numeric value 3) - */ - http_request(url); - }, - { maximumAge: 600000 } // 10 minutes - ); - } else { - //error geolocation not supported - http_request(url); - } - } else { - http_request(url); - } + http_request(url); } Pebble.addEventListener("ready", @@ -169,10 +133,15 @@ Pebble.addEventListener("ready", config = JSON.parse(json); //console.log("loaded config = " + JSON.stringify(config)); config["msg_type"] = MSG.CONFIG; + if (("config_interval" in config) && (typeof config["config_interval"] === 'string')) { + config["config_interval"] = parseInt(config["config_interval"]); + if (isNaN(config["config_interval"])) config["config_interval"] = 300; + } + //console.log("sending options 1 = " + JSON.stringify(config)); Pebble.sendAppMessage(config); // send current config to pebble } catch(e) { - console.log("stored config json parse error"); + console.log("stored config json parse error: " + json + ' - ' + e); Pebble.sendAppMessage({ "msg_type": MSG.ERROR }); } } @@ -183,8 +152,12 @@ Pebble.addEventListener("appmessage", function(e) { //console.log("received message: " + JSON.stringify(e.payload)); - config["msg_type"] = MSG.CONFIG; - Pebble.sendAppMessage(config); // send current config to pebble + if (("config_interval" in config) && (typeof config["config_interval"] === 'string')) { + config["config_interval"] = parseInt(config["config_interval"]); + if (isNaN(config["config_interval"])) config["config_interval"] = 300; + } + //console.log("sending options 2 = " + JSON.stringify(config)); + Pebble.sendAppMessage(config); // send current config to pebble if (config["url"]) { var url = config["url"]; @@ -192,7 +165,7 @@ Pebble.addEventListener("appmessage", if (e.payload["refresh"] == MSG.IN_RETRY) { response = window.localStorage.getItem('pebble-my-data-response'); - Pebble.sendAppMessage(JSON.parse(response)); + Pebble.sendAppMessage({ "msg_type": MSG.JSON_RESPONSE, "content": response }); } else { if (e.payload["refresh"] == MSG.SELECT_SHORT_PRESS_UPDATE) { @@ -242,7 +215,14 @@ Pebble.addEventListener("webviewclosed", function(e) { window.localStorage.setItem('pebble-my-data', e.response); config["msg_type"] = MSG.CONFIG; + //config = extract_fields(config); //console.log("push config = " + JSON.stringify(config)); + + if (("config_interval" in config) && (typeof config["config_interval"] === 'string')) { + config["config_interval"] = parseInt(config["config_interval"]); + if (isNaN(config["config_interval"])) config["config_interval"] = 300; + } + //console.log("sending options 3 = " + JSON.stringify(config)); Pebble.sendAppMessage(config); // send current config to pebble if (config["url"]) { diff --git a/src/pebble-my-data.c b/src/pebble-my-data.c index 4bbf371..c9110a0 100644 --- a/src/pebble-my-data.c +++ b/src/pebble-my-data.c @@ -47,12 +47,12 @@ static uint8_t blink_count; bool config_vibrate = true; bool config_seconds = false; bool config_shake = false; +int config_interval = 300; bool updown = false; bool update_in_progress = false; -#define DEFAULT_REFRESH 300*1000 #define RETRY_DELAY 60*1000 #define IN_RETRY_DELAY 100 #define REQUEST_TIMEOUT 30*1000 @@ -80,7 +80,8 @@ enum { // AppMessage keys KEY_CONFIG_LOCATION, KEY_CONFIG_VIBRATE, KEY_CONFIG_SECONDS, - KEY_CONFIG_SHAKE + KEY_CONFIG_SHAKE, + KEY_CONFIG_INTERVAL }; enum { // msg type @@ -504,6 +505,7 @@ void in_received_handler(DictionaryIterator *received, void *context) { } else { config_vibrate = false; } + persist_write_bool(KEY_CONFIG_VIBRATE, config_vibrate); } Tuple *config_seconds_tuple = dict_find(received, KEY_CONFIG_SECONDS); @@ -520,6 +522,7 @@ void in_received_handler(DictionaryIterator *received, void *context) { handle_timer_tick(NULL, MINUTE_UNIT); } } + persist_write_bool(KEY_CONFIG_SECONDS, config_seconds); } Tuple *config_shake_tuple = dict_find(received, KEY_CONFIG_SHAKE); @@ -535,8 +538,16 @@ void in_received_handler(DictionaryIterator *received, void *context) { accel_tap_service_unsubscribe(); } } + persist_write_bool(KEY_CONFIG_SHAKE, config_shake); } + Tuple *interval = dict_find(received, KEY_CONFIG_INTERVAL); + if (interval) { + config_interval = interval->value->uint32; + if (config_interval) + schedule_update(config_interval*1000, MSG_PERIODIC_UPDATE); + persist_write_int(KEY_CONFIG_INTERVAL, config_interval); + } // TODO location icon? } else if (msg_type_tuple->value->uint8 == MSG_ERROR) { @@ -551,6 +562,7 @@ void in_received_handler(DictionaryIterator *received, void *context) { Tuple *content_tuple = dict_find(received, KEY_CONTENT); if (content_tuple) { memcpy(content, content_tuple->value->cstring, strlen(content_tuple->value->cstring) + 1); + persist_write_string(KEY_CONTENT, content); Tuple *scroll_offset_tuple = dict_find(received, KEY_SCROLL); uint8_t scroll_offset = DONT_SCROLL; @@ -620,14 +632,11 @@ void in_received_handler(DictionaryIterator *received, void *context) { } } - // schedule next update - uint32_t delay = DEFAULT_REFRESH; - Tuple *refresh = dict_find(received, KEY_REFRESH); - if (refresh) { - delay = refresh->value->uint32 * 1000; - } + if (config_shake) + accel_tap_service_subscribe(handle_shake); - schedule_update(delay, MSG_PERIODIC_UPDATE); + if (config_interval) + schedule_update(config_interval*1000, MSG_PERIODIC_UPDATE); } } } @@ -716,6 +725,15 @@ static void click_config_provider_updown(void *context) { // prepare window! static void window_load(Window *window) { + if (persist_exists(KEY_CONFIG_VIBRATE)) + config_vibrate = persist_read_bool(KEY_CONFIG_VIBRATE); + if (persist_exists(KEY_CONFIG_SECONDS)) + config_seconds = persist_read_bool(KEY_CONFIG_SECONDS); + if (persist_exists(KEY_CONFIG_SHAKE)) + config_shake = persist_read_bool(KEY_CONFIG_SHAKE); + if (persist_exists(KEY_CONFIG_INTERVAL)) + config_interval = persist_read_int(KEY_CONFIG_INTERVAL); + Layer *window_layer = window_get_root_layer(window); // time layers @@ -789,8 +807,16 @@ static void window_load(Window *window) { bluetooth_connection_service_subscribe(&handle_bluetooth); handle_bluetooth(bluetooth_connection_service_peek()); - tick_timer_service_subscribe(MINUTE_UNIT, handle_timer_tick); + if (config_seconds) + tick_timer_service_subscribe(SECOND_UNIT, handle_timer_tick); + else + tick_timer_service_subscribe(MINUTE_UNIT, handle_timer_tick); handle_timer_tick(NULL, MINUTE_UNIT); + + if (persist_exists(KEY_CONTENT)) { + persist_read_string(KEY_CONTENT, content, sizeof(content)); + update_info_layer(content, 0, DONT_SCROLL, false); + } } static void window_unload(Window *window) { |