EasyOta
The easiest way to implement ota on an esp8266 / esp32 based chip using an HTTP server
This is a library that allows you to configure OTA updates in your code without much boilerplate code.
Simple example
Steps to use:
1- Define your configuration object (OTAConfig)
2- Instance the EasyOta object with the configuration created
3- Run runUpdateRoutine() to make the verification process of updates
#include <EasyOTA.h>
#include <WifiHelper.h>
#define FW_VERSION "1244"
#define OTA_SERVER_URL "http://192.168.0.222:8266"
// This API endpoint must return an integer indicating the version of the server's latest firmware (eg "1244")
#define OTA_GET_VERSION_ENDPOINT "/version" // or /version.php?device=MyDeviceName if using Apache OTA
// This endpoint must return the firmware file
#define OTA_GET_FIRMWARE_ENDPOINT "/firmware" // or /firmware.php?device=MyDeviceName if using Apache OTA
WiFiClient client;
const char *ssid = "TP-LINK_E056F4";
const char *password = "05127238";
OTAConfig config(
FW_VERSION,
OTA_SERVER_URL,
OTA_GET_VERSION_ENDPOINT,
OTA_GET_FIRMWARE_ENDPOINT,
true // Debug
);
EasyOTA ota(config);
void setup() {
Serial.begin(9600);
Serial.println("Booting");
WifiHelper::startWIFI(ssid, password, true, true);
}
void loop() {
ota.runUpdateRoutine(client);
delay(20000);
}
Python OTA server example
The following API has two endpoints:
/version:
Returns the version of the latest firmware added in the directory /opt/firmware
The version is obtained by the name of the file removing the extension .bin
Example If we add a file to the /opt/firmware directory named 1245.bin then the version that would return this endpoint is 1245
/firmware:
Returns the file of the latest firmware added in the directory /opt/firmware
import glob
import os
from bottle import run, static_file, response, get
OTA_GET_VERSION_ENDPOINT = "/version"
OTA_GET_FIRMWARE_ENDPOINT = "/firmware"
OTA_FIRMWARE_DIR = "/opt/firmware"
OTA_SERVER_PORT = 8266
FIRMWARE_FILE_EXTENSION = '.bin'
class OTAServer:
def __init__(self, port: int, debug_mode: bool = False):
self.debug_mode = debug_mode
self.port = port
def start(self):
run(host='0.0.0.0', port=self.port, debug=self.debug_mode)
@staticmethod
def get_latest_fw_version() -> str:
"""Returns the version of the latest (most recent) firmware file in OTA_FIRMWARE_DIR dir"""
list_of_files = glob.glob('{}/*{}'.format(OTA_FIRMWARE_DIR, FIRMWARE_FILE_EXTENSION))
if not list_of_files:
return None
return max(list_of_files, key=os.path.getctime)\
.replace(FIRMWARE_FILE_EXTENSION, "").replace(OTA_FIRMWARE_DIR+'/', "")
# ------- ENDPOINTS ------- #
@staticmethod
@get(OTA_GET_VERSION_ENDPOINT)
def get_version() -> str:
raw_version = OTAServer.get_latest_fw_version()
if raw_version is None or len(raw_version) == 0:
response.status = 404
return ""
response.status = 200
return str(raw_version)
@staticmethod
@get(OTA_GET_FIRMWARE_ENDPOINT)
def get_firmware():
version = OTAServer.get_latest_fw_version()
if version is None or version == '':
response.status = 404
return ""
return static_file(root=OTA_FIRMWARE_DIR, filename=version + FIRMWARE_FILE_EXTENSION)
if __name__ == '__main__':
path = pathlib.Path(OTA_FIRMWARE_DIR)
last_modified = path.stat().st_mtime
run(host='0.0.0.0', port=OTA_SERVER_PORT, debug=True)
Output
Firmware update sequence
Apache OTA server example
This API is capable of managing multiple firmware images for different devices.
The following API has two endpoints:
/version.php?device={device}:
Returns the version of the latest firmware for the specified added in the directory /firmware
The version is obtained by the name of the file removing the extension .bin and the device name.
Example /firmware/MyTemperatureSensor_1245.bin http://myupdateserver.local/version.php?device=MyTemperatureSensor would return 1245
/firmware.php?device={device}:
Returns the file of the latest firmware for the specified device added in the directory /firmware
version.php
<?php
header("Content-Type:text/html");
include ("functions.php");
if (isset($_GET['device']) && $_GET['device']!="") {
$device = $_GET['device'];
echo return_version($device);
}else{
die("Invalid Request");
}
?>
firmware.php
<?php
header("Content-Type:text/html");
include ("functions.php");
if (isset($_GET['device']) && $_GET['device']!="") {
$device = $_GET['device'];
$version = return_version($device);
$filename = $device . "_" . $version . ".bin";
return_file($filename);
}else{
die("Invalid Request");
}
?>
functions.php
<?php
function return_version($device){
$allfiles = scandir($_SERVER["DOCUMENT_ROOT"] . "/firmware", SCANDIR_SORT_ASCENDING, null); // for the file format like {device}_{version}.bin
$devicefiles = array();
foreach ($allfiles as $string) {
if (str_contains($string, $device)) {
$devicefiles[] = str_replace($device."_","",str_replace(".bin","",$string));
}
}
return max($devicefiles);
}
function return_file($filename)
{
$attachment_location = $_SERVER["DOCUMENT_ROOT"] . "/firmware/" . $filename;
if (file_exists($attachment_location)) {
header($_SERVER["SERVER_PROTOCOL"] . " 200 OK");
header("Cache-Control: public"); // needed for internet explorer
header("Content-Type: application/octet-stream");
header("Content-Transfer-Encoding: Binary");
header("Content-Length:".filesize($attachment_location));
header("Content-Disposition: attachment; filename=" . $filename);
readfile($attachment_location);
die();
} else {
die("Error: File not found.");
}
}
?>