mirror of https://github.com/espruino/BangleApps
Merge branch 'master' into minify_online
commit
0a74f08be0
21
apps.json
21
apps.json
|
@ -2,7 +2,7 @@
|
||||||
{ "id": "boot",
|
{ "id": "boot",
|
||||||
"name": "Bootloader",
|
"name": "Bootloader",
|
||||||
"icon": "bootloader.png",
|
"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",
|
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||||
"tags": "tool,system",
|
"tags": "tool,system",
|
||||||
"type":"bootloader",
|
"type":"bootloader",
|
||||||
|
@ -122,9 +122,10 @@
|
||||||
{ "id": "setting",
|
{ "id": "setting",
|
||||||
"name": "Settings",
|
"name": "Settings",
|
||||||
"icon": "settings.png",
|
"icon": "settings.png",
|
||||||
"version":"0.18",
|
"version":"0.19",
|
||||||
"description": "A menu for setting up Bangle.js",
|
"description": "A menu for setting up Bangle.js",
|
||||||
"tags": "tool,system",
|
"tags": "tool,system",
|
||||||
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"setting.app.js","url":"settings.js"},
|
{"name":"setting.app.js","url":"settings.js"},
|
||||||
{"name":"setting.boot.js","url":"boot.js"},
|
{"name":"setting.boot.js","url":"boot.js"},
|
||||||
|
@ -480,7 +481,7 @@
|
||||||
"name": "Bluetooth Music Controls",
|
"name": "Bluetooth Music Controls",
|
||||||
"shortName": "Music Control",
|
"shortName": "Music Control",
|
||||||
"icon": "hid-music.png",
|
"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!",
|
"description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!",
|
||||||
"tags": "bluetooth",
|
"tags": "bluetooth",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
@ -492,7 +493,7 @@
|
||||||
"name": "Bluetooth Keyboard",
|
"name": "Bluetooth Keyboard",
|
||||||
"shortName": "Bluetooth Kbd",
|
"shortName": "Bluetooth Kbd",
|
||||||
"icon": "hid-keyboard.png",
|
"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",
|
"description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps",
|
||||||
"tags": "bluetooth",
|
"tags": "bluetooth",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
@ -504,7 +505,7 @@
|
||||||
"name": "Binary Bluetooth Keyboard",
|
"name": "Binary Bluetooth Keyboard",
|
||||||
"shortName": "Binary BT Kbd",
|
"shortName": "Binary BT Kbd",
|
||||||
"icon": "hid-binary-keyboard.png",
|
"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",
|
"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",
|
"tags": "bluetooth",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
@ -1188,7 +1189,7 @@
|
||||||
"name": "Active Pedometer",
|
"name": "Active Pedometer",
|
||||||
"shortName":"Active Pedometer",
|
"shortName":"Active Pedometer",
|
||||||
"icon": "app.png",
|
"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.",
|
"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",
|
"tags": "outdoors,widget",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
@ -1254,7 +1255,7 @@
|
||||||
"name": "Battery Chart",
|
"name": "Battery Chart",
|
||||||
"shortName":"Battery Chart",
|
"shortName":"Battery Chart",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.08",
|
"version":"0.09",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"description": "A widget and an app for recording and visualizing battery percentage over time.",
|
"description": "A widget and an app for recording and visualizing battery percentage over time.",
|
||||||
"tags": "app,widget,battery,time,record,chart,tool",
|
"tags": "app,widget,battery,time,record,chart,tool",
|
||||||
|
@ -1404,6 +1405,7 @@
|
||||||
"name": "Metronome",
|
"name": "Metronome",
|
||||||
"icon": "metronome_icon.png",
|
"icon": "metronome_icon.png",
|
||||||
"version": "0.03",
|
"version": "0.03",
|
||||||
|
"readme": "README.md",
|
||||||
"description": "Makes the watch blinking and vibrating with a given rate",
|
"description": "Makes the watch blinking and vibrating with a given rate",
|
||||||
"tags": "tool",
|
"tags": "tool",
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
|
@ -1436,7 +1438,7 @@
|
||||||
"name": "Camera shutter",
|
"name": "Camera shutter",
|
||||||
"shortName":"Cam shutter",
|
"shortName":"Cam shutter",
|
||||||
"icon": "app.png",
|
"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",
|
"description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle",
|
||||||
"tags": "tools",
|
"tags": "tools",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
@ -1486,11 +1488,12 @@
|
||||||
"name": "Pong",
|
"name": "Pong",
|
||||||
"shortName": "Pong",
|
"shortName": "Pong",
|
||||||
"icon": "pong.png",
|
"icon": "pong.png",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "A clone of the Atari game Pong",
|
"description": "A clone of the Atari game Pong",
|
||||||
"tags": "game",
|
"tags": "game",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"pong.app.js","url":"app.js"},
|
{"name":"pong.app.js","url":"app.js"},
|
||||||
{"name":"pong.img","url":"app-icon.js","evaluate":true}
|
{"name":"pong.img","url":"app-icon.js","evaluate":true}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New Widget!
|
0.01: New Widget!
|
||||||
0.02: Distance calculation and display
|
0.02: Distance calculation and display
|
||||||
0.03: Data logging and display
|
0.03: Data logging and display
|
||||||
|
0.04: Steps are set to 0 in log on new day
|
|
@ -18,7 +18,7 @@ Steps are saved to a datafile every 5 minutes. You can watch a graph using the a
|
||||||
* 10600 steps
|
* 10600 steps
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features Widget
|
||||||
|
|
||||||
* Two line display
|
* Two line display
|
||||||
* Can display distance (in km) or steps in each line
|
* 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)
|
* 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
|
* 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 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
|
* Format: now,stepsCounted,active,stepsTooShort,stepsTooLong,stepsOutsideTime
|
||||||
* now is UNIX timestamp in ms
|
* 'now' is UNIX timestamp in ms
|
||||||
* You can chose the app to watch a steps graph
|
* You can use the app to watch a steps graph
|
||||||
* You can import the file into Excel
|
* You can import the file into Excel
|
||||||
* The file does not include a header
|
* 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 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
|
* 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
|
## Settings
|
||||||
|
|
||||||
* Max time (ms): Maximum time between two steps in milliseconds, steps will not be counted if exceeded. Standard: 1100
|
* Max time (ms): Maximum time between two steps in milliseconds, steps will not be counted if exceeded. Standard: 1100
|
||||||
|
|
|
@ -33,27 +33,28 @@
|
||||||
|
|
||||||
function storeData() {
|
function storeData() {
|
||||||
now = new Date();
|
now = new Date();
|
||||||
month = now.getMonth() + 1;
|
month = now.getMonth() + 1; //month is 0-based
|
||||||
if (month < 10) month = "0" + month;
|
if (month < 10) month = "0" + month; //leading 0
|
||||||
filename = filename = "activepedom" + now.getFullYear() + month + now.getDate() + ".data";
|
filename = filename = "activepedom" + now.getFullYear() + month + now.getDate() + ".data"; //new file for each day
|
||||||
dataFile = s.open(filename,"a");
|
dataFile = s.open(filename,"a");
|
||||||
if (dataFile) {
|
if (dataFile) { //check if filen already exists
|
||||||
if (dataFile.getLength() == 0) {
|
if (dataFile.getLength() == 0) {
|
||||||
stepsToWrite = 0;
|
//new day, set steps to 0
|
||||||
}
|
stepsCounted = 0;
|
||||||
else {
|
stepsTooShort = 0;
|
||||||
stepsToWrite = stepsCounted;
|
stepsTooLong = 0;
|
||||||
|
stepsOutsideTime = 0;
|
||||||
}
|
}
|
||||||
dataFile.write([
|
dataFile.write([
|
||||||
now.getTime(),
|
now.getTime(),
|
||||||
stepsToWrite,
|
stepsCounted,
|
||||||
active,
|
active,
|
||||||
stepsTooShort,
|
stepsTooShort,
|
||||||
stepsTooLong,
|
stepsTooLong,
|
||||||
stepsOutsideTime,
|
stepsOutsideTime,
|
||||||
].join(",")+"\n");
|
].join(",")+"\n");
|
||||||
}
|
}
|
||||||
dataFile = undefined;
|
dataFile = undefined; //save memory
|
||||||
}
|
}
|
||||||
|
|
||||||
//return setting
|
//return setting
|
||||||
|
|
|
@ -6,3 +6,4 @@
|
||||||
0.06: Fixes widget events and charting of component states
|
0.06: Fixes widget events and charting of component states
|
||||||
0.07: Improve logging and charting of component states and add widget icon
|
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.
|
0.08: Fix for Home button in the app and README added.
|
||||||
|
0.09: Fix failing dismissal of Gadgetbridge notifications, record (coarse) bluetooth state
|
|
@ -8,7 +8,7 @@ const GraphXMax = GraphXZero + MaxValueCount;
|
||||||
|
|
||||||
const GraphLcdY = GraphYZero + 10;
|
const GraphLcdY = GraphYZero + 10;
|
||||||
const GraphCompassY = GraphYZero + 16;
|
const GraphCompassY = GraphYZero + 16;
|
||||||
// const GraphBluetoothY = GraphYZero + 22;
|
const GraphBluetoothY = GraphYZero + 22;
|
||||||
const GraphGpsY = GraphYZero + 28;
|
const GraphGpsY = GraphYZero + 28;
|
||||||
const GraphHrmY = GraphYZero + 34;
|
const GraphHrmY = GraphYZero + 34;
|
||||||
|
|
||||||
|
@ -175,13 +175,13 @@ function renderData(dataArray) {
|
||||||
g.drawLine(GraphXZero + i, GraphCompassY, GraphXZero + i, GraphCompassY + 1);
|
g.drawLine(GraphXZero + i, GraphCompassY, GraphXZero + i, GraphCompassY + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Bluetooth state
|
// Bluetooth state
|
||||||
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
|
if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.bluetooth) {
|
||||||
// g.setColor(0, 0, 1);
|
g.setColor(0, 0, 1);
|
||||||
// g.setFontAlign(1, -1, 0);
|
g.setFontAlign(1, -1, 0);
|
||||||
// g.drawString("BLE", GraphXZero - GraphMarkerOffset, GraphBluetoothY - 2, true);
|
g.drawString("BLE", GraphXZero - GraphMarkerOffset, GraphBluetoothY - 2, true);
|
||||||
// g.drawLine(GraphXZero + i, GraphBluetoothY, GraphXZero + i, GraphBluetoothY + 1);
|
g.drawLine(GraphXZero + i, GraphBluetoothY, GraphXZero + i, GraphBluetoothY + 1);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Gps state
|
// Gps state
|
||||||
if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.gps) {
|
if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.gps) {
|
||||||
|
|
|
@ -71,8 +71,10 @@
|
||||||
enabledConsumers = enabledConsumers | switchableConsumers.gps;
|
enabledConsumers = enabledConsumers | switchableConsumers.gps;
|
||||||
if (hrmEventReceived)
|
if (hrmEventReceived)
|
||||||
enabledConsumers = enabledConsumers | switchableConsumers.hrm;
|
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
|
// Reset the event registration vars
|
||||||
compassEventReceived = false;
|
compassEventReceived = false;
|
||||||
|
@ -110,19 +112,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
WIDGETS.batchart.width = 24;
|
WIDGETS["batchart"].width = 24;
|
||||||
|
|
||||||
recordingInterval = setInterval(logBatteryData, recordingInterval10Min);
|
recordingInterval = setInterval(logBatteryData, recordingInterval10Min);
|
||||||
|
|
||||||
logBatteryData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the widget
|
// add the widget
|
||||||
WIDGETS.batchart = {
|
WIDGETS["batchart"] = {
|
||||||
area: "tl", width: 24, draw: draw, reload: function () {
|
area: "tl", width: 24, draw: draw, reload: reload
|
||||||
reload();
|
|
||||||
Bangle.drawWidgets();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
reload();
|
reload();
|
||||||
|
|
|
@ -13,3 +13,4 @@
|
||||||
0.13: Now automatically load *.boot.js at startup
|
0.13: Now automatically load *.boot.js at startup
|
||||||
Move alarm code into alarm.boot.js
|
Move alarm code into alarm.boot.js
|
||||||
0.14: Move welcome loaders to *.boot.js
|
0.14: Move welcome loaders to *.boot.js
|
||||||
|
0.15: Added BLE HID option for Joystick and bare Keyboard
|
||||||
|
|
|
@ -4,7 +4,9 @@ E.setFlags({pretokenise:1});
|
||||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||||
if (s.ble!==false) {
|
if (s.ble!==false) {
|
||||||
if (s.HID) { // Human interface device
|
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});
|
NRF.setServices({}, {uart:true, hid:Bangle.HID});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
```
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: Core functionnality
|
||||||
|
0.02: Offer to enable HID if disabled. Handle with/without media keys
|
|
@ -45,13 +45,7 @@ const KEY = {
|
||||||
0 : 39
|
0 : 39
|
||||||
};
|
};
|
||||||
|
|
||||||
function sendHID(code) {
|
var sendHID;
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function showChars(x,chars) {
|
function showChars(x,chars) {
|
||||||
var lines = Math.round(Math.sqrt(chars.length)*2);
|
var lines = Math.round(Math.sqrt(chars.length)*2);
|
||||||
|
@ -103,10 +97,24 @@ function startKeyboardHID() {
|
||||||
}).then(startKeyboardHID);
|
}).then(startKeyboardHID);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!settings.HID) {
|
if (settings.HID=="kb" || settings.HID=="kbmedia") {
|
||||||
E.showMessage('HID disabled');
|
if (settings.HID=="kbmedia") {
|
||||||
setTimeout(load, 1000);
|
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 {
|
} 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();
|
startKeyboardHID();
|
||||||
setWatch(() => {
|
setWatch(() => {
|
||||||
sendHID(44); // space
|
sendHID(44); // space
|
||||||
|
@ -114,4 +122,12 @@ if (!settings.HID) {
|
||||||
setWatch(() => {
|
setWatch(() => {
|
||||||
sendHID(40); // enter
|
sendHID(40); // enter
|
||||||
}, BTN3, {repeat:true});
|
}, 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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: Core functionnality
|
0.01: Core functionnality
|
||||||
|
0.02: Offer to enable HID if disabled
|
||||||
|
|
|
@ -4,7 +4,7 @@ const settings = storage.readJSON('setting.json',1) || { HID: false };
|
||||||
|
|
||||||
var sendHid, camShot, profile;
|
var sendHid, camShot, profile;
|
||||||
|
|
||||||
if (settings.HID) {
|
if (settings.HID=="kbmedia") {
|
||||||
profile = 'camShutter';
|
profile = 'camShutter';
|
||||||
sendHid = function (code, cb) {
|
sendHid = function (code, cb) {
|
||||||
try {
|
try {
|
||||||
|
@ -19,8 +19,13 @@ if (settings.HID) {
|
||||||
};
|
};
|
||||||
camShot = function (cb) { sendHid(0x80, cb); };
|
camShot = function (cb) { sendHid(0x80, cb); };
|
||||||
} else {
|
} else {
|
||||||
E.showMessage('HID disabled');
|
E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
|
||||||
setTimeout(load, 1000);
|
if (enable) {
|
||||||
|
settings.HID = "kbmedia";
|
||||||
|
require("Storage").write('setting.json', settings);
|
||||||
|
setTimeout(load, 1000, "hidcam.app.js");
|
||||||
|
} else setTimeout(load, 1000);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function drawApp() {
|
function drawApp() {
|
||||||
g.clear();
|
g.clear();
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: Core functionnality
|
||||||
|
0.02: Offer to enable HID if disabled. Handle with/without media keys
|
|
@ -4,8 +4,9 @@ const settings = storage.readJSON('setting.json',1) || { HID: false };
|
||||||
|
|
||||||
var sendHid, next, prev, toggle, up, down, profile;
|
var sendHid, next, prev, toggle, up, down, profile;
|
||||||
|
|
||||||
if (settings.HID) {
|
if (settings.HID=="kb" || settings.HID=="kbmedia") {
|
||||||
profile = 'Keyboard';
|
profile = 'Keyboard';
|
||||||
|
if (settings.HID=="kbmedia") {
|
||||||
sendHid = function (code, cb) {
|
sendHid = function (code, cb) {
|
||||||
try {
|
try {
|
||||||
NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => {
|
NRF.sendHIDReport([2,0,0,code,0,0,0,0,0], () => {
|
||||||
|
@ -17,14 +18,32 @@ if (settings.HID) {
|
||||||
print(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); };
|
next = function (cb) { sendHid(0x4f, cb); };
|
||||||
prev = function (cb) { sendHid(0x50, cb); };
|
prev = function (cb) { sendHid(0x50, cb); };
|
||||||
toggle = function (cb) { sendHid(0x2c, cb); };
|
toggle = function (cb) { sendHid(0x2c, cb); };
|
||||||
up = function (cb) {sendHid(0x52, cb); };
|
up = function (cb) {sendHid(0x52, cb); };
|
||||||
down = function (cb) { sendHid(0x51, cb); };
|
down = function (cb) { sendHid(0x51, cb); };
|
||||||
} else {
|
} else {
|
||||||
E.showMessage('HID disabled');
|
E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
|
||||||
setTimeout(load, 1000);
|
if (enable) {
|
||||||
|
settings.HID = "kb";
|
||||||
|
require("Storage").write('setting.json', settings);
|
||||||
|
setTimeout(load, 1000, "hidkbd.app.js");
|
||||||
|
} else setTimeout(load, 1000);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawApp() {
|
function drawApp() {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: Core functionnality
|
|
@ -4,7 +4,7 @@ const settings = storage.readJSON('setting.json',1) || { HID: false };
|
||||||
|
|
||||||
var sendHid, next, prev, toggle, up, down, profile;
|
var sendHid, next, prev, toggle, up, down, profile;
|
||||||
|
|
||||||
if (settings.HID) {
|
if (settings.HID=="kbmedia") {
|
||||||
profile = 'Music';
|
profile = 'Music';
|
||||||
sendHid = function (code, cb) {
|
sendHid = function (code, cb) {
|
||||||
try {
|
try {
|
||||||
|
@ -23,8 +23,13 @@ if (settings.HID) {
|
||||||
up = function (cb) {sendHid(0x40, cb); };
|
up = function (cb) {sendHid(0x40, cb); };
|
||||||
down = function (cb) { sendHid(0x80, cb); };
|
down = function (cb) { sendHid(0x80, cb); };
|
||||||
} else {
|
} else {
|
||||||
E.showMessage('HID disabled');
|
E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
|
||||||
setTimeout(load, 1000);
|
if (enable) {
|
||||||
|
settings.HID = "kbmedia";
|
||||||
|
require("Storage").write('setting.json', settings);
|
||||||
|
setTimeout(load, 1000, "hidmsc.app.js");
|
||||||
|
} else setTimeout(load, 1000);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawApp() {
|
function drawApp() {
|
||||||
|
|
|
@ -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<lastText.length;i++) {
|
||||||
|
var lastCh = lastText[i];
|
||||||
|
var thisCh = thisText[i];
|
||||||
|
if (thisCh==":") x-=4;
|
||||||
|
if (lastCh!=thisCh) {
|
||||||
|
var ch, chn = n;
|
||||||
|
if ((thisCh-1==lastCh ||
|
||||||
|
(thisCh==0 && lastCh==5) ||
|
||||||
|
(thisCh==0 && lastCh==9)))
|
||||||
|
ch = lastCh;
|
||||||
|
else {
|
||||||
|
ch = thisCh;
|
||||||
|
chn = 0;
|
||||||
|
}
|
||||||
|
if (ch=="5") ch = (lastCh==5 && thisCh==0)?"5to0":"5to6";
|
||||||
|
buf.clear();
|
||||||
|
DIGITS[ch](buf,s,p,chn);
|
||||||
|
g.drawImage(bufimg,x-1,y-1);
|
||||||
|
}
|
||||||
|
if (thisCh==":") x-=4;
|
||||||
|
x+=s+p+7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function drawSeconds() {
|
||||||
|
var x = CHARW*6 + CHARP*2 - 4;
|
||||||
|
var y = Y + 2*CHARW + CHARP;
|
||||||
|
var d = new Date();
|
||||||
|
g.reset();
|
||||||
|
g.setFont("6x8");
|
||||||
|
g.setFontAlign(-1,-1);
|
||||||
|
g.drawString(("0"+d.getSeconds()).substr(-2), x, y-8, true);
|
||||||
|
// date
|
||||||
|
g.setFontAlign(0,-1);
|
||||||
|
var date = locale.date(d,false);
|
||||||
|
g.drawString(date, g.getWidth()/2, y+8, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the current time, and animate if needed */
|
||||||
|
function showTime() {
|
||||||
|
if (animInterval) return; // in animation - quit
|
||||||
|
var d = new Date();
|
||||||
|
var t = (" "+d.getHours()).substr(-2)+":"+
|
||||||
|
("0"+d.getMinutes()).substr(-2);
|
||||||
|
var l = lastTime;
|
||||||
|
// same - don't animate
|
||||||
|
if (t==l || l==" ") {
|
||||||
|
drawDigits(t,l,0);
|
||||||
|
drawSeconds();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var n = 0;
|
||||||
|
animInterval = setInterval(function() {
|
||||||
|
n += 1/10;
|
||||||
|
if (n>=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"});
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: 2 players local + improve ai
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Pong
|
||||||
|
|
||||||
|
A clone of the Atari game Pong
|
||||||
|
|
||||||
|
<img src="https://user-images.githubusercontent.com/702227/79855656-2a507a00-83c3-11ea-9162-65732729b992.png" height="384" width="384" />
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
<https://twitter.com/fredericrous>
|
286
apps/pong/app.js
286
apps/pong/app.js
|
@ -8,6 +8,7 @@
|
||||||
* - Let's make pong, One Man Army Studios, Youtube
|
* - Let's make pong, One Man Army Studios, Youtube
|
||||||
* - Pong.js, KanoComputing, Github
|
* - Pong.js, KanoComputing, Github
|
||||||
* - Coding Challenge #67: Pong!, The Coding Train, Youtube
|
* - Coding Challenge #67: Pong!, The Coding Train, Youtube
|
||||||
|
* - Pixl.js Multiplayer Pong, espruino website
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const SCREEN_WIDTH = 240;
|
const SCREEN_WIDTH = 240;
|
||||||
|
@ -15,6 +16,13 @@ const FPS = 16;
|
||||||
const MAX_SCORE = 11;
|
const MAX_SCORE = 11;
|
||||||
let scores = [0, 0];
|
let scores = [0, 0];
|
||||||
let aiSpeedRandom = 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) {
|
function Vector(x, y) {
|
||||||
this.x = x;
|
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 constrain = (n, low, high) => Math.max(Math.min(n, high), low);
|
||||||
const random = (min, max) => Math.random() * (max - min) + min;
|
const random = (min, max) => Math.random() * (max - min) + min;
|
||||||
const intersects = (circ, rect) => {
|
const intersects = (circ, rect, right) => {
|
||||||
var c1 = circ.pos, c2 = {x: circ.pos.x+circ.r, y: circ.pos.y+circ.r};
|
var c = circ.pos;
|
||||||
var r1 = rect.pos, r2 = {x: rect.pos.x+rect.width*2, y: rect.pos.y+rect.height};
|
var r = circ.r;
|
||||||
return !(c1.x > r2.x || c2.x < r1.x ||
|
if (c.y - r < rect.pos.y + rect.height && c.y + r > rect.pos.y) {
|
||||||
c1.y > r2.y || c2.y < r1.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 //////////////////////////////////////////
|
///////////////////////////// Ball //////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -45,12 +59,26 @@ function Ball() {
|
||||||
|
|
||||||
this.reset();
|
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) {
|
if (this.prevPos != null) {
|
||||||
g.setColor(0);
|
g.setColor(invert ? -1 : 0);
|
||||||
g.fillCircle(this.prevPos.x, this.prevPos.y, this.prevPos.r);
|
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);
|
g.fillCircle(this.pos.x, this.pos.y, this.r);
|
||||||
this.prevPos = {
|
this.prevPos = {
|
||||||
x: this.pos.x,
|
x: this.pos.x,
|
||||||
|
@ -58,55 +86,62 @@ Ball.prototype.show = function () {
|
||||||
r: this.r
|
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);
|
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 MAX_BOUNCE_ANGLE = 4 * Math.PI/12;
|
||||||
var bounceAngle = normalizedRelativeIntersectionY * MAX_BOUNCE_ANGLE;
|
var angle = bounceAngle(player.pos.y, this.pos.y, player.height, MAX_BOUNCE_ANGLE)
|
||||||
this.velocity.x = this.speed * Math.cos(bounceAngle) * multiplyX;
|
this.velocity.x = this.speed * angle.x * directionX;
|
||||||
this.velocity.y = this.speed * -Math.sin(bounceAngle) * multiplyY;
|
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)
|
if (player)
|
||||||
return this.bouncePlayer(multiplyX, multiplyY, player);
|
return this.bouncePlayer(directionX, directionY, player);
|
||||||
|
|
||||||
if (multiplyX) {
|
if (directionX) {
|
||||||
this.velocity.x = Math.abs(this.velocity.x) * multiplyX;
|
this.velocity.x = Math.abs(this.velocity.x) * directionX;
|
||||||
}
|
}
|
||||||
if (multiplyY) {
|
if (directionY) {
|
||||||
this.velocity.y = Math.abs(this.velocity.y) * multiplyY;
|
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) {
|
if (this.pos.y < 0) {
|
||||||
this.bounce(0, 1);
|
this.bounce(0, 1);
|
||||||
} else if (this.pos.y > SCREEN_WIDTH) {
|
} else if (this.pos.y > SCREEN_WIDTH) {
|
||||||
this.bounce(0, -1);
|
this.bounce(0, -1);
|
||||||
} else if (this.pos.x < 0) {
|
} else if (this.pos.x < 0) {
|
||||||
scores[1]++;
|
this.fall(1);
|
||||||
if (scores[1] >= MAX_SCORE) {
|
|
||||||
this.restart();
|
|
||||||
state = 3;
|
|
||||||
winnerMessage = "AI Wins!";
|
|
||||||
} else {
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
} else if (this.pos.x > SCREEN_WIDTH) {
|
} else if (this.pos.x > SCREEN_WIDTH) {
|
||||||
scores[0]++;
|
this.fall(0);
|
||||||
if (scores[0] >= MAX_SCORE) {
|
|
||||||
this.restart();
|
|
||||||
state = 3;
|
|
||||||
winnerMessage = "You Win!";
|
|
||||||
} else {
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
Ball.prototype.checkPlayerCollision = function (player) {
|
Ball.prototype.playerCollision = function (player) {
|
||||||
if (intersects(this, player)) {
|
if (intersects(this, player)) {
|
||||||
if (this.pos.x < SCREEN_WIDTH/2) {
|
if (this.pos.x < SCREEN_WIDTH/2) {
|
||||||
this.bounce(1, 1, player);
|
this.bounce(1, 1, player);
|
||||||
|
@ -120,8 +155,8 @@ Ball.prototype.checkPlayerCollision = function (player) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
Ball.prototype.checkCollisions = function () {
|
Ball.prototype.collisions = function () {
|
||||||
return this.checkWallsCollision() || this.checkPlayerCollision(player) || this.checkPlayerCollision(ai);
|
return this.wallCollision() || this.playerCollision(player) || this.playerCollision(ai);
|
||||||
};
|
};
|
||||||
Ball.prototype.updatePosition = function () {
|
Ball.prototype.updatePosition = function () {
|
||||||
var elapsed = new Date().getTime() - this.lastUpdate;
|
var elapsed = new Date().getTime() - this.lastUpdate;
|
||||||
|
@ -132,31 +167,20 @@ Ball.prototype.updatePosition = function () {
|
||||||
Ball.prototype.update = function () {
|
Ball.prototype.update = function () {
|
||||||
this.updatePosition();
|
this.updatePosition();
|
||||||
this.lastUpdate = new Date().getTime();
|
this.lastUpdate = new Date().getTime();
|
||||||
this.checkCollisions();
|
this.collisions();
|
||||||
};
|
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//////////////////////////// Player /////////////////////////////////////////
|
//////////////////////////// Player /////////////////////////////////////////
|
||||||
|
|
||||||
function Player() {
|
function Player(right) {
|
||||||
this.width = 4;
|
this.width = 4;
|
||||||
this.height = 30;
|
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.acc = new Vector(0, 0);
|
||||||
this.speed = 15;
|
this.speed = 15;
|
||||||
this.maxSpeed = 25;
|
this.maxSpeed = 25;
|
||||||
this.prevPos = null;
|
this.prevPos = null;
|
||||||
|
this.right = right;
|
||||||
}
|
}
|
||||||
Player.prototype.show = function () {
|
Player.prototype.show = function () {
|
||||||
if (this.prevPos != null) {
|
if (this.prevPos != null) {
|
||||||
|
@ -196,11 +220,14 @@ function AI() {
|
||||||
AI.prototype = Object.create(Player.prototype);
|
AI.prototype = Object.create(Player.prototype);
|
||||||
AI.prototype.constructor = Player;
|
AI.prototype.constructor = Player;
|
||||||
AI.prototype.update = function () {
|
AI.prototype.update = function () {
|
||||||
var y = ball.pos.y - (this.height/2 * aiSpeedRandom);
|
var y = ball.pos.y - this.height/2;
|
||||||
var yConstrained = constrain(y, 0, SCREEN_WIDTH-this.height);
|
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);
|
this.pos = new Vector(this.pos.x, yConstrained);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/////////////////////////////// Scenes ////////////////////////////////////////
|
||||||
|
|
||||||
function net() {
|
function net() {
|
||||||
var dashSize = 5;
|
var dashSize = 5;
|
||||||
for (let y = dashSize/2; y < SCREEN_WIDTH; y += dashSize*2) {
|
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() {
|
function drawScores() {
|
||||||
let x1 = SCREEN_WIDTH/4-5;
|
let x1 = SCREEN_WIDTH/4-5;
|
||||||
let x2 = SCREEN_WIDTH*3/4-5;
|
let x2 = SCREEN_WIDTH*3/4-5;
|
||||||
|
@ -233,10 +254,80 @@ function drawScores() {
|
||||||
|
|
||||||
function drawGameOver() {
|
function drawGameOver() {
|
||||||
g.setFont("Vector", 20);
|
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) {
|
if (state === 1) {
|
||||||
ball.update();
|
ball.update();
|
||||||
player.update();
|
player.update();
|
||||||
|
@ -261,22 +352,73 @@ function draw() {
|
||||||
drawScores();
|
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.clear();
|
||||||
g.setColor(0);
|
g.setColor(0);
|
||||||
g.fillRect(0,0,240,240);
|
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 => {
|
||||||
setWatch(o => o.state ? player.down() : player.stop(), BTN3, {repeat: true, edge: 'both'});
|
if (state === 0) {
|
||||||
//setWatch(o => o.state ? player.down() : player.stop(), BTN5, {repeat: true, edge: 'both'});
|
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 => {
|
setWatch(o => {
|
||||||
state++;
|
state++;
|
||||||
|
clearInterval();
|
||||||
if (state >= 2) {
|
if (state >= 2) {
|
||||||
ball.restart();
|
|
||||||
g.setColor(0);
|
g.setColor(0);
|
||||||
g.fillRect(0, 0, 240, 240);
|
g.fillRect(0, 0, 240, 240);
|
||||||
|
ball.show(true);
|
||||||
scores = [0, 0];
|
scores = [0, 0];
|
||||||
|
playerBle = null;
|
||||||
|
ball = new Ball();
|
||||||
state = 1;
|
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'});
|
||||||
|
|
|
@ -20,3 +20,4 @@
|
||||||
0.16: Reduce memory usage further when running app settings page
|
0.16: Reduce memory usage further when running app settings page
|
||||||
0.17: Remove need for "settings" in appid.info
|
0.17: Remove need for "settings" in appid.info
|
||||||
0.18: Don't overwrite existing settings on app update
|
0.18: Don't overwrite existing settings on app update
|
||||||
|
0.19: Allow BLE HID settings, add 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
|
|
@ -61,6 +61,8 @@ const boolFormat = v => v ? "On" : "Off";
|
||||||
function showMainMenu() {
|
function showMainMenu() {
|
||||||
var beepV = [false, true, "vib"];
|
var beepV = [false, true, "vib"];
|
||||||
var beepN = ["Off", "Piezo", "Vibrate"];
|
var beepN = ["Off", "Piezo", "Vibrate"];
|
||||||
|
var hidV = [false, "kbmedia", "kb", "joy"];
|
||||||
|
var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"];
|
||||||
const mainmenu = {
|
const mainmenu = {
|
||||||
'': { 'title': 'Settings' },
|
'': { 'title': 'Settings' },
|
||||||
'Make Connectable': ()=>makeConnectable(),
|
'Make Connectable': ()=>makeConnectable(),
|
||||||
|
@ -115,10 +117,11 @@ function showMainMenu() {
|
||||||
'Locale': ()=>showLocaleMenu(),
|
'Locale': ()=>showLocaleMenu(),
|
||||||
'Select Clock': ()=>showClockMenu(),
|
'Select Clock': ()=>showClockMenu(),
|
||||||
'HID': {
|
'HID': {
|
||||||
value: settings.HID,
|
value: 0 | hidV.indexOf(settings.HID),
|
||||||
format: boolFormat,
|
min: 0, max: 3,
|
||||||
onchange: () => {
|
format: v => hidN[v],
|
||||||
settings.HID = !settings.HID;
|
onchange: v => {
|
||||||
|
settings.HID = hidV[v];
|
||||||
updateSettings();
|
updateSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue