включение тренажёра в систему

Подключение исполнительных устройств, датчиков, контроллеров.

Модератор: immortal

Ответить
Аватара пользователя
sergejey
Site Admin
Сообщения: 4284
Зарегистрирован: Пн сен 05, 2011 6:48 pm
Откуда: Минск, Беларусь
Благодарил (а): 75 раз
Поблагодарили: 1559 раз
Контактная информация:

включение тренажёра в систему

Сообщение sergejey » Вт ноя 04, 2014 5:20 pm

Небольшое вступление. У меня в системе в классе состояний системы добавлен объект состояния "Health" (здоровье), который предназначен для того, чтобы следить не за состоянием "железа", а за тем, как себя чувствуют домочадцы.

Выглядит оно вот так:
Изображение

В самой системе это объект класса systemStates:
Изображение

Частично эта тема затрагивалась в топике Медицинское оборудование, но сейчас я хочу рассказать про интеграцию в системе спортивного тренажёра.

Итак, есть эллептический тренажёр вот такого плана:

Изображение

Вещь хорошая, сама по себе, но бывает достаточно сложно себя заставить на нём заниматься :) Я думаю, что 99% владельцев подобных штук сталкивались с тем, что первое время тренажёр используется очень активно, но потом то одно, то второе и занятия становятся в лучшем случае нерегулярными. Собственно, сужу по себе. Так вот, идея была в том, чтобы система могла напоминать о тренировках и вести статистику использования тренажёра.

Для реализации задуманного была сделана вот такая приставка:

Изображение

По сути приставка выполняет несколько функций:
* Счётчик импульсов для отправки в систему с последующим рассчётом скорости (за основу взят этот проект)
* Две кнопки для выбора пользователя, который в данный момент занимается
* Датчик температуры (просто "до кучи")

Питается эта приставка от того же блока питания, что идёт на сам тренажёр (9 Вольт)

Если просто встать на тренажёр и начать заниматься, то данные, получаемые от приставки по радио-каналу (433Mhz, протокол отсюда) будут приходить, но не сохраняться в системе. Алиса при этом скажет что-то вроде "Спорт это здорово, но я не знаю, кто сейчас занимается".

Если с помощью нажатия на кнопку будет выбран текущий пользователь, то в систему будет поступать информация со счётчика импульсов, а дальше преобразовываться в пройденное расстояние и скорость. Так же будет сохранено время последней тренировки, которое используется в вышеописанном статусе здоровья (если прошло более 3х дней с последней тренировки, то статус здоровья становится жёлтым). Данные о "пробеге" сохраняются в объект пользователя и пока просто суммируются, но у меня большие планы на то, как их использовать в будущем, а именно:
* Можно устраивать сореванования, кто больше тренировался за неделю
* Можно просто вести статистику с графиками дневную/недельную/месячную и т.п.
* Можно задействовать какую-то систему бонусов, рекордов, совместных целей и т.п. (тут надо подумать над игровой моделью)
* Можно включать интересную аудио-книгу во время тренировки и выключать по окончанию :)
* ... (предложите варианты)

Вот, собственно, что касается пользовательской части.

Теперь о реализации.

Схема устройства (извините, не умею я их рисовать):
Изображение

Компоненты:
* Контролллер Arduino UNO (любой arduino-совместимый)
* Кнопки (2 шт)
* Геркон с магнитом
* Сопротивление на 10K для кнопок и геркона (3 шт)
* Датчик температуры (Dallas 18B20)
* Сопротивление 4k7 для датчика температуры
* Передатчик на 433Mhz
* Блок питания (использовался от тренажёра)

Скетч прошивки:
КодПоказать

Код: Выделить всё

#include <OneWire.h>
#include <DallasTemperature.h>

#include <VirtualWire.h>
#include <EasyTransferVirtualWire.h>
#include <EEPROM.h> //Needed to access the eeprom read write functions

#define PIN_LED 13
#define PIN_LED_T 13

#define PIN_RF 2
#define PIN_TEMP 3  

#define PIN_BUTTON1 4
#define PIN_BUTTON2 5
#define PIN_KEY 6 

#define PERIOD_SEND_POWER 600 // seconds
#define PERIOD_MAX_COUNTER 5 // circles

#define PERIOD_READ_TEMP 120 // seconds
#define PERIOD_SEND_TEMP 10 // minutes
#define TEMP_ACC 0.3 // temperature accuracy


