diff --git a/.clang-tidy b/.clang-tidy index e0afd471..5cc8a43c 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,17 +1,24 @@ --- Checks: ' - ,readability-avoid-const-params-in-decls, - ,readability-inconsistent-declaration-parameter-name, - ,readability-non-const-parameter, - ,readability-redundant-string-cstr, - ,readability-redundant-string-init, - ,readability-simplify-boolean-expr, -' -WarningsAsErrors: ' - ,readability-avoid-const-params-in-decls, - ,readability-inconsistent-declaration-parameter-name, - ,readability-non-const-parameter, - ,readability-redundant-string-cstr, - ,readability-redundant-string-init, - ,readability-simplify-boolean-expr, +readability*, +-readability-identifier-length, +-readability-braces-around-statements, +-readability-magic-numbers, +-readability-named-parameter, +-readability-function-cognitive-complexity, +-readability-use-anyofallof, +cppcoreguidelines*, +-cppcoreguidelines-init-variables, +-cppcoreguidelines-avoid-magic-numbers, +-cppcoreguidelines-pro-type-vararg, +-cppcoreguidelines-pro-type-const-cast, +-cppcoreguidelines-pro-type-member-init, +-cppcoreguidelines-avoid-const-or-ref-data-members, +-cppcoreguidelines-pro-bounds-array-to-pointer-decay, +-cppcoreguidelines-avoid-c-arrays, +-cppcoreguidelines-pro-bounds-pointer-arithmetic, +-cppcoreguidelines-non-private-member-variables-in-classes, +-cppcoreguidelines-avoid-non-const-global-variables, +-cppcoreguidelines-avoid-do-while ' +WarningsAsErrors: '*' diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 01e9d08d..56cf87fb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,8 +7,8 @@ assignees: '' --- - - + + **Describe the bug** diff --git a/.github/ISSUE_TEMPLATE/configuration-has-failed.md b/.github/ISSUE_TEMPLATE/configuration-has-failed.md index 964abbce..8591bc35 100644 --- a/.github/ISSUE_TEMPLATE/configuration-has-failed.md +++ b/.github/ISSUE_TEMPLATE/configuration-has-failed.md @@ -7,11 +7,10 @@ assignees: '' --- - - + + -**Ouput of `linux-enable-ir-emitter -v -d /dev/videoX configure`** - +**Ouput of `linux-enable-ir-emitter -v configure`**
configure command output ``` @@ -25,7 +24,7 @@ paste here ``` **Ouput of `v4l2-ctl -d /dev/videoX --list-formats-ext`** - +
v4l2 device output ``` @@ -33,6 +32,11 @@ paste here ```
+**Output of `ls -l /dev/v4l/by-path`** +``` +paste here +``` + **Give more information if you have** diff --git a/.github/ISSUE_TEMPLATE/does-not-work-at-boot.md b/.github/ISSUE_TEMPLATE/does-not-work-at-boot.md index 53934da6..c9955e87 100644 --- a/.github/ISSUE_TEMPLATE/does-not-work-at-boot.md +++ b/.github/ISSUE_TEMPLATE/does-not-work-at-boot.md @@ -7,8 +7,8 @@ assignees: '' --- - - + + **Output of `linux-enable-ir-emitter boot status`** ``` diff --git a/.github/workflows/arm.yml b/.github/workflows/arm.yml index 8f233380..10563091 100644 --- a/.github/workflows/arm.yml +++ b/.github/workflows/arm.yml @@ -5,7 +5,7 @@ on: workflow_call: env: - OPENCV_TINY_VERSION: "4.7.0" + OPENCV_TINY_VERSION: "4.8.0" jobs: build: @@ -35,21 +35,25 @@ jobs: --volume "${PWD}/artifacts:/artifacts" --volume "${PWD}/opencv-tiny:/opencv-tiny" install: | - apt-get update -q -y - apt-get install -q -y g++ meson pkg-config curl cmake + apt-get update -y + apt-get install -y python3 python3-pip python3-setuptools python3-wheel ninja-build g++ pkg-config curl cmake libgtk-3-dev + python3 -m pip install --upgrade pip + python3 -m pip install meson + run: | [ "$(ls /opencv-tiny)" ] || curl https://raw.githubusercontent.com/EmixamPP/opencv-tiny/main/build.sh | bash -s ${OPENCV_TINY_VERSION} /opencv-tiny meson setup build --pkg-config-path=$(find opencv-tiny -name pkgconfig | tr '\n' ':') - meson configure build -Dlibdir=lib64 + meson configure build -Dlibdir=lib64 meson compile -C build DESTDIR=install_dir meson install -C build - tar -czvf /artifacts/linux-enable-ir-emitter_aarch64.tar.gz -C build/install_dir . + chown -R root:root build/install_dir + tar -czvf /artifacts/linux-enable-ir-emitter.aarch64.tar.gz -C build/install_dir . - name: Upload tarball uses: actions/upload-artifact@v3 with: - name: linux-enable-ir-emitter_aarch64.tar.gz - path: artifacts/linux-enable-ir-emitter_aarch64.tar.gz + name: linux-enable-ir-emitter.aarch64.tar.gz + path: artifacts/linux-enable-ir-emitter.aarch64.tar.gz if-no-files-found: error diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f358fb25..d87bd7c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: workflow_call: env: - OPENCV_TINY_VERSION: "4.7.0" + OPENCV_TINY_VERSION: "4.8.0" jobs: ci: @@ -21,9 +21,9 @@ jobs: - name: Install dependencies run: | sudo apt-get update -y - sudo apt-get install -y g++ meson pkg-config curl clang-tidy + sudo apt-get install -y python3-setuptools python3-wheel ninja-build g++ pkg-config clang-tidy libgtk-3-dev python -m pip install --upgrade pip - python -m pip install pyright + python -m pip install meson pyright - name: Cache OpenCV dependency id: cache-opencv @@ -61,11 +61,12 @@ jobs: - name: Create tarball run: | DESTDIR=install_dir meson install -C build - tar -czvf build/linux-enable-ir-emitter_x86-64.tar.gz -C build/install_dir . + sudo chown -R root:root build/install_dir + tar -czvf build/linux-enable-ir-emitter.x86-64.tar.gz -C build/install_dir . - name: Upload tarball uses: actions/upload-artifact@v3 with: - name: linux-enable-ir-emitter_x86-64.tar.gz - path: build/linux-enable-ir-emitter_x86-64.tar.gz + name: linux-enable-ir-emitter.x86-64.tar.gz + path: build/linux-enable-ir-emitter.x86-64.tar.gz if-no-files-found: error diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b08d7667..b10e800b 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -2,6 +2,9 @@ name: Pull Request on: pull_request: + paths-ignore: + - 'docs/**' + - 'README.md' jobs: ci: diff --git a/.gitignore b/.gitignore index b157662a..bf0a6703 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build # OpenCV opencv* +!sources/camera/opencv.hpp zlib* # Others diff --git a/CHANGELOG.md b/CHANGELOG.md index d55eaba0..6257a92e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# ? Aug ? 2023 - 5.0.0 +- Automatic ir camera detection +- Automatic ir emitter configuration +- Exhaustive search +- Video feedback # Fri Jun 23 2023 - 4.8.2 - Fix systemd hanging in some cases - More appropriate installation paths @@ -16,7 +21,6 @@ - Support multiple emitters camera - Memorize broken instructions to skip them - Usage of /dev/v4l/by-path for persistence -- Drop distribution repositories support # Tue Sep 13 2022 - 4.1.5 - Fix boot service for custom device # Thu Aug 11 2022 - 4.1.4 @@ -49,4 +53,4 @@ - New fix command, to resolve well know problems - Systemd service file modified to prevent /dev/video file descriptor error # Thu Aug 12 2021 - 2.0.1 -- Initial package \ No newline at end of file +- Initial release diff --git a/README.md b/README.md index b7b275d9..257c0cea 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,12 @@ Provides support for infrared cameras that are not directly enabled out-of-the b `linux-enable-ir-emitter` can automatically generate a lightweight driver (located in user space) for almost any (UVC) infrared emitter. -This tool was created to use [Howdy](https://github.com/boltgolt/howdy), a Windows Hello for linux. - ## Installation Directly refer to the manual buid [section](#manual-build) if your boot service manager is not Systemd but OpenRC. Stay here if you don't know. We support ARM architectures, just download the `aarch64` variant. -Download the latest `linux-enable-ir-emitter-x.x.x_x86-64.tar.gz` [here](https://github.com/EmixamPP/linux-enable-ir-emitter/releases/latest), then execute: +Download the latest `linux-enable-ir-emitter-x.x.x.x86-64.tar.gz` [here](https://github.com/EmixamPP/linux-enable-ir-emitter/releases/latest), then execute: ``` -sudo tar -C / --no-same-owner -h -xzf linux-enable-ir-emitter-*.tar.gz +sudo tar -C / -h -xzf linux-enable-ir-emitter-*.tar.gz ``` It can be uninstalled by executing (remove the last line to keep the emitter configuration): @@ -26,22 +24,27 @@ sudo rm -rf /usr/lib64/linux-enable-ir-emitter \ ``` ## How to enable your infrared emitter? -1. Ensure to not use the camera during the execution. -2. Be patient, do not kill the process, and whatever the reason. (Unless the execution is stuck for more than 10 minutes.) -3. Execute `sudo linux-enable-ir-emitter configure`. - * You can specify your infrared camera with the option `-d`, by default it is `-d /dev/video2`. +1. Stand in front of the camera and make sure the room is well lit. +2. Ensure to not use the camera during the execution. +3. Be patient, do not kill the process. +4. Execute `sudo linux-enable-ir-emitter configure`. * If you have many emitters on the camera, specify it using the option `-e`. E.g. `-e 2` if you have 2 emitters. -4. Answer to the asked questions. -5. Sometimes, it can request you to shut down, then boot and retry ($\neq$ reboot) +5. Answer to the asked questions. +6. Sometimes, it can request you to shut down, then boot and retry ($\neq$ reboot) + +If you like the project, do not hesitate to star the repository to support me, thank you! -If you like the project, do not hesitate to star the repository to support me, thank you ! +Any criticims, ideas and contributions are welcome. -*Please consult the [wiki](https://github.com/EmixamPP/linux-enable-ir-emitter/wiki) before opening an issue.* +If the configuration failed: +1. But you saw the ir emitter flashing, switch to manual mode by using the `-m` option. +2. Also, try the exhaustive search by using the `-l -1` option (caution: this may take several hours; do not combine it `-m`). +3. Otherwise, feel free to open an issue, *but please consult the [docs](docs/README.md) first*. The software supports the configuration of multiple devices, execute the configure command and specify each time which device to configure. ## Manual build -See [wiki](https://github.com/EmixamPP/linux-enable-ir-emitter/wiki/Requirements) for specification concerning build requirements. +See [docs](docs/requirements.md) for specification concerning build requirements. Clone the git: ``` @@ -51,7 +54,7 @@ cd linux-enable-ir-emitter Build my minimal version of OpenCV that will be statically linked. This is not required, you can use the shared opencv library of your distro. But it is recommanded in order to do not have issues after distro updates: ``` -curl https://raw.githubusercontent.com/EmixamPP/opencv-tiny/main/build.sh | bash -s 4.7.0 "${PWD}/opencv-tiny" +curl https://raw.githubusercontent.com/EmixamPP/opencv-tiny/main/build.sh | bash -s 4.8.0 "${PWD}/opencv-tiny" ``` Setup build (remove `--pkg-config-path=...` if you skipped the previous step): diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..7b9d87c2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,53 @@ +### In this documentations you can find help for: +* [Solve common problem and instruction for opening a new issue](issues.md) +* [System, manual build and hardware requirements](requirements.md) +* [Exit code meaning](exit-code.md) +* [Command line usage](#linux-enable-ir-emitter-usage) + +### linux-enable-ir-emitter usage +``` +usage: linux-enable-ir-emitter [-h] [-v] [-V] [-d device] {run,configure,boot,delete} ... + +Provides support for infrared cameras. + +positional arguments: + {run,configure,boot,delete} + run apply drivers + configure generate ir emitter driver + boot enable ir at boot + delete delete drivers + +options: + -h, --help show this help message and exit + -v, --verbose print verbose information + -V, --version show version information and exit + -d device, --device device + specify the infrared camera, automatic detection by default +``` +``` +usage: linux-enable-ir-emitter configure [-h] [-m] [-e ] [-l ] + +options: + -h, --help show this help message and exit + -m, --manual activate manual configuration + -e , --emitters + the number of emitters on the device, by default is 1 + -l , --limit + the number of negative answer before the pattern is skipped, by default is 5. Use -1 for unlimited +``` +``` +usage: linux-enable-ir-emitter boot [-h] {enable,disable,status} + +positional arguments: + {enable,disable,status} + specify the boot action to perform + +optional arguments: + -h, --help show this help message and exit +``` +``` +usage: linux-enable-ir-emitter delete [-h] + +options: + -h, --help show this help message and exit +``` \ No newline at end of file diff --git a/docs/exit-code.md b/docs/exit-code.md new file mode 100644 index 00000000..4cf00e5f --- /dev/null +++ b/docs/exit-code.md @@ -0,0 +1,8 @@ +## Exit code meaning + +| Code | Meaning | How to fix | +|------|---------|------------| +| 0 | SUCCESS | | +| 1 | FAILURE | read the displayed logs output for more information | +| 2 | ROOT_REQUIRED | execute with root privilege | +| 126 | FILE_DESCRIPTOR_ERROR | check the -d option or your running processes which may use the camera| \ No newline at end of file diff --git a/docs/issues.md b/docs/issues.md new file mode 100644 index 00000000..919bafe3 --- /dev/null +++ b/docs/issues.md @@ -0,0 +1,23 @@ +### Please, first, ensure that you have the last version. Compare the [latest release](https://github.com/EmixamPP/linux-enable-ir-emitter/releases/latest) with `linux-enable-ir-emitter -V` + +## The configuration has failed +If you had the message `INFO: Please shut down your computer, then boot and retry.`, please follow these instructions first: +1. Shutdown your computer, (not just rebooting) +2. Remove the AC adapter and if possible the battery +3. Wait one minute +4. Boot +5. Retry + +Otherwise, open an issue using the "Configuration has failed" template. + +## The emitter does not work after successful configuration or after update. +1. If you use Howdy, be sure it uses the infrared camera configured; the path is printed after the message `INFO: Configuring the camera:`. +2. Execute `sudo linux-enable-ir-emitter boot enable`. +3. If none of the previous fix your problem, open an issue using the "Emitter does not work after successful configuration" template. + +## Error with the systemd/openrc or udev boot service +1. Ensure you have `systemd` or `openrc` (and the related version installed) +2. Open an issue using the "Emitter does not work after successful configuration" template. + +## Other bugs +Open an issue using the "Bug report" template. diff --git a/docs/requirements.md b/docs/requirements.md new file mode 100644 index 00000000..89d52f06 --- /dev/null +++ b/docs/requirements.md @@ -0,0 +1,16 @@ +## System requirements +* if you used chicony-ir-toggle, please remove it +* `python3 >= 3.10` +* `video4linux` as video capture devices framework +* `systemd` or `openrc` as service manager +* `glibc`, `libstdc++`, `libgcc`, `gtk3` + +## Manual build requirements +* `meson >= 1.0.0`, `ninja-build`, `pkg-config`, `gtk3 devel` +* `cmake` only if you build OpenCV +* `g++`, `libstdc++-devel` (for `C++17`) +* `curl` + +## Hardware requirements +* An infrared camera with at least one emitter. +* A camera compatible with the UVC standard. Typically, it has to be a USB camera (laptop camera are mostly USB). \ No newline at end of file diff --git a/meson.build b/meson.build index 9ea1bfa9..a6a0b09c 100644 --- a/meson.build +++ b/meson.build @@ -1,17 +1,33 @@ project( 'linux-enable-ir-emitter', 'cpp', - version: '4.8.2', + version: '5.0.0', license: 'MIT', default_options: [ + 'prefix=/usr', 'cpp_std=c++17', 'optimization=3', - 'warning_level=3', - 'prefix=/usr', - ], + 'warning_level=everything', + 'werror=true', + ], ) +# flag ajustement because of warning_level=everything +add_project_arguments( + # fix clang-tidy + '-Wno-unknown-warning-option', '-Wno-unused-macros', + # ignored bugged or boring gcc flags + '-Wno-pragmas', '-Wno-abi-tag', '-Wno-suggest-attribute=pure', + '-Wno-effc++','-Wno-suggest-attribute=const', '-Wno-padded', + '-Wformat-nonliteral', '-Wno-suggest-final-types', '-Wno-inline', + '-Wno-shadow', '-Wno-missing-declarations', '-Wno-sign-promo', + '-Wno-format-nonliteral', '-Wno-suggest-final-methods', + '-Wno-unused-const-variable', + language: 'cpp' +) + opencv_dep = dependency('opencv4', static: true) +gtk_dep = dependency('gtk+-3.0') ################ # Lib executable @@ -19,13 +35,15 @@ opencv_dep = dependency('opencv4', static: true) lib_exec_dir = get_option('libexecdir') / meson.project_name() executable( - 'driver-generator', - 'sources/driver/driver-generator.cpp', - 'sources/driver/camera.cpp', - 'sources/driver/logger.cpp', + 'generate-driver', + 'sources/driver/generate-driver.cpp', + 'sources/camera/camera.cpp', + 'sources/camera/autocamera.cpp', + 'sources/camera/camerainstruction.cpp', 'sources/driver/driver.cpp', 'sources/driver/finder.cpp', - dependencies: opencv_dep, + 'sources/utils/logger.cpp', + dependencies: [opencv_dep, gtk_dep], install: true, install_dir: lib_exec_dir, ) @@ -33,10 +51,23 @@ executable( executable( 'execute-driver', 'sources/driver/execute-driver.cpp', - 'sources/driver/camera.cpp', - 'sources/driver/logger.cpp', + 'sources/camera/camera.cpp', + 'sources/camera/autocamera.cpp', + 'sources/camera/camerainstruction.cpp', 'sources/driver/driver.cpp', - dependencies: opencv_dep, + 'sources/utils/logger.cpp', + dependencies: [opencv_dep, gtk_dep], + install: true, + install_dir: lib_exec_dir, +) + +executable( + 'is-ir-camera', + 'sources/camera/is-ir-camera.cpp', + 'sources/camera/camera.cpp', + 'sources/camera/camerainstruction.cpp', + 'sources/utils/logger.cpp', + dependencies: [opencv_dep, gtk_dep], install: true, install_dir: lib_exec_dir, ) @@ -61,7 +92,8 @@ boot_service = get_option('boot_service') configure_paths_data = configuration_data({ 'SAVE_DRIVER_FOLDER_PATH': get_option('sysconfdir') / meson.project_name(), 'BIN_EXECUTE_DRIVER_PATH': '/usr' / lib_exec_dir / 'execute-driver', - 'BIN_DRIVER_GENERATOR_PATH': '/usr' / lib_exec_dir / 'driver-generator', + 'BIN_GENERATE_DRIVER_PATH': '/usr' / lib_exec_dir / 'generate-driver', + 'BIN_IS_CAMERA_PATH': '/usr' / lib_exec_dir / 'is-ir-camera', 'UDEV_RULE_PATH': '/etc/udev/rules.d/99-linux-enable-ir-emitter.rules', 'BOOT_SERVICE_MANAGER': boot_service, }) diff --git a/sources/camera/autocamera.cpp b/sources/camera/autocamera.cpp new file mode 100644 index 00000000..9917c905 --- /dev/null +++ b/sources/camera/autocamera.cpp @@ -0,0 +1,79 @@ +#include "autocamera.hpp" + +#include +#include +#include +#include +#include +#include +using namespace std; + +#include "opencv.hpp" + +/** + * @brief Obtain the intensity variation sum of camera captures + * + * @return the intensity variation sum + */ +long long unsigned AutoCamera::intensityVariationSum() +{ + openCap(); + shared_ptr cap = getCap(); + vector> frames; + + // capture frames + const auto stopTime = chrono::steady_clock::now() + chrono::milliseconds(captureTimeMs); + while (chrono::steady_clock::now() < stopTime) + { + auto frame = make_unique(); + cap->read(*frame); + if (!frame->empty()) + frames.push_back(std::move(frame)); + } + + // compute lighting intensity + vector>> intensities; + for (auto &frame : frames) + { + auto intensity = make_unique>(); + for (int r = 0; r < frame->rows; ++r) + for (int c = 0; c < frame->cols; ++c) + { + const cv::Vec3b &pixel = frame->at(r, c); + intensity->push_back(pixel[0] + pixel[1] + pixel[2]); + } + intensities.push_back(std::move(intensity)); + } + + // compute difference between each consecutive frame intensity + vector diffs; + for (unsigned i = 0; i < intensities.size() - 1; ++i) + { + const auto &intensity1 = intensities[i]; + const auto &intensity2 = intensities[i + 1]; + int diff = 0; + for (unsigned j = 0; j < intensity1->size(); ++j) + { + diff += intensity1->at(j) - intensity2->at(j); + } + diffs.push_back(diff); + } + + // compute difference between each consecutive intensity difference + // this is the variation in the lighting intensity of the fames + // and sum them all + long long unsigned sum = 0; + for (unsigned i = 0; i < diffs.size() - 1; ++i) + sum += static_cast(abs(diffs[i] - diffs[i + 1])); + + closeCap(); + return sum; +} + +bool AutoCamera::isEmitterWorking() +{ + bool isWorking = intensityVariationSum() > refIntesityVarSum * MAGIC_REF_INTENSITY_VAR_COEF; + return isWorking && Camera::isEmitterWorking(); +} + +AutoCamera::AutoCamera(const string &device, unsigned captureTimeMs) : Camera(device), captureTimeMs(captureTimeMs), refIntesityVarSum(intensityVariationSum()) {} diff --git a/sources/camera/autocamera.hpp b/sources/camera/autocamera.hpp new file mode 100644 index 00000000..e2abbc4c --- /dev/null +++ b/sources/camera/autocamera.hpp @@ -0,0 +1,37 @@ +#ifndef AUTOCAMERA_HPP +#define AUTOCAMERA_HPP + +#include +using namespace std; + +#include "camera.hpp" + +constexpr int MAGIC_REF_INTENSITY_VAR_COEF = 50; + +class AutoCamera : public Camera +{ +private: + unsigned captureTimeMs; + long long unsigned refIntesityVarSum; + + long long unsigned intensityVariationSum(); + +public: + AutoCamera() = delete; + + explicit AutoCamera(const string &device, unsigned captureTimeMs = 1000); + + ~AutoCamera() override = default; + + bool isEmitterWorking() override; + + AutoCamera &operator=(const AutoCamera &) = delete; + + AutoCamera(const AutoCamera &) = delete; + + AutoCamera &operator=(AutoCamera &&other) = delete; + + AutoCamera(AutoCamera && other) = delete; +}; + +#endif \ No newline at end of file diff --git a/sources/camera/camera.cpp b/sources/camera/camera.cpp new file mode 100644 index 00000000..651d6362 --- /dev/null +++ b/sources/camera/camera.cpp @@ -0,0 +1,324 @@ +#include "camera.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#include "opencv.hpp" + +#include "camerainstruction.hpp" +#include "../utils/logger.hpp" + +constexpr int OK_KEY = 121; +constexpr int NOK_KEY = 110; + +/** + * @brief Get the file descriptor previously opened + * + * @return the fd + */ +int Camera::getFd() const noexcept +{ + return fd; +} + +/** + * @brief Get the VideoCapture previsouly opened + * + * @return the cap + */ +shared_ptr Camera::getCap() const noexcept +{ + return cap; +} + +/** + * @brief Obtain the id of any device path + * + * @param device path to the camera + * + * @throw runtime_error if unable to obtain the /dev/videoX path + * + * @return the device id + */ +int Camera::deviceId(const string &device) +{ + std::unique_ptr devDevice(realpath(device.c_str(), nullptr), &free); + int id; + if (devDevice == nullptr || sscanf(devDevice.get(), "/dev/video%d", &id) != 1) + throw runtime_error("CRITICAL: Unable to obtain the /dev/videoX path"); + return id; +} + +/** + * @brief Open a file discriptor if not yet open + * + * @throw CameraException if unable to open the camera device + */ +void Camera::openFd() +{ + closeCap(); + + if (fd < 0) + { + errno = 0; + fd = open(device.c_str(), O_WRONLY); + if (fd < 0 || errno) + throw CameraException(device); + } +} + +/** + * @brief Close the current file descriptor + */ +void Camera::closeFd() noexcept +{ + if (fd != -1) + { + close(fd); + fd = -1; + } +} + +/** + * @brief Open a VideoCapture if not yet open + * + * @throw CameraException if unable to open the camera device + */ +void Camera::openCap() +{ + closeFd(); + + if (!cap->isOpened()) + { + bool isOpened = cap->open(id, cv::CAP_V4L2); + if (!isOpened) + throw CameraException(device); + } +} + +/** + * @brief Close the current VideoCapture + */ +void Camera::closeCap() noexcept +{ + if (cap->isOpened()) + cap->release(); +} + +Camera::Camera(const string &device) + : id(Camera::deviceId(device)), device(device) +{ + cv::utils::logging::setLogLevel(cv::utils::logging::LogLevel::LOG_LEVEL_ERROR); +} + +Camera::~Camera() +{ + closeFd(); + closeCap(); +} + +/** + * @brief Apply an instruction on the camera + * + * @param instruction to apply + * + * @throw CameraException if unable to open the camera device + * + * @return true if success, otherwise false + */ +bool Camera::apply(const CameraInstruction &instruction) +{ + const uvc_xu_control_query query = { + instruction.getUnit(), + instruction.getSelector(), + UVC_SET_CUR, + static_cast(instruction.getCurrent().size()), + const_cast(instruction.getCurrent().data()), // const_cast safe; this is a set query + }; + return executeUvcQuery(query) == 0; +} + +/** + * @brief Read one frame + * + * @throw CameraException if unable to open the camera device + * + * @return the frame + */ +unique_ptr Camera::read1() +{ + openCap(); + auto frame = make_unique(); + cap->read(*frame); + closeCap(); + return frame; +} + +/** + * @brief Check if the emitter is working + * + * @throw CameraException if unable to open the camera device + * + * @return true if yes, false if not + */ +bool Camera::isEmitterWorking() +{ + openCap(); + cv::Mat frame; + int key = -1; + + cout << "Is the video flashing? Press Y or N" << endl; + + while (key != OK_KEY && key != NOK_KEY) + { + cap->read(frame); + cv::imshow("linux-enable-ir-emitter", frame); + key = cv::waitKey(5); + } + + cout << (key == OK_KEY ? "Y pressed" : "N pressed") << endl; + + closeCap(); + cv::destroyAllWindows(); + + return key == OK_KEY; +} + +/** + * @brief Execute an uvc query on the device indicated by the file descriptor + * + * @param query uvc query to execute + * + * @throw CameraException if unable to open the camera device + * + * @return 1 if error, otherwise 0 + **/ +int Camera::executeUvcQuery(const uvc_xu_control_query &query) +{ + openFd(); + errno = 0; + const int result = ioctl(fd, UVCIOC_CTRL_QUERY, &query); + if (result == 1 || errno) + { + /* // ioctl debug not really useful for automated driver generation since linux-enable-ir-emitter v3 + fprintf(stderr, "Ioctl error code: %d, errno: %d\n", result, errno); + switch (errno) { + case ENOENT: + fprintf(stderr, "The device does not support the given control or the specified extension unit could not be found.\n"); + break; + case ENOBUFS: + fprintf(stderr, "The specified buffer size is incorrect (too big or too small).\n"); + break; + case EINVAL: + fprintf(stderr, "An invalid request code was passed.\n"); + break; + case EBADRQC: + fprintf(stderr, "The given request is not supported by the given control.\n"); + break; + case EFAULT: + fprintf(stderr, "The data pointer references an inaccessible memory area.\n"); + break; + case EILSEQ: + fprintf(stderr, "Illegal byte sequence.\n"); + break; + }*/ + return 1; + } + return 0; +} + +/** + * @brief Change the current uvc control value for the camera device + * + * @param unit extension unit ID + * @param selector control selector + * @param control control value + * + * @throw CameraException if unable to open the camera device + * + * @return 1 if error, otherwise 0 + **/ +int Camera::setUvcQuery(uint8_t unit, uint8_t selector, vector &control) +{ + const uvc_xu_control_query query = { + unit, + selector, + UVC_SET_CUR, + static_cast(control.size()), + control.data(), + }; + + return executeUvcQuery(query); +} + +/** + * @brief Get the current, maximale, resolution or minimale value of the uvc control for the camera device + * + * @param query_type UVC_GET_MAX, UVC_GET_RES, UVC_GET_CUR or UVC_GET_MIN + * @param unit extension unit ID + * @param selector control selector + * @param controlSize size of the uvc control + * @param control control value + * + * @throw CameraException if unable to open the camera device + * + * @return 1 if error, otherwise 0 + **/ +int Camera::getUvcQuery(uint8_t query_type, uint8_t unit, uint8_t selector, vector &control) +{ + const uvc_xu_control_query query = { + unit, + selector, + query_type, + static_cast(control.size()), + control.data(), + }; + + return executeUvcQuery(query); +} + +/** + * @brief Get the size of the uvc control for the indicated device. + * + * @param unit extension unit ID + * @param selector control selector + * + * @throw CameraException if unable to open the camera device + * + * @return size of the control, 0 if error + **/ +uint16_t Camera::lenUvcQuery(uint8_t unit, uint8_t selector) +{ + uint8_t data[2] = {0x00, 0x00}; + const uvc_xu_control_query query = { + unit, + selector, + UVC_GET_LEN, + 2, + data, + }; + + if (executeUvcQuery(query) == 1) + return 0; + + uint16_t len = 0; + memcpy(&len, query.data, 2); + return len; +} + +CameraException::CameraException(const string &device) : message("CRITICAL: Cannot access to " + device) {} + +const char *CameraException::what() const noexcept +{ + return message.c_str(); +} diff --git a/sources/camera/camera.hpp b/sources/camera/camera.hpp new file mode 100644 index 00000000..d211df63 --- /dev/null +++ b/sources/camera/camera.hpp @@ -0,0 +1,80 @@ +#ifndef CAMERA_HPP +#define CAMERA_HPP + +#include +#include +#include +#include +#include +using namespace std; + +#include "opencv.hpp" + +class CameraInstruction; + +class Camera +{ +private: + int id; + int fd = -1; + const shared_ptr cap = make_shared(); + +protected: + int getFd() const noexcept; + + shared_ptr getCap() const noexcept; + + void openFd(); + + void closeFd() noexcept; + + void openCap(); + + void closeCap() noexcept; + + static int deviceId(const string &device); + + int executeUvcQuery(const uvc_xu_control_query &query); + +public: + const string device; + + Camera() = delete; + + explicit Camera(const string &device); + + virtual ~Camera(); + + Camera &operator=(const Camera &) = delete; + + Camera(const Camera &) = delete; + + Camera &operator=(Camera &&other) = delete; + + Camera(Camera &&other) = delete; + + bool apply(const CameraInstruction &instruction); + + virtual bool isEmitterWorking(); + + unique_ptr read1(); + + int setUvcQuery(uint8_t unit, uint8_t selector, vector &control); + + int getUvcQuery(uint8_t query_type, uint8_t unit, uint8_t selector, vector &control); + + uint16_t lenUvcQuery(uint8_t unit, uint8_t selector); +}; + +class CameraException : public exception +{ +private: + string message; + +public: + CameraException(const string &device); + + const char *what() const noexcept override; +}; + +#endif \ No newline at end of file diff --git a/sources/camera/camerainstruction.cpp b/sources/camera/camerainstruction.cpp new file mode 100644 index 00000000..eb04c6bf --- /dev/null +++ b/sources/camera/camerainstruction.cpp @@ -0,0 +1,185 @@ +#include "camerainstruction.hpp" + +#include +#include +#include +#include +using namespace std; + +#include "opencv.hpp" + +#include "camera.hpp" +#include "../utils/logger.hpp" + +/** + * @brief Print the control value in the debug log + * + * @param prefixMasg what show before the control + * @param control control value + */ +void CameraInstruction::logDebugCtrl(const string &prefixMsg, const vector &control) noexcept +{ + string msg = prefixMsg; + for (const uint8_t &i : control) + msg += " " + to_string(i); + Logger::debug(msg); +} + +/** + * @brief Construct a new CameraInstruction object + * + * @param camera on which find the control instruction + * @param unit of the instruction + * @param selector of the instruction + * + * @throw CameraInstructionException if information are missing for controlling the device + */ +CameraInstruction::CameraInstruction(Camera &camera, uint8_t unit, uint8_t selector) + : unit(unit), selector(selector) +{ + // get the control instruction lenght + uint16_t ctrlSize = camera.lenUvcQuery(unit, selector); + if (ctrlSize == 0) + throw CameraInstructionException(camera.device, unit, selector); + + // get the current control value + curCtrl.resize(ctrlSize); + if (camera.getUvcQuery(UVC_GET_CUR, unit, selector, curCtrl) == 1) + throw CameraInstructionException(camera.device, unit, selector); + + // ensure the control can be modified + if (camera.setUvcQuery(unit, selector, curCtrl) == 1) + throw CameraInstructionException(camera.device, unit, selector); + + // try to get the maximum control value (it does not necessary exists) + maxCtrl.resize(ctrlSize); + if (camera.getUvcQuery(UVC_GET_MAX, unit, selector, maxCtrl) == 1) + { + Logger::debug("Using default maximum control."); + maxCtrl.assign(ctrlSize, 255); + } + + // try get the minimum control value (it does not necessary exists) + minCtrl.resize(ctrlSize); + if (camera.getUvcQuery(UVC_GET_MIN, unit, selector, minCtrl) == 1) + { + Logger::debug("Using current as minimum control."); + minCtrl.assign(curCtrl.begin(), curCtrl.end()); + } + + Logger::debug(("unit: " + to_string(unit) + " selector: " + to_string(selector))); + logDebugCtrl("current:", curCtrl); + logDebugCtrl("minimum:", minCtrl); + logDebugCtrl("maximum:", maxCtrl); +} + +/** + * @brief Construct a new Camera Instruction object + * Cannot compute next control instruction, and do not check if the instruction is valid + * + * @param unit of the instruction + * @param selector of the instruction + * @param control instruction + */ +CameraInstruction::CameraInstruction(uint8_t unit, uint8_t selector, const vector &control) + : unit(unit), selector(selector), curCtrl(control) {} + +/** + * @brief Compute the next possible control value + * + * @return true if the next value has been set, + * false if the maximum control has already been set + */ +bool CameraInstruction::next() noexcept +{ + for (unsigned i = 0; i < curCtrl.size(); ++i) + { + uint16_t nextCtrli = static_cast(curCtrl[i] + 1); + if (nextCtrli > maxCtrl[i]) + curCtrl[i] = minCtrl[i]; // simulate "overflow" + else + { + curCtrl[i] = static_cast(nextCtrli); + logDebugCtrl("new current:", curCtrl); + return true; + } + } + + setMaxAsCur(); + + return false; +} + +/** + * @brief Get the current control value + * + * @return current control value + */ +const vector &CameraInstruction::getCurrent() const noexcept +{ + return curCtrl; +} + +/** + * @brief Get the unit of the instruction + * + * @return unit + */ +uint8_t CameraInstruction::getUnit() const noexcept +{ + return unit; +} + +/** + * @brief Get the selector of the instruction + * + * @return selector + */ +uint8_t CameraInstruction::getSelector() const noexcept +{ + return selector; +} + +/** + * @brief If a minimun control instruction exists + * and is not already the current, + * set the current control instruction with that value + * + * @return true if success, otherwise false + */ +bool CameraInstruction::setMinAsCur() noexcept +{ + if (minCtrl.empty() || curCtrl == minCtrl) + return false; + + curCtrl.assign(minCtrl.begin(), minCtrl.end()); + logDebugCtrl("new current:", curCtrl); + + return true; +} + +/** + * @brief If a maximum control instruction + * is not already the current, + * set the current control instruction with that value + * + * @return true if success, otherwise false + */ +bool CameraInstruction::setMaxAsCur() noexcept +{ + if (curCtrl == maxCtrl) + return false; + + curCtrl.assign(maxCtrl.begin(), maxCtrl.end()); + logDebugCtrl("new current:", curCtrl); + + return true; +} + +CameraInstructionException::CameraInstructionException(const string &device, uint8_t unit, uint8_t selector) + : message("ERROR: Impossible to obtain the instruction on " + device + " for unit: " + to_string(unit) + " selector:" + to_string(selector)) {} + +const char *CameraInstructionException::what() const noexcept +{ + return message.c_str(); +} diff --git a/sources/camera/camerainstruction.hpp b/sources/camera/camerainstruction.hpp new file mode 100644 index 00000000..24e0d778 --- /dev/null +++ b/sources/camera/camerainstruction.hpp @@ -0,0 +1,65 @@ +#ifndef CAMERAINSTRUCTION_HPP +#define CAMERAINSTRUCTION_HPP + +#include +#include +#include +using namespace std; + +class Camera; + +class CameraInstruction +{ +private: + uint8_t unit; + uint8_t selector; + +protected: + vector curCtrl; + vector maxCtrl; + vector minCtrl; + + static void logDebugCtrl(const string &prefixMsg, const vector &control) noexcept; + +public: + CameraInstruction() = delete; + + explicit CameraInstruction(Camera &camera, uint8_t unit, uint8_t selector); + + explicit CameraInstruction(uint8_t unit, uint8_t selector, const vector &control); + + ~CameraInstruction() = default; + + CameraInstruction &operator=(const CameraInstruction &) = default; + + CameraInstruction(const CameraInstruction &) = default; + + CameraInstruction &operator=(CameraInstruction &&other) = default; + + CameraInstruction(CameraInstruction &&other) = default; + + bool next() noexcept; + + const vector &getCurrent() const noexcept; + + uint8_t getUnit() const noexcept; + + uint8_t getSelector() const noexcept; + + bool setMinAsCur() noexcept; + + bool setMaxAsCur() noexcept; +}; + +class CameraInstructionException : public exception +{ +private: + string message; + +public: + explicit CameraInstructionException(const string &device, uint8_t unit, uint8_t selector); + + const char *what() const noexcept override; +}; + +#endif \ No newline at end of file diff --git a/sources/camera/is-ir-camera.cpp b/sources/camera/is-ir-camera.cpp new file mode 100644 index 00000000..475f7eb1 --- /dev/null +++ b/sources/camera/is-ir-camera.cpp @@ -0,0 +1,24 @@ +#include +using namespace std; + +#include "camera.hpp" +#include "opencv.hpp" + +int main(int, const char **argv) +{ + Camera camera(argv[1]); + unique_ptr frame = camera.read1(); + + if (frame->channels() != 3) + return 1; + + for (int r = 0; r < frame->rows; ++r) + for (int c = 0; c < frame->cols; ++c) + { + const cv::Vec3b &pixel = frame->at(r, c); + if (pixel[0] != pixel[1] || pixel[0] != pixel[2]) + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/sources/camera/opencv.hpp b/sources/camera/opencv.hpp new file mode 100644 index 00000000..bafa0839 --- /dev/null +++ b/sources/camera/opencv.hpp @@ -0,0 +1,107 @@ +#ifndef OPENCV_HPP +#define OPENCV_HPP + +// NOLINTBEGIN +#pragma GCC diagnostic push + +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Winvalid-pch" +#pragma GCC diagnostic ignored "-Wextra" +#pragma GCC diagnostic ignored "-Wpedantic" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wformat=2" +#pragma GCC diagnostic ignored "-Winline" +#pragma GCC diagnostic ignored "-Wmissing-declarations" +#pragma GCC diagnostic ignored "-Wredundant-decls" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wuninitialized" +#pragma GCC diagnostic ignored "-Wwrite-strings" +#pragma GCC diagnostic ignored "-Wdisabled-optimization" +#pragma GCC diagnostic ignored "-Wpacked" +#pragma GCC diagnostic ignored "-Wpadded" +#pragma GCC diagnostic ignored "-Wmultichar" +#pragma GCC diagnostic ignored "-Wswitch-default" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wunused-macros" +#pragma GCC diagnostic ignored "-Wmissing-include-dirs" +#pragma GCC diagnostic ignored "-Wunsafe-loop-optimizations" +#pragma GCC diagnostic ignored "-Wstack-protector" +#pragma GCC diagnostic ignored "-Wstrict-overflow=5" +#pragma GCC diagnostic ignored "-Warray-bounds=2" +#pragma GCC diagnostic ignored "-Wlogical-op" +#pragma GCC diagnostic ignored "-Wstrict-aliasing=3" +#pragma GCC diagnostic ignored "-Wvla" +#pragma GCC diagnostic ignored "-Wdouble-promotion" +#pragma GCC diagnostic ignored "-Wsuggest-attribute=const" +#pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" +#pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +#pragma GCC diagnostic ignored "-Wtrampolines" +#pragma GCC diagnostic ignored "-Wvector-operation-performance" +#pragma GCC diagnostic ignored "-Wsuggest-attribute=format" +#pragma GCC diagnostic ignored "-Wdate-time" +#pragma GCC diagnostic ignored "-Wformat-signedness" +#pragma GCC diagnostic ignored "-Wnormalized=nfc" +#pragma GCC diagnostic ignored "-Wduplicated-cond" +#pragma GCC diagnostic ignored "-Wnull-dereference" +#pragma GCC diagnostic ignored "-Wshift-negative-value" +#pragma GCC diagnostic ignored "-Wshift-overflow=2" +#pragma GCC diagnostic ignored "-Wunused-const-variable=2" +#pragma GCC diagnostic ignored "-Walloca" +#pragma GCC diagnostic ignored "-Walloc-zero" +#pragma GCC diagnostic ignored "-Wformat-overflow=2" +#pragma GCC diagnostic ignored "-Wformat-truncation=2" +#pragma GCC diagnostic ignored "-Wstringop-overflow=3" +#pragma GCC diagnostic ignored "-Wduplicated-branches" +#pragma GCC diagnostic ignored "-Wattribute-alias=2" +#pragma GCC diagnostic ignored "-Wcast-align=strict" +#pragma GCC diagnostic ignored "-Wsuggest-attribute=cold" +#pragma GCC diagnostic ignored "-Wsuggest-attribute=malloc" +#pragma GCC diagnostic ignored "-Wanalyzer-too-complex" +#pragma GCC diagnostic ignored "-Warith-conversion" +#pragma GCC diagnostic ignored "-Wbidi-chars=ucn" +#pragma GCC diagnostic ignored "-Wopenacc-parallelism" +#pragma GCC diagnostic ignored "-Wtrivial-auto-var-init" +#pragma GCC diagnostic ignored "-Wctor-dtor-privacy" +#pragma GCC diagnostic ignored "-Weffc++" +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Woverloaded-virtual" +#pragma GCC diagnostic ignored "-Wsign-promo" +#pragma GCC diagnostic ignored "-Wstrict-null-sentinel" +#pragma GCC diagnostic ignored "-Wnoexcept" +#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma GCC diagnostic ignored "-Wabi-tag" +#pragma GCC diagnostic ignored "-Wuseless-cast" +#pragma GCC diagnostic ignored "-Wconditionally-supported" +#pragma GCC diagnostic ignored "-Wsuggest-final-methods" +#pragma GCC diagnostic ignored "-Wsuggest-final-types" +#pragma GCC diagnostic ignored "-Wsuggest-override" +#pragma GCC diagnostic ignored "-Wmultiple-inheritance" +#pragma GCC diagnostic ignored "-Wplacement-new=2" +#pragma GCC diagnostic ignored "-Wvirtual-inheritance" +#pragma GCC diagnostic ignored "-Waligned-new=all" +#pragma GCC diagnostic ignored "-Wnoexcept-type" +#pragma GCC diagnostic ignored "-Wregister" +#pragma GCC diagnostic ignored "-Wcatch-value=3" +#pragma GCC diagnostic ignored "-Wextra-semi" +#pragma GCC diagnostic ignored "-Wdeprecated-copy-dtor" +#pragma GCC diagnostic ignored "-Wredundant-move" +#pragma GCC diagnostic ignored "-Wcomma-subscript" +#pragma GCC diagnostic ignored "-Wmismatched-tags" +#pragma GCC diagnostic ignored "-Wredundant-tags" +#pragma GCC diagnostic ignored "-Wvolatile" +#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" +#pragma GCC diagnostic ignored "-Wdeprecated-enum-float-conversion" +#pragma GCC diagnostic ignored "-Winvalid-imported-macros" + +#include +#include +#include + +#pragma GCC diagnostic pop +// NOLINTEND + +#endif diff --git a/sources/command/configure.py b/sources/command/configure.py index 632b9ad8..3234010a 100644 --- a/sources/command/configure.py +++ b/sources/command/configure.py @@ -4,38 +4,64 @@ import subprocess from command import boot -from globals import (BIN_DRIVER_GENERATOR_PATH, SAVE_DRIVER_FOLDER_PATH, - ExitCode) +from globals import (BIN_GENERATE_DRIVER_PATH, SAVE_DRIVER_FOLDER_PATH, + BIN_IS_CAMERA_PATH, ExitCode) -def configure(device: str, emitters: int, neg_answer_limit: int) -> NoReturn: +def configure(device: str | None, manual: bool, emitters: int, neg_answer_limit: int) -> NoReturn: """Find a driver for the infrared camera and exit. Args: - device str: path to the infrared camera. - emitters (int): number of emitters on the device. - neg_answer_limit (int): after k negative answer the pattern will be skiped. Use 256 for unlimited. + device (str | None): path to the infrared camera, None for automatic detection + manual (bool): true for enabling the manual configuration + emitters (int): number of emitters on the device + neg_answer_limit (int): after k negative answer the pattern will be skiped, use 256 for unlimited """ logging.info("Ensure to not use the camera during the execution.") logging.info("Warning to do not kill the process !") + if device is None: + dev_devices = subprocess.run( + f"ls /dev/v4l/by-path/*", + shell=True, + capture_output=True, + text=True, + ).stdout.strip().split() + + for dev in dev_devices: + exit_code = subprocess.call( + [BIN_IS_CAMERA_PATH, dev], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + if exit_code == ExitCode.SUCCESS: + device = dev + break + + if device is None: + logging.critical("Impossible to find your infrared camera.") + logging.info( + "Please specify your infrared camera path using the -d option." + ) + exit(ExitCode.FAILURE) + logging.info(f"Configuring the camera: {device}.") log_level = int(logging.getLogger().level == logging.DEBUG) exit_code = subprocess.call( [ - BIN_DRIVER_GENERATOR_PATH, + BIN_GENERATE_DRIVER_PATH, device, str(emitters), str(neg_answer_limit), SAVE_DRIVER_FOLDER_PATH, str(log_level), + str(int(manual)), ] ) if exit_code != ExitCode.SUCCESS: logging.error("The configuration has failed.") - logging.info( - "Do not hesitate to visit the GitHub ! https://github.com/EmixamPP/linux-enable-ir-emitter/wiki" - ) + logging.info("Do not hesitate to visit the GitHub !") + logging.info("https://github.com/EmixamPP/linux-enable-ir-emitter/blob/master/docs/README.md") else: logging.info("The driver has been successfully generated.") boot("enable") diff --git a/sources/driver/camera.cpp b/sources/driver/camera.cpp deleted file mode 100644 index 29df52c8..00000000 --- a/sources/driver/camera.cpp +++ /dev/null @@ -1,560 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -using namespace std; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#pragma GCC diagnostic ignored "-Wconversion" -#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" -#include -#include -#pragma GCC diagnostic pop - -#include "logger.hpp" -#include "camera.hpp" - -int array_gcd(const uint8_t *arr, uint16_t size) -{ - int result = arr[0]; - for (int i = 1; i < size; ++i) - { - result = gcd(arr[i], result); - if (result == 1) - return 1; - } - return result; -} - -/** - * @brief Obtain the id of any device path - * - * @param device path to the camera - * @return the device id - */ -int Camera::deviceId(const char *device) -{ - - char *devDevice = realpath(device, NULL); - int id; - if (devDevice == NULL || sscanf(devDevice, "/dev/video%d", &id) != 1) - { - delete devDevice; - throw runtime_error("CRITICAL: Unable to obtain the /dev/videoX path"); - } - delete devDevice; - return id; -} - -/** - * @brief Open a file discriptor if not yet open - * - * @throw CameraException if unable to open the camera device - */ -void Camera::openFd() -{ - if (fd < 0) - { - errno = 0; - fd = open(device.c_str(), O_WRONLY); - if (fd < 0 || errno) - throw CameraException(device); - } -} - -/** - * @brief Close the current file descriptor - * - */ -void Camera::closeFd() noexcept -{ - if (fd != -1) - { - close(fd); - fd = -1; - } -} - -Camera::Camera(string device) - : id(Camera::deviceId(device.c_str())), device(device) -{ - cv::utils::logging::setLogLevel(cv::utils::logging::LogLevel::LOG_LEVEL_ERROR); -} - -Camera::~Camera() -{ - closeFd(); -} - -/** - * @brief Apply an instruction on the camera - * - * @param instruction to apply - * - * @throw CameraException if unable to open the camera device - * - * @return true if success, otherwise false - */ -bool Camera::apply(CameraInstruction instruction) noexcept -{ - uint8_t *control = new uint8_t[instruction.getSize()]; - memcpy(control, instruction.getCurrent(), instruction.getSize() * sizeof(uint8_t)); - const struct uvc_xu_control_query query = { - instruction.getUnit(), - instruction.getSelector(), - UVC_SET_CUR, - instruction.getSize(), - control, - }; - return executeUvcQuery(&query) == 0; -} - -/** - * @brief Check if the emitter is working - * - * @param deviceID id of the camera device - * - * @throw CameraException if unable to open the camera device - * - * @return true if yes, false if not - */ -bool Camera::isEmitterWorking() -{ - closeFd(); - cv::VideoCapture cap; - cv::Mat frame; - if (!cap.open(id, cv::CAP_V4L2) || !cap.read(frame)) - throw CameraException(device); - - string answer; - cout << "Is the ir emitter flashing (not just turn on) ? Yes/No ? "; - cin >> answer; - while (answer != "yes" && answer != "y" && answer != "Yes" && answer != "Y" && answer != "no" && answer != "n" && answer != "No" && answer != "N") - { - cout << "Yes/No ? "; - cin >> answer; - } - - cap.release(); - frame.release(); - return answer == "yes" || answer == "y" || answer == "Yes" || answer == "Y"; -} - -/** - * @brief Execute an uvc query on the device indicated by the file descriptor - * - * @param query uvc query to execute - * - * @return 1 if error, otherwise 0 - **/ -int Camera::executeUvcQuery(const struct uvc_xu_control_query *query) noexcept -{ - openFd(); - errno = 0; - int result = ioctl(fd, UVCIOC_CTRL_QUERY, query); - if (result || errno) - { - /* // ioctl debug not really useful for automated driver generation since linux-enable-ir-emitter v3 - fprintf(stderr, "Ioctl error code: %d, errno: %d\n", result, errno); - switch (errno) { - case ENOENT: - fprintf(stderr, "The device does not support the given control or the specified extension unit could not be found.\n"); - break; - case ENOBUFS: - fprintf(stderr, "The specified buffer size is incorrect (too big or too small).\n"); - break; - case EINVAL: - fprintf(stderr, "An invalid request code was passed.\n"); - break; - case EBADRQC: - fprintf(stderr, "The given request is not supported by the given control.\n"); - break; - case EFAULT: - fprintf(stderr, "The data pointer references an inaccessible memory area.\n"); - break; - case EILSEQ: - fprintf(stderr, "Illegal byte sequence.\n"); - break; - }*/ - return 1; - } - return 0; -} - -/** - * @brief Change the current uvc control value for the camera device - * - * @param unit extension unit ID - * @param selector control selector - * @param controlSize size of the uvc control - * @param control control value - * - * @return 1 if error, otherwise 0 - **/ -int Camera::setUvcQuery(uint8_t unit, uint8_t selector, uint16_t controlSize, uint8_t *control) noexcept /* NOLINT(readability-non-const-parameter) */ -{ - const struct uvc_xu_control_query query = { - unit, - selector, - UVC_SET_CUR, - controlSize, - control, - }; - - return executeUvcQuery(&query); -} - -/** - * @brief Get the current, maximale, resolution or minimale value of the uvc control for the camera device - * - * @param query_type UVC_GET_MAX, UVC_GET_RES, UVC_GET_CUR or UVC_GET_MIN - * @param unit extension unit ID - * @param selector control selector - * @param controlSize size of the uvc control - * @param control control value - * - * @return 1 if error, otherwise 0 - **/ -int Camera::getUvcQuery(uint8_t query_type, uint8_t unit, uint8_t selector, uint16_t controlSize, uint8_t *control) noexcept /* NOLINT(readability-non-const-parameter) */ -{ - const struct uvc_xu_control_query query = { - unit, - selector, - query_type, - controlSize, - control, - }; - - return executeUvcQuery(&query); -} - -/** - * @brief Get the size of the uvc control for the indicated device. - * - * @param unit extension unit ID - * @param selector control selector - * - * @return size of the control, 0 if error - **/ -uint16_t Camera::lenUvcQuery(uint8_t unit, uint8_t selector) noexcept -{ - uint8_t len[2] = {0x00, 0x00}; - const struct uvc_xu_control_query query = { - unit, - selector, - UVC_GET_LEN, - 2, - len, - }; - - if (executeUvcQuery(&query)) - return 0; - - return (uint16_t)(len[0] + len[1] * 16); // UVC_GET_LEN is in little-endian -} - -/** - * @brief Print the control value in the debug log - * - * @param prefixMasg what show before the control - * @param control control value - * @param len size of the control value - */ -void CameraInstruction::logDebugCtrl(string prefixMsg, const uint8_t *control, uint16_t len) noexcept -{ - for (uint16_t i = 0; i < len; ++i) - prefixMsg += " " + to_string((int)control[i]); - Logger::debug(prefixMsg.c_str()); -} - -/** - * @brief Compute the resolution control instruction composed of 0 or 1 - * by comparing two controls instruction - * we assume isReachable(first, res, second, size) is true - * - * @param first the first instruction - * @param second the second instruction - * @param res the resolution instruction will be stored in it - * @param size of the instructions - */ -void CameraInstruction::computeResCtrl(const uint8_t *first, const uint8_t *second, uint8_t *res, uint16_t size) noexcept -{ - int secondGcd = array_gcd(second, size); - - if (secondGcd > 1) - for (unsigned i = 0; i < size; ++i) - res[i] = (second[i] - first[i]) / secondGcd; - else - for (unsigned i = 0; i < size; ++i) - res[i] = (uint8_t)second[i] != first[i]; -} - -/** - * @brief Check if a resolution control allow to reach a control from another - * - * @param base instruction from which the resolution must be added - * @param res the resolution control - * @param toReach the instruction to reach - * @param size of the instructions - * @return true if reacheable, otherwise false - */ -bool CameraInstruction::isReachable(const uint8_t *base, const uint8_t *res, const uint8_t *toReach, uint16_t size) noexcept -{ - int it = 256; - for (unsigned i = 0; i < size; ++i) - { - if (res[i] != 0) - { - int newit = (toReach[i] - base[i]) / res[i]; // # iterations required for that value - if (newit < 0 || (newit != it && it != 256)) // negative iteration or not all value have the same # iterations - return false; - it = newit; - } - } - - return it != 256; -} - -/** - * @brief Construct a new CameraInstruction object - * And find the first control instruction as current one - * - * @param camera on which find the control instruction - * @param unit of the instruction - * @param selector of the instruction - * - * @throw CameraInstructionException if unit + selector are invalid or if the instruction cannot be modfied - */ -CameraInstruction::CameraInstruction(Camera &camera, uint8_t unit, uint8_t selector) - : unit(unit), selector(selector) -{ - // get the control instruction lenght - ctrlSize = camera.lenUvcQuery(unit, selector); - if (ctrlSize == 0) - throw CameraInstructionException(camera.device, unit, selector); - - // get the current control value - curCtrl = new uint8_t[ctrlSize]; - if (camera.getUvcQuery(UVC_GET_CUR, unit, selector, ctrlSize, curCtrl) == 1) - throw CameraInstructionException(camera.device, unit, selector); - - // ensure the control can be modified - if (camera.setUvcQuery(unit, selector, ctrlSize, curCtrl) == 1) - throw CameraInstructionException(camera.device, unit, selector); - - // try to get the maximum control value (it does not necessary exists) - maxCtrl = new uint8_t[ctrlSize]; - if (camera.getUvcQuery(UVC_GET_MAX, unit, selector, ctrlSize, maxCtrl) == 1) - throw CameraInstructionException(camera.device, unit, selector); - - // try get the minimum control value (it does not necessary exists) - minCtrl = new uint8_t[ctrlSize]; - if (camera.getUvcQuery(UVC_GET_MIN, unit, selector, ctrlSize, minCtrl) == 1) - { - delete[] minCtrl; - minCtrl = nullptr; - } - - // try to get the resolution control value (it does not necessary exists) - // and check if it is consistent - resCtrl = new uint8_t[ctrlSize]; - if (camera.getUvcQuery(UVC_GET_RES, unit, selector, ctrlSize, resCtrl) == 1 || - !isReachable(curCtrl, resCtrl, maxCtrl, ctrlSize)) - { - Logger::debug("Computing the resolution control."); - - if (minCtrl != nullptr) - { - computeResCtrl(minCtrl, curCtrl, resCtrl, ctrlSize); - if (!isReachable(minCtrl, resCtrl, curCtrl, ctrlSize)) - { - Logger::debug("Minimum not consistent, it will be ignored."); - delete[] minCtrl; - minCtrl = nullptr; - computeResCtrl(curCtrl, maxCtrl, resCtrl, ctrlSize); - } - } - else - computeResCtrl(curCtrl, maxCtrl, resCtrl, ctrlSize); - } - - logDebugCtrl("current:", curCtrl, ctrlSize); - logDebugCtrl("maximum:", maxCtrl, ctrlSize); - if (minCtrl != nullptr) - logDebugCtrl("minimum:", minCtrl, ctrlSize); - logDebugCtrl("resolution:", resCtrl, ctrlSize); - Logger::debug(("unit: " + to_string((int)unit) + " selector: " + to_string((int)selector)).c_str()); -} - -/** - * @brief Construct a new Camera Instruction object - * Cannot compute next control instruction, and do not check if the instruction is valid - * - * @param unit of the instruction - * @param selector of the instruction - * @param control instruction - */ -CameraInstruction::CameraInstruction(uint8_t unit, uint8_t selector, uint8_t *control, uint16_t size) - : unit(unit), selector(selector), ctrlSize(size), curCtrl(new uint8_t[size]) -{ - memcpy(curCtrl, control, size * sizeof(uint8_t)); -} - -CameraInstruction::~CameraInstruction() -{ - delete[] curCtrl; - delete[] maxCtrl; - delete[] minCtrl; - delete[] resCtrl; -} - -CameraInstruction &CameraInstruction::operator=(const CameraInstruction &other) -{ - unit = other.unit; - selector = other.selector; - ctrlSize = other.ctrlSize; - curCtrl = new uint8_t[ctrlSize]; - memcpy(curCtrl, other.curCtrl, ctrlSize * sizeof(uint8_t)); - if (other.maxCtrl != nullptr) - { - maxCtrl = new uint8_t[ctrlSize]; - memcpy(maxCtrl, other.maxCtrl, ctrlSize * sizeof(uint8_t)); - } - else - maxCtrl = nullptr; - if (other.resCtrl != nullptr) - { - resCtrl = new uint8_t[ctrlSize]; - memcpy(resCtrl, other.resCtrl, ctrlSize * sizeof(uint8_t)); - } - else - resCtrl = nullptr; - if (other.minCtrl != nullptr) - { - minCtrl = new uint8_t[ctrlSize]; - memcpy(minCtrl, other.minCtrl, ctrlSize * sizeof(uint8_t)); - } - else - minCtrl = nullptr; - return *this; -} - -CameraInstruction::CameraInstruction(const CameraInstruction &other) -{ - operator=(other); -} - -/** - * @brief Compute the next possible control value - * @return true - */ -bool CameraInstruction::next() -{ - if (!hasNext()) - throw range_error("CRITICAL: Maximal instruction already reached."); - - for (unsigned i = 0; i < ctrlSize; ++i) - { - int nextCtrl = curCtrl[i] + resCtrl[i]; // int to avoid overflow - curCtrl[i] = (uint8_t)nextCtrl; - if (nextCtrl > maxCtrl[i]) // not allow to exceed maxCtrl - { - memcpy(curCtrl, maxCtrl, ctrlSize * sizeof(uint8_t)); - break; - } - } - - logDebugCtrl("new current:", curCtrl, ctrlSize); - return true; -} - -/** - * @brief Check if a next control value can be computed - * - * @return true yes, otherwise false - */ -bool CameraInstruction::hasNext() const noexcept -{ - return maxCtrl != nullptr && memcmp(curCtrl, maxCtrl, ctrlSize * sizeof(uint8_t)) != 0; -} - -/** - * @brief Get the current control value - * - * @return const uint8_t const* current control value - */ -const uint8_t *CameraInstruction::getCurrent() const noexcept -{ - return curCtrl; -} - -/** - * @brief Get the size of the current control - * - * @return size of the current control - */ -uint16_t CameraInstruction::getSize() const noexcept -{ - return ctrlSize; -} - -/** - * @brief Get the unit of the instruction - * - * @return unit - */ -uint8_t CameraInstruction::getUnit() const noexcept -{ - return unit; -} - -/** - * @brief Get the selector of the instruction - * - * @return selector - */ -uint8_t CameraInstruction::getSelector() const noexcept -{ - return selector; -} - -/** - * @brief If a minimun control instruction exists - * and is not already the current, - * set the current control instruction with that value - * - * @return true if success, otherwise false - */ -bool CameraInstruction::trySetMinAsCur() noexcept -{ - if (minCtrl == nullptr || memcmp(curCtrl, minCtrl, ctrlSize * sizeof(uint8_t)) == 0) - return false; - - memcpy(curCtrl, minCtrl, ctrlSize * sizeof(uint8_t)); - logDebugCtrl("new current:", curCtrl, ctrlSize); - - return true; -} - -CameraException::CameraException(string device) : message("CRITICAL: Cannot access to " + device) {} - -const char *CameraException::what() const noexcept -{ - return message.c_str(); -} - -CameraInstructionException::CameraInstructionException(string device, uint8_t unit, uint8_t selector) - : message("ERROR: Impossible to obtain the instruction on " + device + " for unit: " + to_string((int)unit) + " selector:" + to_string((int)selector)) {} - -const char *CameraInstructionException::what() const noexcept -{ - return message.c_str(); -} diff --git a/sources/driver/camera.hpp b/sources/driver/camera.hpp deleted file mode 100644 index a5da58ba..00000000 --- a/sources/driver/camera.hpp +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef CAMERA_HPP -#define CAMERA_HPP - -#include -#include -#include -#include -using namespace std; - -class CameraInstruction; -class Camera -{ -private: - int id; - int fd = -1; - - static int deviceId(const char *device); - - void openFd(); - - void closeFd() noexcept; - -public: - string device; - - Camera(string device); - - ~Camera(); - - Camera &operator=(const Camera &) = delete; - - Camera(const Camera &) = delete; - - bool apply(CameraInstruction instruction) noexcept; - - bool isEmitterWorking(); - - int executeUvcQuery(const uvc_xu_control_query *query) noexcept; - - int setUvcQuery(uint8_t unit, uint8_t selector, uint16_t controlSize, uint8_t *control) noexcept; - - int getUvcQuery(uint8_t query_type, uint8_t unit, uint8_t selector, uint16_t controlSize, uint8_t *control) noexcept; - - uint16_t lenUvcQuery(uint8_t unit, uint8_t selector) noexcept; -}; - -class CameraInstruction -{ -private: - uint8_t unit; - uint8_t selector; - uint16_t ctrlSize; - uint8_t *curCtrl = nullptr; - uint8_t *maxCtrl = nullptr; - uint8_t *minCtrl = nullptr; - uint8_t *resCtrl = nullptr; - - static void logDebugCtrl(string prefixMsg, const uint8_t *control, uint16_t len) noexcept; - - static void computeResCtrl(const uint8_t *first, const uint8_t *second, uint8_t *res, uint16_t size) noexcept; - - static bool isReachable(const uint8_t *base, const uint8_t *res, const uint8_t *toReach, uint16_t size) noexcept; - -public: - CameraInstruction(Camera &camera, uint8_t unit, uint8_t selector); - - CameraInstruction(uint8_t unit, uint8_t selector, uint8_t *control, uint16_t size); - - ~CameraInstruction(); - - CameraInstruction &operator=(const CameraInstruction &); - CameraInstruction(const CameraInstruction &); - - bool next(); - - bool hasNext() const noexcept; - - const uint8_t *getCurrent() const noexcept; - - uint16_t getSize() const noexcept; - - uint8_t getUnit() const noexcept; - - uint8_t getSelector() const noexcept; - - bool trySetMinAsCur() noexcept; -}; - -class CameraException : public exception -{ -private: - string message; - -public: - explicit CameraException(string device); - - const char *what() const noexcept override; -}; - -class CameraInstructionException : public exception -{ -private: - string message; - -public: - explicit CameraInstructionException(string device, uint8_t unit, uint8_t selector); - - const char *what() const noexcept override; -}; - -#endif \ No newline at end of file diff --git a/sources/driver/driver.cpp b/sources/driver/driver.cpp index 71cbd31e..f29e98b6 100644 --- a/sources/driver/driver.cpp +++ b/sources/driver/driver.cpp @@ -3,21 +3,13 @@ #include #include #include -#include +#include +#include +#include using namespace std; - -Driver::Driver(string device, uint8_t unit, uint8_t selector, uint16_t size, const uint8_t *control) - : device(device), unit(unit), selector(selector), size(size), control(new uint8_t[size]) -{ - memcpy(this->control, control, size * sizeof(uint8_t)); -} - -Driver::~Driver() -{ - delete[] control; -} - +Driver::Driver(const string &device, uint8_t unit, uint8_t selector, const vector &control) + : device(device), unit(unit), selector(selector), control(control) {} /** * @brief Write the driver in a file @@ -27,20 +19,17 @@ Driver::~Driver() * * @throw ifstream::failure Impossible to write the driver in driverFile */ -void writeDriver(string driverFile, const Driver *driver) +void Driver::writeDriver(const string &driverFile, const unique_ptr &driver) { ofstream file(driverFile); if (!file.is_open()) throw ifstream::failure("CRITICAL: Impossible to write the driver in " + driverFile); file << "device=" << driver->device << endl; - file << "unit=" << (int)driver->unit << endl; - file << "selector=" << (int)driver->selector << endl; - file << "size=" << (int)driver->size << endl; - for (unsigned i = 0; i < driver->size; ++i) - file << "control" << i << "=" << (int)driver->control[i] << endl; - - file.close(); + file << "unit=" << static_cast(driver->unit) << endl; + file << "selector=" << static_cast(driver->selector) << endl; + for (unsigned i = 0; i < driver->control.size(); ++i) + file << "control" << i << "=" << static_cast(driver->control[i]) << endl; } /** @@ -49,35 +38,49 @@ void writeDriver(string driverFile, const Driver *driver) * @param driverFile path where the driver is store * * @throw ifstream::failure Impossible to open the driver at driverFile - * + * @return the driver */ -Driver *readDriver(string driverFile) +unique_ptr Driver::readDriver(const string &driverFile) { - FILE *file = fopen(driverFile.c_str(), "r"); - if (!file) - throw ifstream::failure("CRITICAL: Impossible to open the driver at " + string(driverFile)); + ifstream file(driverFile); + if (!file.is_open()) + throw ifstream::failure("CRITICAL: Impossible to open the driver at " + driverFile); + string line; char device[128]; - uint8_t unit, selector; - uint16_t size; - int count = 0; - count += fscanf(file, " device=%s*", device); - count += fscanf(file, " unit=%hhu", &unit); - count += fscanf(file, " selector=%hhu", &selector); - count += fscanf(file, " size=%hu", &size); - uint8_t *control = new uint8_t[size]; - for (unsigned i = 0; i < size; ++i) + uint8_t unit; + uint8_t selector; + uint8_t controli; + vector control; + int res = 0; + + getline(file, line); + res = sscanf(line.c_str(), "device=%s", device); + if (res == 0) + throw runtime_error("CRITICAL: device is missing in the driver at " + driverFile); + + getline(file, line); + res = sscanf(line.c_str(), "unit=%hhu", &unit); + if (res == 0) + throw runtime_error("CRITICAL: unit is missing in the driver at " + driverFile); + + getline(file, line); + res = sscanf(line.c_str(), "selector=%hhu", &selector); + if (res == 0) + throw runtime_error("CRITICAL: selector is missing in the driver at " + driverFile); + + res = 1; + for (unsigned i = 0; res == 1; ++i) { - string key = " control" + to_string(i) + "=%d"; - count += fscanf(file, key.c_str(), &control[i]); + getline(file, line); + string key = " control" + to_string(i) + "=%hhu"; + res = sscanf(line.c_str(), key.c_str(), &controli); + if (res == 1) + control.push_back(controli); } + if (control.empty()) + throw runtime_error("CRITICAL: control is missing in the driver at " + driverFile); - if (count != 4 + size) - throw runtime_error("CRITICAL: The driver at " + string(driverFile) + " is corrupted"); - - fclose(file); - Driver *driver = new Driver(device, unit, selector, size, control); - delete[] control; - return driver; + return make_unique(device, unit, selector, control); } diff --git a/sources/driver/driver.hpp b/sources/driver/driver.hpp index 6b1906dc..0249ef03 100644 --- a/sources/driver/driver.hpp +++ b/sources/driver/driver.hpp @@ -2,29 +2,38 @@ #define DRIVER_HPP #include +#include #include +#include using namespace std; class Driver { public: - string device; - uint8_t unit; - uint8_t selector; - uint16_t size; - uint8_t *control; + const string device; + const uint8_t unit; + const uint8_t selector; + const vector control; - Driver(string device, uint8_t unit, uint8_t selector, uint16_t size, const uint8_t *control); + Driver() = delete; - ~Driver(); + explicit Driver(const string &device, uint8_t unit, uint8_t selector, const vector &control); + + ~Driver() = default; Driver &operator=(const Driver &) = delete; + Driver(const Driver &) = delete; + Driver &operator=(Driver &&other) = delete; + + Driver(Driver && other) = delete; + + static void writeDriver(const string &driverFile, const unique_ptr &driver); + + static unique_ptr readDriver(const string &driverFile); }; -void writeDriver(string driverFile, const Driver *driver); -Driver *readDriver(string driverFile); #endif diff --git a/sources/driver/execute-driver.cpp b/sources/driver/execute-driver.cpp index b455c30e..bf5a656e 100644 --- a/sources/driver/execute-driver.cpp +++ b/sources/driver/execute-driver.cpp @@ -1,8 +1,10 @@ +#include #include using namespace std; #include "driver.hpp" -#include "camera.hpp" +#include "../camera/camera.hpp" +#include "../camera/camerainstruction.hpp" #define EXIT_FD_ERROR 126; @@ -20,12 +22,12 @@ using namespace std; */ int main(int, const char **argv) { - const Driver *driver = readDriver(argv[1]); + const unique_ptr driver = Driver::readDriver(argv[1]); bool result; try { Camera camera(driver->device); - CameraInstruction instruction = CameraInstruction(driver->unit, driver->selector, driver->control, driver->size); + CameraInstruction instruction = CameraInstruction(driver->unit, driver->selector, driver->control); result = camera.apply(instruction); } catch (CameraException &e) @@ -34,6 +36,5 @@ int main(int, const char **argv) return EXIT_FD_ERROR; } - delete driver; return result ? EXIT_SUCCESS : EXIT_FAILURE; -} \ No newline at end of file +} diff --git a/sources/driver/finder.cpp b/sources/driver/finder.cpp index 6529ff90..b482b7ff 100644 --- a/sources/driver/finder.cpp +++ b/sources/driver/finder.cpp @@ -1,80 +1,27 @@ #include "finder.hpp" -#include -#include -#include #include -#include -#include +#include #include -#include +#include +#include using namespace std; -#include "camera.hpp" -#include "logger.hpp" +#include "../camera/camera.hpp" +#include "../camera/camerainstruction.hpp" #include "driver.hpp" - -/** - * @brief Execute shell command and return the ouput - * - * @param cmd command - * @return output - */ -string *Finder::shellExec(string cmd) noexcept -{ - char buffer[128]; - string *result = new string(); - unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); - if (!pipe) - { - *result = "error"; - return result; - } - - while (fgets(buffer, 128 * sizeof(char), pipe.get()) != nullptr) - *result += buffer; - result->erase(result->end() - 1); // remove last \n - return result; -} - -/** - * @brief Get all the extension unit ID of the camera device - * - * @param Camera the camera device - * - * @return list of units - */ -vector *Finder::getUnits(const Camera &camera) noexcept -{ - const string *vid = Finder::shellExec("udevadm info " + string(camera.device) + " | grep -oP 'E: ID_VENDOR_ID=\\K.*'"); - const string *pid = Finder::shellExec("udevadm info " + string(camera.device) + " | grep -oP 'E: ID_MODEL_ID=\\K.*'"); - const string *units = Finder::shellExec("lsusb -d" + *vid + ":" + *pid + " -v | grep bUnitID | grep -Eo '[0-9]+'"); - auto *unitsList = new vector; - - unsigned i = 0, j = 0; - for (; j < units->length(); ++j) - if (units->at(j) == '\n') - { - unitsList->push_back((uint8_t)stoi(units->substr(i, j - i))); - i = j + 1; - } - unitsList->push_back((uint8_t)stoi(units->substr(i, j - i))); - - delete vid; - delete pid; - delete units; - return unitsList; -} +#include "../utils/logger.hpp" /** * @brief Create a Driver from Instruction object * * @param instruction from which the driver has to be create + * * @return the driver */ -Driver *Finder::createDriverFromInstruction(const CameraInstruction &instruction, uint8_t unit, uint8_t selector) const noexcept +unique_ptr Finder::createDriverFromInstruction(const CameraInstruction &instruction, uint8_t unit, uint8_t selector) const noexcept { - return new Driver(camera.device, unit, selector, instruction.getSize(), instruction.getCurrent()); + return make_unique(camera.device, unit, selector, instruction.getCurrent()); } /** @@ -82,19 +29,20 @@ Driver *Finder::createDriverFromInstruction(const CameraInstruction &instruction * * @return exclude list */ -vector> *Finder::getExcluded() noexcept +unique_ptr>> Finder::getExcluded() noexcept { - auto *excludedList = new vector>; + auto excludedList = make_unique>>(); ifstream file(excludedPath); if (!file.is_open()) return excludedList; - while (file) + while (!file.eof()) { string line; getline(file, line); - uint8_t unit, selector; + uint8_t unit; + uint8_t selector; sscanf(line.c_str(), "%hhu %hhu", &unit, &selector); excludedList->push_back(pair(unit, selector)); } @@ -108,6 +56,7 @@ vector> *Finder::getExcluded() noexcept * * @param unit to check * @param selector to select + * * @return true if they are excluded, otherwise false */ bool Finder::isExcluded(uint8_t unit, uint8_t selector) const noexcept @@ -129,7 +78,7 @@ void Finder::addToExclusion(uint8_t unit, uint8_t selector) noexcept ofstream file(excludedPath, std::ofstream::out | std::ofstream::app); if (!file.is_open()) return; - file << (int)unit << " " << (int)selector << endl; + file << unit << " " << selector << endl; file.close(); } @@ -141,71 +90,61 @@ void Finder::addToExclusion(uint8_t unit, uint8_t selector) noexcept * @param negAnswerLimit skip a patern after negAnswerLimit negative answer * @param excludedPath path where write unit and selector to exclude from the search */ -Finder::Finder(Camera &camera, unsigned emitters, unsigned negAnswerLimit, string excludedPath) noexcept - : camera(camera), units(Finder::getUnits(camera)), emitters(emitters), negAnswerLimit(negAnswerLimit), excludedPath(excludedPath), excluded(nullptr) +Finder::Finder(Camera &camera, unsigned emitters, unsigned negAnswerLimit, const string &excludedPath) + : camera(camera), emitters(emitters), negAnswerLimit(negAnswerLimit), excludedPath(excludedPath) { - string unitsStr = "Extension units: "; - for (auto it = units->begin(); it != units->end(); ++it) - unitsStr += to_string(*it) + " "; - Logger::debug(unitsStr.c_str()); + for (unsigned unit = 0; unit < 256; ++unit) + units.push_back(static_cast(unit)); excluded = getExcluded(); -}; - -Finder::~Finder() -{ - delete units; - delete excluded; } /** - * @brief Find a driver which enable the ir emitter + * @brief Find a driver which enable the ir emitter(s) * - * @return the driver if success otherwise nullptr + * @return a vector containing the driver(s), + * empty if the configuration failed */ -Driver **Finder::find() +unique_ptr>> Finder::find() { - Driver **drivers = new Driver *[emitters]; - unsigned configuredEmitters = 0; + auto drivers = make_unique>>(); - for (const uint8_t unit : *units) - for (int __selector = 0; __selector < 256; ++__selector) + for (const uint8_t unit : units) + for (unsigned __selector = 0; __selector < 256; ++__selector) { - const uint8_t selector = (uint8_t)__selector; // safe: 0 <= __selector <= 255 + const uint8_t selector = static_cast(__selector); // safe: 0 <= __selector <= 255 if (isExcluded(unit, selector)) continue; try { CameraInstruction instruction(camera, unit, selector); - CameraInstruction initInstruction = instruction; // copy for reset later + const CameraInstruction initInstruction = instruction; // copy for reset later - if (!instruction.trySetMinAsCur()) // if no min instruction exists - { - if (instruction.hasNext()) // start from the next one - instruction.next(); - else - continue; - } + if (!instruction.setMinAsCur()) // if no min instruction exists + if (!instruction.next()) // start from the next one + continue; // if no next, skip unsigned negAnswerCounter = 0; do { - if (camera.apply(instruction)) + if (camera.apply(instruction) && camera.isEmitterWorking()) { - if (camera.isEmitterWorking()) - { - drivers[configuredEmitters++] = createDriverFromInstruction(instruction, unit, selector); - if (configuredEmitters == emitters) // all emitters are configured - return drivers; - } - else - camera.apply(initInstruction); // reset + drivers->push_back(createDriverFromInstruction(instruction, unit, selector)); + if (drivers->size() == emitters) // all emitters are configured + return drivers; } ++negAnswerCounter; - } while (negAnswerCounter < negAnswerLimit && instruction.hasNext() && instruction.next()); + + if (negAnswerCounter == negAnswerLimit - 1) + { + instruction.setMaxAsCur(); + continue; + } + + } while (negAnswerCounter < negAnswerLimit && instruction.next()); - if (negAnswerCounter >= negAnswerLimit) - Logger::debug("Negative answer limit exceeded, skipping the pattern."); + camera.apply(initInstruction); + Logger::debug(""); } catch (CameraInstructionException &e) { @@ -220,6 +159,5 @@ Driver **Finder::find() } } - delete[] drivers; - return nullptr; + return drivers; } diff --git a/sources/driver/finder.hpp b/sources/driver/finder.hpp index fe69a85e..4fbb6e7d 100644 --- a/sources/driver/finder.hpp +++ b/sources/driver/finder.hpp @@ -1,47 +1,50 @@ #ifndef FINDER_HPP #define FINDER_HPP -#include #include +#include #include +#include using namespace std; -#include "camera.hpp" +#include "../camera/camera.hpp" #include "driver.hpp" - class Finder { private: Camera &camera; - const vector *units; const unsigned emitters; const unsigned negAnswerLimit; const string excludedPath; - const vector> *excluded; - - static string *shellExec(string cmd) noexcept; - - static vector *getUnits(const Camera &camera) noexcept; + unique_ptr>> excluded = nullptr; + vector units; - Driver* createDriverFromInstruction(const CameraInstruction& instruction, uint8_t unit, uint8_t selector) const noexcept; +protected: + unique_ptr createDriverFromInstruction(const CameraInstruction &instruction, uint8_t unit, uint8_t selector) const noexcept; - vector> *getExcluded() noexcept; + unique_ptr>> getExcluded() noexcept; bool isExcluded(uint8_t unit, uint8_t selector) const noexcept; void addToExclusion(uint8_t unit, uint8_t selector) noexcept; - + public: - Finder(Camera &camera, unsigned emitters, unsigned negAnswerLimit, string excludedPath) noexcept; + Finder() = delete; + + explicit Finder(Camera &camera, unsigned emitters, unsigned negAnswerLimit, const string &excludedPath); - ~Finder(); + ~Finder() = default; Finder &operator=(const Finder &) = delete; Finder(const Finder &) = delete; - Driver **find(); + Finder &operator=(Finder &&other) = delete; + + Finder(Finder &&other) = delete; + + unique_ptr>> find(); }; #endif \ No newline at end of file diff --git a/sources/driver/driver-generator.cpp b/sources/driver/generate-driver.cpp similarity index 51% rename from sources/driver/driver-generator.cpp rename to sources/driver/generate-driver.cpp index 04681df2..ed6ea13c 100644 --- a/sources/driver/driver-generator.cpp +++ b/sources/driver/generate-driver.cpp @@ -1,74 +1,72 @@ -/*** DOCUMENTATION -- https://www.kernel.org/doc/html/latest/userspace-api/media/drivers/uvcvideo.html - info 1: uvc queries are explained - info 2: units can be found by parsing the uvc descriptor -- https://www.mail-archive.com/search?l=linux-uvc-devel@lists.berlios.de&q=subject:%22Re%5C%3A+%5C%5BLinux%5C-uvc%5C-devel%5C%5D+UVC%22&o=newest&f=1 - info 1: selector is on 8 bits and since the manufacturer does not provide a driver, it is impossible to know which value it is. -***/ - -#include #include +#include +#include using namespace std; -#include "camera.hpp" +#include "../camera/camera.hpp" +#include "../camera/autocamera.hpp" #include "finder.hpp" #include "driver.hpp" -#include "logger.hpp" +#include "../utils/logger.hpp" -#define EXIT_FD_ERROR 126 +constexpr unsigned EXIT_FD_ERROR = 126; /** * Generate a driver for the infrared emitter * - * usage: driver-generator [device] [emitters] [negAnswerLimit] [workspace] [debug] + * usage: generate-driver [device] [emitters] [negAnswerLimit] [workspace] [debug] [manual] * device path to the infrared camera * emitters number of emitters on the device - * negAnswerLimit the number of negative answer before the pattern is skiped. Use 256 for unlimited + * negAnswerLimit the number of negative answer before the pattern is skiped. Use -1 for unlimited * workspace directory where store the driver * debug 1 for print debug information, otherwise 0 + * manual 1 for manual configuration, 0 for automatic * * Exit code: 0 Success * 1 Error * 126 Unable to open the camera device */ int main(int, const char *argv[]) -{ +{ const string device = argv[1]; const string deviceName = device.substr(device.find_last_of("/") + 1); - const unsigned emitters = (unsigned) atoi(argv[2]); - const unsigned negAnswerLimit = (unsigned) atoi(argv[3]); + const unsigned emitters = static_cast(atoi(argv[2])); + const unsigned negAnswerLimit = static_cast(atoi(argv[3])); const string workspace = string(argv[4]) + "/"; const string excludedPath = workspace + deviceName + ".excluded"; - if (atoi(argv[5]) == 1) Logger::enableDebug(); + shared_ptr camera; + if (atoi(argv[6]) == 1) + camera = make_shared(device); + else + camera = make_shared(device); + Finder finder(*camera, emitters, negAnswerLimit, excludedPath); try { - Camera camera(device); - if (camera.isEmitterWorking()) + if (camera->Camera::isEmitterWorking()) { Logger::error("Your emiter is already working, skipping the configuration."); return EXIT_FAILURE; } - - Finder finder(camera, emitters, negAnswerLimit, excludedPath); - Driver **driver = finder.find(); - if (driver == nullptr) + + auto drivers = finder.find(); + if (drivers->empty()) return EXIT_FAILURE; - for (unsigned i = 0; i < emitters; ++i) + for (unsigned i = 0; i < drivers->size(); ++i) { - string driverPath = workspace + deviceName + "_emitter" + to_string(i) + ".driver"; ; - writeDriver(driverPath, driver[i]); - delete driver[i]; + string driverPath = workspace + deviceName + "_emitter" + to_string(i) + ".driver"; + auto &driver = drivers->at(i); + Driver::writeDriver(driverPath, driver); } - delete[] driver; } catch (CameraException &e) { cerr << e.what() << endl; return EXIT_FD_ERROR; } + return EXIT_SUCCESS; } diff --git a/sources/driver/logger.hpp b/sources/driver/logger.hpp deleted file mode 100644 index 09e8e852..00000000 --- a/sources/driver/logger.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef LOGGER_HPP -#define LOGGER_HPP - -class Logger -{ -private: - static bool isDebugEnabled; - -public: - Logger(Logger &other) = delete; - void operator=(const Logger &) = delete; - Logger() = delete; - - static void enableDebug(); - - static void debug(const char *text); - - static void info(const char *text); - - static void error(const char *text); - - static void critical(const char *text); -}; - -#endif \ No newline at end of file diff --git a/sources/globals.py b/sources/globals.py index 2e9b8339..8a192672 100644 --- a/sources/globals.py +++ b/sources/globals.py @@ -17,7 +17,8 @@ SAVE_DRIVER_FOLDER_PATH = "@SAVE_DRIVER_FOLDER_PATH@" BIN_EXECUTE_DRIVER_PATH = "@BIN_EXECUTE_DRIVER_PATH@" -BIN_DRIVER_GENERATOR_PATH = "@BIN_DRIVER_GENERATOR_PATH@" +BIN_GENERATE_DRIVER_PATH = "@BIN_GENERATE_DRIVER_PATH@" +BIN_IS_CAMERA_PATH = "@BIN_IS_CAMERA_PATH@" UDEV_RULE_PATH = "@UDEV_RULE_PATH@" BOOT_SERVICE_NAME = "@BOOT_SERVICE_NAME@" diff --git a/sources/linux-enable-ir-emitter.py b/sources/linux-enable-ir-emitter.py index b120a1d0..e3bff812 100755 --- a/sources/linux-enable-ir-emitter.py +++ b/sources/linux-enable-ir-emitter.py @@ -8,12 +8,13 @@ from globals import ExitCode, check_root if __name__ == "__main__": - logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO) + logging.basicConfig( + format="%(levelname)s: %(message)s", level=logging.INFO) parser = argparse.ArgumentParser( description="Provides support for infrared cameras.", prog="linux-enable-ir-emitter", - epilog="For support visit https://github.com/EmixamPP/linux-enable-ir-emitter/wiki", + epilog="https://github.com/EmixamPP/linux-enable-ir-emitter", formatter_class=argparse.RawTextHelpFormatter, ) parser.add_argument( @@ -34,7 +35,7 @@ "-d", "--device", metavar="device", - help="specify the infrared camera, by default is '/dev/video2'", + help="specify the infrared camera, automatic detection by default", nargs=1, ) command_subparser = parser.add_subparsers(dest="command") @@ -46,6 +47,13 @@ "configure", help="generate ir emitter driver", ) + command_configure.add_argument( + "-m", + "--manual", + help="activate manual configuration", + action="store_true", + default=False, + ) command_configure.add_argument( "-e", "--emitters", @@ -59,15 +67,11 @@ "-l", "--limit", metavar="", - help="the number of negative answer before the pattern is skiped, by default is 5. Use 256 for unlimited", + help="the number of negative answer before the pattern is skipped, by default is 5. Use -1 for unlimited", default=[5], type=int, nargs=1, ) - command_delete = command_subparser.add_parser( - "delete", - help="delete drivers", - ) command_boot = command_subparser.add_parser( "boot", help="enable ir at boot", @@ -77,6 +81,10 @@ choices=["enable", "disable", "status"], help="specify the boot action to perform", ) + command_delete = command_subparser.add_parser( + "delete", + help="delete drivers", + ) args = parser.parse_args() @@ -84,13 +92,9 @@ logging.getLogger().setLevel(logging.DEBUG) device: str | None = None - # Determine the device if needed - # In case of configuration: use the specified device otherwise the default one (i.e. /dev/video2) - # In case of run or delete: use the specified device - if args.command == "configure" or ( - args.device and args.command in ("run", "delete") - ): - device = args.device[0] if args.device else "/dev/video2" + # Determine the device path if needed + if args.device and args.command in ("configure", "run", "delete"): + device = args.device[0] # Find the v4l path v4l_device = subprocess.run( f"find -L /dev/v4l/by-path -samefile {device}", @@ -111,8 +115,7 @@ elif args.command == "configure": check_root() - assert(device is not None) - configure(device, args.emitters[0], args.limit[0]) + configure(device, args.manual, args.emitters[0], args.limit[0]) elif args.command == "boot": check_root() diff --git a/sources/driver/logger.cpp b/sources/utils/logger.cpp similarity index 68% rename from sources/driver/logger.cpp rename to sources/utils/logger.cpp index 2dc90bdd..80a0828f 100644 --- a/sources/driver/logger.cpp +++ b/sources/utils/logger.cpp @@ -1,8 +1,6 @@ #include "logger.hpp" #include -#include -#include #include using namespace std; @@ -13,23 +11,23 @@ void Logger::enableDebug() Logger::isDebugEnabled = true; } -void Logger::debug(const char *text) +void Logger::debug(const string &text) { if (Logger::isDebugEnabled) cout << "DEBUG: " << text << endl; } -void Logger::info(const char *text) +void Logger::info(const string &text) { cout << "INFO: " << text << endl; } -void Logger::error(const char *text) +void Logger::error(const string &text) { cerr << "ERROR: " << text << endl; } -void Logger::critical(const char *text) +void Logger::critical(const string &text) { cerr << "CRITICAL: " << text << endl; } \ No newline at end of file diff --git a/sources/utils/logger.hpp b/sources/utils/logger.hpp new file mode 100644 index 00000000..253196c1 --- /dev/null +++ b/sources/utils/logger.hpp @@ -0,0 +1,36 @@ +#ifndef LOGGER_HPP +#define LOGGER_HPP + +#include +using namespace std; + +class Logger +{ +private: + static bool isDebugEnabled; + +public: + Logger() = delete; + + ~Logger() = default; + + Logger(Logger &other) = delete; + + void operator=(const Logger &) = delete; + + Logger &operator=(Logger &&other) = delete; + + Logger(Logger && other) = delete; + + static void enableDebug(); + + static void debug(const string &text); + + static void info(const string &text); + + static void error(const string &text); + + static void critical(const string &text); +}; + +#endif \ No newline at end of file