Skip to content
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

feat(t_keyboard): added t_keyboard component #124

Merged
merged 4 commits into from
Nov 10, 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
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ jobs:
target: esp32s3
- path: 'components/state_machine/example'
target: esp32
- path: 'components/t_keyboard/example'
target: esp32s3
- path: 'components/tabulate/example'
target: esp32
- path: 'components/task/example'
Expand Down
4 changes: 4 additions & 0 deletions components/t_keyboard/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
idf_component_register(
INCLUDE_DIRS "include"
REQUIRES "logger"
)
21 changes: 21 additions & 0 deletions components/t_keyboard/example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# The following 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.5)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)

# add the component directories that we want to use
set(EXTRA_COMPONENT_DIRS
"../../../components/"
)

set(
COMPONENTS
"main esptool_py driver task t_keyboard i2c"
CACHE STRING
"List of components to include"
)

project(t_keyboard_example)

set(CMAKE_CXX_STANDARD 20)
28 changes: 28 additions & 0 deletions components/t_keyboard/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# LilyGo T-Keyboard Example

This example shows how to get the user input (key presses) from the LilyGo
T-Keyboard. It is designed to run on a LilyGo T-Deck.

## How to use example

### Hardware Required

LilyGo T-Deck (or any other board which has a LilyGo T-Keyboard connected)

### Build and Flash

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.)

(To exit the serial monitor, type ``Ctrl-]``.)

See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.

## Example Output

<img width="318" alt="CleanShot 2023-11-10 at 13 17 53@2x" src="https://github.com/esp-cpp/espp/assets/213467/68b89daa-4686-4d29-8a73-400b4d91617d">
2 changes: 2 additions & 0 deletions components/t_keyboard/example/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS ".")
56 changes: 56 additions & 0 deletions components/t_keyboard/example/main/t_keyboard_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include <chrono>
#include <vector>

#include <driver/gpio.h>

#include "i2c.hpp"
#include "t_keyboard.hpp"
#include "task.hpp"

using namespace std::chrono_literals;

extern "C" void app_main(void) {
{
fmt::print("Starting tkeyboard example\n");
//! [tkeyboard example]
// make the I2C that we'll use to communicate
espp::I2c i2c({
.port = I2C_NUM_0,
.sda_io_num = GPIO_NUM_18,
.scl_io_num = GPIO_NUM_8,
});
// now make the tkeyboard which decodes the data
espp::TKeyboard tkeyboard(
{.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3),
.read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3),
.key_cb = [](uint8_t key) { fmt::print("'{}' Pressed!\n", (char)key); },
.auto_start = false, // can't auto start since we need to provide power
.log_level = espp::Logger::Verbosity::WARN});

// on the LilyGo T-Deck, the peripheral power control pin must be set high
// to enable peripheral power
auto power_ctrl = GPIO_NUM_10;
gpio_set_direction(power_ctrl, GPIO_MODE_OUTPUT);
gpio_set_level(power_ctrl, 1);

do {
fmt::print("Waiting for tkeyboard to boot up...\n");
std::this_thread::sleep_for(250ms);
} while (!i2c.probe_device(espp::TKeyboard::DEFAULT_ADDRESS));

fmt::print("Tkeyboard ready!\n");
tkeyboard.start();
//! [tkeyboard example]
while (true) {
std::this_thread::sleep_for(100ms);
}
}

fmt::print("Tkeyboard example complete!\n");

while (true) {
std::this_thread::sleep_for(1s);
}
}
6 changes: 6 additions & 0 deletions components/t_keyboard/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CONFIG_IDF_TARGET="esp32s3"

# Common ESP-related
#
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
177 changes: 177 additions & 0 deletions components/t_keyboard/include/t_keyboard.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#pragma once

#include <atomic>
#include <chrono>
#include <functional>

#include "logger.hpp"
#include "task.hpp"

