© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
R. J. SmytheAdvanced Arduino Techniques in Sciencehttps://doi.org/10.1007/978-1-4842-6784-4_2

2. Development of a Simplified Python Supervisory Control and Data Acquisition System

Richard J. Smythe1  
(1)
Wainfleet, ON, Canada
 

Supervisory control and data acquisition (SCADA ) software has been in use for many years, having been implemented on mainframe computers to manage remote installations such as hydroelectric plants, water treatment facilities, and sewage treatment works. Advances in electronics technology that reduced mainframe computer systems to laptop dimensions, object-oriented programming software advances, and the “unit operations” concepts of chemical engineering now allow the individual experimentalist to create very small, dedicated SCADA systems to interface with equipment on desk- or benchtops, between laboratories, or even between laboratories and mobile, remote, “in-field” experimental setups.

This chapter is designed to introduce the general concept of using open source systems for experimental monitoring and control. As noted in the introduction to this book, investigators will need sufficient familiarity with the concepts of programming and electronics to be able to adapt the code sequences and circuit diagrams depicted here into workable systems on the hardware–software combinations being used in their investigative work.

Open source, freely available software such as Python, with its built-in Tkinter graphical user interface (GUI), when combined with inexpensive hardware such as the Arduino and Raspberry Pi systems, allows the experimenter to assemble customized GUI control systems for their experimental setups at very low cost, measured in dollars and tens of dollars rather than in thousands. Internet capability allows investigators to work in both local and remote locations.

A significant concern with open source systems is being current or “up to date.” Many minds are developing new and better methods for both hardware and software data acquisition with ever increasing computational capabilities. The experimentalist choosing to use an open source system must keep up to date with the current stable version of the system in use. Open source software and hardware systems change with time and at the time of writing, Python is up to version 3.6, Python’s Tkinter is at version 8.5, and Arduino is at version 1.8.13, while the Raspberry Pi board advanced from its first implementation to its fourth revision.

The following six Python program examples have been developed throughout the implementation of two Raspberry Pi (RPi) releases. These programs are working examples of Python–Tkinter–Arduino systems that are able to execute the desired functions of digital data display, component activation, component selection, component adjustment, and realtime analog data recording from a GUI resident on a Model B Raspberry Pi.

Prior to embarking on developing an experiment or process-control system with the Raspberry Pi, the investigator must be aware that the Pi is a Linux system operating on a very inexpensive computing facility with no graphics chips, math co-processor, or large amount of memory to vastly boost system performance.

The very simple USB cable serial interfacing of an Arduino to the RPi greatly increases the control capabilities and range of measurement available to the experimenter. Pulse-width modulation, analog-to-digital conversion (ADC), and substantial computing power from the Arduino’s potent C language–based integrated development environment (IDE) programming aid are readily available from the microcontroller. Although GUI displays place a large demand on available computing resources and require simpler, more utilitarian interfaces, substantial digital signal processing such as time averaging can be performed by the high-speed microcontroller before sending the processed data to the RPi for display.

Because the concept and initial development of the core of this book pre-dates the Raspberry Pi, Arduino, and Python, projects in this chapter will also be behind the current technology when reviewed by experimental investigators. However, the initial code and hardware specified in the following pages is reproducible. The code was developed on different computing platforms, but the final revisions all have been tested and debugged on the RPi Model B with the NOOBs 2.1.0 operating system resident on an 8 GB SD card. The supporting microcontroller was the Arduino Uno or revision 3 boards.

To provide some assistance to those developing their own SCADA system, a series of six programs spanning the primary unit operations required to invoke a working experiment control system are presented. The RPi programs are written in Python, and an Arduino microcontroller provides a signal for display. The microcontroller programs are presented in as simple a form as possible and do not include any significant signal-noise reduction, which can be achieved by more programming of the microcontroller.

As noted earlier, prior to any use of the Python language, RPi, or Arduino components, obtain the latest updates and upgrades for the RPi operating system. The Python language is open source and extensively documented in numerous online tutorials and reference books. Python programs should be developed on the computing system being used to control the experiments at hand. Several Python–Tkinter–Arduino programs developed on Microsoft or Apple systems have been found to be incompatible with RPi systems . The RPi operating system has been designed to facilitate the download of the required supporting software packages, such as the numerical calculation package Numpy; the plotting library Matplotlib; the scientific computing package SciPy; and other specialized libraries, such as the serial port communications library called serial, and the experimenter is advised to use them. Many of the Python–Tkinter GUI screens developed for the RPi do not run on Microsoft- or Apple-based operating systems.

