Multifunctional Bioresonance Therapy device build and testing (Rife, Zapper, Schumann Resonance...)

This is a simple multifunctional device that, thanks to the high precision of the generated frequencies (with two decimal places), can successfully emulate almost all known therapeutic devices for frequency treatment.

Bioresonance therapy is a type of alternative treatment that is based on the idea that the body’s cells, tissues, and organs emit electromagnetic frequencies. The premise behind bioresonance therapy is that these frequencies can be measured, analyzed, and potentially rebalanced to improve health and well-being. The goal of bioresonance therapy is to correct these imbalances by sending counteracting frequencies back into the body to restore harmony. Bioresonance devices typically involve electrodes that are placed on the skin or sometimes in direct contact with acupuncture points or other parts of the body.

The device that is the subject of analysis in this video represents the part that generates a specific frequency or a series of frequencies. Applicable frequencies specifically for certain diseases can be found in several places, and for example I have provided some sources and tables (at the end of the text.) And before I start describing and testing this device, I want to emphasize that this method of treatment is not widely accepted in scientific and medical circles, because there is not enough scientific evidence for its effectiveness, so it is often considered pseudoscience. In this video I want to capture the technical side of this, and similar devices, and I have no intention or knowledge in that area to discuss its effectiveness. From a technical point of view, I can certainly guarantee that this particular device has absolutely no negative impact on the human body, since it is powered by a battery and is actually a generator of weak rectangular signals with an small amplitude. Actually in everyday life, we are constantly exposed to much more aggressive sources of various frequencies.
Basically, the device consists of a color display with a touch screen that is controlled by a microcontroller that also serves to generate frequencies. These frequencies are then fed to a class-D amplifier, to whose output electrodes are connected through a protective resistor. Electrodes are held in the palms of the hands during therapy, or placed on an appropriate part of the body.
To better understand the functioning and composition of such a device, I first made a simple functional prototype on a small plastic board. Only a few components are needed to make a fully functional device:

- CrowPanel 2.4"-ESP32 HMI Dispaly
- 3.7V Lithium Battery
- Small Switch
- Protective resistor 10 Ohm
- Two metal electrodes through which the therapy is applied
A lithium battery is connected to the appropriate battery connector via this small switch. The type (capacity) of the battery is not critical and any shape and capacity can be used. Of course, we need to pay attention to the polarity. The electrodes are connected to the audio output of the HMI displaymodule via a protective resistor. The electrodes can be metal rods made of stainless steel, or for example aluminum cylinders as in my case. Also, for this purpose can serve laminated pertinax boards used for making PCBs. I made assembled test device from PVC material based on the appearance of several similar commercial devices.

When turning on the device, the initial information screen appears, and according to the indicated information, by touching the screen anywhere, we enter the "ILNESS SELECTION" menu to select the disease we want to treat. Here we have two buttons "PREV" and "NEXT" with which we can move through the diseases.

Let me emphasize that in a new device, only the "ZAPPER" function is initially entered here, and the remaining diseases and functions are entered through an appropriate web interface, which will be described further in the text. The selected disease is in red. To continue in the menu for starting the therapy, we need to press the "SELECT" button.

As you can see, the selected disease, the current frequency, and the total number of frequencies for the selected disease are marked here. At the bottom, there is a progress bar on which the elapsed time of the entire therapy is graphically presented. We have the same information in the last information line where the remaining time for therapy is displayed.

After the therapy is completed, the last screen automatically appears, informing us that the therapy is over, under which information is a red button to turn off the device.
If we do not turn off the device manually within one minute, the auto sleep option will be activated, so as not to waste the battery.

