Skip to content

feat(main): implement main functionality #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "components/espp"]
path = components/espp
url = [email protected]:esp-cpp/espp
[submodule "components/esp-protocols"]
path = components/esp-protocols
url = [email protected]:espressif/esp-protocols
7 changes: 3 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS
"components/"
"components/espp/components/"
"components/esp-protocols/components"
)

set(
COMPONENTS
# TODO: add additional esp-idf and espp components you want to use to the line below:
"main esptool_py logger task"
"main esptool_py driver lwip button display display_drivers input_drivers logger lvgl mdns socket task tt21100 wifi gui"
CACHE STRING
"List of components to include"
)

# TODO: update this with your project's name
project(template)
project(wireless-debug-display)

set(CMAKE_CXX_STANDARD 20)
130 changes: 112 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,101 @@
# ESP++ Template
# Wireless Debug Display

This repository contains an example application designed for either
ESP32-WROVER-KIT or ESP32-S3-BOX (selectable via menuconfig) which listens on a
UDP socket for data. It then parses that data and if it matches a certain
format, it will plot the data in a graph, otherwise it will print the data to a
text log for display.

https://github.com/esp-cpp/wireless-debug-display/assets/213467/f835922f-e17f-4f76-95ee-5d6585e84656

## Configuration

You'll need to configure the build using `idf.py set-target <esp32 or esp32s3>`
and then `idf.py menuconfig` to then set the `Wireless Debug Display
Configuration` which allows you to set which hardware you want to run it on, as
well as the WiFi Access Point connection information (ssid/password). It also
allows customization of the port of the UDP server that the debug display is
running.

## Use

This code receives string data from a UDP server. It will parse that string data
and determine which of the following three types of data it is:

* *Commands*: contain the prefix (`+++`) in the string.
* *Plot data*: contain the delimiter (`::`) in the string followed by a
single value which can be converted successfully to a number. If the
conversion fails, the message will be printed as a log.
* *Log / text data*: all data that is not a command and cannot be
plotted.

They are parsed in that priority order.

Some example data (no commands) can be found in [test_data.txt](./test_data.txt).

A couple python scripts are provided for sending data from a computer to your
logger to showcase simple UDP socket sending, as well as automatic service
discovery using mDNS.

- [./send_to_display.py](./send_to_display.py): Uses simple UDP sockets to send
messages or a file to the debug display.
- [./send_to_display_mdns.py](./send_to_display_mdns.py): Uses python's
`zeroconf` package to discover the wireless display on the network and then
send messages or a file to the debug display. NOTE: zeroconf may not be
installed / accessible within the python environment used by ESP-IDF.

## Sending Data to the Display

This display is designed to receive data from any other device on the network,
though it is primarily intended for other embedded wireless devices such as
other ESP-based systems. However, I have provided some scripts to help show how
data can be sent from computers or other systems if you choose.

