forked from FOSS/BangleApps
Merge branch 'master' of github.com:espruino/BangleApps
commit
bc638fead3
25
apps.json
25
apps.json
|
@ -82,7 +82,7 @@
|
|||
{
|
||||
"id": "health",
|
||||
"name": "Health Tracking",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,health",
|
||||
|
@ -3467,7 +3467,7 @@
|
|||
"id": "widgps",
|
||||
"name": "GPS Widget",
|
||||
"version": "0.03",
|
||||
"description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later",
|
||||
"description": "Tiny widget to show the power on/off status of the GPS",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,gps",
|
||||
|
@ -3481,7 +3481,7 @@
|
|||
"id": "widhrt",
|
||||
"name": "HRM Widget",
|
||||
"version": "0.03",
|
||||
"description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later",
|
||||
"description": "Tiny widget to show the power on/off status of the Heart Rate Monitor",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,hrm",
|
||||
|
@ -3524,7 +3524,7 @@
|
|||
"id": "widcom",
|
||||
"name": "Compass Widget",
|
||||
"version": "0.02",
|
||||
"description": "Tiny widget to show the power on/off status of the Compass. Requires firmware v2.08.167 or later",
|
||||
"description": "Tiny widget to show the power on/off status of the Compass",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,compass",
|
||||
|
@ -3665,7 +3665,7 @@
|
|||
"id": "kitchen",
|
||||
"name": "Kitchen Combo",
|
||||
"version": "0.13",
|
||||
"description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later",
|
||||
"description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'",
|
||||
"icon": "kitchen.png",
|
||||
"type": "clock",
|
||||
"tags": "tool,outdoors,gps",
|
||||
|
@ -4215,5 +4215,20 @@
|
|||
{ "name": "qalarm.wid.js", "url": "widget.js" }
|
||||
],
|
||||
"data": [{ "name": "qalarm.json" }]
|
||||
},
|
||||
{
|
||||
"id": "emojuino",
|
||||
"name": "Emojuino",
|
||||
"shortName": "Emojuino",
|
||||
"version": "0.01",
|
||||
"description": "Emojis & Espruino: broadcast Unicode emojis via Bluetooth Low Energy.",
|
||||
"icon": "emojuino.png",
|
||||
"tags": "emoji",
|
||||
"supports" : [ "BANGLEJS2" ],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "emojuino.app.js", "url": "emojuino.js" },
|
||||
{ "name": "emojuino.img", "url": "emojuino-icon.js", "evaluate": true }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,28 @@
|
|||
# Emojuino
|
||||
|
||||
Emojis & Espruino!
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Select an emoji and then tap to transmit! The emoji will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the [InteroperaBLE Identifier](https://reelyactive.github.io/interoperable-identifier/) open standard.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
Currently implements a tiny subset of possible [Unicode emojis](https://unicode.org/emoji/charts/full-emoji-list.html) which are advertised as an [InteroperaBLE Identifier](https://reelyactive.github.io/interoperable-identifier/) encapsulated as Eddystone UID.
|
||||
|
||||
|
||||
## Controls
|
||||
|
||||
Swipe left/right to select the emoji to broadcast. Tap the screen to initiate the broadcast. Emoji will flash while broadcasting, which lasts for 5 seconds.
|
||||
|
||||
|
||||
## Requests
|
||||
|
||||
[Contact reelyActive](https://www.reelyactive.com/contact/) for support/updates.
|
||||
|
||||
|
||||
## Creator
|
||||
|
||||
Developed by [jeffyactive](https://github.com/jeffyactive) of [reelyActive](https://www.reelyactive.com)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBiIAHkUoxGIwUiBxAAGiQVCAAeCkIWNCooADDBYWKDBYWEkc////+cyDBhxDCoQAD+YLDCw0YBQQVFAAYYCwIXFHQRDElGCJYgOCFw8vBwPyOgoJFGAg4BIoQWGDAhJCIwoLBHgYAGJQIjCIwguCnCRFRoeDGAZICAgOPFwaRGDAQfB/AwDBAYuCX44wDAgTrDBoIDBGYP/manBmYFBFYQPDwJeBD4iRGRoQ/FC4QqBEYIbERooTBCAeBNAIjBBQIDDAAggBG4IDDwQXBEQIDDUAgcCHASaBAYQTFMQpcFDYp+EEII9DAARRDFIIfDHIwXBVISlDC4YzD9wA0osFpwIF8lQqgWK8kAgEEBItABIIhGAAfgBoMABIoIChwX0jwED8oNBgoXFqAJBrwHD8IXEBwQNEEIYgFC4wAQ8MRC6sRC+BgULwIwHSINVpwuLC43kaAQABqgaHC4bZHAAkFqhGHGAovFAAYyDCwgwFL4IwGFxAwNFxIwG8lVCoSTEFw7bPCxAYNCxT0LIpIxMCpoyHFhI"))
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* Copyright reelyActive 2021
|
||||
* We believe in an open Internet of Things
|
||||
*/
|
||||
|
||||
|
||||
// Emojis are integer pairs with the form [ image, Unicode code point ]
|
||||
// For code points see https://unicode.org/emoji/charts/emoji-list.html
|
||||
const EMOJIS = [
|
||||
[ ':)', 0x1f642 ], // Slightly smiling
|
||||
[ ':|', 0x1f610 ], // Neutral
|
||||
[ ':(', 0x1f641 ], // Slightly frowning
|
||||
[ '+1', 0x1f44d ], // Thumbs up
|
||||
[ '-1', 0x1f44e ], // Thumbs down
|
||||
[ '<3', 0x02764 ], // Heart
|
||||
];
|
||||
const EMOJI_TRANSMISSION_MILLISECONDS = 5000;
|
||||
const BLINK_PERIOD_MILLISECONDS = 500;
|
||||
const TRANSMIT_BUZZ_MILLISECONDS = 200;
|
||||
const CYCLE_BUZZ_MILLISECONDS = 50;
|
||||
|
||||
// Non-user-configurable constants
|
||||
const IMAGE_INDEX = 0;
|
||||
const CODE_POINT_INDEX = 1;
|
||||
const BTN_WATCH_OPTIONS = { repeat: true, debounce: 20, edge: "falling" };
|
||||
const UNICODE_CODE_POINT_ELIDED_UUID = [ 0x49, 0x6f, 0x49, 0x44, 0x55,
|
||||
0x54, 0x46, 0x2d, 0x33, 0x32 ];
|
||||
|
||||
|
||||
// Global variables
|
||||
let emojiIndex = 0;
|
||||
let isToggleOn = false;
|
||||
let isTransmitting = false;
|
||||
let lastDragX = 0;
|
||||
let lastDragY = 0;
|
||||
|
||||
|
||||
// Cycle through emojis
|
||||
function cycleEmoji(isForward) {
|
||||
if(isTransmitting) { return; }
|
||||
|
||||
if(isForward) {
|
||||
emojiIndex = (emojiIndex + 1) % EMOJIS.length;
|
||||
}
|
||||
else if(--emojiIndex < 0) {
|
||||
emojiIndex = EMOJIS.length - 1;
|
||||
}
|
||||
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]);
|
||||
Bangle.buzz(CYCLE_BUZZ_MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
// Handle a touch: transmit displayed emoji
|
||||
function handleTouch(zone, event) {
|
||||
if(isTransmitting) { return; }
|
||||
|
||||
let emoji = EMOJIS[emojiIndex];
|
||||
transmitEmoji(emoji[IMAGE_INDEX], emoji[CODE_POINT_INDEX],
|
||||
EMOJI_TRANSMISSION_MILLISECONDS);
|
||||
Bangle.buzz(TRANSMIT_BUZZ_MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
// Transmit the given code point for the given duration in milliseconds,
|
||||
// blinking the image once per second.
|
||||
function transmitEmoji(image, codePoint, duration) {
|
||||
let instance = [ 0x00, 0x00, (codePoint >> 24) & 0xff,
|
||||
(codePoint >> 16) & 0xff, (codePoint >> 8) & 0xff,
|
||||
codePoint & 0xff ];
|
||||
|
||||
require('ble_eddystone_uid').advertise(UNICODE_CODE_POINT_ELIDED_UUID,
|
||||
instance);
|
||||
isTransmitting = true;
|
||||
|
||||
let displayIntervalId = setInterval(toggleImage, BLINK_PERIOD_MILLISECONDS,
|
||||
image);
|
||||
|
||||
setTimeout(terminateEmoji, duration, displayIntervalId);
|
||||
}
|
||||
|
||||
|
||||
// Terminate the emoji transmission
|
||||
function terminateEmoji(displayIntervalId) {
|
||||
NRF.setAdvertising({ });
|
||||
isTransmitting = false;
|
||||
clearInterval(displayIntervalId);
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]);
|
||||
}
|
||||
|
||||
|
||||
// Toggle the display between image/off
|
||||
function toggleImage(image) {
|
||||
if(isToggleOn) {
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]);
|
||||
}
|
||||
else {
|
||||
g.clear();
|
||||
}
|
||||
isToggleOn = !isToggleOn;
|
||||
}
|
||||
|
||||
|
||||
// Draw the given emoji
|
||||
function drawImage(image) {
|
||||
g.clear();
|
||||
g.drawString(image, g.getWidth() / 2, g.getHeight() / 2);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
||||
// Handle a drag event
|
||||
function handleDrag(event) {
|
||||
let isFingerReleased = (event.b === 0);
|
||||
|
||||
if(isFingerReleased) {
|
||||
let isHorizontalDrag = (Math.abs(lastDragX) >= Math.abs(lastDragY)) &&
|
||||
(lastDragX !== 0);
|
||||
|
||||
if(isHorizontalDrag) {
|
||||
cycleEmoji(lastDragX > 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
lastDragX = event.dx;
|
||||
lastDragY = event.dy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Special function to handle display switch on
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if(on) {
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// On start: display the first emoji and handle drag and touch events
|
||||
g.clear();
|
||||
g.setFont('Vector', 80);
|
||||
g.setFontAlign(0, 0);
|
||||
drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]);
|
||||
Bangle.on('touch', handleTouch);
|
||||
Bangle.on('drag', handleDrag);
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -5,3 +5,4 @@
|
|||
Don't restart HRM when changing apps if we've already got a good BPM value
|
||||
0.05: Fix daily summary calculation
|
||||
0.06: Fix daily health summary for movement (a line got deleted!)
|
||||
0.07: Added coloured bar charts
|
||||
|
|
|
@ -7,6 +7,8 @@ function setSettings(s) {
|
|||
}
|
||||
|
||||
function menuMain() {
|
||||
swipe_enabled = false;
|
||||
clearButton();
|
||||
E.showMenu({
|
||||
"":{title:"Health Tracking"},
|
||||
"< Back":()=>load(),
|
||||
|
@ -18,6 +20,8 @@ function menuMain() {
|
|||
}
|
||||
|
||||
function menuSettings() {
|
||||
swipe_enabled = false;
|
||||
clearButton();
|
||||
var s=getSettings();
|
||||
E.showMenu({
|
||||
"":{title:"Health Tracking"},
|
||||
|
@ -32,6 +36,8 @@ function menuSettings() {
|
|||
}
|
||||
|
||||
function menuStepCount() {
|
||||
swipe_enabled = false;
|
||||
clearButton();
|
||||
E.showMenu({
|
||||
"":{title:"Step Counting"},
|
||||
"< Back":()=>menuMain(),
|
||||
|
@ -41,6 +47,8 @@ function menuStepCount() {
|
|||
}
|
||||
|
||||
function menuMovement() {
|
||||
swipe_enabled = false;
|
||||
clearButton();
|
||||
E.showMenu({
|
||||
"":{title:"Movement"},
|
||||
"< Back":()=>menuMain(),
|
||||
|
@ -50,6 +58,8 @@ function menuMovement() {
|
|||
}
|
||||
|
||||
function menuHRM() {
|
||||
swipe_enabled = false;
|
||||
clearButton();
|
||||
E.showMenu({
|
||||
"":{title:"Heart Rate"},
|
||||
"< Back":()=>menuMain(),
|
||||
|
@ -66,14 +76,8 @@ function stepsPerHour() {
|
|||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
g.reset();
|
||||
require("graph").drawBar(g, data, {
|
||||
y:24,
|
||||
miny: 0,
|
||||
axes : true,
|
||||
gridx : 6,
|
||||
gridy : 500
|
||||
});
|
||||
Bangle.setUI("updown", ()=>menuStepCount());
|
||||
setButton(menuStepCount);
|
||||
barChart("HOUR", data);
|
||||
}
|
||||
|
||||
function stepsPerDay() {
|
||||
|
@ -83,14 +87,8 @@ function stepsPerDay() {
|
|||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
g.reset();
|
||||
require("graph").drawBar(g, data, {
|
||||
y:24,
|
||||
miny: 0,
|
||||
axes : true,
|
||||
gridx : 5,
|
||||
gridy : 2000
|
||||
});
|
||||
Bangle.setUI("updown", ()=>menuStepCount());
|
||||
setButton(menuStepCount);
|
||||
barChart("DAY", data);
|
||||
}
|
||||
|
||||
function hrmPerHour() {
|
||||
|
@ -105,14 +103,8 @@ function hrmPerHour() {
|
|||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
g.reset();
|
||||
require("graph").drawBar(g, data, {
|
||||
y:24,
|
||||
miny: 0,
|
||||
axes : true,
|
||||
gridx : 6,
|
||||
gridy : 20
|
||||
});
|
||||
Bangle.setUI("updown", ()=>menuHRM());
|
||||
setButton(menuHRM);
|
||||
barChart("HOUR", data);
|
||||
}
|
||||
|
||||
function hrmPerDay() {
|
||||
|
@ -127,14 +119,8 @@ function hrmPerDay() {
|
|||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
g.reset();
|
||||
require("graph").drawBar(g, data, {
|
||||
y:24,
|
||||
miny: 0,
|
||||
axes : true,
|
||||
gridx : 5,
|
||||
gridy : 20
|
||||
});
|
||||
Bangle.setUI("updown", ()=>menuHRM());
|
||||
setButton(menuHRM);
|
||||
barChart("DAY", data);
|
||||
}
|
||||
|
||||
function movementPerHour() {
|
||||
|
@ -144,14 +130,8 @@ function movementPerHour() {
|
|||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
g.reset();
|
||||
require("graph").drawLine(g, data, {
|
||||
y:24,
|
||||
miny: 0,
|
||||
axes : true,
|
||||
gridx : 6,
|
||||
ylabel : null
|
||||
});
|
||||
Bangle.setUI("updown", ()=>menuMovement());
|
||||
setButton(menuMovement);
|
||||
barChart("HOUR", data);
|
||||
}
|
||||
|
||||
function movementPerDay() {
|
||||
|
@ -161,14 +141,112 @@ function movementPerDay() {
|
|||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
g.reset();
|
||||
require("graph").drawBar(g, data, {
|
||||
y:24,
|
||||
miny: 0,
|
||||
axes : true,
|
||||
gridx : 5,
|
||||
ylabel : null
|
||||
});
|
||||
Bangle.setUI("updown", ()=>menuMovement());
|
||||
setButton(menuMovement);
|
||||
barChart("DAY", data);
|
||||
}
|
||||
|
||||
// Bar Chart Code
|
||||
|
||||
const w = g.getWidth();
|
||||
const h = g.getHeight();
|
||||
|
||||
var data_len;
|
||||
var chart_index;
|
||||
var chart_max_datum;
|
||||
var chart_label;
|
||||
var chart_data;
|
||||
var swipe_enabled = false;
|
||||
var btn;
|
||||
|
||||
// find the max value in the array, using a loop due to array size
|
||||
function max(arr) {
|
||||
var m = -Infinity;
|
||||
|
||||
for(var i=0; i< arr.length; i++)
|
||||
if(arr[i] > m) m = arr[i];
|
||||
return m;
|
||||
}
|
||||
|
||||
// find the end of the data, the array might be for 31 days but only have 2 days of data in it
|
||||
function get_data_length(arr) {
|
||||
var nlen = arr.length;
|
||||
|
||||
for(var i = arr.length - 1; i > 0 && arr[i] == 0; i--)
|
||||
nlen--;
|
||||
|
||||
return nlen;
|
||||
}
|
||||
|
||||
function barChart(label, dt) {
|
||||
data_len = get_data_length(dt);
|
||||
chart_index = Math.max(data_len - 5, -5); // choose initial index that puts the last day on the end
|
||||
chart_max_datum = max(dt); // find highest bar, for scaling
|
||||
chart_label = label;
|
||||
chart_data = dt;
|
||||
drawBarChart();
|
||||
swipe_enabled = true;
|
||||
}
|
||||
|
||||
function drawBarChart() {
|
||||
const bar_bot = 140;
|
||||
const bar_width = (w - 2) / 9; // we want 9 bars, bar 5 in the centre
|
||||
var bar_top;
|
||||
var bar;
|
||||
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(0,24,w,h);
|
||||
|
||||
for (bar = 1; bar < 10; bar++) {
|
||||
if (bar == 5) {
|
||||
g.setFont('6x8', 2);
|
||||
g.setFontAlign(0,-1)
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(chart_label + " " + (chart_index + bar -1) + " " + chart_data[chart_index + bar - 1], g.getWidth()/2, 150);
|
||||
g.setColor("#00f");
|
||||
} else {
|
||||
g.setColor("#0ff");
|
||||
}
|
||||
|
||||
// draw a fake 0 height bar if chart_index is outside the bounds of the array
|
||||
if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len)
|
||||
bar_top = bar_bot - 100 * (chart_data[chart_index + bar - 1]) / chart_max_datum;
|
||||
else
|
||||
bar_top = bar_bot;
|
||||
|
||||
g.fillRect( 1 + (bar - 1)* bar_width, bar_bot, 1 + bar*bar_width, bar_top);
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawRect( 1 + (bar - 1)* bar_width, bar_bot, 1 + bar*bar_width, bar_top);
|
||||
}
|
||||
}
|
||||
|
||||
function next_bar() {
|
||||
chart_index = Math.min(data_len - 5, chart_index + 1);
|
||||
}
|
||||
|
||||
function prev_bar() {
|
||||
// HOUR data starts at index 0, DAY data starts at index 1
|
||||
chart_index = Math.max((chart_label == "DAY") ? -3 : -4, chart_index - 1);
|
||||
}
|
||||
|
||||
Bangle.on('swipe', dir => {
|
||||
if (!swipe_enabled) return;
|
||||
if (dir == 1) prev_bar(); else next_bar();
|
||||
drawBarChart();
|
||||
});
|
||||
|
||||
// use setWatch() as Bangle.setUI("updown",..) interacts with swipes
|
||||
function setButton(fn) {
|
||||
if (process.env.HWVERSION == 1)
|
||||
btn = setWatch(fn, BTN2);
|
||||
else
|
||||
btn = setWatch(fn, BTN1);
|
||||
}
|
||||
|
||||
function clearButton() {
|
||||
if (btn !== undefined) {
|
||||
clearWatch(btn);
|
||||
btn = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
|
Loading…
Reference in New Issue