Travis Chantkeady5Thomas Keady
Published © CC BY-NC

FormCheck: Improve Your Strength Training Form

Improper strength training is dangerous, but personal trainers are expensive. FormCheck provides real-time feedback on your exercise form.

AdvancedFull instructions providedOver 2 days2,772

Things used in this project

Hardware components

Xbox One Kinect
Microsoft has discontinued the Xbox Kinect line, but you can buy used Xbox One Kinects on eBay for $30 or so. There's also other 3D depth sensors available like Intel Realsense.
×1
FT232R USB UART IC
You could replace this with an Arduino.
×1
ATMEGA328P
You could replace this with an Arduino.
×1
SparkFun Load Cell Amplifier - HX711
SparkFun Load Cell Amplifier - HX711
There are cheaper generic HX711 alternatives available on Amazon and eBay.
×4
Computer with USB 3.0
If you use Microsoft's Kinect for Windows V2, any computer that runs Windows 10 and has USB 3.0 should work, though you might get better performance on computers with better processors. If you use libfreenect2 instead, Mac OS X and newer Linux distributions should work. You can also probably use an enbedded computer like an ODroid XU4 (haven't tested this yet, but have read research papers that used these)
×1
Load Cell (Generic)
Any compression load cells that can handle 0-150lbs each should work. There are cheap ones on eBay. I ended up salvaging load cells from a used Nintendo Wii Balance Board.
×4

Software apps and online services

Microsoft Kinect for Windows SDK 2.0 (Alternatively, OpenKinect)
An alternative open-source solution would be libfreenect2 which is part of the OpenKinect project https://github.com/OpenKinect/libfreenect2.
Google Cloud Text-To-Speech API
You can use any text-to-speech API or just record your own voice!

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
You don't really need one because all I printed was a mount for the Kinect sensor. I may eventually make an enclosure for all of this which may require a 3D printer.
Miter Saw
If you want to make a wooden frame, you may want a miter saw to make cutting the wood easier. You don't really need a frame if you just rest the Kinect on a hip-level object like a table.

Story

Read more

Custom parts and enclosures

Aluminum Frame CAD

This is the aluminum frame CAD model used to create the ray traced model above

Aluminum Frame CAD Model STL