namespace espp {
/// \brief Class for interacting with the LilyGo T-Keyboard.
/// \details This class is used to interact with the LilyGo T-Keyboard. It is
/// designed as a peripheral component for use with a serial
/// interface such as I2C. On The T-Keyboard, you can press Alt+B to
/// toggle the keyboard backlight.
///
/// \section Example
/// \snippet t_keyboard_example.cpp tkeyboard example
class TKeyboard {
public:
/// The default address of the keyboard.
static constexpr uint8_t DEFAULT_ADDRESS = 0x55;

/// \brief The function signature for the write function.
/// \details This function is used to write data to the keyboard.
/// \param address The address to write to.
/// \param data The data to write.
/// \param size The size of the data to write.
/// \return True if the write was successful, false otherwise.
typedef std::function<bool(uint8_t, uint8_t *, size_t)> write_fn;

/// \brief The function signature for the read function.
/// \details This function is used to read data from the keyboard.
/// \param address The address to read from.
/// \param data The data to read into.
/// \param size The size of the data to read.
/// \return True if the read was successful, false otherwise.
typedef std::function<bool(uint8_t, uint8_t *, size_t)> read_fn;

/// \brief The function signature for the key callback function.
/// \details This function is called when a key is pressed.
/// \param key The key that was pressed.
typedef std::function<void(uint8_t)> key_cb_fn;

/// The configuration structure for the keyboard.
struct Config {
/// The write function to use.
write_fn write;

/// The read function to use.
read_fn read;

/// The key callback function to use. This function will be called when a
/// key is pressed if it is not null and the keyboard task is running.
key_cb_fn key_cb;

/// The address of the keyboard.
uint8_t address = DEFAULT_ADDRESS;

/// The polling interval for the keyboard
std::chrono::milliseconds polling_interval = std::chrono::milliseconds(10);

/// Whether or not to automatically start the keyboard task.
bool auto_start = true;

/// The log level to use.
Logger::Verbosity log_level = Logger::Verbosity::WARN;
};

/// \brief Constructor for the TKeyboard class.
/// \param config The configuration to use.
explicit TKeyboard(const Config &config)
: write_(config.write), read_(config.read), key_cb_(config.key_cb), address_(config.address),
polling_interval_(config.polling_interval),
logger_({.tag = "TKeyboard", .level = config.log_level}) {
logger_.info("TKeyboard created");
task_ = std::make_shared<espp::Task>(espp::Task::Config{
.name = "tkeyboard_task",
.callback =
std::bind(&TKeyboard::key_task, this, std::placeholders::_1, std::placeholders::_2),
.stack_size_bytes = 4 * 1024,
});
if (config.auto_start) {
start();
}
}

/// \brief Get the currently pressed key.
/// \details This function returns the currently pressed key.
/// \return The currently pressed key.
/// \note This function will return 0 if no key has been pressed.
uint8_t get_key() { return pressed_key_; }

/// \brief Read a key from the keyboard.
/// \details This function reads a key from the keyboard.
/// \param ec The error code to set if an error occurs.
/// \return The key that was read.
/// \note This function will return 0 if no key was read.
/// \note This function will return 0 if an error occurs.
/// \note This function will set the error code if the keyboard task is
/// running.
uint8_t read_key(std::error_code &ec) {
if (task_->is_running()) {
logger_.error("Keyboard task is running, use get_key() instead");
ec = std::make_error_code(std::errc::operation_in_progress);
return 0;
}
return read_char(ec);
}

/// \brief Start the keyboard task.
/// \details This function starts the keyboard task. It should be called
/// after the keyboard has been initialized.
/// \return True if the task was started, false otherwise.
bool start() {
logger_.info("Starting keyboard task");
return task_->start();
}

/// \brief Stop the keyboard task.
/// \details This function stops the keyboard task.
/// \return True if the task was stopped, false otherwise.
bool stop() { return task_->stop(); }

protected:
bool key_task(std::mutex &m, std::condition_variable &cv) {
std::error_code ec;
auto start = std::chrono::steady_clock::now();
auto key = read_char(ec);
if (!ec) {
pressed_key_ = key;
if (key != 0 && key_cb_) {
key_cb_(key);
}
} else {
logger_.error("Failed to get key: {}", ec.message());
pressed_key_ = 0;
}
{
std::unique_lock<std::mutex> lock(m);
cv.wait_until(lock, start + polling_interval_);
}
return false;
}

uint8_t read_char(std::error_code &ec) {
uint8_t data = 0;
read(&data, 1, ec);
if (ec) {
logger_.error("Failed to read char: {}", ec.message());
return 0;
}
return data;
}

void write(uint8_t *data, size_t size, std::error_code &ec) {
if (!write_(address_, data, size)) {
ec = std::make_error_code(std::errc::io_error);
}
}

void read(uint8_t *data, size_t size, std::error_code &ec) {
if (!read_(address_, data, size)) {
ec = std::make_error_code(std::errc::io_error);
}
}

write_fn write_;
read_fn read_;
key_cb_fn key_cb_;
uint8_t address_;
std::atomic<uint8_t> pressed_key_{0};
std::chrono::milliseconds polling_interval_;
std::shared_ptr<espp::Task> task_;
espp::Logger logger_;
};
} // namespace espp
2 changes: 2 additions & 0 deletions doc/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ EXAMPLE_PATH += $(PROJECT_PATH)/components/socket/example/main/socket_example.cp
EXAMPLE_PATH += $(PROJECT_PATH)/components/st25dv/example/main/st25dv_example.cpp
EXAMPLE_PATH += $(PROJECT_PATH)/components/state_machine/example/main/hfsm_example.cpp
EXAMPLE_PATH += $(PROJECT_PATH)/components/tabulate/example/main/tabulate_example.cpp
EXAMPLE_PATH += $(PROJECT_PATH)/components/t_keyboard/example/main/t_keyboard_example.cpp
EXAMPLE_PATH += $(PROJECT_PATH)/components/task/example/main/task_example.cpp
EXAMPLE_PATH += $(PROJECT_PATH)/components/thermistor/example/main/thermistor_example.cpp
EXAMPLE_PATH += $(PROJECT_PATH)/components/timer/example/main/timer_example.cpp
Expand Down Expand Up @@ -149,6 +150,7 @@ INPUT += $(PROJECT_PATH)/components/state_machine/include/deep_history_state.hpp
INPUT += $(PROJECT_PATH)/components/state_machine/include/shallow_history_state.hpp
INPUT += $(PROJECT_PATH)/components/state_machine/include/state_base.hpp
INPUT += $(PROJECT_PATH)/components/state_machine/include/state_machine.hpp
INPUT += $(PROJECT_PATH)/components/t_keyboard/include/t_keyboard.hpp
INPUT += $(PROJECT_PATH)/components/tabulate/include/tabulate.hpp
INPUT += $(PROJECT_PATH)/components/task/include/task.hpp
INPUT += $(PROJECT_PATH)/components/thermistor/include/thermistor.hpp
Expand Down
1 change: 1 addition & 0 deletions doc/en/input/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ Input APIs
ft5x06
gt911
tt21100
t_keyboard
touchpad_input
12 changes: 12 additions & 0 deletions doc/en/input/t_keyboard.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
LilyGo T-Keyboard
*****************

