Browse Source

Initial commit

T. Meissner 3 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 @@
1
+
2
+# Ignore build generated files
3
+build
4
+
5
+# Ignore waf lock file
6
+.lock-waf*

+ 52
- 0
appinfo.json View File

@@ -0,0 +1,52 @@
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": ["basalt"],
9
+  "watchapp": {
10
+    "watchface": true
11
+  },
12
+  "appKeys": {
13
+    "KEY_TEMPERATURE": 0,
14
+    "KEY_CONDITIONS": 1,
15
+    "KEY_ROOM_TEMP": 2
16
+  },
17
+  "capabilities":
18
+    [ "location" ],
19
+  "resources": {
20
+    "media": [
21
+      {
22
+        "type": "font",
23
+        "name": "FONT_PERFECT_DOS_16",
24
+        "file": "fonts/perfect-dos-vga.ttf",
25
+        "compatibility":"2.7"
26
+      },
27
+      {
28
+        "type": "font",
29
+        "name": "FONT_PERFECT_DOS_20",
30
+        "file": "fonts/perfect-dos-vga.ttf",
31
+        "compatibility":"2.7"
32
+      },
33
+      {
34
+        "type": "font",
35
+        "name": "FONT_PERFECT_DOS_24",
36
+        "file": "fonts/perfect-dos-vga.ttf",
37
+        "compatibility":"2.7"
38
+      },
39
+      {
40
+        "type": "font",
41
+        "name": "FONT_PERFECT_DOS_48",
42
+        "file": "fonts/perfect-dos-vga.ttf",
43
+        "compatibility":"2.7"
44
+      },
45
+      {
46
+        "type": "png",
47
+        "name": "TUTORIAL_BACKGROUND",
48
+        "file": "images/background.png"
49
+      }
50
+    ]
51
+  }
52
+}

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


BIN
resources/images/background.png View File


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

@@ -0,0 +1,132 @@
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 = 'ca82d3c964da82f54d033abf702a46a5';
19
+
20
+  var lat = Math.round(pos.coords.latitude*10) / 10;
21
+  var lon = Math.round(pos.coords.longitude*10) / 10;
22
+
23
+  // Construct URL
24
+  var url = 'http://api.openweathermap.org/data/2.5/weather?lat=' +
25
+      lat + '&lon=' + lon + '&APPID=' + api_key;
26
+
27
+  // Send request to OpenWeatherMap
28
+  xhrRequest(url, 'GET',
29
+    function(responseText) {
30
+      // responseText contains a JSON object with weather info
31
+      var json = JSON.parse(responseText);
32
+
33
+      // Temperature in Kelvin requires adjustment
34
+      var temperature = Math.round(json.main.temp - 273.15);
35
+      console.log('Temperature is ' + temperature);
36
+
37
+      // Conditions
38
+      var conditions = json.weather[0].main;
39
+      console.log('Conditions are ' + conditions);
40
+
41
+      // Assemble dictionary using our keys
42
+      var dictionary = {
43
+        'KEY_TEMPERATURE': temperature,
44
+        'KEY_CONDITIONS': conditions
45
+      };
46
+
47
+      // Send to Pebble
48
+      Pebble.sendAppMessage(dictionary,
49
+        function(e) {
50
+          console.log('Weather info sent to Pebble successfully!');
51
+        },
52
+        function(e) {
53
+          console.log('Error sending weather info to Pebble!');
54
+        }
55
+      );
56
+    }
57
+  );
58
+
59
+}
60
+
61
+
62
+function locationError(err) {
63
+
64
+  console.log('Error requesting location!');
65
+
66
+}
67
+
68
+
69
+function getWeather() {
70
+
71
+  navigator.geolocation.getCurrentPosition(
72
+    locationSuccess,
73
+    locationError,
74
+    {timeout: 15000, maximumAge: 60000}
75
+  );
76
+
77
+}
78
+
79
+
80
+function getHomeTemp() {
81
+
82
+  var url = "https://raspi.goodcleanfun.de/cgi-bin/raspiweb.py?pwd=d3Vyc3RnZXNpY2h0&period=3";
83
+
84
+  console.log('Try to get room temp');
85
+
86
+  xhrRequest(url, 'POST',
87
+    function(responseText) {
88
+      // responseText contains a JSON object with weather info
89
+      var json = JSON.parse(responseText);
90
+
91
+      // Conditions
92
+      var room_temp = (Math.round(json.room[0]*10) / 10) + '°C @ home';
93
+      console.log('Room temp is ' + room_temp);
94
+
95
+      // Assemble dictionary using our keys
96
+      var dictionary = {
97
+        'KEY_ROOM_TEMP': room_temp
98
+      };
99
+
100
+      // Send to Pebble
101
+      Pebble.sendAppMessage(dictionary,
102
+        function(e) {
103
+          console.log('Room temp sent to Pebble successfully!');
104
+        },
105
+        function(e) {
106
+          console.log('Error sending room temp to Pebble!');
107
+        }
108
+      );
109
+    }
110
+  );
111
+}
112
+
113
+
114
+// Listen for when the watchface is opened
115
+Pebble.addEventListener('ready',
116
+  function(e) {
117
+    console.log('PebbleKit JS ready!');
118
+    // Get the initial weather
119
+    getWeather();
120
+    getHomeTemp();
121
+  }
122
+);
123
+
124
+
125
+// Listen for when an AppMessage is received
126
+Pebble.addEventListener('appmessage',
127
+  function(e) {
128
+    console.log('AppMessage received!');
129
+    getWeather();
130
+    getHomeTemp();
131
+  }
132
+);

+ 301
- 0
src/main.c View File

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

+ 26
- 0
src/pebble_utils.h View File

@@ -0,0 +1,26 @@
1
+#define GCC_VERSION (__GNUC__ * 10000 + \
2
+                     __GNUC_MINOR__ * 100 + \
3
+                     __GNUC_PATCHLEVEL__)
4
+
5
+
6
+#if GCC_VERSION < 40900
7
+
8
+  #error Your GCC version is too old, please switch to >= 4.9
9
+
10
+#endif
11
+
12
+
13
+// Generic wrapper macro for the various dict_write_* functions
14
+// Can be used with gcc >= 4.9 and --std=c11 flag
15
+#define dict_write_value(iter, key, value, ...) _Generic((value), \
16
+    uint8_t: dict_write_uint8, \
17
+    uint16_t: dict_write_uint16, \
18
+    uint32_t: dict_write_uint32, \
19
+    unsigned: dict_write_uint32, \
20
+    int8_t: dict_write_int8, \
21
+    int16_t: dict_write_int16, \
22
+    int32_t: dict_write_int32, \
23
+    int: dict_write_int32, \
24
+    char*: dict_write_cstring, \
25
+    uint8_t*: dict_write_data \
26
+  )(iter, key, value, ## __VA_ARGS__)

+ 41
- 0
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'))