This is the aluminum frame CAD model used to create the ray traced model above (except it's a STL file so that it could be viewed in browser)

Wooden Frame CAD Model STL

This is the wooden frame CAD model used to create the ray traced model above (except it's a STL file so that it could be viewed in browser)

Wooden Frame CAD IGS File

This is the wooden frame CAD model used to create the ray traced model above

Kinect Mount CAD Model STL

This is the mounting bracket I 3D printed so that I could mount the Xbox One Kinect onto a plank of wood.

Schematics

FormCheck PCB/Schematic

Follow this schematic to connect the ATMEGA328P to the HX711 load cell amplifiers and the FT232RL USB to Serial UART.

Code

FormCheck Balance Platform Code

Arduino
This is the code that's running on the microcontroller located on the custom PCB underneath the balance platform. A detailed explanation of this is located under the "Electronics/Hardware Overview" section above.
//FormCheck Platform Electronics Code V1

#include "HX711.h" //Library for the load cell amplifiers
const int PINS[] = {2, 4, 5, 3}; //Pins used for the HX711
const int LOADCELL_SCK_PIN = 6; //Serial clock pin to synchronize data transmission
HX711 lc[4]; //Array of HX711 objects 

long lc_raw_readings[4]; //Array to store raw readings from HX711
int lc_calibrated[4]; //Array to store normalized readings from HX711
long lc_cal_low[4]; //Array to store low readings (ie. when no one is on the platform)
long lc_cal_high[4]; ////Array to store high readings (ie. when someone gets onto the platform) 

void setup() {
  Serial.begin(9600); //Set baud rate to 9600 bps

  for (int i = 0; i < 4; i++) {
    lc[i].begin(PINS[i], LOADCELL_SCK_PIN); 
    Serial.print("Initializing load cell number: ");
    Serial.println(i);
  }
  for (int i = 0; i < 4; i++) {
    lc_cal_low[i] = lc[i].read_average(40); //Averages each load cell readings over 40 samples
    Serial.print("Low average values:");
    Serial.println(lc_cal_low[i]);
  }

  //wait for person to step on
  while (abs(lc[0].read()) < abs(lc_cal_low[0]) + 10000) { 
    delay(50); 
  }

  //wait for person to stand still
  double avgVolatility = 100;
  double volatility[4]; //Array to store volatility calculation for each load cell

  //this code just calculates volatility (stdev) of a time series
  while (avgVolatility > 10) {
    long tempVals[20];
    long squaredDeviations[20];
    long volatilitySum = 0;
    for (int j = 0; j < 4; j++) {
      long sum = 0;
      for (int i = 0; i < 20; i++) {
        tempVals[i] = lc[j].read();
        sum += tempVals[i];
      }
      double average = sum / 20;
      sum = 0;
      for (int i = 0; i < 20; i++) {
        squaredDeviations[i] = tempVals[i] - average;
        squaredDeviations[i] *= squaredDeviations[i];
        sum += squaredDeviations[i];
      }
      volatility[j] = sqrt(sum / 20);
      volatilitySum += volatility[j];
    }
    avgVolatility = volatilitySum / 4; //Averages the volatility over the four load cells
  }

  //Read the high values
  for (int i = 0; i < 4; i++) {
    lc_cal_high[i] = lc[i].read_average(40);
    Serial.print("High average value:");
    Serial.println(lc_cal_high[i]);
  }
}

char sentenceBuf[128];
char charBuf[20];

long lc1, lc2, lc3, lc4;

void loop() {
  //get readings
  for (int i = 0; i < 4; i++) {
    delay(50);
    if (lc[i].is_ready()) {
      lc_raw_readings[i] = lc[i].read();

      //Normalize the raw load cell readings 
      lc_calibrated[i] = map(lc_raw_readings[i], lc_cal_low[i], lc_cal_high[i] + 10000,  0, 255);

      //Send the data to the buffer as integers, with at least 3 digits, and spaces are 
      //replaced by zeroes
      sprintf(charBuf, "%03d", lc_calibrated[i]);
      strcat(sentenceBuf, charBuf);
      strcat(sentenceBuf, " "); //Space deliminate the data
    }
  }

  //Send the space-delimited string to the computer via serial
  Serial.println(sentenceBuf);

  //clear char buffers, OCD perhaps
  memset(&sentenceBuf[0], 0, sizeof(sentenceBuf));
  memset(&charBuf[0], 0, sizeof(charBuf));
}

FormCheck.cpp

C/C++
Program that runs on computer and creates the application window, detects form errors, and plays audio feedback files.
#include "stdafx.h"
#include <strsafe.h>
#include "resource.h"
#include "FormCheck.h"
#include <cmath>
# include <stdio.h>
# include <math.h>
# include <stdlib.h>

//Audio
#include <Mmsystem.h>
#include <Windows.h>
#include <queue> 
#include <string>

//Balance board
#include "pch.h"
#include <stdio.h>
#include <tchar.h>
#include "SerialClass.h"	// Library described above
#include <string>
#include <regex>
#include <iostream> 
#include <sstream>
#include <thread>
using namespace std;
Serial* SP = new Serial("...");    // adjust in library as needed
char incomingData[256] = "";			// don't forget to pre-allocate memory
//printf("%s\n",incomingData);
int dataLength = 255;
int readResult = 0;

static const float c_JointThickness = 3.0f;
static const float c_TrackedBoneThickness = 6.0f;
static const float c_InferredBoneThickness = 1.0f;
static const float c_HandSize = 30.0f;

// Rep Tracking Parameters
int repCount = -1;
int lowerRepThreshold;
int upperRepThreshold = 0;
int initialHandLeftCM = -1; 
int initialHandRightCM = -1;
int initialLeftKneeCM = -1;
int initialRightKneeCM = -1;
int kneeDistance;
int initial = 0;
bool goodRep = true;
enum repState {up, down};
repState currRep = up;
bool onBoard = false;

double currLeftLegAngle = 180;
double currRightLegAngle = 180;

//Audio
queue<string> audioToPlay;

/// <summary>
/// Entry point for the application
/// </summary>
/// <param name="hInstance">handle to the application instance</param>
/// <param name="hPrevInstance">always 0</param>
/// <param name="lpCmdLine">command line arguments</param>
/// <param name="nCmdShow">whether to display minimized, maximized, or normally</param>
/// <returns>status</returns>
int APIENTRY wWinMain(    
	_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine,
    _In_ int nShowCmd
)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

	FormCheck application;
    application.Run(hInstance, nShowCmd);
}

/// <summary>
/// Constructor
/// </summary>
FormCheck::FormCheck() :
    m_hWnd(NULL),
    m_nStartTime(0),
    m_nLastCounter(0),
    m_nFramesSinceUpdate(0),
    m_fFreq(0),
    m_nNextStatusTime(0LL),
    m_pKinectSensor(NULL),
    m_pCoordinateMapper(NULL),
    m_pBodyFrameReader(NULL),
    m_pD2DFactory(NULL),
    m_pRenderTarget(NULL),
    m_pBrushJointTracked(NULL),
    m_pBrushJointInferred(NULL),
    m_pBrushBoneTracked(NULL),
    m_pBrushBoneInferred(NULL)
{
    LARGE_INTEGER qpf = {0};
    if (QueryPerformanceFrequency(&qpf))
    {
        m_fFreq = double(qpf.QuadPart);
    }
}
  

/// <summary>
/// Destructor
/// </summary>
FormCheck::~FormCheck()
{
    DiscardDirect2DResources();

    // clean up Direct2D
    SafeRelease(m_pD2DFactory);

    // done with body frame reader
    SafeRelease(m_pBodyFrameReader);

    // done with coordinate mapper
    SafeRelease(m_pCoordinateMapper);

    // close the Kinect Sensor
    if (m_pKinectSensor)
    {
        m_pKinectSensor->Close();
    }

    SafeRelease(m_pKinectSensor);
}

/// <summary>
/// Creates the main window and begins processing
/// </summary>
/// <param name="hInstance">handle to the application instance</param>
/// <param name="nCmdShow">whether to display minimized, maximized, or normally</param>
int FormCheck::Run(HINSTANCE hInstance, int nCmdShow)
{
    MSG       msg = {0};
    WNDCLASS  wc;

    // Dialog custom window class
    ZeroMemory(&wc, sizeof(wc));
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.cbWndExtra    = DLGWINDOWEXTRA;
    wc.hCursor       = LoadCursorW(NULL, IDC_ARROW);
    wc.hIcon         = LoadIconW(hInstance, MAKEINTRESOURCE(IDI_APP));
    wc.lpfnWndProc   = DefDlgProcW;
    wc.lpszClassName = L"FormCheck";

    if (!RegisterClassW(&wc))
    {
        return 0;
    }

    // Create main application window
    HWND hWndApp = CreateDialogParamW(
        NULL,
        MAKEINTRESOURCE(IDD_APP),
        NULL,
        (DLGPROC)FormCheck::MessageRouter,
        reinterpret_cast<LPARAM>(this));

    // Show window
    ShowWindow(hWndApp, nCmdShow);

    // Main message loop
    while (WM_QUIT != msg.message)
    {
        Update();

        while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
        {
            // If a dialog message will be taken care of by the dialog proc
            if (hWndApp && IsDialogMessageW(hWndApp, &msg))
            {
                continue;
            }

            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }

    return static_cast<int>(msg.wParam);
}

/// <summary>
/// Main processing function
/// </summary>
void FormCheck::Update()
{
    if (!m_pBodyFrameReader)
    {
        return;
    }

    IBodyFrame* pBodyFrame = NULL;

    HRESULT hr = m_pBodyFrameReader->AcquireLatestFrame(&pBodyFrame);

    if (SUCCEEDED(hr))
    {
        INT64 nTime = 0;

        hr = pBodyFrame->get_RelativeTime(&nTime);

        IBody* ppBodies[BODY_COUNT] = {0};

        if (SUCCEEDED(hr))
        {
            hr = pBodyFrame->GetAndRefreshBodyData(_countof(ppBodies), ppBodies);
        }

        if (SUCCEEDED(hr))
        {
            ProcessBody(nTime, BODY_COUNT, ppBodies);
        }

        for (int i = 0; i < _countof(ppBodies); ++i)
        {
            SafeRelease(ppBodies[i]);
        }
    }

    SafeRelease(pBodyFrame);
}

/// <summary>
/// Handles window messages, passes most to the class instance to handle
/// </summary>
/// <param name="hWnd">window message is for</param>
/// <param name="uMsg">message</param>
/// <param name="wParam">message data</param>
/// <param name="lParam">additional message data</param>
/// <returns>result of message processing</returns>
LRESULT CALLBACK FormCheck::MessageRouter(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	FormCheck* pThis = NULL;
    
    if (WM_INITDIALOG == uMsg)
    {
        pThis = reinterpret_cast<FormCheck*>(lParam);
        SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
    }
    else
    {
        pThis = reinterpret_cast<FormCheck*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
    }

    if (pThis)
    {
        return pThis->DlgProc(hWnd, uMsg, wParam, lParam);
    }

    return 0;
}

/// <summary>
/// Handle windows messages for the class instance
/// </summary>
/// <param name="hWnd">window message is for</param>
/// <param name="uMsg">message</param>
/// <param name="wParam">message data</param>
/// <param name="lParam">additional message data</param>
/// <returns>result of message processing</returns>
LRESULT CALLBACK FormCheck::DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(wParam);
    UNREFERENCED_PARAMETER(lParam);

    switch (message)
    {
        case WM_INITDIALOG:
        {
            // Bind application window handle
            m_hWnd = hWnd;

            // Init Direct2D
            D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);

            // Get and initialize the default Kinect sensor
            InitializeDefaultSensor();
        }
        break;

        // If the titlebar X is clicked, destroy app
        case WM_CLOSE:
            DestroyWindow(hWnd);
            break;

        case WM_DESTROY:
            // Quit the main message pump
            PostQuitMessage(0);
            break;
    }

    return FALSE;
}