When turning off, the device does not turn off completely, but goes into a so-called deep sleep mode, during which the consumption is almost zero. In case we have started a long therapy unnecessarily by mistake, we can stop it by double-clicking on the upper half of the screen, which will put the device into deep sleep mode again.
From a hardware point of view, as you can see it's extremely simple. The display device is connected to a 3.7V lithium battery, a power button, and two electrodes through which the therapy is applied. The battery is connected to the appropriate place, and on the board there is an electronic circuit that regulates its charging when the device is connected to an external source via a UCB-C connector. And, the electrodes are connected to the appropriate place, which actually represents the output of the D-class amplifier. To protect the amplifier output from short-circuiting the electrodes, a resistor with a value of 10 Ohms is connected in series to the output of one electrode. Double-sided laminated pertinax boards can be used for electrodes with an indication that the copper must not be varnished or insulated in any way. There is no problem if they darken (oxidize) because even then the conductivity remains. Alternatively, you can make your own electrodes from thin aluminum sheet rolled around a plastic tube, as in this case. The multifunctionality and practicality of this device actually lies in the fact that we can very easily enter, edit, and delete frequencies or sequences ourselves through a simple user-friendly web interface. Now let me explain to you what what I said earlier means practically. We need a PC or laptop with the ability to connect to a Wi-Fi network. We turn on the device and search for the Wi-Fi network with the name: "ESP32-RIFE-AP" and connect to it with the password: 12345678. This is actually the Wi-Fi network generated by this small device. Now we open Google Chrome or any other web browser and in the address field we enter 192.168.4.1 and press enter.

The "Frequency Manager" web interface appears on the screen through which we can wirelessly enter information into the device. At the beginning of the screen we can see the already entered diseases, in our case only the "ZAPPER" function. Under the name of the disease we can see the number and value of the frequencies. Here we have 5 times 30 kHz, meaning "ZAPPER" therapy lasting 5 minutes. Then comes the screen for entering a new disease. We enter the name, with a note that special characters are not allowed. And now the most important part, the places for entering frequencies. As we can see, there are a total of ten fields in which we can enter different frequencies. A curiosity is the fact that we can also enter frequencies with great accuracy (two decimal places), so this device can also successfully simulate a Schumann Resonance frequency generator by entering frequencies with a value of 7.83 Hz. At the end we have the Add Disease button with which we confirm the previously entered information. Now we can see this change on the device screen. Next, let's enter a few more examples. They all appeared on the device display. In case we make a mistake in one of the frequencies when entering, or we have information about a different sequence, we can edit each previously entered disease by pressing the "EDIT" button on the right side of the disease. After editing, we press the "UPDATE DISEASE" button, or "CANCEL". As we can also see next to each disease, to the right of the "EDIT" button there is also a red "DELETE" button with which we can completely delete the disease. All these changes are currently transmitted to the device via the Wi-Fi network. The same procedure for setting up the device can also be performed with a tablet or smartphone.
Now in a few words, let me explain how to turn the device on and off, as well as how to charge the battery. First of all, let me remind you that the device has a built-in battery charger, which is activated when the device is connected to an external power supply via the Type-C connector, but in order to charge the battery, the switch must be in position "1", i.e. the device must be turned on. As you could have previously noticed in the description, the device has a so-called deep-sleep mode in which the consumption is quite low. However, if we do not need to use the device for a long time, then the best option is to turn it off with the switch because then the battery is not consumed at all. Otherwise, when it is in Deep-Sleep mode, to turn it on, we will have to turn it off and on again via the switch.
And now, perhaps the most important part, let's check whether the signal generated at the output corresponds to the description so far. This is actually proof whether the device works from an electronic point of view. I have no knowledge to comment on the effects in a medical sense. For this purpose I will use an Oscilloscope, where the signal should have a rectangular shape and the frequency should be identical to the one we read on the device. Let me just mention that certain small deviations may occur due to the tolerance of the oscilloscope.

And finally, a brief conclusion. This is a simple multifunctional device that, thanks to the high precision of the generated frequencies (with two decimal places), can successfully emulate almost all known therapeutic devices for frequency treatment. Most similar devices of this type have many and confusing menus with countless options, making it very difficult, even impossible, to navigate through them. This device is set and configured wirelessly via a Wi-Fi network, with a simple and intuitive Web Interface. If you are considering using this device for health-related purposes, it is crucial to consult a qualified healthcare professional to ensure safety and efficacy. This video only explains the technical aspect of this device. I would like to mention that this device can be successfully used as a source of Rife frequencies in my previously described projects for making Plasma Rife Machines, as well as in similar commercial devices.

Below is a .zip file with all the required libraries as well as a very detailed description of how to install the code.

UPDATE: Elecrow CrowPanel 2.4inch-ESP32 HMI 320x240 is currently out of stock, but without any modifications you can use the CrowPanel 2.8"-ESP32 HMI 320x240 Display (https://www.elecrow.com/esp32-display-2-8-inch-hmi-display-spi-tft-lcd-touch-screen.html?srsltid=AfmBOoqVGo9QgI2-HizvqvJCLvXN97AH2Diu91P-mBRmYBcodF_-bsxR)

