Skip to content

17. Applications and implications - Project Development

Hero Shot

Assignment :

  • Propose a final project masterpiece that integrates the range of units covered, answering:
  • What will it do?
  • Who’s done what beforehand?
  • What will you design?
  • What materials and components will be used?
  • Where will come from?
  • How much will they cost?
  • What parts and systems will be made?
  • What processes will be used?
  • What questions need to be answered?
  • How will it be evaluated?

Your project should incorporate 2D and 3D design, additive and subtractive fabrication processes, electronics design and production, embedded microcontroller interfacing and programming, system integration and packaging where possible, you should make rather than buy the parts of your project. Projects can be separate or joint, but need to show individual mastery of the skills, and be independently operable.

  • Complete your final project, tracking your progress:
  • what’s working? what’s not?
  • what questions need to be resolved?
  • what will happen when?
  • what have you learned?

Propose a final project masterpiece

Questions to answer

What will it do ?

My project is a remote device thought as a stim toy or fidget and that embed the possibility to share one’s mstate of mind. Using a rotary encoder, the user can share their mood and using a set of rocker switches the level of interaction/sociability they accept.

The informations are displayed as smiley face as it’s an international form of communicating even without reading hability. The program matches both informations to display the third one that mix the previous. I used Emojikitchen that allow uses for other projects.

It is display on a screen near the user (Phone, bracer or pendant) to be shared with people around.

Who’s done what beforehand?

Fidgets and stim toys are pretty commons and became extremely popular with the hand spinner but they existed long before in numbers of form (worry beads,Stress Ball,…). They are used to release stress, to help focus or for “stimming”.

My design is a bit inspired by the Fidget Cube that has multiple fidgets on it especially for proprioceptive and tactile stimming.

I thought I could make my own that could also help at self-awareness and communication.

What will you design?

I’ll design the remote control fidget shell, the electronics, the knob and the interface.

What materials and components will be used?