This chapter contains the following exemplar unit operations:
  • The basic Tkinter display frame

  • An auto-updating, numerical-input screen display

  • Screen-controlled On/Off buttons

  • A mouse-activated screen slider for power control or fine adjustment

  • Radio button exclusive selection of one option from a group

  • Graphical data displays: a realtime strip-chart recorder

Program 1: Display Frame

A very simple display frame is created by the code in Listing 2-1.
# Arduino Advanced Techniques SCADA GUI Development with Python and Tkinter
#
import tkinter
mw = tkinter.Tk()
mw.option_add("font",("Arial", 15, "normal"))
mw.title("Arduino Advanced Techniques")
mw.iconname("Advanced Arduino Techniques")
mw.geometry("320x240+325+200")
lab1 = tkinter.Label(mw, text="Data to Display!",background="white",foreground="red", font="Arial 20")
lab1.pack()
mw.mainloop()
Listing 2-1

Creating an Empty Basic GUI Display Frame with Python

The output from Listing 2-1 is depicted in Figure 2-1. On screen, the size of the display can be varied by dragging the corners of the image frame out or in. The Minimize, Maximize, and Delete buttons all are functional, while the image itself can be “dragged” to a convenient screen location by the usual technique of placing the cursor on the frame’s empty space to the right of the Arduino Advanced Techniques heading and depressing the left mouse button (all Tkinter exercises have been developed on the RPi Pixel OS).

In the simple Tkinter-based window seen in Figure 2-1, the basic outer frame and functional resizable display area are all created by the first six lines of code. Three lines of code define a label, its size, background color, text content, and display font, and paint it into the display window.

The parameters of the label display in Figure 2-1 showing the “Data to Display!” text are all fixed by the code and do not change as the outer frame of the small display window is varied. Although unused in this static demonstration, the bottom line implements the mainloop() function on the mw window display and automatically scans the screen area of the window display for mouse-cursor activity or, if programmed, keyboard activity. In-depth details of the mainloop() function can be found in the Tkinter documentation.
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig1_HTML.jpg
Figure 2-1

A static sub-window display

Program 2: Display of Changing Data in Realtime

In the example in Figure 2-1, a simple label displays the static text coded into the text= option of the Tkinter label method. By substituting special Tkinter variables (see Tkinter documentation) for the “Data to Display!” text message, the label can display the actual value of the variable. By setting the value of the variable to that of an input data stream arriving at the serial port, the label can provide a “realtime” display of incoming data. Analog sensor outputs being monitored by an Arduino board can then be streamed out to the serial port for collection and display on the RPi monitor. With the aid of a Python program, the data arriving at the serial port can be processed into a format compatible with a realtime display in a Tkinter-based GUI.

Monitoring an output signal from a remote sensor with a USB serial connection to a host computer running a Python, Tkinter, GUI display window requires forming a coordinated process for data transfer and display. In terms of relative speeds, the GUI display has a large software overhead and is thus relatively slow to respond to changing experimental data. An easily implemented coordination of the one-way data-flow system is to adjust the rate of delivery from the source of the data stream to the GUI display so that the visual display operates at its normal rate and the delivered data displays correctly. The data delivery rate must be slow enough that the large resource consumption by the GUI neither fails to display nor distorts the incoming data. In the visual digital display of Figure 2-2 the display response is fast enough that a manual turning of the potentiometer shaft is seen virtually immediately in the values observed in the screen display. Timing on Arduino–RPi host displays is often best adjusted experimentally.

In the code provided in Listings 2-2 and 2-3, the base display window of Program 1 has been modified to hold static standard labels identifying the source of the data stream and a second label that displays the contents of a changing Tkinter variable that follows the Arduino ADC values. Tkinter labels have been designed to accept one of the four defined Tkinter variable types to provide a label readout that tracks the changes in the original variable value. By reading the data on the port in fractions-of-a-second intervals, a virtually continuous display of the values arriving at the port is realized.
import serial
from tkinter import *
from time import sleep
#
ser = serial.Serial('/dev/ttyACM0')
ser.baudrate = 9600
#
def update():  # a normal python function definition
    while 1:      # main program loop
        reading.set(ser.readline()) # serial port data read and set to "readings" tkinter string variable
        data_strng = ser.readline() # serial port data string for examination and conversion
        print(data_strng)             # console display
        print(len(data_strng))      # string length determined for slicing operation
        if len(data_strng) == 10:   # limit on number of digits for conversion code
            adcVal = int(data_strng[0:3])  # extraction of integer ADC value
            print("ADC value = ", adcVal)  # display of three-digit ADC values only
        root.update()
        sleep(0.125)  # time value for real time display
