Connect the elliptical trainer and pygame

    Hello friends! It happened once so that to recover 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 on the distance traveled. If you go slowly enough, then the speedometer was 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 we decided to start with was to find a way to get data to a computer. It was decided to use the Arduino board as an intermediate link .
    Why Arduino ? Because at hand there is nothing else suitable.
    On examination, it was found that two wires were going from the simulator to the sensor.
    image
    What is enough to connect it to the Arduino pins . What has been done according to such a circuit here

    On a contact A0 , depending on the position of the pedals, a signal of different sizes will be received.
    In the course of the experiments, many options for supplying a signal from the microcontroller to the computer were tried, and as a result I stopped at this option: the
    symbol is continuously fed to the computer“0” , then when a step is taken on the simulator, “1” is applied . The next step is again “0” and so on in a circle.
    I bring 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); //индикатор
    }
    



    The game


    What else to write on pygame if not a game?

    Idea

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

    Calculations

    Empirically, it was found that under "optimal" circumstances, one full revolution is 4 meters. This is most likely not how many people pass, but how much the central disk is scrolling. Just take this value as an axiom.
    On a virtual track, 1 meter equals 1 pixel. Those. each step we move the character 4 pixels forward.
    Speed ​​will calculate every step.
    v = s / t
    s = 4 m.
    t is the time of one step.
    * One step - full pedal rotation.

    Passion

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

    Today’s character is above, yesterday’s one is below. To be more precise - the character of the last race but agree that the first option sounds cooler.

    Technical details



    Database

    Naturally, since you need to save race information, 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 appearance that is important. For its implementation, the pygame library was used . About the work with which I already wrote.

    Forms


    For forms used 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 development process. No more complicated than in MS studio .
    Forms created in the Qt 4 Creator application .
    Imported them into 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()
    


    Graphs


    For graphs, the matplotlib library is used.
    Here is a sample code too
    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 the Cyrillic alphabet, 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 serial library .
    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.
    Running two threads.
    t1 = threading.Thread(target=main,args = (self.profileId,self.distanceId))
    t2 = threading.Thread(target=getDataFromSimulator)
    t2.start()
    t1.start()
    

    Poor quality video demo


    As ordered.


    I will be glad to comments, criticism and suggestions.
    All sources here

    Also popular now: