📜 ⬆️ ⬇️

Connect the elliptical trainer and pygame

Hello friends! Once it happened that for recovering from an injury I bought myself such a device.

He coped with his direct duties quite satisfactorily, but there was one “but”, and it consisted in the fact that the speedometer was confused in the testimony, and therefore showed different results for the distance traveled. If you walk slowly enough, the speedometer is generally silent. And it was decided to make your speedometer with ... well, you understand.


How to connect a simulator and a computer


The first thing that was decided to start with was to find a way to get data to a computer. Intermediate, it was decided to use the Arduino board.
Why Arduino ? Because at hand there is not anything else suitable.
On examination, it was found that two wires go from the simulator to the sensor.
image
What is quite enough to connect it to the Arduino pins. What was done according to this scheme

Pin A0 , depending on the position of the pedals, will receive a signal of a different size.
During the experiments, many variants of the signal from the microcontroller to the computer were tried, and finally settled on this option:
the symbol “0” is continuously supplied to the computer, then, when a step is made on the simulator, “1” is fed. The next step is again “0” and so on.
Quote sketch
int pin = A0; int ledPin = 13; int minSignal = 600; bool stateUp = false; bool lastState = false; bool oneStep = false; void setup() { pinMode(pin, INPUT); pinMode(ledPin, OUTPUT); Serial.begin(9600); } void loop() { int signal = analogRead(pin); if (signal > minSignal){ stateUp = true; } else{ stateUp = false; } if (lastState != stateUp && lastState == false){ oneStep = not oneStep; } else { } lastState = stateUp; Serial.println(oneStep); digitalWrite(ledPin, oneStep); // } 



A game


What else to write on pygame if not a game?
')
Idea

Elliptical trainer is an imitation of skiing, so it will be a skier race. Each step made on the simulator makes a character in the game. At first I wanted to make a smooth movement / acceleration of the character, but in the end, I decided to give preference to accuracy.

Calculations

It was experimentally found out that under “optimal” circumstances one full turn equals 4 meters. This is most likely not how much a person passes, but how much the central disk scrolls. Just take this value as an axiom.
On a virtual track, 1 meter equals 1 pixel. Those. Each step is moved 4 pixels forward.
Speed ​​will calculate each step.
v = s / t
s = 4 m.
t is the time of one step.
* One step - a full turn of the pedals.

Passion

Yes, there will be graphics and a speedometer with a timer, but I want the spirit of competition.
And what if you compete not with someone, but with yourself, yesterday ? No sooner said than done.

Top character today, bottom - yesterday. To be more precise - the character of the last race, but you must agree, the first version sounds cooler.

Technical details



Database

Naturally, once you need to save information about the races, you need a database. I decided to use mysql . In python I use the MySQLdb library. In the application, the DataManger class is responsible for the interaction.
Scheme attached.

Code example
 lass DataManager: def __init__(self): self.time = time self.currentTimeForLastRace = datetime.now() self.currentTime = self.time.time() self.speed = 0 self.db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="skirunner", charset='utf8') self.cursor = self.db.cursor() self.isGetLastRaceSpeeds = False self.dataLastRace = [] self.lastRaceMinDate = datetime.now() self.value = 0 self.lastValue = 0 self.impulse = 0 self.isRaceStart = False self.currentRaceId = -1 self.currentDistanceId = -1 self.currentProfileId = -1 def getImpulse(self, value): self.impulse = 0 if self.time.time() - self.currentTime > RESET_SPEED_TIME: self.speed = 0 self.value = value if self.value != self.lastValue: time = self.time.time() - self.currentTime self.impulse = POWER_IMPULSE self.isRaceStart = True self.speed = STEP / time #    self.currentTime = self.time.time() self.lastValue = self.value return self.impulse def getLastRaceDistanceAtCurrentTime(self, raceId,currentTime): lastRaceDistance = 0 dateFormat = "%Y-%m-%d %H:%M:%S.%f" if not self.isGetLastRaceSpeeds: sql = """SELECT min(date) FROM runLog WHERE race_id = %s""" % raceId self.cursor.execute(sql) data = self.cursor.fetchall() for rec in data: self.lastRaceMinDate = datetime.strptime(rec[0],dateFormat) sql = """SELECT distance,date FROM runLog WHERE race_id = %s ORDER BY date DESC""" % raceId self.cursor.execute(sql) self.dataLastRace = self.cursor.fetchall() self.isGetLastRaceSpeeds = True if self.isRaceStart: time = datetime.now() - datetime.fromtimestamp(currentTime) for rec in self.dataLastRace: distance, date = rec if time <= (datetime.strptime(date,dateFormat) - self.lastRaceMinDate): lastRaceDistance = distance return lastRaceDistance 



