Table of Contents

Objectives Training Individual Assignment Group Assignment Conclusion References Design Files

Week 14: Interface & Application Programming

Objectives

Training

Using Java Script to perform Document Object Model (DOM) Manipulation

First our instructor, Midlaj helped us undertand how to use Javascript. I learnt to use document.getElementById and .addEventListener able to use Java script to use buttons and for updating text in real time as shown here


                            <!DOCTYPE html>
                            <html lang="en">
                            <head>
                                <meta charset="UTF-8">
                                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                                <title>Document
                            </head>
                            <body>
                                <h1>My name is 
                                <p id="Hello">

<input id="type" placeholder="Enter your name"> <button id="button">Click Here <script> const button = document.getElementById('button'); const type = document.getElementById('type'); const Hello = document.getElementById('Hello'); const name= document.getElementById('name'); button.addEventListener('click', function(Clicktext){ Hello.textContent='Hello There'; }); type.addEventListener('input', function(Update){ name.textContent=type.value; const nameLen = name.textContent.length; if (nameLen>=5 && nameLen<=10){ name.style.color="red"; } else if (nameLen>10){ name.style.color="blue"; } else{ name.style.color="black"; } }) </script> </body> </html>

To speed up my process of learning, I used the help of ChatGPT

Connecting to ESP32 via Web Serial

This is the JavaScript code used for the interface:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">                                                          
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous"/> 
        <!--Link to stylesheet to add bulb icon, crossorigin required to load bulb icon from different domain without credentials -->
    </head>
    <body>
    <h1>Web Serial</h1>
    <button id="connect">Connect</button>  <!-- Adding a button to Connect to USB port -->

    <button id="toggle"><i class="fa-solid fa-lightbulb" style="color:#000000"></i></button> <!-- Adding a toggle button to switch ON and OFF an inbuilt LED in PCB -->


    <div>  <!-- Required for proper alignment of visual elements-->
        <textarea id="SerialOut" rows="10" cols ="30" disabled></textarea> <!-- Serial Monitor where we can read and write data-->
    </div>

    <input type ="type" id="input" placeholder="Enter data here."> <!-- Text box where we can read and write data-->

    <button id="upload">Upload</button> <!-- Button that we can press to upload user input written in input box above-->

        <script>
            //defining variables
            let port;
            let reader;
            let writer;
            let toggle;

            //defining user input textbox, 'Connect' and 'Upload' buttons as different variables. 
            const Connect = document.getElementById('connect');
            
            const addUser = document.getElementById('input');
            const Upload = document.getElementById('upload');
            const toggleBtn = document.getElementById('toggle');

            Connect.addEventListener('click', connectToSerial); //on clicking 'Connect' button, run connecttoSerial function
            Upload.addEventListener('click', uploadMessage); //on clicking 'Upload' button, run uploadMessage function
            toggleBtn.addEventListener('click',toggleLED); //on clicking, LED switches between OFF and ON
            
            async function connectToSerial(){
                port = await navigator.serial.requestPort(); //Wait until a port has been chosen
                await port.open({baudRate:9600}); //Keep connection via port open at baud rate of 9600

                writer = port.writable.getWriter(); //get writer object
                reader = port.readable.getReader(); //get reader object
                const decoder = new TextDecoder(); //get decoder object from TextDecoder class that can convert binary code to human readable format

                while(true){
                    const {value,done}= await reader.read(); //keep reading until data stream has been stopped; extract the data and store in 'value', and whether data stream has been stopped in 'done' variable
                    if (done) break; //if data stream has been stopped; 'done' = true, then exit the while loop
                    const string = decoder.decode(value); //convert the data contained in value to a string constant
                    document.getElementById('SerialOut').textContent+=string;  //add string constant data to text content in <textarea>
                }
            }

            async function uploadMessage(){
                let userInput = addUser.value; //store user input written in input box as data in a variable 'userInput'
                if (!writer) return; //if writer is not found then exit the function

                const encoder = new TextEncoder(); //get new encoder object that can convert human readable text to machine code

                await writer.write(encoder.encode(userInput+'\n')); //keep writing until entire data has been written; write to microncontroller; \n tells the ESP32 that it is the end of the message.
                // document.getElementById('input').value=""; 
                addUser.value=""; //clear the textbox so that user can add new input.
            }

            async function toggleLED(){
                if (!writer) return; //if writer does not exist, return

                const dataToSend = toggle ? '1' : '0' ; //if toggle is true, dataToSend ='1'; else datatoSend='0'
                const encoder = new TextEncoder(); //get new encoder object that can convert human readable text to machine code 

                await writer.write(encoder.encode(dataToSend)); //wait until writer has written dataToSend as machine code
        
                if (dataToSend === '0'){
                    toggleBtn.innerHTML=`<i class="fa-solid fa-lightbulb" style="color:#000000"></i>`; // if dataToSend matches value and datatype of string value of 0, then update bulb icon to black to signify LED is OFF.
                }else{
                    toggleBtn.innerHTML=`<i class="fa-solid fa-lightbulb" style="color:yellow"></i>`; // if dataToSend matches value and datatype of string value of 1, then update bulb icon to black to signify LED is ON.
                }
                toggle=!toggle; //flips state of toggle, ie. if toggle was 'true', now toggle is set to 'false' & viceversa.
            }
        </script>
    </body>
    </html>
                        

This is the code for the Xiao ESP32 S3, programmed in Arduino IDE

    #define LED D3
    char user;
    
    void setup() {
        pinMode(LED,OUTPUT);
        Serial.begin(9600);
    }
    
    void loop() {
        user = Serial.read();
    
        if (user == '1') {
        digitalWrite(LED, HIGH);
        Serial.println("ON");
        } 
        
        else if (user == '0') {
        digitalWrite(LED, LOW);
        Serial.println("OFF");
        }
    }   
                        

Chat GPT prompts I used to learn to write this piece of code are listed below:

Individual Assignment

Controlling a Servo via Web Serial

Web Serial API provides a way for websites to read from and write to serial devices (which includes the input/output/networking devices we made during past weeks) that are connected to a computerc (via USB in our case).

Note that Web Serial only works in Chromium browsers like Microsoft Edge, Google Chrome, etc (See Mistakes and Solutions)

Creating a Webpage

HTML file for webpage


            <!DOCTYPE html>
            <html>
                <head> <h1>Web Serial Servo Controller </head>
                
                <!--Styling for webpage -->
                <style>
                    body{
                        font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; /* Sets the font */
                        background-color: rgb(103, 197, 197); /* Sets the background color */
                        display: flex; /* Uses Flexbox for layout */
                        flex-direction:column;
                        justify-content: center;
                        align-items:center;
                        height:100vh;
                        margin:0;

                    }
                    /* Styling for button */
                    button {
                        padding: 10px 20px;
                        margin:10px;
                        background-color:green;
                        color: white;
                        border-radius: 5px;
                        cursor:pointer;
                    }

                    .textarea{
                        margin:auto;  /* Centers the textarea */
                    }

                </style>

            <body>
                <div>
                <button id="ConnBn">Connect</button>  /* Button to connect/disconnect serial  */
                </div>
                <div style="margin-auto">  /* Required for proper alignment of visual elements */
                <textarea id="SerialOut" style="margin-auto;" rows="10" cols ="30" disabled></textarea> /* Serial Monitor where we can read and write data */   
                </div>
                <div>
                <input type="range" min="0" max="180" value="0" id="Slider"> /* Slider for servo angle */
                <span id="aDisplay" style = "background-color: #e0e0e0;">000 /* Shows current angle */
                </div>

                <script>
                    /* Get references to DOM elements */
                    const connectBtn = document.getElementById('ConnBn');
                    const dataSlider = document.getElementById('Slider');
                    const angleDisplay = document.getElementById('aDisplay');  
                    
                    // angleDisplay.addListenerEvent('input',sendAngle);

                    /* declaring variables for Serial communication */
                    let port
                    let writer
                    let reader
                    let toggleConnect = false

                    connectBtn.addEventListener('click',connectToSerial); /* When button is clicked, connect or disconnect */
                    dataSlider.addEventListener('input',sendAngle); /* When slider changes, send new angle */

                    /* Function to connect or disconnect from serial port */    
                    async function connectToSerial(){
                        if (!toggleConnect){ /* If not connected yet */
                            try{
                                port = await navigator.serial.requestPort() /* Request access to a serial port */
                                await port.open({baudRate:9600}); /* Open with 9600 baud rate */

                                /* Get reader and writer data stream */

                                writer = port.writable.getWriter();
                                reader = port.readable.getReader();  

                                sendData(); /* Send initial data */

                                /*Update button*/

                                connectBtn.style.backgroundColor = "red";  /* Change button to red */
                                connectBtn.textContent = "Disconnect";  /* Update button text to Disconnect*/ 
                                toggleConnect=!toggleConnect /* Flag indicates as connected */

                            }
                            catch(error){
                                console.error("Error, unable to open serial port", error); /*In case of error, display this message' */
                            }
                        }
                        else{ /* If already connected */
                            try{

                                /*Release locks on writer and reader */

                                await writer.releaseLock();
                                await reader.releaseLock();
                                await port.close();
                                
                                /*Remove reference as a fail safe*/

                                writer=null;
                                reader=null;
                                port=null;

                                /*Update button*/

                                connectBtn.style.backgroundColor = "green"; */Update button colour*/
                                connectBtn.textContent = "Connect"; /* Update button text*/ 
                                toggleConnect=!toggleConnect; /* Flag indicates as disconnected */
                            }
                            catch(error){
                                console.error("Error, unable to close serial port", error); /*In case error, print this message in Serial Monitor */
                            }   
                        }
                        
                    }

                    /* Function to send angle values when slider moves */
                
                    async function sendAngle(){
                        sendData(dataSlider.value) /* Send the current slider value */
                        angleDisplay.textContent=dataSlider.value.padStart(3,"0"); /* Display the angle as a 3 digit number */
                    }
                    
                    /* Function to send data to serial */

                    function sendData(value = dataSlider.value){
                        const encoder = new TextEncoder(); /*Create encoder to convert text to bytes */
                        if (!writer) return; /* Exit the function if no writer exists*/
                        
                        value = value.toString().padStart(3,"0"); /*represent as a 3 digit number */

                        writer
                            .write(encoder.encode(value)) /* Send value to serial port */
                            .then (()=>{
                                console.log("Sent: " + value);
                                document.getElementById('SerialOut').scrollTop= document.getElementById('SerialOut').scrollHeight; /*For autoscroll*/
                            });
                            const end = '\n' /* Define line break */
                            writer.write(encoder.encode(end)); /* Send newline to serial */
                            document.getElementById('SerialOut').textContent+="Servo 1 Angle: " + value + '\n'; /* Add message to serial output */
                        }

                </script>    
            </body>
            </html>
                        

Code for Xiao ESP32 S3 I programmed on the Arduino IDE

            //Firmware to serially control a servo
            #include <ESP32Servo.h>

            #define PULSEMIN 500
            #define PULSEMAX 2500
            #define SERVO_PIN D6

            Servo myServo;
            String inputString;

            void setup() {
            Serial.begin(9600);
            ESP32PWM::allocateTimer(0);
            myServo.attach (SERVO_PIN, PULSEMIN, PULSEMAX);

            Serial.println("Serial connection successful!");
            Serial.println("Type servo angle: ");
            }

            void loop() {

            if (Serial.available()){
                char digit = Serial.read();

                if (digit == '\n'){
                Serial.println(inputString);
                int servoAngle = (inputString[0]-48)*100 + (inputString[1]-48)*10 + (inputString[2]-48);
                Serial.println(servoAngle);
                myServo.write(servoAngle);
                
                inputString = "";
                }
                else{
                    inputString=inputString+digit;
                }
                }
            }
                        

Hosting using Vercel

To be able to access this webpage from any computer connected to the internet, I hosted my webpage using Vercel using the help of one of my instructors. Midlaj.

Hosting in Vercel has many advantages, including:

  • Simple and easy deployment
  • Can manage APIs without managing servers
  • You can enable automatic deployments from your repository using Git

To host via Vercel, I need to create a new Github repository and clone it like we have done before. Then I we copy paste the HTML file, rename it to index.html if not already done so and push the file into global repo, which can be accessed by clicking here.

Then link your github account to Vercel, install Vercel, and import your git repo

While deploying I dont have to use any specific framework preset, so I chose Other

This is what a succesful deployment should look like


I have deployed the site here

Using Processing to make a Servo Controller

Processing is an open-source programming language and developmet environment initiated by Ben Fry and Casey Reas in 2001. Processing programming language is based off of Java for the purpose of creating visual graphics and animations for designers and creative professionals easily. To download Processing IDE click here

p5Control library is a graphical user interface (GUI) library used to create different kinds of controllers, including buttons, sliders, knobs, etc. To downloaded p5 Control library, go to Processing IDE > Sketch > Import Library > Manage Library, and type in p5, and click Download

A big portion of the code I used is based off off this instructable that discusses how to control Servo Motors with Processing and Arduino IDE, among other sources. I have listed all references in References

This is the code I used in the Processing IDE


    import processing.serial.*; //Importing Processing library
    import controlP5.*; //Importing controlP5 library
    Knob KnobX; //call KnobX object from Knob class
    Knob KnobY; //call KnobY object from Knob class
    int xpos; // set xpos integer
    int ypos; // set ypos integer

    ControlP5 cp5; //create object cp5 from class ControlP5
    Serial myPort; //create object myPort from class Serial 

    void setup(){
    size(500,500); //set size of canvas/window
    frameRate(100); //set frame rate for animation
    printArray(Serial.list());
    myPort=new Serial(this, Serial.list()[4], 9600);
    cp5 = new ControlP5(this);
    PFont pfont = createFont("Arial",20,true); // use true/false for smooth/no-smooth
    ControlFont font = new ControlFont(pfont,15); //creating new object font from class ControlFont. Font size can be adjusted here
    
    KnobX = cp5.addKnob("Servo X")
            .setRange(0,180) //range of knob
            .setValue(0)  //set deafult value 
            .setPosition(150,25)  //set corordinates
            .setRadius(100) //adjust radius of knob
            .setNumberOfTickMarks(36) //add 36 tick marks that divide 180 degrees into 5 degree divisions
            .setTickMarkLength(10) //adjust length of tick marks
            .snapToTickMarks(true) //snap to 5 degree marks when dragging/moving pointer, angle varies in multiples of 5 degrees
            .setDragDirection(Knob.VERTICAL) //set drag direction
            .setFont(font); //adjust font size according to prespecified font size
    
    KnobY = cp5.addKnob("Servo Y")
        .setRange(0,180)
        .setValue(0)
        .setPosition(150,275)
        .setRadius(100)
        .setNumberOfTickMarks(36)
        .setTickMarkLength(10)
        .snapToTickMarks(true)
        .setDragDirection(Knob.VERTICAL)
        .setFont(font);
    }

    void draw(){
    background(0); //set background to black colour
    xpos=Math.round(KnobX.getValue()); //round pointer value in Knob X to nearest integer and store in xpos
    ypos=Math.round(KnobY.getValue()); //round pointer value in Knob Y to nearest integer and store in ypos
    myPort.write("x"); //Write 'x' as a byte through Serial Port
    //print("x");
    //print(xpos);
    myPort.write(xpos); //Write xpos as multiple bytes through Serial Port
    myPort.write("y"); //Write 'y' as a byte through Serial Port
    myPort.write(ypos); //Write ypos as multiple bytes through Serial Port
    }
                        

This is the code I uploaded to the Xiao ESP32S3 board from Output Devices Week


        #include <ESP32Servo.h>
        char xChannel='x', yChannel='y';
        Servo servoX, servoY;
        #define servoXpin D6
        #define servoYpin D7
        #define PULSEMIN 500
        #define PULSEMAX 2500
        
        char serialChar=0;
        void setup()
        {
            ESP32PWM::allocateTimer(0); //Allocate timer for pulse width modulation
            servoX.attach (servoXpin, PULSEMIN, PULSEMAX); //Set up servo X connection
            servoY.attach (servoYpin, PULSEMIN, PULSEMAX); //Set up servo Y connection
            Serial.begin(9600);  //Set up a serial connection for 9600 bps.
        }
        void loop(){
            while(Serial.available() ==0);  //Wait for a character on the serial port.
            serialChar = Serial.read();     //Returns data in ASCII format (eg: x100 is xd in ASCII)
            
            if(serialChar == xChannel){  //Check to see if the character is the servo ID (ie. x in ASCII format) for the servo X
            while(Serial.available() ==0);  //Wait for the second command byte from the serial port.
            servoX.write(Serial.read());  //Set the servoX position to the value of the second command byte (eg: d = 100) received on the serial port
            }
            else if(serialChar == yChannel){ //Check to see if the character is the servo ID (ie. x in ASCII format) for the servo Y
            while(Serial.available() <= 0);  //Wait for the second command byte from the serial port.
            servoY.write(Serial.read());   //Set the servoY position to the value of the second command byte received on the serial port
            }
        }
                        

Group Assignment

As a group, we explored using Kodular, Pygame, Processing, WebSerial (Java Script), & Flutter to make interfaces. Interfaces are a point of comtact between two different kinds of systems; human or machines, helping them communicate and exchange information. In our case us humans use these interfaces to communicate and control devices we made during previous weeks.

To read more, click here

Conclusion

Mistakes, Solutions & Tips

  • Using Serial.parseInt: sends an additional value of '0' that messed with the code logic; used Serial.read to read as bytes which I manually converted into a integer data type.
  • Web Serial incompatible with Mozilla Firefox: Web Serial is compatible with Chromium browsers, eg: Google Chrome, Microsoft Edge

References

References to help reader understand in detail

Design Files

Connecting to ESP32 via Web Serial: For design files of the PCB check Electronics Design Week

Controlling a Servo via Web Serial: For design files of the PCB check Output Devices Week

For all other code-related files, click here