Browse Source

Initial commit of C watchface tutorial

T. Meissner 3 years ago
commit
a8cb56b85c

+ 6
- 0
create_a_c_watchface/.gitignore View File

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

+ 54
- 0
create_a_c_watchface/appinfo.json View File

@@ -0,0 +1,54 @@
1
+{
2
+  "uuid": "3c6e0682-a917-4283-a27e-349f2de629fa",
3
+  "shortName": "create_a_c_watchface",
4
+  "longName": "create_a_c_watchface",
5
+  "companyName": "goodcleanfun",
6
+  "versionLabel": "1.0",
7
+  "sdkVersion": "3",
8
+  "targetPlatforms": ["aplite", "basalt"],
9
+  "watchapp": {
10
+    "watchface": true
11
+  },
12
+  "appKeys": {
13
+    "dummy": 0
14
+  },
15
+  "capabilities":
16
+    [ "location" ],
17
+  "appKeys": {
18
+    "KEY_TEMPERATURE": 0,
19
+    "KEY_CONDITIONS": 1
20
+  },
21
+  "resources": {
22
+    "media": [
23
+      {
24
+        "type": "font",
25
+        "name": "FONT_PERFECT_DOS_16",
26
+        "file": "fonts/perfect-dos-vga.ttf",
27
+        "compatibility":"2.7"
28
+      },
29
+      {
30
+        "type": "font",
31
+        "name": "FONT_PERFECT_DOS_20",
32
+        "file": "fonts/perfect-dos-vga.ttf",
33
+        "compatibility":"2.7"
34
+      },
35
+      {
36
+        "type": "font",
37
+        "name": "FONT_PERFECT_DOS_24",
38
+        "file": "fonts/perfect-dos-vga.ttf",
39
+        "compatibility":"2.7"
40
+      },
41
+      {
42
+        "type": "font",
43
+        "name": "FONT_PERFECT_DOS_48",
44
+        "file": "fonts/perfect-dos-vga.ttf",
45
+        "compatibility":"2.7"
46
+      },
47
+      {
48
+        "type": "png",
49
+        "name": "TUTORIAL_BACKGROUND",
50
+        "file": "images/background.png"
51
+      }
52
+    ]
53
+  }
54
+}

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


BIN
create_a_c_watchface/resources/images/background.png View File


+ 94
- 0
create_a_c_watchface/src/js/pebble-js-app.js View File

@@ -0,0 +1,94 @@
1
+var xhrRequest = function (url, type, callback) {
2
+
3
+  var xhr = new XMLHttpRequest();
4
+
5
+  xhr.onload = function () {
6
+    callback(this.responseText);
7
+  };
8
+
9
+  xhr.open(type, url);
10
+  xhr.send();
11
+
12
+};
13
+
14
+
15
+function locationSuccess(pos) {
16
+
17
+  // generic API key, has to be replaced by developers one
18
+  var api_key = 'bd82977b86bf27fb59a04b61b657fb6f';
19
+
20
+  // Construct URL
21
+  var url = 'http://api.openweathermap.org/data/2.5/weather?lat=' +
22
+      pos.coords.latitude + '&lon=' + pos.coords.longitude +
23
+      '&APPID=' + api_key;
24
+
25
+  // Send request to OpenWeatherMap
26
+  xhrRequest(url, 'GET',
27
+    function(responseText) {
28
+      // responseText contains a JSON object with weather info
29
+      var json = JSON.parse(responseText);
30
+
31
+      // Temperature in Kelvin requires adjustment
32
+      var temperature = Math.round(json.main.temp - 273.15);
33
+      console.log('Temperature is ' + temperature);
34
+
35
+      // Conditions
36
+      var conditions = json.weather[0].main;
37
+      console.log('Conditions are ' + conditions);
38
+
39
+      // Assemble dictionary using our keys
40
+      var dictionary = {
41
+        'KEY_TEMPERATURE': temperature,
42
+        'KEY_CONDITIONS': conditions
43
+      };
44
+
45
+      // Send to Pebble
46
+      Pebble.sendAppMessage(dictionary,
47
+        function(e) {
48
+          console.log('Weather info sent to Pebble successfully!');
49
+        },
50
+        function(e) {
51
+          console.log('Error sending weather info to Pebble!');
52
+        }
53
+      );
54
+    }
55
+  );
56
+
57
+}
58
+
59
+
60
+function locationError(err) {
61
+
62
+  console.log('Error requesting location!');
63
+
64
+}
65
+
66
+
67
+function getWeather() {
68
+
69
+  navigator.geolocation.getCurrentPosition(
70
+    locationSuccess,
71
+    locationError,
72
+    {timeout: 15000, maximumAge: 60000}
73
+  );
74
+
75
+}
76
+
77
+
78
+// Listen for when the watchface is opened
79
+Pebble.addEventListener('ready',
80
+  function(e) {
81
+    console.log('PebbleKit JS ready!');
82
+  // Get the initial weather
83
+  getWeather();
84
+  }
85
+);
86
+
87
+
88
+// Listen for when an AppMessage is received
89
+Pebble.addEventListener('appmessage',
90
+  function(e) {
91
+    console.log('AppMessage received!');
92
+    getWeather();
93
+  }
94
+);