Graphics

As you can see from the screenshot above, the graphics are primitive, but it’s not the main thing here. For its implementation, the pygame library was used. About the work with which I already wrote.

Forms


For forms I used the PyQt library.
Code example
 class FormProfile(QMainWindow): def __init__(self): super(QMainWindow, self).__init__() uic.loadUi('%s/ui/frm_profile.ui' % DIR, self) self.cb_profile_load() self.te_newProfile.hide() self.bt_addProfile.hide() self.bt_cancel.hide() self.lb_add.hide() self.move(QDesktopWidget().availableGeometry().center() - self.frameGeometry().center()) self.connect(self.bt_ok, SIGNAL("clicked()"), self.bt_ok_clicked) self.connect(self.bt_new, SIGNAL("clicked()"), self.bt_new_clicked) self.connect(self.bt_addProfile,SIGNAL("clicked()"), self.bt_addProfile_clicked) self.connect(self.bt_cancel, SIGNAL("clicked()"), self.bt_cancel_clicked) self.connect(self.bt_graph, SIGNAL("clicked()"), self.bt_graph_clicked) def bt_ok_clicked(self): self.profileId = self.cb_profile.itemData(self.cb_profile.currentIndex()).toString() self.formDistance = FormDistance(self.profileId) self.formDistance.show() self.hide() 


I really liked the window design process. No more difficult than in MS studio .
Forms created in the Qt 4 Creator application.
Imported them into the code.
 uic.loadUi('%s/ui/frm_profile.ui' % DIR, self) 

Associated events and methods
  self.connect(self.bt_ok, SIGNAL("clicked()"), self.bt_ok_clicked) self.connect(self.bt_new, SIGNAL("clicked()"), self.bt_new_clicked) self.connect(self.bt_addProfile,SIGNAL("clicked()"), self.bt_addProfile_clicked) self.connect(self.bt_cancel, SIGNAL("clicked()"), self.bt_cancel_clicked) self.connect(self.bt_graph, SIGNAL("clicked()"), self.bt_graph_clicked) 

And displayed
  self.formProfile = FormProfile() self.formProfile.show() 


Charts


For charts, the matplotlib library is used.
Here is also a sample code
 import matplotlib.pyplot as plt def bt_averageSpeed_clicked(self): ... plt.plot_date(dates, values,'b') plt.plot_date(dates, values,'bo') averageSpeed = len(values) > 0 and (lambda: sum(values) / len(values)) or (lambda: 0) plt.xlabel(u"- = %.2f /  %.2f /" % (float(averageSpeed()),float(averageSpeed()) / 1000 * 3600)) plt.ylabel(u"  (/)") plt.title(u"   %s" % dm.getProfileNameById(self.profileId)) plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%d/%m/%y')) plt.gcf().autofmt_xdate() plt.grid(True) plt.show() 

I would like to note that to display Cyrillic, you need to connect supporting fonts.
 from matplotlib import rc font = {'family': 'Droid Sans', 'weight': 'normal', 'size': 14} rc('font', **font) 



Reading data with arduino

For this purpose I used the library serial .
The following code runs in a separate thread.
 def getDataFromSimulator(): global valueFromSimulator, isRunnig ser = serial.Serial('/dev/ttyACM0', 9600) while isRunnig: value = ser.readline() try: valueFromSimulator = int(value) except: pass 

The variable valueFromSimulator in another thread is used only for reading.
Run two threads.
 t1 = threading.Thread(target=main,args = (self.profileId,self.distanceId)) t2 = threading.Thread(target=getDataFromSimulator) t2.start() t1.start() 

Poor quality video demonstration


As ordered.


I would welcome comments, criticism and suggestions.
All sources here

Source: https://habr.com/ru/post/217891/


All Articles