Skip to content

15. InterFace and Application programming

This week I worked on making a PC software that interfaces with the PCB I made for the networking week.

Research

After taking a look at the material and software suggested by the FabAcademy website, and asking around I have decided to build the software using Python with the PyQt5 library, as the PyQt5 has an additional studio software that helps build more beautiful interfaces in a drag and drop environment, and Python should be easy to use moving from C.

Python

first steps into python

The first thing to do is to learn Python as I have never used it before, I prefer video content for new topics so I did some search and watched this toturial on Python, moving to python from C it was a fairly easy transition, but I have always found it hard to deal with objects and object-oriented programming and python is mostly all objects so I have my fair share of errors on the way.

first project

Now after I have watched almost 3 hours of the tutorial I felt it is enough to try and start using the language, I started with a PyQt5 tutorial project playlist from tech with tim to understand how the interface is built, and get to know the library and its functions.

I usually use Linux subsystem for windows 10 for coding, but since I am building a GUI interface and recall Neil’s interface app in input week) did not work in it I did my work in Windows 10 PowerShell and vs code.

I already had the Python3.9 environment running on my PC when I was working on the input week, I had downloaded it from windows 10 store, it can be downloaded from Python official website too.

Here is the first PyQt5 project I made following the tutorial above, I have added comments to make the code more clear.

#import needed libraries
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
from sys import argv

#define a class responsible for the gui interface
class MyWindow(QMainWindow):
    def __init__(self):#this function runs automaticly when a new object is defines
        super(MyWindow, self).__init__()
        self.setGeometry(200, 200, 600  , 600)
        self.setWindowTitle("RGB Controller")
        self.initUI()

    def initUI(self): #design UI elements
        self.label = QtWidgets.QLabel(self)     #make a label
        self.label.setText("My Lable!!!")       #name the label
        self.label.move(50 , 50)                #position the label

        self.b1 = QtWidgets.QPushButton(self)   #make a button   
        self.b1.setText("Click")                #select button text
        self.b1.clicked.connect(self.clicked)   #connect button to a function

        self.Dial = QtWidgets.QDial(self)   #make a Dial   
        self.Dial.setGeometry(300, 300, 100  , 100)#set the size and location of the dial

        self.Slider = QtWidgets.QSlider(self)   #make a slider   
        self.Slider.setGeometry(0, 300, 100  , 100) #set the size and location of the slider


    def clicked(self):  #funcution for when the button is pressed                        
        self.label.setText("you pressed the button")    
        self.update()

    def update(self):
        self.label.adjustSize()


def window():
    app = QApplication(sys.argv) 
    win = MyWindow() #define an object window

    win.show() # show the window
    sys.exit(app.exec_())

#MainCode
window()

first project screenshot

Interface with a PCB

Now that I have some knowledge about python and PyQt5, its time to make a python code that talks to a PCB, I used the PCB that I had made in the networking week it has a UART interface that I will be using to make a GUI that controls the RGB on the board.

first I had to try and connect python to the serial interface of the PCB, for that I used this tutorial.

after I tried it and made sure it works, I added a simple modification on the python code so that the user gives the com port and the serial communication speed before it runs.

Python Code

# Importing Libraries
import serial
import time
import serial.tools.list_ports

#list availabe com ports
ports = list(serial.tools.list_ports.comports())
for p in ports:
    print(p)

#let user pick com port
boardCom = input("Enter com, example'com3' :")

#let user pick com speed
COMBaudRate = input("Enter communication speed, example'115200' :")

#initialize communication
PCB = serial.Serial(port=boardCom.upper(), baudrate=COMBaudRate, timeout=.1)

def write_read(x):
    PCB.write(bytes(x, 'utf-8'))
    time.sleep(0.05)
    data = PCB.readline()
    return data

while True:
    num = input("Enter a number: ") # Taking input from user
    value = write_read(num)
    print(value) # printing the value

Arduino Code

int x;

void setup() {
  Serial.begin(115200);
  Serial.setTimeout(1);
}

void loop() {
  while (!Serial.available());
  x = Serial.readString().toInt();
  Serial.print(x + 1);
}

Final results

Now its time to work on the week’s assignment, building up my code step by step, testing, and making sure every element works well, I used this tutorial box layout to get the interface to scale if the window size changed by the user, and this [tutorial] to make the Arduino code to read a string and break it apart to get the instruction, for example, the python code sends this string to the PCB <BLUE, 100>, the PCB will read < and understand that its a communication initializer, read the first work till the , symbol, saves it in a string then reads the second word… so and so, thill it reaches the > symbol as a communication ending, sends a replay, and end the string reading.

here is a video running the code, and down below are both the python and Arduino codes.

Video

Assignment python code

# add needed libraries
import serial
import time
import serial.tools.list_ports

from PyQt5.QtWidgets import QDialog, QDial, QHBoxLayout, QVBoxLayout, QApplication, QSpinBox
from PyQt5.QtGui import QIcon
import sys

#get data from user to run serial commuinication correctly
#list availabe com ports
ports = list(serial.tools.list_ports.comports())
for p in ports:
    print(p)

#let user pick com port
boardCom = input("Enter com, example'com3' :")

#let user pick com speed
COMBaudRate = input("Enter communication speed, example'9600' :")

#initialize communication
PCB = serial.Serial(port=boardCom.upper(), baudrate=COMBaudRate, timeout=.1)

#function to send data to PCB and get a responce
def write_read(x):
    PCB.write(x)
    time.sleep(0.05)
    data = PCB.readline()
    return data

#set main window class
class Window(QDialog):
     # function that runs automaticly when defining a new object to this class
    def __init__(self):
        super().__init__()

        title = "Dial pplication"
        top = 40
        left = 200
        width = 450
        height = 300

        icon = "icon.png"

        self.setWindowTitle(title)
        self.setGeometry(top, left, width, height)
        self.setWindowIcon(QIcon(icon))
        self.InitUI()

    def InitUI(self): # function to initlize window widgets
        #define RGB dials
        self.dialRed = QDial(self)
        self.dialRed.setNotchesVisible(True)
        self.dialGreen = QDial(self)
        self.dialGreen.setNotchesVisible(True)
        self.dialBlue = QDial(self)
        self.dialBlue.setNotchesVisible(True)

        #define RGB spin
        self.spinRed = QSpinBox(self)
        self.spinGreen = QSpinBox(self)
        self.spinBlue = QSpinBox(self  )

        #define layout, 2 horizantal, one for dials, and the other for spins
        self.hbox = QHBoxLayout()
        self.hbox2 = QHBoxLayout()
        self.vbox = QVBoxLayout()

        #add 2 horizantal layouts to the vertical layout
        self.vbox.addLayout(self.hbox)
        self.vbox.addLayout(self.hbox2)

        # add dials and spins to the horizantal layouts
        self.hbox.addWidget(self.dialRed)
        self.hbox.addWidget(self.dialGreen)
        self.hbox.addWidget(self.dialBlue)
        self.hbox2.addWidget(self.spinRed)
        self.hbox2.addWidget(self.spinGreen)
        self.hbox2.addWidget(self.spinBlue)

        #set vertical layout for the main window
        self.setLayout(self.vbox)

        #link dials to spins and set ranges to 255
        self.dialRed.valueChanged.connect(self.spinRed.setValue)
        self.spinRed.valueChanged.connect(self.dialRed.setValue)
        self.dialRed.setRange(0, 255)
        self.spinRed.setRange(0, 255)

        self.dialGreen.valueChanged.connect(self.spinGreen.setValue)
        self.spinGreen.valueChanged.connect(self.dialGreen.setValue)
        self.dialGreen.setRange(0, 255)
        self.spinGreen.setRange(0, 255)

        self.dialBlue.valueChanged.connect(self.spinBlue.setValue)
        self.spinBlue.valueChanged.connect(self.dialBlue.setValue)
        self.dialBlue.setRange(0, 255)
        self.spinBlue.setRange(0, 255)

        #link dials to functions
        self.dialRed.valueChanged.connect(self.redValue)
        self.dialGreen.valueChanged.connect(self.greenValue)
        self.dialBlue.valueChanged.connect(self.blueValue)

    # fuction to act when the red dial is changed
    def redValue(self):
        num = self.dialRed.value()
        dataToSend = "<RED," + str(num) + ">"
        value = write_read(dataToSend.encode())
        PCB.write(dataToSend.encode())
        print(value)

    # fuction to act when the green dial is changed
    def greenValue(self):
        num = self.dialGreen.value()
        dataToSend = "<GREEN," + str(num) + ">"
        value = write_read(dataToSend.encode())
        PCB.write(dataToSend.encode())
        print(value)

    # fuction to act when the blue dial is changed
    def blueValue(self):
        num = self.dialBlue.value()
        dataToSend = "<BLUE," + str(num) + ">"
        value = write_read(dataToSend.encode())
        PCB.write(dataToSend.encode())
        print(value)

# function to start the window object
def window():
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    app.exec()


#MainCode
window()

Assignment Arduino code

#define redPin 5
#define greenPin 6
#define bluePin 3

const byte numLEDs = 2;
byte ledPin[numLEDs] = {12, 13};
unsigned long LEDinterval[numLEDs] = {200, 400};
unsigned long prevLEDmillis[numLEDs] = {0, 0};

const byte buffSize = 40;
char inputBuffer[buffSize];
const char startMarker = '<';
const char endMarker = '>';
byte bytesRecvd = 0;
boolean readInProgress = false;
boolean newDataFromPC = false;

char messageFromPC[buffSize] = {0};
int newValue = 0;


struct RGB{
      byte r;
      byte g;
      byte b;
    };


void setup() {
  Serial.begin(9600);   // Initiate a serial communication
  //Serial.setTimeout(1);

  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);

  digitalWrite(redPin, HIGH);    
  digitalWrite(greenPin, HIGH);    
  digitalWrite(bluePin, HIGH);

}

void loop() {
// put your main code here, to run repeatedly:
  getDataFromPC();
  replyToPC();
  updateValue();
  replyToPC();

}

//=============

void getDataFromPC() {

    // receive data from PC and save it into inputBuffer

  if(Serial.available() > 0) {

    char x = Serial.read();

      // the order of these IF clauses is significant

    if (x == endMarker) {
      readInProgress = false;
      newDataFromPC = true;
      inputBuffer[bytesRecvd] = 0;
      parseData();
    }

    if(readInProgress) {
      inputBuffer[bytesRecvd] = x;
      bytesRecvd ++;
      if (bytesRecvd == buffSize) {
        bytesRecvd = buffSize - 1;
      }
    }

    if (x == startMarker) { 
      bytesRecvd = 0; 
      readInProgress = true;
    }
  }
}

//=============

//=============

void parseData() {

    // split the data into its parts

  char * strtokIndx; // this is used by strtok() as an index

  strtokIndx = strtok(inputBuffer,",");      // get the first part - the string
  strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC

  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  newValue = atoi(strtokIndx);     // convert this part to an integer


}

//=============

void replyToPC() {

  if (newDataFromPC) {
    newDataFromPC = false;
    Serial.print("<Msg ");
    Serial.print(messageFromPC);
    Serial.print(" recieved");
    Serial.print(">");

  }
}

//============

void updateValue() {

   // this illustrates using different inputs to call different functions
  if (strcmp(messageFromPC, "RED") == 0) {
    analogWrite(redPin, (255 - newValue));
  }

  if (strcmp(messageFromPC, "GREEN") == 0) {
    analogWrite(greenPin, (255 - newValue));
  }

  if (strcmp(messageFromPC, "BLUE") == 0) {
    analogWrite(bluePin, (255 - newValue));
  }
}

Last update: June 16, 2021