Assuming that your computer is also on the network (you'll need to replace the
IP address below with the ip address displayed in the `info` page of the
display if you don't use the mDNS version):

```console
# this python script uses mDNS to automatically find the display on the network
python ./send_to_display_mdns.py --file <file>
python ./send_to_display_mdns.py --message "<message 1>" --message "<message 2>" ...
# e.g.
python ./send_to_display_mdns.py --file test_data.txt
python ./send_to_display_mdns.py --message "Hello world" --message "trace1::0" --message "trace1::1" --message "Goodbye World"

# this python script uses raw UDP sockets to send data to the display on the network
python ./send_to_display.py --ip <IP Address> --port <port, default 5555> --file <file>
python ./send_to_display.py --ip <IP Address> --port <port, default 5555> --message "<message 1>" --message "<message 2>" ...
# e.g.
python ./send_to_display.py --ip 192.168.1.23 --file additional_data.txt
python ./send_to_display.py --ip 192.168.1.23 --message "Hello world" --message "trace1::0" --message "trace1::1" --message "Goodbye World"
```

### Commands

There are a limited set of commands in the system, which are
determined by a prefix and the command itself. If the prefix is found
_ANYWHERE_ in the string message (where messages are separated by
newlines), then the message is determined to be a command.

Template repository for building an ESP app with ESP++ (espp) components and
ESP-IDF components.
**PREFIX:** `+++` - three plus characters in a row

## Development
* **Remove Plot:** this command (`RP:` followed by the string plot name) will remove the named plot from the graph.
* **Clear Plots:** this command (`CP`) will remove _all_ plots from the graph.
* **Clear Logs:** this command (`CL`) will remove _all_ logs / text.

This repository is designed to be used as a template repository - so you can
sepcify this as the template repository type when creating a new repository on
GitHub.
### Plotting

After setting this as the template, make sure to update the following:
- [This README](./README.md) to contain the relevant description and images of your project
- The [./CMakeLists.txt](./CMakeLists.txt) file to have the components that you
want to use (and any you may have added to the [components
folder](./components)) as well as to update the project name
- The [./main/main.cpp](./main/main.cpp) To run the main code for your app. The
[main folder](./main) is also where you can put additional header and source
files that you don't think belong in their own components but help keep the
main code clean.
Messages which contain the string `::` and which have a value that
successfully and completely converts into a number are determined to
be a plot. Plots are grouped by their name, which is any string
preceding the `::`.

### Logging

All other text is treated as a log and written out to the log
window. Note, we do not wrap lines, so any text that would go off the
edge of the screen is simply not rendered.

## Cloning

Expand Down Expand Up @@ -51,6 +129,22 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui

## Output

Example screenshot of the console output from this app:
### Console Logs:
![initial output](https://github.com/esp-cpp/wireless-debug-display/assets/213467/c20993a7-9873-4c76-bc8e-1b115f63a5e0)
![receiving more info](https://github.com/esp-cpp/wireless-debug-display/assets/213467/0413e79e-018c-497e-b9d7-511481d17385)

### Python script:
![python script](https://github.com/esp-cpp/wireless-debug-display/assets/213467/9d5d4899-3074-47b1-8d57-1ef22aa4bfba)

### ESP32-WROVER-KIT

https://github.com/esp-cpp/wireless-debug-display/assets/213467/395400f6-e677-464c-a258-df06049cc562

### ESP32-S3-BOX

![image](https://github.com/esp-cpp/wireless-debug-display/assets/213467/5aa28996-4ad7-4dbc-bc00-756ecd7ec736)
![image](https://github.com/esp-cpp/wireless-debug-display/assets/213467/2c75f6dc-4528-4663-ae12-f894ec2bcdc9)
![image](https://github.com/esp-cpp/wireless-debug-display/assets/213467/e59536a1-da8c-40fb-9f37-fdfdfb2d5b52)

https://github.com/esp-cpp/wireless-debug-display/assets/213467/f835922f-e17f-4f76-95ee-5d6585e84656

![CleanShot 2023-07-12 at 14 01 21](https://github.com/esp-cpp/template/assets/213467/7f8abeae-121b-4679-86d8-7214a76f1b75)
44 changes: 44 additions & 0 deletions additional_data.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Some additional logs!
This is some more logging
#FF00FF wow# there really are a lot of logs here
you really can't believe it can you?
#FF0000 Hopefully# at some point
I'll be able to stop typing
and I'll have gotten to
the bottom of the logs...
t0::5
t0::7
t0::35
t0::76
t0::32
t0::11
t0::15
t0::13
t0::5
t0::0
t0::10
t0::-31
t0::-75
t0::3
t0::1
t0::1
t0::3
t0::5
r0::0
r0::10
r0::3
r0::5
r0::3
r0::1
r0::1
r0::3
r0::5
r0::0
r0::10
r0::30
r0::5
r0::3
r0::1
r0::-10
r0::-30
r0::5
1 change: 1 addition & 0 deletions components/esp-protocols
Submodule esp-protocols added at 12bacd
4 changes: 4 additions & 0 deletions components/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
idf_component_register(
SRC_DIRS "src"
INCLUDE_DIRS "include"
REQUIRES task display logger)
11 changes: 11 additions & 0 deletions components/gui/include/converter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <cerrno>
#include <climits>
#include <stdlib.h>

class Converter {
public:
enum class Status { Success, Overflow, Underflow, Inconvertible };
static Status str2int(int &i, char const *s, int base = 0);
};
29 changes: 29 additions & 0 deletions components/gui/include/graph_window.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include <string>
#include <cstring>
#include <unordered_map>

#include "window.hpp"

class GraphWindow : public Window {
public:
void init(lv_obj_t *parent, size_t width, size_t height) override;
void update() override;

void clear_plots ( void );
void add_data ( const std::string& plot_name, int new_data );
void remove_plot ( const std::string& plot_name );

protected:
lv_chart_series_t* create_plot ( const std::string& plotName );
lv_chart_series_t* get_plot ( const std::string& plotName );

void update_ticks ( void );

private:
lv_obj_t *chart_;
lv_obj_t *legend_;
std::string y_ticks_;
std::unordered_map<std::string, lv_chart_series_t*> plot_map_;
};
117 changes: 117 additions & 0 deletions components/gui/include/gui.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#pragma once

#include <memory>
#include <mutex>
#include <queue>
#include <sstream>

#include <sdkconfig.h>

#include "converter.hpp"
#include "display.hpp"
#include "task.hpp"
#include "logger.hpp"
#include "text_window.hpp"
#include "graph_window.hpp"

class Gui {
public:
const std::string delimeter_data = "::"; ///< Delimeter indicating this contains plottable data
const std::string delimeter_command = "+++"; ///< Delimeter indicating this contains a command
const std::string command_remove_plot = "RP:"; ///< Command: remove plot
const std::string command_clear_plots = "CP"; ///< Command: clear plots
const std::string command_clear_logs = "CL"; ///< Command: clear logs

struct Config {
std::shared_ptr<espp::Display> display;
espp::Logger::Verbosity log_level{espp::Logger::Verbosity::WARN};
};

explicit Gui(const Config& config)
: display_(config.display)
, logger_({.tag = "Gui", .level = config.log_level}) {
init_ui();
// now start the gui updater task
using namespace std::placeholders;
task_ = espp::Task::make_unique({
.name = "Gui Task",
.callback = std::bind(&Gui::update, this, _1, _2),
.stack_size_bytes = 6 * 1024
});
task_->start();
}

~Gui() {
task_->stop();
deinit_ui();
}

void switch_tab();

void push_data(const std::string& data);
std::string pop_data();

void clear_info();
void add_info(const std::string& info);

bool handle_data();

protected:
void init_ui();
void deinit_ui();

bool update(std::mutex& m, std::condition_variable& cv) {
{
std::lock_guard<std::recursive_mutex> lk(mutex_);
lv_task_handler();
}
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 16ms);
}
// don't want to stop the task
return false;
}

static void event_callback(lv_event_t *e) {
lv_event_code_t event_code = lv_event_get_code(e);
auto user_data = lv_event_get_user_data(e);
auto gui = static_cast<Gui*>(user_data);
if (!gui) {
return;
}
switch (event_code) {
case LV_EVENT_SHORT_CLICKED:
break;
case LV_EVENT_PRESSED:
gui->on_pressed(e);
break;
case LV_EVENT_VALUE_CHANGED:
// gui->on_value_changed(e);
break;
case LV_EVENT_LONG_PRESSED:
break;
case LV_EVENT_KEY:
break;
default:
break;
}
}

void on_pressed(lv_event_t *e);

GraphWindow plot_window_;
TextWindow log_window_;
TextWindow info_window_;
lv_obj_t *tabview_;

std::mutex data_queue_mutex_;
std::queue<std::string> data_queue_;

std::shared_ptr<espp::Display> display_;
std::unique_ptr<espp::Task> task_;

espp::Logger logger_;
std::recursive_mutex mutex_;
};
Loading