-
If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x bootloader, the Firmware Update
- will fail with a message about the bootloader version. If so, please click here to update to bootloader 2v12 and then click the 'Upload' button that appears.
+
If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x DFU, the Firmware Update
+ will fail with a message about the DFU version. If so, please click here to update to DFU 2v12 and then click the 'Upload' button that appears.
The currently available Espruino firmware releases are:
To update, click a link above and then click the 'Upload' button that appears.
-
Advanced ▼
+
+
What is DFU? ▼
+
+
What is DFU?
+
DFU stands for Device Firmware Update . This is the first
+ bit of code that runs when Bangle.js starts, and it is able to update the
+ Bangle.js firmware. Normally you would update firmware via this Firmware
+ Updater app, but if for some reason Bangle.js will not boot, you can
+ always use DFU to to the update manually .
+
DFU is itself a bootloader, but here we're calling it DFU to avoid confusion
+ with the Bootloader app in the app loader (which prepares Bangle.js for running apps).
+
+
+
Advanced ▼
+
Advanced
Firmware updates via this tool work differently to the NRF Connect method mentioned on
the Bangle.js 2 page . Firmware
- is uploaded to a file on the Bangle. Once complete the Bangle reboots and the bootloader copies
+ is uploaded to a file on the Bangle. Once complete the Bangle reboots and DFU copies
the new firmware into internal Storage.
In addition to the links above, you can upload a hex or zip file directly below. This file should be an .app_hex
- file, *not* the normal .hex (as that contains the bootloader as well).
+ file, *not* the normal
.hex (as that contains the DFU as well).
DANGER! No verification is performed on uploaded ZIP or HEX files - you could
- potentially overwrite your bootloader with the wrong binary and brick your Bangle.
+ potentially overwrite your DFU with the wrong binary and brick your Bangle.
Upload
@@ -73,7 +87,7 @@ function onInit(device) {
document.getElementById("fw-ok").style = "";
}
Puck.eval("E.CRC32(E.memoryArea(0xF7000,0x7000))", crc => {
- console.log("Bootloader CRC = "+crc);
+ console.log("DFU CRC = "+crc);
var version = `unknown (CRC ${crc})`;
var ok = true;
if (crc==1339551013) { version = "2v10.219"; ok = false; }
@@ -299,8 +313,8 @@ function createJS_app(binary, startAddress, endAddress) {
bin32[3] = VERSION; // VERSION! Use this to test ourselves
console.log("CRC 0x"+bin32[2].toString(16));
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
- hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1339551013) { print("BOOTLOADER 2v10.219 needs update"); load();}\n`;
- hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("BOOTLOADER 2v10.236 needs update"); load();}\n`;
+ hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1339551013) { print("DFU 2v10.219 needs update"); load();}\n`;
+ hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("DFU 2v10.236 needs update"); load();}\n`;
hexJS += '\x10var s = require("Storage");\n';
hexJS += '\x10s.erase(".firmware");\n';
var CHUNKSIZE = 2048;
@@ -320,7 +334,7 @@ function createJS_app(binary, startAddress, endAddress) {
function createJS_bootloader(binary, startAddress, endAddress) {
var crc = CRC32(binary);
console.log("CRC 0x"+crc.toString(16));
- hexJS = `\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${crc}) { print("BOOTLOADER UP TO DATE!"); load();}\n`;
+ hexJS = `\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${crc}) { print("DFU UP TO DATE!"); load();}\n`;
hexJS += `\x10var _fw = new Uint8Array(${binary.length})\n`;
var CHUNKSIZE = 1024;
for (var i=0;i
it does not buzz very often on turns
+
+- when going backwards we have a tendencing to get a wrong current_segment
+
+* additional features
+
+- config screen
+ - are we on foot (and should use compass)
+
+- we need to buzz 200m before sharp turns (or even better, 30seconds)
+(and look at more than next point)
+
+- display distance to next water/toilet ?
+- dynamic map rescale
+- display scale (100m)
+
+- compress path ?
+
+* misc
+
+- code is becoming messy
diff --git a/apps/gipy/app-icon.js b/apps/gipy/app-icon.js
new file mode 100644
index 000000000..0fc51609f
--- /dev/null
+++ b/apps/gipy/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwkBiIA/AE8VqoAGCy1RiN3CyYuBi93uIXJIBV3AAIuMBY4XjQ5YXPRAIAEOwIABPBC4LF54wGF6IwFC5jWGIwxIJC4xJFgDuJJAxJFC6TEIJBzEHGCIYPGA5JQC44YPGBBJKY4gwRfQL4DGCL4GGCAXPGAxGBAAJIMGAwWCGCoWGC55HHJB5HIC8pGDSChfXC5AWIL5ynOC45GJC4h3IIyYwCFxwADgB1SC44uSC4guSAH4Ab"))
diff --git a/apps/gipy/app.js b/apps/gipy/app.js
new file mode 100644
index 000000000..ae82e5dfb
--- /dev/null
+++ b/apps/gipy/app.js
@@ -0,0 +1,766 @@
+let simulated = false;
+let file_version = 3;
+let code_key = 47490;
+
+var settings = Object.assign(
+ {
+ keep_gps_alive: true,
+ max_speed: 35,
+ },
+ require("Storage").readJSON("gipy.json", true) || {}
+);
+
+let interests_colors = [
+ 0xf800, // Bakery, red
+ 0x001f, // DrinkingWater, blue
+ 0x07ff, // Toilets, cyan
+ 0x07e0, // Artwork, green
+];
+
+function binary_search(array, x) {
+ let start = 0,
+ end = array.length - 1;
+
+ while (start <= end) {
+ let mid = Math.floor((start + end) / 2);
+ if (array[mid] < x) start = mid + 1;
+ else end = mid - 1;
+ }
+ return start;
+}
+
+class Status {
+ constructor(path) {
+ this.path = path;
+ this.on_path = false; // are we on the path or lost ?
+ this.position = null; // where we are
+ this.adjusted_cos_direction = null; // cos of where we look at
+ this.adjusted_sin_direction = null; // sin of where we look at
+ this.current_segment = null; // which segment is closest
+ this.reaching = null; // which waypoint are we reaching ?
+ this.distance_to_next_point = null; // how far are we from next point ?
+ this.paused_time = 0.0; // how long did we stop (stops don't count in avg speed)
+ this.paused_since = getTime();
+
+ let r = [0];
+ // let's do a reversed prefix computations on all distances:
+ // loop on all segments in reversed order
+ let previous_point = null;
+ for (let i = this.path.len - 1; i >= 0; i--) {
+ let point = this.path.point(i);
+ if (previous_point !== null) {
+ r.unshift(r[0] + point.distance(previous_point));
+ }
+ previous_point = point;
+ }
+ this.remaining_distances = r; // how much distance remains at start of each segment
+ this.starting_time = this.paused_since; // time we start
+ this.advanced_distance = 0.0;
+ this.gps_coordinates_counter = 0; // how many coordinates did we receive
+ this.old_points = [];
+ this.old_times = [];
+ }
+ new_position_reached(position) {
+ // we try to figure out direction by looking at previous points
+ // instead of the gps course which is not very nice.
+ this.gps_coordinates_counter += 1;
+ let now = getTime();
+ this.old_points.push(position);
+ this.old_times.push(now);
+
+ if (this.old_points.length == 1) {
+ return null;
+ }
+
+ let last_point = this.old_points[this.old_points.length - 1];
+ let oldest_point = this.old_points[0];
+
+ // every 7 points we count the distance
+ if (this.gps_coordinates_counter % 7 == 0) {
+ let distance = last_point.distance(oldest_point);
+ if (distance < 150.0) {
+ // to avoid gps glitches
+ this.advanced_distance += distance;
+ }
+ }
+
+ if (this.old_points.length == 8) {
+ let p1 = this.old_points[0]
+ .plus(this.old_points[1])
+ .plus(this.old_points[2])
+ .plus(this.old_points[3])
+ .times(1 / 4);
+ let p2 = this.old_points[4]
+ .plus(this.old_points[5])
+ .plus(this.old_points[6])
+ .plus(this.old_points[7])
+ .times(1 / 4);
+ let t1 = (this.old_times[1] + this.old_times[2]) / 2;
+ let t2 = (this.old_times[5] + this.old_times[6]) / 2;
+ this.instant_speed = p1.distance(p2) / (t2 - t1);
+ this.old_points.shift();
+ this.old_times.shift();
+ } else {
+ this.instant_speed =
+ oldest_point.distance(last_point) / (now - this.old_times[0]);
+
+ // update paused time if we are too slow
+ if (this.instant_speed < 2) {
+ if (this.paused_since === null) {
+ this.paused_since = now;
+ }
+ } else {
+ if (this.paused_since !== null) {
+ this.paused_time += now - this.paused_since;
+ this.paused_since = null;
+ }
+ }
+ }
+ // let's just take angle of segment between newest point and a point a bit before
+ let previous_index = this.old_points.length - 3;
+ if (previous_index < 0) {
+ previous_index = 0;
+ }
+ let diff = position.minus(this.old_points[previous_index]);
+ let angle = Math.atan2(diff.lat, diff.lon);
+ return angle;
+ }
+ update_position(new_position, maybe_direction) {
+ let direction = this.new_position_reached(new_position);
+ if (direction === null) {
+ if (maybe_direction === null) {
+ return;
+ } else {
+ direction = maybe_direction;
+ }
+ }
+
+ this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0);
+ this.adjusted_sin_direction = Math.sin(-direction - Math.PI / 2.0);
+ cos_direction = Math.cos(direction);
+ sin_direction = Math.sin(direction);
+ this.position = new_position;
+
+ // detect segment we are on now
+ let res = this.path.nearest_segment(
+ this.position,
+ Math.max(0, this.current_segment - 1),
+ Math.min(this.current_segment + 2, this.path.len - 1),
+ cos_direction,
+ sin_direction
+ );
+ let orientation = res[0];
+ let next_segment = res[1];
+
+ if (this.is_lost(next_segment)) {
+ // it did not work, try anywhere
+ res = this.path.nearest_segment(
+ this.position,
+ 0,
+ this.path.len - 1,
+ cos_direction,
+ sin_direction
+ );
+ orientation = res[0];
+ next_segment = res[1];
+ }
+ // now check if we strayed away from path or back to it
+ let lost = this.is_lost(next_segment);
+ if (this.on_path == lost) {
+ // if status changes
+ if (lost) {
+ Bangle.buzz(); // we lost path
+ setTimeout(() => Bangle.buzz(), 500);
+ setTimeout(() => Bangle.buzz(), 1000);
+ setTimeout(() => Bangle.buzz(), 1500);
+ }
+ this.on_path = !lost;
+ }
+
+ this.current_segment = next_segment;
+
+ // check if we are nearing the next point on our path and alert the user
+ let next_point = this.current_segment + (1 - orientation);
+ this.distance_to_next_point = Math.ceil(
+ this.position.distance(this.path.point(next_point))
+ );
+
+ // disable gps when far from next point and locked
+ if (Bangle.isLocked() && !settings.keep_gps_alive) {
+ let time_to_next_point =
+ (this.distance_to_next_point * 3.6) / settings.max_speed;
+ if (time_to_next_point > 60) {
+ Bangle.setGPSPower(false, "gipy");
+ setTimeout(function () {
+ Bangle.setGPSPower(true, "gipy");
+ }, time_to_next_point);
+ }
+ }
+ if (this.reaching != next_point && this.distance_to_next_point <= 100) {
+ this.reaching = next_point;
+ let reaching_waypoint = this.path.is_waypoint(next_point);
+ if (reaching_waypoint) {
+ Bangle.buzz();
+ setTimeout(() => Bangle.buzz(), 500);
+ setTimeout(() => Bangle.buzz(), 1000);
+ setTimeout(() => Bangle.buzz(), 1500);
+ if (Bangle.isLocked()) {
+ Bangle.setLocked(false);
+ }
+ }
+ }
+ // re-display
+ this.display(orientation);
+ }
+ remaining_distance(orientation) {
+ let remaining_in_correct_orientation =
+ this.remaining_distances[this.current_segment + 1] +
+ this.position.distance(this.path.point(this.current_segment + 1));
+
+ if (orientation == 0) {
+ return remaining_in_correct_orientation;
+ } else {
+ return this.remaining_distances[0] - remaining_in_correct_orientation;
+ }
+ }
+ is_lost(segment) {
+ let distance_to_nearest = this.position.distance_to_segment(
+ this.path.point(segment),
+ this.path.point(segment + 1)
+ );
+ return distance_to_nearest > 50;
+ }
+ display(orientation) {
+ g.clear();
+ this.display_map();
+
+ this.display_interest_points();
+ this.display_stats(orientation);
+ Bangle.drawWidgets();
+ }
+ display_interest_points() {
+ // this is the algorithm in case we have a lot of interest points
+ // let's draw all points for 5 segments centered on current one
+ let starting_group = Math.floor(Math.max(this.current_segment - 2, 0) / 3);
+ let ending_group = Math.floor(
+ Math.min(this.current_segment + 2, this.path.len - 2) / 3
+ );
+ let starting_bucket = binary_search(
+ this.path.interests_starts,
+ starting_group
+ );
+ let ending_bucket = binary_search(
+ this.path.interests_starts,
+ ending_group + 0.5
+ );
+ // we have 5 points per bucket
+ let end_index = Math.min(
+ this.path.interests_types.length - 1,
+ ending_bucket * 5
+ );
+ for (let i = starting_bucket * 5; i <= end_index; i++) {
+ let index = this.path.interests_on_path[i];
+ let interest_point = this.path.interest_point(index);
+ let color = this.path.interest_color(i);
+ let c = interest_point.coordinates(
+ this.position,
+ this.adjusted_cos_direction,
+ this.adjusted_sin_direction
+ );
+ g.setColor(color).fillCircle(c[0], c[1], 5);
+ }
+ }
+ display_stats(orientation) {
+ let remaining_distance = this.remaining_distance(orientation);
+ let rounded_distance = Math.round(remaining_distance / 100) / 10;
+ let total = Math.round(this.remaining_distances[0] / 100) / 10;
+ let now = new Date();
+ let minutes = now.getMinutes().toString();
+ if (minutes.length < 2) {
+ minutes = "0" + minutes;
+ }
+ let hours = now.getHours().toString();
+ g.setFont("6x8:2")
+ .setFontAlign(-1, -1, 0)
+ .setColor(g.theme.fg)
+ .drawString(hours + ":" + minutes, 0, 30);
+
+ g.setFont("6x8:2").drawString(
+ "" + this.distance_to_next_point + "m",
+ 0,
+ g.getHeight() - 49
+ );
+
+ let point_time = this.old_times[this.old_times.length - 1];
+ let done_in = point_time - this.starting_time - this.paused_time;
+ let approximate_speed = Math.round(
+ (this.advanced_distance * 3.6) / done_in
+ );
+ let approximate_instant_speed = Math.round(this.instant_speed * 3.6);
+
+ g.setFont("6x8:2")
+ .setFontAlign(-1, -1, 0)
+ .drawString(
+ "" + approximate_speed + "km/h (in." + approximate_instant_speed + ")",
+ 0,
+ g.getHeight() - 15
+ );
+
+ g.setFont("6x8:2").drawString(
+ "" + rounded_distance + "/" + total,
+ 0,
+ g.getHeight() - 32
+ );
+
+ if (this.distance_to_next_point <= 100) {
+ if (this.path.is_waypoint(this.reaching)) {
+ g.setColor(0.0, 1.0, 0.0)
+ .setFont("6x15")
+ .drawString("turn", g.getWidth() - 50, 30);
+ }
+ }
+ if (!this.on_path) {
+ g.setColor(1.0, 0.0, 0.0)
+ .setFont("6x15")
+ .drawString("lost", g.getWidth() - 55, 35);
+ }
+ }
+ display_map() {
+ // don't display all segments, only those neighbouring current segment
+ // this is most likely to be the correct display
+ // while lowering the cost a lot
+ //
+ // note that all code is inlined here to speed things up from 400ms to 200ms
+ let start = Math.max(this.current_segment - 4, 0);
+ let end = Math.min(this.current_segment + 6, this.path.len);
+ let pos = this.position;
+ let cos = this.adjusted_cos_direction;
+ let sin = this.adjusted_sin_direction;
+ let points = this.path.points;
+ let cx = pos.lon;
+ let cy = pos.lat;
+ let half_width = g.getWidth() / 2;
+ let half_height = g.getHeight() / 2;
+ let previous_x = null;
+ let previous_y = null;
+ for (let i = start; i < end; i++) {
+ let tx = (points[2 * i] - cx) * 40000.0;
+ let ty = (points[2 * i + 1] - cy) * 40000.0;
+ let rotated_x = tx * cos - ty * sin;
+ let rotated_y = tx * sin + ty * cos;
+ let x = half_width - Math.round(rotated_x); // x is inverted
+ let y = half_height + Math.round(rotated_y);
+ if (previous_x !== null) {
+ if (i == this.current_segment + 1) {
+ g.setColor(0.0, 1.0, 0.0);
+ } else {
+ g.setColor(1.0, 0.0, 0.0);
+ }
+ g.drawLine(previous_x, previous_y, x, y);
+
+ if (this.path.is_waypoint(i - 1)) {
+ g.setColor(g.theme.fg);
+ g.fillCircle(previous_x, previous_y, 6);
+ g.setColor(g.theme.bg);
+ g.fillCircle(previous_x, previous_y, 5);
+ }
+ g.setColor(g.theme.fg);
+ g.fillCircle(previous_x, previous_y, 4);
+ g.setColor(g.theme.bg);
+ g.fillCircle(previous_x, previous_y, 3);
+ }
+
+ previous_x = x;
+ previous_y = y;
+ }
+
+ if (this.path.is_waypoint(end - 1)) {
+ g.setColor(g.theme.fg);
+ g.fillCircle(previous_x, previous_y, 6);
+ g.setColor(g.theme.bg);
+ g.fillCircle(previous_x, previous_y, 5);
+ }
+ g.setColor(g.theme.fg);
+ g.fillCircle(previous_x, previous_y, 4);
+ g.setColor(g.theme.bg);
+ g.fillCircle(previous_x, previous_y, 3);
+
+ // now display ourselves
+ g.setColor(g.theme.fgH);
+ g.fillCircle(half_width, half_height, 5);
+
+ // display old points for direction debug
+ // for (let i = 0; i < this.old_points.length; i++) {
+ // let tx = (this.old_points[i].lon - cx) * 40000.0;
+ // let ty = (this.old_points[i].lat - cy) * 40000.0;
+ // let rotated_x = tx * cos - ty * sin;
+ // let rotated_y = tx * sin + ty * cos;
+ // let x = half_width - Math.round(rotated_x); // x is inverted
+ // let y = half_height + Math.round(rotated_y);
+ // g.setColor((i + 1) / 4.0, 0.0, 0.0);
+ // g.fillCircle(x, y, 3);
+ // }
+
+ // display current-segment's projection for debug
+ let projection = pos.closest_segment_point(
+ this.path.point(this.current_segment),
+ this.path.point(this.current_segment + 1)
+ );
+
+ let tx = (projection.lon - cx) * 40000.0;
+ let ty = (projection.lat - cy) * 40000.0;
+ let rotated_x = tx * cos - ty * sin;
+ let rotated_y = tx * sin + ty * cos;
+ let x = half_width - Math.round(rotated_x); // x is inverted
+ let y = half_height + Math.round(rotated_y);
+ g.setColor(g.theme.fg);
+ g.fillCircle(x, y, 4);
+
+ // display direction to next point if lost
+ if (!this.on_path) {
+ let next_point = this.path.point(this.current_segment + 1);
+ let diff = next_point.minus(this.position);
+ let angle = Math.atan2(diff.lat, diff.lon);
+ let tx = Math.cos(angle) * 50.0;
+ let ty = Math.sin(angle) * 50.0;
+ let rotated_x = tx * cos - ty * sin;
+ let rotated_y = tx * sin + ty * cos;
+ let x = half_width - Math.round(rotated_x); // x is inverted
+ let y = half_height + Math.round(rotated_y);
+ g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y);
+ }
+ }
+}
+
+function load_gpc(filename) {
+ let buffer = require("Storage").readArrayBuffer(filename);
+ let offset = 0;
+
+ // header
+ let header = Uint16Array(buffer, offset, 5);
+ offset += 5 * 2;
+ let key = header[0];
+ let version = header[1];
+ let points_number = header[2];
+ if (key != code_key || version > file_version) {
+ E.showMessage("Invalid gpc file");
+ load();
+ }
+
+ // path points
+ let points = Float64Array(buffer, offset, points_number * 2);
+ offset += 8 * points_number * 2;
+
+ // path waypoints
+ let waypoints_len = Math.ceil(points_number / 8.0);
+ let waypoints = Uint8Array(buffer, offset, waypoints_len);
+ offset += waypoints_len;
+
+ // interest points
+ let interests_number = header[3];
+ let interests_coordinates = Float64Array(
+ buffer,
+ offset,
+ interests_number * 2
+ );
+ offset += 8 * interests_number * 2;
+ let interests_types = Uint8Array(buffer, offset, interests_number);
+ offset += interests_number;
+
+ // interests on path
+ let interests_on_path_number = header[4];
+ let interests_on_path = Uint16Array(buffer, offset, interests_on_path_number);
+ offset += 2 * interests_on_path_number;
+ let starts_length = Math.ceil(interests_on_path_number / 5.0);
+ let interests_starts = Uint16Array(buffer, offset, starts_length);
+ offset += 2 * starts_length;
+
+ return [
+ points,
+ waypoints,
+ interests_coordinates,
+ interests_types,
+ interests_on_path,
+ interests_starts,
+ ];
+}
+
+class Path {
+ constructor(arrays) {
+ this.points = arrays[0];
+ this.waypoints = arrays[1];
+ this.interests_coordinates = arrays[2];
+ this.interests_types = arrays[3];
+ this.interests_on_path = arrays[4];
+ this.interests_starts = arrays[5];
+ }
+
+ is_waypoint(point_index) {
+ let i = Math.floor(point_index / 8);
+ let subindex = point_index % 8;
+ let r = this.waypoints[i] & (1 << subindex);
+ return r != 0;
+ }
+
+ // execute op on all segments.
+ // start is index of first wanted segment
+ // end is 1 after index of last wanted segment
+ on_segments(op, start, end) {
+ let previous_point = null;
+ for (let i = start; i < end + 1; i++) {
+ let point = new Point(this.points[2 * i], this.points[2 * i + 1]);
+ if (previous_point !== null) {
+ op(previous_point, point, i);
+ }
+ previous_point = point;
+ }
+ }
+
+ // return point at given index
+ point(index) {
+ let lon = this.points[2 * index];
+ let lat = this.points[2 * index + 1];
+ return new Point(lon, lat);
+ }
+
+ interest_point(index) {
+ let lon = this.interests_coordinates[2 * index];
+ let lat = this.interests_coordinates[2 * index + 1];
+ return new Point(lon, lat);
+ }
+
+ interest_color(index) {
+ return interests_colors[this.interests_types[index]];
+ }
+
+ // return index of segment which is nearest from point.
+ // we need a direction because we need there is an ambiguity
+ // for overlapping segments which are taken once to go and once to come back.
+ // (in the other direction).
+ nearest_segment(point, start, end, cos_direction, sin_direction) {
+ // we are going to compute two min distances, one for each direction.
+ let indices = [0, 0];
+ let mins = [Number.MAX_VALUE, Number.MAX_VALUE];
+ this.on_segments(
+ function (p1, p2, i) {
+ // we use the dot product to figure out if oriented correctly
+ // let distance = point.fake_distance_to_segment(p1, p2);
+
+ let projection = point.closest_segment_point(p1, p2);
+ let distance = point.fake_distance(projection);
+
+ // let d = projection.minus(point).times(40000.0);
+ // let rotated_x = d.lon * acos - d.lat * asin;
+ // let rotated_y = d.lon * asin + d.lat * acos;
+ // let x = g.getWidth() / 2 - Math.round(rotated_x); // x is inverted
+ // let y = g.getHeight() / 2 + Math.round(rotated_y);
+ //
+ let diff = p2.minus(p1);
+ let dot = cos_direction * diff.lon + sin_direction * diff.lat;
+ let orientation = +(dot < 0); // index 0 is good orientation
+ // g.setColor(0.0, 0.0 + orientation, 1.0 - orientation).fillCircle(
+ // x,
+ // y,
+ // 10
+ // );
+ if (distance <= mins[orientation]) {
+ mins[orientation] = distance;
+ indices[orientation] = i - 1;
+ }
+ },
+ start,
+ end
+ );
+ // by default correct orientation (0) wins
+ // but if other one is really closer, return other one
+ if (mins[1] < mins[0] / 10.0) {
+ return [1, indices[1]];
+ } else {
+ return [0, indices[0]];
+ }
+ }
+ get len() {
+ return this.points.length / 2;
+ }
+}
+
+class Point {
+ constructor(lon, lat) {
+ this.lon = lon;
+ this.lat = lat;
+ }
+ coordinates(current_position, cos_direction, sin_direction) {
+ let translated = this.minus(current_position).times(40000.0);
+ let rotated_x =
+ translated.lon * cos_direction - translated.lat * sin_direction;
+ let rotated_y =
+ translated.lon * sin_direction + translated.lat * cos_direction;
+ return [
+ g.getWidth() / 2 - Math.round(rotated_x), // x is inverted
+ g.getHeight() / 2 + Math.round(rotated_y),
+ ];
+ }
+ minus(other_point) {
+ let xdiff = this.lon - other_point.lon;
+ let ydiff = this.lat - other_point.lat;
+ return new Point(xdiff, ydiff);
+ }
+ plus(other_point) {
+ return new Point(this.lon + other_point.lon, this.lat + other_point.lat);
+ }
+ length_squared(other_point) {
+ let d = this.minus(other_point);
+ return d.lon * d.lon + d.lat * d.lat;
+ }
+ times(scalar) {
+ return new Point(this.lon * scalar, this.lat * scalar);
+ }
+ dot(other_point) {
+ return this.lon * other_point.lon + this.lat * other_point.lat;
+ }
+ distance(other_point) {
+ //see https://www.movable-type.co.uk/scripts/latlong.html
+ const R = 6371e3; // metres
+ const phi1 = (this.lat * Math.PI) / 180;
+ const phi2 = (other_point.lat * Math.PI) / 180;
+ const deltaphi = ((other_point.lat - this.lat) * Math.PI) / 180;
+ const deltalambda = ((other_point.lon - this.lon) * Math.PI) / 180;
+
+ const a =
+ Math.sin(deltaphi / 2) * Math.sin(deltaphi / 2) +
+ Math.cos(phi1) *
+ Math.cos(phi2) *
+ Math.sin(deltalambda / 2) *
+ Math.sin(deltalambda / 2);
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+ return R * c; // in meters
+ }
+ fake_distance(other_point) {
+ return Math.sqrt(this.length_squared(other_point));
+ }
+ closest_segment_point(v, w) {
+ // from : https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
+ // Return minimum distance between line segment vw and point p
+ let l2 = v.length_squared(w); // i.e. |w-v|^2 - avoid a sqrt
+ if (l2 == 0.0) {
+ return v; // v == w case
+ }
+ // Consider the line extending the segment, parameterized as v + t (w - v).
+ // We find projection of point p onto the line.
+ // It falls where t = [(p-v) . (w-v)] / |w-v|^2
+ // We clamp t from [0,1] to handle points outside the segment vw.
+ let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2));
+ return v.plus(w.minus(v).times(t)); // Projection falls on the segment
+ }
+ distance_to_segment(v, w) {
+ let projection = this.closest_segment_point(v, w);
+ return this.distance(projection);
+ }
+ fake_distance_to_segment(v, w) {
+ let projection = this.closest_segment_point(v, w);
+ return this.fake_distance(projection);
+ }
+}
+
+Bangle.loadWidgets();
+
+let fake_gps_point = 0.0;
+function simulate_gps(status) {
+ if (fake_gps_point > status.path.len - 1) {
+ return;
+ }
+ let point_index = Math.floor(fake_gps_point);
+ if (point_index >= status.path.len) {
+ return;
+ }
+ //let p1 = status.path.point(0);
+ //let n = status.path.len;
+ //let p2 = status.path.point(n - 1);
+ let p1 = status.path.point(point_index);
+ let p2 = status.path.point(point_index + 1);
+
+ let alpha = fake_gps_point - point_index;
+ let pos = p1.times(1 - alpha).plus(p2.times(alpha));
+ let old_pos = status.position;
+
+ fake_gps_point += 0.05; // advance simulation
+ status.update_position(pos, null);
+}
+
+function drawMenu() {
+ const menu = {
+ "": { title: "choose trace" },
+ };
+ var files = require("Storage").list(".gpc");
+ for (var i = 0; i < files.length; ++i) {
+ menu[files[i]] = start.bind(null, files[i]);
+ }
+ menu["Exit"] = function () {
+ load();
+ };
+ E.showMenu(menu);
+}
+
+function start(fn) {
+ E.showMenu();
+ console.log("loading", fn);
+
+ // let path = new Path(load_gpx("test.gpx"));
+ let path = new Path(load_gpc(fn));
+ let status = new Status(path);
+
+ if (simulated) {
+ status.position = new Point(status.path.point(0));
+ setInterval(simulate_gps, 500, status);
+ } else {
+ // let's display start while waiting for gps signal
+ let p1 = status.path.point(0);
+ let p2 = status.path.point(1);
+ let diff = p2.minus(p1);
+ let direction = Math.atan2(diff.lat, diff.lon);
+ Bangle.setLocked(false);
+ status.update_position(p1, direction);
+
+ let frame = 0;
+ let set_coordinates = function (data) {
+ frame += 1;
+ // 0,0 coordinates are considered invalid since we sometimes receive them out of nowhere
+ let valid_coordinates =
+ !isNaN(data.lat) &&
+ !isNaN(data.lon) &&
+ (data.lat != 0.0 || data.lon != 0.0);
+ if (valid_coordinates) {
+ status.update_position(new Point(data.lon, data.lat), null);
+ }
+ let gps_status_color;
+ if (frame % 2 == 0 || valid_coordinates) {
+ gps_status_color = g.theme.bg;
+ } else {
+ gps_status_color = g.theme.fg;
+ }
+ g.setColor(gps_status_color)
+ .setFont("6x8:2")
+ .drawString("gps", g.getWidth() - 40, 30);
+ };
+
+ Bangle.setGPSPower(true, "gipy");
+ Bangle.on("GPS", set_coordinates);
+ Bangle.on("lock", function (on) {
+ if (!on) {
+ Bangle.setGPSPower(true, "gipy"); // activate gps when unlocking
+ }
+ });
+ }
+}
+
+let files = require("Storage").list(".gpc");
+if (files.length <= 1) {
+ if (files.length == 0) {
+ load();
+ } else {
+ start(files[0]);
+ }
+} else {
+ drawMenu();
+}
diff --git a/apps/gipy/gipy.png b/apps/gipy/gipy.png
new file mode 100644
index 000000000..e9e472f5c
Binary files /dev/null and b/apps/gipy/gipy.png differ
diff --git a/apps/gipy/interface.html b/apps/gipy/interface.html
new file mode 100644
index 000000000..a1c405ed7
--- /dev/null
+++ b/apps/gipy/interface.html
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+ Please select a gpx file to be converted to gpc and loaded.
+
+
+ gpx file :
+
+ gpc filename : .gpc (max 24 characters)
+
+
+ fetch interests from openstreetmap
+
+
+ nice tags could be :
+ shop/bicycle, amenity/bank, shop/supermarket, leisure/picnic_table, tourism/information, amenity/pharmacy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/gipy/metadata.json b/apps/gipy/metadata.json
new file mode 100644
index 000000000..2d06a7c2d
--- /dev/null
+++ b/apps/gipy/metadata.json
@@ -0,0 +1,23 @@
+{
+ "id": "gipy",
+ "name": "Gipy",
+ "shortName": "Gipy",
+ "version": "0.15",
+ "description": "Follow gpx files",
+ "allow_emulator":false,
+ "icon": "gipy.png",
+ "type": "app",
+ "tags": "tool,outdoors,gps",
+ "screenshots": [],
+ "supports": ["BANGLEJS2"],
+ "readme": "README.md",
+ "interface": "interface.html",
+ "storage": [
+ {"name":"gipy.app.js","url":"app.js"},
+ {"name":"gipy.settings.js","url":"settings.js"},
+ {"name":"gipy.img","url":"app-icon.js","evaluate":true}
+ ],
+ "data": [
+ {"name":"gipy.json"}
+ ]
+}
diff --git a/apps/gipy/pkg/gpconv.d.ts b/apps/gipy/pkg/gpconv.d.ts
new file mode 100644
index 000000000..ecffa7b69
--- /dev/null
+++ b/apps/gipy/pkg/gpconv.d.ts
@@ -0,0 +1,75 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+* @param {GpcSvg} gpcsvg
+* @returns {Uint8Array}
+*/
+export function get_gpc(gpcsvg: GpcSvg): Uint8Array;
+/**
+* @param {GpcSvg} gpcsvg
+* @returns {Uint8Array}
+*/
+export function get_svg(gpcsvg: GpcSvg): Uint8Array;
+/**
+* @param {string} input_str
+* @returns {Promise}
+*/
+export function convert_gpx_strings_no_osm(input_str: string): Promise;
+/**
+* @param {string} input_str
+* @param {string} key1
+* @param {string} value1
+* @param {string} key2
+* @param {string} value2
+* @param {string} key3
+* @param {string} value3
+* @param {string} key4
+* @param {string} value4
+* @returns {Promise}
+*/
+export function convert_gpx_strings(input_str: string, key1: string, value1: string, key2: string, value2: string, key3: string, value3: string, key4: string, value4: string): Promise;
+/**
+*/
+export class GpcSvg {
+ free(): void;
+}
+
+export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
+
+export interface InitOutput {
+ readonly memory: WebAssembly.Memory;
+ readonly __wbg_gpcsvg_free: (a: number) => void;
+ readonly get_gpc: (a: number, b: number) => void;
+ readonly get_svg: (a: number, b: number) => void;
+ readonly convert_gpx_strings_no_osm: (a: number, b: number) => number;
+ readonly convert_gpx_strings: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number) => number;
+ readonly __wbindgen_malloc: (a: number) => number;
+ readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
+ readonly __wbindgen_export_2: WebAssembly.Table;
+ readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd: (a: number, b: number, c: number) => void;
+ readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
+ readonly __wbindgen_free: (a: number, b: number) => void;
+ readonly __wbindgen_exn_store: (a: number) => void;
+ readonly wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476: (a: number, b: number, c: number, d: number) => void;
+}
+
+export type SyncInitInput = BufferSource | WebAssembly.Module;
+/**
+* Instantiates the given `module`, which can either be bytes or
+* a precompiled `WebAssembly.Module`.
+*
+* @param {SyncInitInput} module
+*
+* @returns {InitOutput}
+*/
+export function initSync(module: SyncInitInput): InitOutput;
+
+/**
+* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
+* for everything else, calls `WebAssembly.instantiate` directly.
+*
+* @param {InitInput | Promise} module_or_path
+*
+* @returns {Promise}
+*/
+export default function init (module_or_path?: InitInput | Promise): Promise;
diff --git a/apps/gipy/pkg/gpconv.js b/apps/gipy/pkg/gpconv.js
new file mode 100644
index 000000000..97b37e340
--- /dev/null
+++ b/apps/gipy/pkg/gpconv.js
@@ -0,0 +1,645 @@
+
+let wasm;
+
+const heap = new Array(32).fill(undefined);
+
+heap.push(undefined, null, true, false);
+
+function getObject(idx) { return heap[idx]; }
+
+let heap_next = heap.length;
+
+function dropObject(idx) {
+ if (idx < 36) return;
+ heap[idx] = heap_next;
+ heap_next = idx;
+}
+
+function takeObject(idx) {
+ const ret = getObject(idx);
+ dropObject(idx);
+ return ret;
+}
+
+let WASM_VECTOR_LEN = 0;
+
+let cachedUint8Memory0 = new Uint8Array();
+
+function getUint8Memory0() {
+ if (cachedUint8Memory0.byteLength === 0) {
+ cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+ }
+ return cachedUint8Memory0;
+}
+
+const cachedTextEncoder = new TextEncoder('utf-8');
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+ ? function (arg, view) {
+ return cachedTextEncoder.encodeInto(arg, view);
+}
+ : function (arg, view) {
+ const buf = cachedTextEncoder.encode(arg);
+ view.set(buf);
+ return {
+ read: arg.length,
+ written: buf.length
+ };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+ if (realloc === undefined) {
+ const buf = cachedTextEncoder.encode(arg);
+ const ptr = malloc(buf.length);
+ getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
+ WASM_VECTOR_LEN = buf.length;
+ return ptr;
+ }
+
+ let len = arg.length;
+ let ptr = malloc(len);
+
+ const mem = getUint8Memory0();
+
+ let offset = 0;
+
+ for (; offset < len; offset++) {
+ const code = arg.charCodeAt(offset);
+ if (code > 0x7F) break;
+ mem[ptr + offset] = code;
+ }
+
+ if (offset !== len) {
+ if (offset !== 0) {
+ arg = arg.slice(offset);
+ }
+ ptr = realloc(ptr, len, len = offset + arg.length * 3);
+ const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
+ const ret = encodeString(arg, view);
+
+ offset += ret.written;
+ }
+
+ WASM_VECTOR_LEN = offset;
+ return ptr;
+}
+
+function isLikeNone(x) {
+ return x === undefined || x === null;
+}
+
+let cachedInt32Memory0 = new Int32Array();
+
+function getInt32Memory0() {
+ if (cachedInt32Memory0.byteLength === 0) {
+ cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
+ }
+ return cachedInt32Memory0;
+}
+
+const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
+
+cachedTextDecoder.decode();
+
+function getStringFromWasm0(ptr, len) {
+ return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+}
+
+function addHeapObject(obj) {
+ if (heap_next === heap.length) heap.push(heap.length + 1);
+ const idx = heap_next;
+ heap_next = heap[idx];
+
+ heap[idx] = obj;
+ return idx;
+}
+
+function debugString(val) {
+ // primitive types
+ const type = typeof val;
+ if (type == 'number' || type == 'boolean' || val == null) {
+ return `${val}`;
+ }
+ if (type == 'string') {
+ return `"${val}"`;
+ }
+ if (type == 'symbol') {
+ const description = val.description;
+ if (description == null) {
+ return 'Symbol';
+ } else {
+ return `Symbol(${description})`;
+ }
+ }
+ if (type == 'function') {
+ const name = val.name;
+ if (typeof name == 'string' && name.length > 0) {
+ return `Function(${name})`;
+ } else {
+ return 'Function';
+ }
+ }
+ // objects
+ if (Array.isArray(val)) {
+ const length = val.length;
+ let debug = '[';
+ if (length > 0) {
+ debug += debugString(val[0]);
+ }
+ for(let i = 1; i < length; i++) {
+ debug += ', ' + debugString(val[i]);
+ }
+ debug += ']';
+ return debug;
+ }
+ // Test for built-in
+ const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
+ let className;
+ if (builtInMatches.length > 1) {
+ className = builtInMatches[1];
+ } else {
+ // Failed to match the standard '[object ClassName]'
+ return toString.call(val);
+ }
+ if (className == 'Object') {
+ // we're a user defined class or Object
+ // JSON.stringify avoids problems with cycles, and is generally much
+ // easier than looping through ownProperties of `val`.
+ try {
+ return 'Object(' + JSON.stringify(val) + ')';
+ } catch (_) {
+ return 'Object';
+ }
+ }
+ // errors
+ if (val instanceof Error) {
+ return `${val.name}: ${val.message}\n${val.stack}`;
+ }
+ // TODO we could test for more things here, like `Set`s and `Map`s.
+ return className;
+}
+
+function makeMutClosure(arg0, arg1, dtor, f) {
+ const state = { a: arg0, b: arg1, cnt: 1, dtor };
+ const real = (...args) => {
+ // First up with a closure we increment the internal reference
+ // count. This ensures that the Rust closure environment won't
+ // be deallocated while we're invoking it.
+ state.cnt++;
+ const a = state.a;
+ state.a = 0;
+ try {
+ return f(a, state.b, ...args);
+ } finally {
+ if (--state.cnt === 0) {
+ wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
+
+ } else {
+ state.a = a;
+ }
+ }
+ };
+ real.original = state;
+
+ return real;
+}
+function __wbg_adapter_24(arg0, arg1, arg2) {
+ wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(arg0, arg1, addHeapObject(arg2));
+}
+
+function _assertClass(instance, klass) {
+ if (!(instance instanceof klass)) {
+ throw new Error(`expected instance of ${klass.name}`);
+ }
+ return instance.ptr;
+}
+
+function getArrayU8FromWasm0(ptr, len) {
+ return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
+}
+/**
+* @param {GpcSvg} gpcsvg
+* @returns {Uint8Array}
+*/
+export function get_gpc(gpcsvg) {
+ try {
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+ _assertClass(gpcsvg, GpcSvg);
+ wasm.get_gpc(retptr, gpcsvg.ptr);
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
+ var v0 = getArrayU8FromWasm0(r0, r1).slice();
+ wasm.__wbindgen_free(r0, r1 * 1);
+ return v0;
+ } finally {
+ wasm.__wbindgen_add_to_stack_pointer(16);
+ }
+}
+
+/**
+* @param {GpcSvg} gpcsvg
+* @returns {Uint8Array}
+*/
+export function get_svg(gpcsvg) {
+ try {
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+ _assertClass(gpcsvg, GpcSvg);
+ wasm.get_svg(retptr, gpcsvg.ptr);
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
+ var v0 = getArrayU8FromWasm0(r0, r1).slice();
+ wasm.__wbindgen_free(r0, r1 * 1);
+ return v0;
+ } finally {
+ wasm.__wbindgen_add_to_stack_pointer(16);
+ }
+}
+
+/**
+* @param {string} input_str
+* @returns {Promise}
+*/
+export function convert_gpx_strings_no_osm(input_str) {
+ const ptr0 = passStringToWasm0(input_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ const ret = wasm.convert_gpx_strings_no_osm(ptr0, len0);
+ return takeObject(ret);
+}
+
+/**
+* @param {string} input_str
+* @param {string} key1
+* @param {string} value1
+* @param {string} key2
+* @param {string} value2
+* @param {string} key3
+* @param {string} value3
+* @param {string} key4
+* @param {string} value4
+* @returns {Promise}
+*/
+export function convert_gpx_strings(input_str, key1, value1, key2, value2, key3, value3, key4, value4) {
+ const ptr0 = passStringToWasm0(input_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ const ptr1 = passStringToWasm0(key1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len1 = WASM_VECTOR_LEN;
+ const ptr2 = passStringToWasm0(value1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len2 = WASM_VECTOR_LEN;
+ const ptr3 = passStringToWasm0(key2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len3 = WASM_VECTOR_LEN;
+ const ptr4 = passStringToWasm0(value2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len4 = WASM_VECTOR_LEN;
+ const ptr5 = passStringToWasm0(key3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len5 = WASM_VECTOR_LEN;
+ const ptr6 = passStringToWasm0(value3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len6 = WASM_VECTOR_LEN;
+ const ptr7 = passStringToWasm0(key4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len7 = WASM_VECTOR_LEN;
+ const ptr8 = passStringToWasm0(value4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len8 = WASM_VECTOR_LEN;
+ const ret = wasm.convert_gpx_strings(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6, ptr7, len7, ptr8, len8);
+ return takeObject(ret);
+}
+
+function handleError(f, args) {
+ try {
+ return f.apply(this, args);
+ } catch (e) {
+ wasm.__wbindgen_exn_store(addHeapObject(e));
+ }
+}
+function __wbg_adapter_69(arg0, arg1, arg2, arg3) {
+ wasm.wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
+}
+
+/**
+*/
+export class GpcSvg {
+
+ static __wrap(ptr) {
+ const obj = Object.create(GpcSvg.prototype);
+ obj.ptr = ptr;
+
+ return obj;
+ }
+
+ __destroy_into_raw() {
+ const ptr = this.ptr;
+ this.ptr = 0;
+
+ return ptr;
+ }
+
+ free() {
+ const ptr = this.__destroy_into_raw();
+ wasm.__wbg_gpcsvg_free(ptr);
+ }
+}
+
+async function load(module, imports) {
+ if (typeof Response === 'function' && module instanceof Response) {
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
+ try {
+ return await WebAssembly.instantiateStreaming(module, imports);
+
+ } catch (e) {
+ if (module.headers.get('Content-Type') != 'application/wasm') {
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ const bytes = await module.arrayBuffer();
+ return await WebAssembly.instantiate(bytes, imports);
+
+ } else {
+ const instance = await WebAssembly.instantiate(module, imports);
+
+ if (instance instanceof WebAssembly.Instance) {
+ return { instance, module };
+
+ } else {
+ return instance;
+ }
+ }
+}
+
+function getImports() {
+ const imports = {};
+ imports.wbg = {};
+ imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
+ takeObject(arg0);
+ };
+ imports.wbg.__wbg_gpcsvg_new = function(arg0) {
+ const ret = GpcSvg.__wrap(arg0);
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
+ const obj = getObject(arg1);
+ const ret = typeof(obj) === 'string' ? obj : undefined;
+ var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ var len0 = WASM_VECTOR_LEN;
+ getInt32Memory0()[arg0 / 4 + 1] = len0;
+ getInt32Memory0()[arg0 / 4 + 0] = ptr0;
+ };
+ imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
+ const ret = getStringFromWasm0(arg0, arg1);
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
+ const ret = getObject(arg0);
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_fetch_386f87a3ebf5003c = function(arg0) {
+ const ret = fetch(getObject(arg0));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_cb_drop = function(arg0) {
+ const obj = takeObject(arg0).original;
+ if (obj.cnt-- == 1) {
+ obj.a = 0;
+ return true;
+ }
+ const ret = false;
+ return ret;
+ };
+ imports.wbg.__wbg_fetch_749a56934f95c96c = function(arg0, arg1) {
+ const ret = getObject(arg0).fetch(getObject(arg1));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_instanceof_Response_eaa426220848a39e = function(arg0) {
+ let result;
+ try {
+ result = getObject(arg0) instanceof Response;
+ } catch {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_url_74285ddf2747cb3d = function(arg0, arg1) {
+ const ret = getObject(arg1).url;
+ const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ getInt32Memory0()[arg0 / 4 + 1] = len0;
+ getInt32Memory0()[arg0 / 4 + 0] = ptr0;
+ };
+ imports.wbg.__wbg_status_c4ef3dd591e63435 = function(arg0) {
+ const ret = getObject(arg0).status;
+ return ret;
+ };
+ imports.wbg.__wbg_headers_fd64ad685cf22e5d = function(arg0) {
+ const ret = getObject(arg0).headers;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_text_1169d752cc697903 = function() { return handleError(function (arg0) {
+ const ret = getObject(arg0).text();
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) {
+ const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () {
+ const ret = new Headers();
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_append_de37df908812970d = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
+ getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
+ }, arguments) };
+ imports.wbg.__wbindgen_is_object = function(arg0) {
+ const val = getObject(arg0);
+ const ret = typeof(val) === 'object' && val !== null;
+ return ret;
+ };
+ imports.wbg.__wbg_newnoargs_b5b063fc6c2f0376 = function(arg0, arg1) {
+ const ret = new Function(getStringFromWasm0(arg0, arg1));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_next_579e583d33566a86 = function(arg0) {
+ const ret = getObject(arg0).next;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_is_function = function(arg0) {
+ const ret = typeof(getObject(arg0)) === 'function';
+ return ret;
+ };
+ imports.wbg.__wbg_value_1ccc36bc03462d71 = function(arg0) {
+ const ret = getObject(arg0).value;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_iterator_6f9d4f28845f426c = function() {
+ const ret = Symbol.iterator;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_new_0b9bfdd97583284e = function() {
+ const ret = new Object();
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_self_6d479506f72c6a71 = function() { return handleError(function () {
+ const ret = self.self;
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_window_f2557cc78490aceb = function() { return handleError(function () {
+ const ret = window.window;
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_globalThis_7f206bda628d5286 = function() { return handleError(function () {
+ const ret = globalThis.globalThis;
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_global_ba75c50d1cf384f4 = function() { return handleError(function () {
+ const ret = global.global;
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbindgen_is_undefined = function(arg0) {
+ const ret = getObject(arg0) === undefined;
+ return ret;
+ };
+ imports.wbg.__wbg_call_97ae9d8645dc388b = function() { return handleError(function (arg0, arg1) {
+ const ret = getObject(arg0).call(getObject(arg1));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_call_168da88779e35f61 = function() { return handleError(function (arg0, arg1, arg2) {
+ const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_next_aaef7c8aa5e212ac = function() { return handleError(function (arg0) {
+ const ret = getObject(arg0).next();
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_done_1b73b0672e15f234 = function(arg0) {
+ const ret = getObject(arg0).done;
+ return ret;
+ };
+ imports.wbg.__wbg_new_9962f939219f1820 = function(arg0, arg1) {
+ try {
+ var state0 = {a: arg0, b: arg1};
+ var cb0 = (arg0, arg1) => {
+ const a = state0.a;
+ state0.a = 0;
+ try {
+ return __wbg_adapter_69(a, state0.b, arg0, arg1);
+ } finally {
+ state0.a = a;
+ }
+ };
+ const ret = new Promise(cb0);
+ return addHeapObject(ret);
+ } finally {
+ state0.a = state0.b = 0;
+ }
+ };
+ imports.wbg.__wbg_resolve_99fe17964f31ffc0 = function(arg0) {
+ const ret = Promise.resolve(getObject(arg0));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_then_11f7a54d67b4bfad = function(arg0, arg1) {
+ const ret = getObject(arg0).then(getObject(arg1));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_then_cedad20fbbd9418a = function(arg0, arg1, arg2) {
+ const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_buffer_3f3d764d4747d564 = function(arg0) {
+ const ret = getObject(arg0).buffer;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_newwithbyteoffsetandlength_d9aa266703cb98be = function(arg0, arg1, arg2) {
+ const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_new_8c3f0052272a457a = function(arg0) {
+ const ret = new Uint8Array(getObject(arg0));
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) {
+ const ret = JSON.stringify(getObject(arg0));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_get_765201544a2b6869 = function() { return handleError(function (arg0, arg1) {
+ const ret = Reflect.get(getObject(arg0), getObject(arg1));
+ return addHeapObject(ret);
+ }, arguments) };
+ imports.wbg.__wbg_has_8359f114ce042f5a = function() { return handleError(function (arg0, arg1) {
+ const ret = Reflect.has(getObject(arg0), getObject(arg1));
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_set_bf3f89b92d5a34bf = function() { return handleError(function (arg0, arg1, arg2) {
+ const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
+ const ret = debugString(getObject(arg1));
+ const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ getInt32Memory0()[arg0 / 4 + 1] = len0;
+ getInt32Memory0()[arg0 / 4 + 0] = ptr0;
+ };
+ imports.wbg.__wbindgen_throw = function(arg0, arg1) {
+ throw new Error(getStringFromWasm0(arg0, arg1));
+ };
+ imports.wbg.__wbindgen_memory = function() {
+ const ret = wasm.memory;
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbindgen_closure_wrapper947 = function(arg0, arg1, arg2) {
+ const ret = makeMutClosure(arg0, arg1, 147, __wbg_adapter_24);
+ return addHeapObject(ret);
+ };
+
+ return imports;
+}
+
+function initMemory(imports, maybe_memory) {
+
+}
+
+function finalizeInit(instance, module) {
+ wasm = instance.exports;
+ init.__wbindgen_wasm_module = module;
+ cachedInt32Memory0 = new Int32Array();
+ cachedUint8Memory0 = new Uint8Array();
+
+
+ return wasm;
+}
+
+function initSync(module) {
+ const imports = getImports();
+
+ initMemory(imports);
+
+ if (!(module instanceof WebAssembly.Module)) {
+ module = new WebAssembly.Module(module);
+ }
+
+ const instance = new WebAssembly.Instance(module, imports);
+
+ return finalizeInit(instance, module);
+}
+
+async function init(input) {
+ if (typeof input === 'undefined') {
+ input = new URL('gpconv_bg.wasm', import.meta.url);
+ }
+ const imports = getImports();
+
+ if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
+ input = fetch(input);
+ }
+
+ initMemory(imports);
+
+ const { instance, module } = await load(await input, imports);
+
+ return finalizeInit(instance, module);
+}
+
+export { initSync }
+export default init;
diff --git a/apps/gipy/pkg/gpconv_bg.wasm b/apps/gipy/pkg/gpconv_bg.wasm
new file mode 100644
index 000000000..edeb4eb59
Binary files /dev/null and b/apps/gipy/pkg/gpconv_bg.wasm differ
diff --git a/apps/gipy/pkg/gpconv_bg.wasm.d.ts b/apps/gipy/pkg/gpconv_bg.wasm.d.ts
new file mode 100644
index 000000000..6bc5d3719
--- /dev/null
+++ b/apps/gipy/pkg/gpconv_bg.wasm.d.ts
@@ -0,0 +1,16 @@
+/* tslint:disable */
+/* eslint-disable */
+export const memory: WebAssembly.Memory;
+export function __wbg_gpcsvg_free(a: number): void;
+export function get_gpc(a: number, b: number): void;
+export function get_svg(a: number, b: number): void;
+export function convert_gpx_strings_no_osm(a: number, b: number): number;
+export function convert_gpx_strings(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number): number;
+export function __wbindgen_malloc(a: number): number;
+export function __wbindgen_realloc(a: number, b: number, c: number): number;
+export const __wbindgen_export_2: WebAssembly.Table;
+export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(a: number, b: number, c: number): void;
+export function __wbindgen_add_to_stack_pointer(a: number): number;
+export function __wbindgen_free(a: number, b: number): void;
+export function __wbindgen_exn_store(a: number): void;
+export function wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(a: number, b: number, c: number, d: number): void;
diff --git a/apps/gipy/pkg/package.json b/apps/gipy/pkg/package.json
new file mode 100644
index 000000000..dee41f5cc
--- /dev/null
+++ b/apps/gipy/pkg/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "gpconv",
+ "version": "0.1.0",
+ "files": [
+ "gpconv_bg.wasm",
+ "gpconv.js",
+ "gpconv.d.ts"
+ ],
+ "module": "gpconv.js",
+ "types": "gpconv.d.ts",
+ "sideEffects": false
+}
\ No newline at end of file
diff --git a/apps/gipy/screenshot1.png b/apps/gipy/screenshot1.png
new file mode 100644
index 000000000..c7c45fa3b
Binary files /dev/null and b/apps/gipy/screenshot1.png differ
diff --git a/apps/gipy/screenshot2.png b/apps/gipy/screenshot2.png
new file mode 100644
index 000000000..ed61eb795
Binary files /dev/null and b/apps/gipy/screenshot2.png differ
diff --git a/apps/gipy/settings.js b/apps/gipy/settings.js
new file mode 100644
index 000000000..af9cbef22
--- /dev/null
+++ b/apps/gipy/settings.js
@@ -0,0 +1,38 @@
+(function (back) {
+ var FILE = "gipy.json";
+ // Load settings
+ var settings = Object.assign(
+ {
+ keep_gps_alive: false,
+ max_speed: 35,
+ },
+ require("Storage").readJSON(FILE, true) || {}
+ );
+
+ function writeSettings() {
+ require("Storage").writeJSON(FILE, settings);
+ }
+
+ // Show the menu
+ E.showMenu({
+ "": { title: "Gipy" },
+ "< Back": () => back(),
+ "keep gps alive": {
+ value: !!settings.keep_gps_alive, // !! converts undefined to false
+ format: (v) => (v ? "Yes" : "No"),
+ onchange: (v) => {
+ settings.keep_gps_alive = v;
+ writeSettings();
+ },
+ },
+ "max speed": {
+ value: 35 | settings.max_speed, // 0| converts undefined to 0
+ min: 0,
+ max: 130,
+ onchange: (v) => {
+ settings.max_speed = v;
+ writeSettings();
+ },
+ },
+ });
+});
diff --git a/apps/gpsinfo/metadata.json b/apps/gpsinfo/metadata.json
index 60bd90c03..002febd86 100644
--- a/apps/gpsinfo/metadata.json
+++ b/apps/gpsinfo/metadata.json
@@ -5,7 +5,7 @@
"description": "An application that displays information about altitude, lat/lon, satellites and time",
"icon": "gps-info.png",
"type": "app",
- "tags": "gps",
+ "tags": "gps,outdoors",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"gpsinfo.app.js","url":"gps-info.js"},
diff --git a/apps/gpstrek/ChangeLog b/apps/gpstrek/ChangeLog
index d46ada767..c36c23c72 100644
--- a/apps/gpstrek/ChangeLog
+++ b/apps/gpstrek/ChangeLog
@@ -8,3 +8,8 @@
Fix widget adding listeners more than once
0.07: Show checkered flag for target markers
Single waypoints are now shown in the compass view
+0.08: Better handle state in widget
+ Slightly faster drawing by doing some caching
+ Reconstruct battery voltage by using calibrated batFullVoltage
+ Averaging for smoothing compass headings
+ Save state if route or waypoint has been chosen
diff --git a/apps/gpstrek/app.js b/apps/gpstrek/app.js
index f26811ed3..b3ec79fd2 100644
--- a/apps/gpstrek/app.js
+++ b/apps/gpstrek/app.js
@@ -1,48 +1,69 @@
+
+{ //run in own scope for fast switch
const STORAGE = require("Storage");
-const showWidgets = true;
-let numberOfSlices=4;
+const BAT_FULL = require("Storage").readJSON("setting.json").batFullVoltage || 0.3144;
+
+let init = function(){
+ global.screen = 1;
+ global.drawTimeout = undefined;
+ global.lastDrawnScreen = 0;
+ global.firstDraw = true;
+ global.slices = [];
+ global.maxScreens = 1;
+ global.scheduleDraw = false;
-if (showWidgets){
Bangle.loadWidgets();
-}
+ WIDGETS.gpstrek.start(false);
+ if (!WIDGETS.gpstrek.getState().numberOfSlices) WIDGETS.gpstrek.getState().numberOfSlices = 3;
+};
-let state = WIDGETS.gpstrek.getState();
-WIDGETS.gpstrek.start(false);
+let cleanup = function(){
+ if (global.drawTimeout) clearTimeout(global.drawTimeout);
+ delete global.screen;
+ delete global.drawTimeout;
+ delete global.lastDrawnScreen;
+ delete global.firstDraw;
+ delete global.slices;
+ delete global.maxScreens;
+};
-function parseNumber(toParse){
+init();
+scheduleDraw = true;
+
+let parseNumber = function(toParse){
if (toParse.includes(".")) return parseFloat(toParse);
return parseFloat("" + toParse + ".0");
-}
+};
-function parseWaypoint(filename, offset, result){
+let parseWaypoint = function(filename, offset, result){
result.lat = parseNumber(STORAGE.read(filename, offset, 11));
result.lon = parseNumber(STORAGE.read(filename, offset += 11, 12));
return offset + 12;
-}
+};
-function parseWaypointWithElevation(filename, offset, result){
+let parseWaypointWithElevation = function (filename, offset, result){
offset = parseWaypoint(filename, offset, result);
result.alt = parseNumber(STORAGE.read(filename, offset, 6));
return offset + 6;
-}
+};
-function parseWaypointWithName(filename, offset, result){
+let parseWaypointWithName = function(filename, offset, result){
offset = parseWaypoint(filename, offset, result);
return parseName(filename, offset, result);
-}
+};
-function parseName(filename, offset, result){
+let parseName = function(filename, offset, result){
let nameLength = STORAGE.read(filename, offset, 2) - 0;
result.name = STORAGE.read(filename, offset += 2, nameLength);
return offset + nameLength;
-}
+};
-function parseWaypointWithElevationAndName(filename, offset, result){
+let parseWaypointWithElevationAndName = function(filename, offset, result){
offset = parseWaypointWithElevation(filename, offset, result);
return parseName(filename, offset, result);
-}
+};
-function getEntry(filename, offset, result){
+let getEntry = function(filename, offset, result){
result.fileOffset = offset;
let type = STORAGE.read(filename, offset++, 1);
if (type == "") return -1;
@@ -68,12 +89,12 @@ function getEntry(filename, offset, result){
result.fileLength = offset - result.fileOffset;
//print(result);
return offset;
-}
+};
const labels = ["N","NE","E","SE","S","SW","W","NW"];
const loc = require("locale");
-function matchFontSize(graphics, text, height, width){
+let matchFontSize = function(graphics, text, height, width){
graphics.setFontVector(height);
let metrics;
let size = 1;
@@ -81,13 +102,19 @@ function matchFontSize(graphics, text, height, width){
size -= 0.05;
graphics.setFont("Vector",Math.floor(height*size));
}
-}
+};
-function getDoubleLineSlice(title1,title2,provider1,provider2,refreshTime){
+let getDoubleLineSlice = function(title1,title2,provider1,provider2,refreshTime){
let lastDrawn = Date.now() - Math.random()*refreshTime;
+ let lastValue1 = 0;
+ let lastValue2 = 0;
return {
refresh: function (){
- return Date.now() - lastDrawn > (Bangle.isLocked()?(refreshTime?refreshTime:5000):(refreshTime?refreshTime*2:10000));
+ let bigChange1 = (Math.abs(lastValue1 - provider1()) > 1);
+ let bigChange2 = (Math.abs(lastValue2 - provider2()) > 1);
+ let refresh = (Bangle.isLocked()?(refreshTime?refreshTime*5:10000):(refreshTime?refreshTime*2:1000));
+ let old = (Date.now() - lastDrawn) > refresh;
+ return (bigChange1 || bigChange2) && old;
},
draw: function (graphics, x, y, height, width){
lastDrawn = Date.now();
@@ -95,29 +122,29 @@ function getDoubleLineSlice(title1,title2,provider1,provider2,refreshTime){
if (typeof title2 == "function") title2 = title2();
graphics.clearRect(x,y,x+width,y+height);
- let value = provider1();
- matchFontSize(graphics, title1 + value, Math.floor(height*0.5), width);
+ lastValue1 = provider1();
+ matchFontSize(graphics, title1 + lastValue1, Math.floor(height*0.5), width);
graphics.setFontAlign(-1,-1);
graphics.drawString(title1, x+2, y);
graphics.setFontAlign(1,-1);
- graphics.drawString(value, x+width, y);
+ graphics.drawString(lastValue1, x+width, y);
- value = provider2();
- matchFontSize(graphics, title2 + value, Math.floor(height*0.5), width);
+ lastValue2 = provider2();
+ matchFontSize(graphics, title2 + lastValue2, Math.floor(height*0.5), width);
graphics.setFontAlign(-1,-1);
graphics.drawString(title2, x+2, y+(height*0.5));
graphics.setFontAlign(1,-1);
- graphics.drawString(value, x+width, y+(height*0.5));
+ graphics.drawString(lastValue2, x+width, y+(height*0.5));
}
};
-}
+};
-function getTargetSlice(targetDataSource){
+let getTargetSlice = function(targetDataSource){
let nameIndex = 0;
let lastDrawn = Date.now() - Math.random()*3000;
return {
refresh: function (){
- return Date.now() - lastDrawn > (Bangle.isLocked()?10000:3000);
+ return Date.now() - lastDrawn > (Bangle.isLocked()?3000:10000);
},
draw: function (graphics, x, y, height, width){
lastDrawn = Date.now();
@@ -174,9 +201,9 @@ function getTargetSlice(targetDataSource){
}
}
};
-}
+};
-function drawCompass(graphics, x, y, height, width, increment, start){
+let drawCompass = function(graphics, x, y, height, width, increment, start){
graphics.setFont12x20();
graphics.setFontAlign(0,-1);
graphics.setColor(graphics.theme.fg);
@@ -197,14 +224,19 @@ function drawCompass(graphics, x, y, height, width, increment, start){
xpos+=increment*15;
if (xpos > width + 20) break;
}
-}
+};
-function getCompassSlice(compassDataSource){
+let getCompassSlice = function(compassDataSource){
let lastDrawn = Date.now() - Math.random()*2000;
+ let lastDrawnValue = 0;
const buffers = 4;
let buf = [];
return {
- refresh : function (){return Bangle.isLocked()?(Date.now() - lastDrawn > 2000):true;},
+ refresh : function (){
+ let bigChange = (Math.abs(lastDrawnValue - compassDataSource.getCourse()) > 2);
+ let old = (Bangle.isLocked()?(Date.now() - lastDrawn > 2000):true);
+ return bigChange && old;
+ },
draw: function (graphics, x,y,height,width){
lastDrawn = Date.now();
const max = 180;
@@ -212,12 +244,14 @@ function getCompassSlice(compassDataSource){
graphics.clearRect(x,y,x+width,y+height);
- var start = compassDataSource.getCourse() - 90;
- if (isNaN(compassDataSource.getCourse())) start = -90;
+ lastDrawnValue = compassDataSource.getCourse();
+
+ var start = lastDrawnValue - 90;
+ if (isNaN(lastDrawnValue)) start = -90;
if (start<0) start+=360;
start = start % 360;
- if (state.acc && compassDataSource.getCourseType() == "MAG"){
+ if (WIDGETS.gpstrek.getState().acc && compassDataSource.getCourseType() == "MAG"){
drawCompass(graphics,0,y+width*0.05,height-width*0.05,width,increment,start);
} else {
drawCompass(graphics,0,y,height,width,increment,start);
@@ -226,7 +260,8 @@ function getCompassSlice(compassDataSource){
if (compassDataSource.getPoints){
for (let p of compassDataSource.getPoints()){
- var bpos = p.bearing - compassDataSource.getCourse();
+ g.reset();
+ var bpos = p.bearing - lastDrawnValue;
if (bpos>180) bpos -=360;
if (bpos<-180) bpos +=360;
bpos+=120;
@@ -251,6 +286,7 @@ function getCompassSlice(compassDataSource){
}
if (compassDataSource.getMarkers){
for (let m of compassDataSource.getMarkers()){
+ g.reset();
g.setColor(m.fillcolor);
let mpos = m.xpos * width;
if (m.xpos < 0.05) mpos = Math.floor(width*0.05);
@@ -263,9 +299,9 @@ function getCompassSlice(compassDataSource){
graphics.setColor(g.theme.fg);
graphics.fillRect(x,y,Math.floor(width*0.05),y+height);
graphics.fillRect(Math.ceil(width*0.95),y,width,y+height);
- if (state.acc && compassDataSource.getCourseType() == "MAG") {
- let xh = E.clip(width*0.5-height/2+(((state.acc.x+1)/2)*height),width*0.5 - height/2, width*0.5 + height/2);
- let yh = E.clip(y+(((state.acc.y+1)/2)*height),y,y+height);
+ if (WIDGETS.gpstrek.getState().acc && compassDataSource.getCourseType() == "MAG") {
+ let xh = E.clip(width*0.5-height/2+(((WIDGETS.gpstrek.getState().acc.x+1)/2)*height),width*0.5 - height/2, width*0.5 + height/2);
+ let yh = E.clip(y+(((WIDGETS.gpstrek.getState().acc.y+1)/2)*height),y,y+height);
graphics.fillRect(width*0.5 - height/2, y, width*0.5 + height/2, y + Math.floor(width*0.05));
@@ -287,44 +323,48 @@ function getCompassSlice(compassDataSource){
graphics.drawRect(Math.floor(width*0.05),y,Math.ceil(width*0.95),y+height);
}
};
-}
+};
-function radians(a) {
+let radians = function(a) {
return a*Math.PI/180;
-}
+};
-function degrees(a) {
- var d = a*180/Math.PI;
+let degrees = function(a) {
+ let d = a*180/Math.PI;
return (d+360)%360;
-}
+};
-function bearing(a,b){
+let bearing = function(a,b){
if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity;
- var delta = radians(b.lon-a.lon);
- var alat = radians(a.lat);
- var blat = radians(b.lat);
- var y = Math.sin(delta) * Math.cos(blat);
- var x = Math.cos(alat)*Math.sin(blat) -
+ let delta = radians(b.lon-a.lon);
+ let alat = radians(a.lat);
+ let blat = radians(b.lat);
+ let y = Math.sin(delta) * Math.cos(blat);
+ let x = Math.cos(alat)*Math.sin(blat) -
Math.sin(alat)*Math.cos(blat)*Math.cos(delta);
return Math.round(degrees(Math.atan2(y, x)));
-}
+};
-function distance(a,b){
+let distance = function(a,b){
if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity;
- var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
- var y = radians(b.lat-a.lat);
+ let x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
+ let y = radians(b.lat-a.lat);
return Math.round(Math.sqrt(x*x + y*y) * 6371000);
-}
+};
-function triangle (x, y, width, height){
+let getAveragedCompass = function(){
+ return Math.round(WIDGETS.gpstrek.getState().avgComp);
+};
+
+let triangle = function(x, y, width, height){
return [
Math.round(x),Math.round(y),
Math.round(x+width * 0.5), Math.round(y+height),
Math.round(x-width * 0.5), Math.round(y+height)
];
-}
+};
-function onSwipe(dir){
+let onSwipe = function(dir){
if (dir < 0) {
nextScreen();
} else if (dir > 0) {
@@ -332,9 +372,9 @@ function onSwipe(dir){
} else {
nextScreen();
}
-}
+};
-function setButtons(){
+let setButtons = function(){
let options = {
mode: "custom",
swipe: onSwipe,
@@ -342,9 +382,9 @@ function setButtons(){
touch: nextScreen
};
Bangle.setUI(options);
-}
+};
-function getApproxFileSize(name){
+let getApproxFileSize = function(name){
let currentStart = STORAGE.getStats().totalBytes;
let currentSize = 0;
for (let i = currentStart; i > 500; i/=2){
@@ -358,9 +398,9 @@ function getApproxFileSize(name){
currentSize += currentDiff;
}
return currentSize;
-}
+};
-function parseRouteData(filename, progressMonitor){
+let parseRouteData = function(filename, progressMonitor){
let routeInfo = {};
routeInfo.filename = filename;
@@ -406,40 +446,40 @@ function parseRouteData(filename, progressMonitor){
set(routeInfo, 0);
return routeInfo;
-}
+};
-function hasPrev(route){
+let hasPrev = function(route){
if (route.mirror) return route.index < (route.count - 1);
return route.index > 0;
-}
+};
-function hasNext(route){
+let hasNext = function(route){
if (route.mirror) return route.index > 0;
return route.index < (route.count - 1);
-}
+};
-function next(route){
+let next = function(route){
if (!hasNext(route)) return;
if (route.mirror) set(route, --route.index);
if (!route.mirror) set(route, ++route.index);
-}
+};
-function set(route, index){
+let set = function(route, index){
route.currentWaypoint = {};
route.index = index;
getEntry(route.filename, route.refs[index], route.currentWaypoint);
-}
+};
-function prev(route){
+let prev = function(route){
if (!hasPrev(route)) return;
if (route.mirror) set(route, ++route.index);
if (!route.mirror) set(route, --route.index);
-}
+};
let lastMirror;
let cachedLast;
-function getLast(route){
+let getLast = function(route){
let wp = {};
if (lastMirror != route.mirror){
if (route.mirror) getEntry(route.filename, route.refs[0], wp);
@@ -448,14 +488,14 @@ function getLast(route){
cachedLast = wp;
}
return cachedLast;
-}
+};
-function removeMenu(){
+let removeMenu = function(){
E.showMenu();
switchNav();
-}
+};
-function showProgress(progress, title, max){
+let showProgress = function(progress, title, max){
//print("Progress",progress,max)
let message = title? title: "Loading";
if (max){
@@ -466,17 +506,17 @@ function showProgress(progress, title, max){
for (let i = dots; i < 4; i++) message += " ";
}
E.showMessage(message);
-}
+};
-function handleLoading(c){
+let handleLoading = function(c){
E.showMenu();
- state.route = parseRouteData(c, showProgress);
- state.waypoint = null;
+ WIDGETS.gpstrek.getState().route = parseRouteData(c, showProgress);
+ WIDGETS.gpstrek.getState().waypoint = null;
+ WIDGETS.gpstrek.getState().route.mirror = false;
removeMenu();
- state.route.mirror = false;
-}
+};
-function showRouteSelector (){
+let showRouteSelector = function(){
var menu = {
"" : {
back : showRouteMenu,
@@ -488,9 +528,9 @@ function showRouteSelector (){
});
E.showMenu(menu);
-}
+};
-function showRouteMenu(){
+let showRouteMenu = function(){
var menu = {
"" : {
"title" : "Route",
@@ -499,48 +539,48 @@ function showRouteMenu(){
"Select file" : showRouteSelector
};
- if (state.route){
+ if (WIDGETS.gpstrek.getState().route){
menu.Mirror = {
- value: state && state.route && !!state.route.mirror || false,
+ value: WIDGETS.gpstrek.getState() && WIDGETS.gpstrek.getState().route && !!WIDGETS.gpstrek.getState().route.mirror || false,
onchange: v=>{
- state.route.mirror = v;
+ WIDGETS.gpstrek.getState().route.mirror = v;
}
};
menu['Select closest waypoint'] = function () {
- if (state.currentPos && state.currentPos.lat){
- setClosestWaypoint(state.route, null, showProgress); removeMenu();
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){
+ setClosestWaypoint(WIDGETS.gpstrek.getState().route, null, showProgress); removeMenu();
} else {
E.showAlert("No position").then(()=>{E.showMenu(menu);});
}
};
menu['Select closest waypoint (not visited)'] = function () {
- if (state.currentPos && state.currentPos.lat){
- setClosestWaypoint(state.route, state.route.index, showProgress); removeMenu();
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){
+ setClosestWaypoint(WIDGETS.gpstrek.getState().route, WIDGETS.gpstrek.getState().route.index, showProgress); removeMenu();
} else {
E.showAlert("No position").then(()=>{E.showMenu(menu);});
}
};
menu['Select waypoint'] = {
- value : state.route.index,
- min:1,max:state.route.count,step:1,
- onchange : v => { set(state.route, v-1); }
+ value : WIDGETS.gpstrek.getState().route.index,
+ min:1,max:WIDGETS.gpstrek.getState().route.count,step:1,
+ onchange : v => { set(WIDGETS.gpstrek.getState().route, v-1); }
};
menu['Select waypoint as current position'] = function (){
- state.currentPos.lat = state.route.currentWaypoint.lat;
- state.currentPos.lon = state.route.currentWaypoint.lon;
- state.currentPos.alt = state.route.currentWaypoint.alt;
+ WIDGETS.gpstrek.getState().currentPos.lat = WIDGETS.gpstrek.getState().route.currentWaypoint.lat;
+ WIDGETS.gpstrek.getState().currentPos.lon = WIDGETS.gpstrek.getState().route.currentWaypoint.lon;
+ WIDGETS.gpstrek.getState().currentPos.alt = WIDGETS.gpstrek.getState().route.currentWaypoint.alt;
removeMenu();
};
}
- if (state.route && hasPrev(state.route))
- menu['Previous waypoint'] = function() { prev(state.route); removeMenu(); };
- if (state.route && hasNext(state.route))
- menu['Next waypoint'] = function() { next(state.route); removeMenu(); };
+ if (WIDGETS.gpstrek.getState().route && hasPrev(WIDGETS.gpstrek.getState().route))
+ menu['Previous waypoint'] = function() { prev(WIDGETS.gpstrek.getState().route); removeMenu(); };
+ if (WIDGETS.gpstrek.getState().route && hasNext(WIDGETS.gpstrek.getState().route))
+ menu['Next waypoint'] = function() { next(WIDGETS.gpstrek.getState().route); removeMenu(); };
E.showMenu(menu);
-}
+};
-function showWaypointSelector(){
+let showWaypointSelector = function(){
let waypoints = require("waypoints").load();
var menu = {
"" : {
@@ -550,41 +590,41 @@ function showWaypointSelector(){
waypoints.forEach((wp,c)=>{
menu[waypoints[c].name] = function (){
- state.waypoint = waypoints[c];
- state.waypointIndex = c;
- state.route = null;
+ WIDGETS.gpstrek.getState().waypoint = waypoints[c];
+ WIDGETS.gpstrek.getState().waypointIndex = c;
+ WIDGETS.gpstrek.getState().route = null;
removeMenu();
};
});
E.showMenu(menu);
-}
+};
-function showCalibrationMenu(){
+let showCalibrationMenu = function(){
let menu = {
"" : {
"title" : "Calibration",
back : showMenu,
},
"Barometer (GPS)" : ()=>{
- if (!state.currentPos || isNaN(state.currentPos.alt)){
+ if (!WIDGETS.gpstrek.getState().currentPos || isNaN(WIDGETS.gpstrek.getState().currentPos.alt)){
E.showAlert("No GPS altitude").then(()=>{E.showMenu(menu);});
} else {
- state.calibAltDiff = state.altitude - state.currentPos.alt;
- E.showAlert("Calibrated Altitude Difference: " + state.calibAltDiff.toFixed(0)).then(()=>{removeMenu();});
+ WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - WIDGETS.gpstrek.getState().currentPos.alt;
+ E.showAlert("Calibrated Altitude Difference: " + WIDGETS.gpstrek.getState().calibAltDiff.toFixed(0)).then(()=>{removeMenu();});
}
},
"Barometer (Manual)" : {
- value : Math.round(state.currentPos && (state.currentPos.alt != undefined && !isNaN(state.currentPos.alt)) ? state.currentPos.alt: state.altitude),
+ value : Math.round(WIDGETS.gpstrek.getState().currentPos && (WIDGETS.gpstrek.getState().currentPos.alt != undefined && !isNaN(WIDGETS.gpstrek.getState().currentPos.alt)) ? WIDGETS.gpstrek.getState().currentPos.alt: WIDGETS.gpstrek.getState().altitude),
min:-2000,max: 10000,step:1,
- onchange : v => { state.calibAltDiff = state.altitude - v; }
+ onchange : v => { WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - v; }
},
"Reset Compass" : ()=>{ Bangle.resetCompass(); removeMenu();},
};
E.showMenu(menu);
-}
+};
-function showWaypointMenu(){
+let showWaypointMenu = function(){
let menu = {
"" : {
"title" : "Waypoint",
@@ -593,21 +633,21 @@ function showWaypointMenu(){
"Select waypoint" : showWaypointSelector,
};
E.showMenu(menu);
-}
+};
-function showBackgroundMenu(){
+let showBackgroundMenu = function(){
let menu = {
"" : {
"title" : "Background",
back : showMenu,
},
- "Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});},
- "Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});},
+ "Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{showMenu();});},
+ "Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{showMenu();});},
};
E.showMenu(menu);
-}
+};
-function showMenu(){
+let showMenu = function(){
var mainmenu = {
"" : {
"title" : "Main",
@@ -617,50 +657,55 @@ function showMenu(){
"Waypoint" : showWaypointMenu,
"Background" : showBackgroundMenu,
"Calibration": showCalibrationMenu,
- "Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}});},
+ "Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}}).catch(()=>{E.showMenu(mainmenu);});},
"Info rows" : {
- value : numberOfSlices,
+ value : WIDGETS.gpstrek.getState().numberOfSlices,
min:1,max:6,step:1,
- onchange : v => { setNumberOfSlices(v); }
+ onchange : v => { WIDGETS.gpstrek.getState().numberOfSlices = v; }
},
};
E.showMenu(mainmenu);
-}
+};
-let scheduleDraw = true;
-function switchMenu(){
- screen = 0;
- scheduleDraw = false;
- showMenu();
-}
+let switchMenu = function(){
+ stopDrawing();
+ showMenu();
+};
-function drawInTimeout(){
- setTimeout(()=>{
+let stopDrawing = function(){
+ if (drawTimeout) clearTimeout(drawTimeout);
+ scheduleDraw = false;
+};
+
+let drawInTimeout = function(){
+ if (global.drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(()=>{
+ drawTimeout = undefined;
draw();
- if (scheduleDraw)
- setTimeout(drawInTimeout, 0);
- },0);
-}
+ },50);
+};
-function switchNav(){
+let switchNav = function(){
if (!screen) screen = 1;
setButtons();
scheduleDraw = true;
+ firstDraw = true;
drawInTimeout();
-}
+};
-function nextScreen(){
+let nextScreen = function(){
screen++;
if (screen > maxScreens){
screen = 1;
}
-}
+ drawInTimeout();
+};
-function setClosestWaypoint(route, startindex, progress){
- if (startindex >= state.route.count) startindex = state.route.count - 1;
- if (!state.currentPos.lat){
+let setClosestWaypoint = function(route, startindex, progress){
+ if (startindex >= WIDGETS.gpstrek.getState().route.count) startindex = WIDGETS.gpstrek.getState().route.count - 1;
+ if (!WIDGETS.gpstrek.getState().currentPos.lat){
set(route, startindex);
return;
}
@@ -670,7 +715,7 @@ function setClosestWaypoint(route, startindex, progress){
if (progress && (i % 5 == 0)) progress(i-(startindex?startindex:0), "Searching", route.count);
let wp = {};
getEntry(route.filename, route.refs[i], wp);
- let curDist = distance(state.currentPos, wp);
+ let curDist = distance(WIDGETS.gpstrek.getState().currentPos, wp);
if (curDist < minDist){
minDist = curDist;
minIndex = i;
@@ -679,30 +724,28 @@ function setClosestWaypoint(route, startindex, progress){
}
}
set(route, minIndex);
-}
-
-let screen = 1;
+};
const finishIcon = atob("CggB//meZmeZ+Z5n/w==");
const compassSliceData = {
getCourseType: function(){
- return (state.currentPos && state.currentPos.course) ? "GPS" : "MAG";
+ return (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.course) ? "GPS" : "MAG";
},
getCourse: function (){
- if(compassSliceData.getCourseType() == "GPS") return state.currentPos.course;
- return state.compassHeading?state.compassHeading:undefined;
+ if(compassSliceData.getCourseType() == "GPS") return WIDGETS.gpstrek.getState().currentPos.course;
+ return getAveragedCompass();
},
getPoints: function (){
let points = [];
- if (state.currentPos && state.currentPos.lon && state.route && state.route.currentWaypoint){
- points.push({bearing:bearing(state.currentPos, state.route.currentWaypoint), color:"#0f0"});
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.currentWaypoint){
+ points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().route.currentWaypoint), color:"#0f0"});
}
- if (state.currentPos && state.currentPos.lon && state.route){
- points.push({bearing:bearing(state.currentPos, getLast(state.route)), icon: finishIcon});
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route){
+ points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, getLast(WIDGETS.gpstrek.getState().route)), icon: finishIcon});
}
- if (state.currentPos && state.currentPos.lon && state.waypoint){
- points.push({bearing:bearing(state.currentPos, state.waypoint), icon: finishIcon});
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().waypoint){
+ points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().waypoint), icon: finishIcon});
}
return points;
},
@@ -714,79 +757,74 @@ const compassSliceData = {
const waypointData = {
icon: atob("EBCBAAAAAAAAAAAAcIB+zg/uAe4AwACAAAAAAAAAAAAAAAAA"),
getProgress: function() {
- return (state.route.index + 1) + "/" + state.route.count;
+ return (WIDGETS.gpstrek.getState().route.index + 1) + "/" + WIDGETS.gpstrek.getState().route.count;
},
getTarget: function (){
- if (distance(state.currentPos,state.route.currentWaypoint) < 30 && hasNext(state.route)){
- next(state.route);
+ if (distance(WIDGETS.gpstrek.getState().currentPos,WIDGETS.gpstrek.getState().route.currentWaypoint) < 30 && hasNext(WIDGETS.gpstrek.getState().route)){
+ next(WIDGETS.gpstrek.getState().route);
Bangle.buzz(1000);
}
- return state.route.currentWaypoint;
+ return WIDGETS.gpstrek.getState().route.currentWaypoint;
},
getStart: function (){
- return state.currentPos;
+ return WIDGETS.gpstrek.getState().currentPos;
}
};
const finishData = {
icon: atob("EBABAAA/4DmgJmAmYDmgOaAmYD/gMAAwADAAMAAwAAAAAAA="),
getTarget: function (){
- if (state.route) return getLast(state.route);
- if (state.waypoint) return state.waypoint;
+ if (WIDGETS.gpstrek.getState().route) return getLast(WIDGETS.gpstrek.getState().route);
+ if (WIDGETS.gpstrek.getState().waypoint) return WIDGETS.gpstrek.getState().waypoint;
},
getStart: function (){
- return state.currentPos;
+ return WIDGETS.gpstrek.getState().currentPos;
}
};
-let sliceHeight;
-function setNumberOfSlices(number){
- numberOfSlices = number;
- sliceHeight = Math.floor((g.getHeight()-(showWidgets?24:0))/numberOfSlices);
-}
-
-let slices = [];
-let maxScreens = 1;
-setNumberOfSlices(3);
+let getSliceHeight = function(number){
+ return Math.floor(Bangle.appRect.h/WIDGETS.gpstrek.getState().numberOfSlices);
+};
let compassSlice = getCompassSlice(compassSliceData);
let waypointSlice = getTargetSlice(waypointData);
let finishSlice = getTargetSlice(finishData);
let eleSlice = getDoubleLineSlice("Up","Down",()=>{
- return loc.distance(state.up,3) + "/" + (state.route ? loc.distance(state.route.up,3):"---");
+ return loc.distance(WIDGETS.gpstrek.getState().up,3) + "/" + (WIDGETS.gpstrek.getState().route ? loc.distance(WIDGETS.gpstrek.getState().route.up,3):"---");
},()=>{
- return loc.distance(state.down,3) + "/" + (state.route ? loc.distance(state.route.down,3): "---");
+ return loc.distance(WIDGETS.gpstrek.getState().down,3) + "/" + (WIDGETS.gpstrek.getState().route ? loc.distance(WIDGETS.gpstrek.getState().route.down,3): "---");
});
let statusSlice = getDoubleLineSlice("Speed","Alt",()=>{
let speed = 0;
- if (state.currentPos && state.currentPos.speed) speed = state.currentPos.speed;
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.speed) speed = WIDGETS.gpstrek.getState().currentPos.speed;
return loc.speed(speed,2);
},()=>{
let alt = Infinity;
- if (!isNaN(state.altitude)){
- alt = isNaN(state.calibAltDiff) ? state.altitude : (state.altitude - state.calibAltDiff);
+ if (!isNaN(WIDGETS.gpstrek.getState().altitude)){
+ alt = isNaN(WIDGETS.gpstrek.getState().calibAltDiff) ? WIDGETS.gpstrek.getState().altitude : (WIDGETS.gpstrek.getState().altitude - WIDGETS.gpstrek.getState().calibAltDiff);
}
- if (state.currentPos && state.currentPos.alt) alt = state.currentPos.alt;
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.alt) alt = WIDGETS.gpstrek.getState().currentPos.alt;
+ if (isNaN(alt)) return "---";
return loc.distance(alt,3);
});
let status2Slice = getDoubleLineSlice("Compass","GPS",()=>{
- return (state.compassHeading?Math.round(state.compassHeading):"---") + "°";
+ return getAveragedCompass() + "°";
},()=>{
let course = "---°";
- if (state.currentPos && state.currentPos.course) course = state.currentPos.course + "°";
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.course) course = WIDGETS.gpstrek.getState().currentPos.course + "°";
return course;
},200);
let healthSlice = getDoubleLineSlice("Heart","Steps",()=>{
- return state.bpm;
+ return WIDGETS.gpstrek.getState().bpm || "---";
},()=>{
- return state.steps;
+ return !isNaN(WIDGETS.gpstrek.getState().steps)? WIDGETS.gpstrek.getState().steps: "---";
});
let system2Slice = getDoubleLineSlice("Bat","",()=>{
- return (Bangle.isCharging()?"+":"") + E.getBattery().toFixed(0)+"% " + NRF.getBattery().toFixed(2) + "V";
+ return (Bangle.isCharging()?"+":"") + E.getBattery().toFixed(0)+"% " + (analogRead(D3)*4.2/BAT_FULL).toFixed(2) + "V";
},()=>{
return "";
});
@@ -798,17 +836,17 @@ let systemSlice = getDoubleLineSlice("RAM","Storage",()=>{
return (STORAGE.getFree()/1024).toFixed(0)+"kB";
});
-function updateSlices(){
+let updateSlices = function(){
slices = [];
slices.push(compassSlice);
- if (state.currentPos && state.currentPos.lat && state.route && state.route.currentWaypoint && state.route.index < state.route.count - 1) {
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat && WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.currentWaypoint && WIDGETS.gpstrek.getState().route.index < WIDGETS.gpstrek.getState().route.count - 1) {
slices.push(waypointSlice);
}
- if (state.currentPos && state.currentPos.lat && (state.route || state.waypoint)) {
+ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat && (WIDGETS.gpstrek.getState().route || WIDGETS.gpstrek.getState().waypoint)) {
slices.push(finishSlice);
}
- if ((state.route && state.route.down !== undefined) || state.down != undefined) {
+ if ((WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.down !== undefined) || WIDGETS.gpstrek.getState().down != undefined) {
slices.push(eleSlice);
}
slices.push(statusSlice);
@@ -816,42 +854,44 @@ function updateSlices(){
slices.push(healthSlice);
slices.push(systemSlice);
slices.push(system2Slice);
- maxScreens = Math.ceil(slices.length/numberOfSlices);
-}
+ maxScreens = Math.ceil(slices.length/WIDGETS.gpstrek.getState().numberOfSlices);
+};
-function clear() {
- g.clearRect(0,(showWidgets ? 24 : 0), g.getWidth(),g.getHeight());
-}
-let lastDrawnScreen;
-let firstDraw = true;
+let clear = function() {
+ g.clearRect(Bangle.appRect);
+};
-function draw(){
- if (!screen) return;
- let ypos = showWidgets ? 24 : 0;
+let draw = function(){
+ if (!global.screen) return;
+ let ypos = Bangle.appRect.y;
- let firstSlice = (screen-1)*numberOfSlices;
+ let firstSlice = (screen-1)*WIDGETS.gpstrek.getState().numberOfSlices;
updateSlices();
let force = lastDrawnScreen != screen || firstDraw;
if (force){
clear();
- if (showWidgets){
- Bangle.drawWidgets();
- }
}
+ if (firstDraw) Bangle.drawWidgets();
lastDrawnScreen = screen;
- for (let slice of slices.slice(firstSlice,firstSlice + numberOfSlices)) {
+ let sliceHeight = getSliceHeight();
+ for (let slice of slices.slice(firstSlice,firstSlice + WIDGETS.gpstrek.getState().numberOfSlices)) {
g.reset();
if (!slice.refresh || slice.refresh() || force) slice.draw(g,0,ypos,sliceHeight,g.getWidth());
ypos += sliceHeight+1;
g.drawLine(0,ypos-1,g.getWidth(),ypos-1);
}
+
+ if (scheduleDraw){
+ drawInTimeout();
+ }
firstDraw = false;
-}
+};
switchNav();
-g.clear();
+clear();
+}
diff --git a/apps/gpstrek/metadata.json b/apps/gpstrek/metadata.json
index cf5d06baa..3e27a3247 100644
--- a/apps/gpstrek/metadata.json
+++ b/apps/gpstrek/metadata.json
@@ -1,7 +1,7 @@
{
"id": "gpstrek",
"name": "GPS Trekking",
- "version": "0.07",
+ "version": "0.08",
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
"icon": "icon.png",
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
diff --git a/apps/gpstrek/widget.js b/apps/gpstrek/widget.js
index 347df2df5..363ade8ee 100644
--- a/apps/gpstrek/widget.js
+++ b/apps/gpstrek/widget.js
@@ -1,6 +1,28 @@
(() => {
+const SAMPLES=5;
+function initState(){
+ //cleanup volatile state here
+ state = {};
+ state.compassSamples = new Array(SAMPLES).fill(0);
+ state.lastSample = 0;
+ state.sampleIndex = 0;
+ state.currentPos={};
+ state.steps = 0;
+ state.calibAltDiff = 0;
+ state.numberOfSlices = 3;
+ state.steps = 0;
+ state.up = 0;
+ state.down = 0;
+ state.saved = 0;
+ state.avgComp = 0;
+}
+
const STORAGE=require('Storage');
-let state = STORAGE.readJSON("gpstrek.state.json")||{};
+let state = STORAGE.readJSON("gpstrek.state.json");
+if (!state) {
+ state = {};
+ initState();
+}
let bgChanged = false;
function saveState(){
@@ -8,12 +30,13 @@ function saveState(){
STORAGE.writeJSON("gpstrek.state.json", state);
}
-E.on("kill",()=>{
- if (bgChanged){
+function onKill(){
+ if (bgChanged || state.route || state.waypoint){
saveState();
}
-});
+}
+E.on("kill", onKill);
function onPulse(e){
state.bpm = e.bpm;
@@ -23,27 +46,47 @@ function onGPS(fix) {
if(fix.fix) state.currentPos = fix;
}
-function onMag(e) {
- if (!state.compassHeading) state.compassHeading = e.heading;
+let radians = function(a) {
+ return a*Math.PI/180;
+};
- //if (a+180)mod 360 == b then
- //return (a+b)/2 mod 360 and ((a+b)/2 mod 360) + 180 (they are both the solution, so you may choose one depending if you prefer counterclockwise or clockwise direction)
-//else
- //return arctan( (sin(a)+sin(b)) / (cos(a)+cos(b) )
+let degrees = function(a) {
+ let d = a*180/Math.PI;
+ return (d+360)%360;
+};
- /*
- let average;
- let a = radians(compassHeading);
- let b = radians(e.heading);
- if ((a+180) % 360 == b){
- average = ((a+b)/2 % 360); //can add 180 depending on rotation
- } else {
- average = Math.atan( (Math.sin(a)+Math.sin(b))/(Math.cos(a)+Math.cos(b)) );
+function average(samples){
+ let s = 0;
+ let c = 0;
+ for (let h of samples){
+ s += Math.sin(radians(h));
+ c += Math.cos(radians(h));
+ }
+ s /= samples.length;
+ c /= samples.length;
+ let result = degrees(Math.atan(s/c));
+
+ if (c < 0) result += 180;
+ if (s < 0 && c > 0) result += 360;
+
+ result%=360;
+ return result;
+}
+
+function onMag(e) {
+ if (!isNaN(e.heading)){
+ if (Bangle.isLocked() || (Bangle.getGPSFix() && Bangle.getGPSFix().lon))
+ state.avgComp = e.heading;
+ else {
+ state.compassSamples[state.sampleIndex++] = e.heading;
+ state.lastSample = Date.now();
+ if (state.sampleIndex > SAMPLES - 1){
+ state.sampleIndex = 0;
+ let avg = average(state.compassSamples);
+ state.avgComp = average([state.avgComp,avg]);
+ }
+ }
}
- print("Angle",compassHeading,e.heading, average);
- compassHeading = (compassHeading + degrees(average)) % 360;
- */
- state.compassHeading = Math.round(e.heading);
}
function onStep(e) {
@@ -73,6 +116,16 @@ function onAcc (e){
state.acc = e;
}
+function update(){
+ if (state.active){
+ start(false);
+ }
+ if (state.active == !(WIDGETS.gpstrek.width)) {
+ if(WIDGETS.gpstrek) WIDGETS.gpstrek.width = state.active?24:0;
+ Bangle.drawWidgets();
+ }
+}
+
function start(bg){
Bangle.removeListener('GPS', onGPS);
Bangle.removeListener("HRM", onPulse);
@@ -94,9 +147,9 @@ function start(bg){
if (bg){
if (!state.active) bgChanged = true;
state.active = true;
+ update();
saveState();
}
- Bangle.drawWidgets();
}
function stop(bg){
@@ -114,22 +167,10 @@ function stop(bg){
Bangle.removeListener("step", onStep);
Bangle.removeListener("pressure", onPressure);
Bangle.removeListener('accel', onAcc);
+ E.removeListener("kill", onKill);
}
+ update();
saveState();
- Bangle.drawWidgets();
-}
-
-function initState(){
- //cleanup volatile state here
- state.currentPos={};
- state.steps = Bangle.getStepCount();
- state.calibAltDiff = 0;
- state.up = 0;
- state.down = 0;
-}
-
-if (state.saved && state.saved < Date.now() - 60000){
- initState();
}
if (state.active){
@@ -141,11 +182,15 @@ WIDGETS["gpstrek"]={
width:state.active?24:0,
resetState: initState,
getState: function() {
+ if (state.saved && Date.now() - state.saved > 60000 || !state){
+ initState();
+ }
return state;
},
start:start,
stop:stop,
draw:function() {
+ update();
if (state.active){
g.reset();
g.drawImage(atob("GBiBAAAAAAAAAAAYAAAYAAAYAAA8AAA8AAB+AAB+AADbAADbAAGZgAGZgAMYwAMYwAcY4AYYYA5+cA3/sB/D+B4AeBAACAAAAAAAAA=="), this.x, this.y);
diff --git a/apps/ha/metadata.json b/apps/ha/metadata.json
index 052e82fe0..fad052544 100644
--- a/apps/ha/metadata.json
+++ b/apps/ha/metadata.json
@@ -5,7 +5,7 @@
"description": "Integrates your BangleJS into HomeAssistant.",
"icon": "ha.png",
"type": "app",
- "tags": "tool",
+ "tags": "tool,clkinfo",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"custom": "custom.html",
diff --git a/apps/hworldclock/ChangeLog b/apps/hworldclock/ChangeLog
index a4bd84390..8c1517842 100644
--- a/apps/hworldclock/ChangeLog
+++ b/apps/hworldclock/ChangeLog
@@ -7,3 +7,5 @@
0.21: Add Settings
0.22: Use default Bangle formatter for booleans
0.23: Added note to configure position in "my location" if not done yet. Small fixes.
+0.24: Added fast load
+0.25: Minor code optimization
diff --git a/apps/hworldclock/app.js b/apps/hworldclock/app.js
index a0fb4cd20..c80b712da 100644
--- a/apps/hworldclock/app.js
+++ b/apps/hworldclock/app.js
@@ -1,3 +1,5 @@
+{ // must be inside our own scope here so that when we are unloaded everything disappears
+
// ------- Settings file
const SETTINGSFILE = "hworldclock.json";
var secondsMode;
@@ -153,15 +155,15 @@ function updatePos() {
function drawSeconds() {
// get date
- var d = new Date();
- var da = d.toString().split(" ");
+ let d = new Date();
+ let da = d.toString().split(" ");
// default draw styles
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
// draw time
- var time = da[4].split(":");
- var seconds = time[2];
+ let time = da[4].split(":");
+ let seconds = time[2];
g.setFont("5x9Numeric7Seg",primaryTimeFontSize - 3);
if (g.theme.dark) {
@@ -184,15 +186,15 @@ function drawSeconds() {
function draw() {
// get date
- var d = new Date();
- var da = d.toString().split(" ");
+ let d = new Date();
+ let da = d.toString().split(" ");
// default draw styles
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
// draw time
- var time = da[4].split(":");
- var hours = time[0],
+ let time = da[4].split(":");
+ let hours = time[0],
minutes = time[1];
@@ -223,7 +225,7 @@ function draw() {
// am / PM ?
if (_12hour){
//do 12 hour stuff
- //var ampm = require("locale").medidian(new Date()); Not working
+ //let ampm = require("locale").medidian(new Date()); Not working
g.setFont("Vector", 17);
g.drawString(ampm, xyCenterSeconds, yAmPm, true);
}
@@ -232,14 +234,14 @@ function draw() {
// draw Day, name of month, Date
//DATE
- var localDate = require("locale").date(new Date(), 1);
+ let localDate = require("locale").date(new Date(), 1);
localDate = localDate.substring(0, localDate.length - 5);
g.setFont("Vector", 17);
g.drawString(require("locale").dow(new Date(), 1).toUpperCase() + ", " + localDate, xyCenter, yposDate, true);
g.setFont(font, primaryDateFontSize);
// set gmt to UTC+0
- var gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
+ let gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
// Loop through offset(s) and render
offsets.forEach((offset, index) => {
@@ -249,7 +251,7 @@ function draw() {
if (offsets.length === 1) {
- var date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
+ let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
// For a single secondary timezone, draw it bigger and drop time zone to second line
const xOffset = 30;
g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
@@ -295,8 +297,18 @@ g.clear();
// Init the settings of the app
loadMySettings();
-// Show launcher when button pressed
-Bangle.setUI("clock");
+// Show launcher when middle button pressed
+Bangle.setUI({
+ mode : "clock",
+ remove : function() {
+ // Called to unload all of the clock app
+ if (PosInterval) clearInterval(PosInterval);
+ PosInterval = undefined;
+ if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
+ drawTimeoutSeconds = undefined;
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }});
Bangle.loadWidgets();
Bangle.drawWidgets();
@@ -307,7 +319,7 @@ draw();
if (!Bangle.isLocked()) { // Initial state
if (showSunInfo) {
- if (PosInterval != 0) clearInterval(PosInterval);
+ if (PosInterval != 0 && typeof PosInterval != 'undefined') clearInterval(PosInterval);
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
updatePos();
}
@@ -333,7 +345,7 @@ if (!Bangle.isLocked()) { // Initial state
drawTimeout = undefined;
if (showSunInfo) {
- if (PosInterval != 0) clearInterval(PosInterval);
+ if (PosInterval != 0 && typeof PosInterval != 'undefined') clearInterval(PosInterval);
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
updatePos();
}
@@ -378,4 +390,5 @@ Bangle.on('lock',on=>{
}
draw(); // draw immediately, queue redraw
}
- });
\ No newline at end of file
+ });
+}
\ No newline at end of file
diff --git a/apps/hworldclock/metadata.json b/apps/hworldclock/metadata.json
index 653cfc59c..e26599373 100644
--- a/apps/hworldclock/metadata.json
+++ b/apps/hworldclock/metadata.json
@@ -2,7 +2,7 @@
"id": "hworldclock",
"name": "Hanks World Clock",
"shortName": "Hanks World Clock",
- "version": "0.23",
+ "version": "0.25",
"description": "Current time zone plus up to three others",
"allow_emulator":true,
"icon": "app.png",
diff --git a/apps/iconlaunch/ChangeLog b/apps/iconlaunch/ChangeLog
index c71da1467..b03599ae6 100644
--- a/apps/iconlaunch/ChangeLog
+++ b/apps/iconlaunch/ChangeLog
@@ -12,3 +12,9 @@
used Object.assing for the settings
fix cache not deleted when "showClocks" options is changed
added timeOut to return to the clock
+0.11: Cleanup timeout when changing to clock
+ Reset timeout on swipe and drag
+0.12: Use Bangle.load and Bangle.showClock
+0.13: Fix automatic switch to clock
+0.14: Revert use of Bangle.load to classic load calls since widgets would
+still be loaded when they weren't supposed to.
diff --git a/apps/iconlaunch/app.js b/apps/iconlaunch/app.js
index 479956019..ccc39f3bb 100644
--- a/apps/iconlaunch/app.js
+++ b/apps/iconlaunch/app.js
@@ -9,7 +9,6 @@
timeOut:"Off"
}, s.readJSON("iconlaunch.json", true) || {});
- console.log(settings);
if (!settings.fullscreen) {
Bangle.loadWidgets();
Bangle.drawWidgets();
@@ -133,6 +132,7 @@
g.flip();
const itemsN = Math.ceil(launchCache.apps.length / appsN);
let onDrag = function(e) {
+ updateTimeout();
g.setColor(g.theme.fg);
g.setBgColor(g.theme.bg);
let dy = e.dy;
@@ -182,36 +182,27 @@
drag: onDrag,
touch: (_, e) => {
if (e.y < R.y - 4) return;
+ updateTimeout();
let i = YtoIdx(e.y);
selectItem(i, e);
},
- swipe: (h,_) => { if(settings.swipeExit && h==1) { returnToClock(); } },
+ swipe: (h,_) => { if(settings.swipeExit && h==1) { Bangle.showClock(); } },
+ btn: _=> { if (settings.oneClickExit) Bangle.showClock(); },
+ remove: function() {
+ if (timeout) clearTimeout(timeout);
+ }
};
- const returnToClock = function() {
- Bangle.setUI();
- delete launchCache;
- delete launchHash;
- delete drawItemAuto;
- delete drawText;
- delete selectItem;
- delete onDrag;
- delete drawItems;
- delete drawItem;
- delete returnToClock;
- delete idxToY;
- delete YtoIdx;
- delete settings;
- setTimeout(eval, 0, s.read(".bootcde"));
- };
-
-
- if (settings.oneClickExit) mode.btn = returnToClock;
+ let timeout;
+ const updateTimeout = function(){
if (settings.timeOut!="Off"){
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
- setTimeout(returnToClock,time*1000);
- }
-
+ if (timeout) clearTimeout(timeout);
+ timeout = setTimeout(Bangle.showClock,time*1000);
+ }
+ };
+
+ updateTimeout();
Bangle.setUI(mode);
}
diff --git a/apps/iconlaunch/metadata.json b/apps/iconlaunch/metadata.json
index 13e7aee08..155e7bd9b 100644
--- a/apps/iconlaunch/metadata.json
+++ b/apps/iconlaunch/metadata.json
@@ -2,7 +2,7 @@
"id": "iconlaunch",
"name": "Icon Launcher",
"shortName" : "Icon launcher",
- "version": "0.10",
+ "version": "0.14",
"icon": "app.png",
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
"tags": "tool,system,launcher",
diff --git a/apps/imageclock/ChangeLog b/apps/imageclock/ChangeLog
index d681176a7..df7bfd0de 100644
--- a/apps/imageclock/ChangeLog
+++ b/apps/imageclock/ChangeLog
@@ -16,3 +16,5 @@
Fix colorsetting in promises in generated code
Some performance improvements by caching lookups
Activate UI after first draw is complete to prevent drawing over launcher
+0.13: Use widget_utils swipeOn()
+ Allows minification by combining all but picture data into one file
diff --git a/apps/imageclock/app.js b/apps/imageclock/app.js
index 3d7a830d2..90c6163cd 100644
--- a/apps/imageclock/app.js
+++ b/apps/imageclock/app.js
@@ -1,7 +1,12 @@
-let unlockedDrawInterval = [];
-let lockedDrawInterval = [];
-let showWidgets = false;
-let firstDraw = true;
+let s = {};
+// unlocked draw intervals
+s.udi = [];
+// locked draw intervals
+s.ldi = [];
+// full draw
+s.fd = true;
+// performance log
+s.pl = {};
{
let x = g.getWidth()/2;
@@ -21,12 +26,10 @@ let firstDraw = true;
let precompiledJs = eval(require("Storage").read("imageclock.draw.js"));
let settings = require('Storage').readJSON("imageclock.json", true) || {};
- let performanceLog = {};
-
let startPerfLog = () => {};
let endPerfLog = () => {};
Bangle.printPerfLog = () => {print("Deactivated");};
- Bangle.resetPerfLog = () => {performanceLog = {};};
+ Bangle.resetPerfLog = () => {s.pl = {};};
let colormap={
"#000":0,
@@ -64,35 +67,37 @@ let firstDraw = true;
if (settings.perflog){
startPerfLog = function(name){
let time = getTime();
- if (!performanceLog.start) performanceLog.start={};
- performanceLog.start[name] = time;
+ if (!s.pl.start) s.pl.start={};
+ s.pl.start[name] = time;
};
- endPerfLog = function (name){
+ endPerfLog = function (name, once){
let time = getTime();
- if (!performanceLog.last) performanceLog.last={};
- let duration = time - performanceLog.start[name];
- performanceLog.last[name] = duration;
- if (!performanceLog.cum) performanceLog.cum={};
- if (!performanceLog.cum[name]) performanceLog.cum[name] = 0;
- performanceLog.cum[name] += duration;
- if (!performanceLog.count) performanceLog.count={};
- if (!performanceLog.count[name]) performanceLog.count[name] = 0;
- performanceLog.count[name]++;
+ if (!s.pl.start[name]) return;
+ if (!s.pl.last) s.pl.last={};
+ let duration = time - s.pl.start[name];
+ s.pl.last[name] = duration;
+ if (!s.pl.cum) s.pl.cum={};
+ if (!s.pl.cum[name]) s.pl.cum[name] = 0;
+ s.pl.cum[name] += duration;
+ if (!s.pl.count) s.pl.count={};
+ if (!s.pl.count[name]) s.pl.count[name] = 0;
+ s.pl.count[name]++;
+ if (once){s.pl.start[name] = undefined}
};
Bangle.printPerfLog = function(){
let result = "";
let keys = [];
- for (let c in performanceLog.cum){
+ for (let c in s.pl.cum){
keys.push(c);
}
keys.sort();
for (let k of keys){
- print(k, "last:", (performanceLog.last[k] * 1000).toFixed(0), "average:", (performanceLog.cum[k]/performanceLog.count[k]*1000).toFixed(0), "count:", performanceLog.count[k], "total:", (performanceLog.cum[k] * 1000).toFixed(0));
+ print(k, "last:", (s.pl.last[k] * 1000).toFixed(0), "average:", (s.pl.cum[k]/s.pl.count[k]*1000).toFixed(0), "count:", s.pl.count[k], "total:", (s.pl.cum[k] * 1000).toFixed(0));
}
};
}
-
+ startPerfLog("fullDraw");
startPerfLog("loadFunctions");
let delayTimeouts = {};
@@ -609,15 +614,22 @@ let firstDraw = true;
promise.then(()=>{
let currentDrawingTime = Date.now();
- if (showWidgets){
- restoreWidgetDraw();
- }
lastDrawTime = Date.now() - start;
isDrawing=false;
- firstDraw=false;
+ s.fd=false;
requestRefresh = false;
endPerfLog("initialDraw");
- if (!Bangle.uiRemove) setUi();
+ endPerfLog("fullDraw", true);
+
+ if (!Bangle.uiRemove){
+ setUi();
+ let orig = Bangle.drawWidgets;
+ Bangle.drawWidgets = ()=>{};
+ Bangle.loadWidgets();
+ Bangle.drawWidgets = orig;
+ require("widget_utils").swipeOn();
+ Bangle.drawWidgets();
+ }
}).catch((e)=>{
print("Error during drawing", e);
});
@@ -701,16 +713,16 @@ let firstDraw = true;
let handleLock = function(isLocked, forceRedraw){
//print("isLocked", Bangle.isLocked());
- for (let i of unlockedDrawInterval){
+ for (let i of s.udi){
//print("Clearing unlocked", i);
clearInterval(i);
}
- for (let i of lockedDrawInterval){
+ for (let i of s.ldi){
//print("Clearing locked", i);
clearInterval(i);
}
- unlockedDrawInterval = [];
- lockedDrawInterval = [];
+ s.udi = [];
+ s.ldi = [];
if (!isLocked){
if (forceRedraw || !redrawEvents || (redrawEvents.includes("unlock"))){
@@ -726,7 +738,7 @@ let firstDraw = true;
initialDraw(watchfaceResources, watchface);
},unlockedRedraw, (v)=>{
//print("New matched unlocked interval", v);
- unlockedDrawInterval.push(v);
+ s.udi.push(v);
}, lastDrawTime);
if (!events || events.includes("HRM")) Bangle.setHRMPower(1, "imageclock");
if (!events || events.includes("pressure")) Bangle.setBarometerPower(1, 'imageclock');
@@ -744,43 +756,13 @@ let firstDraw = true;
initialDraw(watchfaceResources, watchface);
},lockedRedraw, (v)=>{
//print("New matched locked interval", v);
- lockedDrawInterval.push(v);
+ s.ldi.push(v);
}, lastDrawTime);
Bangle.setHRMPower(0, "imageclock");
Bangle.setBarometerPower(0, 'imageclock');
}
};
-
- let showWidgetsChanged = false;
-
- let restoreWidgetDraw = function(){
- require("widget_utils").show();
- Bangle.drawWidgets();
- };
-
- let handleSwipe = function(lr, ud){
- if (!showWidgets && ud == 1){
- //print("Enable widgets");
- restoreWidgetDraw();
- showWidgetsChanged = true;
- }
- if (showWidgets && ud == -1){
- //print("Disable widgets");
- clearWidgetsDraw();
- firstDraw = true;
- showWidgetsChanged = true;
- }
- if (showWidgetsChanged){
- showWidgetsChanged = false;
- //print("Draw after widget change");
- showWidgets = ud == 1;
- initialDraw();
- }
- };
-
- Bangle.on('swipe', handleSwipe);
-
if (!events || events.includes("pressure")){
Bangle.on('pressure', handlePressure);
try{
@@ -799,14 +781,6 @@ let firstDraw = true;
if (!events || events.includes("charging")) {
Bangle.on('charging', handleCharging);
}
-
- let originalWidgetDraw = {};
- let originalWidgetArea = {};
-
- let clearWidgetsDraw = function(){
- //print("Clear widget draw calls");
- require("widget_utils").hide();
- }
handleLock(Bangle.isLocked(), true);
@@ -819,7 +793,6 @@ let firstDraw = true;
Bangle.setHRMPower(0, "imageclock");
Bangle.setBarometerPower(0, 'imageclock');
- Bangle.removeListener('swipe', handleSwipe);
Bangle.removeListener('lock', handleLock);
Bangle.removeListener('charging', handleCharging);
Bangle.removeListener('HRM', handleHrm);
@@ -829,31 +802,22 @@ let firstDraw = true;
if (initialDrawTimeoutUnlocked) clearTimeout(initialDrawTimeoutUnlocked);
if (initialDrawTimeoutLocked) clearTimeout(initialDrawTimeoutLocked);
- for (let i of global.unlockedDrawInterval){
+ for (let i of global.s.udi){
//print("Clearing unlocked", i);
clearInterval(i);
}
- delete global.unlockedDrawInterval;
- for (let i of global.lockedDrawInterval){
+ for (let i of global.s.ldi){
//print("Clearing locked", i);
clearInterval(i);
}
- delete global.lockedDrawInterval;
- delete global.showWidgets;
- delete global.firstDraw;
delete Bangle.printPerfLog;
if (settings.perflog){
delete Bangle.resetPerfLog;
- delete performanceLog;
}
-
cleanupDelays();
- restoreWidgetDraw();
+ require("widget_utils").show();
}
});
}
-
- Bangle.loadWidgets();
- clearWidgetsDraw();
}
diff --git a/apps/imageclock/custom.html b/apps/imageclock/custom.html
index 8bf7a7bd0..784e6cbdd 100644
--- a/apps/imageclock/custom.html
+++ b/apps/imageclock/custom.html
@@ -4,8 +4,8 @@
-
-
+
+
@@ -25,6 +25,8 @@
Wrap draw calls in timeouts (Slower, more RAM use, better interactivity)
Force use of direct drawing (Even faster, but will produce visible artifacts on not optimized watch faces)
+
+ Do not create combined app flle (slower but more flexible for debugging, incompatible with minification)
Add debug prints to generated code
@@ -32,7 +34,7 @@
Select watchface folder:
or
Select watchface zip file:
-
+
Upload to watch
Save resources file
Save face file
@@ -55,15 +57,15 @@
var expectedFiles = 0;
var rootZip = new JSZip();
var resourcesZip = rootZip.folder("resources");
-
+
function isNativeFormat(){
return document.getElementById("useNative").checked;
}
-
+
function addDebug(){
return document.getElementById("debugprints").checked;
}
-
+
function convertAmazfitTime(time){
var result = {};
if (time.Hours){
@@ -88,7 +90,7 @@
}
return result;
}
-
+
function convertAmazfitDate(date){
var result = {};
if (date.MonthAndDay.Separate.Day) result.Day = convertAmazfitNumber(date.MonthAndDay.Separate.Day, "Day");
@@ -98,11 +100,11 @@
}
return result;
}
-
+
var filesToMove={};
-
+
var zipChangePromise = Promise.resolve();
-
+
function performFileChanges(){
var promise = Promise.resolve();
//rename all files to just numbers without leading zeroes
@@ -111,7 +113,7 @@
var tmp = resultJson[c];
delete resultJson[c];
resultJson[Number(c)] = tmp;
-
+
async function modZip(c){
console.log("Async modification of ", c)
var fileRegex = new RegExp(c + ".*");
@@ -120,27 +122,27 @@
console.log("Filedata is", fileData);
var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/);
var newName = Number(c) + extension;
-
+
console.log("Renaming to", newName);
resourcesZip.remove(c + extension);
resourcesZip.file(newName, fileData);
}
promise = promise.then(modZip(c));
-
+
}
-
-
+
+
console.log("File moves:", filesToMove);
-
+
for (var c in filesToMove){
var tmp = resultJson[c];
console.log("Handle filemove", c, filesToMove[c], tmp);
-
+
var element = resultJson;
var path = filesToMove[c];
-
-
+
+
async function modZip(c){
console.log("Async modification of ", c)
var fileRegex = new RegExp(c + ".*");
@@ -149,13 +151,13 @@
console.log("Filedata is", fileData);
var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/);
var newName = Number(c) + extension;
-
+
console.log("Copying to", newName);
resourcesZip.file(filesToMove[c].join("/") + extension, fileData);
}
promise = promise.then(modZip(c));
-
-
+
+
for (var i = 0; i< path.length; i++){
if (!element[path[i]]) element[path[i]] = {};
if (i == path.length - 1){
@@ -164,7 +166,7 @@
element = element[path[i]];
}
}
-
+
}
promise.then(()=>{
document.getElementById('btnUpload').disabled = true;
@@ -172,7 +174,7 @@
console.log("After moves", resultJson);
return promise;
};
-
+
function convertAmazfitMultistate(multistate, value, minValue, maxValue){
var result = {
MultiState: {
@@ -188,18 +190,18 @@
if (multistate.ImageIndexOff) filesToMove[multistate.ImageIndexOff] = ["status", value, "off"];
return result;
}
-
+
function convertAmazfitStatus(status){
var result = {};
-
+
if (status.Alarm) result.Alarm = convertAmazfitMultistate(status.Alarm,"Alarm");
if (status.Bluetooth) result.Bluetooth = convertAmazfitMultistate(status.Bluetooth,"Bluetooth");
if (status.DoNotDisturb) result.DoNotDisturb = convertAmazfitMultistate(status.DoNotDisturb,"Notifications");
if (status.Lock) result.Lock = convertAmazfitMultistate(status.Lock,"Lock");
-
+
return result;
}
-
+
function convertAmazfitNumber(element, value, minValue, maxValue){
var number = {};
var result = {
@@ -233,10 +235,10 @@
if (maxValue !== undefined) number.MinValue = minValue;
return result;
}
-
+
function moveWeatherIcons(icon){
filesToMove[icon.ImageIndex + 0] = ["weather", "fallback"];
-
+
// Light clouds
filesToMove[icon.ImageIndex + 1] = ["weather", 801];
// Cloudy, possible rain
@@ -282,7 +284,7 @@
// Very heavy shower
filesToMove[icon.ImageIndex + 22] = ["weather", 531];
}
-
+
function convertAmazfitTemperature(temp){
var result = {};
result = convertAmazfitNumber(temp.Number, "WeatherTemperature");
@@ -294,15 +296,15 @@
}
return result;
}
-
+
function convertAmazfitWeather(weather){
var result = {};
-
+
if (weather.Temperature && weather.Temperature.Current){
if (!result.Temperature) result.Temperature = {};
result.Temperature.Current = convertAmazfitTemperature(weather.Temperature.Current);
}
-
+
if (weather.Temperature && weather.Temperature.Today){
if (!result.Temperature) result.Temperature = {};
if (weather.Temperature.Today.Separate){
@@ -327,10 +329,10 @@
}
return result;
}
-
+
function convertAmazfitActivity(activity){
var result = {};
-
+
if (activity.Steps){
result.Steps = convertAmazfitNumber(activity.Steps, "Steps");
}
@@ -339,7 +341,7 @@
}
return result;
}
-
+
function convertAmazfitScale(scale, value, minValue, maxValue){
var result = {};
result.Scale = {
@@ -356,10 +358,10 @@
Y: c.Y
});
}
-
+
return result;
}
-
+
function convertAmazfitStepsProgress(steps){
var result = {};
if (steps.GoalImage){
@@ -378,7 +380,7 @@
}
return result;
}
-
+
function convertAmazfitBattery(battery){
var result = {};
if (battery.Scale){
@@ -389,7 +391,7 @@
}
return result;
}
-
+
function convertAmazfitImage(image){
var result = {
Image: {
@@ -401,11 +403,11 @@
};
return result;
}
-
+
function convertAmazfitColor(color){
return "#" + color.substring(2);
}
-
+
function convertAmazfitHand(hand, rotationValue, minRotationValue, maxRotationValue){
var result = {
Filled: !hand.OnlyBorder,
@@ -418,18 +420,18 @@
MaxRotationValue: maxRotationValue,
MinRotationValue: minRotationValue
};
-
+
result.Vertices = []
for (var c of hand.Shape){
result.Vertices.push(c);
}
return { Poly: result };
}
-
+
function convertAmazfitAnalog(analog, face){
var result = {
};
-
+
if (analog.Hours){
result.Hours = {};
result.Hours.Hand = convertAmazfitHand(analog.Hours, "Hour12Analog", 0, 12);
@@ -464,14 +466,14 @@
}
return result;
}
-
+
function restructureAmazfitFormat(dataString){
console.log("Amazfit data:", dataString);
-
-
+
+
var json = JSON.parse(dataString);
faceJson = json;
-
+
var result = {};
result.Properties = {};
@@ -479,8 +481,8 @@
result.Properties.Redraw.Unlocked = 60000;
result.Properties.Redraw.Locked = 60000;
result.Properties.Redraw.Clear = true;
-
-
+
+
if (json.Background){
result.Background = json.Background;
result.Background.Image.ImagePath = [];
@@ -491,32 +493,32 @@
result.Time = convertAmazfitTime(json.Time);
if (json.AnalogDialFace) result.Time.Plane = 1;
}
-
+
if (json.Date){
result.Date = convertAmazfitDate(json.Date);
if (json.AnalogDialFace) result.Date.Plane = 1;
}
-
+
if (json.Status){
result.Status = convertAmazfitStatus(json.Status);
if (json.AnalogDialFace) result.Status.Plane = 1;
}
-
+
if (json.Weather){
result.Weather = convertAmazfitWeather(json.Weather);
if (json.AnalogDialFace) result.Weather.Plane = 1;
}
-
+
if (json.Activity){
result.Activity = convertAmazfitActivity(json.Activity);
if (json.AnalogDialFace) result.Activity.Plane = 1;
}
-
+
if (json.StepsProgress){
result.StepsProgress = convertAmazfitStepsProgress(json.StepsProgress);
if (json.AnalogDialFace) result.StepsProgress.Plane = 1;
}
-
+
if (json.Battery){
result.Battery = convertAmazfitBattery(json.Battery);
if (json.AnalogDialFace) result.Battery.Plane = 1;
@@ -529,7 +531,7 @@
return result;
}
-
+
function parseFaceJson(jsonString){
if (isNativeFormat()){
return JSON.parse(jsonString);
@@ -537,7 +539,7 @@
return restructureAmazfitFormat(jsonString);
}
}
-
+
function combineProperty(name, source, target){
if (source[name] && target[name]){
if (Array.isArray(target[name])){
@@ -556,7 +558,7 @@
if (typeof element == "string" || typeof element == "number") return [];
for (var c in element){
var next = element[c];
-
+
combineProperty("X",element,next);
combineProperty("Y",element,next);
combineProperty("Width",element,next);
@@ -571,7 +573,7 @@
combineProperty("MaxRotationValue",element,next);
if (typeof element.Plane == "number") next.Plane = element.Plane;
next.Layer = element.Layer ? (element.Layer) : "" + c;
-
+
if (["MultiState","Image","CodedImage","Number","Circle","Poly","Rect","Scale"].includes(c)){
result.push({type:c, value: next});
} else {
@@ -580,12 +582,12 @@
}
return result;
}
-
+
function convertToCode(elements, properties, wrapInTimeouts, forceUseOrigPlane){
var code = "(function (wr, wf) {\n";
code += "var lc;\n";
code += "var p = Promise.resolve();\n";
-
+
//get mapped by layer
var counter = 0;
var planes = {};
@@ -606,20 +608,20 @@
}
if (!planeNumbers.includes(0)) planeNumbers.push(0);
planeNumbers.sort().reverse();
-
+
console.log("Found planes", planes, "with numbers", planeNumbers)
-
+
code += "p0 = g;\n";
-
+
for (var planeIndex = 0; planeIndex < planeNumbers.length; planeIndex++){
var layers = planes[planeNumbers[planeIndex]];
var plane = planeNumbers[planeIndex];
-
+
var lastSetColor;
var lastSetBgColor;
-
+
if (plane != 0) code += "if (!p" + plane + ") p" + plane + " = Graphics.createArrayBuffer(g.getWidth(),g.getHeight(),4,{msb:true});\n";
-
+
if (properties.Redraw && properties.Redraw.Clear){
if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){
code += "p = p.then(()=>delay(0)).then(()=>{\n";
@@ -632,31 +634,31 @@
code += 'endPerfLog("initialDraw_g.clear");'+ "\n";
code += "});\n";
}
-
+
var previousPlane = plane + 1;
if (previousPlane < planeNumbers.length){
code += "p = p.then(()=>{\n";
-
+
if (addDebug()) code += 'print("Copying of plane ' + previousPlane + ' to display");'+"\n";
//code += "g.drawImage(p" + i + ".asImage());";
code += "p0.drawImage({width: p" + previousPlane + ".getWidth(), height: p" + previousPlane + ".getHeight(), bpp: p" + previousPlane + ".getBPP(), buffer: p" + previousPlane + ".buffer, palette: palette});\n";
code += "});\n";
}
-
+
console.log("Got layers", layers);
for (var layername in layers){
var layerElements = layers[layername];
-
+
console.log("Layer elements", layername, layerElements);
//code for whole layer
-
+
if (addDebug()) code += 'print("Starting layer ' + layername + '");' + "\n";
-
+
var checkForLayerChange = false;
var checkcode = "";
-
+
if (!(properties.Redraw && properties.Redraw.Clear)){
- checkcode = 'firstDraw';
+ checkcode = 's.fd';
for (var i = 0; i< layerElements.length; i++){
var layerElement = layerElements[i];
var referencedElement = elements[layerElements[i].index];
@@ -664,38 +666,38 @@
console.log("Check for change:", layerElement, referencedElement);
if (layerElement.element.Value){
if (elementType == "MultiState" && layerElement.element.Value) {
- checkcode += '| isChangedMultistate(wf.Collapsed[' + layerElement.index + '].value)';
+ checkcode += '| isChangedMultistate(wf.c[' + layerElement.index + '].value)';
} else {
- checkcode += '| isChangedNumber(wf.Collapsed[' + layerElement.index + '].value)';
+ checkcode += '| isChangedNumber(wf.c[' + layerElement.index + '].value)';
}
checkForLayerChange = true;
}
}
}
-
-
+
+
//code for elements
for (var i = 0; i< layerElements.length; i++){
var elementIndex = layerElements[i].index;
var c = elements[elementIndex];
console.log("convert to code", c);
-
+
var condition = "";
if (checkcode.length > 0 && checkForLayerChange){
if (condition.length > 0) condition += " && ";
condition = '(' + checkcode + ')';
}
-
+
if (c.value.HideOn && c.value.HideOn.includes("Lock")){
if (condition.length > 0) condition += " && ";
condition = '!Bangle.isLocked()';
}
-
+
if (c.value.Type == "Once"){
if (condition.length > 0) condition += " && ";
- condition += "firstDraw";
+ condition += "s.fd";
}
-
+
var planeName = "p" + plane;
var colorsetting = "";
if (c.value.ForegroundColor && lastSetColor != c.value.ForegroundColor){
@@ -712,7 +714,7 @@
else
colorsetting += planeName + ".setBgColor(\"" + c.value.BackgroundColor + "\");\n";
}
-
+
if (addDebug()) code += 'print("Element condition is ' + condition + '");' + "\n";
code += (condition.length > 0 ? "if (" + condition + "){\n" : "");
if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){
@@ -722,18 +724,18 @@
}
code += "" + colorsetting;
if (addDebug()) code += 'print("Drawing element ' + elementIndex + ' with type ' + c.type + ' on plane ' + planeName + '");' + "\n";
- code += "draw" + c.type + "(" + planeName + ", wr, wf.Collapsed[" + elementIndex + "].value);\n";
+ code += "draw" + c.type + "(" + planeName + ", wr, wf.c[" + elementIndex + "].value);\n";
code += "});\n";
code += (condition.length > 0 ? "}\n" : "");
}
}
console.log("Current plane is", plane);
-
-
+
+
}
-
+
code += "return p;})";
console.log("Code:", code);
return code
@@ -742,14 +744,14 @@
function postProcess(){
moveData(resultJson);
console.log("Created data file", resourceDataString, resourceDataOffset, resultJson);
-
+
var properties = faceJson.Properties;
- faceJson = { Properties: properties, Collapsed: collapseTree(faceJson,{X:0,Y:0})};
+ faceJson = { Properties: properties, c: collapseTree(faceJson,{X:0,Y:0})};
console.log("After collapsing", faceJson);
- precompiledJs = convertToCode(faceJson.Collapsed, properties, document.getElementById('timeoutwrap').checked, document.getElementById('forceOrigPlane').checked);
+ precompiledJs = convertToCode(faceJson.c, properties, document.getElementById('timeoutwrap').checked, document.getElementById('forceOrigPlane').checked);
console.log("After precompiling", precompiledJs);
}
-
+
function convertJsToJson(imgstr){
var E = {};
E.toArrayBuffer = (s)=>s;
@@ -768,7 +770,7 @@
function imageLoaded() {
var options = {};
-
+
options.diffusion = infoJson.diffusion ? infoJson.diffusion : "none";
options.compression = false;
options.alphaToColor = false;
@@ -779,12 +781,12 @@
options.contrast = 0;
options.mode = infoJson.color ? infoJson.color : "1bit";
options.output = "object";
-
+
console.log("Loaded image has path", this.path);
var jsonPath = this.path.split("/");
-
+
var forcedTransparentColorMatch = jsonPath[jsonPath.length-1].match(/.*\.t([^.]+)\..*/)
-
+
var forcedTransparentColor;
if (jsonPath[jsonPath.length-1].includes(".t.")){
options.transparent = true;
@@ -792,13 +794,13 @@
options.transparent = false;
forcedTransparentColor = forcedTransparentColorMatch[1];
}
-
-
+
+
console.log("image has transparency", options.transparent);
console.log("image has forced transparent color", forcedTransparentColor);
jsonPath[jsonPath.length-1] = jsonPath[jsonPath.length-1].replace(/([^.]*)\..*/, "$1");
console.log("Loaded image has json path", jsonPath);
-
+
var canvas = document.getElementById("canvas")
canvas.width = this.width*2;
canvas.height = this.height;
@@ -819,7 +821,7 @@
imgstr = imageconverter.RGBAtoString(rgba, options);
var outputImageData = new ImageData(options.rgbaOut, options.width, options.height);
ctx.putImageData(outputImageData,this.width,0);
-
+
imgstr = convertJsToJson(imgstr);
// checkerboard for transparency on original image
@@ -827,9 +829,9 @@
imageconverter.RGBAtoCheckerboard(imageData.data, {width:this.width,height:this.height});
ctx.putImageData(imageData,0,0);
-
+
var currentElement = resultJson;
-
+
for (var i = 0; i < jsonPath.length; i++){
if (i == jsonPath.length - 1){
var resultingObject = JSON.parse(imgstr);
@@ -841,18 +843,18 @@
currentElement = currentElement[jsonPath[i]];
}
}
-
+
handledFiles++;
console.log("Expected:", expectedFiles, " handled:", handledFiles);
-
+
if (handledFiles == expectedFiles){
if (!isNativeFormat()) {
performFileChanges().then(()=>{
postProcess();
-
+
rootZip.file("face.json", JSON.stringify(faceJson, null, 2));
rootZip.file("info.json", JSON.stringify(infoJson, null, 2));
-
+
document.getElementById('btnSave').disabled = false;
document.getElementById('btnSaveFace').disabled = false;
document.getElementById('btnSaveZip').disabled = false;
@@ -860,21 +862,21 @@
});
} else {
postProcess();
-
+
document.getElementById('btnSave').disabled = false;
document.getElementById('btnSaveFace').disabled = false;
document.getElementById('btnUpload').disabled = false;
}
}
}
-
+
function handleWatchFace(infoFile, faceFile, resourceFiles){
if (isNativeFormat()){
var reader = new FileReader();
reader.path = infoFile.webkitRelativePath;
reader.onload = function(event) {
infoJson = JSON.parse(reader.result);
-
+
handleFaceJson(faceFile, resourceFiles);
};
reader.readAsText(infoFile);
@@ -883,18 +885,18 @@
handleFaceJson(faceFile, resourceFiles);
}
}
-
+
function handleFaceJson(faceFile, resourceFiles){
var reader = new FileReader();
reader.path = faceFile.webkitRelativePath;
reader.onload = function(event) {
faceJson = parseFaceJson(reader.result);
-
+
handleResourceFiles(resourceFiles);
};
reader.readAsText(faceFile);
}
-
+
function handleResourceFiles(files){
for (var current of files){
console.log('Handle resource file ', current);
@@ -917,25 +919,25 @@
reader.readAsDataURL(current);
}
}
-
+
function handleFileSelect(event) {
handledFiles = 0;
expectedFiles = undefined;
-
+
document.getElementById('btnSave').disabled = true;
document.getElementById('btnSaveZip').disabled = true;
document.getElementById('btnSaveFace').disabled = true;
document.getElementById('btnUpload').disabled = true;
-
+
console.log("File select event", event);
if (event.target.files.length == 0) return;
result = "";
resultJson= {};
-
+
var resourceFiles = [];
var faceFile;
var infoFile;
-
+
for (var current of event.target.files){
console.log('Handle file ', current);
if (isNativeFormat()){
@@ -970,10 +972,10 @@
}
}
handleWatchFace(infoFile, faceFile, resourceFiles);
-
+
};
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
-
+
function moveData(json){
console.log("MoveData for", json);
for (var k in json){
@@ -997,11 +999,11 @@
}
}
}
-
+
document.getElementById("timeoutwrap").addEventListener("click", function() {
document.getElementById("forceOrigPlane").disabled = !document.getElementById("timeoutwrap").checked;
});
-
+
document.getElementById("btnSave").addEventListener("click", function() {
var h = document.createElement('a');
h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(resultJson));
@@ -1010,25 +1012,48 @@
h.click();
});
document.getElementById("btnUpload").addEventListener("click", function() {
-
+
+ console.log("Fetching app");
+ fetch('app.js').then((r) => {
+ console.log("Got response", r);
+ return r.text();
+ }
+ ).then((imageclockSrc) => {
+ console.log("Got src", imageclockSrc)
+
+ if (!document.getElementById('separateFiles').checked){
+ if (precompiledJs.length > 0){
+ const replacementString = 'eval(require("Storage").read("imageclock.draw.js"))';
+ console.log("Can replace:", imageclockSrc.includes(replacementString));
+ imageclockSrc = imageclockSrc.replace(replacementString, precompiledJs);
+ }
+ imageclockSrc = imageclockSrc.replace('require("Storage").readJSON("imageclock.face.json")', JSON.stringify(faceJson));
+ imageclockSrc = imageclockSrc.replace('require("Storage").readJSON("imageclock.resources.json")', JSON.stringify(resultJson));
+ }
var appDef = {
id : "imageclock",
storage:[
- {name:"imageclock.app.js", url:"app.js"},
- {name:"imageclock.resources.json", content: JSON.stringify(resultJson)},
{name:"imageclock.img", url:"app-icon.js", evaluate:true},
]
};
+ if (document.getElementById('separateFiles').checked){
+ appDef.storage.push({name:"imageclock.app.js", url:"app.js"});
+ if (precompiledJs.length > 0){
+ appDef.storage.push({name:"imageclock.draw.js", content:precompiledJs});
+ }
+ appDef.storage.push({name:"imageclock.face.json", content: JSON.stringify(faceJson)});
+ appDef.storage.push({name:"imageclock.resources.json", content: JSON.stringify(resultJson)});
+ } else {
+ appDef.storage.push({name:"imageclock.app.js", url:"pleaseminifycontent.js", content:imageclockSrc});
+ }
if (resourceDataString.length > 0){
appDef.storage.push({name:"imageclock.resources.data", content: resourceDataString});
}
- appDef.storage.push({name:"imageclock.draw.js", content: precompiledJs.length > 0 ? precompiledJs : "//empty"});
- appDef.storage.push({name:"imageclock.face.json", content: JSON.stringify(faceJson)});
-
console.log("Uploading app:", appDef);
sendCustomizedApp(appDef);
+ });
});
-
+
function handleZipSelect(evt) {
@@ -1040,18 +1065,18 @@
document.getElementById('btnSaveZip').disabled = true;
document.getElementById('btnUpload').disabled = true;
JSZip.loadAsync(f).then(function(zip) {
-
+
console.log("Zip loaded", zip);
result = "";
resultJson= {};
-
+
var resourceFiles = [];
-
+
var promise = zip.file("face.json").async("string").then((data)=>{
console.log("face.json data", data);
faceJson = parseFaceJson(data);
});
-
+
if (isNativeFormat()){
promise = promise.then(zip.file("info.json").async("string").then((data)=>{
console.log("info.json data", data);
@@ -1062,12 +1087,12 @@
"color": "3bit",
"transparent": true
};
-
+
}
-
+
zip.folder("resources").forEach(function (relativePath, file){
console.log("iterating over", relativePath);
-
+
if (!file.dir){
expectedFiles++;
promise = promise.then(file.async("blob").then(function (blob) {
@@ -1083,10 +1108,10 @@
reader.readAsDataURL(blob);
}));
}
-
+
});
-
-
+
+
}, function (e) {
console.log("Error reading " + f.name + ": " + e.message);
});
@@ -1095,11 +1120,11 @@
console.log("Zip select event", evt);
var files = evt.target.files;
-
+
if (files.length > 1){
alert("Only one file allowed");
}
-
+
handleFile(files[0]);
}
@@ -1113,7 +1138,7 @@
});
}
-
+
document.getElementById("btnSaveFace").addEventListener("click", function() {
var h = document.createElement('a');
h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(faceJson));
@@ -1121,14 +1146,14 @@
h.download = "face.json";
h.click();
});
-
+
document.getElementById('zipLoader').addEventListener('change', handleZipSelect, false);
document.getElementById('btnSaveZip').addEventListener('click', handleZipExport, false);
document.getElementById('btnSave').disabled = true;
document.getElementById('btnSaveFace').disabled = true;
document.getElementById('btnSaveZip').disabled = true;
document.getElementById('btnUpload').disabled = true;
-
+
diff --git a/apps/imageclock/metadata.json b/apps/imageclock/metadata.json
index 51257b435..b291ab01e 100644
--- a/apps/imageclock/metadata.json
+++ b/apps/imageclock/metadata.json
@@ -2,7 +2,7 @@
"id": "imageclock",
"name": "Imageclock",
"shortName": "Imageclock",
- "version": "0.12",
+ "version": "0.13",
"type": "clock",
"description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.",
"icon": "app.png",
diff --git a/apps/imgclock/custom.html b/apps/imgclock/custom.html
index 1d8e06c07..68d059b80 100644
--- a/apps/imgclock/custom.html
+++ b/apps/imgclock/custom.html
@@ -10,7 +10,7 @@
-
+
+
+
+
+
+
+
+
+