在 Processing 中使用距离传感器来控制形状的属性

Using a distance sensor in Processing to control the attributes of shapes

我正在尝试制作一个程序,该程序将使用从距离传感器获得的读数来控制圆的属性(大小、xy 和颜色)。为此,我试图让它记录当前值,并在您按下相关键时将其应用于该值(例如,按 's' 并将大小更改为当时的距离)。 - 理想情况下,当您将手移到传感器上时,我希望圆圈能够动态更改下一个字段,但这似乎超出了我的范围。

我已尽我所能,但我不确定的所有内容都已注释掉。有什么提示或建议吗?当涉及到 类 和构造函数时,我真的不确定我在做什么。

编辑:当我 运行 代码时,没有任何反应。

import processing.serial.*;

int xpos, ypos, s, r, g, b;

Circle circle;
int shapeSize, distance;
String comPortString;
Serial myPort;


void setup(){
 size(displayWidth,displayHeight); //Use entire screen size.

//Open the serial port for communication with the Arduino
myPort = new Serial(this, "/dev/cu.usbmodem1411", 9600);
myPort.bufferUntil('\n'); // Trigger a SerialEvent on new line
}

void draw(){
 background(0);
 delay(50); //Delay used to refresh screen

println(distance);
}


void serialEvent(Serial cPort){
comPortString = (new String(cPort.readBytesUntil('\n')));
 if(comPortString != null) {
 comPortString=trim(comPortString);

 /* Use the distance received by the Arduino to modify the y position
 of the first square (others will follow). Should match the
 code settings on the Arduino. In this case 200 is the maximum
 distance expected. The distance is then mapped to a value
 between 1 and the height of your screen */
 distance = int(map(Integer.parseInt(comPortString),1,200,1,height));
 if(distance<0){
 /*If computer receives a negative number (-1), then the
 sensor is reporting an "out of range" error. Convert all
 of these to a distance of 0. */
 distance = 0;
 }
 }
}



void keyPressed()
{
  // N for new circle (and keep old one)
  if((key == 'N') || (key == 'n')) {
    println("n");
  circle = new Circle(1,1,1,1,1,1);
    }

    //r - change red
      if((key == 'R') || (key == 'r')) {
    float red = map(distance, 0, 700, 0, 255);

    r = int(red);
    println("r " + r);
    }

       //g - change green
      if((key == 'G') || (key == 'g')) {
    float green = map(distance, 0, 700, 0, 255);
   g = int(green);
     println("g " + g);
    }

          //b - change blue
      if((key == 'B') || (key == 'b')) {
    float blue = map(distance, 0, 700, 0, 255);
     b = int(blue);
     println("b " + b);

    }

    //S - change Size
      if((key == 'S') || (key == 's')) {
       s = distance;
        println("s " + s);
    }

    //X - change x  pos
      if((key == 'X') || (key == 'x')) {
     xpos = distance;
         println("x " + xpos);
    }

    //y - change y pos
      if((key == 'Y') || (key == 'y')) {
            ypos = distance;
      println("y " + ypos);
    }
  } 

  class Circle {

   Circle(int xpos, int ypos, int s, int r, int g, int b){
   ellipse(xpos, ypos, s, s);
   color(r, g, b);
   }
   int getX(){
     return xpos;
   }
      int getY(){
     return ypos;
   }
  } 

我会把它分成 steps/tasks:

  1. 连接到 Arduino
  2. 从 Arduino 读取值
  3. 映射读取值
  4. 控制映射

Arduino 部分差不多就在那里,但是当试图将读取值映射到屏幕上的圆圈时,事情看起来很混乱。 现在,为了简单起见,让我们忽略 classes 并专注于简单地绘制一个具有 x,y,size,r,g,b 属性的椭圆。

要了解抖动,您应该不断更新 属性 椭圆,而不仅仅是在按键时。在关键事件上,您应该简单地更改 属性 更新的内容。 您可以使用额外的变量来跟踪您正在更新的椭圆属性。

下面是基于以上几点的代码重构版本:

import processing.serial.*;

int xpos,ypos,s,r,g,b;
int distance;

int propertyID = 0;//keep track of what property should be updated on distance
int PROP_XPOS = 0;
int PROP_YPOS = 1;
int PROP_S = 2;
int PROP_R = 3;
int PROP_G = 4;
int PROP_B = 5;