icon Detailed code installation instructions.zip 6.66MB Download(0)
CODE
// by mircemk, Aug 2025

#include <TFT_eSPI.h>
#include <SPI.h>
#include <driver/ledc.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
#include <FS.h>
#include <ArduinoJson.h>
#include <vector>

using namespace fs;

TFT_eSPI tft = TFT_eSPI();

// Pin definitions
#define TOUCH_CS 21
#define OUTPUT_PIN 25
#define SIGNAL_PIN 32  // New pin for rectangular signal

// LEDC configuration
#define LEDC_CHANNEL 0
#define LEDC_RESOLUTION 10
#define LEDC_BASE_FREQ 1000

// WiFi credentials
const char* ssid = "ESP32-RIFE-AP";
const char* password = "12345678";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Debounce and timing variables
unsigned long lastTouchTime = 0;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 100;
bool buttonPressed = false;

// Menu navigation variables
byte currentSelection = 1;
byte currentPage = 0;
bool isIntroScreen = true;

// Disease structure
struct Disease {
  String name;
  float frequencies[10];
};

// Vector to store diseases
std::vector<Disease> diseases;

// Touch button structure and definitions
struct TouchButton {
  int16_t x, y, w, h;
  const char* label;
};

TouchButton navButtons[2] = {
  {10, 200, 100, 40, "Next"},
  {210, 200, 100, 40, "Prev."}
};

TouchButton selectButton = {110, 200, 100, 40, "Select"};

// Function declarations
void initSPIFFS();
void initWiFi();
void setupWebServer();
String getDiseasesJson();
void saveDiseasesToSPIFFS();
void loadDiseasesFromSPIFFS();
void drawIntroScreen();
void handleIntroScreen();
void drawMenuScreen();
void handleMenuScreen();
void drawNavigationButtons();
void handlePreviousButton();
void handleNextButton();
bool isButtonTouched(TouchButton& btn, uint16_t x, uint16_t y);
void startFrequencyGeneration(byte selectedIllness);
void drawShutDownButton();
void enterDeepSleep();
void initializeDefaultDiseases();
void setupSignalGeneration();
void setFrequency(float frequency);
void showCompletionScreen();

String urlDecode(String str) {
    String decoded = "";
    char temp[] = "0x00";
    unsigned int len = str.length();
    unsigned int i = 0;
    
    while (i < len) {
        char c = str[i];
        if (c == '+') {
            decoded += ' ';
        } else if (c == '%') {
            temp[2] = str[i + 1];
            temp[3] = str[i + 2];
            decoded += (char)strtol(temp, NULL, 16);
            i += 2;
        } else {
            decoded += c;
        }
        i++;
    }
    return decoded;
}

void setupSignalGeneration() {
    ledcSetup(LEDC_CHANNEL, LEDC_BASE_FREQ, LEDC_RESOLUTION);
    ledcAttachPin(SIGNAL_PIN, LEDC_CHANNEL);
}

void setFrequency(float frequency) {
    if (frequency <= 0) {
        ledcWrite(LEDC_CHANNEL, 0);
        return;
    }
    
    ledcChangeFrequency(LEDC_CHANNEL, frequency, LEDC_RESOLUTION);
    ledcWrite(LEDC_CHANNEL, (1 << (LEDC_RESOLUTION - 1))); // 50% duty cycle
}

void setup() {
    Serial.begin(115200);
    
    // Initialize SPIFFS
    initSPIFFS();
    
    // Initialize WiFi
    initWiFi();
    
    // Setup web server
    setupWebServer();
    
    // Initialize display
    tft.init();
    tft.setRotation(1);
    tft.fillScreen(TFT_BLACK);
    
    // Initialize touch
    pinMode(TOUCH_CS, OUTPUT);
    digitalWrite(TOUCH_CS, HIGH);
    
    // Initialize output pins and signal generation
    pinMode(OUTPUT_PIN, OUTPUT);
    setupSignalGeneration();
    
    // Load diseases from SPIFFS
    loadDiseasesFromSPIFFS();
    
    // Initialize with default values if empty
    if (diseases.empty()) {
        initializeDefaultDiseases();
    }
    
    // Show intro screen
    drawIntroScreen();
}