+ 277
- 0
create_a_c_watchface/src/main.c View File

@@ -0,0 +1,277 @@
1
+#include <pebble.h>
2
+
3
+
4
+#define KEY_TEMPERATURE 0
5
+#define KEY_CONDITIONS  1
6
+
7
+
8
+// Function signatures
9
+static void update_time();
10
+static void tick_handler(struct tm *tick_time, TimeUnits units_changed);
11
+static void main_window_load(Window *window);
12
+static void main_window_unload(Window *window);
13
+static void init();
14
+static void deinit();
15
+static void inbox_received_callback(DictionaryIterator *iterator, void *context);
16
+static void inbox_dropped_callback(AppMessageResult reason, void *context);
17
+static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context);
18
+static void outbox_sent_callback(DictionaryIterator *iterator, void *context);
19
+
20
+
21
+// Global variables
22
+static Window      *s_main_window;
23
+static TextLayer   *s_time_layer;
24
+static TextLayer   *s_weather_layer;
25
+static TextLayer   *s_daymonth_layer;
26
+//static TextLayer   *s_year_layer;
27
+static GFont        s_time_font;
28
+static GFont        s_weather_font;
29
+static GFont        s_date_font;
30
+static BitmapLayer *s_background_layer;
31
+static GBitmap     *s_background_bitmap;
32
+
33
+
34
+
35
+int main(void) {
36
+
37
+  init();
38
+  app_event_loop();
39
+  deinit();
40
+
41
+}
42
+
43
+
44
+static void update_time() {
45
+
46
+  // Get a tm structure
47
+  time_t temp = time(NULL);
48
+  struct tm *tick_time = localtime(&temp);
49
+
50
+  // Create a long-lived buffer
51
+  static char buffer[] = "00:00";
52
+
53
+  // Write the current hours and minutes into the buffer
54
+  if (clock_is_24h_style()) {
55
+    // Use 24 hour format
56
+    strftime(buffer, sizeof("00:00"), "%H:%M", tick_time);
57
+  } else {
58
+    // Use 12 hour format
59
+    strftime(buffer, sizeof("00:00"), "%I:%M", tick_time);
60
+  }
61
+
62
+  // Display this time on the TextLayer
63
+  text_layer_set_text(s_time_layer, buffer);
64
+
65
+}
66
+
67
+
68
+static void update_date() {
69
+
70
+  // Get a tm structure
71
+  time_t temp = time(NULL);
72
+  struct tm *tick_time = localtime(&temp);
73
+
74
+  // Create a long-lived buffer
75
+  static char buffer_daymonth[] = "00.000";
76
+
77
+  // Write the current hours and minutes into the buffer
78
+  strftime(buffer_daymonth, sizeof(buffer_daymonth), "%e.%b", tick_time);
79
+
80
+  // Display this time on the TextLayer
81
+  text_layer_set_text(s_daymonth_layer, buffer_daymonth);
82
+
83
+}
84
+
85
+
86
+static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
87
+
88
+  update_time();
89
+  if (units_changed & DAY_UNIT) {
90
+    update_date();
91
+  }
92
+
93
+  // Get weather update every 30 minutes
94
+  if (tick_time->tm_min % 30 == 0) {
95
+    // Begin dictionary
96
+    DictionaryIterator *iter;
97
+    app_message_outbox_begin(&iter);
98
+
99
+    // Add a key-value pair
100
+    dict_write_uint8(iter, 0, 0);
101
+
102
+    // Send the message!
103
+    app_message_outbox_send();
104
+  }
105
+
106
+}
107
+
108
+
109
+static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
110
+
111
+  // Store incoming information
112
+  static char temperature_buffer[8];
113
+  static char conditions_buffer[32];
114
+  static char weather_layer_buffer[32];
115
+
116
+  // Read first item
117
+  Tuple *t = dict_read_first(iterator);
118
+
119
+  // For all items
120
+  while(t != NULL) {
121
+
122
+    // Which key was received?
123
+    switch(t->key) {
124
+      case KEY_TEMPERATURE:
125
+        snprintf(temperature_buffer, sizeof(temperature_buffer), "%d°C", (int)t->value->int32);
126
+        break;
127
+
128
+      case KEY_CONDITIONS:
129
+        snprintf(conditions_buffer, sizeof(conditions_buffer), "%s", t->value->cstring);
130
+        break;
131
+
132
+      default:
133
+        APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key);
134
+        break;
135
+    }
136
+
137
+    // Look for next item
138
+    t = dict_read_next(iterator);
139
+  }
140
+
141
+  // Assemble full string and display
142
+  snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%s, %s", temperature_buffer, conditions_buffer);
143
+  text_layer_set_text(s_weather_layer, weather_layer_buffer);
144
+
145
+}
146
+
147
+
148
+static void inbox_dropped_callback(AppMessageResult reason, void *context) {
149
+  APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!");
150
+}
151
+
152
+
153
+static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) {
154
+  APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed!");
155
+}
156
+
157
+
158
+static void outbox_sent_callback(DictionaryIterator *iterator, void *context) {
159
+  APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!");
160
+}
161
+
162
+
163
+static void main_window_load(Window *window) {
164
+
165
+  // Create GBitmap, then set to created BitmapLayer
166
+  s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TUTORIAL_BACKGROUND);
167
+  s_background_layer = bitmap_layer_create(GRect(0, 0, 144, 168));
168
+  bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
169
+
170
+  // Create time TextLayer
171
+  s_time_layer = text_layer_create(GRect(5, 52, 139, 50));
172
+  text_layer_set_background_color(s_time_layer, GColorClear);
173
+  text_layer_set_text_color(s_time_layer, GColorBlack);
174
+
175
+  // Create temperature Layer
176
+  s_weather_layer = text_layer_create(GRect(0, 130, 144, 25));
177
+  text_layer_set_background_color(s_weather_layer, GColorClear);
178
+  text_layer_set_text_color(s_weather_layer, GColorWhite);
179
+  text_layer_set_text(s_weather_layer, "Loading...");
180
+
181
+  // Create day-month TextLayer
182
+  s_daymonth_layer = text_layer_create(GRect(5, 12, 139, 30));
183
+  text_layer_set_background_color(s_daymonth_layer, GColorBlack);
184
+  text_layer_set_text_color(s_daymonth_layer, GColorWhite);
185
+
186
+  // Create year TextLayer
187
+//  s_year_layer = text_layer_create(GRect(5, 123, 139, 30));
188
+//  text_layer_set_background_color(s_year_layer, GColorBlack);
189
+//  text_layer_set_text_color(s_year_layer, GColorWhite);
190
+
191
+  // Create GFont
192
+  s_time_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_48));
193
+  s_weather_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_16));
194
+  s_date_font = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_PERFECT_DOS_24));
195
+
196
+  // Apply to TextLayer
197
+  text_layer_set_font(s_time_layer, s_time_font);
198
+  text_layer_set_font(s_weather_layer, s_weather_font);
199
+  text_layer_set_font(s_daymonth_layer, s_date_font);
200
+//  text_layer_set_font(s_year_layer, s_date_font);
201
+
202
+  // Improve the layout to be more like a watchface
203
+  text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
204
+  text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter);
205
+  text_layer_set_text_alignment(s_daymonth_layer, GTextAlignmentCenter);
206
+//  text_layer_set_text_alignment(s_year_layer, GTextAlignmentCenter);
207
+
208
+  // Add it as a child layer to the Window's root layer
209
+  layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(s_background_layer));
210
+  layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_time_layer));
211
+  layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_weather_layer));
212
+  layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_daymonth_layer));
213
+  //  layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_year_layer));
214
+
215
+}
216
+
217
+
218
+static void main_window_unload(Window *window) {
219
+
220
+  // Destroy TextLayer
221
+  text_layer_destroy(s_time_layer);
222
+  text_layer_destroy(s_weather_layer);
223
+  text_layer_destroy(s_daymonth_layer);
224
+//  text_layer_destroy(s_year_layer);
225
+
226
+  // Unload GFont
227
+  fonts_unload_custom_font(s_time_font);
228
+  fonts_unload_custom_font(s_weather_font);
229
+  fonts_unload_custom_font(s_date_font);
230
+
231
+  // Destroy GBitmap
232
+  gbitmap_destroy(s_background_bitmap);
233
+
234
+  // Destroy BitmapLayer
235
+  bitmap_layer_destroy(s_background_layer);
236
+
237
+}
238
+
239
+
240
+static void init() {
241
+
242
+  // Create main Window element and assign to pointer
243
+  s_main_window = window_create();
244
+
245
+  // Set handlers to manage the elements inside the Window
246
+  window_set_window_handlers(s_main_window, (WindowHandlers){
247
+    .load = main_window_load,
248
+    .unload = main_window_unload
249
+  });
250
+
251
+  // Show the Window on the watch, with animated=true
252
+  window_stack_push(s_main_window, true);
253
+
254
+  // Register with TickTimerService
255
+  tick_timer_service_subscribe((MINUTE_UNIT | DAY_UNIT), tick_handler);
256
+
257
+  // Register AppMessage callbacks
258
+  app_message_register_inbox_received(inbox_received_callback);
259
+  app_message_register_inbox_dropped(inbox_dropped_callback);
260
+  app_message_register_outbox_failed(outbox_failed_callback);
261
+  app_message_register_outbox_sent(outbox_sent_callback);
262
+
263
+  // Open AppMessage
264
+  app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
265
+
266
+  update_time();
267
+  update_date();
268
+
269
+}
270
+
271
+
272
+static void deinit() {
273
+
274
+  // Destroy Window
275
+  window_destroy(s_main_window);
276
+
277
+}

+ 41
- 0
create_a_c_watchface/wscript View File

@@ -0,0 +1,41 @@
1
+
2
+#
3
+# This file is the default set of rules to compile a Pebble project.
4
+#
5
+# Feel free to customize this to your needs.
6
+#
7
+
8
+import os.path
9
+
10
+top = '.'
11
+out = 'build'
12
+
13
+def options(ctx):
14
+    ctx.load('pebble_sdk')
15
+
16
+def configure(ctx):
17
+    ctx.load('pebble_sdk')
18
+
19
+def build(ctx):
20
+    ctx.load('pebble_sdk')
21
+
22
+    build_worker = os.path.exists('worker_src')
23
+    binaries = []
24
+
25
+    for p in ctx.env.TARGET_PLATFORMS:
26
+        ctx.set_env(ctx.all_envs[p])
27
+        ctx.set_group(ctx.env.PLATFORM_NAME)
28
+        app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
29
+        ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
30
+        target=app_elf)
31
+
32
+        if build_worker:
33
+            worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
34
+            binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
35
+            ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
36
+            target=worker_elf)
37
+        else:
38
+            binaries.append({'platform': p, 'app_elf': app_elf})
39
+
40
+    ctx.set_group('bundle')
41
+    ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))