After countless banging my head against the wall and running post to pillar to send OTA .bin files over GSM to my device, I have figured out a solution for the same, it has a few prerequisites, this is based on the minimal OTA code that was shared by @Ibrahim_Roshdy over here Blynk.Air OTA minimal code
I am using tinygsm with a sim800l and on an ESP32
edit, added a few missing function calls
there may be a few which aren’t used which i may have added during testing, please feel free to point it out
#include <TinyGsmClient.h>
#include <BlynkSimpleTinyGSM.h>
#include<FS.h>
#include "FS.h"
#include "SPIFFS.h"
// Increase RX buffer
#define TINY_GSM_RX_BUFFER 1023
// other include functions and global variables here
void printProgressBar(int progress) {
Serial.print("\rProgress: [");
for (int i = 0; i < 50; ++i) {
Serial.print(i < (progress / 2) ? "=" : " ");
}
Serial.print("] ");
Serial.print(progress);
Serial.print("%");
}
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 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 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 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);
}
}
bool parseURL(const String& url, String& server, String& resource, int& port) {
// Check if the URL starts with "http://"
if (!url.startsWith("http://")) {
Serial.println("Invalid URL format");
return false;
}
// Remove the "http://" part
String urlWithoutProtocol = url.substring(7);
// Find the position of the first "/" after "http://"
int slashPos = urlWithoutProtocol.indexOf('/');
// Extract the server part
server = urlWithoutProtocol.substring(0, slashPos);
// Extract the resource part
resource = urlWithoutProtocol.substring(slashPos);
// Find the position of ":" to check for a custom port
int colonPos = server.indexOf(':');
if (colonPos != -1) {
// Custom port specified
port = server.substring(colonPos + 1).toInt();
server = server.substring(0, colonPos);
} else {
// Use default port 80
port = 80;
}
return true;
}
String overTheAirURL;
BLYNK_WRITE(InternalPinOTA) {
overTheAirURL = param.asString();
String serverOTABlynk;
String resourceOTABlynk;
int portOTABlynk;
Blynk.disconnect();
if (!parseURL(overTheAirURL, serverOTABlynk, resourceOTABlynk, portOTABlynk)) {
Serial.println("Failed to parse URL");
return;
}
TinyGsmClient client(modem);
Serial.print("Connecting to ");
Serial.print(serverOTABlynk);
Serial.print(" on port ");
Serial.println(portOTABlynk);
if (!client.connect(serverOTABlynk.c_str(), portOTABlynk)) {
Serial.println("Connection failed");
Blynk.connect();
return;
}
// Make a HTTP GET request
client.print(String("GET ") + resourceOTABlynk + " HTTP/1.1\r\n" +
"Host: " + serverOTABlynk + "\r\n" + // Use only the server part
"Cache-Control: no-cache\r\n" +
"Connection: close\r\n\r\n");
long timeout = millis();
// Wait for the server to respond
while (!client.available()) {
if (millis() - timeout > 25000L) {
Serial.println(">>> Client Timeout !");
client.stop();
Blynk.connect();
return;
}
}
// Reading response header
uint32_t contentLength = 0;
while (client.available()) {
String line = client.readStringUntil('\n');
line.trim();
Serial.println(line); // Print the response header (uncomment for debugging)
if (line.startsWith("Content-Length: ")) {
contentLength = line.substring(line.lastIndexOf(':') + 1).toInt();
} else if (line.length() == 0) {
break;
}
}
Serial.print("Content-Length: ");
Serial.println(contentLength);
// Open the firmware file in SPIFFS for writing
File firmwareFile = SPIFFS.open("/update.bin", "w");
if (!firmwareFile) {
Serial.println("Failed to open firmware file for writing");
client.stop();
Blynk.connect();
return;
}
// Reading and handling the response
uint32_t readLength = 0;
Serial.println("Downloading firmware...");
while (client.available()) {
uint8_t c = client.read();
readLength++;
// Write firmware data to SPIFFS
if (firmwareFile.write(c) != 1) {
Serial.println("Error writing firmware data to SPIFFS");
firmwareFile.close();
client.stop();
Blynk.connect();
return;
}
// Your update logic here, similar to the previous example
if (contentLength != 0 && readLength % (contentLength / 13) == 0) {
// Print progress if needed
Serial.print("\r");
Serial.println((100.0 * readLength) / contentLength);
Serial.print('%');
}
}
Serial.println(); // New line after progress
// Close the firmware file
firmwareFile.close();
// Close the client connection
client.stop();
// Check CRC and update completion
if (readLength == contentLength) {
Serial.println("Update successful!");
updateFromFS();
// Reboot or perform other necessary actions
ESP.restart();
} else {
Serial.println("Update failed!");
Blynk.connect();
return;
}
}
void setup()
{
//setup serial monitor, configure tinygsm, blynk setup, other functions etc
if (!SPIFFS.begin(true))
{
Serial.println("SPIFFS Mount Failed");
return;
}
SPIFFS.format();
listDir(SPIFFS, "/", 0);
//Format the spiffs to accept updates
}
void loop()
{
Blynk.run();
timer.run();
}
The disconnects you see in the image is me purposefully messing with the power supply of the sim800 to check whether Blynk manages to ship the firmware if there is a signal quality drop and/or total disconnection via GSM
a 1MB .bin file took about 2:30 minutes to download, so about 6.8 KBPS, an acceptable speed for a 2g connection
this is a drop-in replacement for all Blynk sketches that run Blynk 2.0 but are not operating via edgent due to a lack of support
thank you @PeteKnight for pointing me in the right direction earlier when i was stuck with this
Happy Blynking to everyone