Skip to content

18. Invention, intellectual property and income

Hero Shot

Assignment :

  • develop a plan for dissemination of your final project
  • prepare drafts of your summary slide (presentation.png, 1920x1080) and video clip (presentation.mp4, 1080p HTML5, < ~minute, < ~10 MB) and put them in your root directory

Finalizing the final project

The Knob

My first print of the knob was too tight for the shaft. At first, I wanted it to just fit on it but I decided to us a screw and a nut instead. I change my design a bit to have a nut inserted in it. I programmed a puse in the printing to put it in but unhopefully the Ender5 printer didn’t pause at height. I made anorther try with the prusa printer and it worked well.

knob printing paused knob mounted on the rotary encoder

Updating the electronics

I didn’t thought before to add the possibility toturn off the remote control so I added an extra Rocker Switch (different color) between the battery and the PCB. As I was redisgning the shell, I added JST connectors to facilitate the assembly. I also modified one of the JST connectors to solder it on the surface of the PCB.

modifying JST connectors modified elctroncis with on/off switch

The Shell

I redesigned the shell based on the first print and adding some space for the rocker switch wiring and for the rotary encoder. I pay attention to keep an ergonomic shape and added a cut-out for the on/off switch. Finally, I add shape to integrate the PCB and a support to constraint the battery holder and letting some space for the wires. I also profited of this redesign to add more offset for the access panel to slide more smoothly.

fusion 360 rotary side of the shell Fusion 360 switch side of the shell

The printing went quite well.

rotary side of the shell inside of the shell switch side of the shell

assembly of the shell

Wanting a better finished shell, I also printed a PET-G version of the device then sand it and cover it with epoxy resin for nice finition.

PETG print of the shell adding Epoxy on the shell

I had to sand it again because of droplets. The result was really good, the surface being really smooth but i found it’s visually not as as good as the PLA one. I should have add colors in the epoxy. I also regret a bit adding epoxy because I found the tactile sensation more pleasant with a good sanding than with a layer of epoxy. Plus I found it one day a bit broken like if someone tried to force the acces panel the wrong way.

final smooth PETG shell Broken part of the PETG shell

For that, I decided to continue with the PLA one.

Substractive design

I figured out I miss some substractive design parts and I decided to make some vinyl cutting to add informations on the device in a fancy way. In Inkscape, I designed a part for the rotary encoder and another near the rocker switches.

SVG design for interaction acceptance information SVG design for mood information

I chose colors that make good contrast with their supports. The cutting went well but I had more difficulties for the weeding, especially for te letters. I finally chose to keep the outside shape of the letters for the switches parts after failing 2 times at eeding theme properly. Despite that, the result is satisfactory and add some nice colors to the device.

cutting for interaction acceptance information on the device cutting for mood information on the knob

Debugging the rotary encoder

After a few time using my new board, I had some difficulties to have correct data from my rotary encoder. I had sometimes the counter going crazy or changing direction without touching it and sometimes. I tested the continuity with the multimeter and it seemed alright. I thought at first there was a bad connexion with programmer and decided to avoid powering the board through it. I tried to modify the debounce delay in the code, and tried other way of debouncing i found on the internet. Then, I tried to change the component but it does nothing. When studying the KY-040 board, we found with instructor there was some floating signal on the oscilloscope so I added a pull-up resistor on the board. Finally, I figured the bad signals wer worse when the rotary encoder was fixed to the shell and that could be due to constraint on the connectors. I decided to desoldered the connectors on my board and the KY-040 Board and connected them directly with wires and everything was solved. Plus, it made the assembly easier.

added resistor on KY-040 board wires soldered between KY-040 and main PCB

electronics integrated to the shell

Updating the phone version

With the rotary encoder working fine, I updated the code to have a better intrafec on phone. I removed the raw data displayed at the top and added titles above each images. I couldn’t removed the button as they’re used in the script.

#include <WiFi.h>
#include <WebServer.h>
#include <SPIFFS.h>

// Define pins for rotary encoder and button
#define pinCLK 32
#define pinDT  35
#define pinSW  34
#define pinLED 23

#define pinRocker1 25   // Rocker switch 1 connected to GPIO 25
#define pinRocker2 26   // Rocker switch 2 connected to GPIO 26
#define pinRocker3 27   // Rocker switch 3 connected to GPIO 27
#define pinRocker4 14   // Rocker switch 4 connected to GPIO 14

// Network credentials
const char* ssid = "your_ssid";
const char* password = "you_password";

// Create a web server on port 80
WebServer server(80);

// Variables to track the count and last action
int rotaryCount = 1;    // Variable to keep track of the rotary encoder count
int previousStateSW;  // Variable to store the previous state of the SW pin
int previousStateCLK; // Variable to store the previous state of the CLK pin

int previousRockerState1; // Variable to store the previous state of Rocker switch 1
int previousRockerState2; // Variable to store the previous state of Rocker switch 2
int previousRockerState3; // Variable to store the previous state of Rocker switch 3
int previousRockerState4; // Variable to store the previous state of Rocker switch 4

int activatedRockerCount = 1; // Counter for the total number of activated rocker switches

int actualStateSW_global; // Global variable to store the state of SW pin

// HTML content
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mindset Selector</title>
    <style>
        .container {
            display: flex;
            justify-content: space-around;
            margin-bottom: 20px;
            max-height: 20%;
        }
        .image-container {
            text-align: center;
        }
        .image-container img {
            max-width: 100%;
            max-height: 100px;
        }
        .btn-container {
            text-align: center;
            margin-bottom: 20px;
        }
        .btn {
            padding: 10px 20px;
            background-color: #007bff;
            color: #fff;
            border: none;
            cursor: pointer;
            margin: 0 10px;
        }
        #result-container {
            text-align: center;
            margin-top: 20px;
        }
        #result-button-container {
            text-align: center;
            margin-bottom: 20px;
        }
        #result-image {
            max-width: 50%;
            margin: 0 auto;
        }
        #result-image img {
            max-width: 100%;
            height: auto;
        }
    </style>
</head>
<body>
<div id="rotary-count">Rotary Count: </div>
<div id="rocker-count">Activated Rocker Switches: </div>
<div class="container">
    <div class="image-container">
        <h2>Mood</h2>
        <div id="image-container1">
            <img src="/03_smiley.png" alt="Image" data-value="3">
        </div>
        <div class="btn-container">
            <button class="btn" onclick="previousImage(1)">Previous</button>
            <button class="btn" onclick="nextImage(1)">Next</button>
        </div>
    </div>
    <div class="image-container">
        <h2>Interaction acceptance</h2>
        <div id="image-container2">
            <img src="/10_smiley.png" alt="Image" data-value="30">
        </div>
        <div class="btn-container">
            <button class="btn" onclick="previousImage(2)">Previous</button>
            <button class="btn" onclick="nextImage(2)">Next</button>
        </div>
    </div>
</div>

<div id="result-button-container">
    <button class="btn" onclick="addValues()">Set</button>
</div>

<div id="result-container">
    <h2>Mindset</h2>
    <div id="result-text"></div> 
    <div id="result-image"></div>
</div>

<script>
    var currentIndex1 = 2;
    var currentIndex2 = 2;
    var images1 = ["/05_smiley.png", "/04_smiley.png", "/03_smiley.png","/02_smiley.png", "/01_smiley.png"];
    var images2 = ["/50_smiley.png", "/40_smiley.png", "/30_smiley.png", "/20_smiley.png", "/10_smiley.png"];
    var values1 = [5, 4, 3, 2, 1];
    var values2 = [50, 40, 30, 20, 10];
    var allImages = [
        { src: "/smiley_mix11.png", value: 11 },
        { src: "/smiley_mix12.png", value: 12 },
        { src: "/smiley_mix13.png", value: 13 },
        { src: "/smiley_mix14.png", value: 14 },
        { src: "/smiley_mix15.png", value: 15 },
        { src: "/smiley_mix21.png", value: 21 },
        { src: "/smiley_mix22.png", value: 22 },
        { src: "/smiley_mix23.png", value: 23 },
        { src: "/smiley_mix24.png", value: 24 },
        { src: "/smiley_mix25.png", value: 25 },
        { src: "/smiley_mix31.png", value: 31 },
        { src: "/smiley_mix32.png", value: 32 },
        { src: "/smiley_mix33.png", value: 33 },
        { src: "/smiley_mix34.png", value: 34 },
        { src: "/smiley_mix35.png", value: 35 },
        { src: "/smiley_mix41.png", value: 41 },
        { src: "/smiley_mix42.png", value: 42 },
        { src: "/smiley_mix43.png", value: 43 },
        { src: "/smiley_mix44.png", value: 44 },
        { src: "/smiley_mix45.png", value: 45 },
        { src: "/smiley_mix51.png", value: 51 },
        { src: "/smiley_mix52.png", value: 52 },
        { src: "/smiley_mix53.png", value: 53 },
        { src: "/smiley_mix54.png", value: 54 },
        { src: "/smiley_mix55.png", value: 55 },
    ];

    async function fetchData() {
        try {
            const response = await fetch('/data');
            const data = await response.json();
            document.getElementById('rotary-count').innerText = "Rotary Count: " + data.rotaryCount;
            document.getElementById('rocker-count').innerText = "Activated Rocker Switches: " + data.activatedRockerCount;

            // Update currentIndex based on the response
            currentIndex1 = data.rotaryCount - 1; // Using the rotaryCount is used for currentIndex1
            currentIndex2 = data.activatedRockerCount - 1; // Using activatedRockerCount for currentIndex2
            // Check the value of triggerAddValues
                if (data.triggerAddValues) {
                    addValues(); // Call addValues function if triggerAddValues is true
                }
            console.log("Updated indices:", currentIndex1, currentIndex2); // Debugging log
            updateImage(1);
            updateImage(2);
        } catch (error) {
            console.error('Error fetching data:', error);
        }
    }

    setInterval(fetchData, 100);

    function previousImage(containerIndex) {
        if (containerIndex === 1) {
            if (currentIndex1 === 0) return;
            currentIndex1--;
            updateImage(1);
        } else if (containerIndex === 2) {
            if (currentIndex2 === 0) return;
            currentIndex2--;
            updateImage(2);
        }
    }

    function nextImage(containerIndex) {
        if (containerIndex === 1) {
            if (currentIndex1 === images1.length - 1) return;
            currentIndex1++;
            updateImage(1);
        } else if (containerIndex === 2) {
            if (currentIndex2 === images2.length - 1) return;
            currentIndex2++;
            updateImage(2);
        }
    }

    function updateImage(containerIndex) {
        var displayedImage;
        if (containerIndex === 1) {
            displayedImage = document.getElementById("image-container1").getElementsByTagName("img")[0];
            displayedImage.src = images1[currentIndex1];
            displayedImage.dataset.value = values1[currentIndex1];
        } else if (containerIndex === 2) {
            displayedImage = document.getElementById("image-container2").getElementsByTagName("img")[0];
            displayedImage.src = images2[currentIndex2];
            displayedImage.dataset.value = values2[currentIndex2];
        }
    }

    function addValues() {
        var value1 = values1[currentIndex1];
        var value2 = values2[currentIndex2];
        var resultValue = value1 + value2;
        var resultImage = allImages.find(image => image.value === resultValue);
        document.getElementById("result-text").innerText = "Mindset Value: " + resultValue;
        if (resultImage) {
            document.getElementById("result-image").innerHTML = `<img src="${resultImage.src}" alt="Result Image">`;
        } else {
            document.getElementById("result-image").innerHTML = "No matching image found.";
        }
    }
</script>
</body>
</html>
)rawliteral";

// Function to initialize the SPIFFS
void initSPIFFS() {
    if (!SPIFFS.begin(true)) {
        Serial.println("An error occurred while mounting SPIFFS");
        return;
    }
    Serial.println("SPIFFS mounted successfully");
}

void setup() {
    Serial.begin(115200); // Initialize serial communication at 115200 baud rate

    pinMode(pinSW, INPUT_PULLUP); // Set SW pin as input with internal pull-up resistor
    pinMode(pinDT, INPUT);        // Set DT pin as input
    pinMode(pinCLK, INPUT);       // Set CLK pin as input
    pinMode(pinLED, OUTPUT);      // Set LED pin as output

    pinMode(pinRocker1, INPUT_PULLUP); // Set Rocker switch 1 as input with internal pull-up resistor
    pinMode(pinRocker2, INPUT_PULLUP); // Set Rocker switch 2 as input with internal pull-up resistor
    pinMode(pinRocker3, INPUT_PULLUP); // Set Rocker switch 3 as input with internal pull-up resistor
    pinMode(pinRocker4, INPUT_PULLUP); // Set Rocker switch 4 as input with internal pull-up resistor

    previousStateSW  = digitalRead(pinSW);  // Read initial state of the SW pin
    previousStateCLK = digitalRead(pinCLK); // Read initial state of the CLK pin

    previousRockerState1 = digitalRead(pinRocker1); // Read initial state of Rocker switch 1
    previousRockerState2 = digitalRead(pinRocker2); // Read initial state of Rocker switch 2
    previousRockerState3 = digitalRead(pinRocker3); // Read initial state of Rocker switch 3
    previousRockerState4 = digitalRead(pinRocker4); // Read initial state of Rocker switch 4

    analogWrite(pinLED, 0); // Initialize the LED with 0 brightness (off)

    // Initialize SPIFFS
    initSPIFFS();

    // Connect to Wi-Fi
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Connecting to WiFi...");
    }
    Serial.println("Connected to WiFi");

    // Print the IP address
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    // Define server routes
    server.on("/", []() {
        server.send_P(200, "text/html", index_html);
    });

    server.on("/data", []() {
        actualStateSW_global = digitalRead(pinSW); // Read the current state of SW pin
        bool triggerAddValues = (actualStateSW_global == LOW); // Set triggerAddValues based on actualStateSW_global
        String json = "{\"rotaryCount\":" + String(rotaryCount) + ",\"activatedRockerCount\":" + String(activatedRockerCount) + ",\"triggerAddValues\":" + (actualStateSW_global == LOW ? "true" : "false") + "}";
        server.send(200, "application/json", json);
    });

    server.serveStatic("/", SPIFFS, "/");

    server.begin();
    Serial.println("HTTP server started");
}

void loop() {
    server.handleClient();

    int actualStateCLK = digitalRead(pinCLK); // Read current state of the CLK pin
    int actualStateDT  = digitalRead(pinDT);  // Read current state of the DT pin
    int actualStateSW  = digitalRead(pinSW);  // Read current state of the SW pin

    // Read current states of rocker switches again after debounce delay
    int currentRockerState1 = digitalRead(pinRocker1);
    int currentRockerState2 = digitalRead(pinRocker2);
    int currentRockerState3 = digitalRead(pinRocker3);
    int currentRockerState4 = digitalRead(pinRocker4);

    // Check if the state has changed and is stable
    if (currentRockerState1 != previousRockerState1 || 
        currentRockerState2 != previousRockerState2 || 
        currentRockerState3 != previousRockerState3 || 
        currentRockerState4 != previousRockerState4) {
        // Debounce delay for rocker switches
        delay(50);
        // Update activatedRockerCount only if there is a change in any rocker switch state
        activatedRockerCount = 1;
        if (currentRockerState1 == LOW) {
            activatedRockerCount++;
        }
        if (currentRockerState2 == LOW) {
            activatedRockerCount++;
        }
        if (currentRockerState3 == LOW) {
            activatedRockerCount++;
        }
        if (currentRockerState4 == LOW) {
            activatedRockerCount++;
        }

        // Print the number of activated rocker switches to the Serial Monitor
        Serial.print(F("Activated rocker switches: "));
        Serial.println(activatedRockerCount);

        // Update previous rocker states
        previousRockerState1 = currentRockerState1;
        previousRockerState2 = currentRockerState2;
        previousRockerState3 = currentRockerState3;
        previousRockerState4 = currentRockerState4;
    }

    // Push Button verification
    if (actualStateSW != previousStateSW) { // Check if SW state has changed
        previousStateSW = actualStateSW; // Update the previous SW state
        delay(50); // Debounce delay
        if (actualStateSW == LOW) {
            Serial.println(F("SW Button pushed")); // Print message when button is pressed
        } else {
            Serial.println(F("SW Button released")); // Print message when button is released
        }    
    }

    // Rotary encoder verification

    if (rotaryCount < 1) {
        rotaryCount = 1;
    } else if (rotaryCount > 5) {
        rotaryCount = 5;
    }

    if (actualStateCLK != previousStateCLK) {
        delayMicroseconds(200); // Debounce delay
        if (digitalRead(pinCLK) == actualStateCLK) { // Check if the CLK signal has settled
            previousStateCLK = actualStateCLK;

            if (actualStateCLK == LOW) {
                if (actualStateCLK != actualStateDT) { // Comparing CLK and DT, if they're different, direction is counter-clockwise
                    rotaryCount--; // Decrement counter
                    Serial.println(F("counter-clockwise")); // Display value on Serial Monitor
                } else { // When CLK and DT are similar, direction is clockwise
                    rotaryCount++; // Increment counter
                    Serial.println(F("clockwise")); // Display value on Serial Monitor
                }
                Serial.println(rotaryCount);

                // Ensure count stays within bounds (0-5 for PWM)
                if (rotaryCount < 1) {
                    rotaryCount = 1;
                } else if (rotaryCount > 5) {
                    rotaryCount = 5;
                }
                // Map rotaryCount to PWM range (0-128)
                int pwmValue = map(rotaryCount, 1, 5, 0, 128);
                analogWrite(pinLED, pwmValue); // Set LED brightness
            }
        }
    }
}
#include <WiFi.h>
#include <WebServer.h>
#include <SPIFFS.h>

// Define pins for rotary encoder and button
#define pinCLK 32
#define pinDT  35
#define pinSW  34
#define pinLED 23

#define pinRocker1 25   // Rocker switch 1 connected to GPIO 25
#define pinRocker2 26   // Rocker switch 2 connected to GPIO 26
#define pinRocker3 27   // Rocker switch 3 connected to GPIO 27
#define pinRocker4 14   // Rocker switch 4 connected to GPIO 14

// Network credentials
const char* ssid = "your-ssid";
const char* password = "your_password";

// Create a web server on port 80
WebServer server(80);

// Variables to track the count and last action
int rotaryCount = 1;    // Variable to keep track of the rotary encoder count
int previousStateSW;  // Variable to store the previous state of the SW pin
int previousStateCLK; // Variable to store the previous state of the CLK pin

int previousRockerState1; // Variable to store the previous state of Rocker switch 1
int previousRockerState2; // Variable to store the previous state of Rocker switch 2
int previousRockerState3; // Variable to store the previous state of Rocker switch 3
int previousRockerState4; // Variable to store the previous state of Rocker switch 4

int activatedRockerCount = 1; // Counter for the total number of activated rocker switches

int actualStateSW_global; // Global variable to store the state of SW pin

// HTML content
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mindset Selector</title>
    <style>
        .container {
            display: flex;
            justify-content: space-around;
            margin-bottom: 20px;
            max-height: 20%;
        }
        .image-container {
            text-align: center;
        }
        .image-container img {
            max-width: 100%;
            max-height: 100px;
        }
        .btn-container {
            text-align: center;
            margin-bottom: 20px;
        }
        .btn {
            padding: 10px 20px;
            background-color: #007bff;
            color: #fff;
            border: none;
            cursor: pointer;
            margin: 0 10px;
        }
        #result-container {
            text-align: center;
            margin-top: 20px;
        }
        #result-button-container {
            text-align: center;
            margin-bottom: 20px;
        }
        #result-image {
            max-width: 50%;
            margin: 0 auto;
        }
        #result-image img {
            max-width: 100%;
            height: auto;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="image-container">
        <h2>Mood</h2>
        <div id="image-container1">
            <img src="/03_smiley.png" alt="Image" data-value="3">
        </div>
        <div class="btn-container">
            <button class="btn" onclick="previousImage(1)">Previous</button>
            <button class="btn" onclick="nextImage(1)">Next</button>
        </div>
    </div>
    <div class="image-container">
        <h2>Interaction acceptance</h2>
        <div id="image-container2">
            <img src="/10_smiley.png" alt="Image" data-value="30">
        </div>
        <div class="btn-container">
            <button class="btn" onclick="previousImage(2)">Previous</button>
            <button class="btn" onclick="nextImage(2)">Next</button>
        </div>
    </div>
</div>

<div id="result-button-container">
    <button class="btn" onclick="addValues()">Set</button>
</div>

<div id="result-container">
    <h2>Mindset</h2>
    <div id="result-image"></div>
</div>

<script>
    var currentIndex1 = 2;
    var currentIndex2 = 2;
    var images1 = ["/05_smiley.png", "/04_smiley.png", "/03_smiley.png","/02_smiley.png", "/01_smiley.png"];
    var images2 = ["/50_smiley.png", "/40_smiley.png", "/30_smiley.png", "/20_smiley.png", "/10_smiley.png"];
    var values1 = [5, 4, 3, 2, 1];
    var values2 = [50, 40, 30, 20, 10];
    var allImages = [
        { src: "/smiley_mix11.png", value: 11 },
        { src: "/smiley_mix12.png", value: 12 },
        { src: "/smiley_mix13.png", value: 13 },
        { src: "/smiley_mix14.png", value: 14 },
        { src: "/smiley_mix15.png", value: 15 },
        { src: "/smiley_mix21.png", value: 21 },
        { src: "/smiley_mix22.png", value: 22 },
        { src: "/smiley_mix23.png", value: 23 },
        { src: "/smiley_mix24.png", value: 24 },
        { src: "/smiley_mix25.png", value: 25 },
        { src: "/smiley_mix31.png", value: 31 },
        { src: "/smiley_mix32.png", value: 32 },
        { src: "/smiley_mix33.png", value: 33 },
        { src: "/smiley_mix34.png", value: 34 },
        { src: "/smiley_mix35.png", value: 35 },
        { src: "/smiley_mix41.png", value: 41 },
        { src: "/smiley_mix42.png", value: 42 },
        { src: "/smiley_mix43.png", value: 43 },
        { src: "/smiley_mix44.png", value: 44 },
        { src: "/smiley_mix45.png", value: 45 },
        { src: "/smiley_mix51.png", value: 51 },
        { src: "/smiley_mix52.png", value: 52 },
        { src: "/smiley_mix53.png", value: 53 },
        { src: "/smiley_mix54.png", value: 54 },
        { src: "/smiley_mix55.png", value: 55 },
    ];

    async function fetchData() {
        try {
            const response = await fetch('/data');
            const data = await response.json();

            // Update currentIndex based on the response
            currentIndex1 = data.rotaryCount - 1; // Using the rotaryCount for currentIndex1
            currentIndex2 = data.activatedRockerCount - 1; // Using activatedRockerCount for currentIndex2
            // Check the value of triggerAddValues
            if (data.triggerAddValues) {
                addValues(); // Call addValues function if triggerAddValues is true
            }
            console.log("Updated indices:", currentIndex1, currentIndex2); // Debugging log
            updateImage(1);
            updateImage(2);
        } catch (error) {
            console.error('Error fetching data:', error);
        }
    }

    setInterval(fetchData, 100);

    function previousImage(containerIndex) {
        if (containerIndex === 1) {
            if (currentIndex1 === 0) return;
            currentIndex1--;
            updateImage(1);
        } else if (containerIndex === 2) {
            if (currentIndex2 === 0) return;
            currentIndex2--;
            updateImage(2);
        }
    }

    function nextImage(containerIndex) {
        if (containerIndex === 1) {
            if (currentIndex1 === images1.length - 1) return;
            currentIndex1++;
            updateImage(1);
        } else if (containerIndex === 2) {
            if (currentIndex2 === images2.length - 1) return;
            currentIndex2++;
            updateImage(2);
        }
    }

    function updateImage(containerIndex) {
        var displayedImage;
        if (containerIndex === 1) {
            displayedImage = document.getElementById("image-container1").getElementsByTagName("img")[0];
            displayedImage.src = images1[currentIndex1];
            displayedImage.dataset.value = values1[currentIndex1];
        } else if (containerIndex === 2) {
            displayedImage = document.getElementById("image-container2").getElementsByTagName("img")[0];
            displayedImage.src = images2[currentIndex2];
            displayedImage.dataset.value = values2[currentIndex2];
        }
    }

    function addValues() {
        var value1 = values1[currentIndex1];
        var value2 = values2[currentIndex2];
        var resultValue = value1 + value2;
        var resultImage = allImages.find(image => image.value === resultValue);
        if (resultImage) {
            document.getElementById("result-image").innerHTML = `<img src="${resultImage.src}" alt="Result Image">`;
        } else {
            document.getElementById("result-image").innerHTML = "No matching image found.";
        }
    }
</script>
</body>
</html>
)rawliteral";

// Function to initialize the SPIFFS
void initSPIFFS() {
    if (!SPIFFS.begin(true)) {
        Serial.println("An error occurred while mounting SPIFFS");
        return;
    }
    Serial.println("SPIFFS mounted successfully");
}

void setup() {
    Serial.begin(115200); // Initialize serial communication at 115200 baud rate

    pinMode(pinSW, INPUT_PULLUP); // Set SW pin as input with internal pull-up resistor
    pinMode(pinDT, INPUT);        // Set DT pin as input
    pinMode(pinCLK, INPUT);       // Set CLK pin as input
    pinMode(pinLED, OUTPUT);      // Set LED pin as output

    pinMode(pinRocker1, INPUT_PULLUP); // Set Rocker switch 1 as input with internal pull-up resistor
    pinMode(pinRocker2, INPUT_PULLUP); // Set Rocker switch 2 as input with internal pull-up resistor
    pinMode(pinRocker3, INPUT_PULLUP); // Set Rocker switch 3 as input with internal pull-up resistor
    pinMode(pinRocker4, INPUT_PULLUP); // Set Rocker switch 4 as input with internal pull-up resistor

    previousStateSW  = digitalRead(pinSW);  // Read initial state of the SW pin
    previousStateCLK = digitalRead(pinCLK); // Read initial state of the CLK pin

    previousRockerState1 = digitalRead(pinRocker1); // Read initial state of Rocker switch 1
    previousRockerState2 = digitalRead(pinRocker2); // Read initial state of Rocker switch 2
    previousRockerState3 = digitalRead(pinRocker3); // Read initial state of Rocker switch 3
    previousRockerState4 = digitalRead(pinRocker4); // Read initial state of Rocker switch 4

    analogWrite(pinLED, 0); // Initialize the LED with 0 brightness (off)

    // Initialize SPIFFS
    initSPIFFS();

    // Connect to Wi-Fi
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Connecting to WiFi...");
    }
    Serial.println("Connected to WiFi");

    // Print the IP address
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    // Define server routes
    server.on("/", []() {
        server.send_P(200, "text/html", index_html);
    });

    server.on("/data", []() {
        actualStateSW_global = digitalRead(pinSW); // Read the current state of SW pin
        bool triggerAddValues = (actualStateSW_global == LOW); // Set triggerAddValues based on actualStateSW_global
        String json = "{\"rotaryCount\":" + String(rotaryCount) + ",\"activatedRockerCount\":" + String(activatedRockerCount) + ",\"triggerAddValues\":" + (actualStateSW_global == LOW ? "true" : "false") + "}";
        server.send(200, "application/json", json);
    });

    server.serveStatic("/", SPIFFS, "/");

    server.begin();
    Serial.println("HTTP server started");
}

void loop() {
    server.handleClient();

    int actualStateCLK = digitalRead(pinCLK); // Read current state of the CLK pin
    int actualStateDT  = digitalRead(pinDT);  // Read current state of the DT pin
    int actualStateSW  = digitalRead(pinSW);  // Read current state of the SW pin

    // Read current states of rocker switches again after debounce delay
    int currentRockerState1 = digitalRead(pinRocker1);
    int currentRockerState2 = digitalRead(pinRocker2);
    int currentRockerState3 = digitalRead(pinRocker3);
    int currentRockerState4 = digitalRead(pinRocker4);

    // Check if the state has changed and is stable
    if (currentRockerState1 != previousRockerState1 || 
        currentRockerState2 != previousRockerState2 || 
        currentRockerState3 != previousRockerState3 || 
        currentRockerState4 != previousRockerState4) {
        // Debounce delay for rocker switches
        delay(50);
        // Update activatedRockerCount only if there is a change in any rocker switch state
        activatedRockerCount = 1;
        if (currentRockerState1 == LOW) {
            activatedRockerCount++;
        }
        if (currentRockerState2 == LOW) {
            activatedRockerCount++;
        }
        if (currentRockerState3 == LOW) {
            activatedRockerCount++;
        }
        if (currentRockerState4 == LOW) {
            activatedRockerCount++;
        }

        // Print the number of activated rocker switches to the Serial Monitor
        Serial.print(F("Activated rocker switches: "));
        Serial.println(activatedRockerCount);

        // Update previous rocker states
        previousRockerState1 = currentRockerState1;
        previousRockerState2 = currentRockerState2;
        previousRockerState3 = currentRockerState3;
        previousRockerState4 = currentRockerState4;
    }

    // Push Button verification
    if (actualStateSW != previousStateSW) { // Check if SW state has changed
        previousStateSW = actualStateSW; // Update the previous SW state
        delay(50); // Debounce delay
        if (actualStateSW == LOW) {
            Serial.println(F("SW Button pushed")); // Print message when button is pressed
        } else {
            Serial.println(F("SW Button released")); // Print message when button is released
        }    
    }

    // Rotary encoder verification

    if (rotaryCount < 1) {
        rotaryCount = 1;
    } else if (rotaryCount > 5) {
        rotaryCount = 5;
    }

    if (actualStateCLK != previousStateCLK) {
        delayMicroseconds(200); // Debounce delay
        if (digitalRead(pinCLK) == actualStateCLK) { // Check if the CLK signal has settled
            previousStateCLK = actualStateCLK;

            if (actualStateCLK == LOW) {
                if (actualStateCLK != actualStateDT) { // Comparing CLK and DT, if they're different, direction is counter-clockwise
                    rotaryCount--; // Decrement counter
                    Serial.println(F("counter-clockwise")); // Display value on Serial Monitor
                } else { // When CLK and DT are similar, direction is clockwise
                    rotaryCount++; // Increment counter
                    Serial.println(F("clockwise")); // Display value on Serial Monitor
                }
                Serial.println(rotaryCount);

                // Ensure count stays within bounds (0-5 for PWM)
                if (rotaryCount < 1) {
                    rotaryCount = 1;
                } else if (rotaryCount > 5) {
                    rotaryCount = 5;
                }
                // Map rotaryCount to PWM range (0-128)
                int pwmValue = map(rotaryCount, 1, 5, 0, 128);
                analogWrite(pinLED, pwmValue); // Set LED brightness
            }
        }
    }
}

TFT screen display

Now that the remote control worked properly and I could display and control an interface on phone, the bare minimum for my final project was ready. I decided to use the time left to try to make it work on a dedicated screen, the ESP32-2432S028 or Cheap-yellow-display I tested during week 13.

I wanted at first to display the webpage on the screen but it didn’t seem possible or I haven’t find how. Best solution seemed to be displaying images and make them update.

I searched for examples to display images on it and find this site Test for cheap yellow display. I used the first part of it to understand how to get access to the SD Card using SD.h library and how to add Wifi config in it but as I will use ESP-Now I will not need that feature. I also could access to images on the SD card but struggled a bit to display them. Using JpegDecoder Library and with the help of chatGPT, I found out how to display an image, then display multiple images dispatched on the same screen. To make it easier, I had redimensionned all images to fit part of the screen size and reupload them on the SD card. So base emojis are about half the width of the screen and the mixed one are about 2/3 of the length.

getting access to SD card display multiple images - one aligned to the bottom display multiple images - different x attributes display multiple images - all well dispatched

