Merge branch 'feature/apptrace_visualization' into 'master'

Apptrace realtime visualization support

See merge request espressif/esp-idf!22694
This commit is contained in:
Roland Dobai 2023-07-11 17:33:24 +08:00
commit a065f0fc9f
11 changed files with 667 additions and 8 deletions

View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(app_trace_to_plot)

View File

@ -0,0 +1,138 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# Application Level Tracing Example (Plotting)
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates how to use the [Application Level Tracing Library](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/app_trace.html#) (henceforth referred to as **App Trace**) to send and plot dummy sensor data to a host via JTAG instead of the normal method of logging via UART.
UART logs are time consuming and can significantly slow down the function that calls it. Therefore, it is generally a bad idea to use UART logs in time-critical functions. Logging to host via JTAG is significantly faster and can be used in time-critical functions. For more details regarding logging to host via JTAG, refer to the [Logging to Host Documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/app_trace.html#app-trace-logging-to-host).
### Hardware Required
To run this example, you need a supported target dev board connected to a JTAG adapter, which can come in the following forms:
* [ESP-WROVER-KIT](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/modules-and-boards.html#esp-wrover-kit-v4-1) which integrates an on-board JTAG adapter. Ensure that the [required jumpers to enable JTAG are connected](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/get-started-wrover-kit.html#setup-options) on the WROVER-KIT.
* ESP32, ESP32-S2 or ESP32-C2 core board (e.g. ESP32-DevKitC, [ESP32-S2-Saola-1](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/hw-reference/esp32s2/user-guide-saola-1-v1.2.html)) can also work as long as you connect it to an external JTAG adapter (e.g. FT2232H, J-LINK).
- For ESP32-C3 or ESP32-S3, any board with the built-in USB interface (USB_SERIAL_JTAG).
This example will assume that an ESP-WROVER-KIT is used.
#### Connections:
1. Connect the JTAG interface to the target board. For details about how to set up JTAG interface, please see [JTAG Debugging](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html). Power up both the JTAG debugger and target board.
2. To start the tcp socket server, you need to run `read_trace.py` tool under the `esp-idf/examples/system/app_trace_to_plot` path.
3. After connecting JTAG interface and starting the tcp socket server, you need to [Run OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd).
### Configure the project
```
idf.py menuconfig
```
* To enable application tracing, select the `(X) Trace memory` option under `Component config > Application Level Tracing`. This option should have been selected by default.
### Build, Flash, and Run
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
**Run Plotting Tool** To plot data and open TCP socket, there is a tool named `read_trace.py` under the `examples/system/app_trace_to_plot` path. Run this tool in the terminal session with configured IDF development environment by entering the command below. This command opens a TCP socket and plots the given data when OpenOCD triggered to start App Trace. If you are running tool at first time, you need to install dash with `pip install dash` in the same terminal session after running configuring IDF development environment.
```bash
python read_trace.py --plot-config data.json --source tcp://localhost:53535 --output-file data.log
```
**Start App Trace:** Start OpenOCD and App Trace on the target by entering the command below. This command will start OpenOCD and collect all of the bytes from JTAG log data and send them to the tcp socket `tcp://localhost:53535` (note `tcp://` depends on which IP address and port number openned). Assuming that OpenOCD was started in this example's directory, `data.log` will be saved here as well.
After running the plotting tool and starting apptrace with OpenOCD separately, you need access plotting on related socket address. Default address is `http://127.0.0.1:8055/` and also address can be seen from `read_trace.py` output. You can see the plotting result by accessing the address from browser.
```bash
idf.py openocd --openocd-commands 'reset;esp apptrace start tcp://localhost:53535 0 -1 5'
```
**Note:** data.json file is an example for plot config file. It can be changed or modified.
**Note:** For more details on OpenOCD commands regarding App Trace, refer to the [OpenOCD Application Level Tracing Commands](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/app_trace.html#openocd-application-level-tracing-commands)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
### Configration file and data format
#### General format of json file is:
```JSON
"Subplot Name": {
"data_streams"
"Name of the sensor": {
"id": int
"x_axis_data_size": int
"x_axis_timestamp": bool
"y_axis_data_size": int
"data_type": "string"
["precision": int]
["type": "string"]
["mode": "string"]
["any_plot_config": Type defined in plotly]
}
}
"xaxis_title": "string",
"yaxis_title": "string"
}
```
#### Explanations of JSON keys are:
| JSON Keys | Explanation |
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| id | ID value of the incoming packet (e.g. 1, 2, 3) |
| x_axis_data_size | Data size of x axis of the incoming packet in bytes (e.g 2, 4, 8) |
| x_axis_timestamp | Will x axis data be timestamp value (e.g. true, false) |
| y_axis_data_size | Data size of y axis of the incoming packet in bytes (e.g 2, 4, 8) |
| data_type | Data type of y axis of the incoming packet (e.g. "int", "float", "double") |
| precision | Optional. If y axis data of the incoming packet is a floating point number, you need to send it in integer form by multiplying a power of 10 and adjust this field (e.g. 1, 2, 3) |
| type | Optional. Determines the plotting type of the graph (e.g. "scatter", "bar"). Default is "scatter" |
| mode | Optional. Determines how values will look like (e.g. "lines", "markers", "lines+markers"). Default is "lines" |
| xaxis_title | Title of x axis in the plot (e.g "time") |
| yaxis_title | Title of y axis in the plot (e.g "values", "sensor data") |
**Note:** Plotting works with plotly subplot feature. `id`, `timestamp_flag`, `data_size` and `data_type` are the mandatory elements. In addition to these elements, you can config your plot with [properties are passed to the constructor of the specified trace type](https://plotly.com/python/reference/scatter/). Data transfering is in {'x': ..., 'y': ..., ...} format. Graph types like Pie or Scatter3D won't work. Scatter, histogram, box, line and similar graph types are working.
#### Data format in microcontroller side is:
```C
struct format {
char STX[5] = "esp32",
uint32_t id,
any_size y_axis_value,
any_size x_axis_value
char EXT = 0x03,
}
```
**Note** STX and EXT fields are mandatory and constant to check data validity in visualizer side. You cannot change or remove these fields.
**Note** y_axis_value and x_axis_value could be in any value and different sizes like long, short, and int. The only thing to do is enter the correct data size in the config file.
**Note:** If you want to send a floating point number, send it in integer form by multiplying a power of 10 and filling the precision field in the config file.
## Troubleshooting
### Unable to flash when OpenOCD is connected to the target
On ESP32 boards, one likely cause would be an incorrect SPI flash voltage when starting OpenOCD. Suppose a target board/module with a 3.3 V powered SPI flash is being used, but the configuration file (ex. `board/esp32-wrover.cfg` for ESP32) is selected when starting OpenOCD which can set the SPI flash voltage to 1.8 V. In this situation, the SPI flash will not work after OpenOCD connects to the target as OpenOCD has changed the SPI flash voltage. Therefore, you might not be able to flash to the target when OpenOCD is connected.
To work around this issue, users are suggested to use `board/esp32-wrover.cfg` for ESP32 boards/modules operating with an SPI flash voltage of 1.8 V, and `board/esp-wroom-32.cfg` for 3.3 V. Refer to [ESP32 Modules and Boards](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/modules-and-boards.html) and [Set SPI Flash Voltage](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/tips-and-quirks.html#why-to-set-spi-flash-voltage-in-openocd-configuration) for more details.
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)

View File

@ -0,0 +1,72 @@
{
"//data.json": "Apptrace plotting config file for 'read_trace.py'",
"//Summary and usage": "This file is used for apptrace plotting. You can use this json file with '--plot-config' parameter of the 'read_trace.py' file is to configure graphs for visualizing sensor data",
"//More information": "To get more information about apptrace plotting and configuration file, please check 'app_trace_to_plot' example in '../examples/system' and 'read_trace.py' file",
"Temperature sensors": {
"data_streams" : {
"Outside temperature": {
"id": 1,
"x_axis_data_size": 4,
"x_axis_timestamp":true,
"y_axis_data_size": 1,
"data_type": "int",
"opacity": 0.5,
"line_color":"crimson",
"marker_line_width":2,
"marker_size":9,
"mode": "lines+markers"
},
"Engine temperature": {
"id": 2,
"x_axis_data_size": 4,
"x_axis_timestamp":true,
"y_axis_data_size": 2,
"data_type": "int",
"line_color": "#0d0887",
"mode": "lines+markers"
}
},
"xaxis_title": "Timestamp",
"yaxis_title": "Temperature in Celcius"
},
"Altitude sensors": {
"data_streams" : {
"Altitude": {
"id": 3,
"x_axis_data_size": 4,
"x_axis_timestamp":true,
"y_axis_data_size": 4,
"data_type": "int",
"fill": "tozeroy",
"fillcolor": "gray",
"hovertext": "(in meter)",
"type": "scatter",
"fillpattern": {
"shape": "x"
},
"error_y": {
"type":"percent",
"value": 10
},
"mode": "markers"
}
},
"xaxis_title": "Timestamp",
"yaxis_title": "Altitude in meters"
},
"Pressure sensors": {
"data_streams" : {
"Pressure": {
"id": 4,
"x_axis_data_size": 4,
"x_axis_timestamp":true,
"y_axis_data_size": 8,
"data_type": "float",
"precision": 2,
"type": "bar"
}
},
"xaxis_title": "Timestamp",
"yaxis_title": "Pressure in milibar"
}
}

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "app_trace_to_plot.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,167 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Application Trace to Plot Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_app_trace.h"
#include "esp_log.h"
#include <math.h>
#include <limits.h>
#define CYCLE_PERIOD 51
#define STX "esp32"
#define STX_LENGTH 5
#define ETX 0x03
#define MAX_PACKAGE_SIZE 25
static const char *TAG = "example";
typedef struct {
uint32_t id;
uint32_t timestamp;
} __attribute__((packed)) package_header_t;
typedef struct {
package_header_t header;
int8_t value;
} __attribute__((packed)) sensor_data_pkt_t;
typedef struct {
package_header_t header;
int16_t value;
} __attribute__((packed)) sensor2_data_pkt_t;
typedef struct {
package_header_t header;
int32_t value;
} __attribute__((packed)) sensor3_data_pkt_t;
typedef struct {
package_header_t header;
int64_t value;
} __attribute__((packed)) sensor4_data_pkt_t;
int16_t get_engine_temperature(int cnt)
{
/* A healhty engine should work between 90 to 105 degree Celcius */
const int32_t temp_arr[] = {1, 1, 2, 3, 4, 6, 8, 10, 13, 16, 19, 24, 29, 34, 40, 47, 53,
60, 68, 75, 82, 88, 94, 99, 103, 105, 107, 107, 106, 104, 101, 96, 91, 85, 78, 71, 64,
57, 50, 43, 37, 31, 26, 21, 17, 14, 11, 9, 7, 5, 4, 3};
return temp_arr[cnt];
}
int8_t get_outside_temperature(int cnt)
{
/* Recorded hightest temperature was around 57 degree and lowest was -89 degree Celcius */
return (rand() % 146) - 89;
}
int32_t get_altitude(void)
{
/* Very high altidude is around 4000 meter */
return rand() % 4000;
}
int32_t get_pressure(int cnt)
{
/* Highest pressure recorded around 1.084b and lowest was 0.870b on earth */
return (int)(((sin(cnt) / 10) + 1) * 100);
}
void app_main(void)
{
ESP_LOGI(TAG, "Waiting for OpenOCD connection");
while (!esp_apptrace_host_is_connected(ESP_APPTRACE_DEST_JTAG)) {
vTaskDelay(1);
}
ESP_LOGI(TAG, "Sending data to the host...");
uint32_t cnt = 0;
uint8_t buf[MAX_PACKAGE_SIZE] = {0};
sensor_data_pkt_t sensor;
sensor2_data_pkt_t sensor2;
sensor3_data_pkt_t sensor3;
sensor4_data_pkt_t sensor4;
memcpy(buf, (uint8_t *)&STX, STX_LENGTH);
while (esp_apptrace_host_is_connected(ESP_APPTRACE_DEST_JTAG)) {
sensor.header.id = 1;
sensor.header.timestamp = esp_log_timestamp();
sensor.value = get_outside_temperature(cnt);
buf[STX_LENGTH] = sizeof(sensor);
memcpy(buf + STX_LENGTH + 1, (uint8_t *)&sensor, sizeof(sensor));
buf[STX_LENGTH + 1 + sizeof(sensor)] = ETX;
esp_err_t res = esp_apptrace_write(ESP_APPTRACE_DEST_JTAG, buf, sizeof(sensor) + STX_LENGTH + 1/*sensor pkt len*/ + 1/*ETX len*/, ESP_APPTRACE_TMO_INFINITE);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Failed to write data to host [0x%x] (%s)", res, esp_err_to_name(res));
break;
}
/* Delay added for represent the data better on the plot */
vTaskDelay(10 / portTICK_PERIOD_MS);
sensor2.header.id = 2;
sensor2.header.timestamp = esp_log_timestamp();
sensor2.value = get_engine_temperature(cnt);
buf[STX_LENGTH] = sizeof(sensor2);
memcpy(buf + STX_LENGTH + 1, (uint8_t *)&sensor2, sizeof(sensor2));
buf[STX_LENGTH + 1 + sizeof(sensor2)] = ETX;
res = esp_apptrace_write(ESP_APPTRACE_DEST_JTAG, buf, sizeof(sensor2) + STX_LENGTH + 1/*sensor pkt len*/ + 1/*ETX len*/, ESP_APPTRACE_TMO_INFINITE);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Failed to write data to host [0x%x] (%s)", res, esp_err_to_name(res));
break;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
if (cnt % 2 == 0) {
sensor3.header.id = 3;
sensor3.header.timestamp = esp_log_timestamp();
sensor3.value = get_altitude();
buf[STX_LENGTH] = sizeof(sensor3);
memcpy(buf + STX_LENGTH + 1, (uint8_t *)&sensor3, sizeof(sensor3));
buf[STX_LENGTH + 1 + sizeof(sensor3)] = ETX;
res = esp_apptrace_write(ESP_APPTRACE_DEST_JTAG, buf, sizeof(sensor3) + STX_LENGTH + 1/*sensor pkt len*/ + 1/*ETX len*/, ESP_APPTRACE_TMO_INFINITE);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Failed to write data to host [0x%x] (%s)", res, esp_err_to_name(res));
break;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
sensor4.header.id = 4;
sensor4.header.timestamp = esp_log_timestamp();
sensor4.value = get_pressure(cnt);
buf[STX_LENGTH] = sizeof(sensor4);
memcpy(buf + STX_LENGTH + 1, (uint8_t *)&sensor4, sizeof(sensor4));
buf[STX_LENGTH + 1 + sizeof(sensor4)] = ETX;
res = esp_apptrace_write(ESP_APPTRACE_DEST_JTAG, buf, sizeof(sensor4) + STX_LENGTH + 1/*sensor pkt len*/ + 1/*ETX len*/, ESP_APPTRACE_TMO_INFINITE);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Failed to write data to host [0x%x] (%s)", res, esp_err_to_name(res));
break;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
esp_apptrace_flush(ESP_APPTRACE_DEST_JTAG, 1000);
cnt = (cnt + 1) % CYCLE_PERIOD;
}
ESP_LOGE(TAG, "Apptrace connection lost");
ESP_LOGI(TAG, "Data sent and getting back to the UART...");
ESP_LOGI(TAG, "Done!");
}

View File

@ -0,0 +1,264 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import argparse
import datetime
import json
import os
import signal
import sys
from enum import Enum
from functools import partial
from typing import Any, List
try:
import espytrace.apptrace
except ImportError: # cheat and use IDF's copy of espytrace if available
idf_path = os.getenv('IDF_PATH')
if not idf_path or not os.path.exists(idf_path):
print('IDF not found. Please export idf')
raise SystemExit(1)
sys.path.insert(0, os.path.join(idf_path, 'tools', 'esp_app_trace'))
import espytrace.apptrace
try:
import dash
from dash import dcc, html
from dash.dependencies import Input, Output
from plotly.subplots import make_subplots
except ImportError:
print('Dash not found. Try to run \'pip install dash\'')
raise SystemExit(1)
plots = [] # type: List[Any]
output_lines = [] # type: List[Any]
COMMENT_LINE = '//'
class States(Enum):
STX_WAIT = 1
LENGTH_WAIT = 2
DATA_WAIT = 3
ETX_WAIT = 4
app = dash.Dash(__name__)
app.layout = html.Div(
html.Div([
html.H2('Telemetry Data'),
html.Div(id='live-update-data'),
dcc.Graph(id='live-update-graph', style={'height': 800}), # Height of the plotting area setted to 800px
dcc.Interval(
id='interval-component',
interval=5 * 100, # Graph will be updated every 500 ms
n_intervals=0
)
])
)
# Multiple components can update everytime interval gets fired.
@app.callback(Output('live-update-graph', 'figure'),
Input('interval-component', 'n_intervals'))
def update_graph_live(_n: Any) -> Any: # pylint: disable=undefined-argument
excluded_keys_for_plot = {'id', 'x_axis_data_size','y_axis_data_size', 'data_type', 'precision', 'x_axis_timestamp'}
fig = make_subplots(rows=len(plots), cols=1, vertical_spacing=0.2, subplot_titles=[each_plot['title'] for each_plot in plots])
for i, each_plot in enumerate(plots, start=1):
for each_subplot in each_plot['plots']:
plot_dict = {k: each_subplot[k] for k in each_subplot.keys() - excluded_keys_for_plot}
fig.append_trace(plot_dict, i, 1)
fig['layout']['xaxis{}'.format(i)]['title'] = each_plot['xaxis_title']
fig['layout']['yaxis{}'.format(i)]['title'] = each_plot['yaxis_title']
return fig
def get_value_from_key(input_id: int, key: str) -> Any:
for each_plot in plots:
for each_subplot in each_plot['plots']:
if each_subplot['id'] == input_id:
return each_subplot[key]
return None
def parse_data_and_print(packet: bytes, offset_time: Any) -> None:
ID_LENGTH = 4
x_axis_data_length = 4
y_axis_data_length = 4
input_id = int.from_bytes(packet[:ID_LENGTH], 'little')
data_size = get_value_from_key(input_id, 'x_axis_data_size')
x_axis_data_length = data_size
x_axis_raw_data = int.from_bytes(packet[ID_LENGTH:ID_LENGTH + x_axis_data_length], 'little')
is_timestamp = get_value_from_key(input_id, 'x_axis_timestamp')
if is_timestamp:
x_axis_data = offset_time + datetime.timedelta(seconds=x_axis_raw_data / 1000)
else:
x_axis_data = float(x_axis_raw_data)
data_size = get_value_from_key(input_id, 'y_axis_data_size')
y_axis_data_length = data_size
y_axis_data = int.from_bytes(packet[x_axis_data_length + ID_LENGTH:x_axis_data_length + ID_LENGTH + y_axis_data_length], 'little', signed=True)
if get_value_from_key(input_id, 'data_type') in ['float', 'double']:
precision = get_value_from_key(input_id, 'precision')
y_axis_data = y_axis_data / (10 ** precision)
ct = datetime.datetime.now()
str_ctr = ct.strftime('%x-%X.%f')
output_str = str_ctr + '-> ' + str(packet)
output_lines.append(output_str)
arr = [x_axis_data, y_axis_data]
update_specific_graph_list(int(input_id), arr)
class CustomRequestHandler(espytrace.apptrace.TCPRequestHandler):
"""
Handler for incoming TCP connections
"""
def handle(self) -> None:
STX = b'esp32'
ETX = b'\x03'
STX_LEN = 5
ETX_LEN = 1
DATA_LENGTH_LEN = 1
PACKET_TIMEOUT_SECOND = 3
state = States.STX_WAIT
val_to_parse = b''
length = 0
offset_time = datetime.datetime.now()
start_time = datetime.datetime.now()
data = b''
while not self.server.need_stop:
data += self.request.recv(1024)
if state == States.STX_WAIT:
if len(data) >= STX_LEN and data.find(STX) != -1:
data = data[data.find(STX) + STX_LEN:]
state = States.LENGTH_WAIT
start_time = datetime.datetime.now()
else:
data = data[-STX_LEN:]
if state == States.LENGTH_WAIT:
if len(data) > 0:
state = States.DATA_WAIT
length = int.from_bytes(data[:DATA_LENGTH_LEN], 'little')
data = data[DATA_LENGTH_LEN:]
if state == States.DATA_WAIT:
if len(data) >= length:
state = States.ETX_WAIT
val_to_parse = data[:length]
data = data[length:]
if state == States.ETX_WAIT:
if len(data) >= ETX_LEN and data[:ETX_LEN] == ETX:
state = States.STX_WAIT
parse_data_and_print(val_to_parse, offset_time)
data = data[ETX_LEN:]
if state != States.STX_WAIT and (datetime.datetime.now() - start_time).seconds > PACKET_TIMEOUT_SECOND:
print('Packet timed out. Dropping!')
state = States.STX_WAIT
def read_json(file_path: str) -> Any:
with open(file_path, 'r') as f:
data = json.load(f)
return data
def save_data(file_path: str) -> None:
with open(file_path, 'w') as f:
f.writelines(output_lines)
def signal_handler(output_file_path: str, reader: Any, sig: Any, frame: Any) -> None:
del sig, frame
reader.cleanup()
if output_file_path is not None:
save_data(output_file_path)
sys.exit(0)
def update_specific_graph_list(input_id: int, val: List[Any]) -> None:
for each_plot in plots:
for each_subplot in each_plot['plots']:
if each_subplot['id'] == input_id:
each_subplot['x'].append((val[0]))
each_subplot['y'].append(float(val[1]))
return
def check_entry_and_get_struct(entry: dict, data_label: str) -> dict:
mandatory_key = 'id'
other_keys = {'x': [], 'y': [], 'type': 'scatter'}
ret_dict = entry
if ret_dict.get(mandatory_key) is None:
raise KeyError('ID key is missing')
ret_dict['name'] = data_label
for each_key, each_value in other_keys.items():
if ret_dict.get(each_key) is None:
ret_dict[each_key] = each_value
return ret_dict
def validate_json(json_file: Any) -> None:
mandatory_keys = {'id':int, 'x_axis_data_size': int, 'y_axis_data_size': int, 'data_type': str, 'x_axis_timestamp': bool}
for each_plot in json_file:
if each_plot.startswith(COMMENT_LINE):
continue
try:
each_subplot = json_file[each_plot]['data_streams']
except KeyError:
print('data_streams key not found. Aborting')
raise SystemExit(1)
for each_subplot in json_file[each_plot]['data_streams']:
for key, value in mandatory_keys.items():
try:
val = json_file[each_plot]['data_streams'][each_subplot][key]
if not isinstance(val, value):
print('[{}][data_streams][{}][{}] expected {} found {}'.format(each_plot, each_subplot, key, mandatory_keys[key], type(val)))
raise SystemExit(1)
except KeyError:
print('[{}][data_streams][{}][{}] key not found. Aborting'.format(each_plot, each_subplot, key))
raise SystemExit(1)
def configure_plots(json_file: Any) -> None:
for each_plot in json_file:
if each_plot.startswith(COMMENT_LINE):
continue
data_struct = {'title': each_plot, 'plots': [], 'xaxis_title': json_file[each_plot]['xaxis_title'], 'yaxis_title': json_file[each_plot]['yaxis_title']}
for each_subplot in json_file[each_plot]['data_streams']:
subplot_items = json_file[each_plot]['data_streams'][each_subplot]
plot_data_struct = check_entry_and_get_struct(subplot_items, each_subplot)
data_struct['plots'].append(plot_data_struct)
plots.append(data_struct)
def main() -> None:
parser = argparse.ArgumentParser(description='Apptrace Visualizing Tool')
parser.add_argument('--plot-config', help='Path to json file', required=False, type=str,
default=os.path.realpath(os.path.join(os.path.dirname(__file__), 'data.json')))
parser.add_argument('--source', help='Data source path', required=True, type=str)
parser.add_argument('--output-file', help='Path to program output file in txt format', type=str)
args = parser.parse_args()
output_file_path = args.output_file
json_file_name = args.plot_config
data_source = args.source
json_file = read_json(json_file_name)
validate_json(json_file)
configure_plots(json_file)
reader = espytrace.apptrace.reader_create(data_source, 1, CustomRequestHandler)
signal.signal(signal.SIGINT, partial(signal_handler, output_file_path, reader))
app.run_server(debug=True, use_reloader=False, port=8055)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,3 @@
# Enable application tracing by default
CONFIG_APPTRACE_DEST_JTAG=y
CONFIG_APPTRACE_ENABLE=y

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import json

View File

@ -40,6 +40,7 @@ examples/build_system/cmake/idf_as_lib/run.sh
examples/common_components/protocol_examples_tapif_io/make_tap_netif
examples/storage/parttool/parttool_example.py
examples/storage/parttool/parttool_example.sh
examples/system/app_trace_to_plot/read_trace.py
examples/system/ota/otatool/get_running_partition.py
examples/system/ota/otatool/otatool_example.py
examples/system/ota/otatool/otatool_example.sh

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from __future__ import print_function
@ -288,7 +288,7 @@ class TCPReader(NetReader, SocketServer.TCPServer):
"""
TCP socket reader class
"""
def __init__(self, host, port, tmo):
def __init__(self, host, port, tmo, handler=TCPRequestHandler):
"""
Constructor
@ -301,7 +301,7 @@ class TCPReader(NetReader, SocketServer.TCPServer):
tmo : int
see Reader.__init__()
"""
SocketServer.TCPServer.__init__(self, (host, port), TCPRequestHandler)
SocketServer.TCPServer.__init__(self, (host, port), handler)
NetReader.__init__(self, tmo)
@ -316,7 +316,7 @@ class UDPReader(NetReader, SocketServer.UDPServer):
"""
UDP socket reader class
"""
def __init__(self, host, port, tmo):
def __init__(self, host, port, tmo, handler=UDPRequestHandler):
"""
Constructor
@ -329,11 +329,11 @@ class UDPReader(NetReader, SocketServer.UDPServer):
tmo : int
see Reader.__init__()
"""
SocketServer.UDPServer.__init__(self, (host, port), UDPRequestHandler)
SocketServer.UDPServer.__init__(self, (host, port), handler)
NetReader.__init__(self, tmo)
def reader_create(trc_src, tmo):
def reader_create(trc_src, tmo, handler=None):
"""
Creates trace reader.
@ -357,8 +357,12 @@ def reader_create(trc_src, tmo):
else:
return FileReader(url.path, tmo)
if url.scheme == 'tcp':
if handler is not None:
return TCPReader(url.hostname, url.port, tmo, handler)
return TCPReader(url.hostname, url.port, tmo)
if url.scheme == 'udp':
if handler is not None:
return UDPReader(url.hostname, url.port, tmo, handler)
return UDPReader(url.hostname, url.port, tmo)
return None

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#