/// <summary>
/// Initializes the default Kinect sensor
/// </summary>
/// <returns>indicates success or failure</returns>
HRESULT FormCheck::InitializeDefaultSensor()
{
    HRESULT hr;

    hr = GetDefaultKinectSensor(&m_pKinectSensor);
    if (FAILED(hr))
    {
        return hr;
    }

    if (m_pKinectSensor)
    {
        // Initialize the Kinect and get coordinate mapper and the body reader
        IBodyFrameSource* pBodyFrameSource = NULL;

        hr = m_pKinectSensor->Open();

        if (SUCCEEDED(hr))
        {
            hr = m_pKinectSensor->get_CoordinateMapper(&m_pCoordinateMapper);
        }

        if (SUCCEEDED(hr))
        {
            hr = m_pKinectSensor->get_BodyFrameSource(&pBodyFrameSource);
        }

        if (SUCCEEDED(hr))
        {
            hr = pBodyFrameSource->OpenReader(&m_pBodyFrameReader);
        }

        SafeRelease(pBodyFrameSource);
    }

    if (!m_pKinectSensor || FAILED(hr))
    {
        SetStatusMessage(L"No ready Kinect found!", 10000, true);
        return E_FAIL;
    }

    return hr;
}

/// <summary>
/// Handle new body data
/// <param name="nTime">timestamp of frame</param>
/// <param name="nBodyCount">body data count</param>
/// <param name="ppBodies">body data in frame</param>
/// </summary>
void FormCheck::ProcessBody(INT64 nTime, int nBodyCount, IBody** ppBodies)
{
    if (m_hWnd)
    {
        HRESULT hr = EnsureDirect2DResources();

        if (SUCCEEDED(hr) && m_pRenderTarget && m_pCoordinateMapper)
        {
            m_pRenderTarget->BeginDraw();
            m_pRenderTarget->Clear();

            RECT rct;
            GetClientRect(GetDlgItem(m_hWnd, IDC_VIDEOVIEW), &rct);
            int width = rct.right;
            int height = rct.bottom;

            for (int i = 0; i < nBodyCount; ++i)
            {
                IBody* pBody = ppBodies[i];
                if (pBody)
                {
                    BOOLEAN bTracked = false;
                    hr = pBody->get_IsTracked(&bTracked);

                    if (SUCCEEDED(hr) && bTracked)
                    {
                        Joint joints[JointType_Count]; 
                        D2D1_POINT_2F jointPoints[JointType_Count];

                        hr = pBody->GetJoints(_countof(joints), joints);
						bool trackedJoints[JointType_Count] = { 0 }; // Creates a boolean array of size JointType_Count that's all false


                        if (SUCCEEDED(hr))
                        {
                            for (int j = 0; j < _countof(joints); ++j)
                            {
								if (joints[j].TrackingState == TrackingState_Tracked) {
									if (joints[j].JointType == JointType_Head) { trackReps(joints[j], joints); }
									trackedJoints[j] = true;
								}
                                jointPoints[j] = BodyToScreen(joints[j].Position, width, height);
                            }
							
							// Only try to fix form if lifter is currently doing the exercise
							if (currRep == down && onBoard) {
								// Tests go here:
								if ((trackedJoints[JointType_HandLeft] && trackedJoints[JointType_HandRight])) {
									//checkHeadPosition(joints, trackedJoints);
								}
								if ((trackedJoints[JointType_HandLeft] && trackedJoints[JointType_HandRight])) {
									updateHandPosition(joints, trackedJoints, initialHandLeftCM, initialHandRightCM);
								}
								if (trackedJoints[JointType_KneeLeft] && trackedJoints[JointType_KneeRight]) {
									checkKnees(joints, trackedJoints);
								}
								if ((trackedJoints[JointType_KneeLeft] && trackedJoints[JointType_AnkleLeft] && trackedJoints[JointType_HipLeft])
									|| trackedJoints[JointType_KneeRight] && trackedJoints[JointType_AnkleRight] && trackedJoints[JointType_HipRight]) {
									updateSquatDepth(joints, trackedJoints);
								}
							}
								
							else {
								currLeftLegAngle = 180;
								currRightLegAngle = 180;
							}
                            DrawBody(joints, jointPoints);
							if (!audioToPlay.empty()) {
								playAudioFeedback();
							}
                        }
                    }
                }
            }

            hr = m_pRenderTarget->EndDraw();

            // Device lost, need to recreate the render target
            // We'll dispose it now and retry drawing
            if (D2DERR_RECREATE_TARGET == hr)
            {
                hr = S_OK;
                DiscardDirect2DResources();
            }
        }
    }
}

void FormCheck::trackReps(const Joint& head, Joint joints[JointType_Count])
{
	board();
	if (head.TrackingState == TrackingState_Tracked)
	{
		int headYinCM = head.Position.Y * 100;
		if (repCount == -1 && onBoard) 
		{
			//Gather starting data. Start with audio. Please get in starting position and wait for the tone to start exercising.
			if (joints[JointType_FootLeft].TrackingState == TrackingState_Tracked && joints[JointType_FootRight].TrackingState) {
				if (abs(joints[JointType_AnkleLeft].Position.X) < abs(joints[JointType_ShoulderLeft].Position.X)) {
					//feet not far enough apart.
					WCHAR szStatusMessage[120];
					TCHAR* headText = TEXT("Widen your stance");
					StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
					SetStatusMessage(szStatusMessage, 2000, true);
					return;
				}
			}
			else {
				WCHAR szStatusMessage[120];
				TCHAR* headText = TEXT("Feet not in frame");
				StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
				SetStatusMessage(szStatusMessage, 2000, true);
				return;
			}
			//audioToPlay.push("audio/CalibratingWait.wav");
			//playAudioFeedback();
			Sleep(3000);
			WCHAR szStatusMessage[120];
			audioToPlay.push("audio/StartSquatting.wav");
			playAudioFeedback();
			TCHAR* headText = TEXT("Ready to Squat");
			StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
			SetStatusMessage(szStatusMessage, 2000, true);
			initialHandLeftCM = joints[JointType_ShoulderLeft].Position.Z * 100;
			initialHandRightCM = joints[JointType_ShoulderRight].Position.Z * 100;
			repCount++;
			headYinCM = head.Position.Y * 100;
			upperRepThreshold = headYinCM;
			lowerRepThreshold = headYinCM - 20;
			currRep = up;
			//initialLeftKneeCM = joints[JointType_KneeLeft].Position.X * 100;
			//initialRightKneeCM = joints[JointType_KneeRight].Position.X * 100;
			//kneeDistance = abs(initialLeftKneeCM - initialRightKneeCM);

			//std::thread boardThread(board);
		} 
		else if (!onBoard) {
			WCHAR szStatusMessage[120];
			TCHAR* headText = TEXT("Get onto the board");
			StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
			SetStatusMessage(szStatusMessage, 2000, true);
			return;
			//Get on Board
		}
	
		if (currRep == up && headYinCM < lowerRepThreshold && onBoard)
		{
			currRep = down;
		}

		if (currRep == down && headYinCM > (upperRepThreshold - 8) && onBoard)
		{
			checkSquatDepth();
			repCount++;
			if (goodRep) {
				audioToPlay.push("audio/GoodSquat.wav");
				//WCHAR szStatusMessage[120];
				//LPCTSTR stringFormat = TEXT("%s %d");
				//TCHAR* repText = TEXT("Rep Count: ");
				//StringCchPrintf(szStatusMessage, _countof(szStatusMessage), stringFormat, repText, repCount);

				//SetStatusMessage(szStatusMessage, 33, false);
				//Good Rep Audio
			}
			else {
				goodRep = true;
			}
			currRep = up;
		}
	}
}

void FormCheck::board()
{
	if (SP->IsConnected())
	{
		readResult = SP->ReadData(incomingData, dataLength);
		//printf("Bytes read: (0 means no data available) %i\n", readResult);
		incomingData[readResult] = 0;
		//printf("%s", incomingData);
		//WCHAR szStatusMessage[120];
		//LPCTSTR stringFormat = TEXT("%d");
		//TCHAR* headText = TEXT("Get in proper starting position with the bar resting on your shoulders");
		//StringCchPrintf(szStatusMessage, _countof(szStatusMessage), stringFormat, incomingData);
		//SetStatusMessage(szStatusMessage, 2000, true);

		if (readResult == 18) {

			std::stringstream ss;
			ss << incomingData;
			std::string temp;
			int weights[4];
			for (int i = 0; i < 4; i++) {
				ss >> temp;
				if (std::stringstream(temp) >> weights[i])
					temp = "";
			}

			for (int j = 0; j < 4; j++) {
				if (weights[j] > 10) {
					onBoard = true;
					break;
				}
				repCount = -1;
				onBoard = false;
				currRep = up;
				return;
			}

		/*	WCHAR szStatusMessage[120];
			LPCTSTR stringFormat = TEXT("%d %d %d %d");
			StringCchPrintf(szStatusMessage, _countof(szStatusMessage), stringFormat, weights[0], weights[1], weights[2], weights[3]);
			SetStatusMessage(szStatusMessage, 33, false);*/

			/*
			std::cout << "weight 1 = " << weights[0] << std::endl;
			std::cout << "weight 2 = " << weights[1] << std::endl;
			std::cout << "weight 3 = " << weights[2] << std::endl;
			std::cout << "weight 3 = " << weights[3] << std::endl;
			*/
			float lr = float(weights[0] + weights[3]) / float(weights[1] + weights[2]);
			float fb = float(weights[0] + weights[1]) / float(weights[2] + weights[3]);

			if (lr < .7 && repCount > 0 && currRep == down) {

			WCHAR szStatusMessage[120];
			TCHAR* headText = TEXT("Shift your weight to the right");
			StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
			//SetStatusMessage(szStatusMessage, 2000, true);
			audioToPlay.push("audio/imbalanced_weight_right_leg.wav");

			}
			if (lr > 1.5  && repCount > 0 && currRep == down) {
				WCHAR szStatusMessage[120];
				TCHAR* headText = TEXT("Shift your weight to the left");
				StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
				//SetStatusMessage(szStatusMessage, 2000, true);
				audioToPlay.push("audio/imbalanced_weight_left_leg.wav");
			}
			/*if (fb > 1.5) {
				WCHAR szStatusMessage[120];
				TCHAR* headText = TEXT("Shift your weight back");
				LPCTSTR pszFormat = TEXT("%s %d.");
				StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
				//SetStatusMessage(szStatusMessage, 2000, true);
				audioToPlay.push("audio/shift_weight_back.wav");

			}
			if (fb < .5) {
				WCHAR szStatusMessage[120];
				TCHAR* headText = TEXT("Shift your weight forward");
				LPCTSTR pszFormat = TEXT("%s %d.");
				StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
				//SetStatusMessage(szStatusMessage, 2000, true);
				audioToPlay.push("audio/shift_weight_forward.wav");

			}*/

		}

	}
}
void FormCheck::checkHeadPosition(Joint joints[JointType_Count], bool trackedJoints[JointType_Count]) {
	//Not really working that well, frankly. 
	if (trackedJoints[JointType_SpineMid] && trackedJoints[JointType_Neck] && trackedJoints[JointType_Head]) {
		double SpineMidX = joints[JointType_SpineMid].Position.X;
		double NeckX = joints[JointType_Neck].Position.X;
		double HeadX = joints[JointType_Head].Position.X;

		double SpineMidY = joints[JointType_SpineMid].Position.Y;
		double NeckY = joints[JointType_Neck].Position.Y;
		double HeadY = joints[JointType_Head].Position.Y;

		double SpineMidZ = joints[JointType_SpineMid].Position.Z;
		double NeckZ = joints[JointType_Neck].Position.Z;
		double HeadZ = joints[JointType_Head].Position.Z;

		double vectorSpineNeckX = SpineMidX - NeckX;
		double vectorSpineNeckY = SpineMidY - NeckY;
		double vectorSpineNeckZ = SpineMidZ - NeckZ;

		double vectorNeckHeadX = HeadX - NeckX;
		double vectorNeckHeadY = HeadY - NeckY;
		double vectorNeckHeadZ = HeadZ - NeckZ;

		double dotSpineNeck = (vectorSpineNeckX * vectorNeckHeadX)
			+ (vectorSpineNeckY * vectorNeckHeadY)
			+ (vectorSpineNeckZ * vectorNeckHeadZ);

		double magSpineNeck = sqrt(pow(vectorSpineNeckX, 2)
			+ pow(vectorSpineNeckY, 2) + pow(vectorSpineNeckZ, 2));

		double magNeckHead = sqrt(pow(vectorNeckHeadX, 2)
			+ pow(vectorNeckHeadY, 2) + pow(vectorNeckHeadZ, 2));
			double backAngle = 200 - (180.0 / 3.1415) * acos(dotSpineNeck / (magSpineNeck * magNeckHead));
			if (backAngle > 15) {
				WCHAR szStatusMessage[120];
				TCHAR* headText = TEXT("Keep your head in neutral position.");
				LPCTSTR pszFormat = TEXT("%s %d.");
				StringCchPrintf(szStatusMessage, _countof(szStatusMessage), pszFormat, headText, backAngle);
				SetStatusMessage(szStatusMessage, 2000, true);
			}
		
	}
}
void FormCheck::updateHandPosition(Joint joints[JointType_Count], bool trackedJoints[JointType_Count], int initialHandLeftCM, int initialHandRightCM) {
	if (trackedJoints[JointType_ShoulderLeft] && trackedJoints[JointType_ShoulderRight]) {
		int handLeftCM = joints[JointType_ShoulderLeft].Position.Z * 100;
		int handRightCM = joints[JointType_ShoulderRight].Position.Z * 100;
		if (initialHandLeftCM == -1 || initialHandRightCM == -1) {
			//initial didn't update properly.
			return;
		}
		if ((initialHandLeftCM - handLeftCM) >= 20 || (initialHandRightCM - handRightCM) >= 20) {
			WCHAR szStatusMessage[120];
			TCHAR* headText = TEXT("Keep your spine in a neutral position.");
			StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
			SetStatusMessage(szStatusMessage, 2000, true);
			goodRep = false;
			audioToPlay.push("audio/back_rounding.wav");
		}
	}
	return;
}
void FormCheck::checkKnees(Joint joints[JointType_Count], bool trackedJoints[JointType_Count])
{
	if (trackedJoints[JointType_KneeLeft] && trackedJoints[JointType_KneeRight]) {
		int kneeLeftCM = joints[JointType_KneeLeft].Position.X * 100;
		int kneeRightCM = joints[JointType_KneeRight].Position.X * 100;
		int distance = abs(kneeLeftCM - kneeRightCM);

		if (distance <= 24) {
			WCHAR szStatusMessage[120];
			TCHAR* headText = TEXT("Make sure your knees are over your feet.");
			StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
			SetStatusMessage(szStatusMessage, 2000, true);
			goodRep = false;
			audioToPlay.push("audio/knees_over_feet.wav");
		}
	}
	
}

void FormCheck::updateSquatDepth(Joint joints[JointType_Count], bool trackedJoints[JointType_Count])
{
	if (trackedJoints[JointType_KneeLeft] && trackedJoints[JointType_AnkleLeft] && trackedJoints[JointType_HipLeft]) {
		double kneeLeftX = joints[JointType_KneeLeft].Position.X;
		double ankleLeftX = joints[JointType_AnkleLeft].Position.X;
		double hipLeftX = joints[JointType_HipLeft].Position.X;

		double kneeLeftY = joints[JointType_KneeLeft].Position.Y;
		double ankleLeftY = joints[JointType_AnkleLeft].Position.Y;
		double hipLeftY = joints[JointType_HipLeft].Position.Y;

		double kneeLeftZ = joints[JointType_KneeLeft].Position.Z;
		double ankleLeftZ = joints[JointType_AnkleLeft].Position.Z;
		double hipLeftZ = joints[JointType_HipLeft].Position.Z;

		double vectorAnkleKneeLeftX = kneeLeftX - ankleLeftX;
		double vectorAnkleKneeLeftY = kneeLeftY - ankleLeftY;
		double vectorAnkleKneeLeftZ = kneeLeftZ - ankleLeftZ;

		double vectorKneeHipLeftX = hipLeftX - kneeLeftX;
		double vectorKneeHipLeftY = hipLeftY - kneeLeftY;
		double vectorKneeHipLeftZ = hipLeftZ - kneeLeftZ;

		double dotAnkleKneeHip = vectorAnkleKneeLeftX * vectorKneeHipLeftX
			+ vectorAnkleKneeLeftY * vectorKneeHipLeftY
			+ vectorAnkleKneeLeftZ * vectorKneeHipLeftZ;

		double magAnkleKnee = sqrt(pow(vectorAnkleKneeLeftX, 2)
			+ pow(vectorAnkleKneeLeftY, 2) + pow(vectorAnkleKneeLeftZ, 2));

		double magKneeHip = sqrt(pow(vectorKneeHipLeftX, 2)
			+ pow(vectorKneeHipLeftY, 2) + pow(vectorKneeHipLeftZ, 2));

		double leftLegAngle = 200 - (180.0 / 3.1415) * acos(dotAnkleKneeHip / (magAnkleKnee * magKneeHip));
		if (leftLegAngle < currLeftLegAngle) {
			currLeftLegAngle = leftLegAngle;
		}
	}
	if (trackedJoints[JointType_KneeRight] && trackedJoints[JointType_AnkleRight] && trackedJoints[JointType_HipRight]) {
		double kneeRightX = joints[JointType_KneeRight].Position.X;
		double ankleRightX = joints[JointType_AnkleRight].Position.X;
		double hipRightX = joints[JointType_HipRight].Position.X;

		double kneeRightY = joints[JointType_KneeRight].Position.Y;
		double ankleRightY = joints[JointType_AnkleRight].Position.Y;
		double hipRightY = joints[JointType_HipRight].Position.Y;

		double kneeRightZ = joints[JointType_KneeRight].Position.Z;
		double ankleRightZ = joints[JointType_AnkleRight].Position.Z;
		double hipRightZ = joints[JointType_HipRight].Position.Z;

		double vectorAnkleKneeRightX = kneeRightX - ankleRightX;
		double vectorAnkleKneeRightY = kneeRightY - ankleRightY;
		double vectorAnkleKneeRightZ = kneeRightZ - ankleRightZ;

		double vectorKneeHipRightX = hipRightX - kneeRightX;
		double vectorKneeHipRightY = hipRightY - kneeRightY;
		double vectorKneeHipRightZ = hipRightZ - kneeRightZ;

		double dotAnkleKneeHip = (vectorAnkleKneeRightX * vectorKneeHipRightX)
			+ (vectorAnkleKneeRightY * vectorKneeHipRightY)
			+ (vectorAnkleKneeRightZ * vectorKneeHipRightZ);

		double magAnkleKnee = sqrt(pow(vectorAnkleKneeRightX, 2)
			+ pow(vectorAnkleKneeRightY, 2) + pow(vectorAnkleKneeRightZ, 2));

		double magKneeHip = sqrt(pow(vectorKneeHipRightX, 2)
			+ pow(vectorKneeHipRightY, 2) + pow(vectorKneeHipRightZ, 2));

		double rightLegAngle = 200 - (180.0 / 3.1415) * acos(dotAnkleKneeHip / (magAnkleKnee * magKneeHip));
		if (rightLegAngle < currRightLegAngle) {
			currRightLegAngle = rightLegAngle;
		}
	}
	//string s = to_string(currRightLegAngle);
	//wstring stemp = std::wstring(s.begin(), s.end());
	//LPCWSTR sw = stemp.c_str();
	//OutputDebugString(sw);
	//OutputDebugStringW(L"\n");
}

