Skip to content

Commit

Permalink
Merge pull request #93 from kivancsikert/http-update/do-after-restart
Browse files Browse the repository at this point in the history
Run HTTP update as part of startup
  • Loading branch information
lptr authored Feb 7, 2024
2 parents d151ee6 + e36dbc2 commit 5fc466e
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 31 deletions.
22 changes: 16 additions & 6 deletions src/devices/Device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class ConsolePrinter : public Print {
ConsolePrinter() {
static const String spinner = "|/-\\";
static const int spinnerLength = spinner.length();
Task::loop("console", 8192, 1, [this](Task& task) {
Task::loop("console", 2048, 1, [this](Task& task) {
String status;

counter = (counter + 1) % spinnerLength;
Expand Down Expand Up @@ -199,7 +199,13 @@ class MqttTelemetryPublisher : public TelemetryPublisher {
TelemetryCollector& telemetryCollector;
};

class Device : ConsoleProvider {
class ConfiguredKernel : ConsoleProvider {
public:
TDeviceDefinition deviceDefinition;
Kernel<TDeviceConfiguration> kernel { deviceDefinition.config, deviceDefinition.statusLed };
};

class Device {
public:
Device() {

Expand Down Expand Up @@ -277,9 +283,11 @@ class Device : ConsoleProvider {
peripheralManager.publishTelemetry();
}

TDeviceDefinition deviceDefinition;
ConfiguredKernel configuredKernel;
Kernel<TDeviceConfiguration>& kernel = configuredKernel.kernel;
TDeviceDefinition& deviceDefinition = configuredKernel.deviceDefinition;
TDeviceConfiguration& deviceConfig = deviceDefinition.config;
Kernel<TDeviceConfiguration> kernel { deviceConfig, deviceDefinition.statusLed };

shared_ptr<MqttDriver::MqttRoot> mqttDeviceRoot = kernel.mqtt.forRoot("devices/ugly-duckling/" + deviceConfig.instance.get());
PeripheralManager peripheralManager { mqttDeviceRoot };

Expand All @@ -293,15 +301,17 @@ class Device : ConsoleProvider {
MemoryTelemetryProvider memoryTelemetryProvider;
#endif

FileSystem& fs { FileSystem::get() };
FileSystem& fs { kernel.fs };
EchoCommand echoCommand;
RestartCommand restartCommand;
SleepCommand sleepCommand;
FileListCommand fileListCommand { fs };
FileReadCommand fileReadCommand { fs };
FileWriteCommand fileWriteCommand { fs };
FileRemoveCommand fileRemoveCommand { fs };
HttpUpdateCommand httpUpdateCommand { kernel.version };
HttpUpdateCommand httpUpdateCommand { [this](const String& url) {
kernel.prepareUpdate(url);
} };
};

} // namespace farmhub::devices
35 changes: 11 additions & 24 deletions src/kernel/Command.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

#include <ArduinoJson.h>
#include <ArduinoLog.h>
#include <HTTPUpdate.h>
#include <SPIFFS.h>

#include <esp_sleep.h>

#include <kernel/FileSystem.hpp>
#include <kernel/Named.hpp>
#include <kernel/Task.hpp>

using namespace std::chrono;

Expand Down Expand Up @@ -200,9 +200,9 @@ class FileRemoveCommand : public FileCommand {

class HttpUpdateCommand : public Command {
public:
HttpUpdateCommand(const String& currentVersion)
HttpUpdateCommand(const std::function<void(const String&)> prepareUpdate)
: Command("update")
, currentVersion(currentVersion) {
, prepareUpdate(prepareUpdate) {
}

void handle(const JsonObject& request, JsonObject& response) override {
Expand All @@ -215,30 +215,17 @@ class HttpUpdateCommand : public Command {
response["failure"] = "Command contains empty url";
return;
}
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
response["failure"] = update(url, currentVersion);
prepareUpdate(url);
response["success"] = true;
Task::run("update", [](Task& task) {
Log.infoln("Restarting in 5 seconds to apply update");
delay(5000);
ESP.restart();
});
}

private:
static String update(const String& url, const String& currentVersion) {
Log.infoln("Updating from version %s via URL %s",
currentVersion.c_str(), url.c_str());
WiFiClientSecure client;
// Allow insecure connections for testing
client.setInsecure();
HTTPUpdateResult result = httpUpdate.update(client, url, currentVersion);
switch (result) {
case HTTP_UPDATE_FAILED:
return httpUpdate.getLastErrorString() + " (" + String(httpUpdate.getLastError()) + ")";
case HTTP_UPDATE_NO_UPDATES:
return "No updates available";
case HTTP_UPDATE_OK:
return "Update OK";
default:
return "Unknown response";
}
}

const std::function<void(const String&)> prepareUpdate;
const String currentVersion;
};

Expand Down
83 changes: 82 additions & 1 deletion src/kernel/Kernel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
#include <functional>
#include <optional>

#include <HTTPUpdate.h>

#include <freertos/FreeRTOS.h>

#include <ArduinoLog.h>

#include <kernel/FileSystem.hpp>
#include <kernel/drivers/LedDriver.hpp>
#include <kernel/drivers/MdnsDriver.hpp>
#include <kernel/drivers/MqttDriver.hpp>
Expand All @@ -25,6 +28,8 @@ class Kernel;

static RTC_DATA_ATTR int bootCount = 0;

static const String UPDATE_FILE = "/update.json";

// TODO Move this to a separate file
static const String& getMacAddress() {
static String macAddress;
Expand Down Expand Up @@ -60,7 +65,13 @@ class Kernel {
deviceConfig.instance.get().c_str(),
deviceConfig.getHostname());

Task::loop("status-update", 4096, [this](Task&) { updateState(); });
Task::loop("status-update", 2048, [this](Task&) { updateState(); });

httpUpdateResult = handleHttpUpdate();
}

const State& getNetworkReadyState() const {
return networkReadyState;
}

const State& getRtcInSyncState() const {
Expand All @@ -71,8 +82,22 @@ class Kernel {
return kernelReadyState;
}

const String& getHttpUpdateResult() const {
return httpUpdateResult;
}

void prepareUpdate(const String& url) {
auto fUpdate = fs.open(UPDATE_FILE, FILE_WRITE);
DynamicJsonDocument doc(docSizeFor(url));
doc["url"] = url;
serializeJson(doc, fUpdate);
fUpdate.close();
}

const String version;

FileSystem& fs { FileSystem::get() };

private:
enum class KernelState {
BOOTING,
Expand Down Expand Up @@ -146,6 +171,60 @@ class Kernel {
stateManager.awaitStateChange();
}

String handleHttpUpdate() {
if (!fs.exists(UPDATE_FILE)) {
return "";
}

Log.infoln("Starting update...");
auto fUpdate = fs.open(UPDATE_FILE, FILE_READ);
DynamicJsonDocument doc(farmhub::kernel::docSizeFor(fUpdate));
auto error = deserializeJson(doc, fUpdate);
fUpdate.close();
fs.remove(UPDATE_FILE);

if (error) {
return "Failed to parse update.json: " + String(error.c_str());
}
String url = doc["url"];
if (url.length() == 0) {
return "Command contains empty url";
}

Log.traceln("Waiting for network...");
if (!networkReadyState.awaitSet(seconds(60))) {
return "Network not ready, aborting update";
}

httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
Log.infoln("Updating from version %s via URL %s",
VERSION, url.c_str());

HTTPUpdateResult result = HTTP_UPDATE_NO_UPDATES;
// Run in separate task to allocate enough stack
SemaphoreHandle_t completionSemaphore = xSemaphoreCreateBinary();
Task::run("update", 8192, [&](Task& task) {
// Allocate on heap to avoid wasting stack
std::unique_ptr<WiFiClientSecure> client = std::make_unique<WiFiClientSecure>();
// Allow insecure connections for testing
client->setInsecure();
result = httpUpdate.update(*client, url, VERSION);
xSemaphoreGive(completionSemaphore);
});
xSemaphoreTake(completionSemaphore, portMAX_DELAY);

switch (result) {
case HTTP_UPDATE_FAILED:
return httpUpdate.getLastErrorString() + " (" + String(httpUpdate.getLastError()) + ")";
case HTTP_UPDATE_NO_UPDATES:
return "No updates available";
case HTTP_UPDATE_OK:
return "Update OK";
default:
return "Unknown response";
}
}

TDeviceConfiguration& deviceConfig;

LedDriver& statusLed;
Expand All @@ -171,6 +250,8 @@ class Kernel {
MdnsDriver mdns { networkReadyState, deviceConfig.getHostname(), "ugly-duckling", version, mdnsReadyState };
RtcDriver rtc { networkReadyState, mdns, deviceConfig.ntp.get(), rtcInSyncState };

String httpUpdateResult;

public:
MqttDriver mqtt { networkReadyState, mdns, deviceConfig.mqtt.get(), deviceConfig.instance.get(), mqttReadyState };
};
Expand Down

0 comments on commit 5fc466e

Please sign in to comment.