From cdd4e6a8cc83d6a5faac4b0dd835804549669843 Mon Sep 17 00:00:00 2001 From: OmegaRogue Date: Fri, 4 Dec 2020 14:08:09 +0100 Subject: [PATCH] Added DANE Settings --- apps.json | 14 + apps/dane_cfg/ChangeLog | 1 + apps/dane_cfg/README.md | 33 +++ apps/dane_cfg/boot.js | 9 + apps/dane_cfg/settings-icon.js | 1 + apps/dane_cfg/settings.js | 515 +++++++++++++++++++++++++++++++++ apps/dane_cfg/settings.png | Bin 0 -> 1733 bytes 7 files changed, 573 insertions(+) create mode 100644 apps/dane_cfg/ChangeLog create mode 100644 apps/dane_cfg/README.md create mode 100644 apps/dane_cfg/boot.js create mode 100644 apps/dane_cfg/settings-icon.js create mode 100644 apps/dane_cfg/settings.js create mode 100644 apps/dane_cfg/settings.png diff --git a/apps.json b/apps.json index 3a48e8e91..56a1613a1 100644 --- a/apps.json +++ b/apps.json @@ -1455,6 +1455,20 @@ ], "sortorder" : -10 }, + { "id": "dane_cfg", + "name": "DANE Settings", + "icon": "settings.png", + "version":"0.01", + "description": "The default settings app in DANE Style", + "tags": "tool,system", + "readme": "README.md", + "storage": [ + {"name":"dane_cfg.app.js","url":"settings.js"}, + {"name":"dane_cfg.boot.js","url":"boot.js"}, + {"name":"dane_cfg.img","url":"settings-icon.js","evaluate":true} + ], + "sortorder" : -2 + }, { "id": "buffgym", "name": "BuffGym", diff --git a/apps/dane_cfg/ChangeLog b/apps/dane_cfg/ChangeLog new file mode 100644 index 000000000..91f9145cb --- /dev/null +++ b/apps/dane_cfg/ChangeLog @@ -0,0 +1 @@ +0.01: Forked Settings, Styled to match DANE Watchface \ No newline at end of file diff --git a/apps/dane_cfg/README.md b/apps/dane_cfg/README.md new file mode 100644 index 000000000..d7607539a --- /dev/null +++ b/apps/dane_cfg/README.md @@ -0,0 +1,33 @@ +# Settings + +This is Bangle.js's settings menu + +* **Make Connectable** regardless of the current Bluetooth settings, makes Bangle.js so you can connect to it (while the window is up) +* **App/Widget Settings** settings specific to installed applications +* **BLE** Bluetooth Settings menu - see below. +* **Debug Info** should debug info be shown on the watch's screen or not? +* **Beep** most Bangle.js do not have a speaker inside, but they can use the vibration motor to beep in different pitches. You can change the behaviour here to use a Piezo speaker if one is connected +* **Vibration** enable/disable the vibration motor +* **Locale** set time zone/whether the clock is 12/24 hour (for supported clocks) +* **Select Clock** if you have more than one clock face, select the default one +* **HID** When Bluetooth is enabled, Bangle.js can appear as a Bluetooth Keyboard/Joystick/etc to send keypresses to a connected device. + * **NOTE:** on some platforms enabling HID can cause you problems when trying to connect to Bangle.js to upload apps. +* **Set Time** Configure the current time - Note that this can be done much more easily by choosing 'Set Time' from the App Loader +* **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below. +* **Reset Settings** Reset the settings to defaults +* **Turn Off** Turn Bangle.js off + +## BLE - Bluetooth Settings + +* **BLE** is Bluetooth LE enabled and the watch connectable? +* **Programmable** if BLE is on, can the watch be connected to in order to program/upload apps? As long as your watch firmware is up to date, Gadgetbridge will work even with `Programmable` set to `Off`. +* **Passkey BETA** allows you to set a passkey that is required to connect and pair to Bangle.js. **Note:** This is Beta and you will almost certainly encounter issues connecting with Web Bluetooth using this option. +* **Whitelist** allows you to specify only specific devices that you will let connect to your Bangle.js. Simply choose the menu item, then `Add Device`, and then connect to Bangle.js with the device you want to add. If you are already connected you will have to disconnect first. Changes will take effect when you exit the `Settings` app. + * **NOTE:** iOS devices and newer Android devices often implement Address Randomisation and change their Bluetooth address every so often. If you device's address changes, you will be unable to connect until you update the whitelist again. + +## LCD + +* **LCD Brightness** set how bright the LCD is. Due to hardware limitations in the LCD backlight, you may notice flicker if the LCD is not at 100% brightness. +* **LCD Timeout** how long should the LCD stay on for if no activity is detected. 0=stay on forever +* **Wake on X** should the given activity wake up the Bangle.js LCD? +* **Twist X** these options adjust the sensitivity of `Wake on Twist` to ensure Bangle.js wakes up with just the right amount of wrist movement. diff --git a/apps/dane_cfg/boot.js b/apps/dane_cfg/boot.js new file mode 100644 index 000000000..887ba7dc1 --- /dev/null +++ b/apps/dane_cfg/boot.js @@ -0,0 +1,9 @@ +(() => { + var settings = require('Storage').readJSON('dane_cfg.json', true); + if (!settings) return; + if (settings.options) Bangle.setOptions(settings.options); + if (settings.brightness && settings.brightness!=1) Bangle.setLCDBrightness(settings.brightness); + if (settings.passkey!==undefined && settings.passkey.length==6) NRF.setSecurity({passkey:settings.passkey, mitm:1, display:1}); + if (settings.whitelist) NRF.on('connect', function(addr) { if (!settings.whitelist.includes(addr)) NRF.disconnect(); }); + delete settings; +})() diff --git a/apps/dane_cfg/settings-icon.js b/apps/dane_cfg/settings-icon.js new file mode 100644 index 000000000..7b68f80c0 --- /dev/null +++ b/apps/dane_cfg/settings-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwghC/AFEiAAgX/C/4SFkADBgQXFBIgECAAYSCkAWGBIoXGyQTHABBZLkUhiMRiQXLIQwVBAAZlIC44tCAAYxGIxIWFGA4XIFwwwHXBAWHGAwXHFxAwGPAYXTX44XDiAJBgIXGyDAHFAYKDMAq+EGAgXNCwwX/C453XU6IWHa6ZFCC6JJCC4hgEAAoOEC5AwIFwhgEBAgwIBoqmGGBIuFVAgXFGAwLFYAoLFGIYtFeA4MGABMpC4pICkBMGBIpGFC4SuIBIoWFAAxZLC/4X/AFQ")) diff --git a/apps/dane_cfg/settings.js b/apps/dane_cfg/settings.js new file mode 100644 index 000000000..6504b36e9 --- /dev/null +++ b/apps/dane_cfg/settings.js @@ -0,0 +1,515 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +const storage = require('Storage'); +let settings; + +function updateSettings() { + //storage.erase('setting.json'); // - not needed, just causes extra writes if settings were the same + storage.write('dane_cfg.json', settings); +} + +function updateOptions() { + updateSettings(); + Bangle.setOptions(settings.options) +} + +function gToInternal(g) { + // converts g to Espruino internal unit + return g * 8192; +} + +function internalToG(u) { + // converts Espruino internal unit to g + return u / 8192 +} + +function resetSettings() { + settings = { + ble: true, // Bluetooth enabled by default + blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used? + log: false, // Do log messages appear on screen? + timeout: 10, // Default LCD timeout in seconds + vibrate: true, // Vibration enabled by default. App must support + beep: "vib", // Beep enabled by default. App must support + timezone: 0, // Set the timezone for the device + HID: false, // BLE HID mode, off by default + clock: null, // a string for the default clock's name + "12hour" : false, // 12 or 24 hour clock? + brightness: 1, // LCD brightness from 0 to 1 + // welcomed : undefined/true (whether welcome app should show) + options: { + wakeOnBTN1: true, + wakeOnBTN2: true, + wakeOnBTN3: true, + wakeOnFaceUp: false, + wakeOnTouch: false, + wakeOnTwist: true, + twistThreshold: 819.2, + twistMaxY: -800, + twistTimeout: 1000 + } + }; + updateSettings(); +} + +settings = storage.readJSON('dane_cfg.json', 1); +if (!settings) resetSettings(); + +const boolFormat = v => v ? "On" : "Off"; + +function showMainMenu() { + var beepV = [false, true, "vib"]; + var beepN = ["Off", "Piezo", "Vibrate"]; + var hidV = [false, "kbmedia", "kb", "joy"]; + var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"]; + const mainmenu = { + '': { 'title': 'Settings' }, + 'Make Connectable': ()=>makeConnectable(), + 'App/Widget Settings': ()=>showAppSettingsMenu(), + 'BLE': ()=>showBLEMenu(), + 'Debug Info': { + value: settings.log, + format: v => v ? "Show" : "Hide", + onchange: () => { + settings.log = !settings.log; + updateSettings(); + } + }, + 'Beep': { + value: 0 | beepV.indexOf(settings.beep), + min: 0, max: 2, + format: v => beepN[v], + onchange: v => { + settings.beep = beepV[v]; + if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200); } // piezo + else if (v==2) { analogWrite(D13,0.1,{freq:2000});setTimeout(()=>D13.reset(),200); } // vibrate + updateSettings(); + } + }, + 'Vibration': { + value: settings.vibrate, + format: boolFormat, + onchange: () => { + settings.vibrate = !settings.vibrate; + updateSettings(); + if (settings.vibrate) { + VIBRATE.write(1); + setTimeout(() => VIBRATE.write(0), 10); + } + } + }, + 'Locale': ()=>showLocaleMenu(), + 'Select Clock': ()=>showClockMenu(), + 'HID': { + value: 0 | hidV.indexOf(settings.HID), + min: 0, max: 3, + format: v => hidN[v], + onchange: v => { + settings.HID = hidV[v]; + updateSettings(); + } + }, + 'Set Time': ()=>showSetTimeMenu(), + 'LCD': ()=>showLCDMenu(), + 'Reset Settings': ()=>showResetMenu(), + 'Turn Off': ()=>Bangle.off(), + '< Back': ()=>load() + }; + return E.showMenu(mainmenu); +} + + +function showBLEMenu() { + E.showMenu({ + 'BLE': { + value: settings.ble, + format: boolFormat, + onchange: () => { + settings.ble = !settings.ble; + updateSettings(); + } + }, + 'Programmable': { + value: settings.blerepl, + format: boolFormat, + onchange: () => { + settings.blerepl = !settings.blerepl; + updateSettings(); + } + }, + 'Passkey BETA': { + value: settings.passkey?settings.passkey:"none", + onchange: () => setTimeout(showPasskeyMenu) // graphical_menu redraws after the call + }, + 'Whitelist': { + value: settings.whitelist?(settings.whitelist.length+" devs"):"off", + onchange: () => setTimeout(showWhitelistMenu) // graphical_menu redraws after the call + }, + '< Back': ()=>showMainMenu() + }); +} + +function showPasskeyMenu() { + var menu = { + "Disable" : () => { + settings.passkey = undefined; + updateSettings(); + showBLEMenu(); + } + }; + if (!settings.passkey || settings.passkey.length!=6) + settings.passkey = "123456"; + for (var i=0;i<6;i++) (function(i){ + menu[`Digit ${i+1}`] = { + value : 0|settings.passkey[i], + min: 0, max: 9, + onchange: v => { + var p = settings.passkey.split(""); + p[i] = v; + settings.passkey = p.join(""); + updateSettings(); + } + }; + })(i); + menu['< Back']=()=>showBLEMenu(); + E.showMenu(menu); +} + +function showWhitelistMenu() { + var menu = { + "Disable" : () => { + settings.whitelist = undefined; + updateSettings(); + showBLEMenu(); + } + }; + if (settings.whitelist) settings.whitelist.forEach(function(d){ + menu[d.substr(0,17)] = function() { + E.showPrompt('Remove\n'+d).then((v) => { + if (v) { + settings.whitelist.splice(settings.whitelist.indexOf(d),1); + updateSettings(); + } + setTimeout(showWhitelistMenu, 50); + }); + } + }); + menu['Add Device']=function() { + E.showAlert("Connect device\nto add to\nwhitelist","Whitelist").then(function() { + NRF.removeAllListeners('connect'); + showWhitelistMenu(); + }); + NRF.removeAllListeners('connect'); + NRF.on('connect', function(addr) { + if (!settings.whitelist) settings.whitelist=[]; + settings.whitelist.push(addr); + updateSettings(); + NRF.removeAllListeners('connect'); + showWhitelistMenu(); + }); + }; + menu['< Back']=()=>showBLEMenu(); + E.showMenu(menu); +} + +function showLCDMenu() { + const lcdMenu = { + '': { 'title': 'LCD' }, + '< Back': ()=>showMainMenu(), + 'LCD Brightness': { + value: settings.brightness, + min: 0.1, + max: 1, + step: 0.1, + onchange: v => { + settings.brightness = v || 1; + updateSettings(); + Bangle.setLCDBrightness(settings.brightness); + } + }, + 'LCD Timeout': { + value: settings.timeout, + min: 0, + max: 60, + step: 5, + onchange: v => { + settings.timeout = 0 | v; + updateSettings(); + Bangle.setLCDTimeout(settings.timeout); + } + }, + 'Wake on BTN1': { + value: settings.options.wakeOnBTN1, + format: boolFormat, + onchange: () => { + settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1; + updateOptions(); + } + }, + 'Wake on BTN2': { + value: settings.options.wakeOnBTN2, + format: boolFormat, + onchange: () => { + settings.options.wakeOnBTN2 = !settings.options.wakeOnBTN2; + updateOptions(); + } + }, + 'Wake on BTN3': { + value: settings.options.wakeOnBTN3, + format: boolFormat, + onchange: () => { + settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3; + updateOptions(); + } + }, + 'Wake on FaceUp': { + value: settings.options.wakeOnFaceUp, + format: boolFormat, + onchange: () => { + settings.options.wakeOnFaceUp = !settings.options.wakeOnFaceUp; + updateOptions(); + } + }, + 'Wake on Touch': { + value: settings.options.wakeOnTouch, + format: boolFormat, + onchange: () => { + settings.options.wakeOnTouch = !settings.options.wakeOnTouch; + updateOptions(); + } + }, + 'Wake on Twist': { + value: settings.options.wakeOnTwist, + format: boolFormat, + onchange: () => { + settings.options.wakeOnTwist = !settings.options.wakeOnTwist; + updateOptions(); + } + }, + 'Twist Threshold': { + value: internalToG(settings.options.twistThreshold), + min: -0.5, + max: 0.5, + step: 0.01, + onchange: v => { + settings.options.twistThreshold = gToInternal(v || 0.1); + updateOptions(); + } + }, + 'Twist Max Y': { + value: settings.options.twistMaxY, + min: -1500, + max: 1500, + step: 100, + onchange: v => { + settings.options.twistMaxY = v || -800; + updateOptions(); + } + }, + 'Twist Timeout': { + value: settings.options.twistTimeout, + min: 0, + max: 2000, + step: 100, + onchange: v => { + settings.options.twistTimeout = v || 1000; + updateOptions(); + } + } + } + return E.showMenu(lcdMenu) +} + +function showLocaleMenu() { + const localemenu = { + '': { 'title': 'Locale' }, + '< Back': ()=>showMainMenu(), + 'Time Zone': { + value: settings.timezone, + min: -11, + max: 12, + step: 0.5, + onchange: v => { + settings.timezone = v || 0; + updateSettings(); + } + }, + 'Clock Style': { + value: !!settings["12hour"], + format: v => v ? "12hr" : "24hr", + onchange: v => { + settings["12hour"] = v; + updateSettings(); + } + } + }; + return E.showMenu(localemenu); +} + +function showResetMenu() { + const resetmenu = { + '': { 'title': 'Reset' }, + '< Back': ()=>showMainMenu(), + 'Reset Settings': () => { + E.showPrompt('Reset Settings?').then((v) => { + if (v) { + E.showMessage('Resetting'); + resetSettings(); + } + setTimeout(showMainMenu, 50); + }); + } + }; + return E.showMenu(resetmenu); +} + +function makeConnectable() { + try { NRF.wake(); } catch (e) { } + Bluetooth.setConsole(1); + var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", ""); + E.showPrompt(name + "\nStay Connectable?", { title: "Connectable" }).then(r => { + if (settings.ble != r) { + settings.ble = r; + updateSettings(); + } + if (!r) try { NRF.sleep(); } catch (e) { } + showMainMenu(); + }); +} +function showClockMenu() { + var clockApps = require("Storage").list(/\.info$/) + .map(app => {var a=storage.readJSON(app, 1);return (a&&a.type == "clock")?a:undefined}) + .filter(app => app) // filter out any undefined apps + .sort((a, b) => a.sortorder - b.sortorder); + const clockMenu = { + '': { + 'title': 'Select Clock', + }, + '< Back': ()=>showMainMenu(), + }; + clockApps.forEach((app, index) => { + var label = app.name; + if ((!settings.clock && index === 0) || (settings.clock === app.src)) { + label = "* " + label; + } + clockMenu[label] = () => { + if (settings.clock !== app.src) { + settings.clock = app.src; + updateSettings(); + showMainMenu(); + } + }; + }); + if (clockApps.length === 0) { + clockMenu["No Clocks Found"] = () => { }; + } + return E.showMenu(clockMenu); +} + +function showSetTimeMenu() { + d = new Date(); + const timemenu = { + '': { 'title': 'Set Time' }, + '< Back': function () { + setTime(d.getTime() / 1000); + showMainMenu(); + }, + 'Hour': { + value: d.getHours(), + onchange: function (v) { + this.value = (v+24)%24; + d.setHours(this.value); + } + }, + 'Minute': { + value: d.getMinutes(), + onchange: function (v) { + this.value = (v+60)%60; + d.setMinutes(this.value); + } + }, + 'Second': { + value: d.getSeconds(), + onchange: function (v) { + this.value = (v+60)%60; + d.setSeconds(this.value); + } + }, + 'Date': { + value: d.getDate(), + onchange: function (v) { + this.value = ((v+30)%31)+1; + d.setDate(this.value); + } + }, + 'Month': { + value: d.getMonth() + 1, + onchange: function (v) { + this.value = ((v+11)%12)+1; + d.setMonth(this.value - 1); + } + }, + 'Year': { + value: d.getFullYear(), + min: 2019, + max: 2100, + onchange: function (v) { + d.setFullYear(v); + } + } + }; + return E.showMenu(timemenu); +} + +function showAppSettingsMenu() { + let appmenu = { + '': { 'title': 'App Settings' }, + '< Back': ()=>showMainMenu(), + } + const apps = storage.list(/\.settings\.js$/) + .map(s => s.substr(0, s.length-12)) + .map(id => { + const a=storage.readJSON(id+'.info',1) || {name: id}; + return {id:id,name:a.name,sortorder:a.sortorder}; + }) + .sort((a, b) => { + const n = (0|a.sortorder)-(0|b.sortorder); + if (n) return n; // do sortorder first + if (a.nameb.name) return 1; + return 0; + }) + if (apps.length === 0) { + appmenu['No app has settings'] = () => { }; + } + apps.forEach(function (app) { + appmenu[app.name] = () => { showAppSettings(app) }; + }) + E.showMenu(appmenu) +} +function showAppSettings(app) { + const showError = msg => { + E.showMessage(`${app.name}:\n${msg}!\n\nBTN1 to go back`); + setWatch(showAppSettingsMenu, BTN1, { repeat: false }); + } + let appSettings = storage.read(app.id+'.settings.js'); + try { + appSettings = eval(appSettings); + } catch (e) { + console.log(`${app.name} settings error:`, e) + return showError('Error in settings'); + } + if (typeof appSettings !== "function") { + return showError('Invalid settings'); + } + try { + // pass showAppSettingsMenu as "back" argument + appSettings(()=>showAppSettingsMenu()); + } catch (e) { + console.log(`${app.name} settings error:`, e) + return showError('Error in settings'); + } +} + +showMainMenu(); diff --git a/apps/dane_cfg/settings.png b/apps/dane_cfg/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..71e7535238fc79aafe33e060f25ed6ab9b729379 GIT binary patch literal 1733 zcmV;$20HnPP)5?UwC3AlaHEvFmg*cgngh7Sc z*L!Ju&pFTj0Ox%<=bqDYlaY+?f9HAmKF{-eea<;>12^#h4XG4ShkYQWp7Xk>-dxsD zWg7Is^g!=FD-kzV2@(Ktb8R5s4IL8!QS&|EKqcauN{|2`+!WPRjkZ=QY{d%jRH7&4 zMK3lC6)Qkk#Hb3_-wd#b84xO0KtfzGFg+f{v&2Uw?~>xzZ0 z<1m+h82rbo2a8ES%(!X2%Au}zsyK3%emej;4wd?uiuY*_tksV z<`hu=R=o<(b(x+RCp$fXYPfx(zLCb}7K|179`yvibELoL?P7)RDdg3d-9$`&E}P-P z*-yw!MN1waJ2S^#?&{1=!Q`!}xRL@`|+k6YXF1qB5(OeQ`?^kf*z%*6PT zmQ4^m&tqm{Jc{YKTJnc{K|ukB4sD;m&1BahJOlV<=sFJBnUHm>ykZRN#hR}ZYram< z4Zy4AT(UEhxNg~5_@n1pZAa~%5Aub9$8Trf^BWX73arYPXQn5ayf{`Sj_f$`(&1nF zX*O=&)`jSBkZ0raR_Yp3#o~EY**!9_b3aJQnhk8`j`a8b2`knHyp}J&oS6;IU3n!a zd4LnYIlLcbG)O;RvYGW?So-aef$m|yo9V5H|DZF3H-L?yJ^`Y%~ zZPip@OfBA$8h%cP7NBAZc5G22R)DJb{5Qh*a#9;;W5uk3XMu931rSoeqxMh>s+;m< zv3PLXugFhB1A4)2N-S2$t#J24Pwl(2EMJhKTep49p#c2f!0;4gO{jsER(SwJAR<+a$o^lcMsj_8hwkR%c~fZ7stp%?II&0 z*_a~L5_0H>yi-3>wf@kd?em|D3YL&#ybXjT574lz8DlEwYF&vH;q6S&-yN~L|51=j{zKZf=TPa660|0fq;dHQ zuB`|mwY-&@L^6u;ys=_Ci-jfYAU)9iTZK2HFxEAsXkK~Cl9rP(7R@VfAz9kI`9t`{ zi+0z?B|Lt%o_gDXTom4#M3NP&Zl^xA?8;FRmWM#1A;pR>-4=D^0UgVk>Sz5lmY?v0 zx8A&T*M@sr@c$-M0%Hu-i6qH}Myz<%l|?0T<aok*IH2+^mWD z$+2gic-L1I?47eB9*7`D$4sJ{0vA$&%_t!MP*P$?*~f0H34s@`@8R1 zbUXdhbGs%4>B|#cR^eBcWvvPuXq{e??qx8^WoPpZC02j;Lb)$U6H5|Eh3TBxB`pdZ5R*Raq=5cAaX3*7v`* zqLFRGP{AJ@(F-uhby$GmLR8c1at6d(#HecYV#83e|HO*-1}g1{Hk7xNb6D{YD;4(! b{@3^qyIVZM$=d%600000NkvXXu0mjfeqCj+ literal 0 HcmV?d00001