Merge pull request #48 from 916BGAI/maix_key

add long press and powerkey support, initialize all keys by default.
This commit is contained in:
Neucrack
2024-11-08 14:20:08 +08:00
committed by GitHub
3 changed files with 411 additions and 73 deletions

View File

@@ -22,10 +22,11 @@ namespace maix::peripheral::key
enum Keys{
KEY_NONE = 0x000,
KEY_ESC = 0x001,
KEY_POWER = 0x074,
KEY_OK = 0x160,
KEY_OPTION = 0x165,
KEY_NEXT = 0x197,
KEY_PREV = 0x19c
KEY_PREV = 0x19c,
};
/**
@@ -33,8 +34,9 @@ namespace maix::peripheral::key
* @maixpy maix.peripheral.key.State
*/
enum State{
KEY_RELEASED = 0,
KEY_PRESSED = 1,
KEY_RELEASED = 0,
KEY_PRESSED = 1,
KEY_LONG_PRESSED = 2,
};
/**
@@ -50,11 +52,14 @@ namespace maix::peripheral::key
* callback will be called with args key(key.Keys) and value(key.State).
* If set to null, you can get key value by read() function.
* This callback called in a standalone thread, so you can block a while in callback, and you should be carefully when operate shared data.
* @param open auto open device in constructor, if false, you need call open() to open device
* @param open auto open device in constructor, if false, you need call open() to open device.
* @param device Specifies the input device to use. The default initializes all keys,
* for a specific device, provide the path (e.g., "/dev/input/device").
* @param long_press_time The duration (in milliseconds) from pressing the key to triggering the long press event. Default is 2000ms.
* @maixpy maix.peripheral.key.Key.__init__
* @maixcdk maix.peripheral.key.Key.Key
*/
Key(std::function<void(int, int)> callback = nullptr, bool open = true);
Key(std::function<void(int, int)> callback = nullptr, bool open = true, const string &device = "", int long_press_time = 2000);
~Key();
@@ -98,8 +103,17 @@ namespace maix::peripheral::key
*/
std::pair<int, int> read();
/**
* @brief Sets and retrieves the key's long press time.
* @param press_time The long press time to set for the key.
* Setting it to 0 will disable the long press event.
* @return int type, the current long press time for the key (in milliseconds).
* @maixpy maix.peripheral.key.Key.long_press_time
*/
int long_press_time(int press_time = -1);
private:
int _fd;
std::vector<int> _fds;
std::string _device;
std::function<void(int, int)> _callback;
void *_data;

View File