Then I reimplement arrays of images with values and calculation of value in C++ version because javascript don’t work .

I wanted to add touchscreen to navigate in the arrays but when I uploaded the code, images wouldn’t display. I spend much time to debug it, trying to erase part of the code, changing orders. Finally it seems there was conflict between touchscreen and SD card using both SPI to work. If setup start SD before touchscreen, the touch was detected but it was impossible to load images and if the contrary, the touchscreen seem to constantly being pushed and the images were uploaded.

I decided to abandonned using the touchscreen, and made a first code that automatically change the base images and update the mixed image to verify that the images can be changed.

With that success, I continued progressing by implementing ESP-Now communication. I used the previous test I made to implement ESPNow in my code and add the rocker switches counter reading.

I add to redo the code step by step because there was incompatibility between ESPNow Library and JPEGDecoder Library and couldn’t use the receive data as it is. The displayJpeg command couldn’t be called in the ‘void onReceive’ function used for communication. I needed to update variables that could be used to trigger other functions in the loop. I also edited a starting page and implement a clear screen when changing the value.

Here’s the evolution of the code:

#include <WiFi.h>
#include <FS.h>
#include <SPI.h>
#include <SD.h>
#include <TFT_eSPI.h>
#include <ArduinoJson.h>

struct Config {
char ssid[32];
char password[32];
char ep[32];
char port[32];
};

Config config;
const char *cfg = "/etc/config.json"; //file to create where ssid, password, ep and port are registered
TFT_eSPI tft = TFT_eSPI();

void setup() {
Serial.begin(115200);
delay(1000);

Serial.println("Starting setup...");

// TFT initialization
tft.begin();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setCursor(0, 0);
tft.setTextFont(1);
tft.setTextSize(2);
tft.setTextColor(TFT_WHITE, TFT_BLACK);

Serial.println("TFT initialized.");

// SD card initialization
if (!SD.begin()) {
    Serial.println("SD card mount failure");
    return;
}
Serial.println("SD card initialized successfully");
tft.println("SD card initialized successfully");

// Load configuration from SD card
File file = SD.open(cfg);
if (!file) {
    Serial.println("Configuration file not found");
    tft.println("Config file not found");
} else {
    Serial.println("Configuration file found");
    loadConfiguration(config);
    file.close();
}

// Attempt WiFi connection
connectToWiFi();
}

void loop() {
// Placeholder for main loop code
}

void connectToWiFi() {
Serial.println("Attempting to connect to WiFi...");
tft.println("Attempting to connect to WiFi...");

WiFi.begin(config.ssid, config.password);

unsigned long startTime = millis();
while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
    tft.print(".");
    if (millis() - startTime > 20000) { // 20 seconds timeout
    Serial.println("Failed to connect to WiFi");
    tft.println("Failed to connect to WiFi");
    return;
    }
}

Serial.println();
Serial.println("Connected to WiFi");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());

tft.println();
tft.println("Connected to WiFi");
tft.print("IP: ");
tft.println(WiFi.localIP());
}

void loadConfiguration(Config &config) {
File file = SD.open(cfg, FILE_READ);
if (!file) {
    Serial.println("Failed to open config file");
    return;
}

StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, file);
if (error) {
    Serial.print("Failed to parse config file: ");
    Serial.println(error.c_str());
    return;
}

strlcpy(config.ssid, doc["SSID"] | "", sizeof(config.ssid));
strlcpy(config.password, doc["pass"] | "", sizeof(config.password));
strlcpy(config.ep, doc["ep"] | "", sizeof(config.ep));
strlcpy(config.port, doc["port"] | "", sizeof(config.port));

Serial.println("Configuration loaded:");
Serial.print("SSID: "); Serial.println(config.ssid);
Serial.print("Password: "); Serial.println(config.password);
Serial.print("Endpoint: "); Serial.println(config.ep);
Serial.print("Port: "); Serial.println(config.port);
}
#include <FS.h>
#include <SPI.h>
#include <SD.h>
#include <TFT_eSPI.h>
#include <JPEGDecoder.h>

// File paths
const char *imageFile1 = "/images/01_smiley.jpg";
const char *imageFile2 = "/images/20_smiley.jpg";
const char *imageFile3 = "/images/smiley_mix21.jpg";

// TFT and SD
TFT_eSPI tft = TFT_eSPI();

void setup() {
Serial.begin(115200);
delay(1000);

// Initialize TFT
tft.begin();
tft.setRotation(2);  // Adjust as needed
tft.fillScreen(TFT_WHITE);

// Initialize SD card
if (!SD.begin()) {
    Serial.println("SD card initialization failed!");
    return;
}
Serial.println("SD card initialized.");

// Display images
displayJpeg(imageFile1, 0);
displayJpeg(imageFile2, 1);
displayJpeg(imageFile3, 2);
}

void loop() {
// Main loop code (if any)
}

void displayJpeg(const char *filename, int imageIndex) {
// Open the file
File jpegFile = SD.open(filename, FILE_READ);
if (!jpegFile) {
    Serial.println("Failed to open file for reading");
    return;
}

// Decode the JPEG file
if (JpegDec.decodeSdFile(jpegFile)) {
    jpegRender(imageIndex);
} else {
    Serial.println("JPEG file format not supported!");
}

// Close the file
jpegFile.close();
}

void jpegRender(int imageIndex) {
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint32_t max_x = JpegDec.width;
uint32_t max_y = JpegDec.height;

// Calculate the vertical offset
int16_t y_offset = 0;

// Calculate the horizontal offset based on the imageIndex
int16_t x_offset = 0;
if (imageIndex == 0) {
    // First image positioned at 1/4 of TFT width
    x_offset = tft.width() / 4 - max_x / 2;
} else if (imageIndex == 1) {
    // Second image positioned at 3/4 of TFT width
    x_offset = 3 * tft.width() / 4 - max_x / 2;
} else {
    // Center third image at the bottom
    x_offset = (tft.width() - max_x) / 2;
    y_offset = tft.height() - max_y;
}

bool swapBytes = tft.getSwapBytes();
tft.setSwapBytes(true);

while (JpegDec.read()) {
    pImg = JpegDec.pImage;

    int mcu_x = JpegDec.MCUx * mcu_w;
    int mcu_y = JpegDec.MCUy * mcu_h;

    uint32_t win_w = (mcu_x + mcu_w <= max_x) ? mcu_w : (max_x % mcu_w);
    uint32_t win_h = (mcu_y + mcu_h <= max_y) ? mcu_h : (max_y % mcu_h);

    if ((mcu_x + win_w) <= tft.width() && (mcu_y + win_h) <= tft.height()) {
    tft.pushImage(mcu_x + x_offset, mcu_y + y_offset, win_w, win_h, pImg);
    } else if ((mcu_y + win_h) >= tft.height()) {
    JpegDec.abort();
    }
}

tft.setSwapBytes(swapBytes);
}
#include <SPI.h>
#include <TFT_eSPI.h>
#include "ESP32_NOW.h"
#include "WiFi.h"
#include <esp_mac.h>  // For the MAC2STR and MACSTR macros
#include <vector>
#include <JPEGDecoder.h>
#include <SD.h>
#include <FS.h>

// Image paths and corresponding values
const char *images1[] = {"/images/05_smiley.jpg", "/images/04_smiley.jpg", "/images/03_smiley.jpg", "/images/02_smiley.jpg", "/images/01_smiley.jpg"};
const int values1[] = {5, 4, 3, 2, 1};

const char *images2[] = {"/images/50_smiley.jpg", "/images/40_smiley.jpg", "/images/30_smiley.jpg", "/images/20_smiley.jpg", "/images/10_smiley.jpg"};
const int values2[] = {50, 40, 30, 20, 10};

struct ImageData {
const char *src;
int value;
};

ImageData allImages[] = {
    {"/images/smiley_mix11.jpg", 11}, {"/images/smiley_mix12.jpg", 12}, {"/images/smiley_mix13.jpg", 13},
    {"/images/smiley_mix14.jpg", 14}, {"/images/smiley_mix15.jpg", 15}, {"/images/smiley_mix21.jpg", 21},
    {"/images/smiley_mix22.jpg", 22}, {"/images/smiley_mix23.jpg", 23}, {"/images/smiley_mix24.jpg", 24},
    {"/images/smiley_mix25.jpg", 25}, {"/images/smiley_mix31.jpg", 31}, {"/images/smiley_mix32.jpg", 32},
    {"/images/smiley_mix33.jpg", 33}, {"/images/smiley_mix34.jpg", 34}, {"/images/smiley_mix35.jpg", 35},
    {"/images/smiley_mix41.jpg", 41}, {"/images/smiley_mix42.jpg", 42}, {"/images/smiley_mix43.jpg", 43},
    {"/images/smiley_mix44.jpg", 44}, {"/images/smiley_mix45.jpg", 45}, {"/images/smiley_mix51.jpg", 51},
    {"/images/smiley_mix52.jpg", 52}, {"/images/smiley_mix53.jpg", 53}, {"/images/smiley_mix54.jpg", 54},
    {"/images/smiley_mix55.jpg", 55},
};

int currentIndex1 = 2;
int currentIndex2 = 2;
int currentIndexAll = 0; // Initialize the index to 0

const int numImages1 = sizeof(images1) / sizeof(images1[0]);
const int numImages2 = sizeof(images2) / sizeof(images2[0]);
const int numAllImages = sizeof(allImages) / sizeof(allImages[0]);

TFT_eSPI tft = TFT_eSPI();

/* Definitions */
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define FONT_SIZE 4

#define ESPNOW_WIFI_CHANNEL 6

bool updateDisplay = false; // Global flag to indicate display update
bool updateResult = false; // Global flag to indicate display update

/* Classes */

// Creating a new class that inherits from the ESP_NOW_Peer class is required.