Nb Element Link Price (Units) Price (total) Remarks
1 Micro-controller ESP32-WROOM-32UE digikey 2.50$ 2.50$
1 KY-040 Rotary Encoder Amazon ~1.50$ 1.50$
5 Rocker Switch e-bay ~1$ 5$ Cut-out 19x13mm
1 Button Digikey ~1$ 1$
1 Regulator 3.3V 1A Digikey 0.51$ 0.51$ Nolonger manufactured. Prefer [this one(https://www.digikey.com/en/products/detail/diodes-incorporated/AZ1117IH-3-3TRG1/5699682)]
1 vertical connector SMD 1x05 2.54mm Digikey 0.68$ 0.68$ suppressed for direct soldering
1 horizontal connector SMD 1x06 2.54mm Digikey 0.93$ 0.93$ FTDI
1 vertical connector SMD 2x05 2.54mm Digikey 0.84$ 0.84$ for switches
3 JST connector 2 pins + sockets Digikey 0.54$ (8.95$for 100 pins kit) 0.54$ 1 modified to be soldered SMD
1 resistor 10k Digikey 0.10$ 0.10$
1 resistor 1K Digikey 0.10$ 0.10$
2 resistor O ohm Digikey 0.10$ 0.20$
1 LED Clear Blue Digikey 0.38$ 0.38$
1 capacitor 0.1 uF Digikey 0.33$ 0.33$
1 capacitor 1 uF Digikey 0.25$ 0.25$
1 capacitor 10 uF Digikey 0.60$ 0.60$ different packaging in the lab
1 switch slide Digikey 1.39$ 1.39$
15 cable 1.27mm*10 Digikey ~0.012$ (88.32$ for 30.48m) ~ 0.17$ ~ 4cm length each
10 single dupont connectors + sockets Amazon 0.03$ (12.59$ for 400 pins) 0.30$
5 Hook-up Wire Red and Black RS components 0.03$ (8.67$/100m) 0.03$ ~ 6 cm medium length
1 CCL FR1 5.00” x 4.00” Digikey 2.56$ (64.04/25pcs) 2.56$
1 electrical tape Digikey 0.12$ (140.16$ the tape of 54’) 0.12$ 1/2” used for battery holder
10 heat shrinkables tube D:4.8mm * 20 mm PVN ~0.04$ (14.21€ for 85*100mm pcs) ~ 0.40 $ about 2 x 100mm tubes
1 PLA black MatterHackers 0.8$ (57$/kg) 0.8$ 14g - local reference missing
1 PLA-PHA yellow colorFabb 5.75$ (19.86$/750g) 5.75$ 217g
1 screw M4 12mm RS components 0.18$ 0.18$ to fix the knob
1 nut 4mm RS components 0.03 $ 0.03$ included in the knob print

Display part

Nb Element Link Price (Units) Price (total) Remarks
1 ESP32-2432S028 AliExpress 19.84$ (18.47€) 19.84$ usb cable included
1 Powerbank Leclerc 15.72$ (14.64€) 15.72$ From Mall. Dim: 62.5 x 14.5 x 9.9
1 Micro SD Card Digikey 3.61$ 3.61$ 3 Mo needed
1 PLA black MatterHackers 1.54$ (57$/kg) 1.54$ For holder and screen case - 27g - local reference missing
1 PETG black Colorfabb 0.04$ (158.15$/8kg) 0.04$ For clips(*2) - 2g
1 Fabrics 50*6cm Mondial tissus 0.15$ (7.62$ for 1*1.50m) 0.15$ cutted part from curtains
1 Sewing thread Mondial tissus >0.01$(4.47$/2743m) >0.01$ less than 1m

Where will come from?

All components are provided by the Fablab, most of the components came from [Digikey(https://www.digikey.com/rs)] and RS Components.

How much will they cost?

27.19 $ for the remote control and 40.90 $ for the display

What parts and systems will be made?

For the remote control, the pcb, the battery holder, the shell and the knob are made.

What processes will be used?

I’ll use 3d printing, pcb milling, soldering, programming (C++, html, javascript). I also added vinylcutting for information on the case.

What questions need to be answered?

I still need to figure out how to make it more comprehensive for people not sensibilize to this kind of disorder. Also, all my parts are additive fabrication processes and I need to find a way to implement substractive fabrication processes.

How will it be evaluated?

I will test it with different people to figure if usage is comprehensive. In the absolute, testing with people with social interaction disorder and ask if they find it useful in their daily living or during specific activities.

Project Development - Tracking progress

Electronics

I started by finalizing the electronics production. PCB was milled during previous week and I had to solder components I already prepared on it. ESP32 chip was trickier to solder than previous Xiao Board but I managed to do it. I made quite a mess on the pins near the vertical connectors, almost all them (except the GND one) are connected to each other but hopefully none of them are used on my board. Next times, I will avoid soldering pins that aren’t connected to anything. I tested it and could upload a blink program in it.

I then prepared the wiring for the switches. I modified Dupont wires to solder them on the switches (I found out later I could have make my own connectors).

I added shrinkables tube to avoid contacts. Having no spring to maintain the contact in the battery holder, I chose to use a piece of sponge. To make the actual, I used copper tape with a lot of tin With all this parts the electronics of the remote device was complete.

A bit later, I added a switch between the battery and the board to turn off the device.

Pin references :

SW : IO34
DT : IO35
CLK : IO32
Rocker Switch 1 : IO33 (internal pull-up)
Rocker Switch 2 : IO25 (internal pull-up)
Rocker Switch 3 : IO26 (internal pull-up)
Rocker Switch 4 : IO27 (internal pull-up)
Rocker Switch 5 : IO14 (internal pull-up)
LED : IO23

Shell

I made 2 tries of the outer shell. I printed the design I made previous week. That first one had a good grip but not enough space for the components. The cut out for the switches was good and they perfectly fit in it. I also found out that four rocker switches shoud be sufficient and it would feel more natural when handling it as each fingers will have contact whith one except for the thumb that will maintain the grip.

.

I made another design trying to reduce the volume of it at the same time than making more space in it.

All the compenents fit in the second shell, but the grip sensation was very bad. The shape wasn’t ergonomic at all.

The knob was still printing at the end of this week.

Programming

I could make a WIFI communication and generate an html page.

To display an html page with images from the ESP32, I needed to upload the images in it. The chip had not enough memory to store the 35 jpeg needed. I used Riot to convert the emojis to the smallest yet recognizible PNGs. We see on the screenshot tahat I could go from a ~ 60KB jpg file to a 6KB png file.

I used ESP32FS tutorial to upload the 35 images on the ESP32 chip. this tool only works on older version of Arduino IDE.

Using SPIFFS, I could display the webpage with the images embedded in the chip.

I had to remake all the programming, because the debounce didn’t work anymore and the javascript was using serial communication and that didn’t work anymore with this configuration. Unfortunately, I wrote over the code as I was making progress with suggestion of chatGPT and I haven’t saved all the step that lead to the final code.

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

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

// 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 = 0; // 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

String last_action = "";

// 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>ESP32 Rotary Encoder</title>
</head>
<body>
<h1>ESP32 Rotary Encoder</h1>
<div id="count">Count: </div>
<div id="last-action">Last Action: </div>

<script>
    async function fetchData() {
        try {
            const response = await fetch('/data');
            const data = await response.json();
            document.getElementById('count').innerText = "Count: " + data.rotaryCount;
            document.getElementById('last-action').innerText = "Last Action: " + data.last_action;
        } catch (error) {
            console.error('Error fetching data:', error);
        }
    }

    setInterval(fetchData, 1000);
</script>
</body>
</html>
)rawliteral";

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

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

    // 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", []() {
        String json = "{\"rotaryCount\":" + String(rotaryCount) + ", \"last_action\":\"" + last_action + "\"}";
        server.send(200, "application/json", json);
    });

    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

    // 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
            last_action = "SW Button pushed";
        } else {
            Serial.println(F("SW Button released")); // Print message when button is released
            last_action = "SW Button released";
        }    
    }

    // Rotary encoder verification
    if (actualStateCLK != previousStateCLK) {
        delay(10); // 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
                    last_action = "Counter-clockwise";
                } else { // When CLK and DT are similar, direction is clockwise
                    rotaryCount++; // Increment counter
                    Serial.println(F("clockwise")); // Display value on Serial Monitor
                    last_action = "Clockwise";
                }
                Serial.println(rotaryCount);

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

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