void loop() {
    if (isIntroScreen) {
        handleIntroScreen();
    } else {
        handleMenuScreen();
    }
}

void initSPIFFS() {
    if (!SPIFFS.begin(true)) {
        Serial.println("An error occurred while mounting SPIFFS");
        return;
    }
}

void initWiFi() {
    WiFi.softAP(ssid, password);
    Serial.println("Access Point Started");
    Serial.print("IP Address: ");
    Serial.println(WiFi.softAPIP());

      WiFi.softAP(ssid, password);
    Serial.println("Access Point Started");
    Serial.print("SSID: ");
    Serial.println(ssid);
    Serial.print("Password: ");
    Serial.println(password);
    Serial.print("IP Address: ");
    Serial.println(WiFi.softAPIP());
    
    // Add these lines for additional debugging
    Serial.print("Max Connections Allowed: ");
    Serial.println(WiFi.softAPgetStationNum());
    Serial.print("Channel: ");
    Serial.println(WiFi.channel());
}

void setupWebServer() {
    server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
    
    server.on("/api/diseases", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(200, "application/json", getDiseasesJson());
    });
    
    server.on("/api/diseases", HTTP_POST, [](AsyncWebServerRequest *request){}, NULL,
        [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
            DynamicJsonDocument doc(1024);
            deserializeJson(doc, (char*)data);
            
            Disease newDisease;
            newDisease.name = doc["name"].as<String>();
            
            JsonArray freqs = doc["frequencies"];
            for(size_t i = 0; i < 10; i++) {
                if (i < freqs.size()) {
                    newDisease.frequencies[i] = freqs[i].as<String>().toFloat();
                } else {
                    newDisease.frequencies[i] = 0.0;
                }
            }
            
            diseases.push_back(newDisease);
            saveDiseasesToSPIFFS();
            
            request->send(200);
        });

    server.on("/api/diseases/delete", HTTP_DELETE, [](AsyncWebServerRequest *request) {
        if (request->hasParam("name")) {
            String diseaseName = request->getParam("name")->value();
            diseaseName = urlDecode(diseaseName);
            
            for(auto it = diseases.begin(); it != diseases.end(); ++it) {
                if(it->name == diseaseName) {
                    diseases.erase(it);
                    break;
                }
            }
            
            saveDiseasesToSPIFFS();
            request->send(200, "text/plain", "Disease deleted");
        } else {
            request->send(400, "text/plain", "Disease name not provided");
        }
    });
    
    server.begin();
}

String getDiseasesJson() {
    DynamicJsonDocument doc(4096);
    JsonArray array = doc.to<JsonArray>();
    
    for(const Disease& disease : diseases) {
        JsonObject obj = array.createNestedObject();
        obj["name"] = disease.name;
        
        JsonArray freqs = obj.createNestedArray("frequencies");
        for(int i = 0; i < 10; i++) {
            freqs.add(disease.frequencies[i]);
        }
    }
    
    String response;
    serializeJson(doc, response);
    return response;
}

void saveDiseasesToSPIFFS() {
    File file = SPIFFS.open("/diseases.json", "w");
    if(!file) {
        Serial.println("Failed to open file for writing");
        return;
    }
    
    DynamicJsonDocument doc(4096);
    JsonArray array = doc.to<JsonArray>();
    
    for(const Disease& disease : diseases) {
        JsonObject obj = array.createNestedObject();
        obj["name"] = disease.name;
        
        JsonArray freqs = obj.createNestedArray("frequencies");
        for(int i = 0; i < 10; i++) {
            freqs.add(serialized(String(disease.frequencies[i], 2)));
        }
    }
    
    serializeJson(doc, file);
    file.close();
}

void loadDiseasesFromSPIFFS() {
    if(SPIFFS.exists("/diseases.json")) {
        File file = SPIFFS.open("/diseases.json", "r");
        if(!file) {
            Serial.println("Failed to open file for reading");
            return;
        }
        
        DynamicJsonDocument doc(4096);
        deserializeJson(doc, file);
        
        diseases.clear();
        for(JsonObject obj : doc.as<JsonArray>()) {
            Disease disease;
            disease.name = obj["name"].as<String>();
            
            JsonArray freqs = obj["frequencies"];
            for(size_t i = 0; i < 10; i++) {
                if (i < freqs.size()) {
                    disease.frequencies[i] = freqs[i].as<String>().toFloat();
                } else {
                    disease.frequencies[i] = 0.0;
                }
            }
            
            diseases.push_back(disease);
        }
        
        file.close();
    }
}

