diff --git a/esp/speaker-netsender/components/netsender/include/netsender.hpp b/esp/speaker-netsender/components/netsender/include/netsender.hpp index 0e2b336..98e0e66 100644 --- a/esp/speaker-netsender/components/netsender/include/netsender.hpp +++ b/esp/speaker-netsender/components/netsender/include/netsender.hpp @@ -30,12 +30,10 @@ #pragma once #include -#include #include #include #include #include -#include #include #include "esp_err.h" @@ -116,19 +114,27 @@ struct netsender_configuration_t { char reserved[CONFIG_NETSENDER_RESERVED_SIZE]; }; +// Callback types used by the NetSender client. These are intentionally plain +// function pointers, as the callbacks are non-capturing. +using pin_read_fn = std::optional (*)(); +using pin_read_binary_fn = std::optional> (*)(); +using variable_parser_fn = esp_err_t (*)(const char *); + // Pin represents a pin name and value and optional POST data. struct netsender_pin_t { char name[NETSENDER_PIN_SIZE]; - std::function()> read; - std::function>()> read_binary; + pin_read_fn read = nullptr; + pin_read_binary_fn read_binary = nullptr; std::optional value; std::vector data_storage; - uint8_t *data; + uint8_t *data = nullptr; }; -// extract_json gets a string or integer value from JSON. +// extract_json gets a string or integer value from JSON, writing it to the +// supplied fixed-size buffer. Returns false if the field is missing, malformed, +// or does not fit in the output buffer. // NB: This is NOT a general-purpose JSON parser. -bool netsender_extract_json(const std::string &json, const char *name, std::string &value); +bool netsender_extract_json(const char *json, const char *name, char *out, size_t out_len); class Netsender { public: @@ -147,19 +153,19 @@ class Netsender { /** * @brief append a read function and associated pin. */ - esp_err_t register_input(char *pin_name, std::function()> read_func); + esp_err_t register_input(char *pin_name, pin_read_fn read_func); /** * @brief append a binary read function to an associated pin. */ - esp_err_t register_binary_input(char *pin_name, std::function>()> read_func); + esp_err_t register_binary_input(char *pin_name, pin_read_binary_fn read_func); /** * @brief register a callback to handle variable parsing. * * @param parser_func callback function to be called with the var request response. */ - esp_err_t register_variable_parser(std::function parser_func); + esp_err_t register_variable_parser(variable_parser_fn parser_func); /** * @brief makes a request to get variables. @@ -262,7 +268,7 @@ class Netsender { /** * @brief handles response codes from server request. */ - esp_err_t handle_response_code(std::string rc); + esp_err_t handle_response_code(int32_t rc); /** * Read the config from non-volatile storage (NVS). @@ -294,5 +300,5 @@ class Netsender { /** * @brief callback used to handle variable parsing. */ - std::function parse_variable_callback; + variable_parser_fn parse_variable_callback = nullptr; }; diff --git a/esp/speaker-netsender/components/netsender/netsender.cpp b/esp/speaker-netsender/components/netsender/netsender.cpp index e8d2cb2..3adce77 100644 --- a/esp/speaker-netsender/components/netsender/netsender.cpp +++ b/esp/speaker-netsender/components/netsender/netsender.cpp @@ -29,16 +29,19 @@ #include "freertos/FreeRTOS.h" // IWYU pragma: keep +#include #include +#include #include +#include #include +#include #include #include #include #include #include #include -#include #include #include @@ -85,6 +88,25 @@ int check_pins(const char *names); // zero, the name must be null-terminated. bool is_valid_pin_name(const char *name, size_t len); +// parse_int parses a complete, null-terminated base-10 integer without +// throwing. It returns false on empty, malformed, or out-of-range input. +static bool parse_int(const char *value, int32_t &out) +{ + if (value == nullptr || *value == '\0') { + return false; + } + + char *end = nullptr; + errno = 0; + auto parsed = strtol(value, &end, 10); + if (errno != 0 || end == value || *end != '\0' || parsed < INT32_MIN || parsed > INT32_MAX) { + return false; + } + + out = static_cast(parsed); + return true; +} + Netsender::Netsender() { esp_err_t err; @@ -191,8 +213,7 @@ constexpr void Netsender::print_config() const } } -esp_err_t Netsender::register_input(char pin_name[NETSENDER_PIN_SIZE], - std::function()> read_func) +esp_err_t Netsender::register_input(char pin_name[NETSENDER_PIN_SIZE], pin_read_fn read_func) { if (input_cnt >= CONFIG_NETSENDER_MAX_PINS) { ESP_LOGE(TAG, "cannot register more than %d inputs", CONFIG_NETSENDER_MAX_PINS); @@ -211,8 +232,7 @@ esp_err_t Netsender::register_input(char pin_name[NETSENDER_PIN_SIZE], return ESP_OK; } -esp_err_t Netsender::register_binary_input(char pin_name[NETSENDER_PIN_SIZE], - std::function>()> read_binary_func) +esp_err_t Netsender::register_binary_input(char pin_name[NETSENDER_PIN_SIZE], pin_read_binary_fn read_binary_func) { if (input_cnt >= CONFIG_NETSENDER_MAX_PINS) { ESP_LOGE(TAG, "cannot register more than %d inputs", CONFIG_NETSENDER_MAX_PINS); @@ -231,7 +251,7 @@ esp_err_t Netsender::register_binary_input(char pin_name[NETSENDER_PIN_SIZE], return ESP_OK; } -esp_err_t Netsender::register_variable_parser(std::function parser_func) +esp_err_t Netsender::register_variable_parser(variable_parser_fn parser_func) { ESP_LOGD(TAG, "registering new variable callback"); @@ -420,49 +440,63 @@ esp_err_t Netsender::req_config() bool changed = false; - // Parse the incoming config. - std::string param; - std::string json_resp(resp_buf); - lock.unlock(); - if (netsender_extract_json(json_resp, "mp", param) && std::stoi(param) != this->config.monPeriod) { - this->config.monPeriod = std::stoi(param); - ESP_LOGI(TAG, "monPeriod changed: %d", this->config.monPeriod); - changed = true; + // Parse the incoming config from the fixed response buffer. A single + // buffer is large enough for every config field. + static_assert(NETSENDER_IO_SIZE >= NETSENDER_WIFI_SIZE, "config parameter buffer too small"); + char param[NETSENDER_IO_SIZE]; + int32_t number = 0; + + if (netsender_extract_json(resp_buf, "mp", param, sizeof(param)) && parse_int(param, number) && + number != this->config.monPeriod) { + if (number >= 0 && number <= SHRT_MAX) { + this->config.monPeriod = static_cast(number); + ESP_LOGI(TAG, "monPeriod changed: %d", this->config.monPeriod); + changed = true; + } else { + ESP_LOGW(TAG, "invalid monPeriod: %ld", static_cast(number)); + } } - if (netsender_extract_json(json_resp, "ap", param) && std::stoi(param) != this->config.actPeriod) { - this->config.actPeriod = std::stoi(param); - ESP_LOGI(TAG, "actPeriod changed: %d", this->config.actPeriod); - changed = true; + if (netsender_extract_json(resp_buf, "ap", param, sizeof(param)) && parse_int(param, number) && + number != this->config.actPeriod) { + if (number >= 0 && number <= SHRT_MAX) { + this->config.actPeriod = static_cast(number); + ESP_LOGI(TAG, "actPeriod changed: %d", this->config.actPeriod); + changed = true; + } else { + ESP_LOGW(TAG, "invalid actPeriod: %ld", static_cast(number)); + } } - if (netsender_extract_json(json_resp, "wi", param) && param != this->config.wifi) { - pad_copy(this->config.wifi, param.c_str(), NETSENDER_WIFI_SIZE); + if (netsender_extract_json(resp_buf, "wi", param, sizeof(param)) && strcmp(param, this->config.wifi) != 0) { + pad_copy(this->config.wifi, param, NETSENDER_WIFI_SIZE); ESP_LOGI(TAG, "wifi changed: %s", this->config.wifi); changed = true; } - if (netsender_extract_json(json_resp, "dk", param) && param != this->config.dkey) { - pad_copy(this->config.dkey, param.c_str(), CONFIG_NETSENDER_DKEY_SIZE); + if (netsender_extract_json(resp_buf, "dk", param, sizeof(param)) && strcmp(param, this->config.dkey) != 0) { + pad_copy(this->config.dkey, param, CONFIG_NETSENDER_DKEY_SIZE); ESP_LOGI(TAG, "dkey changed: %s", this->config.dkey); changed = true; } - if (netsender_extract_json(json_resp, "ip", param) && param != this->config.inputs) { - if (check_pins(param.c_str()) >= 0) { - pad_copy(this->config.inputs, param.c_str(), NETSENDER_IO_SIZE); + if (netsender_extract_json(resp_buf, "ip", param, sizeof(param)) && strcmp(param, this->config.inputs) != 0) { + if (check_pins(param) >= 0) { + pad_copy(this->config.inputs, param, NETSENDER_IO_SIZE); ESP_LOGI(TAG, "inputs changed: %s", this->config.inputs); changed = true; } else { - ESP_LOGW(TAG, "invalid inputs: %s", param.c_str()); + ESP_LOGW(TAG, "invalid inputs: %s", param); } } - if (netsender_extract_json(json_resp, "op", param) && param != this->config.outputs) { - if (check_pins(param.c_str()) >= 0) { - pad_copy(this->config.outputs, param.c_str(), NETSENDER_IO_SIZE); + if (netsender_extract_json(resp_buf, "op", param, sizeof(param)) && strcmp(param, this->config.outputs) != 0) { + if (check_pins(param) >= 0) { + pad_copy(this->config.outputs, param, NETSENDER_IO_SIZE); ESP_LOGI(TAG, "outputs changed: %s", this->config.outputs); changed = true; } else { - ESP_LOGW(TAG, "invalid outputs: %s", param.c_str()); + ESP_LOGW(TAG, "invalid outputs: %s", param); } } + lock.unlock(); + if (changed) { this->configured = true; write_nvs_config(); @@ -503,7 +537,7 @@ esp_err_t Netsender::req_poll() for (auto i = 0; i < input_cnt; i++) { auto &pin = inputs[i]; - if (pin.read != NULL) { + if (pin.read != nullptr) { // Read non-binary pins. pin.value = pin.read(); if (!pin.value.has_value()) { @@ -512,7 +546,7 @@ esp_err_t Netsender::req_poll() } ESP_LOGI(TAG, "read non-binary pin %s: %d", pin.name, pin.value.value()); pin.data = NULL; - } else if (pin.read_binary != NULL) { + } else if (pin.read_binary != nullptr) { // Read binary pins. auto out = pin.read_binary(); if (!out.has_value()) { @@ -617,24 +651,27 @@ esp_err_t Netsender::req_poll() return ESP_FAIL; } - // TODO: Handle response. + // Parse the response while the HTTP buffer is locked, then act on it + // after releasing the lock, since these actions may issue new requests. ESP_LOGI(TAG, "poll response: %s", this->resp_buf); - std::string rc; - const auto resp = std::string(resp_buf); + char rc[16]; + char vs[16]; + int32_t response_code = 0; + int32_t new_varsum = 0; + const auto has_rc = netsender_extract_json(resp_buf, "rc", rc, sizeof(rc)) && parse_int(rc, response_code); + const auto has_vs = netsender_extract_json(resp_buf, "vs", vs, sizeof(vs)) && parse_int(vs, new_varsum); lock.unlock(); - const auto has_rc = netsender_extract_json(resp, "rc", rc); + if (has_rc) { - ESP_LOGD(TAG, "got response code: %s", rc.c_str()); - if (handle_response_code(rc) != ESP_OK) { + ESP_LOGD(TAG, "got response code: %ld", static_cast(response_code)); + if (handle_response_code(response_code) != ESP_OK) { ESP_LOGE(TAG, "failed to handle response code"); } } - std::string vs; - const auto has_vs = netsender_extract_json(resp, "vs", vs); if (has_vs) { - ESP_LOGD(TAG, "got varsum: %s", vs.c_str()); - if (std::stoi(vs) != this->varsum) { + ESP_LOGD(TAG, "got varsum: %ld", static_cast(new_varsum)); + if (new_varsum != this->varsum) { ESP_LOGD(TAG, "varsum changed, getting vars"); req_vars(); } @@ -711,32 +748,38 @@ esp_err_t Netsender::req_vars() } ESP_LOGI(TAG, "vars response: %s", this->resp_buf); - std::string vs; - const auto resp = std::string(resp_buf); - // Release the lock on the HTTP client handle. + // Parse the varsum while the response buffer is locked. The callback is + // also called while locked so that it can read the response buffer safely. + char vs[16]; + int32_t new_varsum = 0; + const auto has_vs = netsender_extract_json(resp_buf, "vs", vs, sizeof(vs)) && parse_int(vs, new_varsum); + + if (this->parse_variable_callback == nullptr) { + lock.unlock(); + ESP_LOGE(TAG, "no variable parser registered"); + return ESP_FAIL; + } + + err = this->parse_variable_callback(this->resp_buf); lock.unlock(); - err = this->parse_variable_callback(resp); if (err != ESP_OK) { ESP_LOGE(TAG, "unable to parse variables in callback"); return ESP_FAIL; } - const auto has_vs = netsender_extract_json(resp, "vs", vs); if (has_vs) { - ESP_LOGD(TAG, "got varsum: %s", vs.c_str()); + ESP_LOGD(TAG, "got varsum: %ld", static_cast(new_varsum)); // Update varsum with new varsum. - this->varsum = std::stoi(vs); + this->varsum = new_varsum; } return ESP_OK; } -esp_err_t Netsender::handle_response_code(std::string code) +esp_err_t Netsender::handle_response_code(int32_t rc) { - const auto rc = std::stoi(code); - esp_err_t err; switch (rc) { case NETSENDER_RC_OK: @@ -764,7 +807,7 @@ esp_err_t Netsender::handle_response_code(std::string code) // TODO: implement test. break; default: - ESP_LOGE(TAG, "got unexpected response code:"); + ESP_LOGE(TAG, "got unexpected response code: %ld", static_cast(rc)); return ESP_FAIL; } @@ -773,54 +816,146 @@ esp_err_t Netsender::handle_response_code(std::string code) int64_t Netsender::uptime() const { return esp_timer_get_time() / 1000000; } -bool netsender_extract_json(const std::string &json, const char *name, std::string &value) +namespace { + +const char *skip_space(const char *p) { - size_t start = json.find(std::string("\"") + name + "\""); - if (start == std::string::npos) { - return false; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') { + p++; } + return p; +} - start += strlen(name) + 3; // Skip quotes and colon, then skip any whitespace. - while (start < json.length() && json[start] == ' ') { - start++; +// skip_json_string returns the position just after the closing quote of the +// string starting at p, or nullptr if the string is not terminated. +const char *skip_json_string(const char *p) +{ + p++; // Skip the opening quote. + while (*p != '\0') { + if (*p == '\\' && p[1] != '\0') { + p += 2; + continue; + } + if (*p == '"') { + return p + 1; + } + p++; } + return nullptr; +} - if (start >= json.length()) { - return false; +// copy_json_string decodes a quoted JSON string into out. Unsupported escapes +// are preserved verbatim. Returns false if the string is malformed or does not +// fit. +bool copy_json_string(const char *p, char *out, size_t out_len) +{ + p++; // Skip the opening quote. + size_t n = 0; + while (*p != '\0') { + if (*p == '"') { + out[n] = '\0'; + return true; + } + + char c = *p; + if (c == '\\') { + p++; + c = *p; + switch (c) { + case '"': + case '\\': + case '/': + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case '\0': + return false; + default: + // Preserve escapes we do not decode, such as \uXXXX. + if (n + 2 >= out_len) { + return false; + } + out[n++] = '\\'; + break; + } + } + + if (n + 1 >= out_len) { + return false; + } + out[n++] = c; + p++; } - size_t finish; - switch (json[start]) { - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - finish = json.find(',', start); - if (finish == std::string::npos) { - finish = json.find('}', start); + return false; +} + +// copy_json_number copies an unquoted JSON scalar into out. +bool copy_json_number(const char *p, char *out, size_t out_len) +{ + p = skip_space(p); + size_t n = 0; + while (*p != '\0' && *p != ',' && *p != '}' && *p != ']' && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') { + if (n + 1 >= out_len) { + return false; } - break; - case '"': - start++; // Move past the opening quote - finish = json.find('"', start); - break; - default: + out[n++] = *p++; + } + + out[n] = '\0'; + return n > 0; +} + +} // namespace + +bool netsender_extract_json(const char *json, const char *name, char *out, size_t out_len) +{ + if (json == nullptr || name == nullptr || out == nullptr || out_len == 0) { return false; } - if (finish == std::string::npos) { - finish = json.length(); + const auto name_len = strlen(name); + const char *p = json; + while ((p = strchr(p, '"')) != nullptr) { + const char *key_begin = p + 1; + const char *key_end = skip_json_string(p); + if (key_end == nullptr) { + return false; + } + + const char *after_key = skip_space(key_end); + if (*after_key != ':') { + // This was a string value, not a key. Continue searching. + p = key_end; + continue; + } + + const auto key_len = static_cast(key_end - key_begin - 1); + if (key_len == name_len && memcmp(key_begin, name, key_len) == 0) { + const char *value = skip_space(after_key + 1); + if (*value == '"') { + return copy_json_string(value, out, out_len); + } + return copy_json_number(value, out, out_len); + } + + p = key_end; } - value = json.substr(start, finish - start); - return true; + return false; } void pad_copy(char *dst, const char *src, size_t size) diff --git a/esp/speaker-netsender/components/netsender/tools/gen_vars.py b/esp/speaker-netsender/components/netsender/tools/gen_vars.py index de9e827..a311bd6 100644 --- a/esp/speaker-netsender/components/netsender/tools/gen_vars.py +++ b/esp/speaker-netsender/components/netsender/tools/gen_vars.py @@ -80,7 +80,7 @@ class VarType: with open(output_path, "w") as f: # Add includes. f.write( - '#pragma once\n#include \n#include \n#include \n#include \n#include \n\n#include "esp_err.h"\n#include "esp_log.h"\n\n' + '#pragma once\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\n#include "esp_err.h"\n#include "esp_log.h"\n\n' ) f.write(f"// Generated for {client_name} {latest_version}\n\n") @@ -125,9 +125,16 @@ class VarType: f.write(f" {c_type} {var['name']};\n") f.write("};\n\n") - # Add a helper function to parse the variables into the struct. + # Add helpers to parse the variables into the struct without allocating. f.write( - "inline void update_state_member(device_var_state_t& state, const std::string& var_id, const std::string& val) {" + "\ninline bool parse_int_var(std::string_view val, int32_t& out) {\n" + " const auto* begin = val.data();\n" + " const auto* end = begin + val.size();\n" + " auto result = std::from_chars(begin, end, out);\n" + " return result.ec == std::errc{} && result.ptr == end;\n" + "}\n\n" + "inline void update_state_member(device_var_state_t& state, std::string_view var_id, std::string_view val) {\n" + " int32_t number = 0;\n" ) first = True @@ -138,19 +145,20 @@ class VarType: f.write(f"{prefix} (var_id == var::VAR_ID_{var_name_upper}) {{\n") if var["type"] == VarType.BYTE: - f.write(f"state.{var['name']} = static_cast(std::stoi(val));\n") + f.write("if (parse_int_var(val, number)) {\n") + f.write(f" state.{var['name']} = static_cast(number);\n") + f.write("}\n") elif var["type"] == VarType.STRING: - # Using strncpy and ensuring the last byte is always null for safety + # Copy at most the destination size and always null terminate. f.write( - f"strncpy(state.{var['name']}, val.c_str(), sizeof(state.{var['name']}) - 1);\n" - ) - f.write( - f"state.{var['name']}[sizeof(state.{var['name']}) - 1] = '\\0';\n" + f"const auto len = std::min(val.size(), sizeof(state.{var['name']}) - 1);\n" ) + f.write(f"memcpy(state.{var['name']}, val.data(), len);\n") + f.write(f"state.{var['name']}[len] = '\\0';\n") elif var["type"] == VarType.INT32: - f.write( - f"state.{var['name']} = static_cast(std::stoi(val));\n" - ) + f.write("if (parse_int_var(val, number)) {\n") + f.write(f" state.{var['name']} = number;\n") + f.write("}\n") first = False if not first: @@ -160,9 +168,9 @@ class VarType: # Generate the automated write function f.write( - "\ninline esp_err_t write_vars_to_file(const device_var_state_t &state, const std::string& file_path) {\n" + "\ninline esp_err_t write_vars_to_file(const device_var_state_t &state, const char* file_path) {\n" ) - f.write(' FILE* fd = fopen(file_path.c_str(), "w");\n') + f.write(' FILE* fd = fopen(file_path, "w");\n') f.write(" if (fd == NULL) return ESP_FAIL;\n\n") for var in vars: @@ -187,12 +195,12 @@ class VarType: # Write the read function. f.write(""" - inline std::optional read_vars_from_file(const std::string& file_path) + inline std::optional read_vars_from_file(const char* file_path) { device_var_state_t vars = {}; ESP_LOGI("NETSENDER_VARS", "opening file"); - FILE* fd = fopen(file_path.c_str(), "r"); + FILE* fd = fopen(file_path, "r"); if (fd == NULL) { return std::nullopt; } diff --git a/esp/speaker-netsender/main/include/log.hpp b/esp/speaker-netsender/main/include/log.hpp index 9e4b927..2d322bc 100644 --- a/esp/speaker-netsender/main/include/log.hpp +++ b/esp/speaker-netsender/main/include/log.hpp @@ -27,8 +27,11 @@ #pragma once #include -#include #include +#include + +// Reads the latest unread log data, or returns nullopt on error. +using log_read_fn = std::optional> (*)(); /** * @brief initialise logging. @@ -38,4 +41,4 @@ * Initialises multi-output logging. This setup logs to file and via UDP. * */ -std::function>()> init_logging(); +log_read_fn init_logging(); diff --git a/esp/speaker-netsender/main/include/netsender_vars.hpp b/esp/speaker-netsender/main/include/netsender_vars.hpp index 0d0a405..37a79b4 100644 --- a/esp/speaker-netsender/main/include/netsender_vars.hpp +++ b/esp/speaker-netsender/main/include/netsender_vars.hpp @@ -1,9 +1,11 @@ #pragma once +#include #include +#include #include #include #include -#include +#include #include "esp_err.h" #include "esp_log.h" @@ -42,21 +44,35 @@ struct device_var_state_t { char FilePath[MAX_STR_VAR_LEN]; }; -inline void update_state_member(device_var_state_t &state, const std::string &var_id, const std::string &val) +inline bool parse_int_var(std::string_view val, int32_t &out) { + const auto *begin = val.data(); + const auto *end = begin + val.size(); + auto result = std::from_chars(begin, end, out); + return result.ec == std::errc{} && result.ptr == end; +} + +inline void update_state_member(device_var_state_t &state, std::string_view var_id, std::string_view val) +{ + int32_t number = 0; if (var_id == var::VAR_ID_VS) { - state.vs = static_cast(std::stoi(val)); + if (parse_int_var(val, number)) { + state.vs = number; + } } else if (var_id == var::VAR_ID_VOLUME) { - state.Volume = static_cast(std::stoi(val)); + if (parse_int_var(val, number)) { + state.Volume = static_cast(number); + } } else if (var_id == var::VAR_ID_FILEPATH) { - strncpy(state.FilePath, val.c_str(), sizeof(state.FilePath) - 1); - state.FilePath[sizeof(state.FilePath) - 1] = '\0'; + const auto len = std::min(val.size(), sizeof(state.FilePath) - 1); + memcpy(state.FilePath, val.data(), len); + state.FilePath[len] = '\0'; } } -inline esp_err_t write_vars_to_file(const device_var_state_t &state, const std::string &file_path) +inline esp_err_t write_vars_to_file(const device_var_state_t &state, const char *file_path) { - FILE *fd = fopen(file_path.c_str(), "w"); + FILE *fd = fopen(file_path, "w"); if (fd == NULL) { return ESP_FAIL; } @@ -78,12 +94,12 @@ inline esp_err_t write_vars_to_file(const device_var_state_t &state, const std:: return ESP_OK; } -inline std::optional read_vars_from_file(const std::string &file_path) +inline std::optional read_vars_from_file(const char *file_path) { device_var_state_t vars = {}; ESP_LOGI("NETSENDER_VARS", "opening file"); - FILE *fd = fopen(file_path.c_str(), "r"); + FILE *fd = fopen(file_path, "r"); if (fd == NULL) { return std::nullopt; } diff --git a/esp/speaker-netsender/main/log.cpp b/esp/speaker-netsender/main/log.cpp index 9cdded9..3269539 100644 --- a/esp/speaker-netsender/main/log.cpp +++ b/esp/speaker-netsender/main/log.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -112,13 +111,37 @@ esp_err_t init_log_queue() return ESP_OK; } -std::function>()> init_logging() +static std::optional> read_logs() +{ + auto logs_fd = p->get_logs(); + + if (::lseek(logs_fd, 0, SEEK_SET) == -1) { + ESP_LOGE(TAG, "unable to seek logs: %s (%d)", strerror(errno), errno); + ::close(logs_fd); + return std::nullopt; + } + + std::vector buffer(1024); + + // Read logs from stream into buffer + auto bytes_read = ::read(logs_fd, buffer.data(), buffer.size()); + if (bytes_read == -1) { + ESP_LOGE(TAG, "unable to read logs into buffer: %s (%d)", strerror(errno), errno); + return std::nullopt; + } + + buffer.resize(static_cast(bytes_read)); + + return buffer; +} + +log_read_fn init_logging() { // Initialise queue. auto err = init_log_queue(); if (err != ESP_OK) { ESP_LOGE(TAG, "unable to init log queue: %s", esp_err_to_name(err)); - return NULL; + return nullptr; } // Initialise UDP logging. @@ -127,33 +150,12 @@ std::function>()> init_logging() // Start logging task. if (xTaskCreatePinnedToCore(log_processor_task, "logging_task", 4096, NULL, 5, NULL, 1) != pdPASS) { ESP_LOGE(TAG, "log task creation failed"); - return NULL; + return nullptr; } p = std::make_unique("/sdcard/logs"); default_vprintf = esp_log_set_vprintf(log_vprintf); - return []() -> std::optional> { - auto logs_fd = p->get_logs(); - - if (::lseek(logs_fd, 0, SEEK_SET) == -1) { - ESP_LOGE(TAG, "unable to seek logs: %s (%d)", strerror(errno), errno); - ::close(logs_fd); - return std::nullopt; - } - - std::vector buffer(1024); - - // Read logs from stream into buffer - auto bytes_read = ::read(logs_fd, buffer.data(), buffer.size()); - if (bytes_read == -1) { - ESP_LOGE(TAG, "unable to read logs into buffer: %s (%d)", strerror(errno), errno); - return std::nullopt; - } - - buffer.resize(static_cast(bytes_read)); - - return buffer; - }; + return read_logs; } diff --git a/esp/speaker-netsender/main/main.cpp b/esp/speaker-netsender/main/main.cpp index 102e25f..f2183c0 100644 --- a/esp/speaker-netsender/main/main.cpp +++ b/esp/speaker-netsender/main/main.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include "esp_err.h" #include "esp_log.h" @@ -74,34 +73,33 @@ static TaskHandle_t player_handle; // Callback function to be registered with NetSender to parse vars response. // TODO: Consider if this should also be an auto-generated function. -esp_err_t parse_vars(std::string var_resp) +esp_err_t parse_vars(const char *var_resp) { ESP_LOGD(TAG, "parsing variables in callback"); // Get the ID in the response. - std::string id; - auto has_id = netsender_extract_json(var_resp, "id", id); + constexpr const auto MAX_ID_LEN = 64; + char id[MAX_ID_LEN]; + auto has_id = netsender_extract_json(var_resp, "id", id, sizeof(id)); if (!has_id) { ESP_LOGE(TAG, "unable to get ID from var response"); return ESP_FAIL; } // Parse the registered variables into the vars struct. - std::string val; - std::string var_name; - bool has_val; + char val[netsender::MAX_STR_VAR_LEN]; + char var_name[MAX_ID_LEN + 1 + netsender::MAX_STR_VAR_LEN]; for (auto i = 0; i < netsender::VAR_COUNT; i++) { if (strcmp(netsender::VARIABLES[i], netsender::var::VAR_ID_VS) == 0) { // Varsum (vs) is not prepended with id scope. - var_name = netsender::VARIABLES[i]; + snprintf(var_name, sizeof(var_name), "%s", netsender::VARIABLES[i]); } else { - var_name = id + "." + netsender::VARIABLES[i]; + snprintf(var_name, sizeof(var_name), "%s.%s", id, netsender::VARIABLES[i]); } - ESP_LOGI(TAG, "looking for variable: %s", var_name.c_str()); - has_val = netsender_extract_json(var_resp, var_name.c_str(), val); - if (has_val) { + ESP_LOGI(TAG, "looking for variable: %s", var_name); + if (netsender_extract_json(var_resp, var_name, val, sizeof(val))) { netsender::update_state_member(vars, netsender::VARIABLES[i], val); - ESP_LOGI(TAG, "got variable: %s=%s", var_name.c_str(), val.c_str()); + ESP_LOGI(TAG, "got variable: %s=%s", var_name, val); } } @@ -192,7 +190,7 @@ void app_main(void) } else { ESP_LOGE(TAG, "couldn't load new file, continuing with old file"); } - strncpy(cur_audio_file, vars.FilePath, sizeof(cur_audio_file)); + strlcpy(cur_audio_file, vars.FilePath, sizeof(cur_audio_file)); amp.pause(); }