@@ -57,8 +57,10 @@ namespace maix::peripheral::key
public:
thread::Thread *thread;
int fd;
int long_press_time;
bool read_thread_exit;
bool read_thread_need_exit;
std::vector<int> fds;
Key *key;
std::function<void(int, int)> callback;
};
@@ -69,6 +71,7 @@ namespace maix::peripheral::key
Port_Data *data = (Port_Data *)args;
int key = 0;
int value = 0;
uint64_t tick = 0;
// epoll wait key event
struct epoll_event ev;
int epoll_fd = epoll_create(1);
@@ -79,12 +82,16 @@ namespace maix::peripheral::key
return;
}
ev.events = EPOLLIN;
ev.data.fd = data->fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->fd, &ev) < 0)
{
data->read_thread_exit = true;
log::error("epoll_ctl add failed: %s", strerror(errno));
return;
for (int fd : data->fds) {
if (fd > 0) {
ev.data.fd = fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
log::error("epoll_ctl add failed for fd %d: %s", fd, strerror(errno));
data->read_thread_exit = true;
close(epoll_fd);
return;
}
}
}
int fail_count = 0;
while (!data->read_thread_need_exit && !app::need_exit())
@@ -92,10 +99,17 @@ namespace maix::peripheral::key
int nfds = epoll_wait(epoll_fd, &ev, 1, 200);
if (nfds > 0)
{
data->fd = ev.data.fd;
err::Err e = data->key->read(key, value);
if (e == err::Err::ERR_NONE)
{
data->callback(key, value);
// get ticks_ms when the key is pressed.
if (key != 0 && value == 1) {
tick = time::ticks_ms();
} else if (key != 0 && value == 0) {
tick = 0;
}
}
else if (e != err::Err::ERR_NOT_READY)
{
@@ -108,18 +122,24 @@ namespace maix::peripheral::key
}
continue;
}
// if long press event occurs, send KEY_LONG_PRESSED.
if (tick != 0 && data->long_press_time !=0) {
if ((int)(time::ticks_ms()- tick) >= data->long_press_time) {
data->callback(key, State::KEY_LONG_PRESSED);
tick = 0;
}
}
time::sleep_ms(1);
}
log::info("read key thread exit");
data->read_thread_exit = true;
}
Key::Key(std::function<void(int, int)> callback, bool open)
Key::Key(std::function<void(int, int)> callback, bool open, const string &device, int long_press_time)
{
if(_key_defult_listener)
rm_default_listener();
this->_callback = callback;
this->_fd = -1;
this->_data = nullptr;
this->_device = "";
Port_Data *data = new Port_Data();
@@ -130,6 +150,7 @@ namespace maix::peripheral::key
}
data->thread = nullptr;
data->fd = -1;
data->long_press_time = long_press_time;
data->read_thread_exit = false;
data->read_thread_need_exit = false;
data->key = this;
@@ -169,37 +190,49 @@ namespace maix::peripheral::key
err::Err Key::open()
{
if (this->_fd > 0)
if (this->_fds.size())
{
this->close();
}
bool is_open = false;
if(!this->_device.empty())
{
this->_fd = ::open(_device.c_str(), O_RDONLY);
if (this->_fd > 0)
int fd = ::open(_device.c_str(), O_RDONLY);
if (fd > 0) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
this->_fds.push_back(fd);
is_open = true;
}
}
if(!is_open)
{
this->_fd = ::open(KEY_DEVICE, O_RDONLY);
if (this->_fd < 0)
int fd = ::open(KEY_DEVICE, O_RDONLY);
if (fd < 0)
{
this->_fd = ::open(KEY_DEVICE2, O_RDONLY);
if (this->_fd < 0)
{
return err::Err::ERR_NOT_FOUND;
std::vector<std::string> key_devices = {{KEY_DEVICE2}};
for (const auto& device : key_devices) {
int fd = ::open(device.c_str(), O_RDONLY);
if (fd == -1) {
log::error(("Failed to open device: " + device).c_str());
this->_device = device;
return err::Err::ERR_NOT_FOUND;
}
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
this->_fds.push_back(fd);
}
} else {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
this->_fds.push_back(fd);
}
}
// set non-blocking
int flags = fcntl(this->_fd, F_GETFL, 0);
fcntl(this->_fd, F_SETFL, flags | O_NONBLOCK);
if (this->_callback)
{
// new thread to read key
Port_Data *data = (Port_Data *)this->_data;
data->fd = this->_fd;
data->fds = this->_fds;
data->read_thread_exit = false;
data->read_thread_need_exit = false;
data->thread = new thread::Thread(_read_process, this->_data);
@@ -210,14 +243,25 @@ namespace maix::peripheral::key
err::Err Key::read(int &key, int &value)
{
if (this->_fd < 0)
Port_Data *data = (Port_Data *)this->_data;
if (this->_fds.size() == 0)
{
return err::Err::ERR_NOT_OPEN;
}
struct input_event ev;
int ret;
while (1)
{
int ret = ::read(this->_fd, &ev, sizeof(struct input_event));
if (this->_callback == nullptr) {
for (int fd : this->_fds)
{
ret = ::read(fd, &ev, sizeof(struct input_event));
if (ret > 0 && ev.code != 0)
break;
}
} else {
ret = ::read(data->fd, &ev, sizeof(struct input_event));
}
if (ret < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK)
@@ -261,22 +305,39 @@ namespace maix::peripheral::key
{
Port_Data *data = (Port_Data *)this->_data;
data->read_thread_need_exit = true;
if (this->_fd > 0)
bool err = false;
for (int &fd : this->_fds)
{
int ret = ::close(this->_fd);
this->_fd = -1;
if (ret < 0)
if (fd > 0)
{
return err::Err::ERR_IO;
int ret = ::close(fd);
if (ret < 0)
{
log::error("Failed to close fd %d: %s", fd, strerror(errno));
err = true;
}
fd = -1;
}
}
this->_fd = -1;
return err::Err::ERR_NONE;
this->_fds.clear();
return err ? err::Err::ERR_IO : err::Err::ERR_NONE;
}
bool Key::is_opened()
{
return this->_fd > 0;
return this->_fds.size() > 0;
}
int Key::long_press_time(int press_time)
{
Port_Data *data = (Port_Data *)this->_data;
if (press_time < 0) {
return data->long_press_time;
} else {
return data->long_press_time = press_time;
}
}
} // namespace maix::peripheral::key

View File

@@ -11,16 +11,21 @@
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <dirent.h>
#include "maix_app.hpp"
#include "maix_log.hpp"
#include "maix_i2c.hpp"
#define KEY_DEVICE "/dev/input/event_keys"
#define KEY_DEVICE2 "/dev/input/event0"
#define KEY_DEVICE0 "/dev/input/event0"
#define KEY_DEVICE1 "/dev/input/powerkey"
static bool _key_defult_listener = false;
static maix::peripheral::key::Key *_default_key = nullptr;
static maix::peripheral::i2c::I2C *i2c_dev = nullptr;
static void on_key(int key, int state)
{
@@ -40,7 +45,7 @@ namespace maix::peripheral::key
{
if(_default_key)
return;
_default_key = new Key(on_key);
_default_key = new Key(on_key, true, KEY_DEVICE0);
_key_defult_listener = true;
}
@@ -58,9 +63,14 @@ namespace maix::peripheral::key
{
public:
thread::Thread *thread;
int fd;
thread::Thread *powerkey_thread;
int fd, io_fd, uinput_fd;
int long_press_time;
bool read_thread_exit;
bool read_thread_need_exit;
bool powerkey_thread_exit;
bool powerkey_thread_need_exit;
std::vector<int> fds;
Key *key;
std::function<void(int, int)> callback;
};
@@ -71,6 +81,7 @@ namespace maix::peripheral::key
Port_Data *data = (Port_Data *)args;
int key = 0;
int value = 0;
uint64_t tick = 0;
// epoll wait key event
struct epoll_event ev;
int epoll_fd = epoll_create(1);
@@ -81,12 +92,16 @@ namespace maix::peripheral::key
return;
}
ev.events = EPOLLIN;
ev.data.fd = data->fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->fd, &ev) < 0)
{
data->read_thread_exit = true;
log::error("epoll_ctl add failed: %s", strerror(errno));
return;
for (int fd : data->fds) {
if (fd > 0) {
ev.data.fd = fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
log::error("epoll_ctl add failed for fd %d: %s", fd, strerror(errno));
data->read_thread_exit = true;
close(epoll_fd);
return;
}
}
}
int fail_count = 0;
while (!data->read_thread_need_exit && !app::need_exit())
@@ -94,10 +109,17 @@ namespace maix::peripheral::key
int nfds = epoll_wait(epoll_fd, &ev, 1, 200);
if (nfds > 0)
{
data->fd = ev.data.fd;
err::Err e = data->key->read(key, value);
if (e == err::Err::ERR_NONE)
{
data->callback(key, value);
// get ticks_ms when the key is pressed.
if (key != 0 && value == 1) {
tick = time::ticks_ms();
} else if (key != 0 && value == 0) {
tick = 0;
}
}
else if (e != err::Err::ERR_NOT_READY)
{
@@ -110,20 +132,185 @@ namespace maix::peripheral::key
}
continue;
}
// if long press event occurs, send KEY_LONG_PRESSED.
if (tick != 0 && data->long_press_time !=0) {
if ((int)(time::ticks_ms()- tick) >= data->long_press_time) {
data->callback(key, State::KEY_LONG_PRESSED);
tick = 0;
}
}
time::sleep_ms(1);
}
log::info("read key thread exit");
data->read_thread_exit = true;
}
Key::Key(std::function<void(int, int)> callback, bool open)
static void _powerkey_process(void *args)
{
Port_Data *data = (Port_Data *)args;
data->io_fd = open("/sys/class/gpio/gpio448/value", O_RDONLY);
if (data->io_fd < 0) {
data->powerkey_thread_exit = true;
log::error("open gpio failed: %s", strerror(errno));
return;
}
struct epoll_event ev;
int epoll_fd = epoll_create1(0);
if (epoll_fd < 0)
{
data->powerkey_thread_exit = true;
log::error("create epoll failed: %s", strerror(errno));
return;
}
ev.events = EPOLLPRI;
ev.data.fd = data->io_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->io_fd, &ev) < 0) {
log::error("epoll_ctl add failed: %s", strerror(errno));
data->powerkey_thread_exit = true;
close(epoll_fd);
return;
}
char buf[32];
uint8_t i2c_data = 0xFF;
struct input_event uinput_ev;
static bool is_pressed = false;
read(data->io_fd, buf, sizeof(buf));
while (!data->powerkey_thread_need_exit && !app::need_exit())
{
int nfds = epoll_wait(epoll_fd, &ev, 1, 200);
if (nfds > 0) {
if (ev.events & EPOLLPRI) {
lseek(data->io_fd, 0, SEEK_SET);
read(data->io_fd, buf, sizeof(buf));
if (buf[0] == '0' && !is_pressed) {
is_pressed = true;
uinput_ev.type = EV_KEY;
uinput_ev.code = KEY_POWER;
uinput_ev.value = 1;
gettimeofday(&uinput_ev.time, NULL);
write(data->uinput_fd, &uinput_ev, sizeof(uinput_ev));
log::debug("Key pressed.\n");
} else if (buf[0] == '0' && is_pressed) {
is_pressed = false;
uinput_ev.type = EV_KEY;
uinput_ev.code = KEY_POWER;
uinput_ev.value = 0;
gettimeofday(&uinput_ev.time, NULL);
write(data->uinput_fd, &uinput_ev, sizeof(uinput_ev));
log::debug("Key press detected.\n");
}
uinput_ev.type = EV_SYN;
uinput_ev.code = SYN_REPORT;
uinput_ev.value = 0;
gettimeofday(&uinput_ev.time, NULL);
write(data->uinput_fd, &uinput_ev, sizeof(uinput_ev));
if (i2c_dev->writeto_mem(0x34, 0x49, &i2c_data, 1) != 1) {
log::error("clean pmu irq failed");
return;
}
}
}
time::sleep_ms(20);
}
log::info("powerkey thread exit");
data->powerkey_thread_exit = true;
}
static void _init_power_key(void *args)
{
uint8_t i2c_data = 0xFF;
i2c_dev = new peripheral::i2c::I2C(4, i2c::Mode::MASTER);
if (i2c_dev->writeto_mem(0x34, 0x49, &i2c_data, 1) != 1) {
log::error("clean pmu irq failed");
return;
}
if (!fs::exists("/sys/class/gpio/gpio448")) {
system("echo 448 > /sys/class/gpio/export");
usleep(100000);
system("echo \"in\" > /sys/class/gpio/gpio448/direction");
system("echo \"falling\" > /sys/class/gpio/gpio448/edge");
log::info("export axp2101 irq gpio success");
}
Port_Data *data = (Port_Data *)args;
data->uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (data->uinput_fd < 0) {
log::error("open /dev/uinput error");
return;
}
ioctl(data->uinput_fd, UI_SET_EVBIT, EV_KEY);
ioctl(data->uinput_fd, UI_SET_KEYBIT, KEY_POWER);
struct uinput_user_dev uidev;
memset(&uidev, 0, sizeof(uidev));
strncpy(uidev.name, "powerkey", UINPUT_MAX_NAME_SIZE);
uidev.id.bustype = BUS_USB;
uidev.id.vendor = 0x3346;
uidev.id.product = 0x2333;
uidev.id.version = 1;
write(data->uinput_fd, &uidev, sizeof(uidev));
ioctl(data->uinput_fd, UI_DEV_CREATE);
int max_event = -1;
if (fs::exists(KEY_DEVICE1)) {
fs::remove(KEY_DEVICE1);
}
DIR* dir = opendir("/dev/input/");
if (!dir) {
log::error("failed to open /dev/input");
return;
}
try {
struct dirent* ent;
while ((ent = readdir(dir)) != nullptr) {
std::string name(ent->d_name);
if (name.find("event") == 0) {
int eventNumber = std::stoi(name.substr(5));
max_event = std::max(max_event, eventNumber);
}
}
closedir(dir);
std::string targetPath = "/dev/input/event" + std::to_string(max_event);
if (fs::symlink(targetPath.c_str(), KEY_DEVICE1) == -1) {
log::error("%s symlink error.", KEY_DEVICE1);
} else {
log::info("%s symlink to %s.", KEY_DEVICE1, targetPath.c_str());
}
} catch (const std::exception& e) {
closedir(dir);
log::error("Exception occurred: %s", e.what());
}
data->powerkey_thread = new thread::Thread(_powerkey_process, data);
data->powerkey_thread->detach();
}
static void _deinit_power_key(void *args)
{
Port_Data *data = (Port_Data *)args;
ioctl(data->uinput_fd, UI_DEV_DESTROY);
close(data->uinput_fd);
if (data->io_fd > 0) {
close(data->io_fd);
}
fs::remove(KEY_DEVICE1);
}
Key::Key(std::function<void(int, int)> callback, bool open, const string &device, int long_press_time)
{
if(_key_defult_listener)
rm_default_listener();
this->_callback = callback;
this->_fd = -1;
this->_data = nullptr;
this->_device = "";
this->_device = device;
Port_Data *data = new Port_Data();
this->_data = data;
if (!this->_data)
@@ -131,18 +318,31 @@ namespace maix::peripheral::key
throw err::Exception(err::ERR_NO_MEM, "create key data failed");
}
data->thread = nullptr;
data->powerkey_thread = nullptr;
data->fd = -1;
data->io_fd = -1;
data->uinput_fd = -1;
data->long_press_time = long_press_time;
data->read_thread_exit = false;
data->read_thread_need_exit = false;
data->powerkey_thread_exit = false;
data->powerkey_thread_need_exit = false;
data->key = this;
data->callback = callback;
if (sys::device_id() == "maixcam_pro" &&
!fs::exists(KEY_DEVICE1) &&
(device == "" || device == KEY_DEVICE1)) {
log::info("%s: Init pmu power key.", sys::device_name().c_str());
_init_power_key((void*)data);
}
if (open)
{
err::Err e = this->open();
if (e != err::ERR_NONE)
{
throw err::Exception(err::ERR_NOT_FOUND, std::string("Key device") + KEY_DEVICE + " not found");
throw err::Exception(err::ERR_NOT_FOUND, std::string("Key device") + this->_device + " not found");
}
}
}
@@ -164,44 +364,79 @@ namespace maix::peripheral::key
delete data->thread;
data->thread = nullptr;
}
if(data->powerkey_thread)
{
data->powerkey_thread_need_exit = true;
log::info("wait powerkey thread exit");
while (!data->powerkey_thread_exit)
{
time::sleep_ms(1);
}
delete data->powerkey_thread;
data->powerkey_thread = nullptr;
}
if (fs::exists(KEY_DEVICE1)) {
_deinit_power_key((void*)data);
}
delete data;
this->_data = nullptr;
}
if (i2c_dev != nullptr) {
delete i2c_dev;
i2c_dev = nullptr;
}
}
err::Err Key::open()
{
if (this->_fd > 0)
if (this->_fds.size())
{
this->close();
}
bool is_open = false;
if(!this->_device.empty())
{
this->_fd = ::open(_device.c_str(), O_RDONLY);
if (this->_fd > 0)
int fd = ::open(_device.c_str(), O_RDONLY);
if (fd > 0) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
this->_fds.push_back(fd);
is_open = true;
}
}
if(!is_open)
{
this->_fd = ::open(KEY_DEVICE, O_RDONLY);
if (this->_fd < 0)
int fd = ::open(KEY_DEVICE, O_RDONLY);
if (fd < 0)
{
this->_fd = ::open(KEY_DEVICE2, O_RDONLY);
if (this->_fd < 0)
{
return err::Err::ERR_NOT_FOUND;
std::vector<std::string> key_devices;
if (sys::device_id() == "maixcam_pro") {
key_devices = {KEY_DEVICE0, KEY_DEVICE1};
} else {
key_devices = {KEY_DEVICE0};
}
for (const auto& device : key_devices) {
int fd = ::open(device.c_str(), O_RDONLY);
if (fd == -1) {
log::error(("Failed to open device: " + device).c_str());
this->_device = device;
return err::Err::ERR_NOT_FOUND;
}
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
this->_fds.push_back(fd);
}
} else {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
this->_fds.push_back(fd);
}
}
// set non-blocking
int flags = fcntl(this->_fd, F_GETFL, 0);
fcntl(this->_fd, F_SETFL, flags | O_NONBLOCK);
if (this->_callback)
{
// new thread to read key
Port_Data *data = (Port_Data *)this->_data;
data->fd = this->_fd;
data->fds = this->_fds;
data->read_thread_exit = false;
data->read_thread_need_exit = false;
data->thread = new thread::Thread(_read_process, this->_data);
@@ -212,14 +447,25 @@ namespace maix::peripheral::key
err::Err Key::read(int &key, int &value)
{
if (this->_fd < 0)
Port_Data *data = (Port_Data *)this->_data;
if (this->_fds.size() == 0)
{
return err::Err::ERR_NOT_OPEN;
}
struct input_event ev;
int ret;
while (1)
{
int ret = ::read(this->_fd, &ev, sizeof(struct input_event));
if (this->_callback == nullptr) {
for (int fd : this->_fds)
{
ret = ::read(fd, &ev, sizeof(struct input_event));
if (ret > 0 && ev.code != 0)
break;
}
} else {
ret = ::read(data->fd, &ev, sizeof(struct input_event));
}
if (ret < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK)
@@ -266,22 +512,39 @@ namespace maix::peripheral::key
{
Port_Data *data = (Port_Data *)this->_data;
data->read_thread_need_exit = true;
if (this->_fd > 0)
bool err = false;
for (int &fd : this->_fds)
{
int ret = ::close(this->_fd);
this->_fd = -1;
if (ret < 0)
if (fd > 0)
{
return err::Err::ERR_IO;
int ret = ::close(fd);
if (ret < 0)
{
log::error("Failed to close fd %d: %s", fd, strerror(errno));
err = true;
}
fd = -1;
}
}
this->_fd = -1;
return err::Err::ERR_NONE;
this->_fds.clear();
return err ? err::Err::ERR_IO : err::Err::ERR_NONE;
}
bool Key::is_opened()
{
return this->_fd > 0;
return this->_fds.size() > 0;
}
int Key::long_press_time(int press_time)
{
Port_Data *data = (Port_Data *)this->_data;
if (press_time < 0) {
return data->long_press_time;
} else {
return data->long_press_time = press_time;
}
}
} // namespace maix::peripheral::key