void drawIntroScreen() {
    tft.fillScreen(TFT_BLACK);
    tft.fillRect(0, 0, 320,240, TFT_NAVY);
    tft.fillRect(6, 6 , 308,228, TFT_BLACK);
    tft.fillRoundRect(10, 10, 300, 220, 10, TFT_NAVY);
    tft.fillRoundRect(13, 13, 294, 214, 10, TFT_BLACK);
    tft.setTextColor(TFT_GREEN);
    tft.setTextSize(2);
    tft.setCursor(73, 27);
    tft.println("Multifunctional");
    tft.setTextColor(TFT_WHITE);
    tft.setTextSize(3);
    tft.setCursor(55, 57);
    tft.println("BIORESONANCE");
    tft.setCursor(35, 92);
    tft.println("Therapy Device");
    tft.setTextColor(TFT_YELLOW);
    tft.setTextSize(2);
    tft.setCursor(80, 137);
    tft.println("RIFE - ZAPPER");
    tft.setCursor(50, 163);
    tft.println("SCHUMANN RESONANCE");
    tft.setTextColor(TFT_RED);
    tft.setCursor(80, 197);
    tft.println("Touch to Start");
}

void handleIntroScreen() {
    uint16_t x, y;
    if (tft.getTouch(&x, &y)) {
        delay(50);  // Debounce
        isIntroScreen = false;
        drawMenuScreen();
    }
}

void drawMenuScreen() {
    tft.fillScreen(TFT_WHITE);
    
    tft.fillRect(0, 0, 320, 30, TFT_NAVY);
    tft.setTextColor(TFT_WHITE);
    tft.setTextSize(3);
    tft.setCursor(10, 4);
    tft.println("ILLNESS SELECTION");
    
    int displayCount = min(5, (int)diseases.size() - currentPage);
    for (int i = 0; i < displayCount; i++) {
        int diseaseIndex = currentPage + i;
        tft.setCursor(20, 40 + i * 30);
        if (diseaseIndex + 1 == currentSelection) {
            tft.setTextColor(TFT_RED);
        } else {
            tft.setTextColor(TFT_BLACK);
        }
        tft.setTextSize(2);
        tft.println(diseases[diseaseIndex].name);
    }
    
    drawNavigationButtons();
}

void drawNavigationButtons() {
    tft.fillRect(navButtons[0].x, navButtons[0].y, 
                 navButtons[0].w, navButtons[0].h, TFT_DARKGREY);
    tft.setCursor(navButtons[0].x + 10, navButtons[0].y + 15);
    tft.setTextColor(TFT_WHITE);
    tft.println(navButtons[0].label);
    
    tft.fillRect(navButtons[1].x, navButtons[1].y, 
                 navButtons[1].w, navButtons[1].h, TFT_DARKGREY);
    tft.setCursor(navButtons[1].x + 30, navButtons[1].y + 15);
    tft.setTextColor(TFT_WHITE);
    tft.println(navButtons[1].label);
    
    tft.fillRect(selectButton.x, selectButton.y, 
                 selectButton.w, selectButton.h, TFT_GREEN);
    tft.setCursor(selectButton.x + 20, selectButton.y + 15);
    tft.setTextColor(TFT_BLACK);
    tft.println(selectButton.label);
}

void handleMenuScreen() {
    uint16_t x, y;
    
    if (tft.getTouch(&x, &y)) {
        unsigned long currentTime = millis();
        
        if (!buttonPressed && (currentTime - lastDebounceTime > debounceDelay)) {
            buttonPressed = true;
            lastDebounceTime = currentTime;
            
            if (isButtonTouched(navButtons[0], x, y)) {
                handlePreviousButton();
            }
            else if (isButtonTouched(navButtons[1], x, y)) {
                handleNextButton();
            }
            else if (isButtonTouched(selectButton, x, y)) {
                if (diseases.size() > 0 && currentSelection <= diseases.size()) {
                    startFrequencyGeneration(currentSelection);
                }
            }
        }
    } else {
        buttonPressed = false;
    }
}

void handlePreviousButton() {
    if (currentSelection > 1) {
        currentSelection--;
        if (currentSelection < currentPage + 1) {
            currentPage = max(0, currentPage - 5);
        }
        drawMenuScreen();
    }
}