// Checks if squat depth is okay 
void FormCheck::checkSquatDepth() {
	if ((currRightLegAngle > 97) || (currLeftLegAngle > 97)) {
		WCHAR szStatusMessage[120];
		TCHAR* headText = TEXT("Try to squat deeper.");
		StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
		SetStatusMessage(szStatusMessage, 2000, true);
		goodRep = false;
		audioToPlay.push("audio/try_to_squat_deeper.wav");
	}
	else if ((currRightLegAngle < 80) || (currLeftLegAngle < 80)) {

		WCHAR szStatusMessage[120];

		TCHAR* headText = TEXT("You squatted too deep.");
		StringCchPrintf(szStatusMessage, _countof(szStatusMessage), headText);
		SetStatusMessage(szStatusMessage, 2000, true);
		goodRep = false;
		audioToPlay.push("audio/squatted_too_deep.wav");
	}
}

// Play the audio feedback
void FormCheck::playAudioFeedback() {
	if (!audioToPlay.empty()) {
		string audioFile = audioToPlay.front();
		wstring stemp = wstring(audioFile.begin(), audioFile.end());
		PlaySound(stemp.c_str(), NULL, SND_FILENAME | SND_ASYNC | SND_NOSTOP);
		audioToPlay.pop();
	}
}

/// <summary>
/// Set the status bar message
/// </summary>
/// <param name="szMessage">message to display</param>
/// <param name="showTimeMsec">time in milliseconds to ignore future status messages</param>
/// <param name="bForce">force status update</param>
bool FormCheck::SetStatusMessage(_In_z_ WCHAR* szMessage, DWORD nShowTimeMsec, bool bForce)
{
    INT64 now = GetTickCount64();

    if (m_hWnd && (bForce || (m_nNextStatusTime <= now)))
    {
        SetDlgItemText(m_hWnd, IDC_STATUS, szMessage);
        m_nNextStatusTime = now + nShowTimeMsec;

        return true;
    }

    return false;
}

/// <summary>
/// Ensure necessary Direct2d resources are created
/// </summary>
/// <returns>S_OK if successful, otherwise an error code</returns>
HRESULT FormCheck::EnsureDirect2DResources()
{
    HRESULT hr = S_OK;

    if (m_pD2DFactory && !m_pRenderTarget)
    {
        RECT rc;
        GetWindowRect(GetDlgItem(m_hWnd, IDC_VIDEOVIEW), &rc);  

        int width = rc.right - rc.left;
        int height = rc.bottom - rc.top;
        D2D1_SIZE_U size = D2D1::SizeU(width, height);
        D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties();
        rtProps.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE);
        rtProps.usage = D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE;

        // Create a Hwnd render target, in order to render to the window set in initialize
        hr = m_pD2DFactory->CreateHwndRenderTarget(
            rtProps,
            D2D1::HwndRenderTargetProperties(GetDlgItem(m_hWnd, IDC_VIDEOVIEW), size),
            &m_pRenderTarget
        );

        if (FAILED(hr))
        {
            SetStatusMessage(L"Couldn't create Direct2D render target!", 10000, true);
            return hr;
        }

        // light green
        m_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(0.27f, 0.75f, 0.27f), &m_pBrushJointTracked);

        m_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Yellow, 1.0f), &m_pBrushJointInferred);
        m_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Green, 1.0f), &m_pBrushBoneTracked);
        m_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Gray, 1.0f), &m_pBrushBoneInferred);
    }

    return hr;
}

/// <summary>
/// Dispose Direct2d resources 
/// </summary>
void FormCheck::DiscardDirect2DResources()
{
    SafeRelease(m_pRenderTarget);

    SafeRelease(m_pBrushJointTracked);
    SafeRelease(m_pBrushJointInferred);
    SafeRelease(m_pBrushBoneTracked);
    SafeRelease(m_pBrushBoneInferred);
}

/// <summary>
/// Converts a body point to screen space
/// </summary>
/// <param name="bodyPoint">body point to tranform</param>
/// <param name="width">width (in pixels) of output buffer</param>
/// <param name="height">height (in pixels) of output buffer</param>
/// <returns>point in screen-space</returns>
D2D1_POINT_2F FormCheck::BodyToScreen(const CameraSpacePoint& bodyPoint, int width, int height)
{
    // Calculate the body's position on the screen
    DepthSpacePoint depthPoint = {0};
    m_pCoordinateMapper->MapCameraPointToDepthSpace(bodyPoint, &depthPoint);

    float screenPointX = static_cast<float>(depthPoint.X * width) / cDepthWidth;
    float screenPointY = static_cast<float>(depthPoint.Y * height) / cDepthHeight;

    return D2D1::Point2F(screenPointX, screenPointY);
}

/// <summary>
/// Draws a body 
/// </summary>
/// <param name="pJoints">joint data</param>
/// <param name="pJointPoints">joint positions converted to screen space</param>
void FormCheck::DrawBody(const Joint* pJoints, const D2D1_POINT_2F* pJointPoints)
{
    // Draw the bones

    // Torso
    DrawBone(pJoints, pJointPoints, JointType_Head, JointType_Neck);
    DrawBone(pJoints, pJointPoints, JointType_Neck, JointType_SpineShoulder);
    DrawBone(pJoints, pJointPoints, JointType_SpineShoulder, JointType_SpineMid);
    DrawBone(pJoints, pJointPoints, JointType_SpineMid, JointType_SpineBase);
    DrawBone(pJoints, pJointPoints, JointType_SpineShoulder, JointType_ShoulderRight);
    DrawBone(pJoints, pJointPoints, JointType_SpineShoulder, JointType_ShoulderLeft);
    DrawBone(pJoints, pJointPoints, JointType_SpineBase, JointType_HipRight);
    DrawBone(pJoints, pJointPoints, JointType_SpineBase, JointType_HipLeft);
    
    // Right Arm    
    DrawBone(pJoints, pJointPoints, JointType_ShoulderRight, JointType_ElbowRight);
    DrawBone(pJoints, pJointPoints, JointType_ElbowRight, JointType_WristRight);
    DrawBone(pJoints, pJointPoints, JointType_WristRight, JointType_HandRight);
    DrawBone(pJoints, pJointPoints, JointType_HandRight, JointType_HandTipRight);
    DrawBone(pJoints, pJointPoints, JointType_WristRight, JointType_ThumbRight);

    // Left Arm
    DrawBone(pJoints, pJointPoints, JointType_ShoulderLeft, JointType_ElbowLeft);
    DrawBone(pJoints, pJointPoints, JointType_ElbowLeft, JointType_WristLeft);
    DrawBone(pJoints, pJointPoints, JointType_WristLeft, JointType_HandLeft);
    DrawBone(pJoints, pJointPoints, JointType_HandLeft, JointType_HandTipLeft);
    DrawBone(pJoints, pJointPoints, JointType_WristLeft, JointType_ThumbLeft);

    // Right Leg
    DrawBone(pJoints, pJointPoints, JointType_HipRight, JointType_KneeRight);
    DrawBone(pJoints, pJointPoints, JointType_KneeRight, JointType_AnkleRight);
    DrawBone(pJoints, pJointPoints, JointType_AnkleRight, JointType_FootRight);

    // Left Leg
    DrawBone(pJoints, pJointPoints, JointType_HipLeft, JointType_KneeLeft);
    DrawBone(pJoints, pJointPoints, JointType_KneeLeft, JointType_AnkleLeft);
    DrawBone(pJoints, pJointPoints, JointType_AnkleLeft, JointType_FootLeft);

    // Draw the joints
    for (int i = 0; i < JointType_Count; ++i)
    {
        D2D1_ELLIPSE ellipse = D2D1::Ellipse(pJointPoints[i], c_JointThickness, c_JointThickness);

        if (pJoints[i].TrackingState == TrackingState_Inferred)
        {
            m_pRenderTarget->FillEllipse(ellipse, m_pBrushJointInferred);
        }
        else if (pJoints[i].TrackingState == TrackingState_Tracked)
        {
            m_pRenderTarget->FillEllipse(ellipse, m_pBrushJointTracked);
        }
    }
}

