Added Updater to automatically perform OTA updates without user interaction.
This commit is contained in:
parent
3b0410f560
commit
07b1ea3a5c
9
DEPLOY.md
Normal file
9
DEPLOY.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Deploying a new version
|
||||||
|
|
||||||
|
* Update the OTA_VERSION in `include/config.h`, `include/config.sample.h`,
|
||||||
|
`data/_version.txt`, and `update.manifest`.
|
||||||
|
* Commit the changes.
|
||||||
|
* Tag the commit with the new version string.
|
||||||
|
* Push everything.
|
||||||
|
* Create theimages using `pio run` and `pio run -t buildfs`.
|
||||||
|
* Upload `update.manifest`, `firmware.bin` and `spiffs.bin`.
|
1
data/_version.txt
Normal file
1
data/_version.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
1
|
@ -1,13 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// This is a simple number indicating the version for the HTTP Updater.
|
||||||
|
#define OTA_VERSION 1
|
||||||
|
// Comment out to prevent automatic updates.
|
||||||
|
#define OTA_UPDATE_URL "https://files.schle.nz/esmp3/update.manifest"
|
||||||
|
#define OTA_CHECK_INTERVAL 12*60*60*1000 // 12 hours
|
||||||
|
|
||||||
#define SHOW_DEBUG
|
#define SHOW_DEBUG
|
||||||
//#define SHOW_TRACE
|
//#define SHOW_TRACE
|
||||||
#define FTP_DEBUG
|
#define FTP_DEBUG
|
||||||
#define DELAY_AFTER_DEBUG_AND_TRACE 0
|
#define DELAY_AFTER_DEBUG_AND_TRACE 0
|
||||||
|
|
||||||
#define WIFI_SSID "---CHANGEME---"
|
// Here you can define WiFi data to use. But actually, the better way to do
|
||||||
#define WIFI_PASS "---CHANGEME---"
|
// this is by using /_wifi.txt on the sd card.
|
||||||
|
//#define WIFI_SSID "---CHANGEME---"
|
||||||
|
//#define WIFI_PASS "---CHANGEME---"
|
||||||
|
|
||||||
#define VS1053_SLEEP_DELAY 5000
|
#define VS1053_SLEEP_DELAY 5000
|
||||||
#define POSITION_SEND_INTERVAL 5000
|
#define POSITION_SEND_INTERVAL 5000
|
||||||
|
@ -32,6 +32,7 @@ private:
|
|||||||
|
|
||||||
unsigned long _last_rfid_scan_at = 0;
|
unsigned long _last_rfid_scan_at = 0;
|
||||||
unsigned long _last_position_info_at = 0;
|
unsigned long _last_position_info_at = 0;
|
||||||
|
unsigned long _last_update_check_at = 0;
|
||||||
String _serial_buffer = String();
|
String _serial_buffer = String();
|
||||||
String _cmd_queue = "";
|
String _cmd_queue = "";
|
||||||
void _execute_command_ls(String path);
|
void _execute_command_ls(String path);
|
||||||
|
8
include/updater.h
Normal file
8
include/updater.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Updater {
|
||||||
|
public:
|
||||||
|
static void run();
|
||||||
|
static bool do_update(int cmd, String url);
|
||||||
|
static void update_spiffs();
|
||||||
|
};
|
@ -3,6 +3,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
#include "http_server.h"
|
#include "http_server.h"
|
||||||
|
#include "updater.h"
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
Controller::Controller(Player* p, PlaylistManager* playlist_manager) {
|
Controller::Controller(Player* p, PlaylistManager* playlist_manager) {
|
||||||
@ -50,6 +51,14 @@ void Controller::loop() {
|
|||||||
process_message(_cmd_queue);
|
process_message(_cmd_queue);
|
||||||
_cmd_queue = "";
|
_cmd_queue = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef OTA_UPDATE_URL
|
||||||
|
if (!player->is_playing() && _last_update_check_at<millis() && _last_update_check_at + OTA_CHECK_INTERVAL < millis()) {
|
||||||
|
Updater::run();
|
||||||
|
} else {
|
||||||
|
_last_update_check_at = millis();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
TRACE("Controller::loop() done.\n");
|
TRACE("Controller::loop() done.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
src/main.cpp
26
src/main.cpp
@ -10,6 +10,7 @@
|
|||||||
#include "spi_master.h"
|
#include "spi_master.h"
|
||||||
#include "http_server.h"
|
#include "http_server.h"
|
||||||
#include "playlist_manager.h"
|
#include "playlist_manager.h"
|
||||||
|
#include "updater.h"
|
||||||
|
|
||||||
Controller* controller;
|
Controller* controller;
|
||||||
Player* player;
|
Player* player;
|
||||||
@ -38,9 +39,9 @@ void setup() {
|
|||||||
Serial.println("Started.");
|
Serial.println("Started.");
|
||||||
INFO("Starting.\n");
|
INFO("Starting.\n");
|
||||||
#ifdef VERSION
|
#ifdef VERSION
|
||||||
INFO("ESMP3 version %s\n", VERSION);
|
INFO("ESMP3 version %s (OTA_VERSION %d)\n", VERSION, OTA_VERSION);
|
||||||
#else
|
#else
|
||||||
INFO("ESMP3, version unknown\n");
|
INFO("ESMP3, version unknown (OTA_VERSION %d)\n", OTA_VERSION);
|
||||||
#endif
|
#endif
|
||||||
INFO("Initializing...\n");
|
INFO("Initializing...\n");
|
||||||
|
|
||||||
@ -61,7 +62,16 @@ void setup() {
|
|||||||
spi->select_sd(false);
|
spi->select_sd(false);
|
||||||
|
|
||||||
DEBUG("Starting SPIFFS...\n");
|
DEBUG("Starting SPIFFS...\n");
|
||||||
|
uint16_t spiffs_version = 0;
|
||||||
SPIFFS.begin(true);
|
SPIFFS.begin(true);
|
||||||
|
if (SPIFFS.exists("/_version.txt")) {
|
||||||
|
File f = SPIFFS.open("/_version.txt", "r");
|
||||||
|
spiffs_version = f.readString().toInt();
|
||||||
|
f.close();
|
||||||
|
DEBUG("SPIFFS filesystem version is %d.\n", spiffs_version);
|
||||||
|
} else {
|
||||||
|
DEBUG("No SPIFFS filesystem version found - setting spiffs_version to 0.\n");
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG("Initializing PlaylistManager...\n");
|
DEBUG("Initializing PlaylistManager...\n");
|
||||||
pm = new PlaylistManager();
|
pm = new PlaylistManager();
|
||||||
@ -93,9 +103,15 @@ void setup() {
|
|||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
SPIMaster::select_sd(false);
|
SPIMaster::select_sd(false);
|
||||||
|
|
||||||
|
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
|
#if defined(WIFI_SSID) and defined(WIFI_PASS)
|
||||||
DEBUG("Trying hardcoded WiFi data...\n");
|
DEBUG("Trying hardcoded WiFi data...\n");
|
||||||
connected = connect_to_wifi(WIFI_SSID, WIFI_PASS);
|
connected = connect_to_wifi(WIFI_SSID, WIFI_PASS);
|
||||||
|
#else
|
||||||
|
DEBUG("No hardcoded WiFi data set.\n");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
INFO("No WiFi connection!\n");
|
INFO("No WiFi connection!\n");
|
||||||
@ -119,6 +135,12 @@ void setup() {
|
|||||||
INFO("Could not fetch current time via NTP.\n");
|
INFO("Could not fetch current time via NTP.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef OTA_UPDATE_URL
|
||||||
|
if (spiffs_version < OTA_VERSION) {
|
||||||
|
Updater::run();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
INFO("Initialization completed.\n");
|
INFO("Initialization completed.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
127
src/updater.cpp
Normal file
127
src/updater.cpp
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <Update.h>
|
||||||
|
#include "config.h"
|
||||||
|
#include "updater.h"
|
||||||
|
#include "http_client_wrapper.h"
|
||||||
|
|
||||||
|
void Updater::run() {
|
||||||
|
DEBUG("Updater is running...\n");
|
||||||
|
HTTPClientWrapper* http = new HTTPClientWrapper();
|
||||||
|
DEBUG("Requesting update info...\n");
|
||||||
|
bool result = http->get(OTA_UPDATE_URL);
|
||||||
|
if (!result) {
|
||||||
|
ERROR("Updater failed requesting %s.\n", OTA_UPDATE_URL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String line = http->readUntil("\n");
|
||||||
|
if (!line.startsWith("VERSION=")) {
|
||||||
|
ERROR("Expected first line to be VERSION.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint16_t version = line.substring(8).toInt();
|
||||||
|
if (version==0) {
|
||||||
|
ERROR("Could not parse version number.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DEBUG("Found version %d. My version is %d.\n", version, OTA_VERSION);
|
||||||
|
if (version <= OTA_VERSION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line = http->readUntil("\n");
|
||||||
|
if (!line.startsWith("IMAGE_PATH=")) {
|
||||||
|
ERROR("Expected second line to be IMAGE_PATH.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String image_path = line.substring(11);
|
||||||
|
image_path.trim();
|
||||||
|
line = http->readUntil("\n");
|
||||||
|
if (!line.startsWith("SPIFFS_PATH=")) {
|
||||||
|
ERROR("Expected third line to be SPIFFS_PATH.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String spiffs_path = line.substring(12);
|
||||||
|
spiffs_path.trim();
|
||||||
|
|
||||||
|
http->close();
|
||||||
|
delete http;
|
||||||
|
|
||||||
|
result = do_update(U_FLASH, image_path);
|
||||||
|
if (result) {
|
||||||
|
do_update(U_SPIFFS, spiffs_path);
|
||||||
|
}
|
||||||
|
DEBUG("Done. Rebooting...\n");
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Updater::do_update(int command, String url) {
|
||||||
|
HTTPClientWrapper* http = new HTTPClientWrapper();
|
||||||
|
bool result = http->get(url);
|
||||||
|
if (!result) {
|
||||||
|
ERROR("Updater failed requesting %s.\n", url.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = Update.begin(http->getSize(), command);
|
||||||
|
if (!result) {
|
||||||
|
ERROR("Update could not be started.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t buf[512];
|
||||||
|
uint16_t len;
|
||||||
|
while((len = http->read(buf, 512))) {
|
||||||
|
Update.write(buf, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
http->close();
|
||||||
|
delete http;
|
||||||
|
|
||||||
|
result = Update.end();
|
||||||
|
if (!result) {
|
||||||
|
ERROR("Writing the update failed somewhere. Aborted.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::update_spiffs() {
|
||||||
|
HTTPClientWrapper* http = new HTTPClientWrapper();
|
||||||
|
DEBUG("Requesting update info...\n");
|
||||||
|
bool result = http->get(OTA_UPDATE_URL);
|
||||||
|
if (!result) {
|
||||||
|
ERROR("Updater failed requesting %s.\n", OTA_UPDATE_URL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String line = http->readUntil("\n");
|
||||||
|
if (!line.startsWith("VERSION=")) {
|
||||||
|
ERROR("Expected first line to be VERSION.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint16_t version = line.substring(8).toInt();
|
||||||
|
if (version==0) {
|
||||||
|
ERROR("Could not parse version number.\n");
|
||||||
|
return;
|
||||||
|
} else if (version > OTA_VERSION) {
|
||||||
|
DEBUG("Found newer version %d. My version is %d. Starting full update!\n", version, OTA_VERSION);
|
||||||
|
run();
|
||||||
|
} else {
|
||||||
|
DEBUG("Loading SPIFFS image.\n");
|
||||||
|
line = http->readUntil("\n");
|
||||||
|
if (!line.startsWith("IMAGE_PATH=")) {
|
||||||
|
ERROR("Expected second line to be IMAGE_PATH.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line = http->readUntil("\n");
|
||||||
|
if (!line.startsWith("SPIFFS_PATH=")) {
|
||||||
|
ERROR("Expected third line to be SPIFFS_PATH.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String spiffs_path = line.substring(12);
|
||||||
|
spiffs_path.trim();
|
||||||
|
do_update(U_SPIFFS, spiffs_path);
|
||||||
|
DEBUG("Done. Rebooting...\n");
|
||||||
|
delay(500);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
http->close();
|
||||||
|
delete http;
|
||||||
|
}
|
3
update.manifest
Normal file
3
update.manifest
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
VERSION=1
|
||||||
|
IMAGE_PATH=https://files.schle.nz/esmp3/firmware.bin
|
||||||
|
SPIFFS_PATH=https://files.schle.nz/esmp3/spiffs.bin
|
Loading…
Reference in New Issue
Block a user