void handleNextButton() {
    if (currentSelection < diseases.size()) {
        currentSelection++;
        if (currentSelection > currentPage + 5) {
            currentPage = min((int)diseases.size() - 5, currentPage + 5);
        }
        drawMenuScreen();
    }
}

bool isButtonTouched(TouchButton& btn, uint16_t x, uint16_t y) {
    return (x >= btn.x && x <= (btn.x + btn.w) && 
            y >= btn.y && y <= (btn.y + btn.h));
}

void startFrequencyGeneration(byte selectedIllness) {
    const Disease& selectedDisease = diseases[selectedIllness - 1];
    byte ilosc_f = 0;
    
    // Variables for double-tap detection
    static uint16_t firstX = 0, firstY = 0;
    static unsigned long lastTapTime = 0;
    const unsigned long doubleTapMaxInterval = 500;
    const unsigned long doubleTapMinInterval = 100;
    bool isTapValid = false;

    // Count non-zero frequencies
    for (byte i = 0; i < 10; i++) {
        if (selectedDisease.frequencies[i] > 0) ilosc_f++;
    }
    
    const unsigned long frequencyDuration = 60000; // 1 minute per frequency
    const unsigned long totalDuration = frequencyDuration * ilosc_f;
    unsigned long totalElapsedTime = 0;
    
    const int barWidth = 300;
    const int barHeight = 20;
    const int barX = 10;
    const int barY = 208;
    
    for (byte i = 0; i < ilosc_f; i++) {
        float f_gen = selectedDisease.frequencies[i];
        
        if (f_gen == 0) continue;
        
        tft.fillScreen(TFT_GREENYELLOW);
        
        tft.fillRect(0, 0, 320, 35, TFT_NAVY);
        tft.setTextColor(TFT_WHITE);
        tft.setCursor(18, 4);
        tft.setTextSize(3);
        tft.println("Start of therapy");

        tft.setTextColor(TFT_BLACK);
        
        tft.setTextSize(2);
        tft.setCursor(10, 55);
        tft.print("Illness: ");
        tft.println(selectedDisease.name);

        char freqStr[10];
        dtostrf(f_gen, 1, 2, freqStr);
        
        tft.setCursor(10, 95);
        tft.print("Frequency: ");
        tft.print(freqStr);
        tft.println(" Hz");
        
        tft.setCursor(10, 135);
        tft.print("Frequency #: ");
        tft.print(i + 1);
        tft.print(" of ");
        tft.println(ilosc_f);
        
        tft.drawRect(barX, barY, barWidth, barHeight, TFT_WHITE);
        tft.fillRect(barX + 1, barY + 1, barWidth - 2, barHeight - 2, TFT_BLACK);
        
        // Start generating frequency on Pin32
        setFrequency(f_gen);
        
        unsigned long frequencyStartTime = millis();
        while (millis() - frequencyStartTime < frequencyDuration) {
            uint16_t x, y;
            
            if (tft.getTouch(&x, &y)) {
                if (y < 120) {
                    unsigned long currentTime = millis();
                    unsigned long timeSinceLastTap = currentTime - lastTapTime;
                    
                    if (!isTapValid) {
                        firstX = x;
                        firstY = y;
                        lastTapTime = currentTime;
                        isTapValid = true;
                    }
                    else if (timeSinceLastTap >= doubleTapMinInterval && 
                            timeSinceLastTap <= doubleTapMaxInterval) {
                        if (abs(x - firstX) < 50 && abs(y - firstY) < 50) {
                            tft.fillScreen(TFT_BLACK);
                            tft.setTextColor(TFT_WHITE);
                            tft.setTextSize(2);
                            tft.setCursor(50, 100);
                            tft.println("Double tap detected");
                            delay(1000);
                            enterDeepSleep();
                        }
                    }
                    else if (timeSinceLastTap < doubleTapMinInterval) {
                        isTapValid = false;
                    }
                }
            } else {
                if (millis() - lastTapTime > doubleTapMaxInterval) {
                    isTapValid = false;
                }
            }
            
            totalElapsedTime = (i * frequencyDuration) + (millis() - frequencyStartTime);
            int progressWidth = map(totalElapsedTime, 0, totalDuration, 0, barWidth - 2);
            tft.fillRect(barX + 1, barY + 1, progressWidth, barHeight - 2, TFT_RED);
            
            unsigned long totalRemainingSeconds = (totalDuration - totalElapsedTime) / 1000;
            unsigned long remainingMinutes = totalRemainingSeconds / 60;
            unsigned long remainingSeconds = totalRemainingSeconds % 60;
            
            tft.fillRect(10, 175, 300, 20, TFT_GREENYELLOW);
            tft.setCursor(10, 175);
            tft.setTextColor(TFT_RED);
            tft.setTextSize(2);
            tft.print("Total Time Left: ");
            if (remainingMinutes < 10) tft.print("0");
            tft.print(remainingMinutes);
            tft.print(":");
            if (remainingSeconds < 10) tft.print("0");
            tft.print(remainingSeconds);
            
            delay(50);
        }
        
        // Stop frequency before moving to next one
        setFrequency(0);
        delay(1000);
    }
    
    showCompletionScreen();
}

void showCompletionScreen() {
    tft.fillScreen(TFT_BLACK);
    tft.fillRect(0, 0, 320, 240, TFT_DARKGREY);
    tft.fillRect(6, 6, 308, 228, TFT_BLACK);
    tft.fillRoundRect(10, 10, 300, 220, 10, TFT_DARKGREY);
    tft.fillRoundRect(13, 13, 294, 214, 10, TFT_BLACK);
    
    tft.setTextColor(TFT_GREEN);
    tft.setTextSize(4);
    tft.setCursor(80, 40);
    tft.println("THERAPY");
    tft.setCursor(70, 90);
    tft.println("COMPLETE");
    
    drawShutDownButton();
    
    tft.setTextSize(2);
    tft.setTextColor(TFT_YELLOW);
    tft.setCursor(65, 134);
    tft.print("Auto sleep in: ");
    
    int numberX = tft.getCursorX();
    int numberY = 134;
    
    unsigned long shutdownTimer = millis();
    const unsigned long autoShutdownDelay = 60000;
    
    String lastDisplayedTime = "";
    
    while (true) {
        uint16_t x, y;
        
        if (tft.getTouch(&x, &y)) {
            if (x >= 90 && x <= 240 && y >= 165 && y <= 205) {
                enterDeepSleep();
            }
        }
        
        if (millis() - shutdownTimer >= autoShutdownDelay) {
            tft.fillScreen(TFT_BLACK);
            tft.setTextColor(TFT_WHITE);
            tft.setTextSize(2);
            tft.setCursor(20, 100);
            tft.println("Auto-shutdown activated");
            delay(1000);
            enterDeepSleep();
        }
        
        unsigned long remainingTime = (autoShutdownDelay - (millis() - shutdownTimer)) / 1000;
        String currentTimeStr = String(remainingTime);
        
        if (currentTimeStr != lastDisplayedTime) {
            tft.fillRect(numberX, numberY, 60, 30, TFT_BLACK);
            tft.setTextSize(2);
            tft.setTextColor(TFT_YELLOW);
            tft.setCursor(numberX, numberY);
            tft.print(remainingTime);
            tft.print("s");
            lastDisplayedTime = currentTimeStr;
        }
        
        delay(100);
    }
}

void drawShutDownButton() {
    tft.fillRect(90, 165, 150, 40, TFT_RED);
    tft.setTextColor(TFT_WHITE);
    tft.setTextSize(2);
    tft.setCursor(110, 177);
    tft.println("Shut Down");
}

void enterDeepSleep() {
    setFrequency(0);
    ledcDetachPin(SIGNAL_PIN);
    
    tft.fillScreen(TFT_BLACK);
    tft.setTextColor(TFT_WHITE);
    tft.setTextSize(2);
    tft.setCursor(50, 100);
    tft.print("Shutting down...");
    delay(3000);
    
    esp_deep_sleep_start();
}

void initializeDefaultDiseases() {
    if (diseases.empty()) {
        Disease defaultDisease;
        defaultDisease.name = "ZAPPER";
        for (int i = 0; i < 5; i++) {
            defaultDisease.frequencies[i] = 30000;
        }
        for (int i = 5; i < 10; i++) {
            defaultDisease.frequencies[i] = 0;
        }
        diseases.push_back(defaultDisease);
        
        saveDiseasesToSPIFFS();
    }
}
License
All Rights
Reserved
licensBg
0