#
root=Tk()                                  # tkinter window
reading = StringVar()                      # tkinter string value for display in label
w = Label(root, text="Serial Port String") # a static text label
w.pack()                                # place label in window
w = Label(root, textvariable = reading)    # couple label content to serial port data string
w.pack()                             # place label in window
root.after(1,update)                 # tkinter timing function
root.mainloop()                      # window loop
Listing 2-2

Python Tkinter GUI Code for Realtime Data Display

/*
  AnalogRead and Calculation of 100 KΩ wiper voltage output from 5 volts input.
  Reads an analog input on pin 0, prints the result to the serial monitor.
  Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground.
  Pgm calcs wiperVoltage and sequentially sends int ADC (analog-to-digital converter) value and 3 decimal place value for the wiper voltage.
 */
// the setup routine runs once
void setup() {
  // initialize serial communication at 9600 bits per second:
 Serial.begin(9600);
}
// the loop routine:
void loop() {
  // read the input on analog pin 0:
  int sensorValue = analogRead(A0);
  float wiperVoltage = float(sensorValue * 5) / 1023; // NB double float req'd
  // print out sequentially the ADC and wiper voltage:
  Serial.print(sensorValue);
  Serial.println(wiperVoltage, 3); // NB 3 decimals
  delay(250);        // delay in between sensor reads for stability
}
Listing 2-3

Arduino Code to Read Data and Write Values to the Serial Port

The small windows displaying the data are termed widgets, and for simplicity the default properties of the individual label windows have been used in order to minimize the complexity of the GUI creation code. There are a large number of label options available to alter the appearance of the widget display, and as noted the minimize, maximize, and close widget functions are all active, along with the widget cursor-dragging size-adjustment functions.

In Figure 2-2 the widget display from Figure 2-4 has been enlarged for clarity and to display the static text label and the corresponding Tkinter string variable display. As the shaft of the potentiometer connected to the Arduino ADC is rotated, the character string below the static text identifier changes in virtually realtime.
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig2_HTML.jpg
Figure 2-2

A realtime data display widget

Depicted in Figures 2-3 and 2-4 are the graphical widget displays of the Arduino output data stream in the “as received” string format and the Python console display. Tkinter is a GUI creation package, and the ability to display the character string passing through the serial port, although limited, is in most cases probably all that is necessary to use the incoming data stream. A character stream to be used solely as a visual display can be configured to display in the specific format desired by the system operator. If however, the serial port character stream is to be used in a mathematical function or control operation, then the character string must be converted into integers or floating point numbers. Parsing of the character string for conversion to numerical values is best accomplished with Python code.
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig3_HTML.jpg
Figure 2-3

A realtime data display high- to low-digit width transition

../images/503606_1_En_2_Chapter/503606_1_En_2_Fig4_HTML.jpg
Figure 2-4

A realtime data display low- to high-digit width transition

In these two figures, the smaller Tkinter windows on the right contain a text label naming the data being displayed, and beneath the static text is a character string of values. This character string is generated by the Arduino program that is streaming the integer ADC counts from the microprocessor and the actual calculated floating-point wiper voltage in a continuous stream to the serial port. The Python code receives the stream as a character string that can vary from six to nine units in length as the potentiometer is rotated to either extreme. The console output recordings in Figures 2-3 and 2-4 have captured the voltage transit from the 1023 ADC counts and the 5.000V level to the most common mid-range three-digit values then to the transition to the 0 ADC counts and 0.000V at the low-end extreme of the potentiometer setting. Examination of the Python code shows that in this simple demonstration the string-to-integer conversion is fixed in code at ten digits; that is far larger than the midrange three-digit value most likely to occur during potentiometer-shaft rotation. Shaft positions within the limits of 100 and 999 ADC counts trigger the slicing-logic code that picks out only the three-digit ADC values and converts the string characters into integer numbers that are displayed on the console.

To aid the programmer in setting up the slice function, the actual character stream to parse is displayed along with the len() function value that indicates how the Python code is counting characters. Note that the string reportedly arriving at the port as depicted in Figure 2-2 is 4872.380, which the Python code reports as b'4872.380 ', which in turn has a counted character length of ten. When creating the slice function, the programmer can see that the Python len() function counts the b' and ' as single characters.

A point of caution is noted here, and will be repeated later, that involves the nature of the data being streamed out by the Arduino’s analog-to-digital converter (ADC). At either extreme of the potentiometer rotation, the data stream can vary from a single digit to four, and hence the Python code receiving the stream must be able to accommodate the variation in the size of the serial data stream with multiple slice functions to cover the expected width range.

