diff --git a/components/peripheral/include/maix_key.hpp b/components/peripheral/include/maix_key.hpp index 81aa5cb3..b0b7fd7c 100755 --- a/components/peripheral/include/maix_key.hpp +++ b/components/peripheral/include/maix_key.hpp @@ -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 callback = nullptr, bool open = true); + Key(std::function callback = nullptr, bool open = true, const string &device = "", int long_press_time = 2000); ~Key(); @@ -98,8 +103,17 @@ namespace maix::peripheral::key */ std::pair 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 _fds; std::string _device; std::function _callback; void *_data; diff --git a/components/peripheral/port/linux/maix_key.cpp b/components/peripheral/port/linux/maix_key.cpp index cc8be885..967d268f 100755 --- a/components/peripheral/port/linux/maix_key.cpp +++ b/components/peripheral/port/linux/maix_key.cpp @@ -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 fds; Key *key; std::function 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 callback, bool open) + Key::Key(std::function 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 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 diff --git a/components/peripheral/port/maixcam/maix_key.cpp b/components/peripheral/port/maixcam/maix_key.cpp index 98021381..f997737b 100755 --- a/components/peripheral/port/maixcam/maix_key.cpp +++ b/components/peripheral/port/maixcam/maix_key.cpp @@ -11,16 +11,21 @@ #include #include #include +#include #include #include +#include #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 fds; Key *key; std::function 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 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 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 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