...

среда, 2 апреля 2014 г.

Соеденяем эллиптический тренажер и pygame

Привет, друзья! Случилось однажды так, что для восстановления после травмы я прикупил себе такой вот прибор.



Со своими прямыми обязанностями он справлялся вполне удовлетворительно, но было одно «но», и заключалось оно в том, что спидометр путался в показаниях, и следовательно, показывал разные результаты по пройденной дистанции. Если идти достаточно медленно, то спидометр вообще молчал. И решено было сделать свой спидометр с… ну вы поняли.


Как соединить тренажер и компьютер




Первое, с чего решено было начать — найти способ получать данные на компьютер. Промежуточным звеном решено было использовать плату Arduino.

Почему Arduino? Потому что под рукой нет ни чего другого подходящего.

При осмотре обнаружилось, что от тренажера к датчику идут два провода.

image

Чего вполне хватит, чтобы подключить его к пинам Arduino. Что и было сделано по вот такой вот схеме



На контакт A0 в зависимости от положения педалей, будет поступать сигнал разной величины.

В ходе экспериментов были перепробованы многие варианты подачи сигнала от микроконтроллера до компьютера, и в итоге остановился на таком варианте:

на компьютер безпрерывно подается символ «0», затем, когда сделан шаг на тренажере, подается «1». Следующий шаг — снова «0» и так по кругу.

Привожу 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); //индикатор
}





Игра




Что еще писать на pygame если не игру?
Идея



Эллиптический тренажер это имитация ходьбы на лыжах, поэтому это будет гонка лыжников. Каждый шаг, сделанный на тренажере делает персонаж в игре. Сперва хотелось сделать плавное передвижение\ускорение персонажа, но в итоге, решил отдать предпочтение точности.
Расчеты



Опытным путем было выяснено, что при «оптимальных» обстоятельствах один полный оборот равняется 4-м метрам. Это скорей всего не сколько проходит человек, а сколько прокручивается центральный диск. Просто примем это значение за аксиому.

На виртуальной трассе 1 метр равняется 1 пикселю. Т.е. каждый шаг перемещаем перснажа на 4 пикселя вперед.

Скорость будет высчитывать каждый шаг.

v = s / t

s = 4 м.

t — время одного шага.

*один шаг — полный оборот педалей.
Азарт



Да, будут графики и спидометр с таймером, но хочется духа соревнования.

А что, если соревноваться будешь не с кем-то а с самим собой, вчерашним? Сказано — сделано.



Сверху персонаж сегодняшний, снизу — вчерашний. Если быть более точным — персонаж прошлого забега но согласитесь, первый вариант звучит круче.

Технические детали


База данных



Естественно, раз нужно сохранять информацию о забегах, нужна БД. Я решил использовать mysql. В python использую библиотеку MySQLdb. В приложении за взаимодействие отвечает класс DataManger.

Схема прилагается.



Пример кода


с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





Графика



Как можно увидеть из скриншота выше, графика примитивная, но не няшность это тут главное. Для ее реализации была использована библиотека pygame. О работе с которой я уже писал.
Формы





Для форм использовал библиотеку PyQt.

Пример кода


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()







Мне очень понравился процесс разработки окон. Не сложнее, чем в MS studio.

Формы создал в приложении Qt 4 Creator.

Импортировал их в код

uic.loadUi('%s/ui/frm_profile.ui' % DIR, self)




Связал события и методы

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)




И отобразил

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


Графики





Для графиков используется библиотека matplotlib.

Тут тоже пример кода


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()




Хотелось бы заметить, что для отображения кириллицы, нужно подключить поддерживающие шрифты.

from matplotlib import rc
font = {'family': 'Droid Sans',
'weight': 'normal',
'size': 14}
rc('font', **font)





Чтение данных с arduino



Для этой цели использовал библиотеку serial.

Следующий код запускается в отдельном потоке.

def getDataFromSimulator():
global valueFromSimulator, isRunnig
ser = serial.Serial('/dev/ttyACM0', 9600)
while isRunnig:
value = ser.readline()
try:
valueFromSimulator = int(value)
except:
pass




Переменная valueFromSimulator в другом потоке используется только для считывания.

Запуск двух потоков.

t1 = threading.Thread(target=main,args = (self.profileId,self.distanceId))
t2 = threading.Thread(target=getDataFromSimulator)
t2.start()
t1.start()




Видеодемонстрация плохого качества




Как и заказывали.


Буду рад замечаниям, критике и предложениям.

Все исходники тут


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий