diff --git a/android.html b/android.html
index fb5d2557f..485f09a47 100644
--- a/android.html
+++ b/android.html
@@ -176,9 +176,13 @@
- Always allow to reinstall apps in place regardless of the version
+ Always show "reinstall app" button regardless of the version
- Reset to default App Loader settings
+
+
+ Automatically reload watch after app App Loader actions (removes "Hold button" prompt)
+
+ Reset App Loader settings to defaults
diff --git a/apps/Tyreid/ChangeLog b/apps/Tyreid/ChangeLog
new file mode 100644
index 000000000..2f792c8de
--- /dev/null
+++ b/apps/Tyreid/ChangeLog
@@ -0,0 +1 @@
+0.01: Change log created
diff --git a/apps/Tyreid/README.md b/apps/Tyreid/README.md
new file mode 100644
index 000000000..5c205ab57
--- /dev/null
+++ b/apps/Tyreid/README.md
@@ -0,0 +1,18 @@
+Tyreid
+
+Tyreid is a Bluetooth war-driving app for the Bangle.js 2.
+
+Menu options:
+- Start: This turns on the Bluetooth and starts logging Bluetooth packets with time, latitude, and longitude information to a CSV file.
+- Pause/Continue: These functions pause the capture and then allow it to resume.
+- Devices: When paused this menu option will display the MAC addresses of discovered Bluetooth devices. Selecting a device will then display the MAC, Manufacturer code, the time it was first seen, and the RSSI of the first sighting.
+- Marker: This command adds a 'marker' to the CSV log, which consists of the time and location information, but the Bluetooth packet information is replaced with the word MARKER. Markers can also be added by pressing the watch's button.
+- Exit: This exits the app.
+
+The current number of discovered devices is displayed in the top left corner.
+This value is displayed in green when the GPS has a fix, or red otherwise.
+
+To retrieve the CSV file, connect to the watch through the Espruino web IDE (https://www.espruino.com/ide/). From there the files stored on the watch can be downloaded by clicking the storage icon in the IDE's central column.
+
+
+
diff --git a/apps/Tyreid/app-icon.js b/apps/Tyreid/app-icon.js
new file mode 100644
index 000000000..503a87b59
--- /dev/null
+++ b/apps/Tyreid/app-icon.js
@@ -0,0 +1 @@
+E.toArrayBuffer(atob("MDACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAtAAAAAAAAAAAAAAB/QAAAAAAAAAAAAAD/0AAAAAAAAAAAAAH+9AAAAAAAAAAAAAPtfQAAAAAAAAAAAAudH0AAAAAAAAAAAB8tB9AAAAAAAAAAAD0tAfQAAAAAAAAAAHgtAH0AAAAAAAAAAPAtAB9AAAAAAAAAAtAtAAfQAAAAAAAAB8AtAAH0AAAAAAAAD0AtAAB9AAAAAAAALgAtAAAfQAAAAAAAfAAtAAAH0AAAAAAA9AAtAAAB9AAAAAAC4AAtAAAAfQAAAAADwAAtAAAALwAAAAAAQAAtAAAAvgAAAAAAAAAsAAAC9AAAAAAAAAAsAAAP0AAAAAAAAAAsAAB/AAAAAAAAAAAsAAH4AAAAAAAAAAAsAAvQAAAAAAAAAAAsAD9AAAAAAAAAAAAsAD0AAAAAAAAAAAAsAB8AAAAAAAAAAAAsAAtAAAAAAAAAAAAsAAPQAAAAAAAAAAAsAAHwAAAAAAAAAAAsAAC4AAAAAAAAAAAsAAA9AAAAAAAAAAAsAAAPAAAAAAAAAAAsAAAHgAAAAAAAAAA8AAAC0AAAAAAAAAA8AAAA8AAAAAAAAAA8AAAAEAAAAAAAAAA8AAAAAAAAAAAAAAA8AAAAAAAAAAAAAAA8AAAAAAAAAAAAAAA8AAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))
diff --git a/apps/Tyreid/app.js b/apps/Tyreid/app.js
new file mode 100644
index 000000000..8ab63cabd
--- /dev/null
+++ b/apps/Tyreid/app.js
@@ -0,0 +1,272 @@
+//---------------------------- Tyreid ----------------------------//
+//
+// Bluetooth war-driving app for the Bangle.js 2
+//
+// TH10111 2023
+//
+
+Bangle.loadWidgets();
+Bangle.drawWidgets(); // <-- for development only (shouldn't need for a real app)
+
+// Global variables
+var gpsFix_flag = 0;
+var bt_id_arr = [];
+var num_bt_devices = 0;
+var running_flag = 0; // 0 = stopped, 1 = running, 2 = paused
+
+// Log file
+var file = require("Storage").open("tyreid_log.csv","w");
+
+// Logo
+var logo = {
+ width : 176, height : 176, bpp : 2,
+ buffer : atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC///QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf///QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv/////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///v//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///+f//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/+/4f//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//b/gf//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL/8v+Af//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//C/4Af//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4L/gAf//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC//Av+AAb//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/0C/0AAb//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+AP/QAAb//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/wA/9AAAb//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/9AD/0AAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv/gAP/QAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AA/9AAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//QAD/0AAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL/8AAP/QAAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAA/9AAAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4AAD/0AAAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//AAAP/QAAAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/0AAA/9AAAAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/+AAAD/0AAAAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/wAAAP/QAAAAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/9AAAA/9AAAAAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv/wAAAD/0AAAAAAAL//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAP/QAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAA/9AAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAD/0AAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//QAAAAP/QAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL/4AAAAA/9AAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//AAAAAD/0AAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/0AAAAAP/QAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC//AAAAAA/9AAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/wAAAAAD/0AAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/9AAAAAAP/QAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/wAAAAAA/9AAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/9AAAAAAD/0AAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv/gAAAAAAP/QAAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAAA/9AAAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//QAAAAAAD/0AAAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL/8AAAAAAAP/QAAAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//AAAAAAAA/8AAAAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4AAAAAAAD/wAAAAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//AAAAAAAAP/AAAAAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/0AAAAAAAA/8AAAAAAAAAAAAAAH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+AAAAAAAAD/wAAAAAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAf/wAAAAAAAAP/AAAAAAAAAAAAAAAH//QAAAAAAAAAAAAAAAAAAAAAAAAAAAv9AAAAAAAAA/8AAAAAAAAAAAAAAAv/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAAD/wAAAAAAAAAAAAAAP//0AAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAP/AAAAAAAAAAAAAAH//+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAAAAAAB///QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAAAAAv//kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAAAAAAAAAAAAP//5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAAAAAH//9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAAAB///QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAAAAAAAAAAAv//kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAAAAL//5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAAD//9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAAAAAAAAAB///QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAAAf//kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAL//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAAAAAAAAD//9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAB///QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAf//kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAAAAAAAH//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAC//9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAABv/+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAAAAAAf//kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAL//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAH//9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAAAAAv/+QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAC//kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAAAAAH/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAL/5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAf/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAf/0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAv/0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAAv/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAB//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAB//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAAC/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAG/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAAH/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAAAH/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAL/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAAAf/0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAAAAf/0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAv/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAAAAv/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAAAAB//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAB/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAAAAC/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAAAAAH/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAH/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAAAAAL/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAAAAAAL/0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAf/0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAAAAAAAv/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAv/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAAAAAAB/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAAAAAAAB/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAC/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAAAAAAAAAAAH/0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gAAAAAAAAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv+AAAAAAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGqQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")
+};
+
+
+//
+// Functions
+//
+
+function gpsTick(fix) { // GPS fix callback
+ //console.log(fix); // print GPS Fix info to console
+ if (fix.fix > 0) {
+ gpsFix_flag = 1;
+ } else {
+ gpsFix_flag = 0;
+ }
+ WIDGETS["widTyreid"].draw(WIDGETS["widTyreid"]); // update widget with gps tick
+ Bangle.drawWidgets(); // Not sure why I need this here, but otherwise the widget draws over itself without clearing first!
+}
+
+function gpsON() { // Turn GPS on
+ Bangle.on('GPS',gpsTick);
+ Bangle.setGPSPower(true);
+}
+
+function gpsOFF() { // Turn GPS off
+ Bangle.setGPSPower(false);
+}
+
+function btPacket(packet) { // BT packet callback
+ let latest_fix = Bangle.getGPSFix();
+ let mac = packet.id.substring(0,17);
+ let mac_info = packet.id.substring(18);
+
+// console.log(" ");
+// console.log(packet); // print packet info
+ //console.log(mac);
+ //console.log(5*latest_fix.hdop); // print latest GPS fix info
+// console.log(" ");
+
+ // Compile the values to be stored
+ var new_data = [
+ latest_fix.time,
+ latest_fix.lat,
+ latest_fix.lon,
+ latest_fix.alt,
+ latest_fix.hdop*5,
+ packet.name,
+ mac,
+ mac_info,
+ packet.manufacturer,
+ packet.rssi,
+ packet.services,
+ packet.data,
+ packet.serviceData,
+ packet.manufacturerData
+ ];
+ // Write data to the file (including a new line)
+ file.write(new_data.join(",")+"\n");
+
+ if (num_bt_devices < 99) {
+ if (!bt_id_arr.includes(mac)) { // if device id has not been recorded
+ bt_id_arr[num_bt_devices] = mac; // note the id
+ num_bt_devices++; // increment the number of devices found
+ // Add the new device to the devices menu, with information...
+ //console.log(" ");
+ //console.log(mac);
+ //console.log(packet.manufacturer);
+ //console.log(latest_fix.time);
+ //console.log(packet.rssi);
+ //console.log(" ");
+ // Add the new device to the devices menu, with information...
+ device_menu[mac] = () => {
+ E.showPrompt([mac,mac_info],{title:"MAC",buttons:{"Ok":true}}).then(function(v){
+ E.showPrompt(packet.manufacturer,{title:"Manufacturer",buttons:{"Ok":true}}).then(function(v){
+ E.showPrompt(latest_fix.time,{title:"First Seen",buttons:{"Ok":true}}).then(function(v){
+ E.showPrompt(packet.rssi,{title:"RSSI",buttons:{"Ok":true}}).then(function(v){
+ E.showMenu(device_menu);
+ });
+ });
+ });
+ });
+ };
+ }
+ }
+}
+
+function headings() { // Add headings to the file
+ // Compile the values to be stored
+ var new_data = [
+ "Time",
+ "Latitude",
+ "Longitude",
+ "Altitude",
+ "Accuracy",
+ "Name",
+ "MAC",
+ "MAC Info",
+ "Manufacturer",
+ "RSSI",
+ "Services",
+ "Data",
+ "Service Data",
+ "Manufacturer Data"
+ ];
+ // Write data to the file (including a new line)
+ file.write(new_data.join(",")+"\n");
+}
+
+function btON() { // Turn Bluetooth on
+ NRF.setScan(btPacket);
+}
+
+function btOFF() { // Turn Bluetooth off
+ NRF.setScan();
+}
+
+function start() { // Start the application
+ bt_id_arr = [];
+ headings();
+ num_bt_devices = 0;
+ btON();
+ running_flag = 1;
+ E.showMenu(running_menu);
+}
+
+function exit() { // Exit the application
+ gpsOFF();
+ btOFF();
+ running_flag = 0;
+ load();
+}
+
+function pause() { // Pause the application
+ btOFF();
+ running_flag = 2;
+ //console.log(bt_id_arr);
+ E.showMenu(pause_menu);
+}
+
+function resume() { // Continue after pause
+ btON();
+ running_flag = 1;
+ E.showMenu(running_menu);
+}
+
+function marker() { // add a marker packet to the log
+ let latest_fix = Bangle.getGPSFix();
+
+// console.log(" ");
+// console.log("MARKER"); // print packet info
+// console.log(" ");
+
+ // Compile the values to be stored
+ var new_data = [
+ latest_fix.time,
+ latest_fix.lat,
+ latest_fix.lon,
+ latest_fix.alt,
+ latest_fix.hdop,
+ "MARKER"
+ ];
+
+ // Write data to the file (including a new line)
+ file.write(new_data.join(",")+"\n");
+
+ // Indicate that the marker has been added
+ E.showMessage("Marker Added.");
+
+ // Back to the menu
+ if (running_flag == 0) {
+ E.showMenu(init_menu);
+ } else if (running_flag == 1) {
+ E.showMenu(running_menu);
+ } else {
+ E.showMenu(pause_menu);
+ }
+}
+
+setWatch(() => { // If the button is pressed, then add a marker
+ marker();
+}, BTN1, {repeat:true});
+
+WIDGETS["widTyreid"]={
+ area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
+ width: 24, // width of the widget
+ draw: function() {
+ let disp_dev_val = "-";
+
+ if (num_bt_devices < 99) {
+ disp_dev_val = num_bt_devices.toString();
+ } else {
+ disp_dev_val = "99+";
+ }
+
+ g.setFont("6x8",3);
+ if (gpsFix_flag == 1) {
+ g.setColor(0,1,0).drawString(disp_dev_val, this.x+24/2, this.y); // green
+ } else {
+ g.setColor(1,0,0).drawString(disp_dev_val, this.x+24/2, this.y); // red
+ }
+ }
+};
+
+
+let init_menu = {
+ "": { "title": "Tyreid" },
+ "Start": function() { start(); },
+ "Marker": function() { marker(); },
+ "Exit": function() { exit(); },
+};
+
+let running_menu = {
+ "": { "title": "[Running]" },
+ "Pause": function() { pause(); },
+ "Marker": function() { marker(); },
+ "Exit": function() { exit(); },
+};
+
+let pause_menu = {
+ "": { "title": "[Paused]" },
+ "Continue": function() { resume(); },
+ "Devices": function() { E.showMenu(device_menu); },
+ "Marker": function() { marker(); },
+ "Exit": function() { exit(); },
+};
+
+let device_menu = {
+ "": { "title": "Devices:" },
+ "Back": function() {
+ if (running_flag == 0) {
+ E.showMenu(init_menu);
+ } else if (running_flag == 1) {
+ E.showMenu(running_menu);
+ } else {
+ E.showMenu(pause_menu);
+ }
+ },
+};
+
+
+// Main
+gpsON(); // turn GPS on straight away to start trying for a fix
+g.setColor(1,1,1).fillRect(0,0,176,176);
+g.drawImage(logo,0,0); // splash screen
+// 2sec wait before starting initial menu
+setTimeout(function () {
+ E.showMenu(init_menu);
+}, 2*1000);
+
+
diff --git a/apps/Tyreid/metadata.json b/apps/Tyreid/metadata.json
new file mode 100644
index 000000000..4663287e8
--- /dev/null
+++ b/apps/Tyreid/metadata.json
@@ -0,0 +1,14 @@
+{ "id": "Tyreid",
+ "name": "Tyreid",
+ "shortName":"Tyreid",
+ "version":"0.01",
+ "description": "Bluetooth war-driving app for Bangle.js 2",
+ "icon": "small_logo.png",
+ "tags": "",
+ "supports" : ["BANGLEJS2"],
+ "readme": "README.md",
+ "storage": [
+ {"name":"Tyreid.app.js","url":"app.js"},
+ {"name":"Tyreid.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/Tyreid/small_logo.png b/apps/Tyreid/small_logo.png
new file mode 100644
index 000000000..8b705cd3f
Binary files /dev/null and b/apps/Tyreid/small_logo.png differ
diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog
index 7d4d23f57..a41167e76 100644
--- a/apps/alarm/ChangeLog
+++ b/apps/alarm/ChangeLog
@@ -46,3 +46,4 @@
0.41: Fix a menu bug affecting alarms with empty messages.
0.42: Fix date not getting saved in event edit menu when tapping Confirm
0.43: New settings: Show confirm, Show Overflow, Show Type.
+0.44: Add "delete timer after expiration" setting to events.
diff --git a/apps/alarm/app.js b/apps/alarm/app.js
index f8dcdbfed..52dbe40fd 100644
--- a/apps/alarm/app.js
+++ b/apps/alarm/app.js
@@ -106,6 +106,9 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
var isNew = alarmIndex === undefined;
var alarm = require("sched").newDefaultAlarm();
+ if (withDate || selectedAlarm.date) {
+ alarm.del = require("sched").getSettings().defaultDeleteExpiredTimers;
+ }
alarm.dow = handleFirstDayOfWeek(alarm.dow);
if (selectedAlarm) {
@@ -193,6 +196,9 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
/*LANG*/"Repeat": {
value: decodeRepeat(alarm),
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, date || alarm.dow, (repeat, dow) => {
+ if (repeat) {
+ alarm.del = false; // do not auto delete a repeated alarm
+ }
alarm.rp = repeat;
alarm.dow = dow;
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
@@ -204,6 +210,10 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
value: alarm.as,
onchange: v => alarm.as = v
},
+ /*LANG*/"Delete After Expiration": {
+ value: alarm.del,
+ onchange: v => alarm.del = v
+ },
/*LANG*/"Hidden": {
value: alarm.hidden || false,
onchange: v => alarm.hidden = v
@@ -225,6 +235,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
delete menu[/*LANG*/"Day"];
delete menu[/*LANG*/"Month"];
delete menu[/*LANG*/"Year"];
+ delete menu[/*LANG*/"Delete After Expiration"];
}
if (!isNew) {
@@ -283,7 +294,6 @@ function decodeRepeat(alarm) {
}
function showEditRepeatMenu(repeat, day, dowChangeCallback) {
- var originalRepeat = repeat;
var dow;
const menu = {
@@ -316,26 +326,32 @@ function showEditRepeatMenu(repeat, day, dowChangeCallback) {
},
/*LANG*/"Custom": {
value: isCustom ? decodeRepeat({ rp: true, dow: dow }) : false,
- onchange: () => setTimeout(showCustomDaysMenu, 10, dow, dowChangeCallback, originalRepeat, originalDow)
+ onchange: () => setTimeout(showCustomDaysMenu, 10, dow, dowChangeCallback, repeat, originalDow)
}
};
} else {
// var date = day; // eventually: detect day of date and configure a repeat e.g. 3rd Monday of Month
dow = EVERY_DAY;
- repeat = repeat || {interval: "month", num: 1};
+ const repeatObj = repeat || {interval: "month", num: 1};
restOfMenu = {
/*LANG*/"Every": {
- value: repeat.num,
+ value: repeatObj.num,
min: 1,
- onchange: v => repeat.num = v
+ onchange: v => {
+ repeat = repeatObj;
+ repeat.num = v;
+ }
},
/*LANG*/"Interval": {
- value: INTERVALS.indexOf(repeat.interval),
+ value: INTERVALS.indexOf(repeatObj.interval),
format: v => INTERVAL_LABELS[v],
min: 0,
max: INTERVALS.length - 1,
- onchange: v => repeat.interval = INTERVALS[v]
+ onchange: v => {
+ repeat = repeatObj;
+ repeat.interval = INTERVALS[v];
+ }
}
};
}
diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json
index bcd60a376..31a3e5bf0 100644
--- a/apps/alarm/metadata.json
+++ b/apps/alarm/metadata.json
@@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
- "version": "0.43",
+ "version": "0.44",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm",
diff --git a/apps/approxclock/ChangeLog b/apps/approxclock/ChangeLog
new file mode 100644
index 000000000..9ba3e983a
--- /dev/null
+++ b/apps/approxclock/ChangeLog
@@ -0,0 +1,4 @@
+0.1: Initial release
+0.2: Added more descriptive approximations
+0.2f: Bug fixes: Incorrect hour drawn after 50 mins, incorrect quarter minute drawn after 50 mins
+0.3: Added touch interaction to display exact time and date.
\ No newline at end of file
diff --git a/apps/approxclock/app-icon.js b/apps/approxclock/app-icon.js
new file mode 100644
index 000000000..d63ad4b1f
--- /dev/null
+++ b/apps/approxclock/app-icon.js
@@ -0,0 +1 @@
+atob("MDAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArgVYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABW19cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsrNcrAACBVoGsVgAAgVaBrFYAAKzXK4GsKwAAgayBAAAAgVYAVoEAAAAAAAAAAADXVqyBAACs14Gs1ysArNeBrNcrAIHX14HXgQCB14HXrAAAVtdW11YAAAAAAAAAAFbXK4GsAACs1wAr11YArNcAK9dWAADXrABWVgDXgQCB1wAAAKzXgQAAAAAAAAAAAKzXrNfXKwCs1wAA14EArKwAK9dWAADXVgAAAADXgQBW1ysAAIHXVgAAAAAAAAAAANfXgYHXVgCs11aB11YArNcrgddWACvXgSsAAACsrCus1wAAK9es1ysAAAAAAAAAK9dWAACsrACsrKzXrAAArKzX16wAVtfX16wAAAAr19fXKwAArKwArKwAAAAAAAAAAAAAAAAAAACsrAArAAAArIEAKwAAAAAAAAAAAAAAACsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsrAAAAAAArIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWVgAAAAAAVlYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArVoErAAAAAAAAAAAAAAAAAAAAAAArVgAAAAAAAAAAAAAAAAAAAAAAAAArrNfXgQCBrNdWAAAAAAAAAAAAAAAAAAAAAABW1wAAAAAAAAAAAAAAAAAAAAAAACvXrCtWgQAAANdWAAAAAAArKwAAAAAAACsAAABW1wAAAAAAAAAAAAAAAAAAAAAAAFbXKwAAAAAAANdWAAAAAIHX16wAAACB19fXVgBW1wAA14EAAAAAAAAAAAAAAAAAAIGsAAAAAAAAANdWAAAAK9eBVteBACvXgSuBVgBW1wBW1ysAAAAAAAAAAAAAAAAAAIHXAAAAAAAAANdWAAAAVtcrAKyBAFbXKwAAAABW16zX1wAAAAAAAAAAAAAAAAAAAFbXVgAAAAAAANeBAAAAVtcrANeBAFbXKwAAAABW14HXrAAAAAAAAAAAAAAAAAAAAACs14GBrAAAAKzXrIEAK9esrNdWACvX14GBVgBW1wAr11YAAAAAAAAAAAAAAAAAAAAAVoGBgQAAACusrIEAACusrFYAAAArgaysVgBWgQAAgYEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
\ No newline at end of file
diff --git a/apps/approxclock/app.js b/apps/approxclock/app.js
new file mode 100644
index 000000000..4f0bb570a
--- /dev/null
+++ b/apps/approxclock/app.js
@@ -0,0 +1,156 @@
+//load fonts
+require("FontSinclair").add(Graphics);
+require("FontTeletext5x9Ascii").add(Graphics);
+
+//const
+
+const numbers = {
+ "0": "Twelve",
+ "1": "One",
+ "2": "Two",
+ "3": "Three",
+ "4": "Four",
+ "5": "Five",
+ "6": "Six",
+ "7": "Seven",
+ "8": "Eight",
+ "9": "Nine",
+ "10": "Ten",
+ "11": "Eleven",
+ "12": "Twelve",
+ "13": "One",
+ "14": "Two",
+ "15": "Three",
+ "16": "Four",
+ "17": "Five",
+ "18": "Six",
+ "19": "Seven",
+ "20": "Eight",
+ "21": "Nine",
+ "22": "Ten",
+ "23": "Eleven",
+ "24": "Twelve",
+};
+
+const minutesByQuarterString = {
+ 0: "O'Clock",
+ 15: "Fifteen",
+ 30: "Thirty",
+ 45: "Fourty-Five"
+};
+
+const width = g.getWidth();
+const height = g.getHeight();
+let drawTimeout;
+
+const getNearestHour = (hours, minutes) => {
+ if (minutes > 54) {
+ return hours + 1;
+ }
+ return hours;
+};
+
+const getApproximatePrefix = (minutes, minutesByQuarter) => {
+ if (minutes === minutesByQuarter) {
+ return " exactly";
+ } else if (minutesByQuarter - minutes < -54) {
+ return " nearly";
+ } else if (minutesByQuarter - minutes < -5) {
+ return " after";
+ } else if (minutesByQuarter - minutes < 0) {
+ return " just after";
+ } else if (minutesByQuarter - minutes > 5) {
+ return " before";
+ } else {
+ return " nearly";
+ }
+};
+
+const getMinutesByQuarter = minutes => {
+ if (minutes < 10) {
+ return 0;
+ } else if (minutes < 20) {
+ return 15;
+ } else if (minutes < 40) {
+ return 30;
+ } else if (minutes < 55) {
+ return 45;
+ } else {
+ return 0;
+ }
+};
+
+// schedule a draw for the next minute
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function () {
+ drawTimeout = undefined;
+ drawTime();
+ }, 60000 - (Date.now() % 60000));
+}
+
+const drawTimeExact = () => {
+ var dateTime = Date();
+ var hours = dateTime.getHours();
+ var minutes = dateTime.getMinutes().toString().padStart(2,0);
+ var day = dateTime.getDay();
+ var date = dateTime.getDate();
+ var month = dateTime.getMonth();
+ var year = dateTime.getFullYear();
+ g.clear();
+ g.setBgColor(0,0,0);
+ g.clearRect(0,0,width, height);
+ g.setColor(1,1,1);
+ g.setFont("Vector", 30);
+ g.drawString(hours + ":" + minutes, (width - g.stringWidth(hours + ":" + minutes))/2, height * 0.3, false);
+ g.setFont("Vector", 26);
+ g.drawString(month + 1 + "/" + date + "/" + year, (width - g.stringWidth(month + 1 + "/" + date + "/" + year))/2, height * 0.6, false);
+};
+
+const drawTime = () => {
+ //Grab time vars
+ var date = Date();
+ var hour = date.getHours();
+ var minutes = date.getMinutes();
+ var minutesByQuarter = getMinutesByQuarter(minutes);
+
+ //reset graphics
+ g.clear();
+ g.reset();
+
+ //Build watch face
+ g.setBgColor(0, 0, 0);
+ g.clearRect(0, 0, width, height);
+ g.setFont("Vector", 22);
+ g.setColor(1, 1, 1);
+ g.drawString("It's" + getApproximatePrefix(minutes, minutesByQuarter), (width - g.stringWidth("It's" + getApproximatePrefix(minutes, minutesByQuarter))) / 2, height * 0.25, false);
+ g.setFont("Vector", 30);
+ g.drawString(numbers[getNearestHour(hour, minutes)], (width - g.stringWidth(numbers[getNearestHour(hour, minutes)])) / 2, height * 0.45, false);
+ g.setFont("Vector", 22);
+ g.drawString(minutesByQuarterString[minutesByQuarter], (width - g.stringWidth(minutesByQuarterString[minutesByQuarter])) / 2, height * 0.7, false);
+
+ queueDraw();
+};
+
+g.clear();
+drawTime();
+
+Bangle.on('lcdPower', function (on) {
+ if (on) {
+ drawTime();
+ } else {
+ if (idTimeout) {
+ clearTimeout(idTimeout);
+ }
+ }
+});
+
+Bangle.on('touch', function(button, xy){
+ drawTimeExact();
+ setTimeout(drawTime, 7000);
+});
+
+// Show launcher when button pressed
+Bangle.setUI("clock");
+Bangle.loadWidgets();
+Bangle.drawWidgets();
\ No newline at end of file
diff --git a/apps/approxclock/app.png b/apps/approxclock/app.png
new file mode 100644
index 000000000..a5fd8db83
Binary files /dev/null and b/apps/approxclock/app.png differ
diff --git a/apps/approxclock/metadata.json b/apps/approxclock/metadata.json
new file mode 100644
index 000000000..80e476e5b
--- /dev/null
+++ b/apps/approxclock/metadata.json
@@ -0,0 +1,18 @@
+{ "id": "approxclock",
+ "name": "Approximate Clock",
+ "shortName" : "Approx Clock",
+ "version": "0.3",
+ "icon": "app.png",
+ "description": "A really basic spelled out time display for people looking for the vague time at a glance.",
+ "readme": "readme.md",
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS2"],
+ "storage": [
+ {"name":"approxclock.app.js","url":"app.js"},
+ {"name":"approxclock.img","url":"app-icon.js","evaluate":true}
+ ],
+ "screenshots": [
+ {"url": "screenshot.png"}
+ ]
+}
diff --git a/apps/approxclock/readme.md b/apps/approxclock/readme.md
new file mode 100644
index 000000000..aa1b74267
--- /dev/null
+++ b/apps/approxclock/readme.md
@@ -0,0 +1,7 @@
+## Approximate Clock
+
+### Description
+
+Get a rough idea of the time at a quick glance, mostly made for myself based on a similar watchface on pebble. I find this keeps me from checking my watch too often and also saves me from moments of severe brainfart staring at these mysterious symbols we call numbers.
+
+Exact time and date can be viewed temporarily by touching the screen.
\ No newline at end of file
diff --git a/apps/approxclock/screenshot.png b/apps/approxclock/screenshot.png
new file mode 100644
index 000000000..fe13bbe33
Binary files /dev/null and b/apps/approxclock/screenshot.png differ
diff --git a/apps/astral/ChangeLog b/apps/astral/ChangeLog
index 747e5ac2e..e77aa2ca2 100644
--- a/apps/astral/ChangeLog
+++ b/apps/astral/ChangeLog
@@ -3,3 +3,6 @@
0.03: Update to use Bangle.setUI instead of setWatch
0.04: Tell clock widgets to hide.
0.05: Added adjustment for Bangle.js magnetometer heading fix
+0.06: optimized to update much faster
+0.07: added support for bangle.js 2
+0.08: call setUI before loading widgets to indicate we're a clock
diff --git a/apps/astral/README.md b/apps/astral/README.md
index f9d897e2f..894d0cb8a 100644
--- a/apps/astral/README.md
+++ b/apps/astral/README.md
@@ -1,29 +1,29 @@
Astral Clock
============
+NOTE FOR THE BANGLE 2 THIS APP ONLY SUPPORTS USING THE BLACK BACKGROUND CURRENTLY
+
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.

(The clock does have Pluto now - felt bad for leaving it out)
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.
+---------
+**BTN2**: Load side-menu as standard for clocks.
+Swiping left or right will alternate between planets and other astronomy targets, see below for how to change these addtional ones.
-The text will turn blue during calculation and then back again once complete.
+The data is refreshed automatically every 2 minutes. You can force a refresh as well by swiping up or, on Bangle 1, pressing Button 3.
-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.
+Swiping down will disable/enable the compass and GPS.
+
+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. One the Bangle.JS 2, the colour will be a light blue rather than red because the colours are not as vibrant. 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.
+The compass is displayed on the left.
-Also the compass isn\’t tilt compensated so try and keep the face parallel when taking a reading.
+Also the compass isn\’t tilt compensated so try and keep the face parallel when taking a reading. It's more of an indicator, for a more accurate compass reading, you can use one of the many great apps in the apploader that compensated for movement and angles of the watch etc.
Additional Astronomy Targets
----------------------------
@@ -37,7 +37,7 @@ The type property is not utilised as yet but relates to whether the object is (i
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.
+Put together, initially at least, by \"Ben Jabituya\", https://majorinput.co.uk, jabituyaben@gmail.com.
Credit to various sources from which I have literally taken source code and shoehorned to fit on the Bangle:
diff --git a/apps/astral/app.js b/apps/astral/app.js
index a435ca9e3..7c8f5512a 100644
--- a/apps/astral/app.js
+++ b/apps/astral/app.js
@@ -1,7 +1,11 @@
-setupcomplete_colour = "#ff3329";
-default_colour = "#ffffff";
-calc_display_colour = "#00FFFF";
-display_colour = default_colour;
+
+require("Font8x12").add(Graphics);
+require("Font7x11Numeric7Seg").add(Graphics);
+
+var setupcomplete_colour = "#ff3329";
+var default_colour = "#ffffff";
+var calc_display_colour = "#00FFFF";
+var display_colour = default_colour;
var processing = false;
var all_extras_array = [];
@@ -10,11 +14,19 @@ var mode = "planetary";
var modeswitch = false;
var colours_switched = false;
+var sensorsOn = false;
// Load fonts
-require("Font7x11Numeric7Seg").add(Graphics);
+
// position on screen
-const Xaxis = 150, Yaxis = 55;
+var screenSize = g.getHeight();
+console.log(screenSize);
+var Xaxis = 150, Yaxis = 55;
+if (screenSize <= 176) {
+ setupcomplete_colour = "#00FFFF";
+ Xaxis = 110;
+ Yaxis = 40;
+}
//lat lon settings loading
var astral_settings;
@@ -22,36 +34,36 @@ 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;
+ 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.5074,
- lon: 0.1278,
- 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: "132953", 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));
+ astral_settings = {
+ version: 1,
+ lat: 51.5074,
+ lon: 0.1278,
+ 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: "132953", 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 = "--";
@@ -64,66 +76,66 @@ var mins;
var secs;
var calc = {
- lat_degrees: "",
- lat_minutes: "",
- lon_degrees: "",
- lon_minutes: "",
- month: "",
- day: "",
- hour: "",
- minute: "",
- second: "",
- thisday: "",
- south: "",
- north: "",
- west: "",
- east: ""
+ 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");
+ "Mars", "Jupiter", "Saturn ",
+ "Uranus ", "Neptune", "Pluto");
function acos_estimate(x) {
- return (-0.69813170079773212 * x * x - 0.87266462599716477) * x + 1.5707963267948966;
+ 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;
+ 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);
+ // 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();
+ 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;
- }
+ 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
@@ -132,334 +144,335 @@ 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]
+ 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]
+ 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]
+ 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_second = parseInt(coord_string.slice(-2));
+ var extras_minute;
+ var extras_hour;
+ var extras_calc;
- var extras_signcheck = coord_string.charAt(0);
+ 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;
+ 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 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();
+ 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;
+ 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]);
}
- 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;
+ all_extras_array.sort(function (a, b) {
+ return b[0] - a[0];
+ });
- // 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]);
- }
+ 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;
+ 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 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;
+ 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));
+ // 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;
+ // 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));
+ // 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));
+ // 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;
- }
+ 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;
+ // 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);
+ // 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);
+ // 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
+ 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!");
- }
+ 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;
+ var V, E1;
- // initial approximation of eccentric anomaly
- var E = M + e * Math.sin(M) * (1.0 + e * Math.cos(M));
+ // 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);
+ 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));
+ // 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
+ if (V < 0) V = V + (2 * Math.PI); // modulo 2pi
- return V;
+ return V;
}
// converts hour angle in degrees into hour angle string
function ha2str(x) {
- if ((x < 0) || (360 < x)) print("function ha2str() range error!");
+ 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";
+ 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!");
+ 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) + "'";
+ 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 sgn + cintstr(d, 2) + "° ";
}
// 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;
+ 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;
+ 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;
}
//
@@ -470,36 +483,36 @@ function mod2pi(x) {
// 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;
+ 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;
+ // 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;
+ // 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 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);
+ // 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);
+ az = acos_estimate(cos_az);
- // convert radians to degrees
- h.alt = alt * DEGS;
- h.az = az * DEGS;
+ // convert radians to degrees
+ h.alt = alt * DEGS;
+ h.az = az * DEGS;
- // choose hemisphere
- if (Math.sin(ha) > 0) h.az = 360 - h.az;
+ // choose hemisphere
+ if (Math.sin(ha) > 0) h.az = 360 - h.az;
}
//
@@ -511,279 +524,333 @@ function coord_to_horizon(utc, ra, dec, lat, lon, h) {
//
function mean_sidereal_time(lon) {
- if ((month == 1) || (month == 2)) {
- year = year - 1;
- month = month + 12;
- }
+ if ((month == 1) || (month == 2)) {
+ year = year - 1;
+ month = month + 12;
+ }
- var a = Math.floor(year / 100);
- // var a = Math.floor(2019 / 100);
+ 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));
+ 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;
+ // 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;
+ // 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;
+ // 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;
+ mst = mst % 360;
+ 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;
+ 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) + "'";
+ 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) + "'";
+ return sgn + cintstr(d, 3) + "° ";
}
// 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;
+ 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;
+ return cintstr(d, 3) + "° ";
}
// 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;
+ 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;
+ return cintstr(d, 3) + "° ";
}
// format two digits with leading zero if needed
function d2(n) {
- if ((n < 0) || (99 < n)) return "xx";
- return (n < 10) ? ("0" + n) : 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;
+ 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 < width - len; i++) // append leading spaces
+ intgr += ' ';
- for (i = 0; i < len; i++) // append digits
- intgr += str.charAt(i);
+ for (i = 0; i < len; i++) // append digits
+ intgr += str.charAt(i);
- return intgr;
+ return intgr;
}
function frealstr(num, width, fract) {
- var str = num.toFixed(fract);
- var len = str.length;
- var real = "";
- var i;
+ 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 < width - len; i++) // append leading spaces
+ real += ' ';
- for (i = 0; i < len; i++) // append digits
- real += str.charAt(i);
+ for (i = 0; i < len; i++) // append digits
+ real += str.charAt(i);
- return real;
+ return real;
}
function getMoonPhase() {
- var now = new Date();
- year = now.getFullYear();
- month = now.getMonth() + 1;
- day = now.getDate();
+ 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;
+ 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);
+ g.setColor(colour);
+ cursor = Yaxis + 50;
+ if (!ready_to_compute) {
+ g.drawString("mode change:", Xaxis + 50, cursor, false);
+ cursor += 15;
+ g.drawString("swipe up to refresh", Xaxis + 50, cursor, true /*clear background*/);
+ cursor += 15;
+ g.drawString("swipe left/right 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);
+ g.setColor(display_colour);
+ var moonOffset = 12;
+ var mooonRadius = 25;
+ if (screenSize > 176) {
if (phase == 5) {
- g.fillCircle(200, Yaxis, 30);
- g.setColor("#000000");
- g.fillRect(220, 25, 240, 90);
+ g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
+ g.setColor("#000000");
+ g.fillRect(220, 25, 240, 90);
}
else if (phase == 6) {
- g.fillCircle(200, Yaxis, 30);
- g.setColor("#000000");
- g.fillRect(200, 25, 240, 90);
+ g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
+ g.setColor("#000000");
+ g.fillRect(200, 25, 240, 90);
}
else if (phase == 1) {
- g.fillCircle(200, Yaxis, 30);
- g.setColor("#000000");
- g.fillCircle(180, Yaxis, 30);
+ g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
+ g.setColor("#000000");
+ g.fillCircle(180, Yaxis + moonOffset, mooonRadius);
}
else if (phase == 4)
- g.fillCircle(200, Yaxis, 30);
+ g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
else if (phase == 3) {
- g.fillCircle(200, Yaxis, 30);
- g.setColor("#000000");
- g.fillRect(160, 25, 180, 90);
+ g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
+ g.setColor("#000000");
+ g.fillRect(160, 25, 180, 90);
}
else if (phase == 2) {
- g.fillCircle(200, Yaxis, 30);
- g.setColor("#000000");
- g.fillRect(160, 25, 200, 90);
+ g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
+ g.setColor("#000000");
+ g.fillRect(160, 25, 200, 90);
}
else if (phase == 7) {
- g.fillCircle(200, Yaxis, 30);
- g.setColor("#000000");
- g.fillCircle(220, Yaxis, 30);
+ g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
+ g.setColor("#000000");
+ g.fillCircle(220, Yaxis, 25);
}
+ }
+ else {
+ moonOffset = 12;
+ //var moonOffsetX = 150;
+ mooonRadius = 17;
g.setColor(display_colour);
+ if (phase != 0)
+ g.fillCircle(150, Yaxis + moonOffset, mooonRadius);
+ if (phase == 5) {
+ g.setColor("#000000");
+ g.fillRect(165, 25, 180, 90);
+ }
+ else if (phase == 6) {
+ g.setColor("#000000");
+ g.fillRect(150, 25, 240, 90);
+ }
+ else if (phase == 1) {
+ g.setColor("#000000");
+ g.fillCircle(140, Yaxis + moonOffset, mooonRadius);
+ }
+ else if (phase == 4)
+ g.fillCircle(150, Yaxis + moonOffset, mooonRadius);
+ else if (phase == 3) {
+ g.setColor("#000000");
+ g.fillRect(125, 25, 135, 90);
+ }
+ else if (phase == 2) {
+ g.setColor("#000000");
+ g.fillRect(125, 25, 150, 90);
+ }
+ else if (phase == 7) {
+ g.setColor("#000000");
+ g.fillCircle(160, Yaxis + moonOffset, mooonRadius);
+ }
+ }
+ g.setColor(display_colour);
+}
+
+function autoUpdate() {
+ ready_to_compute = true;
+ g.setColor(display_colour);
+ g.fillCircle(15, 160, 5);
+ setTimeout(function () {
+ print("ready");
+ draw();
+ secondInterval = setInterval(draw, 1000);
+ }, 100);
}
function draw() {
- if (astral_settings.astral_default)
- display_colour = default_colour;
- else if (!colours_switched)
- display_colour = setupcomplete_colour;
+ //print("drawing");
+ 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();
+ // 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", 4);
+ g.setFontAlign(1, 1); // align right bottom
+ g.drawString(time, Xaxis + 23, Yaxis + 34, 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) + " ";
+
+ if (screenSize < 177) {
+ g.drawString(dateStr, 100, 20, true /*clear background*/);
+ }
+ else {
+ //bangle 1
+ g.drawString(dateStr, 150, 40, true /*clear background*/);
+ }
+
+ //compute location of objects
+ g.setFontAlign(1, 1);
+ g.setFont("6x8", 1);
+
+ if (ready_to_compute)
+ g.setColor(calc_display_colour);
+
+ if (modeswitch)
+ g.setColor("#000000");
+
+ cursor = Yaxis + 50;
+ if (pstrings.length != 0) {
+
+ for (let i = 0; i < pstrings.length; i++) {
+ g.drawString(pstrings[i], Xaxis + 40, cursor, true /*clear background*/);
+ cursor += 10;
+ }
+ if (secondInterval) clearInterval(secondInterval);
+ secondInterval = undefined;
+ }
+ 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 = [];
+ if (sensorsOn) {
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.fillCircle(160, 160, 5);
+ }
+}
- 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 = [];
+function SwitchSensorState() {
+ if (sensorsOn) {
+ print("turning sensors off");
+ Bangle.setCompassPower(0);
+ Bangle.setGPSPower(0);
+ sensorsOn = 0;
+ g.setColor("#000000");
+ g.fillCircle(screenSize - 15, screenSize - 15, 7);
+ //g.drawString(compass_heading, 30, 115, true /*clear background*/);
+ g.fillRect(0, 100, 30, 120);
+ }
+ else {
+ Bangle.setCompassPower(1);
+ Bangle.setGPSPower(1);
+ sensorsOn = 1;
+ g.setColor(display_colour);
+ g.fillCircle(screenSize - 15, screenSize - 15, 5);
+ }
}
g.clear();
+g.setColor("#000000");
+g.setBgColor(0, 0, 0);
+g.fillRect(0, 0, 175, 175);
current_moonphase = getMoonPhase();
-Bangle.setUI("clockupdown", btn => {
- if (btn==0) {
- if (!processing) {
- if (!modeswitch) {
- modeswitch = true;
- if (mode == "planetary") mode = "extras";
- else mode = "planetary";
- }
- else
- modeswitch = false;
- }
- } else {
- if (!processing)
- ready_to_compute = true;
- }
-});
-
+Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
@@ -792,63 +859,74 @@ 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
- }
-});
+var updateInterval = setInterval(autoUpdate, 120000);
+//var magInterval = setInterval(updateMag, 50);
Bangle.setCompassPower(1);
Bangle.setGPSPower(1);
+var secondInterval;
+
+autoUpdate();
+
+setWatch(SwitchSensorState, BTN1, { repeat: true });
+if(process.env.HWVERSION != 2)
+ setWatch(autoUpdate, BTN3, { repeat: true });
+
// Show launcher when button pressed
-Bangle.setClockMode();
+//Bangle.setClockMode();
-setWatch(function () {
- if (!astral_settings.astral_default) {
- colours_switched = true;
- if (display_colour == setupcomplete_colour)
- display_colour = default_colour;
- else
- display_colour = setupcomplete_colour;
- draw_moon(current_moonphase);
+Bangle.on("swipe", function (directionLR, directionUD) {
+ if (!processing) {
+ if (-1 == directionUD) {
+ g.setColor(display_colour);
+ g.fillCircle(15, 160, 5);
+ ready_to_compute = true;
+ }
+ else if (-1 == directionLR || directionLR == 1) {
+ print("attempting mode switch");
+ if (mode == "planetary") mode = "extras";
+ else mode = "planetary";
+ g.setColor(display_colour);
+ g.fillCircle(15, 160, 5);
+ ready_to_compute = true;
+ }
+ else if (directionUD == 1) {
+ SwitchSensorState();
}
-}, BTN4, { repeat: true });
+ setTimeout(function () {
+ print("ready");
+ draw();
+ secondInterval = setInterval(draw, 1000);
+ }, 100);
+ }
+});
//events
Bangle.on('mag', function (m) {
- g.setFont("6x8",2);
- if (isNaN(m.heading))
- compass_heading = "---";
- else
- compass_heading = Math.round(m.heading);
- // g.setColor("#000000");
- // g.fillRect(160, 10, 160, 20);
+ if (!isNaN(m.heading)) {
+ compass_heading = Math.round(m.heading);
g.setColor(display_colour);
- if(compass_heading<100)
+ if (compass_heading < 100)
compass_heading = " " + compass_heading;
- g.drawString(compass_heading, 150, 20, true /*clear background*/);
+ if (sensorsOn) {
+ g.setFont("8x12");
+ g.setFontAlign(1, 1);
+ g.drawString(compass_heading, 25, 112, true /*clear background*/);
+ }
+ }
+ //var n = magArray.length;
+ //var mean = Math.round( magArray.reduce((a, b) => a + b) / n);
+ //compass_heading = mean;
});
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));
- }
+ if (g.fix) {
+ display_colour = setupcomplete_colour;
+ 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));
+ }
});
diff --git a/apps/astral/metadata.json b/apps/astral/metadata.json
index 647066a13..16696056f 100644
--- a/apps/astral/metadata.json
+++ b/apps/astral/metadata.json
@@ -1,13 +1,13 @@
{
"id": "astral",
"name": "Astral Clock",
- "version": "0.05",
+ "version": "0.08",
"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.",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
- "supports": ["BANGLEJS"],
"readme": "README.md",
+ "supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"astral.app.js","url":"app.js"},
{"name":"astral.img","url":"app-icon.js","evaluate":true}
diff --git a/apps/backswipe/ChangeLog b/apps/backswipe/ChangeLog
index 1e5479d6e..54ffebfc7 100644
--- a/apps/backswipe/ChangeLog
+++ b/apps/backswipe/ChangeLog
@@ -1,2 +1,3 @@
0.01: New App!
0.02: Don't fire if the app uses swipes already.
+0.03: Only count defined handlers in the handler array.
diff --git a/apps/backswipe/boot.js b/apps/backswipe/boot.js
index e46f902eb..d5d54cd59 100644
--- a/apps/backswipe/boot.js
+++ b/apps/backswipe/boot.js
@@ -26,7 +26,7 @@
if (Bangle["#on"+eventType] === undefined) {
return 0;
} else if (Bangle["#on"+eventType] instanceof Array) {
- return Bangle["#on"+eventType].length;
+ return Bangle["#on"+eventType].filter(x=>x).length;
} else if (Bangle["#on"+eventType] !== undefined) {
return 1;
}
diff --git a/apps/backswipe/metadata.json b/apps/backswipe/metadata.json
index c7cb2185f..add609a0f 100644
--- a/apps/backswipe/metadata.json
+++ b/apps/backswipe/metadata.json
@@ -1,7 +1,7 @@
{ "id": "backswipe",
"name": "Back Swipe",
"shortName":"BackSwipe",
- "version":"0.02",
+ "version":"0.03",
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
"icon": "app.png",
"tags": "back,gesture,swipe",
diff --git a/apps/calclock/ChangeLog b/apps/calclock/ChangeLog
index 90bcfb9d4..b157136a5 100644
--- a/apps/calclock/ChangeLog
+++ b/apps/calclock/ChangeLog
@@ -4,3 +4,4 @@
0.04: Improve current time readability in light theme.
0.05: Show calendar colors & improved all day events.
0.06: Improved multi-line locations & titles
+0.07: Buzz 30, 15 and 1 minute before an event
diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js
index 1f98502ef..622b77612 100644
--- a/apps/calclock/calclock.js
+++ b/apps/calclock/calclock.js
@@ -117,6 +117,17 @@ function fullRedraw() {
drawFutureEvents(y);
}
+function buzzForEvents() {
+ let nextEvent = next[0]; if (!nextEvent) return;
+ if (nextEvent.allDay) return;
+ let minToEvent = Math.round((nextEvent.timestamp - getTime()) / 60.0);
+ switch (minToEvent) {
+ case 30: require("buzz").pattern(","); break;
+ case 15: require("buzz").pattern(", ,"); break;
+ case 1: require("buzz").pattern(": : :"); break;
+ }
+}
+
function redraw() {
g.reset();
if (current.find(e=>!isActive(e)) || next.find(isActive)) {
@@ -124,10 +135,12 @@ function redraw() {
} else {
drawCurrentEvents(30);
}
+ buzzForEvents();
}
g.clear();
fullRedraw();
+buzzForEvents();
var minuteInterval = setInterval(redraw, 60 * 1000);
Bangle.setUI("clock");
diff --git a/apps/calclock/metadata.json b/apps/calclock/metadata.json
index bfd847595..5cb47956b 100644
--- a/apps/calclock/metadata.json
+++ b/apps/calclock/metadata.json
@@ -2,7 +2,7 @@
"id": "calclock",
"name": "Calendar Clock",
"shortName": "CalClock",
- "version": "0.06",
+ "version": "0.07",
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
"icon": "calclock.png",
"type": "clock",
diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog
index 12776867f..6edb54f65 100644
--- a/apps/calendar/ChangeLog
+++ b/apps/calendar/ChangeLog
@@ -15,3 +15,5 @@
Display events for current month on touch
0.14: Add support for holidays
0.15: Edit holidays on device in settings
+0.16: Add menu to fast open settings to edit holidays
+ Display Widgets in menus
diff --git a/apps/calendar/README.md b/apps/calendar/README.md
index 5f90d0d52..7fa7bea1c 100644
--- a/apps/calendar/README.md
+++ b/apps/calendar/README.md
@@ -1,6 +1,6 @@
# Calendar
-Basic calendar
+Monthly calendar, displays holidays uploaded from the web interface and scheduled events.
## Usage
@@ -14,4 +14,4 @@ Basic calendar
## Settings
-- B2 Colors: use non-dithering colors (default, recommended for Bangle 2) or the original color scheme.
+B2 Colors: use non-dithering colors (default, recommended for Bangle 2) or the original color scheme.
diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js
index 0ae852d83..7477775ca 100644
--- a/apps/calendar/calendar.js
+++ b/apps/calendar/calendar.js
@@ -1,3 +1,4 @@
+{
const maxX = g.getWidth();
const maxY = g.getHeight();
const fontSize = g.getWidth() > 200 ? 2 : 1;
@@ -17,71 +18,85 @@ const red = "#d41706";
const blue = "#0000ff";
const yellow = "#ffff00";
const cyan = "#00ffff";
-let bgColor = color4;
-let bgColorMonth = color1;
-let bgColorDow = color2;
-let bgColorWeekend = color3;
-let fgOtherMonth = gray1;
-let fgSameMonth = white;
-let bgEvent = blue;
-let bgOtherEvent = "#ff8800";
+let bgColor;
+let bgColorMonth;
+let bgColorDow;
+let bgColorWeekend;
+let fgOtherMonth;
+let fgSameMonth;
+let bgEvent;
+let bgOtherEvent;
const eventsPerDay=6; // how much different events per day we can display
const date = new Date();
const timeutils = require("time_utils");
-let settings = require('Storage').readJSON("calendar.json", true) || {};
let startOnSun = ((require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0) === 0;
- // all alarms that run on a specific date
-const events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
- const date = new Date(a.date);
- const time = timeutils.decodeTime(a.t);
- date.setHours(time.h);
- date.setMinutes(time.m);
- date.setSeconds(time.s);
- return {date: date, msg: a.msg, type: "e"};
-});
-// add holidays & other events
-(require("Storage").readJSON("calendar.days.json",1) || []).forEach(d => {
- const date = new Date(d.date);
- const o = {date: date, msg: d.name, type: d.type};
- if (d.repeat) {
- o.repeat = d.repeat;
- }
- events.push(o);
-});
-
-if (settings.ndColors === undefined) {
- settings.ndColors = !g.theme.dark;
-}
-
-if (settings.ndColors === true) {
- bgColor = white;
- bgColorMonth = blue;
- bgColorDow = black;
- bgColorWeekend = yellow;
- fgOtherMonth = blue;
- fgSameMonth = black;
- bgEvent = color2;
- bgOtherEvent = cyan;
-}
-
-function getDowLbls(locale) {
- let days = startOnSun ? [0, 1, 2, 3, 4, 5, 6] : [1, 2, 3, 4, 5, 6, 0];
+let events;
+const dowLbls = function() {
+ const locale = require('locale').name;
+ const days = startOnSun ? [0, 1, 2, 3, 4, 5, 6] : [1, 2, 3, 4, 5, 6, 0];
const d = new Date();
return days.map(i => {
d.setDate(d.getDate() + (i + 7 - d.getDay()) % 7);
return require("locale").dow(d, 1);
});
-}
+}();
-function sameDay(d1, d2) {
+const loadEvents = () => {
+ // all alarms that run on a specific date
+ events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
+ const date = new Date(a.date);
+ const time = timeutils.decodeTime(a.t);
+ date.setHours(time.h);
+ date.setMinutes(time.m);
+ date.setSeconds(time.s);
+ return {date: date, msg: a.msg, type: "e"};
+ });
+ // add holidays & other events
+ (require("Storage").readJSON("calendar.days.json",1) || []).forEach(d => {
+ const date = new Date(d.date);
+ const o = {date: date, msg: d.name, type: d.type};
+ if (d.repeat) {
+ o.repeat = d.repeat;
+ }
+ events.push(o);
+ });
+};
+
+const loadSettings = () => {
+ let settings = require('Storage').readJSON("calendar.json", true) || {};
+ if (settings.ndColors === undefined) {
+ settings.ndColors = !g.theme.dark;
+ }
+ if (settings.ndColors === true) {
+ bgColor = white;
+ bgColorMonth = blue;
+ bgColorDow = black;
+ bgColorWeekend = yellow;
+ fgOtherMonth = blue;
+ fgSameMonth = black;
+ bgEvent = color2;
+ bgOtherEvent = cyan;
+ } else {
+ bgColor = color4;
+ bgColorMonth = color1;
+ bgColorDow = color2;
+ bgColorWeekend = color3;
+ fgOtherMonth = gray1;
+ fgSameMonth = white;
+ bgEvent = blue;
+ bgOtherEvent = "#ff8800";
+ }
+};
+
+const sameDay = function(d1, d2) {
"jit";
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate();
-}
+};
-function drawEvent(ev, curDay, x1, y1, x2, y2) {
+const drawEvent = function(ev, curDay, x1, y1, x2, y2) {
"ram";
switch(ev.type) {
case "e": // alarm/event
@@ -99,9 +114,33 @@ function drawEvent(ev, curDay, x1, y1, x2, y2) {
g.setColor(bgOtherEvent).fillRect(x1+1, y1+1, x2-1, y2-1);
break;
}
-}
+};
-function drawCalendar(date) {
+const calcDays = (month, monthMaxDayMap, dowNorm) => {
+ "jit";
+ const maxDay = colN * (rowN - 1) + 1;
+ const days = [];
+ let nextMonthDay = 1;
+ let thisMonthDay = 51;
+ const month2 = month;
+ let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm + 1;
+
+ for (let i = 0; i < maxDay; i++) {
+ if (i < dowNorm) {
+ days.push(prevMonthDay);
+ prevMonthDay++;
+ } else if (thisMonthDay <= monthMaxDayMap[month] + 50) {
+ days.push(thisMonthDay);
+ thisMonthDay++;
+ } else {
+ days.push(nextMonthDay);
+ nextMonthDay++;
+ }
+ }
+ return days;
+};
+
+const drawCalendar = function(date) {
g.setBgColor(bgColor);
g.clearRect(0, 0, maxX, maxY);
g.setBgColor(bgColorMonth);
@@ -139,7 +178,6 @@ function drawCalendar(date) {
true
);
- let dowLbls = getDowLbls(require('locale').name);
dowLbls.forEach((lbl, i) => {
g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2);
});
@@ -163,23 +201,7 @@ function drawCalendar(date) {
11: 31
};
- let days = [];
- let nextMonthDay = 1;
- let thisMonthDay = 51;
- let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm + 1;
- for (let i = 0; i < colN * (rowN - 1) + 1; i++) {
- if (i < dowNorm) {
- days.push(prevMonthDay);
- prevMonthDay++;
- } else if (thisMonthDay <= monthMaxDayMap[month] + 50) {
- days.push(thisMonthDay);
- thisMonthDay++;
- } else {
- days.push(nextMonthDay);
- nextMonthDay++;
- }
- }
-
+ const days = calcDays(month, monthMaxDayMap, dowNorm);
const weekBeforeMonth = new Date(date.getTime());
weekBeforeMonth.setDate(weekBeforeMonth.getDate() - 7);
const week2AfterMonth = new Date(date.getFullYear(), date.getMonth()+1, 0);
@@ -189,8 +211,15 @@ function drawCalendar(date) {
ev.date.setFullYear(ev.date.getMonth() < 6 ? week2AfterMonth.getFullYear() : weekBeforeMonth.getFullYear());
}
});
- const eventsThisMonth = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth);
- eventsThisMonth.sort((a,b) => a.date - b.date);
+
+ const eventsThisMonthPerDay = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth).reduce((acc, ev) => {
+ const day = ev.date.getDate();
+ if (!acc[day]) {
+ acc[day] = [];
+ }
+ acc[day].push(ev);
+ return acc;
+ }, []);
let i = 0;
g.setFont("8x12", fontSize);
for (y = 0; y < rowN - 1; y++) {
@@ -205,13 +234,13 @@ function drawCalendar(date) {
const x2 = x * colW + colW;
const y2 = y * rowH + headerH + rowH + rowH;
- if (eventsThisMonth.length > 0) {
+ const eventsThisDay = eventsThisMonthPerDay[curDay.getDate()];
+ if (eventsThisDay && eventsThisDay.length > 0) {
// Display events for this day
- eventsThisMonth.forEach((ev, idx) => {
+ eventsThisDay.forEach((ev, idx) => {
if (sameDay(ev.date, curDay)) {
drawEvent(ev, curDay, x1, y1, x2, y2);
-
- eventsThisMonth.splice(idx, 1); // this event is no longer needed
+ eventsThisDay.splice(idx, 1); // this event is no longer needed
}
});
}
@@ -235,9 +264,44 @@ function drawCalendar(date) {
);
} // end for (x = 0; x < colN; x++)
} // end for (y = 0; y < rowN - 1; y++)
-} // end function drawCalendar
+}; // end function drawCalendar
+
+const showMenu = function() {
+ const menu = {
+ "" : {
+ title : "Calendar",
+ remove: () => {
+ require("widget_utils").show();
+ }
+ },
+ "< Back": () => {
+ require("widget_utils").hide();
+ E.showMenu();
+ setUI();
+ },
+ /*LANG*/"Exit": () => load(),
+ /*LANG*/"Settings": () => {
+ const appSettings = eval(require('Storage').read('calendar.settings.js'));
+ appSettings(() => {
+ loadSettings();
+ loadEvents();
+ showMenu();
+ });
+ },
+ };
+ if (require("Storage").read("alarm.app.js")) {
+ menu[/*LANG*/"Launch Alarms"] = () => {
+ load("alarm.app.js");
+ };
+ }
+ require("widget_utils").show();
+ E.showMenu(menu);
+};
+
+const setUI = function() {
+ require("widget_utils").hide(); // No space for widgets!
+ drawCalendar(date);
-function setUI() {
Bangle.setUI({
mode : "custom",
swipe: (dirLR, dirUD) => {
@@ -261,7 +325,14 @@ function setUI() {
drawCalendar(date);
}
},
- btn: (n) => n === (process.env.HWVERSION === 2 ? 1 : 3) && load(),
+ btn: (n) => {
+ if (process.env.HWVERSION === 2 || n === 2) {
+ showMenu();
+ } else if (n === 3) {
+ // directly exit only on Bangle.js 1
+ load();
+ }
+ },
touch: (n,e) => {
events.sort((a,b) => a.date - b.date);
const menu = events.filter(ev => ev.date.getFullYear() === date.getFullYear() && ev.date.getMonth() === date.getMonth()).map(e => {
@@ -274,16 +345,19 @@ function setUI() {
}
menu[""] = { title: require("locale").month(date) + " " + date.getFullYear() };
menu["< Back"] = () => {
+ require("widget_utils").hide();
E.showMenu();
- drawCalendar(date);
setUI();
};
+ require("widget_utils").show();
E.showMenu(menu);
}
});
-}
+};
+loadSettings();
+loadEvents();
+Bangle.loadWidgets();
require("Font8x12").add(Graphics);
-drawCalendar(date);
setUI();
-// No space for widgets!
+}
diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json
index bd35c8879..e263efe35 100644
--- a/apps/calendar/metadata.json
+++ b/apps/calendar/metadata.json
@@ -1,8 +1,8 @@
{
"id": "calendar",
"name": "Calendar",
- "version": "0.15",
- "description": "Simple calendar",
+ "version": "0.16",
+ "description": "Monthly calendar, displays holidays uploaded from the web interface and scheduled events.",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],
"tags": "calendar,tool",
diff --git a/apps/calendar/settings.js b/apps/calendar/settings.js
index 40eca9f68..50beed8c0 100644
--- a/apps/calendar/settings.js
+++ b/apps/calendar/settings.js
@@ -1,14 +1,15 @@
(function (back) {
- var FILE = "calendar.json";
+ const FILE = "calendar.json";
const HOLIDAY_FILE = "calendar.days.json";
- var settings = require('Storage').readJSON(FILE, true) || {};
- if (settings.ndColors === undefined)
+ const settings = require('Storage').readJSON(FILE, true) || {};
+ if (settings.ndColors === undefined) {
if (process.env.HWVERSION == 2) {
settings.ndColors = true;
} else {
settings.ndColors = false;
}
- const holidays = require("Storage").readJSON(HOLIDAY_FILE,1).sort((a,b) => new Date(a.date) - new Date(b.date)) || [];
+ }
+ const holidays = (require("Storage").readJSON(HOLIDAY_FILE,1)||[]).sort((a,b) => new Date(a.date) - new Date(b.date)) || [];
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
diff --git a/apps/cards/ChangeLog b/apps/cards/ChangeLog
index 00945cd13..24c1bf8ff 100644
--- a/apps/cards/ChangeLog
+++ b/apps/cards/ChangeLog
@@ -1 +1,2 @@
0.01: Simple app to display loyalty cards
+0.02: Hiding widgets while showing the code
diff --git a/apps/cards/app.js b/apps/cards/app.js
index dcef7da76..33b4c9e15 100644
--- a/apps/cards/app.js
+++ b/apps/cards/app.js
@@ -18,9 +18,8 @@ Bangle.drawWidgets();
const WHITE=-1
const BLACK=0
-var FILE = "android.cards.json";
-
-var Locale = require("locale");
+const Locale = require("locale");
+const widget_utils = require('widget_utils');
var fontSmall = "6x8";
var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
@@ -90,6 +89,7 @@ function printLinearCode(binary) {
}
function showCode(card) {
+ widget_utils.hide();
E.showScroller();
// keeping it on rising edge would come back twice..
setWatch(()=>showCard(card), BTN, {edge:"falling"});
@@ -151,6 +151,7 @@ function showCard(card) {
var titleColor = g.theme.fg2;
if (card.color)
titleColor = isLight(titleBgColor) ? BLACK : WHITE;
+ widget_utils.show();
E.showScroller({
h : g.getFontHeight(), // height of each menu item in pixels
c : lines.length, // number of menu items
diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json
index 63b7da847..810741d5f 100644
--- a/apps/cards/metadata.json
+++ b/apps/cards/metadata.json
@@ -1,7 +1,7 @@
{
"id": "cards",
"name": "Cards",
- "version": "0.01",
+ "version": "0.02",
"description": "Display loyalty cards",
"icon": "app.png",
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
diff --git a/apps/contacts/ChangeLog b/apps/contacts/ChangeLog
new file mode 100644
index 000000000..5560f00bc
--- /dev/null
+++ b/apps/contacts/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
diff --git a/apps/contacts/README.md b/apps/contacts/README.md
new file mode 100644
index 000000000..1bfc99c8e
--- /dev/null
+++ b/apps/contacts/README.md
@@ -0,0 +1,29 @@
+# Contacts
+
+This app provides a common way to set up the `contacts.json` file.
+
+## Contacts JSON file
+
+When the app is loaded from the app loader, a file named
+`contacts.json` is loaded along with the javascript etc. The file
+has the following contents:
+
+```
+[
+ {
+ "name":"NONE"
+ },
+ {
+ "name":"First Last",
+ "number":"123456789",
+ }
+]
+```
+
+## Contacts Editor
+
+Clicking on the download icon of `Contents` in the app loader invokes
+the contact editor. The editor downloads and displays the current
+`contacts.json` file. Clicking the `Edit` button beside an entry
+causes the entry to be deleted from the list and displayed in the edit
+boxes. It can be restored - by clicking the `Add` button.
\ No newline at end of file
diff --git a/apps/contacts/app-icon.js b/apps/contacts/app-icon.js
new file mode 100644
index 000000000..3012be8d8
--- /dev/null
+++ b/apps/contacts/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwcBkmSpIC/AVsJCJ+AQaCZBCOeACKGQLKGQBA0ggARPJ4IRsYo0ggR9IoAIGiRiIpEECJsAiACBBYoRGpEAI4JBFI47CBLIRlDHYJrGYQIRCwQICL4MQOgx9GboUSeQ4RFwAFBiSGHCIo4CiVIWZyPICP4RaRIQROgARHdIwICoIIFkDpGBAKqHgGACI0AyVIggIDoEEMQ1ICINJCIj4CfwIREBwUgQYYOCfYoFDJQKDFCIopEO4RoDKAqJHRhAC/ATA="))
diff --git a/apps/contacts/app.png b/apps/contacts/app.png
new file mode 100644
index 000000000..147dcc61a
Binary files /dev/null and b/apps/contacts/app.png differ
diff --git a/apps/contacts/contacts.app.js b/apps/contacts/contacts.app.js
new file mode 100644
index 000000000..85eef625b
--- /dev/null
+++ b/apps/contacts/contacts.app.js
@@ -0,0 +1,189 @@
+/* contacts.js */
+
+var Layout = require("Layout");
+
+const W = g.getWidth();
+const H = g.getHeight();
+
+var wp = require('Storage').readJSON("contacts.json", true) || [];
+// Use this with corrupted contacts
+//var wp = [];
+
+var key; /* Shared between functions, typically wp name */
+
+function writeContact() {
+ require('Storage').writeJSON("contacts.json", wp);
+}
+
+function mainMenu() {
+ var menu = {
+ "< Back" : Bangle.load
+ };
+ if (Object.keys(wp).length==0) Object.assign(menu, {"NO Contacts":""});
+ else for (let id in wp) {
+ let i = id;
+ menu[wp[id]["name"]]=()=>{ decode(i); };
+ }
+ menu["Add"]=addCard;
+ menu["Remove"]=removeCard;
+ g.clear();
+ E.showMenu(menu);
+}
+
+function decode(pin) {
+ var i = wp[pin];
+ var l = i["name"] + "\n" + i["number"];
+ var la = new Layout ({
+ type:"v", c: [
+ {type:"txt", font:"10%", pad:1, fillx:1, filly:1, label: l},
+ {type:"btn", font:"10%", pad:1, fillx:1, filly:1, label:"OK", cb:l=>{mainMenu();}}
+ ], lazy:true});
+ g.clear();
+ la.render();
+}
+
+function showNumpad(text, key_, callback) {
+ key = key_;
+ E.showMenu();
+ function addDigit(digit) {
+ key+=digit;
+ if (1) {
+ l = text[key.length];
+ switch (l) {
+ case '.': case ' ': case "'":
+ key+=l;
+ break;
+ case 'd': case 'D': default:
+ break;
+ }
+ }
+ Bangle.buzz(20);
+ update();
+ }
+ function update() {
+ g.reset();
+ g.clearRect(0,0,g.getWidth(),23);
+ s = key + text.substr(key.length, 999);
+ g.setFont("Vector:24").setFontAlign(1,0).drawString(s,g.getWidth(),12);
+ }
+ ds="12%";
+ var numPad = new Layout ({
+ type:"v", c: [{
+ type:"v", c: [
+ {type:"", height:24},
+ {type:"h",filly:1, c: [
+ {type:"btn", font:ds, width:58, label:"7", cb:l=>{addDigit("7");}},
+ {type:"btn", font:ds, width:58, label:"8", cb:l=>{addDigit("8");}},
+ {type:"btn", font:ds, width:58, label:"9", cb:l=>{addDigit("9");}}
+ ]},
+ {type:"h",filly:1, c: [
+ {type:"btn", font:ds, width:58, label:"4", cb:l=>{addDigit("4");}},
+ {type:"btn", font:ds, width:58, label:"5", cb:l=>{addDigit("5");}},
+ {type:"btn", font:ds, width:58, label:"6", cb:l=>{addDigit("6");}}
+ ]},
+ {type:"h",filly:1, c: [
+ {type:"btn", font:ds, width:58, label:"1", cb:l=>{addDigit("1");}},
+ {type:"btn", font:ds, width:58, label:"2", cb:l=>{addDigit("2");}},
+ {type:"btn", font:ds, width:58, label:"3", cb:l=>{addDigit("3");}}
+ ]},
+ {type:"h",filly:1, c: [
+ {type:"btn", font:ds, width:58, label:"0", cb:l=>{addDigit("0");}},
+ {type:"btn", font:ds, width:58, label:"C", cb:l=>{key=key.slice(0,-1); update();}},
+ {type:"btn", font:ds, width:58, id:"OK", label:"OK", cb:callback}
+ ]}
+ ]}
+ ], lazy:true});
+ g.clear();
+ numPad.render();
+ update();
+}
+
+function removeCard() {
+ var menu = {
+ "" : {title : "Select Contact"},
+ "< Back" : mainMenu
+ };
+ if (Object.keys(wp).length==0) Object.assign(menu, {"No Contacts":""});
+ else {
+ wp.forEach((val, card) => {
+ const name = wp[card].name;
+ menu[name]=()=>{
+ E.showMenu();
+ var confirmRemove = new Layout (
+ {type:"v", c: [
+ {type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:"Delete"},
+ {type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:name},
+ {type:"h", c: [
+ {type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "YES", cb:l=>{
+ wp.splice(card, 1);
+ writeContact();
+ mainMenu();
+ }},
+ {type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: " NO", cb:l=>{mainMenu();}}
+ ]}
+ ], lazy:true});
+ g.clear();
+ confirmRemove.render();
+ };
+ });
+ }
+ E.showMenu(menu);
+}
+
+function askPosition(callback) {
+ let full = "";
+ showNumpad("dddDDDddd", "", function() {
+ callback(key, "");
+ });
+}
+
+function createContact(lat, name) {
+ let n = {};
+ n["name"] = name;
+ n["number"] = lat;
+ wp.push(n);
+ print("add -- contacts", wp);
+ writeContact();
+}
+
+function addCardName2(key) {
+ g.clear();
+ askPosition(function(lat, lon) {
+ print("position -- ", lat, lon);
+ createContact(lat, result);
+ mainMenu();
+ });
+}
+
+function addCardName(key) {
+ result = key;
+ if (wp[result]!=undefined) {
+ E.showMenu();
+ var alreadyExists = new Layout (
+ {type:"v", c: [
+ {type:"txt", font:Math.min(15,100/result.length)+"%", pad:1, fillx:1, filly:1, label:result},
+ {type:"txt", font:"12%", pad:1, fillx:1, filly:1, label:"already exists."},
+ {type:"h", c: [
+ {type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "REPLACE", cb:l=>{ addCardName2(key); }},
+ {type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "CANCEL", cb:l=>{mainMenu();}}
+ ]}
+ ], lazy:true});
+ g.clear();
+ alreadyExists.render();
+ return;
+ }
+ addCardName2(key);
+}
+
+function addCard() {
+ require("textinput").input({text:""}).then(result => {
+ if (result != "") {
+ addCardName(result);
+ } else
+ mainMenu();
+ });
+}
+
+g.reset();
+Bangle.setUI();
+mainMenu();
diff --git a/apps/contacts/contacts.json b/apps/contacts/contacts.json
new file mode 100644
index 000000000..40afa27dd
--- /dev/null
+++ b/apps/contacts/contacts.json
@@ -0,0 +1,6 @@
+[
+ {
+ "name":"EU emergency",
+ "number":"112"
+ }
+]
diff --git a/apps/contacts/interface.html b/apps/contacts/interface.html
new file mode 100644
index 000000000..013478960
--- /dev/null
+++ b/apps/contacts/interface.html
@@ -0,0 +1,249 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Contacts v.2
+
+
+ Reload Upload
+
+
+
+
+
+
+
+
+
+ Name
+ Number
+
+
+
+
+
+
+
Add a new contact
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/contacts/metadata.json b/apps/contacts/metadata.json
new file mode 100644
index 000000000..c466906b5
--- /dev/null
+++ b/apps/contacts/metadata.json
@@ -0,0 +1,19 @@
+{ "id": "contacts",
+ "name": "contacts",
+ "version":"0.01",
+ "description": "Provides means of storing user contacts, viewing/editing them on device and from the App loader",
+ "icon": "app.png",
+ "tags": "tool",
+ "supports" : ["BANGLEJS2"],
+ "allow_emulator": true,
+ "readme": "README.md",
+ "interface": "interface.html",
+ "dependencies": {"textinput":"type"},
+ "storage": [
+ {"name":"contacts.app.js","url":"contacts.app.js"},
+ {"name":"contacts.img","url":"app-icon.js","evaluate":true}
+ ],
+ "data": [
+ {"name":"contacts.json","url":"contacts.json"}
+ ]
+}
diff --git a/apps/espruinoprog/custom.html b/apps/espruinoprog/custom.html
index a12189707..c6c51ca3e 100644
--- a/apps/espruinoprog/custom.html
+++ b/apps/espruinoprog/custom.html
@@ -128,38 +128,36 @@ LED.set();NRF.sleep();`);
posteditor.on("change", editorChanged);
document.getElementById("upload").addEventListener("click", function() {
- if (!hasWarnings()) {
- var precode = preeditor.getValue();
- var jscode = jseditor.getValue();
- var postcode = posteditor.getValue();
- var namePrefix = document.getElementById("nameprefix").value;
- localStorage.setItem(LS_PRECODE, precode);
- localStorage.setItem(LS_JSCODE, jscode);
- localStorage.setItem(LS_POSTCODE, postcode);
- localStorage.setItem(LS_NAMEPREFIX, namePrefix);
+ var precode = preeditor.getValue();
+ var jscode = jseditor.getValue();
+ var postcode = posteditor.getValue();
+ var namePrefix = document.getElementById("nameprefix").value;
+ localStorage.setItem(LS_PRECODE, precode);
+ localStorage.setItem(LS_JSCODE, jscode);
+ localStorage.setItem(LS_POSTCODE, postcode);
+ localStorage.setItem(LS_NAMEPREFIX, namePrefix);
- // force version - as long as we're above 1v96 we get the ability to upload to different storage files
- var ENV = Espruino.Core.Env.getData();
- ENV.VERSION_MAJOR = 2;
- ENV.VERSION_MINOR = 0;
- // Now compile
- Espruino.transform(jscode, {
- SET_TIME_ON_WRITE : false, // time would just be out of date
- SAVE_ON_SEND : 1, // save to flash
- LOAD_STORAGE_FILE : 0, // do not load from storage after saving
- // PRETOKENISE : true,
- // MINIFICATION_LEVEL : "ESPRIMA", // maybe?
- }).then(content => {
- sendCustomizedApp({
- storage: [{ name: "espruinoprog.json", content: JSON.stringify({
- namePrefix : namePrefix,
- pre : Espruino.Core.CodeWriter.reformatCode(precode),
- code : Espruino.Core.CodeWriter.reformatCode(content),
- post : Espruino.Core.CodeWriter.reformatCode(postcode)
- })}]
- });
+ // force version - as long as we're above 1v96 we get the ability to upload to different storage files
+ var ENV = Espruino.Core.Env.getData();
+ ENV.VERSION_MAJOR = 2;
+ ENV.VERSION_MINOR = 0;
+ // Now compile
+ Espruino.transform(jscode, {
+ SET_TIME_ON_WRITE : false, // time would just be out of date
+ SAVE_ON_SEND : 1, // save to flash
+ LOAD_STORAGE_FILE : 0, // do not load from storage after saving
+ // PRETOKENISE : true,
+ // MINIFICATION_LEVEL : "ESPRIMA", // maybe?
+ }).then(content => {
+ sendCustomizedApp({
+ storage: [{ name: "espruinoprog.json", content: JSON.stringify({
+ namePrefix : namePrefix,
+ pre : Espruino.Core.CodeWriter.reformatCode(precode),
+ code : Espruino.Core.CodeWriter.reformatCode(content),
+ post : Espruino.Core.CodeWriter.reformatCode(postcode)
+ })}]
});
- }
+ });
});
document.getElementById("setdefault").addEventListener("click", function(e) {
e.preventDefault();
diff --git a/apps/fastreset/ChangeLog b/apps/fastreset/ChangeLog
index d777d8c28..eec108328 100644
--- a/apps/fastreset/ChangeLog
+++ b/apps/fastreset/ChangeLog
@@ -1,2 +1,5 @@
0.01: New App!
0.02: Shorten the timeout before executing to 250 ms.
+0.03: Add inner timeout of 150 ms so user has more time to release the button
+ before clock ui is initialized and adds it's button watch for going to
+ launcher.
diff --git a/apps/fastreset/boot.js b/apps/fastreset/boot.js
index c099070e3..5d1fd50b1 100644
--- a/apps/fastreset/boot.js
+++ b/apps/fastreset/boot.js
@@ -1,5 +1,5 @@
{let buzzTimeout;
setWatch((e)=>{
- if (e.state) buzzTimeout = setTimeout(()=>{Bangle.buzz(80,0.40);Bangle.showClock();}, 250);
+ if (e.state) buzzTimeout = setTimeout(()=>{Bangle.buzz(80,0.40);setTimeout(Bangle.showClock,150);}, 250);
if (!e.state && buzzTimeout) clearTimeout(buzzTimeout);},
-BTN,{repeat:true, edge:'both' });}
+BTN,{repeat:true,edge:'both'});}
diff --git a/apps/fastreset/metadata.json b/apps/fastreset/metadata.json
index 2d817a91f..455649b48 100644
--- a/apps/fastreset/metadata.json
+++ b/apps/fastreset/metadata.json
@@ -1,7 +1,7 @@
{ "id": "fastreset",
"name": "Fast Reset",
"shortName":"Fast Reset",
- "version":"0.02",
+ "version":"0.03",
"description": "Reset the watch by pressing the hardware button just a little bit longer than a click. If 'Fastload Utils' is installed this will typically be done with fastloading. A buzz acts as indicator.",
"icon": "app.png",
"type": "bootloader",
diff --git a/apps/flashcount/ChangeLog b/apps/flashcount/ChangeLog
new file mode 100644
index 000000000..5560f00bc
--- /dev/null
+++ b/apps/flashcount/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
diff --git a/apps/flashcount/app-icon.js b/apps/flashcount/app-icon.js
new file mode 100644
index 000000000..e1cf5fb54
--- /dev/null
+++ b/apps/flashcount/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4UA///x3ygHo8H1Jf8AgILLoALVgoLHggLCqAgJioLIqgLDGQsBqtAioOBqoYFqtUAIMVBY9VqwCBDIIAECYILCHowLBrWVBZFlrWWyptGgtq1WqJYI7GrQLCFxGrBYJHEBQNV1Wv9IEBEocFKIOq//qJAIZEAoNq3/+1QMBHoYYBrQLB1J4GitaEYZfGtfvBYJ3HtWr9WlNY0V1Nr1WlC4xIBrWmBZWVrJGFcYILBZY4LBoILIgoNBEILvHDIQ5BBY4IBBYMBMAwLBBA4LPBRMAKAoLRiALWAGw="))
\ No newline at end of file
diff --git a/apps/flashcount/app.js b/apps/flashcount/app.js
new file mode 100644
index 000000000..e3f925b27
--- /dev/null
+++ b/apps/flashcount/app.js
@@ -0,0 +1,59 @@
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+E.showMessage("Loading...");
+Bangle.setOptions({hrmPollInterval:5});
+Bangle.setHRMPower(1);
+
+function drawCounter() {
+ g.reset().clearRect(0,24,175,90);
+ //g.drawRect(0,24,175,90);
+ g.setFontAlign(0,0).setFontVector(60);
+ g.drawString(count, 88, 60);
+}
+
+function hadPulse() {
+ count++;
+ drawCounter();
+ g.setColor("#f00").fillCircle(156,156,20);
+ setTimeout(function() {
+ g.setColor(g.theme.bg).fillCircle(156,156,20);
+ }, 500);
+}
+
+if (parseFloat(process.env.VERSION.replace("v","0"))<2019) {
+ E.showMessage("You need at least firmware 2v19","Error");
+} else if (Bangle.hrmRd(0)!=33) { // wrong sensor - probably VC31 from original bangle.js 2
+ E.showMessage("This Bangle.js doesn't have a VC31B HRM sensor","Error");
+} else {
+ Bangle.setOptions({hrmGreenAdjust:false, hrmWearDetect:false, hrmPushEnv:true});
+ Bangle.hrmWr(0x10, 197&0xF8 | 4); // just SLOT2
+ Bangle.hrmWr(0x16, 0); // force env to be used as fast as possible
+
+ var samples = 0, samplesHi = 0;
+ var count = 0;
+ {
+ let last = 0;
+ Bangle.on('HRM-env',v => {
+ if (v) {
+ if (!last) hadPulse();
+ samplesHi++;
+ }
+ last = v;
+ samples++;
+ });
+ }
+
+ drawCounter();
+ setInterval(function() {
+ g.reset().clearRect(0,90,175,130);
+ g.setFontAlign(0,0).setFont("6x8:2");
+ g.drawString(samples+" sps", 88, 100);
+ if (samplesHi*5 > samples) {
+ g.setBgColor("#f00").setColor("#fff");
+ g.clearRect(0,110,175,130).drawString("TOO LIGHT",88,120);
+ }
+ samples=0;
+ samplesHi=0;
+ Bangle.setLCDPower(1); // force LCD on!
+ }, 1000);
+}
\ No newline at end of file
diff --git a/apps/flashcount/app.png b/apps/flashcount/app.png
new file mode 100644
index 000000000..379b9d381
Binary files /dev/null and b/apps/flashcount/app.png differ
diff --git a/apps/flashcount/metadata.json b/apps/flashcount/metadata.json
new file mode 100644
index 000000000..1c0f785fd
--- /dev/null
+++ b/apps/flashcount/metadata.json
@@ -0,0 +1,13 @@
+{ "id": "flashcount",
+ "name": "Flash Counter",
+ "shortName":"FlashCount",
+ "version":"0.01",
+ "description": "Count flashes/pulses of light using the heart rate monitor. Requires a VC31B HRM sensor, which should be in most watches except those produced for the original KickStarter campaign.",
+ "icon": "app.png",
+ "tags": "",
+ "supports" : ["BANGLEJS2"],
+ "storage": [
+ {"name":"flashcount.app.js","url":"app.js"},
+ {"name":"flashcount.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html
index ae955aded..fd16aa878 100644
--- a/apps/fwupdate/custom.html
+++ b/apps/fwupdate/custom.html
@@ -12,7 +12,8 @@
see the Bangle.js 1 instructions