New data processing

This commit is contained in:
Martin Bauer 2020-06-23 21:36:14 +02:00
parent 655d8b0dc8
commit 00be9e1db2
5 changed files with 61 additions and 57 deletions

View File

@ -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]}

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -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
} }