#define COMMAND_TEMP 10 // command code for temperature
#define COMMAND_INPUT_ON 20 // command code for button
#define COMMAND_COUNTER 15 // command code for counter

#define RF_SEND_COUNTER 3 // number of packets to send with transmission
 

unsigned int unique_device_id = 0;


int old_status=0;

long int timeSentPower=0;
long int timeCheckPowerMillis=0;
long int counterPower=0;
long int timePassedPower=0;

long int timeCheckedTemp=10000000;
long int timesentTemp=0;
float sent_temperature=0;

long int uptime = 0;
long int old_uptime = 0;

byte old_button1=0;
byte old_button2=0;

OneWire oneWire(PIN_TEMP);
DallasTemperature sensors(&oneWire);


//create object
EasyTransferVirtualWire ET; 

struct SEND_DATA_STRUCTURE{
  //put your variable definitions here for the data you want to send
  //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO
  //Struct can'e be bigger then 26 bytes for VirtualWire version
  unsigned int device_id;
  unsigned int destination_id;    
  unsigned int packet_id;
  byte command;
  int data;
};

//give a name to the group of data
SEND_DATA_STRUCTURE mydata;

//This function will write a 2 byte integer to the eeprom at the specified address and address + 1
void EEPROMWriteInt(int p_address, unsigned int p_value)
{
  byte lowByte = ((p_value >> 0) & 0xFF);
  byte highByte = ((p_value >> 8) & 0xFF);

  EEPROM.write(p_address, lowByte);
  EEPROM.write(p_address + 1, highByte);
}

//This function will read a 2 byte integer from the eeprom at the specified address and address + 1
unsigned int EEPROMReadInt(int p_address)
{
  byte lowByte = EEPROM.read(p_address);
  byte highByte = EEPROM.read(p_address + 1);

  return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
}

void blinking(int count) {
 for(int i=0;i<count;i++) {
  digitalWrite(PIN_LED, HIGH); 
  delay(200);
  digitalWrite(PIN_LED, LOW);
  delay(200);
 }
}

void sendRFData() {
 Serial.print("Transmitting packets ... ");   
 for(int i=0;i<RF_SEND_COUNTER;i++) {
  if (i>0) {
   delay(200);    
  }
  digitalWrite(PIN_LED_T, HIGH); 
  ET.sendData();
  digitalWrite(PIN_LED_T, LOW);
 }  
 Serial.println("DONE");   
}

void setup()
{
  randomSeed(analogRead(0));
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_LED_T, OUTPUT); 
  pinMode(PIN_KEY, INPUT);   
  pinMode(PIN_BUTTON1, INPUT);     
  pinMode(PIN_BUTTON2, INPUT);       
  Serial.begin(9600); 

  ET.begin(details(mydata));
  vw_set_tx_pin(PIN_RF);
  vw_setup(2000);        // Bits per sec
  randomSeed(analogRead(0));


  // Device ID
  Serial.print("Getting Device ID... "); 
  unique_device_id=EEPROMReadInt(0);
  if (unique_device_id<10000 || unique_device_id>60000 || unique_device_id==26807) {
    Serial.print("N/A, updating... "); 
    unique_device_id=random(10000, 60000);
    EEPROMWriteInt(0, unique_device_id);
  }
  Serial.println(unique_device_id);   
  mydata.device_id = unique_device_id;
  mydata.destination_id = 0;  
  
}