class ESP_NOW_Peer_Class : public ESP_NOW_Peer {
public:
// Constructor of the class
ESP_NOW_Peer_Class(const uint8_t *mac_addr,
                    uint8_t channel,
                    wifi_interface_t iface,
                    const uint8_t *lmk)
    : ESP_NOW_Peer(mac_addr, channel, iface, lmk) {}

// Destructor of the class
~ESP_NOW_Peer_Class() {}

// Function to register the control peer
bool add_peer() {
    if (!add()) {
    log_e("Failed to register the broadcast peer");
    return false;
    }
    return true;
}

// Function to handle received messages from the control
void onReceive(const uint8_t *data, size_t len, bool broadcast) {
    Serial.printf("Received a message from control " MACSTR " (%s)\n", MAC2STR(addr()), broadcast ? "broadcast" : "unicast");
    Serial.printf("  Message: %.*s\n", len, data);  // Print the message as received

    // Try to extract the numeric part from the message
    String message = String((char *)data);
    int numberPart = message.substring(message.lastIndexOf(' ') + 1).toInt();

    if (message.startsWith("clockwise")) {
    if (numberPart >= 1 && numberPart <= 5) {
        currentIndex1 = constrain(numberPart - 1, 0, numImages1 - 1); // Update currentIndex1 based on rotary count
        Serial.printf("Updated currentIndex1 to %d\n", currentIndex1);
        updateDisplay = true; // Set flag to update display1 in the main loop
    } else {
        Serial.println("Invalid message format or value.");
    }
    } else if (message.startsWith("counter-clockwise")) {
    if (numberPart >= 1 && numberPart <= 5) {
        currentIndex1 = constrain(numberPart - 1, 0, numImages1 - 1); // Update currentIndex1 based on rotary count
        Serial.printf("Updated currentIndex1 to %d\n", currentIndex1);
        updateDisplay = true; // Set flag to update display1 in the main loop
    } else {
        Serial.println("Invalid message format or value.");
    }
    } else if (message.startsWith("Rockers")) {
    if (numberPart >= 1 && numberPart <= 5) {
        currentIndex2 = constrain(numberPart - 1, 0, numImages2 - 1); // Update currentIndex2 based on rotary count
        Serial.printf("Updated currentIndex2 to %d\n", currentIndex2);
        updateDisplay = true; // Set flag to updateDisplay in the main loop
    } else {
        Serial.println("Invalid message format or value.");
    } 
    } else if (message.equals("SW: pressed")) {
    int sum = values1[currentIndex1] + values2[currentIndex2];
    for (int i = 0; i < numAllImages; ++i) {
        if (allImages[i].value == sum) {
        // Update the currentIndexAll to the index of the matching image
        currentIndexAll = i;  
        Serial.printf("Updated currentIndexAll to %d\n", currentIndexAll);
        updateResult = true; // Set flag to updateResult in the main loop
        }
    }
    } else {
    Serial.println("Invalid message format or value.");
    }
}
};

/* Global Variables */

// List of all the controls. It will be populated when a new control is registered
std::vector<ESP_NOW_Peer_Class> masters;

/* Callbacks */

// Callback called when an unknown peer sends a message
void register_new_master(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) {
if (memcmp(info->des_addr, ESP_NOW.BROADCAST_ADDR, 6) == 0) {
    Serial.printf("Unknown peer " MACSTR " sent a broadcast message\n", MAC2STR(info->src_addr));
    Serial.println("Registering the peer as a controller");

    ESP_NOW_Peer_Class new_master(info->src_addr, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);

    masters.push_back(new_master);
    if (!masters.back().add_peer()) {
    Serial.println("Failed to register the new controller");
    return;
    }
} else {
    // The peripherals will only receive broadcast messages
    log_v("Received a unicast message from " MACSTR, MAC2STR(info->src_addr));
    log_v("Ignoring the message");
}
}

void setup() {
Serial.begin(115200);

    // Initialize SD card
if (!SD.begin()) {
    Serial.println("SD card initialization failed!");
    return;
}
Serial.println("SD card initialized.");

// Initialize the Wi-Fi module
WiFi.mode(WIFI_STA);
WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
while (!WiFi.STA.started()) delay(100);

Serial.println("ESP-NOW Example - Broadcast Peripherals");
Serial.println("Wi-Fi parameters:");
Serial.println("  Mode: STA");
Serial.println("  MAC Address: " + WiFi.macAddress());
Serial.printf("  Channel: %d\n", ESPNOW_WIFI_CHANNEL);

// Initialize the ESP-NOW protocol
if (!ESP_NOW.begin()) {
    Serial.println("Failed to initialize ESP-NOW");
    Serial.println("Rebooting in 5 seconds...");
    delay(5000);
    ESP.restart();
}

// Register the new peer callback
ESP_NOW.onNewPeer(register_new_master, NULL);

Serial.println("Setup complete. Waiting for a control to broadcast a message...");

tft.init();          // Start the TFT display
tft.setRotation(2);  // Set the TFT display rotation in landscape mode

// Clear the screen before writing to it
tft.fillScreen(TFT_WHITE);
tft.setTextColor(TFT_BLACK, TFT_WHITE);

// Display images based on currentIndex
displayJpeg(images1[currentIndex1], 0);
displayJpeg(images2[currentIndex2], 1);


// Set X and Y coordinates for center of display
    int centerX = SCREEN_HEIGHT / 2;
    int centerY = SCREEN_WIDTH / 2;

tft.drawCentreString("SHOULD", centerX, centerY - 100, FONT_SIZE);
tft.drawCentreString("YOU", centerX, centerY - 80, FONT_SIZE);
tft.drawCentreString("TALK", centerX, centerY - 60, FONT_SIZE);
tft.drawCentreString("TO ME", centerX, centerY - 40, FONT_SIZE);
tft.drawCentreString("-O-", centerX, centerY - 20, FONT_SIZE);
tft.drawCentreString("METER", centerX, centerY, FONT_SIZE);

tft.drawCentreString("Communication is ready", centerX, centerY + 40, 2);

tft.drawCentreString("By Alexis", 200, 300, 2);
}

void loop() {
// Update display if the flag is set
if (updateDisplay) {
    tft.fillScreen(TFT_WHITE);
    tft.setTextColor(TFT_BLACK, TFT_WHITE);
    displayJpeg(images1[currentIndex1], 0);
    displayJpeg(images2[currentIndex2], 1);
    tft.drawCentreString("By Alexis", 200, 300, 2);
    updateDisplay = false; // Reset the flag
}

if (updateResult) {
    tft.fillScreen(TFT_WHITE);
    tft.setTextColor(TFT_BLACK, TFT_WHITE);
    displayJpeg(images1[currentIndex1], 0);
    displayJpeg(images2[currentIndex2], 1);
    displayJpeg(allImages[currentIndexAll].src, 2);
    tft.drawCentreString("By Alexis", 200, 300, 2);
    updateResult = false; // Reset the flag
}
}


void displayJpeg(const char *filename, int imageIndex) {
Serial.print("Opening file: ");
Serial.println(filename);

// Open the file
File jpegFile = SD.open(filename, FILE_READ);
if (!jpegFile) {
    Serial.print("Failed to open file ");
    Serial.println(filename);
    return;
}

// Decode the JPEG file
if (JpegDec.decodeSdFile(jpegFile)) {
    Serial.print("Decoding successful for ");
    Serial.println(filename);
    jpegRender(imageIndex);
} else {
    Serial.print("JPEG file format not supported for ");
    Serial.println(filename);
}

// Close the file
jpegFile.close();
}

void jpegRender(int imageIndex) {
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint32_t max_x = JpegDec.width;
uint32_t max_y = JpegDec.height;

Serial.print("Rendering image at index ");
Serial.print(imageIndex);
Serial.print(", width: ");
Serial.print(max_x);
Serial.print(", height: ");
Serial.println(max_y);

// Calculate the vertical and horizontal offsets
int16_t x_offset = 0;
int16_t y_offset = 0;

if (imageIndex == 0) {
    // First image positioned at 1/4 of TFT width
    x_offset = tft.width() / 4 - max_x / 2;
} else if (imageIndex == 1) {
    // Second image positioned at 3/4 of TFT width
    x_offset = 3 * tft.width() / 4 - max_x / 2;
} else {
    // Center third image at the bottom
    x_offset = (tft.width() - max_x) / 2;
    y_offset = tft.height() - max_y;
}

bool swapBytes = tft.getSwapBytes();
tft.setSwapBytes(true);

while (JpegDec.read()) {
    pImg = JpegDec.pImage;

    int mcu_x = JpegDec.MCUx * mcu_w;
    int mcu_y = JpegDec.MCUy * mcu_h;

    uint32_t win_w = (mcu_x + mcu_w <= max_x) ? mcu_w : (max_x % mcu_w);
    uint32_t win_h = (mcu_y + mcu_h <= max_y) ? mcu_h : (max_y % mcu_h);

    if ((mcu_x + win_w) <= tft.width() && (mcu_y + win_h) <= tft.height()) {
    tft.pushImage(mcu_x + x_offset, mcu_y + y_offset, win_w, win_h, pImg);
    } else if ((mcu_y + win_h) >= tft.height()) {
    JpegDec.abort();
    }
}

tft.setSwapBytes(swapBytes);
}
#include <FS.h>
#include <SPI.h>
#include <SD.h>
#include <TFT_eSPI.h>
#include <JPEGDecoder.h>

// Image paths and corresponding values
const char *images1[] = {"/05_smiley.jpg", "/04_smiley.jpg", "/03_smiley.jpg", "/02_smiley.jpg", "/01_smiley.jpg"};
const int values1[] = {5, 4, 3, 2, 1};

const char *images2[] = {"/50_smiley.jpg", "/40_smiley.jpg", "/30_smiley.jpg", "/20_smiley.jpg", "/10_smiley.jpg"};
const int values2[] = {50, 40, 30, 20, 10};

struct ImageData {
const char *src;
int value;
};

ImageData allImages[] = {
    {"/smiley_mix11.jpg", 11}, {"/smiley_mix12.jpg", 12}, {"/smiley_mix13.jpg", 13},
    {"/smiley_mix14.jpg", 14}, {"/smiley_mix15.jpg", 15}, {"/smiley_mix21.jpg", 21},
    {"/smiley_mix22.jpg", 22}, {"/smiley_mix23.jpg", 23}, {"/smiley_mix24.jpg", 24},
    {"/smiley_mix25.jpg", 25}, {"/smiley_mix31.jpg", 31}, {"/smiley_mix32.jpg", 32},
    {"/smiley_mix33.jpg", 33}, {"/smiley_mix34.jpg", 34}, {"/smiley_mix35.jpg", 35},
    {"/smiley_mix41.jpg", 41}, {"/smiley_mix42.jpg", 42}, {"/smiley_mix43.jpg", 43},
    {"/smiley_mix44.jpg", 44}, {"/smiley_mix45.jpg", 45}, {"/smiley_mix51.jpg", 51},
    {"/smiley_mix52.jpg", 52}, {"/smiley_mix53.jpg", 53}, {"/smiley_mix54.jpg", 54},
    {"/smiley_mix55.jpg", 55},
};

int currentIndex1 = 2;
int currentIndex2 = 2;

const int numImages1 = sizeof(images1) / sizeof(images1[0]);
const int numImages2 = sizeof(images2) / sizeof(images2[0]);
const int numAllImages = sizeof(allImages) / sizeof(allImages[0]);

// TFT and SD
TFT_eSPI tft = TFT_eSPI();

