Commit 1c67d8ff authored by Evan Raskob's avatar Evan Raskob

added full lesson with images and full api explained

parent 7257fe19
/************************************************************************************
GUI
************************************************************************************/
ControlP5 cp5;
// DropdownList serialddl;
// DropdownList baudddl;
ScrollableList serialddl;
ScrollableList baudddl;
Textlabel arduinoLabel;
Button startButton;
Button stopButton;
Button testButton;
void setupGUI() {
//the ControlP5 object
cp5 = new ControlP5(this);
//start button
startButton = cp5.addButton("START")
.setPosition(200, 200)
.setSize(200, 19)
;
//stop button
stopButton = cp5.addButton("STOP")
.setPosition(200, 200)
.setSize(200, 19)
;
stopButton.hide();
//start button
testButton = cp5.addButton("TEST_ON")
.setPosition(100, 227)
.setSize(80, 16)
;
testButton = cp5.addButton("TEST_OFF")
.setPosition(186, 227)
.setSize(80, 16)
;
//start button
testButton = cp5.addButton("ERROR")
.setPosition(272, 227)
.setSize(80, 16)
;
//Serial Port selector
serialddl = cp5.addScrollableList("SerialPort")
.setPosition(50, 100)
.setSize(200, 200)
;
serialddl.setItemHeight(20);
serialddl.setBarHeight(15);
serialddl.getCaptionLabel().set("SELECT ARDUINO SERIAL PORT");
serialddl.getCaptionLabel().getStyle().marginTop = 3;
serialddl.getCaptionLabel().getStyle().marginLeft = 3;
serialddl.getValueLabel().getStyle().marginTop = 3;
//set the serial options
//String SerialList[] = Serial.list();
serialList = Serial.list();
for (int i=0; i<serialList.length; i++) {
String portName = serialList[i];
serialddl.addItem(portName, i);
}
if (serialList.length > 0)
serialddl.setValue(0);
//setup the baud list
// baudddl = cp5.addDropdownList("BaudRate")
baudddl = cp5.addScrollableList("BaudRate")
.setPosition(50, 50)
.setSize(200, 200)
;
baudddl.setItemHeight(20);
baudddl.setBarHeight(15);
baudddl.getCaptionLabel().set("SELECT THE BAUD RATE");
baudddl.getCaptionLabel().getStyle().marginTop = 3;
baudddl.getCaptionLabel().getStyle().marginLeft = 3;
baudddl.getValueLabel().getStyle().marginTop = 3;
//the baud options
for (int i=serialRateStrings.length-1; i>=0; i--) {
String baudString = serialRateStrings[i];
baudddl.addItem(baudString, i);
}
baudddl.setValue(0);
//text labels
arduinoLabel = cp5.addTextlabel("arduinoLabel")
.setText("Serial")
.setPosition(50, 10)
.setColorValue(0xffffff00)
.setFont(createFont("SansSerif", 11))
;
}
/**
/* the function run when the START button is pressed: start everything up!
*/
public void START() {
boolean success = true;
try {
setupSerial();
}
catch (Exception e)
{
// SERIAL ERROR!
System.err.println("SERIAL ERROR::" + e);
success = false;
}
if (success)
{
hideControls();
applicationRunning = true;
}
}
/**
/* the function run when the STOP button is pressed: disconnect from serial
*/
public void STOP()
{
if (applicationRunning)
{
stopSerial();
applicationRunning = false;
}
showControls();
}
//hide all the controls and show the stop button
void hideControls() {
serialddl.hide();
baudddl.hide();
startButton.hide();
//show the stop button
stopButton.show();
}
void showControls() {
serialddl.show();
baudddl.show();
startButton.show();
//hide the stop button
stopButton.hide();
}
void controlEvent(ControlEvent theEvent) {
String eventName = theEvent.getName();
if (eventName == "SerialPort") {
//set the serial port
serialListNumber = int(theEvent.getValue());
} else if (eventName == "BaudRate") {
int index = int(theEvent.getValue());
baud = Integer.parseInt(serialRateStrings[index]);
} else
}
import controlP5.*; //<>//
import processing.serial.*;
/*
* This example reads sensor data from an Arduino
* and relays it to another program using OSC.
* Try changing the TEST_ON and TEST_OFF functions to send some serial data to an Arduino (or other microcontroller) and
* test how that works. Then, in the Serial_functions tab, edit the serialEvent() function to handle some
* incoming serial messages from the Arduino.
*
* copyright 2019 Evan Raskob (e.raskob@gold.ac.uk)
* Jan. 29, 2019
* MIT Licensed. Use at your own risk.
*/
// whether we're connected to an Arduino or not:
boolean applicationRunning = false;
/**
* Test function that does something when pressed (TEST_ON button in GUI)
*/
public void TEST_ON()
{
if (applicationRunning)
{
// 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);
// this one works with the SerialParseTest Arduino sketch:
// serial.write("n 1,0,0" + stopChar);
// AnalogSerialAPI trigger read of analogue port A0
serial.write("ledon" + stopChar);
}
println("finished");
}
/**
* Test function that does something when pressed (TEST_OFF button in GUI)
*/
public void TEST_OFF()
{
if (applicationRunning)
{
// Whatever is in here gets run when the TEST_OFF button is pressed.
// the following works with the SerialSendReceiveTest sketch for Arduino to turn the LEDs on and off
serial.write("ledoff" + stopChar);
}
println("finished");
}
/**
* Test function that does something WRONG when pressed (ERROR button in GUI).
* Useful for testing against bad data situations.
*/
public void ERROR()
{
if (applicationRunning)
{
serial.write("bad data" + stopChar);
// could also leave out the stopChar and see what happens!
}
}
/**
* Look at the list of commands received and handle them properly.
*/
void handleSerialCommands() {
synchronized(commandsReceived) {
// handle all commands in buffer
while (commandsReceived.size() > 0)
{
String command = commandsReceived.remove(0); // remove first command
// There are a few ways to do this...
// one way is to just print out what we got:
println(command);
// we can test for speciic commands using the equals() function of Strings:
//if (command.equals("ledon")) {
//
//}
}
}
// done with all received commands
}
/************************************************************************************
SETUP/DRAW
************************************************************************************/
void setup() {
// configure the screen size and frame rate
size(550, 250, P3D);
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(); // update GUI with data received/sent
handleSerialCommands();
}
}
/************************************************************************************
VISUALIZING INCOMING PACKETS
************************************************************************************/
int lastSerialPacket = 0;
int lastOSCPacket = 0;
void drawIncomingPackets() {
//the serial packet
fill(0);
rect(75, 50, 100, 100);
//the udp packet
rect(325, 50, 100, 100);
int now = millis();
int lightDuration = 75;
if (now - lastSerialPacket < lightDuration) {
fill(255);
rect(85, 60, 80, 80);
}
if (now - lastOSCPacket < lightDuration) {
fill(255);
rect(335, 60, 80, 80);
}
}
void drawIncomingSerial() {
// println("drawIncomingSerial");
lastSerialPacket = millis();
}
void drawIncomingOSC() {
lastOSCPacket = millis();
}
/************************************************************************************
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;
final char stopChar = '\n'; // character for end of line
final String[] serialRateStrings = {
"300", "1200", "2400", "4800", "9600", "14400",
"19200", "28800", "38400", "57600", "115200"
};
String serialList[] = null;
int baud=0;
int serialListNumber = 2;
// we will store the incoming serial values in here
ArrayList<Byte> serialBuffer = new ArrayList<Byte>();
/**
* this is the list of serial data that we've received via serial.
* will be handled in handleSerialCommands() in the main 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) {
//}
/**
* 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) {
synchronized(commandsReceived)
{
commandsReceived.add(line);
}
// 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
baud = int(serialRateStrings[serialRateStrings.length-1]);
}
if (serialList != null) {
serialListNumber = (int)serialddl.getValue();
println("opening " + serialList[serialListNumber]);
serial = new Serial(this, serialList[serialListNumber], baud);
serial.bufferUntil(stopChar);
}
}
/**
* Stop the serial port reading
*/
void stopSerial() {
serial.stop();
// clear list of received serial
synchronized(commandsReceived)
{
commandsReceived.clear();
}
}
# Serial To OSC Template for Processing # Serial To OSC Exercises for Arduino, Unity, Processing
A template to use for apps that take serial from Arduino or other microcontrollers and route to OSC to another piece of software (like Unity) via OSC. These are lessons on how to send and receive serial data in useful ways from Arduino to Processing, and then route that data via OSC to other programs like Unity. These examples show how to read serial data, create strings and parse them for command and parameter patterns, and use this to build your own serial API.
\ No newline at end of file
To use the Unity examples you'll need to download [the OSC example project](https://github.com/thomasfredericks/UnityOSC) and of course [Unity3D](https://unity3d.com/).
For other examples, you'll need a recent version of [Processing](http://processing.org) an [Arduino](http://arduino.org).
# Communication setup
We are going to connect Arduino via serial to Pocessing, and then use Processing to connect over networks to oher software using the OSC (Open Sound Control) protocol. Some example setups are like so:
[![example communication setups for serial to osc](images/SerialtoOSCSetup.jpg "example communication setups for serial to osc")](images/SerialtoOSCSetup.svg)
# Methods of communication
For these examples, we'll look at two methods of communication between the Arduino and another program: __passive__ and __call/response__. In __passive__ mode, data is simply sent from the Arduino as fast as possible, with no feedback on whether or not it was received or even wanted in the first place. It's up to the receiving software to deal with the onslaught of data, constructively. In __call/response__ mode, the Arduino is asked (the *call*) to send something (the *response*).
## Passive mode
Passive mode is pretty strightforward - it's a firehose of data that takes no coordination between the two programs:
![firehose](https://media.giphy.com/media/TVJzpTzptvxDi/giphy.gif "firehose out of control")
## Call/Response
Call/response takes more explaining. In the example diagrams below, a call originates from a Processing app (via OSC) which is requesting the value of an analogue sensor atached to an Arduino. This *call* is routed through another Processing app which acts as an OSC-to-serial bridge, tuning OSC commands into serial commands that the Arduino can process ("Serial Call diagram"):
[![serial response](images/Serial_Call.jpg "Serial Call diagram")](images/Serial_Call.svg)
Then, the Arduino *responds* appopriately via serial to the Processing serial-to-OSC bridge which is then routed via OSC back to the original app that made the *call* ("Serial Response diagram"):
[![serial response](images/Serial_Response.jpg "Serial Response diagram")](images/Serial_Response.svg)
## The API
Both of these methods depend on a common API, or a common language for describing what the calling program wants to receive and the format with which the responding program should phrase it's response.
For example, our simple API is defined as a function identifier which is a foward-slash '/' followed by a single letter, with up to 3 integers for parameters. In the above examples, the phrase "/a,0" translates to "respond withe the value of the sensor attached to port A0 (analogue 0)". "/a" means "respond with the value of an analogue port" and "0" means the first port, port 0 (A0).
We can build other functions on top of this simple format to flesh out our API. For example, a function command "/n" might mean "set the colour of a [NEOPixel](https://learn.adafruit.com/adafruit-neopixel-uberguide/the-magic-of-neopixels)", which could take 3 arguments for H,S,B values. Or, a function command of "/m" might turn on a DC motor atached to the Arduino.
Calls can work in the other direction too. Suppose the Arduino senses a button push on pin 11: it can sen the function command "/b,11,1" meaning "button pushed (1, which is true as opposed to 0, which is false) on pin 11". Then, the Processing sketch that finally receives it can answer back with an affirmative. If it doesn't, the Arduino could keep sending that initial call until a response is finally received.
# Files
These are the lesson files, in order of how they are introduced during the lesson:
## SerialSendReceiveStrings
A basic example that shows how to receive string (character) data from the serial port on the Arduino.
Use Arduino's Serial Monitor to send/receive data by typing it in an pressing RETURN (make sure that the dropdown at the bottom says "Newline" and not "No line ending").
Then, run the __P5SerialSendReceiveStrings__ example in Processing to try sending and receiving those same commands from Processing. The __TEST_ON__ and __TEST_OFF__ buttons send "ledon" and "ledoff" via serial, respectively. Look in the main .pde file for their definition. Also, the __handleSerialCommands()__ function in the same file will process a list of all the lines of serial data (e.g. *commands*) that it has received. It runs inside the __draw()__ function so you can directly raw to the screen inside of it.
Right now, it's not doing much, but perhaps you can change that?
## SerialParseTest
This is a more advanced example that uses __sscanf__ to look for patterns in serial data an interpret them as commands. Again, use the Serial Monitor to send/receive data to it and see how it parses what data you've typed in. Again, run __P5SerialSendReceiveStrings__ and see what Processing is receiving. This is the start of how we define our communications API.
## AnalogSerialAPI
This example is a more complete version of our final communications API. It goes along with the __P5SerialSendReceive__ and __P5OscApp__ examples. With the two of them, it demonstrates a full call/response system where __P5OscApp__ asks (calls) for analogue values and the __AnalogSerialAPI__ on he Arduino responds back with them. Finally, the __P5OscApp__ draws he results of those 6 analogue ports to the screen as a series of colourful rectangles.
## VisualiseOSC
A template to use for receiving OSC messages in Pocessing and trying to visualise them inside an app, using the controlP5 libary.
## oscP5SendReceiveUnity
This example demonstrates how to use OSC to communicate between Processing an Unity. It uses the
Unity [OSC example project](https://github.com/thomasfredericks/UnityOSC) mentioned above and of course [Unity3D](https://unity3d.com/).
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment