banglerun 0.10: Removed kalman filtering to allow distance log to work

Only log data every 5 seconds (not 1 sec)
      Don't create a file until the first log entry is ready
master
Gordon Williams 2021-02-22 15:10:30 +00:00
parent a1454a9d85
commit 056bcd9eda
6 changed files with 38 additions and 59 deletions

View File

@ -1590,7 +1590,7 @@
"name": "BangleRun",
"shortName": "BangleRun",
"icon": "banglerun.png",
"version": "0.09",
"version": "0.10",
"interface": "interface.html",
"description": "An app for running sessions. Displays info and logs your run for later viewing.",
"tags": "run,running,fitness,outdoors",

View File

@ -7,3 +7,6 @@
0.07: Fixed GPS update, added guards against NaN values
0.08: Fix issue with GPS coordinates being wrong after the first one
0.09: Another GPS fix (log raw coordinates - not filtered ones)
0.10: Removed kalman filtering to allow distance log to work
Only log data every 5 seconds (not 1 sec)
Don't create a file until the first log entry is ready

View File

@ -1 +1 @@
!function(){"use strict";const t={STOP:63488,PAUSE:65504,RUN:2016};function n(t,n,r){g.setColor(0),g.fillRect(n-60,r,n+60,r+30),g.setColor(65535),g.drawString(t,n,r)}function r(r){var e;g.setFontVector(30),g.setFontAlign(0,-1,0),n((r.distance/1e3).toFixed(2),60,55),n(function(t){const n=Math.round(t),r=Math.floor(n/3600),e=Math.floor(n/60)%60,o=n%60;return(r?r+":":"")+("0"+e).substr(-2)+":"+("0"+o).substr(-2)}(r.duration),180,55),n(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),r=Math.floor(n/60),e=n%60;return("0"+r).substr(-2)+"'"+("0"+e).substr(-2)+'"'}(r.speed),60,115),n(r.hr.toFixed(0),180,115),n(r.steps.toFixed(0),60,175),n(r.cadence.toFixed(0),180,175),g.setFont("6x8",2),g.setColor(r.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(e=new Date).getHours()).substr(-2)+":"+("0"+e.getMinutes()).substr(-2),120,220),g.setColor(t[r.status]),g.fillRect(160,216,240,240),g.setColor(0),g.drawString(r.status,200,220)}function e(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),r(t),Bangle.drawWidgets()}var o;function a(t){t.status===o.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),r=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(r,"w"),t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n")}(t),t.status===o.Running?t.status=o.Paused:t.status=o.Running,r(t)}!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(o||(o={}));const s={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,v:NaN,t:NaN,dt:NaN,pError:NaN,vError:NaN,hr:60,hrError:100,file:null,drawing:!1,status:o.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var i;i=s,Bangle.on("GPS",t=>function(t,n){t.lat=n.lat,t.lon=n.lon,t.alt=n.alt,t.vel=n.speed/3.6,t.fix=n.fix,t.dop=n.hdop,t.gpsValid=t.fix>0&&t.dop<=5,function(t){const n=Date.now();let r=(n-t.t)/1e3;if(isFinite(r)||(r=0),t.t=n,t.dt+=r,t.status===o.Running&&(t.duration+=r),!t.gpsValid)return;const e=6371008.8+t.alt,a=t.lat*Math.PI/180,s=t.lon*Math.PI/180,i=e*Math.cos(a)*Math.cos(s),d=e*Math.cos(a)*Math.sin(s),u=e*Math.sin(a),g=t.vel;if(!t.x)return t.x=i,t.y=d,t.z=u,t.v=g,t.pError=2.5*t.dop,void(t.vError=.05*t.dop);const l=i-t.x,c=d-t.y,p=u-t.z,f=g-t.v,N=Math.sqrt(l*l+c*c+p*p),h=Math.abs(f);t.pError+=t.v*t.dt,t.dt=0;const S=N+2.5*t.dop,E=h+.05*t.dop,w=t.pError/(t.pError+S)||0,x=t.vError/(t.vError+E)||0;t.x+=l*w,t.y+=c*w,t.z+=p*w,t.v+=f*x,t.pError+=(S-t.pError)*w,t.vError+=(E-t.vError)*x,t.status===o.Running&&(t.distance+=N*w,t.speed=t.distance/t.duration||0,t.cadence=60*t.steps/t.duration||0)}(t),r(t),t.gpsValid&&t.status===o.Running&&function(t){t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(",")+"\n")}(t)}(i,t)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const r=n.bpm-t.hr,e=Math.abs(r)+101-n.confidence,o=t.hrError/(t.hrError+e)||0;t.hr+=r*o,t.hrError+=(e-t.hrError)*o}(t,n)),Bangle.setHRMPower(1)}(s),function(t){Bangle.on("step",()=>function(t){t.status===o.Running&&(t.steps+=1)}(t))}(s),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&e(t)}),e(t)}(s),setWatch(()=>a(s),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(t){t.status===o.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(t),t.status===o.Running?t.status=o.Paused:t.status=o.Stopped,r(t)}(s),BTN3,{repeat:!0,edge:"falling"})}();
!function(){"use strict";const t={STOP:63488,PAUSE:65504,RUN:2016};function n(t,n,e){g.setColor(0),g.fillRect(n-60,e,n+60,e+30),g.setColor(65535),g.drawString(t,n,e)}function e(e){var i;g.setFontVector(30),g.setFontAlign(0,-1,0),n((e.distance/1e3).toFixed(2),60,55),n(function(t){const n=Math.round(t),e=Math.floor(n/3600),i=Math.floor(n/60)%60,o=n%60;return(e?e+":":"")+("0"+i).substr(-2)+":"+("0"+o).substr(-2)}(e.duration),180,55),n(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),e=Math.floor(n/60),i=n%60;return("0"+e).substr(-2)+"'"+("0"+i).substr(-2)+'"'}(e.speed),60,115),n(e.hr.toFixed(0),180,115),n(e.steps.toFixed(0),60,175),n(e.cadence.toFixed(0),180,175),g.setFont("6x8",2),g.setColor(e.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(i=new Date).getHours()).substr(-2)+":"+("0"+i.getMinutes()).substr(-2),120,220),g.setColor(t[e.status]),g.fillRect(160,216,240,240),g.setColor(0),g.drawString(e.status,200,220)}function i(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),e(t),Bangle.drawWidgets()}var o;function s(t){t.status===o.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),e=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(e,"w"),t.fileWritten=!1}(t),t.status===o.Running?t.status=o.Paused:t.status=o.Running,e(t)}!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(o||(o={}));const a={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,t:NaN,timeSinceLog:0,hr:60,hrError:100,file:null,fileWritten:!1,drawing:!1,status:o.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var r;r=a,Bangle.on("GPS",t=>function(t,n){t.lat=n.lat,t.lon=n.lon,t.alt=n.alt,t.vel=n.speed/3.6,t.fix=n.fix,t.dop=n.hdop,t.gpsValid=t.fix>0,function(t){const n=Date.now();let e=(n-t.t)/1e3;if(isFinite(e)||(e=0),t.t=n,t.timeSinceLog+=e,t.status===o.Running&&(t.duration+=e),!t.gpsValid)return;const i=6371008.8+t.alt,s=t.lat*Math.PI/180,a=t.lon*Math.PI/180,r=i*Math.cos(s)*Math.cos(a),g=i*Math.cos(s)*Math.sin(a),d=i*Math.sin(s);if(!t.x)return t.x=r,t.y=g,void(t.z=d);const u=r-t.x,l=g-t.y,c=d-t.z,f=Math.sqrt(u*u+l*l+c*c);t.x=r,t.y=g,t.z=d,t.status===o.Running&&(t.distance+=f,t.speed=t.distance/t.duration||0,t.cadence=60*t.steps/t.duration||0)}(t),e(t),t.gpsValid&&t.status===o.Running&&t.timeSinceLog>5&&(t.timeSinceLog=0,function(t){t.fileWritten||(t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n"),t.fileWritten=!0),t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(",")+"\n")}(t))}(r,t)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const e=n.bpm-t.hr,i=Math.abs(e)+101-n.confidence,o=t.hrError/(t.hrError+i)||0;t.hr+=e*o,t.hrError+=(i-t.hrError)*o}(t,n)),Bangle.setHRMPower(1)}(a),function(t){Bangle.on("step",()=>function(t){t.status===o.Running&&(t.steps+=1)}(t))}(a),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&i(t)}),i(t)}(a),setWatch(()=>s(a),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(t){t.status===o.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(t),t.status===o.Running?t.status=o.Paused:t.status=o.Stopped,e(t)}(a),BTN3,{repeat:!0,edge:"falling"})}();

View File

@ -14,8 +14,6 @@ interface GpsEvent {
}
const EARTH_RADIUS = 6371008.8;
const POS_ACCURACY = 2.5;
const VEL_ACCURACY = 0.05;
function initGps(state: AppState): void {
Bangle.on('GPS', (gps: GpsEvent) => readGps(state, gps));
@ -29,13 +27,17 @@ function readGps(state: AppState, gps: GpsEvent): void {
state.vel = gps.speed / 3.6;
state.fix = gps.fix;
state.dop = gps.hdop;
state.gpsValid = state.fix > 0 && state.dop <= 5;
state.gpsValid = state.fix > 0;
updateGps(state);
draw(state);
if (state.gpsValid && state.status === ActivityStatus.Running) {
/* Only log GPS data every 5 secs if we
have a fix and we're running. */
if (state.gpsValid &&
state.status === ActivityStatus.Running &&
state.timeSinceLog > 5) {
state.timeSinceLog = 0;
updateLog(state);
}
}
@ -44,9 +46,8 @@ function updateGps(state: AppState): void {
const t = Date.now();
let dt = (t - state.t) / 1000;
if (!isFinite(dt)) dt=0;
state.t = t;
state.dt += dt;
state.timeSinceLog += dt;
if (state.status === ActivityStatus.Running) {
state.duration += dt;
@ -62,52 +63,25 @@ function updateGps(state: AppState): void {
const x = r * Math.cos(lat) * Math.cos(lon);
const y = r * Math.cos(lat) * Math.sin(lon);
const z = r * Math.sin(lat);
const v = state.vel;
if (!state.x) {
state.x = x;
state.y = y;
state.z = z;
state.v = v;
state.pError = state.dop * POS_ACCURACY;
state.vError = state.dop * VEL_ACCURACY;
return;
}
const dx = x - state.x;
const dy = y - state.y;
const dz = z - state.z;
const dv = v - state.v;
const dpMag = Math.sqrt(dx * dx + dy * dy + dz * dz);
const dvMag = Math.abs(dv);
state.pError += state.v * state.dt;
state.dt = 0;
const pError = dpMag + state.dop * POS_ACCURACY;
const vError = dvMag + state.dop * VEL_ACCURACY;
const pGain = (state.pError / (state.pError + pError)) || 0;
const vGain = (state.vError / (state.vError + vError)) || 0;
state.x += dx * pGain;
state.y += dy * pGain;
state.z += dz * pGain;
state.v += dv * vGain;
state.pError += (pError - state.pError) * pGain;
state.vError += (vError - state.vError) * vGain;
/*// we're not currently updating lat/lon with the kalman filter
// as it seems not to update them correctly at the moment
// and we only use them for logging (where it makes sense to use
// raw GPS coordinates)
const pMag = Math.sqrt(state.x * state.x + state.y * state.y + state.z * state.z);
state.lat = (Math.asin(state.z / pMag) * 180 / Math.PI) || 0;
state.lon = (Math.atan2(state.y, state.x) * 180 / Math.PI) || 0;
state.alt = pMag - EARTH_RADIUS;*/
state.x = x;
state.y = y;
state.z = z;
if (state.status === ActivityStatus.Running) {
state.distance += dpMag * pGain;
state.distance += dpMag;
state.speed = (state.distance / state.duration) || 0;
state.cadence = (60 * state.steps / state.duration) || 0;
}

View File

@ -8,6 +8,11 @@ function initLog(state: AppState): void {
const time = datetime.substr(9, 6);
const filename = `banglerun_${date}_${time}`;
state.file = require('Storage').open(filename, 'w');
state.fileWritten = false;
}
function updateLog(state: AppState): void {
if (!state.fileWritten) {
state.file.write([
'timestamp',
'latitude',
@ -18,9 +23,8 @@ function initLog(state: AppState): void {
'heartrate',
'steps',
].join(',') + '\n');
state.fileWritten = true;
}
function updateLog(state: AppState): void {
state.file.write([
Date.now().toFixed(0),
state.lat.toFixed(6),

View File

@ -14,15 +14,14 @@ interface AppState {
dop: number;
gpsValid: boolean;
// GPS Kalman data
// Absolute position data
x: number;
y: number;
z: number;
v: number;
// Last fix time
t: number;
dt: number;
pError: number;
vError: number;
// Last time we saved log info
timeSinceLog : number;
// HRM data
hr: number,
@ -30,6 +29,7 @@ interface AppState {
// Logger data
file: File;
fileWritten: boolean;
// Drawing data
drawing: boolean;
@ -62,16 +62,14 @@ function initState(): AppState {
x: NaN,
y: NaN,
z: NaN,
v: NaN,
t: NaN,
dt: NaN,
pError: NaN,
vError: NaN,
timeSinceLog : 0,
hr: 60,
hrError: 100,
file: null,
fileWritten: false,
drawing: false,