Louis authored on10/07/2018 13:58:16
Showing9 changed files
1 1
new file mode 100755
... ...
@@ -0,0 +1,43 @@
1
+
2
+/*
3
+ * Example jshint configuration file for Pebble development.
4
+ *
5
+ * Check out the full documentation at http://www.jshint.com/docs/options/
6
+ */
7
+{
8
+  // Declares the existence of the globals available in PebbleKit JS.
9
+  "globals": {"Float64Array": true, "navigator": true, "Uint32Array": true, "console": true, "localStorage": true, "Pebble": true, "require": true, "Float32Array": true, "Int32Array": true, "XMLHttpRequest": true, "exports": true, "WebSocket": true, "Int8Array": true, "setInterval": true, "setTimeout": true, "module": true, "Uint8Array": true, "Int16Array": true, "Uint16Array": true, "Uint8ClampedArray": true},
10
+
11
+  // Do not mess with standard JavaScript objects (Array, Date, etc)
12
+  "freeze": true,
13
+
14
+  // Do not use eval! Keep this warning turned on (ie: false)
15
+  "evil": false,
16
+
17
+  /*
18
+   * The options below are more style/developer dependent.
19
+   * Customize to your liking.
20
+   */
21
+
22
+  // All variables should be in camelcase - too specific for CloudPebble builds to fail
23
+  // "camelcase": true,
24
+
25
+  // Do not allow blocks without { } - too specific for CloudPebble builds to fail.
26
+  // "curly": true,
27
+
28
+  // Prohibits the use of immediate function invocations without wrapping them in parentheses
29
+  "immed": true,
30
+
31
+  // Don't enforce indentation, because it's not worth failing builds over
32
+  // (especially given our somewhat lacklustre support for it)
33
+  "indent": false,
34
+
35
+  // Do not use a variable before it's defined
36
+  "latedef": "nofunc",
37
+
38
+  // Spot undefined variables
39
+  "undef": "true",
40
+
41
+  // Spot unused variables
42
+  "unused": "true"
43
+}
0 44
new file mode 100755
... ...
@@ -0,0 +1,58 @@
1
+{
2
+    "author": "ljonget@gmail.com",
3
+    "dependencies": {},
4
+    "keywords": [],
5
+    "name": "cam-switch",
6
+    "pebble": {
7
+        "capabilities": [
8
+            "configurable"
9
+        ],
10
+        "displayName": "SynoCam Home switch",
11
+        "enableMultiJS": true,
12
+        "messageKeys": [
13
+            "status",
14
+            "server",
15
+            "username",
16
+            "password"
17
+        ],
18
+        "projectType": "native",
19
+        "resources": {
20
+            "media": [
21
+                {
22
+                    "file": "images/icon.png",
23
+                    "menuIcon": true,
24
+                    "name": "IMAGE_MENU_ICON",
25
+                    "targetPlatforms": null,
26
+                    "type": "bitmap"
27
+                },
28
+                {
29
+                    "file": "images/q_mark.png",
30
+                    "name": "Q_MARK",
31
+                    "targetPlatforms": null,
32
+                    "type": "bitmap"
33
+                },
34
+                {
35
+                    "file": "images/home_off.png",
36
+                    "name": "HOME_OFF",
37
+                    "targetPlatforms": null,
38
+                    "type": "bitmap"
39
+                },
40
+                {
41
+                    "file": "images/home_on.png",
42
+                    "name": "HOME_ON",
43
+                    "targetPlatforms": null,
44
+                    "type": "bitmap"
45
+                }
46
+            ]
47
+        },
48
+        "sdkVersion": "3",
49
+        "targetPlatforms": [
50
+            "diorite"
51
+        ],
52
+        "uuid": "d701bcb8-1076-4adb-9e38-9c21566efc24",
53
+        "watchapp": {
54
+            "watchface": false
55
+        }
56
+    },
57
+    "version": "1.0.0"
58
+}
0 59
new file mode 100755
1 60
Binary files /dev/null and b/resources/images/home_off.png differ
2 61
new file mode 100755
3 62
Binary files /dev/null and b/resources/images/home_on.png differ
4 63
new file mode 100755
5 64
Binary files /dev/null and b/resources/images/icon.png differ
6 65
new file mode 100755
7 66
Binary files /dev/null and b/resources/images/q_mark.png differ
8 67
new file mode 100755
... ...
@@ -0,0 +1,272 @@
1
+#include <pebble.h>
2
+
3
+// Persistent storage key
4
+#define SETTINGS_KEY 1
5
+
6
+// Largest expected inbox and outbox message sizes
7
+const uint32_t inbox_size = 64;
8
+const uint32_t outbox_size = 64;
9
+
10
+
11
+static char s_api[40];
12
+static char s_username[40];
13
+static char s_password[40];
14
+static char s_server[255];
15
+
16
+static Window *s_window;
17
+static TextLayer *s_text_layer;
18
+
19
+static ActionBarLayer *s_action_bar_layer;
20
+
21
+static GBitmap *s_h_on_bitmap, *s_h_off_bitmap, *s_q_mark_bitmap;
22
+
23
+
24
+bool is_home_on = false;
25
+
26
+static uint32_t size ;
27
+  
28
+  
29
+typedef enum {
30
+  status,
31
+  username,
32
+  password,
33
+  server
34
+} AppKey;
35
+
36
+
37
+static char * msg;
38
+
39
+
40
+// Define our settings struct
41
+typedef struct Settings {
42
+  char username;
43
+  char password;
44
+  char server;
45
+} Settings;
46
+
47
+// An instance of the struct
48
+static Settings settings;
49
+
50
+// Save the settings to persistent storage
51
+static void prv_save_settings() {
52
+  persist_write_data(SETTINGS_KEY, &settings, sizeof(settings));
53
+}
54
+
55
+
56
+static void inbox_received_callback(DictionaryIterator *iter, void *context) {
57
+  // A new message has been successfully received
58
+  APP_LOG(APP_LOG_LEVEL_DEBUG, "New message! ");
59
+  
60
+  size = dict_size(iter);
61
+  
62
+  // Read API returns
63
+  // Tuple *api_tuple = dict_read_first(iter);
64
+  Tuple *api_tuple = dict_find(iter, MESSAGE_KEY_status);
65
+  
66
+  if(api_tuple) {
67
+    strncpy(s_api, api_tuple->value->cstring, 40);
68
+    // Display in the TextLayer
69
+    text_layer_set_text(s_text_layer, s_api);
70
+  }else{
71
+    APP_LOG(APP_LOG_LEVEL_DEBUG, "not status message... ");
72
+  }
73
+  
74
+  
75
+  // Read String preferences
76
+  Tuple *username_t = dict_find(iter, MESSAGE_KEY_username);
77
+  if(username_t) {
78
+    strncpy(s_username, username_t->value->cstring, 40);
79
+    prv_save_settings();
80
+    APP_LOG(APP_LOG_LEVEL_DEBUG, "new username in settings... %s",s_username);
81
+  }
82
+
83
+  Tuple *password_t = dict_find(iter, MESSAGE_KEY_password);
84
+  if(password_t) {
85
+    strncpy(s_password, password_t->value->cstring, 40);
86
+    prv_save_settings();
87
+    APP_LOG(APP_LOG_LEVEL_DEBUG, "new password in settings... %s",s_password);
88
+  }
89
+  
90
+  Tuple *server_t = dict_find(iter, MESSAGE_KEY_server);
91
+  if(server_t) {
92
+    strncpy(s_server, server_t->value->cstring, 255);
93
+    prv_save_settings();
94
+    APP_LOG(APP_LOG_LEVEL_DEBUG, "new server in settings... %s",s_server);
95
+  }
96
+
97
+}
98
+
99
+static void inbox_dropped_callback(AppMessageResult reason, void *context) {
100
+  // A message was received, but had to be dropped
101
+  APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped. Reason: %d", (int)reason);
102
+}
103
+
104
+static void outbox_sent_callback(DictionaryIterator *iter, void *context) {
105
+  // The message just sent has been successfully delivered
106
+  APP_LOG(APP_LOG_LEVEL_INFO, "Message sent. ");
107
+}
108
+
109
+static void outbox_failed_callback(DictionaryIterator *iter, AppMessageResult reason, void *context) {
110
+  // The message just sent failed to be delivered
111
+  APP_LOG(APP_LOG_LEVEL_ERROR, "Message send failed. Reason: %d", (int)reason);
112
+}
113
+
114
+
115
+static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
116
+
117
+  // Declare the dictionary's iterator
118
+  DictionaryIterator *out_iter;
119
+  
120
+  // Prepare the outbox buffer for this message
121
+  AppMessageResult result = app_message_outbox_begin(&out_iter);
122
+  
123
+  if(result == APP_MSG_OK) {
124
+    // Add an item to ask for weather data
125
+    msg = "get";
126
+    dict_write_cstring(out_iter, status, msg);
127
+  
128
+    // Send this message
129
+    result = app_message_outbox_send();
130
+    if(result != APP_MSG_OK) {
131
+      APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending the outbox: %d", (int)result);
132
+    }
133
+  } else {
134
+    // The outbox cannot be used right now
135
+    APP_LOG(APP_LOG_LEVEL_ERROR, "Error preparing the outbox: %d", (int)result);
136
+  }
137
+  
138
+  
139
+}
140
+
141
+static void up_click_handler(ClickRecognizerRef recognizer, void *context) {
142
+
143
+  // Declare the dictionary's iterator
144
+  DictionaryIterator *out_iter;
145
+  
146
+  // Prepare the outbox buffer for this message
147
+  AppMessageResult result = app_message_outbox_begin(&out_iter);
148
+  
149
+  if(result == APP_MSG_OK) {
150
+    // Add an item to ask for weather data
151
+    msg = "home_on";
152
+    dict_write_cstring(out_iter, status, msg);
153
+  
154
+    // Send this message
155
+    result = app_message_outbox_send();
156
+    if(result != APP_MSG_OK) {
157
+      APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending the outbox: %d", (int)result);
158
+    }
159
+  } else {
160
+    // The outbox cannot be used right now
161
+    APP_LOG(APP_LOG_LEVEL_ERROR, "Error preparing the outbox: %d", (int)result);
162
+  }
163
+  
164
+  
165
+}
166
+
167
+static void down_click_handler(ClickRecognizerRef recognizer, void *context) {
168
+
169
+  // Declare the dictionary's iterator
170
+  DictionaryIterator *out_iter;
171
+  
172
+  // Prepare the outbox buffer for this message
173
+  AppMessageResult result = app_message_outbox_begin(&out_iter);
174
+  
175
+  if(result == APP_MSG_OK) {
176
+    // Add an item to ask for weather data
177
+    msg = "home_off";
178
+    dict_write_cstring(out_iter, status, msg);
179
+  
180
+    // Send this message
181
+    result = app_message_outbox_send();
182
+    if(result != APP_MSG_OK) {
183
+      APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending the outbox: %d", (int)result);
184
+    }
185
+  } else {
186
+    // The outbox cannot be used right now
187
+    APP_LOG(APP_LOG_LEVEL_ERROR, "Error preparing the outbox: %d", (int)result);
188
+  }
189
+  
190
+  
191
+}
192
+
193
+static void click_config_provider(void *context) {
194
+  window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
195
+  window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
196
+  window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
197
+}
198
+
199
+static void init(void) {
200
+
201
+  // Open AppMessage
202
+  app_message_open(inbox_size, outbox_size);
203
+  
204
+  // Register to be notified about inbox received events
205
+  app_message_register_inbox_received(inbox_received_callback);
206
+  // Register to be notified about inbox dropped events
207
+  app_message_register_inbox_dropped(inbox_dropped_callback);
208
+  // Register to be notified about outbox sent events
209
+  app_message_register_outbox_sent(outbox_sent_callback);
210
+  // Register to be notified about outbox failed events
211
+  app_message_register_outbox_failed(outbox_failed_callback);
212
+  
213
+  // Create a window and get information about the window
214
+  s_window = window_create();
215
+  Layer *window_layer = window_get_root_layer(s_window);
216
+  GRect bounds = layer_get_bounds(window_layer);
217
+  
218
+  //load buttons
219
+  s_h_on_bitmap = gbitmap_create_with_resource(RESOURCE_ID_HOME_ON);
220
+  s_q_mark_bitmap = gbitmap_create_with_resource(RESOURCE_ID_Q_MARK);
221
+  s_h_off_bitmap = gbitmap_create_with_resource(RESOURCE_ID_HOME_OFF);
222
+
223
+  s_action_bar_layer = action_bar_layer_create();
224
+  action_bar_layer_set_icon(s_action_bar_layer, BUTTON_ID_UP, s_h_on_bitmap);
225
+  action_bar_layer_set_icon(s_action_bar_layer, BUTTON_ID_SELECT, s_q_mark_bitmap);
226
+  action_bar_layer_set_icon(s_action_bar_layer, BUTTON_ID_DOWN, s_h_off_bitmap);
227
+  action_bar_layer_add_to_window(s_action_bar_layer, s_window);
228
+  
229
+  
230
+  // Create a text layer and set the text
231
+  const GEdgeInsets label_insets = {.right = ACTION_BAR_WIDTH, .left = ACTION_BAR_WIDTH / 2};
232
+  s_text_layer = text_layer_create(grect_inset(bounds, label_insets));
233
+  text_layer_set_text(s_text_layer, "Welcome to Syno Cam Switch !");
234
+  
235
+  // Set the font and text alignment
236
+  text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
237
+  text_layer_set_text_alignment(s_text_layer, GTextAlignmentCenter);
238
+
239
+  // Add the text layer to the window
240
+  layer_add_child(window_get_root_layer(s_window), text_layer_get_layer(s_text_layer));
241
+  
242
+  // Enable text flow and paging on the text layer, with a slight inset of 10, for round screens
243
+  text_layer_enable_screen_text_flow_and_paging(s_text_layer, 10);
244
+
245
+  // Push the window, setting the window animation to 'true'
246
+  window_stack_push(s_window, true);
247
+  
248
+  // App Logging!
249
+  APP_LOG(APP_LOG_LEVEL_DEBUG, "Just pushed a window!");
250
+  
251
+  // click provider
252
+  window_set_click_config_provider(s_window, click_config_provider);
253
+
254
+  
255
+}
256
+
257
+static void deinit(void) {
258
+  // Destroy the text layer
259
+  text_layer_destroy(s_text_layer);
260
+
261
+  // Destroy the action bar layer
262
+  action_bar_layer_destroy(s_action_bar_layer);
263
+  
264
+  // Destroy the window
265
+  window_destroy(s_window);
266
+}
267
+
268
+int main(void) {
269
+  init();
270
+  app_event_loop();
271
+  deinit();
272
+}
0 273
new file mode 100755
... ...
@@ -0,0 +1,232 @@
1
+
2
+var username;
3
+var password;
4
+var server;
5
+var sid;
6
+var status;
7
+
8
+function get_status() {
9
+  var response;
10
+  sid="";
11
+  console.log('---- authenticate');
12
+  var username=localStorage.getItem('username');
13
+  var password=localStorage.getItem('password');
14
+  var server=localStorage.getItem('server');
15
+  var url = server + "/webapi/auth.cgi?api=SYNO.API.Auth&method=Login&version=2&account="+username+"&passwd="+password+"&session=SurveillanceStation&format=sid";
16
+  var xhr = new XMLHttpRequest();
17
+
18
+  xhr.open("GET", url,false);
19
+  xhr.send();
20
+
21
+  if(xhr.status == 200) {
22
+    response = JSON.parse(xhr.responseText);
23
+    if (response.success == true){
24
+      sid = response.data.sid;
25
+    }
26
+  }else {
27
+    console.log('------Request returned error code ' + xhr.status.toString());
28
+  }
29
+
30
+  if (sid != ""){
31
+    status = "";
32
+    console.log('---- get_status');
33
+    url = server + "/webapi/entry.cgi?api=SYNO.SurveillanceStation.HomeMode&version=1&method=GetInfo&_sid="+sid;
34
+    
35
+    xhr.open("GET", url,false);
36
+    xhr.send();
37
+
38
+    if(xhr.status == 200) {
39
+      response = JSON.parse(xhr.responseText);
40
+      if (response.success == true){
41
+        status=response.data.on;   
42
+        var message;
43
+        switch (status) {
44
+          case true:
45
+            message = "Your Home mode is ON";
46
+            break;
47
+          case false:
48
+            message = "Your Home mode is OFF";
49
+            break;
50
+          default:
51
+            message = "home mode is unknown !";
52
+        }      
53
+        // Build message
54
+        var dict = {
55
+          'status': message,
56
+        };
57
+      
58
+        // Send the message
59
+        Pebble.sendAppMessage(dict, function(e) {
60
+          console.log('sent');
61
+        }, function() {
62
+          console.log('failed');
63
+        });
64
+      }
65
+    }else {
66
+      console.log('------Request returned error code ' + xhr.status.toString());
67
+    }
68
+  }
69
+}
70
+
71
+
72
+function switch_home(bool) {
73
+  var response;
74
+  sid="";
75
+  console.log('---- authenticate');
76
+  var username=localStorage.getItem('username');
77
+  var password=localStorage.getItem('password');
78
+  var server=localStorage.getItem('server');
79
+  var url = server + "/webapi/auth.cgi?api=SYNO.API.Auth&method=Login&version=2&account="+username+"&passwd="+password+"&session=SurveillanceStation&format=sid";
80
+  var xhr = new XMLHttpRequest();
81
+
82
+  xhr.open("GET", url,false);
83
+  xhr.send();
84
+
85
+  if(xhr.status == 200) {
86
+    response = JSON.parse(xhr.responseText);
87
+    if (response.success == true){
88
+      sid = response.data.sid;
89
+      console.log('------ sid = '+sid);
90
+    }
91
+  }else {
92
+    console.log('------Request returned error code ' + xhr.status.toString());
93
+  }
94
+
95
+  if (sid != ""){
96
+    status = "";
97
+    console.log('---- get_status');
98
+    url = server + "/webapi/entry.cgi?api=SYNO.SurveillanceStation.HomeMode&version=1&method=GetInfo&_sid="+sid;
99
+    
100
+    xhr.open("GET", url,false);
101
+    xhr.send();
102
+
103
+    if(xhr.status == 200) {
104
+      response = JSON.parse(xhr.responseText);
105
+      if (response.success == true){
106
+        status=response.data.on;   
107
+        console.log('------ status:'+status);
108
+        var message;
109
+        var dict;
110
+        if ( status != bool){
111
+          console.log('---- switching home mode to '+ bool);
112
+          url = server + "/webapi/entry.cgi?api=SYNO.SurveillanceStation.HomeMode&version=1&method=Switch&on="+bool+"&_sid="+sid;
113
+          
114
+          xhr.open("GET", url,false);
115
+          xhr.send();
116
+      
117
+          if(xhr.status == 200) {
118
+            response = JSON.parse(xhr.responseText);
119
+            if (response.success == true){
120
+              status=bool;
121
+              switch (status) {
122
+                case true:
123
+                  message = "You just set Home mode ON";
124
+                  break;
125
+                case false:
126
+                  message = "You just set Home mode off";
127
+                  break;
128
+                default:
129
+                  message = "something happened, try again !";
130
+              }      
131
+              // Build message
132
+              dict = {
133
+                'status': message,
134
+              };
135
+            
136
+              // Send the message
137
+              Pebble.sendAppMessage(dict, function(e) {
138
+                console.log('sent');
139
+              }, function() {
140
+                console.log('failed');
141
+              });
142
+            }
143
+          }else {
144
+            console.log('------Request returned error code ' + xhr.status.toString());
145
+          }
146
+        }else{
147
+          console.log('---- nothign to do, status already '+status);
148
+          switch (status) {
149
+            case true:
150
+              message = "Your Home Mode is already ON";
151
+              break;
152
+            case false:
153
+              message = "Your Home Mode is already off";
154
+              break;
155
+            default:
156
+              message = "something happened, try again !";
157
+          }       
158
+          // Build message
159
+          dict = {
160
+            'status': message,
161
+          };
162
+
163
+          // Send the message
164
+          Pebble.sendAppMessage(dict, function(e) {
165
+            console.log('sent');
166
+          }, function() {
167
+            console.log('failed');
168
+          }); 
169
+        }
170
+      }
171
+    }else {
172
+      console.log('------Request returned error code ' + xhr.status.toString());
173
+    }
174
+  }
175
+ 
176
+
177
+}
178
+
179
+
180
+// Get AppMessage events
181
+Pebble.addEventListener('appmessage', function(e) {
182
+  // Get the dictionary from the message
183
+  var dict = e.payload;
184
+  
185
+  switch (dict[0]) {
186
+    case 'get':
187
+      get_status();
188
+      break;
189
+    case 'home_on':
190
+      switch_home(true);
191
+      break;
192
+    case 'home_off':
193
+      switch_home(false);
194
+      break;
195
+    default:
196
+      console.log('Sorry.');
197
+}
198
+  
199
+});
200
+
201
+// Get Configuration page 
202
+Pebble.addEventListener('showConfiguration', function() {
203
+  var username=localStorage.getItem('username');
204
+  var password=localStorage.getItem('password');
205
+  var server=localStorage.getItem('server');
206
+  var  url = 'https://jonget.fr/pebble/config_homeswitch.php?username='+username+'&password='+password+'&server='+server;
207
+  
208
+  Pebble.openURL(url);
209
+});
210
+
211
+Pebble.addEventListener('webviewclosed', function(e) {
212
+  // Decode the user's preferences
213
+  var configData = JSON.parse(decodeURIComponent(e.response));
214
+  localStorage.setItem('username',configData.username);
215
+  localStorage.setItem('password',configData.password);
216
+  localStorage.setItem('server',configData.server);
217
+  console.log('---- new username stored : '+localStorage.getItem('username'));
218
+  console.log('---- new password stored : '+localStorage.getItem('password'));
219
+  console.log('---- new server stored : '+localStorage.getItem('server'));
220
+  var dict = {
221
+    'username': configData.username,
222
+    'password': configData.password,
223
+    'server': configData.server
224
+  };
225
+            
226
+  // Send the message
227
+  Pebble.sendAppMessage(dict, function(e) {
228
+    console.log('sent');
229
+  }, function() {
230
+    console.log('failed');
231
+  }); 
232
+});
0 233
\ No newline at end of file
1 234
new file mode 100755
... ...
@@ -0,0 +1,52 @@
1
+#
2
+# This file is the default set of rules to compile a Pebble project.
3
+#
4
+# Feel free to customize this to your needs.
5
+#
6
+
7
+import os.path
8
+try:
9
+    from sh import CommandNotFound, jshint, cat, ErrorReturnCode_2
10
+    hint = jshint
11
+except (ImportError, CommandNotFound):
12
+    hint = None
13
+
14
+top = '.'
15
+out = 'build'
16
+
17
+
18
+def options(ctx):
19
+    ctx.load('pebble_sdk')
20
+
21
+
22
+def configure(ctx):
23
+    ctx.load('pebble_sdk')
24
+
25
+
26
+def build(ctx):
27
+    if False and hint is not None:
28
+        try:
29
+            hint([node.abspath() for node in ctx.path.ant_glob("src/**/*.js")], _tty_out=False) # no tty because there are none in the cloudpebble sandbox.
30
+        except ErrorReturnCode_2 as e:
31
+            ctx.fatal("\nJavaScript linting failed (you can disable this in Project Settings):\n" + e.stdout)
32
+
33
+    ctx.load('pebble_sdk')
34
+
35
+    build_worker = os.path.exists('worker_src')
36
+    binaries = []
37
+
38
+    for p in ctx.env.TARGET_PLATFORMS:
39
+        ctx.set_env(ctx.all_envs[p])
40
+        ctx.set_group(ctx.env.PLATFORM_NAME)
41
+        app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
42
+        ctx.pbl_program(source=ctx.path.ant_glob('src/c/**/*.c'), target=app_elf)
43
+
44
+        if build_worker:
45
+            worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
46
+            binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
47
+            ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/c/**/*.c'), target=worker_elf)
48
+        else:
49
+            binaries.append({'platform': p, 'app_elf': app_elf})
50
+
51
+    ctx.set_group('bundle')
52
+    ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob(['src/pkjs/**/*.js', 'src/pkjs/**/*.json']), js_entry_file='src/pkjs/index.js')