/* ESP32 WiFi Scanning example */
#include "AudioTools.h"
#include "driver/rtc_io.h"
#include "driver/i2s_std.h"
#include "driver/i2c.h"
#include "esp_err.h"
#include "freertos/ringbuf.h"
#include "freertos/task.h"
#include "ezButton.h"
#include "MainHeader.h"
#include <vector>
constexpr int I2CTimeout = 1000; //1 second
constexpr int SSD1306_WIDTH = 128;
constexpr int SSD1306_HEIGHT = 64;
#define I2C_SDA 21
#define I2C_SCL 22
#define WAKEUP_GPIO GPIO_NUM_32
#define SW 32
#define DT 35
#define CLK 34
#define SecondButton 33
#define DIRECTION_CW 0 // clockwise direction
#define DIRECTION_CCW 1 // counter-clockwise direction
int counter = 0;
int direction = DIRECTION_CW;
int CLK_state;
int prev_CLK_state;
ezButton Button(32);
bool UpClicked = false;
bool DownClicked = false;
bool EnterClicked = false;
bool EnterClickedLong = false;
unsigned long pressedTime = 0;
unsigned long releasedTime = 0;
unsigned long lastButtonPress = 0;
unsigned long PreviousTimeTwo = 0;
uint8_t DisplayBuffer[1024];
bool UpdateNeeded = false;
enum class FontSize : int {
SMALL = 1,
MEDIUM = 2,
LARGE = 3
};
void setup() {
Serial.begin(115200);
Serial.println("Starting");
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
pinMode(SW, INPUT_PULLUP);
pinMode(SecondButton, INPUT_PULLUP);
prev_CLK_state = digitalRead(CLK);
Button.setDebounceTime(50);
i2c_config_t conf = { //Configuration for I2C bus.
conf.mode = I2C_MODE_MASTER,
conf.sda_io_num = I2C_SDA,
conf.scl_io_num = I2C_SCL,
conf.sda_pullup_en = GPIO_PULLUP_ENABLE,
conf.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master = {.clk_speed = 400000}, //I2C speed, 400kHz ala "fast mode".
.clk_flags = 0
};
i2c_param_config(I2C_NUM_0, &conf); //Configurate the bus.
i2c_driver_install(I2C_NUM_0, conf.mode, 0, 0, 0); //Install the driver.
memset(DisplayBuffer, 0, 1024); //Clear the buffer incase there is some junk.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::DISPLAY_OFF)); //Turn off the display so we can configure it.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SET_DISPLAY_CLOCK_DIV));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0x80); //Divide by 1.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SET_MULTIPLEX));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306_HEIGHT - 1)); //Height - 1, 0-63.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SET_DISPLAY_OFFSET));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0x00); //No offset.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SET_START_LINE) | 0x0); //Set the start line to 0 aka top.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::CHARGE_PUMP));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0x14); //Enable charge pump, 0x10 to disable (when supplied with 7.5V).
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::MEMORY_MODE));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0x00); //Horizontal.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SEG_REMAP) | 0x1); //Set segment remap to 127.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::COM_SCAN_DEC));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SET_COM_PINS));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0x12); //Alternative, disable COM left/right remap.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SET_CONTRAST));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0xFF); //0xFF = 255, max brightness.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SET_PRECHARGE));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0xF1); //1 : 15, recommended.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SET_VCOM_DETECT));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0x40); //0.77 * VCC, recommended.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::ENTIRE_DISPLAY_ON_RESUME)); //Output RAM to display.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::NORMAL_DISPLAY)); //Set normal display.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::DISPLAY_ON)); //Turn the display on.
}
void loop() {
ScreenLoop();
delay(10); // this speeds up the simulation
}
int CurMenu = 2; //0 = Main, 1 = A2DP, 2 = Stream, 3 = Settings, 4 = Sleep
int CurChoice = 0;
struct Channel {
const char* name = "";
const char* url = "";
};
Channel Channels[] = { //Maybe don't use these, bet the radio stations won't love it.
{"Eurobeat", "https://eurobeat.stream.laut.fm/eurobeat?pl=pls"},
{"Underground 80's", "https://ice6.somafm.com/u80s-256-mp3"},
{"Eurodance", "https://stream.laut.fm/eurodance.pls"},
{"Radiorock", "https://stream.laut.fm/radiorock.pls"},
{"Hiphop Forever", "https://stream.laut.fm/hiphop-forever.pls"},
{"Hardstyle", "https://stream.laut.fm/hardstyle.pls"}
};
char* SArtist = "Nibba";
char* STitle = "Balls Deep In Tino";
int BatteryP = 70;
float Voltage = 36.2f;
int ChannelChoice = 1;
void ScreenLoop(){
Clear();
ScreenRegisterInput();//Register inputs from rotary encoder
switch (CurMenu){
case 0: ScreenMenu(); break;
case 2: StreamingMenu(); break;
case 3: TextMenus(); break;
}
ScreenResetInput(); //Reset inputs set by ScreenRegisterInput();
Update(false);
}
void DrawBattery() {
char Buffer[16]; //Buffer used for the voltage to char conversion.
sprintf(Buffer, "%.1fV", Voltage); //Convert the in into a char and add "V", save to Buffer.
DrawRect(2, 2, 30, 15); //Draw battery outlines.
DrawFilledRect(32, 6, 3, 7); //Draw batterys positive terminal.
DrawString(37, 6, Buffer, FontSize::SMALL, false); //Draw voltage next to the battery.
DrawFilledRect(3, 3, map(BatteryP, 0, 100, 0, 28), 13); //Draw battery indicator.
/*
u8g2.drawFrame(2, 2, 30, 15); //Draw battery outlines.
u8g2.drawBox(32, 6, 3, 7); //Draw batterys positive terminal.
u8g2.setFont(u8g2_font_8x13_t_cyrillic); //Set font for voltage.
u8g2.drawStr(37, 14, Buffer); //Draw voltage next to the battery.
u8g2.drawBox(3, 3, map(gVars.Power.BatteryPercent, 0, 10, 0, 28), 13); //Draw battery indicator.
*/
}
void StreamingMenu(){
//gGI.ActiveMedia = CurrentMedia::INTERNET_RADIO;
DrawBattery();
//u8g2.setFont(u8g_font_7x14); //Set font for info.
//switch (gGI.RadioState) {
//case InternetRadioState::Uninitialized :
//case InternetRadioState::Initialized:
//case InternetRadioState::CheckingForOldConnection:
//case InternetRadioState::Scanning:{
// u8g2.drawStr(20, 30, "Wait"); break;
// break;
//}
//default: break;
//}
DrawRect(2, 20, 122, 16); //Draw filled rectangle for the title background.
DrawString(5, 24, Channels[ChannelChoice].name, FontSize::SMALL, true); //Draw the channel name.
DrawString(2, 40, SArtist, FontSize::SMALL, false); //Draw artist.
DrawString(2, 50, STitle, FontSize::SMALL, false); //Check if we have "-" in our title and draw song's name.
//u8g2.drawFrame(2, 20, 122, 16); //Draw frame around the title.
//u8g2.drawStr(5, 33, Channels[gVars.Streaming.Channel].name);
//if (gRadioInfo.Artist) u8g2.drawStr(2, 50, gRadioInfo.Artist); //Draw artist.
//if (gRadioInfo.Seperator) u8g2.drawStr(2, 64, gRadioInfo.SongTitle); //Check if we have "-" in our title and draw song's name.
}
void ScreenMenu(){
if (UpClicked) CurChoice--;
if (DownClicked) CurChoice++;
if (EnterClicked){
CurMenu = CurChoice + 1;
CurChoice = 0;
return;
}
CurChoice = Wrap(CurChoice, 3, 0);
const char* MainMenus[] = { "A2DP", "STREAM", "SETTINGS", "SLEEP" }; //Main menu items.
const int MainMenusPositions[] = { 10, 10, 10, 70, 10, 30, 50, 10 }; //Text positions, 4 first are X positions, last 4 are Y positions.
DrawString(75, 57, "DB V2.1", FontSize::SMALL, true);
for (int i = 0; i < (sizeof(MainMenus) / sizeof(MainMenus[0])); ++i) { //Loop through all menu items.
bool Bold = CurChoice == i; //Check if this is the chosen option.
DrawString(MainMenusPositions[i], MainMenusPositions[i + 4], MainMenus[i], FontSize::SMALL, Bold);
if (CurChoice == i) {
int Width = GetStringWidth(MainMenus[i], FontSize::SMALL, true);
DrawRect(MainMenusPositions[i] - 3, MainMenusPositions[i + 4] - 5, Width + 6, 16);
}
}
}
void TextMenus(){
if (UpClicked) CurChoice--;
if (DownClicked) CurChoice++;
CurChoice = Wrap(CurChoice, 4, 0);
char* SettingsMenus[5] = { "Audio", "Power Management", "Screen", "Misc", "Return" };
int NumItems = sizeof(SettingsMenus) / sizeof(SettingsMenus[0]) - 1;
DrawString(5, 10, SettingsMenus[CurChoice == 0 ? NumItems : CurChoice - 1], FontSize::SMALL, false);
DrawString(5, 50, SettingsMenus[CurChoice == NumItems ? 0 : CurChoice +1], FontSize::SMALL, false);
DrawString(5, 30, SettingsMenus[CurChoice], FontSize::SMALL, true);
DrawFilledRect(125, 10 + (CurChoice * (10 /NumItems)), 2, 10 /NumItems );
}
void TextMenusTwo(){
}
void ChangeBrightness(uint8_t Brightness) { //0-255.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::DISPLAY_OFF));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::SET_CONTRAST));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), Brightness);
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::DISPLAY_ON));
}
void Update(bool Force) {
if (!UpdateNeeded && !Force) return; //No need to update if nothing has changed.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::PAGE_ADDR));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0x00); //Page start address.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0xFF); //Page end address.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306Commands::COLUMN_ADDR));
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), 0x00); //Column start address.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::COMMAND), static_cast<uint8_t>(SSD1306_WIDTH - 1)); //Page start address, 0-127.
WriteRegister(static_cast<uint8_t>(I2CDeviceAdresses::SSD1306), static_cast<uint8_t>(SSD1306Adresses::DATA), DisplayBuffer, sizeof(DisplayBuffer)); //Send the buffer data to the display.
UpdateNeeded = false; //Reset the update flag.
}
void Clear() {
memset(DisplayBuffer, 0, sizeof(DisplayBuffer)); //Clear the buffer by settings every byte to 0 aka off.
UpdateNeeded = true; //Mark that an update is needed.
}
void SetPixel(int x, int y) { //Lets keep the calculations seperated instead of doing them in the buffer part to make it easier to read (its ass).
if (x < 0 || x >= SSD1306_WIDTH || y < 0 || y >= SSD1306_HEIGHT) return; //Out of bounds check.
uint8_t page_index = static_cast<int>(y) / 8; //Byte row index (0-7).
uint8_t bit_position = static_cast<int>(y) % 8; //Bit position within the byte (0-7).
uint16_t byte_index = static_cast<int>(x) + (page_index * SSD1306_WIDTH); //Calculate the byte index in the buffer.
uint8_t bitmask = (1 << bit_position); //Create a bitmask for the specific bit.
DisplayBuffer[byte_index] |= bitmask; //Set the bit to 1 (pixel on).
UpdateNeeded = true; //Mark that an update is needed.
}
void DrawLine(int x, int y, int x2, int y2) { //Bresenham's algorithm, integer math is love https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm.
int dx = (x2 > x) ? (x2 - x) : (x - x2); //Calculate the difference between start and end.
int dy = (y2 > y) ? (y2 - y) : (y - y2);
int sx = (x < x2) ? 1 : -1; //Define the direction
int sy = (y < y2) ? 1 : -1;
int err = dx - dy; //Difference between ideal path and actual grid.
while (1) {
SetPixel(x, y); //Send it to the buffer.
if (x == x2 && y == y2) break; //Check if we are done.
int e2 = 2 * err; //Calculate the new error term, multiply by 2 to avoid floating point.
if (e2 > -dy) { //Check if the error is greater than the negative dy.
err -= dy; //Adjust the error term.
x += sx; //Move in the x direction.
}
if (e2 < dx) { //Same as above but for y.
err += dx;
y += sy;
}
}
}
void DrawRect(int x, int y, int w, int h) {
DrawLine(x, y, x + w - 1, y); //Top
DrawLine(x, y + h - 1, x + w - 1, y + h - 1); //Bottom
DrawLine(x, y, x, y + h - 1); //Left
DrawLine(x + w - 1, y, x + w - 1, y + h - 1); //Right
}
void DrawFilledRect(int x, int y, int w, int h) {
for (int i = x; i < x + w && i < SSD1306_WIDTH; i++) { //Loop through the width.
for (int j = y; j < y + h && j < SSD1306_HEIGHT; j++) { //Loop through the height.
SetPixel(i, j);
}
}
}
void DrawCircle(int x0, int y0, int r) { //Midpoint algorithm https://en.wikipedia.org/wiki/Midpoint_circle_algorithm.
int x = r; //Start at the rightmost point of the circle.
int y = 0; //Start at the topmost point of the circle.
int err = 0;
while (x >= y) { //Only need to calculate 1/8 of the circle due to symmetry.
SetPixel(x0 + x, y0 + y);
SetPixel(x0 + y, y0 + x);
SetPixel(x0 - y, y0 + x);
SetPixel(x0 - x, y0 + y);
SetPixel(x0 - x, y0 - y);
SetPixel(x0 - y, y0 - x);
SetPixel(x0 + y, y0 - x);
SetPixel(x0 + x, y0 - y);
if (err <= 0) {
y += 1;
err += 2 * y + 1;
}
if (err > 0) {
x -= 1;
err -= 2 * x + 1;
}
}
}
void DrawChar(int x, int y, char c, FontSize Size, bool Bold) {
if (c < 32 || c > 126) return; //Out of bounds.
const uint8_t* Font = Font5x7[c - 32]; //Get the font data for the character.
for (int col = 0; col < 5; col++) { //Each character is 5 columns wide.
int column_data = static_cast<int>(Font[col]); //Get the column data.
for (int row = 0; row < 7; row++) { //Each column is 7 bits high.
if (column_data & (1 << row)) { //Check if the bit is set.
for (int sx = 0; sx < static_cast<int>(Size); sx++) { //Scale the pixel size.
for (int sy = 0; sy < static_cast<int>(Size); sy++) {
int px = x + col * static_cast<int>(Size) + sx; //Calculate the pixel position.
int py = y + row * static_cast<int>(Size) + sy;
SetPixel(px, py);
if (Bold) {
SetPixel(px + 1, py);
if (static_cast<int>(Size) > 1) { //Check if the size if greater than 1 to avoid overdrawing.
SetPixel(px, py + 1);
SetPixel(px + 1, py + 1);
}
}
}
}
}
}
}
}
void DrawString(int x, int y, const char* str, FontSize Size, bool Bold) {
if (!str) return; //Check for null pointer.
int Width = (5 * static_cast<int>(Size)) + (Bold ? 1 : 0);
for (int i = 0; str[i] != '\0'; i++) { //Loop through each character.
DrawChar(x + i * (Width + static_cast<int>(Size)), y, str[i], Size, Bold);
}
}
int GetStringWidth(const char* str, FontSize Size, bool Bold) {
uint8_t Width = (5 * static_cast<int>(Size)) + (Bold ? 1 : 0);
uint8_t Length = strlen(str); //Get the length of the string.
if (Length == 0) return 0; //Return 0 if the string is empty.
return Length * Width + (Length - 1) * static_cast<int>(Size); //Calculate the total width.
}
void WriteRegister(uint8_t Address, uint8_t Registry, uint8_t Data) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); //Create a new command link.
if (!cmd) return;
i2c_master_start(cmd); //Start.
i2c_master_write_byte(cmd, (Address << 1) | I2C_MASTER_WRITE, true); //Address with write bit.
i2c_master_write_byte(cmd, Registry, true); //Write registry.
i2c_master_write_byte(cmd, Data, true); //Write data.
i2c_master_stop(cmd); //Stop.
i2c_master_cmd_begin(I2C_NUM_0, cmd, pdMS_TO_TICKS(I2CTimeout)); //Execute the command link.
i2c_cmd_link_delete(cmd); //Delete the command link.
}
void WriteRegister(uint8_t Address, uint8_t Registry, uint8_t* Data, uint16_t Length) {
esp_err_t Err;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
if (!cmd) return;
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (Address << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, Registry, true);
i2c_master_write(cmd, Data, Length, true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, pdMS_TO_TICKS(I2CTimeout));
i2c_cmd_link_delete(cmd);
}
void ScreenRegisterInput(){
Button.loop();
CLK_state = digitalRead(CLK);
if (CLK_state != prev_CLK_state && CLK_state == 1) {
// if the DT state is HIGH
// the encoder is rotating in counter-clockwise direction => decrease the counter
if (digitalRead(DT) != CLK_state) {
DownClicked = true;
Serial.println("Rotate Neg");
} else {
// the encoder is rotating in clockwise direction => increase the counter
UpClicked = true;
Serial.println("Rotate Pos");
}
}
prev_CLK_state = CLK_state;
if (Button.isReleased()) {
//if ( (millis() - pressedTime) < 1000 ){
EnterClicked = true;
Serial.println("A short press is detected");
// }
//if ( (millis() - pressedTime) > 1000 ){
// EnterClickedLong = true;
// Serial.println("A long press is detected");
// }
}
if (digitalRead(SecondButton) == LOW){
EnterClickedLong = true;
Serial.println("A long press is detected");
}
}
void ScreenResetInput(){
//if ((digitalRead(SW) == HIGH) && (EnterClicked == 1 || EnterClickedLong == 1)) { // unclick
UpClicked = 0;
DownClicked = 0;
EnterClicked = 0;
EnterClickedLong = 0;
//}
}
int Wrap(int Value, int Max, int Min){
if (Value > Max) return Min;
if (Value < Min) return Max;
return Value;
}
float WrapFloat(float Value, float Max, float Min){
if (Value > Max) return Min;
if (Value < Min) return Max;
return Value;
}
void Sleep(){
esp_sleep_enable_ext0_wakeup(WAKEUP_GPIO, 0); //1 = High, 0 = Low
esp_deep_sleep_start();
}