In spite of the large overhead created by the Python software, the speed of the RPi enables the graphic display to keep up with the experimentalist turning the shaft of the potentiometer without any noticeable delay.

Program 3: Activation of Experimental Devices from Button Displays

To create a functioning unit operation such as activating an LED connected to an Arduino microcontroller board, a more complex process must be followed.

The creation of a display-screen button that when pressed by a mouse click or a keyboard entry initiates a desired remote action requires implementation of “event-driven programming.” Event-driven systems require two programs: the first creates an RPi screen displaying a button that is monitored for activation, and the second, resident on the remote device, monitors the serial communications stream for instructions from the button-display monitor program. In this simple demonstration, our screen button will activate an LED already mounted on the remote Arduino microcontroller board.

Tkinter event-driven “objects,” such as buttons and checkboxes, use an internal loop to scan over the possible actions associated with the object in the screen display. “Pressing” the button image with a mouse-button click is caught by the scanning loop software code of the image-object, and an action is initiated that can consist of sending a serial command to the Arduino, which in turn switches on the power to an LED. In the current exercise, event-driven programming is initiated by the user’s activating a screen image that in turn invokes the USB serial communication system between the host computer and the Arduino microcontroller board. Since both the display software and the Arduino microcontroller are activated by looping software code, our event-driven system needs to be coordinated.

To begin the assembly of the button-activated Arduino-LED system, the screen in Figure 2-5 is created with the Python–Tkinter code (Listing 2-4 and 2-5).
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig5_HTML.jpg
Figure 2-5

A simple on/off dual button control panel

# Event handlers join a widget to a type of event and a desired
# resulting action. Command is the method used to detect mouse "<Button-1>"
# events (clicks on the left mouse button) When a button is left clicked with
# mouse, the self.buttonClick() method is invoked to initiate a serial
# transmission of a signal to activate an Arduino action that controls
# an LED.
#
import tkinter      #lowercase t for current python installation
import serial
from time import *
#
#open the COM port in use
ser = serial.Serial("/dev/ttyACM0", 9600)
#
# define the myWindow class
class myWindow:
    def __init__(self):
        self.mw = tkinter.Tk()
        self.mw.title("Arduino Advanced Techniques")
        self.mw.option_add("*font",("Arial", 15, "normal"))
        self.mw.geometry("+250+200")
# GUI function title
        self.lab_1 = tkinter.Label(self.mw, text = "Python Tkinter Control of Arduino LED")
        self.lab_1.pack()
#
# add two buttons to the ui
        self.btn_on = tkinter.Button(self.mw, text = "LED On", command = self.btnOnClick)
        self.btn_on.pack()
        self.btn_off = tkinter.Button(self.mw, text = "LED Off", command = self.btnOffClick)
        self.btn_off.pack()
#
#
        self.mw.mainloop()
#
    def btnOnClick(self):
        ser.write('y'.encode())
        sleep(1)
#
    def btnOffClick(self):
        ser.write('n'.encode())
        sleep(1)
#
if __name__ == "__main__":
    app = myWindow()
Listing 2-4

Python Tkinter GUI for Button Control of Remote Arduino LED

//Arduino monitors serial port for Python command code
//
char serialreader = ' '; // variable to hold data from serial port
//
void setup(){
  // open serial connection
  Serial.begin(9600); // bps rate
  pinMode(13, OUTPUT); // on board led
  Serial.write("1"); //acknowledge readiness
}
void loop() {
  // assign data sent over serial port to serialreader variable
  while (Serial.available() > 0){
    serialreader = Serial.read();
  }
  // turn Led on/off if y/n transmitted
  if (serialreader == 'y') {
    digitalWrite(13, HIGH); //led on
    Serial.print("Led on ");//confirm activation
    serialreader = ' '; // clear contents
  }
  else if (serialreader == 'n') {
    digitalWrite(13, LOW); // led off
    Serial.print("led off ");
    serialreader = ' ';
  }
}
Listing 2-5

Arduino Sketch for Response to Python GUI Button Activation

This relatively simple on-screen GUI does not have an indicator to confirm activation of the LED illumination, as the exercise assumes that the Arduino board is within the field of view of the user. If, however, the device being controlled is remote and out of sight, the researcher may want to have the remote unit send back conformation that the LED or experimental operation is actually powered up by having the code of the remote device send a message back to the initiating GUI to display some form of message label or background-color change to indicate power application to the LED. Alternately, a photoelectric signal derived from light output from the remote device that confirms both power delivery and light-source functionality could be returned to the local GUI.

Program 4: A Sliding-Scale Implementation

