Top Banner

of 25

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
  • Appendix A Javascript and HTML

    A.1 Flex App

    A.1.1 index.html

    Muscle Rehabilitation

    Bluetooth Connection

    Refresh

    Devices

    Select a paired device:

    {{ device.name}}

    Disconnect

    1

  • Bend Threshold Calibration:

    {{ (100.0 * (

    bendThreshold / 1023.0)).toFixed () }}%

    Target number of repetitions:

    {{ repsTarget }}

    Count repetitions:

    {{

    countBtnString }}

    A.1.2 app.js

    var app = angular.module('flexApp ', ['ionic ', 'ngCordova ', 'chart.js '])

    app.run(function($ionicPlatform) {

    $ionicPlatform.ready(function () {

    // Hide the accessory bar by default (remove this to show the accessory bar above the

    keyboard

    // for form inputs)

    if(window.cordova && window.cordova.plugins.Keyboard) {

    cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);

    }

    if(window.StatusBar) {

    StatusBar.styleDefault ();

    }

    });

    2

  • });

    app.controller (" flexController", function($scope , $cordovaBluetoothSerial ,

    $ionicModal , $ionicPlatform , $timeout) {

    $ionicModal.fromTemplateUrl ("bluetooth -modal.html",

    { scope: $scope , animation: "slide -in-up" })

    .then(function(modal) {

    $scope.modal = modal;

    });

    $scope.$on('$destroy ', function () {

    $scope.modal.remove ();

    });

    $ionicPlatform.ready(function () {

    $scope.modal.show();

    $scope.refreshDevices ();

    });

    $scope.devicesEmpty = true;

    $scope.noDevices = false;

    $scope.selectedDevice = 0;

    $scope.connectedDevice = 0;

    $scope.refreshDevices = function () {

    var listPromise = $cordovaBluetoothSerial.list();

    listPromise.then(function(devicesData) {

    $scope.devices = devicesData; // Get rid of placeholder

    $scope.devicesEmpty = false;

    }, function(error) {

    alert(error);

    });

    };

    $scope.selectDevice = function(device , index) {

    $scope.selectedDevice = device;

    $scope.deviceError = 0;

    $scope.connectedDevice = 0;

    var connectionPromise = $cordovaBluetoothSerial.connect(device.address);

    connectionPromise.then(function () {

    $scope.deviceError = 0;

    $scope.connectedDevice = device;

    $timeout($scope.modal.hide(), 1000);

    }, function(error) {

    $scope.deviceError = device;

    $scope.selectedDevice = 0;

    alert(error);

    })

    }

    $scope.disconnect = function () {

    var disconnectPromise = $cordovaBluetoothSerial.disconnect ();

    disconnectPromise.then(function () {

    $scope.connectedDevice = 0;

    $scope.selectedDevice = 0;

    $scope.deviceError = 0;

    }, function(error) {

    alert(error);

    });

    }

    $scope.sendToDevice = function(msg) {

    var writePromise = $cordovaBluetoothSerial.write(msg);

    writePromise.then(null , function(error) { alert(error); });

    }

    $scope.chartOpts = { animation: false ,

    showTooltips : false ,

    scaleShowLabels: false ,

    scaleShowGridLines: false ,

    scaleOverride: true ,

    scaleSteps: 1,

    scaleStepWidth: 1024,

    scaleStartValue: 0,

    3

  • pointDot: false ,

    bezierCurve: false };

    $scope.chartLabels = Array (50).join (".").split (".");

    var zeros = Array.apply(null , new Array (50)).map(Number.prototype.valueOf , 0)

    var thresholdLine = Array.apply(null , new Array (50)).map(Number.prototype.valueOf , 0);

    $scope.chartData = [zeros , thresholdLine ];

    $scope.calibrationLocked = true;

    $scope.bendThreshold = 512;

    $scope.repsTarget = 25;

    $scope.nReps = 0;

    $scope.countBtnString = "Start Counting !";

    $scope.manageCalibration = function () {

    $scope.calibrationLocked = !$scope.calibrationLocked;

    if (! $scope.calibrationLocked) {

    $scope.sendToDevice ("C"); // Start calibration

    $cordovaBluetoothSerial.subscribe ("\r").then(null ,

    function(error) {

    alert(error);

    },

    function(data) {

    $scope.bendThreshold = parseInt(data.substring(0, data.length - 1));

    });

    } else {

    $scope.sendToDevice ("F"); // Finish calibration

    $scope.chartData [1] = thresholdLine.map(function () { return $scope.bendThreshold; });

    $cordovaBluetoothSerial.unsubscribe ();

    }

    }

    var countOn = false;

    $scope.startCounting = function () {

    if (countOn) {

    $scope.countBtnString = "Start Counting !";

    $scope.sendToDevice ("T");

    countOn = false;

    return;

    }

    countOn = true;

    $scope.nReps = 0;

    $scope.countBtnString = $scope.nReps;

    $scope.sendToDevice ("S"); // Start

    $cordovaBluetoothSerial.subscribe ("\r").then(null ,

    function(error) {

    alert(error);

    },

    function(data) {

    data = String(data.substring(0, data.length - 1));

    if (!isNaN(data)) {

    console.log(data);

    $scope.chartData [0]. shift();

    $scope.chartData [0]. push(data);

    } else {

    console.log(" Repetition ");

    $scope.nReps ++;

    $scope.countBtnString ++;

    }

    if ($scope.nReps == $scope.repsTarget) {

    $scope.countBtnString = "Succeeded !";

    countOn = false;

    $scope.sendToDevice ("T"); // Terminate

    $cordovaBluetoothSerial.unsubscribe ();

    $scope.chartData [0] = zeros;

    }

    });

    }

    });

    4

  • A.1.3 style.css

    .grey {

    color: gray;

    }

    .black {

    color: black;

    }

    .selected {

    color: #387 ef5;

    }

    .red {

    color: red;

    }

    .progress -bar {

    width: 100%;

    height: 30px;

    background: #f5f5f5;

    border -radius: 2px;

    }

    .increment {

    height: 100%;

    background: #444;

    transition: all .75s;

    border -radius: 2px;

    }

    .increment -calm {

    height: 100%;

    background: #33 cd5f;

    transition: all .75s;

    border -radius: 2px;

    }

    A.2 EMG App

    A.2.1 index.html

    5

  • EMG Fatigue Analysis

    Bluetooth Connection

    Refresh

    Devices

    Select a paired device:

    {{ device.name}}

    Disconnect

    Please connect the electrode to the target muscle and set the parameters below.

    When you 're ready , press the button to start analysis.

    Fatigue threshold:

    (% of initial value)

    {{ fatigueThreshold

    }}%

    Update frequency:

    (ms)

    {{ psdFreq }}

    Start

    6

  • Fatigued

    Fresh

    Initial Level

    A.2.2 app.js

    var app = angular.module('emgApp ', ['ionic ', 'ngCordova ']);

    app.run(function($ionicPlatform) {

    $ionicPlatform.ready(function () {

    // Hide the accessory bar by default (remove this to show the accessory bar above the

    keyboard

    // for form inputs)

    if(window.cordova && window.cordova.plugins.Keyboard) {

    cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);

    }

    if(window.StatusBar) {

    StatusBar.styleDefault ();

    }

    });

    });

    function arburg(input , m) {

    // Initialise vars

    var N = input.length - 1;

    var Ak = new Float32Array(m+1); // Default values are 0.0

    Ak[0] = 1.0;

    var f = new Float32Array(input);

    var b = new Float32Array(input);

    var t1 = 0.0;

    var t2 = 0.0;

    var mu = 0.0;

    var Dk = 0.0;

    for (i = 0; i

  • return Ak;

    }

    function burg2psd(Ak, NFFT) {

    // Zero -padded array for FFT

    var fourierPadded = new complex_array.ComplexArray(NFFT);

    fourierPadded.map(function(x,i) {

    x.real = (i < Ak.length) ? Ak[i] : 0.0;

    });

    var psd = fourierPadded.FFT();

    var mags = psd.magnitude ()

    for (i = 0; i < mags.length; i++) {

    mags[i] = 1.0/( mags[i]*mags[i]);

    }

    return mags.subarray(0, 0.5*( mags.length));

    }

    function getFreqFromData(data) {

    var BURG_ORDER = 20;

    var burgParameters = arburg(data , BURG_ORDER);

    var psd = burg2psd(burgParameters , data.length);

    var mnf = 0.0;

    var sumPwr = 0.0;

    var nFs = psd.length;

    for (i = 0; i < nFs; i++) {

    mnf += i*psd[i];

    sumPwr += psd[i];

    }

    mnf /= sumPwr;

    return mnf;

    }

    app.controller (" emgController", function($scope , $cordovaBluetoothSerial ,

    $ionicPlatform , $ionicModal , $timeout) {

    $ionicModal.fromTemplateUrl ("bluetooth -modal.html",

    { scope: $scope , animation: "slide -in-up" })

    .then(function(modal) {

    $scope.modal = modal;

    });

    $scope.$on('$destroy ', function () {

    $scope.modal.remove ();

    });

    $ionicPlatform.ready(function () {

    $scope.modal.show();

    $scope.refreshDevices ();

    });

    $scope.devicesEmpty = true;

    $scope.noDevices = false;

    $scope.selectedDevice = 0;

    $scope.connectedDevice = 0;

    $scope.refreshDevices = function () {

    var listPromise = $cordovaBluetoothSerial.list();

    listPromise.then(function(devicesData) {

    $scope.devices = devicesData; // Get rid of placeholder

    $scope.devicesEmpty = false;

    }, function(error) {

    alert(error);

    });

    };

    $scope.selectDevice = function(device , index) {

    $scope.selectedDevice = device;

    $scope.deviceError = 0;

    $scope.connectedDevice = 0;

    8

  • var connectionPromise = $cordovaBluetoothSerial.connect(device.address);

    connectionPromise.then(function () {

    $scope.deviceError = 0;

    $scope.connectedDevice = device;

    $timeout($scope.modal.hide(), 1000);

    }, function(error) {

    $scope.deviceError = device;

    $scope.selectedDevice = 0;

    alert(error);

    })

    }

    $scope.disconnect = function () {

    var disconnectPromise = $cordovaBluetoothSerial.disconnect ();

    disconnectPromise.then(function () {

    $scope.connectedDevice = 0;

    $scope.selectedDevice = 0;

    $scope.deviceError = 0;

    }, function(error) {

    alert(error);

    });

    }

    $scope.sendToDevice = function(msg) {

    var writePromise = $cordovaBluetoothSerial.write(msg);

    writePromise.then(null , function(error) { alert(error); });

    }

    $scope.emgOn = false;

    $scope.fatigueThreshold = 75;

    $scope.psdFreq = 500;

    var startFreq = null;

    $scope.startEMG = function () {

    $scope.sendToDevice ("S");

    var dataWindow = new Float32Array($scope.psdFreq);

    var nInWindow = 0;

    $cordovaBluetoothSerial.subscribe('\r', null ,

    function(error) {

    alert(error);

    }, function(received) {

    received = received.substring(0, received.length - 1);

    console.log(" Received: " + parseFloat(received))

    dataWindow[nInWindow] = parseFloat(received);

    nInWindow ++;

    if (nInWindow == $scope.psdFreq) {

    nInWindow = 0;

    var mnf = getFreqFromData(dataWindow);

    $scope.meanFreq = mnf.toFixed (2);

    }

    });

    var meanFreq = getFreqFromData(dataWindow);

    if (! startFreq) startFreq = meanFreq;

    // $scope.currentFreqPercentage = (80* meanFreq/startFreq).toFixed ();

    $timeout(function () { $scope.emgOn = true; }, 800);

    }

    });

    A.2.3 style.css

    .freq -indicator {

    width: 100%;

    height: 50px;

    background: #3A1414;

    border -radius: 2px;

    }

    .freq -indicator -handle {

    float:left;

    9

  • background: #e42012;

    height: 100%;

    width: 15px;

    opacity: 0.8;

    border -radius: 2px;

    margin -left: 50%;

    transition: all 0.5s;

    }

    .freq -start -marker {

    background: #fff;

    height: 100%;

    width: 6px;

    margin -left: 80%;

    z-index: 10;

    opacity: 0.2;

    }

    .initial -level -text {

    text -align: center;

    margin -left :60%;

    margin -top: 5px;

    }

    A.3 Ergonomics App

    A.3.1 index.html

    10

  • Please fill in the forms below. All measurements are in centimetres (cm).

    If you wish to use the app s accelerometer measurement function , then hold

    down the crosshair icon next to the field while you move the phone through

    the desired distance , then release it.

    {{param.label}}

    Male

    Female

    {{ param.label }}:

    Save

    Target health improvements:

    {{ target.name}}

    Save

    See your

    recommendations

    11

  • Your height is approximately in the:

    {{ heightPercentileString }}

    Percentile for your gender

    Based on this , your approximate measurements are below:

    {{ measurement.val.toFixed ()}}cm

    Here is an analysis of your results. Refer to the diagram below for an explanation

    of measurements.

    Light Level:

    {{ lightLevel }} lx

    Study/Sleep:

    12

  • Too Dark

    OK...

    Good!

    Too Bright

    OK...

    Good!

    A.3.2 app.js

    angular.module('ergoApp.controllers ', []);

    var app = angular.module('ergoApp ', ['ionic ', 'ngCordova ', 'ergoApp.controllers ']);

    app.run(function($ionicPlatform) {

    $ionicPlatform.ready(function () {

    // Hide the accessory bar by default (remove this to show the accessory bar above the

    keyboard

    // for form inputs)

    if(window.cordova && window.cordova.plugins.Keyboard) {

    cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);

    }

    if(window.StatusBar) {

    StatusBar.styleDefault ();

    }

    });

    });

    app.service('personalParameters ', function($filter) {

    var paramsList = [];

    var defaultParams = [ { label : "Gender", val : "Male", type : 'sel ', id : 'gender ' },

    { label : "Height", val : null , type : 'num ', id : 'height ' },

    { label : "Shoe Height", val : null , type: 'num ', id : 'shoeHeight ' },

    { label : "Desk Height", val : null , type: 'num ', id : 'deskHeight ' }

    ];

    this.resetParams = function () {

    paramsList = defaultParams;

    }

    this.getParams = function () {

    return paramsList;

    }

    this.getParam = function(name) {

    return $filter('filter ')(paramsList , { id : name }, true)[0];

    }

    this.paramsFromString = function(paramsString) {

    if (paramsString) {

    var tempParamsObj = angular.fromJson(paramsString);

    if (tempParamsObj.length != defaultParams.length) {

    this.resetParams ();

    13

  • } else {

    paramsList = tempParamsObj;

    }

    } else {

    this.resetParams ();

    }

    }

    this.loadParams = function () {

    this.paramsFromString(window.localStorage['parameters ']);

    }

    this.saveParams = function () {

    window.localStorage['parameters '] = angular.toJson(paramsList);

    }

    });

    app.service('healthTargets ', function () {

    var targets = [ { name : "Upper leg circulation", selected : false },

    { name : "Lower back pain", selected : false },

    { name : "Shoulders slouching", selected : false },

    { name : "Neck strain", selected : false },

    { name : "Leaning shoulders forwards", selected : false },

    { name : "Head -forward posture", selected : false },

    { name : "Slouching (body)", selected : false } ];

    this.getTargets = function () {

    return targets;

    }

    this.targetsFromString = function(targetsString) {

    if (targetsString) {

    var tempTargetsObj = angular.fromJson(targetsString);

    if (tempTargetsObj.length != targets.length) return;

    targets = tempTargetsObj;

    }

    }

    this.loadTargets = function () {

    this.targetsFromString(window.localStorage['healthTargets ']);

    }

    this.saveTargets = function () {

    window.localStorage['healthTargets '] = angular.toJson(targets);

    }

    });

    app.service('measurements ', function($filter) {

    var measurements = [];

    var percentile = null;

    var percentileInterps_ = {

    popliteal : { M : [37.9103 , 0.27397 , -0.00474992 , 0.0000329045] ,

    F : [33.6337 , 0.262061 , -0.00406481 , 0.0000264853]

    },

    buttockPopliteal : { M : [41.9744 , 0.357434 , -0.00627922 ,

    0.0000422334] ,

    F : [41.1117 , 0.314483 , -0.00544547 ,

    0.0000374628] },

    seatedLegHeight : { M : [46.9841 , 0.344695 , -0.00583202 ,

    0.0000380687] ,

    F : [43.5857 , 0.289506 , -0.00513983 ,

    0.0000354398] },

    upperArmLength : { M : [34.4534 , 0.18154 , -0.00265015 ,

    0.0000174923] ,

    F : [31.7875 , 0.14473 , -0.00204166 ,

    0.000014238] },

    seatedShoulderHeight : { M : [48.7464 , 0.139865 , -0.00232796 ,

    0.0000153862] ,

    F : [47.9073 , 0.115281 , -0.0019322 ,

    0.0000128412] }

    14

  • };

    this.calc = function(height , gender) {

    percentile = height2percentile(height);

    measurements = [ { name : "Popliteal Height", id : "popliteal",

    val : polynomial(percentileInterps_['popliteal '][ gender], percentile)

    },

    { name : "Buttock -popliteal Length", id : "buttockPopliteal",

    val : polynomial(percentileInterps_['buttockPopliteal '][ gender],

    percentile) },

    { name : "Seated Leg Height", id : "seatedLegHeight",

    val : polynomial(percentileInterps_['seatedLegHeight '][ gender],

    percentile) },

    { name : "Upper Arm Length" , id : "upperArmLength",

    val : polynomial(percentileInterps_['upperArmLength '][ gender],

    percentile) },

    { name : "Seated Shoulder Height (excl. seat height)", id : '

    seatedShoulderHeight ',

    val : polynomial(percentileInterps_['upperArmLength '][ gender],

    percentile) } ];

    }

    this.getPercentile = function () {

    return percentile;

    }

    this.getMeasurements = function () {

    return measurements;

    }

    this.getVal = function(idIn) {

    var obj = $filter('filter ')(measurements , { id : idIn }, true);

    return obj.val;

    }

    // "Private" helper functions

    var height2percentile = function(h) {

    var d = Math.pow ( -1.68468 e68 + 9.7134 e65*h + 1.18584 e30*Math.sqrt (2.01849 e76 - 2.32737 e74*

    h + 6.70949 e71*h*h), 1/3.);

    return 50.434 - (2.62631 e23/d) + (1.85244e-21*d);

    }

    var polynomial = function(coeffs , x) {

    return coeffs [0] + coeffs [1]*x + coeffs [2]*x*x + coeffs [3]*x*x*x;

    }

    this.seatHeightEqn = function(shoeHeight) {

    var s = getVal('popliteal ') + shoeHeight;

    return { low : s*Math.cos(Math.PI/6.),

    high : s*Math.cos(Math.PI/36.) };

    }

    this.elbowDeskEqn = function () {

    var SEH = getVal('seatedShoulderHeight ') - getVal('upperArmLength ');

    return { low : SEH ,

    high : SEH + 5 };

    }

    this.backrestEqn = function () {

    var SSH = getVal('seatedShoulderHeight ');

    return { low : 0.6*SSH ,

    high : 0.8* SSH };

    }

    this.seatDepthEqn = function () {

    var BPL = getVal('buttockPopliteal ');

    return { low : 0.8*BPL ,

    high : 0.95* BPL };

    }

    this.leaningElbowEqn = function(theta , beta) {

    var SSH = getVal('seatedShoulderHeight ');

    var U = getVal('upperArmLength ');

    var SEH = SSH - U;

    return SEH + U*((1 - Math.cos(theta)) + Math.cos(theta)*(1 - Math.cos(beta)));

    15

  • }this.shoeHeightEqn = function(shoeHeight) {

    var SLH = getVal('seatedLegHeight ');

    return SLH + shoeHeight + 2.;

    }

    });

    app.config(function($stateProvider , $urlRouterProvider) {

    $urlRouterProvider.otherwise('/personal ');

    $stateProvider.state('personal ', {

    url: '/personal ',

    views: {

    personal: {

    templateUrl: 'personal.html ',

    controller: 'personalController '

    }

    }

    });

    $stateProvider.state('results ', {

    url: '/results ',

    views: {

    results: {

    templateUrl: 'results.html ',

    controller: 'resultsController '

    }

    }

    });

    $stateProvider.state('light ', {

    url: '/light ',

    views: {

    light: {

    templateUrl: 'light.html ',

    controller: 'lightController '

    }

    }

    });

    });

    A.3.3 lightController.js

    angular.module('ergoApp.controllers ')

    .controller (" lightController", function($scope) {

    $scope.measureLight = function () {

    AmbientLightPlugin.start();

    AmbientLightPlugin.getValue( function(success) {

    $scope.lightLevel = success.substring (0, success.length - 2);

    $scope.$apply ();

    }, function(error) {

    alert(error);

    });

    AmbientLightPlugin.stop();

    };

    $scope.measureLight = function () { return 600; };

    $scope.lightLevel = $scope.measureLight ();

    $scope.dayLevels = true;

    $scope.nightThreshold = { 'low ' : 80, 'high ' : 140 };

    $scope.dayThreshold = { 'low ' : 750, 'high ' : 1000 };

    $scope.threshold = $scope.dayThreshold;

    });

    A.3.4 personalController.js

    // Helper functions

    function parseAccel(accel) {

    return { x : parseFloat(accel.x), y : parseFloat(accel.y), z : parseFloat(accel.z) };

    }

    function accelComponentsLessThan(accel , threshold) {

    if (Math.abs(accel.x)

  • if (Math.abs(accel.y)
  • }initAccel.x /= 10;

    initAccel.y /= 10;

    initAccel.z /= 10;

    accels.push(parseAccel(accels[accels.length - 1]));

    accels.unshift(parseAccel(accels [0]));

    for (i = 0; i < accels.length; i++) {

    accels[i] = parseAccel(accels[i]);

    accels[i].x -= initAccel.x;

    accels[i].y -= initAccel.y;

    accels[i].z -= initAccel.z;

    accels[i] = accelComponentsLessThan(accels[i], 0.1);

    }

    for (i = 1; i < accels.length - 1; i++) {

    var thisAccel = accels[i];

    if (i > 10 && i < accels.length - 2) {

    thisAccel.x = gauss [0]* accels[i-2].x + gauss [1]* accels[i-1].x + gauss [2]* accels[i].x

    + gauss [3]* accels[i+1].x + gauss [4]* accels[i+2].x;

    thisAccel.y = gauss [0]* accels[i-2].y + gauss [1]* accels[i-1].y + gauss [2]* accels[i].y

    + gauss [3]* accels[i+1].y + gauss [4]* accels[i+2].y;

    thisAccel.z = gauss [0]* accels[i-2].z + gauss [1]* accels[i-1].z + gauss [2]* accels[i].z

    + gauss [3]* accels[i+1].z + gauss [4]* accels[i+2].z;

    }

    vs.push( { x : vs[i-1].x + thisAccel.x * timestep ,

    y : vs[i-1].y + thisAccel.y * timestep ,

    z : vs[i-1].z + thisAccel.z * timestep } );

    rs.push( { x : rs[i-1].x + vs[i].x * timestep + 0.5 * thisAccel.x * timestep * timestep ,

    y : rs[i-1].y + vs[i].y * timestep + 0.5 * thisAccel.y * timestep * timestep ,

    z : rs[i-1].z + vs[i].z * timestep + 0.5 * thisAccel.z * timestep * timestep }

    );

    }

    var e = rs.length - 1;

    return (115* Math.max(Math.abs(rs[e].x), Math.abs(rs[e].y), Math.abs(rs[e].z))).toFixed (2);

    };

    });

    A.3.5 resultsController.js

    function getOrdinal(n) {

    var s=["th","st","nd","rd"],

    v=n%100;

    return n+(s[(v-20) %10]||s[v]||s[0]);

    }

    angular.module('ergoApp.controllers ')

    .controller (" resultsController", function($scope , personalParameters ,

    healthTargets

    ,

    measurements

    )

    {

    personalParameters.loadParams ();

    var height = personalParameters.getParam('height ');

    var gender = personalParameters.getParam('gender ');

    measurements.calc(height.val , gender.val.charAt (0));

    $scope.measurements = measurements.getMeasurements ();

    var percentile = measurements.getPercentile ();

    $scope.heightPercentileString = getOrdinal(percentile.toFixed ());

    });

    18

  • A.3.6 style.css

    .traffic {

    opacity: 1.0 !important;

    cursor: default !important;

    pointer -events: none !important;

    }

    .traffic.traffic -off {

    background: #f6f6f6;

    color: #cecece;

    }

    .traffic.traffic -red {

    background: #D63030;

    color: #400 E0E;

    }

    .traffic.traffic -amber {

    background: #FFBF00;

    color: #473600;

    }

    .traffic.traffic -green {

    background: #33 AD5C;

    color: #0 F341C;

    }

    Appendix B Mathematica

    B.1 percentileInterpolations.nb

    [next page]

    B.2 burg.nb

    [2nd page from here]

    19

  • percentiles = {1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99};heightsM ={percentiles, {156.718, 161.544, 163.83, 167.64, 169.672, 171.704, 173.482,

    174.752, 177.038, 179.324, 182.372, 184.912, 189.484}};heightsF = {percentiles, {145.034, 149.86, 151.892, 155.194, 156.972, 158.496,

    159.766, 161.798, 163.576, 165.354, 168.656, 170.434, 174.752}};poplitealsM = {percentiles, {37.846, 39.37, 40.64, 41.656, 42.418,

    43.18, 43.942, 44.704, 45.212, 46.228, 47.752, 49.022, 50.8}};poplitealsF = {percentiles, {33.274, 35.56, 36.068, 37.338, 38.354,

    39.116, 39.878, 40.64, 41.402, 42.164, 43.18, 44.45, 45.72}};buttockPopM = {percentiles, {41.91, 43.942, 45.466, 46.736, 47.752,

    48.768, 49.53, 50.292, 51.054, 52.07, 53.34, 54.864, 57.658}};buttockPopF = {percentiles, {40.894, 43.18, 43.942, 45.466, 46.228,

    47.244, 48.006, 48.768, 49.53, 50.546, 52.324, 53.34, 55.88}};slhM = {percentiles, {46.482, 49.022, 50.8, 51.816, 52.578, 53.594,

    54.356, 55.118, 55.88, 56.896, 58.166, 59.436, 61.214}};slhF = {percentiles, {43.434, 45.466, 46.228, 47.244, 48.514, 49.022,

    49.784, 50.292, 51.054, 52.07, 53.34, 54.61, 56.896}};uahM = {{5, 10, 15, 25, 50, 75, 85, 90, 95},{13.9, 14.2, 14.4, 14.8, 15.4, 16.0, 16.3, 16.5, 16.9} * 2.54};uahF = {{5, 10, 15, 25, 50, 75, 85, 90, 95},{12.8, 13.0, 13.2, 13.5, 14.1, 14.6, 15.0, 15.2, 15.5} * 2.54};allMetrics = {heightsM, heightsF, poplitealsM, poplitealsF,

    buttockPopM, buttockPopF, slhM, slhF, uahM, uahF};fits = Fit#, 1, x, x2, x3, x & /@ allMetrics;CoefficientList[fits, x]Show[Plot[#1, {x, 0, 100}], ListPlot[#2, PlotStyle PointSize[Medium]],

    PlotRange All] & /@ ({fits, allMetrics})sshM = {{5, 50, 95}, {19.5, 20.3, 21.4} * 2.54};sshF = {{5, 50, 90}, {19.1, 19.8, 20.5} * 2.54};mapSparseData[sparse_, mapFit_] :=

    Max@sparseAll, 2 - Min@sparseAll, 2(mapFit /. x sparse-1, 1) - (mapFit /. x sparse1, 1) mapFit + a /.a ArgMin[#, a] &@

    Total Max@sparseAll, 2 - Min@sparseAll, 2(mapFit /. x sparse-1, 1) - (mapFit /. x sparse1, 1) mapFit +a - y 2 /. {x sparseAll, 1, y sparseAll, 2} // Expand

    mappedSshs = {mapSparseData[sshM, fits[[1]]], mapSparseData[sshF, fits[[2]]]};Show[Plot[#1, {x, 0, 100}, ImageSize Medium],

    ListPlot[#2, PlotStyle PointSize[Large]],PlotRange All] & /@ ({mappedSshs, {sshM, sshF}})

    CoefficientList[mappedSshs, x]mh = fits1Plot[mh, {x, 0, 100}]Discriminant[mh, x]Solve[mh y, x]3

  • zi[arr_List, idx_Integer] := arridx + 1burgMethod[input_List, m_Integer] :=Module{n = Length@input - 1, coeffs = Prepend[ConstantArray[0., m], 1.],

    f = input, b = input, d, , t1, t2}, d = i=0n 2 zi[f, i]2 - zi[f, 0]2 - zi[b, n]2;

    Fork = 0, k < m, k++, = -2

    d

    i=0n-k-1 zi[f, i + k + 1] zi[b, i];Fori = 0, i 1

    2(k + 1), i++,

    t1 = zi[coeffs, i] + zi[coeffs, k + 1 - i];t2 = zi[coeffs, k + 1 - i] + zi[coeffs, i];coeffs[[i + 1]] = t1;coeffs[[k + 2 - i]] = t2;;

    For[i = 0, i n - k - 1, i++,t1 = zi[f, i + k + 1] + zi[b, i];t2 = zi[b, i] + zi[f, i + k + 1];f[[i + k + 2]] = t1;b[[i + 1]] = t2;];

    d = 1 - 2 d - zi[f, k + 1]2 - zi[b, n - k - 1]2;;Return[coeffs]

    data = Table[2 Sin[0.2 n] + Sin[0.5 n] + RandomReal[{-1, 1}], {n, 0, 499}]bd = Drop[burgMethod[data, 20], 1]armaExpansion[coeffs_List, f_, t_Real: 1.] :=

    1 +k=1Length@coeffs (coeffsk Exp[-2 f k t]);

    burgPsd[coeffs_, f_, t_Real: 1.] := tAbs[armaExpansion[coeffs, f, t]]2

    discreteArma[coeffs_List, nfft_Integer] :=Fourier[Evaluate@PadRight[coeffs, nfft, 0.], FourierParameters {1, -1}];

    discretePsd[coeffs_List] := 1Abs[discreteArma[coeffs, 500]]2

    myburg = burgPsd[bd, f / 2] // N;LogPlot[myburg, {f, 0, 1}, PlotRange All]dburg = discretePsd[burgMethod[data, 20]];ListLinePlot[dburg]

  • Appendix C Arduino

    C.1 Flex App

    C.1.1 BendSensor.ino

    #include

    BendSensor* sensor;

    boolean calibrationOn = false;

    boolean countingOn = false;

    void setup () {

    Serial.begin (9600);

    sensor = new BendSensor(A0 , 10);

    }

    void loop() {

    // Read current value

    int bendVal = sensor ->readValue ();

    // If we're counting reps

    if (countingOn) {

    if (sensor ->movedPastThreshold ()) {

    Serial.println ("REP");

    sensor ->ledUp();

    }

    if (sensor ->movedBelowThreshold ()) {

    sensor ->ledDown ();

    }

    }

    // Return the current bend level if in an active state

    if (calibrationOn || countingOn) Serial.println(bendVal);

    }

    void serialEvent () {

    while (Serial.available ()) {

    char serialChar = (char) Serial.read();

    calibrationOn = serialChar == 'C';

    countingOn = serialChar == 'S';

    if (serialChar == 'F') sensor ->lockBendThresholdAtValue ();

    }

    }

    C.1.2 BendSensor.cpp

    #include "BendSensor.h"

    BendSensor :: BendSensor(int pin , int ledPin) {

    pin_ = pin;

    ledPin_ = ledPin;

    bendThreshold_ = 1023;

    count_ = 0;

    value_ = 0;

    pinMode(ledPin_ , OUTPUT);

    digitalWrite(ledPin_ , LOW);

    }

    void BendSensor :: incrementCount () {

    count_ ++;

    }

    int BendSensor :: readValue () {

    previousValue_ = value_;

    value_ = analogRead(pin_);

    delay (50);

    return value_;

    }

    bool BendSensor :: movedPastThreshold () {

    22

  • return value_ > bendThreshold_ && previousValue_
  • // the loop routine runs over and over again forever:

    void loop() {

    // read the input on analog pin 0:

    int sensorValue = analogRead(A0);

    // Convert the analog reading (which goes from 0 - 1023)

    // to a voltage (0 - 5V):

    float voltage = sensorValue * (5.0 / 1023.0);

    // print out the final amplified EMG output signal:

    Serial.println(voltage);

    }

    Appendix D Git Logs

    D.1 Apps Repository

    D.1.1 Log

    * 679 acc5 : Added frequency slider and badges for sliders

    * 184 a496 : Moved all measurements stuff to a service

    * af1a7bc : Moved local storage save to services

    * b7a159a : Updated app names and descriptions in config.xml files

    * a6ac9a6 : Lightened Flex logo

    * 68 ed767 : Added icons

    * 34 ee270 : Added measurement equations service

    * 214789d : Implemented percentile lookup in interface , and added diagram

    * db35d22 : Further improvements to service model. Started results page

    * 6039490 : Refactored healthTargets and parameters to be Angular services - can now share

    between scopes

    * 551739a : Merge branch 'percentile -interp '

    |\

    | * e6b7743 : Completed percentile interpolation and lookup code

    | * 5412199 : Corrections in maths and added height inverse function

    | * f329b0d : Hard -coded interpolation coeffs into JS

    | * cce23b6 : Worked out interps in mathematica

    |/

    * 00 c4f18 : Almost completed discrete percentile data

    * 779 bca0 : Moved ergonomics controllers into separate files

    * 223 f78b : Changed 'desk ' to 'results '

    * a607639 : Changed bend value to percentage

    * 75 b9054 : Changed received BT string handling to work with Arduino

    * 2ad44a8 : Added health targets form

    * 035278d : Changed night/day to sleep/study and updated functionality to reflect this

    * 1dc75f6 : Added night/day differentiation

    * 0094 a16 : Added traffic lights scheme

    * 580 d25f : EMG to BT mode

    * 380035d : Completed PSD estimation capability

    * fbabdd5 : Burg method proof of concept in Mathematica notebook

    * 61 b6b43 : Bluetooth and interface basics for EMG app

    * 456 a675 : Changed serial send strings , polished modal code , renamed controller

    * ffda026 : Merge branch 'flex -app -full '

    |\

    | * 8a8d95f : Merge branch 'flex -app -graph ' into flex -app -full

    | |\

    | | * cfc7346 : Working graph

    | | * b056a9d : Got basic angular -chart example working

    | |/

    | * 2880415 : Mostly completed flex app with reps progress bar

    | * 5c0b41e : Added bend -threshold and target -reps sliders

    | * 45099fb : Switched bluetooth interface modal relationship (more logical)

    | * f10e907 : Actually fixed light sensor error

    | * 416297c : Fixed error with light sensor (JS) code

    | * 21c1ecb : Added light measuring tab

    | * e6a6c5e : Mostly completed the personal details section. Includes save feature

    | * 13146d0 : Created basic tabbed interface (no logic atm)

    |/

    * eb8990e : Fixed bluetooth comms

    * 6c1749e : Updated ionicons css

    * a31a061 : Mostly working bluetooth interface

    * cdad5f5 : Removed echoTest

    * 39 d8a99 : Created 2 new blank apps

    * 65 d001e : Changed title in ergonomics app

    * 444 a4a0 : Renamed from testProject

    * 561 b207 : Wrote light sensor plugin , added to app

    * d1e8e47 : Accelerometer measurement first commit

    * 0edf519 : Added basic accelerometer demo project

    24

  • * 0ac3b47 : Initial commit

    D.1.2 Additions to Master

    D.2 Hardware Repository

    D.2.1 Log

    * 24 f6d00 : Added EMG sketch

    * 0a2fa63 : Fixed some errors and improved code

    * 805 a460 : Updated sketch to (hopefully) work with the app

    * eda30f4 : Added 50ms delay when reading values to eliminate voltage bounce

    * 63 c4f37 : Merge branch 'bend -oop -refactor '

    |\

    | * 9087963 : Added include

    * | 26f7673 : Merge pull request #2 from Team -Rocket -UCL/bend -oop -refactor

    |\ \

    | |/

    | * 14861f5 : Fully separated implementation and definition in library

    | * 4993 bfb : Wrote and added BendSensor library; the sketch should run as before , but the

    code is more generalised

    |/

    * f439b5a : Committed the deletion

    * 2e12d59 : Converted GitHub text files to Arduino IDE sketch folders

    * 126 f3a9 : Create Bend sensor code

    * 215 d6ab : Merge pull request #1 from Team -Rocket -UCL/Bluetooth -thing

    |\

    | * 248 dee4 : Create Bluetooth Calibration

    |/

    * 771844e : Initial commit

    D.2.2 Additions to Master

    25