void setup(){
  size(400,400);
  //setup some defaults to see something on screen
  xpos = ypos = 200;
  s = 20;
  r = g = b = 127;
  //initialize arduino - search for port based on OSX name
  String[] portNames = Serial.list();
  for(int i = 0 ; i < portNames.length; i++){
    if(portNames[i].contains("usbmodem")){
      try{
        Serial arduino = new Serial(this,portNames[i],9600);
        arduino.bufferUntil('\n');
        return;
      }catch(Exception e){
        showSerialError();
      }
    }
  }
  showSerialError();
}
void showSerialError(){
  System.err.println("Error connecting to Arduino!\nPlease check the USB port");
}
void draw(){
  background(0);
  fill(r,g,b);
  ellipse(xpos,ypos,s,s);
}
void serialEvent(Serial arduino){
  String rawString = arduino.readString();//fetch raw string
  if(rawString != null){
    String trimmedString = rawString.trim();//trim the raw string
    int rawDistance = int(trimmedString);//convert to integer
    distance = (int)map(rawDistance,1,200,1,height);
    updatePropsOnDistance();//continously update circle properties
  }
}
void updatePropsOnDistance(){
  if(propertyID == PROP_XPOS) xpos = distance;
  if(propertyID == PROP_YPOS) ypos = distance;
  if(propertyID == PROP_S) s = distance;
  if(propertyID == PROP_R) r = distance;
  if(propertyID == PROP_G) g = distance;
  if(propertyID == PROP_B) b = distance;
}
void keyReleased(){//only change what proprty changes on key press
  if(key == 'x' || key == 'X') propertyID = PROP_XPOS;
  if(key == 'y' || key == 'Y') propertyID = PROP_YPOS;
  if(key == 's' || key == 'S') propertyID = PROP_S;
  if(key == 'r' || key == 'R') propertyID = PROP_R;
  if(key == 'g' || key == 'G') propertyID = PROP_G;
  if(key == 'b' || key == 'B') propertyID = PROP_B;
}
//usually a good idea to test - in this case use mouseY instead of distance sensor
void mouseDragged(){
  distance = mouseY;
  updatePropsOnDistance();
}

如果这有意义,它可以很容易地封装在一个 class 中。 我们可以使用数组来存储这些属性,但是如果像 props[0] for x, props1 for y, etc. is harder to read, you could use an IntDict 这样的东西允许你根据字符串而不是值来索引值(所以你可以做 props["x"] 而不是 props[0]).

这是代码的封装版本:

import processing.serial.*;

Circle circle = new Circle();

void setup(){
  size(400,400);
  //initialize arduino - search for port based on OSX name
  String[] portNames = Serial.list();
  for(int i = 0 ; i < portNames.length; i++){
    if(portNames[i].contains("usbmodem")){
      try{
        Serial arduino = new Serial(this,portNames[i],9600);
        arduino.bufferUntil('\n');
        return;
      }catch(Exception e){
        showSerialError();
      }
    }
  }
  showSerialError();
}
void showSerialError(){
  System.err.println("Error connecting to Arduino!\nPlease check the USB port");
}
void draw(){
  background(0);
  circle.draw();
}
void serialEvent(Serial arduino){
  String rawString = arduino.readString();
  if(rawString != null){
    String trimmedString = rawString.trim();
    int rawDistance = int(trimmedString);
    int distance = (int)map(rawDistance,1,200,1,height);
    circle.update(distance);
  }
}
void keyReleased(){
  circle.setUpdateProperty(key+"");//update the circle property based on what key gets pressed. the +"" is a quick way to make a String from the char
}
//usually a good idea to test - in this case use mouseY instead of distance sensor
void mouseDragged(){
  circle.update(mouseY);
}

class Circle{
  //an IntDict (integer dictionary) is an associative array where instead of accessing values by an integer index (e.g. array[0]
  //you access them by a String index (e.g. array["name"])
  IntDict properties = new IntDict();

  String updateProperty = "x";//property to update

  Circle(){
    //defaults
    properties.set("x",200);
    properties.set("y",200);
    properties.set("s",20);
    properties.set("r",127);
    properties.set("g",127);
    properties.set("b",127);
  }

  void draw(){
    fill(properties.get("r"),properties.get("g"),properties.get("b"));
    ellipse(properties.get("x"),properties.get("y"),properties.get("s"),properties.get("s"));
  }

  void setUpdateProperty(String prop){
    if(properties.hasKey(prop)) updateProperty = prop;
    else{
      println("circle does not contain property: " + prop+"\navailable properties:");
      println(properties.keyArray());
    } 
  }

  void update(int value){
    properties.set(updateProperty,value);   
  }

}

在这两个示例中,您都可以通过在 Y 轴上拖动鼠标来测试距离值。

关于HC-SR04传感器,可以找code on the Arduino Playground to get the distance in cm. I haven't used the sensor myself yet, but I notice other people has some issues with it, so it's worth checking this post as well. If you want to roll your own Arduino code, no problem, you can use the HC-SR04 datasheet(pdf link)得到公式:

Formula: uS / 58 = centimeters or uS / 148 =inch; or: the range = high level time * velocity (340M/S) / 2; we suggest to use over 60ms measurement cycle, in order to prevent trigger signal to the echo signal.

获得准确的值很重要(在 Processing 中使用这些值绘制时可以避免抖动)。此外,您还可以使用 easing or a moving average

这是一个基本的移动平均线示例:

int historySize = 25;//remember a number of past values
int[] x = new int[historySize];
int[] y = new int[historySize];

void setup(){
  size(400,400);
  background(255);
  noFill();
}
void draw(){
  //draw original trails in red
  stroke(192,0,0,127);
  ellipse(mouseX,mouseY,10,10);

  //compute moving average 
  float avgX = average(x,mouseX);
  float avgY = average(y,mouseY);

  //draw moving average in green
  stroke(0,192,0,127);
  ellipse(avgX,avgY,10,10); 
}
void mouseReleased(){
  background(255);
}
float average(int[] values,int newValue){
  //shift elements by 1, from the last to the 2nd: count backwards
  float total = 0;
  int size = values.length;
  for(int i = size-1; i > 0; i--){//count backwards
    values[i] = values[i-1];//copy previous value into current
    total += values[i];//add values to total
  }
  values[0] = newValue;//add the newest value at the start of the list
  total += values[0];//add the latest value to the total
  return (float)total/size;//return the average
}