From d4173f7a89f4524ecea581bf1caeac3dbe32261a Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Tue, 5 Aug 2025 14:47:46 +0800 Subject: [PATCH] init --- .github/ISSUE_TEMPLATE/bug-report.yml | 2 +- README.md | 43 +-- examples/AutoMode/AutoMode.ino | 59 ++++ examples/ContinuousMode/ContinuousMode.ino | 81 +++++ .../MotionSensingMode/MotionSensingMode.ino | 65 ++++ examples/PulseMode/PulseMode.ino | 75 +++++ examples/UsbMode/UsbMode.ino | 113 +++++++ library.json | 19 ++ library.properties | 11 + src/M5ModuleQRCode.cpp | 135 ++++++++ src/M5ModuleQRCode.h | 114 +++++++ src/debug.h | 29 ++ src/qrcode_m14.cpp | 305 ++++++++++++++++++ src/qrcode_m14.h | 260 +++++++++++++++ 14 files changed, 1272 insertions(+), 39 deletions(-) create mode 100644 examples/AutoMode/AutoMode.ino create mode 100644 examples/ContinuousMode/ContinuousMode.ino create mode 100644 examples/MotionSensingMode/MotionSensingMode.ino create mode 100644 examples/PulseMode/PulseMode.ino create mode 100644 examples/UsbMode/UsbMode.ino create mode 100644 library.json create mode 100644 library.properties create mode 100644 src/M5ModuleQRCode.cpp create mode 100644 src/M5ModuleQRCode.h create mode 100644 src/debug.h create mode 100644 src/qrcode_m14.cpp create mode 100644 src/qrcode_m14.h diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 594a2c5..02d32db 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -76,7 +76,7 @@ body: label: Issue checklist description: Please double-check that you have done each of the following things before submitting the issue. options: - - label: I searched for previous reports in [the issue tracker](https://github.com/m5stack/M5Stack/issues?q=) + - label: I searched for previous reports in [the issue tracker](https://github.com/m5stack/M5Module-QRCode/issues?q=) required: true - label: My report contains all necessary details required: true diff --git a/README.md b/README.md index 2be4654..ab3999e 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,15 @@ -# Product Name +# Module-QRCode ## Overview -### SKU:xxx +### SKU:M145 -Description of the product +**Module13.2 QRCode** is a 1D / 2D barcode scanning module in the M5Stack stackable module series. It integrates a 640 x 480 resolution CMOS barcode capture engine and supports three mainstream 2D barcode types and eight 1D barcode types on the market, enabling fast and accurate recognition of barcode information on media such as printouts and labels. By toggling the switch, you can switch between USB and UART communication interfaces for the QR module, allowing external devices to communicate with it. The USB interface can be configured as a USB virtual serial port, USB HID keyboard, or USB HID-POS. The module features a built-in buzzer for audio prompts, illumination and aiming LEDs, and provides rich secondary-development interfaces. It supports multiple scan trigger modes (such as auto mode, continuous mode, motion-sensing mode, pulse mode, etc.) for functional customization and integrated development. The module includes a DC power input port that supports DC 9 ~ 24V input. An internal DC-DC buck circuit supplies power to the entire device, making it suitable for logistics warehousing, retail POS, ticketing systems, production traceability, medical information collection, self-service terminals, and other scenarios. ## Related Link -- [Document & Datasheet](https://docs.m5stack.com/en/unit/product_Link) - -## Required Libraries: - -- [Adafruit_BMP280_Library](https://github.com/adafruit/Required_Libraries_Link) +- [Document & Datasheet](https://docs.m5stack.com/en/products/sku/M145) ## License -- [Product Name- MIT](LICENSE) - -## Remaining steps(Editorial Staff Look,After following the steps, remember to delete all the content below) - -1. Change [clang format check path](./.github/workflows/clang-format-check.yml#L42-L47). -2. Add License content to [LICENSE](/LICENSE). -3. Change link on line 78 of [bug-report.yml](./.github/ISSUE_TEMPLATE/bug-report.yml#L79). - -```cpp -Example -# M5Unit-ENV - -## Overview - -### SKU:U001 & U001-B & U001-C - -Contains M5Stack-**UNIT ENV** series related case programs.ENV is an environmental sensor with integrated SHT30 and QMP6988 internally to detect temperature, humidity, and atmospheric pressure data. - -## Related Link - -- [Document & Datasheet](https://docs.m5stack.com/en/unit/envIII) - -## Required Libraries: - -- [Adafruit_BMP280_Library](https://github.com/adafruit/Adafruit_BMP280_Library) - -## License - -- [M5Unit-ENV - MIT](LICENSE) -``` \ No newline at end of file +- [Module-QRCode- MIT](LICENSE) diff --git a/examples/AutoMode/AutoMode.ino b/examples/AutoMode/AutoMode.ino new file mode 100644 index 0000000..249eb2b --- /dev/null +++ b/examples/AutoMode/AutoMode.ino @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +M5ModuleQRCode module_qrcode; + +void setup() +{ + M5.begin(); + M5.Display.setFont(&fonts::efontCN_16); + M5.Display.setTextScroll(true); + + /* Set module config */ + auto cfg = module_qrcode.getConfig(); + cfg.pin_tx = 17; + cfg.pin_rx = 16; + cfg.baudrate = 115200; + cfg.serial = &Serial1; + module_qrcode.setConfig(cfg); + + /* Init module */ + while (!module_qrcode.begin()) { + M5.Display.setTextColor(TFT_RED); + M5.Display.println(">> Init module failed, retry..."); + delay(1000); + } + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Init module success"); + + /* Set trigger mode */ + M5.Display.println(">> Set trigger mode to auto"); + module_qrcode.setTriggerMode(QRCodeM14::TRIGGER_MODE_AUTO); + + M5.Display.println(">> You can scan QR code now"); +} + +void loop() +{ + M5.update(); + + /* Update module */ + module_qrcode.update(); + + /* If scan result available */ + if (module_qrcode.available()) { + auto result = module_qrcode.getScanResult(); + + /* Display result */ + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Get code:"); + M5.Display.setTextColor(TFT_YELLOW); + M5.Display.println(result.c_str()); + } +} \ No newline at end of file diff --git a/examples/ContinuousMode/ContinuousMode.ino b/examples/ContinuousMode/ContinuousMode.ino new file mode 100644 index 0000000..02392d6 --- /dev/null +++ b/examples/ContinuousMode/ContinuousMode.ino @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +M5ModuleQRCode module_qrcode; + +void setup() +{ + M5.begin(); + M5.Display.setFont(&fonts::efontCN_16); + M5.Display.setTextScroll(true); + + /* Set module config */ + auto cfg = module_qrcode.getConfig(); + cfg.pin_tx = 17; + cfg.pin_rx = 16; + cfg.baudrate = 115200; + cfg.serial = &Serial1; + module_qrcode.setConfig(cfg); + + /* Init module */ + while (!module_qrcode.begin()) { + M5.Display.setTextColor(TFT_RED); + M5.Display.println(">> Init module failed, retry..."); + delay(1000); + } + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Init module success"); + + /* Set light mode */ + M5.Display.println(">> Set lights mode"); + module_qrcode.setFillLightMode(QRCodeM14::FILL_LIGHT_ON_DECODE); + module_qrcode.setPosLightMode(QRCodeM14::POS_LIGHT_ON_DECODE); + + /* Set trigger mode */ + M5.Display.println(">> Set trigger mode to continuous"); + module_qrcode.setTriggerMode(QRCodeM14::TRIGGER_MODE_CONTINUOUS); + module_qrcode.stopDecode(); + + M5.Display.println(">> Click BtnA to toggle scanning"); +} + +void loop() +{ + M5.update(); + + static bool is_scanning = false; + + /* If BtnA was clicked */ + if (M5.BtnA.wasClicked()) { + is_scanning = !is_scanning; + + if (is_scanning) { + module_qrcode.startDecode(); + } else { + module_qrcode.stopDecode(); + } + + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(is_scanning ? ">> Start scanning..." : ">> Stop scanning"); + } + + /* Update module */ + module_qrcode.update(); + + /* If scan result available */ + if (module_qrcode.available()) { + auto result = module_qrcode.getScanResult(); + + /* Display result */ + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Get code:"); + M5.Display.setTextColor(TFT_YELLOW); + M5.Display.println(result.c_str()); + } +} \ No newline at end of file diff --git a/examples/MotionSensingMode/MotionSensingMode.ino b/examples/MotionSensingMode/MotionSensingMode.ino new file mode 100644 index 0000000..96ff1a1 --- /dev/null +++ b/examples/MotionSensingMode/MotionSensingMode.ino @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +M5ModuleQRCode module_qrcode; + +void setup() +{ + M5.begin(); + M5.Display.setFont(&fonts::efontCN_16); + M5.Display.setTextScroll(true); + + /* Set module config */ + auto cfg = module_qrcode.getConfig(); + cfg.pin_tx = 17; + cfg.pin_rx = 16; + cfg.baudrate = 115200; + cfg.serial = &Serial1; + module_qrcode.setConfig(cfg); + + /* Init module */ + while (!module_qrcode.begin()) { + M5.Display.setTextColor(TFT_RED); + M5.Display.println(">> Init module failed, retry..."); + delay(1000); + } + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Init module success"); + + /* Set trigger mode */ + M5.Display.println(">> Set trigger mode to motion sensing"); + module_qrcode.setTriggerMode(QRCodeM14::TRIGGER_MODE_MOTION_SENSING); + module_qrcode.setMotionSensitivity(1); + + /* Set light mode */ + M5.Display.println(">> Set lights mode"); + module_qrcode.setFillLightMode(QRCodeM14::FILL_LIGHT_ON_DECODE); + module_qrcode.setPosLightMode(QRCodeM14::POS_LIGHT_FLASH_ON_DECODE); + + M5.Display.println(">> You can scan QR code now"); +} + +void loop() +{ + M5.update(); + + /* Update module */ + module_qrcode.update(); + + /* If scan result available */ + if (module_qrcode.available()) { + auto result = module_qrcode.getScanResult(); + + /* Display result */ + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Get code:"); + M5.Display.setTextColor(TFT_YELLOW); + M5.Display.println(result.c_str()); + } +} \ No newline at end of file diff --git a/examples/PulseMode/PulseMode.ino b/examples/PulseMode/PulseMode.ino new file mode 100644 index 0000000..1a7facb --- /dev/null +++ b/examples/PulseMode/PulseMode.ino @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +M5ModuleQRCode module_qrcode; + +void setup() +{ + M5.begin(); + M5.Display.setFont(&fonts::efontCN_16); + M5.Display.setTextScroll(true); + + /* Set module config */ + auto cfg = module_qrcode.getConfig(); + cfg.pin_tx = 17; + cfg.pin_rx = 16; + cfg.baudrate = 115200; + cfg.serial = &Serial1; + module_qrcode.setConfig(cfg); + + /* Init module */ + while (!module_qrcode.begin()) { + M5.Display.setTextColor(TFT_RED); + M5.Display.println(">> Init module failed, retry..."); + delay(1000); + } + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Init module success"); + + /* Set light mode */ + M5.Display.println(">> Set lights mode"); + module_qrcode.setFillLightMode(QRCodeM14::FILL_LIGHT_ON_DECODE); + module_qrcode.setPosLightMode(QRCodeM14::POS_LIGHT_ON_DECODE); + + /* Set trigger mode */ + M5.Display.println(">> Set trigger mode to pulse"); + module_qrcode.setTriggerMode(QRCodeM14::TRIGGER_MODE_PULSE); + + M5.Display.println(">> Click BtnA to start a scan"); +} + +void loop() +{ + M5.update(); + + /* If BtnA was clicked */ + if (M5.BtnA.wasClicked()) { + /* Trigger a scan */ + module_qrcode.setTriggerLevel(false); + delay(20); + module_qrcode.setTriggerLevel(true); + + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Start scanning..."); + } + + /* Update module */ + module_qrcode.update(); + + /* If scan result available */ + if (module_qrcode.available()) { + auto result = module_qrcode.getScanResult(); + + /* Display result */ + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Get code:"); + M5.Display.setTextColor(TFT_YELLOW); + M5.Display.println(result.c_str()); + } +} diff --git a/examples/UsbMode/UsbMode.ino b/examples/UsbMode/UsbMode.ino new file mode 100644 index 0000000..355542d --- /dev/null +++ b/examples/UsbMode/UsbMode.ino @@ -0,0 +1,113 @@ +#include +#include +#include + +M5ModuleQRCode module_qrcode; +static bool is_usb_mode = false; + +void setup() +{ + M5.begin(); + M5.Display.setFont(&fonts::efontCN_16); + M5.Display.setTextScroll(true); + + /* Set module config */ + auto cfg = module_qrcode.getConfig(); + cfg.pin_tx = 17; + cfg.pin_rx = 16; + cfg.baudrate = 115200; + cfg.serial = &Serial1; + module_qrcode.setConfig(cfg); + + /* Init module */ + while (!module_qrcode.begin()) { + M5.Display.setTextColor(TFT_RED); + M5.Display.println(">> Init module failed, retrying..."); + delay(1000); + } + + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Port Initial Setting\n(5s later, it will skip)"); + M5.Display.setTextColor(TFT_YELLOW); + M5.Display.println( + "If you are unsure which port was configured on the device before the last power-off, please scan the serial " + "communication configuration code when this message appears; otherwise, the QRCode module may not start " + "correctly."); + + module_qrcode.setTriggerLevel(false); + delay(30); + module_qrcode.setTriggerLevel(true); + delay(5000); + M5.Display.clear(); + M5.Display.setCursor(0, 0); + + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Init module success"); + is_usb_mode = false; + + /* Set light mode */ + M5.Display.println(">> Set lights mode"); + module_qrcode.setFillLightMode(QRCodeM14::FILL_LIGHT_ON_DECODE); + module_qrcode.setPosLightMode(QRCodeM14::POS_LIGHT_ON_DECODE); + + /* Set trigger mode */ + M5.Display.println(">> Set trigger mode to pulse"); + module_qrcode.setTriggerMode(QRCodeM14::TRIGGER_MODE_PULSE); + + M5.Display.println(">> Click BtnA to toggle scanning"); +} + +void loop() +{ + M5.update(); + + if (!is_usb_mode && M5.BtnA.wasClicked()) { + /* Trigger a scan */ + module_qrcode.setTriggerLevel(false); + delay(20); + module_qrcode.setTriggerLevel(true); + + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Start scanning..."); + } + + if (M5.BtnB.wasClicked()) { + module_qrcode.startDecode(); + + // If you want to use the USB virtual serial, please enable the following code. + module_qrcode.setModeUsbSerial(); + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Change to USB Serial"); + + // If you want to use the USB HID keyboard, please enable the following code. + // module_qrcode.setModeUsbKeyboard(); + // M5.Display.setTextColor(TFT_WHITE); + // M5.Display.println(">> Change to USB KeyBoard"); + + // If you want to use the USB HID-POS, please enable the following code. + // module_qrcode.setModeUsbPos(); + // M5.Display.setTextColor(TFT_WHITE); + // M5.Display.println(">> Change to USB POS"); + + M5.Display.setTextColor(TFT_RED); + M5.Display.println( + "If you want to switch back to the serial port, please scan the Serial Communication configuration code in " + "the user guide.\n"); + is_usb_mode = true; + } + + /* Update module */ + module_qrcode.update(); + + /* If scan result available */ + if (module_qrcode.available()) { + is_usb_mode = false; + auto result = module_qrcode.getScanResult(); + + /* Display result */ + M5.Display.setTextColor(TFT_WHITE); + M5.Display.println(">> Get code:"); + M5.Display.setTextColor(TFT_YELLOW); + M5.Display.println(result.c_str()); + } +} diff --git a/library.json b/library.json new file mode 100644 index 0000000..06a38f9 --- /dev/null +++ b/library.json @@ -0,0 +1,19 @@ +{ + "name": "M5Module-QRCode", + "description": "Library for M5Module-QRCode", + "keywords": "M5Stack,M5Module-QRCode", + "authors": { + "name": "M5Stack" + }, + "repository": { + "type": "git", + "url": "https://github.com/M5Stack/M5Module-QRCode.git" + }, + "dependencies": { + "M5Unified": "*", + "M5GFX": "*" + }, + "version": "1.0.0", + "frameworks": "arduino", + "platforms": "espressif32" +} \ No newline at end of file diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..731e7dc --- /dev/null +++ b/library.properties @@ -0,0 +1,11 @@ +name=M5Module-QRCode +version=1.0.0 +author=M5Stack +maintainer=M5Stack +sentence=M5Module-QRCode is a library for M5Module-QRCode +paragraph= +category=Device Control +url=https://github.com/m5stack/M5Module-QRCode.git +architectures=esp32 +includes=M5ModuleQRCode.h +depends=M5Unified \ No newline at end of file diff --git a/src/M5ModuleQRCode.cpp b/src/M5ModuleQRCode.cpp new file mode 100644 index 0000000..e7859ba --- /dev/null +++ b/src/M5ModuleQRCode.cpp @@ -0,0 +1,135 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +#include "M5ModuleQRCode.h" +#include "debug.h" + +#define CHANNEL_QRCODE_POWER_EN 0 +#define CHANNEL_QRCODE_TRIG 4 + +M5ModuleQRCode::~M5ModuleQRCode() +{ + if (_pi4ioe5v6408 != nullptr) { + delete _pi4ioe5v6408; + _pi4ioe5v6408 = nullptr; + } +} + +bool M5ModuleQRCode::begin() +{ + if (!_init_pi4ioe5v6408()) { + return false; + } + + setEnable(true); + delay(300); + + if (!_init_qrcode()) { + return false; + } + + return true; +} + +bool M5ModuleQRCode::_init_pi4ioe5v6408() +{ + _LOG_DEBUG("init pi4ioe5v6408\n"); + + if (_config.i2c == nullptr) { + _config.i2c = &M5.In_I2C; + } + + // Probe device + bool is_found = false; + for (int i = 0; i < 3; i++) { + bool result[128]; + _config.i2c->scanID(result); + if (result[_config.pi4ioe5v6408_addr]) { + is_found = true; + _LOG_DEBUG("pi4ioe5v6408 found at 0x%02x\n", _config.pi4ioe5v6408_addr); + break; + } + _LOG_ERROR("pi4ioe5v6408 not found at 0x%02x, retry %d\n", _config.pi4ioe5v6408_addr, i); + delay(500); + } + if (!is_found) { + _LOG_ERROR("pi4ioe5v6408 not found\n"); + return false; + } + + if (_pi4ioe5v6408 != nullptr) { + delete _pi4ioe5v6408; + _pi4ioe5v6408 = nullptr; + } + + _pi4ioe5v6408 = new m5::PI4IOE5V6408_Class(_config.pi4ioe5v6408_addr, 100000, _config.i2c); + if (!_pi4ioe5v6408->begin()) { + _LOG_ERROR("init pi4ioe5v6408 failed\n"); + return false; + } + + _pi4ioe5v6408->setDirection(CHANNEL_QRCODE_POWER_EN, true); + _pi4ioe5v6408->setPullMode(CHANNEL_QRCODE_POWER_EN, true); + _pi4ioe5v6408->enablePull(CHANNEL_QRCODE_POWER_EN, true); + _pi4ioe5v6408->setHighImpedance(CHANNEL_QRCODE_POWER_EN, false); + + _pi4ioe5v6408->setDirection(CHANNEL_QRCODE_TRIG, true); + _pi4ioe5v6408->setPullMode(CHANNEL_QRCODE_TRIG, true); + _pi4ioe5v6408->enablePull(CHANNEL_QRCODE_TRIG, true); + _pi4ioe5v6408->setHighImpedance(CHANNEL_QRCODE_TRIG, false); + + return true; +} + +bool M5ModuleQRCode::_init_qrcode() +{ + _LOG_DEBUG("init qrcode\n"); + + _LOG_DEBUG("init qrcode serial tx: %d, rx: %d\n", _config.pin_tx, _config.pin_rx); + _config.serial->begin(115200, SERIAL_8N1, _config.pin_rx, _config.pin_tx); + + _setup(_config.serial); + + return true; +} + +bool M5ModuleQRCode::checkConnection() +{ + auto version = getFirmwareVersion(); + return !version.empty(); +} + +void M5ModuleQRCode::setEnable(bool enable) +{ + if (enable) { + _pi4ioe5v6408->digitalWrite(CHANNEL_QRCODE_POWER_EN, true); + setTriggerLevel(true); + } else { + _pi4ioe5v6408->digitalWrite(CHANNEL_QRCODE_POWER_EN, false); + setTriggerLevel(false); + } +} + +void M5ModuleQRCode::setTriggerLevel(bool level) +{ + if (level) { + _pi4ioe5v6408->digitalWrite(CHANNEL_QRCODE_TRIG, true); + } else { + _pi4ioe5v6408->digitalWrite(CHANNEL_QRCODE_TRIG, false); + } +} + +void M5ModuleQRCode::update() +{ + _scan_result.clear(); + + if (QRCodeM14::available()) { + waitScanResult(_scan_result, 0); + + if (_on_scan_result && available()) { + _on_scan_result(_scan_result); + } + } +} diff --git a/src/M5ModuleQRCode.h b/src/M5ModuleQRCode.h new file mode 100644 index 0000000..c649f72 --- /dev/null +++ b/src/M5ModuleQRCode.h @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +#pragma once +#include "qrcode_m14.h" +#include +#include +#include +#include +#include +#include + +class M5ModuleQRCode : public QRCodeM14 { +public: + /** + * @brief Module configuration. + */ + struct Config_t { + int pin_tx = 1; + int pin_rx = 3; + HardwareSerial* serial = &Serial1; + unsigned long baudrate = 115200; + m5::I2C_Class* i2c = &M5.In_I2C; + uint8_t pi4ioe5v6408_addr = 0x43; + }; + + ~M5ModuleQRCode(); + + Config_t getConfig() const + { + return _config; + } + void setConfig(const Config_t& config) + { + _config = config; + } + + /** + * @brief Initialize module. + * + * @return true + * @return false + */ + bool begin(); + + /** + * @brief Check if qr module is connected. + * + * @return true + * @return false + */ + bool checkConnection(); + + /** + * @brief Set qrcode module power enable. + * + * @param enable + */ + void setEnable(bool enable); + + /** + * @brief Set qrcode module trigger level. + * + * @param level + */ + void setTriggerLevel(bool level); + + /** + * @brief Update scan result. + */ + void update(); + + /** + * @brief Check if scan result is available. + * + * @return true + * @return false + */ + inline bool available() const + { + return !_scan_result.empty(); + } + + /** + * @brief Get scan result. + * + * @return std::string + */ + inline std::string getScanResult() const + { + return _scan_result; + } + + /** + * @brief Set on scan result callback. + * + * @param callback + */ + inline void onScanResult(std::function callback) + { + _on_scan_result = callback; + } + +private: + Config_t _config; + m5::PI4IOE5V6408_Class* _pi4ioe5v6408 = nullptr; + std::string _scan_result; + std::function _on_scan_result; + + bool _init_pi4ioe5v6408(); + bool _init_qrcode(); +}; diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..299bcbd --- /dev/null +++ b/src/debug.h @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +#pragma once +#include + +#define MODULE_QRCODE_DEBUG 0 + +#define _LOG_DEBUG(x, ...) \ + if (MODULE_QRCODE_DEBUG) { \ + printf("[debug] "); \ + printf(x, ##__VA_ARGS__); \ + } + +#define _LOG_ERROR(x, ...) \ + printf("[error] "); \ + printf(x, ##__VA_ARGS__); + +inline void debug_print_buffer(const uint8_t* buffer, size_t len) +{ +#if MODULE_QRCODE_DEBUG + for (size_t i = 0; i < len; i++) { + printf("%02X ", buffer[i]); + } + printf("\n"); +#endif +} diff --git a/src/qrcode_m14.cpp b/src/qrcode_m14.cpp new file mode 100644 index 0000000..d8af0fb --- /dev/null +++ b/src/qrcode_m14.cpp @@ -0,0 +1,305 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +#include "qrcode_m14.h" +#include "debug.h" + +/* -------------------------------------------------------------------------- */ +/* Communication */ +/* -------------------------------------------------------------------------- */ +QRCodeM14::CmdResult_t QRCodeM14::sendCmd(const uint8_t* cmd, size_t cmd_len, const uint8_t* cmd_ack, size_t ack_len, + uint32_t timeout_ms) +{ + if (!_qrcode_serial || !cmd || cmd_len == 0) { + return CmdResult_t::INVALID_PARAM; + } + + // Clear rx buffer + while (_qrcode_serial->available()) { + _qrcode_serial->read(); + } + + // Send command + _LOG_DEBUG("tx: "); + debug_print_buffer(cmd, cmd_len); + + _qrcode_serial->write(cmd, cmd_len); + + // If no response is expected + if (!cmd_ack || ack_len == 0) { + return CmdResult_t::SUCCESS; + } + + delay(100); + + // Wait for response + uint32_t start_time = millis(); + while (millis() - start_time < timeout_ms) { + if (_qrcode_serial->available() >= ack_len) { + uint8_t rx[ack_len]; + _qrcode_serial->readBytes(rx, ack_len); + + _LOG_DEBUG("rx: "); + debug_print_buffer(rx, ack_len); + + if (memcmp(rx, cmd_ack, ack_len) == 0) { + return CmdResult_t::SUCCESS; + } else { + return CmdResult_t::ACK_MISMATCH; + } + } + delay(5); + } + + return CmdResult_t::TIMEOUT; +} + +void QRCodeM14::waitResponse(std::vector& response, uint32_t timeout_ms) +{ + response.clear(); + uint32_t start_time = millis(); + do { + int bytes_num = _qrcode_serial->available(); + if (bytes_num > 0) { + response.resize(bytes_num); + _qrcode_serial->readBytes(response.data(), bytes_num); + break; + } + delay(20); + } while (millis() - start_time < timeout_ms); +} + +uint16_t QRCodeM14::getResponseDataSize(std::vector& response) +{ + if (response.size() < 5) { + _LOG_ERROR("invaild response size: %d\n", response.size()); + return 0; + } + + uint16_t size = (static_cast(response[3]) << 8) | response[4]; + return size; +} + +uint16_t QRCodeM14::checkResponseDataSize(std::vector& response) +{ + uint16_t data_size = getResponseDataSize(response); + if (data_size == 0) { + return 0; + } + + size_t data_start_index = 5; + + if (response.size() < data_start_index + data_size) { + _LOG_ERROR("invaild data size: %d < %d\n", response.size(), data_start_index + data_size); + return 0; + } + + return data_size; +} + +void QRCodeM14::waitScanResult(std::string& result, uint32_t timeout_ms) +{ + result.clear(); + std::vector response; + waitResponse(response, timeout_ms); + result = std::string(response.begin(), response.end()); +} + +std::string QRCodeM14::cmdResultToString(QRCodeM14::CmdResult_t result) +{ + switch (result) { + case QRCodeM14::CmdResult_t::SUCCESS: + return "success"; + case QRCodeM14::CmdResult_t::INVALID_PARAM: + return "invalid param"; + break; + case QRCodeM14::CmdResult_t::TIMEOUT: + return "timeout"; + case QRCodeM14::CmdResult_t::ACK_MISMATCH: + return "ack mismatch"; + break; + default: + return "unknown result"; + } +} + +/* -------------------------------------------------------------------------- */ +/* API */ +/* -------------------------------------------------------------------------- */ +void QRCodeM14::startDecode() +{ + const uint8_t cmd[] = {0x32, 0x75, 0x01}; + sendCmd(cmd, sizeof(cmd)); +} + +void QRCodeM14::stopDecode() +{ + const uint8_t cmd[] = {0x32, 0x75, 0x02}; + const uint8_t cmd_ack[] = {0x33, 0x75, 0x02, 0x00, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 150); +} + +void QRCodeM14::setTriggerMode(TriggerMode_t mode) +{ + const uint8_t cmd[] = {0x21, 0x61, 0x41, (uint8_t)mode}; + const uint8_t cmd_ack[] = {0x22, 0x61, 0x41, (uint8_t)mode, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setDecodeDelay(int delay_ms) +{ + uint8_t high_byte = (delay_ms >> 8) & 0xFF; + uint8_t low_byte = delay_ms & 0xFF; + const uint8_t cmd[] = {0x21, 0x61, 0x8A, high_byte, low_byte}; + const uint8_t cmd_ack[] = {0x22, 0x61, 0x8A, 0x00, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setTriggerTimeout(int timeout_ms) +{ + uint8_t high_byte = (timeout_ms >> 8) & 0xFF; + uint8_t low_byte = timeout_ms & 0xFF; + const uint8_t cmd[] = {0x21, 0x61, 0x82, high_byte, low_byte}; + const uint8_t cmd_ack[] = {0x22, 0x61, 0x82, 0x00, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setMotionSensitivity(int level) +{ + const uint8_t cmd[] = {0x21, 0x61, 0x44, (uint8_t)level}; + const uint8_t cmd_ack[] = {0x22, 0x61, 0x44, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setContinuousDecodeDelay(int delay_ms) +{ + uint8_t high_byte = (delay_ms >> 8) & 0xFF; + uint8_t low_byte = delay_ms & 0xFF; + const uint8_t cmd[] = {0x21, 0x61, 0x8C, high_byte, low_byte}; + const uint8_t cmd_ack[] = {0x22, 0x61, 0x8C, 0x00, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setTriggerDecodeDelay(int delay_ms) +{ + uint8_t high_byte = (delay_ms >> 8) & 0xFF; + uint8_t low_byte = delay_ms & 0xFF; + const uint8_t cmd[] = {0x21, 0x61, 0x85, high_byte, low_byte}; + const uint8_t cmd_ack[] = {0x22, 0x61, 0x85, 0x00, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setSameCodeInterval(int interval_ms) +{ + uint8_t high_byte = (interval_ms >> 8) & 0xFF; + uint8_t low_byte = interval_ms & 0xFF; + const uint8_t cmd[] = {0x21, 0x64, 0x82, high_byte, low_byte}; + const uint8_t cmd_ack[] = {0x22, 0x64, 0x82, 0x00, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setDiffCodeInterval(int interval_ms) +{ + uint8_t high_byte = (interval_ms >> 8) & 0xFF; + uint8_t low_byte = interval_ms & 0xFF; + const uint8_t cmd[] = {0x21, 0x64, 0x81, high_byte, low_byte}; + const uint8_t cmd_ack[] = {0x22, 0x64, 0x81, 0x00, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setSameCodeNoDelay(bool enable) +{ + uint8_t mode = enable ? 0x01 : 0x00; + const uint8_t cmd[] = {0x21, 0x64, 0x43, mode}; + const uint8_t cmd_ack[] = {0x22, 0x64, 0x43, mode, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setFillLightMode(FillLightMode_t mode) +{ + const uint8_t cmd[] = {0x21, 0x62, 0x41, (uint8_t)mode}; + const uint8_t cmd_ack[] = {0x22, 0x62, 0x41, (uint8_t)mode, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setFillLightBrightness(int brightness) +{ + brightness = max(0, min(100, brightness)); + const uint8_t cmd[] = {0x21, 0x62, 0x48, (uint8_t)brightness}; + const uint8_t cmd_ack[] = {0x22, 0x62, 0x48, (uint8_t)brightness, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 100); +} + +void QRCodeM14::setPosLightMode(PosLightMode_t mode) +{ + const uint8_t cmd[] = {0x21, 0x62, 0x42, (uint8_t)mode}; + const uint8_t cmd_ack[] = {0x22, 0x62, 0x42, (uint8_t)mode, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 100); +} + +void QRCodeM14::setStartupTone(int mode) +{ + const uint8_t cmd[] = {0x21, 0x63, 0x45, (uint8_t)mode}; + const uint8_t cmd_ack[] = {0x22, 0x63, 0x45, (uint8_t)mode}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 150); +} + +void QRCodeM14::setDecodeSuccessBeep(int count) +{ + const uint8_t cmd[] = {0x21, 0x63, 0x42, (uint8_t)count}; + const uint8_t cmd_ack[] = {0x22, 0x63, 0x42, (uint8_t)count}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 150); +} + +void QRCodeM14::setCaseConversion(int mode) +{ + const uint8_t cmd[] = {0x21, 0x51, 0x48, (uint8_t)mode}; + const uint8_t cmd_ack[] = {0x22, 0x51, 0x48, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setProtocolFormat(int mode) +{ + const uint8_t cmd[] = {0x21, 0x51, 0x43, (uint8_t)mode}; + const uint8_t cmd_ack[] = {0x22, 0x51, 0x43, 0x00}; + sendCmd(cmd, sizeof(cmd), cmd_ack, sizeof(cmd_ack), 200); +} + +void QRCodeM14::setModeUsbSerial() +{ + const uint8_t cmd[] = {0x21, 0x42, 0x40, 0x02}; + sendCmd(cmd, sizeof(cmd)); +} + +void QRCodeM14::setModeUsbKeyboard() +{ + const uint8_t cmd[] = {0x21, 0x42, 0x40, 0x01}; + sendCmd(cmd, sizeof(cmd)); +} + +void QRCodeM14::setModeUsbPos() +{ + const uint8_t cmd[] = {0x21, 0x42, 0x40, 0x03}; + sendCmd(cmd, sizeof(cmd)); +} + +std::string QRCodeM14::getInfos(uint8_t id) +{ + const uint8_t cmd[] = {0x43, 0x02, id}; + std::vector response; + + sendCmd(cmd, sizeof(cmd)); + delay(10); + waitResponse(response); + + uint16_t data_size = checkResponseDataSize(response); + if (data_size == 0) { + return ""; + } + + std::string data(response.begin() + 5, response.begin() + 5 + data_size); + + return data; +} diff --git a/src/qrcode_m14.h b/src/qrcode_m14.h new file mode 100644 index 0000000..82120a4 --- /dev/null +++ b/src/qrcode_m14.h @@ -0,0 +1,260 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +#pragma once +#include +#include +#include + +class QRCodeM14 { +public: + enum CmdResult_t { SUCCESS = 0, INVALID_PARAM = 1, TIMEOUT = 2, ACK_MISMATCH = 3 }; + + enum TriggerMode_t { + TRIGGER_MODE_KEY = 0, // In Key Mode, Triggers a single decode; decoding stops after a successful read. + TRIGGER_MODE_CONTINUOUS = + 1, // In Continuous Mode, pressing the button once starts decoding, and pressing the button again stops + TRIGGER_MODE_AUTO = 2, // In Auto Mode, the module starts decoding when powered on and cannot be stopped. + TRIGGER_MODE_PULSE = + 4, // In Pulse Mode, set the TRIG pin to hold a low level for more than 20ms to trigger decoding once. + TRIGGER_MODE_MOTION_SENSING = 5 // In Motion Sensing Mode, the module automatically triggers decoding when it + // detects a change in the scene based on visual recognition information. + }; + + enum FillLightMode_t { + FILL_LIGHT_OFF = 0, // Light off + FILL_LIGHT_ON_DECODE = 2, // Light on during decoding + FILL_LIGHT_ON = 3 // Light on + }; + + enum PosLightMode_t { + POS_LIGHT_OFF = 0, // Light off + POS_LIGHT_FLASH_ON_DECODE = 1, // Light flashing during decoding + POS_LIGHT_ON_DECODE = 2 // Light on during decoding + }; + + /** + * @brief Start decoding. + */ + void startDecode(); + + /** + * @brief Stop decoding. + */ + void stopDecode(); + + /** + * @brief Set trigger mode for QR code decoding. + * @param mode Trigger mode from TriggerMode_t enum + */ + void setTriggerMode(TriggerMode_t mode); + + /** + * @brief Set decode delay time. + * @param delay_ms Delay time in milliseconds + */ + void setDecodeDelay(int delay_ms); + + /** + * @brief Set trigger timeout duration. + * @param timeout_ms Timeout duration in milliseconds + */ + void setTriggerTimeout(int timeout_ms); + + /** + * @brief Set motion sensing sensitivity level (1~5). + * @param level Sensitivity level (default: 3) + */ + void setMotionSensitivity(int level = 3); + + /** + * @brief Set delay for continuous decode mode. + * @param delay_ms Delay time in milliseconds + */ + void setContinuousDecodeDelay(int delay_ms); + + /** + * @brief Set delay for trigger decode mode. + * @param delay_ms Delay time in milliseconds + */ + void setTriggerDecodeDelay(int delay_ms); + + /** + * @brief Set interval between same QR codes. + * @param interval_ms Interval time in milliseconds + */ + void setSameCodeInterval(int interval_ms); + + /** + * @brief Set interval between different QR codes. + * @param interval_ms Interval time in milliseconds + */ + void setDiffCodeInterval(int interval_ms); + + /** + * @brief Enable or disable no delay for same QR codes. + * @param enable True to enable, false to disable + */ + void setSameCodeNoDelay(bool enable); + + /** + * @brief Set fill light mode. + * @param mode Fill light mode from FillLightMode_t enum (default: FILL_LIGHT_ON_DECODE) + */ + void setFillLightMode(FillLightMode_t mode = FILL_LIGHT_ON_DECODE); + + /** + * @brief Set fill light brightness. + * @param brightness Brightness level (default: 60) + */ + void setFillLightBrightness(int brightness = 60); + + /** + * @brief Set position light mode. + * @param mode Position light mode from PosLightMode_t enum (default: POS_LIGHT_ON_DECODE) + */ + void setPosLightMode(PosLightMode_t mode = POS_LIGHT_ON_DECODE); + + /** + * @brief Set startup tone mode. + * @param mode Tone mode + */ + void setStartupTone(int mode); + + /** + * @brief Set decode success beep count. + * @param count Number of beeps + */ + void setDecodeSuccessBeep(int count); + + /** + * @brief Set case conversion mode for decoded text. + * @param mode Conversion mode (default: 0) + */ + void setCaseConversion(int mode = 0); + + /** + * @brief Set protocol format mode. + * @param mode Protocol format mode + */ + void setProtocolFormat(int mode); + + /** + * @brief Set communication mode to USB Serial. + * + */ + void setModeUsbSerial(); + + /** + * @brief Set communication mode to USB Keyboard. + * + */ + void setModeUsbKeyboard(); + + /** + * @brief Set communication mode to USB POS. + * + */ + void setModeUsbPos(); + + /** + * @brief Get device information by ID. + * @param id Information ID + * @return Information string + */ + std::string getInfos(uint8_t id); + + /** + * @brief Get software version. + * @return Software version string + */ + inline std::string getSoftwareVersion() + { + return getInfos(0xC2); + } + + /** + * @brief Get firmware version. + * @return Firmware version string + */ + inline std::string getFirmwareVersion() + { + return getInfos(0xC1); + } + + /** + * @brief Check if serial data is available. + * @return Number of bytes available + */ + inline int available() const + { + return _qrcode_serial->available(); + } + + /** + * @brief Send command to QR code module. + * @param cmd Command data pointer + * @param cmd_len Command data length + * @param cmd_ack Expected acknowledgment data (optional) + * @param ack_len Acknowledgment data length (optional) + * @param timeout_ms Timeout in milliseconds (default: 1000) + * @return Command execution result + */ + CmdResult_t sendCmd(const uint8_t* cmd, size_t cmd_len, const uint8_t* cmd_ack = nullptr, size_t ack_len = 0, + uint32_t timeout_ms = 1000); + + /** + * @brief Send command to QR code module (vector version). + * @param cmd Command data vector + * @return Command execution result + */ + inline CmdResult_t sendCmd(std::vector cmd) + { + return sendCmd(cmd.data(), cmd.size()); + } + + /** + * @brief Wait for response from QR code module. + * @param response Response data buffer + * @param timeout_ms Timeout in milliseconds (default: 1000) + */ + void waitResponse(std::vector& response, uint32_t timeout_ms = 1000); + + /** + * @brief Get response data size. + * @param response Response data buffer + * @return Data size + */ + uint16_t getResponseDataSize(std::vector& response); + + /** + * @brief Check response data size. + * @param response Response data buffer + * @return Data size + */ + uint16_t checkResponseDataSize(std::vector& response); + + /** + * @brief Wait for QR code scan result. + * @param result Scan result string + * @param timeout_ms Timeout in milliseconds (default: infinite) + */ + void waitScanResult(std::string& result, uint32_t timeout_ms = 0xFFFFFFFF); + + /** + * @brief Convert command result to string. + * @param result Command result enum + * @return Result description string + */ + std::string cmdResultToString(CmdResult_t result); + +protected: + HardwareSerial* _qrcode_serial = nullptr; + + void _setup(HardwareSerial* serial) + { + _qrcode_serial = serial; + } +};