1529
0,4
2014-04-02
Соединяем эллиптический тренажер и pygame
Привет, друзья! Случилось однажды так, что для восстановления после травмы я прикупил себе такой вот прибор.
Со своими прямыми обязанностями он справлялся вполне удовлетворительно, но было одно «но», и заключалось оно в том, что спидометр путался в показаниях, и следовательно, показывал разные результаты по пройденной дистанции. Если идти достаточно медленно, то спидометр вообще молчал. И решено было сделать свой спидометр с… ну вы поняли.
Первое, с чего решено было начать — найти способ получать данные на компьютер. Промежуточным звеном решено было использовать плату Arduino.
Почему Arduino? Потому что под рукой нет ни чего другого подходящего.
При осмотре обнаружилось, что от тренажера к датчику идут два провода.
Чего вполне хватит, чтобы подключить его к пинам Arduino. Что и было сделано по вот такой вот схеме
На контакт A0 в зависимости от положения педалей, будет поступать сигнал разной величины.
В ходе экспериментов были перепробованы многие варианты подачи сигнала от микроконтроллера до компьютера, и в итоге остановился на таком варианте:
на компьютер безпрерывно подается символ «0», затем, когда сделан шаг на тренажере, подается«1». Следующий шаг — снова «0» и так по кругу.
Привожу sketch
Что еще писать на pygame если не игру?
Эллиптический тренажер это имитация ходьбы на лыжах, поэтому это будет гонка лыжников. Каждый шаг, сделанный на тренажере делает персонаж в игре. Сперва хотелось сделать плавное передвижение\ускорение персонажа, но в итоге, решил отдать предпочтение точности.
Опытным путем было выяснено, что при «оптимальных» обстоятельствах один полный оборот равняется 4-м метрам. Это скорей всего не сколько проходит человек, а сколько прокручивается центральный диск. Просто примем это значение за аксиому.
На виртуальной трассе 1 метр равняется 1 пикселю. Т.е. каждый шаг перемещаем перснажа на 4 пикселя вперед.
Скорость будет высчитывать каждый шаг.
v = s /t
s = 4 м.
t — время одного шага.
*один шаг — полный оборот педалей.
Да, будут графики и спидометр с таймером, но хочется духа соревнования.
А что, если соревноваться будешь не с кем-то а с самим собой, вчерашним? Сказано — сделано.
Сверху персонаж сегодняшний, снизу — вчерашний. Если быть более точным — персонаж прошлого забега но согласитесь, первый вариант звучит круче.
Естественно, раз нужно сохранять информацию о забегах, нужна БД. Я решил использовать mysql. В python использую библиотеку MySQLdb. В приложении за взаимодействие отвечает классDataManger.
Схема прилагается.
Пример кода
Как можно увидеть из скриншота выше, графика примитивная, но не няшность это тут главное. Для ее реализации была использована библиотека pygame. О работе с которой я уже писал.
Для форм использовал библиотеку PyQt.
Пример кода
Мне очень понравился процесс разработки окон. Не сложнее, чем в MS studio.
Формы создал в приложении Qt 4 Creator.
Импортировал их в код
Связал события и методы
И отобразил
Для графиков используется библиотека matplotlib.
Тут тоже пример кода
Хотелось бы заметить, что для отображения кириллицы, нужно подключить поддерживающие шрифты.
Для этой цели использовал библиотеку serial.
Следующий код запускается в отдельном потоке.
Переменная valueFromSimulator в другом потоке используется только для считывания.
Запуск двух потоков.
Как и заказывали.
Буду рад замечаниям, критике и предложениям.
Все исходники тут
Источник: habrahabr.ru/post/217891/
Со своими прямыми обязанностями он справлялся вполне удовлетворительно, но было одно «но», и заключалось оно в том, что спидометр путался в показаниях, и следовательно, показывал разные результаты по пройденной дистанции. Если идти достаточно медленно, то спидометр вообще молчал. И решено было сделать свой спидометр с… ну вы поняли.
Как соединить тренажер и компьютер
Первое, с чего решено было начать — найти способ получать данные на компьютер. Промежуточным звеном решено было использовать плату Arduino.
Почему Arduino? Потому что под рукой нет ни чего другого подходящего.
При осмотре обнаружилось, что от тренажера к датчику идут два провода.
Чего вполне хватит, чтобы подключить его к пинам Arduino. Что и было сделано по вот такой вот схеме
На контакт A0 в зависимости от положения педалей, будет поступать сигнал разной величины.
В ходе экспериментов были перепробованы многие варианты подачи сигнала от микроконтроллера до компьютера, и в итоге остановился на таком варианте:
на компьютер безпрерывно подается символ «0», затем, когда сделан шаг на тренажере, подается«1». Следующий шаг — снова «0» и так по кругу.
Привожу sketch
<code class="cpp">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); //индикатор } </code>
Игра
Что еще писать на pygame если не игру?
Идея
Эллиптический тренажер это имитация ходьбы на лыжах, поэтому это будет гонка лыжников. Каждый шаг, сделанный на тренажере делает персонаж в игре. Сперва хотелось сделать плавное передвижение\ускорение персонажа, но в итоге, решил отдать предпочтение точности.
Расчеты
Опытным путем было выяснено, что при «оптимальных» обстоятельствах один полный оборот равняется 4-м метрам. Это скорей всего не сколько проходит человек, а сколько прокручивается центральный диск. Просто примем это значение за аксиому.
На виртуальной трассе 1 метр равняется 1 пикселю. Т.е. каждый шаг перемещаем перснажа на 4 пикселя вперед.
Скорость будет высчитывать каждый шаг.
v = s /t
s = 4 м.
t — время одного шага.
*один шаг — полный оборот педалей.
Азарт
Да, будут графики и спидометр с таймером, но хочется духа соревнования.
А что, если соревноваться будешь не с кем-то а с самим собой, вчерашним? Сказано — сделано.
Сверху персонаж сегодняшний, снизу — вчерашний. Если быть более точным — персонаж прошлого забега но согласитесь, первый вариант звучит круче.
Технические детали
База данных
Естественно, раз нужно сохранять информацию о забегах, нужна БД. Я решил использовать mysql. В python использую библиотеку MySQLdb. В приложении за взаимодействие отвечает классDataManger.
Схема прилагается.
Пример кода
<code class="python">с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 </code>
Графика
Как можно увидеть из скриншота выше, графика примитивная, но не няшность это тут главное. Для ее реализации была использована библиотека pygame. О работе с которой я уже писал.
Формы
Для форм использовал библиотеку PyQt.
Пример кода
<code class="python">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() </code>
Мне очень понравился процесс разработки окон. Не сложнее, чем в MS studio.
Формы создал в приложении Qt 4 Creator.
Импортировал их в код
<code class="python">uic.loadUi('%s/ui/frm_profile.ui' % DIR, self) </code>
Связал события и методы
<code class="python"> 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) </code>
И отобразил
<code class="python"> self.formProfile = FormProfile() self.formProfile.show() </code>
Графики
Для графиков используется библиотека matplotlib.
Тут тоже пример кода
<code class="python">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() </code>
Хотелось бы заметить, что для отображения кириллицы, нужно подключить поддерживающие шрифты.
<code class="python">from matplotlib import rc font = {'family': 'Droid Sans', 'weight': 'normal', 'size': 14} rc('font', **font) </code>
Чтение данных с arduino
Для этой цели использовал библиотеку serial.
Следующий код запускается в отдельном потоке.
<code class="python">def getDataFromSimulator(): global valueFromSimulator, isRunnig ser = serial.Serial('/dev/ttyACM0', 9600) while isRunnig: value = ser.readline() try: valueFromSimulator = int(value) except: pass </code>
Переменная valueFromSimulator в другом потоке используется только для считывания.
Запуск двух потоков.
<code class="python">t1 = threading.Thread(target=main,args = (self.profileId,self.distanceId)) t2 = threading.Thread(target=getDataFromSimulator) t2.start() t1.start() </code>
Видеодемонстрация плохого качества
Как и заказывали.
Буду рад замечаниям, критике и предложениям.
Все исходники тут
Источник: habrahabr.ru/post/217891/