void setup() {
Serial.begin(115200);
delay(1000);

// Initialize TFT
tft.begin();
tft.setRotation(2);  // Adjust as needed
tft.fillScreen(TFT_WHITE);

// Initialize SD card
if (!SD.begin()) {
    Serial.println("SD card initialization failed!");
    return;
}
Serial.println("SD card initialized.");

// Display images based on currentIndex
displayJpeg(images1[currentIndex1], 0);
displayJpeg(images2[currentIndex2], 1);

for (int i = 0; i < numAllImages; i++) {
    displayJpeg(allImages[i].src, 2);
}
}

void loop() {
// Main loop code (if any)
}

void displayJpeg(const char *filename, int imageIndex) {
// Open the file
File jpegFile = SD.open(filename, FILE_READ);
if (!jpegFile) {
    Serial.print("Failed to open file ");
    Serial.println(filename);
    return;
}

// Decode the JPEG file
if (JpegDec.decodeSdFile(jpegFile)) {
    jpegRender(imageIndex);
} else {
    Serial.print("JPEG file format not supported for ");
    Serial.println(filename);
}

// Close the file
jpegFile.close();
}

void jpegRender(int imageIndex) {
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint32_t max_x = JpegDec.width;
uint32_t max_y = JpegDec.height;

// Calculate the vertical and horizontal offsets
int16_t x_offset = 0;
int16_t y_offset = 0;

if (imageIndex == 0) {
    // First image positioned at 1/4 of TFT width
    x_offset = tft.width() / 4 - max_x / 2;
} else if (imageIndex == 1) {
    // Second image positioned at 3/4 of TFT width
    x_offset = 3 * tft.width() / 4 - max_x / 2;
} else {
    // Center third image at the bottom
    x_offset = (tft.width() - max_x) / 2;
    y_offset = tft.height() - max_y;
}

bool swapBytes = tft.getSwapBytes();
tft.setSwapBytes(true);

while (JpegDec.read()) {
    pImg = JpegDec.pImage;

    int mcu_x = JpegDec.MCUx * mcu_w;
    int mcu_y = JpegDec.MCUy * mcu_h;

    uint32_t win_w = (mcu_x + mcu_w <= max_x) ? mcu_w : (max_x % mcu_w);
    uint32_t win_h = (mcu_y + mcu_h <= max_y) ? mcu_h : (max_y % mcu_h);

    if ((mcu_x + win_w) <= tft.width() && (mcu_y + win_h) <= tft.height()) {
    tft.pushImage(mcu_x + x_offset, mcu_y + y_offset, win_w, win_h, pImg);
    } else if ((mcu_y + win_h) >= tft.height()) {
    JpegDec.abort();
    }
}

tft.setSwapBytes(swapBytes);
}
#include <FS.h>
#include <SPI.h>
#include <SD.h>
#include <TFT_eSPI.h>
#include <JPEGDecoder.h>
#include <XPT2046_Touchscreen.h>

// Image paths and corresponding values
const char *images1[] = {"/images/05_smiley.jpg", "/images/04_smiley.jpg", "/images/03_smiley.jpg", "/images/02_smiley.jpg", "/images/01_smiley.jpg"};
const int values1[] = {5, 4, 3, 2, 1};

const char *images2[] = {"/images/50_smiley.jpg", "/images/40_smiley.jpg", "/images/30_smiley.jpg", "/images/20_smiley.jpg", "/images/10_smiley.jpg"};
const int values2[] = {50, 40, 30, 20, 10};

struct ImageData {
const char *src;
int value;
};

ImageData allImages[] = {
    {"/images/smiley_mix11.jpg", 11}, {"/images/smiley_mix12.jpg", 12}, {"/images/smiley_mix13.jpg", 13},
    {"/images/smiley_mix14.jpg", 14}, {"/images/smiley_mix15.jpg", 15}, {"/images/smiley_mix21.jpg", 21},
    {"/images/smiley_mix22.jpg", 22}, {"/images/smiley_mix23.jpg", 23}, {"/images/smiley_mix24.jpg", 24},
    {"/images/smiley_mix25.jpg", 25}, {"/images/smiley_mix31.jpg", 31}, {"/images/smiley_mix32.jpg", 32},
    {"/images/smiley_mix33.jpg", 33}, {"/images/smiley_mix34.jpg", 34}, {"/images/smiley_mix35.jpg", 35},
    {"/images/smiley_mix41.jpg", 41}, {"/images/smiley_mix42.jpg", 42}, {"/images/smiley_mix43.jpg", 43},
    {"/images/smiley_mix44.jpg", 44}, {"/images/smiley_mix45.jpg", 45}, {"/images/smiley_mix51.jpg", 51},
    {"/images/smiley_mix52.jpg", 52}, {"/images/smiley_mix53.jpg", 53}, {"/images/smiley_mix54.jpg", 54},
    {"/images/smiley_mix55.jpg", 55},
};

int currentIndex1 = 2;
int currentIndex2 = 2;

const int numImages1 = sizeof(images1) / sizeof(images1[0]);
const int numImages2 = sizeof(images2) / sizeof(images2[0]);
const int numAllImages = sizeof(allImages) / sizeof(allImages[0]);

// TFT and SD
TFT_eSPI tft = TFT_eSPI();

// Touchscreen pins
#define XPT2046_IRQ 36   // T_IRQ
#define XPT2046_MOSI 32  // T_DIN
#define XPT2046_MISO 39  // T_OUT
#define XPT2046_CLK 25   // T_CLK
#define XPT2046_CS 33    // T_CS

SPIClass touchscreenSPI = SPIClass(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);

#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define FONT_SIZE 2

// Touchscreen coordinates: (x, y) and pressure (z)
int x, y, z;

// Print Touchscreen info about X, Y and Pressure (Z) on the Serial Monitor
void printTouchToSerial(int touchX, int touchY, int touchZ) {
Serial.print("X = ");
Serial.print(touchX);
Serial.print(" | Y = ");
Serial.print(touchY);
Serial.print(" | Pressure = ");
Serial.print(touchZ);
Serial.println();
}

// Print Touchscreen info about X, Y and Pressure (Z) on the TFT Display
void printTouchToDisplay(int touchX, int touchY, int touchZ) {
// Clear TFT screen
tft.fillScreen(TFT_WHITE);
tft.setTextColor(TFT_BLACK, TFT_WHITE);

int centerX = SCREEN_WIDTH / 2;
int textY = 80;

String tempText = "X = " + String(touchX);
tft.drawCentreString(tempText, centerX, textY, FONT_SIZE);

textY += 20;
tempText = "Y = " + String(touchY);
tft.drawCentreString(tempText, centerX, textY, FONT_SIZE);

textY += 20;
tempText = "Pressure = " + String(touchZ);
tft.drawCentreString(tempText, centerX, textY, FONT_SIZE);
}
void setup() {
Serial.begin(115200);
delay(1000);

// Initialize TFT
tft.begin();
tft.setRotation(2);  // Adjust as needed
tft.fillScreen(TFT_WHITE);
Serial.println("TFT initialized.");

// Initialize Touchscreen
touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
touchscreen.setRotation(2);
Serial.println("Touchscreen initialized.");

// Initialize SD card
if (!SD.begin()) {
    Serial.println("SD card initialization failed!");
    return;
}
Serial.println("SD card initialized.");

// Display images based on currentIndex
displayJpeg(images1[currentIndex1], 0);
displayJpeg(images2[currentIndex2], 1);

int sumValue = values1[currentIndex1] + values2[currentIndex2];
const char *thirdImage = findImageByValue(sumValue);
if (thirdImage) {
    displayJpeg(thirdImage, 2);
} else {
    Serial.print("No image found with value ");
    Serial.println(sumValue);
}
Serial.println("Images displayed.");

}

void loop() {
// Checks if Touchscreen was touched, and updates the currentIndex1 and currentIndex2
if (touchscreen.tirqTouched() && touchscreen.touched()) {
    // Get Touchscreen points
    TS_Point p = touchscreen.getPoint();
    // Calibrate Touchscreen points with map function to the correct width and height
    int touchX = map(p.x, 200, 3700, 1, SCREEN_WIDTH);
    int touchY = map(p.y, 240, 3800, 1, SCREEN_HEIGHT);

    // Check if the touch is within the regions where images1 and images2 are displayed
    if (touchX < SCREEN_WIDTH / 2) {
    // Left half of the screen, increment currentIndex1
    currentIndex1 = (currentIndex1 + 1) % numImages1;
    displayJpeg(images1[currentIndex1], 0);
    } else {
    // Right half of the screen, increment currentIndex2
    currentIndex2 = (currentIndex2 + 1) % numImages2;
    displayJpeg(images2[currentIndex2], 1);
    }

    // Calculate the sum value and display the corresponding third image
    int sumValue = values1[currentIndex1] + values2[currentIndex2];
    const char *thirdImage = findImageByValue(sumValue);
    if (thirdImage) {
    displayJpeg(thirdImage, 2);
    } else {
    Serial.print("No image found with value ");
    Serial.println(sumValue);
    }

    delay(100); // debounce delay
}// Main loop code (if any)
}

const char *findImageByValue(int value) {
for (int i = 0; i < numAllImages; i++) {
    if (allImages[i].value == value) {
    return allImages[i].src;
    }
}
return nullptr;  // Return nullptr if no image with the required value is found
} 

void displayJpeg(const char *filename, int imageIndex) {
Serial.print("Opening file: ");
Serial.println(filename);

// Add a delay here if needed
delay(100); // Adjust this delay as needed

// Open the file
File jpegFile = SD.open(filename, FILE_READ);
if (!jpegFile) {
    Serial.print("Failed to open file ");
    Serial.println(filename);
    return;
}

// Decode the JPEG file
if (JpegDec.decodeSdFile(jpegFile)) {
    Serial.print("Decoding successful for ");
    Serial.println(filename);
    jpegRender(imageIndex);
} else {
    Serial.print("JPEG file format not supported for ");
    Serial.println(filename);
}

// Close the file
jpegFile.close();
}

void jpegRender(int imageIndex) {
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint32_t max_x = JpegDec.width;
uint32_t max_y = JpegDec.height;

Serial.print("Rendering image at index ");
Serial.print(imageIndex);
Serial.print(", width: ");
Serial.print(max_x);
Serial.print(", height: ");
Serial.println(max_y);

// Calculate the vertical and horizontal offsets
int16_t x_offset = 0;
int16_t y_offset = 0;

if (imageIndex == 0) {
    // First image positioned at 1/4 of TFT width
    x_offset = tft.width() / 4 - max_x / 2;
} else if (imageIndex == 1) {
    // Second image positioned at 3/4 of TFT width
    x_offset = 3 * tft.width() / 4 - max_x / 2;
} else {
    // Center third image at the bottom
    x_offset = (tft.width() - max_x) / 2;
    y_offset = tft.height() - max_y;
}

bool swapBytes = tft.getSwapBytes();
tft.setSwapBytes(true);

while (JpegDec.read()) {
    pImg = JpegDec.pImage;

    int mcu_x = JpegDec.MCUx * mcu_w;
    int mcu_y = JpegDec.MCUy * mcu_h;

    uint32_t win_w = (mcu_x + mcu_w <= max_x) ? mcu_w : (max_x % mcu_w);
    uint32_t win_h = (mcu_y + mcu_h <= max_y) ? mcu_h : (max_y % mcu_h);

    if ((mcu_x + win_w) <= tft.width() && (mcu_y + win_h) <= tft.height()) {
    tft.pushImage(mcu_x + x_offset, mcu_y + y_offset, win_w, win_h, pImg);
    } else if ((mcu_y + win_h) >= tft.height()) {
    JpegDec.abort();
    }
}

tft.setSwapBytes(swapBytes);
}
#include <FS.h>
#include <SPI.h>
#include <SD.h>
#include <TFT_eSPI.h>
#include <JPEGDecoder.h>

