I have been training for several years and have thought about different ways to have a tracking device that can really help in the gym, there are a lot of good gadgets out there but for a gym enthusiast like me none of them satisfy my needs which are:
- A device that really track your activity for something better than a "dinner talk".
- It should be productive which means it should not consume too much of your time in setting up since every distraction at the gym is considered a bad habit.
- It should incorporate some intelligence to help even during a workout session, thou it will be sort of a training partner or even instructor.
- Need to be portable, messy free or even be "invisible".
This project incorporates technologies to fulfill the above goals, being the core the new mems sensor from Infineon and it's fantastic nanoHub demonstration board.
At this moment in my live, lifting is a live style, I cannot figure out myself in the future without doing it, said that the tracker I have in mind is for serious bodybuilder guys trying to squeeze the most of their workout.
Over the last year I have been trying to progress on several aspects of my training program, but there is one exercise that passion me among all, it's the Squat, the king of the exercises and one of my favorites. There are several ways I use to track my progress, one of them is the weight lifted, my personal record is 320 pounds for 5 repetitions I did a few weeks ago, but it takes some times and routine changes for me to beat it.
The problem is that not all exercises are favorite and my memory is not infinite to remember how good or bad I perform a lift of a given exercise in the past.
What a tracker should do.
The most simple tracker need to count your repetitions (reps) during a set (an exercise done n number of times). But that alone is just the beginning.
The infineon DPS310 took my attention from the beginning due to it's capability to sense very precise the movement in one plane, the Z plane for nerds but people will understand better the altitude measurement.
I realized that not only I ca achieve the reps counter but also I can track the tempo of the exercise, this last feature it's not implemented on my project since there was a lot of work to do before actually take care of it, but more on that in the conclusions of this work.
Dumb vs. Smart Tracker.
To make my Gym Assistant Tracker smarter I took advantage of Infineon background and suggestions and after trying the demo for the nanoHub I get convince that it was just perfect for it. The smart tracker project becomes more of a software project than a hardware project, so this project really will add instead of trying to change or reinvent what exists. It happens that the nanoHub has everything needed to deploy the solution and even conceive a prototype realization without doing a new PCB as you will see later on when I introduce a novelty gym accessory with nanoHub built in.
Gym Tracker software components Overview.The nanoHub has the intelligence to gather the DPS310 sensor values and transmit to a Host via Bluetooth, so it facilitates the DAQ for us. Moreover Android libraries exists to integrate easily on application. So we will use and Android application to interface which is great for two reasons, most of the people theses days owns a mobile Android device phone or tablet, but actually my idea came trying to use the new Android Things platform since I own a DIY Audio board and Pi3, and the meain reason is because I consider a phone a distraction at the gym.
Unfortunately after spending sometime trying to integrate the nanoHub libraries with my pi3 Android Things preview 5.1, I decide to move on and finish the project using a regular mobile device, so most of my tests were done on a Galaxie Tab 10.1 Pro for reference but any other device with required API should work.
User Interaction made smarter.
I get in love with the DIY google project, which gave me the idea to use voice commands to control the Tracker and receive feedback from it. It's actually more natural and of course as always in mind it's less distracting.
This project uses API.AI for interacting with trainee.
I started the project from the template example at the Android SDK for API.AI.
After getting rid of some unwanted features of the demo, I handle to have a seamlessly working project template to start with.
The tracker back-end.
Some time before this contest I discovery an open source project for fitness , workout and weight tracking called wger, at that time I play around the software a little bit but nothing serious enough, perhaps the lacking Front-End clam my developer cravings, once again the same reason, I was not willing to enter my workout logs manually after finishing, there was a time I use to do that but now I have less time in my life.
With nanoHub helping to sense my workouts and wger project to record it, I was ready for something new and fresh using today tools.
Which came first: the chicken or the egg ?
Nanohub hardware with android app give me the tracker measurements, but really, what would you do with it? you need to compare, analyze, store and anything you might have in mind to improve Trainee workouts. The flow of this discussion will now go in the direction to show how the cloud data of workouts get's into your mobile phone and then how the tracker update your progress and save it for later retrieval.
Software Details.Wger is really cool project, the application can be tried without installing locally, just create and account and you are done to start adding workouts and logs, it has an API which I use for getting workout database into android application.
It's just really tricky at the beginning to figure out the API, it's not fully documented and for the logs I am still figuring out things, actually If I would have some time in the future to spend on it's developing I will certainly change a few things as you can see on this issue I opened.
Below you can see how it looks a simple workout routine for a week, you can create exactly the same routine easily using the web interface.
Notice the workouts are Tuesdays, Thursdays and Saturdays which happen to be my actual workout days. If you stop at the git issue, this will be a thing to change in my opinion, since when you create a workout (wger doesn't separate workouts from routines in my opinion) you have to specify days.
Suppose today is Tuesday, you go to the gym, once there you open the android Gym Assistant App and want to see what your instructor prepare for you. A few things should be done for that, let's see.
Regardless the UI to accomplish the above, you have to use the API of wger and make a GET request, actually a couple of requests. To make the GET requests I could have take two paths, do the get directly from Android or externally. SInce I wanted to use the API.AI facilities, it was a non sense to do it in the android app since I was already interacting with teh API.AI and so I can use it's webhooks.
The idea is that the API.AI by means of a webhook in the wger Django application will handle the request and return the application queries back to API.AI which indeed will inform result to our Client app. The advantage of this is that we might do some processing in the webhook as needed, actually we will used it as you will see later on.
As commented before two requests are needed for getting the workout details, the first request to do is for looking at the "training number" which is a number that the wger server has assigned at workout creation time.
The training number is needed to filter the data of interest that returns the canonical API query, which is explained better here, basically it will return all workouts that are setup in the wger trainee account in a JSON format. If you look at the available API endpoints, the first impression is that it should be possible to do a filter using an endpoint that give us only the workout data of interest.
It becomes extremely painful to figure out the correct endpoint and parameters needed, but even worse is that more than one GET request is needed so I give up trying that path and did a nice parsing script in a django model that is called from the webhook view.
First the training number snippet.
@transition(field=state, source='new', target='find_workout')
def at_the_gym(self, date_time):
# self.exercise_day will be based on the actual day of query
# self.exercise_day = datetime.datetime.today().weekday() + 1
# hardwired now for testing until we fill up a whole massive week ;)
self.exercise_day = 2
url = "https://wger.de/api/v2/day/?day=%d" % self.exercise_day
headers = {'Authorization' : WGER_TOKEN}
r = requests.get(url, headers=headers)
if r.status_code == 200:
recv = r.json()
self.training = recv['results'][0]['training']
retExJson = {}
retExJson['Training_Number'] = self.training
return(ast.literal_eval(json.dumps(retExJson)))
else:
raise Exception("day not found")
Please notice the hardwired exercise day, but also notice the commented code before that. During my tests that took several days I was not wanting to have changing data from server or I will be obligated to create a workout for everyday, so I fix it with 2 which is the value for Tuesday.
Notice that the GET request need a Token which you generate using the wger web interface, so replace WGER_TOKEN with yours.
The function above is called "at_the_gym" since it reassembles the API.AI phrase I use for welcome. Notice the decorator @transition, I found very useful to do some checking for the transitions that might involve user utterances, which if used wrong might give unexpected results. To solve this problem I used this django finite state machine (fsm) let's clarify this after taking a look at the canonical query.
@transition(field=state, source='find_workout', target='overview')
def workout_info(self, date_time):
print("training day = %d" % self.training)
# Go get the canonical JSON
url = "https://wger.de/api/v2/workout/%d/canonical_representation/" % self.training
headers = {'Authorization' : WGER_TOKEN}
r = requests.get(url, headers=headers)
if r.status_code == 200:
canonical = r.json()
# Loop over all possible training days lookin for ours
day_index = None
for item in range(len(canonical['day_list'])):
break
if day_index is None:
workout = canonical['day_list'][day_index]['set_list']
# let's total super set the totakl number of sets
tot_super_sets = len(workout)
exList = []
for item in range(tot_super_sets):
exDict = {}
exDict['name'] = workout[item]['exercise_list'][ex_item]['obj']['name']
exDict['reps_list'] = workout[item]['exercise_list'][ex_item]['reps_list']
exDict['weight_list'] = workout[item]['exercise_list'][ex_item]['weight_list']
print(exDict)
exList.append(exDict)
print(exList)
retExJson = {}
retExJson['Exercise_List'] = exList
return(ast.literal_eval(json.dumps(retExJson)))
else:
raise Exception("day not found")
The query get the canonical representation and after filtering we extract a python dictionary that contains the all the exercises of the given training day and including the name, number of sets, number of repetitions and weight to load on the bar. The dictionary is converted into JSON since it's the way to pass it to API.AI response. I notice that for some reason the API.AI didn't like the double quotes on the JSON so I strip them using the convenient trick ast.literal_eval.
You might think that we might have done the two queries on the same function, it's a matter of how you like it or prefer, basically I do a welcome utterance, "at the gym now", then API.AI salute back to me. After that I ask the Gym Assistance app about the workout details using utterance "let me know about today workout".
What follows is that the information is presented to trainee in a table on the mobile device.
One example usage in which the state machine is useful during these two transitions is i.e. in the case where for some reason you have no scheduled workout for the day, in this case the first GET request will return something different from HTTP 200 code, so we just fire an exception. The exception is necessary so that the fsm do not make the state transition.
Actually let me introduce the views.py that handle the API.AI webhook.
from django.http import HttpResponse
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import simplejson as json
from wger.assistant.models import myPersonalNavigator
@csrf_exempt
def webhook(request):
if request.method=='POST':
received_json_data = json.loads(request.body.decode("utf-8"))
parameters = received_json_data['result']['parameters']
print(parameters)
data = ''
response = ''
try:
if 'new_workout' in parameters.keys():
try:
data = myPersonalNavigator.at_the_gym(date_time=parameters['date-time'])
except:
response = "Did you have a scheduled workout?"
return JsonResponse({"speech": response, "displayText": response})
if 'date-time' in parameters.keys():
response = "you arrive at %s" % parameters['date-time']
elif 'workout' in parameters.keys():
try:
data = myPersonalNavigator.workout_info(date_time=parameters['date-time'])
except:
response = "Could not get Workout details"
return JsonResponse({"speech": response, "displayText": response})
if data == '':
response = "Could not get Workout details"
else:
response = "Filling table with workout details"
elif 'sequence' in parameters.keys():
try:
data = myPersonalNavigator.workout_sequence(sequence=parameters['sequence'])
response = '' # API client will handle this
except:
response = "Are you at the gym? Let me know to be in a correct state!" elif 'exercise_action' in parameters.keys():
try:
data = myPersonalNavigator.workout_action(parameters=parameters)
response = '' # API client will handle this
except:
response = "You are not in a state to do any action!" elif 'perfoming' in parameters.keys():
try:
data = myPersonalNavigator.perform_exercise()
response = "Load the bar and lift hard"
except:
response = "You are already performing"
else:
response = "Sorry didn't get that"
except KeyError:
response = "Sorry didn't get that"
return JsonResponse({"speech": response, "displayText": response, "data" : data })
return HttpResponse("Error processing POST")
It's not the best editor for inserting code with lines as long as those but I hope you are able to read it.
Important to note is that we might change the response from API.AI by inserting the utterance in JSON parameters "speech" and "displayText", but also we might inject our data using the data parameter, that is
JsonResponse({"speech": response, "displayText": response, "data" : data })
You can use API.AI simulator to analyze the response JSON before even use the android app, which is a big advantage to speed up tests and get rid of bugs that might involve your webhook. Before proceeding further with application I will explain a little bit of API.AI setup, the webhook and how I test everything.
API.AI and Django webhook integration.
I will suggest to familiarize with API.AI if you really want a deep understanding, I will try to cover things as best as possible.
Intents.
The intents is how you associate utterances with actions to day in a way, I already comment the user utterances of the first two intents that my application requires, the Gym Welcome and the Workout Description.
Let's cover them briefly.
- GymWelcome.
Two parameters are generated from the utterance "at the gym now", the parameters get into the JSON as shown in the table column "PARAMETER NAME".
That is new_workout and date-time, notice how we enable the webhook in the Fulfillment at the bottom.
Now the concept of Entity, "at the gym" is adapted by using a new entity we create in which we define other meanings or synonymous that trainee might use depending on his/her mood. Using the below Entity, user might end up saying "new workout now" which will fire our webhook.
Setup the webhook.
Since our modified django application runs locally, the easiest way to test it is using ngrok, the getting started guide here is all you need to setup the tunnel.
Our wger server runs on port 8000 so just take that into account.
ngrok will give you an https url to access from outside.
Something like
https://1234567890.ngrok.io -> localhost:8000
Then go to API.AI Fulfillment and copy that url into the URL* field and enable it.
Let's see how it looks the JSON response from GymWelcome.
Notice the data field in fulfilment with oir training number.
- Workout Description
This intent uses another custom entity called workout.
Here the json response.
Since we have several exercise, JSON was large, figure above show the Fulfillment that contains the exercises information.
Other Intents are:
- exercise sequence. Allow trainee to move over the table or exercise list by saying "next exercise" or "first exercise".
- exercise setup. Is used to fix the table since trainee is going to perform that exercise, using the fsm it's not possible to come back or move exercise until exercise is done. More on that later when explaining the android app.
- Workout Action. Allow user to specify is ready for the lift, but also there are prevision for other actions such as trainee changing or altering weights.
Resuming Webhook.
The last three Intents briefly described are handle also in webhook, the sequence and setup does not perform really a processing, they might reside only in API.AI but remember that we are using the FSM, so actually they do nothing to the request, i.e. the sequence Intent defines a Entity that has grouped sequence utterances such as next. first, last, those are unaltered by django webhook and handle by android app, but if user instruct Bot one of those in an incorrect state, the webhook will not further process it so android app if reinforce to receive incorrect commands simplifying and making it more robust.
@transition(field=state, source='overview', target='overview')
def workout_sequence(self, sequence):
# for now just build a response, but this method will
# * allow correct state transition
# * provide addictional features
retExJson = {}
retExJson['sequence'] = sequence
return(ast.literal_eval(json.dumps(retExJson)))
@transition(field=state, source='overview', target='performing')
def perform_exercise(self):
retExJson = {}
retExJson["perform"] = "True"
return(ast.literal_eval(json.dumps(retExJson)))
@transition(field=state,source='performing',target=['performing','overview'])
def workout_action(self, parameters):
# self.state = 'performing'
action = parameters['exercise_action']
parameter = 'action'
parse_action = {}
parse_action['error'] = "NoErr"
if action == "start set" or action == "go" or action == "let's go":
# application action
parse_action['log_entry'] = 'new'
elif action == "change weight":
if 'number-integer' in parameters.keys():
parse_action['weight'] = parameters['number-integer']
else:
parse_action['error'] = "WtErr"
elif action == "add set":
pass
elif action == "done with set":
# some houskeeping such as receiving log of this exercise
pass
# self.state = 'overview'
else:
pass
# self.state = 'overview'
retExJson = {}
retExJson[parameter] = parse_action
return(ast.literal_eval(json.dumps(retExJson)))
Android App.The best way to start would be refering to the video that shows how the layout of the Gym Assistant looks like and how the API.AI is initially called and how trainee interact with seen before.
The table contains the exercise name, the Sets to perform, the Reps and the Weight.
The API.AI do it's job making the call to the webhook which returns the exercises in a JSON, the android code that parse the JSON is a function called onResult, an extract is shown below.
Map <String, JsonElement> Data = result.getFulfillment().getData();
if (Data != null && !Data.isEmpty()) {
for(final Map.Entry<String, JsonElement> entry : Data.entrySet()){
try {
Log.i(TAG, "Key : " + entry.getKey());
if (entry.getKey().equals("Training_Number")) {
// not doing anything here with this number now but that was a
// required step of the state machine.
Log.i(TAG, "Training_Number " + entry.getValue().toString());
} else if (entry.getKey().equals("Exercise_List")) {
// The exercise list response
JSONArray ex_list = new JSONArray(entry.getValue().toString());
for (int j = 0; j < ex_list.length(); j++) {
exer_info tmp_info = new exer_info();
JSONObject exer_data = ex_list.getJSONObject(j);
String name = exer_data.getString("name");
tmp_info.name = name;
over_idx = 0;
tmp_info.done = false;
tmp_info.set_log_idx = 0;
tmp_info.reps_info = new ArrayList<exer_reps_info>();
tmp_info.log_info = new ArrayList<exer_reps_info>();
tmp_info.cal = new calibrated();
tmp_info.cal.State = false;
tmp_info.cal.threshold_max = 0.5; // fixing this now for tests
tmp_info.cal.threshold_min = 0.15; // fixing this now for tests
JSONArray reps_list = exer_data.getJSONArray("reps_list");
JSONArray weight_list = exer_data.getJSONArray("weight_list");
if (reps_list != null) {
for (int i = 0; i < reps_list.length(); i++) {
exer_reps_info tmp_reps_info = new exer_reps_info();
exer_reps_info tmp_logs_info = new exer_reps_info();
tmp_reps_info.reps = reps_list.getInt(i);
tmp_reps_info.weight = weight_list.getInt(i);
tmp_info.reps_info.add(tmp_reps_info);
// adding log with reps in zero
tmp_logs_info.reps = 0;
tmp_logs_info.weight = weight_list.getInt(i);
tmp_info.log_info.add(tmp_logs_info);
tmp_reps_info = null;
tmp_logs_info = null;
}
}
exercise_list.add(tmp_info);
}
for (int j = 0; j < exercise_list.size(); j++) {
Log.i(TAG, "EXERCISE #" + j + " : " + exercise_list.get(j).name);
Log.i(TAG, "REPS/WEIGHTS: ");
for (int i = 0; i < exercise_list.get(j).reps_info.size(); i++) {
Log.i(TAG, exercise_list.get(j).reps_info.get(i).reps.toString() +
"/" + exercise_list.get(j).reps_info.get(i).weight);
}
}
// Fill out workout table with initial value
FillWorkoutTable (over_idx);
}else if(entry.getKey().equals("sequence")){
// This will handle navigation throu the exercise table.
Log.i(TAG, String.format("%s: %s", entry.getKey(), entry.getValue().toString()));
String sequence = entry.getValue().getAsString();
String gymapp_speech = sequence;
if (sequence.equals("first")){
over_idx = 0;
Log.i(TAG, "first");
FillWorkoutTable (over_idx);
gymapp_speech = "The first exercise is " + exercise_list.get(over_idx).name;
else if(sequence.equals("next")){
Log.i(TAG, "next");
if(over_idx >= exercise_list.size() - 1) {
gymapp_speech = exercise_list.get(over_idx).name + "is actually the last exercise";
}else {
over_idx++;
gymapp_speech = "The next exercise is " + exercise_list.get(over_idx).name;
FillWorkoutTable(over_idx);
}
}else if(sequence.equals("last")){
over_idx = exercise_list.size() - 1;
Log.i(TAG, "last");
FillWorkoutTable (over_idx);
gymapp_speech = "The last exercise is " + exercise_list.get(over_idx).name;
}else if(sequence.equals("remaining")){
Log.i(TAG, "remaining");
Integer performed = exercise_list.get(over_idx).log_info.size();
if(performed == 0){
gymapp_speech = "You haven't started this exercise, come in don't be lazy";
}else {
Integer remaining = exercise_list.get(over_idx).reps_info.size() -
performed;
String sets = (remaining > 1) ? "sets" : "set";
gymapp_speech = "You still have <say-as interpret-as=\"cardinal\"> " +
remaining + "more " + sets + " to perform";
}
}else{
Log.i(TAG, "else other sequence unhandled");
gymapp_speech = "";
}
TTS.speak(gymapp_speech);
}else if(entry.getKey().equals("action")){
// this will take over actions from trainee such as startinga new set.
try{
JSONObject action_list = new JSONObject(entry.getValue().toString());
if(action_list.has("error")){
String error = action_list.getString("error");
if(error.equals("NoErr")){
// decode received json action
if(action_list.has("log_entry")){
// Right now I am returning new always here so igring this value!
Log.i(TAG, "Log Entry Action: " + action_list.getString("log_entry"));
if(!exercise_list.get(log_idx).done){
// proceed if exercise was not previously performed
RelAltitude.reset();
FillPerfReps(0);
if (!sensor_started) {
sensor_started = true;
if(set_timer == null) {
set_timer = new SetCountDownTimer(60 * 1000, 1000);
}
set_timer.start();
}
}else{
// done with that exercise
}
}else if(action_list.has("weight")){
Log.i(TAG, "Weight Action: " + action_list.getString("weight"));
}
}else{
Log.i(TAG, "An Error occur at webhook processing");
// give error treatment here
}
}// if not error that should be an error
}catch (JSONException e) {
e.printStackTrace();
}
}else if(entry.getKey().equals("perform")){
// this will force a new (in sequence) exercise to be performed by trainee
Log.i(TAG, "Perform: ");
try {
Boolean isActive = entry.getValue().getAsBoolean();
if (isActive) {
Log.i(TAG, "True");
FillWorkoutTable (log_idx); // Fill the table with exercise to perform
}else {
// Right now webhook is always returning True
// if state machine fails it will return some error
Log.i(TAG, "False");
}
}catch (Exception ex){
Log.i(TAG, "Exception parsing perform");
}
}
}catch (JSONException e) {
e.printStackTrace();
}
}
}
A class that holds the table values is implemented using an ArrayList.
The structure that holds the workout details also is used to hold the Log values.
class exer_reps_info {
Integer reps;
Integer weight;
}
class calibrated {
Double threshold_min;
Double threshold_max;
Boolean State;
}
class exer_info {
String name;
ArrayList<exer_reps_info> reps_info;
Boolean done;
Integer set_log_idx;
ArrayList<exer_reps_info> log_info;
calibrated cal;
}
In order to measure the repetitions, the relative position was implemented as another class called RelativeCalculator. Here is an overview of the java classes of android project.
The structure for the log holds a class called calibrated, the idea is that each exercise might require calibration, i.e. it's nit the same to do squats or bicep curls in terms of the distance.
The code that implement the reps counts is located on the well known method that receive the samples.
if(sensor_started) {
if (!sensorEvent.getDataId().equals("a"))
return;
RelAltitude.updateValue(sensorEvent.getSensorValue());
tv_rel_altitude.setText(String.format("Rel Pos = %.2f", RelAltitude.getRelativeValue()));
if (exercise_list.get(log_idx).cal.State) {
if (abs(RelAltitude.getRelativeValue()) <= exercise_list.get(log_idx).cal.threshold_min) {
Integer idx = exercise_list.get(log_idx).set_log_idx;
RelAltitude.reset();
exercise_list.get(log_idx).cal.State = false;
Integer count = exercise_list.get(log_idx).log_info.get(idx).reps;
// for some reason I cannot do reps++
count = count + 1;
exercise_list.get(log_idx).log_info.get(idx).reps = count;
FillPerfReps(count);
Integer goal = exercise_list.get(log_idx).reps_info.get(idx).reps;
Log.i(TAG, "REPS: " + count.toString());
SetCounter.setText(count.toString());
if (count.equals(goal)) {
Log.i(TAG, "Set completed");
TTS.speak("good you are done!");
sensor_started = false;
idx = idx + 1;
// we cancel the timer since we completed the set
set_timer.cancel();
if(exercise_list.get(log_idx).reps_info.size() > idx){
// next time we need to log in the correct position (next Set)
exercise_list.get(log_idx).set_log_idx = idx;
Log.i(TAG, "There are pending sets");
TTS.speak("You have more sets to do, let me know when you are ready!");
}else{
// it means we are done with exercise sets
exercise_list.get(log_idx).done = true;
if(log_idx >= exercise_list.size() - 1) {
// it means we are done with last workout exercise
workout_done = true;
Log.i(TAG, "Finish the workout");
TTS.speak("Glad you are done, go to rest and remember " +
"Winners Train, Losers Complain ");
final Intent mServiceIntent = new Intent();
mServiceIntent.setClass(AIDialogSampleActivity.this, RSSPullService.class);
String jsonLog = getJsonLog(exercise_list);
mServiceIntent.putExtra("logData", jsonLog);
AIDialogSampleActivity.this.startService(mServiceIntent);
}else{
log_idx = log_idx + 1;
if(!exercise_list.get(log_idx).done) {
// proceed if exercise was not previously performed (just to be sure)
FillWorkoutTable(log_idx); // Fill the table with exercise to perform
Log.i(TAG, "Display next exercise");
TTS.speak("You finish your " + exercise_list.get(log_idx-1).name + " sets");
TTS.speak("Next exercise is " + exercise_list.get(log_idx).name );
}
}
}
// this seems to not work here
// sensorHub.setSelectedSensor(sensorHub.getSensorList().get(0).getId());
// sensorHub.stop();
}
}
} else {
// Initial state
if (abs(RelAltitude.getRelativeValue()) >= exercise_list.get(log_idx).cal.threshold_max) {
exercise_list.get(log_idx).cal.State = true;
}
}
} // if started
else
{
tv_rel_altitude.setText("");
SetCounter.setText("");
}
Notice how actually we compare values of threshold_min and threshold_max to conclude a new rep was done.
A short working demo of the Gym Assistant proves the concept and motivate to continue working on the project for advance features.
Sensor is mounted on a little poly-carbonate enclosure for testing at home.
The application would not be complete without the gym tracker device gadget. Normally I don't use a watch to lift, neither a head strap since that would be tennis. Then I realize one of the best places is actually a Hook that is used to hold the plates on the Olympic bar, there are several kinds but modern versions are from plastic, a google search for "Barbell Collar" will give you options.
Benefits.
- Can be used by trainee without distraction as it's actually a gym accessory.
- Gym owners can take advantage and supply them as an optional subscription for use the services.
- Similar devices for dumbbells, wire machines, etc can be designed to track everything possible.
Here is a design proposal for nanoHub, is actually the real size for a Barbell Collar with as real as possible nanoHub board mounted on it.
The nanoHub enclosure is mounted with three screws. on the Barbell Collar. The enclosure has a slide switch to take advantage of the switch already on the board.
One possible advantage is that the same enclosure can be reused for other gym tracker gadgets as mentioned before. Here is the enclosure taken apart.
Comments