2022-11-20 07:40:33 +00:00
|
|
|
let simulated = false;
|
2022-07-22 05:02:45 +00:00
|
|
|
let file_version = 3;
|
2022-07-20 13:09:25 +00:00
|
|
|
let code_key = 47490;
|
2022-07-18 15:01:04 +00:00
|
|
|
|
2022-09-16 11:34:23 +00:00
|
|
|
var settings = Object.assign(
|
|
|
|
{
|
2022-11-05 16:17:49 +00:00
|
|
|
keep_gps_alive: true,
|
2022-09-16 11:34:23 +00:00
|
|
|
max_speed: 35,
|
2022-11-19 12:48:26 +00:00
|
|
|
display_points: true,
|
2022-09-16 11:34:23 +00:00
|
|
|
},
|
|
|
|
require("Storage").readJSON("gipy.json", true) || {}
|
|
|
|
);
|
|
|
|
|
2022-11-19 12:48:26 +00:00
|
|
|
let profile_start_times = [];
|
|
|
|
|
|
|
|
function start_profiling() {
|
|
|
|
profile_start_times.push(getTime());
|
|
|
|
}
|
|
|
|
|
|
|
|
function end_profiling(label) {
|
|
|
|
let end_time = getTime();
|
|
|
|
let elapsed = end_time - profile_start_times.pop();
|
|
|
|
console.log("profile:", label, "took", elapsed);
|
|
|
|
}
|
|
|
|
|
2022-07-21 10:35:24 +00:00
|
|
|
let interests_colors = [
|
2022-07-22 07:49:33 +00:00
|
|
|
0xf800, // Bakery, red
|
2022-07-22 05:31:23 +00:00
|
|
|
0x001f, // DrinkingWater, blue
|
|
|
|
0x07ff, // Toilets, cyan
|
2022-07-22 07:49:33 +00:00
|
|
|
0x07e0, // Artwork, green
|
2022-07-21 10:35:24 +00:00
|
|
|
];
|
|
|
|
|
2022-07-21 12:19:41 +00:00
|
|
|
function binary_search(array, x) {
|
2022-07-22 05:31:23 +00:00
|
|
|
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;
|
2022-07-21 12:19:41 +00:00
|
|
|
}
|
|
|
|
|
2022-07-18 15:01:04 +00:00
|
|
|
class Status {
|
2022-07-22 05:31:23 +00:00
|
|
|
constructor(path) {
|
|
|
|
this.path = path;
|
2022-11-17 13:41:36 +00:00
|
|
|
this.scale_factor = 40000.0; // multiply geo coordinates by this to get pixels coordinates
|
2022-07-22 05:31:23 +00:00
|
|
|
this.on_path = false; // are we on the path or lost ?
|
|
|
|
this.position = null; // where we are
|
2022-07-27 13:35:00 +00:00
|
|
|
this.adjusted_cos_direction = null; // cos of where we look at
|
|
|
|
this.adjusted_sin_direction = null; // sin of where we look at
|
2022-07-22 05:31:23 +00:00
|
|
|
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 ?
|
2022-09-16 14:35:13 +00:00
|
|
|
this.paused_time = 0.0; // how long did we stop (stops don't count in avg speed)
|
|
|
|
this.paused_since = getTime();
|
2022-11-19 12:48:26 +00:00
|
|
|
this.projected_point = null;
|
2022-07-22 05:31:23 +00:00
|
|
|
|
|
|
|
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
|
2022-11-05 13:16:07 +00:00
|
|
|
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
|
2022-07-26 06:43:52 +00:00
|
|
|
this.old_points = [];
|
2022-08-26 11:24:53 +00:00
|
|
|
this.old_times = [];
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
2022-07-26 06:43:52 +00:00
|
|
|
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.
|
2022-11-05 13:16:07 +00:00
|
|
|
this.gps_coordinates_counter += 1;
|
2022-08-26 12:25:35 +00:00
|
|
|
let now = getTime();
|
|
|
|
this.old_points.push(position);
|
|
|
|
this.old_times.push(now);
|
|
|
|
|
2022-07-26 06:43:52 +00:00
|
|
|
if (this.old_points.length == 1) {
|
|
|
|
return null;
|
|
|
|
}
|
2022-08-26 12:25:35 +00:00
|
|
|
|
|
|
|
let last_point = this.old_points[this.old_points.length - 1];
|
2022-08-29 13:20:04 +00:00
|
|
|
let oldest_point = this.old_points[0];
|
2022-08-26 12:25:35 +00:00
|
|
|
|
2022-11-05 16:17:49 +00:00
|
|
|
// every 7 points we count the distance
|
|
|
|
if (this.gps_coordinates_counter % 7 == 0) {
|
2022-11-05 13:16:07 +00:00
|
|
|
let distance = last_point.distance(oldest_point);
|
|
|
|
if (distance < 150.0) {
|
|
|
|
// to avoid gps glitches
|
|
|
|
this.advanced_distance += distance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-14 15:09:00 +00:00
|
|
|
if (this.old_points.length == 8) {
|
2022-09-15 13:17:28 +00:00
|
|
|
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);
|
2022-09-14 15:09:00 +00:00
|
|
|
let t1 = (this.old_times[1] + this.old_times[2]) / 2;
|
|
|
|
let t2 = (this.old_times[5] + this.old_times[6]) / 2;
|
2022-09-15 13:17:28 +00:00
|
|
|
this.instant_speed = p1.distance(p2) / (t2 - t1);
|
2022-08-26 12:25:35 +00:00
|
|
|
this.old_points.shift();
|
|
|
|
this.old_times.shift();
|
2022-09-13 15:19:42 +00:00
|
|
|
} else {
|
|
|
|
this.instant_speed =
|
|
|
|
oldest_point.distance(last_point) / (now - this.old_times[0]);
|
2022-09-16 14:35:13 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
2022-08-26 12:25:35 +00:00
|
|
|
}
|
|
|
|
// 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;
|
2022-07-26 06:43:52 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2022-07-18 15:01:04 +00:00
|
|
|
}
|
2022-07-19 08:05:34 +00:00
|
|
|
|
2022-07-27 13:35:00 +00:00
|
|
|
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);
|
2022-07-22 05:31:23 +00:00
|
|
|
this.position = new_position;
|
|
|
|
|
|
|
|
// detect segment we are on now
|
2022-07-28 06:23:17 +00:00
|
|
|
let res = this.path.nearest_segment(
|
2022-07-22 05:31:23 +00:00
|
|
|
this.position,
|
2022-07-27 13:35:00 +00:00
|
|
|
Math.max(0, this.current_segment - 1),
|
|
|
|
Math.min(this.current_segment + 2, this.path.len - 1),
|
|
|
|
cos_direction,
|
|
|
|
sin_direction
|
2022-07-22 05:31:23 +00:00
|
|
|
);
|
2022-07-28 06:23:17 +00:00
|
|
|
let orientation = res[0];
|
|
|
|
let next_segment = res[1];
|
2022-07-22 05:31:23 +00:00
|
|
|
|
|
|
|
if (this.is_lost(next_segment)) {
|
2022-11-20 07:40:33 +00:00
|
|
|
// start_profiling();
|
2022-07-22 05:31:23 +00:00
|
|
|
// it did not work, try anywhere
|
2022-07-28 06:23:17 +00:00
|
|
|
res = this.path.nearest_segment(
|
2022-07-22 05:31:23 +00:00
|
|
|
this.position,
|
|
|
|
0,
|
|
|
|
this.path.len - 1,
|
2022-07-27 13:35:00 +00:00
|
|
|
cos_direction,
|
|
|
|
sin_direction
|
2022-07-22 05:31:23 +00:00
|
|
|
);
|
2022-07-28 06:23:17 +00:00
|
|
|
orientation = res[0];
|
|
|
|
next_segment = res[1];
|
2022-11-20 07:40:33 +00:00
|
|
|
// end_profiling("repositioning");
|
2022-07-18 15:01:04 +00:00
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
// 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);
|
2022-08-23 05:57:10 +00:00
|
|
|
setTimeout(() => Bangle.buzz(), 1000);
|
|
|
|
setTimeout(() => Bangle.buzz(), 1500);
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
|
|
|
this.on_path = !lost;
|
2022-07-18 15:01:04 +00:00
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
|
|
|
|
this.current_segment = next_segment;
|
|
|
|
|
|
|
|
// check if we are nearing the next point on our path and alert the user
|
2022-07-28 06:23:17 +00:00
|
|
|
let next_point = this.current_segment + (1 - orientation);
|
2022-07-22 05:31:23 +00:00
|
|
|
this.distance_to_next_point = Math.ceil(
|
|
|
|
this.position.distance(this.path.point(next_point))
|
|
|
|
);
|
2022-09-16 09:13:48 +00:00
|
|
|
|
2022-07-26 07:44:42 +00:00
|
|
|
// disable gps when far from next point and locked
|
2022-09-16 11:34:23 +00:00
|
|
|
if (Bangle.isLocked() && !settings.keep_gps_alive) {
|
|
|
|
let time_to_next_point =
|
|
|
|
(this.distance_to_next_point * 3.6) / settings.max_speed;
|
2022-08-15 06:08:54 +00:00
|
|
|
if (time_to_next_point > 60) {
|
2022-07-26 07:44:42 +00:00
|
|
|
Bangle.setGPSPower(false, "gipy");
|
|
|
|
setTimeout(function () {
|
|
|
|
Bangle.setGPSPower(true, "gipy");
|
|
|
|
}, time_to_next_point);
|
|
|
|
}
|
|
|
|
}
|
2022-08-15 06:08:54 +00:00
|
|
|
if (this.reaching != next_point && this.distance_to_next_point <= 100) {
|
2022-07-22 05:31:23 +00:00
|
|
|
this.reaching = next_point;
|
|
|
|
let reaching_waypoint = this.path.is_waypoint(next_point);
|
|
|
|
if (reaching_waypoint) {
|
2022-08-23 05:57:10 +00:00
|
|
|
Bangle.buzz();
|
|
|
|
setTimeout(() => Bangle.buzz(), 500);
|
|
|
|
setTimeout(() => Bangle.buzz(), 1000);
|
|
|
|
setTimeout(() => Bangle.buzz(), 1500);
|
2022-07-22 05:31:23 +00:00
|
|
|
if (Bangle.isLocked()) {
|
|
|
|
Bangle.setLocked(false);
|
|
|
|
}
|
|
|
|
}
|
2022-07-18 15:01:04 +00:00
|
|
|
}
|
2022-07-26 07:44:42 +00:00
|
|
|
// re-display
|
2022-09-15 13:17:28 +00:00
|
|
|
this.display(orientation);
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
2022-09-15 13:17:28 +00:00
|
|
|
remaining_distance(orientation) {
|
|
|
|
let remaining_in_correct_orientation =
|
2022-07-22 05:31:23 +00:00
|
|
|
this.remaining_distances[this.current_segment + 1] +
|
2022-09-15 13:17:28 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
2022-11-19 12:48:26 +00:00
|
|
|
// check if we are lost (too far from segment we think we are on)
|
|
|
|
// if we are adjust scale so that path will still be displayed.
|
|
|
|
// we do the scale adjustment here to avoid recomputations later on.
|
2022-07-22 05:31:23 +00:00
|
|
|
is_lost(segment) {
|
2022-11-19 12:48:26 +00:00
|
|
|
let projection = this.position.closest_segment_point(
|
2022-07-22 05:31:23 +00:00
|
|
|
this.path.point(segment),
|
|
|
|
this.path.point(segment + 1)
|
|
|
|
);
|
2022-11-19 12:48:26 +00:00
|
|
|
this.projected_point = projection; // save this info for display
|
|
|
|
let distance_to_projection = this.position.distance(projection);
|
|
|
|
if (distance_to_projection > 50) {
|
2022-11-20 11:04:49 +00:00
|
|
|
this.scale_factor =
|
|
|
|
Math.min(66.0 / distance_to_projection, 1.0) * 40000.0;
|
2022-11-19 12:48:26 +00:00
|
|
|
return true;
|
2022-11-17 13:41:36 +00:00
|
|
|
} else {
|
2022-11-19 12:48:26 +00:00
|
|
|
this.scale_factor = 40000.0;
|
|
|
|
return false;
|
2022-11-17 13:41:36 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-15 13:17:28 +00:00
|
|
|
display(orientation) {
|
2022-07-27 15:21:51 +00:00
|
|
|
g.clear();
|
2022-11-19 12:48:26 +00:00
|
|
|
// start_profiling();
|
2022-07-22 05:31:23 +00:00
|
|
|
this.display_map();
|
2022-11-19 12:48:26 +00:00
|
|
|
// end_profiling("display_map");
|
2022-08-14 13:36:55 +00:00
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
this.display_interest_points();
|
2022-09-15 13:17:28 +00:00
|
|
|
this.display_stats(orientation);
|
2022-07-22 05:31:23 +00:00
|
|
|
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,
|
2022-07-27 13:35:00 +00:00
|
|
|
this.adjusted_cos_direction,
|
2022-11-17 13:41:36 +00:00
|
|
|
this.adjusted_sin_direction,
|
|
|
|
this.scale_factor
|
2022-07-22 05:31:23 +00:00
|
|
|
);
|
|
|
|
g.setColor(color).fillCircle(c[0], c[1], 5);
|
2022-07-21 10:35:24 +00:00
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
2022-09-15 13:17:28 +00:00
|
|
|
display_stats(orientation) {
|
|
|
|
let remaining_distance = this.remaining_distance(orientation);
|
2022-07-26 14:59:04 +00:00
|
|
|
let rounded_distance = Math.round(remaining_distance / 100) / 10;
|
2022-07-22 05:31:23 +00:00
|
|
|
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;
|
2022-07-18 15:01:04 +00:00
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
let hours = now.getHours().toString();
|
|
|
|
g.setFont("6x8:2")
|
2022-07-28 06:23:17 +00:00
|
|
|
.setFontAlign(-1, -1, 0)
|
2022-07-22 05:31:23 +00:00
|
|
|
.setColor(g.theme.fg)
|
2022-07-28 06:23:17 +00:00
|
|
|
.drawString(hours + ":" + minutes, 0, 30);
|
2022-07-23 14:25:38 +00:00
|
|
|
|
2022-07-28 06:23:17 +00:00
|
|
|
g.setFont("6x8:2").drawString(
|
|
|
|
"" + this.distance_to_next_point + "m",
|
2022-07-23 06:41:23 +00:00
|
|
|
0,
|
2022-07-28 06:23:17 +00:00
|
|
|
g.getHeight() - 49
|
2022-07-23 06:41:23 +00:00
|
|
|
);
|
2022-08-26 11:24:53 +00:00
|
|
|
|
2022-11-05 13:16:07 +00:00
|
|
|
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
|
|
|
|
);
|
2022-08-26 12:25:35 +00:00
|
|
|
let approximate_instant_speed = Math.round(this.instant_speed * 3.6);
|
2022-08-26 11:24:53 +00:00
|
|
|
|
2022-07-28 06:23:17 +00:00
|
|
|
g.setFont("6x8:2")
|
|
|
|
.setFontAlign(-1, -1, 0)
|
2022-08-26 11:24:53 +00:00
|
|
|
.drawString(
|
|
|
|
"" + approximate_speed + "km/h (in." + approximate_instant_speed + ")",
|
|
|
|
0,
|
|
|
|
g.getHeight() - 15
|
|
|
|
);
|
|
|
|
|
2022-07-28 06:23:17 +00:00
|
|
|
g.setFont("6x8:2").drawString(
|
|
|
|
"" + rounded_distance + "/" + total,
|
2022-07-22 05:31:23 +00:00
|
|
|
0,
|
2022-08-26 11:24:53 +00:00
|
|
|
g.getHeight() - 32
|
2022-07-22 05:31:23 +00:00
|
|
|
);
|
2022-07-22 05:54:54 +00:00
|
|
|
|
2022-08-15 06:08:54 +00:00
|
|
|
if (this.distance_to_next_point <= 100) {
|
2022-07-22 16:06:24 +00:00
|
|
|
if (this.path.is_waypoint(this.reaching)) {
|
|
|
|
g.setColor(0.0, 1.0, 0.0)
|
2022-07-23 06:41:23 +00:00
|
|
|
.setFont("6x15")
|
2022-07-28 06:23:17 +00:00
|
|
|
.drawString("turn", g.getWidth() - 50, 30);
|
2022-07-22 16:06:24 +00:00
|
|
|
}
|
2022-07-22 05:54:54 +00:00
|
|
|
}
|
|
|
|
if (!this.on_path) {
|
|
|
|
g.setColor(1.0, 0.0, 0.0)
|
2022-07-23 06:41:23 +00:00
|
|
|
.setFont("6x15")
|
2022-07-22 07:49:33 +00:00
|
|
|
.drawString("lost", g.getWidth() - 55, 35);
|
2022-07-22 05:54:54 +00:00
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
|
|
|
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
|
2022-07-23 09:00:59 +00:00
|
|
|
let start = Math.max(this.current_segment - 4, 0);
|
|
|
|
let end = Math.min(this.current_segment + 6, this.path.len);
|
2022-07-22 05:31:23 +00:00
|
|
|
let pos = this.position;
|
2022-07-27 13:35:00 +00:00
|
|
|
let cos = this.adjusted_cos_direction;
|
|
|
|
let sin = this.adjusted_sin_direction;
|
2022-07-22 05:31:23 +00:00
|
|
|
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;
|
2022-11-17 13:41:36 +00:00
|
|
|
let scale_factor = this.scale_factor;
|
2022-11-20 07:40:33 +00:00
|
|
|
|
|
|
|
// display direction to next point if lost
|
|
|
|
if (!this.on_path) {
|
|
|
|
let next_point = this.path.point(this.current_segment + 1);
|
|
|
|
let previous_point = this.path.point(this.current_segment);
|
|
|
|
let nearest_point;
|
|
|
|
if (
|
|
|
|
previous_point.fake_distance(this.position) <
|
|
|
|
next_point.fake_distance(this.position)
|
|
|
|
) {
|
|
|
|
nearest_point = previous_point;
|
|
|
|
} else {
|
|
|
|
nearest_point = next_point;
|
|
|
|
}
|
|
|
|
let tx = (nearest_point.lon - cx) * scale_factor;
|
|
|
|
let ty = (nearest_point.lat - cy) * scale_factor;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// now display path
|
2022-07-22 05:31:23 +00:00
|
|
|
for (let i = start; i < end; i++) {
|
2022-11-17 13:41:36 +00:00
|
|
|
let tx = (points[2 * i] - cx) * scale_factor;
|
|
|
|
let ty = (points[2 * i + 1] - cy) * scale_factor;
|
2022-07-22 05:31:23 +00:00
|
|
|
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) {
|
2022-11-20 07:40:33 +00:00
|
|
|
g.setColor(g.theme.fg);
|
2022-07-22 05:31:23 +00:00
|
|
|
g.drawLine(previous_x, previous_y, x, y);
|
2022-07-18 15:01:04 +00:00
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
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);
|
2022-07-21 15:58:12 +00:00
|
|
|
}
|
2022-11-19 12:48:26 +00:00
|
|
|
if (settings.display_points) {
|
|
|
|
g.setColor(g.theme.fg);
|
|
|
|
g.fillCircle(previous_x, previous_y, 4);
|
|
|
|
g.setColor(g.theme.bg);
|
|
|
|
g.fillCircle(previous_x, previous_y, 3);
|
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
2022-07-21 15:58:12 +00:00
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
previous_x = x;
|
|
|
|
previous_y = y;
|
2022-07-18 15:01:04 +00:00
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
|
|
|
|
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);
|
2022-07-23 09:00:59 +00:00
|
|
|
|
2022-11-19 12:48:26 +00:00
|
|
|
// display current-segment's projection
|
|
|
|
let tx = (this.projected_point.lon - cx) * scale_factor;
|
|
|
|
let ty = (this.projected_point.lat - cy) * scale_factor;
|
2022-08-14 13:58:03 +00:00
|
|
|
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);
|
2022-11-20 07:40:33 +00:00
|
|
|
g.setColor(g.theme.fgH);
|
2022-08-14 13:58:03 +00:00
|
|
|
g.fillCircle(x, y, 4);
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
2022-07-18 15:01:04 +00:00
|
|
|
}
|
2022-07-11 14:51:03 +00:00
|
|
|
|
2022-08-14 13:36:55 +00:00
|
|
|
function load_gpc(filename) {
|
|
|
|
let buffer = require("Storage").readArrayBuffer(filename);
|
2022-11-22 12:48:12 +00:00
|
|
|
let file_size = buffer.length;
|
2022-08-14 13:36:55 +00:00
|
|
|
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();
|
|
|
|
}
|
2022-07-16 14:23:01 +00:00
|
|
|
|
2022-08-14 13:36:55 +00:00
|
|
|
// 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;
|
|
|
|
|
2022-11-22 18:53:25 +00:00
|
|
|
let path_data = [
|
2022-08-14 13:36:55 +00:00
|
|
|
points,
|
|
|
|
waypoints,
|
|
|
|
interests_coordinates,
|
|
|
|
interests_types,
|
|
|
|
interests_on_path,
|
|
|
|
interests_starts,
|
|
|
|
];
|
2022-11-22 18:53:25 +00:00
|
|
|
|
|
|
|
// checksum file size
|
|
|
|
if (offset != file_size) {
|
|
|
|
console.log("invalid file size", file_size, "expected", offset);
|
|
|
|
let msg = "invalid file\nsize " + file_size + "\ninstead of"+ offset;
|
|
|
|
E.showAlert(msg).then(function() {
|
|
|
|
E.showAlert();
|
|
|
|
start_gipy(path_data);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
start_gipy(path_data);
|
|
|
|
}
|
2022-08-14 13:36:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
2022-07-20 08:52:00 +00:00
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
is_waypoint(point_index) {
|
2022-07-22 16:06:24 +00:00
|
|
|
let i = Math.floor(point_index / 8);
|
|
|
|
let subindex = point_index % 8;
|
|
|
|
let r = this.waypoints[i] & (1 << subindex);
|
|
|
|
return r != 0;
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
2022-07-16 14:23:01 +00:00
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
// 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);
|
|
|
|
}
|
2022-07-21 10:35:24 +00:00
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
interest_point(index) {
|
|
|
|
let lon = this.interests_coordinates[2 * index];
|
|
|
|
let lat = this.interests_coordinates[2 * index + 1];
|
|
|
|
return new Point(lon, lat);
|
|
|
|
}
|
2022-07-21 10:35:24 +00:00
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
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];
|
2022-11-20 11:04:49 +00:00
|
|
|
|
|
|
|
let p1 = new Point(this.points[2 * start], this.points[2 * start + 1]);
|
|
|
|
for (let i = start + 1; i < end + 1; i++) {
|
|
|
|
let p2 = new Point(this.points[2 * i], this.points[2 * i + 1]);
|
|
|
|
|
|
|
|
let closest_point = point.closest_segment_point(p1, p2);
|
|
|
|
let distance = point.length_squared(closest_point);
|
|
|
|
|
|
|
|
let dot =
|
|
|
|
cos_direction * (p2.lon - p1.lon) + sin_direction * (p2.lat - p1.lat);
|
|
|
|
let orientation = +(dot < 0); // index 0 is good orientation
|
|
|
|
if (distance <= mins[orientation]) {
|
|
|
|
mins[orientation] = distance;
|
|
|
|
indices[orientation] = i - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
p1 = p2;
|
|
|
|
}
|
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
// by default correct orientation (0) wins
|
|
|
|
// but if other one is really closer, return other one
|
2022-11-20 07:40:33 +00:00
|
|
|
if (mins[1] < mins[0] / 100.0) {
|
2022-07-28 06:23:17 +00:00
|
|
|
return [1, indices[1]];
|
2022-07-22 05:31:23 +00:00
|
|
|
} else {
|
2022-07-28 06:23:17 +00:00
|
|
|
return [0, indices[0]];
|
2022-07-16 14:23:01 +00:00
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
|
|
|
get len() {
|
|
|
|
return this.points.length / 2;
|
|
|
|
}
|
2022-07-11 14:51:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class Point {
|
2022-07-22 05:31:23 +00:00
|
|
|
constructor(lon, lat) {
|
|
|
|
this.lon = lon;
|
|
|
|
this.lat = lat;
|
|
|
|
}
|
2022-11-17 13:41:36 +00:00
|
|
|
coordinates(current_position, cos_direction, sin_direction, scale_factor) {
|
|
|
|
let translated = this.minus(current_position).times(scale_factor);
|
2022-07-22 05:31:23 +00:00
|
|
|
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) {
|
2022-11-20 07:40:33 +00:00
|
|
|
let londiff = this.lon - other_point.lon;
|
|
|
|
let latdiff = this.lat - other_point.lat;
|
|
|
|
return londiff * londiff + latdiff * latdiff;
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
|
|
|
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));
|
|
|
|
|
2022-07-22 07:49:33 +00:00
|
|
|
return R * c; // in meters
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
|
|
|
fake_distance(other_point) {
|
|
|
|
return Math.sqrt(this.length_squared(other_point));
|
|
|
|
}
|
2022-11-20 07:40:33 +00:00
|
|
|
// return closest point from 'this' on [v,w] segment.
|
|
|
|
// since this function is critical we inline all code here.
|
2022-07-22 07:49:33 +00:00
|
|
|
closest_segment_point(v, w) {
|
2022-07-22 05:31:23 +00:00
|
|
|
// 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
|
2022-11-20 07:40:33 +00:00
|
|
|
let segment_londiff = w.lon - v.lon;
|
|
|
|
let segment_latdiff = w.lat - v.lat;
|
|
|
|
let l2 =
|
|
|
|
segment_londiff * segment_londiff + segment_latdiff * segment_latdiff; // i.e. |w-v|^2 - avoid a sqrt
|
2022-07-22 05:31:23 +00:00
|
|
|
if (l2 == 0.0) {
|
2022-07-27 13:35:00 +00:00
|
|
|
return v; // v == w case
|
2022-07-16 14:23:01 +00:00
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
// 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.
|
2022-11-20 07:40:33 +00:00
|
|
|
|
|
|
|
// let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2)); //inlined below
|
|
|
|
let start_londiff = this.lon - v.lon;
|
|
|
|
let start_latdiff = this.lat - v.lat;
|
|
|
|
let t =
|
|
|
|
(start_londiff * segment_londiff + start_latdiff * segment_latdiff) / l2;
|
|
|
|
if (t < 0) {
|
|
|
|
t = 0;
|
|
|
|
} else {
|
|
|
|
if (t > 1) {
|
|
|
|
t = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let lon = v.lon + segment_londiff * t;
|
|
|
|
let lat = v.lat + segment_latdiff * t;
|
|
|
|
return new Point(lon, lat);
|
2022-07-22 07:49:33 +00:00
|
|
|
}
|
2022-07-11 14:51:03 +00:00
|
|
|
}
|
|
|
|
|
2022-07-12 18:07:25 +00:00
|
|
|
Bangle.loadWidgets();
|
2022-07-21 13:09:16 +00:00
|
|
|
|
2022-07-16 13:20:41 +00:00
|
|
|
let fake_gps_point = 0.0;
|
2022-07-18 15:01:04 +00:00
|
|
|
function simulate_gps(status) {
|
2022-11-20 07:40:33 +00:00
|
|
|
// let's keep the screen on in simulations
|
|
|
|
Bangle.setLCDTimeout(0);
|
|
|
|
Bangle.setLCDPower(1);
|
2022-07-22 05:31:23 +00:00
|
|
|
if (fake_gps_point > status.path.len - 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let point_index = Math.floor(fake_gps_point);
|
2022-11-19 12:48:26 +00:00
|
|
|
if (point_index >= status.path.len / 2 - 1) {
|
2022-07-22 05:31:23 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-11-19 12:48:26 +00:00
|
|
|
let p1 = status.path.point(2 * point_index);
|
|
|
|
let p2 = status.path.point(2 * (point_index + 1));
|
2022-07-18 15:01:04 +00:00
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
let alpha = fake_gps_point - point_index;
|
|
|
|
let pos = p1.times(1 - alpha).plus(p2.times(alpha));
|
|
|
|
let old_pos = status.position;
|
2022-07-16 14:23:01 +00:00
|
|
|
|
2022-08-26 11:24:53 +00:00
|
|
|
fake_gps_point += 0.05; // advance simulation
|
2022-11-20 07:40:33 +00:00
|
|
|
// status.update_position(new Point(1, 1), null); // uncomment to be always lost
|
2022-07-26 06:43:52 +00:00
|
|
|
status.update_position(pos, null);
|
2022-07-13 11:10:42 +00:00
|
|
|
}
|
2022-07-16 13:20:41 +00:00
|
|
|
|
2022-07-21 13:09:16 +00:00
|
|
|
function drawMenu() {
|
|
|
|
const menu = {
|
2022-07-22 05:31:23 +00:00
|
|
|
"": { title: "choose trace" },
|
2022-07-21 13:09:16 +00:00
|
|
|
};
|
|
|
|
var files = require("Storage").list(".gpc");
|
2022-07-22 05:31:23 +00:00
|
|
|
for (var i = 0; i < files.length; ++i) {
|
2022-07-21 13:09:16 +00:00
|
|
|
menu[files[i]] = start.bind(null, files[i]);
|
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
menu["Exit"] = function () {
|
|
|
|
load();
|
|
|
|
};
|
2022-07-21 13:09:16 +00:00
|
|
|
E.showMenu(menu);
|
2022-07-16 14:23:01 +00:00
|
|
|
}
|
2022-07-20 08:52:00 +00:00
|
|
|
|
2022-07-21 13:09:16 +00:00
|
|
|
function start(fn) {
|
2022-07-23 09:00:59 +00:00
|
|
|
E.showMenu();
|
2022-07-22 05:31:23 +00:00
|
|
|
console.log("loading", fn);
|
|
|
|
|
2022-11-22 18:53:25 +00:00
|
|
|
load_gpc(fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
function start_gipy(path_data) {
|
|
|
|
console.log("starting");
|
|
|
|
let path = new Path(path_data);
|
2022-07-22 05:31:23 +00:00
|
|
|
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;
|
2022-09-16 09:13:48 +00:00
|
|
|
// 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);
|
2022-07-22 05:31:23 +00:00
|
|
|
if (valid_coordinates) {
|
2022-07-26 06:43:52 +00:00
|
|
|
status.update_position(new Point(data.lon, data.lat), null);
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
|
|
|
let gps_status_color;
|
|
|
|
if (frame % 2 == 0 || valid_coordinates) {
|
|
|
|
gps_status_color = g.theme.bg;
|
|
|
|
} else {
|
|
|
|
gps_status_color = g.theme.fg;
|
2022-07-21 13:09:16 +00:00
|
|
|
}
|
2022-07-22 05:31:23 +00:00
|
|
|
g.setColor(gps_status_color)
|
|
|
|
.setFont("6x8:2")
|
|
|
|
.drawString("gps", g.getWidth() - 40, 30);
|
|
|
|
};
|
2022-07-21 13:09:16 +00:00
|
|
|
|
2022-07-22 05:31:23 +00:00
|
|
|
Bangle.setGPSPower(true, "gipy");
|
|
|
|
Bangle.on("GPS", set_coordinates);
|
2022-07-26 07:44:42 +00:00
|
|
|
Bangle.on("lock", function (on) {
|
|
|
|
if (!on) {
|
|
|
|
Bangle.setGPSPower(true, "gipy"); // activate gps when unlocking
|
|
|
|
}
|
|
|
|
});
|
2022-07-22 05:31:23 +00:00
|
|
|
}
|
2022-07-21 13:09:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let files = require("Storage").list(".gpc");
|
|
|
|
if (files.length <= 1) {
|
2022-07-22 05:31:23 +00:00
|
|
|
if (files.length == 0) {
|
|
|
|
load();
|
|
|
|
} else {
|
|
|
|
start(files[0]);
|
|
|
|
}
|
2022-07-21 13:09:16 +00:00
|
|
|
} else {
|
2022-07-22 05:31:23 +00:00
|
|
|
drawMenu();
|
2022-07-21 13:09:16 +00:00
|
|
|
}
|