// Image paths and corresponding values
const char *images1[] = {"/images/05_smiley.jpg", "/images/04_smiley.jpg", "/images/03_smiley.jpg", "/images/02_smiley.jpg", "/images/01_smiley.jpg"};
const int values1[] = {5, 4, 3, 2, 1};

const char *images2[] = {"/images/50_smiley.jpg", "/images/40_smiley.jpg", "/images/30_smiley.jpg", "/images/20_smiley.jpg", "/images/10_smiley.jpg"};
const int values2[] = {50, 40, 30, 20, 10};

struct ImageData {
const char *src;
int value;
};

ImageData allImages[] = {
    {"/images/smiley_mix11.jpg", 11}, {"/images/smiley_mix12.jpg", 12}, {"/images/smiley_mix13.jpg", 13},
    {"/images/smiley_mix14.jpg", 14}, {"/images/smiley_mix15.jpg", 15}, {"/images/smiley_mix21.jpg", 21},
    {"/images/smiley_mix22.jpg", 22}, {"/images/smiley_mix23.jpg", 23}, {"/images/smiley_mix24.jpg", 24},
    {"/images/smiley_mix25.jpg", 25}, {"/images/smiley_mix31.jpg", 31}, {"/images/smiley_mix32.jpg", 32},
    {"/images/smiley_mix33.jpg", 33}, {"/images/smiley_mix34.jpg", 34}, {"/images/smiley_mix35.jpg", 35},
    {"/images/smiley_mix41.jpg", 41}, {"/images/smiley_mix42.jpg", 42}, {"/images/smiley_mix43.jpg", 43},
    {"/images/smiley_mix44.jpg", 44}, {"/images/smiley_mix45.jpg", 45}, {"/images/smiley_mix51.jpg", 51},
    {"/images/smiley_mix52.jpg", 52}, {"/images/smiley_mix53.jpg", 53}, {"/images/smiley_mix54.jpg", 54},
    {"/images/smiley_mix55.jpg", 55},
};

int currentIndex1 = 2;
int currentIndex2 = 2;

const int numImages1 = sizeof(images1) / sizeof(images1[0]);
const int numImages2 = sizeof(images2) / sizeof(images2[0]);
const int numAllImages = sizeof(allImages) / sizeof(allImages[0]);

// TFT and SD
TFT_eSPI tft = TFT_eSPI();

void setup() {
Serial.begin(115200);
delay(1000);

// Initialize TFT
tft.begin();
tft.setRotation(2);  // Adjust as needed
tft.fillScreen(TFT_WHITE);

// Initialize SD card
if (!SD.begin()) {
    Serial.println("SD card initialization failed!");
    return;
}
Serial.println("SD card initialized.");

// Display images based on currentIndex
displayJpeg(images1[currentIndex1], 0);
displayJpeg(images2[currentIndex2], 1);

int sumValue = values1[currentIndex1] + values2[currentIndex2];
const char *thirdImage = findImageByValue(sumValue);
if (thirdImage) {
    displayJpeg(thirdImage, 2);
} else {
    Serial.print("No image found with value ");
    Serial.println(sumValue);
}
}

void loop() {
// Main loop code (if any)
}

const char *findImageByValue(int value) {
for (int i = 0; i < numAllImages; i++) {
    if (allImages[i].value == value) {
    return allImages[i].src;
    }
}
return nullptr;  // Return nullptr if no image with the required value is found
}

void displayJpeg(const char *filename, int imageIndex) {
Serial.print("Opening file: ");
Serial.println(filename);

// Open the file
File jpegFile = SD.open(filename, FILE_READ);
if (!jpegFile) {
    Serial.print("Failed to open file ");
    Serial.println(filename);
    return;
}

// Decode the JPEG file
if (JpegDec.decodeSdFile(jpegFile)) {
    Serial.print("Decoding successful for ");
    Serial.println(filename);
    jpegRender(imageIndex);
} else {
    Serial.print("JPEG file format not supported for ");
    Serial.println(filename);
}

// Close the file
jpegFile.close();
}

void jpegRender(int imageIndex) {
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint32_t max_x = JpegDec.width;
uint32_t max_y = JpegDec.height;

Serial.print("Rendering image at index ");
Serial.print(imageIndex);
Serial.print(", width: ");
Serial.print(max_x);
Serial.print(", height: ");
Serial.println(max_y);

// Calculate the vertical and horizontal offsets
int16_t x_offset = 0;
int16_t y_offset = 0;

if (imageIndex == 0) {
    // First image positioned at 1/4 of TFT width
    x_offset = tft.width() / 4 - max_x / 2;
} else if (imageIndex == 1) {
    // Second image positioned at 3/4 of TFT width
    x_offset = 3 * tft.width() / 4 - max_x / 2;
} else {
    // Center third image at the bottom
    x_offset = (tft.width() - max_x) / 2;
    y_offset = tft.height() - max_y;
}

bool swapBytes = tft.getSwapBytes();
tft.setSwapBytes(true);

while (JpegDec.read()) {
    pImg = JpegDec.pImage;

    int mcu_x = JpegDec.MCUx * mcu_w;
    int mcu_y = JpegDec.MCUy * mcu_h;

    uint32_t win_w = (mcu_x + mcu_w <= max_x) ? mcu_w : (max_x % mcu_w);
    uint32_t win_h = (mcu_y + mcu_h <= max_y) ? mcu_h : (max_y % mcu_h);

    if ((mcu_x + win_w) <= tft.width() && (mcu_y + win_h) <= tft.height()) {
    tft.pushImage(mcu_x + x_offset, mcu_y + y_offset, win_w, win_h, pImg);
    } else if ((mcu_y + win_h) >= tft.height()) {
    JpegDec.abort();
    }
}

tft.setSwapBytes(swapBytes);
}
#include <SPI.h>
#include <TFT_eSPI.h>
#include "ESP32_NOW.h"
#include "WiFi.h"
#include <esp_mac.h>  // For the MAC2STR and MACSTR macros
#include <vector>
#include <JPEGDecoder.h>
#include <SD.h>
#include <FS.h>

// Image paths and corresponding values
const char *images1[] = {"/images/05_smiley.jpg", "/images/04_smiley.jpg", "/images/03_smiley.jpg", "/images/02_smiley.jpg", "/images/01_smiley.jpg"};
const int values1[] = {5, 4, 3, 2, 1};

const char *images2[] = {"/images/50_smiley.jpg", "/images/40_smiley.jpg", "/images/30_smiley.jpg", "/images/20_smiley.jpg", "/images/10_smiley.jpg"};
const int values2[] = {50, 40, 30, 20, 10};

struct ImageData {
const char *src;
int value;
};

ImageData allImages[] = {
    {"/images/smiley_mix11.jpg", 11}, {"/images/smiley_mix12.jpg", 12}, {"/images/smiley_mix13.jpg", 13},
    {"/images/smiley_mix14.jpg", 14}, {"/images/smiley_mix15.jpg", 15}, {"/images/smiley_mix21.jpg", 21},
    {"/images/smiley_mix22.jpg", 22}, {"/images/smiley_mix23.jpg", 23}, {"/images/smiley_mix24.jpg", 24},
    {"/images/smiley_mix25.jpg", 25}, {"/images/smiley_mix31.jpg", 31}, {"/images/smiley_mix32.jpg", 32},
    {"/images/smiley_mix33.jpg", 33}, {"/images/smiley_mix34.jpg", 34}, {"/images/smiley_mix35.jpg", 35},
    {"/images/smiley_mix41.jpg", 41}, {"/images/smiley_mix42.jpg", 42}, {"/images/smiley_mix43.jpg", 43},
    {"/images/smiley_mix44.jpg", 44}, {"/images/smiley_mix45.jpg", 45}, {"/images/smiley_mix51.jpg", 51},
    {"/images/smiley_mix52.jpg", 52}, {"/images/smiley_mix53.jpg", 53}, {"/images/smiley_mix54.jpg", 54},
    {"/images/smiley_mix55.jpg", 55},
};

int currentIndex1 = 2;
int currentIndex2 = 2;
int currentIndexAll = 0; // Initialize the index to 0

const int numImages1 = sizeof(images1) / sizeof(images1[0]);
const int numImages2 = sizeof(images2) / sizeof(images2[0]);
const int numAllImages = sizeof(allImages) / sizeof(allImages[0]);

TFT_eSPI tft = TFT_eSPI();

/* Definitions */
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define FONT_SIZE 4

#define ESPNOW_WIFI_CHANNEL 6

bool updateDisplay = false; // Global flag to indicate display update
bool updateResult = false; // Global flag to indicate display update

/* Classes */

// Creating a new class that inherits from the ESP_NOW_Peer class is required.

