SpyLOG

Создаем скетч Processing для наблюдения за пульсом

Получаем еще более стабильный источник пульса: отключаем плату от USB и добавляем имитатор в скетч Processing. Вводим константу управления

final boolean SIM=true;

модернизируем setup()

if (SIM) {
  thread("imitator");
} else {
  println(Serial.list());
  Serial port=new Serial(this, "COM3", 9600);
  port.bufferUntil('\n');
}

и добавляем код самого имитатора

void imitator() {
  while (true) {
    try {
      Thread.sleep((int)random(500, 1000));
    }
    catch (Exception e) {
    }
    addKnock(millis());
  }
}

Мы опять бережем draw() от пустых задержек и код имитатора получается чуть объемнее своего коллеги на плате, но результат одинаков - стабильный пульс 60 ударов в минуту.

Реализуем аскетично-практичную визуализацию пульса. Выводим текущее значение и примитивно изображаем равномерно ползущую влево ленту регистрации: фиксируем количество точек экрана на секунду

final int pxPerSec=50;

в setup() указываем размер графической области скетча и пересчитываем длительность окна хранения отсчетов пульса так, что бы хранить столько отсчетов, сколько их можно нарисовать

size(600, 250);

timeWindow=1000*width/pxPerSec;

и, наконец-то, рисуем

void draw() {
  background(0, 0, 0);
  int currTime=millis();

  stroke(255);  
  strokeWeight(3);
  synchronized(knocks) {
    for (int m : knocks) {
      int x=width-pxPerSec*(currTime-m)/1000;
      line(x, 50, x, 110);
    }
    fill(0, 255, 0);
    textSize(20);
    text(rate(), 10, 20);
  }
}

синхронизировав и addKnock()

void addKnock(int m){
  synchronized(knocks){
   knocks.add(m);
   while(m-knocks.peek()>timeWindow) knocks.remove();
  } 
}

Недостаточно аскетично?! Добавим суровых зеленых вертикальных полос секундных отсчетов и чуть менее зеленых, но все же спартанских отметок 1/5 секунды перед выводом отметок пульса

strokeWeight(1);
for(int t=(currTime/1000)*1000-timeWindow;t<=currTime;t+=1000){
  int x=width-pxPerSec*(currTime-t)/1000;
  stroke(0,100,0);
  for(int x1=x;x1<x+pxPerSec;x1+=pxPerSec/5){
      line(x1, 40, x1, 120);
   }

   stroke(0,255,0);
   line(x, 30, x, 130);
}

Последним штрихом дорисуем посекундный график пульса: устанавливаем количество пикселей на отсчет и интервал

final int pxPerRate=2;
final int rateStoreInterval=1000;

сколько секунд требуется хранить

int maxRates=1;

вычисляем в setup()

maxRates=width/pxPerRate;

Метод и очередь для накапливания отсчетов:

ArrayDeque<Integer> rates=new ArrayDeque<Integer>();
int rateLast=0;

int addRate(int r) {
  int ms=millis();
  if (ms-rateLast>rateStoreInterval) {
    rates.add(r);
    while (rates.size ()>maxRates) rates.remove();
    rateLast=ms;
  }
  return r;
}

Для вызова addRate заменяем в draw()

text(rate(), 10, 20);

на

text(addRate(rate()), 10, 20);

и добавляем код рисования отсчетов. Для экономии места вычитаем 40 ударов в минуту.

int x=width-pxPerRate;
strokeWeight(pxPerRate);
for (Iterator i = rates.descendingIterator (); i.hasNext(); x-=pxPerRate+1) {
  line(x, height-5-(Integer)i.next()+rateShift, x, height-5);
}
stroke(0, 255, 0, 100);
lineRate(60);
lineRate(90);
lineRate(120);

где

void lineRate(int r){
  line(0, height-5-r+rateShift, width, height-5-r+rateShift);
}

и

final int rateShift=40;

Слишком ровно, непорядочек. Для наглядности внесем разнообразие в имитатор - заменим

Thread.sleep(1000);

на

Thread.sleep((int)random(500,1000));

Полный текст:

import processing.serial.*;
import java.util.*;

final boolean SIM=true;
final int pxPerSec=50;
final int pxPerRate=2;
final int rateStoreInterval=1000;
final int rateShift=40;

ArrayDeque<Integer> knocks=new ArrayDeque<Integer>();
ArrayDeque<Integer> rates=new ArrayDeque<Integer>();

int timeWindow=1000*10;
int maxRates=1;
int rateLast=0;

void setup() {
  size(600, 250);
  if (SIM) {
    thread("imitator");
  } else {
    println(Serial.list());
    Serial port=new Serial(this, "COM3", 9600);
    port.bufferUntil('\n');
  }
  timeWindow=1000*width/pxPerSec;
  maxRates=width/pxPerRate;
}

void draw() {
  background(0, 0, 0);
  int currTime=millis();

  strokeWeight(1);
  for (int t= (currTime/1000)*1000-timeWindow; t<=currTime; t+=1000) {
    int x=width-pxPerSec*(currTime-t)/1000;
    stroke(0, 100, 0);
    for (int x1=x; x1<x+pxPerSec; x1+=pxPerSec/5) {
      line(x1, 40, x1, 120);
    }

    stroke(0, 255, 0);
    line(x, 30, x, 130);
  }

  stroke(255);  
  strokeWeight(3);
  synchronized(knocks) {
    for (int m : knocks) {
      int x=width-pxPerSec*(currTime-m)/1000;
      line(x, 50, x, 110);
    }
    fill(0, 255, 0);
    textSize(20);
    text(addRate(rate()), 10, 20);
  }

  int x=width-pxPerRate;
  strokeWeight(pxPerRate);
  for (Iterator i = rates.descendingIterator (); i.hasNext(); x-=pxPerRate+1) {
    line(x, height-5-(Integer)i.next()+rateShift, x, height-5);
  }
  stroke(0, 255, 0, 100);
  lineRate(60);
  lineRate(90);
  lineRate(120);
}

void lineRate(int r) {
  line(0, height-5-r+rateShift, width, height-5-r+rateShift);
}

void serialEvent(Serial p) { 
  p.readString();
  addKnock(millis());
} 

void addKnock(int m) {
  synchronized(knocks) {
    knocks.add(m);
    while (m-knocks.peek ()>timeWindow) knocks.remove();
  }
}

int rate() {
  if (knocks.size()>1) {
    return round(60f*1000*(knocks.size()-1)/(knocks.peekLast()-knocks.peekFirst()));
  }
  return 0;
}

void imitator() {
  while (true) {
    try {
      Thread.sleep((int)random(500, 1000));
    }
    catch (Exception e) {
    }
    addKnock(millis());
  }
}

int addRate(int r) {
  int ms=millis();
  if (ms-rateLast>rateStoreInterval) {
    rates.add(r);
    while (rates.size ()>maxRates) rates.remove();
    rateLast=ms;
  }
  return r;
} 

Запускаем

Теперь можно в Arduino IDE перезалить в плату скетч, отключив в нем эмулятор, поменять значение SIM на false в Processing, отключить ноутбук от сети, надеть на ухо клипсу и получить визуализацию реального пульса.

Но мы этого делать не станем, а перейдем к разработке БОС-игры.

Tech@Psycheya.ru

Полное или частичное использование текста, изображений и других материалов данной публикации запрещено.