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.
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.
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.
The printing went quite well.
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.
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.
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.
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.
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.
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.
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.
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.
Sewing¶
I used a scrap of fabric to sew a strap. I added PETG-printed buckles to tighten it.
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¶
- Final project remote control final design
- screen front-case (unmodified from printables)
- screen back-case (modified from printables)
- Holder for screen case and battery
- CYD JPG display
- CYD JPG Arrays
- CYD JPG and Touchscreen (don’t work)
- CYD code JPG autoswap
- CYD code final
- Fidget code final for TFT Display
- Fidget code final for phone
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.