#define pinRocker1 25
#define pinRocker2 26
#define pinRocker3 27
#define pinRocker4 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, states, and direction
int rotaryCount = 0;
int previousStateSW;
int previousStateCLK;
bool isClockwise = false;

int previousRockerState1;
int previousRockerState2;
int previousRockerState3;
int previousRockerState4;

int activatedRockerCount = 0;

// HTML content with JavaScript for dynamic updates
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>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            function fetchData() {
                fetch('/data')
                    .then(response => response.json())
                    .then(data => {
                        document.getElementById('rotary-count').textContent = 'Rotary Count: ' + data.rotaryCount;
                        document.getElementById('rocker-count').textContent = 'Activated Rocker Switches: ' + data.activatedRockerCount;
                        document.getElementById('direction').textContent = 'Direction: ' + data.direction;
                    });
            }
            setInterval(fetchData, 1000);
        });
    </script>
</head>
<body>
<div id="rotary-count">Rotary Count: </div>
<div id="rocker-count">Activated Rocker Switches: </div>
<div id="direction">Direction: </div>
</body>
</html>
)rawliteral";

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

    pinMode(pinSW, INPUT_PULLUP);
    pinMode(pinDT, INPUT);
    pinMode(pinCLK, INPUT);
    pinMode(pinLED, OUTPUT);

    pinMode(pinRocker1, INPUT_PULLUP);
    pinMode(pinRocker2, INPUT_PULLUP);
    pinMode(pinRocker3, INPUT_PULLUP);
    pinMode(pinRocker4, INPUT_PULLUP);

    previousStateSW = digitalRead(pinSW);
    previousStateCLK = digitalRead(pinCLK);

    previousRockerState1 = digitalRead(pinRocker1);
    previousRockerState2 = digitalRead(pinRocker2);
    previousRockerState3 = digitalRead(pinRocker3);
    previousRockerState4 = digitalRead(pinRocker4);

    analogWrite(pinLED, 0);

    // 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");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

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

    server.on("/data", []() {
        String direction = isClockwise ? "Clockwise" : "Counterclockwise";
        String json = "{\"rotaryCount\":" + String(rotaryCount) + ",\"activatedRockerCount\":" + String(activatedRockerCount) + ",\"direction\":\"" + direction + "\"}";
        server.send(200, "application/json", json);
    });

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

