This commit is contained in:
2021-08-04 22:10:25 +02:00
parent a6c224708d
commit 8b05591702
3 changed files with 114 additions and 100 deletions

View File

@@ -11,6 +11,8 @@ set(dependencies
espchrono espchrono
espcpputils espcpputils
esp_http_client esp_http_client
expected
fmt
) )
idf_component_register( idf_component_register(

View File

@@ -12,6 +12,9 @@
// esp-idf includes // esp-idf includes
#include <esp_log.h> #include <esp_log.h>
// 3rdparty lib includes
#include <fmt/core.h>
// local includes // local includes
#include "cleanuphelper.h" #include "cleanuphelper.h"
#include "taskutils.h" #include "taskutils.h"
@@ -34,7 +37,7 @@ AsyncHttpRequest::AsyncHttpRequest(const char *taskName, espcpputils::CoreAffini
m_taskName{taskName}, m_taskName{taskName},
m_coreAffinity{coreAffinity} m_coreAffinity{coreAffinity}
{ {
assert(eventGroup.handle); assert(m_eventGroup.handle);
} }
AsyncHttpRequest::~AsyncHttpRequest() AsyncHttpRequest::~AsyncHttpRequest()
@@ -42,201 +45,201 @@ AsyncHttpRequest::~AsyncHttpRequest()
endTask(); endTask();
} }
std::optional<std::string> AsyncHttpRequest::startTask() tl::expected<void, std::string> AsyncHttpRequest::startTask()
{ {
if (const auto bits = eventGroup.getBits(); if (const auto bits = m_eventGroup.getBits();
bits & TASK_RUNNING || taskHandle) bits & TASK_RUNNING || m_taskHandle)
{ {
constexpr auto msg = "task already started"; constexpr auto msg = "task already started";
ESP_LOGW(TAG, "%s", msg); ESP_LOGW(TAG, "%s", msg);
return msg; return tl::make_unexpected(msg);
} }
eventGroup.clearBits(TASK_RUNNING | START_REQUEST_BIT | REQUEST_RUNNING_BIT | REQUEST_FINISHED_BIT | END_TASK_BIT | TASK_ENDED); m_eventGroup.clearBits(TASK_RUNNING | START_REQUEST_BIT | REQUEST_RUNNING_BIT | REQUEST_FINISHED_BIT | END_TASK_BIT | TASK_ENDED);
const auto result = espcpputils::createTask(requestTask, m_taskName, 2048, this, 10, &taskHandle, m_coreAffinity); const auto result = espcpputils::createTask(requestTask, m_taskName, 2048, this, 10, &m_taskHandle, m_coreAffinity);
if (result != pdPASS) if (result != pdPASS)
{ {
auto msg = std::string{"failed creating http task "} + std::to_string(result); auto msg = fmt::format("failed creating http task {}", result);
ESP_LOGE(TAG, "%s", msg.c_str()); ESP_LOGE(TAG, "%.*s", msg.size(), msg.data());
return msg; return tl::make_unexpected(std::move(msg));
} }
if (!taskHandle) if (!m_taskHandle)
{ {
constexpr auto msg = "http task handle is null"; constexpr auto msg = "http task handle is null";
ESP_LOGW(TAG, "%s", msg); ESP_LOGW(TAG, "%s", msg);
return msg; return tl::make_unexpected(msg);
} }
ESP_LOGD(TAG, "created http task %s", m_taskName); ESP_LOGD(TAG, "created http task %s", m_taskName);
if (const auto bits = eventGroup.waitBits(TASK_RUNNING, false, false, std::chrono::ceil<espcpputils::ticks>(1s).count()); if (const auto bits = m_eventGroup.waitBits(TASK_RUNNING, false, false, std::chrono::ceil<espcpputils::ticks>(1s).count());
bits & TASK_RUNNING) bits & TASK_RUNNING)
return std::nullopt; return {};
ESP_LOGW(TAG, "http task %s TASK_RUNNING bit not yet set...", m_taskName); ESP_LOGW(TAG, "http task %s TASK_RUNNING bit not yet set...", m_taskName);
while (true) while (true)
if (const auto bits = eventGroup.waitBits(TASK_RUNNING, false, false, portMAX_DELAY); if (const auto bits = m_eventGroup.waitBits(TASK_RUNNING, false, false, portMAX_DELAY);
bits & TASK_RUNNING) bits & TASK_RUNNING)
break; break;
return std::nullopt; return {};
} }
std::optional<std::string> AsyncHttpRequest::endTask() tl::expected<void, std::string> AsyncHttpRequest::endTask()
{ {
if (const auto bits = eventGroup.getBits(); if (const auto bits = m_eventGroup.getBits();
!(bits & TASK_RUNNING)) !(bits & TASK_RUNNING))
return std::nullopt; return {};
else if (bits & END_TASK_BIT) else if (bits & END_TASK_BIT)
{ {
constexpr auto msg = "Another end request is already pending"; constexpr auto msg = "Another end request is already pending";
ESP_LOGE(TAG, "%s", msg); ESP_LOGE(TAG, "%s", msg);
return msg; return tl::make_unexpected(msg);
} }
eventGroup.setBits(END_TASK_BIT); m_eventGroup.setBits(END_TASK_BIT);
if (const auto bits = eventGroup.waitBits(TASK_ENDED, true, false, std::chrono::ceil<espcpputils::ticks>(1s).count()); if (const auto bits = m_eventGroup.waitBits(TASK_ENDED, true, false, std::chrono::ceil<espcpputils::ticks>(1s).count());
bits & TASK_ENDED) bits & TASK_ENDED)
{ {
ESP_LOGD(TAG, "http task %s ended", m_taskName); ESP_LOGD(TAG, "http task %s ended", m_taskName);
return std::nullopt; return {};
} }
ESP_LOGW(TAG, "http task %s TASK_ENDED bit not yet set...", m_taskName); ESP_LOGW(TAG, "http task %s TASK_ENDED bit not yet set...", m_taskName);
while (true) while (true)
if (const auto bits = eventGroup.waitBits(TASK_ENDED, true, false, portMAX_DELAY); if (const auto bits = m_eventGroup.waitBits(TASK_ENDED, true, false, portMAX_DELAY);
bits & TASK_ENDED) bits & TASK_ENDED)
break; break;
ESP_LOGD(TAG, "http task %s ended", m_taskName); ESP_LOGD(TAG, "http task %s ended", m_taskName);
return std::nullopt; return {};
} }
std::optional<std::string> AsyncHttpRequest::createClient(const std::string &url) tl::expected<void, std::string> AsyncHttpRequest::createClient(std::string_view url)
{ {
if (client) if (m_client)
{ {
constexpr auto msg = "client already created"; constexpr auto msg = "m_client already created";
ESP_LOGE(TAG, "%s", msg); ESP_LOGE(TAG, "%s", msg);
return msg; return tl::make_unexpected(msg);
} }
esp_http_client_config_t config{}; esp_http_client_config_t config{};
config.url = url.c_str(); config.url = url.data();
config.event_handler = httpEventHandler; config.event_handler = httpEventHandler;
config.user_data = this; config.user_data = this;
client = espcpputils::http_client{&config}; m_client = espcpputils::http_client{&config};
if (!client) if (!m_client)
{ {
constexpr auto msg = "http client could not be constructed"; constexpr auto msg = "http client could not be constructed";
ESP_LOGE(TAG, "%s", msg); ESP_LOGE(TAG, "%s", msg);
return msg; return tl::make_unexpected(msg);
} }
ESP_LOGD(TAG, "created http client %s", m_taskName); ESP_LOGD(TAG, "created http client %s", m_taskName);
return std::nullopt; return {};
} }
std::optional<std::string> AsyncHttpRequest::deleteClient() tl::expected<void, std::string> AsyncHttpRequest::deleteClient()
{ {
if (!client) if (!m_client)
return std::nullopt; return {};
if (inProgress()) if (inProgress())
{ {
constexpr auto msg = "another request still in progress"; constexpr auto msg = "another request still in progress";
ESP_LOGW(TAG, "%s", msg); ESP_LOGW(TAG, "%s", msg);
return msg; return tl::make_unexpected(msg);
} }
client = {}; m_client = {};
return std::nullopt; return {};
} }
std::optional<std::string> AsyncHttpRequest::start(const std::string &url) tl::expected<void, std::string> AsyncHttpRequest::start(std::string_view url)
{ {
if (!taskHandle) if (!m_taskHandle)
{ {
if (const auto failed = startTask()) if (const auto result = startTask(); !result)
return *failed; return tl::make_unexpected(std::move(result).error());
} }
if (inProgress()) if (inProgress())
{ {
constexpr auto msg = "another request still in progress"; constexpr auto msg = "another request still in progress";
ESP_LOGW(TAG, "%s", msg); ESP_LOGW(TAG, "%s", msg);
return msg; return tl::make_unexpected(msg);
} }
if (!client) if (!m_client)
{ {
if (const auto failed = createClient(url)) if (const auto result = createClient(url); !result)
return *failed; return tl::make_unexpected(std::move(result).error());
} }
else else
{ {
const auto result = client.set_url(url); const auto result = m_client.set_url(url);
ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_DEBUG : ESP_LOG_ERROR), TAG, "client.set_url() returned: %s (%s)", esp_err_to_name(result), url.c_str()); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_DEBUG : ESP_LOG_ERROR), TAG, "m_client.set_url() returned: %s (%.*s)", esp_err_to_name(result), url.size(), url.data());
if (result != ESP_OK) if (result != ESP_OK)
return std::string{"client.set_url() failed: "} + esp_err_to_name(result) + " (" + url + ')'; return tl::make_unexpected(fmt::format("m_client.set_url() failed: {} ({})", esp_err_to_name(result), url));
} }
buf.clear(); m_buf.clear();
clearFinished(); clearFinished();
eventGroup.setBits(START_REQUEST_BIT); m_eventGroup.setBits(START_REQUEST_BIT);
return std::nullopt; return {};
} }
bool AsyncHttpRequest::inProgress() const bool AsyncHttpRequest::inProgress() const
{ {
return eventGroup.getBits() & (START_REQUEST_BIT | REQUEST_RUNNING_BIT); return m_eventGroup.getBits() & (START_REQUEST_BIT | REQUEST_RUNNING_BIT);
} }
bool AsyncHttpRequest::finished() const bool AsyncHttpRequest::finished() const
{ {
return eventGroup.getBits() & REQUEST_FINISHED_BIT; return m_eventGroup.getBits() & REQUEST_FINISHED_BIT;
} }
std::optional<std::string> AsyncHttpRequest::failed() const tl::expected<void, std::string> AsyncHttpRequest::result() const
{ {
if (const auto bits = eventGroup.getBits(); if (const auto bits = m_eventGroup.getBits();
bits & REQUEST_RUNNING_BIT) bits & REQUEST_RUNNING_BIT)
{ {
constexpr auto msg = "request still running"; constexpr auto msg = "request still running";
ESP_LOGW(TAG, "%s", msg); ESP_LOGW(TAG, "%s", msg);
return msg; return tl::make_unexpected(msg);
} }
else if (!(bits & REQUEST_FINISHED_BIT)) else if (!(bits & REQUEST_FINISHED_BIT))
{ {
constexpr auto msg = "request not finished"; constexpr auto msg = "request not finished";
ESP_LOGW(TAG, "%s", msg); ESP_LOGW(TAG, "%s", msg);
return msg; return tl::make_unexpected(msg);
} }
if (result != ESP_OK) if (m_result != ESP_OK)
return std::string{"http request failed: "} + esp_err_to_name(result); return tl::make_unexpected(fmt::format("http request failed: {}", esp_err_to_name(m_result)));
if (statusCode != HttpStatus_Ok) if (m_statusCode != HttpStatus_Ok)
return std::string{"http request failed: "} + std::to_string(statusCode); return tl::make_unexpected(fmt::format("http request failed: {}", m_statusCode));
return std::nullopt; return {};
} }
void AsyncHttpRequest::clearFinished() void AsyncHttpRequest::clearFinished()
{ {
eventGroup.clearBits(REQUEST_FINISHED_BIT); m_eventGroup.clearBits(REQUEST_FINISHED_BIT);
} }
esp_err_t AsyncHttpRequest::httpEventHandler(esp_http_client_event_t *evt) esp_err_t AsyncHttpRequest::httpEventHandler(esp_http_client_event_t *evt)
@@ -255,7 +258,7 @@ esp_err_t AsyncHttpRequest::httpEventHandler(esp_http_client_event_t *evt)
if (std::sscanf(evt->header_value, "%u", &size) == 1) if (std::sscanf(evt->header_value, "%u", &size) == 1)
{ {
//ESP_LOGD(TAG, "reserving %u bytes for http buffer", size); //ESP_LOGD(TAG, "reserving %u bytes for http buffer", size);
_this->buf.reserve(size); _this->m_buf.reserve(size);
} }
else else
{ {
@@ -270,7 +273,7 @@ esp_err_t AsyncHttpRequest::httpEventHandler(esp_http_client_event_t *evt)
else if (evt->data_len <= 0) else if (evt->data_len <= 0)
ESP_LOGW(TAG, "handler with invalid data_len %i", evt->data_len); ESP_LOGW(TAG, "handler with invalid data_len %i", evt->data_len);
else else
_this->buf += std::string_view((const char *)evt->data, evt->data_len); _this->m_buf += std::string_view((const char *)evt->data, evt->data_len);
break; break;
default:; default:;
@@ -285,54 +288,59 @@ void AsyncHttpRequest::requestTask(void *ptr)
assert(_this); assert(_this);
_this->eventGroup.setBits(TASK_RUNNING); _this->requestTask();
}
void AsyncHttpRequest::requestTask()
{
m_eventGroup.setBits(TASK_RUNNING);
// cleanup on task exit // cleanup on task exit
auto helper = cpputils::makeCleanupHelper([&](){ auto helper = cpputils::makeCleanupHelper([&](){
_this->eventGroup.clearBits(TASK_RUNNING); m_eventGroup.clearBits(TASK_RUNNING);
_this->eventGroup.setBits(TASK_ENDED); m_eventGroup.setBits(TASK_ENDED);
_this->taskHandle = NULL; m_taskHandle = NULL;
vTaskDelete(NULL); vTaskDelete(NULL);
}); });
while (true) while (true)
{ {
if (const auto bits = _this->eventGroup.waitBits(START_REQUEST_BIT|END_TASK_BIT, true, false, portMAX_DELAY); if (const auto bits = m_eventGroup.waitBits(START_REQUEST_BIT|END_TASK_BIT, true, false, portMAX_DELAY);
bits & END_TASK_BIT) bits & END_TASK_BIT)
break; break;
else if (!(bits & START_REQUEST_BIT)) else if (!(bits & START_REQUEST_BIT))
continue; continue;
assert(_this->client); assert(m_client);
{ {
const auto bits = _this->eventGroup.getBits(); const auto bits = m_eventGroup.getBits();
assert(!(bits & START_REQUEST_BIT)); assert(!(bits & START_REQUEST_BIT));
assert(!(bits & REQUEST_RUNNING_BIT)); assert(!(bits & REQUEST_RUNNING_BIT));
assert(!(bits & REQUEST_FINISHED_BIT)); assert(!(bits & REQUEST_FINISHED_BIT));
} }
_this->eventGroup.setBits(REQUEST_RUNNING_BIT); m_eventGroup.setBits(REQUEST_RUNNING_BIT);
auto helper2 = cpputils::makeCleanupHelper([&](){ auto helper2 = cpputils::makeCleanupHelper([&](){
_this->eventGroup.clearBits(REQUEST_RUNNING_BIT); m_eventGroup.clearBits(REQUEST_RUNNING_BIT);
_this->eventGroup.setBits(REQUEST_FINISHED_BIT); m_eventGroup.setBits(REQUEST_FINISHED_BIT);
}); });
{ {
esp_err_t result; esp_err_t result;
do do
result = _this->client.perform(); result = m_client.perform();
while (result == EAGAIN || result == EINPROGRESS); while (result == EAGAIN || result == EINPROGRESS);
ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_VERBOSE : ESP_LOG_ERROR), TAG, "client.perform() returned: %s", esp_err_to_name(result)); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_VERBOSE : ESP_LOG_ERROR), TAG, "m_client.perform() returned: %s", esp_err_to_name(result));
_this->result = result; m_result = result;
_this->statusCode = _this->client.get_status_code(); m_statusCode = m_client.get_status_code();
} }
// workaround for esp-idf bug, every request after the first one fails with ESP_ERR_HTTP_FETCH_HEADER // workaround for esp-idf bug, every request after the first one fails with ESP_ERR_HTTP_FETCH_HEADER
const auto result = _this->client.close(); const auto result = m_client.close();
ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_VERBOSE : ESP_LOG_ERROR), TAG, "client.close() returned: %s", esp_err_to_name(result)); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_VERBOSE : ESP_LOG_ERROR), TAG, "m_client.close() returned: %s", esp_err_to_name(result));
if (result != ESP_OK) if (result != ESP_OK)
_this->client = {}; m_client = {};
} }
} }

View File

@@ -1,14 +1,17 @@
#pragma once #pragma once
// system includes // system includes
#include <optional>
#include <string> #include <string>
#include <string_view>
// esp-idf includes // esp-idf includes
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <esp_err.h> #include <esp_err.h>
// 3rdparty lib includes
#include <tl/expected.hpp>
// local includes // local includes
#include "wrappers/http_client.h" #include "wrappers/http_client.h"
#include "wrappers/event_group.h" #include "wrappers/event_group.h"
@@ -20,34 +23,35 @@ public:
AsyncHttpRequest(const char *taskName="httpRequestTask", espcpputils::CoreAffinity coreAffinity=espcpputils::CoreAffinity::Core1); AsyncHttpRequest(const char *taskName="httpRequestTask", espcpputils::CoreAffinity coreAffinity=espcpputils::CoreAffinity::Core1);
~AsyncHttpRequest(); ~AsyncHttpRequest();
std::optional<std::string> startTask(); tl::expected<void, std::string> startTask();
std::optional<std::string> endTask(); tl::expected<void, std::string> endTask();
std::optional<std::string> createClient(const std::string &url); tl::expected<void, std::string> createClient(std::string_view url);
std::optional<std::string> deleteClient(); tl::expected<void, std::string> deleteClient();
std::optional<std::string> start(const std::string &url); tl::expected<void, std::string> start(std::string_view url);
bool inProgress() const; bool inProgress() const;
bool finished() const; bool finished() const;
std::optional<std::string> failed() const; tl::expected<void, std::string> result() const;
void clearFinished(); void clearFinished();
const std::string &buffer() const { return buf; } const std::string &buffer() const { return m_buf; }
std::string &&takeBuffer() { return std::move(buf); } std::string &&takeBuffer() { return std::move(m_buf); }
private: private:
static esp_err_t httpEventHandler(esp_http_client_event_t *evt); static esp_err_t httpEventHandler(esp_http_client_event_t *evt);
static void requestTask(void *ptr); static void requestTask(void *ptr);
void requestTask();
espcpputils::http_client client; espcpputils::http_client m_client;
std::string buf; std::string m_buf;
TaskHandle_t taskHandle{NULL}; TaskHandle_t m_taskHandle{NULL};
espcpputils::event_group eventGroup; espcpputils::event_group m_eventGroup;
esp_err_t result{}; esp_err_t m_result{};
int statusCode{}; int m_statusCode{};
const char * const m_taskName; const char * const m_taskName;
const espcpputils::CoreAffinity m_coreAffinity; const espcpputils::CoreAffinity m_coreAffinity;