void loop()
{

  uptime=round(millis()/1000);
  if (uptime!=old_uptime) {
    Serial.print("Uptime: ");
    Serial.println(uptime);
    old_uptime=uptime;
    timeCheckedTemp++;
    timesentTemp++;
  }
  
  int current_button1=digitalRead(PIN_BUTTON1);
  if (current_button1!=old_button1 && current_button1==1) {
     Serial.println("Button 1 pressed");  
    mydata.packet_id = random(65535);
    mydata.command = COMMAND_INPUT_ON;
    mydata.data = 1;
    sendRFData();
    
  }
  old_button1=current_button1;

  int current_button2=digitalRead(PIN_BUTTON2);
  if (current_button2!=old_button2 && current_button2==1) {
    Serial.println("Button 2 pressed");      
    mydata.packet_id = random(65535);
    mydata.command = COMMAND_INPUT_ON;
    mydata.data = 2;
    sendRFData();   
  }
  old_button2=current_button2;  

  if (timeCheckedTemp>PERIOD_READ_TEMP) {
  // TEMP SENSOR 1
  float current_temp=0;
  sensors.requestTemperatures();
  current_temp=sensors.getTempCByIndex(0);
  if (current_temp == 0.00 || current_temp>50) {
    Serial.print("Temp sensor error: ");
    Serial.println(current_temp);
    blinking(3);
    sensors.begin();
  } else {
   timeCheckedTemp=0;
   Serial.print("T: "); 
   Serial.println(current_temp);
   float diff=(float)sent_temperature-(float)current_temp;
   if ((current_temp<50) && (current_temp>-5) && ((abs(diff)>=TEMP_ACC) || (timesentTemp>(PERIOD_SEND_TEMP*60)))) {
    Serial.print("Temperature: ");
    Serial.println(current_temp);

    mydata.packet_id = random(65535);
    mydata.command = COMMAND_TEMP; //temp
    mydata.data = (int)(current_temp*100);
    sendRFData();
    
    timesentTemp=0;   
    sent_temperature=current_temp;
   }
  }
 }
  
  timePassedPower=uptime-timeSentPower; // time passed since the last power data has been sent  
  int current_ir=digitalRead(PIN_KEY);

  if (current_ir!=old_status)  {       
    if ((current_ir==0) && (millis()-timeCheckPowerMillis>0.5*1000)) {
     timeCheckPowerMillis=millis();
     counterPower++;
     Serial.print(" Power counter: ");
     Serial.print(counterPower);
     Serial.print(" time passed: ");
     Serial.println(timePassedPower);
     if ((counterPower>=PERIOD_MAX_COUNTER) || (timePassedPower>=PERIOD_SEND_POWER)) {
      timeSentPower=uptime;
      mydata.packet_id = random(65535);
      mydata.command = COMMAND_COUNTER; //power counter
      Serial.print("Sending value: ");
      mydata.data = (int)counterPower*1000+(int)timePassedPower;
      Serial.println(mydata.data);
      sendRFData();
      counterPower=0;
     }
    }
    old_status=(int)current_ir;
  } else if (timePassedPower>60 && counterPower>0) {
    counterPower=0;
    timeSentPower=uptime;
    mydata.packet_id = random(65535);
    mydata.command = COMMAND_COUNTER; //power counter
    Serial.print("Sending value on idle: ");
    mydata.data = (int)counterPower*1000+(int)timePassedPower;
    Serial.println(mydata.data);
    sendRFData();    
  } 
  
}

 
Для обработки входящих данных используется вот такой код в сцерании EasyRF:

Код: Выделить всё

} elseif ($device_id=='55889') { 
 runScript('trainingUnitEvent',array('command_id'=>$command_id,'data'=>$data)); 
т.е. это кусок сценария, который обрабатывает все пакеты данных, передающихся по радио и данный отрывок вызывает отдельный сценарий для обработки данных от тренажёра.

Код сценария приёма данных от тренажёра (trainingUnitEvent):
сцераний trainingUnitEventПоказать

Код: Выделить всё

$data=$params['data'];
$command_id=$params['command_id'];
if ($command_id=='20' && $data=='1') {
 say("Сергей, добро пожаловать на тренировку!",2);
 setGlobal('ThisComputer.NowTraining','Serge');
 setTimeOut('TrainingObjectTimeout',"setGlobal('ThisComputer.NowTraining','');",1*60*60); //1h
 setGlobal('ThisComputer.TrainingWarning',0); 
}
if ($command_id=='20' && $data=='2') {
 say("Алеся, добро пожаловать на тренировку!",2);
 setGlobal('ThisComputer.NowTraining','Alesya');
 setTimeOut('TrainingObjectTimeout',"setGlobal('ThisComputer.NowTraining','');",1*60*60); //1h 
 setGlobal('ThisComputer.TrainingWarning',0); 
}
if ($command_id=='15') {
 $nowTraining=getGlobal('ThisComputer.NowTraining');
 if (!$nowTraining) {
  if (!getGlobal('ThisComputer.TrainingWarning')) {
   say("Спорт это здорово, но я не знаю, кто сейчас занимается на тренажёре",2);
   setGlobal('ThisComputer.TrainingWarning',1);
   setTimeOut('TrainingWarningTimeout',"setGlobal('ThisComputer.TrainingWarning',0);",10*60); //10 minutes
  }
 } else {

/* 
290 cycles
2 000 meters
1 cycle =  2 000 / 290 = 6.897 meters
*/
 
  $counter=floor($data/1000);  // cycles counter
  $passed=$data-$counter*1000; // time passed (seconds)
  $distance=round($counter*6.897,3);
  
  setGlobal($nowTraining.'.trainingCounterNow',(int)getGlobal($nowTraining.'.trainingCounterNow')+$counter);  
  setGlobal($nowTraining.'.trainingDistanceToday',(float)getGlobal($nowTraining.'.trainingDistanceToday')+$distance);  
  setGlobal($nowTraining.'.trainingDistanceWeek',(float)getGlobal($nowTraining.'.trainingDistanceWeek')+$distance);    
  
  if ($passed>0) {
   $speed=round($distance/$passed,2); // m / s
   setGlobal($nowTraining.'.trainingSpeed',$speed);
   setTimeOut($nowTraining.'gotTrainingTimeOut',"callMethod($nowTraining.'.gotTraining');",5*60);
  }
 }
 //callMethod("ElectroCounter.onUpdate",array("counter"=>$counter,"passed"=>$passed));
} 
Далее в интерфейсе для графиков или в других сценариях можно использовать данные ThisComputer.NowTraining (кто в данный момент тренируется), Serge.trainingDistanceToday (пройдено сегодня), Serge.trainingDistanceWeek (пройдено за неделю), Serge.trainingSpeed (текущая скорость), так же аналогично для второго пользователя (объект Alesya).

Собственно, всё :)
За это сообщение автора sergejey поблагодарили (всего 2):
Vit (Вт ноя 04, 2014 6:06 pm) • ErmolenkoM (Чт ноя 06, 2014 9:11 pm)
Рейтинг: 2.33%

