New data processing
This commit is contained in:
parent
655d8b0dc8
commit
00be9e1db2
|
@ -7,6 +7,7 @@ import Graph from './Graph';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { stopSession } from '../state/ActionCreators';
|
import { stopSession } from '../state/ActionCreators';
|
||||||
import backgroundColors from './Themes';
|
import backgroundColors from './Themes';
|
||||||
|
import { useKeepAwake } from 'expo-keep-awake';
|
||||||
|
|
||||||
|
|
||||||
function LiveTrainingView(props)
|
function LiveTrainingView(props)
|
||||||
|
@ -19,6 +20,8 @@ function LiveTrainingView(props)
|
||||||
const laps = (analysis.peaks.size / props.peaksPerLap).toFixed(1);
|
const laps = (analysis.peaks.size / props.peaksPerLap).toFixed(1);
|
||||||
const totalMomentum = Math.trunc(analysis.totalMomentum / 10000);
|
const totalMomentum = Math.trunc(analysis.totalMomentum / 10000);
|
||||||
|
|
||||||
|
useKeepAwake();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={backgroundColors[props.theme]}
|
colors={backgroundColors[props.theme]}
|
||||||
|
|
|
@ -1,32 +1,24 @@
|
||||||
import DeviceHttpDataSource from './DeviceDataSource';
|
import DeviceHttpDataSource from './DeviceDataSource';
|
||||||
import { List } from 'immutable';
|
import { List } from 'immutable';
|
||||||
import { reportDeviceData } from '../state/ActionCreators';
|
import { reportDeviceData, resetDeviceData } from '../state/ActionCreators';
|
||||||
import { PeakDetectorSimple} from './PeakDetection';
|
import { PeakDetectorSimple } from './PeakDetection';
|
||||||
|
|
||||||
|
|
||||||
// todo: put in settings?
|
// todo: put in settings?
|
||||||
const NUM_MEASUREMENTS_PER_SECOND = 10;
|
const NUM_MEASUREMENTS_PER_SECOND = 10;
|
||||||
const WINDOW_SIZE_SECS = 5;
|
const WINDOW_SIZE_SECS = 5;
|
||||||
|
|
||||||
// This is the main data processing entry point, coupling between device and redux store
|
|
||||||
// - periodically fetch data
|
|
||||||
// - feeds them to analysis, (manages analysis classes)
|
|
||||||
// - adds them to redux store
|
|
||||||
class DataProcessing {
|
class DataProcessing {
|
||||||
constructor(reduxStore) {
|
constructor(reduxStore) {
|
||||||
this.store = reduxStore;
|
this.store = reduxStore;
|
||||||
this.store.subscribe(this.onStateChange);
|
this.store.subscribe(this.onStateChange);
|
||||||
this.state = this.store.getState();
|
this.state = this.store.getState();
|
||||||
this.dataSource = null;
|
this.dataSource = null;
|
||||||
//console.log("state", this.state);
|
|
||||||
console.assert(this.state.session.running === false, "Created DataProcessing with running=True");
|
|
||||||
this.onDataSourceChanged(this.state.settings.deviceURL);
|
|
||||||
|
|
||||||
this.rawMeasurements = List();
|
|
||||||
this.sessionStartTime = 0;
|
|
||||||
|
|
||||||
this.peakDetectorSimple = new PeakDetectorSimple(this.state.settings.peakDetectorSimpleThreshold);
|
this.peakDetectorSimple = new PeakDetectorSimple(this.state.settings.peakDetectorSimpleThreshold);
|
||||||
this.peaks = List();
|
|
||||||
|
this.onDataSourceChanged(this.state.settings.deviceURL);
|
||||||
|
this.onRunningChanged(this.state.session.running, this.state.settings.deviceURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStateChange = () => {
|
onStateChange = () => {
|
||||||
|
@ -37,13 +29,23 @@ class DataProcessing {
|
||||||
if (newState.session.running !== this.state.session.running) {
|
if (newState.session.running !== this.state.session.running) {
|
||||||
this.onRunningChanged(newState.session.running, newState.settings.deviceURL);
|
this.onRunningChanged(newState.session.running, newState.settings.deviceURL);
|
||||||
};
|
};
|
||||||
if(newState.settings.peakDetectorSimpleThreshold !== this.state.settings.peakDetectorSimpleThreshold) {
|
if (newState.settings.peakDetectorSimpleThreshold !== this.state.settings.peakDetectorSimpleThreshold) {
|
||||||
this.peakDetectorSimple = new PeakDetectorSimple(newState.settings.peakDetectorSimpleThreshold, this.onNewPeak);
|
this.onAnalysisParameterChange();
|
||||||
this.peaks = List(this.peakDetectorSimple.addVector(this.rawMeasurements));
|
|
||||||
};
|
};
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetAnalysis = () => {
|
||||||
|
this.peakDetectorSimple = new PeakDetectorSimple(this.state.settings.peakDetectorSimpleThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAnalysisParameterChange = () => {
|
||||||
|
this.resetAnalysis();
|
||||||
|
this.peakDetectorSimple.addVector(this.state.session.rawData.toArray());
|
||||||
|
const analysis = this.analyzeNewMeasurements(data.values, List());
|
||||||
|
this.store.dispatch(reportDeviceData(this.state.session.sessionId, this.state.session.rawData.size, this.state.session.rawData, analysis));
|
||||||
|
}
|
||||||
|
|
||||||
onDataSourceChanged = (newDeviceURL) => {
|
onDataSourceChanged = (newDeviceURL) => {
|
||||||
if (this.dataSource !== null) {
|
if (this.dataSource !== null) {
|
||||||
this.dataSource.stop();
|
this.dataSource.stop();
|
||||||
|
@ -55,12 +57,12 @@ class DataProcessing {
|
||||||
onRunningChanged = (running, deviceURL) => {
|
onRunningChanged = (running, deviceURL) => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
if (running) {
|
if (running) {
|
||||||
console.log("Starting session", deviceURL + "/api/session/start" );
|
//console.log("Starting session", deviceURL + "/api/session/start");
|
||||||
req.open("GET", deviceURL + "/api/session/start");
|
req.open("GET", deviceURL + "/api/session/start");
|
||||||
this.dataSource.startIndex = 0;
|
this.dataSource.startIndex = 0;
|
||||||
this.dataSource.start();
|
this.dataSource.start();
|
||||||
} else {
|
} else {
|
||||||
console.log("Stopping session");
|
//console.log("Stopping session");
|
||||||
req.open("GET", deviceURL + "/api/session/stop");
|
req.open("GET", deviceURL + "/api/session/stop");
|
||||||
this.dataSource.stop();
|
this.dataSource.stop();
|
||||||
this.dataSource.startIndex = 0;
|
this.dataSource.startIndex = 0;
|
||||||
|
@ -71,55 +73,46 @@ class DataProcessing {
|
||||||
}
|
}
|
||||||
|
|
||||||
onNewData = (data) => {
|
onNewData = (data) => {
|
||||||
data.values;
|
if (data.sessionStartTime == this.state.session.sessionId &&
|
||||||
data.sessionStartTime;
|
data.startIndex == this.state.session.rawData.size) {
|
||||||
data.startIndex;
|
|
||||||
let success = false;
|
|
||||||
if (data.sessionStartTime == this.sessionStartTime && data.startIndex == this.rawMeasurements.size) {
|
|
||||||
// normal case, add received data to measurement array
|
// normal case, add received data to measurement array
|
||||||
this.rawMeasurements = this.rawMeasurements.concat(List(data.values));
|
const newData = this.state.session.rawData.concat(List(data.values));
|
||||||
success = true;
|
const analysis = this.analyzeNewMeasurements(data.values, this.state.session.rawData);
|
||||||
|
this.store.dispatch(reportDeviceData(data.sessionStartTime, data.startIndex, newData, analysis));
|
||||||
}
|
}
|
||||||
else if (data.startIndex === 0) {
|
else if (data.startIndex === 0) {
|
||||||
// new start
|
this.resetAnalysis();
|
||||||
this.sessionStartTime = data.sessionStartTime;
|
const newData = List(data.values);
|
||||||
this.rawMeasurements = List(data.values);
|
const analysis = this.analyzeNewMeasurements(data.values, this.state.session.rawData);
|
||||||
success = true;
|
this.store.dispatch(reportDeviceData(data.sessionStartTime, data.startIndex, newData, analysis));
|
||||||
} else {
|
} else {
|
||||||
console.log("Requery :(");
|
|
||||||
console.log("this.sessionStartTime", this.sessionStartTime);
|
|
||||||
console.log("this.rawMeasurements", this.rawMeasurements.toArray());
|
|
||||||
console.log("data", data);
|
|
||||||
// missed some data -> re-query
|
// missed some data -> re-query
|
||||||
|
console.log("Requery :(");
|
||||||
|
//console.log("Session times", data.sessionStartTime == this.state.session.sessionId, data.sessionStartTime, this.state.session.sessionId);
|
||||||
|
//console.log("Index ",data.startIndex == this.state.session.rawData.size, data.startIndex, this.state.session.rawData.size);
|
||||||
|
this.resetAnalysis();
|
||||||
this.dataSource.startIndex = 0;
|
this.dataSource.startIndex = 0;
|
||||||
this.sessionStartTime = 0;
|
this.store.dispatch(resetDeviceData());
|
||||||
//console.log("Problem: got non-consequtive data. Received:", data,
|
|
||||||
// "Own state ", { startTime: this.sessionStartTime, values: this.rawMeasurements });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
const analysis = this.analyzeNewMeasurements(data.startIndex);
|
|
||||||
const report = reportDeviceData(this.sessionStartTime, data.startIndex, this.rawMeasurements, analysis);
|
|
||||||
this.store.dispatch(report);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
analyzeNewMeasurements = (newDataStartIdx) => {
|
analyzeNewMeasurements = (newData, oldData) => {
|
||||||
//TODO is ".toArray()" really necessary here?
|
const newPeaks = this.peakDetectorSimple.addVector(newData);
|
||||||
const newPeaks = this.peakDetectorSimple.addVector(this.rawMeasurements.slice(newDataStartIdx).toArray());
|
const allPeaks = this.state.session.analysis.peaks.concat(List(newPeaks));
|
||||||
this.peaks = this.peaks.concat(List(newPeaks));
|
|
||||||
const totalMomentum = this.rawMeasurements.reduce((sum, x) => sum + x, 0);
|
const allMeasurements = oldData.concat(List(newData));
|
||||||
const peakMax = this.rawMeasurements.reduce((running, x) => Math.max(x, running), 0);
|
const totalMomentum = allMeasurements.reduce((sum, x) => sum + x, 0);
|
||||||
|
const peakMax = allMeasurements.reduce((running, x) => Math.max(x, running), 0);
|
||||||
|
|
||||||
// windowed quantities
|
// windowed quantities
|
||||||
const windowSizeMeasurements = WINDOW_SIZE_SECS * NUM_MEASUREMENTS_PER_SECOND;
|
const windowSizeMeasurements = WINDOW_SIZE_SECS * NUM_MEASUREMENTS_PER_SECOND;
|
||||||
const windowedSeq = this.rawMeasurements.slice(-windowSizeMeasurements);
|
const windowedSeq = allMeasurements.slice(-windowSizeMeasurements);
|
||||||
const peakMaxWindow = windowedSeq.reduce((running, x) => Math.max(x, running), 0);
|
const peakMaxWindow = windowedSeq.reduce((running, x) => Math.max(x, running), 0);
|
||||||
const momentumWindow = windowedSeq.reduce((sum, x) => sum + x, 0);
|
const momentumWindow = windowedSeq.reduce((sum, x) => sum + x, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
peaks: this.peaks,
|
peaks: allPeaks,
|
||||||
totalTime: this.rawMeasurements.length / NUM_MEASUREMENTS_PER_SECOND,
|
totalTime: allMeasurements.length / NUM_MEASUREMENTS_PER_SECOND,
|
||||||
activeTime: 0,
|
activeTime: 0,
|
||||||
totalMomentum: totalMomentum,
|
totalMomentum: totalMomentum,
|
||||||
peakFrequency: 0,
|
peakFrequency: 0,
|
||||||
|
@ -130,6 +123,6 @@ class DataProcessing {
|
||||||
peakMaxWindow: peakMaxWindow,
|
peakMaxWindow: peakMaxWindow,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export default DataProcessing;
|
export default DataProcessing;
|
|
@ -45,7 +45,6 @@ class DeviceHttpDataSource {
|
||||||
const arrayBuffer = await this.getUrl(url);
|
const arrayBuffer = await this.getUrl(url);
|
||||||
const decoded = msgpack.decode(new Uint8Array(arrayBuffer), { codec: this.msgpackCodec });
|
const decoded = msgpack.decode(new Uint8Array(arrayBuffer), { codec: this.msgpackCodec });
|
||||||
this.startIndex += decoded["values"].length;
|
this.startIndex += decoded["values"].length;
|
||||||
//"values", "sessionStartTime", "startIndex"
|
|
||||||
this.onNewData(decoded);
|
this.onNewData(decoded);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|
|
@ -4,6 +4,7 @@ export const CHANGE_USER_NAME = "SET_USERNAME";
|
||||||
export const CHANGE_THEME = "CHANGE_THEME";
|
export const CHANGE_THEME = "CHANGE_THEME";
|
||||||
export const START_SESSION = "START_SESSION";
|
export const START_SESSION = "START_SESSION";
|
||||||
export const STOP_SESSION = "STOP_SESSION";
|
export const STOP_SESSION = "STOP_SESSION";
|
||||||
|
export const RESET_DEVICE_DATA = "RESET_DEVICE_DATA";
|
||||||
|
|
||||||
export const reportDeviceData = (sessionId, newDataStart, data, analysis) => ({
|
export const reportDeviceData = (sessionId, newDataStart, data, analysis) => ({
|
||||||
type: NEW_DEVICE_DATA,
|
type: NEW_DEVICE_DATA,
|
||||||
|
@ -13,6 +14,10 @@ export const reportDeviceData = (sessionId, newDataStart, data, analysis) => ({
|
||||||
analysis: analysis,
|
analysis: analysis,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const resetDeviceData = () => ({
|
||||||
|
type: RESET_DEVICE_DATA,
|
||||||
|
});
|
||||||
|
|
||||||
export const changeUsername = newUsername => ({
|
export const changeUsername = newUsername => ({
|
||||||
type: CHANGE_USER_NAME,
|
type: CHANGE_USER_NAME,
|
||||||
newUserName: newUsername,
|
newUserName: newUsername,
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import { List } from 'immutable';
|
import { List } from 'immutable';
|
||||||
import { CHANGE_THEME, CHANGE_USER_NAME, NEW_DEVICE_DATA, START_SESSION, STOP_SESSION } from './ActionCreators';
|
import { CHANGE_THEME, CHANGE_USER_NAME, NEW_DEVICE_DATA, START_SESSION, STOP_SESSION, RESET_DEVICE_DATA } from './ActionCreators';
|
||||||
|
|
||||||
const INITIAL_SETTINGS = {
|
const INITIAL_SETTINGS = {
|
||||||
theme: "hot",
|
theme: "hot",
|
||||||
username: "",
|
username: "",
|
||||||
deviceURL: "http://192.168.178.110",
|
deviceURL: "http://192.168.178.107",
|
||||||
peaksPerLap: 30,
|
peaksPerLap: 30,
|
||||||
|
|
||||||
// advanced
|
// advanced
|
||||||
peakDetector: 'SIMPLE', // either 'SIMPLE' or 'ZSCORE'
|
peakDetector: 'SIMPLE', // either 'SIMPLE' or 'ZSCORE'
|
||||||
peakDetectorSimpleThreshold: 4000,
|
peakDetectorSimpleThreshold: 2500,
|
||||||
|
|
||||||
peakDetectorZScoreLag: 8, // peak detector z-score values
|
peakDetectorZScoreLag: 8, // peak detector z-score values
|
||||||
peakDetectorZScoreThreshold: 2,
|
peakDetectorZScoreThreshold: 2,
|
||||||
|
@ -19,6 +19,7 @@ const INITIAL_SETTINGS = {
|
||||||
|
|
||||||
const INITIAL_CURRENT_SESSION = {
|
const INITIAL_CURRENT_SESSION = {
|
||||||
running: false,
|
running: false,
|
||||||
|
sessionId: 0,
|
||||||
rawData: List(),
|
rawData: List(),
|
||||||
analysis: {
|
analysis: {
|
||||||
'peaks': List(),
|
'peaks': List(),
|
||||||
|
@ -62,9 +63,12 @@ const currentSessionReducer = (state = INITIAL_CURRENT_SESSION, action) => {
|
||||||
case NEW_DEVICE_DATA:
|
case NEW_DEVICE_DATA:
|
||||||
return {
|
return {
|
||||||
running: action.data.size > 0,
|
running: action.data.size > 0,
|
||||||
|
sessionId: action.sessionId,
|
||||||
rawData: action.data,
|
rawData: action.data,
|
||||||
analysis: { ...state.analysis, ...action.analysis },
|
analysis: { ...state.analysis, ...action.analysis },
|
||||||
}
|
}
|
||||||
|
case RESET_DEVICE_DATA:
|
||||||
|
return INITIAL_CURRENT_SESSION
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue