vshymanskyy / TinyGSM

A small Arduino library for GSM modules, that just works
GNU Lesser General Public License v3.0
1.91k stars 713 forks source link

GSM OTA code for esp-idf platform instead of Arduino #565

Open krupis opened 2 years ago

krupis commented 2 years ago

Hello. I would like to know whether it is possible to use the OTA over the GSM in the esp-idf platform. The OTA code I have found posted in the issue #133 works perfectly fine in Arduino platform, however, I am trying to move away from Arduino towards the esp-idf and I wonder whether I could port this code to work for esp-idf. The full code is here:

#include <Update.h>
#define TINY_GSM_MODEM_SIM800
// Increase RX buffer
#define TINY_GSM_RX_BUFFER 1030

const char apn[] = "omnitel";
const char user[] = "omni";
const char pass[] = "omni";

#define MODEM_RST             5
#define MODEM_PWRKEY          4
#define MODEM_POWER_ON       23
#define MODEM_TX             27
#define MODEM_RX             26
#define LED_GPIO             13
#define LED_ON               HIGH
#define LED_OFF              LOW

#define SerialAT  Serial1

#include <TinyGsmClient.h>
//#include <CRC32.h>

#include "FS.h"
#include "SPIFFS.h"
#include "WiFi.h"
//#ifdef DUMP_AT_COMMANDS
#include <StreamDebugger.h>
StreamDebugger debugger(SerialAT, Serial);
TinyGsm modem(debugger);
//#else
//TinyGsm modem(SerialAT);
//#endif

TinyGsmClient client(modem);

const char server[] = "192.168.4.200";
const int port = 8080;
const char resource[] = "/PTL.bin"; //here de bin file

uint32_t knownCRC32 = 0x6f50d767;
uint32_t knownFileSize = 1024; // In case server does not send it

void setup()
{

    SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
    // Set console baud rate

    setupModem();
    delay(10);

    if (!SPIFFS.begin(true))
    {
        Serial.println("SPIFFS Mount Failed");
        return;
    }
    SPIFFS.format();
    listDir(SPIFFS, "/", 0);

    // Set GSM module baud rate

    delay(3000);

    // Restart takes quite some time
    // To skip it, call init() instead of restart()
    Serial.println("Initializing modem...");
    modem.restart();

    String modemInfo = modem.getModemInfo();
    Serial.print("Modem: ");
    Serial.println(modemInfo);

    // Unlock your SIM card with a PIN
    //modem.simUnlock("1234");
}

void loop()
{
    Serial.print("Waiting for network...");
    if (!modem.waitForNetwork())
    {
        Serial.println(" fail");
        delay(10000);
        return;
    }
    Serial.println(" OK");

    Serial.print("Connecting to ");
    Serial.print(apn);
    if (!modem.gprsConnect(apn, user, pass))
    {
        Serial.println(" fail");
        delay(10000);
        return;
    }
    Serial.println(" OK");

    Serial.print("Connecting to ");
    Serial.print(server);

    // if you get a connection, report back via serial:
    if (!client.connect(server, port))
    {
        Serial.println(" fail");
        delay(10000);
        return;
    }
    Serial.println(" OK");

    // Make a HTTP request:
    client.print(String("GET ") + resource + " HTTP/1.0\r\n");
    client.print(String("Host: ") + server + "\r\n");
    client.print("Connection: close\r\n\r\n");

    long timeout = millis();
    while (client.available() == 0)
    {
        if (millis() - timeout > 5000L)
        {
            Serial.println(">>> Client Timeout !");
            client.stop();
            delay(10000L);
            return;
        }
    }

    Serial.println("Reading header");
    uint32_t contentLength = knownFileSize;

    File file = SPIFFS.open("/update.bin", FILE_APPEND);

    while (client.available())
    {
        String line = client.readStringUntil('\n');
        line.trim();
        //Serial.println(line);    // Uncomment this to show response header
        line.toLowerCase();
        if (line.startsWith("content-length:"))
        {
            contentLength = line.substring(line.lastIndexOf(':') + 1).toInt();
        }
        else if (line.length() == 0)
        {
            break;
        }
    }

    timeout = millis();
    uint32_t readLength = 0;
    //CRC32 crc;

    unsigned long timeElapsed = millis();
    printPercent(readLength, contentLength);

    while (readLength < contentLength && client.connected() && millis() - timeout < 10000L)
    {
        int i = 0;
        while (client.available())
        {
                // read file data to spiffs
            if (!file.print(char(client.read())))
            {
                Serial.println("Appending file");
            }
            //Serial.print((char)c);       // Uncomment this to show data
            //crc.update(c);
            readLength++;

            if (readLength % (contentLength / 13) == 0)
            {
                printPercent(readLength, contentLength);
            }
            timeout = millis();
        }
    }

    file.close();

    printPercent(readLength, contentLength);
    timeElapsed = millis() - timeElapsed;
    Serial.println();

    client.stop();
    Serial.println("stop client");

    modem.gprsDisconnect();
    Serial.println("gprs disconnect");
    Serial.println();

    float duration = float(timeElapsed) / 1000;
  /*
    Serial.print("Tamaño de Archivo: ");
    Serial.println(contentLength);
    Serial.print("Leido:  ");
    Serial.println(readLength);
    Serial.print("Calculado. CRC32:    0x");
    Serial.println(crc.finalize(), HEX);
    Serial.print("Conocido CRC32:    0x");
    Serial.println(knownCRC32, HEX);
    Serial.print("Bajado en:       ");
    Serial.print(duration);
    Serial.println("s");

    Serial.println("Se genera una espera de 3 segundos");
    for (int i = 0; i < 3; i++)
    {
        Serial.print(String(i) + "...");
        delay(1000);
    }
  */
    //readFile(SPIFFS, "/update.bin");

    updateFromFS();

    // Do nothing forevermore
    while (true)
    {
        delay(1000);
    }
}

void appendFile(fs::FS &fs, const char *path, const char *message)
{
    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if (!file)
    {
        Serial.println("Failed to open file for appending");
        return;
    }
    if (file.print(message))
    {
        Serial.println("APOK");
    }
    else
    {
        Serial.println("APX");
    }
}

void readFile(fs::FS &fs, const char *path)
{
    Serial.printf("Reading file: %s\n", path);

    File file = fs.open(path);
    if (!file || file.isDirectory())
    {
        Serial.println("Failed to open file for reading");
        return;
    }

    Serial.print("Read from file: ");
    while (file.available())
    {
        Serial.write(file.read());
        delayMicroseconds(100);
    }
}

void writeFile(fs::FS &fs, const char *path, const char *message)
{
    Serial.printf("Writing file: %s\n", path);

    File file = fs.open(path, FILE_WRITE);
    if (!file)
    {
        Serial.println("Failed to open file for writing");
        return;
    }
    if (file.print(message))
    {
        Serial.println("File written");
    }
    else
    {
        Serial.println("Write failed");
    }
}

void listDir(fs::FS &fs, const char *dirname, uint8_t levels)
{
    Serial.printf("Listing directory: %s\n", dirname);

    File root = fs.open(dirname);
    if (!root)
    {
        Serial.println("Failed to open directory");
        return;
    }
    if (!root.isDirectory())
    {
        Serial.println("Not a directory");
        return;
    }

    File file = root.openNextFile();
    while (file)
    {
        if (file.isDirectory())
        {
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if (levels)
            {
                listDir(fs, file.name(), levels - 1);
            }
        }
        else
        {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}

void deleteFile(fs::FS &fs, const char *path)
{
    Serial.printf("Deleting file: %s\n", path);
    if (fs.remove(path))
    {
        Serial.println("File deleted");
    }
    else
    {
        Serial.println("Delete failed");
    }
}

void updateFromFS()
{
    File updateBin = SPIFFS.open("/update.bin");
    if (updateBin)
    {
        if (updateBin.isDirectory())
        {
            Serial.println("Directory error");
            updateBin.close();
            return;
        }

        size_t updateSize = updateBin.size();

        if (updateSize > 0)
        {
            Serial.println("Starting update");
            performUpdate(updateBin, updateSize);
        }
        else
        {
            Serial.println("Error, archivo vacío");
        }

        updateBin.close();

        // whe finished remove the binary from sd card to indicate end of the process
        //fs.remove("/update.bin");
    }
    else
    {
        Serial.println("no such binary");
    }
}

void performUpdate(Stream &updateSource, size_t updateSize)
{
    if (Update.begin(updateSize))
    {
        size_t written = Update.writeStream(updateSource);
        if (written == updateSize)
        {
            Serial.println("Writes : " + String(written) + " successfully");
        }
        else
        {
            Serial.println("Written only : " + String(written) + "/" + String(updateSize) + ". Retry?");
        }
        if (Update.end())
        {
            Serial.println("OTA finished!");
            if (Update.isFinished())
            {
                Serial.println("Restart ESP device!");
                ESP.restart();
            }
            else
            {
                Serial.println("OTA not fiished");
            }
        }
        else
        {
            Serial.println("Error occured #: " + String(Update.getError()));
        }
    }
    else
    {
        Serial.println("Cannot beggin update");
    }
}

void printPercent(uint32_t readLength, uint32_t contentLength)
{
    // If we know the total length
    if (contentLength != -1)
    {
        Serial.print("\r ");
        Serial.print((100.0 * readLength) / contentLength);
        Serial.print('%');
    }
    else
    {
        Serial.println(readLength);
    }
}

void setupModem()
{
#ifdef MODEM_RST
    // Keep reset high
    pinMode(MODEM_RST, OUTPUT);
    digitalWrite(MODEM_RST, HIGH);
#endif

    pinMode(MODEM_PWRKEY, OUTPUT);
    pinMode(MODEM_POWER_ON, OUTPUT);

    // Turn on the Modem power first
    digitalWrite(MODEM_POWER_ON, HIGH);

    // Pull down PWRKEY for more than 1 second according to manual requirements
    digitalWrite(MODEM_PWRKEY, HIGH);
    delay(100);
    digitalWrite(MODEM_PWRKEY, LOW);
    delay(1000);
    digitalWrite(MODEM_PWRKEY, HIGH);

    // Initialize the indicator as an output
    pinMode(LED_GPIO, OUTPUT);
    digitalWrite(LED_GPIO, LED_OFF);
}
adrianca88 commented 2 years ago

Hello,

Here is the code that I use for upgrade my esp32 via OTA using SIM808:

void OTA()
{
    // New OTA update, clear and send user notification
    Serial.println(F("Starting OTA update..."));
    Update.begin();
    mCRC32.reset();

    // Download OTA update splitted on small files of size DOWNLOAD_FILE_MAX_SIZE
    uint16_t fileIndex = mOTA_SizeDownloaded / DOWNLOAD_FILE_MAX_SIZE;
    char fileName[32];    
    sprintf(fileName, "/static/%s_%03d", mOTA_Version, fileIndex);

    uint32_t fileSize = (mOTA_Size - mOTA_SizeDownloaded > DOWNLOAD_FILE_MAX_SIZE) ? DOWNLOAD_FILE_MAX_SIZE 
            : mOTA_Size % mOTA_SizeDownloaded; 

    Serial.printf("Downloading file %s (%u bytes)\n", fileName, fileSize);
    uint8_t buffer[fileSize];
    uint32_t numBytes = mSIM.DownloadFile(fileName, buffer, fileSize);

    // Check downloaded file size
    if (numBytes != fileSize) {
        Serial.printf("Error downloading file %s...\n", fileName);
        mOTA_Attempts++;
        if (mOTA_Attempts >= OTA_MAX_ATTEMPTS) {
            Serial.println(F("Max OTA attempts reached, aborting..."));
            Update.abort();
        }
        return;
    }

    // Write new data   
    Update.write(buffer, numBytes);
    mCRC32.update<uint8_t>(buffer, numBytes);
    mOTA_SizeDownloaded += numBytes;
    Serial.printf("%u of %u OTA Downloaded\n", mOTA_SizeDownloaded, mOTA_Size);
    mOTA_Attempts = 0;

    if (mOTA_SizeDownloaded != mOTA_Size) {
        return;
    }

    // All downloaded, check CRC32
    if (mOTA_CRC32 != mCRC32.finalize()) {
        Serial.println(F("Invalid checksum, aborting..."));
        Update.abort();
        return;
    }

    // Apply
    Serial.println(F("OTA Ready, restarting..."));
    Update.end(true);
    ESP.restart();
}

Code for download file:

/** 
     * Download file using http Connection
     * WARNING: buffer must be pre-allocated
     */
    uint32_t DownloadFile(const char* file, uint8_t* buffer, uint32_t size)
    {
        if (!file || !buffer || size <= 0) {
            return 0;
        }

        // Connect to network
        if (!mSIM->isGprsConnected()) {
          connectGSM();
        }

        // Connect to server
        Serial.print(F("Connecting to server..."));
        if (!mSIM_Client->connect(mServer, HTTP_PORT, HTTP_TIMEOUT_SEC)) {
            Serial.println(F(" FAIL"));
            return 0;
        }
        Serial.println(F(" OK!"));

        mSIM_Client->print(String("GET ") + file + " HTTP/1.0\r\n");
        mSIM_Client->print(String("Host: ") + mServer + ":" + HTTP_PORT + "\r\n");
        mSIM_Client->print("Connection: close\r\n\r\n");

        // Parse header
        Serial.println(F("Waiting for response header"));

        uint32_t clientReadStartTime = millis();
        String headerBuffer;
        bool finishedHeader = false;
        uint32_t contentLength = 0;

        while (!finishedHeader) {
            int nlPos;

            if (mSIM_Client->available()) {
                clientReadStartTime = millis();
                while (mSIM_Client->available()) {
                  char c = mSIM_Client->read();
                  headerBuffer += c;

                  /*// Uncomment the lines below to see the data coming into the buffer
                  if (isprint(c))
                    Serial.print(reinterpret_cast<char> (c));
                  else
                    Serial.print('*');*/

                  if (headerBuffer.indexOf(F("\r\n")) >= 0) {
                    break;
                  }
                }
            } else if (millis() - clientReadStartTime > DOWNLOAD_FILE_TIMEOUT) {
                Serial.println(F(">>> Client Timeout !"));
                break;
            }

            // See if we have a new line.
            nlPos = headerBuffer.indexOf(F("\r\n"));

            if (nlPos > 0) {
                headerBuffer.toLowerCase();
                // Check if line contains content-length
                if (headerBuffer.startsWith(F("content-length:"))) {
                    contentLength = headerBuffer.substring(headerBuffer.indexOf(':') + 1).toInt();
                }
                headerBuffer.remove(0, nlPos + 2);  // remove the line
            } else if (nlPos == 0) {
                // if the new line is empty (i.e. "\r\n" is at the beginning of the line), we are done with the header.
                finishedHeader = true;
            }
        }

        if (contentLength != size || !finishedHeader) {
            Serial.println(F("Error parsing header content-length size or different sizes\n"));
            mSIM_Client->stop();
            return 0;
        }

        // Read content
        Serial.println(F("Reading response data"));
        uint32_t readLength = 0;
        clientReadStartTime = millis();
        while (readLength < contentLength && mSIM_Client->connected() && millis() - clientReadStartTime < DOWNLOAD_FILE_TIMEOUT) {
            while (mSIM_Client->available()) {
                buffer[readLength++] = mSIM_Client->read();
                clientReadStartTime = millis();
            }
        }        

        mSIM_Client->stop();

        return readLength;
    }

I hope that it help you.

Regards, Adrián.

krupis commented 2 years ago

Hey. Your code looks like Arduino code. I am looking for esp-idf alternative for GSM OTA

JMvanE commented 2 years ago

@adrianca88 Thanks for sharing this code. Is there a possibility to share a complete example? This would very helpful.

krupis commented 2 years ago

Hey. The code that I have uploaded ( my initial forum post ) is the complete example for the GSM OTA. You just need to change a few things:

const char apn[] = "omnitel"; const char user[] = "omni"; const char pass[] = "omni";

and the server must be changed according to what you use. Also, keep in mind that the example is built on lilygo esp32 module and it has the following pinout for the GSM module:

define MODEM_RST 5

define MODEM_PWRKEY 4

define MODEM_POWER_ON 23

define MODEM_TX 27

define MODEM_RX 26

so if you dont have lilygo board, you may need to change those as well. The rest should be fine

JMvanE commented 2 years ago

@krupis Thank you very much!

roysG commented 2 years ago

Thanks