Week 12: Interface and Application Programming
Download this week's code
- Capacitive sensing Arduino code
- Square size changes from the sensors Processing.org code
- Tiling rectangles with changing colour from the sensors Processing.org code
Assignment requirements
The first checklist
Write an application that interfaces with an input and/or output device that you made, comparing as many tool options as possible.
The second checklist: Learning outcomes
- Interpret and implement design and programming protocols to create a Graphic User Interface (GUI).
The thirds checlist: Have you
- Described your process using words/images/screenshots
- Explained the the GUI that you made and how you did it
- Outlined problems and how you fixed them
- Included original code
Preamble
I've taken a look at what others have done in past years for this week. I've seen processing.org interfaces, I've seen an Android app made in MIT's AppInventor. I really would like to know how to build an app, if only because mobile phones are powerful little computers that almost everyone carries around with them, so it would be good to harness them for this purpose. I use an iphone, so I need to figure out if there are any shortcuts to making IOS apps, or if I have to dive in and learn how to make an app from scratch.
On the other hand, in the interest of my final project, should I be hacking a 3D printer this week? Can I?
I browsed the processing.org website for examples that I could modify, using my capacitance sensors as controllers. I also examined processing.org examples for the same purpose.
I used the same capcitive-sensing boards from the input week, see them here.
Doing it
On the train in I wrote a simple program in Processing to tile rectangles across the canvas whose hue and saturation were controlled by the mouse. I had done little work in the HSB colourspace, and I wanted a two-variable programme where I could swap the mouse control for the capacitance sensors. It looked like this:
PImage previousImage;
int x = 0;
int y = 0;
void setup() {
size(400,300);
colorMode(HSB, 100);
background(100, 100, 100);
previousImage = get();
}
void draw() {
background(0);
tint(100,98);
image(previousImage, 0, 0);
fill(noise((mouseX*.02))*100, noise((mouseY*.02))*100,100);
rect((width/10) * (x%10), (height/10) * ((x%100)/10), width/10, height/10);
x++;
//x = x%100;
println(noise((mouseY*.02))*100);
previousImage = get();
delay(20);
}
I wanted the rectangles to fade as they came up on the screen, and the trick to doing this is the commands previousImage = get();
at the end of the draw() loop, and these commands at the beginning of the loop:
background(0);
tint(100,98);
image(previousImage, 0, 0);
These lines set the background black, then the tint() will present the next image as faded, and the image() commands pastes the frame from the previous draw loop (which was created with the get() command at the end of the draw() loop).
The colour of the rectangle is set by the position of the mouse cursor, affection the fill() commands, and using the mouseX and mouseY parameters. The rectangle is drawn to the screen, using coordinates set by the integers x and y, which as incremented every time the draw() loop is run.
After Emma introduced us to workflow using serial with Arduino and processing, I wrote code to send both sensors through to the serial. Emma only showed a workflow for a single value, but I got information from here for formatting multiple sensors. Here is the code:
/* Capacitive sensing test code.
* Fab Academy 2018 at the Waag in Amsterdam.
* Copyeverything, David McCallum, 2018
* sintheta.org
*/
#include <CapacitiveSensor.h>
#include <SoftwareSerial.h>
#define rxPin 0
#define txPin 1
const int capMax = 11000;
const int capMin = 0;
SoftwareSerial serial(rxPin, txPin);
CapacitiveSensor cs_4_3 = CapacitiveSensor(4,3); // 10M resistor between pins 4 & 2, pin 2 is sensor pin, add a wire and or foil if desired
CapacitiveSensor cs_4_5 = CapacitiveSensor(4,5); // 10M resistor between pins 4 & 2, pin 2 is sensor pin, add a wire and or foil if desired
int a = -1;
void setup() {
// put your setup code here, to run once:
serial.begin(9600);
cs_4_3.set_CS_AutocaL_Millis(0xFFFFFFFF); // turn off autocalibrate on channel 1 - just as an example
cs_4_5.set_CS_AutocaL_Millis(0xFFFFFFFF); // turn off autocalibrate on channel 1 - just as an example
}
void loop() {
// put your main code here, to run repeatedly:
long start = millis();
int total1 = cs_4_3.capacitiveSensor(30);
int total2 = cs_4_5.capacitiveSensor(30);
// Print numbers
serial.print(total1);
serial.print("\t");
serial.println(total2);
delay(10); // arbitrary delay to limit data to serial port
}
Because this code was originally written to print the sensor data to the serial port, I modified it to print no other information than the sensor results. So the serial.print() commands print the first sensor value and the second sensor value, with a tab character (\t) in between.
Then to test the serial connection I wrote a simple programme that changes the size of two squares based on the input from the capacitance sensors. Here:
/* David McCallum, 2018
sintheta.org
fab academy 2018 at the Waag in Amsterdam
*/
import processing.serial.*;
Serial myPort; // Create object from Serial class
int count = 0;
int[] sensors = new int[2];
int rect0Size = 0;
int rect1Size = 0;
// serialEvent method is run automatically
// whenever the buffer receives a linefeed byte
void setup()
{
myPort = new Serial(this, "/dev/cu.usbserial-FTH9HXH0", 9600);
myPort.bufferUntil('\n');
size(400,400);
}
void draw() {
background(125,34, 45);
rect0Size = int( map(sensors[0], 4000, 7000, 0, 100) );
rect1Size = int( map(sensors[1], 4000, 7000, 0, 100) );
rect(10, 10, rect0Size, rect0Size);
rect(10, 200, rect1Size, rect1Size);
//delay(10);
}
void serialEvent(Serial myPort) {
// read the serial buffer:
String myString = myPort.readStringUntil('\n');
// if we get any bytes other than the linefeed:
if (myString != null)
{
// remove the linefeed
myString = trim(myString);
// split the string at the tabs and convert the sections into integers:
int mysensors[] = int(split(myString, '\t'));
sensors[0] = mysensors[0];
sensors[1] = mysensors[1];
}
}
The serial code is handled by the serialEvent() function, which is triggered when something happens on the serial port. It reads into a string until the end of the line: String myString = myPort.readStringUntil('\n');
, trims off the extra whitespace with e trim() function, and splits the sensor dats into two values using the split() function, and converts them to integers using int().
The draw() loop sets a background colour, and sets the size of the rectangles to draw by mapping the sensor data using map(). Then two rectangles are drawn using the rect() commands, specifying two locations on the canvas, with the x and y dimensions set using the sizes determined from the sensors.
And here it is in action:
Capacitive shaky rectangles from David McCallum on Vimeo.
I originally kept getting a serial error with no information about what was causing it. Googling the error showed many people with the same error, and no one had a solution that seemed relevant to my code, or worked for me. Many were trying to draw to the canvas in the serialEvent function. It turns out that I was trying to print() in the serialEvent function, and I guess it doesn't like that.
Then I adapted the programme that I wrote on the train to use the sensor input. Here:
/* David McCallum, 2018
sintheta.org
fab academy 2018 at the Waag in Amsterdam
*/
import processing.serial.*;
Serial myPort; // Create object from Serial class
int count = 0;
int[] sensors = new int[2];
int[] niceNums = new int[2];
import processing.serial.*;
PImage previousImage;
int x = 0;
int y = 0;
void setup() {
size(800,800);
colorMode(HSB, 100);
background(100, 100, 100);
previousImage = get();
myPort = new Serial(this, "/dev/cu.usbserial-FTH9HXH0", 9600);
myPort.bufferUntil('\n');
}
void draw() {
background(0);
tint(100,98);
image(previousImage, 0, 0);
niceNums[0] = int( map(sensors[0], 6000, 14000, 0, 100) );
niceNums[1] = int( map(sensors[1], 4000, 10000, 0, 100) );
println("Sensors: " + sensors[0] + " " + sensors[1]);
println("Mapped: " + niceNums[0] + " " + niceNums[1]);
//fill(noise((mouseX*.02))*100, noise((mouseY*.02))*100,100);
fill(niceNums[0], niceNums[1],100);
rect((width/10) * (x%10), (height/10) * ((x%100)/10), width/10, height/10);
x++;
//x = x%100;
//println(noise((mouseY*.02))*100);
previousImage = get();
delay(20);
}
void serialEvent(Serial myPort) {
String myString = myPort.readStringUntil('\n'); // read serial buffer
if (myString != null) { // in case of NULL
myString = trim(myString); // remove the newline character
int mysensors[] = int(split(myString, '\t')); // split the string into integers
sensors[0] = mysensors[0]; // write them to global variables
sensors[1] = mysensors[1];
}
}
The change here is that the sensor data that was read is formated using the map() commands, and assigned to the niceNums[] array. These values are then used for the fill() command for the rectangle colour, where previously this was set by the mouse location.
And here it is in action:
Capacitive colour rectangles from David McCallum on Vimeo.
And then, guess what? I was exploring my final project stuff, and found that I could send gcode directly to the printer through python, and I thought, hrm, now I have to learn how to read my sensors with python. AND I DID.
Have a look at the first 3D printer controlling here. And the python code for reading the sensors here:
import serial
ser = serial.Serial('/dev/tty.usbserial-FT9QO55I', 9600)
while True:
lineIn = ser.readline()
lineIn= lineIn.decode('UTF-8')
lineIn = lineIn.strip()
capIn = lineIn.split('\t')
print(capIn[0] + " " + capIn[1])
And it looks like this:
egon:python david$ python3 serialTest.py
1947 41
1940 68
1944 45
1943 56
1943 43
1949 50
4651 43
1941 39
1945 47
1938 56
1944 44
1931 59
1941 41
1939 57
1940 43
Code explanation: I import the serial library, then open the serial port at 9600 baud. Then I create a forever loop, I read in the serial data with .readline(), then its encoded in byte encoding so I make it a string I can work with with decode(), I strip the newline character off the end, then I split it into an array, and then I print it.