new device communication & analysis
- switched to websockets - analysis as pure function with internal hidden cache - new redux reducers
This commit is contained in:
@@ -1,37 +1,30 @@
|
||||
|
||||
export const NEW_DEVICE_DATA = "NEW_DEVICE_DATA";
|
||||
|
||||
export const CHANGE_USER_NAME = "SET_USERNAME";
|
||||
export const CHANGE_THEME = "CHANGE_THEME";
|
||||
export const START_SESSION = "START_SESSION";
|
||||
export const STOP_SESSION = "STOP_SESSION";
|
||||
export const RESET_DEVICE_DATA = "RESET_DEVICE_DATA";
|
||||
|
||||
export const reportDeviceData = (sessionId, newDataStart, data, analysis) => ({
|
||||
type: NEW_DEVICE_DATA,
|
||||
sessionId: sessionId,
|
||||
newDataStart: newDataStart,
|
||||
data: data,
|
||||
analysis: analysis,
|
||||
})
|
||||
|
||||
export const resetDeviceData = () => ({
|
||||
type: RESET_DEVICE_DATA,
|
||||
});
|
||||
|
||||
|
||||
export const changeUsername = newUsername => ({
|
||||
type: CHANGE_USER_NAME,
|
||||
newUserName: newUsername,
|
||||
})
|
||||
});
|
||||
|
||||
export const changeTheme = newThemeName => ({
|
||||
type: CHANGE_THEME,
|
||||
newThemeName: newThemeName
|
||||
})
|
||||
});
|
||||
|
||||
export const startSession = () => ({
|
||||
type: START_SESSION
|
||||
})
|
||||
});
|
||||
|
||||
export const stopSession = () => ({
|
||||
type: STOP_SESSION
|
||||
})
|
||||
});
|
||||
|
||||
// ---------------------
|
||||
|
||||
|
||||
|
||||
143
state/DeviceReduxCoupling.js
Normal file
143
state/DeviceReduxCoupling.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import SwimTrackerWebsocketConnection from "../data_processing/SwimTrackerWebsocketConnection";
|
||||
import DataAnalysis from "../data_processing/DataAnalysis";
|
||||
import { List } from "immutable";
|
||||
|
||||
export const ConnState = {
|
||||
DISCONNECTED: 'disconnected',
|
||||
CONNECTED_STOPPED: 'connected_stopped',
|
||||
CONNECTED_RUNNING: 'connected_running',
|
||||
CONNECTED_STARTING: 'connected_starting', // start message sent, but device hasn't ack'ed it yet
|
||||
CONNECTED_STOPPING: 'connected_stopping' // stop message sent..
|
||||
}
|
||||
|
||||
// -------------------------------------------- Actions ---------------------------------------------
|
||||
|
||||
export const DEVICE_DISCONNECT = "DEVICE_DISCONNECT";
|
||||
export const DEVICE_CONNECT = "DEVICE_CONNECT";
|
||||
export const SESSION_STARTED = "SESSION_STARTED";
|
||||
export const SESSION_STOPPED = "SESSION_STOPPED";
|
||||
export const SESSION_NEW_DATA = "SESSION_NEW_DATA";
|
||||
|
||||
export const START_SESSION = "START_SESSION";
|
||||
export const STOP_SESSION = "STOP_SESSION";
|
||||
|
||||
|
||||
export const reportSessionStarted = (sessionId) => ({
|
||||
type: SESSION_STARTED,
|
||||
sessionId: sessionId
|
||||
});
|
||||
|
||||
export const reportSessionStopped = () => ({
|
||||
type: SESSION_STOPPED
|
||||
});
|
||||
|
||||
export const reportNewSessionData = (allMeasurements, analysis) => ({
|
||||
type: SESSION_NEW_DATA,
|
||||
data: allMeasurements,
|
||||
analysis: analysis
|
||||
});
|
||||
|
||||
export const reportDeviceConnect = () => ({
|
||||
type: DEVICE_CONNECT
|
||||
});
|
||||
|
||||
export const reportDeviceDisconnect = () => ({
|
||||
type: DEVICE_DISCONNECT
|
||||
});
|
||||
|
||||
export const startSession = () => ({
|
||||
type: START_SESSION
|
||||
});
|
||||
|
||||
export const stopSession = () => ({
|
||||
type: STOP_SESSION
|
||||
});
|
||||
|
||||
// -------------------------------------------- Device coupling -------------------------------------
|
||||
|
||||
export class DeviceReduxCoupling {
|
||||
constructor(reduxStore) {
|
||||
this.reduxStore = reduxStore;
|
||||
this.analysis = new DataAnalysis();
|
||||
|
||||
this.conn = null;
|
||||
|
||||
this.reduxStore.subscribe(this._onStateChange);
|
||||
this._onStateChange();
|
||||
}
|
||||
|
||||
_onStateChange = () => {
|
||||
const state = this.reduxStore.getState();
|
||||
|
||||
if (this.conn === null || (state.settings.swimTrackerHost != this.conn.swimTrackerHost)) {
|
||||
this.conn = new SwimTrackerWebsocketConnection(state.settings.swimTrackerHost,
|
||||
this._onNewData,
|
||||
(sessionId) => this.reduxStore.dispatch(reportSessionStarted(sessionId)),
|
||||
() => this.reduxStore.dispatch(reportSessionStopped()),
|
||||
() => this.reduxStore.dispatch(reportDeviceConnect()),
|
||||
() => this.reduxStore.dispatch(reportDeviceDisconnect())
|
||||
);
|
||||
}
|
||||
if (state.deviceState.connState === ConnState.CONNECTED_STARTING) {
|
||||
console.log("sending start command to connection");
|
||||
this.conn.sendStartCommand();
|
||||
}
|
||||
else if (state.deviceState.connState === ConnState.CONNECTED_STOPPING)
|
||||
this.conn.sendStopCommand();
|
||||
}
|
||||
|
||||
_onNewData = (newData) => {
|
||||
const state = this.reduxStore.getState();
|
||||
const allMeasurements = state.deviceState.measurements.concat(List(newData));
|
||||
const analysisResult = this.analysis.analyze(state.settings.analysis, state.deviceState.sessionId, allMeasurements);
|
||||
this.reduxStore.dispatch(reportNewSessionData(allMeasurements, analysisResult));
|
||||
}
|
||||
};
|
||||
|
||||
// -------------------------------------------- Reducer -----------------------------------------------
|
||||
|
||||
const INITIAL_ANALYSIS = {
|
||||
'peaks': List(),
|
||||
'totalTime': null,
|
||||
'totalMomentum': null,
|
||||
'peakMax': null,
|
||||
'momentumWindow': null,
|
||||
'peakMaxWindow': null,
|
||||
};
|
||||
|
||||
const INITIAL_DEVICE_STATE = {
|
||||
connState: ConnState.DISCONNECTED,
|
||||
sessionId: 0,
|
||||
measurements: List(),
|
||||
analysis: INITIAL_ANALYSIS,
|
||||
};
|
||||
|
||||
export const deviceStateReducer = (state = INITIAL_DEVICE_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SESSION_NEW_DATA:
|
||||
const res = {
|
||||
...state,
|
||||
measurements: action.data,
|
||||
analysis: { ...state.analysis, ...action.analysis }
|
||||
};
|
||||
return res;
|
||||
case DEVICE_CONNECT:
|
||||
return { ...INITIAL_DEVICE_STATE, connState: ConnState.CONNECTED_STOPPED };
|
||||
case DEVICE_DISCONNECT:
|
||||
return { ...INITIAL_DEVICE_STATE, connState: ConnState.DISCONNECTED };
|
||||
case SESSION_STARTED:
|
||||
return { ...INITIAL_DEVICE_STATE, connState: ConnState.CONNECTED_RUNNING, sessionId: action.sessionId };
|
||||
case SESSION_STOPPED:
|
||||
return { ...INITIAL_DEVICE_STATE, connState: ConnState.CONNECTED_STOPPED };
|
||||
case START_SESSION:
|
||||
return { ...INITIAL_DEVICE_STATE, connState: ConnState.CONNECTED_STARTING };
|
||||
case STOP_SESSION:
|
||||
return { ...INITIAL_DEVICE_STATE, connState: ConnState.CONNECTED_STOPPING };
|
||||
default:
|
||||
console.log("Unhandled state in deviceStateReducer", action.type);
|
||||
return state
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,40 +1,27 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { List } from 'immutable';
|
||||
import { CHANGE_THEME, CHANGE_USER_NAME, NEW_DEVICE_DATA, START_SESSION, STOP_SESSION, RESET_DEVICE_DATA } from './ActionCreators';
|
||||
import { deviceStateReducer } from "./DeviceReduxCoupling";
|
||||
|
||||
const INITIAL_SETTINGS = {
|
||||
theme: "hot",
|
||||
username: "",
|
||||
deviceURL: "http://192.168.178.107",
|
||||
peaksPerLap: 30,
|
||||
swimTrackerHost: "192.168.178.110",
|
||||
|
||||
// advanced
|
||||
peakDetector: 'SIMPLE', // either 'SIMPLE' or 'ZSCORE'
|
||||
peakDetectorSimpleThreshold: 2500,
|
||||
|
||||
peakDetectorZScoreLag: 8, // peak detector z-score values
|
||||
peakDetectorZScoreThreshold: 2,
|
||||
peakDetectorZScoreInfluence: 0.1,
|
||||
};
|
||||
|
||||
const INITIAL_CURRENT_SESSION = {
|
||||
running: false,
|
||||
sessionId: 0,
|
||||
rawData: List(),
|
||||
analysis: {
|
||||
'peaks': List(),
|
||||
'totalTime': null,
|
||||
'activeTime': null,
|
||||
'totalMomentum': null,
|
||||
'peakFrequency': null,
|
||||
'peakMax': null,
|
||||
// windowed quantities
|
||||
'momentumWindow': null,
|
||||
'frequencyWindow': null,
|
||||
'peakMaxWindow': null,
|
||||
peaksPerLap: 30,
|
||||
windowSizeInSecs: 5,
|
||||
numMeasurementsPerSec: 10,
|
||||
|
||||
peakDetector: 'SIMPLE', // either 'SIMPLE' or 'ZSCORE'
|
||||
peakDetectorSimpleThreshold: 2500,
|
||||
|
||||
peakDetectorZScoreLag: 8, // peak detector z-score values
|
||||
peakDetectorZScoreThreshold: 2,
|
||||
peakDetectorZScoreInfluence: 0.1,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const settingsReducer = (state = INITIAL_SETTINGS, action) => {
|
||||
switch (action.type) {
|
||||
case CHANGE_THEME:
|
||||
@@ -46,35 +33,7 @@ const settingsReducer = (state = INITIAL_SETTINGS, action) => {
|
||||
}
|
||||
};
|
||||
|
||||
const currentSessionReducer = (state = INITIAL_CURRENT_SESSION, action) => {
|
||||
switch (action.type) {
|
||||
case START_SESSION:
|
||||
return {
|
||||
running: true,
|
||||
rawData: List(),
|
||||
analysis: INITIAL_CURRENT_SESSION.analysis
|
||||
};
|
||||
case STOP_SESSION:
|
||||
return {
|
||||
running: false,
|
||||
rawData: List(),
|
||||
analysis: INITIAL_CURRENT_SESSION.analysis
|
||||
};
|
||||
case NEW_DEVICE_DATA:
|
||||
return {
|
||||
running: action.data.size > 0,
|
||||
sessionId: action.sessionId,
|
||||
rawData: action.data,
|
||||
analysis: { ...state.analysis, ...action.analysis },
|
||||
}
|
||||
case RESET_DEVICE_DATA:
|
||||
return INITIAL_CURRENT_SESSION
|
||||
default:
|
||||
return state
|
||||
}
|
||||
};
|
||||
|
||||
export default combineReducers({
|
||||
settings: settingsReducer,
|
||||
session: currentSessionReducer,
|
||||
deviceState: deviceStateReducer,
|
||||
});
|
||||
Reference in New Issue
Block a user