Listings 2-6 and 2-7 are the pair required to implement Program 4, which illustrates the use of a sliding-scale selection capability for a screen component that can be used to simulate an analog-type control (Figure 2-6). Rough control of the output signal can be achieved by dragging the scale position-indicator rectangle along the “trough” of the slider image. Fine control of the movement can be achieved by placing the mouse cursor over the trough above or below the rectangular index position indicator and clicking on the mouse button. When the mouse cursor is over the trough between the end of the scale and the indicator, each mouse-button click will move the index indicator one unit toward the scale end at hand.

In this example, the slider output varies the “analog” value written to a pulse-width modulation pin on an Arduino microcontroller. On the PWM pin of the Arduino, a current-control resistor and an LED are mounted so the slider variation can be seen as a dimming of the LED illumination intensity. In addition to the hardware–software interaction, this particular system demonstrates an advantage of the PWM technique. If our hardware–software control system were to instruct the Arduino to apply a normal analog voltage to the output pin, from the 0 voltage to approximately 1.5V the LED would be dark because the applied voltage would be below the device’s “turn-on value.” A PWM technique, however, applies a full 5V to the diode (hence the need for the current-control 220 ohm resistor), and the diode is illuminated at full power for a timed period. By adjusting the duty cycle the illumination can be smoothly varied from nothing to full illumination intensity, and the diode minimum turn-on voltage properties of the LED are bypassed.
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig6_HTML.jpg
Figure 2-6

A power-control sliding scale (“slider”)

# A Sliding-Scale Serial Connection for Power Control.
# A tkinter sliding-scale screen widget is used to provide a continuously
# adjustable serial signal that controls pulse-width variation of a 5 volt
# power signal, applied by an Arduino board to a current-limited LED.
# Sliding the scale tab up or down increases or decreases diode luminosity.
#
import serial
import tkinter as tk
root = tk.Tk()
root.title("Arduino Advanced Techniques")
ser = serial.Serial("/dev/ttyACM0", 9600)
def send(val):
    value = chr(int(val))
    ser.write(value.encode())
brightness = tk.Scale(root, label="LED Brightness", from_=255, to=0,
                      length=400, tickinterval=32, command=send)
brightness.grid(padx=20, pady=20)
tk.mainloop()
Listing 2-6

Python Tkinter GUI Code for Implementation of the Analog Slider Control

// Tkinter Slider -- Arduino Dimmer Code
// An integer from 1 to 255 is used to control the brightness of an LED.
// the integer is created in a Tkinter GUI with a "slider scale." Python
// serial code sends an ASCII-coded value between 1 and 255 that constitutes
// the brightness value
const int ledPin = 9;  // the pin with the LED
int brightness = 0;
void setup()
{
  // initialize the serial communication:
  Serial.begin(9600);
  // initialize the ledPin as an output:
  pinMode(ledPin, OUTPUT);
}
void loop() {
  //check for serial data
  if (Serial.available() > 0)  // check to see if at least one character is available
  {
      brightness = (int) Serial.read();
      analogWrite(ledPin, brightness);
    }
}
Listing 2-7

Arduino Sketch Code for Controlling LED Brightness in Response to Python Tkinter Slider GUI Position

Program 5: Radio Button Controls

GUI radio buttons function in the same manner as the push buttons used in numerous consumer entertainment electronics and electrical appliances. A single button depression exclusively selects a single option from the number of choices available. Program 5 is implemented with Listings 2-8 and 2-9.

In the example created here, the Tkinter panel in Figure 2-7 has been activated with a mouse click to select the third option available, and consequently the third or middle LED in a row of five LEDs connected to an Arduino microcontroller board is illuminated.
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig7_HTML.jpg
Figure 2-7

A radio button selection panel

# Radio button Exclusive Selection of One Option from a Group
# A GUI is created with the Python Tkinter module that contains five labeled
# radio buttons and a display label. Clicking on an empty button ring causes
# the solid center of the button to appear, the display label to indicate the
# identity or number of the button depressed, and serially transmits the number
# of the keyed button to, in this example, an Arduino board with 5 LEDs.
# The logic creating the selection exclusivity is easily implemented with the
# Arduino code.
#
from tkinter import *
import serial
ser = serial.Serial("/dev/ttyACM0", 9600)
def sel():
    selection = "You selected the option " + str(var.get())
    label.config(text = selection)
    #print(str(var.get())) # code for pgm dvlpmnt
    ser.write((str(var.get())).encode('ascii')) # object to string conversion
root = Tk()
root.title("Advanced Arduino Techniques")
var = IntVar() # a Tkinter integer variable object
R1 = Radiobutton(root, text="Option 1", variable=var, value=1,command=sel)
R1.pack(anchor = W)
R2 = Radiobutton(root, text="Option 2", variable=var, value=2,command=sel)
R2.pack(anchor = W)
R3 = Radiobutton(root, text="Option 3", variable=var, value=3,command=sel)
R3.pack(anchor = W)
R4 = Radiobutton(root, text="Option 4", variable=var, value=4,command=sel)
R4.pack(anchor = W)
R5 = Radiobutton(root, text="Option 5", variable=var, value=5,command=sel)
R5.pack(anchor = W)
label = Label(root)
label.pack()
root.mainloop()
Listing 2-8

Python Tkinter GUI for a Radio Button Selection Panel Display

Our radio-button example uses Tkinter variable objects that cannot be passed as normal integer values on the serial bus. (See Tkinter documentation.) Conversion of the object to a string that is compatible with both the serial bus transmission and Arduino C code is required to implement the interfacing of the two system components. The author has left in place one of the “commented out” print statements used in the initial development of the code. Printing variables is a technique that can be used to see what the code, in an experimental passage, is actually producing. Leaving or “commenting out” the invisible statement in place can be an aid in future maintenance or code-modification operations.

For ease of code development and to minimize USB traffic, the exclusivity logic of the radio button selection should be implemented in the Arduino code. When a button is selected in the Tkinter GUI, the button identity can be transmitted to the Arduino for diode illumination, and the Arduino code can then turn off the last LED illuminated.
/*
Switch statement with serial input
Pgm uses a switch statement to choose which LED to illuminate and which LEDs to turn off.
Serial monitor on Arduino should be used to confirm microcontroller
operation before configuring the entire system from Serial Monitor to send any number between 1 and 5 to turn on the appropriate LEDs. Any other character will turn off the LEDs.
 The circuit:
 * 5 LEDs attached to digital pins 2 through 6 each requires a 220-ohm resistor
 */
void setup() {
  // initialize serial communication:
  Serial.begin(9600);
   // initialize the LED pins:
      for (int thisPin = 2; thisPin < 7; thisPin++) {
        pinMode(thisPin, OUTPUT);
      }
}
void loop() {
  // read the sensor:
  if (Serial.available() > 0) {
    int inByte = Serial.read();
      {
    switch (inByte) {
    case '1':
      digitalWrite(2, HIGH);
      digitalWrite(3, LOW);
      digitalWrite(4, LOW);
      digitalWrite(5, LOW);
      digitalWrite(6, LOW);
      break;
    case '2':
      digitalWrite(3, HIGH);
      digitalWrite(2, LOW);
      digitalWrite(4, LOW);
      digitalWrite(5, LOW);
      digitalWrite(6, LOW);
      break;
    case '3':
      digitalWrite(4, HIGH);
      digitalWrite(3, LOW);
      digitalWrite(2, LOW);
      digitalWrite(5, LOW);
      digitalWrite(6, LOW);
      break;
    case '4':
      digitalWrite(5, HIGH);
      digitalWrite(3, LOW);
      digitalWrite(4, LOW);
      digitalWrite(2, LOW);
      digitalWrite(6, LOW);
      break;
    case '5':
      digitalWrite(6, HIGH);
      digitalWrite(3, LOW);
      digitalWrite(4, LOW);
      digitalWrite(5, LOW);
      digitalWrite(2, LOW);
      break;
    default:
    // turn all the LEDs off:
      for (int thisPin = 2; thisPin < 7; thisPin++) {
        digitalWrite(thisPin, LOW);
      }
    }
   }
  }
}
Listing 2-9

Arduino Sketch for Implementing the Radio Button Selection from the Python Tkinter GUI Panel

Program 6: Graphical Data Display—A Realtime Strip-Chart Recording

Many experimental investigations use graphical data displays that may be divided into two basic types: those that use previously recorded or theoretically generated data to create the display and those that monitor and display the present and recent past values of a data stream. Static files of data, collected over years or microseconds and maintained in stored format, can be used to create static or animated images for visual analysis with two- or three-dimensional plotting software, as provided by the Matplotlib facilities of Python. Matplotlib also has the ability to generate continuous graphical displays of streamed, varying data with minimal time delays between the change in data-stream values and the alteration of the visual display. A visual display that tracks a varying-value data stream is commonly referred to as a “realtime” display. Realtime displays are often required in setting up experimental apparatus or following slowly changing signal measurements such as weather conditions, animal movement, or titrations in chemical analysis determinations.

Oscilloscopes electronically record and display relatively high-speed electrical signals over very short time scales, while strip-chart recorders monitor longer time bases for displaying varying signals. Strip-chart recorders are electro-mechanical devices in which a pen draws a trace on a paper strip chart, moving at a constant velocity beneath the potentiometrically balanced pen drive mechanism.

An audio frequency range oscilloscope display can be created from a monitor and a PC sound card with software programs obtained commercially or from internet download. Xy recording and audio-frequency measurements are accessible for more advanced investigations through these various freeware and moderate-cost options.

Python’s plotting library, known as Matplotlib, contains a generic strip-chart recorder program in its list of animation program examples. There are other plotting facilities available in commercial, free, and open source formats obtainable from the internet. (See DAQFactory Express, PL Plot, etc.)

Python’s strip-chart recorder is based upon the animations capability found in the Matplotlib module, and the example program provides its own internal series of “random spiking” events to record for demonstration purposes.

Program 6 is implemented with the Arduino and Python–Matplotlib pair of Listings 2-10 and 2-11.

An easily fabricated experimental setup, capable of providing a continuously varying analog signal that can be digitized, transmitted over a serial connection, and displayed back, in analog form, can be created by coupling a TMP36 integrated-circuit temperature sensor to the analog input on an Arduino microcontroller board, which in turn outputs a temperature reading to the RPi GUI for display. The circuit for the wiring is depicted in Figure 2-8.
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig8_HTML.jpg
Figure 2-8

A circuit configuration for realtime temperature recording from a TMP36 IC

// TMP-36 Temperature sensor monitor
//
// The TMP36 is biased with +5 volts and the IC signal is
// output on the serial port. IC resolution is 10mv/deg C
// and a 500 mv offset is used to allow for sub-zero
// temperature readings.
//
// TMP36 pin assignments
int temperaturePin = 0;
//
void setup()
{
  Serial.begin(9600);
}
//void loop()
{
  float temperature = getVoltage(temperaturePin);
 temperature = (temperature - .5) * 100; // conversion code
 // 10 mv/degC with 500mv offset x 100 to get degC
 Serial.println(temperature); // output value
 delay(1000);  // once per second
}
//
//
float getVoltage(int pin)
{
  return (analogRead(pin)*0.004882814); // convert from
  // 0 -1023 ADC out to 0 - 5 volts.
}
Listing 2-10

Arduino Sketch for Digitization and Serial Transmission Streaming of TMP36 Temperature Data

# Arduino Temperature Monitor
import serial
import numpy as np
from matplotlib import pyplot as plt
ser = serial.Serial('/dev/ttyACM0', 9600)
#
plt.ion() # set plot to animated
#
ydata = [0] * 50
ax1 = plt.axes()
#
# create the plot
line, = plt.plot(ydata)
plt.ylim([10,40])
#
#
# start data collection
s = [0]
while True:
    read_serial = ser.readline() # read arduino output string from serial
    s[0] = str(ser.readline())
    # use slicing to isolate the TMP data
    if len(s[0]) == 12:
        data = float((s[0][2:7]))
        ymin = float(min(ydata))-10
        ymax = float(max(ydata))+10
        plt.ylim([ymin,ymax])
        ydata.append(data)
        del ydata[0]
        line.set_xdata(np.arange(len(ydata)))
        line.set_ydata(ydata)  # update the data
        plt.draw() # update the plot
    else:
        print("error in data")
Listing 2-11

Python Matplotlib Strip-Chart Recording Code for Streamed TMP36 Data

The plotting code originally posted by B. Welt in 2013 and modified by the author only accepts two-digit temperature values but works well as a demonstration of realtime recording.

In Figure 2-9, the RPi screen contains the Python console on the left and the Matplotlib strip-chart recorder output on the right.
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig9_HTML.jpg
Figure 2-9

The RPi console and a SCR display of a TMP output

In the examples section of the Matplotlib documentation an actual strip-chart recorder (SCR) program is presented in which a random-number generator is used to provide data for the program display. A modification of the SCR program to record the streamed output of an Arduino microcontroller monitoring the ambient room light near a window on a cloudy day is presented here. The Arduino and RPi–Python code pair of Figure 2-12 and Listing 2-12 are presented together with a screen display from the RPi in Figure 2-10 and a detail of the actual plotter output in Figure 2-11.

The functions of the buttons in the lower left-hand corner of the graphical display are detailed in the Matplotlib Interactive Navigation documentation. The coordinates of any point on the recorded graphic line can be displayed in the lower right-hand corner of the display by placing the mouse pointer over the desired location.
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig10_HTML.jpg
Figure 2-10

An illumination recording from a light-dependent resistor on a cloudy day

