Table of Contents

Objectives Training Group Assignment Individual 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:

    Group Assignment

    As a group, we explored Kodular, Pygame, Processing, WebSerial (Java Script), & Flutter

    To read more, click here

    Individual Assignment

    Controlling a Servo via Web Serial

    Creating a Webpage

    HTML file for webpage

    
                <!DOCTYPE html>
                <html>
                    <head> <h1>Web Serial Servo Controller </head>
                    <style>
                        body{
                            font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
                            background-color: rgb(103, 197, 197);
                            display: flex;
                            flex-direction:column;
                            justify-content: center;
                            align-items:center;
                            height:100vh;
                            margin:0;
    
                        } 
                        button {
                            padding: 10px 20px;
                            margin:10px;
                            background-color:green;
                            color: white;
                            border-radius: 5px;
                            cursor:pointer;
                        }
    
                        .textarea{
                            margin:auto;
                        }
    
                    </style>
    
                <body>
                    <div>
                    <button id="ConnBn">Connect
                    </div>
                    <div style="margin-auto">  
                    <textarea id="SerialOut" style="margin-auto;" rows="10" cols ="30" disabled>     
                    </div>
                    <div>
                    <input type="range" min="0" max="180" value="0" id="Slider">
                    <span id="aDisplay" style = "background-color: #e0e0e0;">000
                    </div>
    
                    <script>
                        const connectBtn = document.getElementById('ConnBn');
                        const dataSlider = document.getElementById('Slider');
                        const angleDisplay = document.getElementById('aDisplay');  
                        
                        // angleDisplay.addListenerEvent('input',sendAngle);
    
                        let port
                        let writer
                        let reader
                        let toggleConnect = false
    
                        connectBtn.addEventListener('click',connectToSerial);
                        dataSlider.addEventListener('input',sendAngle);
                            
                        async function connectToSerial(){
                            if (!toggleConnect){
                                try{
                                    port = await navigator.serial.requestPort()
                                    await port.open({baudRate:9600});
                                    writer = port.writable.getWriter();
                                    reader = port.readable.getReader();  
                                    sendData();
                                    connectBtn.style.backgroundColor = "red";
                                    connectBtn.textContent = "Disconnect";
                                    toggleConnect=!toggleConnect
                                    //Send message to Serial Monitor saying connection successful.
                                }
                                catch(error){
                                    console.error("Error, unable to open serial port", error);
                                }
                            }
                            else{
                                try{
                                    await writer.releaseLock();
                                    await reader.releaseLock();
                                    await port.close();
                                    //remove reference as a fail safe
                                    writer=null;
                                    reader=null;
                                    port=null;
                                    connectBtn.style.backgroundColor = "green";
                                    connectBtn.textContent = "Connect";
                                    toggleConnect=!toggleConnect;
                                    //Send message to Serial Monitor saying disconnection successful.
                                }
                                catch(error){
                                    console.error("Error, unable to close serial port", error);
                                }   
                            }
                            
                        }
                    
                        async function sendAngle(){
                            sendData(dataSlider.value)
                            angleDisplay.textContent=dataSlider.value.padStart(3,"0");
                        }
                    
                        function sendData(value = dataSlider.value){
                            const encoder = new TextEncoder();
                            if (!writer) return;
                            
                            value = value.toString().padStart(3,"0");
                            writer
                                .write(encoder.encode(value))
                                .then (()=>{
                                    console.log("Sent: " + value);
                                    document.getElementById('SerialOut').scrollTop= document.getElementById('SerialOut').scrollHeight;
                                });
                                const end = '\n'
                                writer.write(encoder.encode(end));
                                document.getElementById('SerialOut').textContent+="Servo 1 Angle: " + value + '\n';
                            }
    
                    </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 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. 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
                }
            }
                            

    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

    Design Files

    Click here to access the design files