2021-01-15 08:00:55 +00:00
|
|
|
import { draw } from './display';
|
|
|
|
import { updateLog } from './log';
|
2020-08-25 22:25:11 +00:00
|
|
|
import { ActivityStatus, AppState } from './state';
|
|
|
|
|
|
|
|
declare var Bangle: any;
|
|
|
|
|
2021-01-13 22:34:49 +00:00
|
|
|
interface GpsEvent {
|
|
|
|
lat: number;
|
|
|
|
lon: number;
|
|
|
|
alt: number;
|
|
|
|
speed: number;
|
|
|
|
hdop: number;
|
|
|
|
fix: number;
|
|
|
|
}
|
|
|
|
|
2020-08-25 22:25:11 +00:00
|
|
|
const EARTH_RADIUS = 6371008.8;
|
|
|
|
const POS_ACCURACY = 2.5;
|
|
|
|
const VEL_ACCURACY = 0.05;
|
|
|
|
|
|
|
|
function initGps(state: AppState): void {
|
2021-01-13 22:34:49 +00:00
|
|
|
Bangle.on('GPS', (gps: GpsEvent) => readGps(state, gps));
|
2020-08-25 22:25:11 +00:00
|
|
|
Bangle.setGPSPower(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseCoordinate(coordinate: string): number {
|
|
|
|
const pivot = coordinate.indexOf('.') - 2;
|
|
|
|
const degrees = parseInt(coordinate.substr(0, pivot));
|
|
|
|
const minutes = parseFloat(coordinate.substr(pivot)) / 60;
|
|
|
|
return (degrees + minutes) * Math.PI / 180;
|
|
|
|
}
|
|
|
|
|
2021-01-13 22:34:49 +00:00
|
|
|
function readGps(state: AppState, gps: GpsEvent): void {
|
|
|
|
state.lat = gps.lat;
|
|
|
|
state.lon = gps.lon;
|
|
|
|
state.alt = gps.alt;
|
|
|
|
state.vel = gps.speed / 3.6;
|
|
|
|
state.fix = gps.fix;
|
|
|
|
state.dop = gps.hdop;
|
2021-01-15 08:00:55 +00:00
|
|
|
|
|
|
|
state.gpsValid = state.fix === 3 && state.dop <= 5;
|
|
|
|
|
|
|
|
updateGps(state);
|
|
|
|
draw(state);
|
2021-01-15 08:52:55 +00:00
|
|
|
|
2021-01-15 08:00:55 +00:00
|
|
|
if (state.gpsValid && state.status === ActivityStatus.Running) {
|
|
|
|
updateLog(state);
|
|
|
|
}
|
2020-08-25 22:25:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function updateGps(state: AppState): void {
|
|
|
|
const t = Date.now();
|
|
|
|
const dt = (t - state.t) / 1000;
|
|
|
|
|
|
|
|
state.t = t;
|
|
|
|
state.dt += dt;
|
|
|
|
|
|
|
|
if (state.status === ActivityStatus.Running) {
|
|
|
|
state.duration += dt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!state.gpsValid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const r = EARTH_RADIUS + state.alt;
|
|
|
|
const x = r * Math.cos(state.lat) * Math.cos(state.lon);
|
|
|
|
const y = r * Math.cos(state.lat) * Math.sin(state.lon);
|
|
|
|
const z = r * Math.sin(state.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);
|
|
|
|
const vGain = state.vError / (state.vError + vError);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
state.lon = (Math.atan2(state.y, state.x) * 180 / Math.PI) || 0;
|
|
|
|
state.alt = pMag - EARTH_RADIUS;
|
|
|
|
|
|
|
|
if (state.status === ActivityStatus.Running) {
|
|
|
|
state.distance += dpMag * pGain;
|
|
|
|
state.speed = (state.distance / state.duration) || 0;
|
|
|
|
state.cadence = (60 * state.steps / state.duration) || 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-13 22:34:49 +00:00
|
|
|
export { initGps, parseCoordinate, readGps, updateGps };
|