Final Project development¶
This page follows the steps I made to complete my final project during the different week of the Fab Academy.
Week 01 - Plan and sketch for a potential final project¶
Socialization acceptance device¶
This idea came to me thinking about people with autistic syndrom for whom socialization is a big difficulty just like the ability to express their mindset. A lot of them have the will to meet and bond with people but they don’t always have the social skill to do so. At first, I thought on made it appear as a message on a t-shirt to be seen by everyone. A less difficult choice would be on a wearable small screen maybe on a bracer or as badges.
I imagined a fidget as an input device. Fidgets are often used with persons with autism for regulating themselves, through manipulating, massaging or chewing.
The person could use to express its mindset, through a mood gauge and socialbility acceptance. With a potentiometer, they could share their mood from angry, sad, neutral, good, or happy. With a bunch of switches, they could share their level of acceptance of socialization from “I need to be alone” to “I need support”. Designing it made me re-think with a dual-scale “mood/interaction acceptance”
Week 02 - Computer Aided Design¶
Input device¶
During the Computer aided design week, I designed a 3D remote device for my final project.
The user couldn’t enter its mood through through the potentiometer and enter his interaction acceptance through the switch activated. To validate the state and send the response to an output device, he needs to push the red button.
Output display¶
For the output, I thought to display a smiley face as it’s an international form of communicating even without reading hability.
I started making a table, combining a mood scale and intercation acceptance scale with smileys. Combining them with Emoji Kitchen could provide a reperesentation of the user’s mindset.
I also started to make a svg file of smileys to make them appear on the input device with cutting or engraving.
Week 03 - Computer controlled cutting¶
I decided to make a plywood model of my final project to see the size. I’m aware that living hinge aren’t strong enough for something that will be manipulated a lot. I have rebuilt my model on fusion using metal sheet.
On my third attempt, I could finally make a nice lasercut and assemble it.
The hinges don’t completely fit, probably because the pattern make it too loose. The size could be a bit longer to facilitate manipulation between the two hands and i’d like it to be thinner to handed it more easily.
For my final project, I don’t think I will use the model I created this week, beacuse it’s too fragile and absolutely not ergonomic. Anyway, it helps me defining more precisely what I need : a shape more elongated to facilitate manibpulation, a more flexible material like rubber or silicone, a reccessed button to avoid unattended push.
Week 06 - Embedded programming¶
During this week, I learned about programming on the RP2040 Micro-controller and chose to test potentiometer programming in prevision of my final project.
To connect the potentiometer to my board, I added connexion header on it to have a better access at the 3.3V pin.
A2 and A3 are available but A3 is link to a 1000 ohms resistor. Potentiometer being a variable resistor, I thought using the pin without resistor was more pertinent. But after testing the two of them, they both works well.
I made this setup.
ChatGPT provided me this code to this prompt and some specification to correct issues:
make an arduino code for a potentiometer lighting 1 then 2 then 3 led as it is turn.
const int potPin = A2; // analog pin for the potentiometer
const int ledPin1 = 26; // the number of the first LED pin
const int ledPin2 = 0; // the number of the second LED pin
const int ledPin3 = 1; // the number of the third LED pin
const int numLEDs = 3; // number of LEDs
void setup() {
pinMode(ledPin1, OUTPUT);
pinMode(ledPin2, OUTPUT);
pinMode(ledPin3, OUTPUT);
}
void loop() {
// Read the potentiometer value
int potValue = analogRead(potPin);
// Map the potentiometer value to the range of LEDs
int ledLevel = map(potValue, 0, 1023, 0, numLEDs + 1);
// Turn on the LEDs based on the mapped value
digitalWrite(ledPin1, ledLevel >= 1);
digitalWrite(ledPin2, ledLevel >= 2);
digitalWrite(ledPin3, ledLevel >= 3);
delay(100); // Adjust delay as needed to control the responsiveness
}
potPin
constant define the Analog pin on A2 and don’t need to be set as input in the setup part.In the main program,
analogRead
will get a value between 0 and 1023 from the potentiometer and make a variable from it.
Then it creates from it a ledLevel
variable with the map
functions. This functions will make corresponding potValue (between 0 and 1023) and ledLevel (between 0 and 4 “pinLEd+1”)
Then it will turn on LED for each ledLevel, corresponding to the potentionmeter value.
The final code is here
Serial Analog test¶
I also test AnalogInOutSerial example to have a monitor feedback of the value of the potentiometer.
I change the code a bit to turn on all LEDs with the fade.
The final code is here
OLED Screen¶
For testing an OLED screen, I used the SSD1306 screen with the U8g2 library as recommended in Neil’s documentation.
I used chat GPT to generate a code to display a text. I modified it a little to set the pins SDA and SCL and change the default text.
Here’s the final code :
#include <U8g2lib.h>
#include <Wire.h>
// OLED display constructor
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 7, /* data=*/ 6);
void setup(void) {
// Initialize the OLED display
u8g2.begin();
}
void loop(void) {
// Clear the screen
u8g2.clearBuffer();
// Set font and position
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.setCursor(0, 20); // Set position (x, y)
// Write text to the screen
u8g2.print("It works! _o/");
// Send the buffer to the display
u8g2.sendBuffer();
// Delay to control the refresh rate
delay(1000);
}
Unfortunately, my I2C connector broke but I learned from datasheet that all of the pins have a functions for I2C. I noticed that not all kind of characters are displayed. I could explore that later. I also should explore image displaying.
Week 11 - Input device¶
Personnal assignment - I have to test a selector and switches and program different response mixing them. If possible try with screen display then with e-paper
During this week, I made a bunch of tests to understand how the selector works. I also implement what I learned from output device week and a bit more about OLED screen.
I ended up with this code :
#define pinCLK 1 // CLK pin connected to GPIO 1
#define pinDT 2 // DT pin connected to GPIO 2
#define pinSW 3 // SW pin connected to GPIO 3
#include <U8g2lib.h>
#include <Wire.h>
// OLED display constructor
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 7, /* data=*/ 6);
int count = 0; // Var to know how many clicks occured (increment clockwise and decrement counter_clockwise)
int previousStateSW; // Var to memorize previous SW state to compare it with actual state
int previousStateCLK; // Var to memorize previous CL state to compare it with actual state
void setup() {
Serial.begin(9600);
// Initialize the OLED display
u8g2.begin();
pinMode(pinSW, INPUT_PULLUP); // INPUT_PULLUP to avoid 'floating'
pinMode(pinDT, INPUT);
pinMode(pinCLK, INPUT);
previousStateSW = digitalRead(pinSW); // initial value for SW
previousStateCLK = digitalRead(pinCLK); // initial value for CLK
delay(200); // delay to stabilize signal before the loop
}
void loop() {
int actualStateCLK = digitalRead(pinCLK); // reading CLK value
int actualStateDT = digitalRead(pinDT); // reading DT value
int actualStateSW = digitalRead(pinSW); // reading SW value
// Clear the screen
u8g2.clearBuffer();
// *************************
// Push Button verification
// *************************
if (actualStateSW != previousStateSW) { // Checking if SW state has changed
previousStateSW = actualStateSW; // Save new state
if (actualStateSW == LOW) {
Serial.println(F("SW Button pushed")); // Display State on serial monitor
// Set font and position
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.setCursor(0, 20); // Set position (x, y)
// Write text to the screen
u8g2.print("SW Button :");
u8g2.setCursor(0, 40); // Set position (x, y)
// Write text to the screen
u8g2.print("Pushed");
} else {
Serial.println(F("SW Button released"));
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.setCursor(0, 20); // Set position (x, y)
// Write text to the screen
u8g2.print("SW Button :");
u8g2.setCursor(0, 40); // Set position (x, y)
// Write text to the screen
u8g2.print("Released");
}
// Send the buffer to the display
u8g2.sendBuffer();
delay(10); // Delay to avoid rebounce
}
// ***************************
// Rotary encoder verification
// ***************************
if (actualStateCLK != previousStateCLK) { // Checking if SW state has changed
previousStateCLK = actualStateCLK; // Save new state
if (actualStateCLK == LOW) {
if (actualStateCLK != actualStateDT) { // comparing CLK and DT, if they're different, direction is counter-clockwise
count--; // decrement counter
Serial.print(F("Direction = counter-clockwise | Counter value = ")); // Display value on Serial Monitor
Serial.println(count);
u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.setCursor(0, 15); // Set position (x, y)
// Write text to the screen
u8g2.print("Direction :");
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.setCursor(0, 28); // Set position (x, y)
u8g2.print("counter-clockwise");
u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.setCursor(0, 45); // Set position (x, y)
// Write text to the screen
u8g2.print("Counter value = ");
u8g2.setCursor(0, 60); // Set position (x, y)
u8g2.println(count);
// Send the buffer to the display
u8g2.sendBuffer();
}
else { // when CLK and DT are similar, direction is clockwise
count++; // increment counter
Serial.print(F("Direction = clockwise | Counter value = ")); // Display value on Serial Monitor
Serial.println(count);
u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.setCursor(0, 15); // Set position (x, y)
// Write text to the screen
u8g2.print("Direction :");
u8g2.setCursor(0, 30); // Set position (x, y)
u8g2.print("clockwise");
u8g2.setCursor(0, 45); // Set position (x, y)
// Write text to the screen
u8g2.print("Counter value = ");
u8g2.setCursor(0, 60); // Set position (x, y)
u8g2.println(count);
// Send the buffer to the display
u8g2.sendBuffer();
}
delay(1); // delay to avoid CLK rebounce
}
}
}
The results was very satisfactory.
I still need to implement in the code the limit to the counter value to the number I decided (I think of -2 to 2 for the 5 levels of mood) and display an image instead of strings.
I didn’t have time to work on e-paper but I found some WEMOS ePaper 2.13 Shield with driver.
Week 13 - Networking and communication¶
During this week, I succeeded to display on a distant screen, inputs from remote controller board.
Using the previous rotary encoder code, I modified it with with blocks of code from examples of ESP NOW.
I succeeded to have a feedback of the SW button being pushed or released but not from the rotary encoder.
- Code for periperal display of message from controller
- Code for controller transmitting rotary encoder inputs
The cheap yellow screen library also need modification to work.
Week 14 - Interface and application programming¶
I made an html interface that could be control with an input device through serialport.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IMU Data and 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; /* Add some spacing between buttons */
}
#result-container {
text-align: center;
margin-top: 20px;
}
#result-button-container {
text-align: center;
margin-bottom: 20px;
}
#result-image {
max-width: 20%; /* Adjust the maximum width of the result image container */
margin: 0 auto; /* Center the result image horizontally */
}
#result-image img {
max-width: 100%; /* Ensure the result image fits within its container */
height: auto; /* Maintain aspect ratio */
}
</style>
</head>
<body>
<div class="container">
<div class="image-container">
<h2>Mood</h2>
<div id="image-container1">
<img src="03_smiley.jpg" 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="30_smiley.jpg" 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>
<!-- Button to trigger the open_IMU function -->
<button class="btn" value="Open IMU" onclick="open_IMU()">Open IMU</button>
<!-- Container to display the container index value -->
<div id="container-index-value"></div>
<!-- JavaScript code -->
<script>
var currentIndex1 = 2; // Index for the first image array
var currentIndex2 = 2; // Index for the second image array
var images1 = ["05_smiley.jpg", "04_smiley.jpg", "03_smiley.jpg","02_smiley.jpg", "01_smiley.jpg"];
var images2 = ["50_smiley.jpg", "40_smiley.jpg", "30_smiley.jpg", "20_smiley.jpg", "10_smiley.jpg"];
var values1 = [5, 4, 3, 2, 1];
var values2 = [50, 40, 30, 20, 10];
var allImages = [
{ src: "smiley_mix11.jpg", value: 11 },
{ src: "smiley_mix12.jpg", value: 12 },
{ src: "smiley_mix13.jpg", value: 13 },
{ src: "smiley_mix14.jpg", value: 14 },
{ src: "smiley_mix15.jpg", value: 15 },
{ src: "smiley_mix21.jpg", value: 21 },
{ src: "smiley_mix22.jpg", value: 22 },
{ src: "smiley_mix23.jpg", value: 23 },
{ src: "smiley_mix24.jpg", value: 24 },
{ src: "smiley_mix25.jpg", value: 25 },
{ src: "smiley_mix31.jpg", value: 31 },
{ src: "smiley_mix32.jpg", value: 32 },
{ src: "smiley_mix33.jpg", value: 33 },
{ src: "smiley_mix34.jpg", value: 34 },
{ src: "smiley_mix35.jpg", value: 35 },
{ src: "smiley_mix41.jpg", value: 41 },
{ src: "smiley_mix42.jpg", value: 42 },
{ src: "smiley_mix43.jpg", value: 43 },
{ src: "smiley_mix44.jpg", value: 44 },
{ src: "smiley_mix45.jpg", value: 45 },
{ src: "smiley_mix51.jpg", value: 51 },
{ src: "smiley_mix52.jpg", value: 52 },
{ src: "smiley_mix53.jpg", value: 53 },
{ src: "smiley_mix54.jpg", value: 54 },
{ src: "smiley_mix55.jpg", value: 55 },
];
async function open_IMU() {
try {
// Requesting the serial port
const port = await navigator.serial.requestPort();
// Opening the serial port
await port.open({ baudRate: 115200, bufferSize: 1 });
// Getting the reader for the port
const reader = port.readable.getReader();
while (true) {
// Waiting for line end
let lineEnd = false;
let line = '';
while (!lineEnd) {
const { value, done } = await reader.read();
let chr = String.fromCharCode(value[0]);
line += chr;
if (chr === '\n' || done) lineEnd = true;
}
if (line.trim() === "SW Button pushed") {
// If the button is pushed, call addValues
addValues();
} else if (line.trim() === "clockwise") {
// If the rotation is clockwise, call nextImage
nextImage(1);
} else if (line.trim() === "counter-clockwise") {
// If the rotation is counter-clockwise, call previousImage
previousImage(1);
}
// Extracting container index
const vars = line.split(',');
const containerIndex = vars[0].trim().toString(); // Convert to string
// Updating the container index value on the webpage
document.getElementById("container-index-value").innerText = "Container Index: " + containerIndex;
}
} catch (error) {
// Handling any errors that occur during the process
console.error("An error occurred:", error);
}
}
function previousImage(containerIndex) {
if (containerIndex === 1) {
if (currentIndex1 === 0) {
return; // Do nothing if already at the first image
}
currentIndex1--;
updateImage(1);
} else if (containerIndex === 2) {
if (currentIndex2 === 0) {
return; // Do nothing if already at the first image
}
currentIndex2--;
updateImage(2);
}
}
function nextImage(containerIndex) {
if (containerIndex === 1) {
if (currentIndex1 === images1.length - 1) {
return; // Do nothing if already at the last image
}
currentIndex1++;
updateImage(1);
} else if (containerIndex === 2) {
if (currentIndex2 === images2.length - 1) {
return; // Do nothing if already at the last image
}
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 result = value1 + value2;
// Find the image object with the matching value
var resultImage = allImages.find(function(image) {
return image.value === result;
});
if (resultImage) {
var resultImageSrc = resultImage.src;
var resultImageValue = resultImage.value; // Get the personalized value
// document.getElementById("result-text").innerText = "Result: " + result + " (" + resultImageValue + ")"; //
document.getElementById("result-image").innerHTML = "<img src='" + resultImageSrc + "' alt='Result Image'>";
} else {
document.getElementById("result-text").innerText = "Result: Invalid";
document.getElementById("result-image").innerHTML = ""; // Clear previous result image
}
}
</script>
</body>
</html>
#define pinCLK 1 // CLK pin connected to GPIO 1
#define pinDT 2 // DT pin connected to GPIO 2
#define pinSW 3 // SW pin connected to GPIO 3
int count = 0; // Var to know how many clicks occured (increment clockwise and decrement counter_clockwise)
int previousStateSW; // Var to memorize previous SW state to compare it with actual state
int previousStateCLK; // Var to memorize previous CL state to compare it with actual state
void setup() {
Serial.begin(9600);
pinMode(pinSW, INPUT_PULLUP); // INPUT_PULLUP to avoid 'floating'
pinMode(pinDT, INPUT);
pinMode(pinCLK, INPUT);
previousStateSW = digitalRead(pinSW); // initial value for SW
previousStateCLK = digitalRead(pinCLK); // initial value for CLK
delay(200); // delay to stabilize signal before the loop
}
void loop() {
int actualStateCLK = digitalRead(pinCLK); // reading CLK value
int actualStateDT = digitalRead(pinDT); // reading DT value
int actualStateSW = digitalRead(pinSW); // reading SW value
// *************************
// Push Button verification
// *************************
if(actualStateSW != previousStateSW) { // Checking if SW state has changed
previousStateSW = actualStateSW; // Save new state
if(actualStateSW == LOW)
Serial.println(F("SW Button pushed")); // Display State on serial monitor
else
Serial.println(F("SW Button released"));
delay(10); // Delay to avoid rebounce
}
// ***************************
// Rotary encoder verification
// ***************************
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
count++; // decrement counter
Serial.println(F("clockwise")); // Display value on Serial Monitor
// Serial.println(count);
}
else { // when CLK and DT are similar, direction is clockwise
count--; // increment counter
Serial.println(F("counter-clockwise")); // Display value on Serial Monitor
// Serial.println(count);
}
}
}
}
}
I create an online version of the interface to test it.
I also wanted to try it on phone but Android Chrome does not allowed serial port communication
Week 15¶
I started to design my final board using an ESP32WROOM32UE and read the datasheet
Week 16 - System integration¶
I made the final design for my PCB
Then I made the BOM for Electronics part.
Nb | Element | Link | Price | Remarks |
---|---|---|---|---|
1 | Micro-controller ESP32-WROOM-32UE | |||
1 | KY-040 Rotary Encoder | |||
5 | Rocker Switch | https://www.sparkinter.com/pdf/230203-0060103.pdf | Cut-out 19x13mm | |
1 | Button | |||
1 | Regulator 3.3V 1A | |||
1 | vertical connector SMD 1x05 2.54mm | |||
1 | horizontal connector SMD 1x06 2.54mm | FTDI | ||
1 | vertical connector SMD 2x05 2.54mm | switches | ||
1 | resistor 10k | |||
1 | resistor 1K | |||
1 | LED Clear Blue | |||
2 | resistor O ohm | |||
1 | capacitor 0.1 uF | |||
1 | capacitor 1 uF | |||
1 | capacitor 10 uF | |||
1 | switch slide | |||
10 | cable | |||
1 | CCL FR1 |
And then I modelized the different part of the remote.
And printed the battery holder
Week 17¶
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 :
Week 18¶
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.
Programmed schedule from Midterm :¶
Week 12 - Molding and casting Create Molding from 3D scan of handshaped model of the fidget for more organic shape. Have to define how every input assembly with it.
Week 13 - Networking and casting
Work on communication between Fidget and Display. Actually working cable, find more easy way to communicate. Wired, distant, inductive, nfc ?
Week 14 - Interface and application programming Send images on micro-controller memory - choose images from switch and selector - display images on screen
Week 15 - Wildcard week Create support for display screen
Week 16 - Apllications and implicatiosn Create support for display screen
Week 17 - Systeme integration Debugging
Week 18 -