Сергей Джейгало, разработчик MajorDoMo
Идеи, ошибки -- за предложениями по исправлению и развитию слежу только здесь!
Профиль Connect -- информация, сотрудничество, услуги
Ivan
Сообщения: 1473
Зарегистрирован: Сб окт 12, 2013 11:03 pm
Благодарил (а): 49 раз
Поблагодарили: 327 раз

Re: включение тренажёра в систему

Сообщение Ivan » Вт ноя 04, 2014 6:24 pm

Класно. Туда бы нашего тренера Вову. Точно никтобы не пропустил :)
Linux, Raspberry PI, MySensors
Connect: http://connect.smartliving.ru/profile/53
Мои проекты: http://smartliving.ru/profile/4
onixtacie
Сообщения: 3
Зарегистрирован: Пн май 20, 2013 12:19 pm
Благодарил (а): 0
Поблагодарили: 0

Re: включение тренажёра в систему

Сообщение onixtacie » Чт ноя 06, 2014 1:48 pm

Из вариантов, как можно использовать показания тренажёра: отсылать их в Endomondo или аналогичную программу, где уже есть статистика, достижения, челленджи и всё такое.

А ещё подумалось, что для показателя здоровье можно привязать ежегодные (ну или раз в полгода) осмотры у врача или зубного, на которые тоже все забивают :) А если забил на напоминалку и не записался ко врачу, то тоже датчик здоровья желтеет.
Аватара пользователя
sergejey
Site Admin
Сообщения: 4284
Зарегистрирован: Пн сен 05, 2011 6:48 pm
Откуда: Минск, Беларусь
Благодарил (а): 75 раз
Поблагодарили: 1559 раз
Контактная информация:

Re: включение тренажёра в систему

Сообщение sergejey » Чт ноя 06, 2014 2:08 pm

onixtacie писал(а):Из вариантов, как можно использовать показания тренажёра: отсылать их в Endomondo или аналогичную программу, где уже есть статистика, достижения, челленджи и всё такое.
Да, я думаю такой вполне возможно. По-моему, у них есть API и можно им "скарливать" рекорды.
onixtacie писал(а):А ещё подумалось, что для показателя здоровье можно привязать ежегодные (ну или раз в полгода) осмотры у врача или зубного, на которые тоже все забивают :) А если забил на напоминалку и не записался ко врачу, то тоже датчик здоровья желтеет.
Хорошая идея :) Правда не уверен, что жёлтый датчик здоровья будет достаточно сильным стимулом, чтобы пойти к зубному )) Но в целом направление мысли правильное -- можно завести датчики по разным аспектам, например финансы с проверкой остатка по счёту или даты последнего ввода расходов (для тех, кто их ведёт).

Сергей Джейгало, разработчик MajorDoMo
Идеи, ошибки -- за предложениями по исправлению и развитию слежу только здесь!
Профиль Connect -- информация, сотрудничество, услуги
Ответить