class ESP_NOW_Peer_Class : public ESP_NOW_Peer {
public:
// Constructor of the class
ESP_NOW_Peer_Class(const uint8_t *mac_addr,
                    uint8_t channel,
                    wifi_interface_t iface,
                    const uint8_t *lmk)
    : ESP_NOW_Peer(mac_addr, channel, iface, lmk) {}

// Destructor of the class
~ESP_NOW_Peer_Class() {}

// Function to register the control peer
bool add_peer() {
    if (!add()) {
    log_e("Failed to register the broadcast peer");
    return false;
    }
    return true;
}

// Function to handle received messages from the control
void onReceive(const uint8_t *data, size_t len, bool broadcast) {
    Serial.printf("Received a message from control " MACSTR " (%s)\n", MAC2STR(addr()), broadcast ? "broadcast" : "unicast");
    Serial.printf("  Message: %.*s\n", len, data);  // Print the message as received

    // Try to extract the numeric part from the message
    String message = String((char *)data);
    int numberPart = message.substring(message.lastIndexOf(' ') + 1).toInt();

    if (message.startsWith("clockwise")) {
    if (numberPart >= 1 && numberPart <= 5) {
        currentIndex1 = constrain(numberPart - 1, 0, numImages1 - 1); // Update currentIndex1 based on rotary count
        Serial.printf("Updated currentIndex1 to %d\n", currentIndex1);
        updateDisplay = true; // Set flag to update display1 in the main loop
    } else {
        Serial.println("Invalid message format or value.");
    }
    } else if (message.startsWith("counter-clockwise")) {
    if (numberPart >= 1 && numberPart <= 5) {
        currentIndex1 = constrain(numberPart - 1, 0, numImages1 - 1); // Update currentIndex1 based on rotary count
        Serial.printf("Updated currentIndex1 to %d\n", currentIndex1);
        updateDisplay = true; // Set flag to update display1 in the main loop
    } else {
        Serial.println("Invalid message format or value.");
    }
    } else if (message.startsWith("Rockers")) {
    if (numberPart >= 1 && numberPart <= 5) {
        currentIndex2 = constrain(numberPart - 1, 0, numImages2 - 1); // Update currentIndex2 based on rotary count
        Serial.printf("Updated currentIndex2 to %d\n", currentIndex2);
        updateDisplay = true; // Set flag to updateDisplay in the main loop
    } else {
        Serial.println("Invalid message format or value.");
    } 
    } else if (message.equals("SW: pressed")) {
    int sum = values1[currentIndex1] + values2[currentIndex2];
    for (int i = 0; i < numAllImages; ++i) {
        if (allImages[i].value == sum) {
        // Update the currentIndexAll to the index of the matching image
        currentIndexAll = i;  
        Serial.printf("Updated currentIndexAll to %d\n", currentIndexAll);
        updateResult = true; // Set flag to updateResult in the main loop
        }
    }
    } else {
    Serial.println("Invalid message format or value.");
    }
}
};

/* Global Variables */

// List of all the controls. It will be populated when a new control is registered
std::vector<ESP_NOW_Peer_Class> masters;

/* Callbacks */

// Callback called when an unknown peer sends a message
void register_new_master(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) {
if (memcmp(info->des_addr, ESP_NOW.BROADCAST_ADDR, 6) == 0) {
    Serial.printf("Unknown peer " MACSTR " sent a broadcast message\n", MAC2STR(info->src_addr));
    Serial.println("Registering the peer as a controller");

    ESP_NOW_Peer_Class new_master(info->src_addr, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);

    masters.push_back(new_master);
    if (!masters.back().add_peer()) {
    Serial.println("Failed to register the new controller");
    return;
    }
} else {
    // The peripherals will only receive broadcast messages
    log_v("Received a unicast message from " MACSTR, MAC2STR(info->src_addr));
    log_v("Ignoring the message");
}
}

void setup() {
Serial.begin(115200);

    // Initialize SD card
if (!SD.begin()) {
    Serial.println("SD card initialization failed!");
    return;
}
Serial.println("SD card initialized.");

// Initialize the Wi-Fi module
WiFi.mode(WIFI_STA);
WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
while (!WiFi.STA.started()) delay(100);

Serial.println("ESP-NOW Example - Broadcast Peripherals");
Serial.println("Wi-Fi parameters:");
Serial.println("  Mode: STA");
Serial.println("  MAC Address: " + WiFi.macAddress());
Serial.printf("  Channel: %d\n", ESPNOW_WIFI_CHANNEL);

// Initialize the ESP-NOW protocol
if (!ESP_NOW.begin()) {
    Serial.println("Failed to initialize ESP-NOW");
    Serial.println("Rebooting in 5 seconds...");
    delay(5000);
    ESP.restart();
}

// Register the new peer callback
ESP_NOW.onNewPeer(register_new_master, NULL);

Serial.println("Setup complete. Waiting for a control to broadcast a message...");

tft.init();          // Start the TFT display
tft.setRotation(2);  // Set the TFT display rotation in landscape mode

// Clear the screen before writing to it
tft.fillScreen(TFT_WHITE);
tft.setTextColor(TFT_BLACK, TFT_WHITE);

// Display images based on currentIndex
displayJpeg(images1[currentIndex1], 0);
displayJpeg(images2[currentIndex2], 1);


// Set X and Y coordinates for center of display
    int centerX = SCREEN_HEIGHT / 2;
    int centerY = SCREEN_WIDTH / 2;

tft.drawCentreString("SHOULD", centerX, centerY - 100, FONT_SIZE);
tft.drawCentreString("YOU", centerX, centerY - 80, FONT_SIZE);
tft.drawCentreString("TALK", centerX, centerY - 60, FONT_SIZE);
tft.drawCentreString("TO ME", centerX, centerY - 40, FONT_SIZE);
tft.drawCentreString("-O-", centerX, centerY - 20, FONT_SIZE);
tft.drawCentreString("METER", centerX, centerY, FONT_SIZE);

tft.drawCentreString("Communication is ready", centerX, centerY + 40, 2);

tft.drawCentreString("By Alexis", 200, 300, 2);
}

void loop() {
// Update display if the flag is set
if (updateDisplay) {
    tft.fillScreen(TFT_WHITE);
    tft.setTextColor(TFT_BLACK, TFT_WHITE);
    displayJpeg(images1[currentIndex1], 0);
    displayJpeg(images2[currentIndex2], 1);
    tft.drawCentreString("By Alexis", 200, 300, 2);
    updateDisplay = false; // Reset the flag
}

if (updateResult) {
    tft.fillScreen(TFT_WHITE);
    tft.setTextColor(TFT_BLACK, TFT_WHITE);
    displayJpeg(images1[currentIndex1], 0);
    displayJpeg(images2[currentIndex2], 1);
    displayJpeg(allImages[currentIndexAll].src, 2);
    tft.drawCentreString("By Alexis", 200, 300, 2);
    updateResult = false; // Reset the flag
}
}


void displayJpeg(const char *filename, int imageIndex) {
Serial.print("Opening file: ");
Serial.println(filename);

// Open the file
File jpegFile = SD.open(filename, FILE_READ);
if (!jpegFile) {
    Serial.print("Failed to open file ");
    Serial.println(filename);
    return;
}

// Decode the JPEG file
if (JpegDec.decodeSdFile(jpegFile)) {
    Serial.print("Decoding successful for ");
    Serial.println(filename);
    jpegRender(imageIndex);
} else {
    Serial.print("JPEG file format not supported for ");
    Serial.println(filename);
}

// Close the file
jpegFile.close();
}

void jpegRender(int imageIndex) {
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint32_t max_x = JpegDec.width;
uint32_t max_y = JpegDec.height;

Serial.print("Rendering image at index ");
Serial.print(imageIndex);
Serial.print(", width: ");
Serial.print(max_x);
Serial.print(", height: ");
Serial.println(max_y);

// Calculate the vertical and horizontal offsets
int16_t x_offset = 0;
int16_t y_offset = 0;

if (imageIndex == 0) {
    // First image positioned at 1/4 of TFT width
    x_offset = tft.width() / 4 - max_x / 2;
} else if (imageIndex == 1) {
    // Second image positioned at 3/4 of TFT width
    x_offset = 3 * tft.width() / 4 - max_x / 2;
} else {
    // Center third image at the bottom
    x_offset = (tft.width() - max_x) / 2;
    y_offset = tft.height() - max_y;
}

bool swapBytes = tft.getSwapBytes();
tft.setSwapBytes(true);

while (JpegDec.read()) {
    pImg = JpegDec.pImage;

    int mcu_x = JpegDec.MCUx * mcu_w;
    int mcu_y = JpegDec.MCUy * mcu_h;

    uint32_t win_w = (mcu_x + mcu_w <= max_x) ? mcu_w : (max_x % mcu_w);
    uint32_t win_h = (mcu_y + mcu_h <= max_y) ? mcu_h : (max_y % mcu_h);

    if ((mcu_x + win_w) <= tft.width() && (mcu_y + win_h) <= tft.height()) {
    tft.pushImage(mcu_x + x_offset, mcu_y + y_offset, win_w, win_h, pImg);
    } else if ((mcu_y + win_h) >= tft.height()) {
    JpegDec.abort();
    }
}

tft.setSwapBytes(swapBytes);
}

As it is, my final project worked well, some improvements could be added such as text on the screen to help understanding. But The most urgent issue was to improve the integration of this screen.

Screen integration

As I was working on the code, I started printing a case for screen. I found a model on printables that I modified with Fusion360 to add loops for attaching a strap.

To power the screen, I bought a powerbank and modelised an holder to fix it to the screen case.

case for the screen in Fusion360 holder for the case and the battery in Fusion360

case, battery and holder in Fusion360

I made several print of the holder but none of them was really good. I stayed with the first one I mistakenly printed without support. one completely broke when I removed the supports and the other missed collapsed during printing. I had to reinforce the first one with tape because due to lacke of support it was a bit too tight for the screen and the battery and cracked a bit.

broken holder holder for the case and the battery

Sewing

I used a scrap of fabric to sew a strap. I added PETG-printed buckles to tighten it.

sewing the strap strap on the screen buckles modelisation in Fusion360 PETG-printed buckles on the strap

screen straped around arm

Individual assignment

Plan for dissemination

My final project isn’t meant for dissemination. When I created this device, I had a particular patient in mind. And even, if I’m not working with him anymore, I thought that creating this assistive device could serve as an example of my skills and to show what can be done to meet specific needs. My project is to provide support services for people with disabilities, to help them create their own projects, impart skills and a work method that promotes their autonomy, and boosts their self-esteem. To promote my services, I talked a lot to my network about what I’m doing in the FabAcademy. I plan to approach various medical and social structures in my region to offer my services to their audiences. I have already received a few invitations to meet with local associations.

Slide

I made the required slide with Krita. I first made a bunch of pictures of the different part and pick some I already made.

I learned to cut out parts of it to have transparent background. From that I could create a montage of cut-out images. I added information to help understanding my device. I also decided to name it Should-you-talk-to-me-O-meter, as it was a light, non-stigmatizing, and somewhat funny name.

Video Clip

I first tried Da Vinci for Video Editing but a lot of unexpected closing occured as I was working on it. I tried to debug it using this tutorial and this one but can’t manage to resolve it, probably because my laptop doesn’t meet the minimum system requirements. I learned a bit the software but couldn’t dive deeper in its functionnalities.

With those difficulties, I decided to use Canva. I used online dowloader to get excerpts from Youtube videos.

For the music, I found free music on youtube. I didn’t found a goodsound downloader so I used VLC to extract the sound from the MP4 video using this tutorial.

Class Archive

Impressions of the week

That were really busy weeks but I’m satisfied of the result. Not everything worked as expected but I had still experimented new processes and learned a lot. I still see improvements (screen integration, easier to wear strap, feedbacks on the fidget, power managment) but as it is, it’s functionnal and pleasant to use.