Skip to content

Can not HTTP GET to the full information in wokwi-cli #18

Open
@crjssy

Description

@crjssy

Hi! @urish

I'm trying to use wokwi cli now to use http get ip-location, but the information I get is incomplete, can you check it?
The code as follows:

#include "esp32-ip-to-geolocation.h"
#include <string.h>

#define MAX_HTTP_OUTPUT_BUFFER 8192

char *output_buffer; // Buffer to store HTTP response
int output_len;      // Stores number of bytes in output_buffer

// Global event group for WiFi status
EventGroupHandle_t s_wifi_event_group;
const int WIFI_CONNECTED_BIT = BIT0;
const int WIFI_FAIL_BIT = BIT1;

void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        esp_wifi_connect();
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        esp_wifi_connect();
        ESP_LOGI(TAG, "retry to connect to the AP");
        xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI(TAG, "got ip:%s", ip4addr_ntoa((ip4_addr_t *)&event->ip_info.ip));
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = CONFIG_WIFI_SSID,
            .password = CONFIG_WIFI_PASSWORD,
            .threshold.authmode = CONFIG_WIFI_AUTH_MODE,
        },
    };

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "wifi_init_sta finished.");

    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    if (bits & WIFI_CONNECTED_BIT)
    {
        ESP_LOGI(TAG, "connected to ap SSID:%s", CONFIG_WIFI_SSID);
    }
    else if (bits & WIFI_FAIL_BIT)
    {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s", CONFIG_WIFI_SSID);
    }
    else
    {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }
}

char *output_buffer = NULL; // Buffer to store HTTP response
int output_len = 0; // Stores number of bytes in output_buffer

esp_err_t http_event_handler(esp_http_client_event_t *evt)
{
    switch (evt->event_id)
    {
    case HTTP_EVENT_ERROR:
        ESP_LOGI(TAG, "HTTP_EVENT_ERROR");
        break;
    case HTTP_EVENT_ON_CONNECTED:
        ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
        break;
    case HTTP_EVENT_HEADER_SENT:
        ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
        break;
    case HTTP_EVENT_ON_HEADER:
        ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
        break;
    case HTTP_EVENT_ON_DATA:
        ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
        if (evt->data_len > 0)
        {
            if (output_buffer == NULL)
            {
                // Allocate memory for the output buffer
                output_buffer = (char *)malloc(evt->data_len + 1);
                if (output_buffer == NULL)
                {
                    ESP_LOGE(TAG, "Failed to allocate memory for output buffer");
                    return ESP_FAIL;
                }
                output_len = 0;
            }
            else
            {
                // Reallocate memory for the output buffer to accommodate new data
                char *new_buffer = (char *)realloc(output_buffer, output_len + evt->data_len + 1);
                if (new_buffer == NULL)
                {
                    ESP_LOGE(TAG, "Failed to reallocate memory for output buffer");
                    free(output_buffer);
                    output_buffer = NULL;
                    output_len = 0;
                    return ESP_FAIL;
                }
                output_buffer = new_buffer;
            }

            // Copy the new data into the buffer
            memcpy(output_buffer + output_len, evt->data, evt->data_len);
            output_len += evt->data_len;
            output_buffer[output_len] = '\0'; // Null-terminate the buffer
        }
        break;
    case HTTP_EVENT_ON_FINISH:
        ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");
        if (output_buffer != NULL)
        {
            // Parse the JSON data
            cJSON *json = cJSON_Parse(output_buffer);
            if (json == NULL)
            {
                ESP_LOGE(TAG, "JSON parsing error");
            }
            else
            {
                const char *keys[] = {
                    "status", "country", "countryCode", "region", "regionName", "city",
                    "zip", "lat", "lon", "timezone", "isp", "org", "as", "query"};
                int numKeys = sizeof(keys) / sizeof(keys[0]);
                for (int i = 0; i < numKeys; i++)
                {
                    cJSON *value = cJSON_GetObjectItemCaseSensitive(json, keys[i]);
                    if (cJSON_IsString(value) && (value->valuestring != NULL))
                    {
                        ESP_LOGI(TAG, "%s: %s", keys[i], value->valuestring);
                    }
                    else if (cJSON_IsNumber(value))
                    {
                        ESP_LOGI(TAG, "%s: %f", keys[i], value->valuedouble);
                    }
                }

                cJSON_Delete(json);
            }
            // Free the output buffer
            free(output_buffer);
            output_buffer = NULL;
            output_len = 0;
        }
        break;
    case HTTP_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
        if (output_buffer != NULL)
        {
            free(output_buffer);
            output_buffer = NULL;
            output_len = 0;
        }
        break;
    case HTTP_EVENT_REDIRECT:
        ESP_LOGI(TAG, "HTTP_EVENT_REDIRECT");
        break;
    default:
        ESP_LOGI(TAG, "Unhandled HTTP event: %d", evt->event_id);
        break;
    }
    return ESP_OK;
}

void http_get_task(void *pvParameters)
{
    output_buffer = NULL;
    output_len = 0;

    esp_http_client_config_t config = {
        .url = URL,
        .event_handler = http_event_handler, 
        .method = HTTP_METHOD_GET
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_err_t err = esp_http_client_perform(client);

    if (err == ESP_OK)
    {
        int status = esp_http_client_get_status_code(client);
        int content_length = esp_http_client_get_content_length(client);
        ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d", status, content_length);
    }
    else
    {
        ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
    }

    esp_http_client_cleanup(client);

    // Free the output buffer if it was allocated
    if (output_buffer != NULL)
    {
        free(output_buffer);
        output_buffer = NULL;
        output_len = 0;
    }
    vTaskDelete(NULL);
}


void app_main()
{
    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);

    esp_log_level_set("*", ESP_LOG_DEBUG);

    wifi_init_sta();
    xTaskCreate(&http_get_task, "http_get_task", 8192, NULL, 5, NULL);
} 

My yml file is as follows:

name:` Build and Test Application

on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main
  workflow_dispatch:

env:
  IDF_PATH: /opt/esp/idf
  test_dirs: .

defaults:
  run:
    shell: bash

jobs:
  build-test-app:
    name: Build Test App
    strategy:
      fail-fast: false
      matrix:
        idf-branch:
          - release-v5.0
          - release-v5.1
        target:
          - esp32
    runs-on: ubuntu-22.04
    container:
      image: espressif/idf:${{ matrix.idf-branch }}
    steps:
      - uses: actions/checkout@v4
      - name: Install Python Dependencies
        run: |
          . $IDF_PATH/export.sh
          python -m pip install --upgrade pip
          python -m pip install idf-build-apps
      - name: Build Test Application with ESP-IDF
        run: |
          . $IDF_PATH/export.sh
          idf-build-apps build \
            -p ${{ env.test_dirs }} \
            --target ${{ matrix.target }} \
            --recursive \
            --build-dir build_${{ matrix.target }}_${{ matrix.idf-branch }} 
            idf.py -DSDKCONFIG=/__w/esp32-ip-to-geolocation/esp32-ip-to-geolocation/sdkconfig build > build_output.log 2>&1
      - name: Upload files to artifacts for run-target job
        uses: actions/upload-artifact@v4
        with:
          name: built_binaries_${{ matrix.target }}_${{ matrix.idf-branch }}
          path: |
            **/build**/bootloader/bootloader.bin
            **/build**/partition_table/partition-table.bin
            **/build**/*.bin
            **/build**/*.elf
            **/build**/flasher_args.json
          if-no-files-found: error

  simulate-test:
    name: Simulate Test on WokWi
    needs: build-test-app
    runs-on: ubuntu-22.04
    strategy:
      fail-fast: false
      matrix:
        idf-branch:
          - release-v5.0
          - release-v5.1
        target:
          - esp32
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.9'  # Ensure this matches the Python version needed for your testing
      - name: Download built binaries from build job
        uses: actions/download-artifact@v4
        with:
          name: built_binaries_${{ matrix.target }}_${{ matrix.idf-branch }}
          path: .
      - name: Install the Wokwi CLI
        run: curl -L https://wokwi.com/ci/install.sh | sh
      - name: Install Python packages for PyTest
        run: pip install -r requirements.txt
      - name: Run Test App in Wokwi Simulation and Save Serial Output
        env:
          WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }}
        run: |
          pytest ${{ env.test_dirs }} \
            --embedded-services idf,wokwi \
            --tb short \
            --junit-xml test_wokwi_${{ matrix.target }}_${{ matrix.idf-branch }}.xml \
            | tee serial_output_${{ matrix.target }}_${{ matrix.idf-branch }}.txt
      - name: Upload Test Results and Serial Output
        uses: actions/upload-artifact@v4
        with:
          name: test_wokwi_${{ matrix.target }}_${{ matrix.idf-branch }}_junit
          path: |
            test_wokwi_${{ matrix.target }}_${{ matrix.idf-branch }}.xml
            serial_output_${{ matrix.target }}_${{ matrix.idf-branch }}.txt

  publish-results:
    name: Publish Test App results
    needs: simulate-test
    runs-on: ubuntu-20.04
    if: always() # (run even if the previous steps have failed)
    steps:
      - name: Download Test results
        uses: actions/download-artifact@v4
        with:
          path: test_results
      - name: Publish Test Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        with:
          files: test_results/**/*.xml

My pytest as follows:

# SPDX-FileCopyrightText: 2022-2024 Hays Chan
# SPDX-License-Identifier: MIT

'''
Steps to run these cases:
- Build
  - . ${IDF_PATH}/export.sh
  - pip install idf_build_apps
  - python tools/build_apps.py components/button/test_apps -t esp32
- Test
  - pip install -r tools/requirements/requirement.pytest.txt
  - pytest components/button/test_apps --target esp32
'''

import pytest
from pytest_embedded import Dut

@pytest.mark.supported_targets("esp32")  # Specify the target, esp32 in this case
def test_esp32_ip_to_geolocation(dut: Dut):
    # Start the test
    dut.expect_exact("wifi_init_sta finished.")
    dut.expect("connected to ap SSID:Wokwi-GUEST")

    dut.expect("HTTP_EVENT_ON_FINISH")  

    # Check for the expected logs from the JSON response
    expected_keys = [
        "status:",
        "country:",
        "countryCode:",
        "region:",
        "regionName:",
        "city:",
        "zip:",
        "lat:",
        "lon:",
        "timezone",
        "isp",
        "org",
        "as",
        "query"
    ]

    #  # Check for a successful HTTP request
    # dut.expect("HTTP GET Status = ", timeout=120)  

    # Check each expected log entry for presence only, not specific content
    for key in expected_keys:
        dut.expect(key)

It doesn't output the full message I want,you can check out my action

https://github.com/crjssy/esp32-ip-to-geolocation/actions/runs/9557963622

Any help on this would be really appreciated! 🙋

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions