Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
511 lines (452 sloc) 14.1 KB
/**
* Global Imports
*/
const locale = require('locale');
const watchMaker = require('watchmaker-js');
let debugFn;
let firstTime = false;
/**
* The Mood App routes, change and init functions
* @type {{goTo: function(*=): void}}
*/
let moodsApp
/**
* Draw a heart for the HRM monitor page
* @param queue
* @param startX
* @param startY
*/
function drawHeart(queue, startX, startY) {
if (queue.length > 0) {
const heartImage = moodsApp.getImage('heart');
const item = `${queue[queue.length - 1]}`;
g.drawImage(heartImage, startX, startY, {scale: 2});
g.setColor(255, 255, 255);
g.setFontVector(item.length === 3 ? 15 : 18);
g.drawString(`${item}`, startX + 17, startY + 20)
g.setFontVector(15);
g.drawString(`${queue.length}`, startX + 50, startY + 20);
}
}
/**
* Set up the LCD Listener, if the user has the setting to close the app on LCD
* close this will check if the user is on the home screen and closes the app
* If the user is on another page it won't close
*/
function setupLCDListener(app) {
let timeout;
// When the LCD Powers down on the menu page, close the app
Bangle.on('lcdPower', (on) => {
const lcdOff = app.getSettings('lcdOffClose');
if (lcdOff) {
if (on && timeout) {
clearTimeout(timeout);
} else if (!on && app.currentRoute === 'home') {
timeout = setTimeout(() => load(), 20000)
}
}
});
}
/**
* Set up the GPS listener, if the user has it toggled on then when we get a fix set
* the last location
*/
function setupGPSListener(app, cb) {
Bangle.on('GPS', (gps) => {
if (cb) {
cb(gps);
}
if (gps.fix) {
app.setState('currentLocation', gps);
}
});
}
function setupHMRListener(app, cb) {
Bangle.on('HRM', (hrm) => {
const confidenceLevel = app.getSettings('confidenceLevel');
const hrmQueue = app.getState('hrmQueue');
if (hrm.confidence >= confidenceLevel) {
hrmQueue.push(hrm.bpm)
app.setState('hrmQueue', hrmQueue);
if (cb) {
cb(hrmQueue, true)
}
} else {
if (cb) {
cb(hrmQueue, false)
}
}
});
}
/**
* Function called when showing intro
* @param step
* @param steps
*/
function showIntro(step, steps) {
steps = steps || watchMaker.convertStringToPage(moodsApp.getText('text.help'));
if (step < 0 || !steps[step]) {
moodsApp.goTo('home')
} else {
watchMaker.createSelectPage('Welcome to Moods', steps[step], {
'Next': step + 1,
'Exit': 0
}, 0, true).then(nextStep => {
if (!nextStep) {
showIntro(-1, steps)
} else {
showIntro(nextStep, steps)
}
}).catch(e => {
E.showAlert(e).then(() => moodsApp.goTo('Home'));
})
}
}
/**
* The main menu view, this first disables any active devices to off, which allows us to use it
* as the last step or exit view for all other views
*/
function viewMainMenu() {
toggleLocationCollection(false);
toggleHMR(false);
const globalDebug = moodsApp.getGlobalSettings('log');
console.log(globalDebug);
if (globalDebug && !debugFn) {
debugFn = debugInfo();
} else if (!globalDebug && debugFn) {
debugFn();
}
let moodText = '';
const lastMood = moodsApp.getData('data', 1)[0];
if (lastMood) {
console.log(lastMood);
moodText = `${locale.time(new Date(lastMood.time), 1)} - ${lastMood.mood}`;
}
const menu = {};
menu["Set Mood"] = () => moodsApp.goTo('addMood');
menu["Recent History"] = () => moodsApp.goTo('showHistory');
menu["Settings"] = () => moodsApp.goTo('settings');
menu["Help"] = () => moodsApp.goTo('help', 0);
if (globalDebug) {
menu["Debug"] = () => moodsApp.goTo('debug');
}
watchMaker.createMenu('Moods', moodText, menu, undefined, () => load())
}
/**
* Create a queue for collecting Heart Rate data of a certain confidence level
* @param seconds
* @param settings
* @return {Promise<unknown>}
*/
function createHRMQueue(seconds, settings) {
moodsApp.setState('hrmQueue', []);
const countdown = watchMaker.createCountdown(seconds / 1000, (left) => {
E.showMessage(`Collecting\nKeep Still...\nCancel - BTN2\n${left} seconds left`, 'Heart Rate')
LED2.write(1);
}, 5);
setWatch(() => {
countdown.cancel()
}, BTN2)
toggleHMR(true)
return countdown.start.then((cancelled) => {
toggleHMR(false);
return cancelled ? [] : moodsApp.getState('hrmQueue')
});
}
function toggleLocationCollection(on) {
Bangle.setGPSPower(on ? 1 : 0);
}
function toggleHMR(on) {
Bangle.setHRMPower(on ? 1 : 0);
}
function debugInfo(tickTime) {
const interval = setInterval(() => {
const memory = process.memory();
const pc = Math.round(memory.usage * 100 / memory.total);
const debugInfo = [Bangle.dbg(), memory, pc]
moodsApp.setState('debugInfo', debugInfo)
}, tickTime || 5000)
return () => clearInterval(interval);
}
/**
*
* @param mood
* @param level
* @param settings
*/
function requestHeartRate(mood, level, settings) {
watchMaker.createSelectPage('Your Mood Data', 'Do you want to collect an average heart rate? Select 30s, 60s or skip this time', {
"30s": 30000,
"60s": 60000,
"Skip": 0,
}).then(seconds => {
if (seconds === 0) {
writeMood(mood, level, 0);
} else {
createHRMQueue(seconds, settings).then(results => {
const average = Math.floor(results.reduce((a, b) => a + b, 0) / results.length);
writeMood(mood, level, average)
})
}
})
}
/**
* Prompt the user for a more detailed mood selection
* @param forMood
*/
function promptMoodList(forMood) {
const emotions = moodsApp.getText(`emotions.${forMood}`)
if (!emotions) {
E.showAlert(`No values for ${forMood}`).then(() => moodsApp.goTo('home'));
return;
}
watchMaker.createScrollPrompt(`${forMood} Mood`, 'Select a more detailed emotion - use BTN1 and BTN3 to scroll',
emotions.reduce((a, k) => {
a[k] = k
return a;
}, {})
).then((k) => moodsApp.goTo('setMoodLevel', k))
}
/**
* Prompt the user for a general mood, this allows us to load the sub-menu of more detailed mood desriptions
*/
function promptMoodGroup() {
const collectLocation = moodsApp.getSettings('collectLocation');
if (collectLocation) {
toggleLocationCollection(true)
}
const emotions = moodsApp.getText('emotions');
E.showMessage('', 'Select An Emotion');
// drawSelect((g.getWidth() / 2) - 48, 140);
setTimeout(() => {
const menuButtons = Object.keys(emotions).reduce((buttons, key) => {
buttons[key] = () => moodsApp.goTo('setMoodType', key);
return buttons;
}, {});
watchMaker.createMenu('Select Emotion', undefined, menuButtons, undefined, () => moodsApp.goTo('home'))
}, 3000);
}
/**
* Get the intensity of feeling from the user, this is on a scale of 1-5.
*
* Next ->
* - If setting `collectHRM` is true -> requestHRM
* - If setting `collectHRM` is false -> writeMood
*
* @param mood
*/
function promptMoodLevel(mood) {
watchMaker.createSelectPage('Mood Intensity', 'How intensely are you feeling this mood?', {
'5': 5,
'4': 4,
'3': 3,
'2': 2,
'1': 1
}, 2).then(level => {
const collectHRM = moodsApp.getSettings('collectHRM');
if (!collectHRM) {
writeMood(mood, level, 0)
} else {
moodsApp.goTo('requestHRM', mood, level);
}
});
}
function showLastMoods(index, lastIndex, moods) {
const maxHistory = moodsApp.getSettings('maxHistory');
moodsApp.getData('data', maxHistory).then(moods => {
if (moods.length === 0) {
E.showAlert('No data yet').then(() => moodsApp.goTo('home'));
return;
}
let btnCount = 1
const buttons = {};
if (moods.length > 1 && index !== 0) {
buttons['<'] = index - 1;
btnCount++;
}
buttons['Exit'] = -1;
if (moods.length > 1 && index !== moods.length - 1) {
buttons['>'] = index + 1;
btnCount++;
}
let selected;
switch (btnCount) {
case 3:
selected = index > lastIndex ? 2 : 0;
break;
case 2:
selected = index === 0 ? 1 : 0;
break;
default:
selected = 0;
}
const mood = moods[index];
watchMaker.createSelectPage(
`${locale.date(new Date(mood.time), 1)}: ${locale.time(new Date(mood.time), 1)}`,
[
`Mood: ${mood.mood}`,
`Level: ${mood.level}`,
mood.bpmRate ? `Heart rate ${mood.bpmRate}bpm` : `No Heart Rate`,
mood.location !== null ? `With Location` : `No Location`
].join('\n'),
buttons,
selected,
true
).then(i => {
if (i === -1) {
moodsApp.goTo('home')
} else {
moodsApp.goTo('showHistory', i, index, moods)
}
})
});
}
/**
* Show the application settings menu
* @param currentSettings
*/
function showSettings() {
const currentSettings = moodsApp.getSettings();
watchMaker.createMenu('Mood Settings', undefined, {
"Log Location": {
value: currentSettings.collectLocation,
format: v => v ? 'On' : 'Off',
onchange: v => currentSettings.collectLocation = v
},
"Log HRM": {
value: currentSettings.collectHRM,
format: v => v ? 'On' : 'Off',
onchange: v => currentSettings.collectHRM = v
},
"Buzz On HRM": {
value: currentSettings.buzzOnHRM,
format: v => v ? 'On' : 'Off',
onchange: v => currentSettings.buzzOnHRM = v
},
"HRM Min Conf": {
value: currentSettings.confidenceLevel,
min: 0,
max: 100,
step: 5,
onchange: v => currentSettings.confidenceLevel = v
},
"Max History": {
value: currentSettings.maxHistory,
min: 0,
max: 50,
step: 5,
onchange: v => currentSettings.maxHistory = v
},
"LCD Off Close": {
value: currentSettings.lcdOffClose,
format: v => v ? 'On' : 'Off',
onchange: v => currentSettings.lcdOffClose = v
}
}, () => {
if (!moodsApp.saveSettings(currentSettings)) {
E.showAlert('Unable to save settings').then(() => {
moodsApp.goTo('home')
})
} else {
moodsApp.goTo('home')
}
})
}
/**
* Write the mood to the moods file
* @param mood
* @param level
* @param rate
*/
function writeMood(mood, level, rate) {
toggleLocationCollection(false);
const currentLocation = moodsApp.getState('currentLocation');
const time = Math.round(Date.now());
const result = {
time: time,
mood: mood,
level: level,
bpmRate: rate,
location: currentLocation && currentLocation.fix ? {
lat: currentLocation.lat,
lon: currentLocation.lon,
alt: currentLocation.alt
} : null
}
moodsApp.getData('data').then(moods => {
moods.unshift(result);
moodsApp.saveData('data', moods).then(() => {
moodsApp.goTo('home')
})
});
}
function showDebug() {
const debugInfo = moodsApp.getState('debugInfo');
if (!debugInfo) {
watchMaker.createAlert('No Debug Info', 'No Debug info available, try again in a few seconds').then(() => {
moodsApp.goTo('home')
});
return;
}
watchMaker.createMenu('Debug Info', undefined, {
"Mem % Used": {value: debugInfo[2]},
"Mem Usage": {value: debugInfo[1].usage},
"Mem Total": {value: debugInfo[1].total},
"Mem Free": {value: debugInfo[1].free},
"Blocksize": {value: debugInfo[1].blocksize},
"Refresh": () => moodsApp.goTo('debug')
}, undefined, () => moodsApp.goTo('home'));
}
/**
* Initialise the application
*/
moodsApp = watchMaker.lug('moods', {
routes: {
home: {view: () => viewMainMenu()},
help: {view: (step) => showIntro(step)},
addMood: {view: () => promptMoodGroup()},
setMoodType: {view: (mood) => promptMoodList(mood)},
setMoodLevel: {view: (mood) => promptMoodLevel(mood)},
showHistory: {view: (index, lastIndex, moods) => showLastMoods(index || 0, lastIndex, moods)},
requestHRM: {view: (mood, level) => requestHeartRate(mood, level)},
settings: {view: () => showSettings()},
debug: {view: () => showDebug()}
},
init: (app) => {
// Fast access images
app.loadImage('heart');
setupLCDListener(app);
setupGPSListener(app, () => {
LED1.write(1);
});
setupHMRListener(app, (currentQueue, buzz) => {
drawHeart(currentQueue, 80, 160);
if (buzz) {
app.getSettings('buzzOnHRM') && Bangle.buzz(200, 1);
}
});
const globalDebug = app.getGlobalSettings('log');
if (globalDebug) {
debugFn = debugInfo();
}
let settings = app.getSettings();
if (!settings) {
app.saveSettings({
collectLocation: false,
collectHRM: true,
buzzOnHRM: true,
confidenceLevel: 65,
lcdOffClose: true,
maxHistory: 10
});
firstTime = true;
}
},
first: (app) => {
if (firstTime) {
app.goTo('help', 0);
} else {
app.goTo('home');
}
}
});