Commit 7257fe19 authored by Evan Raskob's avatar Evan Raskob

added fleshed out serial call and response API for analogue ports with bug fixes

parent e00a8c10
/*
An example showing how to define a simple call and response serial API
between Arduino and Processing.
*/
void setup() {
Serial.begin(57600);
while (!Serial);
}
const unsigned int bufferSize = 256; // number of bytes we can read
char buffer[bufferSize];
int charsRead = 0; // amount of bytes we've read
void loop() {
// we need this while loop to fully empty the serial buffer
while (Serial.available())
{
char charRead = (char)Serial.read();
// be careful not to go over the buffer size (with room
// for string terminator character)!
// note that this doesn't stop the loop - we ignore
// extra bytes
if (charsRead < bufferSize )
{
if (charRead != '\n') {
buffer[charsRead] = charRead;
charsRead = charsRead + 1;
// for testing:
// Serial.print(charsRead);
}
else
{
//Serial.println("process command[");
//Serial.print(charsRead);
//Serial.print("]\n");
buffer[charsRead] = '\0'; // terminate string
// process results
//Serial.print(':');
//Serial.print(buffer);
//Serial.println(':');
// now handle bytes read
// NXXX means note
// sscanf reads the values and stores them in variables we just declared. %d represents a number of digits to read
// the "n" returned by this function is the number of matches. Whitespace is ignored, by default.
// our protocol - 1 character (means an 'action' we perform)
// followed by up to 3 int numbers separated by ,'s
// we do NOT expect to get negative numbers
// we do not expect a cmd character of '-'
const int INVALID = -1;
char cmd = '-'; // the command character: don't expect to read
int a = INVALID; // number 1
int b = INVALID; // number 2
int c = INVALID; // number 3
// define a protocol: call and response for analogue values
// cmd character 'a' means read an analogue port,
// then we expect a port number for variable a
// for example, "a0" means read and return port A0
// that also means "a0,1,2" because we ignore everything after the 1st argument
int n = sscanf(buffer, "/%c,%d,%d,%d", &cmd, &a, &b, &c);
const int ANALOGUE_PORTS = 6;
const int portMappings[] = {
A0, A1, A2, A3, A4, A5
};
int port = INVALID;
if (cmd == 'a')
{
if (a != INVALID && a < ANALOGUE_PORTS && a >= 0) {
port = portMappings[a];
int sensorValue = analogRead(port);
// echo back command in same format
Serial.print('/');
Serial.print(cmd);
Serial.print(",");
Serial.print(a);
Serial.print(",");
Serial.println(sensorValue); // send value, end command
}
else {
Serial.println("ERROR MESSAGE HERE");
}
}
charsRead = 0; // go back to beginning of buffer for next command
}
}
// otherwise too many bytes!
else {
//Serial.print("looped");
// loop back around, so we drain all serial data waiting
charsRead = 0;
}
}
}
/**
* VisualiseWekinatorOSC by Evan Raskob
* For visualising OSC messages from Arduino to Wekinator
*
* TODO: pause button
*/
import oscP5.*;
import netP5.*;
import controlP5.*;
import java.util.List;
import java.util.Collections;
/**
* this is the list of commands that we've received via serial.
* will be handled in draw() call at the current framerate.
*/
List<OscMessage> oscMessagesReceived = Collections.synchronizedList(new ArrayList<OscMessage>());
int inPort = 6969;//6449; // udp input, if needed (not used right now)
int outPort = 6161; //6448; // wekinator port or unity3d osc port, depending
String ipAddress = "127.0.0.1"; // our computer
NetAddress myRemoteLocation;
OscP5 oscP5;
ControlP5 gui;
void setup()
{
size(800, 600);
frameRate(10);
// start oscP5, listening for incoming messages at wekinator port
oscP5 = new OscP5(this, inPort);
myRemoteLocation = new NetAddress(ipAddress, outPort);
gui = new ControlP5(this);
}
void draw()
{
background(0);
for (int i=0; i<6; i++) {
// get analog value - send this to trigger Arduino to send back analogue read of A0
OscMessage oscMsg = new OscMessage("/a");
oscMsg.add(i); // port A0 to A5
oscP5.send(oscMsg, myRemoteLocation);
}
//this next line is because things might happen out of order
synchronized(oscMessagesReceived) {
// handle incoming messages from oscEvent()
for (int i=0; i < oscMessagesReceived.size(); i++)
{
OscMessage msg = oscMessagesReceived.remove(0);
String command = msg.addrPattern();
//print(command + ": ");
String types = msg.typetag();
//println(types);
// draw all analogue inputs as rainbow rectangles
if (command.equals("/a") && types.equals("ii")) {
// draw a corresponding rectangle
float x = width/6.0 * msg.get(0).intValue();
float y = map(msg.get(1).intValue(), 0, 1023, 0, height-10);
colorMode(HSB, 360);
fill(msg.get(0).intValue()*60, 320, 300);
rect(x, 0, width/6.0, 10);
rect(x, 0, width/6.0, y);
}
}
}
}
//
// this triggers whenever OSC messages are received and adds to the list of received messages
// you can ignore it and modify draw() above
//
void oscEvent(OscMessage theOscMessage) {
// add to synchronised list
synchronized(oscMessagesReceived)
{
oscMessagesReceived.add(theOscMessage);
}
}
......@@ -84,11 +84,11 @@ void setupGUI() {
baudddl.getCaptionLabel().getStyle().marginLeft = 3;
baudddl.getValueLabel().getStyle().marginTop = 3;
//the baud options
for (int i=0; i<serialRateStrings.length; i++) {
for (int i=serialRateStrings.length-1; i>=0; i--) {
String baudString = serialRateStrings[i];
baudddl.addItem(baudString, i);
}
baudddl.setValue(serialRateStrings.length - 1);
baudddl.setValue(0);
//udp IP/port
ipAddressField = cp5.addTextfield("IP address")
......
int inPort = 6449; // udp input, if needed (not used right now)
int outPort = 6448; // wekinator port
int inPort = 6161;//6449; // udp input, if needed (not used right now) //<>//
int outPort = 6969; //6448; // wekinator port or unity3d osc port, depending
String ipAddress = "127.0.0.1"; // our computer
OscP5 oscP5;
......@@ -12,40 +12,119 @@ void setupOSC() {
ipAddress = ipAddressField.getText();
outPort = Integer.parseInt(outgoingPortField.getText());
myRemoteLocation = new NetAddress(ipAddress, outPort);
}
void stopOSC()
{
oscP5.stop();
oscP5.stop();
}
/**
* Look at the list of commands received and relay them
* via OSC properly.
*/
void handleSerialCommands() {
synchronized(commandsReceived) {
// handle all commands in buffer
while (commandsReceived.size() > 0)
{
String command[] = commandsReceived.remove(0); // remove first command
String cmd = command[0];
// There are a few ways to do this - you could look at the command and then
// create a cutsom OSC message based on it, for example:
/*
// handle analogue read
if (cmd.equals("/a")) {
float position = float(command[1]); // should be a number between 0 an 1023
position = map(position, 0, 1023, -4, 4);
// consume an "a" (analogue read) command
OscMessage myMessageX = new OscMessage("/CubeX");
myMessageX.add(position);
// send the message
oscP5.send(myMessageX, myRemoteLocation);
}
*/
// OR, you could just route it to OSC without touching it:
OscMessage oscMsg = new OscMessage(cmd);
print(cmd + " ");
for (int i=1; i<command.length; i++)
{
int arg = int(command[i]); // parse as number
// check it's valid!
if (arg != INVALID)
{
print(arg + " ");
oscMsg.add(arg); // add as integer
}
}
println();
// send the message
oscP5.send(oscMsg, myRemoteLocation);
}
}
// done with all received commands
}
/**
* Handle incoming osc messages.
* Handles incoming osc messages. Just echoes them to the serial port.
*/
void oscEvent(OscMessage theOscMessage) {
drawIncomingOSC();
/* print the address pattern and the typetag of the received OscMessage */
print("### received an osc message.");
print(" addrpattern: "+theOscMessage.addrPattern());
print(" typetag: "+theOscMessage.typetag());
print(" timetag: "+theOscMessage.timetag());
if (theOscMessage.checkAddrPattern("/tc")==true) {
/* check if the typetag is the right one. */
print(" TOTAL: " + theOscMessage);
if (theOscMessage.checkTypetag("sfiis")) {
/* parse theOscMessage and extract the values from the osc message arguments. */
String textValue = theOscMessage.get(0).stringValue();
float sizeValue = theOscMessage.get(1).floatValue();
int thirdValue = theOscMessage.get(2).intValue();
print("### received an osc message /test with typetag sfiis.");
println(" values: "+textValue+", "+sizeValue+", "+thirdValue);
return;
}
drawIncomingOSC(); // give some visual feedback that something happened
/// print the address pattern and the typetag of the received OscMessage
//print("### received an osc message.");
//print(" addrpattern: "+theOscMessage.addrPattern());
//print(" typetag: "+theOscMessage.typetag());
//print(" timetag: "+theOscMessage.timetag());
String command = theOscMessage.addrPattern(); // hopefully something 1 character, like "/c"
String types = theOscMessage.typetag(); // this lets us know what data we received -- like "sfff"
// we'll put all arguments into an array of text (strings)
String[] args = new String[types.length()];
// go through all arguments for this message and appen to serial command.
for (int i=0; i < types.length(); i++)
{
char type = types.charAt(i);
switch(type)
{
case 'f':
{
args[i] = ""+theOscMessage.get(i).floatValue();
}
break;
case 's':
{
args[i] = ""+theOscMessage.get(i).stringValue();
}
break;
case 'i':
{
args[i] = ""+theOscMessage.get(i).intValue();
}
break;
default:
println("ERR: bad OSC type:" + type);
} // end switch
// finally, send the serial. Should be the command character followed by list of args separated by ,'s
String cmdOut = command + "," + String.join(",", args);
//println(cmdOut);
serial.write(cmdOut + stopChar);
}
}
......@@ -59,10 +59,13 @@ public void TEST_ON()
// Whatever is in here gets run when the TEST_ON button is pressed.
// the following works with the SerialSendReceiveTest sketch for Arduino to turn the LEDs on and off
serial.write("ledon" + stopChar);
//serial.write("ledon" + stopChar);
// this one works with the SerialParseTest Arduino sketch:
// serial.write("n 1,0,0" + stopChar);
// AnalogSerialAPI trigger read of analogue port A0
serial.write("a" + 0 + stopChar);
}
println("finished");
}
......@@ -97,14 +100,21 @@ public void ERROR()
void setup() {
// configure the screen size and frame rate
size(550, 250, P3D);
frameRate(30);
setupGUI();
frameRate(30); // sometimes this works better here!
}
void draw() {
background(128);
// first, only do something if we're connected to serial and running (START pushed)
if (applicationRunning) {
drawIncomingPackets();
drawIncomingPackets(); // update GUI with data received/sent
handleSerialCommands(); //<>//
}
}
......
......@@ -3,6 +3,11 @@
SERIAL
************************************************************************************/
import java.util.List;
import java.util.LinkedList; // for serial commands list
import java.util.Collections;
//the Serial communcation to the Arduino
Serial serial;
......@@ -21,26 +26,86 @@ int serialListNumber = 2;
// we will store the incoming serial values in here
ArrayList<Byte> serialBuffer = new ArrayList<Byte>();
/**
* this is the list of commands that we've received via serial.
* will be handled in handleSerialCommands() (OSC_Functions tab)
* which is run every draw() call at the current framerate.
*/
List<String[]> commandsReceived = Collections.synchronizedList(new LinkedList<String[]>());
final int INVALID = -1; // a bad command characer
// FYI: (from https://github.com/processing/processing/blob/master/java/libraries/serial/src/processing/serial/Serial.java)
// serialEvent() is invoked in the context of the current (serial) thread
// which means that serialization and atomic variables need to be used to
// guarantee reliable operation (and better not draw() etc..)
// serialAvailable() does not provide any real benefits over using
// available() and read() inside draw - but this function has no
// thread-safety issues since it's being invoked during pre in the context
// of the Processing applet
/**
* This handles the receiving of serial.
* @param {Serial} serial The serial port object
*/
void serialEvent(Serial serial) {
String input = serial.readString();
println("serial received: " + input);
String[] msgs = split(input, ',');
// "c,2,3,4" --> ["c", "2", "3", "4"]
// TODO: handle message here!
//void serialEvent(Serial serial) {
//}
/**
* This handles the receiving of serial and runs before draw() ( in pre() )
* whenever serial data is available. This copies all the serial port data
* into a list for later reading inside draw(). The advanage over using serialEvent()
* for processing data is that this way is thread safe.
*
* @param {Serial} serial The java serial port object
*/
void serialAvailable(Serial serial) {
// read input until we hit stop character (newline) and remove all leading and trailing special characters
// unfortunately, due to threading issues, this crashes the serial port when called
// too quickly!
//String input = trim( serial.readStringUntil(stopChar) );
String serialIn = serial.readString();
println("serial received: " + serialIn);
String serialLines[] = split(serialIn, stopChar); // we might have received a few lines of serial so we
// split them by the stop character (newline)
// for each line, parse it for commands
for (String line : serialLines) {
String input = trim( line ); // trim away space characters and extra newlines etc.
// ex: splits a serial line of "a 324\n" to ["a", "324"]
String[] msgs = split(input, ",");
// check that we've got valid data
if (msgs.length > 0 && msgs[0] != "-") {
synchronized(commandsReceived)
{
commandsReceived.add(msgs);
}
}
}
// another way of doing this:
//ex: "c,2,3,4" --> ["c", "2", "3", "4"]
//String[] msgs = split(trim(input), ',');
}
/**
* Start the serial port reading based on the GUI
*/
void setupSerial() {
// clear list of received serial
synchronized(commandsReceived)
{
commandsReceived.clear();
}
if (baud < 1)
{
// choose highest rate if none was selected
......@@ -54,6 +119,14 @@ void setupSerial() {
}
}
/**
* Stop the serial port reading
*/
void stopSerial() {
serial.stop();
// clear list of received serial
synchronized(commandsReceived)
{
commandsReceived.clear();
}
}
/**
* oscP5sendreceive by andreas schlegel
* example shows how to send and receive osc messages.
* oscP5 website at http://www.sojamo.de/oscP5
*/
import oscP5.*;
import netP5.*;
OscP5 oscP5;
NetAddress myRemoteLocation;
float xPosition = 0; // received x position of cube from Unity
float zPosition = 0;
float yPosition = 0;
void setup() {
size(400, 400, P3D);
frameRate(40); // change this if unity gets overwhelmed
/* start oscP5, listening for incoming messages at port 6161 */
oscP5 = new OscP5(this, 6161); // Unity out port from Unity object
/* myRemoteLocation is a NetAddress. a NetAddress takes 2 parameters,
* an ip address and a port number. myRemoteLocation is used as parameter in
* oscP5.send() when sending osc packets to another computer, device,
* application. usage see below. for testing purposes the listening port
* and the port of the remote location address are the same, hence you will
* send messages back to this sketch.
*/
myRemoteLocation = new NetAddress("127.0.0.1", 6969); //127.0.0.1 is a special address for this computer
}
void draw() {
background(0);
OscMessage myMessageX = new OscMessage("/CubeX");
myMessageX.add(sin(millis()/1000.0));
/* send the message */
oscP5.send(myMessageX, myRemoteLocation);
OscMessage myMessageY = new OscMessage("/CubeY");
myMessageY.add(cos(millis()/1000.0));
/* send the message */
oscP5.send(myMessageY, myRemoteLocation);
fill(255);
// xPosition goes from 0-1
// startX,startY, endX, endY
//println(xPosition);
pushMatrix();
translate(width/2, height/2);
rotate(PI);
translate(-width/2, -height/2);
translate(xPosition, yPosition, zPosition);
rect(0, 50, width/8, 100);
popMatrix();
}
void mousePressed() {
/* in the following different ways of creating osc messages are shown by example */
OscMessage myMessage = new OscMessage("/test");
myMessage.add(123); /* add an int to the osc message */
/* send the message */
oscP5.send(myMessage, myRemoteLocation);
}
//
// HERE IS WHERE WE RECEIVE OSC
//
void oscEvent(OscMessage theOscMessage) {
// print the address pattern and the typetag of the received OscMessage
//print("### received an osc message.");
//print(" addrpattern: "+theOscMessage.addrPattern());
//println(" typetag: "+theOscMessage.typetag());
// receive the cube position on mouse click in Unity
if (theOscMessage.checkAddrPattern("/mouseDown")) {
println(" typetag: "+theOscMessage.typetag());
}
// receive the cube position on mouse click in Unity
if (theOscMessage.checkAddrPattern("/UpdateXYZ")) {
// check if the typetag is the right one.
if (theOscMessage.checkTypetag("fff")) {
xPosition = theOscMessage.get(0).floatValue();
yPosition = theOscMessage.get(1).floatValue();
zPosition = theOscMessage.get(2).floatValue();
}
}
// receive the cube position on mouse click in Unity
if (theOscMessage.checkAddrPattern("/UpdateX")) {
// check if the typetag is the right one.
if (theOscMessage.checkTypetag("f")) {
// parse theOscMessage and extract the values from the osc message arguments.
// index 0 is the first argument
//xPosition = theOscMessage.get(0).floatValue();