diff --git a/apps.json b/apps.json index f167acf5d..7c4eed0ed 100644 --- a/apps.json +++ b/apps.json @@ -2,7 +2,7 @@ { "id": "boot", "name": "Bootloader", "icon": "bootloader.png", - "version":"0.14", + "version":"0.15", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "tags": "tool,system", "type":"bootloader", @@ -122,9 +122,10 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.18", + "version":"0.19", "description": "A menu for setting up Bangle.js", "tags": "tool,system", + "readme": "README.md", "storage": [ {"name":"setting.app.js","url":"settings.js"}, {"name":"setting.boot.js","url":"boot.js"}, @@ -480,7 +481,7 @@ "name": "Bluetooth Music Controls", "shortName": "Music Control", "icon": "hid-music.png", - "version":"0.01", + "version":"0.02", "description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!", "tags": "bluetooth", "storage": [ @@ -492,7 +493,7 @@ "name": "Bluetooth Keyboard", "shortName": "Bluetooth Kbd", "icon": "hid-keyboard.png", - "version":"0.01", + "version":"0.02", "description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps", "tags": "bluetooth", "storage": [ @@ -504,7 +505,7 @@ "name": "Binary Bluetooth Keyboard", "shortName": "Binary BT Kbd", "icon": "hid-binary-keyboard.png", - "version":"0.01", + "version":"0.02", "description": "Enable HID in settings, pair with your phone/PC, then type messages using the onscreen keyboard by tapping repeatedly on the key you want", "tags": "bluetooth", "storage": [ @@ -1188,7 +1189,7 @@ "name": "Active Pedometer", "shortName":"Active Pedometer", "icon": "app.png", - "version":"0.03", + "version":"0.04", "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", "tags": "outdoors,widget", "readme": "README.md", @@ -1254,7 +1255,7 @@ "name": "Battery Chart", "shortName":"Battery Chart", "icon": "app.png", - "version":"0.08", + "version":"0.09", "readme": "README.md", "description": "A widget and an app for recording and visualizing battery percentage over time.", "tags": "app,widget,battery,time,record,chart,tool", @@ -1404,6 +1405,7 @@ "name": "Metronome", "icon": "metronome_icon.png", "version": "0.03", + "readme": "README.md", "description": "Makes the watch blinking and vibrating with a given rate", "tags": "tool", "allow_emulator": true, @@ -1436,7 +1438,7 @@ "name": "Camera shutter", "shortName":"Cam shutter", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle", "tags": "tools", "storage": [ @@ -1486,11 +1488,12 @@ "name": "Pong", "shortName": "Pong", "icon": "pong.png", - "version": "0.01", + "version": "0.02", "description": "A clone of the Atari game Pong", "tags": "game", "type": "app", "allow_emulator": true, + "readme": "README.md", "storage": [ {"name":"pong.app.js","url":"app.js"}, {"name":"pong.img","url":"app-icon.js","evaluate":true} diff --git a/apps/activepedom/ChangeLog b/apps/activepedom/ChangeLog index c1b9ec011..ca26a648a 100644 --- a/apps/activepedom/ChangeLog +++ b/apps/activepedom/ChangeLog @@ -1,3 +1,4 @@ 0.01: New Widget! 0.02: Distance calculation and display -0.03: Data logging and display \ No newline at end of file +0.03: Data logging and display +0.04: Steps are set to 0 in log on new day \ No newline at end of file diff --git a/apps/activepedom/README.md b/apps/activepedom/README.md index f45297e57..a2a351a12 100644 --- a/apps/activepedom/README.md +++ b/apps/activepedom/README.md @@ -18,7 +18,7 @@ Steps are saved to a datafile every 5 minutes. You can watch a graph using the a * 10600 steps ![](10600.png) -## Features +## Features Widget * Two line display * Can display distance (in km) or steps in each line @@ -32,22 +32,23 @@ Steps are saved to a datafile every 5 minutes. You can watch a graph using the a * Steps are saved to a file and read-in at start (to not lose step progress) * Settings can be changed in Settings - App/widget settings - Active Pedometer +## Features App + +* The app accesses the data stored for the current day +* Timespan is choseable (1h, 4h, 8h, 12h, 16h, 20, 24h), standard is 24h, the whole current day + ## Data storage -* Data is stored to a file +* Data is stored to a file named activepedomYYYYMMDD.data (activepedom20200427.data) +* One file is created for each day * Format: now,stepsCounted,active,stepsTooShort,stepsTooLong,stepsOutsideTime -* now is UNIX timestamp in ms -* You can chose the app to watch a steps graph +* 'now' is UNIX timestamp in ms +* You can use the app to watch a steps graph * You can import the file into Excel * The file does not include a header * You can convert UNIX timestamp to a date in Excel using this formula: =DATUM(1970;1;1)+(LINKS(A2;10)/86400) * You have to format the cell with the formula to a date cell. Example: JJJJ-MM-TT-hh-mm-ss -## App - -* The app accesses the data stored for the current day -* Timespan is choseable (1h, 4h, 8h, 12h, 16h, 20, 24h), standard is 24h, the whole current day - ## Settings * Max time (ms): Maximum time between two steps in milliseconds, steps will not be counted if exceeded. Standard: 1100 diff --git a/apps/activepedom/app.js b/apps/activepedom/app.js index 0a9b3b93f..cc875f371 100644 --- a/apps/activepedom/app.js +++ b/apps/activepedom/app.js @@ -162,4 +162,4 @@ settings = storage.readJSON(SETTINGS_FILE, 1) || {}; drawMenu(); -})(); +})(); \ No newline at end of file diff --git a/apps/activepedom/widget.js b/apps/activepedom/widget.js index c6bd410ce..2ae1b9b62 100644 --- a/apps/activepedom/widget.js +++ b/apps/activepedom/widget.js @@ -33,27 +33,28 @@ function storeData() { now = new Date(); - month = now.getMonth() + 1; - if (month < 10) month = "0" + month; - filename = filename = "activepedom" + now.getFullYear() + month + now.getDate() + ".data"; + month = now.getMonth() + 1; //month is 0-based + if (month < 10) month = "0" + month; //leading 0 + filename = filename = "activepedom" + now.getFullYear() + month + now.getDate() + ".data"; //new file for each day dataFile = s.open(filename,"a"); - if (dataFile) { + if (dataFile) { //check if filen already exists if (dataFile.getLength() == 0) { - stepsToWrite = 0; - } - else { - stepsToWrite = stepsCounted; + //new day, set steps to 0 + stepsCounted = 0; + stepsTooShort = 0; + stepsTooLong = 0; + stepsOutsideTime = 0; } dataFile.write([ now.getTime(), - stepsToWrite, + stepsCounted, active, stepsTooShort, stepsTooLong, stepsOutsideTime, ].join(",")+"\n"); } - dataFile = undefined; + dataFile = undefined; //save memory } //return setting diff --git a/apps/batchart/ChangeLog b/apps/batchart/ChangeLog index 439d877be..66b40fbbf 100644 --- a/apps/batchart/ChangeLog +++ b/apps/batchart/ChangeLog @@ -5,4 +5,5 @@ 0.05: Display temperature and LCD state in chart 0.06: Fixes widget events and charting of component states 0.07: Improve logging and charting of component states and add widget icon -0.08: Fix for Home button in the app and README added. \ No newline at end of file +0.08: Fix for Home button in the app and README added. +0.09: Fix failing dismissal of Gadgetbridge notifications, record (coarse) bluetooth state \ No newline at end of file diff --git a/apps/batchart/app.js b/apps/batchart/app.js index 2d0d8e585..472fb3a8a 100644 --- a/apps/batchart/app.js +++ b/apps/batchart/app.js @@ -8,7 +8,7 @@ const GraphXMax = GraphXZero + MaxValueCount; const GraphLcdY = GraphYZero + 10; const GraphCompassY = GraphYZero + 16; -// const GraphBluetoothY = GraphYZero + 22; +const GraphBluetoothY = GraphYZero + 22; const GraphGpsY = GraphYZero + 28; const GraphHrmY = GraphYZero + 34; @@ -175,13 +175,13 @@ function renderData(dataArray) { g.drawLine(GraphXZero + i, GraphCompassY, GraphXZero + i, GraphCompassY + 1); } - // // Bluetooth state - // if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) { - // g.setColor(0, 0, 1); - // g.setFontAlign(1, -1, 0); - // g.drawString("BLE", GraphXZero - GraphMarkerOffset, GraphBluetoothY - 2, true); - // g.drawLine(GraphXZero + i, GraphBluetoothY, GraphXZero + i, GraphBluetoothY + 1); - // } + // Bluetooth state + if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.bluetooth) { + g.setColor(0, 0, 1); + g.setFontAlign(1, -1, 0); + g.drawString("BLE", GraphXZero - GraphMarkerOffset, GraphBluetoothY - 2, true); + g.drawLine(GraphXZero + i, GraphBluetoothY, GraphXZero + i, GraphBluetoothY + 1); + } // Gps state if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.gps) { diff --git a/apps/batchart/widget.js b/apps/batchart/widget.js index 1b8ce79ba..96f8b4b25 100644 --- a/apps/batchart/widget.js +++ b/apps/batchart/widget.js @@ -71,8 +71,10 @@ enabledConsumers = enabledConsumers | switchableConsumers.gps; if (hrmEventReceived) enabledConsumers = enabledConsumers | switchableConsumers.hrm; - //if (Bangle.isBluetoothOn()) - // enabledConsumers = enabledConsumers | switchableConsumers.bluetooth; + + // Very coarse first approach to check if the BLE device is on. + if (NRF.getSecurityStatus().connected) + enabledConsumers = enabledConsumers | switchableConsumers.bluetooth; // Reset the event registration vars compassEventReceived = false; @@ -110,19 +112,14 @@ } function reload() { - WIDGETS.batchart.width = 24; + WIDGETS["batchart"].width = 24; recordingInterval = setInterval(logBatteryData, recordingInterval10Min); - - logBatteryData(); } // add the widget - WIDGETS.batchart = { - area: "tl", width: 24, draw: draw, reload: function () { - reload(); - Bangle.drawWidgets(); - } + WIDGETS["batchart"] = { + area: "tl", width: 24, draw: draw, reload: reload }; reload(); diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 7ab79a5a5..cf5c243f8 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -13,3 +13,4 @@ 0.13: Now automatically load *.boot.js at startup Move alarm code into alarm.boot.js 0.14: Move welcome loaders to *.boot.js +0.15: Added BLE HID option for Joystick and bare Keyboard diff --git a/apps/boot/boot0.js b/apps/boot/boot0.js index dd3b3a9ba..332e33177 100644 --- a/apps/boot/boot0.js +++ b/apps/boot/boot0.js @@ -4,7 +4,9 @@ E.setFlags({pretokenise:1}); var s = require('Storage').readJSON('setting.json',1)||{}; if (s.ble!==false) { if (s.HID) { // Human interface device - Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA==")); + if (s.HID=="joy") Bangle.HID = E.toUint8Array(atob("BQEJBKEBhQMJAaEABQkZASkFFQAlAZUFdQGBApUDdQGBAwUBCTAJMRWBJX91CJUCgQLAwA==")); + else if (s.HID=="kb") Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA==")); + else /*kbmedia*/Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA==")); NRF.setServices({}, {uart:true, hid:Bangle.HID}); } } diff --git a/apps/boot/hid_info.txt b/apps/boot/hid_info.txt new file mode 100644 index 000000000..873b50f63 --- /dev/null +++ b/apps/boot/hid_info.txt @@ -0,0 +1,88 @@ + +## Joystick: + +https://github.com/espruino/BangleApps/issues/349#issuecomment-620231524 + +``` +0x05, 0x01, // Usage Page (Generic Desktop) +0x09, 0x04, // Usage (Joystick) +0xA1, 0x01, // Collection (Application) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Physical) + // Buttons + 0x05, 0x09, // Usage Page (Buttons) + 0x19, 0x01, // Usage Minimum (1) + 0x29, 0x05, // Usage Maximum (5) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x81, 0x02, // Input (Data, Variable, Absolute) + + // padding bits + 0x95, 0x03, // Report Count (3) + 0x75, 0x01, // Report Size (1) + 0x81, 0x03, // Input (Constant) + + // Stick + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x81, // Logical Minimum (-127) + 0x25, 0x7f, // Logical Maximum (127) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data, Variable, Absolute) + 0xC0, // End Collection (Physical) +0xC0 // End Collection (Application) +``` + +## Keyboard + +http://www.espruino.com/BLE+Keyboard + +``` +0x05, 0x01, // Usage Page (Generic Desktop) +0x09, 0x06, // Usage (Keyboard) +0xA1, 0x01, // Collection (Application) +0x05, 0x07, // Usage Page (Key Codes) +0x19, 0xe0, // Usage Minimum (224) +0x29, 0xe7, // Usage Maximum (231) +0x15, 0x00, // Logical Minimum (0) +0x25, 0x01, // Logical Maximum (1) +0x75, 0x01, // Report Size (1) +0x95, 0x08, // Report Count (8) +0x81, 0x02, // Input (Data, Variable, Absolute) + +0x95, 0x01, // Report Count (1) +0x75, 0x08, // Report Size (8) +0x81, 0x01, // Input (Constant) reserved byte(1) + +0x95, 0x05, // Report Count (5) +0x75, 0x01, // Report Size (1) +0x05, 0x08, // Usage Page (Page# for LEDs) +0x19, 0x01, // Usage Minimum (1) +0x29, 0x05, // Usage Maximum (5) +0x91, 0x02, // Output (Data, Variable, Absolute), Led report +0x95, 0x01, // Report Count (1) +0x75, 0x03, // Report Size (3) +0x91, 0x01, // Output (Data, Variable, Absolute), Led report padding + +0x95, 0x06, // Report Count (6) +0x75, 0x08, // Report Size (8) +0x15, 0x00, // Logical Minimum (0) +0x25, 0x73, // Logical Maximum (115 - include F13, etc) +0x05, 0x07, // Usage Page (Key codes) +0x19, 0x00, // Usage Minimum (0) +0x29, 0x73, // Usage Maximum (115 - include F13, etc) +0x81, 0x00, // Input (Data, Array) Key array(6 bytes) + +0x09, 0x05, // Usage (Vendor Defined) +0x15, 0x00, // Logical Minimum (0) +0x26, 0xFF, 0x00, // Logical Maximum (255) +0x75, 0x08, // Report Count (2) +0x95, 0x02, // Report Size (8 bit) +0xB1, 0x02, // Feature (Data, Variable, Absolute) + +0xC0 // End Collection (Application) +``` diff --git a/apps/hidbkbd/ChangeLog b/apps/hidbkbd/ChangeLog new file mode 100644 index 000000000..459bf40b9 --- /dev/null +++ b/apps/hidbkbd/ChangeLog @@ -0,0 +1,2 @@ +0.01: Core functionnality +0.02: Offer to enable HID if disabled. Handle with/without media keys diff --git a/apps/hidbkbd/hid-binary-keyboard.js b/apps/hidbkbd/hid-binary-keyboard.js index fa1017714..81838b42d 100644 --- a/apps/hidbkbd/hid-binary-keyboard.js +++ b/apps/hidbkbd/hid-binary-keyboard.js @@ -45,13 +45,7 @@ const KEY = { 0 : 39 }; -function sendHID(code) { - return new Promise(resolve=>{ - NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => { - NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], resolve); - }); - }); -}; +var sendHID; function showChars(x,chars) { var lines = Math.round(Math.sqrt(chars.length)*2); @@ -103,10 +97,24 @@ function startKeyboardHID() { }).then(startKeyboardHID); }; -if (!settings.HID) { - E.showMessage('HID disabled'); - setTimeout(load, 1000); -} else { +if (settings.HID=="kb" || settings.HID=="kbmedia") { + if (settings.HID=="kbmedia") { + sendHID = function(code) { + return new Promise(resolve=>{ + NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => { + NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], resolve); + }); + }); + }; + } else { + sendHID = function(code) { + return new Promise(resolve=>{ + NRF.sendHIDReport([0,0,code,0,0,0,0,0], () => { + NRF.sendHIDReport([0,0,0,0,0,0,0,0], resolve); + }); + }); + }; + } startKeyboardHID(); setWatch(() => { sendHID(44); // space @@ -114,4 +122,12 @@ if (!settings.HID) { setWatch(() => { sendHID(40); // enter }, BTN3, {repeat:true}); +} else { + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "kb"; + require("Storage").write('setting.json', settings); + setTimeout(load, 1000, "hidbkbd.app.js"); + } else setTimeout(load, 1000); + }); } diff --git a/apps/hidcam/ChangeLog b/apps/hidcam/ChangeLog index 73b3268b7..2823e1f1d 100644 --- a/apps/hidcam/ChangeLog +++ b/apps/hidcam/ChangeLog @@ -1 +1,2 @@ 0.01: Core functionnality +0.02: Offer to enable HID if disabled diff --git a/apps/hidcam/app.js b/apps/hidcam/app.js index 89b8ac4a1..adb1a4b29 100644 --- a/apps/hidcam/app.js +++ b/apps/hidcam/app.js @@ -4,7 +4,7 @@ const settings = storage.readJSON('setting.json',1) || { HID: false }; var sendHid, camShot, profile; -if (settings.HID) { +if (settings.HID=="kbmedia") { profile = 'camShutter'; sendHid = function (code, cb) { try { @@ -19,8 +19,13 @@ if (settings.HID) { }; camShot = function (cb) { sendHid(0x80, cb); }; } else { - E.showMessage('HID disabled'); - setTimeout(load, 1000); + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "kbmedia"; + require("Storage").write('setting.json', settings); + setTimeout(load, 1000, "hidcam.app.js"); + } else setTimeout(load, 1000); + }); } function drawApp() { g.clear(); diff --git a/apps/hidkbd/ChangeLog b/apps/hidkbd/ChangeLog new file mode 100644 index 000000000..459bf40b9 --- /dev/null +++ b/apps/hidkbd/ChangeLog @@ -0,0 +1,2 @@ +0.01: Core functionnality +0.02: Offer to enable HID if disabled. Handle with/without media keys diff --git a/apps/hidkbd/hid-keyboard.js b/apps/hidkbd/hid-keyboard.js index ed406e093..0d489bc0d 100644 --- a/apps/hidkbd/hid-keyboard.js +++ b/apps/hidkbd/hid-keyboard.js @@ -4,27 +4,46 @@ const settings = storage.readJSON('setting.json',1) || { HID: false }; var sendHid, next, prev, toggle, up, down, profile; -if (settings.HID) { +if (settings.HID=="kb" || settings.HID=="kbmedia") { profile = 'Keyboard'; - sendHid = function (code, cb) { - try { - NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => { - NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], () => { - if (cb) cb(); + if (settings.HID=="kbmedia") { + sendHid = function (code, cb) { + try { + NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => { + NRF.sendHIDReport([2,0,0,0,0,0,0,0,0], () => { + if (cb) cb(); + }); }); - }); - } catch(e) { - print(e); - } - }; + } catch(e) { + print(e); + } + }; + } else { + sendHid = function (code, cb) { + try { + NRF.sendHIDReport([0,0,code,0,0,0,0,0], () => { + NRF.sendHIDReport([0,0,0,0,0,0,0,0], () => { + if (cb) cb(); + }); + }); + } catch(e) { + print(e); + } + }; + } next = function (cb) { sendHid(0x4f, cb); }; prev = function (cb) { sendHid(0x50, cb); }; toggle = function (cb) { sendHid(0x2c, cb); }; up = function (cb) {sendHid(0x52, cb); }; down = function (cb) { sendHid(0x51, cb); }; } else { - E.showMessage('HID disabled'); - setTimeout(load, 1000); + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "kb"; + require("Storage").write('setting.json', settings); + setTimeout(load, 1000, "hidkbd.app.js"); + } else setTimeout(load, 1000); + }); } function drawApp() { diff --git a/apps/hidmsic/ChangeLog b/apps/hidmsic/ChangeLog new file mode 100644 index 000000000..73b3268b7 --- /dev/null +++ b/apps/hidmsic/ChangeLog @@ -0,0 +1 @@ +0.01: Core functionnality diff --git a/apps/hidmsic/hid-music.js b/apps/hidmsic/hid-music.js index 034bbd231..db81744f3 100644 --- a/apps/hidmsic/hid-music.js +++ b/apps/hidmsic/hid-music.js @@ -4,7 +4,7 @@ const settings = storage.readJSON('setting.json',1) || { HID: false }; var sendHid, next, prev, toggle, up, down, profile; -if (settings.HID) { +if (settings.HID=="kbmedia") { profile = 'Music'; sendHid = function (code, cb) { try { @@ -23,8 +23,13 @@ if (settings.HID) { up = function (cb) {sendHid(0x40, cb); }; down = function (cb) { sendHid(0x80, cb); }; } else { - E.showMessage('HID disabled'); - setTimeout(load, 1000); + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { + settings.HID = "kbmedia"; + require("Storage").write('setting.json', settings); + setTimeout(load, 1000, "hidmsc.app.js"); + } else setTimeout(load, 1000); + }); } function drawApp() { diff --git a/apps/mclock/clock-morphing-faster.js b/apps/mclock/clock-morphing-faster.js new file mode 100644 index 000000000..69cd72707 --- /dev/null +++ b/apps/mclock/clock-morphing-faster.js @@ -0,0 +1,206 @@ +var locale = require("locale"); +var CHARW = 34; +var CHARP = 2; +var Y = 50; +// Offscreen buffer +var buf = Graphics.createArrayBuffer(CHARW+CHARP*2,CHARW*2 + CHARP*2,1,{msb:true}); +var bufimg = {width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer}; +// The last time that we displayed +var lastTime = " "; +// If animating, this is the interval's id +var animInterval; +var timeInterval; + +/* Get array of lines from digit d to d+1. + n is the amount (0..1) + maxFive is true is this digit only counts 0..5 */ +const DIGITS = { +" ":(g,s,p,n)=>{}, +"0":(g,s,p,n)=>{ +g.fillRect(1+s*n,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+s*n,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1+s*n,1+s-p, 1+s*n,1+2*s+p); +g.fillRect(1+s*n-p,1, 1+s*n+p,1+s)}, +"1":(g,s,p,n)=>{ +g.fillRect(1+(1-n)*s,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1+(1-n)*s,1+s-p, 1+s,1+s+p); +g.fillRect(1-p+(1-n)*s,1+s, 1+p+(1-n)*s,1+2*s); +g.fillRect(1+(1-n)*s,1-p+2*s, 1+s,1+p+2*s)}, +"2":(g,s,p,n)=>{ +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1-p,1+(1+n)*s, 1+p,1+2*s); +g.fillRect(1+s-p,1+(2-n)*s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s-p, 1+s,1+2*s+p)}, +"3":(g,s,p,n)=>{ +g.fillRect(1,1-p, 1+(1-n)*s,1+p); +g.fillRect(1-p,1, 1+p,n); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+s*n,1+2*s-p, 1+s,1+2*s+p)}, +"4":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1+s,1-p, 1+(1-n)*s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+(1-n)*s); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+(1-n)*s,1+2*s-p, 1+s,1+2*s+p)}, +"5to0": (g,s,p,n)=>{ // 5 -> 0 +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s*n,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s*p, 1+s,1+2*s+p); +g.fillRect(1,1+2*s-p, 1,1+2*s+p); +g.fillRect(1+s-p,1+(1-n)*s, 1+s+p,1+s); +g.fillRect(1-p,1+s, 1+p,1+(1+n)*s)}, +"5to6": (g,s,p,n)=>{ // 5 -> 6 +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1-p,2-n, 1+p,1+2*s)}, +"6":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,1+(1-n)*s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s*n,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+(1-n)*s, 1+s+p,1+s); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+s*n,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1-p,1+(1-n)*s, 1+p,1+s*(2-2*n))}, +"7":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,n); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1+(1-n)*s,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1+(1-n)*s,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1+(1-n)*s-p,1+s, 1+(1-n)*s+p,1+2*s)}, +"8":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1,1+s-p, 1+s,1+s+p); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s-p, 1+s,1+2*s+p); +g.fillRect(1-p,1+s, 1+p,1+s*(2-n))}, +"9":(g,s,p,n)=>{ +g.fillRect(1-p,1, 1+p,1+s); +g.fillRect(1,1-p, 1+s,1+p); +g.fillRect(1+s-p,1, 1+s+p,1+s); +g.fillRect(1,1+s-p, 1+(1-n)*s,1+s+p); +g.fillRect(1-p,1+s, 1+p,1+(1+n)*s); +g.fillRect(1+s-p,1+s, 1+s+p,1+2*s); +g.fillRect(1,1+2*s-p, 1+s,1+2*s+p)}, +":":(g,s,p,n)=>{ +g.fillRect(1+s*0.4,1+s*0.4-p, 1+s*0.6,1+s*0.4+p); +g.fillRect(1+s*0.6-p,1+s*0.4, 1+s*0.6+p,1+s*0.6); +g.fillRect(1+s*0.6,1+s*0.6-p, 1+s*0.4,1+s*0.6+p); +g.fillRect(1+s*0.4-p,1+s*0.4, 1+s*0.4+p,1+s*0.6); +g.fillRect(1+s*0.4,1+s*1.4-p, 1+s*0.6,1+s*1.4+p); +g.fillRect(1+s*0.6-p,1+s*1.4, 1+s*0.6+p,1+s*1.6); +g.fillRect(1+s*0.6,1+s*1.6-p, 1+s*0.4,1+s*1.6+p); +g.fillRect(1+s*0.4-p,1+s*1.4, 1+s*0.4+p,1+s*1.6) +}}; + +/* Draw a transition between lastText and thisText. + 'n' is the amount - 0..1 */ +function drawDigits(lastText,thisText,n) { + const p = CHARP; // padding around digits + const s = CHARW; // character size + var x = p; // x offset + var y = Y+p; // y offset + g.reset(); + for (var i=0;i=1) { + n=1; + clearInterval(animInterval); + animInterval = undefined; + } + drawDigits(l,t,n); + }, 20); + lastTime = t; +} + +Bangle.on('lcdPower',function(on) { + if (animInterval) { + clearInterval(animInterval); + animInterval = undefined; + } + if (timeInterval) { + clearInterval(timeInterval); + timeInterval = undefined; + } + if (on) { + showTime(); + timeInterval = setInterval(showTime, 1000); + } +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// Update time once a second +timeInterval = setInterval(showTime, 1000); +showTime(); + +// Show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); diff --git a/apps/pong/ChangeLog b/apps/pong/ChangeLog index 5560f00bc..6433ebce4 100644 --- a/apps/pong/ChangeLog +++ b/apps/pong/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: 2 players local + improve ai diff --git a/apps/pong/README.md b/apps/pong/README.md new file mode 100644 index 000000000..ea4939539 --- /dev/null +++ b/apps/pong/README.md @@ -0,0 +1,28 @@ +# Pong + +A clone of the Atari game Pong + + + +## Features + +- Play against a dumb AI +- Play local Multiplayer against your friends + +## Controls + +Player's controls: +- UP: BTN1 +- DOWN: BTN2 +long press to move faster + +Restart a game: +- RESET: BTN3 + +Buttons for player 2: +- UP: BTN4 +- DOWN: BTN5 + +## Creator + + diff --git a/apps/pong/app.js b/apps/pong/app.js index 4531b3af8..ba34d60b5 100644 --- a/apps/pong/app.js +++ b/apps/pong/app.js @@ -8,6 +8,7 @@ * - Let's make pong, One Man Army Studios, Youtube * - Pong.js, KanoComputing, Github * - Coding Challenge #67: Pong!, The Coding Train, Youtube + * - Pixl.js Multiplayer Pong, espruino website */ const SCREEN_WIDTH = 240; @@ -15,6 +16,13 @@ const FPS = 16; const MAX_SCORE = 11; let scores = [0, 0]; let aiSpeedRandom = 0; +let winnerMessage = ''; + +const sound = { + ping: () => Bangle.beep(8, 466), + pong: () => Bangle.beep(8, 220), + fall: () => Bangle.beep(16*3, 494).then(_ => Bangle.beep(32*3, 3322)) +}; function Vector(x, y) { this.x = x; @@ -28,12 +36,18 @@ Vector.prototype.add = function (x) { const constrain = (n, low, high) => Math.max(Math.min(n, high), low); const random = (min, max) => Math.random() * (max - min) + min; -const intersects = (circ, rect) => { - var c1 = circ.pos, c2 = {x: circ.pos.x+circ.r, y: circ.pos.y+circ.r}; - var r1 = rect.pos, r2 = {x: rect.pos.x+rect.width*2, y: rect.pos.y+rect.height}; - return !(c1.x > r2.x || c2.x < r1.x || - c1.y > r2.y || c2.y < r1.y); -}; +const intersects = (circ, rect, right) => { + var c = circ.pos; + var r = circ.r; + if (c.y - r < rect.pos.y + rect.height && c.y + r > rect.pos.y) { + if (right) { + return c.x + r > rect.pos.x - rect.width*2 && c.x < rect.pos.x + rect.width + } else { + return c.x - r < rect.pos.x + rect.width*2 && c.x > rect.pos.x - rect.width + } + } + return false; +} ///////////////////////////// Ball ////////////////////////////////////////// @@ -45,12 +59,26 @@ function Ball() { this.reset(); } -Ball.prototype.show = function () { +Ball.prototype.reset = function() { + this.speed = this.originalSpeed; + var x = scores[0] < scores[1] || (scores[0] === 0 && scores[1] === 0) ? -this.speed : this.speed; + var bounceAngle = Math.PI/6; + this.velocity = new Vector(x * Math.cos(bounceAngle), this.speed * -Math.sin(bounceAngle)); + this.pos = new Vector(SCREEN_WIDTH/2, random(0, SCREEN_WIDTH)); + this.ballReturn = 0; +}; +Ball.prototype.restart = function() { + this.reset(); + ai.pos = new Vector(SCREEN_WIDTH - ai.width*2, SCREEN_WIDTH/2 - ai.height/2); + player.pos = new Vector(player.width*2, SCREEN_WIDTH/2 - player.height/2); + this.pos = new Vector(SCREEN_WIDTH/2, SCREEN_WIDTH/2); +}; +Ball.prototype.show = function (invert) { if (this.prevPos != null) { - g.setColor(0); + g.setColor(invert ? -1 : 0); g.fillCircle(this.prevPos.x, this.prevPos.y, this.prevPos.r); } - g.setColor(-1); + g.setColor(invert ? 0 : -1); g.fillCircle(this.pos.x, this.pos.y, this.r); this.prevPos = { x: this.pos.x, @@ -58,55 +86,62 @@ Ball.prototype.show = function () { r: this.r }; }; -Ball.prototype.bouncePlayer = function (multiplyX, multiplyY, player) { +function bounceAngle(playerY, ballY, playerHeight, maxHangle) { + let relativeIntersectY = (playerY + (playerHeight/2)) - ballY; + let normalizedRelativeIntersectionY = relativeIntersectY / (playerHeight/2); + let bounceAngle = normalizedRelativeIntersectionY * maxHangle; + return { x: Math.cos(bounceAngle), y: -Math.sin(bounceAngle) }; +} +Ball.prototype.bouncePlayer = function (directionX, directionY, player) { + this.ballReturn++; this.speed = constrain(this.speed + 2, this.originalSpeed, this.maxSpeed); - var relativeIntersectY = (player.pos.y+(player.height/2)) - this.pos.y; - var normalizedRelativeIntersectionY = (relativeIntersectY/(player.height/2)); var MAX_BOUNCE_ANGLE = 4 * Math.PI/12; - var bounceAngle = normalizedRelativeIntersectionY * MAX_BOUNCE_ANGLE; - this.velocity.x = this.speed * Math.cos(bounceAngle) * multiplyX; - this.velocity.y = this.speed * -Math.sin(bounceAngle) * multiplyY; + var angle = bounceAngle(player.pos.y, this.pos.y, player.height, MAX_BOUNCE_ANGLE) + this.velocity.x = this.speed * angle.x * directionX; + this.velocity.y = this.speed * angle.y * directionY; + this.ballReturn % 2 === 0 ? sound.ping() : sound.pong(); }; -Ball.prototype.bounce = function (multiplyX, multiplyY, player) { +Ball.prototype.bounce = function (directionX, directionY, player) { if (player) - return this.bouncePlayer(multiplyX, multiplyY, player); + return this.bouncePlayer(directionX, directionY, player); - if (multiplyX) { - this.velocity.x = Math.abs(this.velocity.x) * multiplyX; + if (directionX) { + this.velocity.x = Math.abs(this.velocity.x) * directionX; } - if (multiplyY) { - this.velocity.y = Math.abs(this.velocity.y) * multiplyY; + if (directionY) { + this.velocity.y = Math.abs(this.velocity.y) * directionY; } }; -Ball.prototype.checkWallsCollision = function () { +Ball.prototype.fall = function (playerId) { + scores[playerId]++; + if (scores[playerId] >= MAX_SCORE) { + this.restart(); + state = 3; + if (playerId === 1) { + winnerMessage = startOption === 0 ? "AI Wins!" : "Player 2 Wins!"; + } else { + winnerMessage = startOption === 0 ? "You Win!" : "Player 1 Wins!"; + } + } else { + sound.fall(); + this.reset(); + } +}; +Ball.prototype.wallCollision = function () { if (this.pos.y < 0) { this.bounce(0, 1); } else if (this.pos.y > SCREEN_WIDTH) { this.bounce(0, -1); } else if (this.pos.x < 0) { - scores[1]++; - if (scores[1] >= MAX_SCORE) { - this.restart(); - state = 3; - winnerMessage = "AI Wins!"; - } else { - this.reset(); - } + this.fall(1); } else if (this.pos.x > SCREEN_WIDTH) { - scores[0]++; - if (scores[0] >= MAX_SCORE) { - this.restart(); - state = 3; - winnerMessage = "You Win!"; - } else { - this.reset(); - } + this.fall(0); } else { return false; } return true; }; -Ball.prototype.checkPlayerCollision = function (player) { +Ball.prototype.playerCollision = function (player) { if (intersects(this, player)) { if (this.pos.x < SCREEN_WIDTH/2) { this.bounce(1, 1, player); @@ -120,8 +155,8 @@ Ball.prototype.checkPlayerCollision = function (player) { } return false; }; -Ball.prototype.checkCollisions = function () { - return this.checkWallsCollision() || this.checkPlayerCollision(player) || this.checkPlayerCollision(ai); +Ball.prototype.collisions = function () { + return this.wallCollision() || this.playerCollision(player) || this.playerCollision(ai); }; Ball.prototype.updatePosition = function () { var elapsed = new Date().getTime() - this.lastUpdate; @@ -132,31 +167,20 @@ Ball.prototype.updatePosition = function () { Ball.prototype.update = function () { this.updatePosition(); this.lastUpdate = new Date().getTime(); - this.checkCollisions(); -}; -Ball.prototype.reset = function() { - this.speed = this.originalSpeed; - var x = scores[0] < scores[1] || (scores[0] === 0 && scores[1] === 0) ? -this.speed : this.speed; - var bounceAngle = Math.PI/6; - this.velocity = new Vector(x * Math.cos(bounceAngle), this.speed * -Math.sin(bounceAngle)); - this.pos = new Vector(SCREEN_WIDTH/2, random(0, SCREEN_WIDTH)); -}; -Ball.prototype.restart = function() { - ai.pos = new Vector(SCREEN_WIDTH - ai.width*2, SCREEN_WIDTH/2 - ai.height/2); - player.pos = new Vector(player.width*2, SCREEN_WIDTH/2 - player.height/2); - this.pos = new Vector(SCREEN_WIDTH/2, SCREEN_WIDTH/2); + this.collisions(); }; //////////////////////////// Player ///////////////////////////////////////// -function Player() { +function Player(right) { this.width = 4; this.height = 30; - this.pos = new Vector(this.width*2, SCREEN_WIDTH/2 - this.height/2); + this.pos = new Vector(right ? SCREEN_WIDTH-this.width : this.width, SCREEN_WIDTH/2 - this.height/2); this.acc = new Vector(0, 0); this.speed = 15; this.maxSpeed = 25; this.prevPos = null; + this.right = right; } Player.prototype.show = function () { if (this.prevPos != null) { @@ -196,11 +220,14 @@ function AI() { AI.prototype = Object.create(Player.prototype); AI.prototype.constructor = Player; AI.prototype.update = function () { - var y = ball.pos.y - (this.height/2 * aiSpeedRandom); - var yConstrained = constrain(y, 0, SCREEN_WIDTH-this.height); + var y = ball.pos.y - this.height/2; + var randomizedY = ball.ballReturn < 3 ? y : y + (aiSpeedRandom * this.height/2); + var yConstrained = constrain(randomizedY, 0, SCREEN_WIDTH-this.height); this.pos = new Vector(this.pos.x, yConstrained); }; +/////////////////////////////// Scenes //////////////////////////////////////// + function net() { var dashSize = 5; for (let y = dashSize/2; y < SCREEN_WIDTH; y += dashSize*2) { @@ -210,12 +237,6 @@ function net() { } } -var player = new Player(); -var ai = new AI(); -var ball = new Ball(); -var state = 0; -var prevScores = [0, 0]; - function drawScores() { let x1 = SCREEN_WIDTH/4-5; let x2 = SCREEN_WIDTH*3/4-5; @@ -233,10 +254,80 @@ function drawScores() { function drawGameOver() { g.setFont("Vector", 20); - g.drawString(winnerMessage, 75, SCREEN_WIDTH/2 - 10); + g.drawString(winnerMessage, startOption === 0 ? 55 : 75, SCREEN_WIDTH/2 - 10); } -function draw() { +function showControls(hide) { + g.setColor(hide ? 0 : -1); + g.setFont("Vector", 8); + var topArrowString = ` + ######## + ## + ## ## + ### ## + ### ## + ### +## +`; + + var arrows = [Graphics.createImage(topArrowString), Graphics.createImage(` + ## + ## +#################### + ## + ## +`), Graphics.createImage(topArrowString.split('\n').reverse().join('\n')) + ]; + + g.drawString('UP', 170, 50); + g.drawImage(arrows[0], 200, 40); + g.drawString('DOWN', 156, 120); + g.drawImage(arrows[1], 200, 120); + g.drawString('START', 152, 190); + g.drawImage(arrows[2], 200, 200); +} + +function drawStartScreen(hide) { + g.setColor(hide ? 0 : -1); + g.setFont("Vector", 10); + g.drawString("1 PLAYER", 95, 80); + g.drawString("2 PLAYERS", 95, 110); + + const ball1 = new Ball(); + ball1.prevPos = null; + ball1.pos = new Vector(87, 86); + ball1.show(hide || !(startOption === 0)); + + const ball2 = new Ball(); + ball2.prevPos = null; + ball2.pos = new Vector(87, 116); + ball2.show(hide || !(startOption === 1)); +} + +function drawStartTimer(count, callback) { + setTimeout(_ => { + player.show(); + ai.show(); + net(); + g.setColor(0); + g.fillRect(117-7, 115-7, 117+14, 115+14); + if (count >= 0) { + g.setFont("Vector", 10); + g.drawString(count+1, 115, 115); + g.setColor(-1); + g.drawString(count === 0 ? 'Go!' : count, 115 - (count === 0 ? 4: 0), 115); + drawStartTimer(count - 1, callback); + } else { + g.setColor(0); + g.fillRect(117-7, 115-7, 117+14, 115+14); + callback(); + } + }, 800); +} + +//////////////////////////////// Main ///////////////////////////////////////// + +function onFrame() { if (state === 1) { ball.update(); player.update(); @@ -261,22 +352,73 @@ function draw() { drawScores(); } +function startThatGame() { + player.show(); + ai.show(); + net(); + drawScores(); + drawStartTimer(3, () => setInterval(onFrame, 1000 / FPS)); +} + +var player = new Player(); +var ai; +var ball = new Ball(); +var state = 0; +var prevScores = [0, 0]; +var playerBle = null; +var startOption = 0; + g.clear(); g.setColor(0); g.fillRect(0,0,240,240); +showControls(); +setTimeout(() => { + showControls(true); + drawStartScreen(); +}, 2000); -setInterval(draw, 1000 / FPS); +////////////////////////////// Controls /////////////////////////////////////// -setWatch(o => o.state ? player.up() : player.stop(), BTN1, {repeat: true, edge: 'both'}); -setWatch(o => o.state ? player.down() : player.stop(), BTN3, {repeat: true, edge: 'both'}); -//setWatch(o => o.state ? player.down() : player.stop(), BTN5, {repeat: true, edge: 'both'}); +setWatch(o => { + if (state === 0) { + if (o.state) { + startOption = startOption === 0 ? startOption : startOption - 1; + drawStartScreen(); + } + } else o.state ? player.up() : player.stop(); +}, BTN1, {repeat: true, edge: 'both'}); +setWatch(o => { + if (state === 0) { + if (o.state) { + startOption = startOption === 1 ? startOption : startOption + 1; + drawStartScreen(); + } + } else o.state ? player.down() : player.stop(); +}, BTN2, {repeat: true, edge: 'both'}); setWatch(o => { state++; + clearInterval(); if (state >= 2) { - ball.restart(); g.setColor(0); - g.fillRect(0,0,240,240); + g.fillRect(0, 0, 240, 240); + ball.show(true); scores = [0, 0]; + playerBle = null; + ball = new Ball(); state = 1; + startThatGame(); + } else { + drawStartScreen(true); + showControls(true); + if (startOption === 1) { + ai = new Player(true); + startThatGame(); + } else { + ai = new AI(); + startThatGame(); + } } -}, BTN2, {repeat: true}); +}, BTN3, {repeat: true}); + +setWatch(o => startOption === 1 && (o.state ? ai.up() : ai.stop()), BTN4, {repeat: true, edge: 'both'}); +setWatch(o => startOption === 1 && (o.state ? ai.down() : ai.stop()), BTN5, {repeat: true, edge: 'both'}); diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 9263b3b13..f168a1fe5 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -20,3 +20,4 @@ 0.16: Reduce memory usage further when running app settings page 0.17: Remove need for "settings" in appid.info 0.18: Don't overwrite existing settings on app update +0.19: Allow BLE HID settings, add README.md diff --git a/apps/setting/README.md b/apps/setting/README.md new file mode 100644 index 000000000..4052da0ff --- /dev/null +++ b/apps/setting/README.md @@ -0,0 +1,18 @@ +# 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** 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? +* **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. +* **Reset Settings** Reset the settings to defaults +* **Turn Off** Turn Bangle.js off diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 97ce464ad..55048a9d4 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -61,6 +61,8 @@ 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(), @@ -115,10 +117,11 @@ function showMainMenu() { 'Locale': ()=>showLocaleMenu(), 'Select Clock': ()=>showClockMenu(), 'HID': { - value: settings.HID, - format: boolFormat, - onchange: () => { - settings.HID = !settings.HID; + value: 0 | hidV.indexOf(settings.HID), + min: 0, max: 3, + format: v => hidN[v], + onchange: v => { + settings.HID = hidV[v]; updateSettings(); } },