The `TKeyboard` component provides a simple interface to the T-Keyboard
keypad. It allows you to read which key is currently pressed.

.. ---------------------------- API Reference ----------------------------------

API Reference
-------------

.. include-build-file:: inc/t_keyboard.inc
1 change: 1 addition & 0 deletions docs/_sources/input/index.rst.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ Input APIs
ft5x06
gt911
tt21100
t_keyboard
touchpad_input
12 changes: 12 additions & 0 deletions docs/_sources/input/t_keyboard.rst.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
LilyGo T-Keyboard
*****************

The `TKeyboard` component provides a simple interface to the T-Keyboard
keypad. It allows you to read which key is currently pressed.

.. ---------------------------- API Reference ----------------------------------

API Reference
-------------

.. include-build-file:: inc/t_keyboard.inc
4 changes: 2 additions & 2 deletions docs/adc/adc_types.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
<li><a href="index.html">ADC APIs</a> &raquo;</li>
<li>ADC Types</li>
<li class="wy-breadcrumbs-aside">
<a href="https://github.com/esp-cpp/espp/blob/cdb8bd4/docs/en/adc/adc_types.rst" class="fa fa-github"> Edit on GitHub</a>
<a href="https://github.com/esp-cpp/espp/blob/15ad5f4/docs/en/adc/adc_types.rst" class="fa fa-github"> Edit on GitHub</a>
</li>
</ul>
<hr/>
Expand All @@ -162,7 +162,7 @@ <h2>API Reference<a class="headerlink" href="#api-reference" title="Permalink to
<section id="header-file">
<h3>Header File<a class="headerlink" href="#header-file" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/0775afc/components/adc/include/adc_types.hpp">components/adc/include/adc_types.hpp</a></p></li>
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/87d6eb4/components/adc/include/adc_types.hpp">components/adc/include/adc_types.hpp</a></p></li>
</ul>
</section>
</section>
Expand Down
4 changes: 2 additions & 2 deletions docs/adc/ads1x15.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
<li><a href="index.html">ADC APIs</a> &raquo;</li>
<li>ADS1x15 I2C ADC</li>
<li class="wy-breadcrumbs-aside">
<a href="https://github.com/esp-cpp/espp/blob/cdb8bd4/docs/en/adc/ads1x15.rst" class="fa fa-github"> Edit on GitHub</a>
<a href="https://github.com/esp-cpp/espp/blob/15ad5f4/docs/en/adc/ads1x15.rst" class="fa fa-github"> Edit on GitHub</a>
</li>
</ul>
<hr/>
Expand All @@ -163,7 +163,7 @@ <h2>API Reference<a class="headerlink" href="#api-reference" title="Permalink to
<section id="header-file">
<h3>Header File<a class="headerlink" href="#header-file" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/cdb8bd4/components/ads1x15/include/ads1x15.hpp">components/ads1x15/include/ads1x15.hpp</a></p></li>
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/15ad5f4/components/ads1x15/include/ads1x15.hpp">components/ads1x15/include/ads1x15.hpp</a></p></li>
</ul>
</section>
<section id="classes">
Expand Down
Loading