diff --git a/.github/workflows/build_release_bins.yml b/.github/workflows/build_release_bins.yml new file mode 100644 index 0000000..d128850 --- /dev/null +++ b/.github/workflows/build_release_bins.yml @@ -0,0 +1,84 @@ +name: Build and Release the OpenIris bin files +on: + push: + branches: + - "mdns-autodiscovery" + - "master" + pull_request: + branches: + - "master" + workflow_dispatch: +jobs: + build: + strategy: + fail-fast: false + matrix: + target_name: [ + esp32AITinker, + esp32M5Stack, + esp32CAM, + esp_eye, + wrover, + ] + target_build_type: [ + "", + _release, + _OTA, + ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Cache pip + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: ${{ runner.os }}-pip- + - name: Cache PlatformIO + uses: actions/cache@v3 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + - name: Setup Python install + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install PlatformIO with Pip + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + - name: Build OpenIris Firmware + working-directory: ./ESP + run: | + pio run --environment ${{ matrix.target_name }}${{ matrix.target_build_type }} + - name: Archive Version File + uses: actions/upload-artifact@v3 + with: + name: version + path: ./ESP/tools/version.txt + - name: Archive Firmware name File + # create an environment variable with the name of the firmware file by catting the firmware_name.txt file + run: | + echo "FIRMWARE_NAME=$(cat ./ESP/tools/firmware_name.txt)" >> $GITHUB_ENV + - name: Archive Firmware Files + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.target_name }}${{ matrix.target_build_type }}-firmware + path: "./ESP/.pio/build/${{ matrix.target_name }}${{ matrix.target_build_type }}/${{ env.FIRMWARE_NAME }}.bin" + if-no-files-found: error + release-openiris: + runs-on: ubuntu-latest + needs: [build] + steps: + - name: Download Firmware Files + uses: actions/download-artifact@v2 + with: + path: release + - name: Release Firmware + uses: ncipollo/release-action@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + artifacts: release/*/*.bin + generateReleaseNotes: true + allowUpdates: true + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/ESP/.clang-format b/ESP/.clang-format index b173e30..e42cf92 100644 --- a/ESP/.clang-format +++ b/ESP/.clang-format @@ -1,5 +1,6 @@ --- language: Cpp BasedOnStyle: Google +ColumnLimit: 120 IndentWidth: 4 --- diff --git a/ESP/.gitignore b/ESP/.gitignore index 31f4c8f..14fd513 100644 --- a/ESP/.gitignore +++ b/ESP/.gitignore @@ -2,3 +2,4 @@ .vscode/ build/ *.log +/tools/firmware_name.txt diff --git a/ESP/lib/src/data/config/project_config.cpp b/ESP/lib/src/data/config/project_config.cpp index 2a94a7d..b005e53 100644 --- a/ESP/lib/src/data/config/project_config.cpp +++ b/ESP/lib/src/data/config/project_config.cpp @@ -42,7 +42,7 @@ void ProjectConfig::initConfig() } this->config.mdns = { _mdnsName, - "", + "openiristracker", }; log_i("MDNS name: %s", _mdnsName.c_str()); @@ -223,11 +223,16 @@ void ProjectConfig::load() //! DeviceConfig //* //********************************************************************************************************************** -void ProjectConfig::setDeviceConfig(const std::string &OTAPassword, int *OTAPort, bool shouldNotify) +void ProjectConfig::setDeviceConfig(const std::string &OTAPassword, int OTAPort, const std::string &binaryName, bool shouldNotify) { log_d("Updating device config"); this->config.device.OTAPassword.assign(OTAPassword); - this->config.device.OTAPort = *OTAPort; + this->config.device.OTAPort = OTAPort; + // check if binary name is empty + if (binaryName.empty()) + log_w("Binary name is empty, using previous value"); + else + this->config.device.binaryName.assign(binaryName); if (shouldNotify) this->notify(ObserverEvent::deviceConfigUpdated); diff --git a/ESP/lib/src/data/config/project_config.hpp b/ESP/lib/src/data/config/project_config.hpp index 388f5b0..af44454 100644 --- a/ESP/lib/src/data/config/project_config.hpp +++ b/ESP/lib/src/data/config/project_config.hpp @@ -29,6 +29,7 @@ public: { std::string OTAPassword; int OTAPort; + std::string binaryName; std::string toRepresentation(); }; @@ -106,7 +107,7 @@ public: MDNSConfig_t *getMDNSConfig(); WiFiTxPower_t *getWiFiTxPowerConfig(); - void setDeviceConfig(const std::string &OTAPassword, int *OTAPort, bool shouldNotify); + void setDeviceConfig(const std::string &OTAPassword, int OTAPort, const std::string &binaryName, bool shouldNotify); void setMDNSConfig(const std::string &hostname, const std::string &service, bool shouldNotify); void setCameraConfig(uint8_t *vflip, uint8_t *framesize, uint8_t *href, uint8_t *quality, uint8_t *brightness, bool shouldNotify); void setWifiConfig(const std::string &networkName, const std::string &ssid, const std::string &password, uint8_t *channel, uint8_t *power, bool adhoc, bool shouldNotify); diff --git a/ESP/lib/src/io/SerialManager/serialmanager.cpp b/ESP/lib/src/io/SerialManager/serialmanager.cpp index bc8a670..a501e6d 100644 --- a/ESP/lib/src/io/SerialManager/serialmanager.cpp +++ b/ESP/lib/src/io/SerialManager/serialmanager.cpp @@ -1,4 +1,4 @@ -#include "serialmanager.hpp" +/* #include "serialmanager.hpp" std::unordered_map SerialManager::command_map = { {"", NO_INPUT}, @@ -80,4 +80,4 @@ void SerialManager::handleSerial() const char *wifi_config_password = jsonDoc["wifi_config_password"]; const char *wifi_config_channel = jsonDoc["wifi_config_channel"]; } -} \ No newline at end of file +} */ \ No newline at end of file diff --git a/ESP/lib/src/io/camera/cameraHandler.cpp b/ESP/lib/src/io/camera/cameraHandler.cpp index 136aae6..a81d243 100644 --- a/ESP/lib/src/io/camera/cameraHandler.cpp +++ b/ESP/lib/src/io/camera/cameraHandler.cpp @@ -50,7 +50,7 @@ void CameraHandler::setupCameraPinout() config.pin_sccb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; - config.xclk_freq_hz = 16500000; // 10000000 stable, + config.xclk_freq_hz = 20000000; // 10000000 stable, // 16500000 optimal, // 20000000 max fps } @@ -64,8 +64,13 @@ void CameraHandler::setupBasicResolution() { log_e("Did not find psram, setting lower image quality"); config.fb_location = CAMERA_FB_IN_DRAM; +<<<<<<< HEAD config.jpeg_quality = 9; config.fb_count = 2; +======= + config.jpeg_quality = 1; + config.fb_count = 1; +>>>>>>> 5e4e1a971c57061f1d3d896b518c2e2828e22500 return; } @@ -191,4 +196,4 @@ void CameraHandler::resetCamera(bool type) Network_Utilities::my_delay(0.05); setupCamera(); } -} \ No newline at end of file +} diff --git a/ESP/lib/src/network/OTA/Github_OTA/GithubOTAHandler.cpp b/ESP/lib/src/network/OTA/Github_OTA/GithubOTAHandler.cpp new file mode 100644 index 0000000..7135f4a --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/GithubOTAHandler.cpp @@ -0,0 +1,109 @@ +#include "GithubOTAHandler.hpp" + +GithubOTAHandler::GithubOTAHandler() {} + +GithubOTAHandler::~GithubOTAHandler() {} + +void GithubOTAHandler::githubOTAEventCallback(void *handler_args, esp_event_base_t base, int32_t id, void *event_data) +{ + Github_OTA_client_handle_t *client = (Github_OTA_client_handle_t *)handler_args; + ESP_LOGI(TAG, "Got Update Callback: %s", Github_OTA_get_event_str((Github_OTA_event_e)id)); + + if (id == Github_OTA_EVENT_FIRMWARE_UPDATE_PROGRESS) + { + /* display some progress with the firmware update */ + ESP_LOGI(TAG, "Firmware Update Progress: %d%%", *((int *)event_data)); + } + (void)client; + return; +} + +void GithubOTAHandler::updateFirmware(const std::string &binaryName) +{ + ESP_LOGI(TAG, "Starting Github OTA Update Handler"); + + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + // initialize our Github_OTA config + Github_OTA_config_t ghconfig = { + .filenamematch = (char *)binaryName.c_str(), + /* 1 minute as a example, but in production you should pick something larger (remember, Github has ratelimits on the API! )*/ + .updateInterval = 30, + }; + // initialize Github_OTA + Github_OTA_client_handle_t *Github_OTA_client = Github_OTA_init(&ghconfig); + if (Github_OTA_client == NULL) + { + ESP_LOGE(TAG, "Github_OTA_client_init failed"); + return; + } + // register for events relating to the update progress + esp_event_handler_register(Github_OTA_EVENTS, ESP_EVENT_ANY_ID, &githubOTAEventCallback, Github_OTA_client); + +#if DO_BACKGROUND_UPDATE + /* for private repositories or to get more API calls than anonymous, set a github username and PAT token + * see https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token + * for more information on how to create a PAT token. + * + * Be carefull, as the PAT token will be stored in your firmware etc and can be used to access your github account. + */ + // ESP_ERROR_CHECK(Github_OTA_set_auth(Github_OTA_client, "", "")); + + // start a timer that will automatically check for updates based on the interval specified above + ESP_ERROR_CHECK(Github_OTA_start_update_timer(Github_OTA_client)); + +#elif DO_FORGROUND_UPDATE + /* or do a check/update now + * This runs in a new task under freeRTOS, so you can do other things while it is running. + */ + ESP_ERROR_CHECK(Github_OTA_start_update_task(Github_OTA_client)); + +#elif DO_MANUAL_CHECK_UPDATE + /* Alternatively you can do manual checks + * but note, you probably have to increase the Stack size for the task this runs on + */ + + /* Query the Github Release API for the latest release */ + ESP_ERROR_CHECK(Github_OTA_check(Github_OTA_client)); + + /* get the semver version of the currently running firmware */ + semver_t *cur = Github_OTA_get_current_version(Github_OTA_client); + if (cur) + { + ESP_LOGI(TAG, "Current version: %d.%d.%d", cur->major, cur->minor, cur->patch); + semver_free(cur); + + /* get the version of the latest release on Github */ + semver_t *new = Github_OTA_get_latest_version(Github_OTA_client); + if (new) + { + ESP_LOGI(TAG, "New version: %d.%d.%d", new->major, new->minor, new->patch); + semver_free(new); + } + + /* do some comparisions */ + if (semver_gt(new, cur) == 1) + { + ESP_LOGI(TAG, "New version is greater than current version"); + } + else if (semver_eq(new, cur) == 1) + { + ESP_LOGI(TAG, "New version is equal to current version"); + } + else + { + ESP_LOGI(TAG, "New version is less than current version"); + } + + /* assuming we have a new version, then do a actual update */ + ESP_ERROR_CHECK(Github_OTA_update(Github_OTA_client)); + /* if there was a new version installed, the esp will reboot after installation and will not reach this code */ + +#endif +} diff --git a/ESP/lib/src/network/OTA/Github_OTA/GithubOTAHandler.hpp b/ESP/lib/src/network/OTA/Github_OTA/GithubOTAHandler.hpp new file mode 100644 index 0000000..f406ac5 --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/GithubOTAHandler.hpp @@ -0,0 +1,30 @@ +#ifndef GITHUB_OTA_HANDLER_HPP +#define GITHUB_OTA_HANDLER_HPP +// #include +#include +#include +#include "nvs_flash.h" + +// Custom Libraries +#include "lib/esp_ota.h" + +//C++ Libraries +#include + +// ESP && FreeRTOS Libraries +#define DO_BACKGROUND_UPDATE 1 +#define DO_FOREGROUND_UPDATE 0 +#define DO_MANUAL_CHECK_UPDATE 0 + +class GithubOTAHandler +{ +private: + +public: + GithubOTAHandler(); + ~GithubOTAHandler(); + void updateFirmware(const std::string &binaryName); + static void githubOTAEventCallback(void *handler_args, esp_event_base_t base, int32_t id, void *event_data); +}; + +#endif // GITHUB_OTA_HANDLER_HPP diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson.c b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson.c new file mode 100644 index 0000000..e767525 --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson.c @@ -0,0 +1,829 @@ +#include +#include "lwjson.h" + +/** + * \brief Internal string object + */ +typedef struct +{ + const char *start; /*!< Original pointer to beginning of JSON object */ + size_t len; /*!< Total length of input json string */ + const char *p; /*!< Current char pointer */ +} lwjson_int_str_t; + +/** + * \brief Allocate new token for JSON block + * \param[in] lw: LwJSON instance + * \return Pointer to new token + */ +static lwjson_token_t * +prv_alloc_token(lwjson_t *lw) +{ + if (lw->next_free_token_pos < lw->tokens_len) + { + memset(&lw->tokens[lw->next_free_token_pos], 0x00, sizeof(*lw->tokens)); + return &lw->tokens[lw->next_free_token_pos++]; + } + return NULL; +} + +/** + * \brief Skip all characters that are considered *blank* as per RFC4627 + * \param[in,out] pobj: Pointer to text that is modified on success + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +static lwjsonr_t +prv_skip_blank(lwjson_int_str_t *pobj) +{ + while (pobj->p != NULL && *pobj->p != '\0' && (size_t)(pobj->p - pobj->start) < pobj->len) + { + if (*pobj->p == ' ' || *pobj->p == '\t' || *pobj->p == '\r' || *pobj->p == '\n' || *pobj->p == '\f') + { + ++pobj->p; +#if LWJSON_CFG_COMMENTS + /* Check for comments and remove them */ + } + else if (*pobj->p == '/') + { + ++pobj->p; + if (pobj->p != NULL && *pobj->p == '*') + { + ++pobj->p; + while (pobj->p != NULL && *pobj->p != '\0' && (size_t)(pobj->p - pobj->start) < pobj->len) + { + if (*pobj->p == '*') + { + ++pobj->p; + if (*pobj->p == '/') + { + ++pobj->p; + break; + } + } + ++pobj->p; + } + } +#endif /* LWJSON_CFG_COMMENTS */ + } + else + { + break; + } + } + if (pobj->p != NULL && *pobj->p != '\0' && (size_t)(pobj->p - pobj->start) < pobj->len) + { + return lwjsonOK; + } + return lwjsonERRJSON; +} + +/** + * \brief Parse JSON string that must start end end with double quotes `"` character + * It just parses length of characters and does not perform any decode operation + * \param[in,out] pobj: Pointer to text that is modified on success + * \param[out] pout: Pointer to pointer to string that is set where string starts + * \param[out] poutlen: Length of string in units of characters is stored here + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +static lwjsonr_t +prv_parse_string(lwjson_int_str_t *pobj, const char **pout, size_t *poutlen) +{ + lwjsonr_t res; + size_t len = 0; + + if ((res = prv_skip_blank(pobj)) != lwjsonOK) + { + return res; + } + if (*pobj->p++ != '"') + { + return lwjsonERRJSON; + } + *pout = pobj->p; + /* Parse string but take care of escape characters */ + for (;; ++pobj->p, ++len) + { + if (pobj->p == NULL || *pobj->p == '\0' || (size_t)(pobj->p - pobj->start) >= pobj->len) + { + return lwjsonERRJSON; + } + /* Check special characters */ + if (*pobj->p == '\\') + { + ++pobj->p; + ++len; + switch (*pobj->p) + { + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + break; + case 'u': + ++pobj->p; + for (size_t i = 0; i < 4; ++i, ++len) + { + if (!((*pobj->p >= '0' && *pobj->p <= '9') || (*pobj->p >= 'a' && *pobj->p <= 'f') || (*pobj->p >= 'A' && *pobj->p <= 'F'))) + { + return lwjsonERRJSON; + } + if (i < 3) + { + ++pobj->p; + } + } + break; + default: + return lwjsonERRJSON; + } + } + else if (*pobj->p == '"') + { + ++pobj->p; + break; + } + } + *poutlen = len; + return res; +} + +/** + * \brief Parse property name that must comply with JSON string format as in RFC4627 + * Property string must be followed by colon character ":" + * \param[in,out] pobj: Pointer to text that is modified on success + * \param[out] t: Token instance to write property name to + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +static lwjsonr_t +prv_parse_property_name(lwjson_int_str_t *pobj, lwjson_token_t *t) +{ + lwjsonr_t res; + + /* Parse property string first */ + if ((res = prv_parse_string(pobj, &t->token_name, &t->token_name_len)) != lwjsonOK) + { + return res; + } + /* Skip any spaces */ + if ((res = prv_skip_blank(pobj)) != lwjsonOK) + { + return res; + } + /* Must continue with colon */ + if (*pobj->p++ != ':') + { + return lwjsonERRJSON; + } + /* Skip any spaces */ + if ((res = prv_skip_blank(pobj)) != lwjsonOK) + { + return res; + } + return lwjsonOK; +} + +/** + * \brief Parse number as described in RFC4627 + * \param[in,out] pobj: Pointer to text that is modified on success + * \param[out] tout: Pointer to output number format + * \param[out] fout: Pointer to output real-type variable. Used if type is REAL. + * \param[out] iout: Pointer to output int-type variable. Used if type is INT. + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +static lwjsonr_t +prv_parse_number(lwjson_int_str_t *pobj, lwjson_type_t *tout, lwjson_real_t *fout, lwjson_int_t *iout) +{ + lwjsonr_t res; + uint8_t is_minus; + lwjson_real_t num; + lwjson_type_t type = LWJSON_TYPE_NUM_INT; + + if ((res = prv_skip_blank(pobj)) != lwjsonOK) + { + return res; + } + if (*pobj->p == '\0' || (size_t)(pobj->p - pobj->start) >= pobj->len) + { + return lwjsonERRJSON; + } + is_minus = *pobj->p == '-' ? (++pobj->p, 1) : 0; + if (*pobj->p == '\0' /* Invalid string */ + || *pobj->p < '0' || *pobj->p > '9' /* Character outside number range */ + || (*pobj->p == '0' && (pobj->p[1] < '0' && pobj->p[1] > '9'))) + { /* Number starts with 0 but not followed by dot */ + return lwjsonERRJSON; + } + + /* Parse number */ + for (num = 0; *pobj->p >= '0' && *pobj->p <= '9'; ++pobj->p) + { + num = num * 10 + (*pobj->p - '0'); + } + if (pobj->p != NULL && *pobj->p == '.') + { /* Number has exponent */ + lwjson_real_t exp, dec_num; + + type = LWJSON_TYPE_NUM_REAL; /* Format is real */ + ++pobj->p; /* Ignore comma character */ + if (*pobj->p < '0' || *pobj->p > '9') + { /* Must be followed by number characters */ + return lwjsonERRJSON; + } + /* Get number after decimal point */ + for (exp = 1, dec_num = 0; *pobj->p >= '0' && *pobj->p <= '9'; ++pobj->p, exp *= 10) + { + dec_num = dec_num * 10 + (*pobj->p - '0'); + } + num += dec_num / exp; /* Add decimal part to number */ + } + if (pobj->p != NULL && (*pobj->p == 'e' || *pobj->p == 'E')) + { /* Engineering mode */ + uint8_t is_minus_exp; + int exp_cnt; + + type = LWJSON_TYPE_NUM_REAL; /* Format is real */ + ++pobj->p; /* Ignore enginnering sing part */ + is_minus_exp = *pobj->p == '-' ? (++pobj->p, 1) : 0; /* Check if negative */ + if (*pobj->p == '+') + { /* Optional '+' is possible too */ + ++pobj->p; + } + if (*pobj->p < '0' || *pobj->p > '9') + { /* Must be followed by number characters */ + return lwjsonERRJSON; + } + + /* Parse exponent number */ + for (exp_cnt = 0; *pobj->p >= '0' && *pobj->p <= '9'; ++pobj->p) + { + exp_cnt = exp_cnt * 10 + (*pobj->p - '0'); + } + /* Calculate new value for exponent 10^exponent */ + if (is_minus_exp) + { + for (; exp_cnt > 0; num /= 10, --exp_cnt) + { + } + } + else + { + for (; exp_cnt > 0; num *= 10, --exp_cnt) + { + } + } + } + if (is_minus) + { + num = -num; + } + + /* Write output values */ + if (tout != NULL) + { + *tout = type; + } + if (type == LWJSON_TYPE_NUM_INT) + { + *iout = (lwjson_int_t)num; + } + else + { + *fout = num; + } + return lwjsonOK; +} + +/** + * \brief Create path segment from input path for search operation + * \param[in,out] p: Pointer to pointer to input path. Pointer is modified + * \param[out] opath: Pointer to pointer to write path segment + * \param[out] olen: Pointer to variable to write length of segment + * \param[out] is_last: Pointer to write if this is last segment + * \return `1` on success, `0` otherwise + */ +static uint8_t +prv_create_path_segment(const char **p, const char **opath, size_t *olen, uint8_t *is_last) +{ + const char *s = *p; + + *is_last = 0; + *opath = NULL; + *olen = 0; + + /* Check input path */ + if (s == NULL || *s == '\0') + { + *is_last = 1; + return 0; + } + + /* + * Path must be one of: + * - literal text + * - "#" followed by dot "." + */ + if (*s == '#') + { + *opath = s; + for (*olen = 0;; ++s, ++(*olen)) + { + if (*s == '.') + { + ++s; + break; + } + else if (*s == '\0') + { + if (*olen == 1) + { + return 0; + } + else + { + break; + } + } + } + *p = s; + } + else + { + *opath = s; + for (*olen = 0; *s != '\0' && *s != '.'; ++(*olen), ++s) + { + } + *p = s + 1; + } + if (*s == '\0') + { + *is_last = 1; + } + return 1; +} + +/** + * \brief Input recursive function for find operation + * \param[in] parent: Parent token of type \ref LWJSON_TYPE_ARRAY or LWJSON_TYPE_OBJECT + * \param[in] path: Path to search for starting this token further + * \return Found token on success, `NULL` otherwise + */ +static const lwjson_token_t * +prv_find(const lwjson_token_t *parent, const char *path) +{ + const char *segment; + size_t segment_len; + uint8_t is_last, result; + + /* Get path segments */ + if ((result = prv_create_path_segment(&path, &segment, &segment_len, &is_last)) != 0) + { + /* Check if detected an array request */ + if (*segment == '#') + { + /* Parent must be array */ + if (parent->type != LWJSON_TYPE_ARRAY) + { + return NULL; + } + + /* Check if index requested */ + if (segment_len > 1) + { + const lwjson_token_t *t; + size_t index = 0; + + /* Parse number */ + for (size_t i = 1; i < segment_len; ++i) + { + if (segment[i] < '0' || segment[i] > '9') + { + return NULL; + } + else + { + index = index * 10 + (segment[i] - '0'); + } + } + + /* Start from beginning */ + for (t = parent->u.first_child; t != NULL && index > 0; t = t->next, --index) + { + } + if (t != NULL) + { + if (is_last) + { + return t; + } + else + { + return prv_find(t, path); + } + } + return NULL; + } + + /* Scan all indexes and get first match */ + for (const lwjson_token_t *tmp_t, *t = parent->u.first_child; t != NULL; t = t->next) + { + if ((tmp_t = prv_find(t, path)) != NULL) + { + return tmp_t; + } + } + } + else + { + if (parent->type != LWJSON_TYPE_OBJECT) + { + return NULL; + } + for (const lwjson_token_t *t = parent->u.first_child; t != NULL; t = t->next) + { + if (t->token_name_len == segment_len && !strncmp(t->token_name, segment, segment_len)) + { + const lwjson_token_t *tmp_t; + if (is_last) + { + return t; + } + if ((tmp_t = prv_find(t, path)) != NULL) + { + return tmp_t; + } + } + } + } + } + return NULL; +} + +/** + * \brief Check for character after opening bracket of array or object + * \param[in,out] pobj: JSON string + * \param[in] t: Token to check for type + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +static inline lwjsonr_t +prv_check_valid_char_after_open_bracket(lwjson_int_str_t *pobj, lwjson_token_t *t) +{ + lwjsonr_t res; + + /* Check next character after object open */ + if ((res = prv_skip_blank(pobj)) != lwjsonOK) + { + return res; + } + if (*pobj->p == '\0' || (t->type == LWJSON_TYPE_OBJECT && (*pobj->p != '"' && *pobj->p != '}')) || (t->type == LWJSON_TYPE_ARRAY && (*pobj->p != '"' && *pobj->p != ']' && *pobj->p != '[' && *pobj->p != '{' && *pobj->p != '-' && (*pobj->p < '0' || *pobj->p > '9') && *pobj->p != 't' && *pobj->p != 'n' && *pobj->p != 'f'))) + { + res = lwjsonERRJSON; + } + return res; +} + +/** + * \brief Setup LwJSON instance for parsing JSON strings + * \param[in,out] lw: LwJSON instance + * \param[in] tokens: Pointer to array of tokens used for parsing + * \param[in] tokens_len: Number of tokens + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +lwjsonr_t +lwjson_init(lwjson_t *lw, lwjson_token_t *tokens, size_t tokens_len) +{ + memset(lw, 0x00, sizeof(*lw)); + memset(tokens, 0x00, sizeof(*tokens) * tokens_len); + lw->tokens = tokens; + lw->tokens_len = tokens_len; + lw->first_token.type = LWJSON_TYPE_OBJECT; + return lwjsonOK; +} + +/** + * \brief Parse JSON data with length parameter + * JSON format must be complete and must comply with RFC4627 + * \param[in,out] lw: LwJSON instance + * \param[in] json_data: JSON string to parse + * \param[in] jsonÄŒlen: JSON data length + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +lwjsonr_t +lwjson_parse_ex(lwjson_t *lw, const void *json_data, size_t json_len) +{ + lwjsonr_t res = lwjsonOK; + lwjson_token_t *t, *to; + lwjson_int_str_t pobj = {.start = json_data, .len = json_len, .p = json_data}; + + /* Check input parameters */ + if (lw == NULL || json_data == NULL || json_len == 0) + { + res = lwjsonERRPAR; + goto ret; + } + + /* set first token */ + to = &lw->first_token; + + /* values from very beginning */ + lw->flags.parsed = 0; + lw->next_free_token_pos = 0; + memset(to, 0x00, sizeof(*to)); + + /* First parse */ + if ((res = prv_skip_blank(&pobj)) != lwjsonOK) + { + goto ret; + } + if (*pobj.p == '{') + { + to->type = LWJSON_TYPE_OBJECT; + } + else if (*pobj.p == '[') + { + to->type = LWJSON_TYPE_ARRAY; + } + else + { + res = lwjsonERRJSON; + goto ret; + } + ++pobj.p; + if ((res = prv_check_valid_char_after_open_bracket(&pobj, to)) != lwjsonOK) + { + goto ret; + } + + /* Process all characters as indicated by input user */ + while (pobj.p != NULL && *pobj.p != '\0' && (size_t)(pobj.p - pobj.start) < pobj.len) + { + /* Filter out blanks */ + if ((res = prv_skip_blank(&pobj)) != lwjsonOK) + { + goto ret; + } + if (*pobj.p == ',') + { + ++pobj.p; + continue; + } + + /* Check if end of object or array*/ + if (*pobj.p == (to->type == LWJSON_TYPE_OBJECT ? '}' : ']')) + { + lwjson_token_t *parent = to->next; + to->next = NULL; + ++pobj.p; + + /* End of string if to == NULL (no parent), check if properly terminated */ + if ((to = parent) == NULL) + { + prv_skip_blank(&pobj); + res = (pobj.p == NULL || *pobj.p == '\0' || (size_t)(pobj.p - pobj.start) == pobj.len) ? lwjsonOK + : lwjsonERR; + goto ret; + } + continue; + } + + /* Allocate new token */ + if ((t = prv_alloc_token(lw)) == NULL) + { + res = lwjsonERRMEM; + goto ret; + } + + /* If object type is not array, first thing is property that starts with quotes */ + if (to->type != LWJSON_TYPE_ARRAY) + { + if (*pobj.p != '"') + { + res = lwjsonERRJSON; + goto ret; + } + if ((res = prv_parse_property_name(&pobj, t)) != lwjsonOK) + { + goto ret; + } + } + + /* Add element to linked list */ + if (to->u.first_child == NULL) + { + to->u.first_child = t; + } + else + { + lwjson_token_t *c; + for (c = to->u.first_child; c->next != NULL; c = c->next) + { + } + c->next = t; + } + + /* Check next character to process */ + switch (*pobj.p) + { + case '{': + case '[': + t->type = *pobj.p == '{' ? LWJSON_TYPE_OBJECT : LWJSON_TYPE_ARRAY; + ++pobj.p; + if ((res = prv_check_valid_char_after_open_bracket(&pobj, t)) != lwjsonOK) + { + goto ret; + } + t->next = to; /* Temporary saved as parent object */ + to = t; + break; + case '"': + if ((res = prv_parse_string(&pobj, &t->u.str.token_value, &t->u.str.token_value_len)) == lwjsonOK) + { + t->type = LWJSON_TYPE_STRING; + } + else + { + goto ret; + } + break; + case 't': + /* RFC4627 is lower-case only */ + if (strncmp(pobj.p, "true", 4) == 0) + { + t->type = LWJSON_TYPE_TRUE; + pobj.p += 4; + } + else + { + res = lwjsonERRJSON; + goto ret; + } + break; + case 'f': + /* RFC4627 is lower-case only */ + if (strncmp(pobj.p, "false", 5) == 0) + { + t->type = LWJSON_TYPE_FALSE; + pobj.p += 5; + } + else + { + res = lwjsonERRJSON; + goto ret; + } + break; + case 'n': + /* RFC4627 is lower-case only */ + if (strncmp(pobj.p, "null", 4) == 0) + { + t->type = LWJSON_TYPE_NULL; + pobj.p += 4; + } + else + { + res = lwjsonERRJSON; + goto ret; + } + break; + default: + if (*pobj.p == '-' || (*pobj.p >= '0' && *pobj.p <= '9')) + { + if (prv_parse_number(&pobj, &t->type, &t->u.num_real, &t->u.num_int) != lwjsonOK) + { + res = lwjsonERRJSON; + goto ret; + } + } + else + { + res = lwjsonERRJSON; + goto ret; + } + break; + } + + /* Below code is used to check characters after valid tokens */ + if (t->type == LWJSON_TYPE_ARRAY || t->type == LWJSON_TYPE_OBJECT) + { + continue; + } + + /* + * Check what are values after the token value + * + * As per RFC4627, every token value may have one or more + * blank characters, followed by one of below options: + * - Comma separator for next token + * - End of array indication + * - End of object indication + */ + if ((res = prv_skip_blank(&pobj)) != lwjsonOK) + { + goto ret; + } + /* Check if valid string is availabe after */ + if (pobj.p == NULL || *pobj.p == '\0' || (*pobj.p != ',' && *pobj.p != ']' && *pobj.p != '}')) + { + res = lwjsonERRJSON; + goto ret; + } + else if (*pobj.p == ',') + { /* Check to advance to next token immediatey */ + ++pobj.p; + } + } + if (to != &lw->first_token || (to != NULL && to->next != NULL)) + { + res = lwjsonERRJSON; + to = NULL; + } + if (to != NULL) + { + if (to->type != LWJSON_TYPE_ARRAY && to->type != LWJSON_TYPE_OBJECT) + { + res = lwjsonERRJSON; + } + to->token_name = NULL; + to->token_name_len = 0; + } +ret: + if (res == lwjsonOK) + { + lw->flags.parsed = 1; + } + return res; +} + +/** + * \brief Parse input JSON format + * JSON format must be complete and must comply with RFC4627 + * \param[in,out] lw: LwJSON instance + * \param[in] json_str: JSON string to parse + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +lwjsonr_t +lwjson_parse(lwjson_t *lw, const char *json_str) +{ + return lwjson_parse_ex(lw, json_str, strlen(json_str)); +} + +/** + * \brief Free token instances (specially used in case of dynamic memory allocation) + * \param[in,out] lw: LwJSON instance + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +lwjsonr_t +lwjson_free(lwjson_t *lw) +{ + memset(lw->tokens, 0x00, sizeof(*lw->tokens) * lw->tokens_len); + lw->flags.parsed = 0; + return lwjsonOK; +} + +/** + * \brief Find first match in the given path for JSON entry + * JSON must be valid and parsed with \ref lwjson_parse function + * \param[in] lw: JSON instance with parsed JSON string + * \param[in] path: Path with dot-separated entries to search for the JSON key to return + * \return Pointer to found token on success, `NULL` if token cannot be found + */ +const lwjson_token_t * +lwjson_find(lwjson_t *lw, const char *path) +{ + if (lw == NULL || !lw->flags.parsed || path == NULL) + { + return NULL; + } + return prv_find(lwjson_get_first_token(lw), path); +} + +/** + * \brief Find first match in the given path for JSON path + * JSON must be valid and parsed with \ref lwjson_parse function + * + * \param[in] lw: JSON instance with parsed JSON string + * \param[in] token: Root token to start search at. + * Token must be type \ref LWJSON_TYPE_OBJECT or \ref LWJSON_TYPE_ARRAY. + * Set to `NULL` to use root token of LwJSON object + * \param[in] path: path with dot-separated entries to search for JSON key + * \return Pointer to found token on success, `NULL` if token cannot be found + */ +const lwjson_token_t * +lwjson_find_ex(lwjson_t *lw, const lwjson_token_t *token, const char *path) +{ + if (lw == NULL || !lw->flags.parsed || path == NULL) + { + return NULL; + } + if (token == NULL) + { + token = lwjson_get_first_token(lw); + } + if (token == NULL || (token->type != LWJSON_TYPE_ARRAY && token->type != LWJSON_TYPE_OBJECT)) + { + return NULL; + } + return prv_find(token, path); +} \ No newline at end of file diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson.h b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson.h new file mode 100644 index 0000000..601a4e1 --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson.h @@ -0,0 +1,356 @@ +/** + * \file lwjson.h + * \brief LwJSON - Lightweight JSON format parser + */ + +/* + * Copyright (c) 2022 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwJSON - Lightweight JSON format parser. + * + * Author: Tilen MAJERLE + * Version: v1.5.0 + */ +#ifndef LWJSON_HDR_H +#define LWJSON_HDR_H + +#include +#include +#include "lwjson_opt.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/** + * \defgroup LWJSON Lightweight JSON format parser + * \brief LwJSON - Lightweight JSON format parser + * \{ + */ + +/** + * \brief Get size of statically allocated array + * \param[in] x: Object to get array size of + * \return Number of elements in array + */ +#define LWJSON_ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0])) + + /** + * \brief List of supported JSON types + */ + typedef enum + { + LWJSON_TYPE_STRING, /*!< String/Text format. Everything that has beginning and ending quote character */ + LWJSON_TYPE_NUM_INT, /*!< Number type for integer */ + LWJSON_TYPE_NUM_REAL, /*!< Number type for real number */ + LWJSON_TYPE_OBJECT, /*!< Object data type */ + LWJSON_TYPE_ARRAY, /*!< Array data type */ + LWJSON_TYPE_TRUE, /*!< True boolean value */ + LWJSON_TYPE_FALSE, /*!< False boolean value */ + LWJSON_TYPE_NULL, /*!< Null value */ + } lwjson_type_t; + + /** + * \brief Real data type + */ + typedef LWJSON_CFG_REAL_TYPE lwjson_real_t; + + /** + * \brief Integer data type + */ + typedef LWJSON_CFG_INT_TYPE lwjson_int_t; + + /** + * \brief JSON token + */ + typedef struct lwjson_token + { + struct lwjson_token *next; /*!< Next token on a list */ + lwjson_type_t type; /*!< Token type */ + const char *token_name; /*!< Token name (if exists) */ + size_t token_name_len; /*!< Length of token name (this is needed to support const input strings to parse) */ + + union + { + struct + { + const char *token_value; /*!< Pointer to the beginning of the string */ + size_t + token_value_len; /*!< Length of token value (this is needed to support const input strings to parse) */ + } str; /*!< String data */ + + lwjson_real_t num_real; /*!< Real number format */ + lwjson_int_t num_int; /*!< Int number format */ + struct lwjson_token *first_child; /*!< First children object for object or array type */ + } u; /*!< Union with different data types */ + } lwjson_token_t; + + /** + * \brief JSON result enumeration + */ + typedef enum + { + lwjsonOK = 0x00, /*!< Function returns successfully */ + lwjsonERR, /*!< Generic error message */ + lwjsonERRJSON, /*!< Error JSON format */ + lwjsonERRMEM, /*!< Memory error */ + lwjsonERRPAR, /*!< Parameter error */ + + lwjsonSTREAMWAITFIRSTCHAR, /*!< Streaming parser did not yet receive first valid character + indicating start of JSON sequence */ + lwjsonSTREAMDONE, /*!< Streaming parser is done, + closing character matched the stream opening one */ + lwjsonSTREAMINPROG, /*!< Stream parsing is still in progress */ + } + + lwjsonr_t; + + /** + * \brief LwJSON instance + */ + typedef struct + { + lwjson_token_t *tokens; /*!< Pointer to array of tokens */ + size_t tokens_len; /*!< Size of all tokens */ + size_t next_free_token_pos; /*!< Position of next free token instance */ + lwjson_token_t first_token; /*!< First token on a list */ + + struct + { + uint8_t parsed : 1; /*!< Flag indicating JSON parsing has finished successfully */ + } flags; /*!< List of flags */ + } lwjson_t; + + lwjsonr_t lwjson_init(lwjson_t *lw, lwjson_token_t *tokens, size_t tokens_len); + lwjsonr_t lwjson_parse_ex(lwjson_t *lw, const void *json_data, size_t len); + lwjsonr_t lwjson_parse(lwjson_t *lw, const char *json_str); + const lwjson_token_t *lwjson_find(lwjson_t *lw, const char *path); + const lwjson_token_t *lwjson_find_ex(lwjson_t *lw, const lwjson_token_t *token, const char *path); + lwjsonr_t lwjson_free(lwjson_t *lw); + + void lwjson_print_token(const lwjson_token_t *token); + void lwjson_print_json(const lwjson_t *lw); + + /** + * \brief Object type for streaming parser + */ + typedef enum + { + LWJSON_STREAM_TYPE_NONE, /*!< No entry - not used */ + LWJSON_STREAM_TYPE_OBJECT, /*!< Object indication */ + LWJSON_STREAM_TYPE_OBJECT_END, /*!< Object end indication */ + LWJSON_STREAM_TYPE_ARRAY, /*!< Array indication */ + LWJSON_STREAM_TYPE_ARRAY_END, /*!< Array end indication */ + LWJSON_STREAM_TYPE_KEY, /*!< Key string */ + LWJSON_STREAM_TYPE_STRING, /*!< Strin type */ + LWJSON_STREAM_TYPE_TRUE, /*!< True primitive */ + LWJSON_STREAM_TYPE_FALSE, /*!< False primitive */ + LWJSON_STREAM_TYPE_NULL, /*!< Null primitive */ + LWJSON_STREAM_TYPE_NUMBER, /*!< Generic number */ + } lwjson_stream_type_t; + + /** + * \brief Stream parsing stack object + */ + typedef struct + { + lwjson_stream_type_t type; /*!< Streaming type - current value */ + + union + { + char name[LWJSON_CFG_STREAM_KEY_MAX_LEN + 1]; /*!< Last known key name, used only for \ref LWJSON_STREAM_TYPE_KEY type */ + uint16_t index; /*!< Current index when type is an array */ + } meta; /*!< Meta information */ + } lwjson_stream_stack_t; + + typedef enum + { + LWJSON_STREAM_STATE_WAITINGFIRSTCHAR = 0x00, /*!< State to wait for very first opening character */ + LWJSON_STREAM_STATE_PARSING, /*!< In parsing of the first char state - detecting next character state */ + LWJSON_STREAM_STATE_PARSING_STRING, /*!< Parse string primitive */ + LWJSON_STREAM_STATE_PARSING_PRIMITIVE, /*!< Parse any primitive that is non-string, either "true", "false", "null" or a number */ + } lwjson_stream_state_t; + + /* Forward declaration */ + struct lwjson_stream_parser; + + /** + * \brief Callback function for various events + * + */ + typedef void (*lwjson_stream_parser_callback_fn)(struct lwjson_stream_parser *jsp, lwjson_stream_type_t type); + + /** + * \brief LwJSON streaming structure + */ + typedef struct lwjson_stream_parser + { + lwjson_stream_stack_t + stack[LWJSON_CFG_STREAM_STACK_SIZE]; /*!< Stack used for parsing. TODO: Add conditional compilation flag */ + size_t stack_pos; /*!< Current stack position */ + + lwjson_stream_state_t parse_state; /*!< Parser state */ + + lwjson_stream_parser_callback_fn evt_fn; /*!< Event function for user */ + + /* State */ + union + { + struct + { + char buff[LWJSON_CFG_STREAM_STRING_MAX_LEN + 1]; /*!< Buffer to write temporary data. TODO: Size to be variable with define */ + size_t buff_pos; /*!< Buffer position for next write (length of bytes in buffer) */ + size_t buff_total_pos; /*!< Total buffer position used up to now (in several data chunks) */ + uint8_t is_last; /*!< Status indicates if this is the last part of the string */ + } str; /*!< String structure. It is only used for keys and string objects. + Use primitive part for all other options */ + + struct + { + char buff[LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN + 1]; /*!< Temporary write buffer */ + size_t buff_pos; /*!< Buffer position for next write */ + } prim; /*!< Primitive object. Used for all types, except key or string */ + + /* Todo: Add other types */ + } data; /*!< Data union used to parse various */ + + char prev_c; /*!< History of characters */ + void *udata; /*!< User data */ + } lwjson_stream_parser_t; + + lwjsonr_t lwjson_stream_init(lwjson_stream_parser_t *jsp, lwjson_stream_parser_callback_fn evt_fn); + lwjsonr_t lwjson_stream_reset(lwjson_stream_parser_t *jsp); + lwjsonr_t lwjson_stream_parse(lwjson_stream_parser_t *jsp, char c); + +/** + * \brief Get number of tokens used to parse JSON + * \param[in] lw: Pointer to LwJSON instance + * \return Number of tokens used to parse JSON + */ +#define lwjson_get_tokens_used(lw) (((lw) != NULL) ? ((lw)->next_free_token_pos + 1) : 0) + +/** + * \brief Get very first token of LwJSON instance + * \param[in] lw: Pointer to LwJSON instance + * \return Pointer to first token + */ +#define lwjson_get_first_token(lw) (((lw) != NULL) ? (&(lw)->first_token) : NULL) + +/** + * \brief Get token value for \ref LWJSON_TYPE_NUM_INT type + * \param[in] token: token with integer type + * \return Int number if type is integer, `0` otherwise + */ +#define lwjson_get_val_int(token) \ + ((lwjson_int_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_INT) ? (token)->u.num_int : 0)) + +/** + * \brief Get token value for \ref LWJSON_TYPE_NUM_REAL type + * \param[in] token: token with real type + * \return Real numbeer if type is real, `0` otherwise + */ +#define lwjson_get_val_real(token) \ + ((lwjson_real_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_REAL) ? (token)->u.num_real : 0)) + +/** + * \brief Get first child token for \ref LWJSON_TYPE_OBJECT or \ref LWJSON_TYPE_ARRAY types + * \param[in] token: token with integer type + * \return Pointer to first child or `NULL` if parent token is not object or array + */ +#define lwjson_get_first_child(token) \ + (const void *)(((token) != NULL && ((token)->type == LWJSON_TYPE_OBJECT || (token)->type == LWJSON_TYPE_ARRAY)) \ + ? (token)->u.first_child \ + : NULL) + + /** + * \brief Get string value from JSON token + * \param[in] token: Token with string type + * \param[out] str_len: Pointer to variable holding length of string. + * Set to `NULL` if not used + * \return Pointer to string or `NULL` if invalid token type + */ + static inline const char * + lwjson_get_val_string(const lwjson_token_t *token, size_t *str_len) + { + if (token != NULL && token->type == LWJSON_TYPE_STRING) + { + if (str_len != NULL) + { + *str_len = token->u.str.token_value_len; + } + return token->u.str.token_value; + } + return NULL; + } + +/** + * \brief Get length of string for \ref LWJSON_TYPE_STRING token type + * \param[in] token: token with string type + * \return Length of string in units of bytes + */ +#define lwjson_get_val_string_length(token) \ + ((size_t)(((token) != NULL && (token)->type == LWJSON_TYPE_STRING) ? (token)->u.str.token_value_len : 0)) + + /** + * \brief Compare string token with user input string for a case-sensitive match + * \param[in] token: Token with string type + * \param[in] str: NULL-terminated string to compare + * \return `1` if equal, `0` otherwise + */ + static inline uint8_t + lwjson_string_compare(const lwjson_token_t *token, const char *str) + { + if (token != NULL && token->type == LWJSON_TYPE_STRING) + { + return strncmp(token->u.str.token_value, str, token->u.str.token_value_len) == 0; + } + return 0; + } + + /** + * \brief Compare string token with user input string for a case-sensitive match + * \param[in] token: Token with string type + * \param[in] str: NULL-terminated string to compare + * \param[in] len: Length of the string in bytes + * \return `1` if equal, `0` otherwise + */ + static inline uint8_t + lwjson_string_compare_n(const lwjson_token_t *token, const char *str, size_t len) + { + if (token != NULL && token->type == LWJSON_TYPE_STRING && len <= token->u.str.token_value_len) + { + return strncmp(token->u.str.token_value, str, len) == 0; + } + return 0; + } + + /** + * \} + */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LWJSON_HDR_H */ \ No newline at end of file diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_debug.c b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_debug.c new file mode 100644 index 0000000..7751898 --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_debug.c @@ -0,0 +1,149 @@ +/** + * \file lwjson_debug.c + * \brief Debug and print function for tokens + */ + +/* + * Copyright (c) 2022 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwJSON - Lightweight JSON format parser. + * + * Author: Tilen MAJERLE + * Version: v1.5.0 + */ +#include +#include +#include "lwjson.h" + +/** + * \brief Token print instance + */ +typedef struct +{ + size_t indent; /*!< Indent level for token print */ +} lwjson_token_print_t; + +/** + * \brief Print token value + * \param[in] p: Token print instance + * \param[in] token: Token to print + */ +static void +prv_print_token(lwjson_token_print_t *p, const lwjson_token_t *token) +{ +#define print_indent() printf("%.*s", (int)((p->indent)), "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"); + + if (token == NULL) + { + return; + } + + /* Check if token has a name */ + print_indent(); + if (token->token_name != NULL) + { + printf("\"%.*s\":", (int)token->token_name_len, token->token_name); + } + + /* Print different types */ + switch (token->type) + { + case LWJSON_TYPE_OBJECT: + case LWJSON_TYPE_ARRAY: + { + printf("%c", token->type == LWJSON_TYPE_OBJECT ? '{' : '['); + if (token->u.first_child != NULL) + { + printf("\n"); + ++p->indent; + for (const lwjson_token_t *t = lwjson_get_first_child(token); t != NULL; t = t->next) + { + prv_print_token(p, t); + } + --p->indent; + print_indent(); + } + printf("%c", token->type == LWJSON_TYPE_OBJECT ? '}' : ']'); + break; + } + case LWJSON_TYPE_STRING: + { + printf("\"%.*s\"", (int)lwjson_get_val_string_length(token), lwjson_get_val_string(token, NULL)); + break; + } + case LWJSON_TYPE_NUM_INT: + { + printf("%lld", (long long)lwjson_get_val_int(token)); + break; + } + case LWJSON_TYPE_NUM_REAL: + { + printf("%f", (double)lwjson_get_val_real(token)); + break; + } + case LWJSON_TYPE_TRUE: + { + printf("true"); + break; + } + case LWJSON_TYPE_FALSE: + { + printf("false"); + break; + } + case LWJSON_TYPE_NULL: + { + printf("NULL"); + break; + } + default: + break; + } + if (token->next != NULL) + { + printf(","); + } + printf("\n"); +} + +/** + * \brief Prints and outputs token data to the stream output + * \note This function is not re-entrant + * \param[in] token: Token to print + */ +void lwjson_print_token(const lwjson_token_t *token) +{ + lwjson_token_print_t p = {0}; + prv_print_token(&p, token); +} + +/** + * \brief Prints and outputs full parsed LwJSON instance + * \note This function is not re-entrant + * \param[in] lw: LwJSON instance to print + */ +void lwjson_print_json(const lwjson_t *lw) +{ + lwjson_token_print_t p = {0}; + prv_print_token(&p, lwjson_get_first_token(lw)); +} \ No newline at end of file diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_debug.hpp b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_debug.hpp new file mode 100644 index 0000000..e69de29 diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_opt.h b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_opt.h new file mode 100644 index 0000000..3929f6f --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_opt.h @@ -0,0 +1,137 @@ +/** + * \file lwjson_opt.h + * \brief LwJSON options + */ + +/* + * Copyright (c) 2022 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwJSON - Lightweight JSON format parser. + * + * Author: Tilen MAJERLE + * Version: v1.5.0 + */ +#ifndef LWJSON_HDR_OPT_H +#define LWJSON_HDR_OPT_H + +// #define LWJSON_DEV 1 +/* Uncomment to ignore user options (or set macro in compiler flags) */ +#define LWJSON_IGNORE_USER_OPTS + +/* Include application options */ +#ifndef LWJSON_IGNORE_USER_OPTS +#include "lwjson_opts.h" +#endif /* LWJSON_IGNORE_USER_OPTS */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/** + * \defgroup LWJSON_OPT Configuration + * \brief LwJSON options + * \{ + */ + +/** + * \brief Real data type used to parse numbers with floating point number + * \note Data type must be signed, normally `float` or `double` + * + * This is used for numbers in \ref LWJSON_TYPE_NUM_REAL token data type. + */ +#ifndef LWJSON_CFG_REAL_TYPE +#define LWJSON_CFG_REAL_TYPE float +#endif + +/** + * \brief Integer type used to parse numbers + * \note Data type must be signed integer + * + * This is used for numbers in \ref LWJSON_TYPE_NUM_INT token data type. + */ +#ifndef LWJSON_CFG_INT_TYPE +#define LWJSON_CFG_INT_TYPE long long +#endif + +/** + * \brief Enables `1` or disables `0` support for inline comments + * + * Default set to `0` to be JSON compliant + */ +#ifndef LWJSON_CFG_COMMENTS +#define LWJSON_CFG_COMMENTS 0 +#endif + +/** + * \defgroup LWJSON_OPT_STREAM JSON stream + * \brief JSON streaming confiuration + * \{ + */ + +/** + * \brief Max length of token key (object key name) to be available for stack storage + * + */ +#ifndef LWJSON_CFG_STREAM_KEY_MAX_LEN +#define LWJSON_CFG_STREAM_KEY_MAX_LEN 32 +#endif + +/** + * \brief Max stack size (depth) in units of \ref lwjson_stream_stack_t structure + * + */ +#ifndef LWJSON_CFG_STREAM_STACK_SIZE +#define LWJSON_CFG_STREAM_STACK_SIZE 16 +#endif + +/** + * \brief Max size of string for single parsing in units of bytes + * + */ +#ifndef LWJSON_CFG_STREAM_STRING_MAX_LEN +#define LWJSON_CFG_STREAM_STRING_MAX_LEN 256 +#endif + +/** + * \brief Max number of bytes used to parse primitive. + * + * Primitives are all numbers and logical values (null, true, false) + */ +#ifndef LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN +#define LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN 32 +#endif + + /** + * \} + */ + + /** + * \} + */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LWJSON_HDR_OPT_H */ \ No newline at end of file diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_stream.c b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_stream.c new file mode 100644 index 0000000..0bc131f --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/lib/LWJSON/lwjson_stream.c @@ -0,0 +1,514 @@ +/** + * \file lwjson_stream.c + * \brief Lightweight JSON format parser + */ + +/* + * Copyright (c) 2022 Tilen MAJERLE + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * This file is part of LwJSON - Lightweight JSON format parser. + * + * Author: Tilen MAJERLE + * Version: v1.5.0 + */ +#include +#include "lwjson.h" + +#if defined(LWJSON_DEV) +#include +#define DEBUG_STRING_PREFIX_SPACES \ + " " +#define LWJSON_DEBUG(jsp, ...) \ + do \ + { \ + if ((jsp) != NULL) \ + { \ + printf("%.*s", (int)(4 * (jsp)->stack_pos), DEBUG_STRING_PREFIX_SPACES); \ + } \ + printf(__VA_ARGS__); \ + } while (0) + +/* Strings for debug */ +static const char *type_strings[] = { + [LWJSON_STREAM_TYPE_NONE] = "none", + [LWJSON_STREAM_TYPE_OBJECT] = "object", + [LWJSON_STREAM_TYPE_OBJECT_END] = "object_end", + [LWJSON_STREAM_TYPE_ARRAY] = "array", + [LWJSON_STREAM_TYPE_ARRAY_END] = "array_end", + [LWJSON_STREAM_TYPE_KEY] = "key", + [LWJSON_STREAM_TYPE_STRING] = "string", + [LWJSON_STREAM_TYPE_TRUE] = "true", + [LWJSON_STREAM_TYPE_FALSE] = "false", + [LWJSON_STREAM_TYPE_NULL] = "null", + [LWJSON_STREAM_TYPE_NUMBER] = "number", +}; +#else +#define LWJSON_DEBUG(jsp, ...) +#endif /* defined(LWJSON_DEV) */ + +/** + * \brief Sends an event to user for further processing + * + */ +#define SEND_EVT(jsp, type) \ + if ((jsp) != NULL && (jsp)->evt_fn != NULL) \ + { \ + (jsp)->evt_fn((jsp), (type)); \ + } + +/** + * \brief Check if character is a space character (with extended chars) + * \param[in] c: Character to check + * \return `1` if considered extended space, `0` otherwise + */ +#define prv_is_space_char_ext(c) ((c) == ' ' || (c) == '\t' || (c) == '\r' || (c) == '\n' || (c) == '\f') + +/** + * \brief Push "parent" state to the artificial stack + * \param jsp: JSON stream parser instance + * \param type: Stream type to be pushed on stack + * \return `1` on success, `0` otherwise + */ +static uint8_t +prv_stack_push(lwjson_stream_parser_t *jsp, lwjson_stream_type_t type) +{ + if (jsp->stack_pos < LWJSON_ARRAYSIZE(jsp->stack)) + { + jsp->stack[jsp->stack_pos].type = type; + jsp->stack[jsp->stack_pos].meta.index = 0; + LWJSON_DEBUG(jsp, "Pushed to stack: %s\r\n", type_strings[type]); + jsp->stack_pos++; + return 1; + } + return 0; +} + +/** + * \brief Pop value from stack (remove it) and return its value + * \param jsp: JSON stream parser instance + * \return Member of \ref lwjson_stream_type_t enumeration + */ +static lwjson_stream_type_t +prv_stack_pop(lwjson_stream_parser_t *jsp) +{ + if (jsp->stack_pos > 0) + { + lwjson_stream_type_t t = jsp->stack[--jsp->stack_pos].type; + jsp->stack[jsp->stack_pos].type = LWJSON_STREAM_TYPE_NONE; + LWJSON_DEBUG(jsp, "Popped from stack: %s\r\n", type_strings[t]); + + /* Take care of array to indicate number of entries */ + if (jsp->stack_pos > 0 && jsp->stack[jsp->stack_pos - 1].type == LWJSON_STREAM_TYPE_ARRAY) + { + jsp->stack[jsp->stack_pos - 1].meta.index++; + } + return t; + } + return LWJSON_STREAM_TYPE_NONE; +} + +/** + * \brief Get top type value currently on the stack + * \param jsp: JSON stream parser instance + * \return Member of \ref lwjson_stream_type_t enumeration + */ +static lwjson_stream_type_t +prv_stack_get_top(lwjson_stream_parser_t *jsp) +{ + if (jsp->stack_pos > 0) + { + return jsp->stack[jsp->stack_pos - 1].type; + } + return LWJSON_STREAM_TYPE_NONE; +} + +/** + * \brief Initialize LwJSON stream object before parsing takes place + * \param[in,out] jsp: Stream JSON structure + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +lwjsonr_t +lwjson_stream_init(lwjson_stream_parser_t *jsp, lwjson_stream_parser_callback_fn evt_fn) +{ + memset(jsp, 0x00, sizeof(*jsp)); + jsp->parse_state = LWJSON_STREAM_STATE_WAITINGFIRSTCHAR; + jsp->evt_fn = evt_fn; + return lwjsonOK; +} + +/** + * \brief Reset LwJSON stream structure + * + * \param jsp: LwJSON stream parser + * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise + */ +lwjsonr_t +lwjson_stream_reset(lwjson_stream_parser_t *jsp) +{ + jsp->parse_state = LWJSON_STREAM_STATE_WAITINGFIRSTCHAR; + jsp->stack_pos = 0; + return lwjsonOK; +} + +/** + * \brief Parse JSON string in streaming mode + * \param[in,out] jsp: Stream JSON structure + * \param[in] c: Character to parse + * \return \ref lwjsonOK if parsing is in progress and no hard error detected + * \ref lwjsonSTREAMDONE when valid JSON was detected and stack level reached back `0` level + */ +lwjsonr_t +lwjson_stream_parse(lwjson_stream_parser_t *jsp, char c) +{ + /* Get first character first */ + if (jsp->parse_state == LWJSON_STREAM_STATE_WAITINGFIRSTCHAR && c != '{' && c != '[') + { + return lwjsonSTREAMDONE; + } + +start_over: + /* + * Determine what to do from parsing state + */ + switch (jsp->parse_state) + { + + /* + * Waiting for very first valid characters, + * that is used to indicate start of JSON stream + */ + case LWJSON_STREAM_STATE_WAITINGFIRSTCHAR: + case LWJSON_STREAM_STATE_PARSING: + { + /* Determine start of object or an array */ + if (c == '{' || c == '[') + { + /* Reset stack pointer if this character came from waiting for first character */ + if (jsp->parse_state == LWJSON_STREAM_STATE_WAITINGFIRSTCHAR) + { + jsp->stack_pos = 0; + } + if (!prv_stack_push(jsp, c == '{' ? LWJSON_STREAM_TYPE_OBJECT : LWJSON_STREAM_TYPE_ARRAY)) + { + LWJSON_DEBUG(jsp, "Cannot push object/array to stack\r\n"); + return lwjsonERRMEM; + } + jsp->parse_state = LWJSON_STREAM_STATE_PARSING; + SEND_EVT(jsp, c == '{' ? LWJSON_STREAM_TYPE_OBJECT : LWJSON_STREAM_TYPE_ARRAY); + + /* Determine end of object or an array */ + } + else if (c == '}' || c == ']') + { + lwjson_stream_type_t t = prv_stack_get_top(jsp); + + /* + * If it is a key last entry on closing area, + * it is an error - an example: {"key":} + */ + if (t == LWJSON_STREAM_TYPE_KEY) + { + LWJSON_DEBUG(jsp, "ERROR - key should not be followed by ] without value for a key\r\n"); + return lwjsonERRJSON; + } + + /* + * Check if closing character matches stack value + * Avoid cases like: {"key":"value"] or ["v1", "v2", "v3"} + */ + if ((c == '}' && t != LWJSON_STREAM_TYPE_OBJECT) || (c == ']' && t != LWJSON_STREAM_TYPE_ARRAY)) + { + LWJSON_DEBUG(jsp, "ERROR - closing character '%c' does not match stack element \"%s\"\r\n", c, + type_strings[t]); + return lwjsonERRJSON; + } + + /* Now remove the array or object from stack */ + if (prv_stack_pop(jsp) == LWJSON_STREAM_TYPE_NONE) + { + return lwjsonERRJSON; + } + + /* + * Check if above is a key type + * and remove it too as we finished with processing of potential case. + * + * {"key":{"abc":1}} - remove "key" part + */ + if (prv_stack_get_top(jsp) == LWJSON_STREAM_TYPE_KEY) + { + prv_stack_pop(jsp); + } + SEND_EVT(jsp, c == '}' ? LWJSON_STREAM_TYPE_OBJECT_END : LWJSON_STREAM_TYPE_ARRAY_END); + + /* If that is the end of JSON */ + if (jsp->stack_pos == 0) + { + return lwjsonSTREAMDONE; + } + + /* Determine start of string - can be key or regular string (in array or after key) */ + } + else if (c == '"') + { +#if defined(LWJSON_DEV) + lwjson_stream_type_t t = prv_stack_get_top(jsp); + if (t == LWJSON_STREAM_TYPE_OBJECT) + { + LWJSON_DEBUG(jsp, "Start of string parsing - expected key name in an object\r\n"); + } + else if (t == LWJSON_STREAM_TYPE_KEY) + { + LWJSON_DEBUG(jsp, + "Start of string parsing - string value associated to previous key in an object\r\n"); + } + else if (t == LWJSON_STREAM_TYPE_ARRAY) + { + LWJSON_DEBUG(jsp, "Start of string parsing - string entry in an array\r\n"); + } +#endif /* defined(LWJSON_DEV) */ + jsp->parse_state = LWJSON_STREAM_STATE_PARSING_STRING; + memset(&jsp->data.str, 0x00, sizeof(jsp->data.str)); + + /* Check for end of key character */ + } + else if (c == ':') + { + lwjson_stream_type_t t = prv_stack_get_top(jsp); + + /* + * Color can only be followed by key on the stack + * + * It is clear JSON error if this is not the case + */ + if (t != LWJSON_STREAM_TYPE_KEY) + { + LWJSON_DEBUG(jsp, "Error - wrong ':' character\r\n"); + return lwjsonERRJSON; + } + /* Check if this is start of number or "true", "false" or "null" */ + } + else if (c == '-' || (c >= '0' && c <= '9') || c == 't' || c == 'f' || c == 'n') + { + LWJSON_DEBUG(jsp, "Start of primitive parsing parsing - %s, First char: %c\r\n", + (c == '-' || (c >= '0' && c <= '9')) ? "number" : "true,false,null", c); + jsp->parse_state = LWJSON_STREAM_STATE_PARSING_PRIMITIVE; + memset(&jsp->data.prim, 0x00, sizeof(jsp->data.prim)); + jsp->data.prim.buff[jsp->data.prim.buff_pos++] = c; + } + break; + } + + /* + * Parse any type of string in a sequence + * + * It is used for key or string in an object or an array + */ + case LWJSON_STREAM_STATE_PARSING_STRING: + { + lwjson_stream_type_t t = prv_stack_get_top(jsp); + + /* + * Quote character may trigger end of string, + * or if backslasled before - it is part of string + * + * TODO: Handle backslash + */ + if (c == '"' && jsp->prev_c != '\\') + { +#if defined(LWJSON_DEV) + if (t == LWJSON_STREAM_TYPE_OBJECT) + { + LWJSON_DEBUG(jsp, "End of string parsing - object key name: \"%s\"\r\n", jsp->data.str.buff); + } + else if (t == LWJSON_STREAM_TYPE_KEY) + { + LWJSON_DEBUG( + jsp, "End of string parsing - string value associated to previous key in an object: \"%s\"\r\n", + jsp->data.str.buff); + } + else if (t == LWJSON_STREAM_TYPE_ARRAY) + { + LWJSON_DEBUG(jsp, "End of string parsing - an array string entry: \"%s\"\r\n", jsp->data.str.buff); + } +#endif /* defined(LWJSON_DEV) */ + + /* Set is_last to 1 as this is the last part of this string token */ + jsp->data.str.is_last = 1; + + /* + * When top of stack is object - string is treated as a key + * When top of stack is a key - string is a value for a key - notify user and pop the value for key + * When top of stack is an array - string is one type - notify user and don't do anything + */ + if (t == LWJSON_STREAM_TYPE_OBJECT) + { + SEND_EVT(jsp, LWJSON_STREAM_TYPE_KEY); + if (prv_stack_push(jsp, LWJSON_STREAM_TYPE_KEY)) + { + size_t len = jsp->data.str.buff_pos; + if (len > (sizeof(jsp->stack[0].meta.name) - 1)) + { + len = sizeof(jsp->stack[0].meta.name) - 1; + } + memcpy(jsp->stack[jsp->stack_pos - 1].meta.name, jsp->data.str.buff, len); + jsp->stack[jsp->stack_pos - 1].meta.name[len] = '\0'; + } + else + { + LWJSON_DEBUG(jsp, "Cannot push key to stack\r\n"); + return lwjsonERRMEM; + } + } + else if (t == LWJSON_STREAM_TYPE_KEY) + { + SEND_EVT(jsp, LWJSON_STREAM_TYPE_STRING); + prv_stack_pop(jsp); + /* Next character to wait for is either space or comma or end of object */ + } + else if (t == LWJSON_STREAM_TYPE_ARRAY) + { + SEND_EVT(jsp, LWJSON_STREAM_TYPE_STRING); + jsp->stack[jsp->stack_pos - 1].meta.index++; + } + jsp->parse_state = LWJSON_STREAM_STATE_PARSING; + } + else + { + /* TODO: Check other backslash elements */ + jsp->data.str.buff[jsp->data.str.buff_pos++] = c; + jsp->data.str.buff_total_pos++; + + /* Handle buffer "overflow" */ + if (jsp->data.str.buff_pos >= (LWJSON_CFG_STREAM_STRING_MAX_LEN - 1)) + { + jsp->data.str.buff[jsp->data.str.buff_pos] = '\0'; + + /* + * - For array or key types - following one is always string + * - For object type - character is key + */ + SEND_EVT(jsp, (t == LWJSON_STREAM_TYPE_KEY || t == LWJSON_STREAM_TYPE_ARRAY) + ? LWJSON_STREAM_TYPE_STRING + : LWJSON_STREAM_TYPE_KEY); + jsp->data.str.buff_pos = 0; + } + } + break; + } + + /* + * Parse any type of primitive that is not a string. + * + * true, false, null or any number primitive + */ + case LWJSON_STREAM_STATE_PARSING_PRIMITIVE: + { + /* Any character except space, comma, or end of array/object are valid */ + if (!prv_is_space_char_ext(c) && c != ',' && c != ']' && c != '}') + { + if (jsp->data.prim.buff_pos < sizeof(jsp->data.prim.buff) - 1) + { + jsp->data.prim.buff[jsp->data.prim.buff_pos++] = c; + } + } + else + { + lwjson_stream_type_t t = prv_stack_get_top(jsp); + +#if defined(LWJSON_DEV) + if (t == LWJSON_STREAM_TYPE_OBJECT) + { + /* TODO: Handle error - primitive cannot be just after object */ + } + else if (t == LWJSON_STREAM_TYPE_KEY) + { + LWJSON_DEBUG( + jsp, + "End of primitive parsing - string value associated to previous key in an object: \"%s\"\r\n", + jsp->data.prim.buff); + } + else if (t == LWJSON_STREAM_TYPE_ARRAY) + { + LWJSON_DEBUG(jsp, "End of primitive parsing - an array string entry: \"%s\"\r\n", + jsp->data.prim.buff); + } +#endif /* defined(LWJSON_DEV) */ + + /* + * This is the end of primitive parsing + * + * It is assumed that buffer for primitive can handle at least + * true, false, null or all number characters (that being real or int number) + */ + if (jsp->data.prim.buff_pos == 4 && strncmp(jsp->data.prim.buff, "true", 4) == 0) + { + LWJSON_DEBUG(jsp, "Primitive parsed as %s\r\n", "true"); + SEND_EVT(jsp, LWJSON_STREAM_TYPE_TRUE); + } + else if (jsp->data.prim.buff_pos == 4 && strncmp(jsp->data.prim.buff, "null", 4) == 0) + { + LWJSON_DEBUG(jsp, "Primitive parsed as %s\r\n", "null"); + SEND_EVT(jsp, LWJSON_STREAM_TYPE_NULL); + } + else if (jsp->data.prim.buff_pos == 5 && strncmp(jsp->data.prim.buff, "false", 5) == 0) + { + LWJSON_DEBUG(jsp, "Primitive parsed as %s\r\n", "false"); + SEND_EVT(jsp, LWJSON_STREAM_TYPE_FALSE); + } + else if (jsp->data.prim.buff[0] == '-' || (jsp->data.prim.buff[0] >= '0' && jsp->data.prim.buff[0] <= '9')) + { + LWJSON_DEBUG(jsp, "Primitive parsed - number\r\n"); + SEND_EVT(jsp, LWJSON_STREAM_TYPE_NUMBER); + } + else + { + LWJSON_DEBUG(jsp, "Invalid primitive type. Got: %s\r\n", jsp->data.prim.buff); + } + if (t == LWJSON_STREAM_TYPE_KEY) + { + prv_stack_pop(jsp); + } + else if (t == LWJSON_STREAM_TYPE_ARRAY) + { + jsp->stack[jsp->stack_pos - 1].meta.index++; + } + + /* + * Received character is not part of the primitive and must be processed again + * + * Set state to default state and start from beginning + */ + jsp->parse_state = LWJSON_STREAM_STATE_PARSING; + goto start_over; + } + break; + } + + /* TODO: Add other case statements */ + default: + break; + } + jsp->prev_c = c; /* Save current c as previous for next round */ + return lwjsonSTREAMINPROG; +} \ No newline at end of file diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/SemVer/semver.c b/ESP/lib/src/network/OTA/Github_OTA/lib/SemVer/semver.c new file mode 100644 index 0000000..1f0c286 --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/lib/SemVer/semver.c @@ -0,0 +1,723 @@ +/* + * semver.c + * + * Copyright (c) 2015-2017 Tomas Aparicio + * MIT licensed + */ + +#include +#include +#include +#include "semver.h" + +#define SLICE_SIZE 50 +#define DELIMITER "." +#define PR_DELIMITER "-" +#define MT_DELIMITER "+" +#define NUMBERS "0123456789" +#define ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define DELIMITERS DELIMITER PR_DELIMITER MT_DELIMITER +#define VALID_CHARS NUMBERS ALPHA DELIMITERS + +static const size_t MAX_SIZE = sizeof(char) * 255; +static const int MAX_SAFE_INT = (unsigned int)-1 >> 1; + +/** + * Define comparison operators, storing the + * ASCII code per each symbol in hexadecimal notation. + */ + +enum operators +{ + SYMBOL_GT = 0x3e, + SYMBOL_LT = 0x3c, + SYMBOL_EQ = 0x3d, + SYMBOL_TF = 0x7e, + SYMBOL_CF = 0x5e +}; + +/** + * Private helpers + */ + +/* + * Remove [begin:len-begin] from str by moving len data from begin+len to begin. + * If len is negative cut out to the end of the string. + */ +static int +strcut(char *str, int begin, int len) +{ + size_t l; + l = strlen(str); + + if ((int)l < 0 || (int)l > MAX_SAFE_INT) + return -1; + + if (len < 0) + len = l - begin + 1; + if (begin + len > (int)l) + len = l - begin; + memmove(str + begin, str + begin + len, l - len + 1 - begin); + + return len; +} + +static int +contains(const char c, const char *matrix, size_t len) +{ + size_t x; + for (x = 0; x < len; x++) + if ((char)matrix[x] == c) + return 1; + return 0; +} + +static int +has_valid_chars(const char *str, const char *matrix) +{ + size_t i, len, mlen; + len = strlen(str); + mlen = strlen(matrix); + + for (i = 0; i < len; i++) + if (contains(str[i], matrix, mlen) == 0) + return 0; + + return 1; +} + +static int +binary_comparison(int x, int y) +{ + if (x == y) + return 0; + if (x > y) + return 1; + return -1; +} + +static int +parse_int(const char *s) +{ + int valid, num; + valid = has_valid_chars(s, NUMBERS); + if (valid == 0) + return -1; + + num = strtol(s, NULL, 10); + if (num > MAX_SAFE_INT) + return -1; + + return num; +} + +/* + * Return a string allocated on the heap with the content from sep to end and + * terminate buf at sep. + */ +static char * +parse_slice(char *buf, char sep) +{ + char *pr, *part; + int plen; + + /* Find separator in buf */ + pr = strchr(buf, sep); + if (pr == NULL) + return NULL; + /* Length from separator to end of buf */ + plen = strlen(pr); + + /* Copy from buf into new string */ + part = (char *)calloc(plen + 1, sizeof(*part)); + if (part == NULL) + return NULL; + memcpy(part, pr + 1, plen); + /* Null terminate new string */ + part[plen] = '\0'; + + /* Terminate buf where separator was */ + *pr = '\0'; + + return part; +} + +/** + * Parses a string as semver expression. + * + * Returns: + * + * `0` - Parsed successfully + * `-1` - In case of error + */ +int semver_parse(const char *str, semver_t *ver) +{ + int valid, res; + size_t len; + char *buf; + bzero(ver, sizeof(semver_t)); + valid = semver_is_valid(str); + if (!valid) + return -1; + if (*str == 'v') + str++; + + len = strlen(str); + buf = (char *)calloc(len + 1, sizeof(*buf)); + if (buf == NULL) + return -1; + strcpy(buf, str); + + ver->metadata = parse_slice(buf, MT_DELIMITER[0]); + ver->prerelease = parse_slice(buf, PR_DELIMITER[0]); + + res = semver_parse_version(buf, ver); +#if DEBUG > 0 + printf("[debug] semver.c %s (%s) = %d.%d.%d - %d\n", str, buf, ver->major, ver->minor, ver->patch, res); + if (ver->prerelease) + printf("[debug] semver.c prerelease: %s\n", ver->prerelease); + if (ver->metadata) + printf("[debug] semver.c metadata: %s\n", ver->metadata); + // printf("[debug] semver.c %s = %d.%d.%d, %s %s\n", str, ver->major, ver->minor, ver->patch, ver->prerelease, ver->metadata); +#endif + free(buf); + return res; +} + +/** + * Parses a given string as semver expression. + * + * Returns: + * + * `0` - Parsed successfully + * `-1` - Parse error or invalid + */ + +int semver_parse_version(const char *str, semver_t *ver) +{ + size_t len; + int index, value; + char *slice, *next, *endptr; + slice = (char *)str; + index = 0; + + while (slice != NULL && index++ < 4) + { + next = strchr(slice, DELIMITER[0]); + if (next == NULL) + len = strlen(slice); + else + len = next - slice; + if (len > SLICE_SIZE) + return -1; + + /* Cast to integer and store */ + value = strtol(slice, &endptr, 10); + if (endptr != next && *endptr != '\0') + return -1; + + switch (index) + { + case 1: + ver->major = value; + break; + case 2: + ver->minor = value; + break; + case 3: + ver->patch = value; + break; + } + + /* Continue with the next slice */ + if (next == NULL) + slice = NULL; + else + slice = next + 1; + } + + return 0; +} + +static int +compare_prerelease(char *x, char *y) +{ + char *lastx, *lasty, *xptr, *yptr, *endptr; + int xlen, ylen, xisnum, yisnum, xnum, ynum; + int xn, yn, min, res; + if (x == NULL && y == NULL) + return 0; + if (y == NULL && x) + return -1; + if (x == NULL && y) + return 1; + + lastx = x; + lasty = y; + xlen = strlen(x); + ylen = strlen(y); + + while (1) + { + if ((xptr = strchr(lastx, DELIMITER[0])) == NULL) + xptr = x + xlen; + if ((yptr = strchr(lasty, DELIMITER[0])) == NULL) + yptr = y + ylen; + + xnum = strtol(lastx, &endptr, 10); + xisnum = endptr == xptr ? 1 : 0; + ynum = strtol(lasty, &endptr, 10); + yisnum = endptr == yptr ? 1 : 0; + + if (xisnum && !yisnum) + return -1; + if (!xisnum && yisnum) + return 1; + + if (xisnum && yisnum) + { + /* Numerical comparison */ + if (xnum != ynum) + return xnum < ynum ? -1 : 1; + } + else + { + /* String comparison */ + xn = xptr - lastx; + yn = yptr - lasty; + min = xn < yn ? xn : yn; + if ((res = strncmp(lastx, lasty, min))) + return res < 0 ? -1 : 1; + if (xn != yn) + return xn < yn ? -1 : 1; + } + + lastx = xptr + 1; + lasty = yptr + 1; + if (lastx == x + xlen + 1 && lasty == y + ylen + 1) + break; + if (lastx == x + xlen + 1) + return -1; + if (lasty == y + ylen + 1) + return 1; + } + + return 0; +} + +int semver_compare_prerelease(semver_t x, semver_t y) +{ + return compare_prerelease(x.prerelease, y.prerelease); +} + +/** + * Performs a major, minor and patch binary comparison (x, y). + * This function is mostly used internally + * + * Returns: + * + * `0` - If versiona are equal + * `1` - If x is higher than y + * `-1` - If x is lower than y + */ + +int semver_compare_version(semver_t x, semver_t y) +{ + int res; + + if ((res = binary_comparison(x.major, y.major)) == 0) + { + if ((res = binary_comparison(x.minor, y.minor)) == 0) + { + return binary_comparison(x.patch, y.patch); + } + } + + return res; +} + +/** + * Compare two semantic versions (x, y). + * + * Returns: + * - `1` if x is higher than y + * - `0` if x is equal to y + * - `-1` if x is lower than y + */ + +int semver_compare(semver_t x, semver_t y) +{ + int res; + + if ((res = semver_compare_version(x, y)) == 0) + { + return semver_compare_prerelease(x, y); + } + + return res; +} + +/** + * Performs a `greater than` comparison + */ + +int semver_gt(semver_t x, semver_t y) +{ + return semver_compare(x, y) == 1; +} + +/** + * Performs a `lower than` comparison + */ + +int semver_lt(semver_t x, semver_t y) +{ + return semver_compare(x, y) == -1; +} + +/** + * Performs a `equality` comparison + */ + +int semver_eq(semver_t x, semver_t y) +{ + return semver_compare(x, y) == 0; +} + +/** + * Performs a `non equal to` comparison + */ + +int semver_neq(semver_t x, semver_t y) +{ + return semver_compare(x, y) != 0; +} + +/** + * Performs a `greater than or equal` comparison + */ + +int semver_gte(semver_t x, semver_t y) +{ + return semver_compare(x, y) >= 0; +} + +/** + * Performs a `lower than or equal` comparison + */ + +int semver_lte(semver_t x, semver_t y) +{ + return semver_compare(x, y) <= 0; +} + +/** + * Checks if version `x` can be satisfied by `y` + * performing a comparison with caret operator. + * + * See: https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4 + * + * Returns: + * + * `1` - Can be satisfied + * `0` - Cannot be satisfied + */ + +int semver_satisfies_caret(semver_t x, semver_t y) +{ + /* Major versions must always match. */ + if (x.major == y.major) + { + /* If major version is 0, minor versions must match */ + if (x.major == 0) + { + /* If minor version is 0, patch must match */ + if (x.minor == 0) + { + return (x.minor == y.minor) && (x.patch == y.patch); + } + /* If minor version is not 0, patch must be >= */ + else if (x.minor == y.minor) + { + return x.patch >= y.patch; + } + else + { + return 0; + } + } + else if (x.minor > y.minor) + { + return 1; + } + else if (x.minor == y.minor) + { + return x.patch >= y.patch; + } + else + { + return 0; + } + } + return 0; +} + +/** + * Checks if version `x` can be satisfied by `y` + * performing a comparison with tilde operator. + * + * See: https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1 + * + * Returns: + * + * `1` - Can be satisfied + * `0` - Cannot be satisfied + */ + +int semver_satisfies_patch(semver_t x, semver_t y) +{ + return x.major == y.major && x.minor == y.minor; +} + +/** + * Checks if both versions can be satisfied + * based on the given comparison operator. + * + * Allowed operators: + * + * - `=` - Equality + * - `>=` - Higher or equal to + * - `<=` - Lower or equal to + * - `<` - Lower than + * - `>` - Higher than + * - `^` - Caret comparison (see https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4) + * - `~` - Tilde comparison (see https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1) + * + * Returns: + * + * `1` - Can be satisfied + * `0` - Cannot be satisfied + */ + +int semver_satisfies(semver_t x, semver_t y, const char *op) +{ + int first, second; + /* Extract the comparison operator */ + first = op[0]; + second = op[1]; + + /* Caret operator */ + if (first == SYMBOL_CF) + return semver_satisfies_caret(x, y); + + /* Tilde operator */ + if (first == SYMBOL_TF) + return semver_satisfies_patch(x, y); + + /* Strict equality */ + if (first == SYMBOL_EQ) + return semver_eq(x, y); + + /* Greater than or equal comparison */ + if (first == SYMBOL_GT) + { + if (second == SYMBOL_EQ) + { + return semver_gte(x, y); + } + return semver_gt(x, y); + } + + /* Lower than or equal comparison */ + if (first == SYMBOL_LT) + { + if (second == SYMBOL_EQ) + { + return semver_lte(x, y); + } + return semver_lt(x, y); + } + + return 0; +} + +/** + * Free heep allocated memory of a given semver. + * This is just a convenient function that you + * should call when you're done. + */ + +void semver_free(semver_t *x) +{ + if (x->metadata) + { + free(x->metadata); + x->metadata = NULL; + } + if (x->prerelease) + { + free(x->prerelease); + x->prerelease = NULL; + } +} + +/** + * Renders + */ + +static void +concat_num(char *str, int x, const char *sep) +{ + char buf[SLICE_SIZE] = {0}; + if (sep == NULL) + sprintf(buf, "%d", x); + else + sprintf(buf, "%s%d", sep, x); + strcat(str, buf); +} + +static void +concat_char(char *str, char *x, const char *sep) +{ + char buf[SLICE_SIZE] = {0}; + sprintf(buf, "%s%s", sep, x); + strcat(str, buf); +} + +/** + * Render a given semver as string + */ + +void semver_render(semver_t *x, char *dest) +{ + concat_num(dest, x->major, NULL); + concat_num(dest, x->minor, DELIMITER); + concat_num(dest, x->patch, DELIMITER); + if (x->prerelease) + concat_char(dest, x->prerelease, PR_DELIMITER); + if (x->metadata) + concat_char(dest, x->metadata, MT_DELIMITER); +} + +/** + * Version bump helpers + */ + +void semver_bump(semver_t *x) +{ + x->major++; +} + +void semver_bump_minor(semver_t *x) +{ + x->minor++; +} + +void semver_bump_patch(semver_t *x) +{ + x->patch++; +} + +/** + * Helpers + */ + +static int +has_valid_length(const char *s) +{ + return strlen(s) <= MAX_SIZE; +} + +/** + * Checks if a given semver string is valid + * + * Returns: + * + * `1` - Valid expression + * `0` - Invalid + */ + +int semver_is_valid(const char *s) +{ + return has_valid_length(s) && has_valid_chars(s, VALID_CHARS); +} + +/** + * Removes non-valid characters in the given string. + * + * Returns: + * + * `0` - Valid + * `-1` - Invalid input + */ + +int semver_clean(char *s) +{ + size_t i, len, mlen; + int res; + if (has_valid_length(s) == 0) + return -1; + + len = strlen(s); + mlen = strlen(VALID_CHARS); + + for (i = 0; i < len; i++) + { + if (contains(s[i], VALID_CHARS, mlen) == 0) + { + res = strcut(s, i, 1); + if (res == -1) + return -1; + --len; + --i; + } + } + + return 0; +} + +static int +char_to_int(const char *str) +{ + int buf; + size_t i, len, mlen; + buf = 0; + len = strlen(str); + mlen = strlen(VALID_CHARS); + + for (i = 0; i < len; i++) + if (contains(str[i], VALID_CHARS, mlen)) + buf += (int)str[i]; + + return buf; +} + +/** + * Render a given semver as numeric value. + * Useful for ordering and filtering. + */ + +int semver_numeric(semver_t *x) +{ + int num; + char buf[SLICE_SIZE * 3]; + memset(&buf, 0, SLICE_SIZE * 3); + + if (x->major) + concat_num(buf, x->major, NULL); + if (x->major || x->minor) + concat_num(buf, x->minor, NULL); + if (x->major || x->minor || x->patch) + concat_num(buf, x->patch, NULL); + + num = parse_int(buf); + if (num == -1) + return -1; + + if (x->prerelease) + num += char_to_int(x->prerelease); + if (x->metadata) + num += char_to_int(x->metadata); + + return num; +} \ No newline at end of file diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/SemVer/semver.h b/ESP/lib/src/network/OTA/Github_OTA/lib/SemVer/semver.h new file mode 100644 index 0000000..5b3e5c7 --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/lib/SemVer/semver.h @@ -0,0 +1,107 @@ +/* + * semver.h + * + * Copyright (c) 2015-2017 Tomas Aparicio + * MIT licensed + */ + +#ifndef __SEMVER_H +#define __SEMVER_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifndef SEMVER_VERSION +#define SEMVER_VERSION "0.2.0" +#endif + + /** + * semver_t struct + */ + + typedef struct semver_version_s + { + int major; + int minor; + int patch; + char *metadata; + char *prerelease; + } semver_t; + + /** + * Set prototypes + */ + + int + semver_satisfies(semver_t x, semver_t y, const char *op); + + int + semver_satisfies_caret(semver_t x, semver_t y); + + int + semver_satisfies_patch(semver_t x, semver_t y); + + int + semver_compare(semver_t x, semver_t y); + + int + semver_compare_version(semver_t x, semver_t y); + + int + semver_compare_prerelease(semver_t x, semver_t y); + + int + semver_gt(semver_t x, semver_t y); + + int + semver_gte(semver_t x, semver_t y); + + int + semver_lt(semver_t x, semver_t y); + + int + semver_lte(semver_t x, semver_t y); + + int + semver_eq(semver_t x, semver_t y); + + int + semver_neq(semver_t x, semver_t y); + + int + semver_parse(const char *str, semver_t *ver); + + int + semver_parse_version(const char *str, semver_t *ver); + + void + semver_render(semver_t *x, char *dest); + + int + semver_numeric(semver_t *x); + + void + semver_bump(semver_t *x); + + void + semver_bump_minor(semver_t *x); + + void + semver_bump_patch(semver_t *x); + + void + semver_free(semver_t *x); + + int + semver_is_valid(const char *s); + + int + semver_clean(char *s); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/esp_ota.c b/ESP/lib/src/network/OTA/Github_OTA/lib/esp_ota.c new file mode 100644 index 0000000..6861279 --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/lib/esp_ota.c @@ -0,0 +1,861 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "esp_ota.h" +#include "LWJSON/lwjson.h" + +ESP_EVENT_DEFINE_BASE(Github_OTA_EVENTS); + +typedef struct Github_OTA_client_handle_t +{ + Github_OTA_config_t config; + char *username; + char *token; + struct + { + char tag_name[CONFIG_MAX_FILENAME_LEN]; + char name[CONFIG_MAX_FILENAME_LEN]; + char url[CONFIG_MAX_URL_LEN]; + char storageurl[CONFIG_MAX_URL_LEN]; + uint8_t flags; + } result; + struct + { + char name[CONFIG_MAX_FILENAME_LEN]; + char url[CONFIG_MAX_URL_LEN]; + } scratch; + semver_t current_version; + semver_t latest_version; + uint32_t countdown; + TaskHandle_t task_handle; + const esp_partition_t *storage_partition; +} Github_OTA_client_handle_t; + +enum release_flags +{ + Github_OTA_RELEASE_GOT_TAG = 0x01, + Github_OTA_RELEASE_GOT_FNAME = 0x02, + Github_OTA_RELEASE_GOT_URL = 0x04, + Github_OTA_RELEASE_GOT_STORAGE = 0x08, + Github_OTA_RELEASE_VALID_ASSET = 0x10, +} release_flags; + +SemaphoreHandle_t Github_OTA_lock = NULL; + +static void SetFlag(Github_OTA_client_handle_t *handle, enum release_flags flag) +{ + handle->result.flags |= flag; +} +static bool GetFlag(Github_OTA_client_handle_t *handle, enum release_flags flag) +{ + return handle->result.flags & flag; +} + +static void ClearFlag(Github_OTA_client_handle_t *handle, enum release_flags flag) +{ + handle->result.flags &= ~flag; +} + +Github_OTA_client_handle_t *Github_OTA_init(Github_OTA_config_t *newconfig) +{ + if (!Github_OTA_lock) + { + Github_OTA_lock = xSemaphoreCreateMutex(); + } + if (xSemaphoreTake(Github_OTA_lock, pdMS_TO_TICKS(1000)) != pdPASS) + { + ESP_LOGE(TAG, "Failed to take lock"); + return NULL; + } + Github_OTA_client_handle_t *handle = malloc(sizeof(Github_OTA_client_handle_t)); + if (handle == NULL) + { + ESP_LOGE(TAG, "Failed to allocate memory for client handle"); + xSemaphoreGive(Github_OTA_lock); + return NULL; + } + bzero(handle, sizeof(Github_OTA_client_handle_t)); + strncpy(handle->config.filenamematch, newconfig->filenamematch, CONFIG_MAX_FILENAME_LEN); + strncpy(handle->config.storagenamematch, newconfig->storagenamematch, CONFIG_MAX_FILENAME_LEN); + strncpy(handle->config.storagepartitionname, newconfig->storagepartitionname, 17); + if (newconfig->hostname == NULL) + asprintf(&handle->config.hostname, CONFIG_GITHUB_HOSTNAME); + else + asprintf(&handle->config.hostname, newconfig->hostname); + + if (newconfig->orgname == NULL) + asprintf(&handle->config.orgname, CONFIG_GITHUB_OWNER); + else + asprintf(&handle->config.orgname, newconfig->orgname); + + if (newconfig->reponame == NULL) + asprintf(&handle->config.reponame, CONFIG_GITHUB_REPO); + else + asprintf(&handle->config.reponame, newconfig->reponame); + + const esp_app_desc_t *app_desc = esp_ota_get_app_description(); + if (semver_parse(app_desc->version, &handle->current_version)) + { + ESP_LOGE(TAG, "Failed to parse current version"); + Github_OTA_free(handle); + xSemaphoreGive(Github_OTA_lock); + return NULL; + } + handle->result.flags = 0; + + // if (newconfig->updateInterval < 60) { + // ESP_LOGE(TAG, "Update interval must be at least 60 Minutes"); + // newconfig->updateInterval = 60; + // } + handle->config.updateInterval = newconfig->updateInterval; + + handle->task_handle = NULL; + xSemaphoreGive(Github_OTA_lock); + + return handle; +} + +esp_err_t Github_OTA_free(Github_OTA_client_handle_t *handle) +{ + if (xSemaphoreTake(Github_OTA_lock, pdMS_TO_TICKS(1000)) != pdPASS) + { + ESP_LOGE(TAG, "Failed to take lock"); + return ESP_FAIL; + } + free(handle->config.hostname); + free(handle->config.orgname); + free(handle->config.reponame); + if (handle->username) + free(handle->username); + if (handle->token) + free(handle->token); + semver_free(&handle->current_version); + semver_free(&handle->latest_version); + xSemaphoreGive(Github_OTA_lock); + return ESP_OK; +} + +esp_err_t Github_OTA_set_auth(Github_OTA_client_handle_t *handle, const char *username, const char *password) +{ + if (xSemaphoreTake(Github_OTA_lock, pdMS_TO_TICKS(1000)) != pdPASS) + { + ESP_LOGE(TAG, "Failed to take lock"); + return ESP_FAIL; + } + asprintf(&handle->username, "%s", username); + asprintf(&handle->token, "%s", password); + xSemaphoreGive(Github_OTA_lock); + return ESP_OK; +} + +static void lwjson_callback(lwjson_stream_parser_t *jsp, lwjson_stream_type_t type) +{ + if (jsp->udata == NULL) + { + ESP_LOGE(TAG, "No user data for callback"); + return; + } + Github_OTA_client_handle_t *handle = (Github_OTA_client_handle_t *)jsp->udata; +#ifdef DEBUG + ESP_LOGI(TAG, "Lwjson Called: %d %d %d %d", jsp->stack_pos, jsp->stack[jsp->stack_pos - 1].type, type, handle->result.flags); + if (jsp->stack[jsp->stack_pos - 1].type == LWJSON_STREAM_TYPE_KEY) + { /* We need key to be before */ + ESP_LOGI(TAG, "Key: %s", jsp->stack[jsp->stack_pos - 1].meta.name); + } +#endif + /* Get a value corresponsing to "tag_name" key */ + if (!GetFlag(handle, Github_OTA_RELEASE_GOT_TAG)) + { + if (jsp->stack_pos >= 2 /* Number of stack entries must be high */ + && jsp->stack[0].type == LWJSON_STREAM_TYPE_OBJECT /* First must be object */ + && jsp->stack[1].type == LWJSON_STREAM_TYPE_KEY /* We need key to be before */ + && strcasecmp(jsp->stack[1].meta.name, "tag_name") == 0) + { + ESP_LOGD(TAG, "Got '%s' with value '%s'", jsp->stack[1].meta.name, jsp->data.str.buff); + strncpy(handle->result.tag_name, jsp->data.str.buff, CONFIG_MAX_FILENAME_LEN); + SetFlag(handle, Github_OTA_RELEASE_GOT_TAG); + } + } + if (!GetFlag(handle, Github_OTA_RELEASE_VALID_ASSET) || !GetFlag(handle, Github_OTA_RELEASE_GOT_STORAGE)) + { + if (jsp->stack_pos == 5 && jsp->stack[0].type == LWJSON_STREAM_TYPE_OBJECT && jsp->stack[1].type == LWJSON_STREAM_TYPE_KEY && strcasecmp(jsp->stack[1].meta.name, "assets") == 0 && jsp->stack[2].type == LWJSON_STREAM_TYPE_ARRAY && jsp->stack[3].type == LWJSON_STREAM_TYPE_OBJECT && jsp->stack[4].type == LWJSON_STREAM_TYPE_KEY) + { + ESP_LOGD(TAG, "Assets Got key '%s' with value '%s'", jsp->stack[jsp->stack_pos - 1].meta.name, jsp->data.str.buff); + if (strcasecmp(jsp->stack[4].meta.name, "name") == 0) + { + strncpy(handle->scratch.name, jsp->data.str.buff, CONFIG_MAX_FILENAME_LEN); + SetFlag(handle, Github_OTA_RELEASE_GOT_FNAME); + ESP_LOGD(TAG, "Got Filename for Asset: %s", handle->scratch.name); + } + if (strcasecmp(jsp->stack[4].meta.name, "url") == 0) + { + strncpy(handle->scratch.url, jsp->data.str.buff, CONFIG_MAX_URL_LEN); + SetFlag(handle, Github_OTA_RELEASE_GOT_URL); + ESP_LOGD(TAG, "Got URL for Asset: %s", handle->scratch.url); + } + /* Now test if we got both name an download url */ + if (GetFlag(handle, Github_OTA_RELEASE_GOT_FNAME) && GetFlag(handle, Github_OTA_RELEASE_GOT_URL)) + { + ESP_LOGD(TAG, "Testing Firmware filenames %s -> %s - Matching Filename against %s and %s", handle->scratch.name, handle->scratch.url, handle->config.filenamematch, handle->config.storagenamematch); + /* see if the filename matches */ + if (!GetFlag(handle, Github_OTA_RELEASE_VALID_ASSET) && fnmatch(handle->config.filenamematch, handle->scratch.name, 0) == 0) + { + strncpy(handle->result.name, handle->scratch.name, CONFIG_MAX_FILENAME_LEN); + strncpy(handle->result.url, handle->scratch.url, CONFIG_MAX_URL_LEN); + ESP_LOGD(TAG, "Valid Firmware Found: %s - %s", handle->result.name, handle->result.url); + SetFlag(handle, Github_OTA_RELEASE_VALID_ASSET); + } + else if (!GetFlag(handle, Github_OTA_RELEASE_GOT_STORAGE) && fnmatch(handle->config.storagenamematch, handle->scratch.name, 0) == 0) + { + strncpy(handle->result.storageurl, handle->scratch.url, CONFIG_MAX_URL_LEN); + ESP_LOGD(TAG, "Valid Storage Asset Found: %s - %s", handle->scratch.name, handle->result.storageurl); + SetFlag(handle, Github_OTA_RELEASE_GOT_STORAGE); + } + else + { + ESP_LOGD(TAG, "Invalid Asset Found: %s", handle->scratch.name); + ClearFlag(handle, Github_OTA_RELEASE_GOT_FNAME); + ClearFlag(handle, Github_OTA_RELEASE_GOT_URL); + } + } + } + } +} + +static esp_err_t _http_event_handler(esp_http_client_event_t *evt) +{ + lwjsonr_t res; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (evt->event_id) + { + case HTTP_EVENT_ON_HEADER: + if (strncasecmp(evt->header_key, "x-ratelimit-remaining", strlen("x-ratelimit-remaining")) == 0) + { + int limit = atoi(evt->header_value); + ESP_LOGD(TAG, "Github API Rate Limit Remaining: %d", limit); + if (limit < 10) + { + ESP_LOGW(TAG, "Github API Rate Limit Remaining is low: %d", limit); + } + } + break; + case HTTP_EVENT_ON_DATA: + if (!esp_http_client_is_chunked_response(evt->client)) + { + char *buf = evt->data; + for (int i = 0; i < evt->data_len; i++) + { + res = lwjson_stream_parse((lwjson_stream_parser_t *)evt->user_data, *buf); + if (!(res == lwjsonOK || res == lwjsonSTREAMDONE || res == lwjsonSTREAMINPROG)) + { + ESP_LOGE(TAG, "Lwjson Error: %d", res); + } + buf++; + } + } + break; + case HTTP_EVENT_DISCONNECTED: + { + int mbedtls_err = 0; + esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL); + if (err != 0) + { + ESP_LOGE(TAG, "Last esp error code: 0x%x", err); + ESP_LOGE(TAG, "Last mbedtls failure: 0x%x", mbedtls_err); + } + break; + } + } +#pragma GCC diagnostic pop + return ESP_OK; +} + +esp_err_t Github_OTA_check(Github_OTA_client_handle_t *handle) +{ + if (xSemaphoreTake(Github_OTA_lock, pdMS_TO_TICKS(1000)) != pdPASS) + { + ESP_LOGE(TAG, "Failed to get lock"); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Checking for new release"); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_START_CHECK, handle, sizeof(Github_OTA_client_handle_t *), portMAX_DELAY)); + lwjson_stream_parser_t stream_parser; + lwjsonr_t res; + + res = lwjson_stream_init(&stream_parser, lwjson_callback); + if (res != lwjsonOK) + { + ESP_LOGE(TAG, "Failed to initialize JSON parser: %d", res); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_NOUPDATE_AVAILABLE, handle, sizeof(Github_OTA_client_handle_t *), portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + stream_parser.udata = (void *)handle; + + char url[CONFIG_MAX_URL_LEN]; + snprintf(url, CONFIG_MAX_URL_LEN, "https://%s/repos/%s/%s/releases/latest", handle->config.hostname, handle->config.orgname, handle->config.reponame); + + esp_http_client_config_t httpconfig = { + .url = url, + .crt_bundle_attach = esp_crt_bundle_attach, + .event_handler = _http_event_handler, + .user_data = &stream_parser, + }; + if (handle->username) + { + ESP_LOGD(TAG, "Using Authenticated Request to %s", url); + httpconfig.username = handle->username; + httpconfig.password = handle->token; + httpconfig.auth_type = HTTP_AUTH_TYPE_BASIC; + } + ESP_LOGI(TAG, "Searching for Firmware from %s", url); + + esp_http_client_handle_t client = esp_http_client_init(&httpconfig); + + esp_err_t err = esp_http_client_perform(client); + if (err == ESP_OK) + { + ESP_LOGD(TAG, "HTTP GET Status = %d, content_length = %d", + esp_http_client_get_status_code(client), + esp_http_client_get_content_length(client)); + } + else + { + ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err)); + esp_http_client_cleanup(client); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_NOUPDATE_AVAILABLE, handle, sizeof(Github_OTA_client_handle_t *), portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + if (esp_http_client_get_status_code(client) == 200) + { + if (GetFlag(handle, Github_OTA_RELEASE_VALID_ASSET)) + { + if (semver_parse(handle->result.tag_name, &handle->latest_version)) + { + ESP_LOGE(TAG, "Failed to parse new version"); + esp_http_client_cleanup(client); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_NOUPDATE_AVAILABLE, handle, sizeof(Github_OTA_client_handle_t *), portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Current Version %d.%d.%d", handle->current_version.major, handle->current_version.minor, handle->current_version.patch); + ESP_LOGI(TAG, "New Version %d.%d.%d", handle->latest_version.major, handle->latest_version.minor, handle->latest_version.patch); + ESP_LOGI(TAG, "Asset: %s", handle->result.name); + ESP_LOGI(TAG, "Firmware URL: %s", handle->result.url); + if (strlen(handle->result.storageurl)) + { + ESP_LOGI(TAG, "Storage URL: %s", handle->result.storageurl); + } + } + else + { + ESP_LOGI(TAG, "Asset: No Valid Firmware Assets Found"); + esp_http_client_cleanup(client); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_NOUPDATE_AVAILABLE, handle, sizeof(Github_OTA_client_handle_t *), portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + } + else + { + ESP_LOGW(TAG, "Github Release API Returned: %d", esp_http_client_get_status_code(client)); + esp_http_client_cleanup(client); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_NOUPDATE_AVAILABLE, handle, sizeof(Github_OTA_client_handle_t *), portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + + esp_http_client_cleanup(client); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_UPDATE_AVAILABLE, handle, sizeof(Github_OTA_client_handle_t *), portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_OK; +} + +static esp_err_t validate_image_header(esp_app_desc_t *new_app_info) +{ + if (new_app_info == NULL) + { + return ESP_ERR_INVALID_ARG; + } + ESP_LOGI(TAG, "New Firmware Details:"); + ESP_LOGI(TAG, "Project name: %s", new_app_info->project_name); + ESP_LOGI(TAG, "Firmware version: %s", new_app_info->version); + ESP_LOGI(TAG, "Compiled time: %s %s", new_app_info->date, new_app_info->time); + ESP_LOGI(TAG, "ESP-IDF: %s", new_app_info->idf_ver); + ESP_LOGI(TAG, "SHA256:"); + ESP_LOG_BUFFER_HEX(TAG, new_app_info->app_elf_sha256, sizeof(new_app_info->app_elf_sha256)); + + const esp_partition_t *running = esp_ota_get_running_partition(); + ESP_LOGD(TAG, "Current partition %s type %d subtype %d (offset 0x%08x)", + running->label, running->type, running->subtype, running->address); + const esp_partition_t *update = esp_ota_get_next_update_partition(NULL); + ESP_LOGD(TAG, "Update partition %s type %d subtype %d (offset 0x%08x)", + update->label, update->type, update->subtype, update->address); + +#ifdef CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK + /** + * Secure version check from firmware image header prevents subsequent download and flash write of + * entire firmware image. However this is optional because it is also taken care in API + * esp_https_ota_finish at the end of OTA update procedure. + */ + const uint32_t hw_sec_version = esp_efuse_read_secure_version(); + if (new_app_info->secure_version < hw_sec_version) + { + ESP_LOGW(TAG, "New firmware security version is less than eFuse programmed, %d < %d", new_app_info->secure_version, hw_sec_version); + return ESP_FAIL; + } +#endif + + return ESP_OK; +} + +static esp_err_t http_client_set_header_cb(esp_http_client_handle_t http_client) +{ + return esp_http_client_set_header(http_client, "Accept", "application/octet-stream"); +} + +esp_err_t _http_event_storage_handler(esp_http_client_event_t *evt) +{ + static int output_pos; + static int last_progress; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + switch (evt->event_id) + { + case HTTP_EVENT_ON_CONNECTED: + { + output_pos = 0; + last_progress = 0; + /* Erase the Partition */ + break; + } + case HTTP_EVENT_ON_DATA: + if (!esp_http_client_is_chunked_response(evt->client)) + { + Github_OTA_client_handle_t *handle = (Github_OTA_client_handle_t *)evt->user_data; + if (output_pos == 0) + { + ESP_LOGD(TAG, "Erasing Partition"); + ESP_ERROR_CHECK(esp_partition_erase_range(handle->storage_partition, 0, handle->storage_partition->size)); + ESP_LOGD(TAG, "Erasing Complete"); + } + + ESP_ERROR_CHECK(esp_partition_write(handle->storage_partition, output_pos, evt->data, evt->data_len)); + output_pos += evt->data_len; + int progress = 100 * ((float)output_pos / (float)handle->storage_partition->size); + if ((progress % 5 == 0) && (progress != last_progress)) + { + ESP_LOGV(TAG, "Storage Firmware Update Progress: %d%%", progress); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_STORAGE_UPDATE_PROGRESS, &progress, sizeof(progress), portMAX_DELAY)); + last_progress = progress; + } + } + break; + case HTTP_EVENT_DISCONNECTED: + { + int mbedtls_err = 0; + esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL); + if (err != 0) + { + ESP_LOGE(TAG, "Last esp error code: 0x%x", err); + ESP_LOGE(TAG, "Last mbedtls failure: 0x%x", mbedtls_err); + } + break; + } + } +#pragma GCC diagnostic pop + return ESP_OK; +} + +esp_err_t Github_OTA_storage_update(Github_OTA_client_handle_t *handle) +{ + if (xSemaphoreTake(Github_OTA_lock, pdMS_TO_TICKS(1000)) != pdTRUE) + { + ESP_LOGE(TAG, "Failed to take lock"); + return ESP_FAIL; + } + if (handle == NULL) + { + ESP_LOGE(TAG, "Invalid Handle"); + xSemaphoreGive(Github_OTA_lock); + return ESP_ERR_INVALID_ARG; + } + if (!strlen(handle->result.storageurl)) + { + ESP_LOGE(TAG, "No Storage URL"); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + if (!strlen(handle->config.storagepartitionname)) + { + ESP_LOGE(TAG, "No Storage Partition Name"); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + handle->storage_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, handle->config.storagepartitionname); + if (handle->storage_partition == NULL) + { + ESP_LOGE(TAG, "Storage Partition Not Found"); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + ESP_LOGD(TAG, "Storage Partition %s - Type %x Subtype %x Found at %x - size %d", handle->storage_partition->label, handle->storage_partition->type, handle->storage_partition->subtype, handle->storage_partition->address, handle->storage_partition->size); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_START_STORAGE_UPDATE, NULL, 0, portMAX_DELAY)); + /* give time for the system to react, such as unmounting the filesystems etc */ + vTaskDelay(pdMS_TO_TICKS(1000)); + + esp_http_client_config_t config = { + .url = handle->result.storageurl, + .event_handler = _http_event_storage_handler, + .crt_bundle_attach = esp_crt_bundle_attach, + .user_data = handle, + .buffer_size_tx = 2048, + + }; + if (handle->username) + { + ESP_LOGD(TAG, "Using Authenticated Request to %s", config.url); + config.username = handle->username; + config.password = handle->token; + config.auth_type = HTTP_AUTH_TYPE_BASIC; + } + esp_http_client_handle_t client = esp_http_client_init(&config); + ESP_ERROR_CHECK(esp_http_client_set_header(client, "Accept", "application/octet-stream")); + esp_err_t err = esp_http_client_perform(client); + if (err == ESP_OK) + { + ESP_LOGD(TAG, "HTTP GET Status = %d, content_length = %d", + esp_http_client_get_status_code(client), + esp_http_client_get_content_length(client)); + uint8_t sha256[32] = {0}; + ESP_ERROR_CHECK(esp_partition_get_sha256(handle->storage_partition, sha256)); + ESP_LOG_BUFFER_HEX("New Storage Partition SHA256:", sha256, sizeof(sha256)); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_FINISH_STORAGE_UPDATE, NULL, 0, portMAX_DELAY)); + } + else + { + ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err)); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_STORAGE_UPDATE_FAILED, NULL, 0, portMAX_DELAY)); + } + esp_http_client_cleanup(client); + xSemaphoreGive(Github_OTA_lock); + return ESP_OK; +} + +esp_err_t Github_OTA_update(Github_OTA_client_handle_t *handle) +{ + esp_err_t ota_finish_err = ESP_OK; + if (xSemaphoreTake(Github_OTA_lock, pdMS_TO_TICKS(1000)) != pdTRUE) + { + ESP_LOGE(TAG, "Failed to take lock"); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Scheduled Check for Firmware Update Starting"); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_START_UPDATE, NULL, 0, portMAX_DELAY)); + if (!GetFlag(handle, Github_OTA_RELEASE_VALID_ASSET)) + { + ESP_LOGE(TAG, "No Valid Release Asset Found"); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_UPDATE_FAILED, NULL, 0, portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + int cmp = semver_compare_version(handle->latest_version, handle->current_version); + if (cmp != 1) + { + ESP_LOGE(TAG, "Current Version is equal or newer than new release"); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_UPDATE_FAILED, NULL, 0, portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_OK; + } + esp_http_client_config_t httpconfig = { + .url = handle->result.url, + .crt_bundle_attach = esp_crt_bundle_attach, + .keep_alive_enable = true, + .buffer_size_tx = 4096, + }; + + if (handle->username) + { + ESP_LOGD(TAG, "Using Authenticated Request to %s", httpconfig.url); + httpconfig.username = handle->username; + httpconfig.password = handle->token; + httpconfig.auth_type = HTTP_AUTH_TYPE_BASIC; + } + + esp_https_ota_config_t ota_config = { + .http_config = &httpconfig, + .http_client_init_cb = http_client_set_header_cb, + }; + + esp_https_ota_handle_t https_ota_handle = NULL; + esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed: %d", err); + goto ota_end; + } + + esp_app_desc_t app_desc; + err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_https_ota_read_img_desc failed: %d", err); + goto ota_end; + } + err = validate_image_header(&app_desc); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "image header verification failed: %d", err); + goto ota_end; + } + int last_progress = -1; + while (1) + { + err = esp_https_ota_perform(https_ota_handle); + if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) + { + break; + } + int32_t dl = esp_https_ota_get_image_len_read(https_ota_handle); + int32_t size = esp_https_ota_get_image_size(https_ota_handle); + int progress = 100 * ((float)dl / (float)size); + if ((progress % 5 == 0) && (progress != last_progress)) + { + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_FIRMWARE_UPDATE_PROGRESS, &progress, sizeof(progress), portMAX_DELAY)); + ESP_LOGV(TAG, "Firmware Update Progress: %d%%", progress); + last_progress = progress; + } + } + + if (esp_https_ota_is_complete_data_received(https_ota_handle) != true) + { + // the OTA image was not completely received and user can customise the response to this situation. + ESP_LOGE(TAG, "Complete data was not received."); + } + else + { + ota_finish_err = esp_https_ota_finish(https_ota_handle); + if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) + { + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_FINISH_UPDATE, NULL, 0, portMAX_DELAY)); + if (strlen(handle->result.storageurl)) + { + xSemaphoreGive(Github_OTA_lock); + if (Github_OTA_storage_update(handle) == ESP_OK) + { + ESP_LOGI(TAG, "Storage Update Successful"); + } + else + { + ESP_LOGE(TAG, "Storage Update Failed"); + } + } + else + { + xSemaphoreGive(Github_OTA_lock); + } + ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ..."); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_PENDING_REBOOT, NULL, 0, portMAX_DELAY)); + vTaskDelay(1000 / portTICK_PERIOD_MS); + esp_restart(); + return ESP_OK; + } + else + { + if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) + { + ESP_LOGE(TAG, "Image validation failed, image is corrupted"); + } + ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed 0x%x", ota_finish_err); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_UPDATE_FAILED, NULL, 0, portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; + } + } + +ota_end: + esp_https_ota_abort(https_ota_handle); + ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed"); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_UPDATE_FAILED, NULL, 0, portMAX_DELAY)); + xSemaphoreGive(Github_OTA_lock); + return ESP_FAIL; +} + +semver_t *Github_OTA_get_current_version(Github_OTA_client_handle_t *handle) +{ + if (!handle) + { + return NULL; + } + semver_t *cur = malloc(sizeof(semver_t)); + memcpy(cur, &handle->current_version, sizeof(semver_t)); + return cur; +} + +semver_t *Github_OTA_get_latest_version(Github_OTA_client_handle_t *handle) +{ + if (!handle) + { + return NULL; + } + if (!GetFlag(handle, Github_OTA_RELEASE_VALID_ASSET)) + { + return NULL; + } + semver_t *new = malloc(sizeof(semver_t)); + memcpy(new, &handle->latest_version, sizeof(semver_t)); + return new; +} + +static void Github_OTA_task(void *pvParameters) +{ + Github_OTA_client_handle_t *handle = (Github_OTA_client_handle_t *)pvParameters; + ESP_LOGI(TAG, "Firmware Update Task Starting"); + if (handle) + { + if (Github_OTA_check(handle) == ESP_OK) + { + if (semver_gt(handle->latest_version, handle->current_version) == 1) + { + ESP_LOGI(TAG, "New Version Available"); + Github_OTA_update(handle); + } + else + { + ESP_LOGI(TAG, "No New Version Available"); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_NOUPDATE_AVAILABLE, NULL, 0, portMAX_DELAY)); + } + } + else + { + ESP_LOGI(TAG, "No Update Available"); + ESP_ERROR_CHECK(esp_event_post(Github_OTA_EVENTS, Github_OTA_EVENT_NOUPDATE_AVAILABLE, NULL, 0, portMAX_DELAY)); + } + } + ESP_LOGI(TAG, "Firmware Update Task Finished"); + vTaskDelete(handle->task_handle); + vTaskDelay(pdMS_TO_TICKS(1000)); + handle->task_handle = NULL; +} + +esp_err_t Github_OTA_start_update_task(Github_OTA_client_handle_t *handle) +{ + if (!handle) + { + return ESP_FAIL; + } + eTaskState state = eInvalid; + TaskHandle_t tmp = xTaskGetHandle("Github_OTA_task"); + if (tmp) + { + state = eTaskGetState(tmp); + } + if (state == eDeleted || state == eInvalid) + { + ESP_LOGD(TAG, "Starting Task to Check for Updates"); + if (xTaskCreate(Github_OTA_task, "Github_OTA_task", 6144, handle, 5, &handle->task_handle) != pdPASS) + { + ESP_LOGW(TAG, "Failed to Start Github_OTA_task"); + return ESP_FAIL; + } + } + else + { + ESP_LOGW(TAG, "Github_OTA_task Already Running"); + return ESP_FAIL; + } + return ESP_OK; +} + +static void Github_OTA_timer_callback(TimerHandle_t xTimer) +{ + Github_OTA_client_handle_t *handle = (Github_OTA_client_handle_t *)pvTimerGetTimerID(xTimer); + if (handle) + { + handle->countdown--; + if (handle->countdown == 0) + { + handle->countdown = handle->config.updateInterval; + Github_OTA_start_update_task(handle); + } + } +} + +esp_err_t Github_OTA_start_update_timer(Github_OTA_client_handle_t *handle) +{ + if (!handle) + { + ESP_LOGE(TAG, "Failed to initialize Github_OTA Client"); + return ESP_FAIL; + } + handle->countdown = handle->config.updateInterval; + + /* run timer every minute */ + uint64_t ticks = pdMS_TO_TICKS(1000) * 60; + TimerHandle_t timer = xTimerCreate("Github_OTA_timer", ticks, pdTRUE, (void *)handle, Github_OTA_timer_callback); + if (timer == NULL) + { + ESP_LOGE(TAG, "Failed to create timer"); + return ESP_FAIL; + } + else + { + if (xTimerStart(timer, 0) != pdPASS) + { + ESP_LOGE(TAG, "Failed to start timer"); + return ESP_FAIL; + } + else + { + ESP_LOGI(TAG, "Started Update Timer for %d Minutes", handle->config.updateInterval); + } + } + return ESP_OK; +} + +const char *Github_OTA_get_event_str(Github_OTA_event_e event) +{ + switch (event) + { + case Github_OTA_EVENT_START_CHECK: + return "Github_OTA_EVENT_START_CHECK"; + case Github_OTA_EVENT_UPDATE_AVAILABLE: + return "Github_OTA_EVENT_UPDATE_AVAILABLE"; + case Github_OTA_EVENT_NOUPDATE_AVAILABLE: + return "Github_OTA_EVENT_NOUPDATE_AVAILABLE"; + case Github_OTA_EVENT_START_UPDATE: + return "Github_OTA_EVENT_START_UPDATE"; + case Github_OTA_EVENT_FINISH_UPDATE: + return "Github_OTA_EVENT_FINISH_UPDATE"; + case Github_OTA_EVENT_UPDATE_FAILED: + return "Github_OTA_EVENT_UPDATE_FAILED"; + case Github_OTA_EVENT_START_STORAGE_UPDATE: + return "Github_OTA_EVENT_START_STORAGE_UPDATE"; + case Github_OTA_EVENT_FINISH_STORAGE_UPDATE: + return "Github_OTA_EVENT_FINISH_STORAGE_UPDATE"; + case Github_OTA_EVENT_STORAGE_UPDATE_FAILED: + return "Github_OTA_EVENT_STORAGE_UPDATE_FAILED"; + case Github_OTA_EVENT_FIRMWARE_UPDATE_PROGRESS: + return "Github_OTA_EVENT_FIRMWARE_UPDATE_PROGRESS"; + case Github_OTA_EVENT_STORAGE_UPDATE_PROGRESS: + return "Github_OTA_EVENT_STORAGE_UPDATE_PROGRESS"; + case Github_OTA_EVENT_PENDING_REBOOT: + return "Github_OTA_EVENT_PENDING_REBOOT"; + } + return "Unknown Event"; +} \ No newline at end of file diff --git a/ESP/lib/src/network/OTA/Github_OTA/lib/esp_ota.h b/ESP/lib/src/network/OTA/Github_OTA/lib/esp_ota.h new file mode 100644 index 0000000..5d2fb4d --- /dev/null +++ b/ESP/lib/src/network/OTA/Github_OTA/lib/esp_ota.h @@ -0,0 +1,159 @@ +#ifndef GITHUB_OTA_H +#define GITHUB_OTA_H + +#include +#include +#include "SemVer/semver.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + static const char *TAG = "Github_OTA"; + ESP_EVENT_DECLARE_BASE(Github_OTA_EVENTS); + + /** + * @brief Github OTA events + * These events are posted to the event loop to track progress of the OTA process + */ + typedef enum + { + Github_OTA_EVENT_START_CHECK = 0x01, /*!< Github OTA check started */ + Github_OTA_EVENT_UPDATE_AVAILABLE = 0x02, /*!< Github OTA update available */ + Github_OTA_EVENT_NOUPDATE_AVAILABLE = 0x04, /*!< Github OTA no update available */ + Github_OTA_EVENT_START_UPDATE = 0x08, /*!< Github OTA update started */ + Github_OTA_EVENT_FINISH_UPDATE = 0x10, /*!< Github OTA update finished */ + Github_OTA_EVENT_UPDATE_FAILED = 0x20, /*!< Github OTA update failed */ + Github_OTA_EVENT_START_STORAGE_UPDATE = 0x40, /*!< Github OTA storage update started. If the storage is mounted, you should unmount it when getting this call */ + Github_OTA_EVENT_FINISH_STORAGE_UPDATE = 0x80, /*!< Github OTA storage update finished. You can mount the new storage after getting this call if needed */ + Github_OTA_EVENT_STORAGE_UPDATE_FAILED = 0x100, /*!< Github OTA storage update failed */ + Github_OTA_EVENT_FIRMWARE_UPDATE_PROGRESS = 0x200, /*!< Github OTA firmware update progress */ + Github_OTA_EVENT_STORAGE_UPDATE_PROGRESS = 0x400, /*!< Github OTA storage update progress */ + Github_OTA_EVENT_PENDING_REBOOT = 0x800, /*!< Github OTA pending reboot */ + } Github_OTA_event_e; + + /** + * @brief Github OTA Configuration + */ + typedef struct Github_OTA_config_t + { + char *filenamematch; /*!< Filename to match against on Github indicating this is a firmware file */ + char storagenamematch[CONFIG_MAX_FILENAME_LEN]; /*!< Filename to match against on Github indicating this is a storage file */ + char storagepartitionname[17]; /*!< Name of the storage partition to update */ + char *hostname; /*!< Hostname of the Github server. Defaults to api.github.com*/ + char *orgname; /*!< Name of the Github organization */ + char *reponame; /*!< Name of the Github repository */ + uint32_t updateInterval; /*!< Interval in Minutes to check for updates if using the Github_OTA_start_update_timer function */ + } Github_OTA_config_t; + + typedef struct Github_OTA_client_handle_t Github_OTA_client_handle_t; + + /** + * @brief Initialize the github ota client + * + * + * @param config [in] Configuration for the github ota client + * @return Github_OTA_client_handle_t* handle to pass to all subsequent calls. If it returns NULL, there is a error in your config + */ + Github_OTA_client_handle_t *Github_OTA_init(Github_OTA_config_t *config); + + /** + * @brief Set the Username and Password to access private repositories or get more API calls + * + * Anonymus API calls are limited to 60 per hour. If you want to get more calls, you need to set a username and password. + * Be aware that this will be stored in the flash and can be read by anyone with access to the flash. + * The password should be a Github Personal Access Token and for good security you should limit what it can do + * + * @param handle the handle returned by Github_OTA_init + * @param username the username to authenticate with + * @param password this Github Personal Access Token + * @return esp_err_t ESP_OK if all is good, ESP_FAIL if there is an error + */ + esp_err_t Github_OTA_set_auth(Github_OTA_client_handle_t *handle, const char *username, const char *password); + /** + * @brief Free the Github_OTA client handle and all resources + * + * @param handle the Handle + * @return esp_err_t if there was a error + */ + esp_err_t Github_OTA_free(Github_OTA_client_handle_t *handle); + + /** + * @brief Perform a check for updates + * + * This will just check if there is a available update on Github releases with download resources that match your configuration + * for firmware and storage files. If it returns ESP_OK, you can call Github_OTA_get_latest_version to get the version of the latest release + * + * @param handle the Github_OTA_client_handle_t handle + * @return esp_err_t ESP_OK if there is a update available, ESP_FAIL if there is no update available or an error + */ + esp_err_t Github_OTA_check(Github_OTA_client_handle_t *handle); + + /** + * @brief Downloads and writes the latest firmware and storage partition (if available) + * + * You should only call this after calling Github_OTA_check and ensuring that there is a update available. + * + * @param handle the Github_OTA_client_handle_t handle + * @return esp_err_t ESP_FAIL if there is a error. If the Update is successful, it will not return, but reboot the device + */ + esp_err_t Github_OTA_update(Github_OTA_client_handle_t *handle); + + /** + * @brief Get the currently running version of the firmware + * + * This will return the version of the firmware currently running on your device. + * consult semver.h for functions to compare versions + * + * @param handle the Github_OTA_client_handle_t handle + * @return semver_t the version of the latest release + */ + + semver_t *Github_OTA_get_current_version(Github_OTA_client_handle_t *handle); + + /** + * @brief Get the version of the latest release on Github. Only valid after calling Github_OTA_check + * + * @param handle the Github_OTA_client_handle_t handle + * @return semver_t* the version of the latest release on Github + */ + semver_t *Github_OTA_get_latest_version(Github_OTA_client_handle_t *handle); + + /** + * @brief Start a new Task that will check for updates and update if available + * + * This is equivalent to calling Github_OTA_check and Github_OTA_update if there is a new update available. + * If no update is available, it will not update the device. + * + * Progress can be monitored by registering for the Github_OTA_EVENTS events on the Global Event Loop + * + * @param handle Github_OTA_client_handle_t handle + * @return esp_err_t ESP_OK if the task was started, ESP_FAIL if there was an error + */ + esp_err_t Github_OTA_start_update_task(Github_OTA_client_handle_t *handle); + + /** + * @brief Install a Timer to automatically check for new updates and update if available + * + * Install a timer that will check for new updates every updateInterval seconds and update if available. + * + * @param handle Github_OTA_client_handle_t handle + * @return esp_err_t ESP_OK if no error, otherwise ESP_FAIL + */ + + esp_err_t Github_OTA_start_update_timer(Github_OTA_client_handle_t *handle); + + /** + * @brief convience function to return a string representation of events emited by this library + * + * @param event the eventid passed to the event handler + * @return char* a string representing the event + */ + const char *Github_OTA_get_event_str(Github_OTA_event_e event); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/ESP/lib/src/network/OTA/OTA.cpp b/ESP/lib/src/network/OTA/OTA.cpp index 96b5549..f91711e 100644 --- a/ESP/lib/src/network/OTA/OTA.cpp +++ b/ESP/lib/src/network/OTA/OTA.cpp @@ -1,6 +1,8 @@ #include "OTA.hpp" -OTA::OTA(ProjectConfig *_deviceConfig) : _deviceConfig(_deviceConfig) {} +OTA::OTA(ProjectConfig *_deviceConfig) : _deviceConfig(_deviceConfig), + _bootTimestamp(0), + _isOtaEnabled(true) {} OTA::~OTA() {} @@ -14,9 +16,7 @@ void OTA::SetupOTA() log_e("THE PASSWORD IS REQUIRED, [[ABORTING]]"); return; } - ArduinoOTA.setPort(localConfig->OTAPort); - ArduinoOTA .onStart([]() { @@ -50,7 +50,6 @@ void OTA::SetupOTA() log_e("End Failed"); break; } }); - log_i("Starting up basic OTA server"); log_i("OTA will be live for 5 minutes, after which it will be disabled until restart"); auto mdnsConfig = _deviceConfig->getMDNSConfig(); diff --git a/ESP/lib/src/network/OTA/OTA.hpp b/ESP/lib/src/network/OTA/OTA.hpp index 0a60e7d..a644328 100644 --- a/ESP/lib/src/network/OTA/OTA.hpp +++ b/ESP/lib/src/network/OTA/OTA.hpp @@ -13,8 +13,8 @@ public: void HandleOTAUpdate(); private: - unsigned long _bootTimestamp = 0; - bool _isOtaEnabled = true; ProjectConfig *_deviceConfig; + unsigned long _bootTimestamp; + bool _isOtaEnabled; }; #endif // OTA_HPP \ No newline at end of file diff --git a/ESP/lib/src/network/WifiHandler/wifiHandler.cpp b/ESP/lib/src/network/WifiHandler/wifiHandler.cpp index 40a19b8..e33f904 100644 --- a/ESP/lib/src/network/WifiHandler/wifiHandler.cpp +++ b/ESP/lib/src/network/WifiHandler/wifiHandler.cpp @@ -16,7 +16,8 @@ WiFiHandler::~WiFiHandler() {} void WiFiHandler::setupWifi() { - if (this->_enable_adhoc || stateManager->getCurrentState() == WiFiState_e::WiFiState_ADHOC) + if (this->_enable_adhoc || + stateManager->getCurrentState() == WiFiState_e::WiFiState_ADHOC) { this->setUpADHOC(); return; @@ -28,40 +29,53 @@ void WiFiHandler::setupWifi() log_i("Initializing connection to wifi \n\r"); stateManager->setState(WiFiState_e::WiFiState_Connecting); - std::vector *networks = configManager->getWifiConfigs(); + std::vector *networks = + configManager->getWifiConfigs(); if (networks->empty()) { log_i("No networks found in config, trying the default one \n\r"); - if (this->iniSTA(this->ssid.c_str(), this->password.c_str(), this->channel, (wifi_power_t)txpower->power)) + if (this->iniSTA(this->ssid.c_str(), this->password.c_str(), this->channel, + (wifi_power_t)txpower->power)) { return; } else { - log_i("Could not connect to the hardcoded network, setting up ADHOC network \n\r"); + log_i( + "Could not connect to the hardcoded network, setting up ADHOC " + "network \n\r"); this->setUpADHOC(); return; } } - for (auto networkIterator = networks->begin(); networkIterator != networks->end(); ++networkIterator) + for (auto networkIterator = networks->begin(); + networkIterator != networks->end(); ++networkIterator) { - if (this->iniSTA(networkIterator->ssid.c_str(), networkIterator->password.c_str(), networkIterator->channel, (wifi_power_t)networkIterator->power)) + if (this->iniSTA( + networkIterator->ssid.c_str(), networkIterator->password.c_str(), + networkIterator->channel, (wifi_power_t)networkIterator->power)) { return; } } // at this point, we've tried every network, let's just setup adhoc - log_i("We've gone through every network, each timed out. Trying to connect to hardcoded network: %s \n\r", this->ssid.c_str()); - if (this->iniSTA(this->ssid.c_str(), this->password.c_str(), this->channel, (wifi_power_t)txpower->power)) + log_i( + "We've gone through every network, each timed out. Trying to connect " + "to hardcoded network: %s \n\r", + this->ssid.c_str()); + if (this->iniSTA(this->ssid.c_str(), this->password.c_str(), this->channel, + (wifi_power_t)txpower->power)) { log_i("Successfully connected to the hardcoded network. \n\r"); } else { - log_i("Could not connect to the hardcoded network, setting up adhoc. \n\r"); + log_i( + "Could not connect to the hardcoded network, setting up adhoc. " + "\n\r"); this->setUpADHOC(); } } @@ -69,7 +83,7 @@ void WiFiHandler::setupWifi() void WiFiHandler::adhoc(const std::string &ssid, uint8_t channel, const std::string &password) { stateManager->setState(WiFiState_e::WiFiState_ADHOC); - + log_i("\n[INFO]: Configuring access point...\n"); WiFi.mode(WIFI_AP); WiFi.setSleep(WIFI_PS_NONE); @@ -111,7 +125,8 @@ void WiFiHandler::setUpADHOC() log_d("\n[DEBUG]: channel: %d\n", configManager->getAPWifiConfig()->channel); } -bool WiFiHandler::iniSTA(const char *ssid, const char *password, uint8_t channel, wifi_power_t power) +bool WiFiHandler::iniSTA(const char *ssid, const char *password, + uint8_t channel, wifi_power_t power) { unsigned long currentMillis = millis(); unsigned long startingMillis = currentMillis; @@ -122,7 +137,8 @@ bool WiFiHandler::iniSTA(const char *ssid, const char *password, uint8_t channel log_i("Trying to connect to: %s \n\r", ssid); auto mdnsConfig = configManager->getMDNSConfig(); - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); // need to call before setting hostname + WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, + INADDR_NONE); // need to call before setting hostname WiFi.setHostname(mdnsConfig->hostname.c_str()); WiFi.begin(ssid, password, channel); while (WiFi.status() != WL_CONNECTED) diff --git a/ESP/lib/src/network/api/baseAPI/baseAPI.cpp b/ESP/lib/src/network/api/baseAPI/baseAPI.cpp index ec60e1b..5997de2 100644 --- a/ESP/lib/src/network/api/baseAPI/baseAPI.cpp +++ b/ESP/lib/src/network/api/baseAPI/baseAPI.cpp @@ -14,10 +14,12 @@ BaseAPI::BaseAPI(int CONTROL_PORT, ProjectConfig *projectConfig, CameraHandler *camera, StateManager *WiFiStateManager, + GithubOTAHandler *otaHandler, const std::string &api_url) : server(new AsyncWebServer(CONTROL_PORT)), projectConfig(projectConfig), camera(camera), WiFiStateManager(WiFiStateManager), + otaHandler(otaHandler), api_url(api_url) {} BaseAPI::~BaseAPI() {} @@ -180,6 +182,7 @@ void BaseAPI::setDeviceConfig(AsyncWebServerRequest *request) std::string hostname; std::string service; std::string ota_password; + std::string firmware_name; int ota_port; for (int i = 0; i < params; i++) @@ -187,11 +190,11 @@ void BaseAPI::setDeviceConfig(AsyncWebServerRequest *request) AsyncWebParameter *param = request->getParam(i); if (param->name() == "hostname") { - hostname = param->value().c_str(); + hostname.assign(param->value().c_str()); } else if (param->name() == "service") { - service = param->value().c_str(); + service.assign(param->value().c_str()); } else if (param->name() == "ota_port") { @@ -199,11 +202,15 @@ void BaseAPI::setDeviceConfig(AsyncWebServerRequest *request) } else if (param->name() == "ota_password") { - ota_password = param->value().c_str(); + ota_password.assign(param->value().c_str()); + } + else if (param->name() == "firmware_name") + { + firmware_name.assign(param->value().c_str()); } } // note: We're passing empty params by design, this is done to reset specific fields - projectConfig->setDeviceConfig(ota_password, &ota_port, true); + projectConfig->setDeviceConfig(ota_password, ota_port, firmware_name, true); projectConfig->setMDNSConfig(hostname, service, true); request->send(200, MIMETYPE_JSON, "{\"msg\":\"Done. Device Config has been set.\"}"); } @@ -289,6 +296,26 @@ void BaseAPI::factoryReset(AsyncWebServerRequest *request) } } +void BaseAPI::initOTAHandler(AsyncWebServerRequest *request) +{ + switch (_networkMethodsMap_enum[request->method()]) + { + case POST: + { + log_d("OTA Update Initiated"); + auto otaConfig = projectConfig->getDeviceConfig(); + // start the OTA update + otaHandler->updateFirmware(otaConfig->binaryName); + request->send(200, MIMETYPE_JSON, "{\"msg\":\"OTA Update In progress\"}"); + } + default: + { + request->send(400, MIMETYPE_JSON, "{\"msg\":\"Invalid Request\"}"); + break; + } + } +} + //********************************************************************************************* //! Camera Command Functions //********************************************************************************************* diff --git a/ESP/lib/src/network/api/baseAPI/baseAPI.hpp b/ESP/lib/src/network/api/baseAPI/baseAPI.hpp index 7dee85c..b15f080 100644 --- a/ESP/lib/src/network/api/baseAPI/baseAPI.hpp +++ b/ESP/lib/src/network/api/baseAPI/baseAPI.hpp @@ -15,13 +15,14 @@ #define XHTTP_OPTIONS 0b01000000; #define XHTTP_ANY 0b01111111; */ +#include "network/OTA/Github_OTA/GithubOTAHandler.hpp" #define HTTP_ANY 0b01111111 #define HTTP_GET 0b00000001 #include #include -//#include "network/api/utilities/apiUtilities.hpp" //! Only needed for the shaEncoder function (for now) +// #include "network/api/utilities/apiUtilities.hpp" //! Only needed for the shaEncoder function (for now) #include "data/utilities/network_utilities.hpp" #include "data/config/project_config.hpp" @@ -47,6 +48,7 @@ protected: void setWiFiTXPower(AsyncWebServerRequest *request); void getJsonConfig(AsyncWebServerRequest *request); void factoryReset(AsyncWebServerRequest *request); + void initOTAHandler(AsyncWebServerRequest *request); void setDeviceConfig(AsyncWebServerRequest *request); void rebootDevice(AsyncWebServerRequest *request); void ping(AsyncWebServerRequest *request); @@ -98,13 +100,15 @@ protected: AsyncWebServer *server; CameraHandler *camera; StateManager *WiFiStateManager; + GithubOTAHandler *otaHandler; public: BaseAPI(int CONTROL_PORT, - ProjectConfig *projectConfig, - CameraHandler *camera, - StateManager *WiFiStateManager, - const std::string &api_url); + ProjectConfig *projectConfig, + CameraHandler *camera, + StateManager *WiFiStateManager, + GithubOTAHandler *otaHandler, + const std::string &api_url); virtual ~BaseAPI(); virtual void begin(); diff --git a/ESP/lib/src/network/api/webserverHandler.cpp b/ESP/lib/src/network/api/webserverHandler.cpp index 1428284..e65c91c 100644 --- a/ESP/lib/src/network/api/webserverHandler.cpp +++ b/ESP/lib/src/network/api/webserverHandler.cpp @@ -8,10 +8,12 @@ APIServer::APIServer(int CONTROL_PORT, ProjectConfig *projectConfig, CameraHandler *camera, StateManager *WiFiStateManager, + GithubOTAHandler *otaHandler, const std::string &api_url) : BaseAPI(CONTROL_PORT, projectConfig, camera, WiFiStateManager, + otaHandler, api_url) {} APIServer::~APIServer() {} diff --git a/ESP/lib/src/network/api/webserverHandler.hpp b/ESP/lib/src/network/api/webserverHandler.hpp index adb0b03..13a41c9 100644 --- a/ESP/lib/src/network/api/webserverHandler.hpp +++ b/ESP/lib/src/network/api/webserverHandler.hpp @@ -8,10 +8,11 @@ class APIServer : public BaseAPI { public: APIServer(int CONTROL_PORT, - ProjectConfig *projectConfig, - CameraHandler *camera, - StateManager *WiFiStateManager, - const std::string &api_url); + ProjectConfig *projectConfig, + CameraHandler *camera, + StateManager *WiFiStateManager, + GithubOTAHandler *otaHandler, + const std::string &api_url); virtual ~APIServer(); void begin(); diff --git a/ESP/lib/src/network/mDNS/auto/AutoDiscovery.cpp b/ESP/lib/src/network/mDNS/auto/AutoDiscovery.cpp new file mode 100644 index 0000000..ef61e00 --- /dev/null +++ b/ESP/lib/src/network/mDNS/auto/AutoDiscovery.cpp @@ -0,0 +1,107 @@ +#include "AutoDiscovery.hpp" + +AutoDiscovery::AutoDiscovery(StateManager *stateManager, + ProjectConfig *configManager) : stateManager(stateManager), + configManager(configManager), + mdnsConfig(NULL), + _queryTimeout(5000), + _currentTime(millis()), + _previousTime(0) +{ + if (mdns_init() != ESP_OK) + { + stateManager->setState(MDNSState_e::MDNSState_Error); + return; + } + mdnsConfig = configManager->getMDNSConfig(); +} + +AutoDiscovery::~AutoDiscovery() +{ + stop(); +} + +void AutoDiscovery::start() +{ + stateManager->setState(MDNSState_e::MDNSState_QueryStarted); + queryService(mdnsConfig->hostname.c_str(), "tcp"); + stop(); +} + +/** + * @brief Query for the number of services on the network + * + * @param service string - the number of services found + * @param protocol string - the protocol of the service + * @return int - the number of services found + */ +void AutoDiscovery::queryService(const std::string &service, const std::string &protocol) +{ + if (stateManager->getCurrentState() != MDNSState_e::MDNSState_QueryComplete) + { + int numServices = 0; + if (_currentTime - _previousTime >= _queryTimeout) + { + _previousTime = _currentTime; + int n = MDNS.queryService(service.c_str(), protocol.c_str()); + if (n != numServices) + { + numServices = n; + } + } + if (numServices == 0) + { + log_w("no services found"); + // set the hostname for mDNS - this is what we will need to connect to + if (mdnsConfig->hostname != "openiristracker") + { + log_w("No hostname set, using default"); + configManager->setMDNSConfig(MDNS_HOSTNAME, mdnsConfig->hostname.c_str(), true); + return; + } + log_i("Setting hostname to %s", mdnsConfig->hostname.c_str()); + configManager->setMDNSConfig(mdnsConfig->hostname.c_str(), mdnsConfig->service.c_str(), true); + return; + } + + log_i("%d services found", numServices); + for (int i = 0; i < numServices; ++i) + { + // print details for each service found + log_i("service #%d: %s - %s:%d", i + 1, MDNS.hostname(i).c_str(), MDNS.IP(i).toString().c_str(), MDNS.port(i)); + return; + } + std::string iterator = std::to_string(numServices); + std::string hostname = mdnsConfig->hostname.assign(iterator); + + if (mdnsConfig->hostname != "openiristracker") + { + log_w("No hostname set, using default"); + configManager->setMDNSConfig(MDNS_HOSTNAME, mdnsConfig->hostname.c_str(), true); + return; + } + log_i("Setting hostname to %s", hostname.c_str()); + configManager->setMDNSConfig(hostname.c_str(), mdnsConfig->service.c_str(), true); + return; + } +} + +void AutoDiscovery::stop() +{ + stateManager->setState(MDNSState_e::MDNSState_QueryComplete); +} + +void AutoDiscovery::update(ObserverEvent::Event event) +{ + switch (event) + { + case ObserverEvent::Event::mdnsConfigUpdated: + MDNS.end(); + start(); + break; + default: + break; + } +} + +// Path: lib\src\network\mDNS\auto\AutoDiscovery.hpp \ No newline at end of file diff --git a/ESP/lib/src/network/mDNS/auto/AutoDiscovery.hpp b/ESP/lib/src/network/mDNS/auto/AutoDiscovery.hpp new file mode 100644 index 0000000..7139837 --- /dev/null +++ b/ESP/lib/src/network/mDNS/auto/AutoDiscovery.hpp @@ -0,0 +1,32 @@ +#ifndef AutoDiscovery_hpp +#define AutoDiscovery_hpp + +#include +#include +#include "data/StateManager/StateManager.hpp" +#include "data/utilities/Observer.hpp" +#include "data/utilities/helpers.hpp" +#include "data/config/project_config.hpp" + +class AutoDiscovery : public IObserver +{ +private: + StateManager *stateManager; + ProjectConfig *configManager; + ProjectConfig::MDNSConfig_t *mdnsConfig; + int _queryTimeout; + unsigned long _currentTime; + unsigned long _previousTime; + + void queryService(const std::string &service, const std::string &protocol); + void stop(); + +public: + AutoDiscovery(StateManager *stateManager, + ProjectConfig *configManager); + ~AutoDiscovery(); + void start(); + void update(ObserverEvent::Event event); +}; + +#endif // AutoDiscovery_hpp \ No newline at end of file diff --git a/ESP/partitions.csv b/ESP/partitions.csv new file mode 100644 index 0000000..fec65df --- /dev/null +++ b/ESP/partitions.csv @@ -0,0 +1,8 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +otadata, data, ota, , 0x2000 +ota_0, app, ota_0, , 1M, +ota_1, app, ota_1, , 1M +storage, data, spiffs, , 0xF0000, \ No newline at end of file diff --git a/ESP/platformio.ini b/ESP/platformio.ini index 1d762dd..308bfdf 100644 --- a/ESP/platformio.ini +++ b/ESP/platformio.ini @@ -11,220 +11,215 @@ [platformio] default_envs = esp32AITinker ; do not change this value unless you know what you are doing -; The below options are available for all environments -; The ssid and password are requried for the trackers to connect to your network!!! [wifi] -ssid="" ; your wifi network name goes here -password="" ; your wifi network password goes here -mDNSName="openiristracker" ; the name of the tracker as it will appear on your network -channel=1 ; wifi channel -ap_ssid="EyeTrackVR" ; your AP wifi network name goes here -ap_password="test" ; Place your AP Wifi password here -OTAPassword="12345678" -OTAServerPort=3232 -enableADHOC=0 ; 0 = disable, 1 = enable -adhocChannel=1 ; channel to use for adhoc network -enableOTA=1 ; 0 = disable, 1 = enable - -; DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING -[pinoutsESPCAM_M5STACK] -; M5-Stack camera -# WARNING: The M5-Stack camera does not have PSRAM please set the noPSRM flag to 1 -PWDN_GPIO_NUM = -1 -RESET_GPIO_NUM = 15 -XCLK_GPIO_NUM = 27 -SIOD_GPIO_NUM = 25 -SIOC_GPIO_NUM = 23 -Y9_GPIO_NUM = 19 -Y8_GPIO_NUM = 36 -Y7_GPIO_NUM = 18 -Y6_GPIO_NUM = 39 -Y5_GPIO_NUM = 5 -Y4_GPIO_NUM = 34 -Y3_GPIO_NUM = 35 -Y2_GPIO_NUM = 17 -VSYNC_GPIO_NUM = 22 -HREF_GPIO_NUM = 26 -PCLK_GPIO_NUM = 21 +ssid = "" +password = "" +mdnsname = "openiristracker" +channel = 1 +ap_ssid = "EyeTrackVR" +ap_password = "test" +otapassword = "12345678" +otaserverport = 3232 +enableadhoc = 0 +adhocchannel = 1 +enableota = 1 +[ESP_GITHUB_OTA] +maxfilenamelen = 64 +maxurllen = 128 +githubhostname = "api.github.com" +githubowner = "lorow" +githubrepo = "OpenIris" build_flags = - ; CAMERA PINOUT DEFINITIONS - -DPWDN_GPIO_NUM=${pinoutsESPCAM_M5STACK.PWDN_GPIO_NUM} ; Set the PWDN pin - -DRESET_GPIO_NUM=${pinoutsESPCAM_M5STACK.RESET_GPIO_NUM} ; Set the RESET pin - -DXCLK_GPIO_NUM=${pinoutsESPCAM_M5STACK.XCLK_GPIO_NUM} ; Set the XCLK pin - -DSIOD_GPIO_NUM=${pinoutsESPCAM_M5STACK.SIOD_GPIO_NUM} ; Set the SIOD pin - -DSIOC_GPIO_NUM=${pinoutsESPCAM_M5STACK.SIOC_GPIO_NUM} ; Set the SIOC pin - -DY9_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y9_GPIO_NUM} ; Set the Y9 pin - -DY8_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y8_GPIO_NUM} ; Set the Y8 pin - -DY7_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y7_GPIO_NUM} ; Set the Y7 pin - -DY6_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y6_GPIO_NUM} ; Set the Y6 pin - -DY5_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y5_GPIO_NUM} ; Set the Y5 pin - -DY4_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y4_GPIO_NUM} ; Set the Y4 pin - -DY3_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y3_GPIO_NUM} ; Set the Y3 pin - -DY2_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y2_GPIO_NUM} ; Set the Y2 pin - -DVSYNC_GPIO_NUM=${pinoutsESPCAM_M5STACK.VSYNC_GPIO_NUM} ; Set the VSYNC pin - -DHREF_GPIO_NUM=${pinoutsESPCAM_M5STACK.HREF_GPIO_NUM} ; Set the HREF pin - -DPCLK_GPIO_NUM=${pinoutsESPCAM_M5STACK.PCLK_GPIO_NUM} ; Set the PCLK pin + -DCONFIG_MAX_FILENAME_LEN=${ESP_GITHUB_OTA.maxFilenameLen} + -DCONFIG_MAX_URL_LEN=${ESP_GITHUB_OTA.maxURLLen} + '-DCONFIG_GITHUB_HOSTNAME=${ESP_GITHUB_OTA.githubHostname}' + '-DCONFIG_GITHUB_OWNER=${ESP_GITHUB_OTA.githubOwner}' + '-DCONFIG_GITHUB_REPO=${ESP_GITHUB_OTA.githubRepo}' -[pinoutsESPCAM] -; ESP32CAM, the ov2640 -PWDN_GPIO_NUM = 32 -RESET_GPIO_NUM = 33 -XCLK_GPIO_NUM = 4 -SIOD_GPIO_NUM = 18 -SIOC_GPIO_NUM = 23 -Y9_GPIO_NUM = 36 -Y8_GPIO_NUM = 19 -Y7_GPIO_NUM = 21 -Y6_GPIO_NUM = 39 -Y5_GPIO_NUM = 35 -Y4_GPIO_NUM = 14 -Y3_GPIO_NUM = 13 -Y2_GPIO_NUM = 34 -VSYNC_GPIO_NUM = 5 -HREF_GPIO_NUM = 27 -PCLK_GPIO_NUM = 25 +[pinoutsESPCAM_M5STACK] +pwdn_gpio_num = -1 +reset_gpio_num = 15 +xclk_gpio_num = 27 +siod_gpio_num = 25 +sioc_gpio_num = 23 +y9_gpio_num = 19 +y8_gpio_num = 36 +y7_gpio_num = 18 +y6_gpio_num = 39 +y5_gpio_num = 5 +y4_gpio_num = 34 +y3_gpio_num = 35 +y2_gpio_num = 17 +vsync_gpio_num = 22 +href_gpio_num = 26 +pclk_gpio_num = 21 +build_flags = + '-DCAMERA_MODULE_NAME="ESP32_CAM_M5STACK"' + -DPWDN_GPIO_NUM=${pinoutsESPCAM_M5STACK.PWDN_GPIO_NUM} + -DRESET_GPIO_NUM=${pinoutsESPCAM_M5STACK.RESET_GPIO_NUM} + -DXCLK_GPIO_NUM=${pinoutsESPCAM_M5STACK.XCLK_GPIO_NUM} + -DSIOD_GPIO_NUM=${pinoutsESPCAM_M5STACK.SIOD_GPIO_NUM} + -DSIOC_GPIO_NUM=${pinoutsESPCAM_M5STACK.SIOC_GPIO_NUM} + -DY9_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y9_GPIO_NUM} + -DY8_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y8_GPIO_NUM} + -DY7_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y7_GPIO_NUM} + -DY6_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y6_GPIO_NUM} + -DY5_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y5_GPIO_NUM} + -DY4_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y4_GPIO_NUM} + -DY3_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y3_GPIO_NUM} + -DY2_GPIO_NUM=${pinoutsESPCAM_M5STACK.Y2_GPIO_NUM} + -DVSYNC_GPIO_NUM=${pinoutsESPCAM_M5STACK.VSYNC_GPIO_NUM} + -DHREF_GPIO_NUM=${pinoutsESPCAM_M5STACK.HREF_GPIO_NUM} + -DPCLK_GPIO_NUM=${pinoutsESPCAM_M5STACK.PCLK_GPIO_NUM} -build_flags = +[pinoutsESPCam] +pwdn_gpio_num = 32 +reset_gpio_num = 33 +xclk_gpio_num = 4 +siod_gpio_num = 18 +sioc_gpio_num = 23 +y9_gpio_num = 36 +y8_gpio_num = 19 +y7_gpio_num = 21 +y6_gpio_num = 39 +y5_gpio_num = 35 +y4_gpio_num = 14 +y3_gpio_num = 13 +y2_gpio_num = 34 +vsync_gpio_num = 5 +href_gpio_num = 27 +pclk_gpio_num = 25 +build_flags = '-DCAMERA_MODULE_NAME="ESP32_CAM"' -DCONFIG_CAMERA_MODULE_CAM_BOARD=1 - ; CAMERA PINOUT DEFINITIONS - -DPWDN_GPIO_NUM=${pinoutsESPCAM.PWDN_GPIO_NUM} ; Set the PWDN pin - -DRESET_GPIO_NUM=${pinoutsESPCAM.RESET_GPIO_NUM} ; Set the RESET pin - -DXCLK_GPIO_NUM=${pinoutsESPCAM.XCLK_GPIO_NUM} ; Set the XCLK pin - -DSIOD_GPIO_NUM=${pinoutsESPCAM.SIOD_GPIO_NUM} ; Set the SIOD pin - -DSIOC_GPIO_NUM=${pinoutsESPCAM.SIOC_GPIO_NUM} ; Set the SIOC pin - -DY9_GPIO_NUM=${pinoutsESPCAM.Y9_GPIO_NUM} ; Set the Y9 pin - -DY8_GPIO_NUM=${pinoutsESPCAM.Y8_GPIO_NUM} ; Set the Y8 pin - -DY7_GPIO_NUM=${pinoutsESPCAM.Y7_GPIO_NUM} ; Set the Y7 pin - -DY6_GPIO_NUM=${pinoutsESPCAM.Y6_GPIO_NUM} ; Set the Y6 pin - -DY5_GPIO_NUM=${pinoutsESPCAM.Y5_GPIO_NUM} ; Set the Y5 pin - -DY4_GPIO_NUM=${pinoutsESPCAM.Y4_GPIO_NUM} ; Set the Y4 pin - -DY3_GPIO_NUM=${pinoutsESPCAM.Y3_GPIO_NUM} ; Set the Y3 pin - -DY2_GPIO_NUM=${pinoutsESPCAM.Y2_GPIO_NUM} ; Set the Y2 pin - -DVSYNC_GPIO_NUM=${pinoutsESPCAM.VSYNC_GPIO_NUM} ; Set the VSYNC pin - -DHREF_GPIO_NUM=${pinoutsESPCAM.HREF_GPIO_NUM} ; Set the HREF pin - -DPCLK_GPIO_NUM=${pinoutsESPCAM.PCLK_GPIO_NUM} ; Set the PCLK pin + -DPWDN_GPIO_NUM=${pinoutsESPCam.PWDN_GPIO_NUM} + -DRESET_GPIO_NUM=${pinoutsESPCam.RESET_GPIO_NUM} + -DXCLK_GPIO_NUM=${pinoutsESPCam.XCLK_GPIO_NUM} + -DSIOD_GPIO_NUM=${pinoutsESPCam.SIOD_GPIO_NUM} + -DSIOC_GPIO_NUM=${pinoutsESPCam.SIOC_GPIO_NUM} + -DY9_GPIO_NUM=${pinoutsESPCam.Y9_GPIO_NUM} + -DY8_GPIO_NUM=${pinoutsESPCam.Y8_GPIO_NUM} + -DY7_GPIO_NUM=${pinoutsESPCam.Y7_GPIO_NUM} + -DY6_GPIO_NUM=${pinoutsESPCam.Y6_GPIO_NUM} + -DY5_GPIO_NUM=${pinoutsESPCam.Y5_GPIO_NUM} + -DY4_GPIO_NUM=${pinoutsESPCam.Y4_GPIO_NUM} + -DY3_GPIO_NUM=${pinoutsESPCam.Y3_GPIO_NUM} + -DY2_GPIO_NUM=${pinoutsESPCam.Y2_GPIO_NUM} + -DVSYNC_GPIO_NUM=${pinoutsESPCam.VSYNC_GPIO_NUM} + -DHREF_GPIO_NUM=${pinoutsESPCam.HREF_GPIO_NUM} + -DPCLK_GPIO_NUM=${pinoutsESPCam.PCLK_GPIO_NUM} [pinoutsAITinker] -; AI Tinker camera, the ov2650 -PWDN_GPIO_NUM = 32 -RESET_GPIO_NUM = -1 -XCLK_GPIO_NUM = 0 -SIOD_GPIO_NUM = 26 -SIOC_GPIO_NUM = 27 -Y9_GPIO_NUM = 35 -Y8_GPIO_NUM = 34 -Y7_GPIO_NUM = 39 -Y6_GPIO_NUM = 36 -Y5_GPIO_NUM = 21 -Y4_GPIO_NUM = 19 -Y3_GPIO_NUM = 18 -Y2_GPIO_NUM = 5 -VSYNC_GPIO_NUM = 25 -HREF_GPIO_NUM = 23 -PCLK_GPIO_NUM = 22 - -build_flags = +pwdn_gpio_num = 32 +reset_gpio_num = -1 +xclk_gpio_num = 0 +siod_gpio_num = 26 +sioc_gpio_num = 27 +y9_gpio_num = 35 +y8_gpio_num = 34 +y7_gpio_num = 39 +y6_gpio_num = 36 +y5_gpio_num = 21 +y4_gpio_num = 19 +y3_gpio_num = 18 +y2_gpio_num = 5 +vsync_gpio_num = 25 +href_gpio_num = 23 +pclk_gpio_num = 22 +build_flags = '-DCAMERA_MODULE_NAME="ESP32_CAM_AI_THINKER"' -DCONFIG_CAMERA_MODULE_AITINKER_BOARD=1 - ; CAMERA PINOUT DEFINITIONS - -DPWDN_GPIO_NUM=${pinoutsAITinker.PWDN_GPIO_NUM} ; Set the PWDN pin - -DRESET_GPIO_NUM=${pinoutsAITinker.RESET_GPIO_NUM} ; Set the RESET pin - -DXCLK_GPIO_NUM=${pinoutsAITinker.XCLK_GPIO_NUM} ; Set the XCLK pin - -DSIOD_GPIO_NUM=${pinoutsAITinker.SIOD_GPIO_NUM} ; Set the SIOD pin - -DSIOC_GPIO_NUM=${pinoutsAITinker.SIOC_GPIO_NUM} ; Set the SIOC pin - -DY9_GPIO_NUM=${pinoutsAITinker.Y9_GPIO_NUM} ; Set the Y9 pin - -DY8_GPIO_NUM=${pinoutsAITinker.Y8_GPIO_NUM} ; Set the Y8 pin - -DY7_GPIO_NUM=${pinoutsAITinker.Y7_GPIO_NUM} ; Set the Y7 pin - -DY6_GPIO_NUM=${pinoutsAITinker.Y6_GPIO_NUM} ; Set the Y6 pin - -DY5_GPIO_NUM=${pinoutsAITinker.Y5_GPIO_NUM} ; Set the Y5 pin - -DY4_GPIO_NUM=${pinoutsAITinker.Y4_GPIO_NUM} ; Set the Y4 pin - -DY3_GPIO_NUM=${pinoutsAITinker.Y3_GPIO_NUM} ; Set the Y3 pin - -DY2_GPIO_NUM=${pinoutsAITinker.Y2_GPIO_NUM} ; Set the Y2 pin - -DVSYNC_GPIO_NUM=${pinoutsAITinker.VSYNC_GPIO_NUM} ; Set the VSYNC pin - -DHREF_GPIO_NUM=${pinoutsAITinker.HREF_GPIO_NUM} ; Set the HREF pin - -DPCLK_GPIO_NUM=${pinoutsAITinker.PCLK_GPIO_NUM} ; Set the PCLK pin + -DPWDN_GPIO_NUM=${pinoutsAITinker.PWDN_GPIO_NUM} + -DRESET_GPIO_NUM=${pinoutsAITinker.RESET_GPIO_NUM} + -DXCLK_GPIO_NUM=${pinoutsAITinker.XCLK_GPIO_NUM} + -DSIOD_GPIO_NUM=${pinoutsAITinker.SIOD_GPIO_NUM} + -DSIOC_GPIO_NUM=${pinoutsAITinker.SIOC_GPIO_NUM} + -DY9_GPIO_NUM=${pinoutsAITinker.Y9_GPIO_NUM} + -DY8_GPIO_NUM=${pinoutsAITinker.Y8_GPIO_NUM} + -DY7_GPIO_NUM=${pinoutsAITinker.Y7_GPIO_NUM} + -DY6_GPIO_NUM=${pinoutsAITinker.Y6_GPIO_NUM} + -DY5_GPIO_NUM=${pinoutsAITinker.Y5_GPIO_NUM} + -DY4_GPIO_NUM=${pinoutsAITinker.Y4_GPIO_NUM} + -DY3_GPIO_NUM=${pinoutsAITinker.Y3_GPIO_NUM} + -DY2_GPIO_NUM=${pinoutsAITinker.Y2_GPIO_NUM} + -DVSYNC_GPIO_NUM=${pinoutsAITinker.VSYNC_GPIO_NUM} + -DHREF_GPIO_NUM=${pinoutsAITinker.HREF_GPIO_NUM} + -DPCLK_GPIO_NUM=${pinoutsAITinker.PCLK_GPIO_NUM} [pinoutsESP-EYE] -; CAMERA_MODULE_ESP-EYE_KIT -PWDN_GPIO_NUM = -1 -RESET_GPIO_NUM = -1 -XCLK_GPIO_NUM = 4 -SIOD_GPIO_NUM = 18 -SIOC_GPIO_NUM = 23 -Y9_GPIO_NUM = 36 -Y8_GPIO_NUM = 37 -Y7_GPIO_NUM = 38 -Y6_GPIO_NUM = 39 -Y5_GPIO_NUM = 35 -Y4_GPIO_NUM = 14 -Y3_GPIO_NUM = 13 -Y2_GPIO_NUM = 34 -VSYNC_GPIO_NUM = 5 -HREF_GPIO_NUM = 27 -PCLK_GPIO_NUM = 25 - +pwdn_gpio_num = -1 +reset_gpio_num = -1 +xclk_gpio_num = 4 +siod_gpio_num = 18 +sioc_gpio_num = 23 +y9_gpio_num = 36 +y8_gpio_num = 37 +y7_gpio_num = 38 +y6_gpio_num = 39 +y5_gpio_num = 14 +y4_gpio_num = 19 +y3_gpio_num = 13 +y2_gpio_num = 34 +vsync_gpio_num = 5 +href_gpio_num = 27 +pclk_gpio_num = 25 build_flags = '-DCAMERA_MODULE_NAME="ESP32_CAM_ESP_EYE"' -DCONFIG_CAMERA_MODULE_ESP_EYE=1 - ; CAMERA PINOUT DEFINITIONS - -DPWDN_GPIO_NUM=${pinoutsESP-EYE.PWDN_GPIO_NUM} ; Set the PWDN pin - -DRESET_GPIO_NUM=${pinoutsESP-EYE.RESET_GPIO_NUM} ; Set the RESET pin - -DXCLK_GPIO_NUM=${pinoutsESP-EYE.XCLK_GPIO_NUM} ; Set the XCLK pin - -DSIOD_GPIO_NUM=${pinoutsESP-EYE.SIOD_GPIO_NUM} ; Set the SIOD pin - -DSIOC_GPIO_NUM=${pinoutsESP-EYE.SIOC_GPIO_NUM} ; Set the SIOC pin - -DY9_GPIO_NUM=${pinoutsESP-EYE.Y9_GPIO_NUM} ; Set the Y9 pin - -DY8_GPIO_NUM=${pinoutsESP-EYE.Y8_GPIO_NUM} ; Set the Y8 pin - -DY7_GPIO_NUM=${pinoutsESP-EYE.Y7_GPIO_NUM} ; Set the Y7 pin - -DY6_GPIO_NUM=${pinoutsESP-EYE.Y6_GPIO_NUM} ; Set the Y6 pin - -DY5_GPIO_NUM=${pinoutsESP-EYE.Y5_GPIO_NUM} ; Set the Y5 pin - -DY4_GPIO_NUM=${pinoutsESP-EYE.Y4_GPIO_NUM} ; Set the Y4 pin - -DY3_GPIO_NUM=${pinoutsESP-EYE.Y3_GPIO_NUM} ; Set the Y3 pin - -DY2_GPIO_NUM=${pinoutsESP-EYE.Y2_GPIO_NUM} ; Set the Y2 pin - -DVSYNC_GPIO_NUM=${pinoutsESP-EYE.VSYNC_GPIO_NUM} ; Set the VSYNC pin - -DHREF_GPIO_NUM=${pinoutsESP-EYE.HREF_GPIO_NUM} ; Set the HREF pin - -DPCLK_GPIO_NUM=${pinoutsESP-EYE.PCLK_GPIO_NUM} ; Set the PCLK pin + -DPWDN_GPIO_NUM=${pinoutsESP-EYE.PWDN_GPIO_NUM} + -DRESET_GPIO_NUM=${pinoutsESP-EYE.RESET_GPIO_NUM} + -DXCLK_GPIO_NUM=${pinoutsESP-EYE.XCLK_GPIO_NUM} + -DSIOD_GPIO_NUM=${pinoutsESP-EYE.SIOD_GPIO_NUM} + -DSIOC_GPIO_NUM=${pinoutsESP-EYE.SIOC_GPIO_NUM} + -DY9_GPIO_NUM=${pinoutsESP-EYE.Y9_GPIO_NUM} + -DY8_GPIO_NUM=${pinoutsESP-EYE.Y8_GPIO_NUM} + -DY7_GPIO_NUM=${pinoutsESP-EYE.Y7_GPIO_NUM} + -DY6_GPIO_NUM=${pinoutsESP-EYE.Y6_GPIO_NUM} + -DY5_GPIO_NUM=${pinoutsESP-EYE.Y5_GPIO_NUM} + -DY4_GPIO_NUM=${pinoutsESP-EYE.Y4_GPIO_NUM} + -DY3_GPIO_NUM=${pinoutsESP-EYE.Y3_GPIO_NUM} + -DY2_GPIO_NUM=${pinoutsESP-EYE.Y2_GPIO_NUM} + -DVSYNC_GPIO_NUM=${pinoutsESP-EYE.VSYNC_GPIO_NUM} + -DHREF_GPIO_NUM=${pinoutsESP-EYE.HREF_GPIO_NUM} + -DPCLK_GPIO_NUM=${pinoutsESP-EYE.PCLK_GPIO_NUM} [pinoutsESPWROVER] -; CAMERA_MODULE_WROVER_KIT -PWDN_GPIO_NUM = -1 -RESET_GPIO_NUM = -1 -XCLK_GPIO_NUM = 21 -SIOD_GPIO_NUM = 26 -SIOC_GPIO_NUM = 27 -Y9_GPIO_NUM = 35 -Y8_GPIO_NUM = 34 -Y7_GPIO_NUM = 39 -Y6_GPIO_NUM = 36 -Y5_GPIO_NUM = 19 -Y4_GPIO_NUM = 18 -Y3_GPIO_NUM = 5 -Y2_GPIO_NUM = 4 -VSYNC_GPIO_NUM = 25 -HREF_GPIO_NUM = 23 -PCLK_GPIO_NUM = 22 - +pwdn_gpio_num = -1 +reset_gpio_num = -1 +xclk_gpio_num = 21 +siod_gpio_num = 26 +sioc_gpio_num = 27 +y9_gpio_num = 35 +y8_gpio_num = 34 +y7_gpio_num = 39 +y6_gpio_num = 36 +y5_gpio_num = 19 +y4_gpio_num = 18 +y3_gpio_num = 5 +y2_gpio_num = 4 +vsync_gpio_num = 25 +href_gpio_num = 23 +pclk_gpio_num = 22 build_flags = '-DCAMERA_MODULE_NAME="ESP32_CAM_WROVER_KIT"' -DCONFIG_CAMERA_MODULE_WROVER_KIT=1 - ; CAMERA PINOUT DEFINITIONS - -DPWDN_GPIO_NUM=${pinoutsESPWROVER.PWDN_GPIO_NUM} ; Set the PWDN pin - -DRESET_GPIO_NUM=${pinoutsESPWROVER.RESET_GPIO_NUM} ; Set the RESET pin - -DXCLK_GPIO_NUM=${pinoutsESPWROVER.XCLK_GPIO_NUM} ; Set the XCLK pin - -DSIOD_GPIO_NUM=${pinoutsESPWROVER.SIOD_GPIO_NUM} ; Set the SIOD pin - -DSIOC_GPIO_NUM=${pinoutsESPWROVER.SIOC_GPIO_NUM} ; Set the SIOC pin - -DY9_GPIO_NUM=${pinoutsESPWROVER.Y9_GPIO_NUM} ; Set the Y9 pin - -DY8_GPIO_NUM=${pinoutsESPWROVER.Y8_GPIO_NUM} ; Set the Y8 pin - -DY7_GPIO_NUM=${pinoutsESPWROVER.Y7_GPIO_NUM} ; Set the Y7 pin - -DY6_GPIO_NUM=${pinoutsESPWROVER.Y6_GPIO_NUM} ; Set the Y6 pin - -DY5_GPIO_NUM=${pinoutsESPWROVER.Y5_GPIO_NUM} ; Set the Y5 pin - -DY4_GPIO_NUM=${pinoutsESPWROVER.Y4_GPIO_NUM} ; Set the Y4 pin - -DY3_GPIO_NUM=${pinoutsESPWROVER.Y3_GPIO_NUM} ; Set the Y3 pin - -DY2_GPIO_NUM=${pinoutsESPWROVER.Y2_GPIO_NUM} ; Set the Y2 pin - -DVSYNC_GPIO_NUM=${pinoutsESPWROVER.VSYNC_GPIO_NUM} ; Set the VSYNC pin - -DHREF_GPIO_NUM=${pinoutsESPWROVER.HREF_GPIO_NUM} ; Set the HREF pin - -DPCLK_GPIO_NUM=${pinoutsESPWROVER.PCLK_GPIO_NUM} ; Set the PCLK pin + -DPWDN_GPIO_NUM=${pinoutsESPWROVER.PWDN_GPIO_NUM} + -DRESET_GPIO_NUM=${pinoutsESPWROVER.RESET_GPIO_NUM} + -DXCLK_GPIO_NUM=${pinoutsESPWROVER.XCLK_GPIO_NUM} + -DSIOD_GPIO_NUM=${pinoutsESPWROVER.SIOD_GPIO_NUM} + -DSIOC_GPIO_NUM=${pinoutsESPWROVER.SIOC_GPIO_NUM} + -DY9_GPIO_NUM=${pinoutsESPWROVER.Y9_GPIO_NUM} + -DY8_GPIO_NUM=${pinoutsESPWROVER.Y8_GPIO_NUM} + -DY7_GPIO_NUM=${pinoutsESPWROVER.Y7_GPIO_NUM} + -DY6_GPIO_NUM=${pinoutsESPWROVER.Y6_GPIO_NUM} + -DY5_GPIO_NUM=${pinoutsESPWROVER.Y5_GPIO_NUM} + -DY4_GPIO_NUM=${pinoutsESPWROVER.Y4_GPIO_NUM} + -DY3_GPIO_NUM=${pinoutsESPWROVER.Y3_GPIO_NUM} + -DY2_GPIO_NUM=${pinoutsESPWROVER.Y2_GPIO_NUM} + -DVSYNC_GPIO_NUM=${pinoutsESPWROVER.VSYNC_GPIO_NUM} + -DHREF_GPIO_NUM=${pinoutsESPWROVER.HREF_GPIO_NUM} + -DPCLK_GPIO_NUM=${pinoutsESPWROVER.PCLK_GPIO_NUM} [common] platform = espressif32 @@ -233,19 +228,16 @@ monitor_speed = 115200 monitor_rts = 0 monitor_dtr = 0 monitor_filters = - ;colorize -- uncomment this to get a colorful text in your terminal log2file time default esp32_exception_decoder - -;board_build.partitions = ${common.board_build.partitions} ; uncomment this to use the huge_app partition table, does not support OTA and only 1MB of SPIFFS -board_build.partitions = min_spiffs.csv ; uncomment this to use the min_spiffs partition table, supports OTA and 1MB of SPIFFS - +board_build.partitions = min_spiffs.csv build_flags = - !python tools/\autoversioning.py ; add build version to build as preprocessor defines - !python tools/\git_rev.py ; add git revision to build as preprocessor defines - -DOTA_SERVER_PORT=${wifi.OTAServerPort} ; Set the OTA server + ${ESP_GITHUB_OTA.build_flags} + !python tools/autoversioning.py + !python tools/git_rev.py + -DOTA_SERVER_PORT=${wifi.OTAServerPort} -DENABLE_ADHOC=${wifi.enableADHOC} -DADHOC_CHANNEL=${wifi.adhocChannel} -DWIFI_CHANNEL=${wifi.channel} @@ -270,10 +262,9 @@ upload_speed = 115200 ;115200 is used for compatability - if you are on windows lib_deps = ;esp32-camera leftcoast/LC_baseTools@^1.5 - ; geeksville/Micro-RTSP @ ^0.1.6 ; Micro-RTSP library for streaming video over RTSP - will be implemented soon + bblanchon/ArduinoJson@^6.19.4 https://github.com/me-no-dev/ESPAsyncWebServer.git https://github.com/me-no-dev/AsyncTCP.git - https://github.com/bblanchon/ArduinoJson.git build_type = debug extra_scripts = pre:tools/customname.py @@ -286,11 +277,11 @@ monitor_speed = ${common.monitor_speed} monitor_rts = ${common.monitor_rts} monitor_dtr = ${common.monitor_dtr} monitor_filters = ${common.monitor_filters} -;build_unflags = ${common.build_unflags} -board_build.partitions = ${common.board_build.partitions} ; +board_build.partitions = ${common.board_build.partitions} lib_ldf_mode = ${common.lib_ldf_mode} upload_speed = ${common.upload_speed} -lib_deps = ${common.lib_deps} +lib_deps = + ${common.lib_deps} build_type = ${common.build_type} extra_scripts = ${common.extra_scripts} build_flags = ${common.build_flags} @@ -306,11 +297,11 @@ framework = ${common.framework} monitor_speed = ${common.monitor_speed} monitor_rts = ${common.monitor_rts} monitor_dtr = ${common.monitor_dtr} -;build_unflags = ${common.build_unflags} -board_build.partitions = ${common.board_build.partitions} ; +board_build.partitions = ${common.board_build.partitions} lib_ldf_mode = ${common.lib_ldf_mode} upload_speed = ${common.upload_speed} -lib_deps = ${common.lib_deps} +lib_deps = + ${common.lib_deps} build_type = release extra_scripts = ${common.extra_scripts} build_flags = ${common.build_flags} @@ -336,8 +327,129 @@ upload_speed = ${common.upload_speed} monitor_speed = ${common.monitor_speed} monitor_rts = ${common.monitor_rts} monitor_dtr = ${common.monitor_dtr} -;build_unflags = ${common.build_unflags} -board_build.partitions = ${common.board_build.partitions} ; uncomment this to use the min_spiffs partition table, great for using OTA +board_build.partitions = ${common.board_build.partitions} +lib_ldf_mode = ${common.lib_ldf_mode} +upload_port = 192.168.1.38 +upload_protocol = espota +upload_flags = + --port=3232 + --auth=12345678 +build_type = release +extra_scripts = ${common.extra_scripts} + +[env:esp_eye] +platform = ${common.platform} +board = esp32cam +framework = ${common.framework} +monitor_speed = ${common.monitor_speed} +monitor_filters = ${common.monitor_filters} +board_build.partitions = ${common.board_build.partitions} +lib_ldf_mode = ${common.lib_ldf_mode} +upload_speed = ${common.upload_speed} +lib_deps = + ${common.lib_deps} +build_type = ${common.build_type} +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + -DDEBUG_MODE=1 + -DCORE_DEBUG_LEVEL=4 + ${pinoutsESP-EYE.build_flags} + +[env:esp_eye_release] +platform = ${common.platform} +board = esp32cam +framework = ${common.framework} +monitor_speed = ${common.monitor_speed} +board_build.partitions = ${common.board_build.partitions} +lib_ldf_mode = ${common.lib_ldf_mode} +upload_speed = ${common.upload_speed} +lib_deps = + ${common.lib_deps} +build_type = release +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + -DDEBUG_MODE=0 + -DCORE_DEBUG_LEVEL=1 + ${pinoutsESP-EYE.build_flags} + +[env:esp_eye_OTA] +platform = ${common.platform} +board = esp32cam +framework = ${common.framework} +build_flags = ${common.build_flags} + -DDEBUG_MODE=0 + -DCORE_DEBUG_LEVEL=1 + -DDEBUG_ESP_OTA + ${pinoutsESP-EYE.build_flags} +lib_deps = + ${common.lib_deps} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +board_build.partitions = ${common.board_build.partitions} +lib_ldf_mode = ${common.lib_ldf_mode} +upload_port = 192.168.1.38 +upload_protocol = espota +upload_flags = + --port=3232 + --auth=12345678 +build_type = release +extra_scripts = ${common.extra_scripts} + +[env:esp32M5Stack] +platform = ${common.platform} +board = esp32cam +framework = ${common.framework} +monitor_speed = ${common.monitor_speed} +monitor_rts = ${common.monitor_rts} +monitor_dtr = ${common.monitor_dtr} +monitor_filters = ${common.monitor_filters} +board_build.partitions = ${common.board_build.partitions} +lib_ldf_mode = ${common.lib_ldf_mode} +upload_speed = ${common.upload_speed} +lib_deps = + ${common.lib_deps} +build_type = ${common.build_type} +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + -DDEBUG_MODE=1 + -DCORE_DEBUG_LEVEL=4 + ${pinoutsESPCAM_M5STACK.build_flags} + +[env:esp32M5Stack_release] +platform = ${common.platform} +board = esp32cam +framework = ${common.framework} +monitor_speed = ${common.monitor_speed} +monitor_rts = ${common.monitor_rts} +monitor_dtr = ${common.monitor_dtr} +board_build.partitions = ${common.board_build.partitions} +lib_ldf_mode = ${common.lib_ldf_mode} +upload_speed = ${common.upload_speed} +lib_deps = + ${common.lib_deps} +build_type = release +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + -DDEBUG_MODE=0 + -DCORE_DEBUG_LEVEL=1 + ${pinoutsESPCAM_M5STACK.build_flags} + +[env:esp32M5Stack_OTA] +platform = ${common.platform} +board = esp32cam +framework = ${common.framework} +build_flags = ${common.build_flags} + -DDEBUG_MODE=0 + -DCORE_DEBUG_LEVEL=1 + -DDEBUG_ESP_OTA + ${pinoutsESPCAM_M5STACK.build_flags} +lib_deps = + ${common.lib_deps} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +monitor_rts = ${common.monitor_rts} +monitor_dtr = ${common.monitor_dtr} +board_build.partitions = ${common.board_build.partitions} lib_ldf_mode = ${common.lib_ldf_mode} upload_port = 192.168.1.38 upload_protocol = espota @@ -478,7 +590,7 @@ board_build.partitions = ${common.board_build.partitions} ; uncomment this to us lib_ldf_mode = ${common.lib_ldf_mode} upload_port = 192.168.1.38 upload_protocol = espota -upload_flags = +upload_flags = --port=3232 --auth=12345678 build_type = release @@ -551,7 +663,7 @@ board_build.partitions = ${common.board_build.partitions} ; uncomment this to us lib_ldf_mode = ${common.lib_ldf_mode} upload_port = 192.168.1.38 upload_protocol = espota -upload_flags = +upload_flags = --port=3232 --auth=12345678 build_type = release @@ -565,16 +677,13 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_speed = ${common.monitor_speed} monitor_filters = ${common.monitor_filters} -;build_unflags = ${common.build_unflags} board_build.partitions = ${common.board_build.partitions} lib_ldf_mode = ${common.lib_ldf_mode} upload_speed = ${common.upload_speed} -lib_deps = ${common.lib_deps} -;upload_port = COM6 +lib_deps = + ${common.lib_deps} build_type = ${common.build_type} extra_scripts = ${common.extra_scripts} -;monitor_rts = ${common.monitor_rts} -;monitor_dtr = ${common.monitor_dtr} build_flags = ${common.build_flags} -DCORE_DEBUG_LEVEL=4 ; set the debug level -DDEBUG_MODE=1 ; Set the debug mode @@ -588,23 +697,19 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_speed = ${common.monitor_speed} monitor_filters = ${common.monitor_filters} -;build_unflags = ${common.build_unflags} -board_build.partitions = ${common.board_build.partitions} ; uncomment this to use the huge_app partition table, does not support OTA and only 1MB of SPIFFS +board_build.partitions = ${common.board_build.partitions} lib_ldf_mode = ${common.lib_ldf_mode} upload_speed = ${common.upload_speed} -lib_deps = ${common.lib_deps} -;upload_port = COM6 +lib_deps = + ${common.lib_deps} build_type = ${common.build_type} extra_scripts = ${common.extra_scripts} -;monitor_rts = ${common.monitor_rts} -;monitor_dtr = ${common.monitor_dtr} build_flags = ${common.build_flags} -DDEBUG_MODE=0 ; Set the debug mode -DCORE_DEBUG_LEVEL=1 ; CAMERA PINOUT DEFINITIONS ${pinoutsESPWROVER.build_flags} -; Experimental OTA Environment - do not select unless you know what you are doing [env:wrover_OTA] platform = ${common.platform} board = esp-wrover-kit @@ -613,16 +718,13 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_speed = ${common.monitor_speed} monitor_filters = ${common.monitor_filters} -;build_unflags = ${common.build_unflags} -board_build.partitions = ${common.board_build.partitions} ; uncomment this to use the min_spiffs partition table, great for using OTA +board_build.partitions = ${common.board_build.partitions} lib_ldf_mode = ${common.lib_ldf_mode} upload_speed = ${common.upload_speed} -lib_deps = ${common.lib_deps} +lib_deps = + ${common.lib_deps} build_type = release extra_scripts = ${common.extra_scripts} -;monitor_rts = ${common.monitor_rts} -;monitor_dtr = ${common.monitor_dtr} - build_flags = ${common.build_flags} -DDEBUG_MODE=0 ; Set the debug mode -DCORE_DEBUG_LEVEL=1 diff --git a/ESP/src/main.cpp b/ESP/src/main.cpp index 1ca31a7..b01a24b 100644 --- a/ESP/src/main.cpp +++ b/ESP/src/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -9,12 +10,12 @@ //! TODO: Setup OTA enabled state to be controllable by API if enabled at compile time #if ENABLE_OTA #include +#include "network/OTA/Github_OTA/GithubOTAHandler.hpp" #endif // ENABLE_OTA #include #include -//#include -//#include // Serial Manager +// #include int STREAM_SERVER_PORT = 80; int CONTROL_SERVER_PORT = 81; @@ -28,13 +29,15 @@ int CONTROL_SERVER_PORT = 81; ProjectConfig deviceConfig("openiris", MDNS_HOSTNAME); #if ENABLE_OTA OTA ota(&deviceConfig); +GithubOTAHandler otaHandler; #endif // ENABLE_OTA LEDManager ledManager(33); CameraHandler cameraHandler(&deviceConfig, &ledStateManager); // SerialManager serialManager(&deviceConfig); WiFiHandler wifiHandler(&deviceConfig, &wifiStateManager, WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL); -APIServer apiServer(CONTROL_SERVER_PORT, &deviceConfig, &cameraHandler, &wifiStateManager, "/control"); +APIServer apiServer(CONTROL_SERVER_PORT, &deviceConfig, &cameraHandler, &wifiStateManager, &otaHandler, "/control"); MDNSHandler mdnsHandler(&mdnsStateManager, &deviceConfig); +AutoDiscovery autoDiscoveryHandler(&mdnsStateManager, &deviceConfig); StreamServer streamServer(STREAM_SERVER_PORT); void setup() @@ -51,7 +54,22 @@ void setup() deviceConfig.load(); wifiHandler._enable_adhoc = ENABLE_ADHOC; wifiHandler.setupWifi(); - mdnsHandler.startMDNS(); + + mdnsStateManager.setState(MDNSState_e::MDNSState_Starting); + switch (mdnsStateManager.getCurrentState()) + { + case MDNSState_e::MDNSState_Starting: + autoDiscoveryHandler.start(); + break; + case MDNSState_e::MDNSState_Error: + break; + case MDNSState_e::MDNSState_QueryComplete: + mdnsHandler.startMDNS(); + break; + default: + break; + } + switch (wifiStateManager.getCurrentState()) { case WiFiState_e::WiFiState_Disconnected: diff --git a/ESP/tools/version.txt b/ESP/tools/version.txt new file mode 100644 index 0000000..5b0c9c4 --- /dev/null +++ b/ESP/tools/version.txt @@ -0,0 +1 @@ +v1.0.10 \ No newline at end of file