Merge remote-tracking branch 'upstream/master' into develop
|
@ -26,3 +26,6 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
|
|||
* Added ability to specify dependencies (used for `notify` at the moment)
|
||||
* Fixed Promise-based bug in removeApp
|
||||
* Fixed bin/firmwaremaker and bin/apploader CLI to handle binary file uploads correctly
|
||||
* Added progress bar on Bangle.js for uploads
|
||||
* Provide a proper error message in case JSON decode fails
|
||||
* Check you're connecting with a Bangle.js of the correct version
|
||||
|
|
10
README.md
|
@ -219,7 +219,12 @@ and which gives information about the app for the Launcher.
|
|||
"shortName": "Short name", // short name for launcher
|
||||
"icon": "icon.png", // icon in apps/
|
||||
"description": "...", // long description (can contain markdown)
|
||||
"type":"...", // optional(if app) - 'app'/'widget'/'launch'/'bootloader'
|
||||
"type":"...", // optional(if app) -
|
||||
// 'app' - an application
|
||||
// 'widget' - a widget
|
||||
// 'launch' - replacement launcher app
|
||||
// 'bootloader' - code that runs at startup only
|
||||
// 'RAM' - code that runs and doesn't upload anything to storage
|
||||
"tags": "", // comma separated tag list for searching
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
|
||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||
|
@ -241,7 +246,8 @@ and which gives information about the app for the Launcher.
|
|||
// add an icon to allow your app to be tested
|
||||
|
||||
"storage": [ // list of files to add to storage
|
||||
{"name":"appid.js", // filename to use in storage
|
||||
{"name":"appid.js", // filename to use in storage.
|
||||
// If name=='RAM', the code is sent directly to Bangle.js and is not saved to a file
|
||||
"url":"", // URL of file to load (currently relative to apps/)
|
||||
"content":"..." // if supplied, this content is loaded directly
|
||||
"evaluate":true // if supplied, data isn't quoted into a String before upload
|
||||
|
|
300
apps.json
|
@ -1,11 +1,11 @@
|
|||
[
|
||||
{ "id": "boot",
|
||||
"name": "Bootloader",
|
||||
"icon": "bootloader.png",
|
||||
"version":"0.21",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"tags": "tool,system",
|
||||
"type":"bootloader",
|
||||
"icon": "bootloader.png",
|
||||
"version":"0.22",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"storage": [
|
||||
{"name":".boot0","url":"boot0.js"},
|
||||
{"name":".bootcde","url":"bootloader.js"}
|
||||
|
@ -80,7 +80,7 @@
|
|||
"name": "Notifications (default)",
|
||||
"shortName":"Notifications",
|
||||
"icon": "notify.png",
|
||||
"version":"0.03",
|
||||
"version":"0.05",
|
||||
"description": "A handler for displaying notifications that displays them in a bar at the top of the screen",
|
||||
"tags": "widget",
|
||||
"type": "notify",
|
||||
|
@ -93,7 +93,7 @@
|
|||
"name": "Fullscreen Notifications",
|
||||
"shortName":"Notifications",
|
||||
"icon": "notify.png",
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.",
|
||||
"tags": "widget",
|
||||
"type": "notify",
|
||||
|
@ -118,6 +118,24 @@
|
|||
{"name":"welcome.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "mywelcome",
|
||||
"name": "Customised Welcome",
|
||||
"shortName": "My Welcome",
|
||||
"icon": "app.png",
|
||||
"version":"0.11",
|
||||
"description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting",
|
||||
"tags": "start,welcome",
|
||||
"custom":"custom.html",
|
||||
"storage": [
|
||||
{"name":"mywelcome.boot.js","url":"boot.js"},
|
||||
{"name":"mywelcome.app.js","url":"app.js"},
|
||||
{"name":"mywelcome.settings.js","url":"settings.js"},
|
||||
{"name":"mywelcome.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"mywelcome.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "gbridge",
|
||||
"name": "Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
|
@ -152,7 +170,7 @@
|
|||
{ "id": "setting",
|
||||
"name": "Settings",
|
||||
"icon": "settings.png",
|
||||
"version":"0.21",
|
||||
"version":"0.22",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"tags": "tool,system",
|
||||
"readme": "README.md",
|
||||
|
@ -344,7 +362,7 @@
|
|||
{ "id": "gpsrec",
|
||||
"name": "GPS Recorder",
|
||||
"icon": "app.png",
|
||||
"version":"0.12",
|
||||
"version":"0.13",
|
||||
"interface": "interface.html",
|
||||
"description": "Application that allows you to record a GPS track. Can run in background",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
|
@ -429,6 +447,33 @@
|
|||
{"name": "weather.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "chargeanim",
|
||||
"name": "Charge Animation",
|
||||
"icon": "icon.png",
|
||||
"version":"0.01",
|
||||
"description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.",
|
||||
"tags": "battery",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
{"name":"chargeanim.app.js","url":"app.js"},
|
||||
{"name":"chargeanim.boot.js","url":"boot.js"},
|
||||
{"name":"chargeanim.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "bluetoothdock",
|
||||
"name": "Bluetooth Dock",
|
||||
"shortName":"Dock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
|
||||
"tags": "bluetooth",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bluetoothdock.app.js","url":"app.js"},
|
||||
{"name":"bluetoothdock.boot.js","url":"boot.js"},
|
||||
{"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "widbat",
|
||||
"name": "Battery Level Widget",
|
||||
"icon": "widget.png",
|
||||
|
@ -500,8 +545,8 @@
|
|||
{ "id": "hrm",
|
||||
"name": "Heart Rate Monitor",
|
||||
"icon": "heartrate.png",
|
||||
"version":"0.01",
|
||||
"description": "Measure your current heart rate",
|
||||
"version":"0.02",
|
||||
"description": "Measure your heart rate and see live sensor data",
|
||||
"tags": "health",
|
||||
"storage": [
|
||||
{"name":"hrm.app.js","url":"heartrate.js"},
|
||||
|
@ -692,6 +737,19 @@
|
|||
{"name":"sclock.img","url":"clock-simple-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "vibrclock",
|
||||
"name": "Vibrate Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "When BTN1 is pressed, vibrate out the time as a series of buzzes, one digit at a time. Hours, then Minutes. Zero is signified by one long buzz. Otherwise a simple digital clock.",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
{"name":"vibrclock.app.js","url":"app.js"},
|
||||
{"name":"vibrclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "svclock",
|
||||
"name": "Simple V-Clock",
|
||||
"icon": "vclock-simple.png",
|
||||
|
@ -873,7 +931,7 @@
|
|||
"id": "gpsinfo",
|
||||
"name": "GPS Info",
|
||||
"icon": "gps-info.png",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
||||
"tags": "gps",
|
||||
"type": "app",
|
||||
|
@ -882,6 +940,16 @@
|
|||
{"name":"gpsinfo.img","url": "gps-info-icon.js","evaluate": true}
|
||||
]
|
||||
},
|
||||
{ "id": "assistedgps",
|
||||
"name": "Assisted GPS Update",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Downloads assisted GPS data to Bangle.js for faster GPS startup and more accurate fixes",
|
||||
"custom": "custom.html",
|
||||
"tags": "tool,outdoors",
|
||||
"type": "RAM",
|
||||
"storage": [ ]
|
||||
},
|
||||
{
|
||||
"id": "pomodo",
|
||||
"name":"Pomodoro",
|
||||
|
@ -1219,7 +1287,7 @@
|
|||
"id": "rpgdice",
|
||||
"name": "RPG dice",
|
||||
"icon": "rpgdice.png",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Simple RPG dice rolling app.",
|
||||
"tags": "game,fun",
|
||||
"type": "app",
|
||||
|
@ -1382,7 +1450,7 @@
|
|||
"name": "BLE Detector",
|
||||
"shortName":"BLE Detector",
|
||||
"icon": "bledetect.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Detect BLE devices and show some informations.",
|
||||
"tags": "app,bluetooth,tool",
|
||||
"readme": "README.md",
|
||||
|
@ -1560,6 +1628,21 @@
|
|||
{"name":"hidcam.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "swlclk",
|
||||
"name": "SWL Clock / Short Wave Listner Clock",
|
||||
"shortName": "SWL Clock",
|
||||
"icon": "swlclk.png",
|
||||
"version":"0.01",
|
||||
"description": "Display Local, UTC time and some programs on the shorts waves along the day, with the frequencies",
|
||||
"tags": "tool,clock",
|
||||
"type":"clock",
|
||||
"readme": "README.md",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
{"name":"swlclk.app.js","url":"app.js"},
|
||||
{"name":"swlclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rclock",
|
||||
"name": "Round clock with seconds, minutes and date",
|
||||
|
@ -1667,7 +1750,7 @@
|
|||
"id": "largeclock",
|
||||
"name": "Large Clock",
|
||||
"icon": "largeclock.png",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "A readable and informational digital watch, with date, seconds and moon phase",
|
||||
"readme": "README.md",
|
||||
"tags": "clock",
|
||||
|
@ -1714,7 +1797,7 @@
|
|||
"name": "Xiaomi Plant Sensor",
|
||||
"shortName":"Mi Plant",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Reads and displays data from Xiaomi bluetooth plant moisture sensors",
|
||||
"tags": "xiaomi,mi,plant,ble,bluetooth",
|
||||
"storage": [
|
||||
|
@ -1726,7 +1809,7 @@
|
|||
"id": "simpletimer",
|
||||
"name": "Timer",
|
||||
"icon": "app.png",
|
||||
"version": "0.05",
|
||||
"version": "0.07",
|
||||
"description": "Simple timer, useful when playing board games or cooking",
|
||||
"tags": "timer",
|
||||
"readme": "README.md",
|
||||
|
@ -1776,8 +1859,8 @@
|
|||
"name": "Find Phone",
|
||||
"shortName":"Find Phone",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳",
|
||||
"version":"0.02",
|
||||
"description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.",
|
||||
"tags": "tool,android",
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
|
@ -1948,8 +2031,8 @@
|
|||
"name": "Vertical watch face",
|
||||
"shortName":"Vertical Face",
|
||||
"icon": "app.png",
|
||||
"version":"0.05",
|
||||
"description": "A simple vertical watch face with the date.",
|
||||
"version":"0.07",
|
||||
"description": "A simple vertical watch face with the date. Heart rate monitor is toggled with BTN1",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
|
@ -2099,7 +2182,7 @@
|
|||
"name": "Acceleration Recorder",
|
||||
"shortName":"Accel Rec",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"interface": "interface.html",
|
||||
"description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.",
|
||||
"tags": "",
|
||||
|
@ -2165,7 +2248,7 @@
|
|||
{"id": "counter",
|
||||
"name": "Counter",
|
||||
"icon": "counter_icon.png",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Simple counter",
|
||||
"tags": "tool",
|
||||
"allow_emulator": true,
|
||||
|
@ -2217,6 +2300,19 @@
|
|||
{"name":"cscsensor.img","url":"cscsensor-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "fileman",
|
||||
"name": "File manager",
|
||||
"shortName":"FileManager",
|
||||
"icon": "icons8-filing-cabinet-48.png",
|
||||
"version":"0.01",
|
||||
"description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files",
|
||||
"tags": "tools",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"fileman.app.js","url":"fileman.app.js"},
|
||||
{"name":"fileman.img","url":"fileman-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "worldclock",
|
||||
"name": "World Clock - 4 time zones",
|
||||
"shortName":"World Clock",
|
||||
|
@ -2232,5 +2328,165 @@
|
|||
{"name":"worldclock.settings.json"},
|
||||
{"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "id": "digiclock",
|
||||
"name": "Digital Clock Face",
|
||||
"shortName":"Digi Clock",
|
||||
"icon": "digiclock.png",
|
||||
"version":"0.01",
|
||||
"description": "A simple digital clock with the time, day, month, and year",
|
||||
"tags": "clock",
|
||||
"type" : "clock",
|
||||
"storage": [
|
||||
{"name":"digiclock.app.js","url":"digiclock.js"},
|
||||
{"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "dsdrelay",
|
||||
"name": "DSD BLE Relay controller",
|
||||
"shortName":"DSDRelay",
|
||||
"icon": "icons8-relay-48.png",
|
||||
"version":"0.01",
|
||||
"description": "Control BLE relay board from the watch",
|
||||
"tags": "ble,bluetooth",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"dsdrelay.app.js","url":"dsdrelay.app.js"},
|
||||
{"name":"dsdrelay.img","url":"dsdrelay-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "mandel",
|
||||
"name": "Mandelbrot",
|
||||
"shortName":"Mandel",
|
||||
"icon": "mandel.png",
|
||||
"version":"0.01",
|
||||
"description": "Draw a zoomable Mandelbrot set",
|
||||
"tags": "game",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"mandel.app.js","url":"mandel.min.js"},
|
||||
{"name":"mandel.img","url":"mandel-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "petrock",
|
||||
"name": "Pet rock",
|
||||
"icon": "petrock.png",
|
||||
"version": "0.02",
|
||||
"description": "A virtual pet rock with wobbly eyes",
|
||||
"tags": "game",
|
||||
"type": "app",
|
||||
"storage": [
|
||||
{"name": "petrock.app.js", "url": "app.js"},
|
||||
{"name": "petrock.img", "url": "app-icon.js", "evaluate": true}
|
||||
]
|
||||
},
|
||||
{ "id": "smartibot",
|
||||
"name": "Smartibot controller",
|
||||
"shortName":"Smartibot",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Control a [Smartibot Robot](https://thecraftyrobot.net/) straight from your Bangle.js",
|
||||
"tags": "",
|
||||
"storage": [
|
||||
{"name":"smartibot.app.js","url":"app.js"},
|
||||
{"name":"smartibot.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "widncr",
|
||||
"name": "NCR Logo Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.01",
|
||||
"description": "Show the NodeConf Remote logo in the top left",
|
||||
"tags": "widget",
|
||||
"type":"widget",
|
||||
"storage": [
|
||||
{"name":"widncr.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "ncrclk",
|
||||
"name": "NCR Clock",
|
||||
"shortName":"NCR Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "NodeConf Remote clock",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"storage": [
|
||||
{"name":"ncrclk.app.js","url":"app.js"},
|
||||
{"name":"ncrclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "isoclock",
|
||||
"name": "ISO Compliant Clock Face",
|
||||
"shortName":"ISO Clock",
|
||||
"icon": "isoclock.png",
|
||||
"version":"0.01",
|
||||
"description": "Tweaked fork of digiclock for ISO date and time",
|
||||
"tags": "clock",
|
||||
"type" : "clock",
|
||||
"storage": [
|
||||
{"name":"isoclock.app.js","url":"isoclock.js"},
|
||||
{"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "gpstimeserver",
|
||||
"name": "GPS Time Server",
|
||||
"icon": "widget.png",
|
||||
"version":"0.01",
|
||||
"description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.",
|
||||
"tags": "widget",
|
||||
"type": "widget",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gpstimeserver.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "tilthydro",
|
||||
"name": "Tilt Hydrometer Display",
|
||||
"shortName":"Tilt Hydro",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "A display for the [Tilt Hydrometer](https://tilthydrometer.com/) - [more info here](http://www.espruino.com/Tilt+Hydrometer+Display)",
|
||||
"tags": "tools,bluetooth",
|
||||
"storage": [
|
||||
{"name":"tilthydro.app.js","url":"app.js"},
|
||||
{"name":"tilthydro.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "supmariodark",
|
||||
"name": "Super mario clock night mode",
|
||||
"shortName":"supmariodark",
|
||||
"icon": "supmariodark.png",
|
||||
"version":"0.01",
|
||||
"description": "Super mario clock in night mode",
|
||||
"tags": "clock",
|
||||
"type" : "clock",
|
||||
"storage": [
|
||||
{"name":"supmariodark.app.js","url":"supmariodark.js"},
|
||||
{"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true},
|
||||
{"name":"supmario30x24.bin","url":"supmario30x24.bin.js"},
|
||||
{"name":"supmario30x24.wdt","url":"supmario30x24.wdt.js"},
|
||||
{"name":"banner-up.img","url":"banner-up.js","evaluate":true},
|
||||
{"name":"banner-down.img","url":"banner-down.js","evaluate":true},
|
||||
{"name":"brick2.img","url":"brick2.js","evaluate":true},
|
||||
{"name":"enemy.img","url":"enemy.js","evaluate":true},
|
||||
{"name":"flower.img","url":"flower.js","evaluate":true},
|
||||
{"name":"flower_b.img","url":"flower_b.js","evaluate":true},
|
||||
{"name":"mario_wh.img","url":"mario_wh.js","evaluate":true},
|
||||
{"name":"pipe.img","url":"pipe.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "gmeter",
|
||||
"name": "G-Meter",
|
||||
"shortName":"G-Meter",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Simple G-Meter",
|
||||
"tags": "",
|
||||
"storage": [
|
||||
{"name":"gmeter.app.js","url":"app.js"},
|
||||
{"name":"gmeter.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"warn",
|
||||
"off",
|
||||
2,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Increase record time to 5 second
|
||||
Calculate the time moving in graph display
|
||||
Trigger on 1.04g now, and record 10 samples before trigger
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
var acc;
|
||||
var HZ = 100;
|
||||
var SAMPLES = 2*HZ; // 2 seconds
|
||||
var SAMPLES = 5*HZ; // 5 seconds
|
||||
var SCALE = 5000;
|
||||
var THRESH = 1.01;
|
||||
var THRESH = 1.04;
|
||||
var accelx = new Int16Array(SAMPLES);
|
||||
var accely = new Int16Array(SAMPLES); // North
|
||||
var accelz = new Int16Array(SAMPLES); // Into clock face
|
||||
var accelIdx = 0;
|
||||
var lastAccel = undefined;
|
||||
var lastAccel;
|
||||
function accelHandlerTrigger(a) {"ram"
|
||||
if (a.mag*2>THRESH) { // *2 because 8g mode
|
||||
tStart = getTime();
|
||||
g.drawString("Recording",g.getWidth()/2,g.getHeight()/2,1);
|
||||
Bangle.removeListener('accel',accelHandlerTrigger);
|
||||
Bangle.on('accel',accelHandlerRecord);
|
||||
if (lastAccel) accelHandlerRecord(lastAccel);
|
||||
lastAccel.forEach(accelHandlerRecord);
|
||||
accelHandlerRecord(a);
|
||||
} else {
|
||||
if (lastAccel.length>10) lastAccel.shift();
|
||||
lastAccel.push(a);
|
||||
}
|
||||
lastAccel = a;
|
||||
}
|
||||
function accelHandlerRecord(a) {"ram"
|
||||
var i = accelIdx++;
|
||||
|
@ -29,7 +31,8 @@ function accelHandlerRecord(a) {"ram"
|
|||
function recordStart() {"ram"
|
||||
Bangle.setLCDTimeout(0); // force LCD on
|
||||
accelIdx = 0;
|
||||
lastAccel = undefined;
|
||||
lastAccel = [];
|
||||
Bangle.accelWr(0x18,0b01110100); // off, +-8g
|
||||
Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter
|
||||
Bangle.accelWr(0x18,0b11110100); // +-8g
|
||||
Bangle.setPollInterval(10); // 100hz input
|
||||
|
@ -42,8 +45,9 @@ function recordStart() {"ram"
|
|||
|
||||
|
||||
function recordStop() {"ram"
|
||||
console.log("Length:",getTime()-tStart);
|
||||
//console.log("Length:",getTime()-tStart);
|
||||
Bangle.setPollInterval(80); // default poll interval
|
||||
Bangle.accelWr(0x18,0b01101100); // off, +-4g
|
||||
Bangle.accelWr(0x1B,0x0); // default 12.5hz output
|
||||
Bangle.accelWr(0x18,0b11101100); // +-4g
|
||||
Bangle.removeListener('accel',accelHandlerRecord);
|
||||
|
@ -76,9 +80,14 @@ function showData() {
|
|||
|
||||
// work out stats
|
||||
var maxAccel = 0;
|
||||
var tStart = SAMPLES, tEnd = 0;
|
||||
var vel = 0, maxVel = 0;
|
||||
for (var i=0;i<SAMPLES;i++) {
|
||||
var a = accely[i]/SCALE;
|
||||
if (a>0.1) {
|
||||
if (i<tStart) tStart=i;
|
||||
if (i>tEnd) tEnd=i;
|
||||
}
|
||||
if (a>maxAccel) maxAccel=a;
|
||||
vel += a/HZ;
|
||||
if (vel>maxVel) maxVel=vel;
|
||||
|
@ -87,6 +96,7 @@ function showData() {
|
|||
g.setFont("6x8").setFontAlign(1,0);
|
||||
g.drawString("Max Y Accel: "+maxAccel.toFixed(2)+" g",g.getWidth()-14,g.getHeight()-50);
|
||||
g.drawString("Max Y Vel: "+maxVel.toFixed(2)+" m/s",g.getWidth()-14,g.getHeight()-40);
|
||||
g.drawString("Time moving: "+(tEnd-tStart)/HZ+" s",g.getWidth()-14,g.getHeight()-30);
|
||||
//console.log("End Velocity "+vel);
|
||||
g.setFont("6x8").setFontAlign(0,0,1);
|
||||
g.drawString("FINISH",g.getWidth()-4,g.getHeight()/2);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,113 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Assisted GPS</h2>
|
||||
<p>GPS can take a long time (~5 minutes) to get an accurate position the first time it is used.
|
||||
AGPS uploads a few hints to the GPS receiver about satellite positions that allow it
|
||||
to get a faster, more accurate fix - however they are only valid for a short period of time.</p>
|
||||
<p>You can upload data that covers a longer period of time, but the upload will take longer.</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AGPS Validity time</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1d" checked><i class="form-icon"></i> 1 day (8kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="2d"><i class="form-icon"></i> 2 days (14kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="3d"><i class="form-icon"></i> 3 days (20kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1wk"><i class="form-icon"></i> 1 week (46kB)
|
||||
</label>
|
||||
</div>
|
||||
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
var radios = document.getElementsByName('agpsperiod');
|
||||
var url = "https://www.espruino.com/agps/assistnow_1d.base64";
|
||||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
url = "https://www.espruino.com/agps/assistnow_"+radios[i].value+".base64";
|
||||
console.log("Sending...");
|
||||
//var text = document.getElementById("agpsperiod").value;
|
||||
get(url, function(b64) {
|
||||
var js = jsFromBase64(b64);
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"RAM", content:js},
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function UBX_CMD(cmd) {
|
||||
var d = [0xB5,0x62]; // sync chars
|
||||
d = d.concat(cmd);
|
||||
var a=0,b=0;
|
||||
for (var i=2;i<d.length;i++) {
|
||||
a += d[i];
|
||||
b += a;
|
||||
}
|
||||
d.push(a&255,b&255);
|
||||
return d;
|
||||
}
|
||||
function UBX_MGA_INI_TIME_UTC() {
|
||||
var a = new Uint8Array(4+24);
|
||||
a.set([0x13,0x40,24,0]);
|
||||
a.set([ 0x10, // 0: type
|
||||
0, // 1: version
|
||||
0, // 2: ref - none
|
||||
0x80] ); // 3: leapsecs - unknown
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime()+d.getTimezoneOffset()*60000); // get as UTC
|
||||
var dv = new DataView(a.buffer, 4);
|
||||
dv.setUint16(4, d.getFullYear());
|
||||
dv.setUint8(6, d.getMonth()+1);
|
||||
dv.setUint8(7, d.getDate());
|
||||
dv.setUint8(8, d.getHours());
|
||||
dv.setUint8(9, d.getMinutes());
|
||||
dv.setUint8(10, d.getSeconds());
|
||||
dv.setUint16(16, 10*60); // seconds part of accuracy - 10 minutes
|
||||
return UBX_CMD([].slice.call(a));
|
||||
}
|
||||
|
||||
function jsFromBase64(b64) {
|
||||
var bin = atob(b64);
|
||||
var chunkSize = 128;
|
||||
var js = "\x10Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
||||
js += "\x10E.showMessage('Uploading...','AGPS');function p(n) {g.fillRect(0,g.getHeight()-80,g.getWidth()*n,g.getHeight()-60);}";
|
||||
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
||||
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
|
||||
|
||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
||||
var chunk = bin.substr(i,chunkSize);
|
||||
js += `\x10p(${Math.round(100*i/bin.length)/100});Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||
}
|
||||
js += "\x10p(1);\n"; // finish progress bar
|
||||
return js;
|
||||
}
|
||||
|
||||
function get(url,callback) {
|
||||
console.log("Loading "+url);
|
||||
var oReq = new XMLHttpRequest();
|
||||
oReq.addEventListener("load", function() {
|
||||
var b64 = this.responseText;
|
||||
console.log("Response received...");
|
||||
callback(b64);
|
||||
});
|
||||
oReq.open("GET", url);
|
||||
oReq.send();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -59,7 +59,7 @@ function hitMe() {
|
|||
if(playerWeight == 21)
|
||||
EndGameMessdage('WINNER');
|
||||
else if(playerWeight > 21)
|
||||
EndGameMessdage('LOOSER');
|
||||
EndGameMessdage('LOSER');
|
||||
}
|
||||
|
||||
function calcWeight(hand, hideCard) {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Fixed issue with wrong device informations
|
||||
0.03: Ensure manufacturer:undefined doesn't overflow screen
|
||||
|
|
|
@ -18,7 +18,7 @@ function showDeviceInfo(device){
|
|||
value: device.rssi
|
||||
},
|
||||
"manufacturer": {
|
||||
value: device.manufacturer
|
||||
value: device.manufacturer===undefined ? "-" : device.manufacturer
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,35 @@
|
|||
# Charging Dock
|
||||
|
||||
When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen.
|
||||
|
||||
Rotates by 90 degrees if it detects it is sideways, allowing for use
|
||||
in a Charging Dock.
|
||||
|
||||
When devices are out of range (eg low water level in a plant) they are
|
||||
highlighted red.
|
||||
|
||||
Currently supported devices:
|
||||
|
||||
* Mi Flora/other Xiaomi
|
||||
* Bluetooth 0x1809 (eg. [Espruino Apps](https://espruino.github.io/EspruinoApps/#bletemp))
|
||||
* Espruino Manufacturer Data (0x0590)
|
||||
|
||||
In the future it'd be nice to support more types of device in the future!
|
||||
|
||||
## Espruino Devices
|
||||
|
||||
To use your own Espruino device, use code like the following:
|
||||
|
||||
```
|
||||
var data = {a:1,t:E.getTemperature()};
|
||||
NRF.setAdvertising({},{
|
||||
showName:false,
|
||||
manufacturer:0x0590,
|
||||
manufacturerData:JSON.stringify(data)
|
||||
});
|
||||
```
|
||||
|
||||
Currently:
|
||||
|
||||
* `t` is the temperature (if defined)
|
||||
* `t` is the alert status (1 or 0)
|
|
@ -0,0 +1,15 @@
|
|||
// Create an entry in apps.json as follows:
|
||||
{ "id": "bluetoothdock",
|
||||
"name": "Bluetooth Dock",
|
||||
"shortName":"Dock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
|
||||
"tags": "bluetooth",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bluetoothdock.app.js","url":"app.js"},
|
||||
{"name":"bluetoothdock.boot.js","url":"boot.js"},
|
||||
{"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwggNK93gEikO93uC6gWBF6ECkQuVkUikAuVAAIuVAAIuGGZgXDlwuDhWkpWqAARHLkQpChWql3kC4YYHmQXDSQWq0Xu8QXE0AWEgYWESQIuC90qlQwJFwoABFwnyGBBdEC4guC1X/GBAXIVYJdC/4wFUw4XFFYX/GApIDC5BJBC4YwEC6QwEC5pHD+YwE0IXMGAX//U/GAgXNU4X60YwEU5YABnQXC0RhEFxkv+YXCl5iBF4gXKLQM6IgIuBGoIXCIxOqlRaBRoIABFwYXBUheqGAIACFwYXKBoYwBFwwXGVoQuDGAguEC4MzCwUQC4UKBwmvFw2qgczmUikAWCC4OikUzAAQvH+YXCCwcAmQVDC4YwFBIIVEgA3BAALADR48zmAWEh4VBPAS/DAIQXKJwIlDd4f6AgQXIIoSPCFwWqC4IFDL4YAFmAXCFIYXB0RhBKQRvDAAa/Dl4oCC4Mv//ya4gWFC4eiLAQUBFwgXBAA8Bc4qnCFwehC5EAC5AuD0AXRFwYXTFweqwAXJPAQXDFwh2JC5AuE0QXKJAouFLxQwGFwhGLPJAuPMI4uQDBAKD"))
|
|
@ -0,0 +1,182 @@
|
|||
var deviceInfo = {};
|
||||
if (Bangle.getAccel().x < -0.7)
|
||||
g.setRotation(3); // assume watch in charge cradle
|
||||
// Tile sizes
|
||||
var TILESIZE = 60;
|
||||
// Tiles along width of screen
|
||||
var TILEX = 4;
|
||||
|
||||
// Map devices to nice names...
|
||||
var deviceNames = {
|
||||
"eb:44:c1:71:2e:89 random" : "Office",
|
||||
"c4:7c:8d:6a:ac:79 public" : "Peacelily"
|
||||
};
|
||||
|
||||
var scanHandlers = [
|
||||
{ filter : {serviceData:{"fe95":{}}}, // Xiaomi
|
||||
handler : function(device) {
|
||||
if (!device.serviceData["fe95"]) return;
|
||||
var d = new DataView(device.serviceData["fe95"]);
|
||||
var frame = d.getUint16(0,true);
|
||||
var offset = 5;
|
||||
if (frame&16) offset+=6; // mac address
|
||||
if (frame&32) offset+=1; // capabilitities
|
||||
if (frame&64) { // event
|
||||
var l = d.getUint8(offset+2);
|
||||
var code = d.getUint16(offset,true);
|
||||
if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id};
|
||||
event = deviceInfo[device.id];
|
||||
switch (code) {
|
||||
case 0x1004: event.temperature = d.getInt16(offset+3,true)/10; break;
|
||||
case 0x1006: event.humidity = d.getInt16(offset+3)/10; break;
|
||||
case 0x100D:
|
||||
event.temperature = d.getInt16(offset+3,true)/10;
|
||||
event.humidity = d.getInt16(offset+5)/10; break;
|
||||
case 0x1008: event.moisture = d.getUint8(offset+3); break;
|
||||
case 0x1009: event.fertility = d.getUint16(offset+3,true)/10; break;
|
||||
// case 0x1007: break; // 3 bytes? got 84,0,0 or 68,0,0
|
||||
default: event.code = code;
|
||||
event.raw = new Uint8Array(d.buffer, offset+3, l);
|
||||
break;
|
||||
}
|
||||
}}}, {
|
||||
filter : {serviceData:{"1809":{}}}, // Standard Bluetooth
|
||||
handler : function(device) {
|
||||
if (!device.serviceData["1809"]) return;
|
||||
var d = new DataView(device.serviceData["1809"]);
|
||||
if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id,name:device.name};
|
||||
event = deviceInfo[device.id];
|
||||
event.temperature = d.getInt16(0,1)/100;
|
||||
}}, {
|
||||
filter : { manufacturerData:{0x0590:{}} }, // Espruino
|
||||
handler : function(device) {
|
||||
if (!device.manufacturerData) return;
|
||||
var j;
|
||||
try { j = JSON.parse(E.toString(device.manufacturerData)); }
|
||||
catch (e) { return; } // not JSON
|
||||
if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id,name:device.name};
|
||||
event = deviceInfo[device.id];
|
||||
if (j.t) event.temperature = j.t;
|
||||
if (j.a) event.alert = j.a;
|
||||
}}
|
||||
];
|
||||
|
||||
function getImgHum() {
|
||||
return require("heatshrink").decompress(atob("jUoxH+AEtlsoYYDS4ZYDAYaVDLAYFDSQYHDSIZYDBIaPDLAYLDRoZYDBoaLDLAYPDRIZYDCIaHDLAYTDQoZYDCoaDDOQYXAA+JxIYX1utDSwYBAAIzYGiwZUTgpODQpzPGGgY3OdI4aRDIIaMDJIYCDIztDGRwaJP5oaWDAwaRDBAbOC5YcKB5I="));
|
||||
}
|
||||
function getImgTemp() {
|
||||
return require("heatshrink").decompress(atob("iUqxH+AA2sAAQLHCBASMCAoSLCPOBAAQRfI/5Hn3YACy4ACCL4ADCL5H/I/AQHCRAQJCQwQLCQgQNCQYRQCB4A/ADaPjYqTpSCRYQGCZALFA"));
|
||||
}
|
||||
|
||||
function drawAlert(tile,x,y) {
|
||||
g.setFont("Vector",56).setFontAlign(0,0);
|
||||
g.drawString("!",x+TILESIZE/2,y+10+TILESIZE/2);
|
||||
}
|
||||
|
||||
function drawMoisture(tile,x,y) {
|
||||
g.drawImage(getImgHum(),x+2,y+18);
|
||||
g.setFont("Vector",28);
|
||||
g.drawString(tile.device.moisture,x+26,y+12);
|
||||
}
|
||||
|
||||
function drawTemperature(tile,x,y) {
|
||||
g.drawImage(getImgTemp(),x+3,y+16);
|
||||
g.setFont("Vector",30);
|
||||
var t = Math.round(tile.device.temperature);
|
||||
g.drawString(t,x+25,y+13);
|
||||
}
|
||||
|
||||
function getTiles() {
|
||||
var tiles = [];
|
||||
Object.keys(deviceInfo).forEach(id=>{
|
||||
var dev = deviceInfo[id];
|
||||
if (dev.alert) {
|
||||
tiles.push({
|
||||
alert: true, device: dev,
|
||||
draw: drawAlert
|
||||
});
|
||||
}
|
||||
if (dev.moisture && dev.moisture<40) {
|
||||
tiles.push({
|
||||
alert: true, device: dev,
|
||||
draw: drawMoisture
|
||||
});
|
||||
}
|
||||
if (dev.temperature) {
|
||||
tiles.push({
|
||||
device: dev,
|
||||
draw: drawTemperature
|
||||
});
|
||||
}
|
||||
});
|
||||
tiles.sort((a,b)=>(b.alert|0)-(a.alert|0))
|
||||
return tiles;
|
||||
}
|
||||
|
||||
|
||||
g.clear();
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
function drawClock() {
|
||||
var d = new Date();
|
||||
var size = 3;
|
||||
var x = (g.getWidth()/2) - size*6,
|
||||
y = size;
|
||||
g.reset();
|
||||
g.setFont("7x11Numeric7Seg",size).setFontAlign(1,-1);
|
||||
g.drawString(d.getHours(), x, y, true);
|
||||
g.setFontAlign(-1,-1);
|
||||
if (d.getSeconds()&1) g.drawString(":", x,y);
|
||||
g.drawString(("0"+d.getMinutes()).substr(-2),x+size*4,y, true);
|
||||
// draw seconds
|
||||
g.setFont("7x11Numeric7Seg",size/2);
|
||||
g.drawString(("0"+d.getSeconds()).substr(-2),x+size*18,y + size*7, true);
|
||||
// date
|
||||
var s = d.toString().split(" ").slice(0,4).join(" ");
|
||||
g.reset().setFontAlign(0,-1);
|
||||
g.drawString(s,g.getWidth()/2, y + size*12, true);
|
||||
// keep screen on
|
||||
g.flip();
|
||||
}
|
||||
function drawTiles() {
|
||||
// draw tiles
|
||||
var tiles = getTiles();
|
||||
for (var i=0;i<6;i++) {
|
||||
var x = (i%TILEX)*TILESIZE;
|
||||
var y = TILESIZE + TILESIZE*((i/TILEX)|0);
|
||||
g.reset();
|
||||
var tile = tiles[i];
|
||||
if (tile && tile.alert) {
|
||||
g.setBgColor(0.5,0,0);
|
||||
}
|
||||
g.clearRect(x,y,x+TILESIZE-1,y+TILESIZE-1);
|
||||
if (tile) {
|
||||
g.reset().setFont("6x8");
|
||||
var t = deviceNames[tile.device.id];
|
||||
if (!t) t = tile.device.name || tile.device.id.substr(0,17);
|
||||
g.drawString(t,x+2,y+2);
|
||||
tile.draw(tile, x, y);
|
||||
if (tile.alert) {
|
||||
g.setColor(1,1,0);
|
||||
g.drawRect(x,y,x+TILESIZE-1,y+TILESIZE-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
g.flip(); // keep forcing display on
|
||||
}
|
||||
|
||||
setInterval(drawClock, 1000);
|
||||
setInterval(drawTiles, 10000);
|
||||
drawClock();
|
||||
drawTiles();
|
||||
|
||||
function parseDevice(dev) {
|
||||
if (!dev.serviceData) dev.serviceData={};
|
||||
scanHandlers.forEach(s=>s.handler(dev));
|
||||
}
|
||||
NRF.setScan(parseDevice, { filters: scanHandlers.map(s=>s.filter), timeout: 2000 });
|
||||
|
||||
if (Bangle.isCharging()) {
|
||||
Bangle.on("charging", isCharging => {
|
||||
if (!isCharging) load();
|
||||
});
|
||||
}
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1 @@
|
|||
Bangle.on("charging", isCharging => { if (isCharging) load("bluetoothdock.app.js"); });
|
|
@ -20,3 +20,4 @@
|
|||
0.19: Tweaks to simplify code and lower memory usage
|
||||
0.20: Allow Gadgetbridge to work even with programmable:off
|
||||
0.21: Handle echo off char from Gadgetbridge app when programmable:off (fix #558)
|
||||
0.22: Stop LCD timeout being disabled on first run (when there is no settings.json)
|
||||
|
|
|
@ -50,7 +50,7 @@ if (!Bangle.F_BEEPSET) {
|
|||
});
|
||||
};
|
||||
}
|
||||
Bangle.setLCDTimeout(s.timeout);
|
||||
if (s.timeout!==undefined) Bangle.setLCDTimeout(s.timeout);
|
||||
if (!s.timeout) Bangle.setLCDPower(1);
|
||||
E.setTimeZone(s.timezone);
|
||||
delete s;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4ASwoAIF/4pZABYuuGDIv/F/4v/F/4vt1Ivt6Zft1Ivu6fNF9nT6a+JF8SNBF9ouBXxQvhFwQvrRoTuLF8BeDXxQvfFwYAFGgwvdRoYAGd840GX84AC5rCLF8ReLF8wMJF8K+CRpAvmNhQvhdwIuKF8SNLF8guLF8JdML8Yv/F/4v/F/4v/GDguYAH4A/AGYA=="))
|
|
@ -0,0 +1,28 @@
|
|||
g.clear().flip();
|
||||
var imgbat = require("heatshrink").decompress(atob("nlWhH+AH4A/AH4AHwoAQHXQ8pHf47rF6YAXHXQ8OHVo8NHf47/Hf47/Hf47/Hf47/Hf47/Hf47r1I766Y756Z351I766ayTHco6BHfCxBHfI6CdyY7jHQQ73WIayUHcQ6DHew6EHeqxEdyo7gOwo70HQqyVHbyxFHeo6GHeY6Hdyo7cWI47zHQ6yWHbY6IHeKxIABa9MHbI6TQJo7YHUI7YWMKzbQKQYOHdYYPHcK9IWJw7sDKA7hHTA7pWKA7qDKQ7gdwwaTHcyxSHcR2ZHcwZUHcqxUHcLuEHSo7kHSw7gWLI7kHS47iHTA7fdwKxYHcQ6ZHb46bO8A76ADg7/Hf47/Hf47/Hf47/Hf47/Hf47/HbY8uHRg8tHRwA/AH4AsA=="));
|
||||
var imgbubble = require("heatshrink").decompress(atob("ikQhH+AAc0AAgKEAAwRFCpgMDnVerwULCIuCCYoUGCQQQBnQ9MA4Q3GChI5DEpATIJYISKCY46LCYwANCa4UObJ7INeCoSOCpAOI"));
|
||||
|
||||
var W=240,H=240;
|
||||
var bubbles = [];
|
||||
for (var i=0;i<10;i++) {
|
||||
bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()});
|
||||
}
|
||||
|
||||
function anim() {
|
||||
/* we don't use any kind of buffering here. Just draw one image
|
||||
at a time (image contains a background) too, and there is minimal
|
||||
flicker. */
|
||||
var mx = 120, my = 120;
|
||||
bubbles.forEach(f=>{
|
||||
f.y-=f.v;if (f.y<-24) f.y=H+8;
|
||||
g.drawImage(imgbubble,f.y,f.x,{scale:f.s});
|
||||
});
|
||||
g.drawImage(imgbat, mx,my,{rotate:Math.sin(getTime()*2)*0.5-Math.PI/2});
|
||||
g.flip();
|
||||
}
|
||||
|
||||
setInterval(anim,20);
|
||||
|
||||
Bangle.on("charging", isCharging => {
|
||||
if (!isCharging) load();
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
Bangle.on("charging", isCharging => { if (isCharging) load("chargeanim.app.js"); });
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Added decrement and touch functions
|
||||
|
|
|
@ -6,22 +6,41 @@ function updateScreen() {
|
|||
g.clearRect(0, 50, 250, 150);
|
||||
g.setFont("Vector",40).setFontAlign(0,0);
|
||||
g.drawString(Math.floor(counter), g.getWidth()/2, 100);
|
||||
g.drawString('-', 45, 100);
|
||||
g.drawString('+', 185, 100);
|
||||
}
|
||||
|
||||
|
||||
// add a count by using BTN1
|
||||
// add a count by using BTN1 or BTN5
|
||||
setWatch(() => {
|
||||
counter += 1;
|
||||
updateScreen();
|
||||
}, BTN1, {repeat:true});
|
||||
|
||||
setWatch(() => {
|
||||
counter = 0;
|
||||
counter += 1;
|
||||
updateScreen();
|
||||
}, BTN5, {repeat:true});
|
||||
|
||||
// subtract a count by using BTN3 or BTN4
|
||||
setWatch(() => {
|
||||
counter -= 1;
|
||||
updateScreen();
|
||||
}, BTN4, {repeat:true});
|
||||
|
||||
setWatch(() => {
|
||||
counter -= 1;
|
||||
updateScreen();
|
||||
}, BTN3, {repeat:true});
|
||||
|
||||
// reset by using BTN2
|
||||
setWatch(() => {
|
||||
counter = 0;
|
||||
updateScreen();
|
||||
}, BTN2, {repeat:true});
|
||||
|
||||
g.clear(1).setFont("6x8");
|
||||
g.drawString('Use BTN1 to increase\nthe counter by one.\nUse BTN3 to reset counter.', 25, 200);
|
||||
g.drawString('Tap right or BTN1 to increase\nTap left or BTN3 to decrease\nPress BTN2 to reset.', 25, 200);
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
|
|
@ -9,8 +9,10 @@ Currently the app displays the following data:
|
|||
- maximum speed
|
||||
- trip distance traveled
|
||||
- total distance traveled
|
||||
- an icon with the battery status of the remote sensor
|
||||
|
||||
Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app.
|
||||
If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor.
|
||||
|
||||
I do not have access to a cadence sensor at the moment, so only the speed part is currently implemented. Values displayed are imperial or metric (depending on locale),
|
||||
the wheel circumference can be adjusted in the global settings app.
|
||||
|
|
|
@ -26,8 +26,10 @@ class CSCSensor {
|
|||
this.speedUnit = this.qMetric ? "km/h" : "mph";
|
||||
this.distUnit = this.qMetric ? "km" : "mi";
|
||||
this.distFactor = this.qMetric ? 1.609344 : 1;
|
||||
this.screenInit = true;
|
||||
this.batteryLevel = -1;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.settings.totaldist = this.totaldist;
|
||||
storage.writeJSON(SETTINGS_FILE, this.settings);
|
||||
|
@ -35,10 +37,31 @@ class CSCSensor {
|
|||
this.movingTime = 0;
|
||||
this.lastRevsStart = this.lastRevs;
|
||||
this.maxSpeed = 0;
|
||||
this.screenInit = true;
|
||||
}
|
||||
|
||||
setBatteryLevel(level) {
|
||||
if (level!=this.batteryLevel) {
|
||||
this.batteryLevel = level;
|
||||
this.drawBatteryIcon();
|
||||
}
|
||||
}
|
||||
|
||||
updateBatteryLevel(event) {
|
||||
if (event.target.uuid == "0x2a19") this.setBatteryLevel(event.target.value.getUint8(0));
|
||||
}
|
||||
|
||||
drawBatteryIcon() {
|
||||
g.setColor(1, 1, 1).drawRect(10, 55, 20, 75).fillRect(14, 53, 16, 55).setColor(0).fillRect(11, 56, 19, 74);
|
||||
if (this.batteryLevel!=-1) {
|
||||
if (this.batteryLevel<25) g.setColor(1, 0, 0);
|
||||
else if (this.batteryLevel<50) g.setColor(1, 0.5, 0);
|
||||
else g.setColor(0, 1, 0);
|
||||
g.fillRect(11, 74-18*this.batteryLevel/100, 19, 74);
|
||||
}
|
||||
else g.setFontVector(14).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16, 66);
|
||||
}
|
||||
|
||||
updateScreen() {
|
||||
var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*this.wheelCirc/63360.0;
|
||||
var ddist = Math.round(100*dist)/100;
|
||||
|
@ -48,16 +71,18 @@ class CSCSensor {
|
|||
if (dmins.length<2) dmins = "0"+dmins;
|
||||
var dsecs = (Math.floor(this.movingTime) % 60).toString();
|
||||
if (dsecs.length<2) dsecs = "0"+dsecs;
|
||||
var avespeed = (this.movingTime>2 ? Math.round(10*dist/(this.movingTime/3600))/10 : 0);
|
||||
var avespeed = (this.movingTime>3 ? Math.round(10*dist/(this.movingTime/3600))/10 : 0);
|
||||
var maxspeed = Math.round(10*this.distFactor*this.maxSpeed)/10;
|
||||
if (this.screenInit) {
|
||||
for (var i=0; i<6; ++i) {
|
||||
if ((i&1)==0) g.setColor(0, 0, 0);
|
||||
else g.setColor(0.2, 0.1, 0.4);
|
||||
else g.setColor(0x30cd);
|
||||
g.fillRect(0, 48+i*32, 86, 48+(i+1)*32);
|
||||
if ((i&1)==1) g.setColor(0, 0, 0);
|
||||
else g.setColor(0.2, 0.1, 0.4);
|
||||
if ((i&1)==1) g.setColor(0);
|
||||
else g.setColor(0x30cd);
|
||||
g.fillRect(87, 48+i*32, 239, 48+(i+1)*32);
|
||||
g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239).drawRect(0, 48, 87, 239);
|
||||
g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239);
|
||||
g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80);
|
||||
}
|
||||
g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0);
|
||||
g.drawString("Time:", 87, 66);
|
||||
|
@ -66,23 +91,24 @@ class CSCSensor {
|
|||
g.drawString("Max spd:", 87, 162);
|
||||
g.drawString("Trip:", 87, 194);
|
||||
g.drawString("Total:", 87, 226);
|
||||
g.setFontAlign(-1, 0, 0).setFontVector(26).setColor(1, 1, 1);//.clearRect(92, 60, 239, 239);
|
||||
g.drawString(dmins+":"+dsecs, 92, 66);
|
||||
g.drawString(dspeed+" "+this.speedUnit, 92, 98);
|
||||
g.drawString(avespeed + " " + this.speedUnit, 92, 130);
|
||||
g.drawString(maxspeed + " " + this.speedUnit, 92, 162);
|
||||
g.drawString(ddist + " " + this.distUnit, 92, 194);
|
||||
g.drawString(tdist + " " + this.distUnit, 92, 226);
|
||||
if (this.batteryLevel!=-1) {
|
||||
g.setColor(1, 1, 1).drawRect(10, 55, 20, 75).fillRect(14, 53, 16, 55);
|
||||
if (this.batteryLevel<25) g.setColor(1, 0, 0);
|
||||
else if (this.batteryLevel<50) g.setColor(1, 0.5, 0);
|
||||
else g.setColor(0, 1, 0);
|
||||
g.fillRect(11, 74-18*this.batteryLevel/100, 19, 74);
|
||||
console.log(this.batteryLevel);
|
||||
this.batteryLevel = -1;
|
||||
this.drawBatteryIcon();
|
||||
this.screenInit = false;
|
||||
}
|
||||
g.setFontAlign(-1, 0, 0).setFontVector(26);
|
||||
g.setColor(0x30cd).fillRect(88, 49, 238, 79);
|
||||
g.setColor(0xffff).drawString(dmins+":"+dsecs, 92, 66);
|
||||
g.setColor(0).fillRect(88, 81, 238, 111);
|
||||
g.setColor(0xffff).drawString(dspeed+" "+this.speedUnit, 92, 98);
|
||||
g.setColor(0x30cd).fillRect(88, 113, 238, 143);
|
||||
g.setColor(0xffff).drawString(avespeed + " " + this.speedUnit, 92, 130);
|
||||
g.setColor(0).fillRect(88, 145, 238, 175);
|
||||
g.setColor(0xffff).drawString(maxspeed + " " + this.speedUnit, 92, 162);
|
||||
g.setColor(0x30cd).fillRect(88, 177, 238, 207);
|
||||
g.setColor(0xffff).drawString(ddist + " " + this.distUnit, 92, 194);
|
||||
g.setColor(0).fillRect(88, 209, 238, 238);
|
||||
g.setColor(0xffff).drawString(tdist + " " + this.distUnit, 92, 226);
|
||||
}
|
||||
|
||||
updateSensor(event) {
|
||||
var qChanged = false;
|
||||
if (event.target.uuid == "0x2a5b") {
|
||||
|
@ -103,7 +129,7 @@ class CSCSensor {
|
|||
var dBT = (Date.now()-this.lastBangleTime)/1000;
|
||||
this.lastBangleTime = Date.now();
|
||||
if (dT<0) dT+=64;
|
||||
if (Math.abs(dT-dBT)>2) dT = dBT;
|
||||
if (Math.abs(dT-dBT)>3) dT = dBT;
|
||||
this.lastTime = wheelTime;
|
||||
this.speed = this.lastSpeed;
|
||||
if (dRevs>0 && dT>0) {
|
||||
|
@ -120,7 +146,7 @@ class CSCSensor {
|
|||
}
|
||||
}
|
||||
this.lastSpeed = this.speed;
|
||||
if (this.speed > this.maxSpeed) this.maxSpeed = this.speed;
|
||||
if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed;
|
||||
}
|
||||
if (qChanged && this.qUpdateScreen) this.updateScreen();
|
||||
}
|
||||
|
@ -132,9 +158,8 @@ function getSensorBatteryLevel(gatt) {
|
|||
gatt.getPrimaryService("180f").then(function(s) {
|
||||
return s.getCharacteristic("2a19");
|
||||
}).then(function(c) {
|
||||
return c.readValue();
|
||||
}).then(function(d) {
|
||||
mySensor.setBatteryLevel(d.buffer[0]);
|
||||
c.on('characteristicvaluechanged', (event)=>mySensor.updateBatteryLevel(event));
|
||||
return c.startNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -163,14 +188,17 @@ function parseDevice(d) {
|
|||
})}
|
||||
|
||||
function connection_setup() {
|
||||
NRF.setScan();
|
||||
mySensor.screenInit = true;
|
||||
NRF.setScan(parseDevice, { filters: [{services:["1816"]}], timeout: 2000});
|
||||
g.clearRect(0, 60, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0);
|
||||
g.clearRect(0, 48, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0);
|
||||
g.drawString("Scanning for CSC sensor...", 120, 120);
|
||||
}
|
||||
|
||||
connection_setup();
|
||||
setWatch(function() { mySensor.reset(); g.clearRect(0, 60, 239, 239); mySensor.updateScreen(); }, BTN1, {repeat:true, debounce:20});
|
||||
setWatch(function() { mySensor.reset(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN1, {repeat:true, debounce:20});
|
||||
E.on('kill',()=>{ if (gatt!=undefined) gatt.disconnect(); mySensor.settings.totaldist = mySensor.totaldist; storage.writeJSON(SETTINGS_FILE, mySensor.settings); });
|
||||
setWatch(function() { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }, BTN3, {repeat:true, debounce:20});
|
||||
NRF.on('disconnect', connection_setup);
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: App Made!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("/wA/AH4A/AH4A/ACmsAEQuMlcAAD0rGBQKBFr4ADGBOsqwvjqwvJRsCRFF/8Gg4ADEZYQEgwvWg8+AAgwKCJgvQDgoABF5IRMF5xEBJpBhGCJwvNDQM4AYMNAAQaBnCAFCJ4vNIwQeBAAkxQAwGCmIRFFwIRDF64dDgwGBgwRNF/4v/F/4v/F/4v/F/4dJmIdECIkxF7MHFwUHhoACg4eCAYIACCJ4vNDQIgCAAgICKwoROF5yAEAAgtFCKAvQJpAAICJgvQgEGg4ADFxIwCAAcGBYovRADov6qwvjqwvJ1gvjEoIvHGASRgRoIuJGAYAhFxQA/AH4A/AH4A/ABQ"))
|
|
@ -0,0 +1,154 @@
|
|||
//load fonts
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
require("FontHaxorNarrow7x17").add(Graphics);
|
||||
//screen position
|
||||
const X = 170;
|
||||
const Y = 140;
|
||||
|
||||
function draw() {
|
||||
// Date Variables
|
||||
var date = new Date();
|
||||
var h = date.getHours();
|
||||
var m = date.getMinutes();
|
||||
var day = date.getDay();
|
||||
var month = date.getMonth();
|
||||
var dateNum = date.getDate();
|
||||
var year = date.getFullYear();
|
||||
var half = "AM";
|
||||
var time = (" " + h).substr(-2) + ":" + ("0" + m).substr(-2);
|
||||
|
||||
//convert day into string
|
||||
switch (day) {
|
||||
case 0:
|
||||
day = "Sunday";
|
||||
break;
|
||||
|
||||
case 1:
|
||||
day = "Monday";
|
||||
break;
|
||||
|
||||
case 2:
|
||||
day = "Tuesday";
|
||||
break;
|
||||
|
||||
case 3:
|
||||
day = "Wednesday";
|
||||
break;
|
||||
|
||||
case 4:
|
||||
day = "Thursday";
|
||||
break;
|
||||
|
||||
case 5:
|
||||
day = "Friday";
|
||||
break;
|
||||
|
||||
case 6:
|
||||
day = "Saturday";
|
||||
break;
|
||||
|
||||
default:
|
||||
day = "ERROR";
|
||||
break;
|
||||
}
|
||||
|
||||
//convert month into String
|
||||
switch(month) {
|
||||
case 0:
|
||||
month = "Jan";
|
||||
break;
|
||||
|
||||
case 1:
|
||||
month = "Feb";
|
||||
break;
|
||||
|
||||
case 2:
|
||||
month = "Mar";
|
||||
break;
|
||||
|
||||
case 3:
|
||||
month = "Apr";
|
||||
break;
|
||||
|
||||
case 4:
|
||||
month = "May";
|
||||
break;
|
||||
|
||||
case 5:
|
||||
month = "Jun";
|
||||
break;
|
||||
|
||||
case 6:
|
||||
month = "Jul";
|
||||
break;
|
||||
|
||||
case 7:
|
||||
month = "Aug";
|
||||
break;
|
||||
|
||||
case 8:
|
||||
month = "Sep";
|
||||
break;
|
||||
|
||||
case 9:
|
||||
month = "Oct";
|
||||
break;
|
||||
|
||||
case 10:
|
||||
month = "Nov";
|
||||
break;
|
||||
|
||||
case 11:
|
||||
month = "Dec";
|
||||
break;
|
||||
|
||||
default:
|
||||
month = "ERROR";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (h > 12) {
|
||||
half = "PM";
|
||||
h = h - 12;
|
||||
}
|
||||
//reset graphics
|
||||
g.reset();
|
||||
//draw the time
|
||||
g.setFont("7x11Numeric7Seg", 5);
|
||||
g.setFontAlign(1,1);
|
||||
g.drawString(time, X, Y, true /*clear background*/);
|
||||
g.setFont("7x11Numeric7Seg", 3);
|
||||
g.drawString(("0"+date.getSeconds()).substr(-2), X+50, Y, true /*clear background*/);
|
||||
g.setFontAlign(0,1);
|
||||
g.setFont("HaxorNarrow7x17", 2);
|
||||
g.drawString(half, X+30, Y-35, true);
|
||||
g.setFont("HaxorNarrow7x17", 3);
|
||||
g.drawString(day, X-60, Y+53, true);
|
||||
g.drawString(month, X-100, Y+95, true);
|
||||
g.drawString(dateNum, X-40, Y+95, true);
|
||||
g.drawString(year, X-90, Y-55, true);
|
||||
|
||||
|
||||
}
|
||||
|
||||
//clear screen at startup
|
||||
g.clear();
|
||||
//draw immediatly
|
||||
draw();
|
||||
|
||||
var secondInterval = setInterval(draw, 1000);
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on) {
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat : false, edge: "falling"});
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: New app!
|
|
@ -0,0 +1,14 @@
|
|||
# DSDRelay
|
||||
|
||||
Small app to control DSD Tech BLE relay boards from the watch. I have seen them being sold as 1-, 2- and 4-relay boards. The app shows controls for
|
||||
4 relays, regardless of the actual configuration of the board connected.
|
||||
|
||||

|
||||
|
||||
## Controls
|
||||
- buttons 1 and 3 cycle the selection of the currently active channel
|
||||
- swipe right turns the selected channel's relay *on*
|
||||
- swipe left turns the selected channel's relay *off*
|
||||
|
||||
I only own a 1-relay board, so only the "Ch 1" functionality was tested; the other channels were implemented per the manufacturer's documentation.
|
||||
In particular, the method for determining the relay states on app startup for channels 2-4 was mostly an educated guess.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AH4A/AAcK1QAO0AXFCx4ABFyowGC/4X/C/4X/C48AC6IWEGCIuFAAUN6AED7vdAwgEDC/4X/C/4X86AGGC85fpAH4A/AH4ASA"))
|
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,116 @@
|
|||
var device;
|
||||
var gatt;
|
||||
var service;
|
||||
var characteristic;
|
||||
|
||||
// on/off commands
|
||||
// Channel 1 ON: A00101A2
|
||||
// Channel 1 OFF: A00100A1
|
||||
// Channel 2 ON: A00201A3
|
||||
// Channel 2 OFF: A00200A2
|
||||
// Channel 3 ON: A00301A4
|
||||
// Channel 3 OFF: A00300A3
|
||||
// Channel 4 ON: A00401A5
|
||||
// Channel 4 OFF: A00400A4
|
||||
|
||||
var cmds = [{ on: new Uint8Array([0xa0, 0x01, 0x01, 0xa2]),
|
||||
off: new Uint8Array([0xa0, 0x01, 0x00, 0xa1]) },
|
||||
{ on: new Uint8Array([0xa0, 0x02, 0x01, 0xa3]),
|
||||
off: new Uint8Array([0xa0, 0x02, 0x00, 0xa2]) },
|
||||
{ on: new Uint8Array([0xa0, 0x03, 0x01, 0xa4]),
|
||||
off: new Uint8Array([0xa0, 0x03, 0x00, 0xa3]) },
|
||||
{ on: new Uint8Array([0xa0, 0x04, 0x01, 0xa5]),
|
||||
off: new Uint8Array([0xa0, 0x04, 0x00, 0xa4]) }];
|
||||
|
||||
const button_w = 100;
|
||||
const button_h = 36;
|
||||
const button_r = button_h/2-4;
|
||||
const button_sp = 46;
|
||||
|
||||
var n_channels = 4;
|
||||
var channel = 0;
|
||||
var channel_state = [];
|
||||
|
||||
function drawButton(x, y, state) {
|
||||
if (state) g.setColor(0.2, 0.2, 0.95);
|
||||
else g.setColor(0.5, 0.5, 0.5);
|
||||
g.fillCircle(x+button_h/2, y+button_h/2, button_h/2).
|
||||
fillRect(x+button_h/2, y, x+button_w-button_h/2, y+button_h).
|
||||
fillCircle(x+button_w-button_h/2, y+button_h/2, button_h/2);
|
||||
g.setColor(0.85, 0.85, 0.85);
|
||||
if (state)
|
||||
g.fillCircle(x+button_w-button_h/2, y+button_h/2, button_r);
|
||||
else
|
||||
g.fillCircle(x+button_h/2, y+button_h/2, button_r);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function setup_screen() {
|
||||
g.clearRect(0, 60, g.getWidth()-1, g.getHeight()-1);
|
||||
for (var i=0; i<4; ++i) {
|
||||
g.setFontVector(22).setFontAlign(-1, 0, 0).setColor(0xffff).drawString("Ch"+String(i+1), 16, 60+i*button_sp+button_h/2);
|
||||
drawButton((g.getWidth()-button_w)/2, 60+i*button_sp, channel_state[i]);
|
||||
}
|
||||
moveChannelFrame(channel, channel);
|
||||
}
|
||||
|
||||
function parseDevice(d) {
|
||||
device = d;
|
||||
g.clearRect(0, 60, 239, 239).setFontAlign(0, 0, 0).setColor(0, 1, 0).drawString("Found device", 120, 120).flip();
|
||||
device.gatt.connect().then(function(ga) {
|
||||
gatt = ga;
|
||||
g.clearRect(0, 60, 239, 239).setFontAlign(0, 0, 0).setColor(0, 1, 0).drawString("Connected", 120, 120).flip();
|
||||
return gatt.getPrimaryService("FFE0");
|
||||
}).then(function(s) {
|
||||
service = s;
|
||||
return service.getCharacteristic("FFE1");
|
||||
}).then(function(c) {
|
||||
characteristic = c;
|
||||
console.log(c);
|
||||
return;
|
||||
}).then(function() {
|
||||
console.log("Done!");
|
||||
g.clearRect(0, 60, 239, 239).setColor(1, 1, 1).flip();
|
||||
setup_app();
|
||||
}).catch(function(e) {
|
||||
g.clearRect(0, 60, 239, 239).setColor(1, 0, 0).setFontAlign(0, 0, 0).drawString("ERROR"+e, 120, 120).flip();
|
||||
console.log(e);
|
||||
})}
|
||||
|
||||
function connection_setup() {
|
||||
NRF.setScan();
|
||||
NRF.setScan(parseDevice, { filters: [{services:["FFE0"]}], timeout: 2000});
|
||||
g.clearRect(0, 60, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0);
|
||||
g.drawString("Scanning for relay...", 120, 120);
|
||||
}
|
||||
|
||||
function moveChannelFrame(oldc, newc) {
|
||||
g.setColor(0).drawRect(8, 60+oldc*button_sp-4, g.getWidth()-8, 60+oldc*button_sp+button_h+4);
|
||||
g.setColor(0.9, 0.9, 0.9).drawRect(8, 60+newc*button_sp-4, g.getWidth()-8, 60+newc*button_sp+button_h+4);
|
||||
}
|
||||
|
||||
function setup_app() {
|
||||
characteristic.readValue().then(function(r) {
|
||||
for (var i=0; i<r.buffer.length/4; ++i) channel_state.push(r.buffer[i*4+2]==1);
|
||||
}).then(setup_screen);
|
||||
|
||||
Bangle.on('swipe', function(direction){
|
||||
switch(direction){
|
||||
case 1:
|
||||
drawButton((g.getWidth()-button_w)/2, 60+channel*button_sp, true);
|
||||
characteristic.writeValue(cmds[channel].on);
|
||||
break;
|
||||
case -1:
|
||||
drawButton((g.getWidth()-button_w)/2, 60+channel*button_sp, false);
|
||||
characteristic.writeValue(cmds[channel].off);
|
||||
break;
|
||||
}});
|
||||
setWatch(function() { var nc = channel-1; if (nc<0) nc = n_channels-1; moveChannelFrame(channel, nc); channel = nc; }, BTN1, {repeat:true, debounce:30});
|
||||
setWatch(function() { moveChannelFrame(channel, (channel+1)%n_channels); channel = (channel+1)%n_channels; }, BTN3, {repeat:true, debounce:30});
|
||||
}
|
||||
|
||||
|
||||
connection_setup();
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
|
@ -0,0 +1 @@
|
|||
{"id":"dsdrelay","name":"DSD Relay","src":"dsdrelay.app.js","icon":"dsdrelay.img"}
|
After Width: | Height: | Size: 409 B |
|
@ -0,0 +1 @@
|
|||
0.01: New app!
|
|
@ -0,0 +1,11 @@
|
|||
# FileManager
|
||||
|
||||
A small file manager, mostly written for debugging issues on the watch.
|
||||
Upon opening, the app will display a list of all the files in storage (it will contract the sub-components of a StorageFile into one entry).
|
||||
When selecting a file the following options appear (depending on file type detected by extension):
|
||||
|
||||
- Length: file size in bytes
|
||||
- Display file: print out file contents on screen (will attempt to add back newlines for minimized JS code)
|
||||
- Load file [*.js files only, no widgets]: load and execute javascript file
|
||||
- Display image [*.img files only]: attempt to render file contents as image on screen
|
||||
- Delete file: delete file (asks for confirmation first, will delete all components of a StorageFile)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/ABuIABgWIhAXNwAuVGBIXZmYAKC/4XXkQACC9Z3/C7YANC8J3/C8ciAAQXrO/4XbABoXhO/4XjkQACC9Z3/C7YANC78ICxuAC44wOCxAABiMRBKIAtA=="))
|
|
@ -0,0 +1,101 @@
|
|||
const STOR = require("Storage");
|
||||
|
||||
const n = 9;
|
||||
var nstart = 0;
|
||||
var nend;
|
||||
var m;
|
||||
var files;
|
||||
|
||||
function delete_file(fn) {
|
||||
E.showPrompt("Delete\n"+fn+"?", {buttons: {"No":false, "Yes":true}}).then(function(v) {
|
||||
if (v) {
|
||||
if (fn.charCodeAt(fn.length-1)==1) {
|
||||
var fh = STOR.open(fn.substr(0, fn.length-1), "w");
|
||||
fh.erase();
|
||||
}
|
||||
else STOR.erase(fn);
|
||||
}
|
||||
}).then(function() { files=get_pruned_file_list(); }).then(drawMenu);
|
||||
}
|
||||
|
||||
function get_length(fn) {
|
||||
var len;
|
||||
if (fn.charCodeAt(fn.length-1)==1) {
|
||||
var fh = STOR.open(fn.substr(0, fn.length-1), "r");
|
||||
len = fh.getLength();
|
||||
}
|
||||
else len = STOR.read(fn).length;
|
||||
return len;
|
||||
}
|
||||
|
||||
function display_file(fn, qJS) {
|
||||
g.clear().setColor(1, 1, 1);
|
||||
var qStorageFile = (fn.charCodeAt(fn.length-1)==1);
|
||||
var np = 0;
|
||||
Terminal.println("");
|
||||
var file_len = get_length(fn);
|
||||
var fb = (qStorageFile ? STOR.open(fn.substr(0, fn.length-1), "r") : STOR.read(fn));
|
||||
for (var i=0; i<file_len; ++i) {
|
||||
if (np++ > 38) {
|
||||
Terminal.println("");
|
||||
np = 0;
|
||||
}
|
||||
var c = (qStorageFile ? fb.read(1) : fb[i]);
|
||||
if (c=="\n") np = 0;
|
||||
if (qJS && !qStorageFile && c==";" && fb[i+1]!="\n") {
|
||||
Terminal.println(";");
|
||||
np = 0;
|
||||
}
|
||||
else Terminal.print(c);
|
||||
}
|
||||
Terminal.println("");
|
||||
}
|
||||
|
||||
function visit_file(fn) {
|
||||
var menu = {
|
||||
'' : {'title' : fn + (fn.charCodeAt(fn.length-1)==1 ? "(S)" : "")}
|
||||
};
|
||||
var qJS = fn.endsWith(".js");
|
||||
menu['Length: '+get_length(fn)+' bytes'] = function() {};
|
||||
menu['Display file'] = function () { display_file(fn, qJS); };
|
||||
if (qJS && !fn.endsWith(".wid.js")) menu['Load file'] = function() { load(fn); }
|
||||
if (fn.endsWith(".img")) menu['Display image'] = function() { g.clear().drawImage(STOR.read(fn),0,20); }
|
||||
menu['Delete file'] = function () { delete_file(fn); }
|
||||
menu['< Back'] = drawMenu;
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function drawMenu() {
|
||||
nend = (nstart+n<files.length)?nstart+n : files.length;
|
||||
var menu = {
|
||||
'': { 'title': 'Dir('+nstart+'-'+nend+')/'+files.length }
|
||||
};
|
||||
menu["< prev"] = function() {
|
||||
nstart -= n;
|
||||
if (nstart<0) nstart = files.length-n>0 ? files.length-n : 0;
|
||||
menu = {};
|
||||
drawMenu();
|
||||
}
|
||||
for (var i=nstart; i<nend; ++i) {
|
||||
menu[files[i]] = visit_file.bind(null, files[i]);
|
||||
}
|
||||
menu["> next"] = function() {
|
||||
if (nstart+n<files.length) nstart += n;
|
||||
else nstart = 0;
|
||||
menu = {};
|
||||
drawMenu();
|
||||
m.move(-1);
|
||||
}
|
||||
m = E.showMenu(menu);
|
||||
}
|
||||
|
||||
function get_pruned_file_list() {
|
||||
var fl = STOR.list(/^[^\.]/);
|
||||
fl.sort();
|
||||
fl = fl.concat(STOR.list(/^\./));
|
||||
fl = fl.filter(f => (f.charCodeAt(f.length-1)>31 || f.charCodeAt(f.length-1)<2));
|
||||
return fl;
|
||||
}
|
||||
|
||||
files = get_pruned_file_list();
|
||||
drawMenu();
|
After Width: | Height: | Size: 467 B |
|
@ -1 +1,2 @@
|
|||
0.01: First Version
|
||||
0.02: Remove HID requirement, update screen
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
Ring your phone via GadgetBridge if you lost it somewhere.
|
||||
|
||||
1. Enable HID in settings
|
||||
2. Connect GadgetBridge
|
||||
3. Lose phone
|
||||
4. Open app
|
||||
5. Click any button or screen
|
||||
1. Connect GadgetBridge
|
||||
2. Lose phone
|
||||
3. Open app
|
||||
4. Click any button or screen
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
var storage = require('Storage');
|
||||
|
||||
//notify your phone
|
||||
|
||||
var finding = false;
|
||||
|
||||
function draw() {
|
||||
// show message
|
||||
g.clear(1);
|
||||
require("Font8x12").add(Graphics);
|
||||
g.setFont("8x12",3);
|
||||
g.setFontAlign(0,0);
|
||||
if (finding) {
|
||||
g.drawString("Finding...", g.getWidth()/2, (g.getHeight()/2)-20);
|
||||
g.drawString("Click to stop", g.getWidth()/2, (g.getHeight()/2)+20);
|
||||
} else {
|
||||
g.drawString("Click to find", g.getWidth()/2, g.getHeight()/2);
|
||||
}
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function find(){
|
||||
Bluetooth.println(JSON.stringify({t:"findPhone", n:true}));
|
||||
finding = !finding;
|
||||
draw();
|
||||
Bluetooth.println("\n"+JSON.stringify({t:"findPhone", n:finding}));
|
||||
}
|
||||
|
||||
//init graphics
|
||||
g.clear();
|
||||
require("Font8x12").add(Graphics);
|
||||
g.setFont("8x12",3);
|
||||
g.setFontAlign(0,0);
|
||||
g.flip();
|
||||
draw();
|
||||
|
||||
//init settings
|
||||
const settings = storage.readJSON('setting.json',1) || { HID: false };
|
||||
|
||||
//check if HID enabled and show message
|
||||
if (settings.HID=="kb" || settings.HID=="kbmedia") {
|
||||
g.setColor(0x03E0);
|
||||
g.drawString("click to find", g.getWidth()/2, g.getHeight()/2);
|
||||
|
||||
//register all buttons and screen to find phone
|
||||
setWatch(find, BTN1);
|
||||
setWatch(find, BTN2);
|
||||
setWatch(find, BTN3);
|
||||
setWatch(find, BTN4);
|
||||
setWatch(find, BTN5);
|
||||
|
||||
}else{
|
||||
g.setColor(0xf800);
|
||||
g.drawString("enable HID!", g.getWidth()/2, g.getHeight()/2);
|
||||
}
|
||||
//register all buttons and screen to find phone
|
||||
setWatch(find, BTN1, {repeat:true});
|
||||
setWatch(find, BTN2, {repeat:true});
|
||||
setWatch(find, BTN3, {repeat:true});
|
||||
setWatch(find, BTN4, {repeat:true});
|
||||
setWatch(find, BTN5, {repeat:true});
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4ATlgAGFlgylEYdWq+BwOs1gDBq8yGL4eCmQqB64AIGgIyDFzQtBFhIAFGIZcYqxbKMZFWMSoVCLiBiGGCguM2YACGBgub1uJsoAExOtGDK7CFo4sFAAhjIYYQvOmTqGLYetE4mzM4L0JmQvNRpAuCQpAnDqx2GSJxeBFxDnKFwSmIMBheHXYQuPUwxgNBYIWFRh4uECQusF5iOFLwQuRUQIwFSBQ6Bq69GLw+swNdwKMGIgOJCQlXMBK+HXpAbCAAS7F2Z0GYBQJBwQZLVYeBDwREFIo4vMJIi+IDQIqCAgNdF5jwKF4xfBVIovHL5ovMDQztHR4pEER6ovGdwzvFq4TGd6YbFxNl1phHL4JdGaoSlFIYQvHGAMyJQxgHABReBIgsyFxLwHACZeBRwruKYBeJMJy9CLwq+KSBLBCF5ouCXoqOMMBYCERhQuGLxpgDYI5iBQApdM1heNMAdWEg7CJBQI6HqxeOSJQATrouQGDi8PF4wwXLoQvSGAdWeg4AK1i7CFyYxEmRiQwMyFq5iFGIJjK1gtDFzIxFGQNXwI0BFQOBq4sDFrgxHABItfGRgskAH4A/AFwA="))
|
|
@ -0,0 +1,6 @@
|
|||
g.clear();
|
||||
g.setFont("6x8",5);g.setFontAlign(-1,0);
|
||||
Bangle.on('accel',function(accelData) {
|
||||
g.drawString(" "+accelData.mag.toFixed(1)+" ",75,105,true);
|
||||
g.drawString('G\'s',75,180,true);
|
||||
});
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,2 +1,3 @@
|
|||
0.02: Ensure screen doesn't display garbage at startup
|
||||
0.03: Show number of satellites while waiting for fix
|
||||
0.04: Add Maidenhead readout of GPS location
|
||||
|
|
|
@ -21,7 +21,35 @@ function formatTime(now) {
|
|||
var date = [fd[0], fd[1], fd[2]].join(" ");
|
||||
return time + " - " + date;
|
||||
}
|
||||
function getMaidenHead(param1,param2){
|
||||
var lat=-100.0;
|
||||
var lon=0.0;
|
||||
var U = 'ABCDEFGHIJKLMNOPQRSTUVWX';
|
||||
var L = U.toLowerCase();
|
||||
|
||||
lat = param1;
|
||||
lon = param2;
|
||||
|
||||
lon = lon + 180;
|
||||
t = lon/20;
|
||||
fLon = Math.floor(t);
|
||||
t = (t % fLon)*10;
|
||||
sqLon = Math.floor(t);
|
||||
t=(t-sqLon)*24;
|
||||
subLon = Math.floor(t);
|
||||
extLon = Math.floor((t-subLon)*10);
|
||||
|
||||
lat = lat + 90;
|
||||
t = lat/10;
|
||||
fLat = Math.floor(t);
|
||||
t = (t % fLat)*10;
|
||||
sqLat = Math.floor(t);
|
||||
t=(t-sqLat)*24;
|
||||
subLat = Math.floor(t);
|
||||
extLat = Math.floor((t-subLat)*10);
|
||||
|
||||
return U[fLon]+U[fLat]+sqLon+sqLat+L[subLon]+L[subLat]+extLon+extLat;
|
||||
}
|
||||
function onGPS(fix) {
|
||||
lastFix = fix;
|
||||
g.clear();
|
||||
|
@ -38,15 +66,16 @@ function onGPS(fix) {
|
|||
var speed = fix.speed;
|
||||
var time = formatTime(fix.time);
|
||||
var satellites = fix.satellites;
|
||||
|
||||
var maidenhead = getMaidenHead(lat,lon);
|
||||
var s = 15;
|
||||
g.setFontVector(s);
|
||||
g.drawString("Altitude: "+alt+" m",10,44);
|
||||
g.drawString("Lat: "+lat,10,44+20);
|
||||
g.drawString("Lon: "+lon,10,44+40);
|
||||
g.drawString("Speed: "+speed.toFixed(1)+" km/h",10,44+60);
|
||||
g.drawString("Time: "+time,10,44+80);
|
||||
g.drawString("Satellites: "+satellites,10,44+100);
|
||||
g.drawString("Altitude: "+alt+" m",10,36);
|
||||
g.drawString("Lat: "+lat,10,54);
|
||||
g.drawString("Lon: "+lon,10,72);
|
||||
g.drawString("Speed: "+speed.toFixed(1)+" km/h",10,90);
|
||||
g.drawString("Time: "+time,10,108);
|
||||
g.drawString("Satellites: "+satellites,10,126);
|
||||
g.drawString("Maidenhead: "+maidenhead,10,144);
|
||||
} else {
|
||||
g.setFontAlign(0, 1);
|
||||
g.setFont("6x8", 2);
|
||||
|
|
|
@ -13,3 +13,5 @@
|
|||
0.10: Can now graph altitude & speed
|
||||
0.11: Ensure we don't turn GPS off if it was previously on (eg from another app/widget)
|
||||
0.12: Add option to plot on top of OpenStreetMap tiles (when they are installed on the watch)
|
||||
0.13: Increase GPS recording accuracy by one decimal place
|
||||
Ensure default time period is 10
|
||||
|
|
|
@ -37,9 +37,9 @@ function showMainMenu() {
|
|||
}
|
||||
},
|
||||
'Time Period': {
|
||||
value: settings.period||1,
|
||||
value: settings.period||10,
|
||||
min: 1,
|
||||
max: 60,
|
||||
max: 120,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
settings.recording = false;
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
periodCtr = settings.period;
|
||||
if (gpsTrack) gpsTrack.write([
|
||||
fix.time.getTime(),
|
||||
fix.lat.toFixed(5),
|
||||
fix.lon.toFixed(5),
|
||||
fix.lat.toFixed(6),
|
||||
fix.lon.toFixed(6),
|
||||
fix.alt
|
||||
].join(",")+"\n");
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New Widget!
|
|
@ -0,0 +1,59 @@
|
|||
# GPS Time Server
|
||||
|
||||
A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server, UUID 0x1805.
|
||||
|
||||
Other Espruino Bluetooth devices can then find it and use it to synchronise time.
|
||||
|
||||
**Note:** Because GPS is kept on, you'll need to keep your Bangle.js on charge for this to be useful.
|
||||
|
||||
## Usage
|
||||
|
||||
Just install this widget, and from then on any app which loads widgets will
|
||||
display the icon  in the top left, and Bangle.js will be
|
||||
broadcasting the current time to any device that connects.
|
||||
|
||||
## Technical
|
||||
|
||||
This implements the [Bluetooth Time Service](https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=292957) listed [here](https://www.bluetooth.com/specifications/gatt/).
|
||||
|
||||
The Bluetooth docs are verbose and hard to read, so here's a rundown of how it works.
|
||||
|
||||
* The Bangle advertises service `0x1805`
|
||||
* You connect to it, and request service `0x1805` and characteristic `0x2A2B`
|
||||
* A 10 byte array is returned:
|
||||
|
||||
```
|
||||
[
|
||||
year_lowbyte,
|
||||
year_highbyte,
|
||||
month,
|
||||
day_of_month,
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
day_of_week, // 1=monday...7=sunday
|
||||
subseconds, // 0..255
|
||||
update_reason // 0=unknown currently
|
||||
]
|
||||
```
|
||||
|
||||
```
|
||||
//NRF.requestDevice({ filters: [{ services: ['1805'] }] }).then(print)
|
||||
|
||||
var gatt;
|
||||
NRF.connect("c7:4b:2e:c6:f5:45 random").then(function(g) {
|
||||
gatt = g;
|
||||
return gatt.getPrimaryService("1805");
|
||||
}).then(function(service) {
|
||||
return service.getCharacteristic("2A2B");
|
||||
}).then(function(c) {
|
||||
return c.readValue();
|
||||
}).then(function(d) {
|
||||
console.log("Got:", JSON.stringify(d.buffer));
|
||||
var year = d.getUint16(0,1);
|
||||
// ...
|
||||
gatt.disconnect();
|
||||
}).catch(function(e) {
|
||||
console.log("Something's broken.",e);
|
||||
});
|
||||
```
|
|
@ -0,0 +1,53 @@
|
|||
(() => {
|
||||
|
||||
function getBLECurrentTimeData(d) {
|
||||
var updateReason = 0; // unknown update reason
|
||||
return [
|
||||
d.getFullYear()&0xFF,
|
||||
d.getFullYear()>>8,
|
||||
d.getMonth()+1,
|
||||
d.getDate(),
|
||||
d.getHours(),
|
||||
d.getMinutes(),
|
||||
d.getSeconds(),
|
||||
d.getDay() ? d.getDay() : 7/*sunday*/,
|
||||
Math.floor(d.getMilliseconds()*255/1000),
|
||||
updateReason
|
||||
];
|
||||
}
|
||||
|
||||
NRF.setServices({
|
||||
0x1805 : {
|
||||
0x2A2B : {
|
||||
value : getBLECurrentTimeData(new Date()),
|
||||
readable : true,
|
||||
notify : true
|
||||
}
|
||||
}
|
||||
}, { advertise: [ '1805' ] });
|
||||
|
||||
Bangle.on('GPS', function(fix) {
|
||||
if (fix.time !== undefined) {
|
||||
NRF.updateServices({
|
||||
0x1805 : {
|
||||
0x2A2B : {
|
||||
value : getBLECurrentTimeData(fix.time),
|
||||
notify : true
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Bangle.setGPSPower(1);
|
||||
|
||||
|
||||
function draw() {
|
||||
g.reset();
|
||||
g.drawImage(require("heatshrink").decompress(atob("i0XxH+CR0HhEHEyEOi1AAAMWhAUNisW6/XwICBi0PHpgUC69WAYUWIpcVxAVGsgsLi2sCAOsg4EDiwVPlZYCCoUzss6IwxBE68rDYJBBldlAAVeNpIADNoNdxIWDssrCYMJgKZDF4SZCxGtCollmcJAALFDnTFE1utxNdrtXq9WqwVDeJAVB1tdrwABFgM6maOKwQWCIQgbBmQVJmQVCCwlXF4LoKCoaHDCoSgFAAldCwYtCqxbCLRQVECwNWr4VBr4VJmYWFrpcDCpM6neJC4pdCChEsss7C4+IFRI4DC4LBKCpBQLAAgA=")), this.x, this.y);
|
||||
}
|
||||
WIDGETS["gpstimeserver"]={
|
||||
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
|
||||
width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
|
||||
draw:draw // called to draw the widget
|
||||
};
|
||||
})()
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Use HRM data and calculations from Bangle.js (don't access hardware directly)
|
||||
|
|
|
@ -1,74 +1,60 @@
|
|||
Bangle.setLCDPower(1);
|
||||
Bangle.setLCDTimeout(0);
|
||||
Bangle.ioWr(0x80,0)
|
||||
x=0;
|
||||
Bangle.setHRMPower(1);
|
||||
var hrmInfo, hrmOffset = 0;
|
||||
var hrmInterval;
|
||||
function onHRM(h) {
|
||||
// this is the first time we're called
|
||||
if (counter!==undefined) {
|
||||
counter = undefined;
|
||||
g.clear();
|
||||
}
|
||||
hrmInfo = h;
|
||||
hrmOffset = 0;
|
||||
if (hrmInterval) clearInterval(hrmInterval);
|
||||
hrmInterval = setInterval(readHRM,40);
|
||||
|
||||
var px = g.getWidth()/2;
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0,24,239,90);
|
||||
g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75);
|
||||
var str = hrmInfo.bpm;
|
||||
g.setFontVector(40).drawString(str,px,45);
|
||||
px += g.stringWidth(str)/2;
|
||||
g.setFont("6x8");
|
||||
g.drawString("BPM",px+15,45);
|
||||
}
|
||||
Bangle.on('HRM', onHRM);
|
||||
|
||||
// It takes 5 secs for us to get the first HRM event
|
||||
var counter = 5;
|
||||
function countDown() {
|
||||
E.showMessage("Please wait...\n"+counter--);
|
||||
if (counter) setTimeout(countDown, 1000);
|
||||
}
|
||||
countDown();
|
||||
|
||||
|
||||
var min=0,max=0;
|
||||
var wasHigh = 0, wasLow = 0;
|
||||
var lastHigh = getTime();
|
||||
var hrmList = [];
|
||||
var hrm;
|
||||
var hrmInfo;
|
||||
|
||||
function readHRM() {
|
||||
var a = analogRead(D29);
|
||||
var h = getTime();
|
||||
min=Math.min(min*0.97+a*0.03,a);
|
||||
max=Math.max(max*0.97+a*0.03,a);
|
||||
y = E.clip(170 - (a*960*4),100,230);
|
||||
if (x==0) {
|
||||
if (!hrmInfo) return;
|
||||
|
||||
if (hrmOffset==0) {
|
||||
g.clearRect(0,100,239,239);
|
||||
g.moveTo(-100,0);
|
||||
}
|
||||
/*g.setColor(0,1,0);
|
||||
var z = 170 - (min*960*4); g.fillRect(x,z,x,z);
|
||||
var z = 170 - (max*960*4); g.fillRect(x,z,x,z);*/
|
||||
for (var i=0;i<2;i++) {
|
||||
var a = hrmInfo.raw[hrmOffset];
|
||||
hrmOffset++;
|
||||
min=Math.min(min*0.97+a*0.03,a);
|
||||
max=Math.max(max*0.97+a*0.03,a);
|
||||
y = E.clip(170 - (a*4),100,230);
|
||||
g.setColor(1,1,1);
|
||||
g.lineTo(x,y);
|
||||
if ((max-min)>0.005) {
|
||||
if (4*a > (min+3*max)) { // high
|
||||
g.setColor(1,0,0);
|
||||
g.fillRect(x,230,x,239);
|
||||
g.setColor(1,1,1);
|
||||
if (!wasHigh && wasLow) {
|
||||
var currentHrm = 60/(h-lastHigh);
|
||||
lastHigh = h;
|
||||
if (currentHrm<250) {
|
||||
while (hrmList.length>12) hrmList.shift();
|
||||
hrmList.push(currentHrm);
|
||||
// median filter
|
||||
var t = hrmList.slice(); // copy
|
||||
t.sort();
|
||||
// average the middle 3
|
||||
var mid = t.length>>1;
|
||||
if (mid+2<t.length)
|
||||
hrm = (t[mid]+t[mid+1]+t[mid+2])/3;
|
||||
else if (mid<t.length)
|
||||
hrm = t[mid];
|
||||
else
|
||||
hrm = 0;
|
||||
g.setFontVector(40);
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0,0,239,100);
|
||||
var str = hrm ? Math.round(hrm) : "?";
|
||||
var px = 120;
|
||||
g.drawString(str,px,40);
|
||||
px += g.stringWidth(str)/2;
|
||||
g.setFont("6x8");
|
||||
g.drawString("BPM",px+20,60);
|
||||
g.lineTo(hrmOffset, y);
|
||||
}
|
||||
}
|
||||
wasLow = 0;
|
||||
wasHigh = 1;
|
||||
} else if (4*a < (max+3*min)) { // low
|
||||
wasLow = 1;
|
||||
} else { // middle
|
||||
g.setColor(0.5,0,0);
|
||||
g.fillRect(x,230,x,239);
|
||||
g.setColor(1,1,1);
|
||||
wasHigh = 0;
|
||||
}
|
||||
}
|
||||
x++;
|
||||
if (x>239)x=0;
|
||||
}
|
||||
|
||||
setInterval(readHRM,50);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Created app based on digiclock with some small tweaks.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwUC1QA/ACev/4AG/QLB3ptHvwLB+ALHh4LB6ALHg4LDnkD/8An4eBBYsPgcA+E8BY8AgfAAYILG+ALJF4ILJJwPDBZMDBZMMEQJHJL5J3LBfX/M4PAgaRB/gLC6ZnCmEPNQM8BYpnBWwQLG/4ZBBYvQn7UCC5ILXmAKBI4pfDLoIBB//HR8p0BAA0PBYO9BY9+BYOv/4AG/QLBAH4ASA="))
|
|
@ -0,0 +1,95 @@
|
|||
//load fonts
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
require("FontHaxorNarrow7x17").add(Graphics);
|
||||
//screen position
|
||||
const X = 170;
|
||||
const Y = 140;
|
||||
|
||||
function draw() {
|
||||
// Date Variables
|
||||
var date = new Date();
|
||||
var h = date.getHours();
|
||||
var m = date.getMinutes();
|
||||
var day = date.getDay();
|
||||
var month = date.getMonth()+1;
|
||||
var dateNum = date.getDate();
|
||||
var year = date.getFullYear();
|
||||
var half = "AM";
|
||||
var time = ("0" + h).substr(-2) + ":" + ("0" + m).substr(-2);
|
||||
|
||||
//convert day into string
|
||||
switch (day) {
|
||||
case 0:
|
||||
day = "Sunday";
|
||||
break;
|
||||
|
||||
case 1:
|
||||
day = "Monday";
|
||||
break;
|
||||
|
||||
case 2:
|
||||
day = "Tuesday";
|
||||
break;
|
||||
|
||||
case 3:
|
||||
day = "Wednesday";
|
||||
break;
|
||||
|
||||
case 4:
|
||||
day = "Thursday";
|
||||
break;
|
||||
|
||||
case 5:
|
||||
day = "Friday";
|
||||
break;
|
||||
|
||||
case 6:
|
||||
day = "Saturday";
|
||||
break;
|
||||
|
||||
default:
|
||||
day = "ERROR";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (h > 12) {
|
||||
half = "PM";
|
||||
h = h - 12;
|
||||
}
|
||||
//reset graphics
|
||||
g.reset();
|
||||
//draw the time
|
||||
g.setFont("7x11Numeric7Seg", 5);
|
||||
g.setFontAlign(1,1);
|
||||
g.drawString(time, X+10, Y, true /*clear background*/);
|
||||
g.setFont("7x11Numeric7Seg", 3);
|
||||
g.drawString(("0"+date.getSeconds()).substr(-2), X+55, Y, true /*clear background*/);
|
||||
g.setFontAlign(0,1);
|
||||
g.setFont("HaxorNarrow7x17", 3);
|
||||
g.drawString(day, X-60, Y+53, true);
|
||||
g.drawString(year+"-"+month+"-"+dateNum, X-55, Y-55, true);
|
||||
|
||||
|
||||
}
|
||||
|
||||
//clear screen at startup
|
||||
g.clear();
|
||||
//draw immediatly
|
||||
draw();
|
||||
|
||||
var secondInterval = setInterval(draw, 1000);
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on) {
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat : false, edge: "falling"});
|
After Width: | Height: | Size: 9.7 KiB |
|
@ -4,3 +4,4 @@
|
|||
0.04: Adjust layout to account for new vector font
|
||||
0.05: Add support for 12 hour time
|
||||
0.06: Allow to disable BTN1 and BTN3 buttons
|
||||
0.07: Don't clear all intervals during initialisation
|
||||
|
|
|
@ -198,7 +198,6 @@ if (BTN3app) setWatch(
|
|||
);
|
||||
|
||||
g.clear();
|
||||
clearInterval();
|
||||
drawClockFace();
|
||||
interval = setInterval(drawClockFace, REFRESH_RATE);
|
||||
|
||||
|
|
|
@ -143,10 +143,10 @@ var locales = {
|
|||
int_curr_symbol: "JPY",
|
||||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°F",
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%y/%M/%d", 1: "%y/%m;/%d" },
|
||||
datePattern: { 0: "%Y/%m/%d", 1: "%y/%m/%d" },
|
||||
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
|
||||
month: "January,February,March,April,May,June,July,August,September,October,November,December",
|
||||
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# Mandel
|
||||
|
||||
Draw a colored rendition of the famous Mandelbrot set. Pushing button 2 activates zoom mode: the top and left edge of the zoom region can be moved up/down
|
||||
with buttons 1 and 3 and left/right by touching the screen left right. Pushing button 2 again allows movement of the bottom and right edge in the same manner.
|
||||
Pushing button 2 a third time renders the selected region full screen.
|
||||
|
||||
The code uses inlined C code for perfomance reasons. Full source is provided on github.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkA/4A/AEGIACQX/C/4X3x4XX/AXV/4XsBoYYFC6IPFC5gWFGAgXSDAgXIXAYXGF5mPA4ICCF6QUGC5wWKI5YVKR5ovWL7CPZX6zvXC5KPMDBYXVFwgXOB4QWFC9GPC65pKC5aBLC/4X/C54A/ADo"))
|
|
@ -0,0 +1,192 @@
|
|||
|
||||
var aux = new Float32Array(8);
|
||||
var p_aux = E.getAddressOf(aux, true);
|
||||
aux[0] = -2;
|
||||
aux[1] = -1.5;
|
||||
aux[2] = 1;
|
||||
aux[3] = 1.5;
|
||||
|
||||
var buf_height = 40;
|
||||
|
||||
var frameX1=0, frameX2=239, frameY1=0, frameY2=239;
|
||||
var frameCorner = 0;
|
||||
|
||||
var imbuf = Graphics.createArrayBuffer(240, buf_height, 16);
|
||||
var imbufaddr = E.getAddressOf(imbuf.buffer, true);
|
||||
var mimg = {
|
||||
width :imbuf.getWidth(),
|
||||
height:imbuf.getHeight(),
|
||||
bpp :16,
|
||||
buffer:imbuf.buffer
|
||||
};
|
||||
|
||||
var c = E.compiledC(`
|
||||
// void mandel(int, int)
|
||||
union shortbytes {
|
||||
short s;
|
||||
char b[2];
|
||||
};
|
||||
void mandel(float *p, short *ib) {
|
||||
float mincx = p[0];
|
||||
float mincy = p[1];
|
||||
float maxcx = p[2];
|
||||
float maxcy = p[3];
|
||||
int minx = (int)p[4];
|
||||
int miny = (int)p[5];
|
||||
int maxx = (int)p[6];
|
||||
int maxy = (int)p[7];
|
||||
int pib = 0;
|
||||
for (int y=miny; y<maxy; ++y)
|
||||
for (int x=minx; x<maxx; ++x) {
|
||||
float cr = mincx+(maxcx-mincx)*x/240.0;
|
||||
float ci = mincy+(maxcy-mincy)*y/240.0;
|
||||
float zr=0, zi=0;
|
||||
char niter = 31;
|
||||
while (--niter && zr>-2 && zr<1 && zi>-1.5 && zi<1.5) {
|
||||
float nr = zr*zr-zi*zi + cr;
|
||||
zi = 2*zr*zi + ci;
|
||||
zr = nr;
|
||||
}
|
||||
union shortbytes c, d;
|
||||
c.s = niter | (niter << 7)&0x7ff | (niter<<13)&0xffff;
|
||||
d.b[0] = c.b[1];
|
||||
d.b[1] = c.b[0];
|
||||
ib[pib++] = d.s;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
function drawRectangle(x1, y1, x2, y2) {
|
||||
if (frameCorner==1) g.setColor(1, 0, 0);
|
||||
else g.setColor(1, 1, 1);
|
||||
g.drawLine(x1, y1, x2, y1).drawLine(x1, y1, x1, y2);
|
||||
if (frameCorner==2) g.setColor(1, 0, 0);
|
||||
else g.setColor(1, 1, 1);
|
||||
g.drawLine(x1, y2, x2, y2).drawLine(x2, y1, x2, y2);
|
||||
}
|
||||
|
||||
function restoreRow(y) {
|
||||
mimg.width = 240;
|
||||
mimg.height = 1;
|
||||
aux[4] = 0;
|
||||
aux[5] = y;
|
||||
aux[6] = 240;
|
||||
aux[7] = y+1;
|
||||
c.mandel(p_aux, imbufaddr);
|
||||
g.drawImage(mimg, 0, y);
|
||||
}
|
||||
|
||||
function restoreCol(x) {
|
||||
mimg.width = 1;
|
||||
mimg.height = 240;
|
||||
aux[4] = x;
|
||||
aux[5] = 0;
|
||||
aux[6] = x+1;
|
||||
aux[7] = 240;
|
||||
c.mandel(p_aux, imbufaddr);
|
||||
g.drawImage(mimg, x, 0);
|
||||
}
|
||||
|
||||
function moveUp() {
|
||||
restoreCol(frameX1);
|
||||
restoreCol(frameX2);
|
||||
if (frameCorner==1 && frameY1>3) {
|
||||
restoreRow(frameY1);
|
||||
frameY1 -= 4;
|
||||
}
|
||||
if (frameCorner==2 && frameY2>3) {
|
||||
restoreRow(frameY2);
|
||||
frameY2 -= 4;
|
||||
}
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
|
||||
function moveDown() {
|
||||
restoreCol(frameX1);
|
||||
restoreCol(frameX2);
|
||||
if (frameCorner==1 && frameY1<235) {
|
||||
restoreRow(frameY1);
|
||||
frameY1 += 4;
|
||||
}
|
||||
if (frameCorner==2 && frameY2<235) {
|
||||
restoreRow(frameY2);
|
||||
frameY2 += 4;
|
||||
}
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
|
||||
function moveRight() {
|
||||
restoreRow(frameY1);
|
||||
restoreRow(frameY2);
|
||||
if (frameCorner==1 && frameX1<235) {
|
||||
restoreCol(frameX1);
|
||||
frameX1 += 4;
|
||||
}
|
||||
if (frameCorner==2 && frameX2<235) {
|
||||
restoreCol(frameX2);
|
||||
frameX2 += 4;
|
||||
}
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
|
||||
function moveLeft() {
|
||||
restoreRow(frameY1);
|
||||
restoreRow(frameY2);
|
||||
if (frameCorner==1 && frameX1>3) {
|
||||
restoreCol(frameX1);
|
||||
frameX1 -= 4;
|
||||
}
|
||||
if (frameCorner==2 && frameX2>3) {
|
||||
restoreCol(frameX2);
|
||||
frameX2 -= 4;
|
||||
}
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
|
||||
|
||||
function toggleFrame() {
|
||||
if (frameCorner<2) {
|
||||
frameCorner++;
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
else {
|
||||
frameCorner = 0;
|
||||
var mincx = aux[0] + (aux[2]-aux[0])*frameX1/240.0;
|
||||
var maxcx = aux[0] + (aux[2]-aux[0])*frameX2/240.0;
|
||||
var mincy = aux[1] + (aux[3]-aux[1])*frameY1/240.0;
|
||||
var maxcy = aux[1] + (aux[3]-aux[1])*frameY2/240.0;
|
||||
aux[0] = mincx;
|
||||
aux[1] = mincy;
|
||||
aux[2] = maxcx;
|
||||
aux[3] = maxcy;
|
||||
drawIt();
|
||||
}
|
||||
}
|
||||
|
||||
setWatch(toggleFrame, BTN2, {repeat: true});
|
||||
setWatch(moveUp, BTN1, {repeat: true});
|
||||
setWatch(moveDown, BTN3, {repeat: true});
|
||||
Bangle.on('touch', function(button) {
|
||||
switch(button) {
|
||||
case 1: moveLeft(); break;
|
||||
case 2: moveRight(); break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function drawIt() {
|
||||
aux[4] = 0;
|
||||
aux[5] = 0;
|
||||
aux[6] = 240;
|
||||
aux[7] = buf_height;
|
||||
mimg.width = 240;
|
||||
mimg.height = buf_height;
|
||||
for (var y=0; y<240/buf_height; ++y) {
|
||||
c.mandel(p_aux, imbufaddr);
|
||||
aux[5] += buf_height;
|
||||
aux[7] += buf_height;
|
||||
g.drawImage(mimg, 0, y*buf_height);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(drawIt, 50);
|
|
@ -0,0 +1 @@
|
|||
{"id":"mandel","name":"Mandel","src":"mandel.app.js","icon":"mandel.img"}
|
|
@ -0,0 +1,163 @@
|
|||
|
||||
var aux = new Float32Array(8);
|
||||
var p_aux = E.getAddressOf(aux, true);
|
||||
aux[0] = -2;
|
||||
aux[1] = -1.5;
|
||||
aux[2] = 1;
|
||||
aux[3] = 1.5;
|
||||
|
||||
var buf_height = 40;
|
||||
|
||||
var frameX1=0, frameX2=239, frameY1=0, frameY2=239;
|
||||
var frameCorner = 0;
|
||||
|
||||
var imbuf = Graphics.createArrayBuffer(240, buf_height, 16);
|
||||
var imbufaddr = E.getAddressOf(imbuf.buffer, true);
|
||||
var mimg = {
|
||||
width :imbuf.getWidth(),
|
||||
height:imbuf.getHeight(),
|
||||
bpp :16,
|
||||
buffer:imbuf.buffer
|
||||
};
|
||||
|
||||
var c = (function(){
|
||||
var bin=atob("0O0EepDtAFrQ7QFK0O0COpDtAzqf7URK/e7nei3p8EMX7pBa0O0Fev3u53rF68V8F+6QStDtBnr97ud6ACMX7pCK0O0Hev3u53q47gAqF+6QmvfuABq/7ggaTEVi2gzrAwaeRgHrRgYqRvfuCCqu6wUDQkUTRETaB+4QKnPuxXq47sd6HyNn7od6B+4QSsfuhGq47sd6c+5kenbuhWpn7od6n+0iesfuhFrw7kd6de6kWgE7E/D/AxPQ9O7CevHuEPoO3fTu4Xrx7hD6CdW07sF68e4Q+gTdtO7ievHuEPoR1NgBwPMKAEPqQzMDQ8PzByBg8wcHY/MPJyb4EnABMrXnATSp5yfuR2rw7mUKp+6nanfup3rn7icKdu4merDuYHrG573o8IMAAHBDAAAAAA==");
|
||||
return {
|
||||
mandel:E.nativeCall(1, "void(int, int)", bin),
|
||||
};
|
||||
})();
|
||||
|
||||
function drawRectangle(x1, y1, x2, y2) {
|
||||
if (frameCorner==1) g.setColor(1, 0, 0);
|
||||
else g.setColor(1, 1, 1);
|
||||
g.drawLine(x1, y1, x2, y1).drawLine(x1, y1, x1, y2);
|
||||
if (frameCorner==2) g.setColor(1, 0, 0);
|
||||
else g.setColor(1, 1, 1);
|
||||
g.drawLine(x1, y2, x2, y2).drawLine(x2, y1, x2, y2);
|
||||
}
|
||||
|
||||
function restoreRow(y) {
|
||||
mimg.width = 240;
|
||||
mimg.height = 1;
|
||||
aux[4] = 0;
|
||||
aux[5] = y;
|
||||
aux[6] = 240;
|
||||
aux[7] = y+1;
|
||||
c.mandel(p_aux, imbufaddr);
|
||||
g.drawImage(mimg, 0, y);
|
||||
}
|
||||
|
||||
function restoreCol(x) {
|
||||
mimg.width = 1;
|
||||
mimg.height = 240;
|
||||
aux[4] = x;
|
||||
aux[5] = 0;
|
||||
aux[6] = x+1;
|
||||
aux[7] = 240;
|
||||
c.mandel(p_aux, imbufaddr);
|
||||
g.drawImage(mimg, x, 0);
|
||||
}
|
||||
|
||||
function moveUp() {
|
||||
restoreCol(frameX1);
|
||||
restoreCol(frameX2);
|
||||
if (frameCorner==1 && frameY1>3) {
|
||||
restoreRow(frameY1);
|
||||
frameY1 -= 4;
|
||||
}
|
||||
if (frameCorner==2 && frameY2>3) {
|
||||
restoreRow(frameY2);
|
||||
frameY2 -= 4;
|
||||
}
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
|
||||
function moveDown() {
|
||||
restoreCol(frameX1);
|
||||
restoreCol(frameX2);
|
||||
if (frameCorner==1 && frameY1<235) {
|
||||
restoreRow(frameY1);
|
||||
frameY1 += 4;
|
||||
}
|
||||
if (frameCorner==2 && frameY2<235) {
|
||||
restoreRow(frameY2);
|
||||
frameY2 += 4;
|
||||
}
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
|
||||
function moveRight() {
|
||||
restoreRow(frameY1);
|
||||
restoreRow(frameY2);
|
||||
if (frameCorner==1 && frameX1<235) {
|
||||
restoreCol(frameX1);
|
||||
frameX1 += 4;
|
||||
}
|
||||
if (frameCorner==2 && frameX2<235) {
|
||||
restoreCol(frameX2);
|
||||
frameX2 += 4;
|
||||
}
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
|
||||
function moveLeft() {
|
||||
restoreRow(frameY1);
|
||||
restoreRow(frameY2);
|
||||
if (frameCorner==1 && frameX1>3) {
|
||||
restoreCol(frameX1);
|
||||
frameX1 -= 4;
|
||||
}
|
||||
if (frameCorner==2 && frameX2>3) {
|
||||
restoreCol(frameX2);
|
||||
frameX2 -= 4;
|
||||
}
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
|
||||
|
||||
function toggleFrame() {
|
||||
if (frameCorner<2) {
|
||||
frameCorner++;
|
||||
drawRectangle(frameX1, frameY1, frameX2, frameY2);
|
||||
}
|
||||
else {
|
||||
frameCorner = 0;
|
||||
var mincx = aux[0] + (aux[2]-aux[0])*frameX1/240.0;
|
||||
var maxcx = aux[0] + (aux[2]-aux[0])*frameX2/240.0;
|
||||
var mincy = aux[1] + (aux[3]-aux[1])*frameY1/240.0;
|
||||
var maxcy = aux[1] + (aux[3]-aux[1])*frameY2/240.0;
|
||||
aux[0] = mincx;
|
||||
aux[1] = mincy;
|
||||
aux[2] = maxcx;
|
||||
aux[3] = maxcy;
|
||||
drawIt();
|
||||
}
|
||||
}
|
||||
|
||||
setWatch(toggleFrame, BTN2, {repeat: true});
|
||||
setWatch(moveUp, BTN1, {repeat: true});
|
||||
setWatch(moveDown, BTN3, {repeat: true});
|
||||
Bangle.on('touch', function(button) {
|
||||
switch(button) {
|
||||
case 1: moveLeft(); break;
|
||||
case 2: moveRight(); break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function drawIt() {
|
||||
aux[4] = 0;
|
||||
aux[5] = 0;
|
||||
aux[6] = 240;
|
||||
aux[7] = buf_height;
|
||||
mimg.width = 240;
|
||||
mimg.height = buf_height;
|
||||
for (var y=0; y<240/buf_height; ++y) {
|
||||
c.mandel(p_aux, imbufaddr);
|
||||
aux[5] += buf_height;
|
||||
aux[7] += buf_height;
|
||||
g.drawImage(mimg, 0, y*buf_height);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(drawIt, 50);
|
After Width: | Height: | Size: 353 B |
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Adjust alignment for >1 device found
|
||||
|
|
|
@ -49,19 +49,19 @@ eg. {
|
|||
*/
|
||||
function show(event) {
|
||||
g.reset().setFont("6x8");
|
||||
var y = 45 + 50*Object.keys(deviceInfo).indexOf(event.id);
|
||||
var y = 45 + 55*Object.keys(deviceInfo).indexOf(event.id);
|
||||
|
||||
g.drawString(event.id.substr(0,17),0,y);
|
||||
g.drawImage(getImgHum(),0,y+15);
|
||||
g.drawImage(getImgHum(),0,y+10);
|
||||
g.setFont("6x8",2);
|
||||
var t = (event.moisture===undefined) ? "?" : event.moisture;
|
||||
g.drawString((t+" ").substr(0,3),35,y+25,true);
|
||||
g.drawImage(getImgFert(),80,y+15);
|
||||
g.drawString((t+" ").substr(0,3),35,y+20,true);
|
||||
g.drawImage(getImgFert(),80,y+10);
|
||||
t = Math.round(event.fertility) || "?";
|
||||
g.drawString((t+" ").substr(0,3), 120, y+25, true);
|
||||
g.drawImage(getImgTemp(),160,y+15);
|
||||
g.drawString((t+" ").substr(0,3), 120, y+20, true);
|
||||
g.drawImage(getImgTemp(),160,y+10);
|
||||
t = Math.round(event.temperature) || "?";
|
||||
g.drawString((t+" ").substr(0,3), 180, y+25, true);
|
||||
g.drawString((t+" ").substr(0,3), 180, y+20, true);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
0.01: New App!
|
||||
0.02: Animate balloon intro
|
||||
0.03: BTN3 now won't restart when at the end
|
||||
0.04: Fix regression after tweaks to Storage.readJSON
|
||||
0.05: Move configuration into App/widget settings
|
||||
0.06: Move loader into welcome.boot.js
|
||||
0.07: Run again when updated
|
||||
Don't run again when settings app is updated (or absent)
|
||||
Add "Run Now" option to settings
|
||||
0.08: Don't overwrite existing settings on app update
|
||||
0.09: Allow welcome to run after a fresh install
|
||||
More useful app menu
|
||||
BTN2 now goes to menu on release
|
||||
0.10: Add birthday style
|
||||
0.11: Skip double buffering, use 240x240 size
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AU5gAEFtoxnEwXN53WAAXO5oJB42Wy26AAIueFoPXFggAD4AwEGTQiB6otBFgwAD3QvFGC5dCFxiRGGClhrdbv67BXAIuLMBIwPsIABF4OpLwXOFxjBCF6gtBw2r1mHXoXWFxqQWFwOH62rL4IeB6xeOAAIvHGBYuC6+rR4QvCXpovXw3X1i/DR4QuPR5AvKFQOs6+GF4eod4IvPd5AvLwvWLwQvCv4fBR54vURwOHF4iQCX0yOCF4aQBX0QvHSAoAN3SOSd4WyF4yQPLyhgD1YvDMCJeIFxhgCF47BN4BeHFxpgDSAiRORpAuPMIYAFGBYuaF5aSHFwQvEFqQwOeggSBLa4xNF4X+4wAC/xeCFjIADrYwGBIIvlMQiPDBAOk0gDBz2XF8BlEF4eIxADFF8lcF9n+wIrFF05bHF9AsGF9wupGAYv/F8QupGAov/F/4wOF1gA/AH4Ap"))
|
|
@ -0,0 +1,298 @@
|
|||
// exec each function from seq one after the other
|
||||
function animate(seq,period) {
|
||||
var i = setInterval(function() {
|
||||
if (seq.length) {
|
||||
var f = seq.shift();
|
||||
if (f) f();
|
||||
} else clearInterval(i);
|
||||
},period);
|
||||
}
|
||||
|
||||
// Fade in to FG color with angled lines
|
||||
function fade(col, callback) {
|
||||
var n = 0;
|
||||
function f() {
|
||||
g.setColor(col);
|
||||
for (var i=n;i<240;i+=10) {
|
||||
g.drawLine(i,0,0,i).drawLine(i,240,240,i);
|
||||
}
|
||||
g.flip();
|
||||
n++;
|
||||
if (n<10) setTimeout(f,0);
|
||||
else callback();
|
||||
}
|
||||
f();
|
||||
}
|
||||
|
||||
|
||||
var scenes = [
|
||||
function() {
|
||||
console.log("Start app");
|
||||
g.clear(1);
|
||||
eval(require("Storage").read("mywelcome.custom.js"));
|
||||
},function() {
|
||||
g.clear(1);
|
||||
g.setFont("4x6",2);
|
||||
var n=0;
|
||||
var i = setInterval(function() {
|
||||
n+=0.04;
|
||||
g.setColor(n,n,n);
|
||||
g.drawImage(Bangle.getLogo(),(240-222)/2,(240-100)/2);
|
||||
if (n>=1) {
|
||||
clearInterval(i);
|
||||
setTimeout(()=>g.drawString("Open",34,144), 500);
|
||||
setTimeout(()=>g.drawString("Hackable",34,156), 1000);
|
||||
setTimeout(()=>g.drawString("Smart Watch",34,168), 1500);
|
||||
}
|
||||
},50);
|
||||
},function() {
|
||||
var img = require("heatshrink").decompress(atob("ptRxH+qYAfvl70mj5gAC0ekvd8FkAAdz3HJAYAH4+eJXWkJJYAF0hK2vfNJaIAB5t7S3fN5/V6wAD6vOTg9SumXy2W3QAB3eXul2JdnO63XAApPEVYvAJQIACJoRQDzBLoJQ3W5/NIwr4GJohMFAAROgJYvVJQiPGABZNN3bsdvYyESwnWJSIAC3RNM3V1JjZAES4nVJSYAB4xMNJrbkE56WD5xLVdB5NbFofNJbgABJh26qREPrFXrlbAAWjFgfWJgRLaTQhMLy5KNJINhsJLDrYrD5xLC6pLa5nGTR7oLq9bJQJMKTAXWJbbnR3RLJSoRMHv4pC5rkec6SaIrBLGw2r2XW1epcoqYeJiOXJYziEsOH2RBBw7lF56Yg5nGc6FScZOGJQPX2TmDFIfVTEBMSc4hLEw5KB6+rsJMH63X6pMf5hMQzBLCq5LD1ZLEJhTlfJiWXTA2GJYpMIcwPNc2O6TAuGRIPX1igDJg/PJmyYDcgXWwxMH1ApC53XcsHAJiVYcg2HJYZME0YpC5vWJkhLNJgLlDTAeFJhF/FQfVJkG6JiGXcomyJgOrJYhMErYqD53NJj7lRzBMDcoeGJhzoBJb3GJiN1qZBCJgWyJYpNF1LigAAXAJiNSJgzlGJgt/JkZLRy9TJgeHJhznFcuSZGw5MHJomjcuhLBqdcJiSaiTChMV1CYxy5LCqdXIAWy6+rJhCalTCN2JgdYH4WHJiGpTF7kDc43W2RMJTUZLQzBLFc4mr6+GJh2jTFmXJYyaEwuyc5Sag4xLZTQmG2WFJhxNaJYZMLJZSaEJoOHTR9/Ja+6JbdTqRNETRRNF1JLV4BLcAANYI5ToK1BLYJhWYJZwABq5NoJZ91JaAABdAZNS0ZLey9SJaRNYv5KM426JZmXuxKUJrKcL0lTzBLKzBKYJrVXvfGSol7EYWXJI27zF1JLQADq5NUrgYB4wAEEIV0comXI7wAFrCcPJgYWBTIIAETIN2JYmWuhMkdSdYCgOeJgueqRLFyzhfTi9bq4TC45MF49TuuXJlpONcogAC0hKB0gHDvZMEqRMpAANSq9crlbJAYADqwRDxGk0mIA4eCTQOeveXJdYAHqxNFdAeIAAQGCrOI0oHEAGVXTRJMGvgGCwRM7TAZMHwQGCvhM1rBMERIhMGAwdZJmtSqVTwNcwJEDJg19cvIADa4d9JhANDJnSLHJgrl6AAhFFAwpZDegjn7vhMGcvwABrJAFJgjl/TQpBBI4jl/AAN8TQhHDcv4ADcJBMDvpM+IYaeDAAhL+qd9SgycEJn7iEAA18Jf7nEcv4AIrJLIcv6aMcv4ADvhMHrJJ/AAbl/c6ZM/AAt9cv7nSIv7nLcv4AHrLl/TRpJBvgnjA=="));
|
||||
g.reset();
|
||||
g.setBgColor("#6633ff");
|
||||
var y = 240, speed = 5;
|
||||
function balloon(callback) {
|
||||
y-=speed;
|
||||
var x = (240-77)/2;
|
||||
g.drawImage(img,x,y);
|
||||
g.clearRect(x,y+81,x+77,y+81+speed);
|
||||
if (y>60) setTimeout(balloon,0,callback);
|
||||
else callback();
|
||||
}
|
||||
fade("#6633ff", function() {
|
||||
balloon(function() {
|
||||
g.setColor(-1);
|
||||
g.setFont("6x8",3);
|
||||
g.setFontAlign(0,0);
|
||||
g.drawString("Welcome.",120,160);
|
||||
});
|
||||
});
|
||||
setTimeout(function() {
|
||||
var n=0;
|
||||
var i = setInterval(function() {
|
||||
n+=5;
|
||||
g.scroll(0,-5);
|
||||
if (n>170)
|
||||
clearInterval(i);
|
||||
},20);
|
||||
},3500);
|
||||
|
||||
},function() {
|
||||
g.reset();
|
||||
g.setBgColor("#ffa800");g.clear();
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,0);
|
||||
var x = 80, y = 35, h=35;
|
||||
animate([
|
||||
()=>g.drawString("Your",x,y+=h),
|
||||
()=>g.drawString("Bangle.js",x,y+=h),
|
||||
()=>g.drawString("has",x,y+=h),
|
||||
()=>g.drawString("3 buttons",x,y+=h),
|
||||
()=>{g.setFont("Vector",36);g.drawString("1",200,40);},
|
||||
()=>g.drawString("2",200,120),
|
||||
()=>g.drawString("3",200,200)
|
||||
],200);
|
||||
},
|
||||
function() {
|
||||
g.reset();
|
||||
g.setBgColor("#00a8ff");g.clear();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector",48);
|
||||
g.drawString("1",200,40);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.setFont("6x8",2);
|
||||
g.drawString("Move up\nin menus\n\nTurn Bangle.js on\nif it was off", 20,40);
|
||||
},
|
||||
function() {
|
||||
g.reset();
|
||||
g.setBgColor("#00a8ff");g.clear();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector",48);
|
||||
g.drawString("2",200,120);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.setFont("6x8",2);
|
||||
g.drawString("Select menu\nitem\n\nLaunch app\nwhen watch\nis showing", 20,70);
|
||||
},
|
||||
function() {
|
||||
g.reset();
|
||||
g.setBgColor("#00a8ff");g.clear();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector",48);
|
||||
g.drawString("3",200,200);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.setFont("6x8",2);
|
||||
g.drawString("Move down\nin menus\n\nLong press\nto exit app\nand go back\nto clock", 20,100);
|
||||
},
|
||||
function() {
|
||||
g.reset();
|
||||
g.setBgColor("#ff3300");g.clear();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector",48);
|
||||
g.drawString("1",200,40);
|
||||
g.drawString("2",200,120);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.setFont("6x8",2);
|
||||
g.drawString("If Bangle.js\never stops,\nhold buttons\n1 and 2 for\naround six\nseconds.\n\n\n\nBangle.js will\nthen reboot.", 20,20);
|
||||
},
|
||||
function() {
|
||||
g.reset();
|
||||
g.setBgColor("#00a8ff");g.clear();
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,0);
|
||||
var x = 120, y = 10, h=21;
|
||||
animate([
|
||||
()=>{g.drawString("Bangle.js has a",x,y+=h);
|
||||
g.drawString("simple touchscreen",x,y+=h);},
|
||||
0,0,
|
||||
()=>{g.drawString("It'll detect touch",x,y+=h*2);
|
||||
g.drawString("on left and right",x,y+=h);},
|
||||
0,0,
|
||||
()=>{g.drawString("Horizontal swipes",x,y+=h*2);
|
||||
g.drawString("work too. Try now",x,y+=h);
|
||||
g.drawString("to change page.",x,y+=h);}
|
||||
],300);
|
||||
},
|
||||
function() {
|
||||
g.reset();
|
||||
g.setBgColor("#339900");g.clear();
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,0);
|
||||
var x = 120, y = 10, h=21;
|
||||
animate([
|
||||
()=>{g.drawString("Bangle.js",x,y+=h);
|
||||
g.drawString("comes with",x,y+=h);
|
||||
g.drawString("a few simple",x,y+=h);
|
||||
g.drawString("apps installed",x,y+=h);},
|
||||
0,0,
|
||||
()=>{g.drawString("To add more, visit",x,y+=h*2);
|
||||
g.drawString("banglejs.com/apps",x,y+=h);
|
||||
g.drawString("with a Bluetooth",x,y+=h);
|
||||
g.drawString("capable device",x,y+=h);},
|
||||
],400);
|
||||
},
|
||||
function() {
|
||||
g.reset();
|
||||
g.setBgColor("#990066");g.clear();
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,0);
|
||||
var x = 120, y = 10, h=21;
|
||||
g.drawString("You can also make",x,y+=h);
|
||||
g.drawString("your own apps!",x,y+=h);
|
||||
y=160;
|
||||
g.drawString("Check out",x,y+=h);
|
||||
g.drawString("banglejs.com",x,y+=h);
|
||||
|
||||
var rx = 0, ry = 0;
|
||||
var h = Graphics.createArrayBuffer(96,96,1,{msb:true});
|
||||
// draw a cube
|
||||
function draw() {
|
||||
// rotate
|
||||
rx += 0.1;
|
||||
ry += 0.11;
|
||||
var rcx=Math.cos(rx),
|
||||
rsx=Math.sin(rx),
|
||||
rcy=Math.cos(ry),
|
||||
rsy=Math.sin(ry);
|
||||
// Project 3D coordinates into 2D
|
||||
function p(x,y,z) {
|
||||
var t;
|
||||
t = x*rcy + z*rsy;
|
||||
z = z*rcy - x*rsy;
|
||||
x=t;
|
||||
t = y*rcx + z*rsx;
|
||||
z = z*rcx - y*rsx;
|
||||
y=t;
|
||||
z += 4;
|
||||
return [96*(0.5+x/z), 96*(0.5+y/z)];
|
||||
}
|
||||
|
||||
var a;
|
||||
// draw a series of lines to make up our cube
|
||||
h.clear();
|
||||
a = p(-1,-1,-1); h.moveTo(a[0],a[1]);
|
||||
a = p(1,-1,-1); h.lineTo(a[0],a[1]);
|
||||
a = p(1,1,-1); h.lineTo(a[0],a[1]);
|
||||
a = p(-1,1,-1); h.lineTo(a[0],a[1]);
|
||||
a = p(-1,-1,-1); h.lineTo(a[0],a[1]);
|
||||
a = p(-1,-1,1); h.moveTo(a[0],a[1]);
|
||||
a = p(1,-1,1); h.lineTo(a[0],a[1]);
|
||||
a = p(1,1,1); h.lineTo(a[0],a[1]);
|
||||
a = p(-1,1,1); h.lineTo(a[0],a[1]);
|
||||
a = p(-1,-1,1); h.lineTo(a[0],a[1]);
|
||||
a = p(-1,-1,-1); h.moveTo(a[0],a[1]);
|
||||
a = p(-1,-1,1); h.lineTo(a[0],a[1]);
|
||||
a = p(1,-1,-1); h.moveTo(a[0],a[1]);
|
||||
a = p(1,-1,1); h.lineTo(a[0],a[1]);
|
||||
a = p(1,1,-1); h.moveTo(a[0],a[1]);
|
||||
a = p(1,1,1); h.lineTo(a[0],a[1]);
|
||||
a = p(-1,1,-1); h.moveTo(a[0],a[1]);
|
||||
a = p(-1,1,1); h.lineTo(a[0],a[1]);
|
||||
g.drawImage({width:96,height:96,buffer:h.buffer},(240-96)/2,68);
|
||||
}
|
||||
|
||||
setInterval(draw,50);
|
||||
},
|
||||
function() {
|
||||
g.reset();
|
||||
g.setBgColor("#660099");g.clear();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector",36);
|
||||
g.drawString("2",200,120);
|
||||
g.setFont("6x8",2);
|
||||
|
||||
var x = 90, y = 30, h=21;
|
||||
animate([
|
||||
()=>g.drawString("That's it!",x,y+=h),
|
||||
()=>{g.drawString("Press",x,y+=h*3);
|
||||
g.drawString("Button 2",x,y+=h);
|
||||
g.drawString("to start",x,y+=h);
|
||||
g.drawString("Bangle.js",x,y+=h);}
|
||||
],400);
|
||||
}
|
||||
];
|
||||
|
||||
var sceneNumber = 0;
|
||||
|
||||
function move(dir) {
|
||||
if (dir>0 && sceneNumber+1 == scenes.length) return; // at the end
|
||||
sceneNumber = (sceneNumber+dir)%scenes.length;
|
||||
if (sceneNumber<0) sceneNumber=0;
|
||||
clearInterval();
|
||||
Bangle.setLCDMode();
|
||||
g.clear();
|
||||
scenes[sceneNumber]();
|
||||
if (sceneNumber>2) {
|
||||
var l = scenes.length;
|
||||
for (var i=0;i<l-2;i++) {
|
||||
var x = 120+(i-(l-2)/2)*12;
|
||||
if (i<sceneNumber-1) {
|
||||
g.setColor(-1);
|
||||
g.fillCircle(x,230,4);
|
||||
} else {
|
||||
g.setColor(0);
|
||||
g.fillCircle(x,230,4);
|
||||
g.setColor(-1);
|
||||
g.drawCircle(x,230,4);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sceneNumber < scenes.length-1)
|
||||
setTimeout(function() {
|
||||
move(1);
|
||||
}, (sceneNumber==0) ? 20000 : 5000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Bangle.on('swipe',move);
|
||||
setWatch(()=>move(1), BTN3, {repeat:true});
|
||||
setWatch(()=>{
|
||||
// If we're on the last page
|
||||
if (sceneNumber == scenes.length-1) {
|
||||
load();
|
||||
}
|
||||
}, BTN2, {repeat:true,edge:"falling"});
|
||||
setWatch(()=>move(-1), BTN1, {repeat:true});
|
||||
|
||||
Bangle.setLCDTimeout(0);
|
||||
Bangle.setLCDPower(1);
|
||||
move(0);
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,9 @@
|
|||
(function() {
|
||||
let s = require('Storage').readJSON('mywelcome.json', 1) || {};
|
||||
if (!s.welcomed) {
|
||||
setTimeout(() => {
|
||||
require('Storage').write('mywelcome.json', {welcomed: true})
|
||||
load('mywelcome.app.js')
|
||||
})
|
||||
}
|
||||
})()
|
|
@ -0,0 +1,137 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="form-group">
|
||||
<p>Style: <select class="form-select" id="style">
|
||||
<option>Christmas</option>
|
||||
<option>Birthday</option>
|
||||
</select>
|
||||
</div></p>
|
||||
<p>Line 1: <input type="text" id="line1" class="form-input" value="Merry Christmas"></p>
|
||||
<p>Line 2: <input type="text" id="line2" class="form-input" value="Someone"></p>
|
||||
<p>Line 3 (smaller): <input type="text" id="line3" class="form-input" value="Love from"></p>
|
||||
<p>Line 4 (smaller): <input type="text" id="line4" class="form-input" value="Espruino Team"></p>
|
||||
</div>
|
||||
<p><button id="try" class="btn">Try in Emulator</button></p>
|
||||
<p><button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
<p>This is currently Christmas-themed, but more themes will be added in the future.</p>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
function getApp() {
|
||||
// get the text to add
|
||||
var line1 = document.getElementById("line1").value;
|
||||
var line2 = document.getElementById("line2").value;
|
||||
var line3 = document.getElementById("line3").value;
|
||||
var line4 = document.getElementById("line4").value;
|
||||
var style = document.getElementById("style").value;
|
||||
// build the app's text using a templated String
|
||||
if (style=="Birthday") return `(function() {
|
||||
var ib = require("heatshrink").decompress(atob("jk0ggGDhOZAAWQCYwMEBxAMFAAIaHyc/+c5DgwMC/84Dg4aCBgwcDBoOf+Y4GBoQEBn4zCI44DBDQ4NEyf4BpgoIBoefxINMBhApEBrQAKBrrrGWpANZHBT7FBpYqIFAYcJBggNOFQwoFDgwMHBwoMIBwYMKBrkykANLmcwBu0zBrMDBv4AFN5gA/ADY"));
|
||||
var ir = require("heatshrink").decompress(atob("jk0ggGDhvdAAXQCYwMEBxAMFAAIaH6c/+c9DgwMC/8zDg4aC/4YCHIwNB7/zHAwNCAgM/DQwqDAYIaHBonT/oNMFBAND74NNBhApEBrQAKBrrrGWpANZHBT7FBpYqIFAYcJBgkA5oMF7gNFFQwoFDgwMHHIoMIAAPM5gMKBrk0oANLmcwBu0zBrMDBv4AFN5gA/ADYA="));
|
||||
var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/+c3DgwMC/8yDg4aC/4YCHIwNBv/zHAwNCAgM/DQwqDAYIaHBolz+4NMFBANDv8nBpgMIFIgNaABQNddYy1IBrI4KfYoNLFRAoDDhIMEgHnBgt+BooqGFAoqGBg4OFBhAODBhQNcmUgBpczmAN2mYNZgYN/AApvMAH4Ab"));
|
||||
var igift = require("heatshrink").decompress(atob("q1QxH+ADOi0QbZ5nMHDQAbKgIACKa4ACKnJWVKghW0KgxWTKgxWyKhBWRKhBWwKhRWPKhRWuKhhWNKhhWtKpxWKKhys8KxBU8Ky5U+KypU/KyhU/KyhU/KynGKn5WTKn5WUKmHCADpJJE7uYABZUfKuuYKv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/AAv+Kv5VT/wADyIAaKpIlbABZSEKv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/ADNtKv6rdKzZVwKhAABy5V/Khw"));
|
||||
|
||||
var W=240,H=240;
|
||||
var blns = [];
|
||||
function updateFlake(f) {
|
||||
f.im = [ir,ig,ib][Math.round(Math.random()*100)%3];
|
||||
f.s = 0.4+Math.random()*0.5;
|
||||
}
|
||||
|
||||
for (var i=0;i<6;i++) {
|
||||
var f = {
|
||||
y:Math.random()*H,x:(0.5+(i<3?i:i+5))*W/11,
|
||||
v:0.5+Math.random(),r:0,
|
||||
t:(0.5+Math.random())*0.15,
|
||||
};
|
||||
updateFlake(f);
|
||||
blns.push(f);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
blns.forEach(f=>{
|
||||
f.y-=f.v;f.r+=f.t;
|
||||
if (f.y<-22) {
|
||||
f.y=H+22;
|
||||
updateFlake(f);
|
||||
}
|
||||
g.drawImage(f.im,f.x,f.y,{rotate:Math.sin(f.r)/2,scale:f.s});
|
||||
});
|
||||
var x = W/2, y = H/2;
|
||||
g.drawImage(igift,x-43,y-80);
|
||||
g.setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString(${JSON.stringify(line1)},x,y+=20);
|
||||
g.drawString(${JSON.stringify(line2)},x,y+=20);
|
||||
g.setFont("6x8");
|
||||
g.drawString(${JSON.stringify(line3)},x,y+=20);
|
||||
g.drawString(${JSON.stringify(line4)},x,y+=10);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
g.clear();
|
||||
setInterval(draw,50);
|
||||
})()`;
|
||||
// if (style=="Christmas")
|
||||
return `(function() {
|
||||
var isnow = require("heatshrink").decompress(atob("jEagQWTgfAAocf+gFDh4FDiARBggVB3AFBl3Agf8jfkn/AgX/v/9/+Agfv/2//YrBgfwh4wCgfghYFJCIYdFFIw1EIIpNFL44FFOIoAP"));
|
||||
var itree = require("heatshrink").decompress(atob("mtWxH+ADHHDTI0aGuXH5vNGmhqvTYIzBGtoxF6fTG4g4oGgQyBAAZssGoI0Ga1g1FGdo01ZgIAEGmHHNoLSuAAN/rdb0YFBGlgCBGYIABA4YArGYY1CGn4znAAM6GeVd5PQ5Iyurc/vQ0oGZFAn+d4XC3d5GddiGYIEBy+7zoEBGlFhoEcsQ9GT08+oFk1mkGdaVBMgNArnJ6/KzswGs/J6GlrlbqtbvPC5PCy8wGohniMIPJvIpCqmX3e7vI0BqhqlMIY0DqhtBqoEBa0xgBMIIoEqoABGQwzfsIhBv4qHABM50vQGjg1CGaN66DoBGt1ioGd5LoBGjo1PGYNhvLoCa7wnBqgvGA4YzCAgN5GUAsCqoDBmAHCAYU/wPQ0oSDGcBiDqkwAYcxoFd5PX6GdGjrIIqtUAAc3jk5vPC4fCy5pef5I2BTQMcnAHBy+7y95T0oADnFk1ekBpI2aGRUin7NGAA9hsIzVsIgHTAKZBZoPJ5LNDGhBpXGolcwOsrtcA4TNB3bNDGb/+sVin9AoGe6HX5InEvN/TkP+5XQwM/sRsBzqWB4QuKGjvC6HQ4QdDvKWBZYMwmAuHmFUCYNbqibX3fD5O7qolEZQQ0FBwgKDqgJBGiphEDwNUEgJbBFIQqCAgYOCB4IzCnE6GyhYFGoQnDABYzGAAQ1UAAo2NBoQSBnOB0t/Gjo2EABIPCoGe6HX4QzTGRIAEqtVF4QEBBQc4oE4y/J5PCvIxeABk/oADBvO73eXTyAyZMwM/Awd5vIOFGslAr2Av4PLNcU/jmA6HX5I1KasFcn8dTIOd5PJ4SZGGiNhAAIyNn0ckU+ZYe7AAJpJEYJnNGZk+n9kw9cBAcwGoN5aZg1JJJQABm8/oEjoDKC5ALCrUwqh/NrvQ6HDGp04n9doEdoE/sQJBZQZhCqgABGZk6zw0K/1dnVAoNAFwOlCYL1FubJBy4GCGh1AnOX4XC3YzHFYOeCgdV5PQ5OdD4rKBqqYNGYlbv+X3edGY3CGgKMDAAO7JAJgDAClcr2BEYgADaIZ0DL4uXGbDuB6HX5I1GsP+sNhOgWXIhBmWd4Od5PK4TwFGIJoBAYI2BAD0/jlcQoO7AAJaEGQQADGr0/sjNEvOdAoZmDGgw2ZsVAkeAZpQACGZI2VsU/kVGn1bZoPJZogpGGhA4GfRYwBoGC1mlBQbNFFoo0JNxAGCEod/wM6oFAn9iv/J6/Kzo1Ey9/MZQAKCg4GCFgTDEvPCSwI0BC5I0RN4ocEYYPQ5OdHgeXSwTFKGaJyKFYPC3f+MIdbpzFLAD4zB/1OqtbqtOGgYArGAIADGl9UAAI0wGQN5GoQ0vvIABGoI0uGYQABqo0zNOg0uaQY0/GllOGn40//w="));
|
||||
|
||||
var W=g.getWidth(),H=g.getHeight();
|
||||
var flakes = [];
|
||||
for (var i=0;i<10;i++) {
|
||||
var f = {
|
||||
y:Math.random()*H,
|
||||
x:(0.5+(i<5?i:i+5))*W/15,
|
||||
v:0.7+Math.random(),
|
||||
s:0.6+Math.random(),
|
||||
r:0,
|
||||
t:0.1*(Math.random()-0.5)
|
||||
};
|
||||
f.v = f.s * (1+Math.random());
|
||||
flakes.push(f);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
flakes.forEach(f=>{
|
||||
f.y+=f.v;f.r+=f.t;
|
||||
if (f.y>H+16) f.y=-8;
|
||||
g.drawImage(isnow,f.x,f.y,{rotate:f.r,scale:f.s});
|
||||
});
|
||||
var x = W/2, y = H/2;
|
||||
g.drawImage(itree,x-27,y-80);
|
||||
g.setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString(${JSON.stringify(line1)},x,y+=20);
|
||||
g.drawString(${JSON.stringify(line2)},x,y+=20);
|
||||
g.setFont("6x8");
|
||||
g.drawString(${JSON.stringify(line3)},x,y+=20);
|
||||
g.drawString(${JSON.stringify(line4)},x,y+=10);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
g.clear();
|
||||
setInterval(draw,50);
|
||||
})();
|
||||
`;
|
||||
}
|
||||
// when 'try' is clicked, load the emulator...
|
||||
document.getElementById("try").addEventListener("click", function() {
|
||||
window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload");
|
||||
});
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
// send finished app (in addition to contents of app.json)
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"mywelcome.custom.js", url:"app.js", content:getApp()},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
(function(back) {
|
||||
let settings = require('Storage').readJSON('mywelcome.json', 1)
|
||||
|| require('Storage').readJSON('setting.json', 1) || {}
|
||||
E.showMenu({
|
||||
'': { 'title': 'Welcome App' },
|
||||
'Run next boot': {
|
||||
value: !settings.welcomed,
|
||||
format: v => v ? 'Yes' : 'No',
|
||||
onchange: v => require('Storage').write('mywelcome.json', {welcomed: !v}),
|
||||
},
|
||||
'Run Now': () => load('mywelcome.app.js'),
|
||||
'Turn off & run next': () => {
|
||||
require('Storage').write('mywelcome.json', {welcomed: false});
|
||||
Bangle.off();
|
||||
},
|
||||
'< Back': back,
|
||||
})
|
||||
})
|
|
@ -0,0 +1 @@
|
|||
0.01: A copy of the analogimgclk to work for NodeConf Remote
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkGswA/AFEiAAMoCqcykWEDQQWW0YYNsQXCn8//8zDgMiwwWNmf/CwICCDAUmIpYWD+YYFkIuKkYTBCogGCmUhGBAuBn4QBF4wJBiR6IFwYRCFoYBCVZBGBRQIYFFwaUBkUWFw4XKBIUhGAwXBEwYXFmcTBIMxC4pGBUgQXCLYc/kMvAgKqBSIheGGInyiQGCn8SC43zCwouDHQfzF4x2DFAgFCCwbaBSAi9CAAPxiMTRIcvEQIYCeQIXI+chiMSn8zGgJeDn8yiQXHBoMzDAMRiEzCwgXBF5IPCCwMQCoZUDYAhHFCQUBgIHFF5YRDkMDCwpfKAAn074UDC5QOHC48xL4jvDF5kznFGC4ovOmciwwXFWwIACB4M0C48hC4x4EC44kB+UYI4h4DGIhHEBIUyjAWEC4JIEF4VPF4shlAXFJAYQD+gXBEAcziReEGAg/F74ECBIIuHC4UhCAIZC+UzOokhkIXHJAMTDAQCGmOEkwXHGASSDAQk4oUSCxAwCiUjC4sooUhFxIYCkMilEzCwMymIgBRg5JGiUiwoDBlUijFCCxgYDIQIXCRZAAJJQIWBCqIA/AC4="))
|
|
@ -0,0 +1,129 @@
|
|||
var locale = require("locale");
|
||||
|
||||
var bgimg = {
|
||||
width : 100, height : 100, bpp : 4,
|
||||
buffer : require("heatshrink").decompress(atob("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"))
|
||||
}
|
||||
|
||||
function getImg(g, col) {
|
||||
return {
|
||||
width:g.getWidth(),
|
||||
height:g.getHeight(),
|
||||
bpp:1,transparent:0,
|
||||
buffer:g.buffer,
|
||||
palette:new Uint16Array([0,col])};
|
||||
}
|
||||
|
||||
var handSizeMin = 40;
|
||||
var handSizeHr = 25;
|
||||
var handSizeSec = 50;
|
||||
var gmin = Graphics.createArrayBuffer(6,handSizeMin*2,1,{msb:true});
|
||||
var gminimg = getImg(gmin, 0xFFFF);
|
||||
var ghr = Graphics.createArrayBuffer(8,handSizeHr*2,1,{msb:true});
|
||||
var ghrimg = getImg(ghr, g.setColor("#E0E0E0").getColor());
|
||||
var gsec = Graphics.createArrayBuffer(2,handSizeSec*2,1,{msb:true});
|
||||
var gsecimg = getImg(gsec, g.setColor("#FF0000").getColor());
|
||||
var lastDate;
|
||||
|
||||
// create hand images
|
||||
var c = gmin.getHeight()/2;
|
||||
var o = 8; // overhang
|
||||
gmin.fillCircle(2,2,2);
|
||||
gmin.fillCircle(2,c+o,2);
|
||||
gmin.fillRect(0,2,4,c+o);
|
||||
c = ghr.getHeight()/2;
|
||||
ghr.fillCircle(4,4,4);
|
||||
ghr.fillCircle(4,c+o,4);
|
||||
ghr.fillRect(0,4,7,c+o);
|
||||
c = gsec.getHeight()/2;
|
||||
gsec.fillRect(0,1,2,c+o);
|
||||
|
||||
// last positions of hands (in radians)
|
||||
var lastrmin=0, lastrhr=0, lastrsec=0;
|
||||
|
||||
// draw hands - just the bit of the image that changed
|
||||
function drawHands(full) {
|
||||
var d = new Date();
|
||||
var rsec = d.getSeconds()*Math.PI/30;
|
||||
var rmin = d.getMinutes()*Math.PI/30;
|
||||
// hack so hour hand only moves every 10 minutes
|
||||
var rhr = (d.getHours() + Math.round(d.getMinutes()/10)/6)*Math.PI/6;
|
||||
var bounds = {};
|
||||
if (!full) { // work out the bounds of the hands
|
||||
var x1 = (g.getWidth()/2)-10;
|
||||
var y1 = (g.getHeight()/2)-10 - 36;
|
||||
var x2 = (g.getWidth()/2)+10;
|
||||
var y2 = (g.getHeight()/2)+10 - 36;
|
||||
function addPt(ang, r, ry) {
|
||||
var x = (g.getWidth()/2) + Math.sin(ang)*r + Math.cos(ang)*ry;
|
||||
var y = (g.getHeight()/2) - Math.cos(ang)*r + Math.sin(ang)*ry - 36;
|
||||
//g.setColor("#ff0000").fillRect(x-2,y-2,x+2,y+2);
|
||||
if (x<x1)x1=x;
|
||||
if (y<y1)y1=y;
|
||||
if (x>x2)x2=x;
|
||||
if (y>y2)y2=y;
|
||||
}
|
||||
function addSec(r) {
|
||||
addPt(r,handSizeSec+5,5);addPt(r,handSizeSec+5,-5);
|
||||
addPt(r,-(o+10),5);addPt(r,-(o+10),-5);
|
||||
}
|
||||
function addMin(r) {
|
||||
addPt(r,handSizeMin,5);addPt(r,handSizeMin,-5);
|
||||
addPt(r,-(o+8),5);addPt(r,-(o+8),-5);
|
||||
}
|
||||
function addHr(r) {
|
||||
addPt(r,handSizeHr,8);addPt(r,handSizeHr,-8);
|
||||
addPt(r,-(o+8),8);addPt(r,-(o+8),-8);
|
||||
}
|
||||
if (rsec!=lastrsec) {
|
||||
addSec(rsec);addSec(lastrsec);
|
||||
}
|
||||
if (rmin!=lastrmin) {
|
||||
addMin(rmin);addMin(lastrmin);
|
||||
}
|
||||
if (rhr!=lastrhr) {
|
||||
addHr(rhr);addHr(lastrhr);
|
||||
}
|
||||
bounds = {x:x1,y:y1,width:1+x2-x1,height:1+y2-y1};
|
||||
}
|
||||
|
||||
g.drawImages([
|
||||
{image:bgimg,x:20,y:25,scale:2},
|
||||
{image:ghrimg,x:120,y:120-31,center:true,rotate:rhr},
|
||||
{image:gminimg,x:120,y:120-31,center:true,rotate:rmin},
|
||||
{image:gsecimg,x:120,y:120-31,center:true,rotate:rsec}
|
||||
],bounds);
|
||||
lastrsec = rsec;
|
||||
lastrmin = rmin;
|
||||
lastrhr = rhr;
|
||||
|
||||
// Date
|
||||
var date = locale.date(new Date(),false);
|
||||
if (date === lastDate) return;
|
||||
lastDate = date;
|
||||
g.reset();
|
||||
g.setFont("6x8");
|
||||
g.setFontAlign(0,-1);
|
||||
g.drawString(date, g.getWidth()/2, 232, true);
|
||||
}
|
||||
|
||||
var secondInterval = setInterval(drawHands,1000);
|
||||
// handle display switch on/off
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (secondInterval) {
|
||||
clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
}
|
||||
if (on) {
|
||||
drawHands();
|
||||
secondInterval = setInterval(drawHands,1000);
|
||||
}
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawHands(true);
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
After Width: | Height: | Size: 5.8 KiB |
|
@ -1,3 +1,4 @@
|
|||
0.01: New Library!
|
||||
0.02: Add notification ID option
|
||||
0.03: Pass `area{x,y,w,h}` to render callback instead of just `y`
|
||||
0.05: Adjust position of notification src text
|
||||
|
|
|
@ -94,8 +94,8 @@ exports.show = function(options) {
|
|||
g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 2);
|
||||
g.drawString(title.trim().substring(0, 13), x+25,y+3);
|
||||
if (options.title && options.src) {
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString(options.src.substring(0, 10), x+215,y+5);
|
||||
g.setFont("6x8", 1).setFontAlign(1, 1, 0);
|
||||
g.drawString(options.src.substring(0, 10), g.getWidth()-23,y+18);
|
||||
}
|
||||
y += 20;h -= 20;
|
||||
}
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Fix custom render callback
|
||||
0.04: Pass `area{x,y,w,h}` to render callback instead of just `y`
|
||||
0.05: Fix `g` corruption issue if .hide gets called twice
|
||||
0.06: Adjust position of notification src text and notifications without title
|
||||
|
|
|
@ -49,14 +49,13 @@ exports.show = function(options) {
|
|||
if (size>120) {size=120}
|
||||
Bangle.setLCDMode("direct");
|
||||
let x = 0,
|
||||
y = 0,
|
||||
y = 40,
|
||||
w = 240,
|
||||
h = 240;
|
||||
h = size;
|
||||
// clear screen
|
||||
g.clear(1);
|
||||
// top bar
|
||||
if (options.title||options.src) {
|
||||
y=40;h=size;
|
||||
const title = options.title || options.src
|
||||
g.setColor(0x39C7).fillRect(x, y, x+w-1, y+30);
|
||||
g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 3);
|
||||
|
@ -64,7 +63,8 @@ exports.show = function(options) {
|
|||
if (options.title && options.src) {
|
||||
g.setColor(-1).setFontAlign(1, 1, 0).setFont("6x8", 2);
|
||||
// above drawing area, but we are fullscreen
|
||||
g.drawString(options.src.substring(0, 10), x+235, y-32);
|
||||
print(options.src.substring(0, 10), w-23, y-4);
|
||||
g.drawString(options.src.substring(0, 10), w-16, y-4);
|
||||
}
|
||||
y += 30;h -= 30;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Ensure eyes are white even if a widget (eg Bluetooth) changes the draw color
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("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"))
|
After Width: | Height: | Size: 4.1 KiB |
|
@ -1 +1,2 @@
|
|||
0.01: First release
|
||||
0.02: Colour changes dependent on roll result
|
||||
|
|
|
@ -14,12 +14,18 @@ function getDie() {
|
|||
}
|
||||
|
||||
function setColors(lastBounce) {
|
||||
if (lastBounce) {
|
||||
bgColor = 0xFFFF;
|
||||
if (lastBounce && face == getDie()) {
|
||||
bgColor = 0x0000; // Critical Hit
|
||||
fgColor = 0xF800;
|
||||
} else if (lastBounce && face == 1){
|
||||
bgColor = 0xF800; // Critical Miss
|
||||
fgColor = 0x0000;
|
||||
} else {
|
||||
bgColor = 0x0000
|
||||
} else if (lastBounce){
|
||||
bgColor = 0x0000; // Other Result
|
||||
fgColor = 0xFFFF;
|
||||
} else {
|
||||
bgColor = 0x0000; // Still Rolling
|
||||
fgColor = 0x7BEF;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,3 +24,4 @@
|
|||
0.20: Fix set time menu, allow dates to roll over
|
||||
0.21: Add passkey pairing option (BETA)
|
||||
Add whitelist option (fix #78)
|
||||
0.22: Move HID to BLE menu
|
||||
|
|