forked from FOSS/BangleApps
Merge branch 'master' of github.com:espruino/BangleApps
commit
a0ea14c00a
31
apps.json
31
apps.json
|
@ -1394,7 +1394,7 @@
|
|||
"name": "Dev Stopwatch",
|
||||
"shortName":"Dev Stopwatch",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Stopwatch with 5 laps supported (cyclically replaced)",
|
||||
"tags": "stopwatch, chrono, timer, chronometer",
|
||||
"allow_emulator":true,
|
||||
|
@ -1553,7 +1553,7 @@
|
|||
"name": "BangleRun",
|
||||
"shortName": "BangleRun",
|
||||
"icon": "banglerun.png",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"interface": "interface.html",
|
||||
"description": "An app for running sessions. Displays info and logs your run for later viewing.",
|
||||
"tags": "run,running,fitness,outdoors",
|
||||
|
@ -2578,5 +2578,32 @@
|
|||
{"name":"lazyclock.app.js","url":"lazyclock-app.js"},
|
||||
{"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "astral",
|
||||
"name": "Astral Clock",
|
||||
"icon": "app-icon.png",
|
||||
"version":"0.01",
|
||||
"readme": "README.md",
|
||||
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"storage": [
|
||||
{"name":"astral.app.js","url":"app.js"},
|
||||
{"name":"astral.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "lifeclk",
|
||||
"name": "Game of Life Clock",
|
||||
"shortName":"Conway's Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.05",
|
||||
"description": "Modification and clockification of Conway's Game of Life",
|
||||
"tags": "clock",
|
||||
"type" : "clock",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"lifeclk.app.js","url":"app.js"},
|
||||
{"name":"lifeclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 675 KiB |
|
@ -0,0 +1,48 @@
|
|||
Astral Clock
|
||||
============
|
||||
Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting.
|
||||
|
||||

|
||||
<sup>(The clock does have Pluto now - felt bad for leaving it out)</sup>
|
||||
|
||||
Functions
|
||||
---------
|
||||
**BTN1**: Refreshes Alt/Az readings. The coordinates are NOT continually updated, this is to save resources and battery usage plus it avoids you having to wait for calculations to finish before you can do anything else on the watch - it doesn't take long but it could still be annoying.
|
||||
**BTN2**: Load side-menu as standard for clocks.
|
||||
**BTN3**: Changes between planet mode and extra/other targets - discussed below (will still need to press button 1 after switching to update calcs).
|
||||
**BTN4**: This is the left touchscreen, and when the LCD is on you can use this to change the font between red/white. This will only work after the GPS location has been set initially.
|
||||
|
||||
The text will turn blue during calculation and then back again once complete.
|
||||
|
||||
When you first install it, all positions will be estimated from UK as the default location and all the text will be white; from the moment you get your first GPS lock with the clock, it will save your location, recalculate accordingly and change the text to red, ideal for maintaining night vision, the calculations will also now be relevant to your location and time. If you have not used the GPS yet, I suggest using it outside briefly to get your first fix as the initial one can take a bit longer, although it should still just be a minute or 2 max normally.
|
||||
Lat and Lon are saved in a file called **astral.config**. You can review this file if you want to confirm current coordinates or even hard set different values \- although be careful doing the latter as there is no error handling to manage bad values here so you would have to delete the file and have the app generate a new one if that happens, also the GPS functionality will overwrite anything you put in here once it picks up your location.
|
||||
|
||||
There can currently be a slight error mainly to the Az at times due to a firmware issue for acos (arccosine) that affect spherical calculations but I have used an estimator function that gives a good enough accuracy for general observation so shouldn't noticeably be too far off. I\'ll be implementing acos for better accuracy when the fix is in a standard release and the update will still include the current estimate function to support a level of backward compatibility.
|
||||
|
||||
The moon phases are split into the 8 phases with an image for each - new moon would show no image.
|
||||
|
||||
The compass is displayed above the minute digits, if you get strange values or dashes the compass needs calibration but you just need to move the watch around a bit for this each time - ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or doing Wing Chun Kuen.
|
||||
|
||||
Also the compass isn\’t tilt compensated so try and keep the face parallel when taking a reading.
|
||||
|
||||
Additional Astronomy Targets
|
||||
----------------------------
|
||||
There are currently 12 extra targets as default in the config file, and these were selected based on well known named objects listed in various sources as good choices for both binoculars and telescopes. The objects are processed and then 9 are displayed and ordered descendingly by altitude on the basis those higher up will have better visibility.
|
||||
|
||||
You can input different objects rather than those listed in the galaxies/extras mode by changing the astral.config file with the relevant details for: Object name, Right Ascension and Declination, below is an example. Again, there is little in the way of error handling to streamline the app so be sure to input these in exactly the same format as you see in the file, namely signed 6 digit values with double quotes, example:
|
||||
|
||||
*{"name": "Andromeda", "ra": "004244", "de": "411609", "type": 3}*
|
||||
|
||||
The type property is not utilised as yet but relates to whether the object is (in order): a cluster, nebula or galaxy. If you try putting more than 12 or so, the clock will try processing all of them but I advise against doing that because you will get memory errors if you put in too many. A better approach is to put a limited set in seasonally based on what's best in your location.
|
||||
|
||||
Updates & Feedback
|
||||
------------------
|
||||
Put together, initially at least, by \"Ben Jabituya\", https://jabituyaben.wixsite.com/majorinput, jabituyaben@gmail.com. Feel free to get in touch for any feature request. Also I\'m not precious at all - if you know of efficiencies or improvements you could make, just put the changes in. One thing that would probably be ideal is to change some of the functions to inline C to make it faster.
|
||||
|
||||
Credit to various sources from which I have literally taken source code and shoehorned to fit on the Bangle:
|
||||
|
||||
-Stephen R. Schmitt:
|
||||
https://codepen.io/lulunac27/full/NRoyxE
|
||||
|
||||
-(Not sure who put this one together initially):
|
||||
http://www.voidware.com/moon_phase.htm
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mUyxH+AH4AG3YAGF1w0oExYykEZwyhEIyRJGUAfEYpgxjLxQNEGEajMGTohPGMBTQOZwwTGKoyXDASVWGSwtHKYYAJZbYVEGR7bSGKQWkDRQbOCAoxYRI4wMCIYxXXpQSYP6L4NCRLGXLZwdVMJwAWGKgwbD6aUTSzoRKfCAxbAogcJBxQx/GP4x/GP4xNAAoKKBxwxaGRQZPSqwZmGOZ7VY8oxnPZoJPGP57TBJavWGL7gRRaiPVGJxRGBJgxcACYxfHJIRLSrTHxGODHvGSgwcAEY="))
|
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,849 @@
|
|||
setupcomplete_colour = "#ff3329";
|
||||
default_colour = "#ffffff";
|
||||
calc_display_colour = "#00FFFF";
|
||||
display_colour = default_colour;
|
||||
|
||||
var processing = false;
|
||||
var all_extras_array = [];
|
||||
var ready_to_compute = false;
|
||||
var mode = "planetary";
|
||||
var modeswitch = false;
|
||||
|
||||
var colours_switched = false;
|
||||
|
||||
// Load fonts
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
// position on screen
|
||||
const Xaxis = 150, Yaxis = 55;
|
||||
|
||||
//lat lon settings loading
|
||||
var astral_settings;
|
||||
var config_file = require("Storage").open("astral.config.txt", "r");
|
||||
var test_file = config_file.read(config_file.getLength());
|
||||
|
||||
if (test_file !== undefined) {
|
||||
astral_settings = JSON.parse(test_file);
|
||||
if (astral_settings.astral_default)
|
||||
display_colour = default_colour;
|
||||
else
|
||||
display_colour = setupcomplete_colour;
|
||||
}
|
||||
|
||||
if (astral_settings === undefined) {
|
||||
astral_settings = {
|
||||
version: 1,
|
||||
lat: 51.9492,
|
||||
lon: 0.2834,
|
||||
astral_default: true,
|
||||
extras: [
|
||||
{ name: "Andromeda", ra: "004244", de: "411609", type: 3 },
|
||||
{ name: "Cigar", ra: "095552", de: "694047", type: 3 },
|
||||
{ name: "Pinwheel", ra: "140313", de: "542057", type: 3 },
|
||||
{ name: "Whirlpool", ra: "1329553", de: "471143", type: 3 },
|
||||
{ name: "Orion", ra: "053517", de: "-052328", type: 2 },
|
||||
{ name: "Hercules", ra: "160515", de: "174455", type: 1 },
|
||||
{ name: "Beehive", ra: "084024", de: "195900", type: 1 },
|
||||
{ name: "SilverCoin", ra: "004733", de: "-251718", type: 3 },
|
||||
{ name: "Lagoon", ra: "180337", de: "-242312", type: 2 },
|
||||
{ name: "Trifid", ra: "180223", de: " -230148", type: 2 },
|
||||
{ name: "Dumbbell", ra: "195935", de: "224316", type: 2 },
|
||||
{ name: "Pleiades", ra: "034724", de: "240700", type: 1 }
|
||||
]
|
||||
};
|
||||
config_file = require("Storage").open("astral.config.txt", "w");
|
||||
config_file.write(JSON.stringify(astral_settings));
|
||||
}
|
||||
|
||||
var compass_heading = "--";
|
||||
var current_moonphase;
|
||||
var year;
|
||||
var month;
|
||||
var day;
|
||||
var hour;
|
||||
var mins;
|
||||
var secs;
|
||||
|
||||
var calc = {
|
||||
lat_degrees: "",
|
||||
lat_minutes: "",
|
||||
lon_degrees: "",
|
||||
lon_minutes: "",
|
||||
month: "",
|
||||
day: "",
|
||||
hour: "",
|
||||
minute: "",
|
||||
second: "",
|
||||
thisday: "",
|
||||
south: "",
|
||||
north: "",
|
||||
west: "",
|
||||
east: ""
|
||||
};
|
||||
|
||||
var pstrings = [];
|
||||
|
||||
var pname = new Array("Mercury", "Venus", "Sun",
|
||||
"Mars", "Jupiter", "Saturn ",
|
||||
"Uranus ", "Neptune", "Pluto");
|
||||
|
||||
function acos_estimate(x) {
|
||||
return (-0.69813170079773212 * x * x - 0.87266462599716477) * x + 1.5707963267948966;
|
||||
}
|
||||
|
||||
function ConvertDEGToDMS(deg, lat) {
|
||||
var absolute = Math.abs(deg);
|
||||
var degrees = Math.floor(absolute);
|
||||
var minutesNotTruncated = (absolute - degrees) * 60;
|
||||
var minutes = Math.floor(minutesNotTruncated);
|
||||
return minutes;
|
||||
}
|
||||
|
||||
function test() {
|
||||
// coords = [42.407211,-71.082439];
|
||||
coords = [astral_settings.lat, astral_settings.lon];
|
||||
//coords = [-33.8688, 133.775];
|
||||
calc.lat_degrees = Math.abs(coords[0]).toFixed(0);
|
||||
calc.lon_degrees = Math.abs(coords[1]).toFixed(0);
|
||||
|
||||
calc.lat_minutes = ConvertDEGToDMS(coords[0], true).toString();
|
||||
calc.lon_minutes = ConvertDEGToDMS(coords[1]).toString();
|
||||
|
||||
if (coords[1] < 0) {
|
||||
calc.west = false;
|
||||
calc.east = true;
|
||||
}
|
||||
else {
|
||||
calc.west = true;
|
||||
calc.east = false;
|
||||
}
|
||||
if (coords[0] < 0) {
|
||||
calc.south = true;
|
||||
calc.north = false;
|
||||
}
|
||||
else {
|
||||
calc.south = false;
|
||||
calc.north = true;
|
||||
}
|
||||
}
|
||||
|
||||
var DEGS = 180 / Math.PI; // convert radians to degrees
|
||||
var RADS = Math.PI / 180; // convert degrees to radians
|
||||
var EPS = 1.0e-12; // machine error constant
|
||||
|
||||
// right ascension, declination coordinate structure
|
||||
function coord() {
|
||||
ra = parseFloat("0"); // right ascension [deg]
|
||||
dec = parseFloat("0"); // declination [deg]
|
||||
rvec = parseFloat("0"); // distance [AU]
|
||||
}
|
||||
|
||||
// altitude, azimuth coordinate structure
|
||||
function horizon() {
|
||||
alt = parseFloat("0"); // altitude [deg]
|
||||
az = parseFloat("0"); // azimuth [deg]
|
||||
}
|
||||
|
||||
// orbital element structure
|
||||
function elem() {
|
||||
a = parseFloat("0"); // semi-major axis [AU]
|
||||
e = parseFloat("0"); // eccentricity of orbit
|
||||
i = parseFloat("0"); // inclination of orbit [deg]
|
||||
O = parseFloat("0"); // longitude of the ascending node [deg]
|
||||
w = parseFloat("0"); // longitude of perihelion [deg]
|
||||
L = parseFloat("0"); // mean longitude [deg]
|
||||
}
|
||||
|
||||
function process_extras_coord(coord_string) {
|
||||
var extras_second = parseInt(coord_string.slice(-2));
|
||||
var extras_minute;
|
||||
var extras_hour;
|
||||
var extras_calc;
|
||||
|
||||
var extras_signcheck = coord_string.charAt(0);
|
||||
|
||||
if (extras_signcheck == "-") {
|
||||
extras_minute = parseInt(coord_string.slice(3, -2));
|
||||
extras_hour = parseInt(coord_string.slice(1, 3));
|
||||
extras_calc = (extras_hour + extras_minute / 60 + extras_second / 3600) * -1;
|
||||
}
|
||||
else {
|
||||
extras_minute = parseInt(coord_string.slice(2, -2));
|
||||
extras_hour = parseInt(coord_string.slice(0, 2));
|
||||
extras_calc = extras_hour + extras_minute / 60 + extras_second / 3600;
|
||||
}
|
||||
return extras_calc;
|
||||
}
|
||||
|
||||
// compute ...
|
||||
function compute() {
|
||||
var lat_degrees = parseInt(calc.lat_degrees, 10);
|
||||
var lat_minutes = parseInt(calc.lat_minutes, 10);
|
||||
var lon_degrees = parseInt(calc.lon_degrees, 10);
|
||||
var lon_minutes = parseInt(calc.lon_minutes, 10);
|
||||
|
||||
var now = new Date();
|
||||
year = now.getFullYear();
|
||||
month = now.getMonth() + 1;
|
||||
day = now.getDay();
|
||||
hour = now.getHours();
|
||||
mins = now.getMinutes();
|
||||
secs = now.getSeconds();
|
||||
|
||||
if (isNaN(lat_degrees) || (lat_degrees < 0) || (lat_degrees >= 90) ||
|
||||
isNaN(lat_minutes) || (lat_minutes < 0) || (lat_minutes >= 60) ||
|
||||
isNaN(lon_degrees) || (lon_degrees < 0) || (lon_degrees >= 180) ||
|
||||
isNaN(lon_minutes) || (lon_minutes < 0) || (lon_minutes >= 60)) {
|
||||
print("Invalid input!");
|
||||
return;
|
||||
}
|
||||
|
||||
var lat = dms2real(lat_degrees, lat_minutes, 0);
|
||||
var lon = dms2real(lon_degrees, lon_minutes, 0);
|
||||
if (calc.south == true) lat = -lat;
|
||||
if (calc.west == true) lon = -lon;
|
||||
|
||||
// compute day number for date/time
|
||||
var dn = day_number(year, month, day, hour, mins);
|
||||
|
||||
var p;
|
||||
var obj = new coord();
|
||||
var h = new horizon();
|
||||
|
||||
pstrings = [];
|
||||
|
||||
if (mode == "planetary") {
|
||||
for (p = 0; p < 9; p++) {
|
||||
get_coord(obj, p, dn);
|
||||
coord_to_horizon(now, obj.ra, obj.dec, lat, lon, h);
|
||||
display_string = (pname[p] + " " + dec2str(h.alt) + " " + degr2str(h.az));
|
||||
|
||||
pstrings.push(display_string);
|
||||
}
|
||||
}
|
||||
else {
|
||||
all_extras_arrray = [];
|
||||
for (p = 0; p < astral_settings.extras.length; p++) {
|
||||
var extras_ra = process_extras_coord(astral_settings.extras[p].ra);
|
||||
extras_ra *= 15;
|
||||
|
||||
var extras_dec = process_extras_coord(astral_settings.extras[p].de);
|
||||
|
||||
coord_to_horizon(now, extras_ra, extras_dec, lat, lon, h);
|
||||
display_string = (astral_settings.extras[p].name + " " + dec2str(h.alt) + " " + degr2str(h.az));
|
||||
|
||||
all_extras_array.push([h.alt, display_string]);
|
||||
}
|
||||
|
||||
all_extras_array.sort(function (a, b) {
|
||||
return b[0] - a[0];
|
||||
});
|
||||
|
||||
for (p = 0; p < 9; p++) {
|
||||
pstrings.push(all_extras_array[p][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// day number to/from J2000 (Jan 1.5, 2000)
|
||||
function day_number(y, m, d, hour, mins) {
|
||||
var h = hour + mins / 60;
|
||||
var rv = 367 * y
|
||||
- Math.floor(7 * (y + Math.floor((m + 9) / 12)) / 4)
|
||||
+ Math.floor(275 * m / 9) + d - 730531.5 + h / 24;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// compute RA, DEC, and distance of planet-p for day number-d
|
||||
// result returned in structure obj in degrees and astronomical units
|
||||
function get_coord(obj, p, d) {
|
||||
var planet = new elem();
|
||||
mean_elements(planet, p, d);
|
||||
var ap = planet.a;
|
||||
var ep = planet.e;
|
||||
var ip = planet.i;
|
||||
var op = planet.O;
|
||||
var pp = planet.w;
|
||||
var lp = planet.L;
|
||||
|
||||
var earth = new elem();
|
||||
mean_elements(earth, 2, d);
|
||||
var ae = earth.a;
|
||||
var ee = earth.e;
|
||||
var ie = earth.i;
|
||||
var oe = earth.O;
|
||||
var pe = earth.w;
|
||||
var le = earth.L;
|
||||
|
||||
// position of Earth in its orbit
|
||||
var me = mod2pi(le - pe);
|
||||
var ve = true_anomaly(me, ee);
|
||||
var re = ae * (1 - ee * ee) / (1 + ee * Math.cos(ve));
|
||||
|
||||
// heliocentric rectangular coordinates of Earth
|
||||
var xe = re * Math.cos(ve + pe);
|
||||
var ye = re * Math.sin(ve + pe);
|
||||
var ze = 0.0;
|
||||
|
||||
// position of planet in its orbit
|
||||
var mp = mod2pi(lp - pp);
|
||||
var vp = true_anomaly(mp, planet.e);
|
||||
var rp = ap * (1 - ep * ep) / (1 + ep * Math.cos(vp));
|
||||
|
||||
// heliocentric rectangular coordinates of planet
|
||||
var xh = rp * (Math.cos(op) * Math.cos(vp + pp - op) - Math.sin(op) * Math.sin(vp + pp - op) * Math.cos(ip));
|
||||
var yh = rp * (Math.sin(op) * Math.cos(vp + pp - op) + Math.cos(op) * Math.sin(vp + pp - op) * Math.cos(ip));
|
||||
var zh = rp * (Math.sin(vp + pp - op) * Math.sin(ip));
|
||||
|
||||
if (p == 2) // earth --> compute sun
|
||||
{
|
||||
xh = 0;
|
||||
yh = 0;
|
||||
zh = 0;
|
||||
}
|
||||
|
||||
// convert to geocentric rectangular coordinates
|
||||
var xg = xh - xe;
|
||||
var yg = yh - ye;
|
||||
var zg = zh - ze;
|
||||
|
||||
// rotate around x axis from ecliptic to equatorial coords
|
||||
var ecl = 23.439281 * RADS; //value for J2000.0 frame
|
||||
var xeq = xg;
|
||||
var yeq = yg * Math.cos(ecl) - zg * Math.sin(ecl);
|
||||
var zeq = yg * Math.sin(ecl) + zg * Math.cos(ecl);
|
||||
|
||||
// find the RA and DEC from the rectangular equatorial coords
|
||||
obj.ra = mod2pi(Math.atan2(yeq, xeq)) * DEGS;
|
||||
obj.dec = Math.atan(zeq / Math.sqrt(xeq * xeq + yeq * yeq)) * DEGS;
|
||||
obj.rvec = Math.sqrt(xeq * xeq + yeq * yeq + zeq * zeq);
|
||||
}
|
||||
|
||||
// Compute the elements of the orbit for planet-i at day number-d
|
||||
// result is returned in structure p
|
||||
function mean_elements(p, i, d) {
|
||||
var cy = d / 36525; // centuries since J2000
|
||||
|
||||
switch (i) {
|
||||
case 0: // Mercury
|
||||
p.a = 0.38709893 + 0.00000066 * cy;
|
||||
p.e = 0.20563069 + 0.00002527 * cy;
|
||||
p.i = (7.00487 - 23.51 * cy / 3600) * RADS;
|
||||
p.O = (48.33167 - 446.30 * cy / 3600) * RADS;
|
||||
p.w = (77.45645 + 573.57 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((252.25084 + 538101628.29 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 1: // Venus
|
||||
p.a = 0.72333199 + 0.00000092 * cy;
|
||||
p.e = 0.00677323 - 0.00004938 * cy;
|
||||
p.i = (3.39471 - 2.86 * cy / 3600) * RADS;
|
||||
p.O = (76.68069 - 996.89 * cy / 3600) * RADS;
|
||||
p.w = (131.53298 - 108.80 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((181.97973 + 210664136.06 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 2: // Earth/Sun
|
||||
p.a = 1.00000011 - 0.00000005 * cy;
|
||||
p.e = 0.01671022 - 0.00003804 * cy;
|
||||
p.i = (0.00005 - 46.94 * cy / 3600) * RADS;
|
||||
p.O = (-11.26064 - 18228.25 * cy / 3600) * RADS;
|
||||
p.w = (102.94719 + 1198.28 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((100.46435 + 129597740.63 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 3: // Mars
|
||||
p.a = 1.52366231 - 0.00007221 * cy;
|
||||
p.e = 0.09341233 + 0.00011902 * cy;
|
||||
p.i = (1.85061 - 25.47 * cy / 3600) * RADS;
|
||||
p.O = (49.57854 - 1020.19 * cy / 3600) * RADS;
|
||||
p.w = (336.04084 + 1560.78 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((355.45332 + 68905103.78 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 4: // Jupiter
|
||||
p.a = 5.20336301 + 0.00060737 * cy;
|
||||
p.e = 0.04839266 - 0.00012880 * cy;
|
||||
p.i = (1.30530 - 4.15 * cy / 3600) * RADS;
|
||||
p.O = (100.55615 + 1217.17 * cy / 3600) * RADS;
|
||||
p.w = (14.75385 + 839.93 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((34.40438 + 10925078.35 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 5: // Saturn
|
||||
p.a = 9.53707032 - 0.00301530 * cy;
|
||||
p.e = 0.05415060 - 0.00036762 * cy;
|
||||
p.i = (2.48446 + 6.11 * cy / 3600) * RADS;
|
||||
p.O = (113.71504 - 1591.05 * cy / 3600) * RADS;
|
||||
p.w = (92.43194 - 1948.89 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((49.94432 + 4401052.95 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 6: // Uranus
|
||||
p.a = 19.19126393 + 0.00152025 * cy;
|
||||
p.e = 0.04716771 - 0.00019150 * cy;
|
||||
p.i = (0.76986 - 2.09 * cy / 3600) * RADS;
|
||||
p.O = (74.22988 - 1681.40 * cy / 3600) * RADS;
|
||||
p.w = (170.96424 + 1312.56 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((313.23218 + 1542547.79 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 7: // Neptune
|
||||
p.a = 30.06896348 - 0.00125196 * cy;
|
||||
p.e = 0.00858587 + 0.00002510 * cy;
|
||||
p.i = (1.76917 - 3.64 * cy / 3600) * RADS;
|
||||
p.O = (131.72169 - 151.25 * cy / 3600) * RADS;
|
||||
p.w = (44.97135 - 844.43 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((304.88003 + 786449.21 * cy / 3600) * RADS);
|
||||
break;
|
||||
case 8: // Pluto
|
||||
p.a = 39.48168677 - 0.00076912 * cy;
|
||||
p.e = 0.24880766 + 0.00006465 * cy;
|
||||
p.i = (17.14175 + 11.07 * cy / 3600) * RADS;
|
||||
p.O = (110.30347 - 37.33 * cy / 3600) * RADS;
|
||||
p.w = (224.06676 - 132.25 * cy / 3600) * RADS;
|
||||
p.L = mod2pi((238.92881 + 522747.90 * cy / 3600) * RADS);
|
||||
break;
|
||||
default:
|
||||
print("function mean_elements() failed!");
|
||||
}
|
||||
}
|
||||
|
||||
// compute the true anomaly from mean anomaly using iteration
|
||||
// M - mean anomaly in radians
|
||||
// e - orbit eccentricity
|
||||
function true_anomaly(M, e) {
|
||||
var V, E1;
|
||||
|
||||
// initial approximation of eccentric anomaly
|
||||
var E = M + e * Math.sin(M) * (1.0 + e * Math.cos(M));
|
||||
|
||||
do // iterate to improve accuracy
|
||||
{
|
||||
E1 = E;
|
||||
E = E1 - (E1 - e * Math.sin(E1) - M) / (1 - e * Math.cos(E1));
|
||||
}
|
||||
while (Math.abs(E - E1) > EPS);
|
||||
|
||||
// convert eccentric anomaly to true anomaly
|
||||
V = 2 * Math.atan(Math.sqrt((1 + e) / (1 - e)) * Math.tan(0.5 * E));
|
||||
|
||||
if (V < 0) V = V + (2 * Math.PI); // modulo 2pi
|
||||
|
||||
return V;
|
||||
}
|
||||
|
||||
// converts hour angle in degrees into hour angle string
|
||||
function ha2str(x) {
|
||||
if ((x < 0) || (360 < x)) print("function ha2str() range error!");
|
||||
|
||||
var ra = x / 15; // degrees to hours
|
||||
var h = Math.floor(ra);
|
||||
var m = 60 * (ra - h);
|
||||
return cintstr(h, 3) + "h " + frealstr(m, 4, 1) + "m";
|
||||
}
|
||||
|
||||
// converts declination angle in degrees into string
|
||||
function dec2str(x) {
|
||||
if ((x < -90) || (+90 < x)) print("function dec2str() range error!");
|
||||
|
||||
var dec = Math.abs(x);
|
||||
var sgn = (x < 0) ? "-" : " ";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return sgn + cintstr(d, 2) + "° " + frealstr(m, 4, 1) + "'";
|
||||
}
|
||||
|
||||
// return the integer part of a number
|
||||
function abs_floor(x) {
|
||||
var r;
|
||||
if (x >= 0.0) r = Math.floor(x);
|
||||
else r = Math.ceil(x);
|
||||
return r;
|
||||
}
|
||||
|
||||
// return an angle in the range 0 to 2pi radians
|
||||
function mod2pi(x) {
|
||||
var b = x / (2 * Math.PI);
|
||||
var a = (2 * Math.PI) * (b - abs_floor(b));
|
||||
if (a < 0) a = (2 * Math.PI) + a;
|
||||
return a;
|
||||
}
|
||||
|
||||
//
|
||||
// compute horizon coordinates from ra, dec, lat, lon, and utc
|
||||
// ra, dec, lat, lon in degrees
|
||||
// utc is a time number in seconds
|
||||
//
|
||||
// results returned in h : horizon record structure
|
||||
//
|
||||
function coord_to_horizon(utc, ra, dec, lat, lon, h) {
|
||||
var lmst, ha, sin_alt, cos_az, alt, az;
|
||||
|
||||
// compute hour angle in degrees
|
||||
ha = mean_sidereal_time(0) - ra;
|
||||
//ha = mean_sidereal_time(lon) - ra;
|
||||
if (ha < 0) ha = ha + 360;
|
||||
|
||||
// convert degrees to radians
|
||||
ha = ha * RADS;
|
||||
dec = dec * RADS;
|
||||
lat = lat * RADS;
|
||||
|
||||
|
||||
// compute altitude in radians
|
||||
sin_alt = Math.sin(dec) * Math.sin(lat) + Math.cos(dec) * Math.cos(lat) * Math.cos(ha);
|
||||
alt = Math.asin(sin_alt);
|
||||
|
||||
// compute azimuth in radians
|
||||
// divide by zero error at poles or if alt = 90 deg
|
||||
cos_az = (Math.sin(dec) - Math.sin(alt) * Math.sin(lat)) / (Math.cos(alt) * Math.cos(lat));
|
||||
//az = Math.acos(cos_az);
|
||||
|
||||
az = acos_estimate(cos_az);
|
||||
|
||||
// convert radians to degrees
|
||||
h.alt = alt * DEGS;
|
||||
h.az = az * DEGS;
|
||||
|
||||
// choose hemisphere
|
||||
if (Math.sin(ha) > 0) h.az = 360 - h.az;
|
||||
}
|
||||
|
||||
//
|
||||
// "mean_sidereal_time" returns the Mean Sidereal Time in units of degrees.
|
||||
// Use lon = 0 to get the Greenwich MST.
|
||||
// East longitudes are positive; West longitudes are negative
|
||||
//
|
||||
// returns: time in degrees
|
||||
//
|
||||
function mean_sidereal_time(lon) {
|
||||
|
||||
if ((month == 1) || (month == 2)) {
|
||||
year = year - 1;
|
||||
month = month + 12;
|
||||
}
|
||||
|
||||
var a = Math.floor(year / 100);
|
||||
// var a = Math.floor(2019 / 100);
|
||||
|
||||
var b = 2 - a + Math.floor(a / 4);
|
||||
var c = Math.floor(365.25 * year);
|
||||
var da = Math.floor(30.6001 * (month + 1));
|
||||
|
||||
// days since J2000.0
|
||||
var jd = b + c + da - 730550.5 + day
|
||||
+ (hour + mins / 60.0 + secs / 3600.0) / 24.0;
|
||||
|
||||
// julian centuries since J2000.0
|
||||
var jt = jd / 36525.0;
|
||||
|
||||
// mean sidereal time
|
||||
var mst = 280.46061837 + 360.98564736629 * jd
|
||||
+ 0.000387933 * jt * jt - jt * jt * jt / 38710000 + lon;
|
||||
|
||||
if (mst > 0.0) {
|
||||
while (mst > 360.0)
|
||||
mst = mst - 360.0;
|
||||
}
|
||||
else {
|
||||
while (mst < 0.0)
|
||||
mst = mst + 360.0;
|
||||
}
|
||||
return mst;
|
||||
}
|
||||
|
||||
// convert angle (deg, min, sec) to degrees as real
|
||||
function dms2real(deg, min, sec) {
|
||||
var rv;
|
||||
if (deg < 0) rv = deg - min / 60 - sec / 3600;
|
||||
else rv = deg + min / 60 + sec / 3600;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// converts angle in degrees into string
|
||||
function degr2str(x) {
|
||||
var dec = Math.abs(x);
|
||||
var sgn = (x < 0) ? "-" : " ";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return sgn + cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'";
|
||||
}
|
||||
|
||||
// converts latitude in signed degrees into string
|
||||
function lat2str(x) {
|
||||
var dec = Math.abs(x);
|
||||
var sgn = (x < 0) ? " S" : " N";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'" + sgn;
|
||||
}
|
||||
|
||||
// converts longitude in signed degrees into string
|
||||
function lon2str(x) {
|
||||
var dec = Math.abs(x);
|
||||
var sgn = (x < 0) ? " W" : " E";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'" + sgn;
|
||||
}
|
||||
|
||||
// format two digits with leading zero if needed
|
||||
function d2(n) {
|
||||
if ((n < 0) || (99 < n)) return "xx";
|
||||
return (n < 10) ? ("0" + n) : n;
|
||||
}
|
||||
|
||||
// UTILITY FUNCTIONS
|
||||
|
||||
// format an integer
|
||||
function cintstr(num, width) {
|
||||
var str = num.toString(10);
|
||||
var len = str.length;
|
||||
var intgr = "";
|
||||
var i;
|
||||
|
||||
for (i = 0; i < width - len; i++) // append leading spaces
|
||||
intgr += ' ';
|
||||
|
||||
for (i = 0; i < len; i++) // append digits
|
||||
intgr += str.charAt(i);
|
||||
|
||||
return intgr;
|
||||
}
|
||||
|
||||
function frealstr(num, width, fract) {
|
||||
var str = num.toFixed(fract);
|
||||
var len = str.length;
|
||||
var real = "";
|
||||
var i;
|
||||
|
||||
for (i = 0; i < width - len; i++) // append leading spaces
|
||||
real += ' ';
|
||||
|
||||
for (i = 0; i < len; i++) // append digits
|
||||
real += str.charAt(i);
|
||||
|
||||
return real;
|
||||
}
|
||||
|
||||
function getMoonPhase() {
|
||||
var now = new Date();
|
||||
year = now.getFullYear();
|
||||
month = now.getMonth() + 1;
|
||||
day = now.getDate();
|
||||
|
||||
if (month < 3) {
|
||||
year = year - 1;
|
||||
month += 12;
|
||||
}
|
||||
month = month + 1;
|
||||
c = 365.25 * year;
|
||||
e = 30.6 * month;
|
||||
jd = c + e + day - 694039.09; //jd is total days elapsed
|
||||
jd /= 29.5305882; //divide by the moon cycle
|
||||
b = parseInt(jd); //int(jd) -> b, take integer part of jd
|
||||
jd -= b; //subtract integer part to leave fractional part of original jd
|
||||
b = Math.round(jd * 8); //scale fraction from 0-8 and round
|
||||
if (b >= 8) {
|
||||
b = 0; //0 and 8 are the same so turn 8 into 0
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
function write_refresh_note(colour) {
|
||||
g.setColor(colour);
|
||||
cursor = Yaxis + 50;
|
||||
if (!ready_to_compute) {
|
||||
g.drawString("mode change:", Xaxis + 50, cursor, false);
|
||||
cursor += 15;
|
||||
g.drawString("BTN1 to refresh", Xaxis + 50, cursor, true /*clear background*/);
|
||||
cursor += 15;
|
||||
g.drawString("BTN3 to cancel", Xaxis + 50, cursor, true /*clear background*/);
|
||||
}
|
||||
else
|
||||
g.drawString("updating, please wait", Xaxis + 50, cursor, false);
|
||||
}
|
||||
|
||||
function draw_moon(phase) {
|
||||
g.setColor(display_colour);
|
||||
if (phase == 5) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(220, 20, 240, 90);
|
||||
}
|
||||
else if (phase == 6) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(200, 20, 240, 90);
|
||||
}
|
||||
else if (phase == 1) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillCircle(180, Yaxis, 30);
|
||||
}
|
||||
else if (phase == 4)
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
else if (phase == 3) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(160, 20, 180, 90);
|
||||
}
|
||||
else if (phase == 2) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(160, 20, 200, 90);
|
||||
}
|
||||
else if (phase == 7) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.setColor("#000000");
|
||||
g.fillCircle(220, Yaxis, 30);
|
||||
}
|
||||
g.setColor(display_colour);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (astral_settings.astral_default)
|
||||
display_colour = default_colour;
|
||||
else if (!colours_switched)
|
||||
display_colour = setupcomplete_colour;
|
||||
|
||||
// work out how to display the current time
|
||||
var d = new Date();
|
||||
var h = d.getHours(), m = d.getMinutes();
|
||||
var time = (" " + h).substr(-2) + ":" + ("0" + m).substr(-2);
|
||||
// Reset the state of the graphics library
|
||||
g.reset();
|
||||
g.setColor(display_colour);
|
||||
// draw the current time (4x size 7 segment)
|
||||
g.setFont("7x11Numeric7Seg", 5);
|
||||
g.setFontAlign(1, 1); // align right bottom
|
||||
g.drawString(time, Xaxis + 20, Yaxis + 30, true /*clear background*/);
|
||||
|
||||
g.setFont("6x8");
|
||||
g.setFontAlign(1, 1); // align center bottom
|
||||
// pad the date - this clears the background if the date were to change length
|
||||
var dateStr = " " + require("locale").date(d) + " ";
|
||||
g.drawString(dateStr, Xaxis - 40, Yaxis - 40, true /*clear background*/);
|
||||
|
||||
//compute location of objects
|
||||
g.setFontAlign(1, 1);
|
||||
g.setFont("6x8");
|
||||
|
||||
if (ready_to_compute)
|
||||
g.setColor(calc_display_colour);
|
||||
|
||||
if (modeswitch)
|
||||
g.setColor("#000000");
|
||||
|
||||
cursor = Yaxis + 50;
|
||||
if (pstrings.length == 0) {
|
||||
if (ready_to_compute)
|
||||
g.drawString("updating, please wait", Xaxis + 50, cursor, true);
|
||||
else
|
||||
g.drawString("press BTN1 to update", Xaxis + 50, cursor, true /*clear background*/);
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < pstrings.length; i++) {
|
||||
g.drawString(pstrings[i], Xaxis + 50, cursor, true /*clear background*/);
|
||||
cursor += 15;
|
||||
}
|
||||
}
|
||||
|
||||
if (modeswitch)
|
||||
if (ready_to_compute)
|
||||
write_refresh_note(calc_display_colour);
|
||||
else
|
||||
write_refresh_note(display_colour);
|
||||
|
||||
if (ready_to_compute) {
|
||||
processing = true;
|
||||
ready_to_compute = false;
|
||||
test();
|
||||
compute();
|
||||
g.setColor("#000000");
|
||||
g.fillRect(Xaxis - 150, Yaxis + 40, Xaxis + 200, Yaxis + 200);
|
||||
modeswitch = false;
|
||||
processing = false;
|
||||
Bangle.buzz();
|
||||
}
|
||||
|
||||
current_moonphase = getMoonPhase();
|
||||
all_extras_array = [];
|
||||
}
|
||||
|
||||
g.clear();
|
||||
current_moonphase = getMoonPhase();
|
||||
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
draw_moon(current_moonphase);
|
||||
draw();
|
||||
|
||||
var secondInterval = setInterval(draw, 1000);
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower', on => {
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
Bangle.setCompassPower(0);
|
||||
if (!astral_settings.astral_default)
|
||||
Bangle.setGPSPower(0);
|
||||
if (on) {
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
if (current_moonphase !== undefined) {
|
||||
draw_moon(current_moonphase);
|
||||
}
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
|
||||
// Buttons
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
|
||||
setWatch(function () {
|
||||
if (!processing) {
|
||||
if (!modeswitch) {
|
||||
modeswitch = true;
|
||||
if (mode == "planetary") mode = "extras";
|
||||
else mode = "planetary";
|
||||
}
|
||||
else
|
||||
modeswitch = false;
|
||||
}
|
||||
}, BTN3, { repeat: true });
|
||||
|
||||
setWatch(function () {
|
||||
if (!processing)
|
||||
ready_to_compute = true;
|
||||
}, BTN1, { repeat: true });
|
||||
|
||||
setWatch(function () {
|
||||
if (!astral_settings.astral_default) {
|
||||
colours_switched = true;
|
||||
if (display_colour == setupcomplete_colour)
|
||||
display_colour = default_colour;
|
||||
else
|
||||
display_colour = setupcomplete_colour;
|
||||
}
|
||||
}, BTN4, { repeat: true });
|
||||
|
||||
//events
|
||||
Bangle.on('mag', function (m) {
|
||||
if (isNaN(m.heading))
|
||||
compass_heading = "--";
|
||||
else
|
||||
compass_heading = 360 - Math.round(m.heading);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(100, 10, 140, 20);
|
||||
g.setColor(display_colour);
|
||||
g.drawString(" " + (compass_heading) + " ", 130, 20, true /*clear background*/);
|
||||
});
|
||||
|
||||
Bangle.on('GPS', function (g) {
|
||||
if (g.fix) {
|
||||
astral_settings.lat = g.lat;
|
||||
astral_settings.lon = g.lon;
|
||||
astral_settings.astral_default = false;
|
||||
config_file = require("Storage").open("astral.config.txt", "w");
|
||||
config_file.write(JSON.stringify(astral_settings));
|
||||
}
|
||||
});
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Fix distance >=10 km (fix #529)
|
||||
0.04: Use offscreen buffer for flickerless updates
|
||||
0.05: Complete rewrite. New UI, GPS & HRM Kalman filters, activity logging
|
||||
0.06: Reading HDOP directly from the GPS event (needs Espruino 2v07 or above)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# BangleRun
|
||||
|
||||
An app for running sessions. Displays info and logs your run for later viewing.
|
||||
|
||||
## Compilation
|
||||
|
||||
The app is written in Typescript, and needs to be transpiled in order to be
|
||||
run on the BangleJS. The easiest way to perform this step is by using the
|
||||
ubiquitous [NPM package manager](https://www.npmjs.com/get-npm).
|
||||
|
||||
After having installed NPM for your platform, checkout the `BangleApps` repo,
|
||||
open a terminal, and navigate into the `apps/banglerun` folder. Then issue:
|
||||
|
||||
```
|
||||
npm i
|
||||
```
|
||||
|
||||
to install the project's build tools, and:
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
To build the app. The last command will generate the `app.js` file, containing
|
||||
the transpiled code for the BangleJS.
|
|
@ -1 +1 @@
|
|||
!function(){"use strict";const t={STOP:63488,PAUSE:65504,RUN:2016};function n(t,n,r){g.setColor(0),g.fillRect(n-60,r,n+60,r+30),g.setColor(65535),g.drawString(t,n,r)}function r(r){var e;g.setFontVector(30),g.setFontAlign(0,-1,0),n((r.distance/1e3).toFixed(2),60,55),n(function(t){const n=Math.round(t),r=Math.floor(n/3600),e=Math.floor(n/60)%60,a=n%60;return(r?r+":":"")+("0"+e).substr(-2)+":"+("0"+a).substr(-2)}(r.duration),180,55),n(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),r=Math.floor(n/60),e=n%60;return("0"+r).substr(-2)+"'"+("0"+e).substr(-2)+'"'}(r.speed),60,115),n(r.hr.toFixed(0),180,115),n(r.steps.toFixed(0),60,175),n(r.cadence.toFixed(0),180,175),g.setFont("6x8",2),g.setColor(r.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(e=new Date).getHours()).substr(-2)+":"+("0"+e.getMinutes()).substr(-2),120,220),g.setColor(t[r.status]),g.fillRect(160,216,240,240),g.setColor(0),g.drawString(r.status,200,220)}function e(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),r(t),Bangle.drawWidgets()}var a;function o(t){t.status===a.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),r=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(r,"w"),t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(","))}(t),t.status===a.Running?t.status=a.Paused:t.status=a.Running,r(t)}!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(a||(a={}));function s(t){const n=t.indexOf(".")-2;return(parseInt(t.substr(0,n))+parseFloat(t.substr(n))/60)*Math.PI/180}const i={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,v:NaN,t:NaN,dt:NaN,pError:NaN,vError:NaN,hr:60,hrError:100,file:null,drawing:!1,status:a.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var d;d=i,Bangle.on("GPS-raw",t=>function(t,n){const e=n.split(",");switch(e[0].substr(3,3)){case"GGA":t.lat=s(e[2])*("N"===e[3]?1:-1),t.lon=s(e[4])*("E"===e[5]?1:-1),t.alt=parseFloat(e[9]);break;case"VTG":t.vel=parseFloat(e[7])/3.6;break;case"GSA":t.fix=parseInt(e[2]),t.dop=parseFloat(e[15]);break;case"GLL":t.gpsValid=3===t.fix&&t.dop<=5,function(t){const n=Date.now(),r=(n-t.t)/1e3;if(t.t=n,t.dt+=r,t.status===a.Running&&(t.duration+=r),!t.gpsValid)return;const e=6371008.8+t.alt,o=e*Math.cos(t.lat)*Math.cos(t.lon),s=e*Math.cos(t.lat)*Math.sin(t.lon),i=e*Math.sin(t.lat),d=t.vel;if(!t.x)return t.x=o,t.y=s,t.z=i,t.v=d,t.pError=2.5*t.dop,void(t.vError=.05*t.dop);const u=o-t.x,l=s-t.y,g=i-t.z,c=d-t.v,p=Math.sqrt(u*u+l*l+g*g),f=Math.abs(c);t.pError+=t.v*t.dt,t.dt=0;const N=p+2.5*t.dop,h=f+.05*t.dop,S=t.pError/(t.pError+N),E=t.vError/(t.vError+h);t.x+=u*S,t.y+=l*S,t.z+=g*S,t.v+=c*E,t.pError+=(N-t.pError)*S,t.vError+=(h-t.vError)*E;const w=Math.sqrt(t.x*t.x+t.y*t.y+t.z*t.z);t.lat=180*Math.asin(t.z/w)/Math.PI,t.lon=180*Math.atan2(t.y,t.x)/Math.PI||0,t.alt=w-6371008.8,t.status===a.Running&&(t.distance+=p*S,t.speed=t.distance/t.duration||0,t.cadence=60*t.steps/t.duration||0)}(t),r(t),t.gpsValid&&t.status===a.Running&&function(t){t.file.write("\n"),t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(","))}(t)}}(d,t)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const r=n.bpm-t.hr,e=Math.abs(r)+101-n.confidence,a=t.hrError/(t.hrError+e);t.hr+=r*a,t.hrError+=(e-t.hrError)*a}(t,n)),Bangle.setHRMPower(1)}(i),function(t){Bangle.on("step",()=>function(t){t.status===a.Running&&(t.steps+=1)}(t))}(i),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&e(t)}),e(t)}(i),setWatch(()=>o(i),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(t){t.status===a.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(t),t.status===a.Running?t.status=a.Paused:t.status=a.Stopped,r(t)}(i),BTN3,{repeat:!0,edge:"falling"})}();
|
||||
!function(){"use strict";const t={STOP:63488,PAUSE:65504,RUN:2016};function n(t,n,e){g.setColor(0),g.fillRect(n-60,e,n+60,e+30),g.setColor(65535),g.drawString(t,n,e)}function e(e){var r;g.setFontVector(30),g.setFontAlign(0,-1,0),n((e.distance/1e3).toFixed(2),60,55),n(function(t){const n=Math.round(t),e=Math.floor(n/3600),r=Math.floor(n/60)%60,a=n%60;return(e?e+":":"")+("0"+r).substr(-2)+":"+("0"+a).substr(-2)}(e.duration),180,55),n(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),e=Math.floor(n/60),r=n%60;return("0"+e).substr(-2)+"'"+("0"+r).substr(-2)+'"'}(e.speed),60,115),n(e.hr.toFixed(0),180,115),n(e.steps.toFixed(0),60,175),n(e.cadence.toFixed(0),180,175),g.setFont("6x8",2),g.setColor(e.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(r=new Date).getHours()).substr(-2)+":"+("0"+r.getMinutes()).substr(-2),120,220),g.setColor(t[e.status]),g.fillRect(160,216,240,240),g.setColor(0),g.drawString(e.status,200,220)}function r(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),e(t),Bangle.drawWidgets()}var a;function s(t){t.status===a.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),e=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(e,"w"),t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n")}(t),t.status===a.Running?t.status=a.Paused:t.status=a.Running,e(t)}!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(a||(a={}));const o={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,v:NaN,t:NaN,dt:NaN,pError:NaN,vError:NaN,hr:60,hrError:100,file:null,drawing:!1,status:a.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var i;i=o,Bangle.on("GPS",t=>function(t,n){t.lat=n.lat,t.lon=n.lon,t.alt=n.alt,t.vel=n.speed/3.6,t.fix=n.fix,t.dop=n.hdop}(i,t)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const e=n.bpm-t.hr,r=Math.abs(e)+101-n.confidence,a=t.hrError/(t.hrError+r);t.hr+=e*a,t.hrError+=(r-t.hrError)*a}(t,n)),Bangle.setHRMPower(1)}(o),function(t){Bangle.on("step",()=>function(t){t.status===a.Running&&(t.steps+=1)}(t))}(o),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&r(t)}),r(t)}(o),setWatch(()=>s(o),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(t){t.status===a.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(t),t.status===a.Running?t.status=a.Paused:t.status=a.Stopped,e(t)}(o),BTN3,{repeat:!0,edge:"falling"})}();
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
"build": "rollup -c",
|
||||
"test": "ts-node -P tsconfig.spec.json node_modules/jasmine/bin/jasmine --config=jasmine.json"
|
||||
},
|
||||
"author": "Stefano Baldan",
|
||||
"author": {
|
||||
"name": "Stefano Baldan",
|
||||
"email": "singintime@gmail.com"
|
||||
},
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-typescript": "^4.1.1",
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
import { draw } from './display';
|
||||
import { updateLog } from './log';
|
||||
import { ActivityStatus, AppState } from './state';
|
||||
|
||||
declare var Bangle: any;
|
||||
|
||||
interface GpsEvent {
|
||||
lat: number;
|
||||
lon: number;
|
||||
alt: number;
|
||||
speed: number;
|
||||
hdop: number;
|
||||
fix: number;
|
||||
}
|
||||
|
||||
const EARTH_RADIUS = 6371008.8;
|
||||
const POS_ACCURACY = 2.5;
|
||||
const VEL_ACCURACY = 0.05;
|
||||
|
||||
function initGps(state: AppState): void {
|
||||
Bangle.on('GPS-raw', (nmea: string) => parseNmea(state, nmea));
|
||||
Bangle.on('GPS', (gps: GpsEvent) => readGps(state, gps));
|
||||
Bangle.setGPSPower(1);
|
||||
}
|
||||
|
||||
|
@ -20,38 +27,13 @@ function parseCoordinate(coordinate: string): number {
|
|||
return (degrees + minutes) * Math.PI / 180;
|
||||
}
|
||||
|
||||
function parseNmea(state: AppState, nmea: string): void {
|
||||
const tokens = nmea.split(',');
|
||||
const sentence = tokens[0].substr(3, 3);
|
||||
|
||||
// FIXME: Bangle.js reports HDOP from GGA - can this be used instead
|
||||
// of manually parsing all of the raw GPS data, which can cause FIFO_FULL
|
||||
// errors?
|
||||
|
||||
switch (sentence) {
|
||||
case 'GGA':
|
||||
state.lat = parseCoordinate(tokens[2]) * (tokens[3] === 'N' ? 1 : -1);
|
||||
state.lon = parseCoordinate(tokens[4]) * (tokens[5] === 'E' ? 1 : -1);
|
||||
state.alt = parseFloat(tokens[9]);
|
||||
break;
|
||||
case 'VTG':
|
||||
state.vel = parseFloat(tokens[7]) / 3.6;
|
||||
break;
|
||||
case 'GSA':
|
||||
state.fix = parseInt(tokens[2]);
|
||||
state.dop = parseFloat(tokens[15]);
|
||||
break;
|
||||
case 'GLL':
|
||||
state.gpsValid = state.fix === 3 && state.dop <= 5;
|
||||
updateGps(state);
|
||||
draw(state);
|
||||
if (state.gpsValid && state.status === ActivityStatus.Running) {
|
||||
updateLog(state);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
function readGps(state: AppState, gps: GpsEvent): void {
|
||||
state.lat = gps.lat;
|
||||
state.lon = gps.lon;
|
||||
state.alt = gps.alt;
|
||||
state.vel = gps.speed / 3.6;
|
||||
state.fix = gps.fix;
|
||||
state.dop = gps.hdop;
|
||||
}
|
||||
|
||||
function updateGps(state: AppState): void {
|
||||
|
@ -121,4 +103,4 @@ function updateGps(state: AppState): void {
|
|||
}
|
||||
}
|
||||
|
||||
export { initGps, parseCoordinate, parseNmea, updateGps };
|
||||
export { initGps, parseCoordinate, readGps, updateGps };
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: App created
|
||||
0.02: Persist state to storage to enable stopwatch to continue in the background
|
||||
|
|
|
@ -9,21 +9,25 @@ const Y_BTN3 = 225;
|
|||
const FONT = '6x8';
|
||||
const CHRONO = '/* C H R O N O */';
|
||||
|
||||
var laps = [EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP];
|
||||
var started = false;
|
||||
var reset = false;
|
||||
var whenStarted;
|
||||
var whenStartedTotal;
|
||||
var currentLapIndex = 1;
|
||||
var currentLap = '';
|
||||
var chronoInterval;
|
||||
|
||||
// Read state from storage or create default state if it doesn't exist
|
||||
var state = require("Storage").readJSON("devstopwatch.state.json",1) || {
|
||||
started: false,
|
||||
whenStarted: null,
|
||||
whenStartedTotal: null,
|
||||
currentLapIndex: 1,
|
||||
laps: [EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP],
|
||||
};
|
||||
|
||||
// Set laps.
|
||||
setWatch(() => {
|
||||
|
||||
reset = false;
|
||||
|
||||
if (started) {
|
||||
if (state.started) {
|
||||
changeLap();
|
||||
} else {
|
||||
if (!reset) {
|
||||
|
@ -39,10 +43,10 @@ setWatch(() => { resetChrono(); }, BTN3, { repeat: true, edge: 'rising' });
|
|||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: 'falling' });
|
||||
|
||||
function resetChrono() {
|
||||
laps = [EMPTY_H, EMPTY_H, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP];
|
||||
started = false;
|
||||
state.laps = [EMPTY_H, EMPTY_H, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP, EMPTY_LAP];
|
||||
state.started = false;
|
||||
reset = true;
|
||||
currentLapIndex = 1;
|
||||
state.currentLapIndex = 1;
|
||||
currentLap = '';
|
||||
|
||||
if (chronoInterval !== undefined) {
|
||||
|
@ -54,32 +58,32 @@ function resetChrono() {
|
|||
|
||||
function chronometer() {
|
||||
|
||||
if (!started) {
|
||||
if (!state.started) {
|
||||
var rightNow = Date.now();
|
||||
whenStarted = rightNow;
|
||||
whenStartedTotal = rightNow;
|
||||
started = true;
|
||||
state.whenStarted = rightNow;
|
||||
state.whenStartedTotal = rightNow;
|
||||
state.started = true;
|
||||
reset = false;
|
||||
}
|
||||
|
||||
currentLap = calculateLap(whenStarted);
|
||||
total = calculateLap(whenStartedTotal);
|
||||
currentLap = calculateLap(state.whenStarted);
|
||||
total = calculateLap(state.whenStartedTotal);
|
||||
|
||||
laps[0] = total;
|
||||
laps[1] = currentLap;
|
||||
state.laps[0] = total;
|
||||
state.laps[1] = currentLap;
|
||||
printChrono();
|
||||
}
|
||||
|
||||
function changeLap() {
|
||||
|
||||
currentLapIndex++;
|
||||
state.currentLapIndex++;
|
||||
|
||||
if ((currentLapIndex) > MAX_LAPS) {
|
||||
currentLapIndex = 2;
|
||||
if ((state.currentLapIndex) > MAX_LAPS) {
|
||||
state.currentLapIndex = 2;
|
||||
}
|
||||
|
||||
laps[currentLapIndex] = currentLap;
|
||||
whenStarted = Date.now();
|
||||
state.laps[state.currentLapIndex] = currentLap;
|
||||
state.whenStarted = Date.now();
|
||||
}
|
||||
|
||||
function calculateLap(whenStarted) {
|
||||
|
@ -108,8 +112,8 @@ function printChrono() {
|
|||
|
||||
g.setColor(0, 220, 0);
|
||||
g.setFont(FONT, 3);
|
||||
print = ` T ${laps[0]}\n`;
|
||||
print += ` C ${laps[1]}\n`;
|
||||
print = ` T ${state.laps[0]}\n`;
|
||||
print += ` C ${state.laps[1]}\n`;
|
||||
g.drawString(print, XY_CENTER, Y_HEADER, true);
|
||||
|
||||
g.setColor(255, 255, 255);
|
||||
|
@ -119,12 +123,12 @@ function printChrono() {
|
|||
|
||||
g.setColor(255, 255, 255);
|
||||
let suffix = ' ';
|
||||
if (currentLapIndex === i) {
|
||||
if (state.currentLapIndex === i) {
|
||||
let suffix = '*';
|
||||
g.setColor(255, 200, 0);
|
||||
}
|
||||
|
||||
const lapLine = `L${i - 1} ${laps[i]} ${suffix}\n`;
|
||||
const lapLine = `L${i - 1} ${state.laps[i]} ${suffix}\n`;
|
||||
g.drawString(lapLine, XY_CENTER, Y_LAPS + (15 * (i - 1)), true);
|
||||
}
|
||||
|
||||
|
@ -156,4 +160,13 @@ g.clear();
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
resetChrono();
|
||||
// Write the current state to storage
|
||||
E.on('kill', function(){
|
||||
require("Storage").writeJSON("devstopwatch.state.json", state);
|
||||
});
|
||||
|
||||
if(state.started){
|
||||
chronoInterval = setInterval(chronometer, 10);
|
||||
} else {
|
||||
resetChrono();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
0.01: New App! 2021-01-07
|
||||
0.02: Faster algorithm, hours and minutes are now displayable whenever, using the upper button 2021-01-14
|
||||
0.03: Ah yes. Some people prefer the 12 hour system 2021-01-14
|
||||
0.04: Fixed a bug, doesn't run while display's on now 2021-01-18
|
||||
0.05: Fixed a bug, doesn't count the time it was asleep when calculating the update time 2021-01-19
|
|
@ -0,0 +1,12 @@
|
|||
# Game of Life Clock
|
||||
|
||||
Modification of the "Game of Life" ("life") app that also displays the current time, including seconds.
|
||||
|
||||
## Usage
|
||||
|
||||
Upon launch, the clock loads a randomly generated 27x23 grid of "cells", with a block in the middle showing the time.
|
||||
Hours and minute are only displayed for 10 generations after launch, reset or after pressing the top button. After 10 generations, the cells that showed the hours and minutes become part of the game of life.
|
||||
|
||||
* Top button: Immediately displays hours and minutes, killing any cells underneath the time block.
|
||||
* Middle button: Shows the launcher. The state of the game is not saved.
|
||||
* Lower button: Resets the cells, randomly generates new ones.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AFkLC6+wC6u73YuVC7JIUCwIZBLywwTIwYwTC4QwUC4OwGCgXCCoQwRXwYwTO4wwQO4wwQO44wPO44wPO5G7olACY9EAAVLO44LCCosECwYXDGAm0BQIvFCwoABCwIcCCoQuHCwwXEAAguFBQgFBoEEC451GIwQVDAgIXBIhQwECoYEBYAS5NCogEBC6BGFVAQXPGAoXRGAoXSO6owGC6Z3VGAoXUd4gWRGAYXVIwQXUIwReSC7gWUgELFyoXBCyoA/ACwA=="))
|
|
@ -0,0 +1,475 @@
|
|||
Bangle.setLCDTimeout(30);
|
||||
|
||||
const is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
|
||||
const squaresize = 5;
|
||||
const padding = 2;
|
||||
const bigSquareSize = squaresize+padding;
|
||||
const noOfSquX = 27;
|
||||
const noOfSquY = 23;
|
||||
const screenWidth = noOfSquX * bigSquareSize - padding;
|
||||
const screenHeight = noOfSquY * bigSquareSize - padding;
|
||||
const ulCornerX = parseInt((220-screenWidth)/2);
|
||||
const ulCornerY = parseInt(40 + (160-screenHeight)/2);
|
||||
const textWidth = 27;
|
||||
const textHeight = 7;
|
||||
const sectextWidth = 9;
|
||||
const textWidthScreen = textWidth*bigSquareSize;
|
||||
const textHeightScreen = textHeight*bigSquareSize;
|
||||
const textWidthScreenMin = (textWidth-sectextWidth)*bigSquareSize;
|
||||
const textWidthScreenSec = sectextWidth*bigSquareSize;
|
||||
const ulCornerTextX = parseInt((noOfSquX-textWidth)/2);
|
||||
const ulCornerTextY = parseInt((noOfSquY-textHeight)/2);
|
||||
const ulCornerTextXScreen = ulCornerX+parseInt((noOfSquX-textWidth)
|
||||
*bigSquareSize/2);
|
||||
const ulCornerTextYScreen = ulCornerY+parseInt((noOfSquY-textHeight)
|
||||
*bigSquareSize/2);
|
||||
const seculCornerTextX = ulCornerTextX+textWidth-sectextWidth;
|
||||
|
||||
let buf = Graphics.createArrayBuffer(screenWidth,screenHeight,1,{msb:true});
|
||||
let textbufMin = Graphics.createArrayBuffer((textWidth-sectextWidth)*bigSquareSize,
|
||||
textHeight*bigSquareSize,
|
||||
1,{msb:true});
|
||||
let textPreBufMin = Graphics.createArrayBuffer(textWidth-sectextWidth,
|
||||
textHeight,
|
||||
8,{msb:true});
|
||||
let textbufSec = Graphics.createArrayBuffer(sectextWidth*bigSquareSize,
|
||||
textHeight*bigSquareSize,
|
||||
1,{msb:true});
|
||||
let textPreBufSec = Graphics.createArrayBuffer(sectextWidth,
|
||||
textHeight,
|
||||
8,{msb:true});
|
||||
|
||||
let genNow = new Uint8Array((noOfSquX+2)*(noOfSquY+2));
|
||||
let genFut = new Uint8Array((noOfSquX+2)*(noOfSquY+2));
|
||||
let generation=0;
|
||||
const minuteGens = 10;
|
||||
let genEnd = minuteGens;
|
||||
let lastupdatetime=null;
|
||||
|
||||
function fillLinesFirstFewGens(){
|
||||
let fade = (genEnd-generation)/minuteGens;
|
||||
g.setColor(fade,fade,fade);
|
||||
// vertical lines
|
||||
g.fillRect(ulCornerTextXScreen,
|
||||
ulCornerTextYScreen,
|
||||
ulCornerTextXScreen,
|
||||
ulCornerTextYScreen+textHeightScreen-padding);
|
||||
// horizontal lines
|
||||
g.fillRect(ulCornerTextXScreen,
|
||||
ulCornerTextYScreen,
|
||||
ulCornerTextXScreen+textWidthScreenMin,
|
||||
ulCornerTextYScreen);
|
||||
g.fillRect(ulCornerTextXScreen,
|
||||
ulCornerTextYScreen+textHeightScreen-padding,
|
||||
ulCornerTextXScreen+textWidthScreenMin,
|
||||
ulCornerTextYScreen+textHeightScreen-padding);
|
||||
g.setColor(1,1,1);
|
||||
}
|
||||
|
||||
function clearLinesFirstFewGens(){
|
||||
// vertical lines
|
||||
g.clearRect(ulCornerTextXScreen,
|
||||
ulCornerTextYScreen,
|
||||
ulCornerTextXScreen,
|
||||
ulCornerTextYScreen+textHeightScreen-padding);
|
||||
// horizontal lines
|
||||
g.clearRect(ulCornerTextXScreen,
|
||||
ulCornerTextYScreen,
|
||||
ulCornerTextXScreen+textWidthScreenMin,
|
||||
ulCornerTextYScreen);
|
||||
g.clearRect(ulCornerTextXScreen,
|
||||
ulCornerTextYScreen+textHeightScreen-padding,
|
||||
ulCornerTextXScreen+textWidthScreenMin,
|
||||
ulCornerTextYScreen+textHeightScreen-padding);
|
||||
}
|
||||
|
||||
function fillLinesLaterGens(){
|
||||
g.setColor(1,1,1);
|
||||
// horizontal lines
|
||||
g.fillRect(ulCornerTextXScreen+textWidthScreenMin,
|
||||
ulCornerTextYScreen,
|
||||
ulCornerTextXScreen+textWidthScreenMin+textWidthScreenSec,
|
||||
ulCornerTextYScreen);
|
||||
g.fillRect(ulCornerTextXScreen+textWidthScreenMin,
|
||||
ulCornerTextYScreen+textHeightScreen-padding,
|
||||
ulCornerTextXScreen+textWidthScreenMin+textWidthScreenSec,
|
||||
ulCornerTextYScreen+textHeightScreen-padding);
|
||||
// vertical lines
|
||||
g.fillRect(ulCornerTextXScreen+textWidthScreenMin,
|
||||
ulCornerTextYScreen,
|
||||
ulCornerTextXScreen+textWidthScreenMin,
|
||||
ulCornerTextYScreen+textHeightScreen-padding);
|
||||
g.fillRect(ulCornerTextXScreen+textWidthScreenMin+textWidthScreenSec,
|
||||
ulCornerTextYScreen,
|
||||
ulCornerTextXScreen+textWidthScreenMin+textWidthScreenSec,
|
||||
ulCornerTextYScreen+textHeightScreen-padding);
|
||||
}
|
||||
|
||||
function renderSecText(){
|
||||
g.clearRect(ulCornerTextXScreen+18*bigSquareSize+padding,
|
||||
ulCornerTextYScreen,
|
||||
ulCornerTextXScreen+textWidthScreen,
|
||||
ulCornerTextYScreen+textHeightScreen-1);
|
||||
fillLinesLaterGens();
|
||||
g.drawImage({width:textWidthScreenSec,
|
||||
height:textHeightScreen,
|
||||
bpp:1,transparent:0,buffer:textbufSec.buffer},
|
||||
ulCornerTextXScreen+textWidthScreenMin,ulCornerTextYScreen);
|
||||
}
|
||||
|
||||
function flip(dontclear) {
|
||||
g.setColor(1,1,1);
|
||||
g.drawImage({width:screenWidth,height:screenHeight,bpp:1,buffer:buf.buffer},
|
||||
ulCornerX,ulCornerY);
|
||||
if(generation<genEnd){
|
||||
g.clearRect(ulCornerTextXScreen,
|
||||
ulCornerTextYScreen,
|
||||
ulCornerTextXScreen+textWidthScreenMin,
|
||||
ulCornerTextYScreen+textHeightScreen);
|
||||
g.drawImage({width:textWidthScreenMin,
|
||||
height:textHeightScreen,
|
||||
bpp:1,buffer:textbufMin.buffer},
|
||||
ulCornerTextXScreen,ulCornerTextYScreen);
|
||||
renderSecText();
|
||||
fillLinesFirstFewGens();
|
||||
}
|
||||
else{
|
||||
renderSecText();
|
||||
}
|
||||
if(!dontclear){buf.clear();}
|
||||
}
|
||||
|
||||
function skipCell(x,y){
|
||||
return (x>=seculCornerTextX && x<seculCornerTextX+sectextWidth
|
||||
&& y>=ulCornerTextY && y<ulCornerTextY+textHeight)
|
||||
|| (generation<genEnd
|
||||
&& x>=ulCornerTextX && x<ulCornerTextX+textWidth
|
||||
&& y>=ulCornerTextY && y<ulCornerTextY+textHeight);
|
||||
}
|
||||
|
||||
function addNeighbors(p){
|
||||
genFut[p-noOfSquX-3]+=2;
|
||||
genFut[p-noOfSquX-2]+=2;
|
||||
genFut[p-noOfSquX-1]+=2;
|
||||
genFut[p-1]+=2;
|
||||
genFut[p+1]+=2;
|
||||
genFut[p+noOfSquX+1]+=2;
|
||||
genFut[p+noOfSquX+2]+=2;
|
||||
genFut[p+noOfSquX+3]+=2;
|
||||
}
|
||||
|
||||
function initDraw(){
|
||||
buf.clear();
|
||||
for (let y = 1; y<noOfSquY+1; y++){
|
||||
updateSecondText();
|
||||
for (let x = 1; x<noOfSquX+1; x++) {
|
||||
let ind = x+y*(noOfSquX+2);
|
||||
if(skipCell(x-1,y-1)){
|
||||
break; // rest of the line would be skipped, too
|
||||
}
|
||||
if(Math.random()<0.3){
|
||||
genFut[ind] += 1;
|
||||
addNeighbors(ind);
|
||||
var Xr=bigSquareSize*(x-1);
|
||||
var Yr=bigSquareSize*(y-1);
|
||||
buf.fillRect(Xr,Yr, Xr+squaresize,Yr+squaresize);
|
||||
}
|
||||
}
|
||||
flip(true); // experimental
|
||||
}
|
||||
flip(false);
|
||||
let tmp = genFut; genFut = genNow; genNow = tmp; // reference swapping
|
||||
genFut.fill(0);
|
||||
}
|
||||
|
||||
let sleeptime = 0;
|
||||
|
||||
function howlong(){
|
||||
generation++;
|
||||
if(generation==genEnd){
|
||||
clearLinesFirstFewGens();
|
||||
fillLinesLaterGens();
|
||||
}
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(-1,-1,0);
|
||||
let gentime = Math.floor(Date.now()-lastupdatetime-sleeptime);
|
||||
lastupdatetime = Date.now();
|
||||
sleeptime = 0;
|
||||
g.drawString('Gen:'+generation+' '+gentime+'ms ',20,220,true);
|
||||
}
|
||||
|
||||
let lastDate=null;
|
||||
let lastGen=-1;
|
||||
|
||||
function improveLetter(textPreBuf, char, x,y){
|
||||
switch(char){
|
||||
case "0":{
|
||||
textPreBuf.setColor(1);
|
||||
textPreBuf.drawString(".",x-1,y-4);
|
||||
textPreBuf.drawString(".",x+1,y-4);
|
||||
textPreBuf.drawString(".",x-1,y);
|
||||
textPreBuf.drawString(".",x+1,y);
|
||||
return;}
|
||||
case "2":{
|
||||
textPreBuf.drawString(".",x+1,y-4);
|
||||
textPreBuf.drawString(".",x+1,y-2);
|
||||
textPreBuf.drawString(".",x-1,y-2);
|
||||
return;}
|
||||
case "3":{
|
||||
textPreBuf.drawString(".",x+1,y-4);
|
||||
textPreBuf.drawString(".",x+1,y-2);
|
||||
textPreBuf.drawString(".",x+1,y);
|
||||
return;}
|
||||
case "4":{
|
||||
textPreBuf.setColor(0);
|
||||
textPreBuf.drawString(".",x,y-4);
|
||||
textPreBuf.drawString(".",x-1,y-3);
|
||||
textPreBuf.setColor(1);
|
||||
textPreBuf.drawString(".",x,y-3);
|
||||
return;}
|
||||
case "5":{
|
||||
textPreBuf.drawString(".",x-1,y-2);
|
||||
textPreBuf.drawString(".",x+1,y-2);
|
||||
textPreBuf.drawString(".",x+1,y);
|
||||
return;}
|
||||
case "6":{
|
||||
textPreBuf.drawString(".",x-1,y-4);
|
||||
textPreBuf.drawString(".",x+1,y-2);
|
||||
textPreBuf.drawString(".",x-1,y);
|
||||
textPreBuf.drawString(".",x+1,y);
|
||||
return;}
|
||||
case "7":{
|
||||
textPreBuf.setColor(0);
|
||||
textPreBuf.drawString(".",x,y);
|
||||
textPreBuf.drawString(".",x,y-1);
|
||||
textPreBuf.setColor(1);
|
||||
textPreBuf.drawString(".",x+1,y);
|
||||
textPreBuf.drawString(".",x+1,y-1);
|
||||
return;}
|
||||
case "8":{
|
||||
textPreBuf.drawString(".",x-1,y-4);
|
||||
textPreBuf.drawString(".",x+1,y-4);
|
||||
textPreBuf.drawString(".",x-1,y-2);
|
||||
textPreBuf.drawString(".",x+1,y-2);
|
||||
textPreBuf.drawString(".",x-1,y);
|
||||
textPreBuf.drawString(".",x+1,y);
|
||||
return;}
|
||||
case "9":{
|
||||
textPreBuf.drawString(".",x-1,y-4);
|
||||
textPreBuf.drawString(".",x+1,y-4);
|
||||
textPreBuf.drawString(".",x-1,y-2);
|
||||
textPreBuf.drawString(".",x-1,y);
|
||||
textPreBuf.drawString(".",x+1,y);
|
||||
return;}
|
||||
default: return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function fillMinTextBuf(date){
|
||||
textPreBufMin.clear();
|
||||
textPreBufMin.setColor(1);
|
||||
let hours = date.getHours();
|
||||
if (is12Hour) {hours = ((hours + 11) % 12) + 1;}
|
||||
let hourStr = ("0"+hours).slice(-2);
|
||||
let minStr = ("0"+date.getMinutes()).slice(-2);
|
||||
textPreBufMin.drawString(hourStr[0],1,1);
|
||||
improveLetter(textPreBufMin, hourStr[0],1,1);
|
||||
textPreBufMin.drawString(hourStr[1],5,1);
|
||||
improveLetter(textPreBufMin, hourStr[1],5,1);
|
||||
textPreBufMin.drawString(":",8,0);
|
||||
textPreBufMin.drawString(minStr[0],11,1);
|
||||
improveLetter(textPreBufMin, minStr[0],11,1);
|
||||
textPreBufMin.drawString(minStr[1],15,1);
|
||||
improveLetter(textPreBufMin, minStr[1],15,1);
|
||||
}
|
||||
|
||||
function updateMinuteText(date){
|
||||
fillMinTextBuf(date);
|
||||
textbufMin.clear();
|
||||
g.setColor(1,1,1);
|
||||
for(let i = 0; i<textPreBufMin.buffer.length; i++){
|
||||
if(textPreBufMin.buffer[i]==0){continue;}
|
||||
let x = i%(textWidth-sectextWidth);
|
||||
let y = parseInt(i/(textWidth-sectextWidth));
|
||||
|
||||
let Xr=bigSquareSize*(x);
|
||||
let Yr=bigSquareSize*(y);
|
||||
textbufMin.fillRect(Xr,Yr, Xr+squaresize,Yr+squaresize);
|
||||
}
|
||||
g.clearRect(ulCornerTextXScreen,ulCornerTextYScreen,
|
||||
ulCornerTextXScreen+textWidthScreen-textWidthScreenSec,
|
||||
ulCornerTextYScreen+textHeightScreen-1);
|
||||
fillLinesFirstFewGens();
|
||||
|
||||
g.drawImage({width:textWidthScreen-textWidthScreenSec,
|
||||
height:textHeightScreen,
|
||||
bpp:1,transparent:0,buffer:textbufMin.buffer},
|
||||
ulCornerTextXScreen,ulCornerTextYScreen);
|
||||
}
|
||||
|
||||
function updateSecondText(){
|
||||
let startDate = new Date();
|
||||
if(lastDate && lastDate.getSeconds() == startDate.getSeconds()){
|
||||
return;
|
||||
}
|
||||
textPreBufSec.clear();
|
||||
textPreBufSec.setColor(1);
|
||||
textbufSec.clear();
|
||||
if(!lastDate||(generation<genEnd && lastDate.getMinutes() != startDate.getMinutes())){
|
||||
updateMinuteText(startDate);
|
||||
}
|
||||
let secStr = ("0"+startDate.getSeconds()).slice(-2);
|
||||
textPreBufSec.drawString(secStr[0],1,1);
|
||||
improveLetter(textPreBufSec, secStr[0],1,1);
|
||||
textPreBufSec.drawString(secStr[1],5,1);
|
||||
improveLetter(textPreBufSec, secStr[1],5,1);
|
||||
|
||||
g.setColor(1,1,1);
|
||||
for(let i = 0; i<textPreBufSec.buffer.length; i++){
|
||||
if(textPreBufSec.buffer[i]==0){continue;}
|
||||
let x = i%sectextWidth;
|
||||
let y = parseInt(i/sectextWidth);
|
||||
if (textPreBufSec.buffer[i]==1){
|
||||
let Xr=bigSquareSize*(x);
|
||||
let Yr=bigSquareSize*(y);
|
||||
textbufSec.fillRect(Xr,Yr, Xr+squaresize,Yr+squaresize);
|
||||
}
|
||||
}
|
||||
renderSecText();
|
||||
|
||||
lastDate = startDate;
|
||||
}
|
||||
|
||||
function writeMinuteText(){
|
||||
for(let i = 0; i<textPreBufMin.buffer.length; i++){
|
||||
if(textPreBufMin.buffer[i]==0){continue;}
|
||||
let x = i%(textWidth-sectextWidth);
|
||||
let y = parseInt(i/(textWidth-sectextWidth));
|
||||
let ind = (ulCornerTextX+x+1)+(ulCornerTextY+y+1)*(noOfSquX+2);
|
||||
|
||||
genFut[ind] +=1;
|
||||
addNeighbors(ind);
|
||||
}
|
||||
}
|
||||
|
||||
let currentY = 1;
|
||||
let runOn = true;
|
||||
|
||||
function nextLineComp(){
|
||||
updateSecondText();
|
||||
let y = currentY;
|
||||
for (let x = 1; x<noOfSquX+1; x++){
|
||||
if(skipCell(x-1,y-1)){break;} // the rest of this line would be skipped, too
|
||||
let ind = x+y*(noOfSquX+2);
|
||||
if (genNow[ind]>4 && genNow[ind]<8){
|
||||
genFut[ind]+=1;
|
||||
addNeighbors(ind);
|
||||
let Xr=bigSquareSize*(x-1);
|
||||
let Yr=bigSquareSize*(y-1);
|
||||
buf.fillRect(Xr,Yr, Xr+squaresize,Yr+squaresize);
|
||||
}
|
||||
}
|
||||
if(y == noOfSquY){
|
||||
if(generation == genEnd-1){writeMinuteText();}
|
||||
flip();
|
||||
let tmp = genFut; genFut = genNow; genNow = tmp; // reference swapping
|
||||
genFut.fill(0);
|
||||
howlong();
|
||||
currentY = 1;
|
||||
}
|
||||
else{
|
||||
currentY++;
|
||||
}
|
||||
if(runOn){setTimeout(nextLineComp, 50);}
|
||||
}
|
||||
|
||||
function stopdraw() {
|
||||
runOn = false;
|
||||
}
|
||||
|
||||
function startdraw(init) {
|
||||
if (init===undefined) init=false;
|
||||
Bangle.drawWidgets();
|
||||
g.reset();
|
||||
g.setColor(1,1,1);
|
||||
g.setFont("6x8",1);
|
||||
g.setFontAlign(0,0,3);
|
||||
g.drawString("RESET",230,200);
|
||||
g.drawString("LAUNCH",230,130);
|
||||
g.drawString("CLOCK",230,60);
|
||||
if(!init){
|
||||
updateSecondText();
|
||||
// stopdraw();
|
||||
runOn = true;
|
||||
sleeptime = 0;
|
||||
nextLineComp();
|
||||
}
|
||||
}
|
||||
|
||||
function regen(){
|
||||
g.setColor(1,1,1);
|
||||
generation = 0;
|
||||
genEnd = minuteGens;
|
||||
currentY=1;
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.clearRect(20,220,220,240);
|
||||
g.drawString('Gen:'+generation+' 0ms ',20,220,true);
|
||||
lastupdatetime=Date.now();
|
||||
updateSecondText();
|
||||
genNow.fill(0);
|
||||
genFut.fill(0);
|
||||
initDraw();
|
||||
// stopdraw();
|
||||
runOn = true;
|
||||
sleeptime = 0;
|
||||
nextLineComp();
|
||||
}
|
||||
|
||||
function showMinAgain(){
|
||||
if(genEnd != generation+minuteGens){
|
||||
genEnd = generation+minuteGens;
|
||||
lastDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
setWatch(showMinAgain, BTN1, {repeat:true,edge:"falling"});
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||
setWatch(regen, BTN3, {repeat:true,edge:"falling"});
|
||||
}
|
||||
|
||||
let wentToSleepAt;
|
||||
|
||||
var SCREENACCESS = {
|
||||
withApp:true,
|
||||
request:function(){
|
||||
this.withApp=false;
|
||||
stopdraw();
|
||||
wentToSleepAt = Date.now();
|
||||
},
|
||||
release:function(){
|
||||
this.withApp=true;
|
||||
sleeptime = Date.now()-wentToSleepAt;
|
||||
startdraw();
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (!SCREENACCESS.withApp) return;
|
||||
if (on) {
|
||||
startdraw();
|
||||
sleeptime = Date.now()-wentToSleepAt;
|
||||
} else {
|
||||
stopdraw();
|
||||
wentToSleepAt = Date.now();
|
||||
}
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
setButtons();
|
||||
regen();
|
||||
startdraw(true);
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Loading…
Reference in New Issue