../images/503606_1_En_2_Chapter/503606_1_En_2_Fig11_HTML.jpg
Figure 2-11

The illumination recording from Figure 2-10

Arduino Sketch to Monitor, Digitize, and Serially Stream LDR Data for Python–Matplotlib SCR Presentation shown in Figure 2-12.
../images/503606_1_En_2_Chapter/503606_1_En_2_Fig12_HTML.jpg
Figure 2-12

An Arduino IDE code listing for LDR response

"""
A Strip Chart Recorder Graphical Display for Raspberry Pi
"""
# The RPi display is driven by an Arduino serial port output
#
import matplotlib
import numpy as np
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import serial
class Scope:
    def __init__(self, ax, maxt=40, dt=0.02):
        """maxt time width of display in minutes"""
        self.ax = ax
        self.dt = dt
        self.maxt = maxt
        self.tdata = [0]
        self.ydata = [0]
        self.line = Line2D(self.tdata, self.ydata)
        self.ax.add_line(self.line)
        self.ax.set_ylim(0.0,1000.0)  # y axis scale
        self.ax.set_xlim(0, self.maxt)
    def update(self, y):
        lastt = self.tdata[-1]
        if lastt > self.tdata[0] + self.maxt: # reset the arrays
            self.tdata = [self.tdata[-1]]
            self.ydata = [self.ydata[-1]]
            self.ax.set_xlim(self.tdata[0], self.tdata[0] + self.maxt)
            self.ax.figure.canvas.draw()
        t = self.tdata[-1] + self.dt
        self.tdata.append(t)
        self.ydata.append(y)
        self.line.set_data(self.tdata, self.ydata)
        return self.line,
ser = serial.Serial("/dev/ttyACM0", 9600)
#
def rd_data():
    while True:
        inPutln = ser.readline()
        #print("inPutln = ", inPutln)
        line = int(str(inPutln)[slice(2,-3)]) # convert arduino serial output stream to a Python string, parse out the numerical symbols, and convert to a value.
        #print(line)
        yield (line)
fig = plt.figure()
fig.suptitle("The Scientyst's Ayde", fontsize = 12)
ax = fig.add_subplot(111)
ax.set_xlabel("Time")
ax.set_ylabel("Arduino LDR ADC Units")
scope = Scope(ax)
# use rd_data() as a generator to produce data for the update func, the delay(n)
# in the Arduino code determines rate of data feed to the animated
# screen display. Software overhead limits response speed of display.
ani = animation.FuncAnimation(fig, scope.update, rd_data, interval=50,
blit=True)
plt.show()
Listing 2-12

Python Matplotlib SCR Display Code

When Listing 2-12 is run on the RPi, it generates the dual screens of Figure 2-10, with Python on the left and the graphic display trace depicted on the right and in close up in Figure 2-11. The SCR display is divided into forty-minute increments, and data display is initiated and timed from the loading and running of the Python code. After the accumulation and display of forty minutes of data, the program advances to the next forty-minute window, as is visible on the time axis in Figures 2-10 and 2-11. The SCR program code does not have any provision for storing the data from a previous window time increment other than the ability to save the current graphical display provided by the Save button on the right-hand end of the lower left panel of buttons. The area of the plot window can be dragged to any convenient size by the usual mouse window-edge manipulation techniques.

All of the programs and code presented in this chapter are very simple, have no signal-to-noise reduction code, and are uncalibrated. Most of the code has been commented, and some contains print statements for diagnosing malfunctioning code that have been “commented out” for actual use. Where applicable, cautions have been added with respect to processing the incoming data-stream variations that may occur. The analog-to-digital converter on an Arduino can output values from 0 to 1023. In other words, the incoming stream can vary from a single-digit value to a four-digit value together with the control characters required to uniquely define the transmission of the data stream. There are numerous serial data-transmission protocols used in various sensors, and the experimenter must accommodate these protocols when modifying the Python codes presented here for data display.

The programs work and can be used as a known starting point for the development of more-complex, very low-cost, small-scale, Linux–Python–based SCADA systems.

Summary

  • A series of six basic “unit operations” that can be used to assemble a SCADA system are developed.

  • Data displays and analog-and-digital control functions are developed in Python–Tkinter–based GUI screen displays resident on the RPi, and serial data or operational commands are streamed back and forth with an Arduino microcontroller functioning as an intelligent peripheral or interface.

  • A Python–Matplotlib continuous strip-chart recorder data display is demonstrated by providing a continuous recording of Arduino data streams following ambient temperature and lighting conditions.

In Chapter 3, the care required when generating and working with high heats and high temperatures in experimental investigations is explored.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.90.205.166