/// <summary>
/// Draws one bone of a body (joint to joint)
/// </summary>
/// <param name="pJoints">joint data</param>
/// <param name="pJointPoints">joint positions converted to screen space</param>
/// <param name="pJointPoints">joint positions converted to screen space</param>
/// <param name="joint0">one joint of the bone to draw</param>
/// <param name="joint1">other joint of the bone to draw</param>
void FormCheck::DrawBone(const Joint* pJoints, const D2D1_POINT_2F* pJointPoints, JointType joint0, JointType joint1)
{
    TrackingState joint0State = pJoints[joint0].TrackingState;
    TrackingState joint1State = pJoints[joint1].TrackingState;

    // If we can't find either of these joints, exit
    if ((joint0State == TrackingState_NotTracked) || (joint1State == TrackingState_NotTracked))
    {
        return;
    }

    // Don't draw if both points are inferred
    if ((joint0State == TrackingState_Inferred) && (joint1State == TrackingState_Inferred))
    {
        return;
    }

    // We assume all drawn bones are inferred unless BOTH joints are tracked
    if ((joint0State == TrackingState_Tracked) && (joint1State == TrackingState_Tracked))
    {
        m_pRenderTarget->DrawLine(pJointPoints[joint0], pJointPoints[joint1], m_pBrushBoneTracked, c_TrackedBoneThickness);
    }
    else
    {
...

This file has been truncated, please download it to see its full contents.

FormCheck.h

C/C++
Header file for FormCheck.cpp. A lot of this header file is reused from the BodyBasics example in the Kinect for Windows SDK 2.0.
#pragma once

#include "resource.h"

class FormCheck
{
    static const int        cDepthWidth  = 512;
    static const int        cDepthHeight = 424;

public:
    /// <summary>
    /// Constructor
    /// </summary>
	FormCheck();

    /// <summary>
    /// Destructor
    /// </summary>
    ~FormCheck();

    /// <summary>
    /// Handles window messages, passes most to the class instance to handle
    /// </summary>
    /// <param name="hWnd">window message is for</param>
    /// <param name="uMsg">message</param>
    /// <param name="wParam">message data</param>
    /// <param name="lParam">additional message data</param>
    /// <returns>result of message processing</returns>
    static LRESULT CALLBACK MessageRouter(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    /// <summary>
    /// Handle windows messages for a class instance
    /// </summary>
    /// <param name="hWnd">window message is for</param>
    /// <param name="uMsg">message</param>
    /// <param name="wParam">message data</param>
    /// <param name="lParam">additional message data</param>
    /// <returns>result of message processing</returns>
    LRESULT CALLBACK        DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    /// <summary>
    /// Creates the main window and begins processing
    /// </summary>
    /// <param name="hInstance"></param>
    /// <param name="nCmdShow"></param>
    int                     Run(HINSTANCE hInstance, int nCmdShow);

private:
    HWND                    m_hWnd;
    INT64                   m_nStartTime;
    INT64                   m_nLastCounter;
    double                  m_fFreq;
    INT64                   m_nNextStatusTime;
    DWORD                   m_nFramesSinceUpdate;

    // Current Kinect
    IKinectSensor*          m_pKinectSensor;
    ICoordinateMapper*      m_pCoordinateMapper;

    // Body reader
    IBodyFrameReader*       m_pBodyFrameReader;

    // Direct2D
    ID2D1Factory*           m_pD2DFactory;

    // Body/hand drawing
    ID2D1HwndRenderTarget*  m_pRenderTarget;
    ID2D1SolidColorBrush*   m_pBrushJointTracked;
    ID2D1SolidColorBrush*   m_pBrushJointInferred;
    ID2D1SolidColorBrush*   m_pBrushBoneTracked;
    ID2D1SolidColorBrush*   m_pBrushBoneInferred;
    ID2D1SolidColorBrush*   m_pBrushHandClosed;
    ID2D1SolidColorBrush*   m_pBrushHandOpen;
    ID2D1SolidColorBrush*   m_pBrushHandLasso;

    /// <summary>
    /// Main processing function
    /// </summary>
    void                    Update();

    /// <summary>
    /// Initializes the default Kinect sensor
    /// </summary>
    /// <returns>S_OK on success, otherwise failure code</returns>
    HRESULT                 InitializeDefaultSensor();
    
    /// <summary>
    /// Handle new body data
    /// <param name="nTime">timestamp of frame</param>
    /// <param name="nBodyCount">body data count</param>
    /// <param name="ppBodies">body data in frame</param>
    /// </summary>
    void                    ProcessBody(INT64 nTime, int nBodyCount, IBody** ppBodies);

    /// <summary>
    /// Set the status bar message
    /// </summary>
    /// <param name="szMessage">message to display</param>
    /// <param name="nShowTimeMsec">time in milliseconds for which to ignore future status messages</param>
    /// <param name="bForce">force status update</param>
    bool                    SetStatusMessage(_In_z_ WCHAR* szMessage, DWORD nShowTimeMsec, bool bForce);

    /// <summary>
    /// Ensure necessary Direct2d resources are created
    /// </summary>
    /// <returns>S_OK if successful, otherwise an error code</returns>
    HRESULT EnsureDirect2DResources();

    /// <summary>
    /// Dispose Direct2d resources 
    /// </summary>
    void DiscardDirect2DResources();

    /// <summary>
    /// Converts a body point to screen space
    /// </summary>
    /// <param name="bodyPoint">body point to tranform</param>
    /// <param name="width">width (in pixels) of output buffer</param>
    /// <param name="height">height (in pixels) of output buffer</param>
    /// <returns>point in screen-space</returns>
    D2D1_POINT_2F BodyToScreen(const CameraSpacePoint& bodyPoint, int width, int height);

	void trackReps(const Joint& head, Joint joints[JointType_Count]);
	void checkKnees(Joint joints[JointType_Count], bool trackedJoints[JointType_Count]);
	void updateSquatDepth(Joint joints[JointType_Count], bool trackedJoints[JointType_Count]);
	void updateHandPosition(Joint joints[JointType_Count], bool trackedJoints[JointType_Count], int initialHandLeftCM, int initialHandRightCM);
	void checkSquatDepth();
	void checkHeadPosition(Joint joints[JointType_Count], bool trackedJoints[JointType_Count]);
	void playAudioFeedback();
	void board();
    /// <summary>
    /// Draws a body 
    /// </summary>
    /// <param name="pJoints">joint data</param>
    /// <param name="pJointPoints">joint positions converted to screen space</param>
    void                    DrawBody(const Joint* pJoints, const D2D1_POINT_2F* pJointPoints);

    /// <summary>
    /// Draws a hand symbol if the hand is tracked: red circle = closed, green circle = opened; blue circle = lasso
    /// </summary>
    /// <param name="handState">state of the hand</param>
    /// <param name="handPosition">position of the hand</param>
    void                    DrawHand(HandState handState, const D2D1_POINT_2F& handPosition);

    /// <summary>
    /// Draws one bone of a body (joint to joint)
    /// </summary>
    /// <param name="pJoints">joint data</param>
    /// <param name="pJointPoints">joint positions converted to screen space</param>
    /// <param name="pJointPoints">joint positions converted to screen space</param>
    /// <param name="joint0">one joint of the bone to draw</param>
    /// <param name="joint1">other joint of the bone to draw</param>
    void                    DrawBone(const Joint* pJoints, const D2D1_POINT_2F* pJointPoints, JointType joint0, JointType joint1);
};

SerialClass.cpp

C/C++
Enables serial communication between computer and Arduino. Taken from: https://blog.manash.me/serial-communication-with-an-arduino-using-c-on-windows-d08710186498
#include "pch.h"
#include "SerialClass.h"
#include <tchar.h> // in case it isn't included 

Serial::Serial(const char *portName)
{
	//We're not yet connected
	this->connected = false;

	//Try to connect to the given port throuh CreateFile
	this->hSerial = CreateFile(_T("\\\\.\\COM13"),
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	//Check if the connection was successfull
	if (this->hSerial == INVALID_HANDLE_VALUE)
	{
		//If not success full display an Error
		if (GetLastError() == ERROR_FILE_NOT_FOUND) {

			//Print Error if neccessary
			printf("ERROR: Handle was not attached. Reason: %s not available.\n", portName);

		}
		else
		{
			printf("ERROR!!!");
		}
	}
	else
	{
		//If connected we try to set the comm parameters
		DCB dcbSerialParams = { 0 };

		//Try to get the current
		if (!GetCommState(this->hSerial, &dcbSerialParams))
		{
			//If impossible, show an error
			printf("failed to get current serial parameters!");
		}
		else
		{
			//Define serial connection parameters for the arduino board
			dcbSerialParams.BaudRate = CBR_9600;
			dcbSerialParams.ByteSize = 8;
			dcbSerialParams.StopBits = ONESTOPBIT;
			dcbSerialParams.Parity = NOPARITY;
			//Setting the DTR to Control_Enable ensures that the Arduino is properly
			//reset upon establishing a connection
			dcbSerialParams.fDtrControl = DTR_CONTROL_ENABLE;

			//Set the parameters and check for their proper application
			if (!SetCommState(hSerial, &dcbSerialParams))
			{
				printf("ALERT: Could not set Serial Port parameters");
			}
			else
			{
				//If everything went fine we're connected
				this->connected = true;
				//Flush any remaining characters in the buffers 
				PurgeComm(this->hSerial, PURGE_RXCLEAR | PURGE_TXCLEAR);
				//We wait 2s as the arduino board will be reseting
				Sleep(ARDUINO_WAIT_TIME);
			}
		}
	}

}

Serial::~Serial()
{
	//Check if we are connected before trying to disconnect
	if (this->connected)
	{
		//We're no longer connected
		this->connected = false;
		//Close the serial handler
		CloseHandle(this->hSerial);
	}
}

int Serial::ReadData(char *buffer, unsigned int nbChar)
{
	//Number of bytes we'll have read
	DWORD bytesRead;
	//Number of bytes we'll really ask to read
	unsigned int toRead;

	//Use the ClearCommError function to get status info on the Serial port
	ClearCommError(this->hSerial, &this->errors, &this->status);

	//Check if there is something to read
	if (this->status.cbInQue > 0)
	{
		//If there is we check if there is enough data to read the required number
		//of characters, if not we'll read only the available characters to prevent
		//locking of the application.
		if (this->status.cbInQue > nbChar)
		{
			toRead = nbChar;
		}
		else
		{
			toRead = this->status.cbInQue;
		}

		//Try to read the require number of chars, and return the number of read bytes on success
		if (ReadFile(this->hSerial, buffer, toRead, &bytesRead, NULL))
		{
			return bytesRead;
		}

	}

	//If nothing has been read, or that an error was detected return 0
	return 0;

}


bool Serial::WriteData(const char *buffer, unsigned int nbChar)
{
	DWORD bytesSend;

	//Try to write the buffer on the Serial port
	if (!WriteFile(this->hSerial, (void *)buffer, nbChar, &bytesSend, 0))
	{
		//In case it don't work get comm error and return false
		ClearCommError(this->hSerial, &this->errors, &this->status);

		return false;
	}
	else
		return true;
}

bool Serial::IsConnected()
{
	//Simply return the connection status
	return this->connected;
}

SerialClass.h

C/C++
Header file for SerialClass.cpp. Taken from: https://blog.manash.me/serial-communication-with-an-arduino-using-c-on-windows-d08710186498
#pragma once
#ifndef SERIALCLASS_H_INCLUDED
#define SERIALCLASS_H_INCLUDED

#define ARDUINO_WAIT_TIME 2000

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

class Serial
{
private:
	//Serial comm handler
	HANDLE hSerial;
	//Connection status
	bool connected;
	//Get various information about the connection
	COMSTAT status;
	//Keep track of last error
	DWORD errors;

public:
	//Initialize Serial communication with the given COM port
	Serial(const char *portName);
	//Close the connection
	~Serial();
	//Read data in a buffer, if nbChar is greater than the
	//maximum number of bytes available, it will return only the
	//bytes available. The function return -1 when nothing could
	//be read, the number of bytes actually read.
	int ReadData(char *buffer, unsigned int nbChar);
	//Writes data from a buffer through the Serial connection
	//return true on success.
	bool WriteData(const char *buffer, unsigned int nbChar);
	//Check if we are actually connected
	bool IsConnected();


};

#endif // SERIALCLASS_H_INCLUDED

Credits

Travis Chan

Travis Chan

2 projects • 2 followers
Computer Engineering Student at Johns Hopkins University, IOT Data Engineer Intern at Micron Technology.
tkeady5

tkeady5

0 projects • 1 follower
Thomas Keady

Thomas Keady

0 projects • 1 follower
JHU ECE '18
Thanks to Luke Robinson, Jack Ravekes, and Kinect for Windows 2.0 SDK.

Comments