import document from "document"; import clock from "clock"; import * as messaging from "messaging"; import * as fs from "fs"; import { me } from "appbit"; import { me as device } from "device"; import { Accelerometer } from "accelerometer"; import { HeartRateSensor } from "heart-rate"; import { today } from "user-activity"; import { goals } from "user-activity"; import { user } from "user-profile"; import { vibration } from "haptics"; import { display } from "display"; import { preferences } from "user-settings"; if (!device.screen) device.screen = { width: 348, height: 250 }; import * as util from "../common/utils"; const DEBUG = false; const COLORDEBUG = false; let accel = new Accelerometer({ frequency: 4 }); let hrm = new HeartRateSensor; let touchArea = document.getElementById('touchArea'); let overlay = document.getElementById('overlay'); let mainHealthText = document.getElementById('mainHealthText'); let timeElements = document.getElementsByClassName('time'); let healthTextInstances = document.getElementsByClassName('healthTextInstance'); let timeElementsNum = timeElements.length; let averageAccelZ = 10.0; let lastAccelZ = 10.0; let hasOverlay = false; let overlayTimer; let sublineTimer; let colorTimer; let saveTimer; let hrTimer; let currentColor; let hiddenSubline = false; const smoothingFactor = 0.2; const colorGreen = [0.525,1.0,0.0]; const colorYellow = [0.3,1.0,0.0]; const colorOrange = [0.213,1.0,0.0]; const colorRed = [0.108,1.0,0.5]; const colorNeutral = [0.632,0.8,0.7]; const placeholdertext = '· · · '; let hrLabel = placeholdertext; const sublines = ['-', 'date', 'steps', 'cal', 'dist', 'elev', 'actmin', 'hr']; const defaults = {leadingZero: false, secondary: 'steps', tap: 'overlay', colors: 'manual', customcolor:['0.632','0.8','0.7'], dateformat: 'md/', colorgoal: 'steps'}; let userSettings; try { //console.log("Reading settings"); userSettings = fs.readFileSync("echocentric_settings.json", "json"); } catch (e) { //console.log("No settings found. Using default settings"); userSettings = defaults; } function writeSettings() { //console.log("Writing settings"); fs.writeFileSync("echocentric_settings.json", userSettings, "json"); } me.onunload = () => { //console.log("Unloading. Good Bye"); writeSettings(); } messaging.peerSocket.onmessage = e => { //console.log("Message received -> "+e.data.key+": "+e.data.newValue); display.poke(); vibration.start("bump"); let doClockUpdate = false; let doSublineChange = false; let doSublineUpdate = false; let doColorUpdate = false; switch (e.data.key) { case 'leadingZero': userSettings.leadingZero = (e.data.newValue == "true"); doClockUpdate = true; break; case 'secondary': let oldval = userSettings.secondary; let newval = JSON.parse(e.data.newValue).values[0].value; if (oldval != newval) { userSettings.secondary = newval; doSublineChange = true; } break; case 'tap': userSettings.tap = JSON.parse(e.data.newValue).values[0].value; break; case 'colors': userSettings.colors = JSON.parse(e.data.newValue).values[0].value; doColorUpdate = true; break; case 'customcolor': userSettings.customcolor = e.data.newValue.replace(/["']/g, "").split(','); doColorUpdate = true; break; case 'colorgoal': userSettings.colorgoal = JSON.parse(e.data.newValue).values[0].value; doColorUpdate = true; if (userSettings.colorgoal === 'hr') { doClockUpdate = true; } break; case 'dateformat': userSettings.dateformat = JSON.parse(e.data.newValue).values[0].value; doSublineUpdate = true; break; }; clearTimeout(saveTimer); saveTimer = setTimeout(() => { writeSettings(); }, 8000); if (doColorUpdate) { updateColors(false); } if (doClockUpdate) { updateClock(); } if (doSublineChange) { changeSubline(); } if (doSublineUpdate) { updateSubline(); } } messaging.peerSocket.onopen = () => { //console.log("Clockface opened socket"); }; messaging.peerSocket.close = () => { //console.log("Clockface closed socket"); }; messaging.peerSocket.onerror = (err) => { //console.log("Clockface socket error: " + err.code + " - " + err.message); } function sendSetting() { if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) { messaging.peerSocket.send(userSettings); } } display.onchange = () => { if (display.on) { accel.start(); averageAccelZ = lastAccelZ = Math.max(accel.z,5); callback(); if (hiddenSubline === false) { mainHealthText.animate('enable'); } } else { accel.stop(); if (hiddenSubline === false) { mainHealthText.animate('disable'); } } } function updateClock() { let currDate = new Date(); let hours = currDate.getHours(); let displayHours = hours; if (preferences.clockDisplay === '12h') { displayHours = displayHours % 12; displayHours = displayHours ? displayHours : 12; } if (userSettings.leadingZero === true) { displayHours = util.zeroPad(displayHours); } let mins = util.zeroPad(currDate.getMinutes()); let timeString = `${displayHours}:${mins}`; for(var i = 0; i < timeElementsNum; i++) { timeElements[i].text = timeString; } if (userSettings.colors === 'random') { updateColors(false); } updateSubline(); switch (userSettings.secondary) { case 'steps': case 'cal': case 'dist': case 'elev': case 'actmin': case 'hr': clearInterval(sublineTimer); sublineTimer = setInterval(() => { updateSubline(); },2000); } if (userSettings.colorgoal === 'hr') { clearInterval(colorTimer); colorTimer = setInterval(() => { updateColors(false); },2000); } if (userSettings.colors === 'auto') { updateColors(false); } } function updateSubline() { if (userSettings.secondary === 'hr') { mainHealthText.getElementById('healthTextLabel').text = hrLabel; hrm.start(); } else { let sublineText = getActivityText(userSettings.secondary); if (mainHealthText.getElementById('healthTextLabel').text !== sublineText) mainHealthText.getElementById('healthTextLabel').text = sublineText; } if (display.on === false) { clearInterval(sublineTimer); } } function getActivityNumber(activity) { let values = {raw: 0, display: 0}; switch (activity) { case 'steps': values.raw = (today.adjusted.steps || 0); values.display = util.addCommas(values.raw); break; case 'cal': values.raw = (today.adjusted.calories || 0); values.display = util.addCommas(values.raw); break; case 'dist': values.raw = (today.adjusted.distance || 0); values.display = (Math.round(values.raw/10)/100 || 0); break; case 'elev': values.raw = values.display = (today.adjusted.elevationGain || 0); break; case 'actmin': values.raw = (today.adjusted.activeMinutes || 0); values.display = util.addCommas(values.raw); break; }; return values; } function getActivityText(activity) { let label = placeholdertext; switch (activity) { case 'hr': label = hrLabel; break; case 'steps': case 'cal': case 'actmin': case 'dist': case 'elev': label = getActivityNumber(activity).display; break; case 'date': let currDate = new Date(); let day = currDate.getDate(); let month = currDate.getMonth()+1; let zeroPadded = false; let ddmm = false; let delimiter = '/'; let endsWithDelimiter = false; if (userSettings.dateformat[0] === 'd') { ddmm = true; } delimiter = userSettings.dateformat[2]; endsWithDelimiter = ((delimiter === '.') ? true : false); if (zeroPadded) { day = util.zeroPad(day); month = util.zeroPad(month); } if (ddmm) { label = day+delimiter+month+(endsWithDelimiter ? delimiter : ''); } else { label = month+delimiter+day+(endsWithDelimiter ? delimiter : ''); } break; default: break; }; return label; } function activityGoalColor(activity, heartRate) { let level = colorNeutral; if (heartRate !== false) { let hrZone = (user.heartRateZone(heartRate) || 'none'); switch (hrZone) { case 'out-of-range': case 'below-custom': level = colorGreen; break; case 'fat-burn': case 'custom': level = colorYellow; break; case 'cardio': level = colorOrange; break; case 'above-custom': case 'peak': level = colorRed; break; } return level; } else { if (activity === 'hr') { hrm.start(); } else { let goal = 0; switch (activity) { case 'steps': goal = (goals.steps || 0); break; case 'cal': goal = (goals.calories || 0); break; case 'dist': goal = (goals.distance || 0); break; case 'elev': goal = (Math.floor(goals.elevationGain/10) || 0); break; case 'actmin': goal = (goals.activeMinutes || 0); break; }; let current = getActivityNumber(activity).raw; //console.log('Goal for '+activity+' is '+current+' of '+goal+' = '+(current/goal)); if (goal > 0) { let goalPercentage = current/goal; if (goalPercentage > 1) { level = colorGreen; } else if (goalPercentage > 0.666) { level = util.interpolateColors(goalPercentage, 0.666, 1, colorYellow, colorGreen); } else if (goalPercentage > 0.333) { level = util.interpolateColors(goalPercentage, 0.333, 0.666, colorOrange, colorYellow); } else { level = util.interpolateColors(goalPercentage, 0, 0.333, colorRed, colorOrange); } } } } return level; } function changeSubline() { if (hiddenSubline === false) { mainHealthText.animate('disable'); } let href = ''; switch (userSettings.secondary) { case 'date': case 'steps': case 'cal': case 'dist': case 'elev': case 'actmin': href = 'img/icon-'+userSettings.secondary+'.png'; break; default: break; }; setTimeout(() => { updateSubline(); if (userSettings.secondary === 'hr') { hrm.start(); } mainHealthText.getElementById('healthTextIcon').href = href; if (userSettings.secondary === 'hr') { mainHealthText.getElementById("healthTextHrIcon").style.display="inline"; } else { mainHealthText.getElementById("healthTextHrIcon").style.display="none"; } if (userSettings.secondary !== '-') { mainHealthText.animate('enable'); hiddenSubline = false; } else { hiddenSubline = true; } }, 200); } function updateColors(color) { let newColor = false; if (color !== false) { newColor = color; } else { switch (userSettings.colors) { case 'auto': let activityColor = activityGoalColor(userSettings.colorgoal, false); if (userSettings.colorgoal !== 'hr') { newColor = activityColor; } break; case 'manual': newColor = userSettings.customcolor; break; case 'random': default: newColor = [Math.random(),1,0.5]; break; } } if (COLORDEBUG) { newColor = [Math.random(),1,0]; } //console.log('Color comparison '+newColor+' == '+currentColor+': '+(newColor == currentColor)); if (newColor !== false && newColor != currentColor) { //console.log('Color was changed'); currentColor = newColor; for(var i = 0; i < timeElementsNum-1; i++) { let fraction = i/timeElementsNum; timeElements[i].style.fill = util.hslToHex(newColor[0]-fraction*0.3, newColor[1]-fraction*newColor[2], fraction*0.74+0.15); } } else { //console.log('Color is unchanged'); } if (display.on === false) { clearInterval(colorTimer); } } function setPositions(offset) { for(var i = 0; i < timeElementsNum; i++) { let ypos = ((1-(offset-9))*5*(timeElementsNum-0.5-i))+20; timeElements[i].y = ypos; if (i == timeElementsNum-1) { mainHealthText.y = ypos+160+((device.screen.height-250)/2); } } } hrm.onreading = () => { //console.log("Reading heart rate"); if (display.on) { let heartRate = (hrm.heartRate || 0); hrLabel = heartRate; document.getElementById('hrText').getElementById('healthTextLabel').text = heartRate; if (userSettings.secondary == 'hr') { mainHealthText.getElementById('healthTextLabel').text = heartRate; } if (userSettings.colorgoal === 'hr') { let color = activityGoalColor('hr', heartRate); //console.log('got HR color from hr '+heartRate+': '+color); updateColors(color); } } clearTimeout(hrTimer); hrTimer = setTimeout(() => { hrLabel = placeholdertext; }, 1500); hrm.stop(); } function updateOverlay() { document.getElementById('hrText').getElementById('healthTextLabel').text = hrLabel; hrm.start(); document.getElementById('stepsText').getElementById('healthTextLabel').text = getActivityText('steps'); document.getElementById('calText').getElementById('healthTextLabel').text = getActivityText('cal'); document.getElementById('distText').getElementById('healthTextLabel').text = getActivityText('dist'); document.getElementById('elevText').getElementById('healthTextLabel').text = getActivityText('elev'); document.getElementById('actminText').getElementById('healthTextLabel').text = getActivityText('actmin'); } touchArea.onclick = (e) => { if (COLORDEBUG) { updateColors(false); } else { switch (userSettings.tap) { case 'overlay': if (hasOverlay == false) { vibration.start("bump"); addOverlay(); } else { removeOverlay(); } break; case 'cycle': vibration.start("bump"); let next = sublines[0]; let index = sublines.indexOf(userSettings.secondary); if(index >= 0 && index < sublines.length - 1) { next = sublines[index + 1]; } userSettings.secondary = next; changeSubline(); sendSetting(); clearTimeout(saveTimer); saveTimer = setTimeout(() => { writeSettings(); }, 8000); break; } } } function addOverlay() { updateOverlay(); overlay.style.display = 'inline'; hasOverlay = true; overlayTimer = setTimeout(() => { removeOverlay(); },6000); document.getElementById('overlayShadeInstance').animate('enable'); for(var i = 0; i < healthTextInstances.length; i++) { let index = i; setTimeout(() => { healthTextInstances[index].animate('enable'); }, 200+index*100); } } function removeOverlay() { clearTimeout(overlayTimer); document.getElementById('overlayShadeInstance').animate('disable'); for(var i = 0; i < healthTextInstances.length; i++) { healthTextInstances[i].animate('disable'); } setTimeout(() => { overlay.style.display = 'none'; }, 100); hasOverlay = false; } accel.onreading = () => { //console.log(accel.z); if (hasOverlay == false) { lastAccelZ = Math.max(accel.z,5); } //accel.stop(); } function callback(timestamp) { if (hasOverlay == false) { averageAccelZ = (lastAccelZ*smoothingFactor)+(averageAccelZ*(1.0-smoothingFactor)); setPositions(averageAccelZ); requestAnimationFrame(callback); } } updateColors(false); changeSubline(); clock.granularity = "minutes"; clock.ontick = () => updateClock(); document.getElementById("hrText").getElementById("healthTextHrIcon").style.display="inline"; updateClock(); updateOverlay(); requestAnimationFrame(callback); accel.start(); if (DEBUG) { display.autoOff = false; display.on = true; }