void loop() {
    server.handleClient();

    int actualStateCLK = digitalRead(pinCLK);
    int actualStateDT  = digitalRead(pinDT);
    int actualStateSW  = digitalRead(pinSW);

    int currentRockerState1 = digitalRead(pinRocker1);
    int currentRockerState2 = digitalRead(pinRocker2);
    int currentRockerState3 = digitalRead(pinRocker3);
    int currentRockerState4 = digitalRead(pinRocker4);

    // Update direction based on the rotary encoder movement
    if (actualStateCLK != previousStateCLK) {
        delayMicroseconds(200); // Debounce delay
        if (digitalRead(pinCLK) == actualStateCLK) {
            previousStateCLK = actualStateCLK;

            if (actualStateCLK == LOW) {
                isClockwise = (actualStateCLK != actualStateDT);
                rotaryCount += isClockwise ? 1 : -1;
                rotaryCount = constrain(rotaryCount, 1, 5);
                Serial.println(rotaryCount);

                int pwmValue = map(rotaryCount, 1, 5, 0, 128);
                analogWrite(pinLED, pwmValue);
            }
        }
    }

    // Update activated rocker switch count
    if (currentRockerState1 != previousRockerState1 || 
        currentRockerState2 != previousRockerState2 || 
        currentRockerState3 != previousRockerState3 || 
        currentRockerState4 != previousRockerState4) {
        delay(50);
        activatedRockerCount = (currentRockerState1 == LOW) + (currentRockerState2 == LOW) + 
                            (currentRockerState3 == LOW) + (currentRockerState4 == LOW);
        Serial.print("Activated rocker switches: ");
        Serial.println(activatedRockerCount);

        previousRockerState1 = currentRockerState1;
        previousRockerState2 = currentRockerState2;
        previousRockerState3 = currentRockerState3;
        previousRockerState4 = currentRockerState4;
    }

    // Push button verification
    if (actualStateSW != previousStateSW) {
        delay(50); // Debounce delay
        previousStateSW = actualStateSW;
        Serial.println(actualStateSW == LOW ? "SW Button pushed" : "SW Button released");
    }
}
#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
            }
        }
    }
}

Here’s the code working :

Video editing

Also, I downloaded DaVinci Resolve and mad a first try at video editing.

Class archives

Impressions of the week

I was glad to finally succeed to make the bare minimum for my final project to work. I could spend the time left on improvement and there is a lot I can do. First, I had to upgrade the shell and debug the bouncing on the rotary encoder. I also wish to use a dedicated display so I will work on the Cheap Yellow Display. Also, I need to find a way to implement substractive fabrication processes. I thought about vinyl cutting to add information on the device or lasercutting to make a support for the screen (if the screen works).