Interface and Application programming

Individual assignment:

This week I have been out sick. Fortunately, I already did application programming 2 weeks ago as part of Input Devices.

I’d suggest reviewing documentation for week 12

In this document I will just document the parts that are missing, but you can find all the evidences, details and the proofs in week 12.

What are we trying to achieve

My guess is that some hidden goals for this week are:

Using Processing to interpret and display data from our board

As I already described in week 12, I decided to use Processing to visualize and display my sensor’s data.

In this document we will slow go through the code of the script to explain what it does as well as the why, and the how it works.

Processing Script

Setup and Draw methods

Just like arduino has the setup() and loop() methods, processing has 2 similar methods, with identical purposes setup()and draw().

A quick at this week’s code, shows how we use the setup() for once-off initializations (detecting which serial ports are available and selecting the first one), and how we use draw() repeatedly to continuously poll the serial data that is being sent to us and then using UI functions to redraw and update the display window that is visible on our laptop’s screen.

import processing.serial.*;

Serial arduinoSerial;

int BACKGROUND_COLOR = 70;
int LINE_FEED = 10;
int MERCURY_WIDTH = 30;

void setup(){
  size(300, 800);
  String portName = Serial.list()[0];
  arduinoSerial = new Serial(this, portName, 9600);
}

void draw(){
  drawThermometer();
  visualizeSerialData();
  delay(100);
}

The beauty of dividing code in different layers (abstracting away some details, and hiding them), is that it allows the reader to focus on what is going on in that specific layer of abstraction. We don’t need to know what visualizeSerialData() does. All we need to know is that we repeatedly (every 100ms) update the screen and draw the serial data that we receive (and the how is hidden away, until we are ready to read it and understand it).

Drawing a UI

If we scroll down a bit more, we will see how drawThermometer() does its magic.

Let’s dive one layer deeper and find out together!

In a similar way to what we did in the layer before, we can easily read what the code is trying to do, as the function names are using descriptive “plain english” analogies.

If we go one level deeper, we will see how each of these functions do the low-level operations.

These low-level operations, are not actually that low level; they are in fact the first point of contact between our custom application code, and the Processing API (Application Programming Interface), which is both intuitive and well documented in the online language reference.

I hope it is clear that, if you could see the code for those functions (which are defined inside Processing libraries), you would see similar abstraction layers that go deeper and deeper, eventually, these Processing libraries reach their ” lowest level” and they delegate tasks to the operating system libraries, then the display drivers .... and it’s basically, “turtles all the way down”

void drawThermometer(){
  background(BACKGROUND_COLOR);
  drawBorder();
  drawTube();
  drawReservoir();
  drawScale();
}

void drawBorder(){
  fill(255, 255, 255, 255);
  rect(20, 50, 260, 700);
}

void drawTube(){
  fill(255,255,255,255);
  stroke(90, 90, 90);
  strokeWeight(4);
  quad( 50, 100, 
        250, 100, 
        250, 650, 
        50, 650);
  noStroke();
}

void drawReservoir(){
  int centerX = 150;
  int centerY = 700;


  fill(255,0,0,255);
  circle(centerX, centerY, 70);
  rect(centerX-(MERCURY_WIDTH/2), centerY, MERCURY_WIDTH, -70);
}

void drawScale(){
  scaleMarking(80, 600, 0);
  scaleMarking(80, 550, 10);
  scaleMarking(80, 500, 20);
  scaleMarking(80, 450, 30);
  scaleMarking(80, 400, 40);
  scaleMarking(80, 350, 50);
  scaleMarking(80, 300, 60);
  scaleMarking(80, 250, 70);
  scaleMarking(80, 200, 80);
  scaleMarking(80, 150, 90);
}

The key insight is that programming is like trying to get a super-corporation to complete a very complex product.

There are 10000 layers of middle managers and which do nothing but delegate work to their immediate subordinates in the right combination and tempo, and some very hidden away workers (that nobody sees but actually do all the work).

When everything works fine, the CEO-Developer congratulates itself on being a supreme being for having coordinated all the work beautifully… But make no mistake, it is not the CEO/developer’s achievement. It is not the individual low-level instruction either, they have no context of what is being done 1 layer above them, they just execute.

Without the last level of instructions (which actually do the work), all that coordination achieves nothing. Without the beautiful coordination, it would be extremely difficult to write programs of minimal/intermediate complexity.

The credit belongs to neither and both, at the same time.

Custom logic to visualize the data we care about

Alright, so we’ve seen several interesting things until now, but let’s get to the important part, how do we use programming to reduce cognitive load for our end users?

Remember how we talked about visualizeSerialData() but never checked it out? It’s time to dive into it.

This code works in a very similar way to the previous one, except that it is parameterized, and the parameter is the key data that we care about (the bits and bytes of data that our SAMD11C microcontroller is sending to our laptop via Serial port).

The fundamental behaviour of this code is the same as the previous one, except that at some point, we added a small tweak so that the voltage readings we get affect the height of the red rectangle that represents the thermometer’s mercury bar.

void visualizeSerialData(){
  if(arduinoSerial.available() > 0){
    String serialData = arduinoSerial.readStringUntil(LINE_FEED);
    float actualVolt = (3.3 / 1024) * float(serialData);
    fill(204, 102, 0);
    textSize(34);
    float position = map(float(serialData), 0, 1024, 750, 50);
    text("v = " + actualVolt, 40, position);
    printResistance(actualVolt);
  };
}

void printResistance(float realVolt){
  textSize(25);
  fill(204, 102, 0);

  float resistance = calculateResistanceOfProbe(realVolt);
  text("Resistance = " + resistance, 50, 30);

  printTemperature(resistance);
}

void printTemperature(float resistance){
  textSize(25);
  fill(30, 30, 0);

  float temp = convertResistanceToTemperature(resistance);
  text("Temperature = " + temp, 50, 65);
  drawMercuryBar(temp);
}

float calculateResistanceOfProbe(float realVolt){
  return (5 - realVolt) * 100000 / realVolt;
}

float convertResistanceToTemperature(float resistance){
  return ((-19.89212 * log(resistance)) + 257.7);
}

// God bless all mathematicians. Engineering would be nothing without you :P
float getYforNormalizedVoltage(float temp){
  float position = map(temp, 0, 90, 600, 150);
  return position;
}

The last important thing I wanted to hightlight in this code is that I don’t particularly like how hard it is to read:

Some thoughts on this code:

These are not excellent examples of these principles (depending on your stance, and how you’d like to slice responsibilities), but overall the code does show the value of these principles and these minor hiccups could be refactored away if we wanted the code to be optimized to improve the ease of maintenance (which was not the case for this hacked-in-2h-while-learning-processing-syntax-on-the-fly exercise)

Due to time constraints and having done this code rather quickly at the end of week 12, I decided to not spend valuable time optimizing and perfecting it.

Group Project