Browse Source

Initial commit

master
T. Meissner 6 years ago
commit
78c06b3dcb
8 changed files with 558 additions and 0 deletions
  1. +6
    -0
      .gitignore
  2. +52
    -0
      appinfo.json
  3. BIN
      resources/fonts/perfect-dos-vga.ttf
  4. BIN
      resources/images/background.png
  5. +132
    -0
      src/js/pebble-js-app.js
  6. +301
    -0
      src/main.c
  7. +26
    -0
      src/pebble_utils.h
  8. +41
    -0
      wscript

+ 6
- 0
.gitignore View File

@ -0,0 +1,6 @@
# Ignore build generated files
build
# Ignore waf lock file
.lock-waf*

+ 52
- 0
appinfo.json View File

@ -0,0 +1,52 @@
{
"uuid": "3c6e0682-a917-4283-a27e-349f2de629fa",
"shortName": "create_a_c_watchface",
"longName": "create_a_c_watchface",
"companyName": "goodcleanfun",
"versionLabel": "1.0",
"sdkVersion": "3",
"targetPlatforms": ["basalt"],
"watchapp": {
"watchface": true
},
"appKeys": {
"KEY_TEMPERATURE": 0,
"KEY_CONDITIONS": 1,
"KEY_ROOM_TEMP": 2
},
"capabilities":
[ "location" ],
"resources": {
"media": [
{
"type": "font",
"name": "FONT_PERFECT_DOS_16",
"file": "fonts/perfect-dos-vga.ttf",
"compatibility":"2.7"
},
{
"type": "font",
"name": "FONT_PERFECT_DOS_20",
"file": "fonts/perfect-dos-vga.ttf",
"compatibility":"2.7"
},
{
"type": "font",
"name": "FONT_PERFECT_DOS_24",
"file": "fonts/perfect-dos-vga.ttf",
"compatibility":"2.7"
},
{
"type": "font",
"name": "FONT_PERFECT_DOS_48",
"file": "fonts/perfect-dos-vga.ttf",
"compatibility":"2.7"
},
{
"type": "png",
"name": "TUTORIAL_BACKGROUND",
"file": "images/background.png"
}
]
}
}

BIN
resources/fonts/perfect-dos-vga.ttf View File


BIN
resources/images/background.png View File

Before After
Width: 144  |  Height: 168  |  Size: 33 KiB

+ 132
- 0
src/js/pebble-js-app.js View File

@ -0,0 +1,132 @@
var xhrRequest = function (url, type, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
callback(this.responseText);
};
xhr.open(type, url);
xhr.send();
};
function locationSuccess(pos) {
// generic API key, has to be replaced by developers one
var api_key = 'ca82d3c964da82f54d033abf702a46a5';
var lat = Math.round(pos.coords.latitude*10) / 10;
var lon = Math.round(pos.coords.longitude*10) / 10;
// Construct URL
var url = 'http://api.openweathermap.org/data/2.5/weather?lat=' +
lat + '&lon=' + lon + '&APPID=' + api_key;
// Send request to OpenWeatherMap
xhrRequest(url, 'GET',
function(responseText) {
// responseText contains a JSON object with weather info
var json = JSON.parse(responseText);
// Temperature in Kelvin requires adjustment
var temperature = Math.round(json.main.temp - 273.15);
console.log('Temperature is ' + temperature);
// Conditions
var conditions = json.weather[0].main;
console.log('Conditions are ' + conditions);
// Assemble dictionary using our keys
var dictionary = {
'KEY_TEMPERATURE': temperature,
'KEY_CONDITIONS': conditions
};
// Send to Pebble
Pebble.sendAppMessage(dictionary,
function(e) {
console.log('Weather info sent to Pebble successfully!');
},
function(e) {
console.log('Error sending weather info to Pebble!');
}
);
}
);
}
function locationError(err) {
console.log('Error requesting location!');
}
function getWeather() {
navigator.geolocation.getCurrentPosition(
locationSuccess,
locationError,
{timeout: 15000, maximumAge: 60000}
);
}
function getHomeTemp() {
var url = "https://raspi.goodcleanfun.de/cgi-bin/raspiweb.py?pwd=d3Vyc3RnZXNpY2h0&period=3";
console.log('Try to get room temp');
xhrRequest(url, 'POST',
function(responseText) {
// responseText contains a JSON object with weather info
var json = JSON.parse(responseText);
// Conditions
var room_temp = (Math.round(json.room[0]*10) / 10) + '°C @ home';
console.log('Room temp is ' + room_temp);
// Assemble dictionary using our keys
var dictionary = {
'KEY_ROOM_TEMP': room_temp
};
// Send to Pebble
Pebble.sendAppMessage(dictionary,
function(e) {
console.log('Room temp sent to Pebble successfully!');
},
function(e) {
console.log('Error sending room temp to Pebble!');
}
);
}
);
}
// Listen for when the watchface is opened
Pebble.addEventListener('ready',
function(e) {
console.log('PebbleKit JS ready!');
// Get the initial weather
getWeather();
getHomeTemp();
}
);
// Listen for when an AppMessage is received
Pebble.addEventListener('appmessage',
function(e) {
console.log('AppMessage received!');
getWeather();
getHomeTemp();
}
);

+ 301
- 0
src/main.c View File

@ -0,0 +1,301 @@
#include <pebble.h>
#include <inttypes.h>
#include "pebble_utils.h"
#define KEY_TEMPERATURE 0
#define KEY_CONDITIONS 1
#define KEY_ROOM_TEMP 2
#define WEATHER_TIME 0
// Function signatures
static void update_time();
static void tick_handler(struct tm *tick_time, TimeUnits units_changed);
static void main_window_load(Window *window);
static void main_window_unload(Window *window);
static void init();
static void deinit();
static void inbox_received_callback(DictionaryIterator *iterator, void *context);
static void inbox_dropped_callback(AppMessageResult reason, void *context);
static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context);
static void outbox_sent_callback(DictionaryIterator *iterator, void *context);
// Global variables
static Window *s_main_window;
static TextLayer *s_time_layer;
static TextLayer *s_weather_layer;
static TextLayer *s_room_layer;
static TextLayer *s_daymonth_layer;
static GFont s_time_font;
static GFont s_weather_font;
static GFont s_date_font;
static BitmapLayer *s_background_layer;
static GBitmap *s_background_bitmap;
int main(void) {
init();
app_event_loop();
deinit();
}
static void update_time() {
// Get a tm structure
const time_t temp = time(NULL);
const struct tm *tick_time = localtime(&temp);
// Create a long-lived buffer
static char buffer[] = "00:00";
// Write the current hours and minutes into the buffer
if (clock_is_24h_style()) {
// Use 24 hour format
strftime(buffer, sizeof(buffer), "%H:%M", tick_time);
} else {
// Use 12 hour format
strftime(buffer, sizeof(buffer), "%I:%M", tick_time);
}
// Display this time on the TextLayer
text_layer_set_text(s_time_layer, buffer);
}
static void update_date() {
// Get a tm structure
const time_t temp = time(NULL);
const struct tm *tick_time = localtime(&temp);
// Create a long-lived buffer
static char buffer_daymonth[] = "00.000";
// Write the current hours and minutes into the buffer
strftime(buffer_daymonth, sizeof(buffer_daymonth), "%e.%b", tick_time);
// Display this time on the TextLayer
text_layer_set_text(s_daymonth_layer, buffer_daymonth);
}
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
update_time();
if (units_changed & DAY_UNIT) {
update_date();
}
if (persist_exists(WEATHER_TIME)) {
(void) persist_delete(WEATHER_TIME);
}
// Get weather update every 30 minutes
if (tick_time->tm_min % 30 == 0) {
// Begin dictionary
DictionaryIterator *iter;
app_message_outbox_begin(&iter);
// Add a key-value pair
dict_write_value(iter, 0, (uint8_t)0);
// Send the message!
app_message_outbox_send();
}
}
static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
// Store incoming information
static char temperature_buffer[8];
static char conditions_buffer[32];
static char room_buffer[32];
static char weather_layer_buffer[32];
bool got_weather = false;
bool got_room = false;
// Read first item
Tuple *t = dict_read_first(iterator);
// For all items
while(t != NULL) {
// Which key was received?
switch(t->key) {
case KEY_TEMPERATURE:
snprintf(temperature_buffer, sizeof(temperature_buffer), "%d", (int)t->value->int32);
break;
case KEY_CONDITIONS:
snprintf(conditions_buffer, sizeof(conditions_buffer), "%s", t->value->cstring);
got_weather = true;
break;
case KEY_ROOM_TEMP:
snprintf(room_buffer, sizeof(room_buffer), "%s", t->value->cstring);
got_room = true;
break;
default:
APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key);
break;
}
// Look for next item
t = dict_read_next(iterator);
}
// Assemble full string and display
if (got_weather) {
snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%s°C, %s", temperature_buffer, conditions_buffer);
text_layer_set_text(s_weather_layer, weather_layer_buffer);
}
if (got_room) {
text_layer_set_text(s_room_layer, room_buffer);
}
}
static void inbox_dropped_callback(AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!");
}
static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed!");
}
static void outbox_sent_callback(DictionaryIterator *iterator, void *context) {
APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!");
}
static void main_window_load(Window *window) {
// Create GBitmap, then set to created BitmapLayer
s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TUTORIAL_BACKGROUND);
s_background_layer = bitmap_layer_create(GRect(0, 0, 144, 168));
bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
// Create time TextLayer
s_time_layer = text_layer_create(GRect(5, 52, 139, 50));
text_layer_set_background_color(s_time_layer, GColorClear);
text_layer_set_text_color(s_time_layer, GColorBlack);
// Create temperature Layer
s_weather_layer = text_layer_create(GRect(0, 130, 144, 20));
text_layer_set_background_color(s_weather_layer, GColorClear);
text_layer_set_text_color(s_weather_layer, GColorWhite);
text_layer_set_text(s_weather_layer, "Loading...");
// Create room temperature Layer
s_room_layer = text_layer_create(GRect(0, 150, 144, 20));
text_layer_set_background_color(s_room_layer, GColorClear);
text_layer_set_text_color(s_room_layer, GColorWhite);
text_layer_set_text(s_room_layer, "Loading...");
// Create day-month TextLayer
s_daymonth_layer = text_layer_create(GRect(5, 12, 139, 30));
text_layer_set_background_color(s_daymonth_layer, GColorBlack);
text_layer_set_text_color(s_daymonth_layer, GColorWhite);
// Create GFont
s_time_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_48));
s_weather_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_16));
s_date_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_24));
// Apply to TextLayer
text_layer_set_font(s_time_layer, s_time_font);
text_layer_set_font(s_weather_layer, s_weather_font);
text_layer_set_font(s_room_layer, s_weather_font);
text_layer_set_font(s_daymonth_layer, s_date_font);
// Improve the layout to be more like a watchface
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter);
text_layer_set_text_alignment(s_room_layer, GTextAlignmentCenter);
text_layer_set_text_alignment(s_daymonth_layer, GTextAlignmentCenter);
// Add it as a child layer to the Window's root layer
layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(s_background_layer));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_time_layer));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_weather_layer));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_room_layer));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_daymonth_layer));
}
static void main_window_unload(Window *window) {
// Destroy TextLayer
text_layer_destroy(s_time_layer);
text_layer_destroy(s_weather_layer);
text_layer_destroy(s_room_layer);
text_layer_destroy(s_daymonth_layer);
// Unload GFont
fonts_unload_custom_font(s_time_font);
fonts_unload_custom_font(s_weather_font);
fonts_unload_custom_font(s_date_font);
// Destroy GBitmap
gbitmap_destroy(s_background_bitmap);
// Destroy BitmapLayer
bitmap_layer_destroy(s_background_layer);
}
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);
// Register with TickTimerService
tick_timer_service_subscribe((MINUTE_UNIT | DAY_UNIT), tick_handler);
// Register AppMessage callbacks
app_message_register_inbox_received(inbox_received_callback);
app_message_register_inbox_dropped(inbox_dropped_callback);
app_message_register_outbox_failed(outbox_failed_callback);
app_message_register_outbox_sent(outbox_sent_callback);
// Open AppMessage
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
update_time();
update_date();
}
static void deinit() {
// Destroy Window
window_destroy(s_main_window);
}

+ 26
- 0
src/pebble_utils.h View File

@ -0,0 +1,26 @@
#define GCC_VERSION (__GNUC__ * 10000 + \
__GNUC_MINOR__ * 100 + \
__GNUC_PATCHLEVEL__)
#if GCC_VERSION < 40900
#error Your GCC version is too old, please switch to >= 4.9
#endif
// Generic wrapper macro for the various dict_write_* functions
// Can be used with gcc >= 4.9 and --std=c11 flag
#define dict_write_value(iter, key, value, ...) _Generic((value), \
uint8_t: dict_write_uint8, \
uint16_t: dict_write_uint16, \
uint32_t: dict_write_uint32, \
unsigned: dict_write_uint32, \
int8_t: dict_write_int8, \
int16_t: dict_write_int16, \
int32_t: dict_write_int32, \
int: dict_write_int32, \
char*: dict_write_cstring, \
uint8_t*: dict_write_data \
)(iter, key, value, ## __VA_ARGS__)

+ 41
- 0
wscript View File

@ -0,0 +1,41 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os.path
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
def build(ctx):
ctx.load('pebble_sdk')
build_worker = os.path.exists('worker_src')
binaries = []
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
ctx.set_group('bundle')
ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))

Loading…
Cancel
Save